用 AVFoundationAVPlayer 循环播放视频?

在 AVFoundation 中有一种相对简单的循环播放视频的方法吗?

我已经创建了我的 AVPlayer 和 AVPlayerLayer,如下所示:

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


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

然后我播放我的视频:

[avPlayer play];

视频播放得很好,但到最后就停止了。使用 MPMoviePlayerController,您所要做的就是将其 repeatMode属性设置为正确的值。AVPlayer 上似乎没有类似的属性。似乎也没有一个回调将告诉我什么时候电影已经完成,所以我可以寻找到开始,并再次播放它。

我不使用 MPMoviePlayerController,因为它有一些严重的限制。我希望能够同时播放多个视频流。

101677 次浏览

当播放器结束时,您可以得到一个通知。请检查 AVPlayerItemDidPlayToEndTimeNotification

设置播放器时:

目的

  avPlayer.actionAtItemEnd = AVPlayerActionAtItemEndNone;


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

这将防止玩家在结束时暂停。

在通知书内:

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

这会让电影倒回去。

不要忘记在释放玩家时取消注册通知。

斯威夫特

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

我建议使用 AVQueuePlayer 来无缝循环您的视频

AVPlayerItemDidPlayToEndTimeNotification

在它的选择器中,循环播放你的视频

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

以下是我为了避免“暂停打嗝”问题而采取的措施:

斯威夫特:

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

目标丙:

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

注意: 我没有使用 avPlayer.actionAtItemEnd = AVPlayerActionAtItemEndNone,因为它是不需要的。

为了避免视频倒带时的空白,在一篇作文中使用同一资产的多个副本对我来说很有效。我在这里发现它: Www.developers-life.com/avplayer-looping-video-without-hiccupdelays.html(链接现在死亡)。

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

此属性已在 AVAudioPlayer中定义。希望这能帮助您。 我用的是 Xcode 6.3。

斯威夫特:

当播放器结束时,您可以获得一个通知... 选中 AVPlayerItemDidPlayToEndTimeNotification

当设置播放器时:

avPlayer.actionAtItemEnd = AVPlayerActionAtItemEnd.None


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

这将防止玩家在结束时暂停。

在通知书内:

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)

这会让电影倒回去。

释放玩家时不要忘记注销通知。

这对我来说没有打嗝的问题,重点是暂停播放器之前调用 SeekToTime 方法:

  1. 初始化 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. registering notification

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

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

将视频加载到 AVPlayer 后(当然是通过 AVPlayerItem) :

 [self addDidPlayToEndTimeNotificationForPlayerItem:item];

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

返回文章页面隐藏方法:

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

在实现文件中视图控制器的接口声明中:

id _notificationToken;

在你尝试之前需要看到这个启动并运行吗? 下载并运行这个示例应用程序:

Https://developer.apple.com/library/prerelease/ios/samplecode/avbasicvideooutput/listings/avbasicvideooutput_aplviewcontroller_m.html#//apple_ref/doc/uid/dts40013109-avbasicvideooutput_aplviewcontroller_m-dontlinkelementid_8

在我的应用程序中,使用的就是这个代码,在视频结束和开始之间没有任何停顿。事实上,取决于视频,我没有办法告诉视频是在开始再次,保存时间码显示。

使用 AVPlayerViewController 下面的代码,它为我工作

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

所有要显示的控件,希望对您有所帮助

在 iOS/tvOS 10中,有一个新的 AVPlayerLooper () ,你可以用它来创建视频的无缝循环(Swift) :

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

这是在 WWDC 2016年的“ AVFoundation 回放进展”中提出的: Https://developer.apple.com/videos/play/wwdc2016/503/

即使使用这段代码,我还是遇到了一些问题,直到我向苹果公司提交了一份错误报告,得到了这样的回复:

具有比音频/视频轨道更长的电影持续时间的电影文件是 FigPlayer _ File 禁用无间隙转换是因为 音轨编辑比电影持续时间短(15.682 vs 15.787).

您需要修复电影文件,以使电影持续时间和 跟踪持续时间是相同的长度或者你可以使用的时间范围 参数 AVPlayerLooper (设置时间范围从0到持续时间 音轨)

事实证明,Premiere 一直在导出与视频长度略有不同的音轨文件。在我的情况下,完全删除音频是可以的,这就解决了问题。

您可以添加 AVPlayerItemDidPlayToEndTimeNotification 观察器并重播视频 从选择器开始,代码如下

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

我的解决方案在目标-c 与 AVQueuePlayer-似乎你必须复制 AVPlayerItem,并在完成播放第一个元素立即添加另一个副本。“差不多”很有道理,对我来说没有任何问题

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

为了 Swift 3 & 4

NotificationCenter.default.addObserver(forName: .AVPlayerItemDidPlayToEndTime, object: self.avPlayer?.currentItem, queue: .main) { _ in
self.avPlayer?.seek(to: kCMTimeZero)
self.avPlayer?.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];
}
}];

以下是我在 WKWebView 中的 Swift 4.1版本 WKwebviewConfiguration 中 WKWebView 的主要部分

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)

迅捷4.2 在 Xcode 10.1。

是的 ,在 AVKit/AVFoundation中有一个相对简单的循环视频的方法,使用 AVQueuePlayer(),键值观察(KVO)技术和一个标记。

这对于一大堆 H.264/HEVC 视频来说肯定是可行的,对 CPU 的负担也是最小的。

这是密码:

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

迅捷5:

我对以前的答案做了一些细微的调整,比如在将 playerItem 添加到 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()

并使 playerLooper 成为 UIViewController 的属性,否则视频可能只能播放一次。

SWIFT 5:

private var player: AVPlayer?


override func viewDidLoad() {
super.viewDidLoad()


NotificationCenter.default.addObserver(self,
selector: #selector(restartVideo),
name: .AVPlayerItemDidPlayToEndTime,
object: self.player?.currentItem)
}


@objc func restartVideo() {
player?.pause()
player?.currentItem?.seek(to: CMTime.zero, completionHandler: { _ in
self.player?.play()
})
}

我在答案中找不到答案。观察指定给资产期限的边界时间是有帮助的。当观察者被触发时,寻找开始和重播。

player?.addBoundaryTimeObserver(forTimes: [NSValue(time: asset.duration)], queue: .main) { [weak self] in
self?.player?.seek(to: .zero, completionHandler: { [weak self] _ in
self?.player?.play()
})
}

Swift 5

import UIKit
import AVKit
import AVFoundation


class VideoViewControler: UIViewController {
    

// init video background and its path
var player: AVPlayer?
let videoURL: NSURL = Bundle.main.url(forResource: "farmer_watering", withExtension: "mp4")! as NSURL
    

override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white


        

// begin implementing the avplayer
player = AVPlayer(url: videoURL as URL)
player?.actionAtItemEnd = .none
player?.isMuted = true
        

let playerLayer = AVPlayerLayer(player: player)
playerLayer.videoGravity = AVLayerVideoGravity.resizeAspect
playerLayer.zPosition = -1
        

playerLayer.frame = view.frame
        

view.layer.addSublayer(playerLayer)
        

player?.play()
        

// add observer to watch for video end in order to loop video
NotificationCenter.default.addObserver(
self,
selector: #selector(loopVideo),
name: .AVPlayerItemDidPlayToEndTime,
object: self.player?.currentItem
)
}
    

// if video ends, will restart
func playerItemDidReachEnd() {
player?.seek(to: CMTime.zero)
}
    

// add this loop at the end, after viewDidLoad
@objc func loopVideo() {
playerItemDidReachEnd()
player?.play()
}
}