Implement optional cached resource data streaming

This commit is contained in:
Ali 2019-12-24 20:22:38 +04:00
parent 2ec22db1ff
commit e868873425
9 changed files with 233 additions and 79 deletions

View File

@ -71,13 +71,24 @@ private protocol AnimatedStickerFrameSource: class {
var frameRate: Int { get } var frameRate: Int { get }
var frameCount: 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, *) @available(iOS 9.0, *)
private final class AnimatedStickerCachedFrameSource: AnimatedStickerFrameSource { private final class AnimatedStickerCachedFrameSource: AnimatedStickerFrameSource {
private let queue: Queue private let queue: Queue
private let data: Data private var data: Data
private var dataComplete: Bool
private let notifyUpdated: () -> Void
private var scratchBuffer: Data private var scratchBuffer: Data
let width: Int let width: Int
let bytesPerRow: Int let bytesPerRow: Int
@ -90,9 +101,11 @@ private final class AnimatedStickerCachedFrameSource: AnimatedStickerFrameSource
var decodeBuffer: Data var decodeBuffer: Data
var frameBuffer: Data var frameBuffer: Data
init?(queue: Queue, data: Data) { init?(queue: Queue, data: Data, complete: Bool, notifyUpdated: @escaping () -> Void) {
self.queue = queue self.queue = queue
self.data = data self.data = data
self.dataComplete = complete
self.notifyUpdated = notifyUpdated
self.scratchBuffer = Data(count: compression_decode_scratch_buffer_size(COMPRESSION_LZFSE)) self.scratchBuffer = Data(count: compression_decode_scratch_buffer_size(COMPRESSION_LZFSE))
var offset = 0 var offset = 0
@ -152,7 +165,7 @@ private final class AnimatedStickerCachedFrameSource: AnimatedStickerFrameSource
assert(self.queue.isCurrent()) assert(self.queue.isCurrent())
} }
func takeFrame() -> AnimatedStickerFrame { func takeFrame() -> AnimatedStickerFrame? {
var frameData: Data? var frameData: Data?
var isLastFrame = false var isLastFrame = false
@ -163,8 +176,24 @@ private final class AnimatedStickerCachedFrameSource: AnimatedStickerFrameSource
let frameIndex = self.frameIndex let frameIndex = self.frameIndex
self.data.withUnsafeBytes { (bytes: UnsafePointer<UInt8>) -> Void in 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 var frameLength: Int32 = 0
memcpy(&frameLength, bytes.advanced(by: self.offset), 4) memcpy(&frameLength, bytes.advanced(by: self.offset), 4)
if self.offset + 4 + Int(frameLength) > dataLength {
return
}
self.offset += 4 self.offset += 4
self.scratchBuffer.withUnsafeMutableBytes { (scratchBytes: UnsafeMutablePointer<UInt8>) -> Void in self.scratchBuffer.withUnsafeMutableBytes { (scratchBytes: UnsafeMutablePointer<UInt8>) -> Void in
@ -194,7 +223,7 @@ private final class AnimatedStickerCachedFrameSource: AnimatedStickerFrameSource
self.frameIndex += 1 self.frameIndex += 1
self.offset += Int(frameLength) self.offset += Int(frameLength)
if self.offset == dataLength { if self.offset == dataLength && self.dataComplete {
isLastFrame = true isLastFrame = true
self.frameIndex = 0 self.frameIndex = 0
self.offset = self.initialOffset 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()) assert(self.queue.isCurrent())
} }
func takeFrame() -> AnimatedStickerFrame { func takeFrame() -> AnimatedStickerFrame? {
let frameIndex = self.currentFrame % self.frameCount let frameIndex = self.currentFrame % self.frameCount
self.currentFrame += 1 self.currentFrame += 1
var frameData = Data(count: self.bytesPerRow * self.height) var frameData = Data(count: self.bytesPerRow * self.height)
@ -271,15 +309,23 @@ private final class AnimatedStickerFrameQueue {
func take() -> AnimatedStickerFrame? { func take() -> AnimatedStickerFrame? {
if self.frames.isEmpty { 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() let frame = self.frames.removeFirst()
return frame return frame
} else {
return nil
}
} }
func generateFramesIfNeeded() { func generateFramesIfNeeded() {
if self.frames.isEmpty { 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 { 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> func directDataPath() -> Signal<String, NoError>
} }
@ -312,7 +358,7 @@ public final class AnimatedStickerNodeLocalFileSource: AnimatedStickerNodeSource
return .single(self.path) 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() return .never()
} }
} }
@ -330,9 +376,10 @@ public final class AnimatedStickerNode: ASDisplayNode {
private var reportedStarted = false private var reportedStarted = false
private let timer = Atomic<SwiftSignalKit.Timer?>(value: nil) 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 directData: (Data, String, Int, Int)?
private var cachedData: Data? private var cachedData: (Data, Bool)?
private var renderer: (AnimationRenderer & ASDisplayNode)? private var renderer: (AnimationRenderer & ASDisplayNode)?
@ -421,16 +468,31 @@ public final class AnimatedStickerNode: ASDisplayNode {
})) }))
case .cached: case .cached:
self.disposable.set((source.cachedDataPath(width: width, height: height) 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 { guard let strongSelf = self else {
return return
} }
strongSelf.cachedData = try? Data(contentsOf: URL(fileURLWithPath: path), options: [.mappedRead]) 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 { if strongSelf.isPlaying {
strongSelf.play() strongSelf.play()
} else if strongSelf.canDisplayFirstFrame { } else if strongSelf.canDisplayFirstFrame {
strongSelf.play(firstFrame: true) strongSelf.play(firstFrame: true)
} }
}
}
})) }))
} }
} }
@ -464,15 +526,24 @@ public final class AnimatedStickerNode: ASDisplayNode {
let cachedData = self.cachedData let cachedData = self.cachedData
let queue = self.queue let queue = self.queue
let timerHolder = self.timer let timerHolder = self.timer
let frameSourceHolder = self.frameSource
self.queue.async { [weak self] in self.queue.async { [weak self] in
var maybeFrameSource: AnimatedStickerFrameSource? var maybeFrameSource: AnimatedStickerFrameSource?
var notifyUpdated: (() -> Void)?
if let directData = directData { if let directData = directData {
maybeFrameSource = AnimatedStickerDirectFrameSource(queue: queue, data: directData.0, width: directData.2, height: directData.3) 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, *) { 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 { guard let frameSource = maybeFrameSource else {
return return
} }
@ -541,9 +612,9 @@ public final class AnimatedStickerNode: ASDisplayNode {
var maybeFrameSource: AnimatedStickerFrameSource? var maybeFrameSource: AnimatedStickerFrameSource?
if let directData = directData { if let directData = directData {
maybeFrameSource = AnimatedStickerDirectFrameSource(queue: queue, data: directData.0, width: directData.2, height: directData.3) 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, *) { 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 { guard let frameSource = maybeFrameSource else {

View File

@ -148,7 +148,7 @@ public final class WriteBuffer: MemoryBuffer {
self.offset = 0 self.offset = 0
} }
public func write(_ data: UnsafeRawPointer, offset: Int, length: Int) { public func write(_ data: UnsafeRawPointer, offset: Int = 0, length: Int) {
if self.offset + length > self.capacity { if self.offset + length > self.capacity {
self.capacity = self.offset + length + 256 self.capacity = self.offset + length + 256
if self.length == 0 { if self.length == 0 {

View File

@ -85,6 +85,9 @@ public enum MediaResourceDataFetchError {
} }
public enum CachedMediaResourceRepresentationResult { public enum CachedMediaResourceRepresentationResult {
case reset
case data(Data)
case done
case temporaryPath(String) case temporaryPath(String)
case tempFile(TempBoxFile) case tempFile(TempBoxFile)
} }
@ -107,9 +110,19 @@ private struct CachedMediaResourceRepresentationKey: Hashable {
} }
} }
private final class CachedMediaResourceRepresentationSubscriber {
let update: (MediaResourceData) -> Void
let onlyComplete: Bool
init(update: @escaping (MediaResourceData) -> Void, onlyComplete: Bool) {
self.update = update
self.onlyComplete = onlyComplete
}
}
private final class CachedMediaResourceRepresentationContext { private final class CachedMediaResourceRepresentationContext {
var currentData: MediaResourceData? var currentData: MediaResourceData?
let dataSubscribers = Bag<(MediaResourceData) -> Void>() let dataSubscribers = Bag<CachedMediaResourceRepresentationSubscriber>()
let disposable = MetaDisposable() let disposable = MetaDisposable()
var initialized = false var initialized = false
} }
@ -191,7 +204,7 @@ public final class MediaBox {
return ResourceStorePaths(partial: "\(self.basePath)/\(fileNameForId(id))_partial", complete: "\(self.basePath)/\(fileNameForId(id))") return ResourceStorePaths(partial: "\(self.basePath)/\(fileNameForId(id))_partial", complete: "\(self.basePath)/\(fileNameForId(id))")
} }
private func cachedRepresentationPathForId(_ id: MediaResourceId, representation: CachedMediaResourceRepresentation) -> String { private func cachedRepresentationPathsForId(_ id: MediaResourceId, representation: CachedMediaResourceRepresentation) -> ResourceStorePaths {
let cacheString: String let cacheString: String
switch representation.keepDuration { switch representation.keepDuration {
case .general: case .general:
@ -199,7 +212,7 @@ public final class MediaBox {
case .shortLived: case .shortLived:
cacheString = "short-cache" cacheString = "short-cache"
} }
return "\(self.basePath)/\(cacheString)/\(fileNameForId(id)):\(representation.uniqueId)" return ResourceStorePaths(partial: "\(self.basePath)/\(cacheString)/\(fileNameForId(id))_partial:\(representation.uniqueId)", complete: "\(self.basePath)/\(cacheString)/\(fileNameForId(id)):\(representation.uniqueId)")
} }
public func storeResourceData(_ id: MediaResourceId, data: Data, synchronous: Bool = false) { public func storeResourceData(_ id: MediaResourceId, data: Data, synchronous: Bool = false) {
@ -662,7 +675,7 @@ public final class MediaBox {
public func storeCachedResourceRepresentation(_ resource: MediaResource, representation: CachedMediaResourceRepresentation, data: Data) { public func storeCachedResourceRepresentation(_ resource: MediaResource, representation: CachedMediaResourceRepresentation, data: Data) {
self.dataQueue.async { self.dataQueue.async {
let path = self.cachedRepresentationPathForId(resource.id, representation: representation) let path = self.cachedRepresentationPathsForId(resource.id, representation: representation).complete
let _ = try? data.write(to: URL(fileURLWithPath: path)) let _ = try? data.write(to: URL(fileURLWithPath: path))
} }
} }
@ -672,26 +685,26 @@ public final class MediaBox {
let disposable = MetaDisposable() let disposable = MetaDisposable()
let begin: () -> Void = { let begin: () -> Void = {
let path = self.cachedRepresentationPathForId(resource.id, representation: representation) let paths = self.cachedRepresentationPathsForId(resource.id, representation: representation)
if let size = fileSize(path) { if let size = fileSize(paths.complete) {
self.timeBasedCleanup.touch(paths: [ self.timeBasedCleanup.touch(paths: [
path paths.complete
]) ])
if let pathExtension = pathExtension { if let pathExtension = pathExtension {
let symlinkPath = path + ".\(pathExtension)" let symlinkPath = paths.complete + ".\(pathExtension)"
if fileSize(symlinkPath) == nil { if fileSize(symlinkPath) == nil {
let _ = try? FileManager.default.linkItem(atPath: path, toPath: symlinkPath) let _ = try? FileManager.default.linkItem(atPath: paths.complete, toPath: symlinkPath)
} }
subscriber.putNext(MediaResourceData(path: symlinkPath, offset: 0, size: size, complete: true)) subscriber.putNext(MediaResourceData(path: symlinkPath, offset: 0, size: size, complete: true))
subscriber.putCompletion() subscriber.putCompletion()
} else { } else {
subscriber.putNext(MediaResourceData(path: path, offset: 0, size: size, complete: true)) subscriber.putNext(MediaResourceData(path: paths.complete, offset: 0, size: size, complete: true))
subscriber.putCompletion() subscriber.putCompletion()
} }
} else if fetch { } else if fetch {
if attemptSynchronously && complete { if attemptSynchronously && complete {
subscriber.putNext(MediaResourceData(path: path, offset: 0, size: 0, complete: false)) subscriber.putNext(MediaResourceData(path: paths.partial, offset: 0, size: 0, complete: false))
} }
self.dataQueue.async { self.dataQueue.async {
let key = CachedMediaResourceRepresentationKey(resourceId: resource.id, representation: representation) let key = CachedMediaResourceRepresentationKey(resourceId: resource.id, representation: representation)
@ -703,7 +716,7 @@ public final class MediaBox {
self.cachedRepresentationContexts[key] = context self.cachedRepresentationContexts[key] = context
} }
let index = context.dataSubscribers.add(({ data in let index = context.dataSubscribers.add(CachedMediaResourceRepresentationSubscriber(update: { data in
if !complete || data.complete { if !complete || data.complete {
if let pathExtension = pathExtension, data.complete { if let pathExtension = pathExtension, data.complete {
let symlinkPath = data.path + ".\(pathExtension)" let symlinkPath = data.path + ".\(pathExtension)"
@ -718,7 +731,7 @@ public final class MediaBox {
if data.complete { if data.complete {
subscriber.putCompletion() subscriber.putCompletion()
} }
})) }, onlyComplete: complete))
if let currentData = context.currentData { if let currentData = context.currentData {
if !complete || currentData.complete { if !complete || currentData.complete {
subscriber.putNext(currentData) subscriber.putNext(currentData)
@ -727,7 +740,7 @@ public final class MediaBox {
subscriber.putCompletion() subscriber.putCompletion()
} }
} else if !complete { } else if !complete {
subscriber.putNext(MediaResourceData(path: path, offset: 0, size: 0, complete: false)) subscriber.putNext(MediaResourceData(path: paths.partial, offset: 0, size: 0, complete: false))
} }
disposable.set(ActionDisposable { [weak context] in disposable.set(ActionDisposable { [weak context] in
@ -752,32 +765,65 @@ public final class MediaBox {
} }
|> deliverOn(self.dataQueue) |> deliverOn(self.dataQueue)
context.disposable.set(signal.start(next: { [weak self, weak context] next in context.disposable.set(signal.start(next: { [weak self, weak context] next in
guard let strongSelf = self else {
return
}
if let next = next { if let next = next {
var isDone = false
switch next { switch next {
case let .temporaryPath(temporaryPath): case let .temporaryPath(temporaryPath):
rename(temporaryPath, path) rename(temporaryPath, paths.complete)
isDone = true
case let .tempFile(tempFile): case let .tempFile(tempFile):
rename(tempFile.path, path) rename(tempFile.path, paths.complete)
TempBox.shared.dispose(tempFile) TempBox.shared.dispose(tempFile)
isDone = true
case .reset:
let file = ManagedFile(queue: strongSelf.dataQueue, path: paths.partial, mode: .readwrite)
file?.truncate(count: 0)
unlink(paths.complete)
case let .data(dataPart):
let file = ManagedFile(queue: strongSelf.dataQueue, path: paths.partial, mode: .append)
let dataCount = dataPart.count
dataPart.withUnsafeBytes { (bytes: UnsafePointer<UInt8>) -> Void in
file?.write(bytes, count: dataCount)
}
case .done:
link(paths.partial, paths.complete)
isDone = true
} }
if let strongSelf = self, let currentContext = strongSelf.cachedRepresentationContexts[key], currentContext === context { if let strongSelf = self, let currentContext = strongSelf.cachedRepresentationContexts[key], currentContext === context {
if isDone {
currentContext.disposable.dispose() currentContext.disposable.dispose()
strongSelf.cachedRepresentationContexts.removeValue(forKey: key) strongSelf.cachedRepresentationContexts.removeValue(forKey: key)
if let size = fileSize(path) { }
let data = MediaResourceData(path: path, offset: 0, size: size, complete: true) if let size = fileSize(paths.complete) {
let data = MediaResourceData(path: paths.complete, offset: 0, size: size, complete: isDone)
currentContext.currentData = data currentContext.currentData = data
for subscriber in currentContext.dataSubscribers.copyItems() { for subscriber in currentContext.dataSubscribers.copyItems() {
subscriber(data) if !subscriber.onlyComplete || isDone {
subscriber.update(data)
}
}
} else if let size = fileSize(paths.partial) {
let data = MediaResourceData(path: paths.partial, offset: 0, size: size, complete: isDone)
currentContext.currentData = data
for subscriber in currentContext.dataSubscribers.copyItems() {
if !subscriber.onlyComplete || isDone {
subscriber.update(data)
}
} }
} }
} }
} else { } else {
if let strongSelf = self, let context = strongSelf.cachedRepresentationContexts[key] { if let strongSelf = self, let context = strongSelf.cachedRepresentationContexts[key] {
let data = MediaResourceData(path: path, offset: 0, size: 0, complete: false) let data = MediaResourceData(path: paths.partial, offset: 0, size: 0, complete: false)
context.currentData = data context.currentData = data
for subscriber in context.dataSubscribers.copyItems() { for subscriber in context.dataSubscribers.copyItems() {
subscriber(data) if !subscriber.onlyComplete {
subscriber.update(data)
}
} }
} }
} }
@ -785,7 +831,7 @@ public final class MediaBox {
} }
} }
} else { } else {
subscriber.putNext(MediaResourceData(path: path, offset: 0, size: 0, complete: false)) subscriber.putNext(MediaResourceData(path: paths.partial, offset: 0, size: 0, complete: false))
subscriber.putCompletion() subscriber.putCompletion()
} }
} }

View File

@ -166,8 +166,28 @@ public final class StickerPackPreviewController: ViewController, StandalonePrese
} }
switch next { switch next {
case let .result(_, items, _): case let .result(info, items, _):
var preloadSignals: [Signal<Bool, NoError>] = [] var preloadSignals: [Signal<Bool, NoError>] = []
if let thumbnail = info.thumbnail {
let signal = Signal<Bool, NoError> { subscriber in
let fetched = fetchedMediaResource(mediaBox: account.postbox.mediaBox, reference: .stickerPackThumbnail(stickerPack: .id(id: info.id.id, accessHash: info.accessHash), resource: thumbnail.resource)).start()
let data = account.postbox.mediaBox.resourceData(thumbnail.resource, option: .incremental(waitUntilFetchStatus: false)).start(next: { data in
if data.complete {
subscriber.putNext(true)
subscriber.putCompletion()
} else {
subscriber.putNext(false)
}
})
return ActionDisposable {
fetched.dispose()
data.dispose()
}
}
preloadSignals.append(signal)
}
let topItems = items.prefix(16) let topItems = items.prefix(16)
for item in topItems { for item in topItems {
if let item = item as? StickerPackItem, item.file.isAnimatedSticker { if let item = item as? StickerPackItem, item.file.isAnimatedSticker {

View File

@ -183,13 +183,12 @@ private let threadPool: ThreadPool = {
}() }()
@available(iOS 9.0, *) @available(iOS 9.0, *)
public func experimentalConvertCompressedLottieToCombinedMp4(data: Data, size: CGSize, fitzModifier: EmojiFitzModifier? = nil, cacheKey: String) -> Signal<String, NoError> { public func experimentalConvertCompressedLottieToCombinedMp4(data: Data, size: CGSize, fitzModifier: EmojiFitzModifier? = nil, cacheKey: String) -> Signal<Data, NoError> {
return Signal({ subscriber in return Signal({ subscriber in
let cancelled = Atomic<Bool>(value: false) let cancelled = Atomic<Bool>(value: false)
threadPool.addTask(ThreadPoolTask({ _ in threadPool.addTask(ThreadPoolTask({ _ in
if cancelled.with({ $0 }) { if cancelled.with({ $0 }) {
//print("cancelled 1")
return return
} }
@ -206,12 +205,6 @@ public func experimentalConvertCompressedLottieToCombinedMp4(data: Data, size: C
let endFrame = Int(player.frameCount) let endFrame = Int(player.frameCount)
if cancelled.with({ $0 }) { if cancelled.with({ $0 }) {
//print("cancelled 2")
return
}
let path = NSTemporaryDirectory() + "\(arc4random64()).lz4v"
guard let fileContext = ManagedFile(queue: nil, path: path, mode: .readwrite) else {
return return
} }
@ -219,16 +212,19 @@ public func experimentalConvertCompressedLottieToCombinedMp4(data: Data, size: C
var currentFrame: Int32 = 0 var currentFrame: Int32 = 0
let writeBuffer = WriteBuffer()
var numberOfFramesCommitted = 0
var fps: Int32 = player.frameRate var fps: Int32 = player.frameRate
var frameCount: Int32 = player.frameCount var frameCount: Int32 = player.frameCount
let _ = fileContext.write(&fps, count: 4) writeBuffer.write(&fps, length: 4)
let _ = fileContext.write(&frameCount, count: 4) writeBuffer.write(&frameCount, length: 4)
var widthValue: Int32 = Int32(size.width) var widthValue: Int32 = Int32(size.width)
var heightValue: Int32 = Int32(size.height) var heightValue: Int32 = Int32(size.height)
var bytesPerRowValue: Int32 = Int32(bytesPerRow) var bytesPerRowValue: Int32 = Int32(bytesPerRow)
let _ = fileContext.write(&widthValue, count: 4) writeBuffer.write(&widthValue, length: 4)
let _ = fileContext.write(&heightValue, count: 4) writeBuffer.write(&heightValue, length: 4)
let _ = fileContext.write(&bytesPerRowValue, count: 4) writeBuffer.write(&bytesPerRowValue, length: 4)
let frameLength = bytesPerRow * Int(size.height) let frameLength = bytesPerRow * Int(size.height)
assert(frameLength % 16 == 0) assert(frameLength % 16 == 0)
@ -262,7 +258,6 @@ public func experimentalConvertCompressedLottieToCombinedMp4(data: Data, size: C
while currentFrame < endFrame { while currentFrame < endFrame {
if cancelled.with({ $0 }) { if cancelled.with({ $0 }) {
//print("cancelled 3")
return return
} }
@ -298,8 +293,8 @@ public func experimentalConvertCompressedLottieToCombinedMp4(data: Data, size: C
compressedFrameData.withUnsafeMutableBytes { (bytes: UnsafeMutablePointer<UInt8>) -> Void in compressedFrameData.withUnsafeMutableBytes { (bytes: UnsafeMutablePointer<UInt8>) -> Void in
let length = compression_encode_buffer(bytes, compressedFrameDataLength, previousYuvaFrameData.assumingMemoryBound(to: UInt8.self), yuvaLength, scratchData, COMPRESSION_LZFSE) let length = compression_encode_buffer(bytes, compressedFrameDataLength, previousYuvaFrameData.assumingMemoryBound(to: UInt8.self), yuvaLength, scratchData, COMPRESSION_LZFSE)
var frameLengthValue: Int32 = Int32(length) var frameLengthValue: Int32 = Int32(length)
let _ = fileContext.write(&frameLengthValue, count: 4) writeBuffer.write(&frameLengthValue, length: 4)
let _ = fileContext.write(bytes, count: length) writeBuffer.write(bytes, length: length)
} }
let tmp = previousYuvaFrameData let tmp = previousYuvaFrameData
@ -309,16 +304,33 @@ public func experimentalConvertCompressedLottieToCombinedMp4(data: Data, size: C
compressionTime += CACurrentMediaTime() - compressionStartTime compressionTime += CACurrentMediaTime() - compressionStartTime
currentFrame += 1 currentFrame += 1
numberOfFramesCommitted += 1
if numberOfFramesCommitted >= 5 {
numberOfFramesCommitted = 0
subscriber.putNext(writeBuffer.makeData())
writeBuffer.reset()
/*#if DEBUG
usleep(500000)
#endif*/
}
}
if writeBuffer.length != 0 {
subscriber.putNext(writeBuffer.makeData())
} }
subscriber.putNext(path)
subscriber.putCompletion() subscriber.putCompletion()
print("animation render time \(CACurrentMediaTime() - startTime)") /*print("animation render time \(CACurrentMediaTime() - startTime)")
print("of which drawing time \(drawingTime)") print("of which drawing time \(drawingTime)")
print("of which appending time \(appendingTime)") print("of which appending time \(appendingTime)")
print("of which delta time \(deltaTime)") print("of which delta time \(deltaTime)")
print("of which compression time \(compressionTime)") print("of which compression time \(compressionTime)")*/
} }
} }
})) }))

View File

@ -18,13 +18,13 @@ public final class AnimatedStickerResourceSource: AnimatedStickerNodeSource {
self.fitzModifier = fitzModifier self.fitzModifier = fitzModifier
} }
public func cachedDataPath(width: Int, height: Int) -> Signal<String, NoError> { public func cachedDataPath(width: Int, height: Int) -> Signal<(String, Bool), NoError> {
return chatMessageAnimationData(postbox: self.account.postbox, resource: self.resource, fitzModifier: self.fitzModifier, width: width, height: height, synchronousLoad: false) return chatMessageAnimationData(postbox: self.account.postbox, resource: self.resource, fitzModifier: self.fitzModifier, width: width, height: height, synchronousLoad: false)
|> filter { data in |> filter { data in
return data.complete return data.size != 0
} }
|> map { data -> String in |> map { data -> (String, Bool) in
return data.path return (data.path, data.complete)
} }
} }

View File

@ -855,8 +855,11 @@ private func fetchAnimatedStickerRepresentation(account: Account, resource: Medi
return Signal({ subscriber in return Signal({ subscriber in
if let data = try? Data(contentsOf: URL(fileURLWithPath: resourceData.path), options: [.mappedIfSafe]) { if let data = try? Data(contentsOf: URL(fileURLWithPath: resourceData.path), options: [.mappedIfSafe]) {
if #available(iOS 9.0, *) { if #available(iOS 9.0, *) {
return experimentalConvertCompressedLottieToCombinedMp4(data: data, size: CGSize(width: CGFloat(representation.width), height: CGFloat(representation.height)), fitzModifier: representation.fitzModifier, cacheKey: "\(resource.id.uniqueId)-\(representation.uniqueId)").start(next: { path in subscriber.putNext(.reset)
subscriber.putNext(.temporaryPath(path)) return experimentalConvertCompressedLottieToCombinedMp4(data: data, size: CGSize(width: CGFloat(representation.width), height: CGFloat(representation.height)), fitzModifier: representation.fitzModifier, cacheKey: "\(resource.id.uniqueId)-\(representation.uniqueId)").start(next: { data in
subscriber.putNext(.data(data))
}, completed: {
subscriber.putNext(.done)
subscriber.putCompletion() subscriber.putCompletion()
}) })
} else { } else {

View File

@ -148,7 +148,7 @@ class StickerPanePeerSpecificSetupGridItemNode: GridItemNode {
let textSpacing: CGFloat = 3.0 let textSpacing: CGFloat = 3.0
let buttonSpacing: CGFloat = 6.0 let buttonSpacing: CGFloat = 6.0
let (installLayout, installApply) = makeInstallLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.strings.Stickers_GroupChooseStickerPack, font: buttonFont, textColor: item.theme.list.itemAccentColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - params.leftInset - params.rightInset - leftInset - rightInset, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) let (installLayout, installApply) = makeInstallLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.strings.Stickers_GroupChooseStickerPack, font: buttonFont, textColor: item.theme.list.itemCheckColors.foregroundColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - params.leftInset - params.rightInset - leftInset - rightInset, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.strings.Stickers_GroupStickers.uppercased(), font: titleFont, textColor: item.theme.chat.inputMediaPanel.stickersSectionTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - params.leftInset - params.rightInset - leftInset - rightInset - 20.0 - installLayout.size.width, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.strings.Stickers_GroupStickers.uppercased(), font: titleFont, textColor: item.theme.chat.inputMediaPanel.stickersSectionTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - params.leftInset - params.rightInset - leftInset - rightInset - 20.0 - installLayout.size.width, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))

View File

@ -530,6 +530,8 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
}) })
} }
self.animatedStickerNode?.visibility = true
self.checkTimer() self.checkTimer()
} }