如何在Swift中循环使用AVPlayer?

96

我似乎找不到答案的一个简单问题。

如何在Swift中循环AVPlayer?

numberOfLoops = -1 仅适用于AVAudioPlayer

我需要它循环,没有任何延迟/黑屏等。这就是为什么我不使用MPMoviePlayerViewController的原因。

感谢任何帮助。

代码:

    let url_1 = NSURL.fileURLWithPath(outputFilePath_1)

    let asset_1 = AVAsset.assetWithURL(url_1) as? AVAsset
    let playerItem_1 = AVPlayerItem(asset: asset_1)

    let player_1 = AVPlayer(playerItem: self.playerItem_1)

    let playerLayer_1 = AVPlayerLayer(player: self.player_1)

    playerLayer_1!.frame = self.view.frame

    self.view.layer.addSublayer(self.playerLayer_1)

    player_1!.play()

3
AVPlayer使用通知而不是属性。基本上,您需要监听视频结束事件,然后将其定位到视频的开头。这里有一个示例(用Objective-C编写,但您可以理解):https://dev59.com/bW435IYBdhLWcg3wigkk - Msencenb
8个回答

214

Swift 5(iOS 10.0+)

var playerLooper: AVPlayerLooper! // should be defined in class
var queuePlayer: AVQueuePlayer!
...

let asset: AVAsset = ... // AVAsset with its 'duration' property value loaded
let playerItem = AVPlayerItem(asset: asset)
self.queuePlayer = AVQueuePlayer(playerItem: playerItem)

// Create a new player looper with the queue player and template item
self.playerLooper = AVPlayerLooper(player: queuePlayer, templateItem: playerItem)

< iOS 10.0

适用于任何iOS版本。但是,对于< iOS 10.0,这是唯一的解决方案。

Swift 4

var player: AVPlayer!

...

NotificationCenter.default.addObserver(forName: .AVPlayerItemDidPlayToEndTime, object: self.player.currentItem, queue: .main) { [weak self] _ in
    self?.player?.seek(to: CMTime.zero)
    self?.player?.play()
}

Swift 3

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

Swift 2

在配置了AVPlayerItem并创建了播放器之后:

var player: AVPlayer!

...

// Invoke after player is created and AVPlayerItem is specified
NSNotificationCenter.defaultCenter().addObserver(self,
    selector: "playerItemDidReachEnd:",
    name: AVPlayerItemDidPlayToEndTimeNotification,
    object: self.player.currentItem)

...
 
func playerItemDidReachEnd(notification: NSNotification) {
    self.player.seekToTime(kCMTimeZero)
    self.player.play()
}

别忘了 import AVFoundation


4
你可以添加 queue: .main,这样你就不需要将任务分派到主队列上了。 - David Beck
这是真正的答案,其他答案对我无效(Swift 3)。 - changbenny
我指的是代码中的这部分 NSNotificationCenter.defaultCenter().addObserver(self, selector: "playerItemDidReachEnd:", name: AVPlayerItemDidPlayToEndTimeNotification, object: self.player.currentItem) 你将 self.player.currentItem 作为参数传递给了 object - Zonily Jame
1
在Swift 3的解决方案中,使用self.在dispatch块内部会导致对象被保留。 - glm4
1
Swift 4: NotificationCenter.default.addObserver(forName: .AVPlayerItemDidPlayToEndTime, object: self.player?.currentItem, queue: .main) { _ in self.player?.seek(to: CMTime.zero) self.player?.play() } - Jordan
显示剩余8条评论

62

iOS 10及其以上版本,不再需要使用通知循环播放视频,只需要使用AVPlayerLooper即可实现。示例代码如下:

let asset = AVAsset(url: videoURL)
let item = AVPlayerItem(asset: asset)
let player = AVQueuePlayer(playerItem: item)
videoLooper = AVPlayerLooper(player: player, templateItem: item)

确保此循环器在函数作用域之外创建,以防止函数返回时停止。


4
谢天谢地。"KVO"只是一种笨拙的做事方式。 - Anjan Biswas
1
这种方法确实有效,但我发现任何附加到playerItem的AVPlayerItemMetadataOutput都不会被触发(至少在iOS 11.4.1上),这迫使我返回到AVPlayerItemDidPlayToEndTimeNotification方法。 - Jaysen Marais
4
BrightFuture没有使用"let videoLooper"的原因是,如他们所述,looper需要在函数作用域之外保留。所以类设置中会有一个"var videoLooper",而在此函数内部你只是对它进行了赋值。如果您在设置函数内部使用"let"并且没有将其赋值给函数外的属性,则在播放视频时不会有可供循环的变量。 - Ben Stahl
你好,我能找到VideoLooper的play方法吗? - famfamfam
还可以写成 AVPlayerItem(url: videoURL) 而不是 AVPlayerItem(asset: ...) - Roman Aliyev
显示剩余2条评论

