请求 AVAsset 时维护顺序

Posted

技术标签:

【中文标题】请求 AVAsset 时维护顺序【英文标题】:Maintaining order when requesting AVAssets 【发布时间】:2017-08-19 19:27:03 【问题描述】:

我正在尝试构建一个视频合并应用程序,该应用程序允许用户从集合视图中选择几个短片,然后生成所有合并为一个的视频的预览。我正在使用照片框架 (PHCachingImageManager) 来填充集合视图,并将所选 PHAsset 的数组传递给下面的函数,以请求低质量的 AVAsset(用于合并和生成预览)。

问题是,我需要保持 AVAssets 用户选择它们的顺序,但是“requestAVAsset”函数是异步的,并且完成处理程序经常被多次调用。我以前从未使用过调度组,但尝试在下面使用它们……有时 AVAsset 仍然出现故障。

func requestAVAssets(assets: [PHAsset]) -> [AVAsset] 
    var videoArray: [AVAsset] = []
    let dispatchGroup = DispatchGroup()
    let videoOptions = PHVideoRequestOptions()
    videoOptions.isNetworkAccessAllowed = true
    videoOptions.deliveryMode = .fastFormat
    for asset in assets 
        dispatchGroup.enter()
        self.imageManager.requestAVAsset(forVideo: asset, options: videoOptions, resultHandler:  (video, audioMix, info) in
            guard video != nil else  return 
            videoArray.append(video!)
            dispatchGroup.leave()
        )
    
    dispatchGroup.wait()
    return videoArray

我猜我要么放错了一些代码,要么是以完全错误的方式处理这个问题!任何建议表示赞赏。

【问题讨论】:

【参考方案1】:

如果在迭代 AVAsset 时捕获当前索引,则可以插入而不是追加。至少我是这样做的。

func requestAVAssets(assets: [PHAsset]) -> [AVAsset] 
    var videoArray = [AVAsset?](repeating: nil, count: assets.count)
    let videoOptions = PHVideoRequestOptions()
    videoOptions.isNetworkAccessAllowed = true
    videoOptions.deliveryMode = .fastFormat
    for (i, asset) in assets.enumerated() 
        self.imageManager.requestAVAsset(forVideo: asset, options: videoOptions, resultHandler:  (video, audioMix, info) in
            guard let video = video else  return 
            videoArray.remove(at: i)
            videoArray.insert(video, at: i)
        )
    
    return videoArray.flatMap  $0 

为数组提供所需的项目数 nil 将阻止它在插入项目时出错,然后在下载完成后,删除现有的 nil 值并将其替换为实际的 AVAsset

最后,对结果数组进行 flatMap 以解压缩选项(并可选择通过将其与传入的 assets 数组进行比较来检查您是否拥有所需数量的项目)。

【讨论】:

谢谢丹尼尔,这就像一个魅力。我认为你的意思是“videoArray [I] = video”是“videoArray.insert(video,at:i)”,否则你会得到一个数组索引越界错误。我也不知道用于解压缩选项的平面映射快捷方式! :) 这将教我在没有静态分析器的情况下编写代码!你还需要你的DispatchGroupDispatchSemaphore 来等待所有请求完成,然后再从函数返回(但我相信你已经明白了)。【参考方案2】:

完全回避调度问题,因为它已经很晚了,我度过了糟糕的一天,但是如果你保留与视频相关联的“正确”索引,然后对其进行排序呢?我认为这样的事情会奏效。

struct SelectedVideo 
    let index: Int
    let asset: AVAsset


func requestAVAssets(assets: [PHAsset]) -> [AVAsset] 
    var videoArray: [SelectedVideo] = []
    let dispatchGroup = DispatchGroup()
    let videoOptions = PHVideoRequestOptions()
    videoOptions.isNetworkAccessAllowed = true
    videoOptions.deliveryMode = .fastFormat
    for (index, asset) in assets.enumerated() 
        dispatchGroup.enter()
        self.imageManager.requestAVAsset(forVideo: asset, options: videoOptions, resultHandler:  (videoMb, audioMixMb, infoMb) in
            guard let video = videoMb else return
            videoArray.append(SelectedVideo(index, video))
            dispatchGroup.leave()
        )
    
    dispatchGroup.wait()
    return videoArray.sort  $0.index < $1.index.map($0.video)

这是一种 hack(甚至没有尝试编译它),但就像我说的,这是糟糕的一天。

需要注意的几个小改动:我将闭包的参数更改为“Mb”,意思是“可能”,这是我见过的一个很好的约定,用于命名传递给闭包的可选参数。此外,最好不要执行“guard video != nil”然后强制展开,而是执行“guard let video = videoMb”,然后 video 是非可选的。

【讨论】:

我首先在下面阅读了丹尼尔的回答,但我很确定这也可以,所以谢谢!我喜欢“Mb”命名约定和避免强制展开的提示。

以上是关于请求 AVAsset 时维护顺序的主要内容,如果未能解决你的问题,请参考以下文章

AVAsset 的 NaturalSize 与视频文件不同

当设备被蓝牙耳机锁定时,AVAsset 无法播放

使用 HTTP NSURL 创建 AVAsset

Cocoa:来自原始数据的 AVAsset(即 NSData)

MPMediaItem 到 AVAsset 到 .MP3

iOS-如何使用 AVAsset 或 AVURLAsset 获取 .mp4 文件的持续时间