使用AVFoundation AVPlayer循环播放视频?


142

在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,因为它有一些严重的限制。我希望能够一次播放多个视频流。


1
看到这个答案的链接,实际工作代码:stackoverflow.com/questions/7822808/...
MoDJ

Answers:


275

播放器结束时,您会收到通知。检查一下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)
    }
}

迅捷4+

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

6
...如果您想在[p seekToTime:kCMTimeZero]之后播放(某种“倒带”),只需再次执行[p play]。
thomax 2011年

24
这应该没有必要...如果您avPlayer.actionAtItemEnd = AVPlayerActionAtItemEndNone;不会停止,那么就无需将其设置为再次播放
巴斯蒂安(Bastian)

要使声音再次播放,您必须[player play];在倒带后打电话。
肯尼·温克

12
此解决方案有效,但并非完全无缝。我有一个很小的停顿。难道我做错了什么?
Joris van Liempd iDeveloper,2012年

3
@Praxiteles,您需要在视图销毁,删除视频播放器或执行任何操作时注销它。)[[NSNotificationCenter defaultCenter] removeObserver:self];例如,在self侦听通知时可以使用。
巴斯蒂安

63

如果有帮助,在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/

即使使用此代码,在我向Apple提交错误报告并得到以下答复之前,我也一直感到疲倦:

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

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

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


2
没有其他为我工作。我正在使用AVPlayerLooper并遇到此错误,并解决了视频/音频长度之间的差异,从而解决了该问题。
Kevin Heap

1
感谢您提供有关Premiere的信息。我在循环播放器中添加了timeRange,并解决了“视频闪烁”的问题。
亚历山大·弗兰尼克

@Nabha是否可以在视频中的特定时间范围内使用它?例如,视频是60秒,但是我只想循环播放前10秒
Lance Samaria

29

Swift中

播放器结束时,您可以收到通知...检查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)
    }
}

迅捷3

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

这将倒带电影。

释放播放器时,请不要忘记注销通知。


4
我看到这种方法在循环之间出现了小小的故障。我在Adobe Premier中打开了视频,并确认视频中没有重复的帧,因此短暂的打definitely肯定是在播放中。有没有人找到快速无缝制作视频循环的方法?
SpaceManGalaxy

@SpaceManGalaxy我也注意到了打ic。您是否找到解决此故障的方法?
Lance Samaria '18

18

为了防止停顿打issue,我最终要做的是:

迅速:

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

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

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


2
@KostiaDombrovsky您尝试使用实际设备还是其他视频?
Islam Q. 2015年

@伊斯兰堡 我录制了一个MP4文件,然后尝试像snapchat那样循环播放。
Kostia Dombrovsky

@KostiaDombrovsky您是否同时比较了播放和snapchat?我认为,因为开始帧和结束帧不匹配,似乎好像已经暂停了,但从未暂停过。
Islam Q. 2015年

也没有为我工作。我有一个6秒钟的视频,音频不断,我一直用这种方法听到一秒钟的寂静
Cbas

使用此方法时,我看到内存泄漏。这与[weakSelf.avPlayer seekToTime:kCMTimeZero]; [weakSelf.avPlayer play];行有关-当我注释掉这些行时,不再有内存泄漏。我已经在乐器中对此进行了介绍。
Solsma Dev

3

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

AVPlayerItemDidPlayToEndTimeNotification

然后在其选择器中循环播放视频

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

我尝试了这一点,它与@Bastian建议的方法相比未显示任何改进。您是否设法完全消除了这一点?
amadour 2014年

2
@amadour您可以做的是在初始化后在AVQueuePlayer播放器中添加2个相同的视频,并且当播放器发布AVPlayerItemDidPlayToEndTimeNotification时,将相同的视频添加到播放器的队列中。
kevinnguy 2014年

3

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

我想你是说这个链接devbrief.blogspot.se/2011/12/…–
flame3

2

这对我来说没有任何打issues的问题,关键是在调用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. 注册通知

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

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

3
谢谢-我尝试过此操作,但仍然需要暂停。
Nabha

2

对于Swift 3和4

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

1

我在Objective-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


1

斯威夫特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的属性,否则视频只能播放一次。


0

将视频加载到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];
    }];
}

在您的viewWillDisappear方法中:

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

在使用此代码的应用中,在视频结尾和开头之间没有任何暂停。实际上,根据时间的不同,除了时间码显示之外,我无法告诉视频还是重新开始。


0

您可以添加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];
}

0

以下是在Swift 4.1中的WKWebView中为我工作的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)

0

我要做的是使其循环播放,如下面的代码所示:

[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

Xcode 10.1中的Swift 4.2

是的,有一种相对简单的方法可以在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() }
            }
        }
    }
}

-1

在代码下方使用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()

所有控件都将显示,希望对您有所帮助


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

此属性已在内部定义AVAudioPlayer。希望这可以帮到你。我正在使用Xcode 6.3。


8
那是用于音频,而不是AVPlayer
Yariv Nissim
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.