mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-12-24 07:05:35 +00:00
Implement optional cached resource data streaming
This commit is contained in:
@@ -71,13 +71,24 @@ private protocol AnimatedStickerFrameSource: class {
|
||||
var frameRate: Int { get }
|
||||
var frameCount: Int { get }
|
||||
|
||||
func takeFrame() -> AnimatedStickerFrame
|
||||
func takeFrame() -> AnimatedStickerFrame?
|
||||
}
|
||||
|
||||
private final class AnimatedStickerFrameSourceWrapper {
|
||||
let value: AnimatedStickerFrameSource
|
||||
|
||||
init(_ value: AnimatedStickerFrameSource) {
|
||||
self.value = value
|
||||
}
|
||||
}
|
||||
|
||||
@available(iOS 9.0, *)
|
||||
private final class AnimatedStickerCachedFrameSource: AnimatedStickerFrameSource {
|
||||
private let queue: Queue
|
||||
private let data: Data
|
||||
private var data: Data
|
||||
private var dataComplete: Bool
|
||||
private let notifyUpdated: () -> Void
|
||||
|
||||
private var scratchBuffer: Data
|
||||
let width: Int
|
||||
let bytesPerRow: Int
|
||||
@@ -90,9 +101,11 @@ private final class AnimatedStickerCachedFrameSource: AnimatedStickerFrameSource
|
||||
var decodeBuffer: Data
|
||||
var frameBuffer: Data
|
||||
|
||||
init?(queue: Queue, data: Data) {
|
||||
init?(queue: Queue, data: Data, complete: Bool, notifyUpdated: @escaping () -> Void) {
|
||||
self.queue = queue
|
||||
self.data = data
|
||||
self.dataComplete = complete
|
||||
self.notifyUpdated = notifyUpdated
|
||||
self.scratchBuffer = Data(count: compression_decode_scratch_buffer_size(COMPRESSION_LZFSE))
|
||||
|
||||
var offset = 0
|
||||
@@ -152,7 +165,7 @@ private final class AnimatedStickerCachedFrameSource: AnimatedStickerFrameSource
|
||||
assert(self.queue.isCurrent())
|
||||
}
|
||||
|
||||
func takeFrame() -> AnimatedStickerFrame {
|
||||
func takeFrame() -> AnimatedStickerFrame? {
|
||||
var frameData: Data?
|
||||
var isLastFrame = false
|
||||
|
||||
@@ -163,8 +176,24 @@ private final class AnimatedStickerCachedFrameSource: AnimatedStickerFrameSource
|
||||
let frameIndex = self.frameIndex
|
||||
|
||||
self.data.withUnsafeBytes { (bytes: UnsafePointer<UInt8>) -> Void in
|
||||
if self.offset + 4 > dataLength {
|
||||
if self.dataComplete {
|
||||
self.frameIndex = 0
|
||||
self.offset = self.initialOffset
|
||||
self.frameBuffer.withUnsafeMutableBytes { (bytes: UnsafeMutablePointer<UInt8>) -> Void in
|
||||
memset(bytes, 0, frameBufferLength)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
var frameLength: Int32 = 0
|
||||
memcpy(&frameLength, bytes.advanced(by: self.offset), 4)
|
||||
|
||||
if self.offset + 4 + Int(frameLength) > dataLength {
|
||||
return
|
||||
}
|
||||
|
||||
self.offset += 4
|
||||
|
||||
self.scratchBuffer.withUnsafeMutableBytes { (scratchBytes: UnsafeMutablePointer<UInt8>) -> Void in
|
||||
@@ -194,7 +223,7 @@ private final class AnimatedStickerCachedFrameSource: AnimatedStickerFrameSource
|
||||
|
||||
self.frameIndex += 1
|
||||
self.offset += Int(frameLength)
|
||||
if self.offset == dataLength {
|
||||
if self.offset == dataLength && self.dataComplete {
|
||||
isLastFrame = true
|
||||
self.frameIndex = 0
|
||||
self.offset = self.initialOffset
|
||||
@@ -204,7 +233,16 @@ private final class AnimatedStickerCachedFrameSource: AnimatedStickerFrameSource
|
||||
}
|
||||
}
|
||||
|
||||
return AnimatedStickerFrame(data: frameData!, type: .yuva, width: self.width, height: self.height, bytesPerRow: self.bytesPerRow, index: frameIndex, isLastFrame: isLastFrame)
|
||||
if let frameData = frameData {
|
||||
return AnimatedStickerFrame(data: frameData, type: .yuva, width: self.width, height: self.height, bytesPerRow: self.bytesPerRow, index: frameIndex, isLastFrame: isLastFrame)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func updateData(data: Data, complete: Bool) {
|
||||
self.data = data
|
||||
self.dataComplete = complete
|
||||
}
|
||||
}
|
||||
|
||||
@@ -241,7 +279,7 @@ private final class AnimatedStickerDirectFrameSource: AnimatedStickerFrameSource
|
||||
assert(self.queue.isCurrent())
|
||||
}
|
||||
|
||||
func takeFrame() -> AnimatedStickerFrame {
|
||||
func takeFrame() -> AnimatedStickerFrame? {
|
||||
let frameIndex = self.currentFrame % self.frameCount
|
||||
self.currentFrame += 1
|
||||
var frameData = Data(count: self.bytesPerRow * self.height)
|
||||
@@ -271,15 +309,23 @@ private final class AnimatedStickerFrameQueue {
|
||||
|
||||
func take() -> AnimatedStickerFrame? {
|
||||
if self.frames.isEmpty {
|
||||
self.frames.append(self.source.takeFrame())
|
||||
if let frame = self.source.takeFrame() {
|
||||
self.frames.append(frame)
|
||||
}
|
||||
}
|
||||
if !self.frames.isEmpty {
|
||||
let frame = self.frames.removeFirst()
|
||||
return frame
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
let frame = self.frames.removeFirst()
|
||||
return frame
|
||||
}
|
||||
|
||||
func generateFramesIfNeeded() {
|
||||
if self.frames.isEmpty {
|
||||
self.frames.append(self.source.takeFrame())
|
||||
if let frame = self.source.takeFrame() {
|
||||
self.frames.append(frame)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -297,7 +343,7 @@ public struct AnimatedStickerStatus: Equatable {
|
||||
}
|
||||
|
||||
public protocol AnimatedStickerNodeSource {
|
||||
func cachedDataPath(width: Int, height: Int) -> Signal<String, NoError>
|
||||
func cachedDataPath(width: Int, height: Int) -> Signal<(String, Bool), NoError>
|
||||
func directDataPath() -> Signal<String, NoError>
|
||||
}
|
||||
|
||||
@@ -312,7 +358,7 @@ public final class AnimatedStickerNodeLocalFileSource: AnimatedStickerNodeSource
|
||||
return .single(self.path)
|
||||
}
|
||||
|
||||
public func cachedDataPath(width: Int, height: Int) -> Signal<String, NoError> {
|
||||
public func cachedDataPath(width: Int, height: Int) -> Signal<(String, Bool), NoError> {
|
||||
return .never()
|
||||
}
|
||||
}
|
||||
@@ -330,9 +376,10 @@ public final class AnimatedStickerNode: ASDisplayNode {
|
||||
private var reportedStarted = false
|
||||
|
||||
private let timer = Atomic<SwiftSignalKit.Timer?>(value: nil)
|
||||
private let frameSource = Atomic<QueueLocalObject<AnimatedStickerFrameSourceWrapper>?>(value: nil)
|
||||
|
||||
private var directData: (Data, String, Int, Int)?
|
||||
private var cachedData: Data?
|
||||
private var cachedData: (Data, Bool)?
|
||||
|
||||
private var renderer: (AnimationRenderer & ASDisplayNode)?
|
||||
|
||||
@@ -421,15 +468,30 @@ public final class AnimatedStickerNode: ASDisplayNode {
|
||||
}))
|
||||
case .cached:
|
||||
self.disposable.set((source.cachedDataPath(width: width, height: height)
|
||||
|> deliverOnMainQueue).start(next: { [weak self] path in
|
||||
|> deliverOnMainQueue).start(next: { [weak self] path, complete in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
strongSelf.cachedData = try? Data(contentsOf: URL(fileURLWithPath: path), options: [.mappedRead])
|
||||
if strongSelf.isPlaying {
|
||||
strongSelf.play()
|
||||
} else if strongSelf.canDisplayFirstFrame {
|
||||
strongSelf.play(firstFrame: true)
|
||||
if let data = try? Data(contentsOf: URL(fileURLWithPath: path), options: [.mappedRead]) {
|
||||
if let (_, currentComplete) = strongSelf.cachedData {
|
||||
if !currentComplete {
|
||||
strongSelf.cachedData = (data, complete)
|
||||
strongSelf.frameSource.with { frameSource in
|
||||
frameSource?.with { frameSource in
|
||||
if let frameSource = frameSource.value as? AnimatedStickerCachedFrameSource {
|
||||
frameSource.updateData(data: data, complete: complete)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
strongSelf.cachedData = (data, complete)
|
||||
if strongSelf.isPlaying {
|
||||
strongSelf.play()
|
||||
} else if strongSelf.canDisplayFirstFrame {
|
||||
strongSelf.play(firstFrame: true)
|
||||
}
|
||||
}
|
||||
}
|
||||
}))
|
||||
}
|
||||
@@ -464,15 +526,24 @@ public final class AnimatedStickerNode: ASDisplayNode {
|
||||
let cachedData = self.cachedData
|
||||
let queue = self.queue
|
||||
let timerHolder = self.timer
|
||||
let frameSourceHolder = self.frameSource
|
||||
self.queue.async { [weak self] in
|
||||
var maybeFrameSource: AnimatedStickerFrameSource?
|
||||
var notifyUpdated: (() -> Void)?
|
||||
if let directData = directData {
|
||||
maybeFrameSource = AnimatedStickerDirectFrameSource(queue: queue, data: directData.0, width: directData.2, height: directData.3)
|
||||
} else if let cachedData = cachedData {
|
||||
} else if let (cachedData, cachedDataComplete) = cachedData {
|
||||
if #available(iOS 9.0, *) {
|
||||
maybeFrameSource = AnimatedStickerCachedFrameSource(queue: queue, data: cachedData)
|
||||
maybeFrameSource = AnimatedStickerCachedFrameSource(queue: queue, data: cachedData, complete: cachedDataComplete, notifyUpdated: {
|
||||
notifyUpdated?()
|
||||
})
|
||||
}
|
||||
}
|
||||
let _ = frameSourceHolder.swap(maybeFrameSource.flatMap { maybeFrameSource in
|
||||
return QueueLocalObject(queue: queue, generate: {
|
||||
return AnimatedStickerFrameSourceWrapper(maybeFrameSource)
|
||||
})
|
||||
})
|
||||
guard let frameSource = maybeFrameSource else {
|
||||
return
|
||||
}
|
||||
@@ -541,9 +612,9 @@ public final class AnimatedStickerNode: ASDisplayNode {
|
||||
var maybeFrameSource: AnimatedStickerFrameSource?
|
||||
if let directData = directData {
|
||||
maybeFrameSource = AnimatedStickerDirectFrameSource(queue: queue, data: directData.0, width: directData.2, height: directData.3)
|
||||
} else if let cachedData = cachedData {
|
||||
} else if let (cachedData, cachedDataComplete) = cachedData {
|
||||
if #available(iOS 9.0, *) {
|
||||
maybeFrameSource = AnimatedStickerCachedFrameSource(queue: queue, data: cachedData)
|
||||
maybeFrameSource = AnimatedStickerCachedFrameSource(queue: queue, data: cachedData, complete: cachedDataComplete, notifyUpdated: {})
|
||||
}
|
||||
}
|
||||
guard let frameSource = maybeFrameSource else {
|
||||
|
||||
Reference in New Issue
Block a user