Looping un video con AVFoundation AVPlayer?

¿Hay una manera relativamente fácil de reproducir un video en AVFoundation?

Creé mi AVPlayer y AVPlayerLayer así:

avPlayer = [[AVPlayer playerWithURL:videoUrl] retain]; avPlayerLayer = [[AVPlayerLayer playerLayerWithPlayer:avPlayer] retain]; avPlayerLayer.frame = contentView.layer.bounds; [contentView.layer addSublayer: avPlayerLayer]; 

y luego reproduzco mi video con:

 [avPlayer play]; 

El video se reproduce bien, pero se detiene al final. Con MPMoviePlayerController, todo lo que tiene que hacer es establecer su propiedad repeatMode en el valor correcto. No parece haber una propiedad similar en AVPlayer. Tampoco parece haber una callback que me diga cuándo ha terminado la película, así puedo buscar el principio y volver a reproducirlo.

No estoy usando MPMoviePlayerController porque tiene algunas limitaciones serias. Quiero poder reproducir varias transmisiones de video a la vez.

Puede recibir una Notificación cuando el jugador finaliza. Compruebe AVPlayerItemDidPlayToEndTimeNotification

Cuando configura el reproductor:

ObjC

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

esto evitará que el jugador pause al final.

en la notificación:

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

esto rebobinará la película.

No olvide anular el registro de la notificación cuando libere el reproductor.

Rápido

 NotificationCenter.default.addObserver(self, selector: #selector(playerItemDidReachEnd(notification:)), name: Notification.Name.AVPlayerItemDidPlayToEndTime, object: avPlayer?.currentItem) @objc func playerItemDidReachEnd(notification: Notification) { if let playerItem: AVPlayerItem = notification.object as? AVPlayerItem { playerItem.seek(to: kCMTimeZero, completionHandler: nil) } } 

Si ayuda, en iOS / TVOS 10, hay un nuevo AVPlayerLooper () que puede usar para crear un bucle continuo de video (Swift):

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

Esto se presentó en la WWDC 2016 en “Avances en la reproducción de AVFoundation”: https://developer.apple.com/videos/play/wwdc2016/503/

Incluso usando este código, tuve un tropiezo hasta que archivé un informe de error con Apple y obtuve esta respuesta:

El problema es el archivo de película que tiene una duración de película más larga que las pistas de audio / video. FigPlayer_File está desactivando la transición gapless porque la edición de la pista de audio es más corta que la duración de la película (15.682 vs 15.787).

Necesita corregir los archivos de película para que la duración de la película y las duraciones de la pista sean de la misma duración o puede usar el parámetro de intervalo de tiempo de AVPlayerLooper (establezca un rango de tiempo de 0 a la duración de la pista de audio)

Resulta que Premiere había estado exportando archivos con una pista de audio de una longitud ligeramente diferente a la del video. En mi caso, estuvo bien eliminar el audio por completo, y eso solucionó el problema.

En Swift :

Puede recibir una Notificación cuando el jugador finaliza … ver AVPlayerItemDidPlayToEndTimeNotification

cuando configura el reproductor:

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

esto evitará que el jugador pause al final.

en la notificación:

 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) 

esto rebobinará la película.

No olvide anular el registro de la notificación cuando libere el reproductor.

Esto es lo que terminé haciendo para evitar el problema de pausa e interrupción:

Rápido:

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

C objective:

 __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]; }]; 

NOTA: No avPlayer.actionAtItemEnd = AVPlayerActionAtItemEndNone ya que no es necesario.

Recomiendo usar AVQueuePlayer para reproducir sus videos sin interrupciones. Agregar el observador de notificación

 AVPlayerItemDidPlayToEndTimeNotification 

y en su selector, repite tu video

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

Para evitar la brecha cuando se rebobina el video, usar varias copias del mismo activo en una composición funcionó bien para mí. Lo encontré aquí: http://www.developers-life.com/avplayer-looping-video-without-hiccupdelays.html (link now dead).

 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]; 

esto funcionó para mí sin problemas, el punto es detener al jugador antes de llamar al método 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. registrando notificación

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

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

Para Swift 3 y 4

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

Después de cargar el video en el AVPlayer (a través de su AVPlayerItem, por supuesto):

  [self addDidPlayToEndTimeNotificationForPlayerItem:item]; 

El método addDidPlayToEndTimeNotificationForPlayerItem:

 - (void)addDidPlayToEndTimeNotificationForPlayerItem:(AVPlayerItem *)item { if (_notificationToken) _notificationToken = nil; /* Setting actionAtItemEnd to None prevents the movie from getting paused at item end. A very simplistic, and not gapless, looped playback. */ _player.actionAtItemEnd = AVPlayerActionAtItemEndNone; _notificationToken = [[NSNotificationCenter defaultCenter] addObserverForName:AVPlayerItemDidPlayToEndTimeNotification object:item queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) { // Simple item playback rewind. [[_player currentItem] seekToTime:kCMTimeZero]; }]; } 

En su método viewWillDisappear:

 if (_notificationToken) { [[NSNotificationCenter defaultCenter] removeObserver:_notificationToken name:AVPlayerItemDidPlayToEndTimeNotification object:_player.currentItem]; _notificationToken = nil; } 

En su vista, la statement de la interfaz del controlador dentro del archivo de implementación:

 id _notificationToken; 

¿Necesita ver esto funcionando antes de intentarlo? Descargue y ejecute esta aplicación de ejemplo:

https://developer.apple.com/library/prerelease/ios/samplecode/AVBasicVideoOutput/Listings/AVBasicVideoOutput_APLViewController_m.html#//apple_ref/doc/uid/DTS40013109-AVBasicVideoOutput_APLViewController_m-DontLinkElementID_8

En mi aplicación, que usa este mismo código, no hay pausa alguna entre el final del video y el comienzo. De hecho, dependiendo del video, no hay manera de que diga que el video está al principio otra vez, guarde la visualización del código de tiempo.

puede agregar un observador AVPlayerItemDidPlayToEndTimeNotification y reproducir el video desde el inicio en el selector, escriba el siguiente código

  //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]; } 

mi solución en Object-C con AVQueuePlayer: parece que tienes que duplicar el AVPlayerItem y al finalizar la reproducción del primer elemento al instante agregar otra copia. “Tipo de” tiene sentido y funciona para mí sin ningún tipo de contratiempo

 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  *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/06c3b5c911fdd3ca7c7800dccf7202ad

Lo que hice es hacer que [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]; } }]; en bucle, como mi código a continuación: [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]; } }]; [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]; } }];

Lo siguiente funciona para mí en WKWebView en swift 4.1 La parte principal de WKWebView en 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) 

use AVPlayerViewController debajo del código, funciona para mí

  let type : String! = "mp4" let targetURL : String? = NSBundle.mainBundle().pathForResource("Official Apple MacBook Air Video YouTube", ofType: "mp4") let videoURL = NSURL(fileURLWithPath:targetURL!) let player = AVPlayer(URL: videoURL) let playerController = AVPlayerViewController() playerController.player = player self.addChildViewController(playerController) self.playView.addSubview(playerController.view) playerController.view.frame = playView.bounds player.play() 

Todos los controles se mostrarán, espero que sea útil

 /* "numberOfLoops" is the number of times that the sound will return to the beginning upon reaching the end. A value of zero means to play the sound just once. A value of one will result in playing the sound twice, and so on.. Any negative number will loop indefinitely until stopped. */ @property NSInteger numberOfLoops; 

Esta propiedad ya está definida dentro de AVAudioPlayer . Espero que esto le pueda ayudar. Estoy usando Xcode 6.3.