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];
}
...
}
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];
});
}
}];
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With