Ein Video mit AVFoundation AVPlayer schleifen?

142

Gibt es eine relativ einfache Möglichkeit, ein Video in AVFoundation zu schleifen?

Ich habe meinen AVPlayer und AVPlayerLayer folgendermaßen erstellt:

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

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

und dann spiele ich mein Video mit:

[avPlayer play];

Das Video wird gut abgespielt, stoppt aber am Ende. Mit dem MPMoviePlayerController müssen Sie lediglich seine repeatModeEigenschaft auf den richtigen Wert setzen. Auf AVPlayer scheint es keine ähnliche Eigenschaft zu geben. Es scheint auch keinen Rückruf zu geben, der mir sagt, wann der Film fertig ist, damit ich zum Anfang suchen und ihn erneut abspielen kann.

Ich verwende MPMoviePlayerController nicht, da es einige schwerwiegende Einschränkungen gibt. Ich möchte mehrere Videostreams gleichzeitig wiedergeben können.

orj
quelle
1
Siehe diese Antwort für einen Link zum tatsächlichen Arbeitscode
MoDJ

Antworten:

275

Sie können eine Benachrichtigung erhalten, wenn der Spieler endet. PrüfenAVPlayerItemDidPlayToEndTimeNotification

Beim Einrichten des Players:

ObjC

  avPlayer.actionAtItemEnd = AVPlayerActionAtItemEndNone; 

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

Dadurch wird verhindert, dass der Spieler am Ende pausiert.

in der Benachrichtigung:

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

Dadurch wird der Film zurückgespult.

Vergessen Sie nicht, die Registrierung aufzuheben, wenn Sie den Player freigeben.

Schnell

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)
    }
}
Bastian
quelle
6
... und wenn Sie es direkt nach [p seekToTime: kCMTimeZero] (eine Art "Rücklauf") abspielen möchten, wiederholen Sie einfach [p play].
Thomax
24
Dies sollte nicht notwendig sein ... wenn Sie dies tun avPlayer.actionAtItemEnd = AVPlayerActionAtItemEndNone;, wird es nicht aufhören, so dass Sie es nicht einstellen müssen, um erneut zu spielen
Bastian
Um den Sound wiederzugeben, müssen Sie [player play];nach dem Zurückspulen anrufen .
Kenny Winker
12
Diese Lösung funktioniert, ist jedoch nicht vollständig nahtlos. Ich habe eine sehr kleine Pause. Mache ich etwas falsch?
Joris van Liempd iDeveloper
3
@Praxiteles Sie müssen die Registrierung aufheben, wenn die Ansicht zerstört wird oder wenn Sie den Videoplayer entfernen oder was auch immer Sie tun.) Sie können [[NSNotificationCenter defaultCenter] removeObserver:self];beispielsweise verwenden, wenn selfdie Benachrichtigungen abgehört werden.
Bastian
63

Wenn es hilft, gibt es in iOS / tvOS 10 einen neuen AVPlayerLooper (), mit dem Sie nahtlose Video-Loops erstellen können (Swift):

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

Dies wurde auf der WWDC 2016 in "Fortschritte bei der AVFoundation-Wiedergabe" vorgestellt: https://developer.apple.com/videos/play/wwdc2016/503/

Selbst wenn ich diesen Code verwendete, hatte ich einen Schluckauf, bis ich einen Fehlerbericht bei Apple einreichte und die folgende Antwort erhielt:

Das Problem ist die Filmdatei mit einer längeren Filmdauer als Audio- / Videospuren. FigPlayer_File deaktiviert den lückenlosen Übergang, da die Bearbeitung von Audiospuren kürzer als die Filmdauer ist (15.682 gegenüber 15.787).

Sie müssen entweder die Filmdateien so korrigieren, dass Filmdauer und Spurdauer gleich lang sind, oder Sie können den Zeitbereichsparameter von AVPlayerLooper verwenden (Zeitbereich von 0 bis Dauer der Audiospur einstellen).

Es stellte sich heraus, dass Premiere Dateien mit einer Audiospur exportiert hatte, die etwas anders lang war als das Video. In meinem Fall war es in Ordnung, das Audio vollständig zu entfernen, und das hat das Problem behoben.

