10

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()
}
4

2 回答 2

24

这个问题的解决方案是使用AVAssetExportSessionand AVAssetResourceLoaderDelegate

第一步是添加通知以了解视频何时结束。然后我们可以开始将它保存到磁盘。

override func viewDidLoad() {

    super.viewDidLoad()

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

    ...
}

deinit {

    NSNotificationCenter.defaultCenter().removeObserver(self)
}

我们函数的实现:

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)
        })
    }
}

最后,我们需要制作我们AVURLAsset的 AVAssetResourceLoaderDelegate 委托:

lazy var asset: AVURLAsset = {

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

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

    return asset
}()

和:

extension ViewController : AVAssetResourceLoaderDelegate {

}

我在 GitHub 中使用此代码创建了一个小演示。

于 2016-06-03T10:06:42.637 回答
3

Calm 的团队已经开源了我们的实现。它可以作为 CocoaPod 使用。它被称为PersistentStreamPlayer

特点包括:

  • 音频文件的流式传输,一旦第一个数据可用就开始播放
  • 缓冲区完成后,还会将流数据保存到文件 URL 暴露 timeBuffered,有助于在 UI 中显示缓冲区进度条
  • 在缓冲流停止后处理重新启动音频文件(例如慢速网络)
  • 简单的播放、暂停和销毁方法(销毁清除所有内存资源)
  • 不将音频文件数据保存在内存中,因此它支持不适合 RAM 的大文件

你可以在这里找到它:https ://github.com/calmcom/PersistentStreamPlayer

于 2016-10-07T21:58:44.487 回答