Vérifier l'état de lecture d'AVPlayer


Réponses:


57

Pour recevoir une notification pour atteindre la fin d'un élément (via Apple ):

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

Et pour suivre la lecture, vous pouvez:

« suivre l' évolution de la position de la tête de lecture dans un objet AVPlayer » en utilisant addPeriodicTimeObserverForInterval: file: usingBlock: ou addBoundaryTimeObserverForTimes: file: usingBlock: .

L'exemple vient d'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];
}];

Vous pouvez lancer un drapeau booléen pour vérifier l'état de la lecture.
moonman239

Les notifications peuvent poser des problèmes si vous changez l'élément du joueur, par exemple avec -replaceCurrentItemWithPlayerItem:, et que vous ne le gérez pas correctement. Une manière plus fiable pour moi est de suivre AVPlayerle statut de KVO en utilisant KVO. Voir ma réponse ci-dessous pour plus de détails: stackoverflow.com/a/34321993/3160561
maxkonovalov

2
Vous avez également besoin d'une notification d'erreur? AVPlayerItemFailedToPlayToEndTimeNotification
ToolmakerSteve

332

Vous pouvez dire qu'il joue en utilisant:

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

Extension Swift 3:

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

29
Pas nécessairement, il ne gère pas les situations où le lecteur se bloque en raison d'une erreur dans le fichier. Quelque chose que j'ai découvert à la dure ...
Dermot

7
Comme Dermot l'a dit, si vous essayez de jouer à quelque chose en mode avion, le taux d'AVPlayer est toujours réglé sur 1.0, car cela implique l'intention de jouer.
phi

4
L'AVPlayer a une propriété d'erreur, vérifiez simplement qu'elle n'est pas nulle et vérifiez que le taux n'est pas 0 :)
James Campbell

23
Notez que la réponse a été mise à jour pour éviter le scénario @Dermot. Donc, celui-ci fonctionne réellement.
jomafer

2
Ne fonctionnera pas lors de la lecture de séquences vidéo inversées ( - [AVPlayer setRate:-1.0]), car il -1.0est inférieur à 0.
Julian F. Weinert

49

Dans iOS10, il existe une propriété intégrée pour cela maintenant: timeControlStatus

Par exemple, cette fonction lit ou met en pause le avPlayer en fonction de son état et met à jour le bouton lecture / pause de manière appropriée.

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

Quant à votre deuxième question, pour savoir si l'avPlayer a atteint la fin, la chose la plus simple à faire serait de mettre en place une notification.

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

Lorsqu'il arrive à la fin, par exemple, vous pouvez le faire revenir au début de la vidéo et réinitialiser le bouton Pause sur Lecture.

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

Ces exemples sont utiles si vous créez vos propres contrôles, mais si vous utilisez un AVPlayerViewController, les contrôles sont intégrés.


Facile à utiliser pour iOS 10+
technerd

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

21

raten'est PAS le moyen de vérifier si une vidéo est en cours de lecture (elle pourrait se bloquer). De la documentation de rate:

Indique la vitesse de lecture souhaitée; 0.0 signifie "en pause", 1.0 indique un désir de jouer à la vitesse naturelle de l'élément actuel.

Mots clés «désir de jouer» - un taux de 1.0ne signifie pas que la vidéo est en cours de lecture.

La solution depuis iOS 10.0 est d'utiliser AVPlayerTimeControlStatusce qui peut être observé sur la AVPlayer timeControlStatuspropriété.

La solution antérieure à iOS 10.0 (9.0, 8.0, etc.) consiste à lancer votre propre solution. Un taux de 0.0signifie que la vidéo est mise en pause. Lorsque rate != 0.0cela signifie que la vidéo est en cours de lecture ou est bloquée.

Vous pouvez découvrir la différence en observant le temps du joueur via: func addPeriodicTimeObserver(forInterval interval: CMTime, queue: DispatchQueue?, using block: @escaping (CMTime) -> Void) -> Any

Le bloc renvoie l'heure actuelle du joueur CMTime, donc une comparaison de lastTime(l'heure qui a été reçue pour la dernière fois du bloc) et currentTime(l'heure à laquelle le bloc vient d'être signalé) dira si le joueur joue ou est bloqué. Par exemple, si lastTime == currentTimeet rate != 0.0, alors le joueur a calé.

