Gibt es eine relativ einfache Möglichkeit, ein Video in AVFoundation zu schleifen?
Ich habe meinen AVPlayer und AVPlayerLayer wie folgt 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 läuft gut, endet aber am Ende. Mit dem MPMoviePlayerController müssen Sie lediglich die repeatMode
-Eigenschaft auf den richtigen Wert setzen. Es scheint keine ähnliche Eigenschaft in AVPlayer zu sein. Es scheint auch kein Rückruf zu geben, der mir sagt, wann der Film zu Ende ist, also kann ich den Anfang suchen und ihn erneut abspielen.
Ich verwende MPMoviePlayerController nicht, da er einige gravierende Einschränkungen hat. Ich möchte mehrere Video-Streams gleichzeitig wiedergeben können.
Sie können eine Benachrichtigung erhalten, wenn der Spieler beendet ist. AVPlayerItemDidPlayToEndTimeNotification
prüfen
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 Player 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 der Benachrichtigung aufzuheben, wenn Sie den Player freigeben.
Swift
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)
}
}
Wenn es hilft, gibt es in iOS/tvOS 10 einen neuen AVPlayerLooper (), mit dem Sie nahtloses Video-Looping (Swift) erstellen können:
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 in der AVFoundation-Wiedergabe" vorgestellt: https://developer.Apple.com/videos/play/wwdc2016/503/
Selbst mit diesem Code hatte ich einen Hickup, bis ich einen Fehlerbericht bei Apple einreichte und diese Antwort erhielt:
Die Filmdatei mit einer längeren Filmdauer als Audio-/Videospuren lautet das Problem. FigPlayer_File deaktiviert den lückenlosen Übergang, weil Audiospurbearbeitung ist kürzer als die Filmdauer (15.682 vs 15.787).
Sie müssen die Filmdateien entweder auf die Filmdauer und .__ einstellen. Track-Dauern gleich lang sein oder Sie können den Zeitbereich verwenden Parameter des AVPlayerLooper (Zeitbereich von 0 bis Dauer von Audiospur einstellen)
Es stellte sich heraus, dass Premiere Dateien mit einer etwas anderen Audiospur als das Video exportiert hatte. In meinem Fall war es in Ordnung, das Audio vollständig zu entfernen, und das Problem wurde behoben.
In Swift :
Sie können eine Benachrichtigung erhalten, wenn der Player beendet ist ... Aktivieren 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 Player 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 der Benachrichtigung aufzuheben, wenn Sie den Player freigeben.
Am Ende habe ich getan, um das Problem der Pause-Schluckaufe zu verhindern:
Swift:
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 avPlayer.actionAtItemEnd = AVPlayerActionAtItemEndNone
nicht verwendet, da es nicht benötigt wird.
Ich empfehle die Verwendung von AVQueuePlayer, um Ihre Videos nahtlos zu wiederholen. Fügen Sie den Benachrichtigungsbeobachter hinzu
AVPlayerItemDidPlayToEndTimeNotification
und schleifen Sie Ihr Video in der Auswahl
AVPlayerItem *video = [[AVPlayerItem alloc] initWithURL:videoURL];
[self.player insertItem:video afterItem:nil];
[self.player play];
Um die Lücke zu vermeiden, wenn das Video zurückgespult wird, hat die Verwendung mehrerer Kopien desselben Objekts in einer Komposition für mich gut funktioniert. Ich habe es hier gefunden: www.developers-life.com/avplayer-looping-video-ohne-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];
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()
}
dies funktionierte für mich ohne Probleme mit dem Schluckauf, Punkt ist in Pause der Spieler vor dem Aufrufen der seekToTime-Methode:
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()
registrierungsbenachrichtigung
NSNotificationCenter.defaultCenter().addObserver(self, selector: "videoLoop", name: AVPlayerItemDidPlayToEndTimeNotification, object: self.backgroundPlayer!.currentItem)
videoLoop-Funktion
func videoLoop() {
self.backgroundPlayer?.pause()
self.backgroundPlayer?.currentItem?.seekToTime(kCMTimeZero)
self.backgroundPlayer?.play()
}
meine Lösung in Objective-C mit AVQueuePlayer - es scheint, dass Sie das AVPlayerItem duplizieren müssen und nach dem Abspielen des ersten Elements sofort eine weitere Kopie hinzufügen. "Art" macht Sinn und arbeitet für mich 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
Was ich gemacht habe, ist das Loop-Play 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];
}
}];
Folgendes funktioniert für mich in WKWebView in Swift 4.1 Der Hauptteil von 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)
Swift 4.2 in Xcode 10.1.
Ja , es gibt eine relativ einfache Möglichkeit, ein Video in AVKit
/AVFoundation
zu schleifen, wobei AVQueuePlayer()
, die Key-Value-Observation (KVO) -Technik und ein Token dafür verwendet werden.
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() }
}
}
}
}
sie können einen Beobachter für AVPlayerItemDidPlayToEndTimeNotification hinzufügen und Video vom Start in der Auswahl aus wiedergeben
//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];
}