Various improvements

This commit is contained in:
Isaac 2025-03-07 17:52:50 +01:00
parent a63a3074bd
commit 383ab9e479
33 changed files with 373 additions and 203 deletions

View File

@ -112,7 +112,7 @@ public final class DirectAnimatedStickerNode: ASDisplayNode, AnimatedStickerNode
} }
if source.isVideo { if source.isVideo {
if let videoSource = makeVideoStickerDirectFrameSource(queue: DirectAnimatedStickerNode.sharedQueue, path: path, width: width, height: height, cachePathPrefix: nil, unpremultiplyAlpha: false) { if let videoSource = makeVideoStickerDirectFrameSource(queue: DirectAnimatedStickerNode.sharedQueue, path: path, hintVP9: true, width: width, height: height, cachePathPrefix: nil, unpremultiplyAlpha: false) {
strongSelf.setupPlayback(videoSource: videoSource) strongSelf.setupPlayback(videoSource: videoSource)
} }
} else { } else {

View File

@ -273,8 +273,8 @@ private final class VideoStickerFrameSourceCache {
private let useCache = true private let useCache = true
public func makeVideoStickerDirectFrameSource(queue: Queue, path: String, width: Int, height: Int, cachePathPrefix: String?, unpremultiplyAlpha: Bool) -> AnimatedStickerFrameSource? { public func makeVideoStickerDirectFrameSource(queue: Queue, path: String, hintVP9: Bool, width: Int, height: Int, cachePathPrefix: String?, unpremultiplyAlpha: Bool) -> AnimatedStickerFrameSource? {
return VideoStickerDirectFrameSource(queue: queue, path: path, width: width, height: height, cachePathPrefix: cachePathPrefix, unpremultiplyAlpha: unpremultiplyAlpha) return VideoStickerDirectFrameSource(queue: queue, path: path, isVP9: hintVP9, width: width, height: height, cachePathPrefix: cachePathPrefix, unpremultiplyAlpha: unpremultiplyAlpha)
} }
public final class VideoStickerDirectFrameSource: AnimatedStickerFrameSource { public final class VideoStickerDirectFrameSource: AnimatedStickerFrameSource {
@ -290,7 +290,7 @@ public final class VideoStickerDirectFrameSource: AnimatedStickerFrameSource {
public var duration: Double public var duration: Double
fileprivate var currentFrame: Int fileprivate var currentFrame: Int
private let source: SoftwareVideoSource? private var source: FFMpegFileReader?
public var frameIndex: Int { public var frameIndex: Int {
if self.frameCount == 0 { if self.frameCount == 0 {
@ -300,7 +300,7 @@ public final class VideoStickerDirectFrameSource: AnimatedStickerFrameSource {
} }
} }
public init?(queue: Queue, path: String, width: Int, height: Int, cachePathPrefix: String?, unpremultiplyAlpha: Bool = true) { public init?(queue: Queue, path: String, isVP9: Bool = true, width: Int, height: Int, cachePathPrefix: String?, unpremultiplyAlpha: Bool = true) {
self.queue = queue self.queue = queue
self.path = path self.path = path
self.width = width self.width = width
@ -329,12 +329,25 @@ public final class VideoStickerDirectFrameSource: AnimatedStickerFrameSource {
self.frameCount = 1 self.frameCount = 1
self.duration = 0.0 self.duration = 0.0
} else { } else {
let source = SoftwareVideoSource(path: path, hintVP9: true, unpremultiplyAlpha: unpremultiplyAlpha) let source = FFMpegFileReader(
self.source = source source: .file(path),
passthroughDecoder: false,
useHardwareAcceleration: false,
selectedStream: .mediaType(.video),
seek: nil,
maxReadablePts: nil
)
if let source {
self.source = source
self.frameRate = min(30, source.frameRate())
self.duration = source.duration().seconds
} else {
self.source = nil
self.frameRate = 30
self.duration = 0.0
}
self.image = nil self.image = nil
self.frameRate = min(30, source.getFramerate())
self.frameCount = 0 self.frameCount = 0
self.duration = source.reportedDuration.seconds
} }
} }
@ -365,56 +378,66 @@ public final class VideoStickerDirectFrameSource: AnimatedStickerFrameSource {
} else if useCache, let cache = self.cache, let yuvData = cache.readUncompressedYuvaFrame(index: frameIndex) { } else if useCache, let cache = self.cache, let yuvData = cache.readUncompressedYuvaFrame(index: frameIndex) {
return AnimatedStickerFrame(data: yuvData, type: .yuva, width: self.width, height: self.height, bytesPerRow: self.width * 2, index: frameIndex, isLastFrame: frameIndex == self.frameCount - 1, totalFrames: self.frameCount) return AnimatedStickerFrame(data: yuvData, type: .yuva, width: self.width, height: self.height, bytesPerRow: self.width * 2, index: frameIndex, isLastFrame: frameIndex == self.frameCount - 1, totalFrames: self.frameCount)
} else if let source = self.source { } else if let source = self.source {
let frameAndLoop = source.readFrame(maxPts: nil) let frameAndLoop = source.readFrame(argb: true)
if frameAndLoop.0 == nil { switch frameAndLoop {
if frameAndLoop.3 { case let .frame(frame):
if self.frameCount == 0 { var frameData = Data(count: self.bytesPerRow * self.height)
if let cache = self.cache { frameData.withUnsafeMutableBytes { buffer -> Void in
if cache.storedFrames == frameIndex { guard let bytes = buffer.baseAddress?.assumingMemoryBound(to: UInt8.self) else {
self.frameCount = frameIndex return
cache.storeFrameRateAndCount(frameRate: self.frameRate, frameCount: self.frameCount)
} else {
Logger.shared.log("VideoSticker", "Missed a frame? \(frameIndex) \(cache.storedFrames)")
}
} else {
self.frameCount = frameIndex
}
} }
self.currentFrame = 0
} else { let imageBuffer = CMSampleBufferGetImageBuffer(frame.sampleBuffer)
Logger.shared.log("VideoSticker", "Skipped a frame?") CVPixelBufferLockBaseAddress(imageBuffer!, CVPixelBufferLockFlags(rawValue: 0))
let bytesPerRow = CVPixelBufferGetBytesPerRow(imageBuffer!)
let width = CVPixelBufferGetWidth(imageBuffer!)
let height = CVPixelBufferGetHeight(imageBuffer!)
let srcData = CVPixelBufferGetBaseAddress(imageBuffer!)
var sourceBuffer = vImage_Buffer(data: srcData, height: vImagePixelCount(height), width: vImagePixelCount(width), rowBytes: bytesPerRow)
var destBuffer = vImage_Buffer(data: bytes, height: vImagePixelCount(self.height), width: vImagePixelCount(self.width), rowBytes: self.bytesPerRow)
let _ = vImageScale_ARGB8888(&sourceBuffer, &destBuffer, nil, vImage_Flags(kvImageDoNotTile))
CVPixelBufferUnlockBaseAddress(imageBuffer!, CVPixelBufferLockFlags(rawValue: 0))
} }
return nil
}
guard let frame = frameAndLoop.0 else {
return nil
}
var frameData = Data(count: self.bytesPerRow * self.height)
frameData.withUnsafeMutableBytes { buffer -> Void in
guard let bytes = buffer.baseAddress?.assumingMemoryBound(to: UInt8.self) else {
return
}
let imageBuffer = CMSampleBufferGetImageBuffer(frame.sampleBuffer)
CVPixelBufferLockBaseAddress(imageBuffer!, CVPixelBufferLockFlags(rawValue: 0))
let bytesPerRow = CVPixelBufferGetBytesPerRow(imageBuffer!)
let width = CVPixelBufferGetWidth(imageBuffer!)
let height = CVPixelBufferGetHeight(imageBuffer!)
let srcData = CVPixelBufferGetBaseAddress(imageBuffer!)
var sourceBuffer = vImage_Buffer(data: srcData, height: vImagePixelCount(height), width: vImagePixelCount(width), rowBytes: bytesPerRow)
var destBuffer = vImage_Buffer(data: bytes, height: vImagePixelCount(self.height), width: vImagePixelCount(self.width), rowBytes: self.bytesPerRow)
let _ = vImageScale_ARGB8888(&sourceBuffer, &destBuffer, nil, vImage_Flags(kvImageDoNotTile))
CVPixelBufferUnlockBaseAddress(imageBuffer!, CVPixelBufferLockFlags(rawValue: 0))
}
self.cache?.storeUncompressedRgbFrame(index: frameIndex, rgbData: frameData) self.cache?.storeUncompressedRgbFrame(index: frameIndex, rgbData: frameData)
return AnimatedStickerFrame(data: frameData, type: .argb, width: self.width, height: self.height, bytesPerRow: self.bytesPerRow, index: frameIndex, isLastFrame: frameIndex == self.frameCount - 1, totalFrames: self.frameCount, multiplyAlpha: true) return AnimatedStickerFrame(data: frameData, type: .argb, width: self.width, height: self.height, bytesPerRow: self.bytesPerRow, index: frameIndex, isLastFrame: frameIndex == self.frameCount - 1, totalFrames: self.frameCount, multiplyAlpha: true)
case .endOfStream:
if self.frameCount == 0 {
if let cache = self.cache {
if cache.storedFrames == frameIndex {
self.frameCount = frameIndex
cache.storeFrameRateAndCount(frameRate: self.frameRate, frameCount: self.frameCount)
} else {
Logger.shared.log("VideoSticker", "Missed a frame? \(frameIndex) \(cache.storedFrames)")
}
} else {
self.frameCount = frameIndex
}
}
self.currentFrame = 0
self.source = FFMpegFileReader(
source: .file(self.path),
passthroughDecoder: false,
useHardwareAcceleration: false,
selectedStream: .mediaType(.video),
seek: nil,
maxReadablePts: nil
)
if let cache = self.cache {
if let yuvData = cache.readUncompressedYuvaFrame(index: self.currentFrame) {
return AnimatedStickerFrame(data: yuvData, type: .yuva, width: self.width, height: self.height, bytesPerRow: self.width * 2, index: frameIndex, isLastFrame: frameIndex == self.frameCount - 1, totalFrames: self.frameCount)
}
}
return nil
case .waitingForMoreData, .error:
return nil
}
} else { } else {
return nil return nil
} }

View File

@ -25,6 +25,7 @@ public final class AvatarVideoNode: ASDisplayNode {
private var emojiMarkup: TelegramMediaImage.EmojiMarkup? private var emojiMarkup: TelegramMediaImage.EmojiMarkup?
private var videoFileDisposable: Disposable?
private var fileDisposable = MetaDisposable() private var fileDisposable = MetaDisposable()
private var animationFile: TelegramMediaFile? private var animationFile: TelegramMediaFile?
private var itemLayer: EmojiKeyboardItemLayer? private var itemLayer: EmojiKeyboardItemLayer?
@ -32,6 +33,7 @@ public final class AvatarVideoNode: ASDisplayNode {
private var animationNode: AnimatedStickerNode? private var animationNode: AnimatedStickerNode?
private let stickerFetchedDisposable = MetaDisposable() private let stickerFetchedDisposable = MetaDisposable()
private var videoItemLayer: EmojiKeyboardItemLayer?
private var videoNode: UniversalVideoNode? private var videoNode: UniversalVideoNode?
private var videoContent: NativeVideoContent? private var videoContent: NativeVideoContent?
private let playbackStartDisposable = MetaDisposable() private let playbackStartDisposable = MetaDisposable()
@ -55,6 +57,7 @@ public final class AvatarVideoNode: ASDisplayNode {
} }
deinit { deinit {
self.videoFileDisposable?.dispose()
self.fileDisposable.dispose() self.fileDisposable.dispose()
self.stickerFetchedDisposable.dispose() self.stickerFetchedDisposable.dispose()
self.playbackStartDisposable.dispose() self.playbackStartDisposable.dispose()
@ -137,6 +140,7 @@ public final class AvatarVideoNode: ASDisplayNode {
self.videoLoopCount += 1 self.videoLoopCount += 1
if self.videoLoopCount >= maxVideoLoopCount { if self.videoLoopCount >= maxVideoLoopCount {
self.itemLayer?.isVisibleForAnimations = false self.itemLayer?.isVisibleForAnimations = false
self.videoItemLayer?.isVisibleForAnimations = false
} }
} }
} }
@ -211,6 +215,9 @@ public final class AvatarVideoNode: ASDisplayNode {
if videoContent.id != self.videoContent?.id { if videoContent.id != self.videoContent?.id {
self.videoNode?.removeFromSupernode() self.videoNode?.removeFromSupernode()
self.videoContent = videoContent self.videoContent = videoContent
self.videoFileDisposable?.dispose()
self.videoFileDisposable = fetchedMediaResource(mediaBox: self.context.account.postbox.mediaBox, userLocation: .peer(peer.id), userContentType: .avatar, reference: videoFileReference.resourceReference(videoFileReference.media.resource)).startStrict()
} }
} }
} }
@ -231,56 +238,111 @@ public final class AvatarVideoNode: ASDisplayNode {
} }
self.animationNode?.visibility = isVisible self.animationNode?.visibility = isVisible
if isVisible, let videoContent = self.videoContent, self.videoLoopCount < maxVideoLoopCount { if isVisible, let videoContent = self.videoContent, self.videoLoopCount < maxVideoLoopCount {
if self.videoNode == nil { var useDirectCache = false
let context = self.context if self.internalSize.width <= 200.0 {
let mediaManager = context.sharedContext.mediaManager useDirectCache = true
let videoNode = UniversalVideoNode(context: context, postbox: context.account.postbox, audioSession: mediaManager.audioSession, manager: mediaManager.universalVideoManager, decoration: VideoDecoration(), content: videoContent, priority: .embedded) }
videoNode.clipsToBounds = true
videoNode.isUserInteractionEnabled = false if useDirectCache {
videoNode.isHidden = true if self.videoItemLayer == nil {
videoNode.playbackCompleted = { [weak self] in let animationData = EntityKeyboardAnimationData(file: TelegramMediaFile.Accessor(videoContent.fileReference.media))
if let strongSelf = self { let videoItemLayer = EmojiKeyboardItemLayer(
strongSelf.videoLoopCount += 1 item: EmojiPagerContentComponent.Item(
if strongSelf.videoLoopCount >= maxVideoLoopCount { animationData: animationData,
if let videoNode = strongSelf.videoNode { content: .animation(animationData),
strongSelf.videoNode = nil itemFile: TelegramMediaFile.Accessor(videoContent.fileReference.media),
videoNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak videoNode] _ in subgroupId: nil,
videoNode?.removeFromSupernode() icon: .none,
}) tintMode: .none
),
context: self.context,
attemptSynchronousLoad: false,
content: .animation(animationData),
cache: self.context.animationCache,
renderer: self.context.animationRenderer,
placeholderColor: .clear,
blurredBadgeColor: .clear,
accentIconColor: .white,
pointSize: self.internalSize,
onUpdateDisplayPlaceholder: { _, _ in
}
)
videoItemLayer.onLoop = { [weak self] in
if let self {
self.videoLoopCount += 1
if self.videoLoopCount >= maxVideoLoopCount {
self.itemLayer?.isVisibleForAnimations = false
} }
} }
} }
self.videoItemLayer = videoItemLayer
self.layer.addSublayer(videoItemLayer)
} }
} else {
if let _ = videoContent.startTimestamp { if let videoItemLayer = self.videoItemLayer {
self.playbackStartDisposable.set((videoNode.status self.videoItemLayer = nil
|> map { status -> Bool in videoItemLayer.removeFromSuperlayer()
if let status = status, case .playing = status.status { }
return true }
} else {
return false if useDirectCache {
} if let videoNode = self.videoNode {
} self.videoNode = nil
|> filter { playing in videoNode.removeFromSupernode()
return playing }
} } else {
|> take(1) if self.videoNode == nil {
|> deliverOnMainQueue).startStrict(completed: { [weak self] in let context = self.context
let mediaManager = context.sharedContext.mediaManager
let videoNode = UniversalVideoNode(context: context, postbox: context.account.postbox, audioSession: mediaManager.audioSession, manager: mediaManager.universalVideoManager, decoration: VideoDecoration(), content: videoContent, priority: .embedded)
videoNode.clipsToBounds = true
videoNode.isUserInteractionEnabled = false
videoNode.isHidden = true
videoNode.playbackCompleted = { [weak self] in
if let strongSelf = self { if let strongSelf = self {
Queue.mainQueue().after(0.15) { strongSelf.videoLoopCount += 1
strongSelf.videoNode?.isHidden = false if strongSelf.videoLoopCount >= maxVideoLoopCount {
if let videoNode = strongSelf.videoNode {
strongSelf.videoNode = nil
videoNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak videoNode] _ in
videoNode?.removeFromSupernode()
})
}
} }
} }
})) }
} else {
self.playbackStartDisposable.set(nil) if let _ = videoContent.startTimestamp {
videoNode.isHidden = false self.playbackStartDisposable.set((videoNode.status
|> map { status -> Bool in
if let status = status, case .playing = status.status {
return true
} else {
return false
}
}
|> filter { playing in
return playing
}
|> take(1)
|> deliverOnMainQueue).startStrict(completed: { [weak self] in
if let strongSelf = self {
Queue.mainQueue().after(0.15) {
strongSelf.videoNode?.isHidden = false
}
}
}))
} else {
self.playbackStartDisposable.set(nil)
videoNode.isHidden = false
}
videoNode.canAttachContent = true
videoNode.play()
self.addSubnode(videoNode)
self.videoNode = videoNode
} }
videoNode.canAttachContent = true
videoNode.play()
self.addSubnode(videoNode)
self.videoNode = videoNode
} }
} else if let videoNode = self.videoNode { } else if let videoNode = self.videoNode {
self.videoNode = nil self.videoNode = nil
@ -289,6 +351,7 @@ public final class AvatarVideoNode: ASDisplayNode {
if self.videoLoopCount < maxVideoLoopCount { if self.videoLoopCount < maxVideoLoopCount {
self.itemLayer?.isVisibleForAnimations = isVisible self.itemLayer?.isVisibleForAnimations = isVisible
} }
self.videoItemLayer?.isVisibleForAnimations = isVisible
} }
public func updateLayout(size: CGSize, cornerRadius: CGFloat, transition: ContainedViewLayoutTransition) { public func updateLayout(size: CGSize, cornerRadius: CGFloat, transition: ContainedViewLayoutTransition) {
@ -301,6 +364,9 @@ public final class AvatarVideoNode: ASDisplayNode {
videoNode.frame = CGRect(origin: .zero, size: size) videoNode.frame = CGRect(origin: .zero, size: size)
videoNode.updateLayout(size: size, transition: transition) videoNode.updateLayout(size: size, transition: transition)
} }
if let videoItemLayer = self.videoItemLayer {
videoItemLayer.frame = CGRect(origin: .zero, size: size)
}
let itemSize = CGSize(width: size.width * 0.67, height: size.height * 0.67) let itemSize = CGSize(width: size.width * 0.67, height: size.height * 0.67)
let itemFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - itemSize.width) / 2.0), y: floorToScreenPixels((size.height - itemSize.height) / 2.0)), size: itemSize) let itemFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - itemSize.width) / 2.0), y: floorToScreenPixels((size.height - itemSize.height) / 2.0)), size: itemSize)

