web-dev-qa-db-fra.com

En boucle une vidéo avec AVFoundation AVPlayer?

Existe-t-il un moyen relativement simple de boucler une vidéo dans AVFoundation? 

J'ai créé mon AVPlayer et mon AVPlayerLayer comme suit:

avPlayer = [[AVPlayer playerWithURL:videoUrl] retain];
avPlayerLayer = [[AVPlayerLayer playerLayerWithPlayer:avPlayer] retain];

avPlayerLayer.frame = contentView.layer.bounds;
[contentView.layer addSublayer: avPlayerLayer];

et puis je joue ma vidéo avec:

[avPlayer play];

La vidéo est bien lue mais s'arrête à la fin. Avec MPMoviePlayerController, tout ce que vous avez à faire est de définir sa propriété repeatMode sur la bonne valeur. Il ne semble pas y avoir de propriété similaire sur AVPlayer. De plus, il ne semble pas y avoir de rappel qui me dira quand le film sera terminé pour que je puisse chercher au début et le rejouer.

Je n'utilise pas MPMoviePlayerController car il présente de sérieuses limitations. Je veux pouvoir lire plusieurs flux vidéo à la fois.

129
orj

Vous pouvez obtenir une notification lorsque le joueur se termine. Vérifier AVPlayerItemDidPlayToEndTimeNotification

Lors de la configuration du lecteur:

ObjC

  avPlayer.actionAtItemEnd = AVPlayerActionAtItemEndNone; 

  [[NSNotificationCenter defaultCenter] addObserver:self
                                           selector:@selector(playerItemDidReachEnd:)
                                               name:AVPlayerItemDidPlayToEndTimeNotification
                                             object:[avPlayer currentItem]];

cela empêchera le joueur de faire une pause à la fin.

dans la notification:

- (void)playerItemDidReachEnd:(NSNotification *)notification {
    AVPlayerItem *p = [notification object];
    [p seekToTime:kCMTimeZero];
}

cela rembobinera le film.

N'oubliez pas d'annuler la notification lors de la libération du lecteur.

Rapide

avPlayer?.actionAtItemEnd = .none

NotificationCenter.default.addObserver(self,
                                       selector: #selector(playerItemDidReachEnd(notification:)),
                                       name: .AVPlayerItemDidPlayToEndTime,
                                       object: avPlayer?.currentItem)

@objc func playerItemDidReachEnd(notification: Notification) {
    if let playerItem = notification.object as? AVPlayerItem {
        playerItem.seek(to: kCMTimeZero)
    }
}
261
Bastian

Si cela vous aide, dans iOS/tvOS 10, il existe un nouvel AVPlayerLooper () que vous pouvez utiliser pour créer une boucle transparente de la vidéo (Swift):

player = AVQueuePlayer()
playerLayer = AVPlayerLayer(player: player)
playerItem = AVPlayerItem(url: videoURL)
playerLooper = AVPlayerLooper(player: player, templateItem: playerItem)
player.play()    

Cela a été présenté à la WWDC 2016 dans "Avancées dans la lecture AVFoundation": https://developer.Apple.com/videos/play/wwdc2016/503/

Même en utilisant ce code, j'ai eu un hoquet jusqu'à ce que je dépose un rapport de bug avec Apple et reçoive cette réponse:

Le fichier vidéo dont la durée est supérieure à celle des pistes audio/vidéo est le problème. FigPlayer_File désactive la transition sans intervalle car l’édition de piste audio est plus courte que la durée du film (15.682 vs 15.787).

Vous devez soit réparer les fichiers de film pour avoir la durée du film et suivre les durées pour qu'elles soient identiques ou vous pouvez utiliser la plage de temps paramètre de AVPlayerLooper (plage de temps définie de 0 à la durée de la piste audio )

Il s'avère que Premiere exportait des fichiers avec une piste audio d'une longueur légèrement différente de celle de la vidéo. Dans mon cas, il était bien de supprimer complètement l'audio, et cela a résolu le problème.

45
Nabha

Dans Swift :

Vous pouvez obtenir une notification à la fin du lecteur ... vérifiez AVPlayerItemDidPlayToEndTimeNotification

lors de la configuration du lecteur:

avPlayer.actionAtItemEnd = AVPlayerActionAtItemEnd.None

NSNotificationCenter.defaultCenter().addObserver(self, 
                                                 selector: "playerItemDidReachEnd:", 
                                                 name: AVPlayerItemDidPlayToEndTimeNotification, 
                                                 object: avPlayer.currentItem)

cela empêchera le joueur de faire une pause à la fin.

dans la notification:

func playerItemDidReachEnd(notification: NSNotification) {
    if let playerItem: AVPlayerItem = notification.object as? AVPlayerItem {
        playerItem.seekToTime(kCMTimeZero)
    }
}

Swift3

