mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Implement optional cached resource data streaming
This commit is contained in:
parent
2ec22db1ff
commit
e868873425
@ -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 {
|
||||
|
@ -148,7 +148,7 @@ public final class WriteBuffer: MemoryBuffer {
|
||||
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 {
|
||||
self.capacity = self.offset + length + 256
|
||||
if self.length == 0 {
|
||||
|
@ -85,6 +85,9 @@ public enum MediaResourceDataFetchError {
|
||||
}
|
||||
|
||||
public enum CachedMediaResourceRepresentationResult {
|
||||
case reset
|
||||
case data(Data)
|
||||
case done
|
||||
case temporaryPath(String)
|
||||
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 {
|
||||
var currentData: MediaResourceData?
|
||||
let dataSubscribers = Bag<(MediaResourceData) -> Void>()
|
||||
let dataSubscribers = Bag<CachedMediaResourceRepresentationSubscriber>()
|
||||
let disposable = MetaDisposable()
|
||||
var initialized = false
|
||||
}
|
||||
@ -191,7 +204,7 @@ public final class MediaBox {
|
||||
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
|
||||
switch representation.keepDuration {
|
||||
case .general:
|
||||
@ -199,7 +212,7 @@ public final class MediaBox {
|
||||
case .shortLived:
|
||||
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) {
|
||||
@ -662,7 +675,7 @@ public final class MediaBox {
|
||||
|
||||
public func storeCachedResourceRepresentation(_ resource: MediaResource, representation: CachedMediaResourceRepresentation, data: Data) {
|
||||
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))
|
||||
}
|
||||
}
|
||||
@ -672,26 +685,26 @@ public final class MediaBox {
|
||||
let disposable = MetaDisposable()
|
||||
|
||||
let begin: () -> Void = {
|
||||
let path = self.cachedRepresentationPathForId(resource.id, representation: representation)
|
||||
if let size = fileSize(path) {
|
||||
let paths = self.cachedRepresentationPathsForId(resource.id, representation: representation)
|
||||
if let size = fileSize(paths.complete) {
|
||||
self.timeBasedCleanup.touch(paths: [
|
||||
path
|
||||
paths.complete
|
||||
])
|
||||
|
||||
if let pathExtension = pathExtension {
|
||||
let symlinkPath = path + ".\(pathExtension)"
|
||||
let symlinkPath = paths.complete + ".\(pathExtension)"
|
||||
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.putCompletion()
|
||||
} 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()
|
||||
}
|
||||
} else if fetch {
|
||||
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 {
|
||||
let key = CachedMediaResourceRepresentationKey(resourceId: resource.id, representation: representation)
|
||||
@ -703,7 +716,7 @@ public final class MediaBox {
|
||||
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 let pathExtension = pathExtension, data.complete {
|
||||
let symlinkPath = data.path + ".\(pathExtension)"
|
||||
@ -718,7 +731,7 @@ public final class MediaBox {
|
||||
if data.complete {
|
||||
subscriber.putCompletion()
|
||||
}
|
||||
}))
|
||||
}, onlyComplete: complete))
|
||||
if let currentData = context.currentData {
|
||||
if !complete || currentData.complete {
|
||||
subscriber.putNext(currentData)
|
||||
@ -727,7 +740,7 @@ public final class MediaBox {
|
||||
subscriber.putCompletion()
|
||||
}
|
||||
} 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
|
||||
@ -752,32 +765,65 @@ public final class MediaBox {
|
||||
}
|
||||
|> deliverOn(self.dataQueue)
|
||||
context.disposable.set(signal.start(next: { [weak self, weak context] next in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
if let next = next {
|
||||
var isDone = false
|
||||
switch next {
|
||||
case let .temporaryPath(temporaryPath):
|
||||
rename(temporaryPath, path)
|
||||
case let .tempFile(tempFile):
|
||||
rename(tempFile.path, path)
|
||||
TempBox.shared.dispose(tempFile)
|
||||
case let .temporaryPath(temporaryPath):
|
||||
rename(temporaryPath, paths.complete)
|
||||
isDone = true
|
||||
case let .tempFile(tempFile):
|
||||
rename(tempFile.path, paths.complete)
|
||||
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 {
|
||||
currentContext.disposable.dispose()
|
||||
strongSelf.cachedRepresentationContexts.removeValue(forKey: key)
|
||||
if let size = fileSize(path) {
|
||||
let data = MediaResourceData(path: path, offset: 0, size: size, complete: true)
|
||||
if isDone {
|
||||
currentContext.disposable.dispose()
|
||||
strongSelf.cachedRepresentationContexts.removeValue(forKey: key)
|
||||
}
|
||||
if let size = fileSize(paths.complete) {
|
||||
let data = MediaResourceData(path: paths.complete, offset: 0, size: size, complete: isDone)
|
||||
currentContext.currentData = data
|
||||
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 {
|
||||
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
|
||||
for subscriber in context.dataSubscribers.copyItems() {
|
||||
subscriber(data)
|
||||
if !subscriber.onlyComplete {
|
||||
subscriber.update(data)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -785,7 +831,7 @@ public final class MediaBox {
|
||||
}
|
||||
}
|
||||
} 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()
|
||||
}
|
||||
}
|
||||
|
@ -166,8 +166,28 @@ public final class StickerPackPreviewController: ViewController, StandalonePrese
|
||||
}
|
||||
|
||||
switch next {
|
||||
case let .result(_, items, _):
|
||||
case let .result(info, items, _):
|
||||
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)
|
||||
for item in topItems {
|
||||
if let item = item as? StickerPackItem, item.file.isAnimatedSticker {
|
||||
|
@ -183,13 +183,12 @@ private let threadPool: ThreadPool = {
|
||||
}()
|
||||
|
||||
@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
|
||||
let cancelled = Atomic<Bool>(value: false)
|
||||
|
||||
threadPool.addTask(ThreadPoolTask({ _ in
|
||||
if cancelled.with({ $0 }) {
|
||||
//print("cancelled 1")
|
||||
return
|
||||
}
|
||||
|
||||
@ -206,12 +205,6 @@ public func experimentalConvertCompressedLottieToCombinedMp4(data: Data, size: C
|
||||
let endFrame = Int(player.frameCount)
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
@ -219,16 +212,19 @@ public func experimentalConvertCompressedLottieToCombinedMp4(data: Data, size: C
|
||||
|
||||
var currentFrame: Int32 = 0
|
||||
|
||||
let writeBuffer = WriteBuffer()
|
||||
var numberOfFramesCommitted = 0
|
||||
|
||||
var fps: Int32 = player.frameRate
|
||||
var frameCount: Int32 = player.frameCount
|
||||
let _ = fileContext.write(&fps, count: 4)
|
||||
let _ = fileContext.write(&frameCount, count: 4)
|
||||
writeBuffer.write(&fps, length: 4)
|
||||
writeBuffer.write(&frameCount, length: 4)
|
||||
var widthValue: Int32 = Int32(size.width)
|
||||
var heightValue: Int32 = Int32(size.height)
|
||||
var bytesPerRowValue: Int32 = Int32(bytesPerRow)
|
||||
let _ = fileContext.write(&widthValue, count: 4)
|
||||
let _ = fileContext.write(&heightValue, count: 4)
|
||||
let _ = fileContext.write(&bytesPerRowValue, count: 4)
|
||||
writeBuffer.write(&widthValue, length: 4)
|
||||
writeBuffer.write(&heightValue, length: 4)
|
||||
writeBuffer.write(&bytesPerRowValue, length: 4)
|
||||
|
||||
let frameLength = bytesPerRow * Int(size.height)
|
||||
assert(frameLength % 16 == 0)
|
||||
@ -262,7 +258,6 @@ public func experimentalConvertCompressedLottieToCombinedMp4(data: Data, size: C
|
||||
|
||||
while currentFrame < endFrame {
|
||||
if cancelled.with({ $0 }) {
|
||||
//print("cancelled 3")
|
||||
return
|
||||
}
|
||||
|
||||
@ -298,8 +293,8 @@ public func experimentalConvertCompressedLottieToCombinedMp4(data: Data, size: C
|
||||
compressedFrameData.withUnsafeMutableBytes { (bytes: UnsafeMutablePointer<UInt8>) -> Void in
|
||||
let length = compression_encode_buffer(bytes, compressedFrameDataLength, previousYuvaFrameData.assumingMemoryBound(to: UInt8.self), yuvaLength, scratchData, COMPRESSION_LZFSE)
|
||||
var frameLengthValue: Int32 = Int32(length)
|
||||
let _ = fileContext.write(&frameLengthValue, count: 4)
|
||||
let _ = fileContext.write(bytes, count: length)
|
||||
writeBuffer.write(&frameLengthValue, length: 4)
|
||||
writeBuffer.write(bytes, length: length)
|
||||
}
|
||||
|
||||
let tmp = previousYuvaFrameData
|
||||
@ -309,16 +304,33 @@ public func experimentalConvertCompressedLottieToCombinedMp4(data: Data, size: C
|
||||
compressionTime += CACurrentMediaTime() - compressionStartTime
|
||||
|
||||
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()
|
||||
print("animation render time \(CACurrentMediaTime() - startTime)")
|
||||
/*print("animation render time \(CACurrentMediaTime() - startTime)")
|
||||
print("of which drawing time \(drawingTime)")
|
||||
print("of which appending time \(appendingTime)")
|
||||
print("of which delta time \(deltaTime)")
|
||||
|
||||
print("of which compression time \(compressionTime)")
|
||||
print("of which compression time \(compressionTime)")*/
|
||||
}
|
||||
}
|
||||
}))
|
||||
|
@ -18,13 +18,13 @@ public final class AnimatedStickerResourceSource: AnimatedStickerNodeSource {
|
||||
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)
|
||||
|> filter { data in
|
||||
return data.complete
|
||||
return data.size != 0
|
||||
}
|
||||
|> map { data -> String in
|
||||
return data.path
|
||||
|> map { data -> (String, Bool) in
|
||||
return (data.path, data.complete)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -855,8 +855,11 @@ private func fetchAnimatedStickerRepresentation(account: Account, resource: Medi
|
||||
return Signal({ subscriber in
|
||||
if let data = try? Data(contentsOf: URL(fileURLWithPath: resourceData.path), options: [.mappedIfSafe]) {
|
||||
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(.temporaryPath(path))
|
||||
subscriber.putNext(.reset)
|
||||
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()
|
||||
})
|
||||
} else {
|
||||
|
@ -148,7 +148,7 @@ class StickerPanePeerSpecificSetupGridItemNode: GridItemNode {
|
||||
let textSpacing: CGFloat = 3.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()))
|
||||
|
||||
|
@ -530,6 +530,8 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
|
||||
})
|
||||
}
|
||||
|
||||
self.animatedStickerNode?.visibility = true
|
||||
|
||||
self.checkTimer()
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user