1

can someone help me to understand the correct way to switch CIFilters without restart video player?

I have a local video playing inside a view. If I tap a cell in collection view, video will change the CIFilter.

my code

let filter = CIFilter(name: "CIPhotoEffectNoir")!
let asset = AVAsset(url: fooURL)
let item = AVPlayerItem(asset: asset)
item.videoComposition = AVVideoComposition(asset: asset,  applyingCIFiltersWithHandler: { request in
    let source = request.sourceImage.clampedToExtent()
    filter.setValue(source, forKey: kCIInputImageKey)

    let output = filter.outputImage

    request.finish(with: output!, context: nil)
})

player = AVPlayer(playerItem: item)
let playerLayer = AVPlayerLayer(player: player)
playerLayer.frame = videoViewDetail.bounds
videoViewDetail.layer.addSublayer(playerLayer)
player?.play()

code works great and effect is correct apply

but i have a collectionview with many CIFilters. On tap to a cell i cannot find the way to switch the filter associated to video. If i recreate a new player with the new filter and I add it as substitute to the current "addsublayer" the player will restart video.

@IBAction func tap(_ sender: UITapGestureRecognizer) {

    let location = sender.location(in: self.collectionView)
    let indexPath = self.collectionView.indexPathForItem(at: location)

    if let index = indexPath {
        // code to switch to another CIFilter like for example "CISepiaTone"
    }
}

What is the best way to change CIFilters to the playing video without restart the video Is it possible to save the video with the new filter?

Thanks!

Stefano Vet
  • 567
  • 1
  • 7
  • 18

1 Answers1

3

The handler block that you supply when creating the AVVideoComposition is continuously called for every video frame the player is showing. That means you just have to switch the filter that is used inside that block.

The easiest way to achieve that is to reference a filter from outside the block instead of capturing it. Then you can just change the reference at runtime.

For example, assuming your code is run inside some view controller method, you can do the following:

class MyViewController: UIViewController {

    var filter: CIFilter = CIFilter(name: "CIPhotoEffectNoir")!

    func createPlayer() {
        let asset = AVAsset(url: fooURL)
        let item = AVPlayerItem(asset: asset)
        item.videoComposition = AVVideoComposition(asset: asset,  applyingCIFiltersWithHandler: { [weak self] request in
            guard let self = self else {
                request.finish(with error: SomeError)
                return
            }
            let source = request.sourceImage.clampedToExtent()
            self.filter.setValue(source, forKey: kCIInputImageKey)

            let output = self.filter.outputImage

            request.finish(with: output!, context: nil)
        })

        player = AVPlayer(playerItem: item)
        let playerLayer = AVPlayerLayer(player: player)
        playerLayer.frame = videoViewDetail.bounds
        videoViewDetail.layer.addSublayer(playerLayer)
        player?.play()
    }

    // ...

}

Then you can easily change the filter by just changing self.filter.

(You may want to synchronize access to the filter though, to avoid concurrency issues.)

Frank Rupprecht
  • 9,191
  • 31
  • 56
  • Works great!! thanks! For saving the result i'm trying to use an AVMutableComposition as an AVPlayerItem's asset .... is the correct way? – Stefano Vet Oct 20 '19 at 12:25
  • For exporting you best use an `AVAssetExportSession`. It can also work with an `AVVideoComposition` and it will write the video into a file URL you specify. – Frank Rupprecht Oct 20 '19 at 12:47
  • i try to pass asset (let asset = AVAsset(url: fooURL)) to AVAssetExportSession. The new video save correctly but without filters. Any idea? I use let exporter = AVAssetExportSession(asset: asset!, presetName: AVAssetExportPresetHighestQuality). Thanks Frank!!! – Stefano Vet Oct 20 '19 at 13:47
  • 1
    You also have to set the `videoComposition` of the export session as you did with the player item. – Frank Rupprecht Oct 20 '19 at 14:26