Riproduzione di un video in loop con AVFoundation AVPlayer?


142

Esiste un modo relativamente semplice per riprodurre in loop un video in AVFoundation?

Ho creato AVPlayer e AVPlayerLayer in questo modo:

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

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

e poi riproduco il mio video con:

[avPlayer play];

Il video viene riprodotto correttamente ma alla fine si interrompe. Con MPMoviePlayerController non devi fare altro che impostarlorepeatMode proprietà sul valore corretto. Non sembra esserci una proprietà simile su AVPlayer. Inoltre non sembra esserci un callback che mi dirà quando il film è finito, così posso cercare l'inizio e riprodurlo di nuovo.

Non sto usando MPMoviePlayerController perché ha alcune serie limitazioni. Voglio essere in grado di riprodurre più flussi video contemporaneamente.


1
Vedere questa risposta per un collegamento al codice di lavoro vero e proprio: stackoverflow.com/questions/7822808/...
MoDJ

Risposte:


275

Puoi ricevere una notifica quando il giocatore finisce. Dai un'occhiataAVPlayerItemDidPlayToEndTimeNotification

Durante l'installazione del lettore:

objC

  avPlayer.actionAtItemEnd = AVPlayerActionAtItemEndNone; 

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

questo impedirà al giocatore di fermarsi alla fine.

nella notifica:

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

questo riavvolgerà il film.

Non dimenticare di annullare la registrazione della notifica quando si rilascia il lettore.

veloce

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

Swift 4+

@objc func playerItemDidReachEnd(notification: Notification) {
    if let playerItem = notification.object as? AVPlayerItem {
        playerItem.seek(to: CMTime.zero, completionHandler: nil)
    }
}

6
... e se vuoi riprodurlo subito dopo [p seekToTime: kCMTimeZero] (una sorta di "riavvolgimento"), esegui semplicemente [p play] di nuovo.
Thomax,

24
questo non dovrebbe essere necessario ... se lo fai avPlayer.actionAtItemEnd = AVPlayerActionAtItemEndNone;non si fermerà, quindi non è necessario impostarlo per giocare di nuovo
Bastian

Per riprodurre nuovamente il suono, è necessario chiamare [player play];dopo il riavvolgimento.
Kenny Winker,

12
Questa soluzione funziona, ma non è completamente perfetta. Ho una pausa molto piccola. Sto facendo qualcosa di sbagliato?
Joris van Liempd iDeveloper il

3
@Praxiteles devi annullare la registrazione quando la vista viene distrutta, o quando rimuovi il videoplayer o qualunque cosa tu faccia,) Puoi usare [[NSNotificationCenter defaultCenter] removeObserver:self];ad esempio, quando selfascolti le notifiche.
Bastian,

63

Se aiuta, in iOS / tvOS 10, c'è un nuovo AVPlayerLooper () che puoi usare per creare un loop continuo di video (Swift):

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

Questo è stato presentato al WWDC 2016 in "Advances in AVFoundation Playback": https://developer.apple.com/videos/play/wwdc2016/503/

Anche usando questo codice, ho avuto un singhiozzo fino a quando ho presentato una segnalazione di bug con Apple e ho ottenuto questa risposta:

Il problema è che il film ha una durata del film più lunga delle tracce audio / video. FigPlayer_File disabilita la transizione gapless perché la modifica della traccia audio è più breve della durata del film (15,682 vs 15,787).

È necessario correggere i file del film in modo che la durata del filmato e la durata della traccia abbiano la stessa lunghezza oppure è possibile utilizzare il parametro dell'intervallo di tempo di AVPlayerLooper (impostare l'intervallo di tempo da 0 alla durata della traccia audio)

Si è scoperto che Premiere aveva esportato file con una traccia audio di lunghezza leggermente diversa rispetto al video. Nel mio caso andava bene rimuovere completamente l'audio e questo ha risolto il problema.


2
Nient'altro ha funzionato per me. Sto usando un AVPlayerLooper e ho riscontrato questo errore e risolto il problema con la discrepanza tra le lunghezze video / audio.
Kevin Heap,

1
Grazie per quelle informazioni su Premiere. Ho aggiunto un intervallo di tempo al looper e questo ha risolto il mio problema di "video lampeggiante".
Alexander Flenniken,

@Nabha è possibile usarlo per un certo lasso di tempo all'interno del video? Ad esempio, il video dura 60 secondi, ma voglio fare un giro solo dei primi 10 secondi
Lance Samaria,

29

In Swift :

Puoi ricevere una notifica quando termina il giocatore ... controlla AVPlayerItemDidPlayToEndTimeNotification

durante l'installazione del lettore:

avPlayer.actionAtItemEnd = AVPlayerActionAtItemEnd.None

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

questo impedirà al giocatore di fermarsi alla fine.

nella notifica:

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)

questo riavvolgerà il film.

Non dimenticare di annullare la registrazione della notifica quando si rilascia il lettore.


4
Sto vedendo un piccolo singhiozzo tra i loop con questo metodo. Ho aperto il mio video in Adobe Premier e verificato che non ci siano frame duplicati nel video, quindi il breve singhiozzo è sicuramente in fase di riproduzione. Qualcuno ha trovato un modo per realizzare un loop video senza soluzione di continuità in modo rapido?
SpaceManGalaxy,

@SpaceManGalaxy Ho anche notato il singhiozzo. Hai trovato un modo per risolvere questo problema?
Lance Samaria,

