ios 使用 AVFoundation AVPlayer 循环播放视频?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/5361145/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-30 19:17:17  来源:igfitidea点击:

Looping a video with AVFoundation AVPlayer?

iosobjective-cswiftavfoundationavplayer

提问by orj

Is there a relatively easy way of looping a video in AVFoundation?

在 AVFoundation 中循环播放视频是否有相对简单的方法?

I've created my AVPlayer and AVPlayerLayer like so:

我已经像这样创建了我的 AVPlayer 和 AVPlayerLayer:

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

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

and then I play my video with:

然后我播放我的视频:

[avPlayer play];

The video plays fine but stops at the end. With the MPMoviePlayerController all you have to do is set its repeatModeproperty to the right value. There doesn't appear to be a similar property on AVPlayer. There also doesn't seem to be a callback that will tell me when the movie has finished so I can seek to the beginning and play it again.

视频播放正常,但在最后停止。使用 MPMoviePlayerController,您所要做的就是将其repeatMode属性设置为正确的值。AVPlayer 上似乎没有类似的属性。似乎也没有回调会告诉我电影何时结束,这样我就可以找到开头并再次播放。

I'm not using MPMoviePlayerController because it has some serious limitations. I want to be able to play back multiple video streams at once.

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

回答by Bastian

You can get a Notification when the player ends. Check AVPlayerItemDidPlayToEndTimeNotification

当玩家结束时,您可以获得通知。查看AVPlayerItemDidPlayToEndTimeNotification

When setting up the player:

设置播放器时:

ObjC

对象

  avPlayer.actionAtItemEnd = AVPlayerActionAtItemEndNone; 

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

this will prevent the player to pause at the end.

这将防止玩家在最后暂停。

in the notification:

在通知中:

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

this will rewind the movie.

这将倒带电影。

Don't forget un unregister the notification when releasing the player.

释放播放器时不要忘记取消注册通知。

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

Swift 4+

斯威夫特 4+

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

回答by Nabha

If it helps, in iOS / tvOS 10, there's a new AVPlayerLooper() that you can use to create seamless looping of video (Swift):

如果有帮助,在 iOS / tvOS 10 中,有一个新的 AVPlayerLooper() 可用于创建视频的无缝循环 (Swift):

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

This was presented at WWDC 2016 in "Advances in AVFoundation Playback": https://developer.apple.com/videos/play/wwdc2016/503/

这是在 WWDC 2016 上的“AVFoundation Playback 的进展”中介绍的:https: //developer.apple.com/videos/play/wwdc2016/503/

Even using this code, I had a hiccup until I filed a bug report with Apple and got this response:

即使使用此代码,我也遇到了问题,直到我向 Apple 提交了错误报告并得到了以下回复:

The movie file having movie duration longer than audio/video tracks is the problem. FigPlayer_File is disabling gapless transition because audio track edit is shorter than the movie duration (15.682 vs 15.787).

You need to either fix the movie files to have the movie duration and track durations to be same length or you can use the time range parameter of AVPlayerLooper (set time range from 0 to duration of audio track)

电影持续时间长于音频/视频轨道的电影文件是问题所在。FigPlayer_File 正在禁用无间隙过渡,因为音轨编辑比电影持续时间短(15.682 对 15.787)。

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

It turns out that Premiere had been exporting files with an audio track of a slightly different length than the video. In my case it was fine to remove the audio entirely, and that fixed the problem.

事实证明,Premiere 导出的文件的音轨长度与视频略有不同。就我而言,完全删除音频没问题,这解决了问题。

回答by King-Wizard

In Swift:

斯威夫特

You can get a Notification when the player ends... check AVPlayerItemDidPlayToEndTimeNotification

您可以在播放器结束时收到通知...检查 AVPlayerItemDidPlayToEndTimeNotification

when setting up the player:

设置播放器时:

avPlayer.actionAtItemEnd = AVPlayerActionAtItemEnd.None

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

this will prevent the player to pause at the end.

这将防止玩家在最后暂停。

in the notification:

在通知中:

func playerItemDidReachEnd(notification: NSNotification) {
    if let playerItem: AVPlayerItem = notification.object as? AVPlayerItem {
        playerItem.seekToTime(kCMTimeZero)
    }
}

Swift3

斯威夫特3

NotificationCenter.default.addObserver(self,
    selector: #selector(PlaylistViewController.playerItemDidReachEnd),
     name: NSNotification.Name.AVPlayerItemDidPlayToEndTime,
     object: avPlayer?.currentItem)

this will rewind the movie.

这将倒带电影。

Do not forget to unregister the notification when releasing the player.

释放播放器时不要忘记取消注册通知。

回答by Islam Q.

Here's what I ended up doing to prevent the pause-hiccup issue:

这是我最终为防止暂停打嗝问题所做的事情:

Swift:

迅速:

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


Objective C:

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


NOTE:I didn't use avPlayer.actionAtItemEnd = AVPlayerActionAtItemEndNoneas it's not needed.

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

回答by user2581875

To avoid the gap when the video is rewound, using multiple copies of the same asset in a composition worked well for me. I found it here: www.developers-life.com/avplayer-looping-video-without-hiccupdelays.html(link now dead).

为了避免视频倒带时的间隙,在合成中使用相同资产的多个副本对我来说效果很好。我在这里找到它: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];

回答by kevinnguy

I recommend using AVQueuePlayer to loop your videos seamlessly. Add the notification observer

我建议使用 AVQueuePlayer 无缝循环播放您的视频。添加通知观察者

AVPlayerItemDidPlayToEndTimeNotification

and in its selector, loop your video

并在其选择器中循环播放您的视频

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

回答by Vojta

this worked for me without hiccup issues, point is in pausingthe player before calling seekToTime method:

这对我有用,没有打嗝问题,重点是在调用 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. 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()
    }
    
  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. 登记通知

    NSNotificationCenter.defaultCenter().addObserver(self, selector: "videoLoop", name: AVPlayerItemDidPlayToEndTimeNotification, object: self.backgroundPlayer!.currentItem)
    
  3. 视频循环功能

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

回答by vp2698

For Swift 3 & 4

对于 Swift 3 & 4

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

回答by neon M3

my solution in objective-c wth AVQueuePlayer - it seems you have to duplicate the AVPlayerItem and upon finishing playback of first element instantly add another copy. "Kind of" makes sense and works for me without any hiccup

我在带有 AVQueuePlayer 的 Objective-c 中的解决方案-似乎您必须复制 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

https://gist.github.com/neonm3/06c3b5c911fdd3ca7c7800dccf7202ad

回答by shujucn

you can add a AVPlayerItemDidPlayToEndTimeNotification observer and replay video from start in selector, code like below

您可以在选择器中添加一个 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];
}