How to stream a video with AVURLAsset and save to disk the cached data

17,128

Solution 1

The solution for this problem is to use AVAssetExportSession and AVAssetResourceLoaderDelegate:

First step is to add a notification to know when the video finish. Then we can start saving it to disk.

override func viewDidLoad() {

    super.viewDidLoad()

    NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(playerItemDidReachEnd(_:)), name: AVPlayerItemDidPlayToEndTimeNotification, object: nil)

    ...
}

deinit {

    NSNotificationCenter.defaultCenter().removeObserver(self)
}

The implementation of our function:

func playerItemDidReachEnd(notification: NSNotification) {

    if notification.object as? AVPlayerItem  == player.currentItem {

        let exporter = AVAssetExportSession(asset: asset, presetName: AVAssetExportPresetHighestQuality)

        let filename = "filename.mp4"

        let documentsDirectory = NSFileManager.defaultManager().URLsForDirectory(NSSearchPathDirectory.DocumentDirectory, inDomains: NSSearchPathDomainMask.UserDomainMask).last!

        let outputURL = documentsDirectory.URLByAppendingPathComponent(filename)

        exporter?.outputURL = outputURL
        exporter?.outputFileType = AVFileTypeMPEG4

        exporter?.exportAsynchronouslyWithCompletionHandler({

            print(exporter?.status.rawValue)
            print(exporter?.error)
        })
    }
}

Finally we need to make our AVURLAsset delegate of AVAssetResourceLoaderDelegate:

lazy var asset: AVURLAsset = {

    var asset: AVURLAsset = AVURLAsset(URL: self.url)

    asset.resourceLoader.setDelegate(self, queue: dispatch_get_main_queue())

    return asset
}()

And:

extension ViewController : AVAssetResourceLoaderDelegate {

}

I created a small demo with this code in GitHub.

Solution 2

The team at Calm has open-sourced our implementation to this. It's available as a CocoaPod. It's called PersistentStreamPlayer.

Features include:

  • streaming of audio file, starting playback as soon as first data is available
  • also saves streamed data to a file URL as soon as the buffer completes exposes timeBuffered, helpful for displaying buffer progress bars in the UI
  • handles re-starting the audio file after the buffer stream stalls (e.g. slow network)
  • simple play, pause and destroy methods (destroy clears all memory resources)
  • does not keep audio file data in memory, so that it supports large files that don't fit in RAM

You can find it here: https://github.com/calmcom/PersistentStreamPlayer

Share:
17,128
Gabriel.Massana
Author by

Gabriel.Massana

http://www.linkedin.com/in/gabrielmassana/en https://github.com/GabrielMassana https://cocoapods.org/owners/10374 https://somethingaboutios.wordpress.com/

Updated on June 03, 2022

