2

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?

Liftoff
  • 24,717
  • 13
  • 66
  • 119

2 Answers2

3

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.

Rhythmic Fistman
  • 34,352
  • 5
  • 87
  • 159
  • I think you're correct. I think that I was getting an error because I was changing the contents of `_activeMetadata` too frequently and it was getting overwritten while being copied to the now playing center. Reducing the frame update interval to 2fps instead of 60fps and creating a copy of the NSDictionary before assigning it to the now playing info seems to have fixed the problem. I haven't had a crash since. – Liftoff Sep 18 '16 at 21:41
  • I agree with you, that it's not good to call it that often. But I think, it should not crash, just because it is called very frequently.. I ran into the same issue, when switching tracks too fast. Making a copy of the Dictionary before fixed it for me. Thx. – d4Rk Oct 26 '16 at 13:14
  • @d4Rk Making a copy of the Dictionary before fixed ? Im still stuck – Kiran eldho Sep 30 '17 at 16:38
  • @David Please explain with an example..still stuck – Kiran eldho Sep 30 '17 at 16:54
  • @Kiraneldho The issue is actually well described in Davids comment. So just create a copy of the Dictionary before you hand it over to now playing. – d4Rk Oct 02 '17 at 12:44
0

Apple fixed this crash in iOS 10.3 and above. So if you want to support iOS 10.2.1 and below, be sure to throttle how often you set [MPNowPlayingInfoCenter defaultCenter].nowPlayingInfo property. Perhaps limiting setting the property only once a second.

David Liu
  • 952
  • 7
  • 15