mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-11-11 11:13:29 +00:00
Various improvements
This commit is contained in:
parent
3c077c2820
commit
894e0e4705
@ -298,7 +298,6 @@ private final class ItemNode: ASDisplayNode {
|
|||||||
self.shortTitleActiveNode.visibility = title.enableAnimations
|
self.shortTitleActiveNode.visibility = title.enableAnimations
|
||||||
|
|
||||||
if themeUpdated || titleUpdated {
|
if themeUpdated || titleUpdated {
|
||||||
//TODO:release
|
|
||||||
self.titleNode.attributedText = title.attributedString(font: Font.medium(14.0), textColor: presentationData.theme.list.itemSecondaryTextColor)
|
self.titleNode.attributedText = title.attributedString(font: Font.medium(14.0), textColor: presentationData.theme.list.itemSecondaryTextColor)
|
||||||
self.titleActiveNode.attributedText = title.attributedString(font: Font.medium(14.0), textColor: presentationData.theme.list.itemAccentColor)
|
self.titleActiveNode.attributedText = title.attributedString(font: Font.medium(14.0), textColor: presentationData.theme.list.itemAccentColor)
|
||||||
|
|
||||||
|
|||||||
@ -637,7 +637,6 @@ final class VideoChatParticipantsComponent: Component {
|
|||||||
|
|
||||||
private var ignoreScrolling: Bool = false
|
private var ignoreScrolling: Bool = false
|
||||||
|
|
||||||
//TODO:release
|
|
||||||
private var gridParticipants: [VideoParticipant] = []
|
private var gridParticipants: [VideoParticipant] = []
|
||||||
private var listParticipants: [GroupCallParticipantsContext.Participant] = []
|
private var listParticipants: [GroupCallParticipantsContext.Participant] = []
|
||||||
|
|
||||||
|
|||||||
@ -2420,7 +2420,6 @@ final class VideoChatScreenComponent: Component {
|
|||||||
}
|
}
|
||||||
videoButtonContent = .audio(audio: buttonAudio, isEnabled: buttonIsEnabled)
|
videoButtonContent = .audio(audio: buttonAudio, isEnabled: buttonIsEnabled)
|
||||||
} else {
|
} else {
|
||||||
//TODO:release
|
|
||||||
videoButtonContent = .video(isActive: false)
|
videoButtonContent = .video(isActive: false)
|
||||||
}
|
}
|
||||||
let _ = self.videoButton.update(
|
let _ = self.videoButton.update(
|
||||||
|
|||||||
@ -285,7 +285,6 @@ extension VideoChatScreenComponent.View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//let isScheduled = strongSelf.isScheduled
|
//let isScheduled = strongSelf.isScheduled
|
||||||
//TODO:release
|
|
||||||
let isScheduled: Bool = !"".isEmpty
|
let isScheduled: Bool = !"".isEmpty
|
||||||
|
|
||||||
let canSpeak: Bool
|
let canSpeak: Bool
|
||||||
|
|||||||
@ -90,7 +90,7 @@ public final class BatchVideoRenderingContext {
|
|||||||
|
|
||||||
private final class TargetContext {
|
private final class TargetContext {
|
||||||
weak var target: Target?
|
weak var target: Target?
|
||||||
let file: TelegramMediaFile
|
let file: FileMediaReference
|
||||||
let userLocation: MediaResourceUserLocation
|
let userLocation: MediaResourceUserLocation
|
||||||
|
|
||||||
var readingContext: QueueLocalObject<ReadingContext>?
|
var readingContext: QueueLocalObject<ReadingContext>?
|
||||||
@ -100,7 +100,7 @@ public final class BatchVideoRenderingContext {
|
|||||||
|
|
||||||
init(
|
init(
|
||||||
target: Target,
|
target: Target,
|
||||||
file: TelegramMediaFile,
|
file: FileMediaReference,
|
||||||
userLocation: MediaResourceUserLocation
|
userLocation: MediaResourceUserLocation
|
||||||
) {
|
) {
|
||||||
self.target = target
|
self.target = target
|
||||||
@ -128,7 +128,7 @@ public final class BatchVideoRenderingContext {
|
|||||||
self.context = context
|
self.context = context
|
||||||
}
|
}
|
||||||
|
|
||||||
public func add(target: Target, file: TelegramMediaFile, userLocation: MediaResourceUserLocation) -> TargetHandle {
|
public func add(target: Target, file: FileMediaReference, userLocation: MediaResourceUserLocation) -> TargetHandle {
|
||||||
let id = self.nextId
|
let id = self.nextId
|
||||||
self.nextId += 1
|
self.nextId += 1
|
||||||
|
|
||||||
@ -158,11 +158,11 @@ public final class BatchVideoRenderingContext {
|
|||||||
mediaBox: self.context.account.postbox.mediaBox,
|
mediaBox: self.context.account.postbox.mediaBox,
|
||||||
userLocation: targetContext.userLocation,
|
userLocation: targetContext.userLocation,
|
||||||
userContentType: .sticker,
|
userContentType: .sticker,
|
||||||
reference: .media(media: .standalone(media: targetContext.file), resource: targetContext.file.resource)
|
reference: targetContext.file.resourceReference(targetContext.file.media.resource)
|
||||||
).startStrict()
|
).startStrict()
|
||||||
}
|
}
|
||||||
if targetContext.dataDisposable == nil {
|
if targetContext.dataDisposable == nil {
|
||||||
targetContext.dataDisposable = (self.context.account.postbox.mediaBox.resourceData(targetContext.file.resource)
|
targetContext.dataDisposable = (self.context.account.postbox.mediaBox.resourceData(targetContext.file.media.resource)
|
||||||
|> deliverOnMainQueue).startStrict(next: { [weak self, weak targetContext] data in
|
|> deliverOnMainQueue).startStrict(next: { [weak self, weak targetContext] data in
|
||||||
guard let self, let targetContext else {
|
guard let self, let targetContext else {
|
||||||
return
|
return
|
||||||
|
|||||||
@ -40,6 +40,7 @@ swift_library(
|
|||||||
"//submodules/TelegramUI/Components/EntityKeyboardGifContent:EntityKeyboardGifContent",
|
"//submodules/TelegramUI/Components/EntityKeyboardGifContent:EntityKeyboardGifContent",
|
||||||
"//submodules/TelegramUI/Components/LegacyMessageInputPanelInputView:LegacyMessageInputPanelInputView",
|
"//submodules/TelegramUI/Components/LegacyMessageInputPanelInputView:LegacyMessageInputPanelInputView",
|
||||||
"//submodules/AttachmentTextInputPanelNode",
|
"//submodules/AttachmentTextInputPanelNode",
|
||||||
|
"//submodules/TelegramUI/Components/BatchVideoRendering",
|
||||||
],
|
],
|
||||||
visibility = [
|
visibility = [
|
||||||
"//visibility:public",
|
"//visibility:public",
|
||||||
|
|||||||
@ -1912,6 +1912,7 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode {
|
|||||||
interaction: interaction,
|
interaction: interaction,
|
||||||
inputNodeInteraction: inputNodeInteraction,
|
inputNodeInteraction: inputNodeInteraction,
|
||||||
mode: mappedMode,
|
mode: mappedMode,
|
||||||
|
batchVideoRenderingContext: nil,
|
||||||
trendingGifsPromise: trendingGifsPromise,
|
trendingGifsPromise: trendingGifsPromise,
|
||||||
cancel: {
|
cancel: {
|
||||||
},
|
},
|
||||||
|
|||||||
@ -12,12 +12,15 @@ import ChatControllerInteraction
|
|||||||
import MultiplexedVideoNode
|
import MultiplexedVideoNode
|
||||||
import ChatPresentationInterfaceState
|
import ChatPresentationInterfaceState
|
||||||
import EntityKeyboardGifContent
|
import EntityKeyboardGifContent
|
||||||
|
import BatchVideoRendering
|
||||||
|
|
||||||
final class GifPaneSearchContentNode: ASDisplayNode & PaneSearchContentNode {
|
final class GifPaneSearchContentNode: ASDisplayNode & PaneSearchContentNode {
|
||||||
private let context: AccountContext
|
private let context: AccountContext
|
||||||
private let interaction: ChatEntityKeyboardInputNode.Interaction
|
private let interaction: ChatEntityKeyboardInputNode.Interaction
|
||||||
private let inputNodeInteraction: ChatMediaInputNodeInteraction
|
private let inputNodeInteraction: ChatMediaInputNodeInteraction
|
||||||
|
|
||||||
|
private let batchVideoRenderingContext: BatchVideoRenderingContext
|
||||||
|
|
||||||
private var theme: PresentationTheme
|
private var theme: PresentationTheme
|
||||||
private var strings: PresentationStrings
|
private var strings: PresentationStrings
|
||||||
|
|
||||||
@ -45,10 +48,11 @@ final class GifPaneSearchContentNode: ASDisplayNode & PaneSearchContentNode {
|
|||||||
|
|
||||||
private var hasInitialText = false
|
private var hasInitialText = false
|
||||||
|
|
||||||
init(context: AccountContext, theme: PresentationTheme, strings: PresentationStrings, interaction: ChatEntityKeyboardInputNode.Interaction, inputNodeInteraction: ChatMediaInputNodeInteraction, trendingPromise: Promise<ChatMediaInputGifPaneTrendingState?>) {
|
init(context: AccountContext, theme: PresentationTheme, strings: PresentationStrings, interaction: ChatEntityKeyboardInputNode.Interaction, inputNodeInteraction: ChatMediaInputNodeInteraction, batchVideoRenderingContext: BatchVideoRenderingContext, trendingPromise: Promise<ChatMediaInputGifPaneTrendingState?>) {
|
||||||
self.context = context
|
self.context = context
|
||||||
self.interaction = interaction
|
self.interaction = interaction
|
||||||
self.inputNodeInteraction = inputNodeInteraction
|
self.inputNodeInteraction = inputNodeInteraction
|
||||||
|
self.batchVideoRenderingContext = batchVideoRenderingContext
|
||||||
self.trendingPromise = trendingPromise
|
self.trendingPromise = trendingPromise
|
||||||
|
|
||||||
self.theme = theme
|
self.theme = theme
|
||||||
@ -217,7 +221,7 @@ final class GifPaneSearchContentNode: ASDisplayNode & PaneSearchContentNode {
|
|||||||
super.willEnterHierarchy()
|
super.willEnterHierarchy()
|
||||||
|
|
||||||
if self.multiplexedNode == nil {
|
if self.multiplexedNode == nil {
|
||||||
let multiplexedNode = MultiplexedVideoNode(account: self.context.account, theme: self.theme, strings: self.strings)
|
let multiplexedNode = MultiplexedVideoNode(context: self.context, theme: self.theme, strings: self.strings, batchVideoRenderingContext: self.batchVideoRenderingContext)
|
||||||
self.multiplexedNode = multiplexedNode
|
self.multiplexedNode = multiplexedNode
|
||||||
if let layout = self.validLayout {
|
if let layout = self.validLayout {
|
||||||
multiplexedNode.frame = CGRect(origin: CGPoint(), size: layout)
|
multiplexedNode.frame = CGRect(origin: CGPoint(), size: layout)
|
||||||
|
|||||||
@ -14,6 +14,7 @@ import MultiplexedVideoNode
|
|||||||
import FeaturedStickersScreen
|
import FeaturedStickersScreen
|
||||||
import StickerPeekUI
|
import StickerPeekUI
|
||||||
import EntityKeyboardGifContent
|
import EntityKeyboardGifContent
|
||||||
|
import BatchVideoRendering
|
||||||
|
|
||||||
private let searchBarHeight: CGFloat = 52.0
|
private let searchBarHeight: CGFloat = 52.0
|
||||||
|
|
||||||
@ -54,7 +55,7 @@ public final class PaneSearchContainerNode: ASDisplayNode, EntitySearchContainer
|
|||||||
return self.contentNode.ready
|
return self.contentNode.ready
|
||||||
}
|
}
|
||||||
|
|
||||||
public init(context: AccountContext, theme: PresentationTheme, strings: PresentationStrings, interaction: ChatEntityKeyboardInputNode.Interaction, inputNodeInteraction: ChatMediaInputNodeInteraction, mode: ChatMediaInputSearchMode, stickerActionTitle: String? = nil, trendingGifsPromise: Promise<ChatMediaInputGifPaneTrendingState?>, cancel: @escaping () -> Void, peekBehavior: EmojiContentPeekBehavior?) {
|
public init(context: AccountContext, theme: PresentationTheme, strings: PresentationStrings, interaction: ChatEntityKeyboardInputNode.Interaction, inputNodeInteraction: ChatMediaInputNodeInteraction, mode: ChatMediaInputSearchMode, batchVideoRenderingContext: BatchVideoRenderingContext?, stickerActionTitle: String? = nil, trendingGifsPromise: Promise<ChatMediaInputGifPaneTrendingState?>, cancel: @escaping () -> Void, peekBehavior: EmojiContentPeekBehavior?) {
|
||||||
self.context = context
|
self.context = context
|
||||||
self.mode = mode
|
self.mode = mode
|
||||||
self.interaction = interaction
|
self.interaction = interaction
|
||||||
@ -62,7 +63,7 @@ public final class PaneSearchContainerNode: ASDisplayNode, EntitySearchContainer
|
|||||||
self.peekBehavior = peekBehavior
|
self.peekBehavior = peekBehavior
|
||||||
switch mode {
|
switch mode {
|
||||||
case .gif:
|
case .gif:
|
||||||
self.contentNode = GifPaneSearchContentNode(context: context, theme: theme, strings: strings, interaction: interaction, inputNodeInteraction: inputNodeInteraction, trendingPromise: trendingGifsPromise)
|
self.contentNode = GifPaneSearchContentNode(context: context, theme: theme, strings: strings, interaction: interaction, inputNodeInteraction: inputNodeInteraction, batchVideoRenderingContext: batchVideoRenderingContext ?? BatchVideoRenderingContext(context: context), trendingPromise: trendingGifsPromise)
|
||||||
case .sticker, .trending:
|
case .sticker, .trending:
|
||||||
self.contentNode = StickerPaneSearchContentNode(context: context, theme: theme, strings: strings, interaction: interaction, inputNodeInteraction: inputNodeInteraction, stickerActionTitle: stickerActionTitle)
|
self.contentNode = StickerPaneSearchContentNode(context: context, theme: theme, strings: strings, interaction: interaction, inputNodeInteraction: inputNodeInteraction, stickerActionTitle: stickerActionTitle)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -51,6 +51,7 @@ swift_library(
|
|||||||
"//submodules/TelegramCore/FlatBuffers",
|
"//submodules/TelegramCore/FlatBuffers",
|
||||||
"//submodules/TelegramCore/FlatSerialization",
|
"//submodules/TelegramCore/FlatSerialization",
|
||||||
"//submodules/TelegramUI/Components/BatchVideoRendering",
|
"//submodules/TelegramUI/Components/BatchVideoRendering",
|
||||||
|
"//submodules/TelegramUI/Components/GifVideoLayer",
|
||||||
],
|
],
|
||||||
visibility = [
|
visibility = [
|
||||||
"//visibility:public",
|
"//visibility:public",
|
||||||
|
|||||||
@ -20,115 +20,7 @@ import AVFoundation
|
|||||||
import PhotoResources
|
import PhotoResources
|
||||||
import ShimmerEffect
|
import ShimmerEffect
|
||||||
import BatchVideoRendering
|
import BatchVideoRendering
|
||||||
|
import GifVideoLayer
|
||||||
private class GifVideoLayer: AVSampleBufferDisplayLayer, BatchVideoRenderingContext.Target {
|
|
||||||
private let context: AccountContext
|
|
||||||
private let batchVideoContext: BatchVideoRenderingContext
|
|
||||||
private let userLocation: MediaResourceUserLocation
|
|
||||||
private let file: TelegramMediaFile?
|
|
||||||
|
|
||||||
private var batchVideoTargetHandle: BatchVideoRenderingContext.TargetHandle?
|
|
||||||
var batchVideoRenderingTargetState: BatchVideoRenderingContext.TargetState?
|
|
||||||
|
|
||||||
private var thumbnailDisposable: Disposable?
|
|
||||||
|
|
||||||
private var isReadyToRender: Bool = false
|
|
||||||
|
|
||||||
var started: (() -> Void)?
|
|
||||||
|
|
||||||
var shouldBeAnimating: Bool = false {
|
|
||||||
didSet {
|
|
||||||
if self.shouldBeAnimating == oldValue {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
self.updateShouldBeRendering()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
init(context: AccountContext, batchVideoContext: BatchVideoRenderingContext, userLocation: MediaResourceUserLocation, file: TelegramMediaFile?, synchronousLoad: Bool) {
|
|
||||||
self.context = context
|
|
||||||
self.batchVideoContext = batchVideoContext
|
|
||||||
self.userLocation = userLocation
|
|
||||||
self.file = file
|
|
||||||
|
|
||||||
super.init()
|
|
||||||
|
|
||||||
self.videoGravity = .resizeAspectFill
|
|
||||||
|
|
||||||
if let file = self.file {
|
|
||||||
if let dimensions = file.dimensions {
|
|
||||||
self.thumbnailDisposable = (mediaGridMessageVideo(postbox: context.account.postbox, userLocation: userLocation, videoReference: .savedGif(media: file), synchronousLoad: synchronousLoad, nilForEmptyResult: true)
|
|
||||||
|> deliverOnMainQueue).start(next: { [weak self] transform in
|
|
||||||
guard let strongSelf = self else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
let boundingSize = CGSize(width: 93.0, height: 93.0)
|
|
||||||
let imageSize = dimensions.cgSize.aspectFilled(boundingSize)
|
|
||||||
|
|
||||||
if let image = transform(TransformImageArguments(corners: ImageCorners(), imageSize: imageSize, boundingSize: boundingSize, intrinsicInsets: UIEdgeInsets(), resizeMode: .fill(.clear)))?.generateImage() {
|
|
||||||
Queue.mainQueue().async {
|
|
||||||
if let strongSelf = self {
|
|
||||||
strongSelf.contents = image.cgImage
|
|
||||||
strongSelf.setupVideo()
|
|
||||||
strongSelf.started?()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
strongSelf.setupVideo()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
self.setupVideo()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override init(layer: Any) {
|
|
||||||
guard let layer = layer as? GifVideoLayer else {
|
|
||||||
preconditionFailure()
|
|
||||||
}
|
|
||||||
|
|
||||||
self.context = layer.context
|
|
||||||
self.batchVideoContext = layer.batchVideoContext
|
|
||||||
self.userLocation = layer.userLocation
|
|
||||||
self.file = layer.file
|
|
||||||
|
|
||||||
super.init(layer: layer)
|
|
||||||
}
|
|
||||||
|
|
||||||
required init?(coder: NSCoder) {
|
|
||||||
fatalError("init(coder:) has not been implemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
deinit {
|
|
||||||
self.thumbnailDisposable?.dispose()
|
|
||||||
}
|
|
||||||
|
|
||||||
private func setupVideo() {
|
|
||||||
self.isReadyToRender = true
|
|
||||||
self.updateShouldBeRendering()
|
|
||||||
}
|
|
||||||
|
|
||||||
private func updateShouldBeRendering() {
|
|
||||||
let shouldBeRendering = self.shouldBeAnimating && self.isReadyToRender
|
|
||||||
|
|
||||||
if shouldBeRendering, let file = self.file {
|
|
||||||
if self.batchVideoTargetHandle == nil {
|
|
||||||
self.batchVideoTargetHandle = self.batchVideoContext.add(target: self, file: file, userLocation: self.userLocation)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
self.batchVideoTargetHandle = nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func setSampleBuffer(sampleBuffer: CMSampleBuffer) {
|
|
||||||
if #available(iOS 17.0, *) {
|
|
||||||
self.sampleBufferRenderer.enqueue(sampleBuffer)
|
|
||||||
} else {
|
|
||||||
self.enqueue(sampleBuffer)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public final class GifPagerContentComponent: Component {
|
public final class GifPagerContentComponent: Component {
|
||||||
public typealias EnvironmentType = (EntityKeyboardChildEnvironment, PagerComponentChildEnvironment)
|
public typealias EnvironmentType = (EntityKeyboardChildEnvironment, PagerComponentChildEnvironment)
|
||||||
@ -388,7 +280,7 @@ public final class GifPagerContentComponent: Component {
|
|||||||
self.item = item
|
self.item = item
|
||||||
self.onUpdateDisplayPlaceholder = onUpdateDisplayPlaceholder
|
self.onUpdateDisplayPlaceholder = onUpdateDisplayPlaceholder
|
||||||
|
|
||||||
super.init(context: context, batchVideoContext: batchVideoContext, userLocation: .other, file: item?.file.media, synchronousLoad: attemptSynchronousLoad)
|
super.init(context: context, batchVideoContext: batchVideoContext, userLocation: .other, file: item?.file, synchronousLoad: attemptSynchronousLoad)
|
||||||
|
|
||||||
if item == nil {
|
if item == nil {
|
||||||
self.updateDisplayPlaceholder(displayPlaceholder: true, duration: 0.0)
|
self.updateDisplayPlaceholder(displayPlaceholder: true, duration: 0.0)
|
||||||
|
|||||||
23
submodules/TelegramUI/Components/GifVideoLayer/BUILD
Normal file
23
submodules/TelegramUI/Components/GifVideoLayer/BUILD
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
|
||||||
|
|
||||||
|
swift_library(
|
||||||
|
name = "GifVideoLayer",
|
||||||
|
module_name = "GifVideoLayer",
|
||||||
|
srcs = glob([
|
||||||
|
"Sources/**/*.swift",
|
||||||
|
]),
|
||||||
|
copts = [
|
||||||
|
"-warnings-as-errors",
|
||||||
|
],
|
||||||
|
deps = [
|
||||||
|
"//submodules/Display",
|
||||||
|
"//submodules/TelegramUI/Components/BatchVideoRendering",
|
||||||
|
"//submodules/AccountContext",
|
||||||
|
"//submodules/TelegramCore",
|
||||||
|
"//submodules/SSignalKit/SwiftSignalKit",
|
||||||
|
"//submodules/PhotoResources",
|
||||||
|
],
|
||||||
|
visibility = [
|
||||||
|
"//visibility:public",
|
||||||
|
],
|
||||||
|
)
|
||||||
@ -0,0 +1,118 @@
|
|||||||
|
import Foundation
|
||||||
|
import UIKit
|
||||||
|
import Display
|
||||||
|
import BatchVideoRendering
|
||||||
|
import AccountContext
|
||||||
|
import TelegramCore
|
||||||
|
import AVFoundation
|
||||||
|
import SwiftSignalKit
|
||||||
|
import PhotoResources
|
||||||
|
|
||||||
|
open class GifVideoLayer: AVSampleBufferDisplayLayer, BatchVideoRenderingContext.Target {
|
||||||
|
private let context: AccountContext
|
||||||
|
private let batchVideoContext: BatchVideoRenderingContext
|
||||||
|
private let userLocation: MediaResourceUserLocation
|
||||||
|
private let file: FileMediaReference?
|
||||||
|
|
||||||
|
private var batchVideoTargetHandle: BatchVideoRenderingContext.TargetHandle?
|
||||||
|
public var batchVideoRenderingTargetState: BatchVideoRenderingContext.TargetState?
|
||||||
|
|
||||||
|
private var thumbnailDisposable: Disposable?
|
||||||
|
|
||||||
|
private var isReadyToRender: Bool = false
|
||||||
|
|
||||||
|
public var started: (() -> Void)?
|
||||||
|
|
||||||
|
public var shouldBeAnimating: Bool = false {
|
||||||
|
didSet {
|
||||||
|
if self.shouldBeAnimating == oldValue {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
self.updateShouldBeRendering()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public init(context: AccountContext, batchVideoContext: BatchVideoRenderingContext, userLocation: MediaResourceUserLocation, file: FileMediaReference?, synchronousLoad: Bool) {
|
||||||
|
self.context = context
|
||||||
|
self.batchVideoContext = batchVideoContext
|
||||||
|
self.userLocation = userLocation
|
||||||
|
self.file = file
|
||||||
|
|
||||||
|
super.init()
|
||||||
|
|
||||||
|
self.videoGravity = .resizeAspectFill
|
||||||
|
|
||||||
|
if let file = self.file {
|
||||||
|
if let dimensions = file.media.dimensions {
|
||||||
|
self.thumbnailDisposable = (mediaGridMessageVideo(postbox: context.account.postbox, userLocation: userLocation, videoReference: file, synchronousLoad: synchronousLoad, nilForEmptyResult: true)
|
||||||
|
|> deliverOnMainQueue).start(next: { [weak self] transform in
|
||||||
|
guard let strongSelf = self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let boundingSize = CGSize(width: 93.0, height: 93.0)
|
||||||
|
let imageSize = dimensions.cgSize.aspectFilled(boundingSize)
|
||||||
|
|
||||||
|
if let image = transform(TransformImageArguments(corners: ImageCorners(), imageSize: imageSize, boundingSize: boundingSize, intrinsicInsets: UIEdgeInsets(), resizeMode: .fill(.clear)))?.generateImage() {
|
||||||
|
Queue.mainQueue().async {
|
||||||
|
if let strongSelf = self {
|
||||||
|
strongSelf.contents = image.cgImage
|
||||||
|
strongSelf.setupVideo()
|
||||||
|
strongSelf.started?()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
strongSelf.setupVideo()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
self.setupVideo()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override public init(layer: Any) {
|
||||||
|
guard let layer = layer as? GifVideoLayer else {
|
||||||
|
preconditionFailure()
|
||||||
|
}
|
||||||
|
|
||||||
|
self.context = layer.context
|
||||||
|
self.batchVideoContext = layer.batchVideoContext
|
||||||
|
self.userLocation = layer.userLocation
|
||||||
|
self.file = layer.file
|
||||||
|
|
||||||
|
super.init(layer: layer)
|
||||||
|
}
|
||||||
|
|
||||||
|
required public init?(coder: NSCoder) {
|
||||||
|
fatalError("init(coder:) has not been implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
deinit {
|
||||||
|
self.thumbnailDisposable?.dispose()
|
||||||
|
}
|
||||||
|
|
||||||
|
private func setupVideo() {
|
||||||
|
self.isReadyToRender = true
|
||||||
|
self.updateShouldBeRendering()
|
||||||
|
}
|
||||||
|
|
||||||
|
private func updateShouldBeRendering() {
|
||||||
|
let shouldBeRendering = self.shouldBeAnimating && self.isReadyToRender
|
||||||
|
|
||||||
|
if shouldBeRendering, let file = self.file {
|
||||||
|
if self.batchVideoTargetHandle == nil {
|
||||||
|
self.batchVideoTargetHandle = self.batchVideoContext.add(target: self, file: file, userLocation: self.userLocation)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
self.batchVideoTargetHandle = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public func setSampleBuffer(sampleBuffer: CMSampleBuffer) {
|
||||||
|
if #available(iOS 17.0, *) {
|
||||||
|
self.sampleBufferRenderer.enqueue(sampleBuffer)
|
||||||
|
} else {
|
||||||
|
self.enqueue(sampleBuffer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -19,6 +19,8 @@ swift_library(
|
|||||||
"//submodules/TelegramPresentationData:TelegramPresentationData",
|
"//submodules/TelegramPresentationData:TelegramPresentationData",
|
||||||
"//submodules/ShimmerEffect:ShimmerEffect",
|
"//submodules/ShimmerEffect:ShimmerEffect",
|
||||||
"//submodules/SoftwareVideo:SoftwareVideo",
|
"//submodules/SoftwareVideo:SoftwareVideo",
|
||||||
|
"//submodules/TelegramUI/Components/BatchVideoRendering",
|
||||||
|
"//submodules/TelegramUI/Components/GifVideoLayer",
|
||||||
],
|
],
|
||||||
visibility = [
|
visibility = [
|
||||||
"//visibility:public",
|
"//visibility:public",
|
||||||
|
|||||||
@ -10,6 +10,9 @@ import ContextUI
|
|||||||
import TelegramPresentationData
|
import TelegramPresentationData
|
||||||
import ShimmerEffect
|
import ShimmerEffect
|
||||||
import SoftwareVideo
|
import SoftwareVideo
|
||||||
|
import BatchVideoRendering
|
||||||
|
import GifVideoLayer
|
||||||
|
import AccountContext
|
||||||
|
|
||||||
final class MultiplexedVideoPlaceholderNode: ASDisplayNode {
|
final class MultiplexedVideoPlaceholderNode: ASDisplayNode {
|
||||||
private let effectNode: ShimmerEffectNode
|
private let effectNode: ShimmerEffectNode
|
||||||
@ -101,9 +104,115 @@ public final class MultiplexedVideoNodeFiles {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public final class MultiplexedVideoNode: ASDisplayNode, ASScrollViewDelegate {
|
public final class MultiplexedVideoNode: ASDisplayNode, ASScrollViewDelegate {
|
||||||
private let account: Account
|
public final class Item: Equatable {
|
||||||
|
public let file: FileMediaReference
|
||||||
|
public let contextResult: (ChatContextResultCollection, ChatContextResult)?
|
||||||
|
|
||||||
|
public init(file: FileMediaReference, contextResult: (ChatContextResultCollection, ChatContextResult)?) {
|
||||||
|
self.file = file
|
||||||
|
self.contextResult = contextResult
|
||||||
|
}
|
||||||
|
|
||||||
|
public static func ==(lhs: Item, rhs: Item) -> Bool {
|
||||||
|
if lhs === rhs {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if lhs.file.media.fileId != rhs.file.media.fileId {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if (lhs.contextResult == nil) != (rhs.contextResult != nil) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fileprivate final class ItemLayer: GifVideoLayer {
|
||||||
|
let item: Item?
|
||||||
|
|
||||||
|
private var disposable: Disposable?
|
||||||
|
private var fetchDisposable: Disposable?
|
||||||
|
|
||||||
|
private var isInHierarchyValue: Bool = false
|
||||||
|
public var isVisibleForAnimations: Bool = false {
|
||||||
|
didSet {
|
||||||
|
if self.isVisibleForAnimations != oldValue {
|
||||||
|
self.updatePlayback()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private(set) var displayPlaceholder: Bool = false
|
||||||
|
let onUpdateDisplayPlaceholder: (Bool, Double) -> Void
|
||||||
|
|
||||||
|
init(
|
||||||
|
item: Item?,
|
||||||
|
context: AccountContext,
|
||||||
|
batchVideoContext: BatchVideoRenderingContext,
|
||||||
|
groupId: String,
|
||||||
|
attemptSynchronousLoad: Bool,
|
||||||
|
onUpdateDisplayPlaceholder: @escaping (Bool, Double) -> Void
|
||||||
|
) {
|
||||||
|
self.item = item
|
||||||
|
self.onUpdateDisplayPlaceholder = onUpdateDisplayPlaceholder
|
||||||
|
|
||||||
|
super.init(context: context, batchVideoContext: batchVideoContext, userLocation: .other, file: item?.file, synchronousLoad: attemptSynchronousLoad)
|
||||||
|
|
||||||
|
if item == nil {
|
||||||
|
self.updateDisplayPlaceholder(displayPlaceholder: true, duration: 0.0)
|
||||||
|
}
|
||||||
|
|
||||||
|
self.started = { [weak self] in
|
||||||
|
let _ = self
|
||||||
|
//self?.updateDisplayPlaceholder(displayPlaceholder: false, duration: 0.2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override init(layer: Any) {
|
||||||
|
self.item = nil
|
||||||
|
self.onUpdateDisplayPlaceholder = { _, _ in }
|
||||||
|
|
||||||
|
super.init(layer: layer)
|
||||||
|
}
|
||||||
|
|
||||||
|
required init?(coder: NSCoder) {
|
||||||
|
fatalError("init(coder:) has not been implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
deinit {
|
||||||
|
self.disposable?.dispose()
|
||||||
|
self.fetchDisposable?.dispose()
|
||||||
|
}
|
||||||
|
|
||||||
|
override func action(forKey event: String) -> CAAction? {
|
||||||
|
if event == kCAOnOrderIn {
|
||||||
|
self.isInHierarchyValue = true
|
||||||
|
} else if event == kCAOnOrderOut {
|
||||||
|
self.isInHierarchyValue = false
|
||||||
|
}
|
||||||
|
self.updatePlayback()
|
||||||
|
return nullAction
|
||||||
|
}
|
||||||
|
|
||||||
|
private func updatePlayback() {
|
||||||
|
let shouldBePlaying = self.isInHierarchyValue && self.isVisibleForAnimations
|
||||||
|
|
||||||
|
self.shouldBeAnimating = shouldBePlaying
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateDisplayPlaceholder(displayPlaceholder: Bool, duration: Double) {
|
||||||
|
if self.displayPlaceholder == displayPlaceholder {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
self.displayPlaceholder = displayPlaceholder
|
||||||
|
self.onUpdateDisplayPlaceholder(displayPlaceholder, duration)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private let context: AccountContext
|
||||||
private var theme: PresentationTheme
|
private var theme: PresentationTheme
|
||||||
private var strings: PresentationStrings
|
private var strings: PresentationStrings
|
||||||
|
private let batchVideoRenderingContext: BatchVideoRenderingContext
|
||||||
private let trackingNode: MultiplexedVideoTrackingNode
|
private let trackingNode: MultiplexedVideoTrackingNode
|
||||||
|
|
||||||
public var didScroll: ((CGFloat, CGFloat) -> Void)?
|
public var didScroll: ((CGFloat, CGFloat) -> Void)?
|
||||||
@ -142,41 +251,39 @@ public final class MultiplexedVideoNode: ASDisplayNode, ASScrollViewDelegate {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private var displayItems: [VisibleVideoItem] = []
|
private var displayItems: [VisibleVideoItem] = []
|
||||||
private var visibleThumbnailLayers: [VisibleVideoItem.Id: SoftwareVideoThumbnailNode] = [:]
|
|
||||||
private var visiblePlaceholderNodes: [Int: MultiplexedVideoPlaceholderNode] = [:]
|
private var visiblePlaceholderNodes: [Int: MultiplexedVideoPlaceholderNode] = [:]
|
||||||
|
|
||||||
private let contextContainerNode: ContextControllerSourceNode
|
private let contextContainerNode: ContextControllerSourceNode
|
||||||
public let scrollNode: ASScrollNode
|
public let scrollNode: ASScrollNode
|
||||||
|
|
||||||
private var visibleLayers: [VisibleVideoItem.Id: (SoftwareVideoLayerFrameManager, SampleBufferLayer)] = [:]
|
private var visibleLayers: [VisibleVideoItem.Id: ItemLayer] = [:]
|
||||||
|
|
||||||
private let trendingTitleNode: ImmediateTextNode
|
private let trendingTitleNode: ImmediateTextNode
|
||||||
|
|
||||||
private var displayLink: CADisplayLink!
|
|
||||||
private var timeOffset = 0.0
|
|
||||||
private var pauseTime = 0.0
|
|
||||||
|
|
||||||
private let timebase: CMTimebase
|
|
||||||
|
|
||||||
public var fileSelected: ((MultiplexedVideoNodeFile, ASDisplayNode, CGRect) -> Void)?
|
public var fileSelected: ((MultiplexedVideoNodeFile, ASDisplayNode, CGRect) -> Void)?
|
||||||
public var fileContextMenu: ((MultiplexedVideoNodeFile, ASDisplayNode, CGRect, ContextGesture, Bool) -> Void)?
|
public var fileContextMenu: ((MultiplexedVideoNodeFile, ASDisplayNode, CGRect, ContextGesture, Bool) -> Void)?
|
||||||
public var enableVideoNodes = false
|
public var enableVideoNodes = false {
|
||||||
|
didSet {
|
||||||
|
if self.enableVideoNodes != oldValue {
|
||||||
|
for (_, itemLayer) in self.visibleLayers {
|
||||||
|
itemLayer.isVisibleForAnimations = self.enableVideoNodes
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private var currentActivatingId: VisibleVideoItem.Id?
|
private var currentActivatingId: VisibleVideoItem.Id?
|
||||||
private var isFinishingActivation = false
|
private var isFinishingActivation = false
|
||||||
|
|
||||||
public init(account: Account, theme: PresentationTheme, strings: PresentationStrings) {
|
public init(context: AccountContext, theme: PresentationTheme, strings: PresentationStrings, batchVideoRenderingContext: BatchVideoRenderingContext) {
|
||||||
self.account = account
|
self.context = context
|
||||||
self.theme = theme
|
self.theme = theme
|
||||||
self.strings = strings
|
self.strings = strings
|
||||||
|
self.batchVideoRenderingContext = batchVideoRenderingContext
|
||||||
|
|
||||||
self.trackingNode = MultiplexedVideoTrackingNode()
|
self.trackingNode = MultiplexedVideoTrackingNode()
|
||||||
self.trackingNode.isLayerBacked = true
|
self.trackingNode.isLayerBacked = true
|
||||||
|
|
||||||
var timebase: CMTimebase?
|
|
||||||
CMTimebaseCreateWithSourceClock(allocator: nil, sourceClock: CMClockGetHostTimeClock(), timebaseOut: &timebase)
|
|
||||||
CMTimebaseSetRate(timebase!, rate: 0.0)
|
|
||||||
self.timebase = timebase!
|
|
||||||
|
|
||||||
self.contextContainerNode = ContextControllerSourceNode()
|
self.contextContainerNode = ContextControllerSourceNode()
|
||||||
self.contextContainerNode.animateScale = false
|
self.contextContainerNode.animateScale = false
|
||||||
self.scrollNode = ASScrollNode()
|
self.scrollNode = ASScrollNode()
|
||||||
@ -197,35 +304,8 @@ public final class MultiplexedVideoNode: ASDisplayNode, ASScrollViewDelegate {
|
|||||||
self.addSubnode(self.contextContainerNode)
|
self.addSubnode(self.contextContainerNode)
|
||||||
self.contextContainerNode.addSubnode(self.scrollNode)
|
self.contextContainerNode.addSubnode(self.scrollNode)
|
||||||
|
|
||||||
class DisplayLinkProxy: NSObject {
|
|
||||||
weak var target: MultiplexedVideoNode?
|
|
||||||
|
|
||||||
init(target: MultiplexedVideoNode) {
|
|
||||||
self.target = target
|
|
||||||
}
|
|
||||||
|
|
||||||
@objc func displayLinkEvent() {
|
|
||||||
self.target?.displayLinkEvent()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
self.displayLink = CADisplayLink(target: DisplayLinkProxy(target: self), selector: #selector(DisplayLinkProxy.displayLinkEvent))
|
|
||||||
self.displayLink.add(to: RunLoop.main, forMode: .common)
|
|
||||||
if #available(iOS 10.0, *) {
|
|
||||||
self.displayLink.preferredFramesPerSecond = 25
|
|
||||||
} else {
|
|
||||||
self.displayLink.frameInterval = 2
|
|
||||||
}
|
|
||||||
self.displayLink.isPaused = true
|
|
||||||
|
|
||||||
self.trackingNode.inHierarchyUpdated = { [weak self] value in
|
self.trackingNode.inHierarchyUpdated = { [weak self] value in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
if !value {
|
|
||||||
CMTimebaseSetRate(strongSelf.timebase, rate: 0.0)
|
|
||||||
} else {
|
|
||||||
CMTimebaseSetRate(strongSelf.timebase, rate: 1.0)
|
|
||||||
}
|
|
||||||
strongSelf.displayLink.isPaused = !value
|
|
||||||
if value && !strongSelf.enableVideoNodes {
|
if value && !strongSelf.enableVideoNodes {
|
||||||
strongSelf.enableVideoNodes = true
|
strongSelf.enableVideoNodes = true
|
||||||
strongSelf.validVisibleItemsOffset = nil
|
strongSelf.validVisibleItemsOffset = nil
|
||||||
@ -261,8 +341,8 @@ public final class MultiplexedVideoNode: ASDisplayNode, ASScrollViewDelegate {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if let currentActivatingId = strongSelf.currentActivatingId, let (_, layer) = strongSelf.visibleLayers[currentActivatingId] {
|
if let currentActivatingId = strongSelf.currentActivatingId, let itemLayer = strongSelf.visibleLayers[currentActivatingId] {
|
||||||
let layer = layer.layer
|
let layer = itemLayer
|
||||||
|
|
||||||
let targetContentRect: CGRect = layer.bounds
|
let targetContentRect: CGRect = layer.bounds
|
||||||
|
|
||||||
@ -286,21 +366,17 @@ public final class MultiplexedVideoNode: ASDisplayNode, ASScrollViewDelegate {
|
|||||||
case .begin:
|
case .begin:
|
||||||
let sublayerTransform = CATransform3DTranslate(CATransform3DScale(CATransform3DIdentity, currentScale, currentScale, 1.0), scaleMidX, scaleMidY, 0.0)
|
let sublayerTransform = CATransform3DTranslate(CATransform3DScale(CATransform3DIdentity, currentScale, currentScale, 1.0), scaleMidX, scaleMidY, 0.0)
|
||||||
layer.sublayerTransform = sublayerTransform
|
layer.sublayerTransform = sublayerTransform
|
||||||
|
|
||||||
if let thumbnail = strongSelf.visibleThumbnailLayers[currentActivatingId] {
|
|
||||||
thumbnail.isHidden = true
|
|
||||||
}
|
|
||||||
case .ended:
|
case .ended:
|
||||||
if !strongSelf.isFinishingActivation {
|
if !strongSelf.isFinishingActivation {
|
||||||
strongSelf.isFinishingActivation = true
|
strongSelf.isFinishingActivation = true
|
||||||
let sublayerTransform = CATransform3DTranslate(CATransform3DScale(CATransform3DIdentity, currentScale, currentScale, 1.0), scaleMidX, scaleMidY, 0.0)
|
let sublayerTransform = CATransform3DTranslate(CATransform3DScale(CATransform3DIdentity, currentScale, currentScale, 1.0), scaleMidX, scaleMidY, 0.0)
|
||||||
let previousTransform = layer.sublayerTransform
|
let previousTransform = layer.sublayerTransform
|
||||||
layer.sublayerTransform = sublayerTransform
|
layer.sublayerTransform = sublayerTransform
|
||||||
layer.animate(from: NSValue(caTransform3D: previousTransform), to: NSValue(caTransform3D: sublayerTransform), keyPath: "sublayerTransform", timingFunction: CAMediaTimingFunctionName.easeOut.rawValue, duration: 0.2, completion: { _ in
|
layer.animate(from: NSValue(caTransform3D: previousTransform), to: NSValue(caTransform3D: sublayerTransform), keyPath: "sublayerTransform", timingFunction: CAMediaTimingFunctionName.easeOut.rawValue, duration: 0.2, completion: { [weak strongSelf] _ in
|
||||||
strongSelf.isFinishingActivation = false
|
guard let strongSelf else {
|
||||||
if let thumbnail = strongSelf.visibleThumbnailLayers[currentActivatingId] {
|
return
|
||||||
thumbnail.isHidden = false
|
|
||||||
}
|
}
|
||||||
|
strongSelf.isFinishingActivation = false
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -328,19 +404,6 @@ public final class MultiplexedVideoNode: ASDisplayNode, ASScrollViewDelegate {
|
|||||||
}
|
}
|
||||||
|
|
||||||
deinit {
|
deinit {
|
||||||
self.displayLink.invalidate()
|
|
||||||
self.displayLink.isPaused = true
|
|
||||||
for (_, value) in self.visibleLayers {
|
|
||||||
value.1.isFreed = true
|
|
||||||
}
|
|
||||||
clearSampleBufferLayerPoll()
|
|
||||||
}
|
|
||||||
|
|
||||||
private func displayLinkEvent() {
|
|
||||||
let timestamp = CMTimebaseGetTime(self.timebase).seconds
|
|
||||||
for (_, (manager, _)) in self.visibleLayers {
|
|
||||||
manager.tick(timestamp: timestamp)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private var validSize: CGSize?
|
private var validSize: CGSize?
|
||||||
@ -404,7 +467,6 @@ public final class MultiplexedVideoNode: ASDisplayNode, ASScrollViewDelegate {
|
|||||||
let minVisibleThumbnailY = visibleThumbnailBounds.minY
|
let minVisibleThumbnailY = visibleThumbnailBounds.minY
|
||||||
let maxVisibleThumbnailY = visibleThumbnailBounds.maxY
|
let maxVisibleThumbnailY = visibleThumbnailBounds.maxY
|
||||||
|
|
||||||
var visibleThumbnailIds = Set<VisibleVideoItem.Id>()
|
|
||||||
var visibleIds = Set<VisibleVideoItem.Id>()
|
var visibleIds = Set<VisibleVideoItem.Id>()
|
||||||
|
|
||||||
var maxVisibleIndex = -1
|
var maxVisibleIndex = -1
|
||||||
@ -421,31 +483,6 @@ public final class MultiplexedVideoNode: ASDisplayNode, ASScrollViewDelegate {
|
|||||||
|
|
||||||
maxVisibleIndex = max(maxVisibleIndex, index)
|
maxVisibleIndex = max(maxVisibleIndex, index)
|
||||||
|
|
||||||
visibleThumbnailIds.insert(item.id)
|
|
||||||
|
|
||||||
let thumbnailLayer: SoftwareVideoThumbnailNode
|
|
||||||
if let current = self.visibleThumbnailLayers[item.id] {
|
|
||||||
thumbnailLayer = current
|
|
||||||
if ensureFrames {
|
|
||||||
thumbnailLayer.frame = item.frame
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
var existingPlaceholderNode: MultiplexedVideoPlaceholderNode?
|
|
||||||
if let placeholderNode = self.visiblePlaceholderNodes[index] {
|
|
||||||
existingPlaceholderNode = placeholderNode
|
|
||||||
self.visiblePlaceholderNodes.removeValue(forKey: index)
|
|
||||||
placeholderNode.removeFromSupernode()
|
|
||||||
}
|
|
||||||
|
|
||||||
thumbnailLayer = SoftwareVideoThumbnailNode(account: self.account, fileReference: item.file.file, synchronousLoad: synchronous, usePlaceholder: true, existingPlaceholder: existingPlaceholderNode)
|
|
||||||
thumbnailLayer.frame = item.frame
|
|
||||||
self.scrollNode.addSubnode(thumbnailLayer)
|
|
||||||
self.visibleThumbnailLayers[item.id] = thumbnailLayer
|
|
||||||
}
|
|
||||||
|
|
||||||
thumbnailLayer.update(theme: self.theme, size: item.frame.size)
|
|
||||||
thumbnailLayer.updateAbsoluteRect(item.frame.offsetBy(dx: 0.0, dy: absoluteContainerOffset), within: absoluteContainerSize)
|
|
||||||
|
|
||||||
if item.frame.maxY < minVisibleY {
|
if item.frame.maxY < minVisibleY {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@ -455,22 +492,25 @@ public final class MultiplexedVideoNode: ASDisplayNode, ASScrollViewDelegate {
|
|||||||
|
|
||||||
visibleIds.insert(item.id)
|
visibleIds.insert(item.id)
|
||||||
|
|
||||||
if let (_, layerHolder) = self.visibleLayers[item.id] {
|
if let itemLayer = self.visibleLayers[item.id] {
|
||||||
if ensureFrames {
|
if ensureFrames {
|
||||||
layerHolder.layer.frame = item.frame
|
itemLayer.frame = item.frame
|
||||||
}
|
}
|
||||||
|
itemLayer.isVisibleForAnimations = self.enableVideoNodes
|
||||||
} else {
|
} else {
|
||||||
let layerHolder = takeSampleBufferLayer()
|
let itemLayer = ItemLayer(
|
||||||
layerHolder.layer.videoGravity = AVLayerVideoGravity.resizeAspectFill
|
item: Item(file: item.file.file, contextResult: item.file.contextResult),
|
||||||
layerHolder.layer.frame = item.frame
|
context: self.context,
|
||||||
self.scrollNode.layer.addSublayer(layerHolder.layer)
|
batchVideoContext: self.batchVideoRenderingContext,
|
||||||
let manager = SoftwareVideoLayerFrameManager(account: self.account, userLocation: .other, userContentType: .other, fileReference: item.file.file, layerHolder: layerHolder)
|
groupId: "",
|
||||||
self.visibleLayers[item.id] = (manager, layerHolder)
|
attemptSynchronousLoad: false,
|
||||||
self.visibleThumbnailLayers[item.id]?.ready = { [weak self] in
|
onUpdateDisplayPlaceholder: { _, _ in
|
||||||
if let strongSelf = self {
|
|
||||||
strongSelf.visibleLayers[item.id]?.0.start()
|
|
||||||
}
|
}
|
||||||
}
|
)
|
||||||
|
itemLayer.frame = item.frame
|
||||||
|
self.scrollNode.layer.addSublayer(itemLayer)
|
||||||
|
self.visibleLayers[item.id] = itemLayer
|
||||||
|
itemLayer.isVisibleForAnimations = self.enableVideoNodes
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -518,13 +558,6 @@ public final class MultiplexedVideoNode: ASDisplayNode, ASScrollViewDelegate {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var removeThumbnailIds: [VisibleVideoItem.Id] = []
|
|
||||||
for id in self.visibleThumbnailLayers.keys {
|
|
||||||
if !visibleThumbnailIds.contains(id) {
|
|
||||||
removeThumbnailIds.append(id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var removePlaceholderIndices: [Int] = []
|
var removePlaceholderIndices: [Int] = []
|
||||||
for index in self.visiblePlaceholderNodes.keys {
|
for index in self.visiblePlaceholderNodes.keys {
|
||||||
if !visiblePlaceholderIndices.contains(index) {
|
if !visiblePlaceholderIndices.contains(index) {
|
||||||
@ -533,17 +566,11 @@ public final class MultiplexedVideoNode: ASDisplayNode, ASScrollViewDelegate {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for id in removeIds {
|
for id in removeIds {
|
||||||
let (_, layerHolder) = self.visibleLayers[id]!
|
let itemLayer = self.visibleLayers[id]!
|
||||||
layerHolder.layer.removeFromSuperlayer()
|
itemLayer.removeFromSuperlayer()
|
||||||
self.visibleLayers.removeValue(forKey: id)
|
self.visibleLayers.removeValue(forKey: id)
|
||||||
}
|
}
|
||||||
|
|
||||||
for id in removeThumbnailIds {
|
|
||||||
let thumbnailLayer = self.visibleThumbnailLayers[id]!
|
|
||||||
thumbnailLayer.removeFromSupernode()
|
|
||||||
self.visibleThumbnailLayers.removeValue(forKey: id)
|
|
||||||
}
|
|
||||||
|
|
||||||
for index in removePlaceholderIndices {
|
for index in removePlaceholderIndices {
|
||||||
if let placeholderNode = self.visiblePlaceholderNodes[index] {
|
if let placeholderNode = self.visiblePlaceholderNodes[index] {
|
||||||
placeholderNode.removeFromSupernode()
|
placeholderNode.removeFromSupernode()
|
||||||
|
|||||||
@ -332,6 +332,7 @@ private final class StickerSelectionComponent: Component {
|
|||||||
interaction: interaction,
|
interaction: interaction,
|
||||||
inputNodeInteraction: inputNodeInteraction,
|
inputNodeInteraction: inputNodeInteraction,
|
||||||
mode: mappedMode,
|
mode: mappedMode,
|
||||||
|
batchVideoRenderingContext: nil,
|
||||||
stickerActionTitle: presentationData.strings.StickerPack_AddSticker,
|
stickerActionTitle: presentationData.strings.StickerPack_AddSticker,
|
||||||
trendingGifsPromise: self.component?.getController()?.node.trendingGifsPromise ?? Promise(nil),
|
trendingGifsPromise: self.component?.getController()?.node.trendingGifsPromise ?? Promise(nil),
|
||||||
cancel: {
|
cancel: {
|
||||||
|
|||||||
@ -445,7 +445,6 @@ extension ChatControllerImpl {
|
|||||||
|
|
||||||
let purchaseScreen = strongSelf.context.sharedContext.makeStarsPurchaseScreen(context: strongSelf.context, starsContext: starsContext, options: options, purpose: .reactions(peerId: message.id.peerId, requiredStars: 1), completion: { result in
|
let purchaseScreen = strongSelf.context.sharedContext.makeStarsPurchaseScreen(context: strongSelf.context, starsContext: starsContext, options: options, purpose: .reactions(peerId: message.id.peerId, requiredStars: 1), completion: { result in
|
||||||
let _ = result
|
let _ = result
|
||||||
//TODO:release
|
|
||||||
})
|
})
|
||||||
strongSelf.push(purchaseScreen)
|
strongSelf.push(purchaseScreen)
|
||||||
})
|
})
|
||||||
|
|||||||
@ -1787,7 +1787,6 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
|
|
||||||
let purchaseScreen = strongSelf.context.sharedContext.makeStarsPurchaseScreen(context: strongSelf.context, starsContext: starsContext, options: options, purpose: .reactions(peerId: peerId, requiredStars: 1), completion: { result in
|
let purchaseScreen = strongSelf.context.sharedContext.makeStarsPurchaseScreen(context: strongSelf.context, starsContext: starsContext, options: options, purpose: .reactions(peerId: peerId, requiredStars: 1), completion: { result in
|
||||||
let _ = result
|
let _ = result
|
||||||
//TODO:release
|
|
||||||
})
|
})
|
||||||
strongSelf.push(purchaseScreen)
|
strongSelf.push(purchaseScreen)
|
||||||
})
|
})
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user