Verifique o estado de reprodução do AVPlayer

Respostas:

57

Para obter uma notificação por chegar ao final de um item (via Apple ):

[[NSNotificationCenter defaultCenter] 
      addObserver:<self>
      selector:@selector(<#The selector name#>)
      name:AVPlayerItemDidPlayToEndTimeNotification 
      object:<#A player item#>];

E para acompanhar a reprodução, você pode:

"rastrear mudanças na posição do indicador de reprodução em um objeto AVPlayer" usando addPeriodicTimeObserverForInterval: queue: usingBlock: ou addBoundaryTimeObserverForTimes: queue: usingBlock: .

O exemplo é da Apple:

// Assume a property: @property (retain) id playerObserver;

Float64 durationSeconds = CMTimeGetSeconds([<#An asset#> duration]);
CMTime firstThird = CMTimeMakeWithSeconds(durationSeconds/3.0, 1);
CMTime secondThird = CMTimeMakeWithSeconds(durationSeconds*2.0/3.0, 1);
NSArray *times = [NSArray arrayWithObjects:[NSValue valueWithCMTime:firstThird], [NSValue valueWithCMTime:secondThird], nil];

self.playerObserver = [<#A player#> addBoundaryTimeObserverForTimes:times queue:NULL usingBlock:^{
    // Passing NULL for the queue specifies the main queue.

    NSString *timeDescription = (NSString *)CMTimeCopyDescription(NULL, [self.player currentTime]);
    NSLog(@"Passed a boundary at %@", timeDescription);
    [timeDescription release];
}];
Todd Hopkinson
fonte
Você pode lançar uma bandeira booleana para verificar o status do jogo.
moonman239
As notificações podem causar problemas se você alterar o item do jogador, por exemplo, com -replaceCurrentItemWithPlayerItem:, e não manuseá-lo corretamente. Uma maneira mais confiável para mim é rastrear AVPlayero status de usando o KVO. Veja minha resposta abaixo para mais detalhes: stackoverflow.com/a/34321993/3160561
maxkonovalov
2
Também precisa de notificação de erro? AVPlayerItemFailedToPlayToEndTimeNotification
Toolmaker Steve
332

Você pode saber que está jogando usando:

AVPlayer *player = ...
if ((player.rate != 0) && (player.error == nil)) {
    // player is playing
}

Extensão Swift 3:

extension AVPlayer {
    var isPlaying: Bool {
        return rate != 0 && error == nil
    }
}
maz
fonte
29
Não necessariamente, ele não lida com situações em que o player trava devido a um erro no arquivo. Algo que descobri da maneira mais difícil ...
Dermot
7
Como disse o Dermot, se você tentar tocar algo enquanto estiver no modo avião, a taxa do AVPlayer ainda será definida como 1.0, uma vez que indica a intenção de jogar.
phi
4
O AVPlayer tem uma propriedade de erro, apenas verifique se não é nulo e também se a taxa não é 0 :)
James Campbell
23
Observe que a resposta foi atualizada para evitar o cenário @Dermot. Então, este realmente funciona.
jomafer
2
Não funcionará ao reproduzir o vídeo reverso ( - [AVPlayer setRate:-1.0]), porque -1.0é inferior a 0.
Julian F. Weinert
49

No iOS10, há uma propriedade integrada para isso agora: timeControlStatus

Por exemplo, esta função reproduz ou pausa o avPlayer com base em seu status e atualiza o botão play / pause apropriadamente.

@IBAction func btnPlayPauseTap(_ sender: Any) {
    if aPlayer.timeControlStatus == .playing {
        aPlayer.pause()
        btnPlay.setImage(UIImage(named: "control-play"), for: .normal)
    } else if aPlayer.timeControlStatus == .paused {
        aPlayer.play()
        btnPlay.setImage(UIImage(named: "control-pause"), for: .normal)
    }
}

Quanto à sua segunda pergunta, para saber se o avPlayer chegou ao fim, a coisa mais fácil de fazer seria configurar uma notificação.

NotificationCenter.default.addObserver(self, selector: #selector(self.didPlayToEnd), name: .AVPlayerItemDidPlayToEndTime, object: nil)

Quando chegar ao final, por exemplo, você pode retroceder até o início do vídeo e redefinir o botão Pausa para Reproduzir.

@objc func didPlayToEnd() {
    aPlayer.seek(to: CMTimeMakeWithSeconds(0, 1))
    btnPlay.setImage(UIImage(named: "control-play"), for: .normal)
}

Esses exemplos são úteis se você estiver criando seus próprios controles, mas se usar um AVPlayerViewController, os controles vêm integrados.

Travis M.
fonte
Fácil de usar para iOS 10+
technerd
2
@objc func didPlayToEnd () para Swift 4.2
Sean Stayns
21

rateNÃO é a maneira de verificar se um vídeo está sendo reproduzido (ele pode travar). Da documentação de rate:

Indica a taxa desejada de reprodução; 0,0 significa "pausado", 1,0 indica um desejo de jogar na taxa natural do item atual.

Palavras-chave "desejo de reproduzir" - uma taxa de 1.0não significa que o vídeo está sendo reproduzido.

A solução desde o iOS 10.0 é usar o AVPlayerTimeControlStatusque pode ser observado na AVPlayer timeControlStatuspropriedade.

A solução anterior ao iOS 10.0 (9.0, 8.0 etc.) é lançar sua própria solução. Uma taxa de 0.0significa que o vídeo está pausado. Quando rate != 0.0isso significa que o vídeo está sendo reproduzido ou está parado.

Você pode descobrir a diferença observando o tempo do jogador via: func addPeriodicTimeObserver(forInterval interval: CMTime, queue: DispatchQueue?, using block: @escaping (CMTime) -> Void) -> Any

O bloco retorna a hora atual do jogador CMTime, então uma comparação de lastTime(a hora que foi recebida pela última vez do bloco) e currentTime(a hora que o bloco acabou de reportar) dirá se o jogador está jogando ou está parado. Por exemplo, se lastTime == currentTimeerate != 0.0 , então o jogador estagnou.

Conforme observado por outros, descobrir se a reprodução terminou é indicado por AVPlayerItemDidPlayToEndTimeNotification.

kgaidis
fonte
18

Uma alternativa mais confiável NSNotificationé adicionar-se como observador à ratepropriedade do jogador .

[self.player addObserver:self
              forKeyPath:@"rate"
                 options:NSKeyValueObservingOptionNew
                 context:NULL];

Em seguida, verifique se o novo valor para a taxa observada é zero, o que significa que a reprodução foi interrompida por algum motivo, como chegar ao fim ou travar devido ao buffer vazio.

- (void)observeValueForKeyPath:(NSString *)keyPath
                      ofObject:(id)object
                        change:(NSDictionary<NSString *,id> *)change
                       context:(void *)context {
    if ([keyPath isEqualToString:@"rate"]) {
        float rate = [change[NSKeyValueChangeNewKey] floatValue];
        if (rate == 0.0) {
            // Playback stopped
        } else if (rate == 1.0) {
            // Normal playback
        } else if (rate == -1.0) {
            // Reverse playback
        }
    }
}

Por rate == 0.0exemplo, para saber o que exatamente causou a interrupção da reprodução, você pode fazer as seguintes verificações:

if (self.player.error != nil) {
    // Playback failed
}
if (CMTimeGetSeconds(self.player.currentTime) >=
    CMTimeGetSeconds(self.player.currentItem.duration)) {
    // Playback reached end
} else if (!self.player.currentItem.playbackLikelyToKeepUp) {
    // Not ready to play, wait until enough data is loaded
}

E não se esqueça de fazer o seu jogador parar quando chegar ao fim:

self.player.actionAtItemEnd = AVPlayerActionAtItemEndPause;

maxkonovalov
fonte
Acho que também precisa de notificação de falha para chegar ao fim - AVPlayerItemFailedToPlayToEndTimeNotification. Se esse erro acontecer, nunca chegará ao horário de término. Ou lendo a resposta de maz, adicione ao seu código uma verificação para player.error != nil, caso em que a reprodução foi "encerrada" devido a um erro.
Toolmaker Steve
BTW, o que você achou "não confiável" sobre o uso de NSNotification de AVPlayerItemDidPlayToEndTimeNotification?
Toolmaker Steve
ToolmakerSteve, obrigado por sua observação, adicionou verificação de erro à minha resposta. Para notificações não confiáveis ​​- eu me deparei com o problema ao implementar visualizações de jogador repetidas e reutilizáveis ​​na visualização de coleção, e o KVO tornou as coisas mais claras, pois permitiu manter tudo mais local sem que notificações de diferentes jogadores interferissem umas nas outras.
maxkonovalov
17

Para Swift :

AVPlayer :

let player = AVPlayer(URL: NSURL(string: "http://www.sample.com/movie.mov"))
if (player.rate != 0 && player.error == nil) {
   println("playing")
}

Atualização :
player.rate > 0condição alterada para player.rate != 0porque se o vídeo estiver sendo reproduzido ao contrário, pode ser negativo graças a Julian por apontar.
Nota : Isso pode ser parecido com a resposta acima (Maz), mas no Swift '! Player.error' estava me dando um erro do compilador, então você deve verificar se há erros usando 'player.error == nil' no Swift. (Devido à propriedade de erro não é do tipo 'Bool')

AVAudioPlayer:

if let theAudioPlayer =  appDelegate.audioPlayer {
   if (theAudioPlayer.playing) {
       // playing
   }
}

AVQueuePlayer:

if let theAudioQueuePlayer =  appDelegate.audioPlayerQueue {
   if (theAudioQueuePlayer.rate != 0 && theAudioQueuePlayer.error == nil) {
       // playing
   }
}
Aks
fonte
2
AVPlayer! = AVAudioPlayer
quemeful
2
Advertências: todas mencionadas acima na resposta que apareceu dois anos antes desta.
Dan Rosenstark
1
@Yar Eu sei que também votou positivamente na resposta acima, mas isso é para o swift e em Swift '! Player.error' não estava funcionando para mim, é permitido apenas para tipos bool, então eu adicionei a resposta oops votou positivamente em seu comentário por engano para responder usando este aplicativo de estouro de pilha :)
Aks de
Minha preocupação é que eu não sei o quão bem o jogador.error não ser nulo realmente funciona.
Dan Rosenstark
1
Não funcionará ao reproduzir a sequência de vídeo reversa ( player.rate = -1.0), porque -1.0 é menor que 0.
Julian F. Weinert
9

Extensão Swift baseada na resposta de maz

extension AVPlayer {

    var isPlaying: Bool {
        return ((rate != 0) && (error == nil))
    }
}
Mark Bridges
fonte
6

A versão Swift da resposta de maxkonovalov é esta:

player.addObserver(self, forKeyPath: "rate", options: NSKeyValueObservingOptions.New, context: nil)

e

override func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [String : AnyObject]?, context: UnsafeMutablePointer<Void>) {
    if keyPath == "rate" {
        if let rate = change?[NSKeyValueChangeNewKey] as? Float {
            if rate == 0.0 {
                print("playback stopped")
            }
            if rate == 1.0 {
                print("normal playback")
            }
            if rate == -1.0 {
                print("reverse playback")
            }
        }
    }
}

Obrigado maxkonovalov!

Sr. Stanev
fonte
Olá Sr. Stanev, obrigado pela sua resposta. Isso não funciona para mim (também conhecido como não imprime nada no console?)
Cesare
Esqueça, funciona - meu jogador sim nil! Mas preciso saber quando a visualização começa (não termina). Alguma maneira de fazer isso?
Cesare
3

Atualmente, com o swift 5, a maneira mais fácil de verificar se o player está jogando ou pausado é verificar a variável .timeControlStatus .

player.timeControlStatus == .paused
player.timeControlStatus == .playing
azemi
fonte
1

A resposta é o objetivo C

if (player.timeControlStatus == AVPlayerTimeControlStatusPlaying) {
    //player is playing
}
else if (player.timeControlStatus == AVPlayerTimeControlStatusPaused) {
    //player is pause
}
else if (player.timeControlStatus == AVPlayerTimeControlStatusWaitingToPlayAtSpecifiedRate) {
    //player is waiting to play
}
iOS Lifee
fonte
0
player.timeControlStatus == AVPlayer.TimeControlStatus.playing
Noda Hikaru
fonte
1
A resposta fornecida pode estar correta, mas pode se beneficiar de uma explicação . Respostas apenas em código não são consideradas "boas" respostas. Aqui estão algumas diretrizes para Como escrevo uma boa resposta? . Da revisão .
MyNameIsCaleb