Controlla lo stato di riproduzione di AVPlayer


Risposte:


57

Per ricevere una notifica per aver raggiunto la fine di un elemento (tramite Apple ):

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

E per tenere traccia della riproduzione puoi:

"tenere traccia delle modifiche nella posizione dell'indicatore di riproduzione in un oggetto AVPlayer" utilizzando addPeriodicTimeObserverForInterval: coda: usingBlock: o addBoundaryTimeObserverForTimes: coda: usingBlock: .

L'esempio è di 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];
}];

Potresti inserire una bandiera booleana per controllare lo stato di riproduzione.
moonman239

Le notifiche potrebbero causare problemi se modifichi l'elemento del giocatore, ad esempio con -replaceCurrentItemWithPlayerItem:, e non lo gestisci correttamente. Un modo più affidabile per me è monitorare lo AVPlayerstato di utilizzando KVO. Vedere la mia risposta qui sotto per maggiori dettagli: stackoverflow.com/a/34321993/3160561
maxkonovalov

2
Serve anche la notifica di errore? AVPlayerItemFailedToPlayToEndTimeNotification
ToolmakerSteve

332

Puoi dire che sta giocando usando:

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

Estensione Swift 3:

extension AVPlayer {
    var isPlaying: Bool {
        return rate != 0 && error == nil
    }
}

29
Non necessariamente, non gestisce situazioni in cui il giocatore si blocca a causa di un errore nel file. Qualcosa che ho scoperto nel modo più duro ...
Dermot

7
Come ha detto Dermot, se provi a riprodurre qualcosa mentre sei in modalità aereo, la velocità di AVPlayer è ancora impostata su 1.0, poiché implica l'intenzione di giocare.
phi

4
AVPlayer ha una proprietà di errore, controlla che non sia nulla e controlla che il tasso non sia 0 :)
James Campbell

23
Nota che la risposta è stata aggiornata per evitare lo scenario @Dermot. Quindi questo funziona davvero.
jomafer

2
Non funzionerà durante la riproduzione di filmati video al contrario ( - [AVPlayer setRate:-1.0]), perché -1.0è inferiore a 0.
Julian F. Weinert

49

In iOS10, c'è una proprietà incorporata per questo ora: timeControlStatus

Ad esempio, questa funzione riproduce o mette in pausa avPlayer in base al suo stato e aggiorna il pulsante di riproduzione / pausa in modo appropriato.

@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)
    }
}

Per quanto riguarda la tua seconda domanda, per sapere se avPlayer è arrivato alla fine, la cosa più semplice da fare sarebbe impostare una notifica.

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

Quando arriva alla fine, ad esempio, puoi farlo riavvolgere all'inizio del video e reimpostare il pulsante Pausa su Riproduci.

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

Questi esempi sono utili se stai creando i tuoi controlli, ma se usi un AVPlayerViewController, i controlli sono integrati.


Facile da usare per iOS 10+
tecnico

2
@objc func didPlayToEnd () per Swift 4.2
Sean Stayns

21

rateNON è il modo per verificare se un video è in riproduzione (potrebbe bloccarsi). Dalla documentazione di rate:

Indica la velocità di riproduzione desiderata; 0.0 significa "in pausa", 1.0 indica il desiderio di giocare alla velocità naturale dell'elemento corrente.

Parole chiave "desiderio di giocare": un tasso di 1.0non significa che il video è in riproduzione.

La soluzione da iOS 10.0 consiste nell'utilizzare ciò AVPlayerTimeControlStatusche può essere osservato sulla AVPlayer timeControlStatusproprietà.

La soluzione precedente a iOS 10.0 (9.0, 8.0 ecc.) Consiste nel lanciare la propria soluzione. Una frequenza 0.0indica che il video è in pausa. Quando rate != 0.0significa che il video è in riproduzione o è bloccato.

Puoi scoprire la differenza osservando il tempo del giocatore tramite: func addPeriodicTimeObserver(forInterval interval: CMTime, queue: DispatchQueue?, using block: @escaping (CMTime) -> Void) -> Any

