diff --git a/submodules/ChatListUI/Sources/Node/ChatListItem.swift b/submodules/ChatListUI/Sources/Node/ChatListItem.swift
index 9795c13e04..bbf9f243f3 100644
--- a/submodules/ChatListUI/Sources/Node/ChatListItem.swift
+++ b/submodules/ChatListUI/Sources/Node/ChatListItem.swift
@@ -1146,7 +1146,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
}
break inner
} 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
contentImageSpecs.append((message, .file(file), fitSize))
}
diff --git a/submodules/MediaPlayer/Sources/FFMpegMediaVideoFrameDecoder.swift b/submodules/MediaPlayer/Sources/FFMpegMediaVideoFrameDecoder.swift
index 0d95e9e7c9..56b89852b6 100644
--- a/submodules/MediaPlayer/Sources/FFMpegMediaVideoFrameDecoder.swift
+++ b/submodules/MediaPlayer/Sources/FFMpegMediaVideoFrameDecoder.swift
@@ -277,7 +277,7 @@ public final class FFMpegMediaVideoFrameDecoder: MediaTrackFrameDecoder {
options as CFDictionary,
&pixelBufferRef)
}
-
+
guard let pixelBuffer = pixelBufferRef else {
return nil
}
@@ -399,11 +399,7 @@ public final class FFMpegMediaVideoFrameDecoder: MediaTrackFrameDecoder {
return nil
}
}
-
- func decodeImage() {
-
- }
-
+
public func reset() {
self.codecContext.flushBuffers()
self.resetDecoderOnNextFrame = true
diff --git a/submodules/SoftwareVideo/BUILD b/submodules/SoftwareVideo/BUILD
new file mode 100644
index 0000000000..0d0da4f9db
--- /dev/null
+++ b/submodules/SoftwareVideo/BUILD
@@ -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",
+ ],
+)
diff --git a/submodules/SoftwareVideo/Info.plist b/submodules/SoftwareVideo/Info.plist
new file mode 100644
index 0000000000..e1fe4cfb7b
--- /dev/null
+++ b/submodules/SoftwareVideo/Info.plist
@@ -0,0 +1,22 @@
+
+
+
+
+ CFBundleDevelopmentRegion
+ $(DEVELOPMENT_LANGUAGE)
+ CFBundleExecutable
+ $(EXECUTABLE_NAME)
+ CFBundleIdentifier
+ $(PRODUCT_BUNDLE_IDENTIFIER)
+ CFBundleInfoDictionaryVersion
+ 6.0
+ CFBundleName
+ $(PRODUCT_NAME)
+ CFBundlePackageType
+ FMWK
+ CFBundleShortVersionString
+ 1.0
+ CFBundleVersion
+ $(CURRENT_PROJECT_VERSION)
+
+
diff --git a/submodules/TelegramUI/Sources/SampleBufferPool.swift b/submodules/SoftwareVideo/Sources/SampleBufferPool.swift
similarity index 86%
rename from submodules/TelegramUI/Sources/SampleBufferPool.swift
rename to submodules/SoftwareVideo/Sources/SampleBufferPool.swift
index 1e67917933..1bc235682e 100644
--- a/submodules/TelegramUI/Sources/SampleBufferPool.swift
+++ b/submodules/SoftwareVideo/Sources/SampleBufferPool.swift
@@ -14,19 +14,19 @@ private final class SampleBufferLayerImpl: AVSampleBufferDisplayLayer {
}
}
-final class SampleBufferLayer {
- let layer: AVSampleBufferDisplayLayer
+public final class SampleBufferLayer {
+ public let layer: AVSampleBufferDisplayLayer
private let enqueue: (AVSampleBufferDisplayLayer) -> Void
- var isFreed: Bool = false
+ public var isFreed: Bool = false
fileprivate init(layer: AVSampleBufferDisplayLayer, enqueue: @escaping (AVSampleBufferDisplayLayer) -> Void) {
self.layer = layer
self.enqueue = enqueue
}
deinit {
- if !isFreed {
+ if !self.isFreed {
self.enqueue(self.layer)
}
}
@@ -34,11 +34,11 @@ final class SampleBufferLayer {
private let pool = Atomic<[AVSampleBufferDisplayLayer]>(value: [])
-func clearSampleBufferLayerPoll() {
+public func clearSampleBufferLayerPoll() {
let _ = pool.modify { _ in return [] }
}
-func takeSampleBufferLayer() -> SampleBufferLayer {
+public func takeSampleBufferLayer() -> SampleBufferLayer {
var layer: AVSampleBufferDisplayLayer?
let _ = pool.modify { list in
var list = list
diff --git a/submodules/TelegramUI/Sources/SoftwareVideoLayerFrameManager.swift b/submodules/SoftwareVideo/Sources/SoftwareVideoLayerFrameManager.swift
similarity index 94%
rename from submodules/TelegramUI/Sources/SoftwareVideoLayerFrameManager.swift
rename to submodules/SoftwareVideo/Sources/SoftwareVideoLayerFrameManager.swift
index 859da17ac8..70d00e8aba 100644
--- a/submodules/TelegramUI/Sources/SoftwareVideoLayerFrameManager.swift
+++ b/submodules/SoftwareVideo/Sources/SoftwareVideoLayerFrameManager.swift
@@ -10,7 +10,7 @@ private let applyQueue = Queue()
private let workers = ThreadPool(threadCount: 3, threadPriority: 0.2)
private var nextWorker = 0
-final class SoftwareVideoLayerFrameManager {
+public final class SoftwareVideoLayerFrameManager {
private let fetchDisposable: Disposable
private var dataDisposable = MetaDisposable()
private let source = Atomic(value: nil)
@@ -32,7 +32,10 @@ final class SoftwareVideoLayerFrameManager {
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 secondaryResource: MediaResource?
for attribute in fileReference.media.attributes {
@@ -61,7 +64,7 @@ final class SoftwareVideoLayerFrameManager {
self.dataDisposable.dispose()
}
- func start() {
+ public func start() {
func stringForResource(_ resource: MediaResource?) -> String {
guard let resource = resource else {
return ""
@@ -115,7 +118,7 @@ final class SoftwareVideoLayerFrameManager {
}))
}
- func tick(timestamp: Double) {
+ public func tick(timestamp: Double) {
applyQueue.async {
if self.baseTimestamp == nil && !self.frames.isEmpty {
self.baseTimestamp = timestamp
@@ -148,6 +151,13 @@ final class SoftwareVideoLayerFrameManager {
self.layerHolder.layer.setAffineTransform(transform)
}*/
self.layerHolder.layer.enqueue(frame.sampleBuffer)
+
+ if !self.didStart {
+ self.didStart = true
+ Queue.mainQueue().async {
+ self.started()
+ }
+ }
}
}
diff --git a/submodules/SoftwareVideo/Sources/VideoStickerNode.swift b/submodules/SoftwareVideo/Sources/VideoStickerNode.swift
new file mode 100644
index 0000000000..dad2e31a65
--- /dev/null
+++ b/submodules/SoftwareVideo/Sources/VideoStickerNode.swift
@@ -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)
+ }
+}
diff --git a/submodules/StickerPackPreviewUI/BUILD b/submodules/StickerPackPreviewUI/BUILD
index 548a88134f..be58773907 100644
--- a/submodules/StickerPackPreviewUI/BUILD
+++ b/submodules/StickerPackPreviewUI/BUILD
@@ -31,6 +31,7 @@ swift_library(
"//submodules/ShimmerEffect:ShimmerEffect",
"//submodules/UndoUI:UndoUI",
"//submodules/ContextUI:ContextUI",
+ "//submodules/SoftwareVideo:SoftwareVideo",
],
visibility = [
"//visibility:public",
diff --git a/submodules/StickerPackPreviewUI/Sources/StickerPackPreviewGridItem.swift b/submodules/StickerPackPreviewUI/Sources/StickerPackPreviewGridItem.swift
index 5f40225493..a613cd7879 100644
--- a/submodules/StickerPackPreviewUI/Sources/StickerPackPreviewGridItem.swift
+++ b/submodules/StickerPackPreviewUI/Sources/StickerPackPreviewGridItem.swift
@@ -11,6 +11,7 @@ import AnimatedStickerNode
import TelegramAnimatedStickerNode
import TelegramPresentationData
import ShimmerEffect
+import SoftwareVideo
final class StickerPackPreviewInteraction {
var previewedItem: StickerPreviewPeekItem?
@@ -60,13 +61,19 @@ final class StickerPackPreviewGridItemNode: GridItemNode {
private var isEmpty: Bool?
private let imageNode: TransformImageNode
private var animationNode: AnimatedStickerNode?
+ private var videoNode: VideoStickerNode?
private var placeholderNode: StickerShimmerEffectNode?
private var theme: PresentationTheme?
override var isVisibleInGrid: Bool {
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 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)
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 = item, let dimensions = item.file.dimensions?.cgSize {
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.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 {
- 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)
}
}
diff --git a/submodules/StickerPackPreviewUI/Sources/StickerPreviewPeekContent.swift b/submodules/StickerPackPreviewUI/Sources/StickerPreviewPeekContent.swift
index 50f5d7fe0f..83e1821310 100644
--- a/submodules/StickerPackPreviewUI/Sources/StickerPreviewPeekContent.swift
+++ b/submodules/StickerPackPreviewUI/Sources/StickerPreviewPeekContent.swift
@@ -9,6 +9,7 @@ import StickerResources
import AnimatedStickerNode
import TelegramAnimatedStickerNode
import ContextUI
+import SoftwareVideo
public enum StickerPreviewPeekItem: Equatable {
case pack(StickerPackItem)
@@ -71,6 +72,7 @@ public final class StickerPreviewPeekContentNode: ASDisplayNode, PeekControllerC
private var textNode: ASTextNode
public var imageNode: TransformImageNode
public var animationNode: AnimatedStickerNode?
+ public var videoNode: VideoStickerNode?
private var containerLayout: (ContainerViewLayout, CGFloat)?
@@ -86,16 +88,24 @@ public final class StickerPreviewPeekContentNode: ASDisplayNode, PeekControllerC
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()
self.animationNode = animationNode
let dimensions = item.file.dimensions ?? PixelDimensions(width: 512, height: 512)
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))
- self.animationNode?.visibility = true
- self.animationNode?.addSubnode(self.textNode)
+ animationNode.setup(source: AnimatedStickerResourceSource(account: account, resource: item.file.resource), width: Int(fittedDimensions.width), height: Int(fittedDimensions.height), mode: .direct(cachePathPrefix: nil))
+ animationNode.visibility = true
+ animationNode.addSubnode(self.textNode)
} else {
self.imageNode.addSubnode(self.textNode)
self.animationNode = nil
@@ -107,7 +117,9 @@ public final class StickerPreviewPeekContentNode: ASDisplayNode, PeekControllerC
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)
} else {
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()))()
let imageFrame = CGRect(origin: CGPoint(x: 0.0, y: textSize.height + textSpacing), size: imageSize)
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.updateLayout(size: imageSize)
}
diff --git a/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaFile.swift b/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaFile.swift
index cb91d3f12f..37f5479b09 100644
--- a/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaFile.swift
+++ b/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaFile.swift
@@ -478,6 +478,26 @@ public final class TelegramMediaFile: Media, Equatable, Codable {
return false
}
+ public var isVideoSticker: Bool {
+ if self.mimeType == "video/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 {
for attribute in self.attributes {
if case .HasLinkedStickers = attribute {
diff --git a/submodules/TelegramUI/BUILD b/submodules/TelegramUI/BUILD
index df390590b1..66ea767ea2 100644
--- a/submodules/TelegramUI/BUILD
+++ b/submodules/TelegramUI/BUILD
@@ -254,6 +254,7 @@ swift_library(
"//submodules/Components/ReactionImageComponent:ReactionImageComponent",
"//submodules/Translate:Translate",
"//submodules/TabBarUI:TabBarUI",
+ "//submodules/SoftwareVideo:SoftwareVideo",
] + select({
"@build_bazel_rules_apple//apple:ios_armv7": [],
"@build_bazel_rules_apple//apple:ios_arm64": appcenter_targets,
diff --git a/submodules/TelegramUI/Sources/ChatContextResultPeekContentNode.swift b/submodules/TelegramUI/Sources/ChatContextResultPeekContentNode.swift
index d863811fcb..0190387cc0 100644
--- a/submodules/TelegramUI/Sources/ChatContextResultPeekContentNode.swift
+++ b/submodules/TelegramUI/Sources/ChatContextResultPeekContentNode.swift
@@ -9,6 +9,7 @@ import AVFoundation
import PhotoResources
import AppBundle
import ContextUI
+import SoftwareVideo
final class ChatContextResultPeekContent: PeekControllerContent {
let account: Account
diff --git a/submodules/TelegramUI/Sources/ChatMediaInputStickerGridItem.swift b/submodules/TelegramUI/Sources/ChatMediaInputStickerGridItem.swift
index 9254a99e15..45e2644df8 100644
--- a/submodules/TelegramUI/Sources/ChatMediaInputStickerGridItem.swift
+++ b/submodules/TelegramUI/Sources/ChatMediaInputStickerGridItem.swift
@@ -11,6 +11,7 @@ import AccountContext
import AnimatedStickerNode
import TelegramAnimatedStickerNode
import ShimmerEffect
+import SoftwareVideo
enum ChatMediaInputStickerGridSectionAccessory {
case none
@@ -174,6 +175,7 @@ final class ChatMediaInputStickerGridItemNode: GridItemNode {
private var currentSize: CGSize?
let imageNode: TransformImageNode
private(set) var animationNode: AnimatedStickerNode?
+ private(set) var videoNode: VideoStickerNode?
private(set) var placeholderNode: StickerShimmerEffectNode?
private var didSetUpAnimationNode = false
private var item: ChatMediaInputStickerGridItem?
@@ -265,7 +267,23 @@ final class ChatMediaInputStickerGridItemNode: GridItemNode {
}
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 {
let animationNode = AnimatedStickerNode()
animationNode.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.imageNodeTap(_:))))
@@ -306,16 +324,23 @@ final class ChatMediaInputStickerGridItemNode: GridItemNode {
if let (_, _, mediaDimensions) = self.currentState {
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()))()
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 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)
}
+ 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 {
self.isPlaying = isPlaying
self.animationNode?.visibility = isPlaying
+ self.videoNode?.update(isPlaying: isPlaying)
if let item = self.item, isPlaying, !self.didSetUpAnimationNode {
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)
- let fittedDimensions = dimensions.cgSize.aspectFitted(fitSize)
- self.animationNode?.setup(source: AnimatedStickerResourceSource(account: item.account, resource: item.stickerItem.file.resource), width: Int(fittedDimensions.width), height: Int(fittedDimensions.height), mode: .cached)
+
+ if let videoNode = self.videoNode {
+ videoNode.update(account: item.account, fileReference: stickerPackFileReference(item.stickerItem.file))
+ } 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)
+ }
}
}
}
diff --git a/submodules/TelegramUI/Sources/ChatMediaInputStickerPackItem.swift b/submodules/TelegramUI/Sources/ChatMediaInputStickerPackItem.swift
index f1f4ecd228..a43ce4dcf5 100644
--- a/submodules/TelegramUI/Sources/ChatMediaInputStickerPackItem.swift
+++ b/submodules/TelegramUI/Sources/ChatMediaInputStickerPackItem.swift
@@ -11,6 +11,7 @@ import ItemListStickerPackItem
import AnimatedStickerNode
import TelegramAnimatedStickerNode
import ShimmerEffect
+import SoftwareVideo
final class ChatMediaInputStickerPackItem: ListViewItem {
let account: Account
@@ -83,6 +84,7 @@ final class ChatMediaInputStickerPackItemNode: ListViewItemNode {
private let scalingNode: ASDisplayNode
private let imageNode: TransformImageNode
private var animatedStickerNode: AnimatedStickerNode?
+ private var videoStickerNode: VideoStickerNode?
private var placeholderNode: StickerShimmerEffectNode?
private let highlightNode: ASImageNode
private let titleNode: ImmediateTextNode
@@ -287,6 +289,9 @@ final class ChatMediaInputStickerPackItemNode: ListViewItemNode {
animatedStickerNode.frame = self.imageNode.frame
animatedStickerNode.updateLayout(size: self.imageNode.frame.size)
}
+ if let videoNode = self.videoStickerNode {
+ videoNode.frame = self.imageNode.frame
+ }
if let placeholderNode = self.placeholderNode {
placeholderNode.bounds = CGRect(origin: CGPoint(), size: boundingImageSize)
placeholderNode.position = self.imageNode.position
diff --git a/submodules/TelegramUI/Sources/ChatMessageAnimatedStickerItemNode.swift b/submodules/TelegramUI/Sources/ChatMessageAnimatedStickerItemNode.swift
index b995fa3a3d..c750fbbfbd 100644
--- a/submodules/TelegramUI/Sources/ChatMessageAnimatedStickerItemNode.swift
+++ b/submodules/TelegramUI/Sources/ChatMessageAnimatedStickerItemNode.swift
@@ -26,6 +26,7 @@ import WallpaperBackgroundNode
import LocalMediaResources
import AppBundle
import LottieMeshSwift
+import SoftwareVideo
private let nameFont = Font.medium(14.0)
private let inlineBotPrefixFont = Font.regular(14.0)
@@ -54,26 +55,11 @@ extension SlotMachineAnimationNode: GenericAnimatedStickerNode {
}
}
-private class VideoStickerNode: ASDisplayNode, GenericAnimatedStickerNode {
- private var layerHolder: SampleBufferLayer?
- var manager: SoftwareVideoLayerFrameManager?
-
+extension VideoStickerNode: GenericAnimatedStickerNode {
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 {
return 0
}
@@ -201,9 +187,6 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
private var didSetUpAnimationNode = false
private var isPlaying = false
- private var displayLink: ConstantDisplayLinkAnimator?
- private var displayLinkTimestamp: Double = 0.0
-
private var additionalAnimationNodes: [ChatMessageTransitionNode.DecorationItemNode] = []
private var overlayMeshAnimationNode: ChatMessageTransitionNode.DecorationItemNode?
private var enqueuedAdditionalAnimations: [(Int, Double)] = []
@@ -436,8 +419,6 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
if self.visibilityStatus != oldValue {
self.updateVisibility()
self.haptic?.enabled = self.visibilityStatus
-
-
}
}
}
@@ -470,9 +451,24 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
}
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()
- 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
} else {
let animationNode = AnimatedStickerNode()
@@ -591,25 +587,21 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
}
let isPlaying = self.visibilityStatus && !self.forceStopAnimations
- if let _ = self.animationNode as? VideoStickerNode {
- let displayLink: ConstantDisplayLinkAnimator
- if let current = self.displayLink {
- displayLink = current
- } else {
- displayLink = ConstantDisplayLinkAnimator { [weak self] in
- guard let strongSelf = self, let animationNode = strongSelf.animationNode as? VideoStickerNode else {
- return
+ if let videoNode = self.animationNode as? VideoStickerNode {
+ if self.isPlaying != isPlaying {
+ self.isPlaying = isPlaying
+
+ if self.isPlaying && !self.didSetUpAnimationNode {
+ self.didSetUpAnimationNode = true
+
+ 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 {
- let isPlaying = self.visibilityStatus && !self.forceStopAnimations
-
if !isPlaying {
for decorationNode in self.additionalAnimationNodes {
if let transitionNode = item.controllerInteraction.getMessageTransitionNode() {
@@ -1203,9 +1195,12 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
if strongSelf.animationNode?.supernode === strongSelf.contextSourceNode.contentNode {
strongSelf.animationNode?.frame = animationNodeFrame
- }
- if let animationNode = strongSelf.animationNode as? AnimatedStickerNode, strongSelf.animationNode?.supernode === strongSelf.contextSourceNode.contentNode {
- animationNode.updateLayout(size: updatedContentFrame.insetBy(dx: imageInset, dy: imageInset).size)
+ if let videoNode = strongSelf.animationNode as? VideoStickerNode {
+ videoNode.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
diff --git a/submodules/TelegramUI/Sources/ChatMessageItem.swift b/submodules/TelegramUI/Sources/ChatMessageItem.swift
index bcca9e881e..456a774b11 100644
--- a/submodules/TelegramUI/Sources/ChatMessageItem.swift
+++ b/submodules/TelegramUI/Sources/ChatMessageItem.swift
@@ -380,7 +380,7 @@ public final class ChatMessageItem: ListViewItem, CustomStringConvertible {
loop: for media in self.message.media {
if let telegramFile = media as? TelegramMediaFile {
- if let fileName = telegramFile.fileName, fileName.hasSuffix(".webm") {
+ if telegramFile.isVideoSticker {
viewClassName = ChatMessageAnimatedStickerItemNode.self
break loop
}
diff --git a/submodules/TelegramUI/Sources/HorizontalListContextResultsChatInputPanelItem.swift b/submodules/TelegramUI/Sources/HorizontalListContextResultsChatInputPanelItem.swift
index 30b75a0e3b..99cbcde95e 100644
--- a/submodules/TelegramUI/Sources/HorizontalListContextResultsChatInputPanelItem.swift
+++ b/submodules/TelegramUI/Sources/HorizontalListContextResultsChatInputPanelItem.swift
@@ -14,6 +14,7 @@ import TelegramAnimatedStickerNode
import TelegramPresentationData
import AccountContext
import ShimmerEffect
+import SoftwareVideo
final class HorizontalListContextResultsChatInputPanelItem: ListViewItem {
let account: Account
diff --git a/submodules/TelegramUI/Sources/MultiplexedVideoNode.swift b/submodules/TelegramUI/Sources/MultiplexedVideoNode.swift
index 70084b7a3f..fb63510fe1 100644
--- a/submodules/TelegramUI/Sources/MultiplexedVideoNode.swift
+++ b/submodules/TelegramUI/Sources/MultiplexedVideoNode.swift
@@ -9,6 +9,7 @@ import AVFoundation
import ContextUI
import TelegramPresentationData
import ShimmerEffect
+import SoftwareVideo
final class MultiplexedVideoPlaceholderNode: ASDisplayNode {
private let effectNode: ShimmerEffectNode
diff --git a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoHeaderNode.swift b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoHeaderNode.swift
index 712a3c61b5..4c3f25c74d 100644
--- a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoHeaderNode.swift
+++ b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoHeaderNode.swift
@@ -125,13 +125,14 @@ final class PeerInfoHeaderButtonNode: HighlightableButtonNode {
func update(size: CGSize, text: String, icon: PeerInfoHeaderButtonIcon, isActive: Bool, isExpanded: Bool, presentationData: PresentationData, transition: ContainedViewLayoutTransition) {
let previousIcon = self.icon
+ let themeUpdated = self.theme != presentationData.theme
let iconUpdated = self.icon != icon
let isActiveUpdated = self.isActive != isActive
self.isActive = isActive
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.icon = icon
@@ -194,9 +195,7 @@ final class PeerInfoHeaderButtonNode: HighlightableButtonNode {
let animationNode: AnimationNode
if let current = self.animationNode {
animationNode = current
- if iconUpdated {
- animationNode.setAnimation(name: animationName, colors: colors)
- }
+ animationNode.setAnimation(name: animationName, colors: colors)
} else {
animationNode = AnimationNode(animation: animationName, colors: colors, scale: 1.0)
self.referenceNode.addSubnode(animationNode)
diff --git a/submodules/TelegramUI/Sources/PeerInfoGifPaneNode.swift b/submodules/TelegramUI/Sources/PeerInfoGifPaneNode.swift
index d16b75a67f..16367af592 100644
--- a/submodules/TelegramUI/Sources/PeerInfoGifPaneNode.swift
+++ b/submodules/TelegramUI/Sources/PeerInfoGifPaneNode.swift
@@ -13,6 +13,7 @@ import GridMessageSelectionNode
import UniversalMediaPlayer
import ListMessageItem
import ChatMessageInteractiveMediaBadge
+import SoftwareVideo
private final class FrameSequenceThumbnailNode: ASDisplayNode {
private let context: AccountContext