NotificationCenter.default.addObserver(self,
    selector: #selector(PlaylistViewController.playerItemDidReachEnd),
     name: NSNotification.Name.AVPlayerItemDidPlayToEndTime,
     object: avPlayer?.currentItem)

cela rembobinera le film.

N'oubliez pas d'annuler la notification lors de la libération du lecteur.

24
King-Wizard

Voici ce que j'ai fini par faire pour empêcher le problème pause-hiccup:

Rapide:

NotificationCenter.default.addObserver(forName: .AVPlayerItemDidPlayToEndTime,
                                       object: nil,
                                       queue: nil) { [weak self] note in
                                        self?.avPlayer.seek(to: kCMTimeZero)
                                        self?.avPlayer.play()
}

Objectif c:

__weak typeof(self) weakSelf = self; // prevent memory cycle
NSNotificationCenter *noteCenter = [NSNotificationCenter defaultCenter];
[noteCenter addObserverForName:AVPlayerItemDidPlayToEndTimeNotification
                        object:nil
                         queue:nil
                    usingBlock:^(NSNotification *note) {
                        [weakSelf.avPlayer seekToTime:kCMTimeZero];
                        [weakSelf.avPlayer play];
                    }];

NOTE: Je n'ai pas utilisé avPlayer.actionAtItemEnd = AVPlayerActionAtItemEndNone car ce n'est pas nécessaire.

16
Islam Q.

Je recommande d'utiliser AVQueuePlayer pour boucler vos vidéos de manière transparente. Ajouter l'observateur de notification 

AVPlayerItemDidPlayToEndTimeNotification

et dans son sélecteur, bouclez votre vidéo

AVPlayerItem *video = [[AVPlayerItem alloc] initWithURL:videoURL];
[self.player insertItem:video afterItem:nil];
[self.player play];
3
kevnguy

Pour éviter le décalage lors du rembobinage de la vidéo, l’utilisation de plusieurs copies du même élément d’une composition a bien fonctionné pour moi. Je l'ai trouvé ici: www.developers-life.com/avplayer-looping-video-without-hiccupdelays.html (lien maintenant mort).

AVURLAsset *tAsset = [AVURLAsset assetWithURL:tURL];
CMTimeRange tEditRange = CMTimeRangeMake(CMTimeMake(0, 1), CMTimeMake(tAsset.duration.value, tAsset.duration.timescale));
AVMutableComposition *tComposition = [[[AVMutableComposition alloc] init] autorelease];
for (int i = 0; i < 100; i++) { // Insert some copies.
    [tComposition insertTimeRange:tEditRange ofAsset:tAsset atTime:tComposition.duration error:nil];
}
AVPlayerItem *tAVPlayerItem = [[AVPlayerItem alloc] initWithAsset:tComposition];
AVPlayer *tAVPlayer = [[AVPlayer alloc] initWithPlayerItem:tAVPlayerItem];
3
user2581875

Pour Swift 3 & 4

NotificationCenter.default.addObserver(forName: .AVPlayerItemDidPlayToEndTime, object: self.avPlayer?.currentItem, queue: .main) { _ in
     self.avPlayer?.seek(to: kCMTimeZero)
     self.avPlayer?.play()
}
2
vp2698

cela a fonctionné pour moi sans problème, le point est en pause le joueur avant d'appeler la méthode seekToTime:

  1. init AVPlayer

    let url = NSBundle.mainBundle().URLForResource("loop", withExtension: "mp4")
    let playerItem = AVPlayerItem(URL: url!)
    
    self.backgroundPlayer = AVPlayer(playerItem: playerItem)
    let playerLayer = AVPlayerLayer(player: self.backgroundPlayer)
    
    playerLayer.frame = CGRectMake(0, 0, UIScreen.mainScreen().bounds.width, UIScreen.mainScreen().bounds.height)
    self.layer.addSublayer(playerLayer)
    self.backgroundPlayer!.actionAtItemEnd = .None
    self.backgroundPlayer!.play()
    
  2. notification d'enregistrement

    NSNotificationCenter.defaultCenter().addObserver(self, selector: "videoLoop", name: AVPlayerItemDidPlayToEndTimeNotification, object: self.backgroundPlayer!.currentItem)
    
  3. fonction videoLoop

    func videoLoop() {
      self.backgroundPlayer?.pause()
      self.backgroundPlayer?.currentItem?.seekToTime(kCMTimeZero)
      self.backgroundPlayer?.play()
    }
    
1
Vojta

ma solution dans objectif-avec AVQueuePlayer - il semble que vous deviez dupliquer l’AVPlayerItem et à la fin de la lecture du premier élément, ajoutez instantanément une autre copie. "Kind of" a du sens et fonctionne pour moi sans aucun hoquet

NSURL *videoLoopUrl; 
// as [[NSBundle mainBundle] URLForResource:@"assets/yourVideo" withExtension:@"mp4"]];
AVQueuePlayer *_loopVideoPlayer;

