AVPlayer未在后台加载媒体

时间:2017-03-09 23:50:11

标签: ios avplayer background-process avplayeritem

在后台运行时,我的AVPlayer实现无法播放下载的音频(例如播客),但能够播放本地存储的歌曲。仅在手机与计算机断开连接时才能在后台播放。如果我的手机直接连接到我的计算机/调试器,则任何本地或下载的媒体都可以正常播放。在前台,播放任何媒体类型也没有问题。

这是我的实施:

AVPlayer                    *moviePlayer;               
AVPlayerItem                *playerItem;
NSURL *address = /* the url of either a local song or remote media */

if (moviePlayer != nil) {
    NSLog(@"removing rate, playbackBufferEmpty, playbackLikelyToKeepUp observers before creating new player");
    [moviePlayer removeObserver:self forKeyPath:@"rate"];
    [playerItem removeObserver:self forKeyPath:@"playbackBufferEmpty"];
    [playerItem removeObserver:self forKeyPath:@"playbackLikelyToKeepUp"];
    [playerItem removeObserver:self forKeyPath:@"status"];
    [[NSNotificationCenter defaultCenter] removeObserver:self name:AVPlayerItemDidPlayToEndTimeNotification object:playerItem];
    [[NSNotificationCenter defaultCenter] removeObserver:self name:AVPlayerItemFailedToPlayToEndTimeNotification object:playerItem];
    [[NSNotificationCenter defaultCenter] removeObserver:self name:AVPlayerItemPlaybackStalledNotification object:playerItem];
    [self setMoviePlayer:nil];  // release and nilify
}

// The following block of code was an experiment to see if starting a background task would resolve the problem. As implemented, this did not help.
if([[UIApplication sharedApplication] applicationState] != UIApplicationStateActive)
{
    NSLog(@"Experiment. Starting background task to keep iOS awake");
    task = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^(void) {
    }];
}

 playerItem = [[AVPlayerItem alloc]initWithURL:address];
 moviePlayer = [[AVPlayer alloc]initWithPlayerItem:playerItem];

 // Add a notification to make sure that the player starts playing. This is handled by observeValueForKeyPath
 [moviePlayer addObserver:self
               forKeyPath:@"rate"
                  options:NSKeyValueObservingOptionNew
                  context:nil];

[playerItem addObserver:self forKeyPath:@"playbackBufferEmpty" options:NSKeyValueObservingOptionNew context:nil];
[playerItem addObserver:self forKeyPath:@"playbackLikelyToKeepUp" options:NSKeyValueObservingOptionNew context:nil];
[playerItem addObserver:self forKeyPath:@"status" options:NSKeyValueObservingOptionNew context:nil];

 // The following 2 notifications handle the end of play
 [[NSNotificationCenter defaultCenter] addObserver:self
                                          selector:@selector(moviePlayBackDidFinish:)
                                              name:AVPlayerItemDidPlayToEndTimeNotification
                                            object:playerItem];

 [[NSNotificationCenter defaultCenter] addObserver:self
                                          selector:@selector(moviePlayBackDidFinish:)
                                              name:AVPlayerItemFailedToPlayToEndTimeNotification
                                            object:playerItem];

[[NSNotificationCenter defaultCenter] addObserver:self
                                         selector:@selector(moviePlayBackStalled:)
                                             name:AVPlayerItemPlaybackStalledNotification
                                           object:playerItem];

 // Indicate the action the player should take when it finishes playing.
 moviePlayer.actionAtItemEnd = AVPlayerActionAtItemEndPause;

 moviePlayer.automaticallyWaitsToMinimizeStalling = NO;

更新:在上面的实现中,我还展示了启动后台任务的实验尝试,希望能够让AVPlayer在后台播放播客。这也没有帮助,但我将其包括在内以供参考。未显示,但我也在AVPlayerItem playbackLikelyToKeepUp状态变为TRUE后结束后台任务。

然后我有以下代码来处理keyPath通知:

-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
    if ([keyPath isEqualToString:@"rate"]) {
        NSString *debugString = [NSString stringWithFormat: @"In observeValueForKeyPath: rate"];
        DLog(@"%@", debugString);
        debugString = [self appendAvPlayerStatus: debugString];
        [[FileHandler sharedInstance] logDebugString:debugString];
    }
    else if (object == playerItem && [keyPath isEqualToString:@"playbackBufferEmpty"]) {

        NSString *debugString = [NSString stringWithFormat: @"In observeValueForKeyPath: playbackBufferEmpty"];
        DLog(@"%@", debugString);
        debugString = [self appendAvPlayerStatus: debugString];
        [[FileHandler sharedInstance] logDebugString:debugString];
    }
    else if (object == playerItem && [keyPath isEqualToString:@"playbackLikelyToKeepUp"]) {

        NSString *debugString = [NSString stringWithFormat: @"In observeValueForKeyPath: playbackLikelyToKeepUp"];
        DLog(@"%@", debugString);
        debugString = [self appendAvPlayerStatus: debugString];
        [[FileHandler sharedInstance] logDebugString:debugString];
    }

    else if (object == playerItem && [keyPath isEqualToString:@"status"]) {

        NSString *debugString = [NSString stringWithFormat: @"In observeValueForKeyPath: status"];
        DLog(@"%@", debugString);
        debugString = [self appendAvPlayerStatus: debugString];
        [[FileHandler sharedInstance] logDebugString:debugString];
    }
}

我有以下方法来处理notificationCenter通知:

