使用 iCloud PHAsset 请求 AVAsset 返回一个没有 VideoTracks 的 AVAsset

Swi*_*bit 7 ios icloud avasset swift ios14

最近,开始注意到从照片库导入某些资产时没有显示它们。有问题的资产存储在 iCloud 中,不会缓存在设备上。我相信这是一个 iOS 14 问题,因为我从未在 iOS 13 上遇到过这个问题。(我不是 100% 确定,因为我无法将我的个人设备推出到 iOS 13)。

这是我在iOS 14 中所做的:

  1. 使用新的选择器视图控制器导入视频资产
var configuration = PHPickerConfiguration(photoLibrary: PHPhotoLibrary.shared())
configuration.filter = PHPickerFilter.videos
let videoPickerController = PHPickerViewController(configuration: configuration)
videoPickerController.delegate = self
present(videoPickerController, animated: true, completion: nil)
Run Code Online (Sandbox Code Playgroud)
  1. 我从 [PHPickerResult](委托方法)中提取 PHAsset
func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
  picker.dismiss(animated: true, completion: nil)
  guard results.count > 0 else {
    return
  }
  guard let firstPHAssetIdentifier = results.first?.assetIdentifier else {
    fatalError("No asset identifier")
  }
  let fetchOptions = PHFetchOptions()
  guard let phAsset = PHAsset.fetchAssets(withLocalIdentifiers: [firstPHAssetIdentifier], options: fetchOptions).firstObject else {
    fatalError("No matching PHAsset")
  }
  guard phAsset.mediaType == .video else {
    fatalError("Asset not of the video type")
  }
}
Run Code Online (Sandbox Code Playgroud)
  1. 然后,我为匹配的 PHAsset 请求一个 AVAsset
let options = PHVideoRequestOptions()
options.isNetworkAccessAllowed = true
options.progressHandler = { progress, _, _, _ in
  print("Progress: \(progress)")
}

PHCachingImageManager.default().requestAVAsset(forVideo: phAsset, options: options) { [weak self] avAsset, _, info in
  guard info?[PHImageCancelledKey] == nil && info?[PHImageErrorKey] == nil else {
    print("Error or cancelled. Info: \(String(describing: info))")
    return
  }
  guard let avAsset = avAsset else {
    print("Asset is nil. Info: \(String(describing: info))")
    return
  }
  guard let videoTrack = avAsset.tracks(withMediaType: .video).first else {
    print("Cound not extract video track from AvAsset") // <- My issue
    return
  }
}
Run Code Online (Sandbox Code Playgroud)

问题:通常,当资产来自 iCloud 时,我不会有 videoTrack。avAsset.duration 也将为0.0

我会看到下载进度,但我会陷入最后的守卫声明。有时,一旦资产已下载并且无法加载 videoTrack,重试将立即失败(它不会再次尝试加载资产,似乎它已损坏)。它将落入最后的守卫声明。

我注意到在 PHVideoRequestOptions 上使用 deliveryMode = .highQualityFormat 使其工作,但我宁愿只下载 720p 视频,而不是高质量视频。

我想我在这里做错了什么。我应该以这种方式从 PHPickerResult 获取 phAsset 吗?任何指针/帮助将不胜感激。我创建了这个 repo 来复制https://github.com/SwiftRabbit/RequestAVAssetIssues/tree/main/RequestAVAssetIssues。不能 100% 重现,并且必须是不再存在于设备上的 iCloud 视频。

附加说明/尝试:

  • 我在 PHCachingImageManager 和 PHImageManager 上遇到了同样的问题
  • 一些流行的应用程序似乎对某些资产有同样的问题(例如 Instagram、TikTok...)
  • PHCachingImageManager.default().requestPlayerItem 也不起作用。它返回一个不包含任何轨道的 AVPlayerItem。
  • iPhone XS,企业托管设备

Jan*_*rdt 0

我能够通过在 iPad 上录制视频、等待视频同步到 iCloud,然后在 iPhone SE 2016 上请求 AVAsset 来重现该问题。我在 github 上创建了一个存储库来说明这一点。这是一个 Objective-C 项目,但是对于 Swift 来说结论应该是一样的。存储库的重要部分位于https://github.com/Jan-E/ikyle.me-code-examples/blob/master/Photo%20Picker%20ObjC/Photo%20Picker%20ObjC/PHPickerController.m

这是我正在使用的代码:

for (PHAsset *phAsset in assetResults) {
    float recordingDuration = phAsset.duration;
    NSLog(@"recordingDuration %f", recordingDuration);
    NSDate *PHAssetCreationDate = phAsset.creationDate;
    NSLog(@"PHAssetCreationDate %@", PHAssetCreationDate);
    PHVideoRequestOptions *options = [[PHVideoRequestOptions alloc] init];
    options.networkAccessAllowed = NO;
    [[PHImageManager defaultManager] requestAVAssetForVideo:phAsset
                                                            options:options
                                                      resultHandler:^(AVAsset *avAsset, AVAudioMix *audioMix, NSDictionary *info) {
        NSURL *videoURL = (NSURL *)[[(AVURLAsset *)avAsset URL] fileReferenceURL];
        NSLog(@"videoURL absoluteString = %@", [videoURL absoluteString]);
        NSLog(@"videoURL relativePath   = %@", [videoURL relativePath]);
        AVURLAsset *avUrl = [AVURLAsset assetWithURL:videoURL];
        CMTime time = [avUrl duration];
        float recordingDuration;
        recordingDuration = time.value/time.timescale;
        NSArray *tracks = [avUrl tracksWithMediaType:AVMediaTypeVideo];
        NSLog(@"duration %f, tracks %lu", recordingDuration, (unsigned long)tracks.count);
    }];
}
Run Code Online (Sandbox Code Playgroud)

当视频仍然仅在 iCloud 上时,这些是 NSLog 返回的结果:

  1. phAsset.duration 返回正确的值
  2. phAsset.creationDate 返回正确的值
  3. avAsset URL 返回 (null)
  4. avAsset 的持续时间为 0.0 秒
  5. avAsset的轨道数为0

通过使用,result.itemProvider loadFileRepresentationForTypeIdentifier您可以强制将视频从 iCloud 下载到设备。那么结果如下:

  1. phAsset.duration 返回正确的值
  2. phAsset.creationDate 返回正确的值
  3. avAsset URL 返回正确的路径
  4. avAsset 的持续时间是正确的值
  5. avAsset的轨道数为1

完整的 NSLog 输出位于完成该操作的 2 个提交中的最后一个下面的 2 个注释中:https://github.com/Jan-E/ikyle.me-code-examples/commit/2d0174c2766abf742b6d76d053abc0a46199358f

编辑:请注意,在设备上没有视频的情况下启动的示例中,从 iCloud 下载 1.2GB 视频需要 4:23 分钟 (2020-12-24 15:45:13.669505+0100 - 2020-12-24 15: 49:36.224240+0100)。并非在所有情况下都需要下载。phAsset.duration 的值为 680.044983 秒的信息可能是您在应用程序中需要的所有信息,因为它意味着视频至少有 1 个轨道。

编辑2:通过使用options:nil我正在使用隐式networkAccessAllowed = NO. 通过编辑,我将其明确化。它还可能给出持续时间 0.0 和跟踪 0 的解释。如果允许网络访问,但网络连接失败怎么办?然后,您仍然会获得有意义的 phAsset.duration 值(来自早期的 iCloud 同步),但不会获得 avAsset 的实时值。