Comments

  • Gabriel.Massana
    Gabriel.Massana almost 2 years

    Some days ago I was asked to check how difficult is to play a video while downloading it from Internet. I know it's an easy task because someone told me a while ago. So, I checked and it was super easy.

    The problem was that I wanted to save to disk the video to do not force the user to download it again and again.

    The problem was to access the buffer and store it to disk.

    Many answers in Stackoverflow says it is nor possible. Specially with videos.

    My original code to play the video:

    import AVFoundation
    
    ....
    
    //MARK: - Accessors
    
    lazy var player: AVPlayer = {
    
        var player: AVPlayer = AVPlayer(playerItem: self.playerItem)
    
        player.actionAtItemEnd = AVPlayerActionAtItemEnd.None
    
        return player
    }()
    
    lazy var playerItem: AVPlayerItem = {
    
        var playerItem: AVPlayerItem = AVPlayerItem(asset: self.asset)
    
        return playerItem
    }()
    
    lazy var asset: AVURLAsset = {
    
        var asset: AVURLAsset = AVURLAsset(URL: self.url)
    
        return asset
    }()
    
    lazy var playerLayer: AVPlayerLayer = {
    
        var playerLayer: AVPlayerLayer = AVPlayerLayer(player: self.player)
    
        playerLayer.frame = UIScreen.mainScreen().bounds
        playerLayer.backgroundColor = UIColor.clearColor().CGColor
    
        return playerLayer
    }()
    
    var url: NSURL = {
    
        var url = NSURL(string: "https://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4")
    
        return url!
    }()
    
    //MARK: - ViewLifeCycle
    
    override func viewDidLoad() {
    
        super.viewDidLoad()
    
        view.layer.addSublayer(playerLayer)
    
        player.play()
    }
    
  • Guig
    Guig over 7 years
    It looks quite similar than this code: gist.github.com/anonymous/83a93746d1ea52e9d23f An issue I'm having is that when using the AVAssetResourceLoaderDelegate the readyToPlay events happens only once the entire file is downloaded, while it used to happen as soon as enough data was received to play the beginning of the video
  • Tyler Sheaffer
    Tyler Sheaffer over 7 years
    Yes it is very similar to that code, as I started from there as a branching off point. I've added several nice features on top of that, however.
  • Tyler Sheaffer
    Tyler Sheaffer over 7 years
    As for the AVAssetResourceLoaderDelegate, @Guig notice that your instance of PersistentStreamPlayer is implementing that protocol, so I'm not sure how you're hooking in to listen for that. If you'd like to open an issue on the repo I'll be happy to work through the issue with you, or feel free to open a Pull Request there
  • Guig
    Guig over 7 years
    done, thanks. I can try to add more details / repro if you need and if I've not lost my toy project already
  • Frade
    Frade over 7 years
    Thanks! does it also support video?
  • Tyler Sheaffer
    Tyler Sheaffer over 7 years
    @Frade It should in theory, but I've only tested it in production with audio. If it doesn't quite fit your use case it should be much easier to fork and PR to get video support working than to start from scratch. Cheers.
  • Frade
    Frade over 7 years
    @Tyler, Thanks, will try it to load video
  • Frade
    Frade over 7 years
    Hi Tyler! apparently cocoapods are fetching Installing version 0.1.0: "Persistent StreamPlayer (0.1.0)"
  • Tyler Sheaffer
    Tyler Sheaffer over 7 years
    Thanks @Frade It should be close to working with video. Pull requests welcome. In any case, let's more this discussion to github.com/calm/PersistentStreamPlayer/issues/3
  • Lane Rettig
    Lane Rettig about 7 years
    Why is AVAssetResourceLoaderDelegate necessary here? I don't see any of these being used: developer.apple.com/reference/avfoundation/…
  • Lane Rettig
    Lane Rettig about 7 years
    I get the following error when I try to run this code:Error Domain=AVFoundationErrorDomain Code=-11838 "Operation Stopped" UserInfo={NSUnderlyingError=0x61800024ebe0 {Error Domain=NSOSStatusErrorDomain Code=-12109 "(null)"}, NSLocalizedFailureReason=The operation is not supported for this media., NSLocalizedDescription=Operation Stopped}. I made sure that the outputURL and outputFileType are correct.
  • KavyaKavita
    KavyaKavita about 7 years
    Worked for me. What are the precautions we need to take when we play one url at a time ?
  • user3069232
    user3069232 about 7 years
    This look great, but I tried the demo project and it didn't save it, giving me the error. Optional(Error Domain=AVFoundationErrorDomain Code=-11823 "Cannot Save"
  • Farid Al Haddad
    Farid Al Haddad almost 7 years
    Hello, is this demo implementable in a collectionView of videos without making the scroll laggy ?
  • GoodSp33d
    GoodSp33d over 6 years
    Could you please let me know if this will be exporting the video file from the buffered cache, was just curious since we are downloading through the new asset and was not able to understand as to how it will get the current player item buffer
  • kelin
    kelin about 5 years
    If you have the The operation is not supported for this media. error - add the delegate!
  • Legolas
    Legolas almost 5 years
    Is this actually working for anyone? I am getting the error as @LaneRettig
  • Greg Ellis
    Greg Ellis over 4 years
    FYI: This code is unfinished. It does not allow for proper saving of file if seeking while the file is still being downloaded.
  • heyfrank
    heyfrank over 3 years
    What happens if I start watching a longer video and then scrub to the end? Will it export something? If yes, does it just export the watched sequences?
  • heyfrank
    heyfrank over 3 years
    In my case it's always failing on simulator for some reason, but on a real device it's working.