diff --git a/submodules/AccountContext/Sources/UniversalVideoNode.swift b/submodules/AccountContext/Sources/UniversalVideoNode.swift index 70dc74e14d..80273b5756 100644 --- a/submodules/AccountContext/Sources/UniversalVideoNode.swift +++ b/submodules/AccountContext/Sources/UniversalVideoNode.swift @@ -37,6 +37,7 @@ public protocol UniversalVideoContentNode: AnyObject { func setBaseRate(_ baseRate: Double) func setVideoQuality(_ videoQuality: UniversalVideoContentVideoQuality) func videoQualityState() -> (current: Int, preferred: UniversalVideoContentVideoQuality, available: [Int])? + func videoQualityStateSignal() -> Signal<(current: Int, preferred: UniversalVideoContentVideoQuality, available: [Int])?, NoError> func addPlaybackCompleted(_ f: @escaping () -> Void) -> Int func removePlaybackCompleted(_ index: Int) func fetchControl(_ control: UniversalVideoNodeFetchControl) @@ -367,6 +368,16 @@ public final class UniversalVideoNode: ASDisplayNode { return result } + public func videoQualityStateSignal() -> Signal<(current: Int, preferred: UniversalVideoContentVideoQuality, available: [Int])?, NoError> { + var result: Signal<(current: Int, preferred: UniversalVideoContentVideoQuality, available: [Int])?, NoError>? + self.manager.withUniversalVideoContent(id: self.content.id, { contentNode in + if let contentNode { + result = contentNode.videoQualityStateSignal() + } + }) + return result ?? .single(nil) + } + public func continuePlayingWithoutSound(actionAtEnd: MediaPlayerPlayOnceWithSoundActionAtEnd = .loopDisablingSound) { self.manager.withUniversalVideoContent(id: self.content.id, { contentNode in if let contentNode = contentNode { diff --git a/submodules/GalleryUI/Sources/Items/UniversalVideoGalleryItem.swift b/submodules/GalleryUI/Sources/Items/UniversalVideoGalleryItem.swift index a7776fedcd..f3a46f5fbc 100644 --- a/submodules/GalleryUI/Sources/Items/UniversalVideoGalleryItem.swift +++ b/submodules/GalleryUI/Sources/Items/UniversalVideoGalleryItem.swift @@ -678,10 +678,11 @@ final class SettingsHeaderButton: HighlightableButtonNode { } else if let speedBadge = self.speedBadge { self.speedBadge = nil if let speedBadgeView = speedBadge.view { - transition.setAlpha(layer: speedBadgeView.layer, alpha: 0.0, completion: { [weak speedBadgeView] _ in - speedBadgeView?.layer.removeFromSuperlayer() + let speedBadgeLayer = speedBadgeView.layer + transition.setAlpha(layer: speedBadgeLayer, alpha: 0.0, completion: { [weak speedBadgeLayer] _ in + speedBadgeLayer?.removeFromSuperlayer() }) - transition.setScale(layer: speedBadgeView.layer, scale: 0.001) + transition.setScale(layer: speedBadgeLayer, scale: 0.001) } } @@ -719,10 +720,11 @@ final class SettingsHeaderButton: HighlightableButtonNode { } else if let qualityBadge = self.qualityBadge { self.qualityBadge = nil if let qualityBadgeView = qualityBadge.view { - transition.setAlpha(layer: qualityBadgeView.layer, alpha: 0.0, completion: { [weak qualityBadgeView] _ in - qualityBadgeView?.layer.removeFromSuperlayer() + let qualityBadgeLayer = qualityBadgeView.layer + transition.setAlpha(layer: qualityBadgeLayer, alpha: 0.0, completion: { [weak qualityBadgeLayer] _ in + qualityBadgeLayer?.removeFromSuperlayer() }) - transition.setScale(layer: qualityBadgeView.layer, scale: 0.001) + transition.setScale(layer: qualityBadgeLayer, scale: 0.001) } } } @@ -1888,7 +1890,7 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode { } var rateString: String? - if abs(playbackRate - 1.0) > 0.1 { + if abs(playbackRate - 1.0) > 0.05 { var stringValue = String(format: "%.1fx", playbackRate) if stringValue.hasSuffix(".0x") { stringValue = stringValue.replacingOccurrences(of: ".0x", with: "x") @@ -3284,9 +3286,9 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode { let contextController = ContextController(presentationData: self.presentationData.withUpdated(theme: defaultDarkColorPresentationTheme), source: .reference(HeaderContextReferenceContentSource(controller: controller, sourceNode: sourceNode)), items: items |> map { items in if !items.topItems.isEmpty { - return ContextController.Items(content: .twoLists(items.items, items.topItems)) + return ContextController.Items(id: AnyHashable(0), content: .twoLists(items.items, items.topItems)) } else { - return ContextController.Items(content: .list(items.items)) + return ContextController.Items(id: AnyHashable(0), content: .list(items.items)) } }, gesture: gesture) if isSettings { @@ -3459,9 +3461,12 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode { peer = .single(nil) } - return combineLatest(queue: Queue.mainQueue(), videoNode.status, peer) - |> take(1) - |> map { [weak self] status, peer -> (items: [ContextMenuItem], topItems: [ContextMenuItem]) in + return combineLatest(queue: Queue.mainQueue(), + videoNode.status |> take(1), + peer, + videoNode.videoQualityStateSignal() + ) + |> map { [weak self] status, peer, videoQualityState -> (items: [ContextMenuItem], topItems: [ContextMenuItem]) in guard let status = status, let strongSelf = self else { return ([], []) } @@ -3483,7 +3488,7 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode { sliderValuePromise.set(newValue) }), true)) - if let videoQualityState = strongSelf.videoNode?.videoQualityState(), !videoQualityState.available.isEmpty { + if let videoQualityState, !videoQualityState.available.isEmpty { } else { items.append(.custom(SectionTitleContextItem(text: strongSelf.presentationData.strings.Gallery_VideoSettings_SpeedSectionTitle), false)) for (text, _, rate) in strongSelf.speedList(strings: strongSelf.presentationData.strings) { @@ -3510,7 +3515,7 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode { } } - if let videoQualityState = strongSelf.videoNode?.videoQualityState(), !videoQualityState.available.isEmpty { + if let videoQualityState, !videoQualityState.available.isEmpty { items.append(.custom(SectionTitleContextItem(text: strongSelf.presentationData.strings.Gallery_VideoSettings_QualitySectionTitle), false)) do { @@ -3522,7 +3527,7 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode { } else { textLayout = .singleLine } - items.append(.action(ContextMenuActionItem(text: qualityText, textLayout: textLayout, icon: { _ in + items.append(.action(ContextMenuActionItem(id: AnyHashable("q"), text: qualityText, textLayout: textLayout, icon: { _ in if isSelected { return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: .white) } else { @@ -3536,10 +3541,6 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode { } videoNode.setVideoQuality(.auto) self.videoQualityPromise.set(.auto) - - /*if let controller = strongSelf.galleryController() as? GalleryController { - controller.updateSharedPlaybackRate(rate) - }*/ }))) } diff --git a/submodules/TelegramUI/Components/RasterizedCompositionComponent/Sources/RasterizedCompositionComponent.swift b/submodules/TelegramUI/Components/RasterizedCompositionComponent/Sources/RasterizedCompositionComponent.swift index d21571f69f..cc45809ad1 100644 --- a/submodules/TelegramUI/Components/RasterizedCompositionComponent/Sources/RasterizedCompositionComponent.swift +++ b/submodules/TelegramUI/Components/RasterizedCompositionComponent/Sources/RasterizedCompositionComponent.swift @@ -261,7 +261,7 @@ public final class RasterizedCompositionMonochromeLayer: SimpleLayer { override public init() { super.init() - self.maskedLayer.isHidden = true + self.maskedLayer.opacity = 0.0 self.addSublayer(self.maskedLayer) self.maskedLayer.mask = self.contentsLayer @@ -377,9 +377,9 @@ public final class RasterizedCompositionMonochromeLayer: SimpleLayer { } private func updateRasterizationMode() { - self.maskedLayer.isHidden = !self.contentsLayer.hasAnimationsInTree - if self.rasterizedLayer.isHidden != (!self.maskedLayer.isHidden) { - self.rasterizedLayer.isHidden = (!self.maskedLayer.isHidden) + self.maskedLayer.opacity = self.contentsLayer.hasAnimationsInTree ? 1.0 : 0.0 + if self.rasterizedLayer.isHidden != (self.maskedLayer.opacity != 0.0) { + self.rasterizedLayer.isHidden = self.maskedLayer.opacity != 0.0 if !self.rasterizedLayer.isHidden { self.updateContents() } diff --git a/submodules/TelegramUniversalVideoContent/Sources/HLSVideoJSNativeContentNode.swift b/submodules/TelegramUniversalVideoContent/Sources/HLSVideoJSNativeContentNode.swift index ef59b5b078..9d89d4112b 100644 --- a/submodules/TelegramUniversalVideoContent/Sources/HLSVideoJSNativeContentNode.swift +++ b/submodules/TelegramUniversalVideoContent/Sources/HLSVideoJSNativeContentNode.swift @@ -978,6 +978,18 @@ final class HLSVideoJSNativeContentNode: ASDisplayNode, UniversalVideoContentNod } } + private struct VideoQualityState: Equatable { + var current: Int + var preferred: UniversalVideoContentVideoQuality + var available: [Int] + + init(current: Int, preferred: UniversalVideoContentVideoQuality, available: [Int]) { + self.current = current + self.preferred = preferred + self.available = available + } + } + fileprivate static var sharedBandwidthEstimate: Double? private let postbox: Postbox @@ -1047,8 +1059,12 @@ final class HLSVideoJSNativeContentNode: ASDisplayNode, UniversalVideoContentNod fileprivate var playerRate: Double = 0.0 fileprivate var playerDefaultRate: Double = 1.0 fileprivate var playerTime: Double = 0.0 + fileprivate var playerAvailableLevels: [Int: Level] = [:] fileprivate var playerCurrentLevelIndex: Int? + + private var videoQualityStateValue: VideoQualityState? + private let videoQualityStatePromise = Promise(nil) private var hasRequestedPlayerLoad: Bool = false @@ -1265,6 +1281,8 @@ final class HLSVideoJSNativeContentNode: ASDisplayNode, UniversalVideoContentNod self.playerCurrentLevelIndex = nil } + self.updateVideoQualityState() + if self.playerIsReady { if !self.hasRequestedPlayerLoad { if !self.playerAvailableLevels.isEmpty { @@ -1566,11 +1584,24 @@ final class HLSVideoJSNativeContentNode: ASDisplayNode, UniversalVideoContentNod } } + self.updateVideoQualityState() + if self.playerIsReady { SharedHLSVideoWebView.shared.webView?.evaluateJavaScript("window.hlsPlayer_instances[\(self.instanceId)].playerSetLevel(\(self.requestedLevelIndex ?? -1));", completionHandler: nil) } } + private func updateVideoQualityState() { + var videoQualityState: VideoQualityState? + if let value = self.videoQualityState() { + videoQualityState = VideoQualityState(current: value.current, preferred: value.preferred, available: value.available) + } + if self.videoQualityStateValue != videoQualityState { + self.videoQualityStateValue = videoQualityState + self.videoQualityStatePromise.set(.single(videoQualityState)) + } + } + func videoQualityState() -> (current: Int, preferred: UniversalVideoContentVideoQuality, available: [Int])? { if self.playerAvailableLevels.isEmpty { if let qualitySet = HLSQualitySet(baseFile: self.fileReference), let minQualityFile = HLSVideoContent.minimizedHLSQuality(file: self.fileReference)?.file { @@ -1594,6 +1625,16 @@ final class HLSVideoJSNativeContentNode: ASDisplayNode, UniversalVideoContentNod return (min(currentLevel.width, currentLevel.height), self.preferredVideoQuality, available) } + public func videoQualityStateSignal() -> Signal<(current: Int, preferred: UniversalVideoContentVideoQuality, available: [Int])?, NoError> { + return self.videoQualityStatePromise.get() + |> map { value -> (current: Int, preferred: UniversalVideoContentVideoQuality, available: [Int])? in + guard let value else { + return nil + } + return (value.current, value.preferred, value.available) + } + } + func addPlaybackCompleted(_ f: @escaping () -> Void) -> Int { return self.playbackCompletedListeners.add(f) } diff --git a/submodules/TelegramUniversalVideoContent/Sources/NativeVideoContent.swift b/submodules/TelegramUniversalVideoContent/Sources/NativeVideoContent.swift index 4767b433e7..7a157b4635 100644 --- a/submodules/TelegramUniversalVideoContent/Sources/NativeVideoContent.swift +++ b/submodules/TelegramUniversalVideoContent/Sources/NativeVideoContent.swift @@ -667,6 +667,10 @@ private final class NativeVideoContentNode: ASDisplayNode, UniversalVideoContent return nil } + func videoQualityStateSignal() -> Signal<(current: Int, preferred: UniversalVideoContentVideoQuality, available: [Int])?, NoError> { + return .single(nil) + } + func continuePlayingWithoutSound(actionAtEnd: MediaPlayerPlayOnceWithSoundActionAtEnd) { assert(Queue.mainQueue().isCurrent()) let action = { [weak self] in diff --git a/submodules/TelegramUniversalVideoContent/Sources/PlatformVideoContent.swift b/submodules/TelegramUniversalVideoContent/Sources/PlatformVideoContent.swift index 1a2ca5b5a5..2481eb2a85 100644 --- a/submodules/TelegramUniversalVideoContent/Sources/PlatformVideoContent.swift +++ b/submodules/TelegramUniversalVideoContent/Sources/PlatformVideoContent.swift @@ -459,6 +459,10 @@ private final class PlatformVideoContentNode: ASDisplayNode, UniversalVideoConte return nil } + func videoQualityStateSignal() -> Signal<(current: Int, preferred: UniversalVideoContentVideoQuality, available: [Int])?, NoError> { + return .single(nil) + } + func addPlaybackCompleted(_ f: @escaping () -> Void) -> Int { return self.playbackCompletedListeners.add(f) } diff --git a/submodules/TelegramUniversalVideoContent/Sources/SystemVideoContent.swift b/submodules/TelegramUniversalVideoContent/Sources/SystemVideoContent.swift index ea061ae46c..fae099ae4c 100644 --- a/submodules/TelegramUniversalVideoContent/Sources/SystemVideoContent.swift +++ b/submodules/TelegramUniversalVideoContent/Sources/SystemVideoContent.swift @@ -296,6 +296,10 @@ private final class SystemVideoContentNode: ASDisplayNode, UniversalVideoContent return nil } + func videoQualityStateSignal() -> Signal<(current: Int, preferred: UniversalVideoContentVideoQuality, available: [Int])?, NoError> { + return .single(nil) + } + func addPlaybackCompleted(_ f: @escaping () -> Void) -> Int { return self.playbackCompletedListeners.add(f) } diff --git a/submodules/TelegramUniversalVideoContent/Sources/WebEmbedVideoContent.swift b/submodules/TelegramUniversalVideoContent/Sources/WebEmbedVideoContent.swift index 60aa892cb6..ebe1c8b9ac 100644 --- a/submodules/TelegramUniversalVideoContent/Sources/WebEmbedVideoContent.swift +++ b/submodules/TelegramUniversalVideoContent/Sources/WebEmbedVideoContent.swift @@ -194,6 +194,10 @@ final class WebEmbedVideoContentNode: ASDisplayNode, UniversalVideoContentNode { return nil } + func videoQualityStateSignal() -> Signal<(current: Int, preferred: UniversalVideoContentVideoQuality, available: [Int])?, NoError> { + return .single(nil) + } + func addPlaybackCompleted(_ f: @escaping () -> Void) -> Int { return self.playbackCompletedListeners.add(f) }