При работе в фоновом режиме моя реализация 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 воспроизводить подкаст в фоновом режиме. Это тоже не помогло, но я включаю его для справки. Не показано, но я также завершаю фоновую задачу после того, как статус AVPlayerItemplayLikelyToKeepUp изменится на 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-адресом, а AVPlayer инициализируется с помощью playerItem. Это приводит к тому, что будет опубликовано уведомление «Наблюдение за значениемФорКэйпас: скорость», но это будет последнее полученное уведомление. Аудио не воспроизводится. Плеер просто зависает. Вывод из моего журнала, показывающий различные флаги 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-адреса и инициализации AVPlayer с помощью playerItem появляется серия уведомлений. опубликовано для keyPath: rate, проигрываниеBufferEmpty, проигрываниеLikelyToKeepUp и 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
, когда начинаете играть? В документации говорится, что его не следует использовать для работы в фоновом режиме, но также говорится, что это полезно для незавершенных дел: developer.apple.com/reference/uikit/uiapplication/ - person Rhythmic Fistman   schedule 14.03.2017AVPlayer
требуется время для потоковой передачи аудиоданных, прежде чем он сможет начать воспроизведение. - person Rhythmic Fistman   schedule 14.03.2017endBackgroundTask
в обработчике истечения срока, это выглядит нормально. Можете ли вы создать небольшой проект, в котором воспроизводится проблема, и связать его с этим вопросом? - person Rhythmic Fistman   schedule 15.03.2017