Il blocco restituisce il tempo del giocatore corrente in CMTime, quindi un confronto tra lastTime(il tempo che è stato ricevuto l'ultima volta dal blocco) e currentTime(il tempo che il blocco appena segnalato) dirà se il giocatore sta giocando o è in stallo. Ad esempio, se lastTime == currentTimee rate != 0.0, il giocatore si è bloccato.

Come notato da altri, capire se la riproduzione è terminata è indicato da AVPlayerItemDidPlayToEndTimeNotification.


18

Un'alternativa più affidabile NSNotificationè aggiungerti come osservatore alla rateproprietà del giocatore .

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

Quindi controlla se il nuovo valore per la velocità osservata è zero, il che significa che la riproduzione si è interrotta per qualche motivo, come raggiungere la fine o lo stallo a causa del buffer vuoto.

- (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
        }
    }
}

Ad esempio rate == 0.0, per sapere cosa ha causato esattamente l'interruzione della riproduzione, è possibile eseguire i seguenti controlli:

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 non dimenticare di far fermare il tuo lettore quando raggiunge la fine:

self.player.actionAtItemEnd = AVPlayerActionAtItemEndPause;


Penso che sia necessaria anche la notifica del mancato raggiungimento della fine - AVPlayerItemFailedToPlayToEndTimeNotification. Se si verifica questo errore, non raggiungerà mai l'ora di fine. Oppure, leggendo la risposta di maz, aggiungi al tuo codice un segno di spunta player.error != nil, nel qual caso la riproduzione è "terminata" a causa di un errore.
ToolmakerSteve

A proposito, cosa hai trovato "non affidabile" sull'utilizzo di NSNotification di AVPlayerItemDidPlayToEndTimeNotification?
ToolmakerSteve

ToolmakerSteve, grazie per la tua nota, ha aggiunto il controllo degli errori alla mia risposta. Per notifiche non affidabili: mi sono imbattuto nel problema da solo durante l'implementazione di visualizzazioni del giocatore ripetute e riutilizzabili nella vista raccolta e KVO ha reso le cose più chiare, poiché ha permesso di mantenere tutto più locale senza che le notifiche dei diversi giocatori interferissero tra loro.
maxkonovalov

17

Per Swift :

AVPlayer :

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

Aggiornamento :
player.rate > 0condizione cambiata in player.rate != 0perché se il video viene riprodotto al contrario può essere negativo grazie a Julian per averlo segnalato.
Nota : potrebbe sembrare uguale alla risposta precedente (Maz) ma in Swift "! Player.error" mi dava un errore del compilatore, quindi devi verificare la presenza di errori utilizzando "player.error == nil" in Swift. (Perché la proprietà error non è di 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
   }
}

2
AVPlayer! = AVAudioPlayer
quemeful

2
Avvertenze: tutte menzionate sopra nella risposta che è emersa due anni prima di questa.
Dan Rosenstark

1
@Yar So anche che ha votato positivamente sopra la risposta ma questo è per swift e in Swift "! Player.error" non funzionava per me è consentito solo per i tipi bool quindi ho aggiunto la risposta oops ho votato per errore il tuo commento per rispondere usando questa app di overflow dello stack :)
Risponde il

La mia preoccupazione è che non so quanto bene funzioni il player.error che non è nullo.
Dan Rosenstark

1
Non funzionerà durante la riproduzione di riprese video al contrario ( player.rate = -1.0), perché -1.0 è inferiore a 0.
Julian F. Weinert

9

Estensione rapida basata sulla risposta di maz

extension AVPlayer {

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

6

La versione Swift della risposta di maxkonovalov è questa:

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")
            }
        }
    }
}

Grazie maxkonovalov!


Ciao signor Stanev, grazie per la tua risposta. Questo non funziona per me (aka non stampa nulla nella console?)
Cesare

Non importa, funziona: il mio lettore lo era nil! Ma ho bisogno di sapere quando inizia la visualizzazione (non finisce). Come posso farlo?
Cesare

3

Attualmente con swift 5 il modo più semplice per verificare se il giocatore sta giocando o è in pausa è controllare la variabile .timeControlStatus .

player.timeControlStatus == .paused
player.timeControlStatus == .playing

1

La risposta è l'obiettivo 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
}

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.