AVPlayerLooper fails when there is a AVVideoComposition

I have a 30 second video I want to loop and uses an AVVideoComposition. It works flawlessly in the Catalina Simulator, but fails on my iPad 9.7". Originally I thought the problem was with the CIFilters, but in the end it was the simple existence of the composition block:


func runMovie() {
    playerItem = AVPlayerItem(asset: asset)
    let videoComposition = AVVideoComposition(asset: asset, applyingCIFiltersWithHandler: { request in
        let outputImage = request.sourceImage
        request.finish(with: outputImage, context: nil)
    })
    playerItem.videoComposition = videoComposition

    let duration = CMTime(seconds: 30.0, preferredTimescale: CMTimeScale(1))
    let plyr = AVQueuePlayer()

    let timeRange = CMTimeRange(start: CMTime.zero, duration: duration)
    playerLooper = AVPlayerLooper(player: plyr, templateItem: playerItem, timeRange: timeRange)


    playerLayer = AVPlayerLayer(player: player)
    playerLayer.frame = view.bounds //bounds of the view in which AVPlayer should be displayed
    playerLayer.videoGravity = .resizeAspect

    view.layer.addSublayer(playerLayer)

    plyr.play()
    print("PLAY VIDEO", player.status.rawValue)

    startDate = Date()
    let _ = timer// starts 1 second timer
}


The player runs one or more loops, then the video stops. I have a timer running checking the AVLooper status, and this is the error I see:


//if playerLooper.status == .failed {
//  print("ERROR:", playerLooper.error ?? "No Error")
//  ERROR: Error Domain=AVFoundationErrorDomain Code=-11819 "Cannot Complete Action" UserInfo={NSLocalizedDescription=Cannot Complete Action, NSLocalizedRecoverySuggestion=Try again later.}
Leave the video composition out, it loops forever, add the compostion, it fails in a min or two.


PS: this is a HEVC video I created from a standard mov per the 2019 WWDC 506 Session:


guard let export = AVAssetExportSession(asset: asset, presetName: AVAssetExportPresetHEVCHighestQualityWithAlpha) else { fatalError() }


let scale = CMTimeScale(1)
export.timeRange = CMTimeRange(start: CMTime.zero, duration: CMTime(seconds: 31.0, preferredTimescale: scale))
export.outputFileType = AVFileType.mov
//let url = URL(fileURLWithPath: "/tmp/Original-transparent.mov")
let url = URL(fileURLWithPath: "/tmp/Original-transparent.mov")
try? FileManager.default.removeItem(at: url)
export.outputURL = url
export.videoComposition = videoComposition
export.exportAsynchronously(completionHandler: {
    DispatchQueue.main.async {
        NSLog("...VIDEO DONE")
        self.makingVideoNow = false
    }
})
NSLog("START VIDEO...")

Replies

Now I'm really pulling my hair out. I added this test to be 100% sure the video was properly constructed:


let ret = videoComposition.isValid(for: asset, timeRange: timeRange, validationDelegate: self)
print("VALIDATE", ret)


All possible delegate calls implemented with print statemens - but it returns true and I got no delegate calls. But now, the looper is not failing! Sixteen loops so far and still going strong (will leave it on!). Grrrrr

OK - never fails now if there is no filtering?!?! But if I add a filter (either of the two), then it fails at the next loop:


    let videoComposition = AVVideoComposition(asset: asset, applyingCIFiltersWithHandler: { request in
        var outputImage = request.sourceImage
        defer {
            request.finish(with: outputImage, context: nil)
        }

        do {
            var angle = self.hueAngle
            let radians = degrees2radians(angle)
            angle = (angle + 1) % 360
            self.hueAngle = angle

            self.hueFilter.angle = radians
            self.hueFilter.inputImage = outputImage
            outputImage = self.hueFilter.outputImage!
        }

        do {
            var radius = self.radius
            let inc = self.inc
            radius += inc
            if radius > 5 {
                radius = 5
                self.inc *= -1
            }
            if radius < 0 {
                radius = 0
                self.inc *= -1
            }
            self.radius = radius

            self.blurFilter.radius = radius
            self.blurFilter.inputImage = outputImage
            outputImage = self.blurFilter.outputImage!
        }
    })


Obviously validating the composition makes something better in the composition object.