Nabha
quelle
2
Sonst hat bei mir nichts funktioniert. Ich verwende einen AVPlayerLooper und hatte diesen Fehler und das Beheben der Diskrepanz zwischen Video- / Audiolängen löste das Problem.
Kevin Heap
1
Vielen Dank für diese Informationen zu Premiere. Ich habe dem Looper einen Zeitbereich hinzugefügt, der mein Problem mit dem "blinkenden Video" behoben hat.
Alexander Flenniken
@Nabha ist es möglich, dies für einen bestimmten Zeitraum innerhalb des Videos zu verwenden? Zum Beispiel ist das Video 60 Sekunden lang, aber ich möchte nur die ersten 10 Sekunden wiederholen
Lance Samaria
29

In Swift :

Sie können eine Benachrichtigung erhalten, wenn der Player endet. Überprüfen Sie AVPlayerItemDidPlayToEndTimeNotification

beim Einrichten des Players:

avPlayer.actionAtItemEnd = AVPlayerActionAtItemEnd.None

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

Dadurch wird verhindert, dass der Spieler am Ende pausiert.

in der Benachrichtigung:

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)

Dadurch wird der Film zurückgespult.

Vergessen Sie nicht, die Registrierung aufzuheben, wenn Sie den Player freigeben.

König-Zauberer
quelle
4
Bei dieser Methode tritt ein kleiner Schluckauf zwischen den Schleifen auf. Ich habe mein Video in Adobe Premier geöffnet und überprüft, dass das Video keine doppelten Frames enthält. Daher wird der kurze Schluckauf definitiv wiedergegeben. Hat jemand einen Weg gefunden, eine Video-Schleife schnell und nahtlos zu erstellen?
SpaceManGalaxy
@ SpaceManGalaxy Ich habe auch den Schluckauf bemerkt. Haben Sie einen Weg gefunden, diesen Fehler zu beheben?
Lance Samaria
18

Folgendes habe ich getan, um das Problem mit dem Pausenschluckauf zu vermeiden:

Schnell:

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

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

HINWEIS: Ich habe es nicht verwendet, avPlayer.actionAtItemEnd = AVPlayerActionAtItemEndNoneda es nicht benötigt wird.

Islam Q.
quelle
2
@KostiaDombrovsky hast du ein aktuelles Gerät oder andere Videos anprobiert?
Islam Q.
@IsQQ. Ich nehme eine MP4-Datei auf und versuche dann, sie in einer Schleife abzuspielen, wie es Snapchat tut.
Kostia Dombrovsky
@KostiaDombrovsky hast du deine Wiedergabe mit Snapchat nebeneinander verglichen? Ich denke, weil der Anfangs- und der Endrahmen nicht übereinstimmen, scheint es, als ob er angehalten wurde, aber er wird nie angehalten.
Islam Q.
Hat auch bei mir nicht funktioniert. Ich habe ein 6-Sekunden-Video mit unaufhörlichem Audio und
höre
Bei diesem Ansatz tritt ein Speicherverlust auf. Es hat mit den [weakSelf.avPlayer seekToTime:kCMTimeZero]; [weakSelf.avPlayer play];Zeilen zu tun - wenn ich diese Zeilen auskommentiere, gibt es keinen Speicherverlust mehr. Ich habe dies in Instrumenten profiliert.
Solsma Dev
3

Ich empfehle die Verwendung von AVQueuePlayer, um Ihre Videos nahtlos zu schleifen. Fügen Sie den Benachrichtigungsbeobachter hinzu

AVPlayerItemDidPlayToEndTimeNotification

und schleifen Sie in der Auswahl Ihr Video

AVPlayerItem *video = [[AVPlayerItem alloc] initWithURL:videoURL];
[self.player insertItem:video afterItem:nil];
[self.player play];
kevinnguy
quelle
Ich habe es versucht und es zeigt keine Verbesserung gegenüber der von @Bastian vorgeschlagenen Methode. Haben Sie es geschafft, den Schluckauf damit vollständig zu beseitigen?
Amadour
2
@amadour Was Sie tun können, ist, zwei der gleichen Videos im AVQueuePlayer-Player hinzuzufügen, wenn sie initialisiert werden, und wenn der Player die AVPlayerItemDidPlayToEndTimeNotification veröffentlicht, fügen Sie dasselbe Video zur Warteschlange des Players hinzu.
Kevinnguy
3

