objective c - Looping un video con AVFoundation AVPlayer?
objective-c ios (14)
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 declaración 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:
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.
¿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 devolución de llamada 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.
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 objetivo:
__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.
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]; } }];
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()
}
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í: 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];
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)
}
}
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];
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.
esto funcionó para mí sin problemas, el punto es detener al jugador antes de llamar al método seekToTime:
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()
registrando notificación
NSNotificationCenter.defaultCenter().addObserver(self, selector: "videoLoop", name: AVPlayerItemDidPlayToEndTimeNotification, object: self.backgroundPlayer!.currentItem)
función videoLoop
func videoLoop() { self.backgroundPlayer?.pause() self.backgroundPlayer?.currentItem?.seekToTime(kCMTimeZero) self.backgroundPlayer?.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 <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/06c3b5c911fdd3ca7c7800dccf7202ad
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];
}
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.