18

Ecco cosa ho finito per evitare il problema di pausa-singhiozzo:

Swift:

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

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

NOTA: non l'ho usato avPlayer.actionAtItemEnd = AVPlayerActionAtItemEndNoneperché non è necessario.


2
@KostiaDombrovsky hai provato su un dispositivo reale o su video diversi?
Islam Q.

@IslamQ. Ho registrato un file MP4 e poi ho provato a riprodurlo in un ciclo come fa Snapchat.
Kostia Dombrovsky,

@KostiaDombrovsky hai confrontato la tua riproduzione con snapchat fianco a fianco? Penso che perché i frame iniziale e finale non coincidono, sembra che sia stato messo in pausa, ma non si ferma mai.
Islam Q.

Neanche ha funzionato per me. Ho un video di 6 secondi con audio incessante e continuo a sentire una frazione di secondo di silenzio con questo metodo
Cbas

Vedo una perdita di memoria quando utilizzo questo approccio. Ha a che fare con le [weakSelf.avPlayer seekToTime:kCMTimeZero]; [weakSelf.avPlayer play];righe: quando commento queste righe non c'è più una perdita di memoria. Ho profilato questo negli strumenti.
Solsma Dev,

3

Ti consiglio di utilizzare AVQueuePlayer per riprodurre i tuoi video senza interruzioni. Aggiungi l'osservatore delle notifiche

AVPlayerItemDidPlayToEndTimeNotification

e nel suo selettore, esegui il looping del tuo video

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

Ho provato questo e non mostra alcun miglioramento rispetto al metodo suggerito da @Bastian. Sei riuscito a rimuovere completamente il singhiozzo con questo?
amadour,

2
@amadour quello che puoi fare è aggiungere 2 degli stessi video nel lettore AVQueuePlayer quando viene inizializzato e quando il giocatore pubblica AVPlayerItemDidPlayToEndTimeNotification, aggiungi lo stesso video alla coda del giocatore.
kevinnguy,

3

Per evitare il divario quando il video viene riavvolto, l'uso di più copie della stessa risorsa in una composizione ha funzionato bene per me. L'ho trovato qui: www.developers-life.com/avplayer-looping-video-without-hiccupdelays.html (link ora morto).

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

Immagino che intendi questo link devbrief.blogspot.se/2011/12/…
flame3

2

questo ha funzionato per me senza problemi di singhiozzo, il punto è mettere in pausa il lettore prima di chiamare il metodo 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. notifica di registrazione

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

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

3
Grazie - l'ho provato, ma c'è ancora una pausa per me.
Nabha,

2

Per Swift 3 e 4

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

1

la mia soluzione in obiettivo con AVQueuePlayer: sembra che devi duplicare AVPlayerItem e al termine della riproduzione del primo elemento aggiungi immediatamente un'altra copia. "Kind of" ha senso e funziona per me senza alcun inconveniente

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


1

Swift 5:

Ho apportato alcune lievi modifiche dalle risposte precedenti come l'aggiunta di playerItem alla coda prima di aggiungerlo a playerLayer.

let playerItem = AVPlayerItem(url: url)
let player = AVQueuePlayer(playerItem: playerItem)
let playerLayer = AVPlayerLayer(player: player)

playerLooper = AVPlayerLooper(player: player, templateItem: playerItem)

playerLayer.frame = cell.eventImage.bounds
playerLayer.videoGravity = AVLayerVideoGravity.resizeAspectFill

// Add the playerLayer to a UIView.layer

player.play()

E rendi playerLooper una proprietà di UIViewController, altrimenti il ​​video potrebbe essere riprodotto una sola volta.


0

Dopo aver caricato il video in AVPlayer (ovviamente tramite AVPlayerItem):

 [self addDidPlayToEndTimeNotificationForPlayerItem:item];

Il metodo 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];
    }];
}

A tuo avviso, il metodo scompare:

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

Nella dichiarazione dell'interfaccia del controller di visualizzazione all'interno del file di implementazione:

id _notificationToken;

Devi vederlo attivo prima di provare? Scarica ed esegui questa app di esempio:

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

Nella mia app, che utilizza proprio questo codice, non c'è alcuna pausa tra la fine del video e l'inizio. In effetti, a seconda del video, non c'è modo per me di dire che il video è di nuovo all'inizio, salvare la visualizzazione del codice temporale.


0

puoi aggiungere un osservatore AVPlayerItemDidPlayToEndTimeNotification e riprodurre il video dall'inizio nel selettore, codice come sotto

 //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

Quanto segue funziona per me in WKWebView in rapido 4.1 La parte principale di WKWebView in 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

Quello che ho fatto è farlo girare in loop, come il mio codice qui sotto:

[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

Swift 4.2 in Xcode 10.1.

, esiste un modo relativamente semplice per inserire AVKit/ AVFoundationutilizzare un video in loopAVQueuePlayer() inserire , la tecnica KVO (Key-Value Observation) e un token.

Questo funziona sicuramente per molti video H.264 / HEVC con un carico minimo per la CPU.

Ecco un codice:

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

-1

usa AVPlayerViewController sotto il codice, funziona per me

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

Tutti i controlli da mostrare, spero che sia utile


-2
/* "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;

Questa proprietà è già definita all'interno AVAudioPlayer. Spero che questo possa aiutarti. Sto usando Xcode 6.3.


8
questo è per l'audio, non per AVPlayer
Yariv Nissim,
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.