diff --git a/Anytype/Sources/ServiceLayer/VideoPreviewStorage/VideoPreviewStorage.swift b/Anytype/Sources/ServiceLayer/VideoPreviewStorage/VideoPreviewStorage.swift index 1bedef3e8c..cd724fd072 100644 --- a/Anytype/Sources/ServiceLayer/VideoPreviewStorage/VideoPreviewStorage.swift +++ b/Anytype/Sources/ServiceLayer/VideoPreviewStorage/VideoPreviewStorage.swift @@ -7,17 +7,16 @@ protocol VideoPreviewStorageProtocol { func preview(url: URL, size: CGSize) async throws -> UIImage } -final class VideoPreviewStorage: VideoPreviewStorageProtocol { +actor VideoPreviewStorage: VideoPreviewStorageProtocol { private struct StorageKey: Hashable { let url: URL let size: CGSize } + private var taskCache = [StorageKey: Task]() private let storage: Storage? - private let operationsQueue = DispatchQueue(label: "VideoPreviewQueue") - init() { let diskConfig = DiskConfig(name: "VideoPreviewCache", maxSize: 30 * 1024 * 1024) // 30 Mb let memoryConfig = MemoryConfig(expiry: .never, countLimit: 100, totalCostLimit: 300) @@ -31,58 +30,42 @@ final class VideoPreviewStorage: VideoPreviewStorageProtocol { } func preview(url: URL, size: CGSize) async throws -> UIImage { - return try await withCheckedThrowingContinuation { continuation in - loadPreviewInQueue(url: url, size: size) { result in - switch result { - case .success(let success): - continuation.resume(returning: success) - case .failure(let failure): - continuation.resume(throwing: failure) - } - } - } - } - - private func loadPreviewInQueue(url: URL, size: CGSize, completion: @escaping (Result) -> Void) { let key = StorageKey(url: url, size: size) - operationsQueue.async { [weak self] in - - guard let self else { - completion(.failure(CommonError.undefined)) - return - } - - if let image = try? storage?.object(forKey: key) { - completion(.success(image)) - return - } - - guard let image = UIImage(videoPreview: url, size: size) else { - completion(.failure(CommonError.undefined)) - return - } - + if let task = taskCache[key] { + return try await task.value + } + + if let image = try? storage?.object(forKey: key) { + return image + } + + let task = Task { + return try UIImage(videoPreview: url, size: size) + } + + taskCache[key] = task + + do { + let image = try await task.value try? storage?.setObject(image, forKey: key) - completion(.success(image)) + return image + } catch { + taskCache[key] = nil + throw error } } } fileprivate extension UIImage { - convenience init?(videoPreview path: URL?, size: CGSize) { - guard let path else { return nil } - do { - let asset = AVURLAsset(url: path, options: nil) - let imgGenerator = AVAssetImageGenerator(asset: asset) - imgGenerator.appliesPreferredTrackTransform = true - imgGenerator.maximumSize = size - let cgImage = try imgGenerator.copyCGImage(at: CMTimeMake(value: 0, timescale: 1), actualTime: nil) - self.init(cgImage: cgImage) - } catch { - return nil - } + convenience init(videoPreview path: URL, size: CGSize) throws { + let asset = AVURLAsset(url: path, options: nil) + let imgGenerator = AVAssetImageGenerator(asset: asset) + imgGenerator.appliesPreferredTrackTransform = true + imgGenerator.maximumSize = size + let cgImage = try imgGenerator.copyCGImage(at: CMTimeMake(value: 0, timescale: 1), actualTime: nil) + self.init(cgImage: cgImage) } }