Comme indiqué par d'autres, déterminer si la lecture est terminée est indiqué par AVPlayerItemDidPlayToEndTimeNotification.


18

Une alternative plus fiable NSNotificationconsiste à vous ajouter en tant qu'observateur à la ratepropriété du joueur .

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

Vérifiez ensuite si la nouvelle valeur du taux observé est zéro, ce qui signifie que la lecture s'est arrêtée pour une raison quelconque, comme atteindre la fin ou bloquer en raison d'un tampon vide.

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

Par rate == 0.0exemple, pour savoir exactement ce qui a provoqué l'arrêt de la lecture, vous pouvez effectuer les vérifications suivantes:

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
}

Et n'oubliez pas de faire arrêter votre lecteur lorsqu'il atteint la fin:

self.player.actionAtItemEnd = AVPlayerActionAtItemEndPause;


Je pense également besoin de notification de l'échec pour atteindre la fin - AVPlayerItemFailedToPlayToEndTimeNotification. Si cette erreur se produit, n'atteindra jamais l'heure de fin. Ou en lisant la réponse de maz, ajoutez à votre code une vérification player.error != nil, auquel cas la lecture s'est "terminée" en raison d'une erreur.
ToolmakerSteve

BTW, qu'est-ce que vous avez trouvé «non fiable» à propos de l'utilisation de NSNotification AVPlayerItemDidPlayToEndTimeNotification?
ToolmakerSteve

ToolmakerSteve, merci pour votre note, a ajouté un contrôle d'erreur à ma réponse. Pour les notifications non fiables - je suis tombé sur le problème moi-même lors de la mise en œuvre de vues de joueur répétitives et réutilisables dans la vue de collection, et KVO a rendu les choses plus claires, car il permettait de tout garder plus local sans que les notifications des différents joueurs n'interfèrent les unes avec les autres.
maxkonovalov

17

Pour Swift :

AVPlayer :

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

Mise à jour : la
player.rate > 0condition a été modifiée player.rate != 0car si la vidéo est lue à l'envers, elle peut être négative grâce à Julian pour l'avoir signalé.
Remarque : Cela pourrait ressembler à la réponse ci-dessus (Maz), mais dans Swift '! Player.error' me donnait une erreur de compilation, vous devez donc vérifier l'erreur en utilisant 'player.error == nil' dans Swift. (Parce que la propriété d'erreur n'est pas de type '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
Mises en garde: toutes mentionnées ci-dessus dans la réponse qui est apparue deux ans avant celle-ci.
Dan Rosenstark

1
@Yar Je sais également avoir voté pour la réponse ci-dessus, mais c'est pour Swift et dans Swift '! Player.error' ne fonctionnait pas pour moi, il n'est autorisé que pour les types de booléens, j'ai donc ajouté la réponse oops a voté pour votre commentaire par erreur pour donner une réponse en utilisant cette application de débordement de pile :)
Aks

Ma préoccupation est que je ne sais pas dans quelle mesure le player.error n'est pas nul fonctionne réellement.
Dan Rosenstark

1
Ne fonctionnera pas lors de la lecture de séquences vidéo inversées ( player.rate = -1.0), car -1,0 est inférieur à 0.
Julian F. Weinert

9

Extension Swift basée sur la réponse de Maz

extension AVPlayer {

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

6

La version Swift de la réponse de maxkonovalov est la suivante:

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

et

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

Merci maxkonovalov!


Bonjour M. Stanev, merci pour votre réponse. Cela ne fonctionne pas pour moi (aka n'imprime rien dans la console?)
Cesare

Peu importe, cela fonctionne - mon lecteur l'était nil! Mais j'ai besoin de savoir quand la vue commence (pas se termine). Un moyen de faire ça?
Cesare

3

Actuellement, avec swift 5, le moyen le plus simple de vérifier si le lecteur est en cours de lecture ou en pause est de vérifier la variable .timeControlStatus .

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

1

La réponse est l'objectif 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
}

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.