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 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 {
|
||||||
|
@ -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 {
|
||||||
|
@ -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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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 {
|
||||||
|
@ -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)")*/
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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 {
|
||||||
|
@ -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()))
|
||||||
|
|
||||||
|
@ -530,6 +530,8 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.animatedStickerNode?.visibility = true
|
||||||
|
|
||||||
self.checkTimer()
|
self.checkTimer()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user