Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Getting mp3 artwork crashes on iOS 8 but works on iOS 7

EDIT: The culprit was iOS 8, not the simulator (which I didn't realize was already running iOS 8) I've renamed the title to reflect this.

I was happily using the code from this SO question to load album artwork from mp3 files. This was on my iPhone 5 with iOS 7.1.

But then I traced crashing in the iOS simulator to this code. Further investigation revealed that this code also crashed on my iPad. It crashed on my iPad after upgrading it to iOS 8.

It appears the dictionary containing the image is corrupted.

I created a dummy iOS project that only loads album art and got the same result. Below is the code from that viewcontroller.

- (void) viewDidAppear:(BOOL)animated
{
    self.titleText = @"Overkill"; // Set song filename here
    NSString *filePath = [[NSBundle mainBundle] pathForResource:self.titleText ofType:@"mp3"];
    if (!filePath) {
        return;
    }
    NSURL *fileURL = [NSURL fileURLWithPath:filePath];

    NSLog(@"Getting song metadata for %@", self.titleText);
    AVAsset *asset = [AVURLAsset URLAssetWithURL:fileURL options:nil];
    if (asset != nil) {
        NSArray *keys = [NSArray arrayWithObjects:@"commonMetadata", nil];
        [asset loadValuesAsynchronouslyForKeys:keys completionHandler:^{
            NSArray *artworks = [AVMetadataItem metadataItemsFromArray:asset.commonMetadata
                                                               withKey:AVMetadataCommonKeyArtwork
                                                              keySpace:AVMetadataKeySpaceCommon];
            UIImage *albumArtWork;

            for (AVMetadataItem *item in artworks) {
                if ([item.keySpace isEqualToString:AVMetadataKeySpaceID3]) {
                    NSDictionary *dict = [item.value copyWithZone:nil];

                    // **********
                    // Crashes here with SIGABRT. dict is not a valid dictionary.
                    // **********

                    if ([dict objectForKey:@"data"]) { 
                        albumArtWork = [UIImage imageWithData:[dict objectForKey:@"data"]];
                    }
                }
                else if ([item.keySpace isEqualToString:AVMetadataKeySpaceiTunes]) {
                    // This doesn't appear to get called for images set (ironically) in iTunes
                    albumArtWork = [UIImage imageWithData:[item.value copyWithZone:nil]];
                }
            }

            if (albumArtWork != nil) {
                dispatch_sync(dispatch_get_main_queue(), ^{
                    [self.albumArtImageView setImage:albumArtWork];
                });
            }

        }];
    }

}

I've marked the line with the crash. It expects a file Overkill.mp3 to be in the bundle. I tested with multiple mp3's and m4a's exported from iTunes and Amazon, so I know the files themselves are correctly encoded.

Tested in Xcode 6.0 and 6.1.

Any ideas why it would work on iPhone but not the simulator or iPad?

EDIT / UPDATE:

Logging the item.value reveals differences.

On iPhone 5 (works):

(lldb) po item.value
{
    MIME = JPG;
    data = <ffd8ffe0 .... several Kb of data ..... 2a17ffd9>;
    identifier = "";
    picturetype = Other;
}

On Simulator (crashes)

(lldb) po item.value
<ffd8ffe0 .... several Kb of data ..... 2a17ffd9>

So it appears that on the simulator there is no dictionary, just the raw artwork.

Changing the code to not expect a dictionary, but take item.value as a UIImage works!

        for (AVMetadataItem *item in artworks) {
            if ([item.keySpace isEqualToString:AVMetadataKeySpaceID3]) {
                NSData *newImage = [item.value copyWithZone:nil];
                albumArtWork = [UIImage imageWithData:newImage];
            }
            ...
        }
like image 905
Stan James Avatar asked Sep 11 '14 03:09

Stan James


1 Answers

It seems the returned data structure has changed in iOS 8. The value of the AVMetadataItem object is no longer a dictionary, but the actual raw UIImage data.

Adding a test for the NSFoundationVersionNumber solves the problem. There is probably a cleaner solution.

- (void) viewDidAppear:(BOOL)animated
{
    self.titleText = @"Overkill";
    NSString *filePath = [[NSBundle mainBundle] pathForResource:self.titleText ofType:@"mp3"];
    if (!filePath) {
        return;
    }
    NSURL *fileURL = [NSURL fileURLWithPath:filePath];

    NSLog(@"Getting song metadata for %@", self.titleText);
    AVAsset *asset = [AVURLAsset URLAssetWithURL:fileURL options:nil];
    if (asset != nil) {
        NSArray *keys = [NSArray arrayWithObjects:@"commonMetadata", nil];
        [asset loadValuesAsynchronouslyForKeys:keys completionHandler:^{
            NSArray *artworks = [AVMetadataItem metadataItemsFromArray:asset.commonMetadata
                                                               withKey:AVMetadataCommonKeyArtwork
                                                              keySpace:AVMetadataKeySpaceCommon];
            UIImage *albumArtWork;

            for (AVMetadataItem *item in artworks) {
                if ([item.keySpace isEqualToString:AVMetadataKeySpaceID3]) {

                    // *** WE TEST THE IOS VERSION HERE ***

                    if (TARGET_OS_IPHONE && NSFoundationVersionNumber > NSFoundationVersionNumber_iOS_7_1) {
                        NSData *newImage = [item.value copyWithZone:nil];
                        albumArtWork = [UIImage imageWithData:newImage];
                    }
                    else {
                        NSDictionary *dict = [item.value copyWithZone:nil];
                        if ([dict objectForKey:@"data"]) {
                            albumArtWork = [UIImage imageWithData:[dict objectForKey:@"data"]];
                        }
                    }
                }
                else if ([item.keySpace isEqualToString:AVMetadataKeySpaceiTunes]) {
                    // This doesn't appear to get called for images set (ironically) in iTunes
                    albumArtWork = [UIImage imageWithData:[item.value copyWithZone:nil]];
                }
            }

            if (albumArtWork != nil) {
                dispatch_sync(dispatch_get_main_queue(), ^{
                    [self.albumArtImageView setImage:albumArtWork];
                });
            }

        }];
    }

}
like image 139
Stan James Avatar answered Nov 13 '22 08:11

Stan James