David David - 2 months ago 20
Objective-C Question

MPNowPlayingInfoCenter throwing EXC_BAD_ACCESS

I am making an app that plays back audio and I have set it up so that the lock screen gets updated through

MPNowPlayingInfoCenter
, but I've run into a problem.

At seemingly random times, I get an
EXC_BAD_ACCESS
error when trying to update the now playing info.

Here's the code that does so:

- (void)updatePlayback
{
if(!active)
return;

NowPlayingController* npc = [AudioController nowPlayingController];
CMTime elapsed = player.currentTime;
Float64 elInterval = CMTimeGetSeconds(elapsed);
[npc setElapsed:elInterval];

CMTime duration = player.currentItem.duration;
Float64 durInterval = CMTimeGetSeconds(duration);
[npc setRemaining:ceilf(durInterval - elInterval)];

[npc setPlayPauseValue:isPlaying];
if(durInterval > 0)
{
[npc setProgressValue:elInterval/durInterval];
[npc setAudioDuration:durInterval];
}

_activeMetadata[MPMediaItemPropertyPlaybackDuration] = @(durInterval);
_activeMetadata[MPNowPlayingInfoPropertyPlaybackRate] = @(isPlaying);
_activeMetadata[MPNowPlayingInfoPropertyElapsedPlaybackTime] = @(elInterval);

MPNowPlayingInfoCenter* npInfoCenter = [MPNowPlayingInfoCenter defaultCenter];
if(npInfoCenter && _activeMetadata)
{
if([npInfoCenter respondsToSelector:@selector(setNowPlayingInfo:)])
{

//////////THE FOLLOWING LINE TRIGGERS EXC_BAD_ACCESS SOMETIMES////////////
[npInfoCenter setNowPlayingInfo:_activeMetadata];
}

}
}


99.9% of the time, this works, but sometimes when resigning the app to the background or when changing audio files, or just randomly,

[npInfoCenter setNowPlayingInfo:_activeMetadata];


throws
EXC_BAD_ACCESS
.

Also,
_activeMetadata
is declared as:

@property (atomic, strong, retain) NSMutableDictionary* activeMetadata;


It is instantiated when the AVPlayer is created:

AVAsset* asset = [AVAsset assetWithURL:[NSURL fileURLWithPath:path]];
AVPlayerItem* playerItem = [AVPlayerItem playerItemWithAsset:asset];
player = [AVPlayer playerWithPlayerItem:playerItem];

CMTime duration = player.currentItem.duration;
NSTimeInterval durInterval = CMTimeGetSeconds(duration);
NSLog(@"%f", durInterval);

MPMediaItemArtwork* albumArtwork = [[MPMediaItemArtwork alloc] initWithImage:[downloader useCachedImage:CacheKeySeriesBanners withName:nil withURL:info[@"image"]]];
NSDictionary* nowPlayingInfo = @{MPMediaItemPropertyTitle:ptString,
MPMediaItemPropertyArtist:spString,
MPMediaItemPropertyArtwork:albumArtwork,
MPMediaItemPropertyAlbumTitle:info[@"title"],
MPMediaItemPropertyPlaybackDuration:@(durInterval),
MPNowPlayingInfoPropertyPlaybackRate:@(1),
MPNowPlayingInfoPropertyElapsedPlaybackTime:@(0)};
[[MPNowPlayingInfoCenter defaultCenter] setNowPlayingInfo:nowPlayingInfo];

_activeMetadata = [nowPlayingInfo mutableCopy];


updatePlayback
is called via a CADisplayLink on every frame.

Any ideas what could be causing the exception?

Answer

I think you're calling setNowPlayingInfo way too often. Granted, it really shouldn't crash but there's no need to use CADisplayLink to call it 60 times a second.

So why are you calling it so often? If it's because you want to progress bar to track smoothly, there's still no need. From the MPNowPlayingInfoPropertyElapsedPlaybackTime declaration:

// The elapsed time of the now playing item, in seconds.
// Note the elapsed time will be automatically extrapolated from the previously 
// provided elapsed time and playback rate, so updating this property frequently
// is not required (or recommended.)

p.s. I tried the code with an m4a file and found durInterval was NotANumber. With the correct duration and calling setNowPlayingInfo only once, the progress bar tracked fine & nothing crashed.