View File

@ -1747,7 +1747,11 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode {
self.avatarNode.font = avatarPlaceholderFont(size: avatarFontSize) self.avatarNode.font = avatarPlaceholderFont(size: avatarFontSize)
} }
} }
self.avatarNode.setPeer(context: item.context, theme: item.presentationData.theme, peer: peer, overrideImage: overrideImage, emptyColor: item.presentationData.theme.list.mediaPlaceholderColor, clipStyle: isForumAvatar ? .roundedRect : .round, synchronousLoad: synchronousLoads, displayDimensions: CGSize(width: 60.0, height: 60.0)) if peer.smallProfileImage != nil && overrideImage == nil {
self.avatarNode.setPeerV2(context: item.context, theme: item.presentationData.theme, peer: peer, overrideImage: overrideImage, emptyColor: item.presentationData.theme.list.mediaPlaceholderColor, clipStyle: isForumAvatar ? .roundedRect : .round, synchronousLoad: synchronousLoads, displayDimensions: CGSize(width: 60.0, height: 60.0))
} else {
self.avatarNode.setPeer(context: item.context, theme: item.presentationData.theme, peer: peer, overrideImage: overrideImage, emptyColor: item.presentationData.theme.list.mediaPlaceholderColor, clipStyle: isForumAvatar ? .roundedRect : .round, synchronousLoad: synchronousLoads, displayDimensions: CGSize(width: 60.0, height: 60.0))
}
if peer.isPremium && peer.id != item.context.account.peerId { if peer.isPremium && peer.id != item.context.account.peerId {
let context = item.context let context = item.context

View File

@ -954,10 +954,10 @@ final class ChatSendMessageContextScreenComponent: Component {
} }
var customEffectResource: (FileMediaReference, MediaResource)? var customEffectResource: (FileMediaReference, MediaResource)?
if let effectAnimation = messageEffect.effectAnimation { if let effectAnimation = messageEffect.effectAnimation?._parse() {
customEffectResource = (FileMediaReference.standalone(media: effectAnimation), effectAnimation.resource) customEffectResource = (FileMediaReference.standalone(media: effectAnimation), effectAnimation.resource)
} else { } else {
let effectSticker = messageEffect.effectSticker let effectSticker = messageEffect.effectSticker._parse()
if let effectFile = effectSticker.videoThumbnails.first { if let effectFile = effectSticker.videoThumbnails.first {
customEffectResource = (FileMediaReference.standalone(media: effectSticker), effectFile.resource) customEffectResource = (FileMediaReference.standalone(media: effectSticker), effectFile.resource)
} }

View File

@ -309,7 +309,7 @@ final class MessageItemView: UIView {
} }
let effectIconContent: ChatSendMessageScreenEffectIcon.Content let effectIconContent: ChatSendMessageScreenEffectIcon.Content
if let staticIcon = effect.staticIcon { if let staticIcon = effect.staticIcon {
effectIconContent = .file(staticIcon) effectIconContent = .file(staticIcon._parse())
} else { } else {
effectIconContent = .text(effect.emoticon) effectIconContent = .text(effect.emoticon)
} }

View File

@ -52,6 +52,8 @@ final class NavigationTransitionCoordinator {
private var currentCompletion: (() -> Void)? private var currentCompletion: (() -> Void)?
private var didUpdateProgress: ((CGFloat, ContainedViewLayoutTransition, CGRect, CGRect) -> Void)? private var didUpdateProgress: ((CGFloat, ContainedViewLayoutTransition, CGRect, CGRect) -> Void)?
private var frameRateLink: SharedDisplayLinkDriver.Link?
init(transition: NavigationTransition, isInteractive: Bool, isFlat: Bool, container: NavigationContainer, topNode: ASDisplayNode, topNavigationBar: NavigationBar?, bottomNode: ASDisplayNode, bottomNavigationBar: NavigationBar?, didUpdateProgress: ((CGFloat, ContainedViewLayoutTransition, CGRect, CGRect) -> Void)? = nil) { init(transition: NavigationTransition, isInteractive: Bool, isFlat: Bool, container: NavigationContainer, topNode: ASDisplayNode, topNavigationBar: NavigationBar?, bottomNode: ASDisplayNode, bottomNavigationBar: NavigationBar?, didUpdateProgress: ((CGFloat, ContainedViewLayoutTransition, CGRect, CGRect) -> Void)? = nil) {
self.transition = transition self.transition = transition
self.isInteractive = isInteractive self.isInteractive = isInteractive
@ -114,6 +116,8 @@ final class NavigationTransitionCoordinator {
self.maybeCreateNavigationBarTransition() self.maybeCreateNavigationBarTransition()
self.updateProgress(0.0, transition: .immediate, completion: {}) self.updateProgress(0.0, transition: .immediate, completion: {})
self.frameRateLink = SharedDisplayLinkDriver.shared.add(framesPerSecond: .max, { _ in })
} }
required init(coder aDecoder: NSCoder) { required init(coder aDecoder: NSCoder) {

View File

@ -403,6 +403,22 @@ public final class FFMpegFileReader {
deinit { deinit {
} }
public func frameRate() -> Int {
if let stream = self.stream {
return Int(stream.info.fps.seconds)
} else {
return 0
}
}
public func duration() -> CMTime {
if let stream = self.stream {
return stream.info.duration
} else {
return .zero
}
}
private func readPacketInternal() -> FFMpegPacket? { private func readPacketInternal() -> FFMpegPacket? {
guard let avFormatContext = self.avFormatContext else { guard let avFormatContext = self.avFormatContext else {
return nil return nil
@ -452,7 +468,7 @@ public final class FFMpegFileReader {
return nil return nil
} }
public func readFrame() -> ReadFrameResult { public func readFrame(argb: Bool = false) -> ReadFrameResult {
guard let stream = self.stream else { guard let stream = self.stream else {
return .error return .error
} }
@ -461,7 +477,7 @@ public final class FFMpegFileReader {
var result: MediaTrackFrame? var result: MediaTrackFrame?
switch stream.decoder { switch stream.decoder {
case let .video(decoder): case let .video(decoder):
result = decoder.decode(ptsOffset: nil, forceARGB: false, unpremultiplyAlpha: false, displayImmediately: false) result = decoder.decode(ptsOffset: nil, forceARGB: argb, unpremultiplyAlpha: false, displayImmediately: false)
case let .videoPassthrough(decoder): case let .videoPassthrough(decoder):
result = decoder.decode() result = decoder.decode()
case let .audio(decoder): case let .audio(decoder):

View File

@ -448,7 +448,7 @@ private func chatMessageImageFileThumbnailDatas(account: Account, userLocation:
return signal return signal
} }
private func chatMessageVideoDatas(postbox: Postbox, userLocation: MediaResourceUserLocation, customUserContentType: MediaResourceUserContentType? = nil, fileReference: FileMediaReference, previewSourceFileReference: FileMediaReference?, thumbnailSize: Bool = false, onlyFullSize: Bool = false, useLargeThumbnail: Bool = false, synchronousLoad: Bool = false, autoFetchFullSizeThumbnail: Bool = false, forceThumbnail: Bool = false) -> Signal<Tuple3<Data?, Tuple2<Data, String>?, Bool>, NoError> { private func chatMessageVideoDatas(postbox: Postbox, userLocation: MediaResourceUserLocation, customUserContentType: MediaResourceUserContentType? = nil, fileReference: FileMediaReference, previewSourceFileReference: FileMediaReference?, alternativeFileAndRange: Signal<(TelegramMediaFile, Range<Int64>), NoError>? = nil, thumbnailSize: Bool = false, onlyFullSize: Bool = false, useLargeThumbnail: Bool = false, synchronousLoad: Bool = false, autoFetchFullSizeThumbnail: Bool = false, forceThumbnail: Bool = false) -> Signal<Tuple3<Data?, Tuple2<Data, String>?, Bool>, NoError> {
let fullSizeResource = fileReference.media.resource let fullSizeResource = fileReference.media.resource
var reducedSizeResource: MediaResource? var reducedSizeResource: MediaResource?
if let videoThumbnail = fileReference.media.videoThumbnails.first { if let videoThumbnail = fileReference.media.videoThumbnails.first {
@ -1627,7 +1627,7 @@ public func mediaGridMessageVideo(postbox: Postbox, userLocation: MediaResourceU
} }
} }
public func internalMediaGridMessageVideo(postbox: Postbox, userLocation: MediaResourceUserLocation, customUserContentType: MediaResourceUserContentType? = nil, videoReference: FileMediaReference, previewSourceFileReference: FileMediaReference? = nil, imageReference: ImageMediaReference? = nil, onlyFullSize: Bool = false, useLargeThumbnail: Bool = false, synchronousLoad: Bool = false, autoFetchFullSizeThumbnail: Bool = false, overlayColor: UIColor? = nil, nilForEmptyResult: Bool = false, useMiniThumbnailIfAvailable: Bool = false, blurred: Bool = false) -> Signal<(() -> CGSize?, (TransformImageArguments) -> DrawingContext?), NoError> { public func internalMediaGridMessageVideo(postbox: Postbox, userLocation: MediaResourceUserLocation, customUserContentType: MediaResourceUserContentType? = nil, videoReference: FileMediaReference, previewSourceFileReference: FileMediaReference? = nil, imageReference: ImageMediaReference? = nil, alternativeFileAndRange: Signal<(TelegramMediaFile, Range<Int64>), NoError>? = nil, onlyFullSize: Bool = false, useLargeThumbnail: Bool = false, synchronousLoad: Bool = false, autoFetchFullSizeThumbnail: Bool = false, overlayColor: UIColor? = nil, nilForEmptyResult: Bool = false, useMiniThumbnailIfAvailable: Bool = false, blurred: Bool = false) -> Signal<(() -> CGSize?, (TransformImageArguments) -> DrawingContext?), NoError> {
let signal: Signal<Tuple3<Data?, Tuple2<Data, String>?, Bool>, NoError> let signal: Signal<Tuple3<Data?, Tuple2<Data, String>?, Bool>, NoError>
if let imageReference = imageReference { if let imageReference = imageReference {
signal = chatMessagePhotoDatas(postbox: postbox, userLocation: userLocation, customUserContentType: customUserContentType, photoReference: imageReference, tryAdditionalRepresentations: true, synchronousLoad: synchronousLoad, forceThumbnail: blurred) signal = chatMessagePhotoDatas(postbox: postbox, userLocation: userLocation, customUserContentType: customUserContentType, photoReference: imageReference, tryAdditionalRepresentations: true, synchronousLoad: synchronousLoad, forceThumbnail: blurred)
@ -1638,7 +1638,7 @@ public func internalMediaGridMessageVideo(postbox: Postbox, userLocation: MediaR
return Tuple(thumbnailData, fullSizeData.flatMap({ Tuple($0, "") }), fullSizeComplete) return Tuple(thumbnailData, fullSizeData.flatMap({ Tuple($0, "") }), fullSizeComplete)
} }
} else { } else {
signal = chatMessageVideoDatas(postbox: postbox, userLocation: userLocation, customUserContentType: customUserContentType, fileReference: videoReference, previewSourceFileReference: previewSourceFileReference, onlyFullSize: onlyFullSize, useLargeThumbnail: useLargeThumbnail, synchronousLoad: synchronousLoad, autoFetchFullSizeThumbnail: autoFetchFullSizeThumbnail, forceThumbnail: blurred) signal = chatMessageVideoDatas(postbox: postbox, userLocation: userLocation, customUserContentType: customUserContentType, fileReference: videoReference, previewSourceFileReference: previewSourceFileReference, alternativeFileAndRange: alternativeFileAndRange, onlyFullSize: onlyFullSize, useLargeThumbnail: useLargeThumbnail, synchronousLoad: synchronousLoad, autoFetchFullSizeThumbnail: autoFetchFullSizeThumbnail, forceThumbnail: blurred)
} }
return signal return signal

View File

@ -1893,7 +1893,7 @@ public final class ReactionContextNode: ASDisplayNode, ASScrollViewDelegate {
for i in 0 ..< 2 { for i in 0 ..< 2 {
let groupId = i == 0 ? "reactions" : "stickers" let groupId = i == 0 ? "reactions" : "stickers"
for item in i == 0 ? reactionEffects : stickerEffects { for item in i == 0 ? reactionEffects : stickerEffects {
let itemFile: TelegramMediaFile = item.effectSticker let itemFile = item.effectSticker
var tintMode: EmojiPagerContentComponent.Item.TintMode = .none var tintMode: EmojiPagerContentComponent.Item.TintMode = .none
if itemFile.isCustomTemplateEmoji { if itemFile.isCustomTemplateEmoji {
@ -1917,11 +1917,11 @@ public final class ReactionContextNode: ASDisplayNode, ASScrollViewDelegate {
} }
} }
let animationData = EntityKeyboardAnimationData(file: TelegramMediaFile.Accessor(itemFile), partialReference: .none) let animationData = EntityKeyboardAnimationData(file: itemFile, partialReference: .none)
let resultItem = EmojiPagerContentComponent.Item( let resultItem = EmojiPagerContentComponent.Item(
animationData: animationData, animationData: animationData,
content: .animation(animationData), content: .animation(animationData),
itemFile: TelegramMediaFile.Accessor(itemFile), itemFile: itemFile,
subgroupId: nil, subgroupId: nil,
icon: icon, icon: icon,
tintMode: tintMode tintMode: tintMode
@ -2257,7 +2257,7 @@ public final class ReactionContextNode: ASDisplayNode, ASScrollViewDelegate {
for i in 0 ..< 2 { for i in 0 ..< 2 {
let groupId = i == 0 ? "reactions" : "stickers" let groupId = i == 0 ? "reactions" : "stickers"
for item in i == 0 ? reactionEffects : stickerEffects { for item in i == 0 ? reactionEffects : stickerEffects {
let itemFile: TelegramMediaFile = item.effectSticker let itemFile = item.effectSticker
var tintMode: EmojiPagerContentComponent.Item.TintMode = .none var tintMode: EmojiPagerContentComponent.Item.TintMode = .none
if itemFile.isCustomTemplateEmoji { if itemFile.isCustomTemplateEmoji {
@ -2281,11 +2281,11 @@ public final class ReactionContextNode: ASDisplayNode, ASScrollViewDelegate {
} }
} }
let animationData = EntityKeyboardAnimationData(file: TelegramMediaFile.Accessor(itemFile), partialReference: .none) let animationData = EntityKeyboardAnimationData(file: itemFile, partialReference: .none)
let resultItem = EmojiPagerContentComponent.Item( let resultItem = EmojiPagerContentComponent.Item(
animationData: animationData, animationData: animationData,
content: .animation(animationData), content: .animation(animationData),
itemFile: TelegramMediaFile.Accessor(itemFile), itemFile: itemFile,
subgroupId: nil, subgroupId: nil,
icon: icon, icon: icon,
tintMode: tintMode tintMode: tintMode

View File

@ -976,7 +976,7 @@ func applyLoadMessageHistoryThreadsResults(accountPeerId: PeerId, transaction: T
transaction.replaceMessageTagSummary(peerId: result.peerId, threadId: item.threadId, tagMask: .unseenReaction, namespace: Namespaces.Message.Cloud, customTag: nil, count: item.unreadReactionsCount, maxId: item.topMessage) transaction.replaceMessageTagSummary(peerId: result.peerId, threadId: item.threadId, tagMask: .unseenReaction, namespace: Namespaces.Message.Cloud, customTag: nil, count: item.unreadReactionsCount, maxId: item.topMessage)
if item.topMessage != 0 { if item.topMessage != 0 {
transaction.removeHole(peerId: result.peerId, threadId: item.threadId, namespace: Namespaces.Message.Cloud, space: .everywhere, range: item.topMessage ... (Int32.max - 1)) //transaction.removeHole(peerId: result.peerId, threadId: item.threadId, namespace: Namespaces.Message.Cloud, space: .everywhere, range: item.topMessage ... (Int32.max - 1))
} }
for message in result.messages { for message in result.messages {

View File

@ -2,6 +2,8 @@ import Foundation
import TelegramApi import TelegramApi
import Postbox import Postbox
import SwiftSignalKit import SwiftSignalKit
import FlatBuffers
import FlatSerialization
public final class AvailableMessageEffects: Equatable, Codable { public final class AvailableMessageEffects: Equatable, Codable {
public final class MessageEffect: Equatable, Codable { public final class MessageEffect: Equatable, Codable {
@ -10,24 +12,27 @@ public final class AvailableMessageEffects: Equatable, Codable {
case isPremium case isPremium
case emoticon case emoticon
case staticIcon case staticIcon
case staticIconData = "sid"
case effectSticker case effectSticker
case effectStickerData = "esd"
case effectAnimation case effectAnimation
case effectAnimationData = "ead"
} }
public let id: Int64 public let id: Int64
public let isPremium: Bool public let isPremium: Bool
public let emoticon: String public let emoticon: String
public let staticIcon: TelegramMediaFile? public let staticIcon: TelegramMediaFile.Accessor?
public let effectSticker: TelegramMediaFile public let effectSticker: TelegramMediaFile.Accessor
public let effectAnimation: TelegramMediaFile? public let effectAnimation: TelegramMediaFile.Accessor?
public init( public init(
id: Int64, id: Int64,
isPremium: Bool, isPremium: Bool,
emoticon: String, emoticon: String,
staticIcon: TelegramMediaFile?, staticIcon: TelegramMediaFile.Accessor?,
effectSticker: TelegramMediaFile, effectSticker: TelegramMediaFile.Accessor,
effectAnimation: TelegramMediaFile? effectAnimation: TelegramMediaFile.Accessor?
) { ) {
self.id = id self.id = id
self.isPremium = isPremium self.isPremium = isPremium
@ -66,19 +71,28 @@ public final class AvailableMessageEffects: Equatable, Codable {
self.isPremium = try container.decodeIfPresent(Bool.self, forKey: .isPremium) ?? false self.isPremium = try container.decodeIfPresent(Bool.self, forKey: .isPremium) ?? false
self.emoticon = try container.decode(String.self, forKey: .emoticon) self.emoticon = try container.decode(String.self, forKey: .emoticon)
if let staticIconData = try container.decodeIfPresent(AdaptedPostboxDecoder.RawObjectData.self, forKey: .staticIcon) { if let staticIconData = try container.decodeIfPresent(Data.self, forKey: .staticIconData) {
self.staticIcon = TelegramMediaFile(decoder: PostboxDecoder(buffer: MemoryBuffer(data: staticIconData.data))) var byteBuffer = ByteBuffer(data: staticIconData)
self.staticIcon = TelegramMediaFile.Accessor(FlatBuffers_getRoot(byteBuffer: &byteBuffer) as TelegramCore_TelegramMediaFile, staticIconData)
} else if let staticIconData = try container.decodeIfPresent(AdaptedPostboxDecoder.RawObjectData.self, forKey: .staticIcon) {
self.staticIcon = TelegramMediaFile.Accessor(TelegramMediaFile(decoder: PostboxDecoder(buffer: MemoryBuffer(data: staticIconData.data))))
} else { } else {
self.staticIcon = nil self.staticIcon = nil
} }
do { if let effectStickerData = try container.decodeIfPresent(Data.self, forKey: .effectStickerData) {
var byteBuffer = ByteBuffer(data: effectStickerData)
self.effectSticker = TelegramMediaFile.Accessor(FlatBuffers_getRoot(byteBuffer: &byteBuffer) as TelegramCore_TelegramMediaFile, effectStickerData)
} else {
let effectStickerData = try container.decode(AdaptedPostboxDecoder.RawObjectData.self, forKey: .effectSticker) let effectStickerData = try container.decode(AdaptedPostboxDecoder.RawObjectData.self, forKey: .effectSticker)
self.effectSticker = TelegramMediaFile(decoder: PostboxDecoder(buffer: MemoryBuffer(data: effectStickerData.data))) self.effectSticker = TelegramMediaFile.Accessor(TelegramMediaFile(decoder: PostboxDecoder(buffer: MemoryBuffer(data: effectStickerData.data))))
} }
if let effectAnimationData = try container.decodeIfPresent(AdaptedPostboxDecoder.RawObjectData.self, forKey: .effectAnimation) { if let effectAnimationData = try container.decodeIfPresent(Data.self, forKey: .effectAnimationData) {
self.effectAnimation = TelegramMediaFile(decoder: PostboxDecoder(buffer: MemoryBuffer(data: effectAnimationData.data))) var byteBuffer = ByteBuffer(data: effectAnimationData)
self.effectAnimation = TelegramMediaFile.Accessor(FlatBuffers_getRoot(byteBuffer: &byteBuffer) as TelegramCore_TelegramMediaFile, effectAnimationData)
} else if let effectAnimationData = try container.decodeIfPresent(AdaptedPostboxDecoder.RawObjectData.self, forKey: .effectAnimation) {
self.effectAnimation = TelegramMediaFile.Accessor(TelegramMediaFile(decoder: PostboxDecoder(buffer: MemoryBuffer(data: effectAnimationData.data))))
} else { } else {
self.effectAnimation = nil self.effectAnimation = nil
} }
@ -91,12 +105,26 @@ public final class AvailableMessageEffects: Equatable, Codable {
try container.encode(self.emoticon, forKey: .emoticon) try container.encode(self.emoticon, forKey: .emoticon)
try container.encode(self.isPremium, forKey: .isPremium) try container.encode(self.isPremium, forKey: .isPremium)
if let staticIcon = self.staticIcon { let encodeFileItem: (TelegramMediaFile.Accessor, CodingKeys) throws -> Void = { file, key in
try container.encode(PostboxEncoder().encodeObjectToRawData(staticIcon), forKey: .staticIcon) if let serializedFile = file._wrappedData {
try container.encode(serializedFile, forKey: key)
} else if let file = file._wrappedFile {
var builder = FlatBufferBuilder(initialSize: 1024)
let value = file.encodeToFlatBuffers(builder: &builder)
builder.finish(offset: value)
let serializedFile = builder.data
try container.encode(serializedFile, forKey: key)
} else {
preconditionFailure()
}
} }
try container.encode(PostboxEncoder().encodeObjectToRawData(self.effectSticker), forKey: .effectSticker)
if let staticIcon = self.staticIcon {
try encodeFileItem(staticIcon, .staticIconData)
}
try encodeFileItem(self.effectSticker, .effectStickerData)
if let effectAnimation = self.effectAnimation { if let effectAnimation = self.effectAnimation {
try container.encode(PostboxEncoder().encodeObjectToRawData(effectAnimation), forKey: .effectAnimation) try encodeFileItem(effectAnimation, .effectAnimationData)
} }
} }
} }
@ -142,8 +170,6 @@ public final class AvailableMessageEffects: Equatable, Codable {
} }
} }
//availableEffect flags:# premium_required:flags.2?true id:long emoticon:string static_icon_id:flags.0?long effect_sticker_id:long effect_animation_id:flags.1?long = AvailableEffect;
private extension AvailableMessageEffects.MessageEffect { private extension AvailableMessageEffects.MessageEffect {
convenience init?(apiMessageEffect: Api.AvailableEffect, files: [Int64: TelegramMediaFile]) { convenience init?(apiMessageEffect: Api.AvailableEffect, files: [Int64: TelegramMediaFile]) {
switch apiMessageEffect { switch apiMessageEffect {
@ -157,9 +183,9 @@ private extension AvailableMessageEffects.MessageEffect {
id: id, id: id,
isPremium: isPremium, isPremium: isPremium,
emoticon: emoticon, emoticon: emoticon,
staticIcon: staticIconId.flatMap({ files[$0] }), staticIcon: staticIconId.flatMap({ files[$0].flatMap(TelegramMediaFile.Accessor.init) }),
effectSticker: effectSticker, effectSticker: TelegramMediaFile.Accessor(effectSticker),
effectAnimation: effectAnimationId.flatMap({ files[$0] }) effectAnimation: effectAnimationId.flatMap({ files[$0].flatMap(TelegramMediaFile.Accessor.init) })
) )
} }
} }
@ -289,22 +315,3 @@ func managedSynchronizeAvailableMessageEffects(postbox: Postbox, network: Networ
) )
|> restart |> restart
} }
public extension Message {
func messageEffect(availableMessageEffects: AvailableMessageEffects?) -> AvailableMessageEffects.MessageEffect? {
guard let availableMessageEffects else {
return nil
}
for attribute in self.attributes {
if let attribute = attribute as? EffectMessageAttribute {
for effect in availableMessageEffects.messageEffects {
if effect.id == attribute.id {
return effect
}
}
break
}
}
return nil
}
}

View File

@ -1411,7 +1411,7 @@ public final class InstantPage: PostboxCoding, Equatable {
self.url = decoder.decodeStringForKey("url", orElse: "") self.url = decoder.decodeStringForKey("url", orElse: "")
self.views = decoder.decodeOptionalInt32ForKey("v") self.views = decoder.decodeOptionalInt32ForKey("v")
#if DEBUG #if DEBUG && false
var builder = FlatBufferBuilder(initialSize: 1024) var builder = FlatBufferBuilder(initialSize: 1024)
let offset = self.encodeToFlatBuffers(builder: &builder) let offset = self.encodeToFlatBuffers(builder: &builder)
builder.finish(offset: offset) builder.finish(offset: offset)

View File

@ -104,7 +104,7 @@ public final class StickerPackCollectionInfo: ItemCollectionInfo, Equatable {
encoder.encodeInt32(self.flags.rawValue, forKey: "f") encoder.encodeInt32(self.flags.rawValue, forKey: "f")
encoder.encodeInt32(self.count, forKey: "n") encoder.encodeInt32(self.count, forKey: "n")
#if DEBUG #if DEBUG && false
var builder = FlatBufferBuilder(initialSize: 1024) var builder = FlatBufferBuilder(initialSize: 1024)
let offset = self.encodeToFlatBuffers(builder: &builder) let offset = self.encodeToFlatBuffers(builder: &builder)
builder.finish(offset: offset) builder.finish(offset: offset)

View File

@ -347,7 +347,7 @@ public final class TelegramChannel: Peer, Equatable {
self.verificationIconFileId = decoder.decodeOptionalInt64ForKey("vfid") self.verificationIconFileId = decoder.decodeOptionalInt64ForKey("vfid")
self.sendPaidMessageStars = decoder.decodeCodable(StarsAmount.self, forKey: "sendPaidMessageStars") self.sendPaidMessageStars = decoder.decodeCodable(StarsAmount.self, forKey: "sendPaidMessageStars")
#if DEBUG #if DEBUG && false
var builder = FlatBufferBuilder(initialSize: 1024) var builder = FlatBufferBuilder(initialSize: 1024)
let offset = self.encodeToFlatBuffers(builder: &builder) let offset = self.encodeToFlatBuffers(builder: &builder)
builder.finish(offset: offset) builder.finish(offset: offset)

View File

@ -207,7 +207,7 @@ public final class TelegramGroup: Peer, Equatable {
self.creationDate = decoder.decodeInt32ForKey("d", orElse: 0) self.creationDate = decoder.decodeInt32ForKey("d", orElse: 0)
self.version = Int(decoder.decodeInt32ForKey("v", orElse: 0)) self.version = Int(decoder.decodeInt32ForKey("v", orElse: 0))
#if DEBUG #if DEBUG && false
var builder = FlatBufferBuilder(initialSize: 1024) var builder = FlatBufferBuilder(initialSize: 1024)
let offset = self.encodeToFlatBuffers(builder: &builder) let offset = self.encodeToFlatBuffers(builder: &builder)
builder.finish(offset: offset) builder.finish(offset: offset)

View File

@ -698,8 +698,7 @@ public final class TelegramMediaFile: Media, Equatable, Codable {
} else if let lhsWrappedData = lhs._wrappedData, let rhsWrappedData = rhs._wrappedData { } else if let lhsWrappedData = lhs._wrappedData, let rhsWrappedData = rhs._wrappedData {
return lhsWrappedData == rhsWrappedData return lhsWrappedData == rhsWrappedData
} else { } else {
assertionFailure() return lhs._parse() == rhs._parse()
return false
} }
} }
} }
@ -903,13 +902,6 @@ public final class TelegramMediaFile: Media, Equatable, Codable {
try container.encode(postboxEncoder.makeData(), forKey: .data) try container.encode(postboxEncoder.makeData(), forKey: .data)
} }
public func encodeToFlatBuffersData() -> Data {
var builder = FlatBufferBuilder(initialSize: 1024)
let value = self.encodeToFlatBuffers(builder: &builder)
builder.finish(offset: value)
return builder.data
}
public init(flatBuffersObject: TelegramCore_TelegramMediaFile) throws { public init(flatBuffersObject: TelegramCore_TelegramMediaFile) throws {
self.fileId = MediaId(namespace: flatBuffersObject.fileId.namespace, id: flatBuffersObject.fileId.id) self.fileId = MediaId(namespace: flatBuffersObject.fileId.namespace, id: flatBuffersObject.fileId.id)
self.partialReference = try flatBuffersObject.partialReference.flatMap { try PartialMediaReference(flatBuffersObject: $0 ) } self.partialReference = try flatBuffersObject.partialReference.flatMap { try PartialMediaReference(flatBuffersObject: $0 ) }

View File

@ -21,10 +21,21 @@ private let cloudSoundMapping: [Int32: Int64] = [
108: 5078299559046677216, 108: 5078299559046677216,
109: 5078299559046677217, 109: 5078299559046677217,
110: 5078299559046677218, 110: 5078299559046677218,
111: 5078299559046677219 111: 5078299559046677219,
200: 5032932652722685163,
201: 5032932652722685160,
202: 5032932652722685159,
203: 5032932652722685158,
204: 5032932652722685168,
205: 5032932652722685167,
206: 5032932652722685166,
207: 5032932652722685165,
208: 5032932652722685164,
209: 5032932652722685162,
210: 5032932652722685161
] ]
public let defaultCloudPeerNotificationSound: PeerMessageSound = .cloud(fileId: cloudSoundMapping[100]!) public let defaultCloudPeerNotificationSound: PeerMessageSound = .cloud(fileId: cloudSoundMapping[200]!)
public enum CloudSoundBuiltinCategory { public enum CloudSoundBuiltinCategory {
case modern case modern

View File

@ -331,7 +331,7 @@ public final class TelegramUser: Peer, Equatable {
self.subscriberCount = decoder.decodeOptionalInt32ForKey("ssc") self.subscriberCount = decoder.decodeOptionalInt32ForKey("ssc")
self.verificationIconFileId = decoder.decodeOptionalInt64ForKey("vfid") self.verificationIconFileId = decoder.decodeOptionalInt64ForKey("vfid")
#if DEBUG #if DEBUG && false
var builder = FlatBufferBuilder(initialSize: 1024) var builder = FlatBufferBuilder(initialSize: 1024)
let offset = self.encodeToFlatBuffers(builder: &builder) let offset = self.encodeToFlatBuffers(builder: &builder)
builder.finish(offset: offset) builder.finish(offset: offset)

View File

@ -642,3 +642,22 @@ public func _internal_parseMediaAttachment(data: Data) -> Media? {
return nil return nil
} }
} }
public extension Message {
func messageEffect(availableMessageEffects: AvailableMessageEffects?) -> AvailableMessageEffects.MessageEffect? {
guard let availableMessageEffects else {
return nil
}
for attribute in self.attributes {
if let attribute = attribute as? EffectMessageAttribute {
for effect in availableMessageEffects.messageEffects {
if effect.id == attribute.id {
return effect
}
}
break
}
}
return nil
}
}

View File

@ -14,7 +14,18 @@ private let modernSoundsNamePaths: [KeyPath<PresentationStrings, String>] = [
\.NotificationsSound_Keys, \.NotificationsSound_Keys,
\.NotificationsSound_Popcorn, \.NotificationsSound_Popcorn,
\.NotificationsSound_Pulse, \.NotificationsSound_Pulse,
\.NotificationsSound_Synth \.NotificationsSound_Synth,
\.NotificationsSound_Rebound,
\.NotificationsSound_Antic,
\.NotificationsSound_Cheers,
\.NotificationsSound_Droplet,
\.NotificationsSound_Handoff,
\.NotificationsSound_Milestone,
\.NotificationsSound_Passage,
\.NotificationsSound_Portal,
\.NotificationsSound_Rattle,
\.NotificationsSound_Slide,
\.NotificationsSound_Welcome
] ]
private let classicSoundNamePaths: [KeyPath<PresentationStrings, String>] = [ private let classicSoundNamePaths: [KeyPath<PresentationStrings, String>] = [

View File

@ -1116,7 +1116,7 @@ public class ChatMessageDateAndStatusNode: ASDisplayNode {
var centerAnimation: TelegramMediaFile? var centerAnimation: TelegramMediaFile?
centerAnimation = messageEffect.staticIcon centerAnimation = messageEffect.staticIcon?._parse()
node.update( node.update(
context: arguments.context, context: arguments.context,

View File

@ -2286,7 +2286,8 @@ public final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTr
if automaticDownload != .none, let file = media as? TelegramMediaFile, NativeVideoContent.isHLSVideo(file: file) { if automaticDownload != .none, let file = media as? TelegramMediaFile, NativeVideoContent.isHLSVideo(file: file) {
let postbox = context.account.postbox let postbox = context.account.postbox
let fetchSignal = HLSVideoContent.minimizedHLSQualityPreloadData(postbox: context.account.postbox, file: .message(message: MessageReference(message), media: file), userLocation: .peer(message.id.peerId), prefixSeconds: 10, autofetchPlaylist: true, codecConfiguration: HLSCodecConfiguration(context: context)) let fetchSignal: Signal<Never, NoError>
fetchSignal = HLSVideoContent.minimizedHLSQualityPreloadData(postbox: context.account.postbox, file: .message(message: MessageReference(message), media: file), userLocation: .peer(message.id.peerId), prefixSeconds: 10, autofetchPlaylist: true, codecConfiguration: HLSCodecConfiguration(context: context))
|> mapToSignal { fileAndRange -> Signal<Never, NoError> in |> mapToSignal { fileAndRange -> Signal<Never, NoError> in
guard let fileAndRange else { guard let fileAndRange else {
return .complete() return .complete()

View File

@ -938,13 +938,13 @@ open class ChatMessageItemView: ListViewItemNode, ChatMessageItemNodeProtocol {
} }
self.playedEffectAnimation = true self.playedEffectAnimation = true
if let effectAnimation = effect.effectAnimation { if let effectAnimation = effect.effectAnimation?._parse() {
self.playEffectAnimation(resource: effectAnimation.resource) self.playEffectAnimation(resource: effectAnimation.resource)
if self.fetchEffectDisposable == nil { if self.fetchEffectDisposable == nil {
self.fetchEffectDisposable = freeMediaFileResourceInteractiveFetched(account: item.context.account, userLocation: .other, fileReference: .standalone(media: effectAnimation), resource: effectAnimation.resource).startStrict() self.fetchEffectDisposable = freeMediaFileResourceInteractiveFetched(account: item.context.account, userLocation: .other, fileReference: .standalone(media: effectAnimation), resource: effectAnimation.resource).startStrict()
} }
} else { } else {
let effectSticker = effect.effectSticker let effectSticker = effect.effectSticker._parse()
if let effectFile = effectSticker.videoThumbnails.first { if let effectFile = effectSticker.videoThumbnails.first {
self.playEffectAnimation(resource: effectFile.resource) self.playEffectAnimation(resource: effectFile.resource)
if self.fetchEffectDisposable == nil { if self.fetchEffectDisposable == nil {

View File

@ -501,7 +501,7 @@ public func effectMessageReactions(context: AccountContext) -> Signal<[ReactionI
} }
existingIds.insert(messageEffect.id) existingIds.insert(messageEffect.id)
let mainFile = TelegramMediaFile.Accessor(messageEffect.effectSticker) let mainFile = messageEffect.effectSticker
result.append(ReactionItem( result.append(ReactionItem(
reaction: ReactionItem.Reaction(rawValue: .custom(messageEffect.id)), reaction: ReactionItem.Reaction(rawValue: .custom(messageEffect.id)),

View File

@ -82,18 +82,20 @@ public func generateTopicIcon(title: String, backgroundColors: [UIColor], stroke
}) })
} }
public enum AnimationCacheAnimationType { public enum AnimationCacheAnimationType: Equatable {
case still case still
case lottie case lottie
case video case video(isVP9: Bool)
} }
public extension AnimationCacheAnimationType { public extension AnimationCacheAnimationType {
init(file: TelegramMediaFile) { init(file: TelegramMediaFile) {
if file.isVideoSticker || file.isVideoEmoji { if file.isVideoSticker || file.isVideoEmoji {
self = .video self = .video(isVP9: true)
} else if file.isAnimatedSticker { } else if file.isAnimatedSticker {
self = .lottie self = .lottie
} else if file.isVideo {
self = .video(isVP9: false)
} else { } else {
self = .still self = .still
} }
@ -122,8 +124,8 @@ public func animationCacheFetchFile(postbox: Postbox, userLocation: MediaResourc
} }
switch type { switch type {
case .video: case let .video(isVP9):
cacheVideoAnimation(path: result, width: Int(options.size.width), height: Int(options.size.height), writer: options.writer, firstFrameOnly: options.firstFrameOnly, customColor: customColor) cacheVideoAnimation(path: result, hintVP9: isVP9, width: Int(options.size.width), height: Int(options.size.height), writer: options.writer, firstFrameOnly: options.firstFrameOnly, customColor: customColor)
case .lottie: case .lottie:
guard let data = try? Data(contentsOf: URL(fileURLWithPath: result)) else { guard let data = try? Data(contentsOf: URL(fileURLWithPath: result)) else {
options.writer.finish() options.writer.finish()
@ -153,8 +155,8 @@ public func animationCacheLoadLocalFile(name: String, type: AnimationCacheAnimat
} }
switch type { switch type {
case .video: case let .video(isVP9):
cacheVideoAnimation(path: result, width: Int(options.size.width), height: Int(options.size.height), writer: options.writer, firstFrameOnly: options.firstFrameOnly, customColor: customColor) cacheVideoAnimation(path: result, hintVP9: isVP9, width: Int(options.size.width), height: Int(options.size.height), writer: options.writer, firstFrameOnly: options.firstFrameOnly, customColor: customColor)
case .lottie: case .lottie:
guard let data = try? Data(contentsOf: URL(fileURLWithPath: result)) else { guard let data = try? Data(contentsOf: URL(fileURLWithPath: result)) else {
options.writer.finish() options.writer.finish()

View File

@ -33,7 +33,7 @@ public final class EmojiKeyboardItemLayer: MultiAnimationRenderTarget {
case locked case locked
case featured case featured
case text(String) case text(String)
case customFile(TelegramMediaFile) case customFile(TelegramMediaFile.Accessor)
} }
public let item: EmojiPagerContentComponent.Item public let item: EmojiPagerContentComponent.Item

View File

@ -43,10 +43,10 @@ public final class EntityKeyboardAnimationData: Equatable {
case gift(String) case gift(String)
} }
public enum ItemType { public enum ItemType: Equatable {
case still case still
case lottie case lottie
case video case video(isVP9: Bool)
var animationCacheAnimationType: AnimationCacheAnimationType { var animationCacheAnimationType: AnimationCacheAnimationType {
switch self { switch self {
@ -54,8 +54,8 @@ public final class EntityKeyboardAnimationData: Equatable {
return .still return .still
case .lottie: case .lottie:
return .lottie return .lottie
case .video: case let .video(isVP9):
return .video return .video(isVP9: isVP9)
} }
} }
} }
@ -105,9 +105,11 @@ public final class EntityKeyboardAnimationData: Equatable {
public convenience init(file: TelegramMediaFile.Accessor, isReaction: Bool = false, partialReference: PartialMediaReference? = nil) { public convenience init(file: TelegramMediaFile.Accessor, isReaction: Bool = false, partialReference: PartialMediaReference? = nil) {
let type: ItemType let type: ItemType
if file.isVideoSticker || file.isVideoEmoji { if file.isVideoSticker || file.isVideoEmoji {
type = .video type = .video(isVP9: true)
} else if file.isAnimatedSticker { } else if file.isAnimatedSticker {
type = .lottie type = .lottie
} else if file.isVideo {
type = .video(isVP9: false)
} else { } else {
type = .still type = .still
} }
@ -406,7 +408,7 @@ public final class EmojiPagerContentComponent: Component {
case locked case locked
case premium case premium
case text(String) case text(String)
case customFile(TelegramMediaFile) case customFile(TelegramMediaFile.Accessor)
} }
public enum TintMode: Equatable { public enum TintMode: Equatable {

View File

@ -287,7 +287,9 @@ public extension EmojiPagerContentComponent {
if item.file.isAnimatedSticker { if item.file.isAnimatedSticker {
type = .lottie type = .lottie
} else if item.file.isVideoEmoji || item.file.isVideoSticker { } else if item.file.isVideoEmoji || item.file.isVideoSticker {
type = .video type = .video(isVP9: true)
} else if item.file.isVideo {
type = .video(isVP9: false)
} else { } else {
type = .still type = .still
} }
@ -1390,7 +1392,9 @@ public extension EmojiPagerContentComponent {
if item.file.isAnimatedSticker { if item.file.isAnimatedSticker {
type = .lottie type = .lottie
} else if item.file.isVideoEmoji || item.file.isVideoSticker { } else if item.file.isVideoEmoji || item.file.isVideoSticker {
type = .video type = .video(isVP9: true)
} else if item.file.isVideo {
type = .video(isVP9: false)
} else { } else {
type = .still type = .still
} }
@ -1477,7 +1481,9 @@ public extension EmojiPagerContentComponent {
if item.file.isAnimatedSticker { if item.file.isAnimatedSticker {
type = .lottie type = .lottie
} else if item.file.isVideoEmoji || item.file.isVideoSticker { } else if item.file.isVideoEmoji || item.file.isVideoSticker {
type = .video type = .video(isVP9: true)
} else if item.file.isVideo {
type = .video(isVP9: false)
} else { } else {
type = .still type = .still
} }
@ -1774,7 +1780,9 @@ public extension EmojiPagerContentComponent {
if item.file.isAnimatedSticker { if item.file.isAnimatedSticker {
type = .lottie type = .lottie
} else if item.file.isVideoEmoji || item.file.isVideoSticker { } else if item.file.isVideoEmoji || item.file.isVideoSticker {
type = .video type = .video(isVP9: true)
} else if item.file.isVideo {
type = .video(isVP9: false)
} else { } else {
type = .still type = .still
} }
@ -2011,7 +2019,9 @@ public extension EmojiPagerContentComponent {
if item.file.isAnimatedSticker { if item.file.isAnimatedSticker {
type = .lottie type = .lottie
} else if item.file.isVideoEmoji || item.file.isVideoSticker { } else if item.file.isVideoEmoji || item.file.isVideoSticker {
type = .video type = .video(isVP9: true)
} else if item.file.isVideo {
type = .video(isVP9: false)
} else { } else {
type = .still type = .still
} }
@ -2090,7 +2100,9 @@ public extension EmojiPagerContentComponent {
if item.file.isAnimatedSticker { if item.file.isAnimatedSticker {
type = .lottie type = .lottie
} else if item.file.isVideoEmoji || item.file.isVideoSticker { } else if item.file.isVideoEmoji || item.file.isVideoSticker {
type = .video type = .video(isVP9: true)
} else if item.file.isVideo {
type = .video(isVP9: false)
} else { } else {
type = .still type = .still
} }
@ -2234,7 +2246,7 @@ public extension EmojiPagerContentComponent {
continue continue
} }
let itemFile: TelegramMediaFile = item.effectSticker let itemFile = item.effectSticker
var tintMode: Item.TintMode = .none var tintMode: Item.TintMode = .none
if itemFile.isCustomTemplateEmoji { if itemFile.isCustomTemplateEmoji {
@ -2258,11 +2270,11 @@ public extension EmojiPagerContentComponent {
} }
} }
let animationData = EntityKeyboardAnimationData(file: TelegramMediaFile.Accessor(itemFile), partialReference: .none) let animationData = EntityKeyboardAnimationData(file: itemFile, partialReference: .none)
let resultItem = EmojiPagerContentComponent.Item( let resultItem = EmojiPagerContentComponent.Item(
animationData: animationData, animationData: animationData,
content: .animation(animationData), content: .animation(animationData),
itemFile: TelegramMediaFile.Accessor(itemFile), itemFile: itemFile,
subgroupId: nil, subgroupId: nil,
icon: icon, icon: icon,
tintMode: tintMode tintMode: tintMode

View File

@ -81,7 +81,7 @@ final class PremiumBadgeView: UIView {
context: self.context, context: self.context,
userLocation: .other, userLocation: .other,
attemptSynchronousLoad: false, attemptSynchronousLoad: false,
file: customFile, file: customFile._parse(),
cache: self.context.animationCache, cache: self.context.animationCache,
renderer: self.context.animationRenderer, renderer: self.context.animationRenderer,
unique: false, unique: false,

View File

@ -18,9 +18,9 @@ private func roundUp(_ numToRound: Int, multiple: Int) -> Int {
return numToRound + multiple - remainder return numToRound + multiple - remainder
} }
public func cacheVideoAnimation(path: String, width: Int, height: Int, writer: AnimationCacheItemWriter, firstFrameOnly: Bool, customColor: UIColor?) { public func cacheVideoAnimation(path: String, hintVP9: Bool, width: Int, height: Int, writer: AnimationCacheItemWriter, firstFrameOnly: Bool, customColor: UIColor?) {
let work: () -> Void = { let work: () -> Void = {
guard let frameSource = makeVideoStickerDirectFrameSource(queue: writer.queue, path: path, width: roundUp(width, multiple: 16), height: roundUp(height, multiple: 16), cachePathPrefix: nil, unpremultiplyAlpha: false) else { guard let frameSource = makeVideoStickerDirectFrameSource(queue: writer.queue, path: path, hintVP9: hintVP9, width: roundUp(width, multiple: 16), height: roundUp(height, multiple: 16), cachePathPrefix: nil, unpremultiplyAlpha: false) else {
return return
} }
let frameDuration = 1.0 / Double(frameSource.frameRate) let frameDuration = 1.0 / Double(frameSource.frameRate)

View File

@ -102,7 +102,7 @@ private final class EffectBadgeView: UIView {
} }
let effectIconContent: ChatSendMessageScreenEffectIcon.Content let effectIconContent: ChatSendMessageScreenEffectIcon.Content
if let staticIcon = effect.staticIcon { if let staticIcon = effect.staticIcon {
effectIconContent = .file(staticIcon) effectIconContent = .file(staticIcon._parse())
} else { } else {
effectIconContent = .text(effect.emoticon) effectIconContent = .text(effect.emoticon)
} }