AVPlayer 未在后台加载媒体

2024-01-10

在后台运行时,我的 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 在后台播放播客。这也没有帮助,但我将其包含在内以供参考。未显示,但我也在 AVPlayerItemplaybackLikelyToKeepUp 状态更改为 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];
    }
}

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

- (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地址,并且AVPlayer会使用playerItem进行初始化。这会导致通知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。

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


尝试打电话beginBackgroundTask在你开始玩之前。这文档 https://developer.apple.com/reference/uikit/uiapplication/1623031-beginbackgroundtask说它不应该用于在后台运行,但它也说它对于未完成的业务很有用,我相信这描述了您的情况。

我的理由是这样的:

  1. 让您的应用程序在后台运行audio背景模式你must当 iOS 通常会取消安排您时正在播放音频
  2. 当播放远程媒体时,自然比本地媒体需要更多的时间,当你告诉AVPlayer to play当音频实际开始播放时,允许您的应用程序继续运行。

因此,后台任务可以给您的应用程序更多的时间(有人说最多3分钟 https://stackoverflow.com/q/12071726/22147) 加载远程媒体并播放音频。

你应该做这个把戏吗?可能不是——这是一个AVPlayer/iOS 调度程序实现细节 - 为了使 iOS 调度程序在后台音频情况下“公平”,它确实应该通过让AVPlayer.play()标记背景音频的开始。如果由于某种原因播放失败,那么好吧,重新安排。无论如何,如果您对此有强烈的感觉,您可以提出功能请求。

附注别忘了打电话endBackgroundTask!无论是在过期处理程序中,还是成为一名优秀的移动设备公民,一旦音频开始播放,即可确保您的后台持续运行。如果endBackgroundTask正在影响您在后台播放音频的能力,那么您调用它为时过早!

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

AVPlayer 未在后台加载媒体 的相关文章

  • 使用 AFNetworking 重置基本身份验证凭据

    我正在编写一个 REST 客户端 使用 AFNetworking 并且需要能够在应用程序的单个实例中触发新会话的创建 换句话说 我想 1 通过服务器进行身份验证2 进行一些 REST 调用3 模拟 注销 4 重新与服务器进行身份验证5 进行
  • 更改iOS11中的UISearchBar背景图片

    我想更改我的 UISearchBar 背景图像 当将它添加为我的 UITableView 中的标题视图时 它工作得很好 但是 当我想更改它以将 SearchBar 设置为 navigationItem 的 searchController
  • SwiftUI 键盘工具栏有条件

    不确定这是否是一个错误或者我是否做错了什么 但如果我使用 toolbar ToolbarItemGroup placement navigationBarLeading if isFocused zipCode Text Test 当等于
  • Android:iOS UIActionSheet 等效项

    我正在转换一个 iOS 应用程序 并且需要实现从 iOS 到 Android 的 UIActionSheet 的等效项 什么 UI 元素最能模仿这一点 我的目标是 Android 2 2 及更高版本 您将使用 AlertDialog 或 D
  • 图像高斯模糊 - iOS 8

    我有一个移动的背景图像 我想模糊它的底部 我would只用 Photoshop 就能做到 但由于图像会移动 效果不太好 这就是我的意思 看图片底部 基本上就像底座对 iPhone 的影响一样 我使用的是 iOS 8 但不是 Swift 我根
  • 如何检查 iOS 分发配置文件是否启用了推送通知?

    我有一个应用程序应该启用推送通知 但由于某种原因没有启用它们 我见过其他人下载并安装了该应用程序 但它甚至没有提示他们授予发送推送通知的权限 正如预期的那样 此应用程序不会出现在其 设置 gt 通知 中 但是 在我的 iPad 上 我能够从
  • 错误 ITMS-90207 Apple Store 提交

    当我在模拟器或设备上运行我的应用程序时 用于调试和发布构建配置 它可以完美运行 但是当我尝试将我的应用程序提交到 Apple Store 时 出现以下错误 错误 ITMS 90207 捆绑包无效 APPNAME app 处的捆绑包确实 不包
  • 使用 iOS 7 检索设备 WiFi MAC 地址

    我们的应用程序使用设备 WiFi MAC 地址来唯一标识设备 根据苹果文档 https developer apple com news id 8222013a我们将开始使用 UIDevice 的identifierForVendor 属性
  • GCD 和线程

    我想了解一些有关 GCD 和线程的知识 我的视图控制器中有一个 for 循环 它要求我的模型执行一些异步网络请求 因此 如果循环运行 5 次 模型将发出 5 个网络请求 考虑到我正在使用 NSURLConnection 的 sendAsyn
  • 在 Flurry 中记录比错误 ID 更多信息的方法?

    我目前使用 iOS 版 Flurry 5 4 0 我担心在方法方面是否能够记录更多信息 而不仅仅是错误 ID void logError NSString errorID message NSString message error NSE
  • ios 使用 HTTP POST 上传图像和文本

    谢谢阅读 我是 iOS 新手 我正在尝试使用上传图像和文本multi part form encoding在 iOS 中 The curl等价的是这样的 curl F param1 value1 F email protected cdn
  • 将两个字符的字符串转换为布尔数组的快速方法是什么?

    我有一个很长的字符串 有时超过 1000 个字符 我想将其转换为布尔值数组 它需要非常快速地多次执行此操作 let input String 001 let output Bool false false true 我天真的尝试是这样的 i
  • 我如何在 viewDidLoad 中执行 UIView animateWithDuration ? IOS 7

    我在 viewDidAppear 中尝试这个 但我有一秒钟的延迟 我能做什么 在 viewDidLoad 中工作 void viewDidAppear BOOL animated fullRotation CABasicAnimation
  • 如何在不使用 viewWillDisappear 的情况下使 NSTimer 无效/取消初始化?

    var faderTimer NSTimer override func viewDidLoad super viewDidLoad self faderTimer NSTimer scheduledTimerWithTimeInterva
  • 在运行时动态创建核心数据模型

    是否可以在运行时从服务器上的一组实体生成核心数据模型 例如SharePoint 列表或 SQL MySQL Parse 我正在尝试采用动态路线 因为 SharePoint 列表 SQL Parse 中的字段可能会在将来随时添加 这意味着应用
  • 如何在 UIAlertView (iOS) 中的其他两个按钮(堆叠)之间添加取消按钮

    我正在尝试创建一个带有三个按钮 将堆叠 的 UIAlertView 我希望 取消 按钮位于其他两个按钮之间的中间 我尝试将 cancelButtonIndex 设置为 1 但如果还有其他两个按钮 它只会将它们放置在索引 0 和 1 处 我知
  • 在 iOS 上的 OpenGL ES 2.0 中创建 16 位亮度纹理

    我的文件中有 16 位数据 我正在尝试将其加载到 iOS 上的 OpenGL 亮度纹理中 如果我手动将 16 位值重新调整为 8 位 我可以按如下方式加载和显示数据 glTexImage2D GL TEXTURE 2D 0 GL LUMIN
  • 在完成块中保留循环

    在我的课堂上 我创建了这个方法 void refreshDatasourceWithSuccess CreateDataSourceSuccessBlock successBlock failure CreateDataSourceFail
  • 如何使用 iOS 可达性

    我正在开发一个使用网络的 iPhone 应用程序 iPhone 通过 HTTP 请求与我的服务器通信 并且应该可以在 WiFi 和 3G 上运行 我目前使用NSURLConnection initWithRequest向我的服务器发送异步请
  • UILocalNotification 在后台 10 分钟后不提示

    In didFinishLaunchingWithOptions调用函数的定时器循环httpRequest每 1 分钟间隔一次 BOOL application UIApplication application didFinishLaun

随机推荐