Um die Lücke beim Zurückspulen des Videos zu vermeiden, hat es für mich gut funktioniert, mehrere Kopien desselben Assets in einer Komposition zu verwenden. Ich habe es hier gefunden: www.developers-life.com/avplayer-looping-video-without-hiccupdelays.html (Link jetzt tot).

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];
user2581875
quelle
Ich denke du meinst diesen Link devbrief.blogspot.se/2011/12/…
Flamme3
2

Dies funktionierte bei mir ohne Probleme mit dem Schluckauf. Es geht darum, den Player anzuhalten, bevor die Methode seekToTime aufgerufen wird:

  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. Benachrichtigung registrieren

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

    func videoLoop() {
      self.backgroundPlayer?.pause()
      self.backgroundPlayer?.currentItem?.seekToTime(kCMTimeZero)
      self.backgroundPlayer?.play()
    }
Vojta
quelle
3
Danke - ich habe es versucht, aber es gibt immer noch eine Pause für mich.
Nabha
2

Für Swift 3 & 4

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

Meine Lösung in Ziel-c mit AVQueuePlayer - anscheinend müssen Sie das AVPlayerItem duplizieren und nach Abschluss der Wiedergabe des ersten Elements sofort eine weitere Kopie hinzufügen. "Art of" macht Sinn und funktioniert bei mir ohne Schluckauf

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

Neon M3
quelle
1

Swift 5:

Ich habe einige geringfügige Anpassungen gegenüber früheren Antworten vorgenommen, z. B. das Hinzufügen des playerItem zur Warteschlange, bevor es dem playerLayer hinzugefügt wird.

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

Und machen Sie playerLooper zu einer Eigenschaft Ihres UIViewControllers, da das Video sonst möglicherweise nur einmal abgespielt wird.

NSCoder
quelle
0

Nach dem Laden des Videos in den AVPlayer (natürlich über dessen AVPlayerItem):

 [self addDidPlayToEndTimeNotificationForPlayerItem:item];

Die Methode 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];
    }];
}

In Ihrer viewWillDisappear-Methode:

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

In der Schnittstellendeklaration Ihres View Controllers in der Implementierungsdatei:

id _notificationToken;

Müssen Sie dies betriebsbereit sehen, bevor Sie es versuchen? Laden Sie diese Beispiel-App herunter und führen Sie sie aus:

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

In meiner App, die genau diesen Code verwendet, gibt es zwischen dem Ende des Videos und dem Anfang keinerlei Pause. Abhängig vom Video kann ich nicht feststellen, dass das Video wieder am Anfang steht. Speichern Sie die Timecode-Anzeige.

James Bush
quelle
0

Sie können einen AVPlayerItemDidPlayToEndTimeNotification-Beobachter hinzufügen und das Video von Anfang an in der Auswahl wiedergeben, Code wie unten

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

Das Folgende funktioniert für mich in WKWebView in Swift 4.1. Der Hauptteil des 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)
Nrv
quelle
0

Was ich getan habe, ist, es als Loop-Spiel zu machen, wie mein Code unten:

[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];
    }
}];
Johnny Zhang
quelle
0

Swift 4.2 in Xcode 10.1.

Ja , es gibt eine relativ einfache Möglichkeit, ein Video einzuschleifenAVKit / AVFoundationusing zuAVQueuePlayer() , Schlüsselwert Beobachtung (KVO) Technik und ein Token dafür.

Dies funktioniert definitiv für eine Reihe von H.264 / HEVC-Videos mit einer minimalen Belastung für die CPU.

Hier ist ein 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() }
            }
        }
    }
}
Andy Fedoroff
quelle
-1

Verwenden Sie den folgenden AVPlayerViewController-Code, der für mich funktioniert

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

Alle zu zeigenden Kontrollen, hoffe es ist hilfreich

Iyyappan Ravi
quelle
-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;

Diese Eigenschaft ist bereits im Inneren definiert AVAudioPlayer. Hoffe das kann dir helfen. Ich benutze Xcode 6.3.

Julz
quelle
8
Das ist für Audio, nicht für AVPlayer
Yariv Nissim