1

I am building off of the AVPlayerLooper example code Apple provides, specifically utilizing the example AVPlayerLooper setup they've given you in PlayerLooper.swift, LooperViewController.swift, and the Looper.swift protocol.

What I would like to do is be able to update the timeRange property on the AVPlayerLooper that is instantiated inside of the PlayerLooper.swift file.

To this end, I have slightly modified the following function which instantiates and starts the player looper:

func start(in parentLayer: CALayer, loopTimeRange: CMTimeRange) {
        player = AVQueuePlayer()
        playerLayer = AVPlayerLayer(player: player)

        guard let playerLayer = playerLayer else { fatalError("Error creating player layer") }
        playerLayer.frame = parentLayer.bounds
        parentLayer.addSublayer(playerLayer)

        let playerItem = AVPlayerItem(url: videoURL)
        playerItem.asset.loadValuesAsynchronously(forKeys: [ObserverContexts.playerItemDurationKey], completionHandler: {()->Void in
            /*
             The asset invokes its completion handler on an arbitrary queue when
             loading is complete. Because we want to access our AVPlayerLooper
             in our ensuing set-up, we must dispatch our handler to the main queue.
             */
            DispatchQueue.main.async(execute: {
                guard let player = self.player else { return }

                var durationError: NSError? = nil
                let durationStatus = playerItem.asset.statusOfValue(forKey: ObserverContexts.playerItemDurationKey, error: &durationError)
                guard durationStatus == .loaded else { fatalError("Failed to load duration property with error: \(String(describing: durationError))") }

                //self.playerLooper = AVPlayerLooper(player: player, templateItem: playerItem)

                self.playerLooper = AVPlayerLooper(player: player, templateItem: playerItem, timeRange: loopTimeRange)

                self.startObserving()
                player.play()
            })
        })
    }

For demonstration purposes, in LooperViewController, I created a simple button that triggers looper?.start() with a new CMTimeRange like so:

looper?.start(in: view.layer, loopTimeRange: CMTimeRange(start: CMTime(value: 0, timescale: 600), duration: CMTime(value: 3000, timescale: 600)))

Before that function is called, however, I call looper?.stop(), which does the following:

func stop() {
        player?.pause()
        stopObserving()

        playerLooper?.disableLooping()
        playerLooper = nil

        playerLayer?.removeFromSuperlayer()
        playerLayer = nil

        player = nil
    }

I'm basically completely re-instantiating the AVPlayerLooper in order to set the new timeRange property because I don't see any way to actually access, and reset, that property once it's been setup the first time.

The problem is that, while this seems to initially work and the looper player will adjust and start looping the new timerange, it will eventually just stop playing after a few loops. No errors are thrown anywhere, and none of the observers that are already setup in the code are reporting that the loop is stopping or that there was some error with the loop.

Is my approach here entirely wrong? Is AVPlayerLooper meant to be adjusted in this way or should I look for another approach to having an adjustable looping player?

AdjunctProfessorFalcon
  • 1,790
  • 6
  • 26
  • 62

1 Answers1

4

You can actually update the AVPlayerLooper without tearing down the whole thing. What you need to do is to remove all items from the AVQueuePlayer first and then reinstantiate the looper with the new time range. Something like this:

if self.avQueuePlayer.rate == 0 {
   self.avQueuePlayer.removeAllItems()
   let range = CMTimeRange(start: self.startTime, end: self.endTime)
   self.avPlayerLooper = AVPlayerLooper(player: self.avQueuePlayer, templateItem: self.avPlayerItem, timeRange: range)
   self.avQueuePlayer.play()
}

You must be sure to removeAllItems() or it will crash. Otherwise this will change the time range while allowing you to use the current layer, etc., setup to view the player.

C6Silver
  • 3,127
  • 2
  • 21
  • 49