【问题标题】:How do you loop AVPlayer in Swift?你如何在 Swift 中循环播放 AVPlayer?
【发布时间】:2026-01-18 13:30:02
【问题描述】:

一个简单的问题,由于某种原因,我似乎找不到答案。

如何在 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()

【问题讨论】:

  • AVPlayer 使用通知而不是属性。本质上,您需要收听视频确实结束事件,然后寻找视频的开头。这是一个例子(目标 c,但你明白了):*.com/questions/5361145/…

标签: ios swift


【解决方案1】:

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 版本。但是,对于

斯威夫特 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()
}

斯威夫特 3

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

斯威夫特 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

【讨论】:

  • 你可以添加queue: .main,然后你就不需要调度到主队列了。
  • 这是真正的答案,其他答案对我不起作用(swift 3)。
  • 5 秒后第一次调用此通知。如何处理?
  • 在 swift 3 解决方案中使用 self.在调度块内部将导致保留对象。
  • 斯威夫特 4:NotificationCenter.default.addObserver(forName: .AVPlayerItemDidPlayToEndTime, object: self.player?.currentItem, queue: .main) { _ in self.player?.seek(to: CMTime.zero) self.player?.play() }
【解决方案2】:

从 iOS 10 开始,无需使用通知来循环播放视频,只需使用 AVPlayerLooper,就像这样:

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

确保这个循环器是在函数范围之外创建的,以防它在函数返回时停止。

【讨论】:

  • 感谢上帝。 KVO 只是一种骇人听闻的做事方式。
  • 此方法确实有效,但是我发现附加到 playerItem 的任何AVPlayerItemMetadataOutput 都不会被触发(至少从 iOS 11.4.1 开始),这迫使我返回到 @987654325 @方法
  • 应该是let videoLooper,否则你会得到Use of unresolved identifier 'videoLooper'
  • @AlexanderFlenniken BrightFuture 没有“let videoLooper”的原因是因为正如他们所指出的,looper 需要保留在函数范围之外。所以在类设置中的某处和函数内部会有一个“var videoLooper”,你只是在分配它。如果您在 setup 函数内部执行了“let”,并且没有将其分配给函数外部的属性,则播放视频时将无法进行循环。
  • 嗨,我可以找到 videoLooper 的播放方法吗?
【解决方案3】:

@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 的玩家项目。

【讨论】:

    【解决方案4】:

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

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

    【讨论】:

    • 很好的答案,非常简单。如果您有多个玩家,请确保将 AVPlayer 指定为“对象”参数,否则每个注册玩家都会在任何玩家完成时收到通知。
    • 我正在 avplayer 中播放一个视频,它在后台运行,当我想播放另一个视频但它没有播放视频时
    • 更正@BenStahl 的答案,您应该将AVPlayerItem 指定为“对象”而不是AVPlayer。所以应该是videoPlayer.currentItem
    【解决方案5】:

    好的,我已经解决了。感谢 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()
    
    }
    

    【讨论】:

      【解决方案6】:

      我已经设法在 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 即可。

      【讨论】:

        【解决方案7】:

        斯威夫特 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()
            }
        

        【讨论】:

          【解决方案8】:

          斯威夫特 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()
            }
          }
          

          【讨论】:

            最近更新 更多