+(void) nextVideoInstance:(NSNotification*)notif
{
 AVPlayerItem *currItem = [AVPlayerItem playerItemWithURL: videoLoopUrl];

[[NSNotificationCenter defaultCenter] addObserver:self
                                      selector:@selector(nextVideoInstance:)
                                      name:AVPlayerItemDidPlayToEndTimeNotification
                                      object: currItem];

 [_loopVideoPlayer insertItem:currItem afterItem:nil];
 [_loopVideoPlayer advanceToNextItem];

}

+(void) initVideoPlayer {
 videoCopy1 = [AVPlayerItem playerItemWithURL: videoLoopUrl];
 videoCopy2 = [AVPlayerItem playerItemWithURL: videoLoopUrl];
 NSArray <AVPlayerItem *> *dummyArray = [NSArray arrayWithObjects: videoCopy1, videoCopy2, nil];
 _loopVideoPlayer = [AVQueuePlayer queuePlayerWithItems: dummyArray];

 [[NSNotificationCenter defaultCenter] addObserver: self
                                      selector: @selector(nextVideoInstance:)
                                      name: AVPlayerItemDidPlayToEndTimeNotification
                                      object: videoCopy1];

 [[NSNotificationCenter defaultCenter] addObserver: self
                                      selector: @selector(nextVideoInstance:)
                                      name: AVPlayerItemDidPlayToEndTimeNotification
                                      object: videoCopy2];
}

https://Gist.github.com/neonm3/06c3b5c911fdd3c7c7800dccf7202ad

0
neon M3

Ce que j'ai fait est de le faire jouer en boucle, comme dans mon code ci-dessous:

[player addPeriodicTimeObserverForInterval:CMTimeMake(1.0, 1.0)
queue:dispatch_get_main_queue() usingBlock:^(CMTime time) {
    float current = CMTimeGetSeconds(time);
    float total = CMTimeGetSeconds([playerItem duration]);
    if (current >= total) {
        [[self.player currentItem] seekToTime:kCMTimeZero];
        [self.player play];
    }
}];
0
Victor John

Ce qui suit fonctionne pour moi dans WKWebView dans Swift 4.1 La partie principale de WKWebView dans WKwebviewConfiguration

wkwebView.navigationDelegate = self
wkwebView.allowsBackForwardNavigationGestures = true
self.wkwebView =  WKWebView(frame: CGRect(x: 0, y: 0, width: self.view.frame.size.width, height: self.view.frame.size.height))
let config = WKWebViewConfiguration()
config.allowsInlineMediaPlayback = true
wkwebView = WKWebView(frame: wkwebView.frame, configuration: config)
self.view.addSubview(wkwebView)
self.wkwebView.load(NSURLRequest(url: URL(string: self.getUrl())!) as URLRequest)
0
Nrv

Swift 4.2 dans Xcode 10.1.

Oui , il existe un moyen relativement facile de boucler une vidéo avec AVKit/AVFoundation en utilisant AVQueuePlayer(), la technique KVO (Key-Value Observation) et un jeton correspondant.

Cela fonctionne certainement pour un tas de vidéos H.264/HEVC avec une charge minimale pour le processeur.

Voici un code:

import UIKit
import AVFoundation
import AVKit

class ViewController: UIViewController {

    private let player = AVQueuePlayer()
    let clips = ["01", "02", "03", "04", "05", "06", "07"]
    private var token: NSKeyValueObservation?
    var avPlayerView = AVPlayerViewController()

    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(true)

        self.addAllVideosToPlayer()
        present(avPlayerView, animated: true, completion: { self.player.play() })
    }

    func addAllVideosToPlayer() {
        avPlayerView.player = player

        for clip in clips {
            let urlPath = Bundle.main.path(forResource: clip, ofType: "m4v")!
            let url = URL(fileURLWithPath: urlPath)
            let playerItem = AVPlayerItem(url: url)
            player.insert(playerItem, after: player.items().last)

            token = player.observe(\.currentItem) { [weak self] player, _ in
                if self!.player.items().count == 1 { self?.addAllVideosToPlayer() }
            }
        }
    }
}
0
ARGeo

vous pouvez ajouter un observateur AVPlayerItemDidPlayToEndTimeNotification et relire la vidéo depuis le début dans le sélecteur, code comme ci-dessous

 //add observer
[[NSNotificationCenter defaultCenter] addObserver:self                                                 selector:@selector(playbackFinished:)                                                     name:AVPlayerItemDidPlayToEndTimeNotification
object:_aniPlayer.currentItem];

-(void)playbackFinished:(NSNotification *)notification{
    [_aniPlayer seekToTime:CMTimeMake(0, 1)];//replay from start
    [_aniPlayer play];
}
0
shujucn