AVPlayer拖动时AVAssetImageGenerator生成有序预览缩略图的问题
解决AVAssetImageGenerator拖动seek时缩略图顺序混乱的问题
问题核心:每次触发seek就新建AVAssetImageGenerator,且未终止之前的异步请求,导致旧请求的结果延迟返回,和新请求的结果顺序混乱。
修复方案
- 将
AVAssetImageGenerator改为类的实例属性,避免重复创建,同时方便管理请求。 - 每次发起新的缩略图请求前,取消所有未完成的生成任务。
- 在回调中验证返回的请求时间是否为当前最新的seek时间,过滤旧请求的无效结果。
修改后的完整代码
import AVFoundation import UIKit class PlayerThumbnailManager { weak var playerView: PlayerView? // 替换为你的播放器视图类型 private var imageGenerator: AVAssetImageGenerator? private var currentSeekTime: CMTime = .zero func takeSnapshot(seekTime: CMTime) { guard let asset = playerView?.player?.currentItem?.asset else { debugPrint("无可用资源") return } // 记录当前最新的seek时间 currentSeekTime = seekTime // 初始化或复用imageGenerator if imageGenerator == nil { imageGenerator = AVAssetImageGenerator(asset: asset) imageGenerator?.appliesPreferredTrackTransform = true imageGenerator?.maximumSize = CGSize(width: 320, height: 180) } else if imageGenerator?.asset != asset { // 资源变更时重新创建 imageGenerator?.cancelAllCGImageGeneration() imageGenerator = AVAssetImageGenerator(asset: asset) imageGenerator?.appliesPreferredTrackTransform = true imageGenerator?.maximumSize = CGSize(width: 320, height: 180) } // 取消之前未完成的请求 imageGenerator?.cancelAllCGImageGeneration() if #available(tvOS 16.0, *) { DispatchQueue.global(qos: .userInitiated).async { [weak self] in guard let self = self, let generator = self.imageGenerator else { return } generator.generateCGImageAsynchronously(for: seekTime) { [weak self] image, requestedTime, error in guard let self = self else { return } // 验证是否为当前最新的请求时间 guard CMTimeCompare(requestedTime, self.currentSeekTime) == 0 else { debugPrint("忽略旧请求结果") return } guard let image = image else { debugPrint("生成失败:\(error?.localizedDescription ?? "未知错误") \(error ?? "")") return } let thumbnail = UIImage(cgImage: image) // 回到主线程更新UI DispatchQueue.main.async { // 在这里更新你的缩略图展示,比如赋值给UIImageView // self.thumbnailImageView.image = thumbnail } } } } else { DispatchQueue.global(qos: .userInitiated).async { [weak self] in guard let self = self, let generator = self.imageGenerator else { return } generator.generateCGImagesAsynchronously(forTimes: [NSValue(time: seekTime)]) { [weak self] requestedTime, image, actualTime, result, error in guard let self = self else { return } // 验证是否为当前最新的请求时间 guard CMTimeCompare(requestedTime.timeValue, self.currentSeekTime) == 0 else { debugPrint("忽略旧请求结果") return } guard let image = image, result == .succeeded else { debugPrint("生成失败:\(error?.localizedDescription ?? "未知错误") \(error ?? "")") return } let thumbnail = UIImage(cgImage: image) // 回到主线程更新UI DispatchQueue.main.async { // 在这里更新你的缩略图展示,比如赋值给UIImageView // self.thumbnailImageView.image = thumbnail } } } } } // 播放器销毁或资源切换时调用,清理资源 func cleanUp() { imageGenerator?.cancelAllCGImageGeneration() imageGenerator = nil } } // 使用示例 // let manager = PlayerThumbnailManager() // manager.playerView = yourPlayerView // let seekTime = CMTime(seconds: 1, preferredTimescale: 1) // manager.takeSnapshot(seekTime: seekTime)
关键改动说明
- 复用AVAssetImageGenerator:避免重复创建对象,减少性能开销,同时统一管理请求。
- 取消旧请求:每次发起新请求前调用
cancelAllCGImageGeneration(),终止所有未完成的异步任务,避免旧结果干扰。 - 时间验证:在回调中对比返回的请求时间和当前记录的最新seek时间,只处理最新请求的结果,确保缩略图和拖动位置匹配。
- 主线程更新UI:缩略图生成在后台线程,更新UI必须回到主线程,避免线程错误。
内容的提问来源于stack exchange,提问作者Sanjeev Esuka
相关产品推荐
相关产品推荐

