mirror of
https://github.com/divkit/divkit.git
synced 2026-05-07 20:02:32 +00:00
Decode video preview on background queue
commit_hash:a30188831672e51e39dcde7729bf6a8e451e9008
This commit is contained in:
@@ -18738,6 +18738,7 @@
|
||||
"client/ios/DivKit/Form/DivSubmitter.swift":"divkit/public/client/ios/DivKit/Form/DivSubmitter.swift",
|
||||
"client/ios/DivKit/Form/SubmitRequest.swift":"divkit/public/client/ios/DivKit/Form/SubmitRequest.swift",
|
||||
"client/ios/DivKit/IdToPath.swift":"divkit/public/client/ios/DivKit/IdToPath.swift",
|
||||
"client/ios/DivKit/Images/AsyncDataImageHolder.swift":"divkit/public/client/ios/DivKit/Images/AsyncDataImageHolder.swift",
|
||||
"client/ios/DivKit/Images/CachedRemoteImageHolder.swift":"divkit/public/client/ios/DivKit/Images/CachedRemoteImageHolder.swift",
|
||||
"client/ios/DivKit/Images/DivImageHolderFactory.swift":"divkit/public/client/ios/DivKit/Images/DivImageHolderFactory.swift",
|
||||
"client/ios/DivKit/LayoutProvider/DivLayoutProviderHandler.swift":"divkit/public/client/ios/DivKit/LayoutProvider/DivLayoutProviderHandler.swift",
|
||||
|
||||
@@ -38,7 +38,13 @@ extension DivVideo: DivBlockModeling {
|
||||
let elapsedTime: Binding<Int>? = elapsedTimeVariable.flatMap {
|
||||
context.makeBinding(variableName: $0, defaultValue: 0)
|
||||
}
|
||||
let preview: Image? = resolvePreview(resolver).flatMap(_makeImage(base64:))
|
||||
let preview: ImageHolder = context
|
||||
.imageHolderFactory.make(
|
||||
nil,
|
||||
resolvePreview(resolver)
|
||||
.map { .imageData(ImageData(base64: $0)) }
|
||||
)
|
||||
|
||||
let videoData = VideoData(videos: videoSources?
|
||||
.compactMap { $0.makeVideo(resolver: resolver) } ?? []
|
||||
)
|
||||
@@ -124,18 +130,3 @@ extension DivVideoScale {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func _makeImage(base64: String) -> Image? {
|
||||
decode(base64: base64).flatMap(Image.init(data:))
|
||||
}
|
||||
|
||||
fileprivate func decode(base64: String) -> Data? {
|
||||
if let data = Data(base64Encoded: base64) {
|
||||
return data
|
||||
}
|
||||
if let url = URL(string: base64),
|
||||
let dataHoldingURL = try? Data(contentsOf: url) {
|
||||
return dataHoldingURL
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
import Foundation
|
||||
import VGSL
|
||||
|
||||
@preconcurrency @MainActor
|
||||
final class AsyncDataImageHolder: ImageHolder {
|
||||
private(set) weak var image: Image?
|
||||
|
||||
let placeholder: ImagePlaceholder?
|
||||
|
||||
private let imageData: ImageData
|
||||
private let imageProcessingQueue: OperationQueueType
|
||||
private let imageDecoder: (@Sendable (_ data: Data) -> Image?)?
|
||||
|
||||
nonisolated var debugDescription: String {
|
||||
onMainThreadSync {
|
||||
let imagePart = if let image {
|
||||
String(format: "%.0fx%.0f", image.size.width, image.size.height)
|
||||
} else {
|
||||
"nil"
|
||||
}
|
||||
return "AsyncDataImageHolder(image=\(imagePart), placeholder=\(dbgStr(placeholder?.debugDescription)), customDecoder=\(imageDecoder != nil), queue=\(imageProcessingQueue), imageData=\(String(describing: imageData)))"
|
||||
}
|
||||
}
|
||||
|
||||
init(
|
||||
data: ImageData,
|
||||
imageProcessingQueue: OperationQueueType,
|
||||
imageDecoder: (@Sendable (_ data: Data) -> Image?)? = nil,
|
||||
placeholder: ImagePlaceholder? = nil
|
||||
) {
|
||||
self.imageData = data
|
||||
self.imageProcessingQueue = imageProcessingQueue
|
||||
self.imageDecoder = imageDecoder
|
||||
self.placeholder = placeholder
|
||||
}
|
||||
|
||||
func requestImageWithCompletion(
|
||||
_ completion: @escaping @MainActor (Image?) -> Void
|
||||
) -> Cancellable? {
|
||||
imageData.makeImage(
|
||||
queue: imageProcessingQueue,
|
||||
decoder: imageDecoder
|
||||
) { [weak self] image in
|
||||
guard let self else { return }
|
||||
completion(image)
|
||||
self.image = image
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func reused(
|
||||
with placeholder: ImagePlaceholder?,
|
||||
remoteImageURL url: URL?
|
||||
) -> (any ImageHolder)? {
|
||||
(self.placeholder === placeholder && url == nil) ? self : nil
|
||||
}
|
||||
|
||||
func equals(_ other: any ImageHolder) -> Bool {
|
||||
guard let other = other as? Self else {
|
||||
return false
|
||||
}
|
||||
|
||||
return self.imageData == other.imageData && self.imageProcessingQueue === other
|
||||
.imageProcessingQueue
|
||||
}
|
||||
}
|
||||
|
||||
extension ImagePlaceholder {
|
||||
public func toAsyncDataImageHolder(
|
||||
imageProcessingQueue: OperationQueueType,
|
||||
decoder: (@Sendable (Data) -> Image?)? = nil
|
||||
) -> ImageHolder {
|
||||
switch self {
|
||||
case .image, .color, .view:
|
||||
toImageHolder()
|
||||
case let .imageData(imageData):
|
||||
AsyncDataImageHolder(
|
||||
data: imageData,
|
||||
imageProcessingQueue: imageProcessingQueue,
|
||||
imageDecoder: decoder
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -80,7 +80,9 @@ final class DefaultImageHolderFactory: DivImageHolderFactory {
|
||||
|
||||
func make(_ url: URL?, _ placeholder: ImagePlaceholder?) -> ImageHolder {
|
||||
guard let url else {
|
||||
return placeholder?.toImageHolder() ?? NilImageHolder()
|
||||
return placeholder?.toAsyncDataImageHolder(
|
||||
imageProcessingQueue: imageProcessingQueue
|
||||
) ?? NilImageHolder()
|
||||
}
|
||||
return RemoteImageHolder(
|
||||
url: url,
|
||||
|
||||
@@ -29,17 +29,25 @@ public final class SVGImageHolderFactory: DivImageHolderFactory {
|
||||
}
|
||||
|
||||
public func make(_ url: URL?, _ placeholder: ImagePlaceholder?) -> ImageHolder {
|
||||
guard let url else {
|
||||
return placeholder?.toImageHolder() ?? NilImageHolder()
|
||||
let decoder: (
|
||||
@Sendable (_ data: Data) -> Image?
|
||||
)? = { [weak self] in
|
||||
self?.svgDecoder.decode(data: $0)
|
||||
}
|
||||
|
||||
return if let url {
|
||||
RemoteImageHolder(
|
||||
url: url,
|
||||
placeholder: placeholder,
|
||||
requester: requester,
|
||||
imageProcessingQueue: imageProcessingQueue,
|
||||
imageDecoder: decoder
|
||||
)
|
||||
} else {
|
||||
placeholder?.toAsyncDataImageHolder(
|
||||
imageProcessingQueue: imageProcessingQueue,
|
||||
decoder: decoder
|
||||
) ?? NilImageHolder()
|
||||
}
|
||||
return RemoteImageHolder(
|
||||
url: url,
|
||||
placeholder: placeholder,
|
||||
requester: requester,
|
||||
imageProcessingQueue: imageProcessingQueue,
|
||||
imageDecoder: { [weak self] in
|
||||
self?.svgDecoder.decode(data: $0)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -157,8 +157,14 @@ private final class VideoBlockView: BlockView, VisibleBoundsTrackingContainer {
|
||||
player?.set(isMuted: model.playbackConfig.isMuted)
|
||||
}
|
||||
|
||||
if let previewImage = model.preview {
|
||||
preview.value.image = previewImage
|
||||
if let preview = model.preview {
|
||||
if !compare(preview, oldValue.preview) {
|
||||
preview.requestImageWithCompletion { [weak self] image in
|
||||
self?.updatePreview(image)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
self.preview.value.image = nil
|
||||
}
|
||||
|
||||
if let elapsedTime = model.elapsedTime?.value,
|
||||
@@ -168,6 +174,11 @@ private final class VideoBlockView: BlockView, VisibleBoundsTrackingContainer {
|
||||
}
|
||||
}
|
||||
|
||||
private func updatePreview(_ image: Image?) {
|
||||
preview.value.image = image
|
||||
preview.value.frame = adjustPreviewFrame()
|
||||
}
|
||||
|
||||
private func adjustPreviewFrame() -> CGRect {
|
||||
guard let videoView,
|
||||
let videoRatio = videoView.videoRatio else {
|
||||
|
||||
@@ -5,7 +5,7 @@ public struct VideoBlockViewModel: Equatable {
|
||||
public let playbackConfig: PlaybackConfig
|
||||
public var elapsedTime: Binding<Int>?
|
||||
public var duration: Binding<Int>?
|
||||
public let preview: Image?
|
||||
public let preview: ImageHolder?
|
||||
public let resumeActions: [UserInterfaceAction]
|
||||
public let pauseActions: [UserInterfaceAction]
|
||||
public let bufferingActions: [UserInterfaceAction]
|
||||
@@ -18,7 +18,7 @@ public struct VideoBlockViewModel: Equatable {
|
||||
public init(
|
||||
videoData: VideoData,
|
||||
playbackConfig: PlaybackConfig,
|
||||
preview: Image? = nil,
|
||||
preview: ImageHolder? = nil,
|
||||
elapsedTime: Binding<Int>? = nil,
|
||||
duration: Binding<Int>? = nil,
|
||||
resumeActions: [UserInterfaceAction] = [],
|
||||
|
||||
Reference in New Issue
Block a user