You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

AVPlayer拖动时AVAssetImageGenerator生成有序预览缩略图的问题

解决AVAssetImageGenerator拖动seek时缩略图顺序混乱的问题

问题核心:每次触发seek就新建AVAssetImageGenerator,且未终止之前的异步请求,导致旧请求的结果延迟返回,和新请求的结果顺序混乱。

修复方案

  1. 将AVAssetImageGenerator改为类的实例属性,避免重复创建,同时方便管理请求。
  2. 每次发起新的缩略图请求前,取消所有未完成的生成任务。
  3. 在回调中验证返回的请求时间是否为当前最新的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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.29 19:10:42