mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +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
|
||||
|
||||
if themeUpdated || titleUpdated {
|
||||
//TODO:release
|
||||
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)
|
||||
|
||||
|
@ -637,7 +637,6 @@ final class VideoChatParticipantsComponent: Component {
|
||||
|
||||
private var ignoreScrolling: Bool = false
|
||||
|
||||
//TODO:release
|
||||
private var gridParticipants: [VideoParticipant] = []
|
||||
private var listParticipants: [GroupCallParticipantsContext.Participant] = []
|
||||
|
||||
|
@ -2420,7 +2420,6 @@ final class VideoChatScreenComponent: Component {
|
||||
}
|
||||
videoButtonContent = .audio(audio: buttonAudio, isEnabled: buttonIsEnabled)
|
||||
} else {
|
||||
//TODO:release
|
||||
videoButtonContent = .video(isActive: false)
|
||||
}
|
||||
let _ = self.videoButton.update(
|
||||
|
@ -285,7 +285,6 @@ extension VideoChatScreenComponent.View {
|
||||
}
|
||||
|
||||
//let isScheduled = strongSelf.isScheduled
|
||||
//TODO:release
|
||||
let isScheduled: Bool = !"".isEmpty
|
||||
|
||||
let canSpeak: Bool
|
||||
|
@ -90,7 +90,7 @@ public final class BatchVideoRenderingContext {
|
||||
|
||||
private final class TargetContext {
|
||||
weak var target: Target?
|
||||
let file: TelegramMediaFile
|
||||
let file: FileMediaReference
|
||||
let userLocation: MediaResourceUserLocation
|
||||
|
||||
var readingContext: QueueLocalObject<ReadingContext>?
|
||||
@ -100,7 +100,7 @@ public final class BatchVideoRenderingContext {
|
||||
|
||||
init(
|
||||
target: Target,
|
||||
file: TelegramMediaFile,
|
||||
file: FileMediaReference,
|
||||
userLocation: MediaResourceUserLocation
|
||||
) {
|
||||
self.target = target
|
||||
@ -128,7 +128,7 @@ public final class BatchVideoRenderingContext {
|
||||
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
|
||||
self.nextId += 1
|
||||
|
||||
@ -158,11 +158,11 @@ public final class BatchVideoRenderingContext {
|
||||
mediaBox: self.context.account.postbox.mediaBox,
|
||||
userLocation: targetContext.userLocation,
|
||||
userContentType: .sticker,
|
||||
reference: .media(media: .standalone(media: targetContext.file), resource: targetContext.file.resource)
|
||||
reference: targetContext.file.resourceReference(targetContext.file.media.resource)
|
||||
).startStrict()
|
||||
}
|
||||
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
|
||||
guard let self, let targetContext else {
|
||||
return
|
||||
|
@ -40,6 +40,7 @@ swift_library(
|
||||
"//submodules/TelegramUI/Components/EntityKeyboardGifContent:EntityKeyboardGifContent",
|
||||
"//submodules/TelegramUI/Components/LegacyMessageInputPanelInputView:LegacyMessageInputPanelInputView",
|
||||
"//submodules/AttachmentTextInputPanelNode",
|
||||
"//submodules/TelegramUI/Components/BatchVideoRendering",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
@ -1912,6 +1912,7 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode {
|
||||
interaction: interaction,
|
||||
inputNodeInteraction: inputNodeInteraction,
|
||||
mode: mappedMode,
|
||||
batchVideoRenderingContext: nil,
|
||||
trendingGifsPromise: trendingGifsPromise,
|
||||
cancel: {
|
||||
},
|
||||
|
@ -12,12 +12,15 @@ import ChatControllerInteraction
|
||||
import MultiplexedVideoNode
|
||||
import ChatPresentationInterfaceState
|
||||
import EntityKeyboardGifContent
|
||||
import BatchVideoRendering
|
||||
|
||||
final class GifPaneSearchContentNode: ASDisplayNode & PaneSearchContentNode {
|
||||
private let context: AccountContext
|
||||
private let interaction: ChatEntityKeyboardInputNode.Interaction
|
||||
private let inputNodeInteraction: ChatMediaInputNodeInteraction
|
||||
|
||||
private let batchVideoRenderingContext: BatchVideoRenderingContext
|
||||
|
||||
private var theme: PresentationTheme
|
||||
private var strings: PresentationStrings
|
||||
|
||||
@ -45,10 +48,11 @@ final class GifPaneSearchContentNode: ASDisplayNode & PaneSearchContentNode {
|
||||
|
||||
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.interaction = interaction
|
||||
self.inputNodeInteraction = inputNodeInteraction
|
||||
self.batchVideoRenderingContext = batchVideoRenderingContext
|
||||
self.trendingPromise = trendingPromise
|
||||
|
||||
self.theme = theme
|
||||
@ -217,7 +221,7 @@ final class GifPaneSearchContentNode: ASDisplayNode & PaneSearchContentNode {
|
||||
super.willEnterHierarchy()
|
||||
|
||||
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
|
||||
if let layout = self.validLayout {
|
||||
multiplexedNode.frame = CGRect(origin: CGPoint(), size: layout)
|
||||
|
@ -14,6 +14,7 @@ import MultiplexedVideoNode
|
||||
import FeaturedStickersScreen
|
||||
import StickerPeekUI
|
||||
import EntityKeyboardGifContent
|
||||
import BatchVideoRendering
|
||||
|
||||
private let searchBarHeight: CGFloat = 52.0
|
||||
|
||||
@ -54,7 +55,7 @@ public final class PaneSearchContainerNode: ASDisplayNode, EntitySearchContainer
|
||||
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.mode = mode
|
||||
self.interaction = interaction
|
||||
@ -62,7 +63,7 @@ public final class PaneSearchContainerNode: ASDisplayNode, EntitySearchContainer
|
||||
self.peekBehavior = peekBehavior
|
||||
switch mode {
|
||||
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:
|
||||
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/FlatSerialization",
|
||||
"//submodules/TelegramUI/Components/BatchVideoRendering",
|
||||
"//submodules/TelegramUI/Components/GifVideoLayer",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
@ -20,115 +20,7 @@ import AVFoundation
|
||||
import PhotoResources
|
||||
import ShimmerEffect
|
||||
import BatchVideoRendering
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
import GifVideoLayer
|
||||
|
||||
public final class GifPagerContentComponent: Component {
|
||||
public typealias EnvironmentType = (EntityKeyboardChildEnvironment, PagerComponentChildEnvironment)
|
||||
@ -388,7 +280,7 @@ public final class GifPagerContentComponent: Component {
|
||||
self.item = item
|
||||
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 {
|
||||
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/ShimmerEffect:ShimmerEffect",
|
||||
"//submodules/SoftwareVideo:SoftwareVideo",
|
||||
"//submodules/TelegramUI/Components/BatchVideoRendering",
|
||||
"//submodules/TelegramUI/Components/GifVideoLayer",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
@ -10,6 +10,9 @@ import ContextUI
|
||||
import TelegramPresentationData
|
||||
import ShimmerEffect
|
||||
import SoftwareVideo
|
||||
import BatchVideoRendering
|
||||
import GifVideoLayer
|
||||
import AccountContext
|
||||
|
||||
final class MultiplexedVideoPlaceholderNode: ASDisplayNode {
|
||||
private let effectNode: ShimmerEffectNode
|
||||
@ -101,9 +104,115 @@ public final class MultiplexedVideoNodeFiles {
|
||||
}
|
||||
|
||||
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 strings: PresentationStrings
|
||||
private let batchVideoRenderingContext: BatchVideoRenderingContext
|
||||
private let trackingNode: MultiplexedVideoTrackingNode
|
||||
|
||||
public var didScroll: ((CGFloat, CGFloat) -> Void)?
|
||||
@ -142,41 +251,39 @@ public final class MultiplexedVideoNode: ASDisplayNode, ASScrollViewDelegate {
|
||||
}
|
||||
|
||||
private var displayItems: [VisibleVideoItem] = []
|
||||
private var visibleThumbnailLayers: [VisibleVideoItem.Id: SoftwareVideoThumbnailNode] = [:]
|
||||
private var visiblePlaceholderNodes: [Int: MultiplexedVideoPlaceholderNode] = [:]
|
||||
|
||||
private let contextContainerNode: ContextControllerSourceNode
|
||||
public let scrollNode: ASScrollNode
|
||||
|
||||
private var visibleLayers: [VisibleVideoItem.Id: (SoftwareVideoLayerFrameManager, SampleBufferLayer)] = [:]
|
||||
private var visibleLayers: [VisibleVideoItem.Id: ItemLayer] = [:]
|
||||
|
||||
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 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 isFinishingActivation = false
|
||||
|
||||
public init(account: Account, theme: PresentationTheme, strings: PresentationStrings) {
|
||||
self.account = account
|
||||
public init(context: AccountContext, theme: PresentationTheme, strings: PresentationStrings, batchVideoRenderingContext: BatchVideoRenderingContext) {
|
||||
self.context = context
|
||||
self.theme = theme
|
||||
self.strings = strings
|
||||
self.batchVideoRenderingContext = batchVideoRenderingContext
|
||||
|
||||
self.trackingNode = MultiplexedVideoTrackingNode()
|
||||
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.animateScale = false
|
||||
self.scrollNode = ASScrollNode()
|
||||
@ -197,35 +304,8 @@ public final class MultiplexedVideoNode: ASDisplayNode, ASScrollViewDelegate {
|
||||
self.addSubnode(self.contextContainerNode)
|
||||
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
|
||||
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 {
|
||||
strongSelf.enableVideoNodes = true
|
||||
strongSelf.validVisibleItemsOffset = nil
|
||||
@ -261,8 +341,8 @@ public final class MultiplexedVideoNode: ASDisplayNode, ASScrollViewDelegate {
|
||||
return
|
||||
}
|
||||
|
||||
if let currentActivatingId = strongSelf.currentActivatingId, let (_, layer) = strongSelf.visibleLayers[currentActivatingId] {
|
||||
let layer = layer.layer
|
||||
if let currentActivatingId = strongSelf.currentActivatingId, let itemLayer = strongSelf.visibleLayers[currentActivatingId] {
|
||||
let layer = itemLayer
|
||||
|
||||
let targetContentRect: CGRect = layer.bounds
|
||||
|
||||
@ -286,21 +366,17 @@ public final class MultiplexedVideoNode: ASDisplayNode, ASScrollViewDelegate {
|
||||
case .begin:
|
||||
let sublayerTransform = CATransform3DTranslate(CATransform3DScale(CATransform3DIdentity, currentScale, currentScale, 1.0), scaleMidX, scaleMidY, 0.0)
|
||||
layer.sublayerTransform = sublayerTransform
|
||||
|
||||
if let thumbnail = strongSelf.visibleThumbnailLayers[currentActivatingId] {
|
||||
thumbnail.isHidden = true
|
||||
}
|
||||
case .ended:
|
||||
if !strongSelf.isFinishingActivation {
|
||||
strongSelf.isFinishingActivation = true
|
||||
let sublayerTransform = CATransform3DTranslate(CATransform3DScale(CATransform3DIdentity, currentScale, currentScale, 1.0), scaleMidX, scaleMidY, 0.0)
|
||||
let previousTransform = layer.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
|
||||
strongSelf.isFinishingActivation = false
|
||||
if let thumbnail = strongSelf.visibleThumbnailLayers[currentActivatingId] {
|
||||
thumbnail.isHidden = false
|
||||
layer.animate(from: NSValue(caTransform3D: previousTransform), to: NSValue(caTransform3D: sublayerTransform), keyPath: "sublayerTransform", timingFunction: CAMediaTimingFunctionName.easeOut.rawValue, duration: 0.2, completion: { [weak strongSelf] _ in
|
||||
guard let strongSelf else {
|
||||
return
|
||||
}
|
||||
strongSelf.isFinishingActivation = false
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -328,19 +404,6 @@ public final class MultiplexedVideoNode: ASDisplayNode, ASScrollViewDelegate {
|
||||
}
|
||||
|
||||
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?
|
||||
@ -404,7 +467,6 @@ public final class MultiplexedVideoNode: ASDisplayNode, ASScrollViewDelegate {
|
||||
let minVisibleThumbnailY = visibleThumbnailBounds.minY
|
||||
let maxVisibleThumbnailY = visibleThumbnailBounds.maxY
|
||||
|
||||
var visibleThumbnailIds = Set<VisibleVideoItem.Id>()
|
||||
var visibleIds = Set<VisibleVideoItem.Id>()
|
||||
|
||||
var maxVisibleIndex = -1
|
||||
@ -421,31 +483,6 @@ public final class MultiplexedVideoNode: ASDisplayNode, ASScrollViewDelegate {
|
||||
|
||||
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 {
|
||||
continue
|
||||
}
|
||||
@ -455,22 +492,25 @@ public final class MultiplexedVideoNode: ASDisplayNode, ASScrollViewDelegate {
|
||||
|
||||
visibleIds.insert(item.id)
|
||||
|
||||
if let (_, layerHolder) = self.visibleLayers[item.id] {
|
||||
if let itemLayer = self.visibleLayers[item.id] {
|
||||
if ensureFrames {
|
||||
layerHolder.layer.frame = item.frame
|
||||
itemLayer.frame = item.frame
|
||||
}
|
||||
itemLayer.isVisibleForAnimations = self.enableVideoNodes
|
||||
} else {
|
||||
let layerHolder = takeSampleBufferLayer()
|
||||
layerHolder.layer.videoGravity = AVLayerVideoGravity.resizeAspectFill
|
||||
layerHolder.layer.frame = item.frame
|
||||
self.scrollNode.layer.addSublayer(layerHolder.layer)
|
||||
let manager = SoftwareVideoLayerFrameManager(account: self.account, userLocation: .other, userContentType: .other, fileReference: item.file.file, layerHolder: layerHolder)
|
||||
self.visibleLayers[item.id] = (manager, layerHolder)
|
||||
self.visibleThumbnailLayers[item.id]?.ready = { [weak self] in
|
||||
if let strongSelf = self {
|
||||
strongSelf.visibleLayers[item.id]?.0.start()
|
||||
let itemLayer = ItemLayer(
|
||||
item: Item(file: item.file.file, contextResult: item.file.contextResult),
|
||||
context: self.context,
|
||||
batchVideoContext: self.batchVideoRenderingContext,
|
||||
groupId: "",
|
||||
attemptSynchronousLoad: false,
|
||||
onUpdateDisplayPlaceholder: { _, _ in
|
||||
}
|
||||
}
|
||||
)
|
||||
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] = []
|
||||
for index in self.visiblePlaceholderNodes.keys {
|
||||
if !visiblePlaceholderIndices.contains(index) {
|
||||
@ -533,17 +566,11 @@ public final class MultiplexedVideoNode: ASDisplayNode, ASScrollViewDelegate {
|
||||
}
|
||||
|
||||
for id in removeIds {
|
||||
let (_, layerHolder) = self.visibleLayers[id]!
|
||||
layerHolder.layer.removeFromSuperlayer()
|
||||
let itemLayer = self.visibleLayers[id]!
|
||||
itemLayer.removeFromSuperlayer()
|
||||
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 {
|
||||
if let placeholderNode = self.visiblePlaceholderNodes[index] {
|
||||
placeholderNode.removeFromSupernode()
|
||||
|
@ -332,6 +332,7 @@ private final class StickerSelectionComponent: Component {
|
||||
interaction: interaction,
|
||||
inputNodeInteraction: inputNodeInteraction,
|
||||
mode: mappedMode,
|
||||
batchVideoRenderingContext: nil,
|
||||
stickerActionTitle: presentationData.strings.StickerPack_AddSticker,
|
||||
trendingGifsPromise: self.component?.getController()?.node.trendingGifsPromise ?? Promise(nil),
|
||||
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 _ = result
|
||||
//TODO:release
|
||||
})
|
||||
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 _ = result
|
||||
//TODO:release
|
||||
})
|
||||
strongSelf.push(purchaseScreen)
|
||||
})
|
||||
|
Loading…
x
Reference in New Issue
Block a user