41

@Christopher Pickslay的答案已更新,适用于Swift 4

func loopVideo(videoPlayer: AVPlayer) {
    NotificationCenter.default.addObserver(forName: NSNotification.Name.AVPlayerItemDidPlayToEndTime, object: nil, queue: nil) { notification in
        videoPlayer.seek(to: CMTime.zero)
        videoPlayer.play()
    }
}

但是,正如我在他的回答下面提到的那样,请确保将对象指定为AVPlayer的播放器项目,如果您有多个播放器。


31

或者,你可以使用基于块的NSNotificationCenter API:

func loopVideo(videoPlayer: AVPlayer) {
    NSNotificationCenter.defaultCenter().addObserverForName(AVPlayerItemDidPlayToEndTimeNotification, object: nil, queue: nil) { notification in
        videoPlayer.seekToTime(kCMTimeZero)
        videoPlayer.play()
    }
}

非常好的答案,非常简单。如果您有多个播放器,请确保将AVPlayer指定为“object”参数,否则每个已注册的播放器都会在任何一个播放器完成时收到通知。 - Ben Stahl
我正在使用AVPlayer播放视频,此时该视频已在后台运行。但当我想要播放另一个视频时,该视频却无法播放。 - Mahesh Narla
2
更正@BenStahl的答案,你应该将“AVPlayerItem”指定为“object”,而不是“AVPlayer”。因此,它应该是videoPlayer.currentItem - Chan Jing Hong

25

好的,我已经解决了。感谢Msencenb用Objective C的答案指引我找到正确的方向。

player_1?.actionAtItemEnd = .None

//set a listener for when the video ends   
NSNotificationCenter.defaultCenter().addObserver(self,
        selector: "restartVideoFromBeginning",
        name: AVPlayerItemDidPlayToEndTimeNotification,
        object: player_1?.currentItem)


//function to restart the video
func restartVideoFromBeginning()  {

    //create a CMTime for zero seconds so we can go back to the beginning
    let seconds : Int64 = 0
    let preferredTimeScale : Int32 = 1
    let seekTime : CMTime = CMTimeMake(seconds, preferredTimeScale)

    player_1!.seekToTime(seekTime)

    player_1!.play()

}

12

Swift 5.5:

func loopVideo(videoPlayer: AVPlayer) {
  NotificationCenter.default.addObserver(forName: NSNotification.Name.AVPlayerItemDidPlayToEndTime, object: nil, queue: nil) { notification in
    videoPlayer.seek(to: .zero)
    videoPlayer.play()
  }
}

这很棒,只需要在代码中的某个地方进行videoPlayer.play()的初始调用即可,如果这不是显而易见的话。上面的代码将在初始播放后处理循环。我建议将调用videoPlayer.play()放在此函数中,或者只需与您的代码一起使用内部,并在代码中进行videoPlayer.play()的初始调用。 - Andy Weinstein
非常好的答案,不需要更多的细节,谢谢。 - Fattie

10
我已经成功地在Swift 3中为OSX创建了无缝视频循环。它应该也能在iOS上运行,并且只需要进行少量修改就可以在Swift 2上使用。
var player : AVQueuePlayer!

// Looping video initial call
internal func selectVideoWithLoop(url : URL)
{
    let asset = AVAsset(url: url)
    player.pause()
    let playerItem1 = AVPlayerItem(asset: asset)
    let playerItem2 = AVPlayerItem(asset: asset)
    player.removeAllItems()
    player.replaceCurrentItem(with: playerItem1)
    player.insert(playerItem2, after: playerItem1)
    player.actionAtItemEnd = AVPlayerActionAtItemEnd.advance
    player.play()

    let selector = #selector(ViewController.playerItemDidReachEnd(notification:))
    let name = NSNotification.Name.AVPlayerItemDidPlayToEndTime
    // removing old observer and adding it again for sequential calls. 
    // Might not be necessary, but I like to unregister old notifications.
    NotificationCenter.default.removeObserver(self, name: name, object: nil)
    NotificationCenter.default.addObserver(self, selector: selector, name: name, object: nil)
}

// Loop video with threadmill pattern
// Called by NotificationCenter, don't call directly
func playerItemDidReachEnd(notification: Notification)
{
    let item = player.currentItem!
    player.remove(item)
    item.seek(to: kCMTimeZero)
    player.insert(item, after: nil)
}

当您想更改视频时,只需再次使用不同的url调用selectVideoWithLoop。

5

Swift 3.0:

首先检查视频终点:

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



func videoDidReachEnd() {

        //now use seek to make current playback time to the specified time in this case (O)
        let duration : Int64 = 0 (can be Int32 also)
        let preferredTimeScale : Int32 = 1
        let seekTime : CMTime = CMTimeMake(duration, preferredTimeScale)
        player!.seek(to: seekTime)
        player!.play()
    }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接