- (void) moviePlayBackDidFinish:(NSNotification*)notification {

    NSLog(@"moviePlaybackDidFinish. Time to stopMoviePlayerWithMusicPlayIndication");
    [self stopMoviePlayer: YES];  // stop the movie player 
}

- (void) moviePlayBackStalled:(NSNotification*)notification {

    NSString *debugString = [NSString stringWithFormat: @"In moviePlayBackStalled. Restarting player"];
    DLog(@"%@", debugString);
    debugString = [self appendAvPlayerStatus: debugString];
    [[FileHandler sharedInstance] logDebugString:debugString];
    [moviePlayer play];
}

通过实现一个日志工具来跟踪与计算机断开连接时的执行情况,我正在观察的是:

当在与计算机断开连接的后台运行时,playerItem将加载url地址,并使用playerItem初始化AVPlayer。这会导致通知observeValueForKeyPath:rate被发布,但这是收到的最后一个通知。音频无法播放。玩家只是挂起。显示各种moviePlayer和playerItem标志的日志输出如下:

ready to play movie/podcast/song dedication/Song from url: http://feedproxy.google.com/~r/1019TheMix-EricAndKathy/~5/ZZnF09tuxr0/20170309-1_1718764.mp3
In observeValueForKeyPath: rate - timeCntrlStatus: AVPlayerTimeControlStatusPlaying, itemStatus: AVPlayerItemStatusUnknown, playbackToKeepUp: 0, playbackBufferEmpty: 1, playbackBufferFull: 0, rate: 1.0

但是,当直接连接到计算机或在前台运行时在后台运行时,您可以从下面的日志输出中看到,在加载URL地址后,使用playerItem初始化AVPlayer,系列发布了针对keyPath的通知:rate,playbackBufferEmpty,playbackLikelyToKeepUp和status。然后音频开始播放。显示各种moviePlayer和playerItem标志的输出如下:

ready to play movie/podcast/song dedication/Song from url: http://feedproxy.google.com/~r/1019TheMix-EricAndKathy/~5/d3w52TBzd88/20170306-1-16_1717980.mp3
In observeValueForKeyPath: rate - timeCntrlStatus: AVPlayerTimeControlStatusPlaying, itemStatus: AVPlayerItemStatusUnknown, playbackToKeepUp: 0, playbackBufferEmpty: 1, playbackBufferFull: 0, rate: 1.0
In observeValueForKeyPath: playbackBufferEmpty - timeCntrlStatus: AVPlayerTimeControlStatusPlaying, itemStatus: AVPlayerItemStatusUnknown, playbackToKeepUp: 0, playbackBufferEmpty: 0, playbackBufferFull: 0, rate: 1.0
In observeValueForKeyPath: playbackLikelyToKeepUp - timeCntrlStatus: AVPlayerTimeControlStatusPlaying, itemStatus: AVPlayerItemStatusUnknown, playbackToKeepUp: 0, playbackBufferEmpty: 0, playbackBufferFull: 0, rate: 1.0
In observeValueForKeyPath: status - timeCntrlStatus: AVPlayerTimeControlStatusPlaying, itemStatus: AVPlayerItemStatusReadyToPlay, playbackToKeepUp: 0, playbackBufferEmpty: 0, playbackBufferFull: 0, rate: 1.0
In observeValueForKeyPath: playbackLikelyToKeepUp - timeCntrlStatus: AVPlayerTimeControlStatusPlaying, itemStatus: AVPlayerItemStatusReadyToPlay, playbackToKeepUp: 1, playbackBufferEmpty: 0, playbackBufferFull: 0, rate: 1.0
In observeValueForKeyPath: rate - timeCntrlStatus: AVPlayerTimeControlStatusPlaying, itemStatus: AVPlayerItemStatusReadyToPlay, playbackToKeepUp: 1, playbackBufferEmpty: 0, playbackBufferFull: 0, rate: 1.0

总而言之,您可以看到,如果直接连接到计算机/调试器,在前台或后台运行时,AVPlayer会成功加载播放缓冲区并播放音频。但是当在后台运行而没有连接到计算机/调试器时,播放器似乎没有加载媒体而只是挂起。

在所有情况下,都不会收到AVPlayerItemPlaybackStalledNotification。

很抱歉很长的解释。任何人都可以看到什么可能导致播放器在未连接到计算机时挂在后台?

1 个答案:

答案 0 :(得分:1)

在开始游戏之前尝试拨打beginBackgroundTaskdocumentation表示它不应该用于在后台运行,但它也说它对未完成的业务很有用,我认为这可以描述你的情况。

我的理由是:

  1. 让您的应用在audio后台模式下在后台运行,必须播放音频时,iOS通常会安排您
  2. 在播放远程媒体时,当您告诉AVPlayerplay以及音频实际开始播放时,自然需要比本地媒体更多的时间,让您的应用继续播放。
  3. 因此,后台任务是为您的应用提供更多时间(some say as much as 3 minutes)来加载远程媒体并播放音频。

    你应该做这个伎俩吗?可能不是 - 这是一个AVPlayer / iOS调度程序实现细节 - 对于iOS调度程序在后台音频情况下“公平”,它真的应该通过让{{{{{{ 1}}标记背景音频的开头。如果由于某种原因播放失败,那么很好,取消安排。无论如何,如果您对此感到强烈,可以提交功能请求。

    P.S。别忘了给AVPlayer.play()打个电话!在到期处理程序和成为一个好的移动设备公民时,一旦音频开始播放,确保您继续运行后台。如果endBackgroundTask影响您在后台播放音频的能力,那么您太早称之为!