mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Video Stickers Improvements
This commit is contained in:
parent
b102311660
commit
47363cb2e3
@ -1146,7 +1146,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
|
|||||||
}
|
}
|
||||||
break inner
|
break inner
|
||||||
} else if let file = media as? TelegramMediaFile {
|
} else if let file = media as? TelegramMediaFile {
|
||||||
if file.isVideo, !file.isInstantVideo, let _ = file.dimensions {
|
if file.isVideo, !file.isInstantVideo && !file.isVideoSticker, let _ = file.dimensions {
|
||||||
let fitSize = contentImageSize
|
let fitSize = contentImageSize
|
||||||
contentImageSpecs.append((message, .file(file), fitSize))
|
contentImageSpecs.append((message, .file(file), fitSize))
|
||||||
}
|
}
|
||||||
|
@ -277,7 +277,7 @@ public final class FFMpegMediaVideoFrameDecoder: MediaTrackFrameDecoder {
|
|||||||
options as CFDictionary,
|
options as CFDictionary,
|
||||||
&pixelBufferRef)
|
&pixelBufferRef)
|
||||||
}
|
}
|
||||||
|
|
||||||
guard let pixelBuffer = pixelBufferRef else {
|
guard let pixelBuffer = pixelBufferRef else {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -399,11 +399,7 @@ public final class FFMpegMediaVideoFrameDecoder: MediaTrackFrameDecoder {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func decodeImage() {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public func reset() {
|
public func reset() {
|
||||||
self.codecContext.flushBuffers()
|
self.codecContext.flushBuffers()
|
||||||
self.resetDecoderOnNextFrame = true
|
self.resetDecoderOnNextFrame = true
|
||||||
|
23
submodules/SoftwareVideo/BUILD
Normal file
23
submodules/SoftwareVideo/BUILD
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
|
||||||
|
|
||||||
|
swift_library(
|
||||||
|
name = "SoftwareVideo",
|
||||||
|
module_name = "SoftwareVideo",
|
||||||
|
srcs = glob([
|
||||||
|
"Sources/**/*.swift",
|
||||||
|
]),
|
||||||
|
copts = [
|
||||||
|
"-warnings-as-errors",
|
||||||
|
],
|
||||||
|
deps = [
|
||||||
|
"//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit",
|
||||||
|
"//submodules/AsyncDisplayKit:AsyncDisplayKit",
|
||||||
|
"//submodules/Display:Display",
|
||||||
|
"//submodules/TelegramCore:TelegramCore",
|
||||||
|
"//submodules/AccountContext:AccountContext",
|
||||||
|
"//submodules/MediaPlayer:UniversalMediaPlayer",
|
||||||
|
],
|
||||||
|
visibility = [
|
||||||
|
"//visibility:public",
|
||||||
|
],
|
||||||
|
)
|
22
submodules/SoftwareVideo/Info.plist
Normal file
22
submodules/SoftwareVideo/Info.plist
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>CFBundleDevelopmentRegion</key>
|
||||||
|
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||||
|
<key>CFBundleExecutable</key>
|
||||||
|
<string>$(EXECUTABLE_NAME)</string>
|
||||||
|
<key>CFBundleIdentifier</key>
|
||||||
|
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||||
|
<key>CFBundleInfoDictionaryVersion</key>
|
||||||
|
<string>6.0</string>
|
||||||
|
<key>CFBundleName</key>
|
||||||
|
<string>$(PRODUCT_NAME)</string>
|
||||||
|
<key>CFBundlePackageType</key>
|
||||||
|
<string>FMWK</string>
|
||||||
|
<key>CFBundleShortVersionString</key>
|
||||||
|
<string>1.0</string>
|
||||||
|
<key>CFBundleVersion</key>
|
||||||
|
<string>$(CURRENT_PROJECT_VERSION)</string>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
@ -14,19 +14,19 @@ private final class SampleBufferLayerImpl: AVSampleBufferDisplayLayer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final class SampleBufferLayer {
|
public final class SampleBufferLayer {
|
||||||
let layer: AVSampleBufferDisplayLayer
|
public let layer: AVSampleBufferDisplayLayer
|
||||||
private let enqueue: (AVSampleBufferDisplayLayer) -> Void
|
private let enqueue: (AVSampleBufferDisplayLayer) -> Void
|
||||||
|
|
||||||
|
|
||||||
var isFreed: Bool = false
|
public var isFreed: Bool = false
|
||||||
fileprivate init(layer: AVSampleBufferDisplayLayer, enqueue: @escaping (AVSampleBufferDisplayLayer) -> Void) {
|
fileprivate init(layer: AVSampleBufferDisplayLayer, enqueue: @escaping (AVSampleBufferDisplayLayer) -> Void) {
|
||||||
self.layer = layer
|
self.layer = layer
|
||||||
self.enqueue = enqueue
|
self.enqueue = enqueue
|
||||||
}
|
}
|
||||||
|
|
||||||
deinit {
|
deinit {
|
||||||
if !isFreed {
|
if !self.isFreed {
|
||||||
self.enqueue(self.layer)
|
self.enqueue(self.layer)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -34,11 +34,11 @@ final class SampleBufferLayer {
|
|||||||
|
|
||||||
private let pool = Atomic<[AVSampleBufferDisplayLayer]>(value: [])
|
private let pool = Atomic<[AVSampleBufferDisplayLayer]>(value: [])
|
||||||
|
|
||||||
func clearSampleBufferLayerPoll() {
|
public func clearSampleBufferLayerPoll() {
|
||||||
let _ = pool.modify { _ in return [] }
|
let _ = pool.modify { _ in return [] }
|
||||||
}
|
}
|
||||||
|
|
||||||
func takeSampleBufferLayer() -> SampleBufferLayer {
|
public func takeSampleBufferLayer() -> SampleBufferLayer {
|
||||||
var layer: AVSampleBufferDisplayLayer?
|
var layer: AVSampleBufferDisplayLayer?
|
||||||
let _ = pool.modify { list in
|
let _ = pool.modify { list in
|
||||||
var list = list
|
var list = list
|
@ -10,7 +10,7 @@ private let applyQueue = Queue()
|
|||||||
private let workers = ThreadPool(threadCount: 3, threadPriority: 0.2)
|
private let workers = ThreadPool(threadCount: 3, threadPriority: 0.2)
|
||||||
private var nextWorker = 0
|
private var nextWorker = 0
|
||||||
|
|
||||||
final class SoftwareVideoLayerFrameManager {
|
public final class SoftwareVideoLayerFrameManager {
|
||||||
private let fetchDisposable: Disposable
|
private let fetchDisposable: Disposable
|
||||||
private var dataDisposable = MetaDisposable()
|
private var dataDisposable = MetaDisposable()
|
||||||
private let source = Atomic<SoftwareVideoSource?>(value: nil)
|
private let source = Atomic<SoftwareVideoSource?>(value: nil)
|
||||||
@ -32,7 +32,10 @@ final class SoftwareVideoLayerFrameManager {
|
|||||||
|
|
||||||
private var layerRotationAngleAndAspect: (CGFloat, CGFloat)?
|
private var layerRotationAngleAndAspect: (CGFloat, CGFloat)?
|
||||||
|
|
||||||
init(account: Account, fileReference: FileMediaReference, layerHolder: SampleBufferLayer, hintVP9: Bool = false) {
|
private var didStart = false
|
||||||
|
var started: () -> Void = { }
|
||||||
|
|
||||||
|
public init(account: Account, fileReference: FileMediaReference, layerHolder: SampleBufferLayer, hintVP9: Bool = false) {
|
||||||
var resource = fileReference.media.resource
|
var resource = fileReference.media.resource
|
||||||
var secondaryResource: MediaResource?
|
var secondaryResource: MediaResource?
|
||||||
for attribute in fileReference.media.attributes {
|
for attribute in fileReference.media.attributes {
|
||||||
@ -61,7 +64,7 @@ final class SoftwareVideoLayerFrameManager {
|
|||||||
self.dataDisposable.dispose()
|
self.dataDisposable.dispose()
|
||||||
}
|
}
|
||||||
|
|
||||||
func start() {
|
public func start() {
|
||||||
func stringForResource(_ resource: MediaResource?) -> String {
|
func stringForResource(_ resource: MediaResource?) -> String {
|
||||||
guard let resource = resource else {
|
guard let resource = resource else {
|
||||||
return "<none>"
|
return "<none>"
|
||||||
@ -115,7 +118,7 @@ final class SoftwareVideoLayerFrameManager {
|
|||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
func tick(timestamp: Double) {
|
public func tick(timestamp: Double) {
|
||||||
applyQueue.async {
|
applyQueue.async {
|
||||||
if self.baseTimestamp == nil && !self.frames.isEmpty {
|
if self.baseTimestamp == nil && !self.frames.isEmpty {
|
||||||
self.baseTimestamp = timestamp
|
self.baseTimestamp = timestamp
|
||||||
@ -148,6 +151,13 @@ final class SoftwareVideoLayerFrameManager {
|
|||||||
self.layerHolder.layer.setAffineTransform(transform)
|
self.layerHolder.layer.setAffineTransform(transform)
|
||||||
}*/
|
}*/
|
||||||
self.layerHolder.layer.enqueue(frame.sampleBuffer)
|
self.layerHolder.layer.enqueue(frame.sampleBuffer)
|
||||||
|
|
||||||
|
if !self.didStart {
|
||||||
|
self.didStart = true
|
||||||
|
Queue.mainQueue().async {
|
||||||
|
self.started()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
56
submodules/SoftwareVideo/Sources/VideoStickerNode.swift
Normal file
56
submodules/SoftwareVideo/Sources/VideoStickerNode.swift
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
import Foundation
|
||||||
|
import AVFoundation
|
||||||
|
import AsyncDisplayKit
|
||||||
|
import Display
|
||||||
|
import TelegramCore
|
||||||
|
|
||||||
|
public class VideoStickerNode: ASDisplayNode {
|
||||||
|
private var layerHolder: SampleBufferLayer?
|
||||||
|
private var manager: SoftwareVideoLayerFrameManager?
|
||||||
|
|
||||||
|
private var displayLink: ConstantDisplayLinkAnimator?
|
||||||
|
private var displayLinkTimestamp: Double = 0.0
|
||||||
|
|
||||||
|
public var started: () -> Void = {}
|
||||||
|
|
||||||
|
private var validLayout: CGSize?
|
||||||
|
|
||||||
|
public func update(isPlaying: Bool) {
|
||||||
|
let displayLink: ConstantDisplayLinkAnimator
|
||||||
|
if let current = self.displayLink {
|
||||||
|
displayLink = current
|
||||||
|
} else {
|
||||||
|
displayLink = ConstantDisplayLinkAnimator { [weak self] in
|
||||||
|
guard let strongSelf = self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
strongSelf.manager?.tick(timestamp: strongSelf.displayLinkTimestamp)
|
||||||
|
strongSelf.displayLinkTimestamp += 1.0 / 30.0
|
||||||
|
}
|
||||||
|
displayLink.frameInterval = 2
|
||||||
|
self.displayLink = displayLink
|
||||||
|
}
|
||||||
|
self.displayLink?.isPaused = !isPlaying
|
||||||
|
}
|
||||||
|
|
||||||
|
public func update(account: Account, fileReference: FileMediaReference) {
|
||||||
|
let layerHolder = takeSampleBufferLayer()
|
||||||
|
layerHolder.layer.videoGravity = AVLayerVideoGravity.resizeAspectFill
|
||||||
|
if let size = self.validLayout {
|
||||||
|
layerHolder.layer.frame = CGRect(origin: CGPoint(), size: size)
|
||||||
|
}
|
||||||
|
self.layer.addSublayer(layerHolder.layer)
|
||||||
|
self.layerHolder = layerHolder
|
||||||
|
|
||||||
|
let manager = SoftwareVideoLayerFrameManager(account: account, fileReference: fileReference, layerHolder: layerHolder, hintVP9: true)
|
||||||
|
manager.started = self.started
|
||||||
|
self.manager = manager
|
||||||
|
manager.start()
|
||||||
|
}
|
||||||
|
|
||||||
|
public func updateLayout(size: CGSize) {
|
||||||
|
self.validLayout = size
|
||||||
|
|
||||||
|
self.layerHolder?.layer.frame = CGRect(origin: CGPoint(), size: size)
|
||||||
|
}
|
||||||
|
}
|
@ -31,6 +31,7 @@ swift_library(
|
|||||||
"//submodules/ShimmerEffect:ShimmerEffect",
|
"//submodules/ShimmerEffect:ShimmerEffect",
|
||||||
"//submodules/UndoUI:UndoUI",
|
"//submodules/UndoUI:UndoUI",
|
||||||
"//submodules/ContextUI:ContextUI",
|
"//submodules/ContextUI:ContextUI",
|
||||||
|
"//submodules/SoftwareVideo:SoftwareVideo",
|
||||||
],
|
],
|
||||||
visibility = [
|
visibility = [
|
||||||
"//visibility:public",
|
"//visibility:public",
|
||||||
|
@ -11,6 +11,7 @@ import AnimatedStickerNode
|
|||||||
import TelegramAnimatedStickerNode
|
import TelegramAnimatedStickerNode
|
||||||
import TelegramPresentationData
|
import TelegramPresentationData
|
||||||
import ShimmerEffect
|
import ShimmerEffect
|
||||||
|
import SoftwareVideo
|
||||||
|
|
||||||
final class StickerPackPreviewInteraction {
|
final class StickerPackPreviewInteraction {
|
||||||
var previewedItem: StickerPreviewPeekItem?
|
var previewedItem: StickerPreviewPeekItem?
|
||||||
@ -60,13 +61,19 @@ final class StickerPackPreviewGridItemNode: GridItemNode {
|
|||||||
private var isEmpty: Bool?
|
private var isEmpty: Bool?
|
||||||
private let imageNode: TransformImageNode
|
private let imageNode: TransformImageNode
|
||||||
private var animationNode: AnimatedStickerNode?
|
private var animationNode: AnimatedStickerNode?
|
||||||
|
private var videoNode: VideoStickerNode?
|
||||||
private var placeholderNode: StickerShimmerEffectNode?
|
private var placeholderNode: StickerShimmerEffectNode?
|
||||||
|
|
||||||
private var theme: PresentationTheme?
|
private var theme: PresentationTheme?
|
||||||
|
|
||||||
override var isVisibleInGrid: Bool {
|
override var isVisibleInGrid: Bool {
|
||||||
didSet {
|
didSet {
|
||||||
self.animationNode?.visibility = self.isVisibleInGrid && self.interaction?.playAnimatedStickers ?? true
|
let visibility = self.isVisibleInGrid && (self.interaction?.playAnimatedStickers ?? true)
|
||||||
|
if let videoNode = self.videoNode {
|
||||||
|
videoNode.update(isPlaying: visibility)
|
||||||
|
} else if let animationNode = self.animationNode {
|
||||||
|
animationNode.visibility = visibility
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -139,7 +146,20 @@ final class StickerPackPreviewGridItemNode: GridItemNode {
|
|||||||
|
|
||||||
if self.currentState == nil || self.currentState!.0 !== account || self.currentState!.1 != stickerItem || self.isEmpty != isEmpty {
|
if self.currentState == nil || self.currentState!.0 !== account || self.currentState!.1 != stickerItem || self.isEmpty != isEmpty {
|
||||||
if let stickerItem = stickerItem {
|
if let stickerItem = stickerItem {
|
||||||
if stickerItem.file.isAnimatedSticker {
|
if stickerItem.file.isVideoSticker {
|
||||||
|
if self.videoNode == nil {
|
||||||
|
let videoNode = VideoStickerNode()
|
||||||
|
self.videoNode = videoNode
|
||||||
|
self.addSubnode(videoNode)
|
||||||
|
videoNode.started = { [weak self] in
|
||||||
|
self?.imageNode.isHidden = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.videoNode?.update(account: account, fileReference: stickerPackFileReference(stickerItem.file))
|
||||||
|
|
||||||
|
self.imageNode.setSignal(chatMessageSticker(account: account, file: stickerItem.file, small: true))
|
||||||
|
self.stickerFetchedDisposable.set(freeMediaFileResourceInteractiveFetched(account: account, fileReference: stickerPackFileReference(stickerItem.file), resource: chatMessageStickerResource(file: stickerItem.file, small: true)).start())
|
||||||
|
} else if stickerItem.file.isAnimatedSticker {
|
||||||
let dimensions = stickerItem.file.dimensions ?? PixelDimensions(width: 512, height: 512)
|
let dimensions = stickerItem.file.dimensions ?? PixelDimensions(width: 512, height: 512)
|
||||||
self.imageNode.setSignal(chatMessageAnimatedSticker(postbox: account.postbox, file: stickerItem.file, small: false, size: dimensions.cgSize.aspectFitted(CGSize(width: 160.0, height: 160.0))))
|
self.imageNode.setSignal(chatMessageAnimatedSticker(postbox: account.postbox, file: stickerItem.file, small: false, size: dimensions.cgSize.aspectFitted(CGSize(width: 160.0, height: 160.0))))
|
||||||
|
|
||||||
@ -202,10 +222,15 @@ final class StickerPackPreviewGridItemNode: GridItemNode {
|
|||||||
if let (_, item) = self.currentState {
|
if let (_, item) = self.currentState {
|
||||||
if let item = item, let dimensions = item.file.dimensions?.cgSize {
|
if let item = item, let dimensions = item.file.dimensions?.cgSize {
|
||||||
let imageSize = dimensions.aspectFitted(boundingSize)
|
let imageSize = dimensions.aspectFitted(boundingSize)
|
||||||
|
let imageFrame = CGRect(origin: CGPoint(x: floor((bounds.size.width - imageSize.width) / 2.0), y: (bounds.size.height - imageSize.height) / 2.0), size: imageSize)
|
||||||
self.imageNode.asyncLayout()(TransformImageArguments(corners: ImageCorners(), imageSize: imageSize, boundingSize: imageSize, intrinsicInsets: UIEdgeInsets()))()
|
self.imageNode.asyncLayout()(TransformImageArguments(corners: ImageCorners(), imageSize: imageSize, boundingSize: imageSize, intrinsicInsets: UIEdgeInsets()))()
|
||||||
self.imageNode.frame = CGRect(origin: CGPoint(x: floor((bounds.size.width - imageSize.width) / 2.0), y: (bounds.size.height - imageSize.height) / 2.0), size: imageSize)
|
self.imageNode.frame = imageFrame
|
||||||
|
if let videoNode = self.videoNode {
|
||||||
|
videoNode.frame = imageFrame
|
||||||
|
videoNode.updateLayout(size: imageSize)
|
||||||
|
}
|
||||||
if let animationNode = self.animationNode {
|
if let animationNode = self.animationNode {
|
||||||
animationNode.frame = CGRect(origin: CGPoint(x: floor((bounds.size.width - imageSize.width) / 2.0), y: (bounds.size.height - imageSize.height) / 2.0), size: imageSize)
|
animationNode.frame = imageFrame
|
||||||
animationNode.updateLayout(size: imageSize)
|
animationNode.updateLayout(size: imageSize)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,6 +9,7 @@ import StickerResources
|
|||||||
import AnimatedStickerNode
|
import AnimatedStickerNode
|
||||||
import TelegramAnimatedStickerNode
|
import TelegramAnimatedStickerNode
|
||||||
import ContextUI
|
import ContextUI
|
||||||
|
import SoftwareVideo
|
||||||
|
|
||||||
public enum StickerPreviewPeekItem: Equatable {
|
public enum StickerPreviewPeekItem: Equatable {
|
||||||
case pack(StickerPackItem)
|
case pack(StickerPackItem)
|
||||||
@ -71,6 +72,7 @@ public final class StickerPreviewPeekContentNode: ASDisplayNode, PeekControllerC
|
|||||||
private var textNode: ASTextNode
|
private var textNode: ASTextNode
|
||||||
public var imageNode: TransformImageNode
|
public var imageNode: TransformImageNode
|
||||||
public var animationNode: AnimatedStickerNode?
|
public var animationNode: AnimatedStickerNode?
|
||||||
|
public var videoNode: VideoStickerNode?
|
||||||
|
|
||||||
private var containerLayout: (ContainerViewLayout, CGFloat)?
|
private var containerLayout: (ContainerViewLayout, CGFloat)?
|
||||||
|
|
||||||
@ -86,16 +88,24 @@ public final class StickerPreviewPeekContentNode: ASDisplayNode, PeekControllerC
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
if item.file.isAnimatedSticker {
|
if item.file.isVideoSticker {
|
||||||
|
let videoNode = VideoStickerNode()
|
||||||
|
self.videoNode = videoNode
|
||||||
|
|
||||||
|
videoNode.update(account: self.account, fileReference: .standalone(media: item.file))
|
||||||
|
videoNode.update(isPlaying: true)
|
||||||
|
|
||||||
|
videoNode.addSubnode(self.textNode)
|
||||||
|
} else if item.file.isAnimatedSticker {
|
||||||
let animationNode = AnimatedStickerNode()
|
let animationNode = AnimatedStickerNode()
|
||||||
self.animationNode = animationNode
|
self.animationNode = animationNode
|
||||||
|
|
||||||
let dimensions = item.file.dimensions ?? PixelDimensions(width: 512, height: 512)
|
let dimensions = item.file.dimensions ?? PixelDimensions(width: 512, height: 512)
|
||||||
let fittedDimensions = dimensions.cgSize.aspectFitted(CGSize(width: 400.0, height: 400.0))
|
let fittedDimensions = dimensions.cgSize.aspectFitted(CGSize(width: 400.0, height: 400.0))
|
||||||
|
|
||||||
self.animationNode?.setup(source: AnimatedStickerResourceSource(account: account, resource: item.file.resource), width: Int(fittedDimensions.width), height: Int(fittedDimensions.height), mode: .direct(cachePathPrefix: nil))
|
animationNode.setup(source: AnimatedStickerResourceSource(account: account, resource: item.file.resource), width: Int(fittedDimensions.width), height: Int(fittedDimensions.height), mode: .direct(cachePathPrefix: nil))
|
||||||
self.animationNode?.visibility = true
|
animationNode.visibility = true
|
||||||
self.animationNode?.addSubnode(self.textNode)
|
animationNode.addSubnode(self.textNode)
|
||||||
} else {
|
} else {
|
||||||
self.imageNode.addSubnode(self.textNode)
|
self.imageNode.addSubnode(self.textNode)
|
||||||
self.animationNode = nil
|
self.animationNode = nil
|
||||||
@ -107,7 +117,9 @@ public final class StickerPreviewPeekContentNode: ASDisplayNode, PeekControllerC
|
|||||||
|
|
||||||
self.isUserInteractionEnabled = false
|
self.isUserInteractionEnabled = false
|
||||||
|
|
||||||
if let animationNode = self.animationNode {
|
if let videoNode = self.videoNode {
|
||||||
|
self.addSubnode(videoNode)
|
||||||
|
} else if let animationNode = self.animationNode {
|
||||||
self.addSubnode(animationNode)
|
self.addSubnode(animationNode)
|
||||||
} else {
|
} else {
|
||||||
self.addSubnode(self.imageNode)
|
self.addSubnode(self.imageNode)
|
||||||
@ -125,7 +137,10 @@ public final class StickerPreviewPeekContentNode: ASDisplayNode, PeekControllerC
|
|||||||
self.imageNode.asyncLayout()(TransformImageArguments(corners: ImageCorners(), imageSize: imageSize, boundingSize: imageSize, intrinsicInsets: UIEdgeInsets()))()
|
self.imageNode.asyncLayout()(TransformImageArguments(corners: ImageCorners(), imageSize: imageSize, boundingSize: imageSize, intrinsicInsets: UIEdgeInsets()))()
|
||||||
let imageFrame = CGRect(origin: CGPoint(x: 0.0, y: textSize.height + textSpacing), size: imageSize)
|
let imageFrame = CGRect(origin: CGPoint(x: 0.0, y: textSize.height + textSpacing), size: imageSize)
|
||||||
self.imageNode.frame = imageFrame
|
self.imageNode.frame = imageFrame
|
||||||
if let animationNode = self.animationNode {
|
if let videoNode = self.videoNode {
|
||||||
|
videoNode.frame = imageFrame
|
||||||
|
videoNode.updateLayout(size: imageSize)
|
||||||
|
} else if let animationNode = self.animationNode {
|
||||||
animationNode.frame = imageFrame
|
animationNode.frame = imageFrame
|
||||||
animationNode.updateLayout(size: imageSize)
|
animationNode.updateLayout(size: imageSize)
|
||||||
}
|
}
|
||||||
|
@ -478,6 +478,26 @@ public final class TelegramMediaFile: Media, Equatable, Codable {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public var isVideoSticker: Bool {
|
||||||
|
if let fileName = self.fileName, fileName.hasSuffix(".webm") {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if let _ = self.fileName, self.mimeType == "video/webm" {
|
||||||
|
var hasSticker = false
|
||||||
|
var hasAnimated = false
|
||||||
|
for attribute in self.attributes {
|
||||||
|
if case .Sticker = attribute {
|
||||||
|
hasSticker = true
|
||||||
|
}
|
||||||
|
if case .Animated = attribute {
|
||||||
|
hasAnimated = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return hasSticker && hasAnimated
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
public var hasLinkedStickers: Bool {
|
public var hasLinkedStickers: Bool {
|
||||||
for attribute in self.attributes {
|
for attribute in self.attributes {
|
||||||
if case .HasLinkedStickers = attribute {
|
if case .HasLinkedStickers = attribute {
|
||||||
|
@ -254,6 +254,7 @@ swift_library(
|
|||||||
"//submodules/Components/ReactionImageComponent:ReactionImageComponent",
|
"//submodules/Components/ReactionImageComponent:ReactionImageComponent",
|
||||||
"//submodules/Translate:Translate",
|
"//submodules/Translate:Translate",
|
||||||
"//submodules/TabBarUI:TabBarUI",
|
"//submodules/TabBarUI:TabBarUI",
|
||||||
|
"//submodules/SoftwareVideo:SoftwareVideo",
|
||||||
] + select({
|
] + select({
|
||||||
"@build_bazel_rules_apple//apple:ios_armv7": [],
|
"@build_bazel_rules_apple//apple:ios_armv7": [],
|
||||||
"@build_bazel_rules_apple//apple:ios_arm64": appcenter_targets,
|
"@build_bazel_rules_apple//apple:ios_arm64": appcenter_targets,
|
||||||
|
@ -9,6 +9,7 @@ import AVFoundation
|
|||||||
import PhotoResources
|
import PhotoResources
|
||||||
import AppBundle
|
import AppBundle
|
||||||
import ContextUI
|
import ContextUI
|
||||||
|
import SoftwareVideo
|
||||||
|
|
||||||
final class ChatContextResultPeekContent: PeekControllerContent {
|
final class ChatContextResultPeekContent: PeekControllerContent {
|
||||||
let account: Account
|
let account: Account
|
||||||
|
@ -11,6 +11,7 @@ import AccountContext
|
|||||||
import AnimatedStickerNode
|
import AnimatedStickerNode
|
||||||
import TelegramAnimatedStickerNode
|
import TelegramAnimatedStickerNode
|
||||||
import ShimmerEffect
|
import ShimmerEffect
|
||||||
|
import SoftwareVideo
|
||||||
|
|
||||||
enum ChatMediaInputStickerGridSectionAccessory {
|
enum ChatMediaInputStickerGridSectionAccessory {
|
||||||
case none
|
case none
|
||||||
@ -174,6 +175,7 @@ final class ChatMediaInputStickerGridItemNode: GridItemNode {
|
|||||||
private var currentSize: CGSize?
|
private var currentSize: CGSize?
|
||||||
let imageNode: TransformImageNode
|
let imageNode: TransformImageNode
|
||||||
private(set) var animationNode: AnimatedStickerNode?
|
private(set) var animationNode: AnimatedStickerNode?
|
||||||
|
private(set) var videoNode: VideoStickerNode?
|
||||||
private(set) var placeholderNode: StickerShimmerEffectNode?
|
private(set) var placeholderNode: StickerShimmerEffectNode?
|
||||||
private var didSetUpAnimationNode = false
|
private var didSetUpAnimationNode = false
|
||||||
private var item: ChatMediaInputStickerGridItem?
|
private var item: ChatMediaInputStickerGridItem?
|
||||||
@ -265,7 +267,23 @@ final class ChatMediaInputStickerGridItemNode: GridItemNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let dimensions = item.stickerItem.file.dimensions {
|
if let dimensions = item.stickerItem.file.dimensions {
|
||||||
if item.stickerItem.file.isAnimatedSticker {
|
if item.stickerItem.file.isVideoSticker {
|
||||||
|
if self.videoNode == nil {
|
||||||
|
let videoNode = VideoStickerNode()
|
||||||
|
videoNode.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.imageNodeTap(_:))))
|
||||||
|
self.videoNode = videoNode
|
||||||
|
videoNode.started = { [weak self] in
|
||||||
|
self?.imageNode.isHidden = true
|
||||||
|
}
|
||||||
|
if let placeholderNode = self.placeholderNode {
|
||||||
|
self.insertSubnode(videoNode, belowSubnode: placeholderNode)
|
||||||
|
} else {
|
||||||
|
self.addSubnode(videoNode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.imageNode.setSignal(chatMessageSticker(account: item.account, file: item.stickerItem.file, small: !item.large, synchronousLoad: synchronousLoads && isVisible))
|
||||||
|
self.stickerFetchedDisposable.set(freeMediaFileResourceInteractiveFetched(account: item.account, fileReference: stickerPackFileReference(item.stickerItem.file), resource: item.stickerItem.file.resource).start())
|
||||||
|
} else if item.stickerItem.file.isAnimatedSticker {
|
||||||
if self.animationNode == nil {
|
if self.animationNode == nil {
|
||||||
let animationNode = AnimatedStickerNode()
|
let animationNode = AnimatedStickerNode()
|
||||||
animationNode.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.imageNodeTap(_:))))
|
animationNode.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.imageNodeTap(_:))))
|
||||||
@ -306,16 +324,23 @@ final class ChatMediaInputStickerGridItemNode: GridItemNode {
|
|||||||
|
|
||||||
if let (_, _, mediaDimensions) = self.currentState {
|
if let (_, _, mediaDimensions) = self.currentState {
|
||||||
let imageSize = mediaDimensions.aspectFitted(boundingSize)
|
let imageSize = mediaDimensions.aspectFitted(boundingSize)
|
||||||
|
let imageFrame = CGRect(origin: CGPoint(x: floor((size.width - imageSize.width) / 2.0), y: (size.height - imageSize.height) / 2.0), size: imageSize)
|
||||||
self.imageNode.asyncLayout()(TransformImageArguments(corners: ImageCorners(), imageSize: imageSize, boundingSize: imageSize, intrinsicInsets: UIEdgeInsets()))()
|
self.imageNode.asyncLayout()(TransformImageArguments(corners: ImageCorners(), imageSize: imageSize, boundingSize: imageSize, intrinsicInsets: UIEdgeInsets()))()
|
||||||
if self.imageNode.supernode === self {
|
if self.imageNode.supernode === self {
|
||||||
self.imageNode.frame = CGRect(origin: CGPoint(x: floor((size.width - imageSize.width) / 2.0), y: (size.height - imageSize.height) / 2.0), size: imageSize)
|
self.imageNode.frame = imageFrame
|
||||||
}
|
}
|
||||||
if let animationNode = self.animationNode {
|
if let animationNode = self.animationNode {
|
||||||
if animationNode.supernode === self {
|
if animationNode.supernode === self {
|
||||||
animationNode.frame = CGRect(origin: CGPoint(x: floor((size.width - imageSize.width) / 2.0), y: (size.height - imageSize.height) / 2.0), size: imageSize)
|
animationNode.frame = imageFrame
|
||||||
}
|
}
|
||||||
animationNode.updateLayout(size: imageSize)
|
animationNode.updateLayout(size: imageSize)
|
||||||
}
|
}
|
||||||
|
if let videoNode = self.videoNode {
|
||||||
|
if videoNode.supernode === self {
|
||||||
|
videoNode.frame = imageFrame
|
||||||
|
}
|
||||||
|
videoNode.updateLayout(size: imageSize)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -365,12 +390,18 @@ final class ChatMediaInputStickerGridItemNode: GridItemNode {
|
|||||||
if self.isPlaying != isPlaying {
|
if self.isPlaying != isPlaying {
|
||||||
self.isPlaying = isPlaying
|
self.isPlaying = isPlaying
|
||||||
self.animationNode?.visibility = isPlaying
|
self.animationNode?.visibility = isPlaying
|
||||||
|
self.videoNode?.update(isPlaying: isPlaying)
|
||||||
if let item = self.item, isPlaying, !self.didSetUpAnimationNode {
|
if let item = self.item, isPlaying, !self.didSetUpAnimationNode {
|
||||||
self.didSetUpAnimationNode = true
|
self.didSetUpAnimationNode = true
|
||||||
let dimensions = item.stickerItem.file.dimensions ?? PixelDimensions(width: 512, height: 512)
|
|
||||||
let fitSize = item.large ? CGSize(width: 384.0, height: 384.0) : CGSize(width: 160.0, height: 160.0)
|
if let videoNode = self.videoNode {
|
||||||
let fittedDimensions = dimensions.cgSize.aspectFitted(fitSize)
|
videoNode.update(account: item.account, fileReference: stickerPackFileReference(item.stickerItem.file))
|
||||||
self.animationNode?.setup(source: AnimatedStickerResourceSource(account: item.account, resource: item.stickerItem.file.resource), width: Int(fittedDimensions.width), height: Int(fittedDimensions.height), mode: .cached)
|
} else if let animationNode = self.animationNode {
|
||||||
|
let dimensions = item.stickerItem.file.dimensions ?? PixelDimensions(width: 512, height: 512)
|
||||||
|
let fitSize = item.large ? CGSize(width: 384.0, height: 384.0) : CGSize(width: 160.0, height: 160.0)
|
||||||
|
let fittedDimensions = dimensions.cgSize.aspectFitted(fitSize)
|
||||||
|
animationNode.setup(source: AnimatedStickerResourceSource(account: item.account, resource: item.stickerItem.file.resource), width: Int(fittedDimensions.width), height: Int(fittedDimensions.height), mode: .cached)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,6 +11,7 @@ import ItemListStickerPackItem
|
|||||||
import AnimatedStickerNode
|
import AnimatedStickerNode
|
||||||
import TelegramAnimatedStickerNode
|
import TelegramAnimatedStickerNode
|
||||||
import ShimmerEffect
|
import ShimmerEffect
|
||||||
|
import SoftwareVideo
|
||||||
|
|
||||||
final class ChatMediaInputStickerPackItem: ListViewItem {
|
final class ChatMediaInputStickerPackItem: ListViewItem {
|
||||||
let account: Account
|
let account: Account
|
||||||
@ -83,6 +84,7 @@ final class ChatMediaInputStickerPackItemNode: ListViewItemNode {
|
|||||||
private let scalingNode: ASDisplayNode
|
private let scalingNode: ASDisplayNode
|
||||||
private let imageNode: TransformImageNode
|
private let imageNode: TransformImageNode
|
||||||
private var animatedStickerNode: AnimatedStickerNode?
|
private var animatedStickerNode: AnimatedStickerNode?
|
||||||
|
private var videoStickerNode: VideoStickerNode?
|
||||||
private var placeholderNode: StickerShimmerEffectNode?
|
private var placeholderNode: StickerShimmerEffectNode?
|
||||||
private let highlightNode: ASImageNode
|
private let highlightNode: ASImageNode
|
||||||
private let titleNode: ImmediateTextNode
|
private let titleNode: ImmediateTextNode
|
||||||
@ -287,6 +289,9 @@ final class ChatMediaInputStickerPackItemNode: ListViewItemNode {
|
|||||||
animatedStickerNode.frame = self.imageNode.frame
|
animatedStickerNode.frame = self.imageNode.frame
|
||||||
animatedStickerNode.updateLayout(size: self.imageNode.frame.size)
|
animatedStickerNode.updateLayout(size: self.imageNode.frame.size)
|
||||||
}
|
}
|
||||||
|
if let videoNode = self.videoStickerNode {
|
||||||
|
videoNode.frame = self.imageNode.frame
|
||||||
|
}
|
||||||
if let placeholderNode = self.placeholderNode {
|
if let placeholderNode = self.placeholderNode {
|
||||||
placeholderNode.bounds = CGRect(origin: CGPoint(), size: boundingImageSize)
|
placeholderNode.bounds = CGRect(origin: CGPoint(), size: boundingImageSize)
|
||||||
placeholderNode.position = self.imageNode.position
|
placeholderNode.position = self.imageNode.position
|
||||||
|
@ -26,6 +26,7 @@ import WallpaperBackgroundNode
|
|||||||
import LocalMediaResources
|
import LocalMediaResources
|
||||||
import AppBundle
|
import AppBundle
|
||||||
import LottieMeshSwift
|
import LottieMeshSwift
|
||||||
|
import SoftwareVideo
|
||||||
|
|
||||||
private let nameFont = Font.medium(14.0)
|
private let nameFont = Font.medium(14.0)
|
||||||
private let inlineBotPrefixFont = Font.regular(14.0)
|
private let inlineBotPrefixFont = Font.regular(14.0)
|
||||||
@ -54,26 +55,11 @@ extension SlotMachineAnimationNode: GenericAnimatedStickerNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class VideoStickerNode: ASDisplayNode, GenericAnimatedStickerNode {
|
extension VideoStickerNode: GenericAnimatedStickerNode {
|
||||||
private var layerHolder: SampleBufferLayer?
|
|
||||||
var manager: SoftwareVideoLayerFrameManager?
|
|
||||||
|
|
||||||
func setOverlayColor(_ color: UIColor?, replace: Bool, animated: Bool) {
|
func setOverlayColor(_ color: UIColor?, replace: Bool, animated: Bool) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func update(context: AccountContext, fileReference: FileMediaReference, size: CGSize) {
|
|
||||||
let layerHolder = takeSampleBufferLayer()
|
|
||||||
layerHolder.layer.videoGravity = AVLayerVideoGravity.resizeAspectFill
|
|
||||||
layerHolder.layer.frame = CGRect(origin: CGPoint(), size: size)
|
|
||||||
self.layer.addSublayer(layerHolder.layer)
|
|
||||||
self.layerHolder = layerHolder
|
|
||||||
|
|
||||||
let manager = SoftwareVideoLayerFrameManager(account: context.account, fileReference: fileReference, layerHolder: layerHolder, hintVP9: true)
|
|
||||||
self.manager = manager
|
|
||||||
manager.start()
|
|
||||||
}
|
|
||||||
|
|
||||||
var currentFrameIndex: Int {
|
var currentFrameIndex: Int {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
@ -201,9 +187,6 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
|||||||
private var didSetUpAnimationNode = false
|
private var didSetUpAnimationNode = false
|
||||||
private var isPlaying = false
|
private var isPlaying = false
|
||||||
|
|
||||||
private var displayLink: ConstantDisplayLinkAnimator?
|
|
||||||
private var displayLinkTimestamp: Double = 0.0
|
|
||||||
|
|
||||||
private var additionalAnimationNodes: [ChatMessageTransitionNode.DecorationItemNode] = []
|
private var additionalAnimationNodes: [ChatMessageTransitionNode.DecorationItemNode] = []
|
||||||
private var overlayMeshAnimationNode: ChatMessageTransitionNode.DecorationItemNode?
|
private var overlayMeshAnimationNode: ChatMessageTransitionNode.DecorationItemNode?
|
||||||
private var enqueuedAdditionalAnimations: [(Int, Double)] = []
|
private var enqueuedAdditionalAnimations: [(Int, Double)] = []
|
||||||
@ -436,8 +419,6 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
|||||||
if self.visibilityStatus != oldValue {
|
if self.visibilityStatus != oldValue {
|
||||||
self.updateVisibility()
|
self.updateVisibility()
|
||||||
self.haptic?.enabled = self.visibilityStatus
|
self.haptic?.enabled = self.visibilityStatus
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -470,9 +451,24 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
|||||||
}
|
}
|
||||||
self.animationNode = animationNode
|
self.animationNode = animationNode
|
||||||
}
|
}
|
||||||
} else if let telegramFile = self.telegramFile, let fileName = telegramFile.fileName, fileName.hasSuffix(".webm") {
|
} else if let telegramFile = self.telegramFile, telegramFile.mimeType == "video/webm" {
|
||||||
let videoNode = VideoStickerNode()
|
let videoNode = VideoStickerNode()
|
||||||
videoNode.update(context: item.context, fileReference: .standalone(media: telegramFile), size: CGSize(width: 184.0, height: 184.0))
|
videoNode.started = { [weak self] in
|
||||||
|
if let strongSelf = self {
|
||||||
|
strongSelf.imageNode.alpha = 0.0
|
||||||
|
if !strongSelf.enableSynchronousImageApply {
|
||||||
|
let current = CACurrentMediaTime()
|
||||||
|
if let setupTimestamp = strongSelf.setupTimestamp, current - setupTimestamp > 0.3 {
|
||||||
|
if !strongSelf.placeholderNode.alpha.isZero {
|
||||||
|
strongSelf.animationNode?.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
||||||
|
strongSelf.removePlaceholder(animated: true)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
strongSelf.removePlaceholder(animated: false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
self.animationNode = videoNode
|
self.animationNode = videoNode
|
||||||
} else {
|
} else {
|
||||||
let animationNode = AnimatedStickerNode()
|
let animationNode = AnimatedStickerNode()
|
||||||
@ -591,25 +587,21 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let isPlaying = self.visibilityStatus && !self.forceStopAnimations
|
let isPlaying = self.visibilityStatus && !self.forceStopAnimations
|
||||||
if let _ = self.animationNode as? VideoStickerNode {
|
if let videoNode = self.animationNode as? VideoStickerNode {
|
||||||
let displayLink: ConstantDisplayLinkAnimator
|
if self.isPlaying != isPlaying {
|
||||||
if let current = self.displayLink {
|
self.isPlaying = isPlaying
|
||||||
displayLink = current
|
|
||||||
} else {
|
if self.isPlaying && !self.didSetUpAnimationNode {
|
||||||
displayLink = ConstantDisplayLinkAnimator { [weak self] in
|
self.didSetUpAnimationNode = true
|
||||||
guard let strongSelf = self, let animationNode = strongSelf.animationNode as? VideoStickerNode else {
|
|
||||||
return
|
if let file = self.telegramFile {
|
||||||
|
videoNode.update(account: item.context.account, fileReference: .standalone(media: file))
|
||||||
}
|
}
|
||||||
animationNode.manager?.tick(timestamp: strongSelf.displayLinkTimestamp)
|
|
||||||
strongSelf.displayLinkTimestamp += 1.0 / 30.0
|
|
||||||
}
|
}
|
||||||
displayLink.frameInterval = 2
|
|
||||||
self.displayLink = displayLink
|
videoNode.update(isPlaying: isPlaying)
|
||||||
}
|
}
|
||||||
self.displayLink?.isPaused = !isPlaying
|
|
||||||
} else if let animationNode = self.animationNode as? AnimatedStickerNode {
|
} else if let animationNode = self.animationNode as? AnimatedStickerNode {
|
||||||
let isPlaying = self.visibilityStatus && !self.forceStopAnimations
|
|
||||||
|
|
||||||
if !isPlaying {
|
if !isPlaying {
|
||||||
for decorationNode in self.additionalAnimationNodes {
|
for decorationNode in self.additionalAnimationNodes {
|
||||||
if let transitionNode = item.controllerInteraction.getMessageTransitionNode() {
|
if let transitionNode = item.controllerInteraction.getMessageTransitionNode() {
|
||||||
@ -1203,9 +1195,12 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
|||||||
|
|
||||||
if strongSelf.animationNode?.supernode === strongSelf.contextSourceNode.contentNode {
|
if strongSelf.animationNode?.supernode === strongSelf.contextSourceNode.contentNode {
|
||||||
strongSelf.animationNode?.frame = animationNodeFrame
|
strongSelf.animationNode?.frame = animationNodeFrame
|
||||||
}
|
if let videoNode = strongSelf.animationNode as? VideoStickerNode {
|
||||||
if let animationNode = strongSelf.animationNode as? AnimatedStickerNode, strongSelf.animationNode?.supernode === strongSelf.contextSourceNode.contentNode {
|
videoNode.updateLayout(size: updatedContentFrame.insetBy(dx: imageInset, dy: imageInset).size)
|
||||||
animationNode.updateLayout(size: updatedContentFrame.insetBy(dx: imageInset, dy: imageInset).size)
|
}
|
||||||
|
if let animationNode = strongSelf.animationNode as? AnimatedStickerNode {
|
||||||
|
animationNode.updateLayout(size: updatedContentFrame.insetBy(dx: imageInset, dy: imageInset).size)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
strongSelf.enableSynchronousImageApply = true
|
strongSelf.enableSynchronousImageApply = true
|
||||||
|
@ -380,7 +380,7 @@ public final class ChatMessageItem: ListViewItem, CustomStringConvertible {
|
|||||||
|
|
||||||
loop: for media in self.message.media {
|
loop: for media in self.message.media {
|
||||||
if let telegramFile = media as? TelegramMediaFile {
|
if let telegramFile = media as? TelegramMediaFile {
|
||||||
if let fileName = telegramFile.fileName, fileName.hasSuffix(".webm") {
|
if telegramFile.isVideoSticker {
|
||||||
viewClassName = ChatMessageAnimatedStickerItemNode.self
|
viewClassName = ChatMessageAnimatedStickerItemNode.self
|
||||||
break loop
|
break loop
|
||||||
}
|
}
|
||||||
|
@ -14,6 +14,7 @@ import TelegramAnimatedStickerNode
|
|||||||
import TelegramPresentationData
|
import TelegramPresentationData
|
||||||
import AccountContext
|
import AccountContext
|
||||||
import ShimmerEffect
|
import ShimmerEffect
|
||||||
|
import SoftwareVideo
|
||||||
|
|
||||||
final class HorizontalListContextResultsChatInputPanelItem: ListViewItem {
|
final class HorizontalListContextResultsChatInputPanelItem: ListViewItem {
|
||||||
let account: Account
|
let account: Account
|
||||||
|
@ -9,6 +9,7 @@ import AVFoundation
|
|||||||
import ContextUI
|
import ContextUI
|
||||||
import TelegramPresentationData
|
import TelegramPresentationData
|
||||||
import ShimmerEffect
|
import ShimmerEffect
|
||||||
|
import SoftwareVideo
|
||||||
|
|
||||||
final class MultiplexedVideoPlaceholderNode: ASDisplayNode {
|
final class MultiplexedVideoPlaceholderNode: ASDisplayNode {
|
||||||
private let effectNode: ShimmerEffectNode
|
private let effectNode: ShimmerEffectNode
|
||||||
|
@ -125,13 +125,14 @@ final class PeerInfoHeaderButtonNode: HighlightableButtonNode {
|
|||||||
|
|
||||||
func update(size: CGSize, text: String, icon: PeerInfoHeaderButtonIcon, isActive: Bool, isExpanded: Bool, presentationData: PresentationData, transition: ContainedViewLayoutTransition) {
|
func update(size: CGSize, text: String, icon: PeerInfoHeaderButtonIcon, isActive: Bool, isExpanded: Bool, presentationData: PresentationData, transition: ContainedViewLayoutTransition) {
|
||||||
let previousIcon = self.icon
|
let previousIcon = self.icon
|
||||||
|
let themeUpdated = self.theme != presentationData.theme
|
||||||
let iconUpdated = self.icon != icon
|
let iconUpdated = self.icon != icon
|
||||||
let isActiveUpdated = self.isActive != isActive
|
let isActiveUpdated = self.isActive != isActive
|
||||||
self.isActive = isActive
|
self.isActive = isActive
|
||||||
|
|
||||||
let iconSize = CGSize(width: 40.0, height: 40.0)
|
let iconSize = CGSize(width: 40.0, height: 40.0)
|
||||||
|
|
||||||
if self.theme != presentationData.theme || self.icon != icon {
|
if themeUpdated || iconUpdated {
|
||||||
self.theme = presentationData.theme
|
self.theme = presentationData.theme
|
||||||
self.icon = icon
|
self.icon = icon
|
||||||
|
|
||||||
@ -194,9 +195,7 @@ final class PeerInfoHeaderButtonNode: HighlightableButtonNode {
|
|||||||
let animationNode: AnimationNode
|
let animationNode: AnimationNode
|
||||||
if let current = self.animationNode {
|
if let current = self.animationNode {
|
||||||
animationNode = current
|
animationNode = current
|
||||||
if iconUpdated {
|
animationNode.setAnimation(name: animationName, colors: colors)
|
||||||
animationNode.setAnimation(name: animationName, colors: colors)
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
animationNode = AnimationNode(animation: animationName, colors: colors, scale: 1.0)
|
animationNode = AnimationNode(animation: animationName, colors: colors, scale: 1.0)
|
||||||
self.referenceNode.addSubnode(animationNode)
|
self.referenceNode.addSubnode(animationNode)
|
||||||
|
@ -13,6 +13,7 @@ import GridMessageSelectionNode
|
|||||||
import UniversalMediaPlayer
|
import UniversalMediaPlayer
|
||||||
import ListMessageItem
|
import ListMessageItem
|
||||||
import ChatMessageInteractiveMediaBadge
|
import ChatMessageInteractiveMediaBadge
|
||||||
|
import SoftwareVideo
|
||||||
|
|
||||||
private final class FrameSequenceThumbnailNode: ASDisplayNode {
|
private final class FrameSequenceThumbnailNode: ASDisplayNode {
|
||||||
private let context: AccountContext
|
private let context: AccountContext
|
||||||
|
Loading…
x
Reference in New Issue
Block a user