diff --git a/submodules/GalleryUI/Sources/GalleryController.swift b/submodules/GalleryUI/Sources/GalleryController.swift index 1f1e17ba77..b141da652a 100644 --- a/submodules/GalleryUI/Sources/GalleryController.swift +++ b/submodules/GalleryUI/Sources/GalleryController.swift @@ -1405,8 +1405,15 @@ public class GalleryController: ViewController, StandalonePresentableController, } } - self.galleryNode.completeCustomDismiss = { [weak self] in - self?._hiddenMedia.set(.single(nil)) + self.galleryNode.completeCustomDismiss = { [weak self] isPictureInPicture in + if isPictureInPicture { + if let chatController = self?.baseNavigationController?.topViewController as? ChatController { + chatController.updatePushedTransition(0.0, transition: .animated(duration: 0.45, curve: .customSpring(damping: 180.0, initialVelocity: 0.0))) + } + } else { + self?._hiddenMedia.set(.single(nil)) + } + self?.presentingViewController?.dismiss(animated: false, completion: nil) } diff --git a/submodules/GalleryUI/Sources/GalleryControllerNode.swift b/submodules/GalleryUI/Sources/GalleryControllerNode.swift index c0f1930656..84cccba91c 100644 --- a/submodules/GalleryUI/Sources/GalleryControllerNode.swift +++ b/submodules/GalleryUI/Sources/GalleryControllerNode.swift @@ -25,7 +25,7 @@ open class GalleryControllerNode: ASDisplayNode, ASScrollViewDelegate, ASGesture public var pager: GalleryPagerNode public var beginCustomDismiss: (Bool) -> Void = { _ in } - public var completeCustomDismiss: () -> Void = { } + public var completeCustomDismiss: (Bool) -> Void = { _ in } public var baseNavigationController: () -> NavigationController? = { return nil } public var galleryController: () -> ViewController? = { return nil } @@ -123,9 +123,9 @@ open class GalleryControllerNode: ASDisplayNode, ASScrollViewDelegate, ASGesture } } - self.pager.completeCustomDismiss = { [weak self] in + self.pager.completeCustomDismiss = { [weak self] isPictureInPicture in if let strongSelf = self { - strongSelf.completeCustomDismiss() + strongSelf.completeCustomDismiss(isPictureInPicture) } } diff --git a/submodules/GalleryUI/Sources/GalleryItemNode.swift b/submodules/GalleryUI/Sources/GalleryItemNode.swift index 3cbd05e5a6..222a018862 100644 --- a/submodules/GalleryUI/Sources/GalleryItemNode.swift +++ b/submodules/GalleryUI/Sources/GalleryItemNode.swift @@ -25,7 +25,7 @@ open class GalleryItemNode: ASDisplayNode { public var updateOrientation: (UIInterfaceOrientation) -> Void = { _ in } public var dismiss: () -> Void = { } public var beginCustomDismiss: (Bool) -> Void = { _ in } - public var completeCustomDismiss: () -> Void = { } + public var completeCustomDismiss: (Bool) -> Void = { _ in } public var baseNavigationController: () -> NavigationController? = { return nil } public var galleryController: () -> ViewController? = { return nil } public var alternativeDismiss: () -> Bool = { return false } diff --git a/submodules/GalleryUI/Sources/GalleryPagerNode.swift b/submodules/GalleryUI/Sources/GalleryPagerNode.swift index 1ee1b3af55..59bfe42de1 100644 --- a/submodules/GalleryUI/Sources/GalleryPagerNode.swift +++ b/submodules/GalleryUI/Sources/GalleryPagerNode.swift @@ -110,7 +110,7 @@ public final class GalleryPagerNode: ASDisplayNode, ASScrollViewDelegate, ASGest public var updateOrientation: (UIInterfaceOrientation) -> Void = { _ in } public var dismiss: () -> Void = { } public var beginCustomDismiss: (Bool) -> Void = { _ in } - public var completeCustomDismiss: () -> Void = { } + public var completeCustomDismiss: (Bool) -> Void = { _ in } public var baseNavigationController: () -> NavigationController? = { return nil } public var galleryController: () -> ViewController? = { return nil } diff --git a/submodules/GalleryUI/Sources/Items/ChatImageGalleryItem.swift b/submodules/GalleryUI/Sources/Items/ChatImageGalleryItem.swift index bb911f25ea..3085831148 100644 --- a/submodules/GalleryUI/Sources/Items/ChatImageGalleryItem.swift +++ b/submodules/GalleryUI/Sources/Items/ChatImageGalleryItem.swift @@ -687,7 +687,7 @@ final class ChatImageGalleryItemNode: ZoomableContentGalleryItemNode { context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: context, chatLocation: .peer(peer), subject: .message(id: .id(message.id), highlight: ChatControllerSubject.MessageHighlight(quote: nil), timecode: nil, setupReply: false))) Queue.mainQueue().after(0.3) { - self.completeCustomDismiss() + self.completeCustomDismiss(false) } } f(.default) @@ -719,7 +719,7 @@ final class ChatImageGalleryItemNode: ZoomableContentGalleryItemNode { context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: context, chatLocation: .peer(peer), subject: .message(id: .id(message.id), highlight: ChatControllerSubject.MessageHighlight(quote: nil), timecode: nil, setupReply: true))) Queue.mainQueue().after(0.3) { - self.completeCustomDismiss() + self.completeCustomDismiss(false) } } f(.default) diff --git a/submodules/GalleryUI/Sources/Items/UniversalVideoGalleryItem.swift b/submodules/GalleryUI/Sources/Items/UniversalVideoGalleryItem.swift index 9f95f5e933..a04cc80bc0 100644 --- a/submodules/GalleryUI/Sources/Items/UniversalVideoGalleryItem.swift +++ b/submodules/GalleryUI/Sources/Items/UniversalVideoGalleryItem.swift @@ -804,6 +804,245 @@ private final class PictureInPictureContentImpl: NSObject, PictureInPictureConte } } +@available(iOS 15.0, *) +private final class NativePictureInPictureContentImpl: NSObject, AVPictureInPictureControllerDelegate { + private final class PlaybackDelegate: NSObject, AVPictureInPictureSampleBufferPlaybackDelegate { + private let node: UniversalVideoNode + private var statusDisposable: Disposable? + private var status: MediaPlayerStatus? + weak var pictureInPictureController: AVPictureInPictureController? + + private var previousIsPlaying = false + init(node: UniversalVideoNode) { + self.node = node + + super.init() + + var invalidatedStateOnce = false + self.statusDisposable = (self.node.status + |> deliverOnMainQueue).start(next: { [weak self] status in + guard let strongSelf = self else { + return + } + strongSelf.status = status + if let status { + let isPlaying = status.status == .playing + if !invalidatedStateOnce { + invalidatedStateOnce = true + strongSelf.pictureInPictureController?.invalidatePlaybackState() + } else if strongSelf.previousIsPlaying != isPlaying { + strongSelf.previousIsPlaying = isPlaying + strongSelf.pictureInPictureController?.invalidatePlaybackState() + } + } + }).strict() + } + + deinit { + self.statusDisposable?.dispose() + } + + public func pictureInPictureController(_ pictureInPictureController: AVPictureInPictureController, setPlaying playing: Bool) { + self.node.togglePlayPause() + } + + public func pictureInPictureControllerTimeRangeForPlayback(_ pictureInPictureController: AVPictureInPictureController) -> CMTimeRange { + guard let status = self.status else { + return CMTimeRange(start: CMTime(seconds: 0.0, preferredTimescale: CMTimeScale(30.0)), duration: CMTime(seconds: 0.0, preferredTimescale: CMTimeScale(30.0))) + } + return CMTimeRange(start: CMTime(seconds: 0.0, preferredTimescale: CMTimeScale(30.0)), duration: CMTime(seconds: status.duration - status.timestamp, preferredTimescale: CMTimeScale(30.0))) + } + + public func pictureInPictureControllerIsPlaybackPaused(_ pictureInPictureController: AVPictureInPictureController) -> Bool { + guard let status = self.status else { + return false + } + switch status.status { + case .playing: + return false + case .buffering, .paused: + return true + } + } + + public func pictureInPictureController(_ pictureInPictureController: AVPictureInPictureController, didTransitionToRenderSize newRenderSize: CMVideoDimensions) { + } + + public func pictureInPictureController(_ pictureInPictureController: AVPictureInPictureController, skipByInterval skipInterval: CMTime, completion completionHandler: @escaping () -> Void) { + let node = self.node + let _ = (self.node.status + |> take(1) + |> deliverOnMainQueue).start(next: { [weak node] status in + if let node = node, let timestamp = status?.timestamp, let duration = status?.duration { + let nextTimestamp = timestamp + skipInterval.seconds + if nextTimestamp > duration { + node.seek(0.0) + node.pause() + } else { + node.seek(min(duration, nextTimestamp)) + } + } + + completionHandler() + }) + } + + public func pictureInPictureControllerShouldProhibitBackgroundAudioPlayback(_ pictureInPictureController: AVPictureInPictureController) -> Bool { + return false + } + } + + private let context: AccountContext + private let accountId: AccountRecordId + private let hiddenMedia: (MessageId, Media)? + private weak var mediaManager: MediaManager? + private var pictureInPictureController: AVPictureInPictureController? + private var contentDelegate: PlaybackDelegate? + private let node: UniversalVideoNode + private let willBegin: (NativePictureInPictureContentImpl) -> Void + private let didBegin: (NativePictureInPictureContentImpl) -> Void + private let expand: (@escaping () -> Void) -> Void + private var pictureInPictureTimer: SwiftSignalKit.Timer? + private var didExpand: Bool = false + + private var hiddenMediaManagerIndex: Int? + + private var messageRemovedDisposable: Disposable? + + private var isNativePictureInPictureActiveDisposable: Disposable? + + init(context: AccountContext, mediaManager: MediaManager, accountId: AccountRecordId, hiddenMedia: (MessageId, Media)?, videoNode: UniversalVideoNode, canSkip: Bool, willBegin: @escaping (NativePictureInPictureContentImpl) -> Void, didBegin: @escaping (NativePictureInPictureContentImpl) -> Void, expand: @escaping (@escaping () -> Void) -> Void) { + self.context = context + self.mediaManager = mediaManager + self.accountId = accountId + self.hiddenMedia = hiddenMedia + self.node = videoNode + self.willBegin = willBegin + self.didBegin = didBegin + self.expand = expand + + super.init() + + if let videoLayer = videoNode.getVideoLayer() { + let contentDelegate = PlaybackDelegate(node: self.node) + self.contentDelegate = contentDelegate + + let pictureInPictureController = AVPictureInPictureController(contentSource: AVPictureInPictureController.ContentSource(sampleBufferDisplayLayer: videoLayer, playbackDelegate: contentDelegate)) + self.pictureInPictureController = pictureInPictureController + contentDelegate.pictureInPictureController = pictureInPictureController + + pictureInPictureController.canStartPictureInPictureAutomaticallyFromInline = false + pictureInPictureController.requiresLinearPlayback = !canSkip + pictureInPictureController.delegate = self + self.pictureInPictureController = pictureInPictureController + } + + if let (messageId, _) = hiddenMedia { + self.messageRemovedDisposable = (context.engine.data.subscribe(TelegramEngine.EngineData.Item.Messages.Message(id: messageId)) + |> map { message -> Bool in + if let _ = message { + return false + } else { + return true + } + } + |> filter { $0 } + |> take(1) + |> deliverOnMainQueue).start(next: { [weak self] _ in + guard let strongSelf = self else { + return + } + strongSelf.node.canAttachContent = false + }) + } + } + + deinit { + self.messageRemovedDisposable?.dispose() + self.isNativePictureInPictureActiveDisposable?.dispose() + self.pictureInPictureTimer?.invalidate() + self.node.setCanPlaybackWithoutHierarchy(false) + + if let hiddenMediaManagerIndex = self.hiddenMediaManagerIndex, let mediaManager = self.mediaManager { + mediaManager.galleryHiddenMediaManager.removeSource(hiddenMediaManagerIndex) + } + } + + func updateIsCentral(isCentral: Bool) { + guard let pictureInPictureController = self.pictureInPictureController else { + return + } + + if isCentral { + pictureInPictureController.canStartPictureInPictureAutomaticallyFromInline = true + } else { + pictureInPictureController.canStartPictureInPictureAutomaticallyFromInline = false + } + } + + func beginPictureInPicture() { + guard let pictureInPictureController = self.pictureInPictureController else { + return + } + if pictureInPictureController.isPictureInPicturePossible { + pictureInPictureController.startPictureInPicture() + } + } + + func invalidatePlaybackState() { + self.pictureInPictureController?.invalidatePlaybackState() + } + + public func pictureInPictureControllerWillStartPictureInPicture(_ pictureInPictureController: AVPictureInPictureController) { + self.node.setCanPlaybackWithoutHierarchy(true) + + if let hiddenMedia = self.hiddenMedia, let mediaManager = self.mediaManager { + let accountId = self.accountId + self.hiddenMediaManagerIndex = mediaManager.galleryHiddenMediaManager.addSource(Signal<(MessageId, Media)?, NoError>.single(hiddenMedia) + |> map { messageIdAndMedia in + if let (messageId, media) = messageIdAndMedia { + return .chat(accountId, messageId, media) + } else { + return nil + } + }) + } + + self.willBegin(self) + } + + public func pictureInPictureControllerDidStartPictureInPicture(_ pictureInPictureController: AVPictureInPictureController) { + self.didBegin(self) + } + + public func pictureInPictureController(_ pictureInPictureController: AVPictureInPictureController, failedToStartPictureInPictureWithError error: Error) { + print(error) + } + + public func pictureInPictureControllerWillStopPictureInPicture(_ pictureInPictureController: AVPictureInPictureController) { + } + + public func pictureInPictureControllerDidStopPictureInPicture(_ pictureInPictureController: AVPictureInPictureController) { + self.node.setCanPlaybackWithoutHierarchy(false) + if let hiddenMediaManagerIndex = self.hiddenMediaManagerIndex, let mediaManager = self.mediaManager { + mediaManager.galleryHiddenMediaManager.removeSource(hiddenMediaManagerIndex) + self.hiddenMediaManagerIndex = nil + } + } + + public func pictureInPictureController(_ pictureInPictureController: AVPictureInPictureController, restoreUserInterfaceForPictureInPictureStopWithCompletionHandler completionHandler: @escaping (Bool) -> Void) { + self.expand { [weak self] in + guard let strongSelf = self else { + return + } + + strongSelf.didExpand = true + + completionHandler(true) + } + } +} + final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode { private let context: AccountContext private let presentationData: PresentationData @@ -875,12 +1114,17 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode { private let isShowingContextMenuPromise = ValuePromise(false, ignoreRepeated: true) private let hasExpandedCaptionPromise = Promise() private var hideControlsDisposable: Disposable? + private var automaticPictureInPictureDisposable: Disposable? var playbackCompleted: (() -> Void)? private var customUnembedWhenPortrait: ((OverlayMediaItemNode) -> Bool)? private var pictureInPictureContent: AnyObject? + private var nativePictureInPictureContent: AnyObject? + + private var activePictureInPictureNavigationController: NavigationController? + private var activePictureInPictureController: ViewController? init(context: AccountContext, presentationData: PresentationData, performAction: @escaping (GalleryControllerInteractionTapAction) -> Void, openActionOptions: @escaping (GalleryControllerInteractionTapAction, Message) -> Void, present: @escaping (ViewController, Any?) -> Void) { self.context = context @@ -1064,6 +1308,7 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode { self.mediaPlaybackStateDisposable.dispose() self.scrubbingFrameDisposable?.dispose() self.hideControlsDisposable?.dispose() + self.automaticPictureInPictureDisposable?.dispose() } override func ready() -> Signal { @@ -1259,6 +1504,10 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode { strongSelf.videoNode?.setBaseRate(playbackRate) } } + + if strongSelf.nativePictureInPictureContent == nil { + strongSelf.setupNativePictureInPicture() + } } } self.videoNode = videoNode @@ -1749,6 +1998,12 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode { } } } + + if #available(iOS 15.0, *) { + if let nativePictureInPictureContent = self.nativePictureInPictureContent as? NativePictureInPictureContentImpl { + nativePictureInPictureContent.updateIsCentral(isCentral: isCentral) + } + } } } @@ -2330,13 +2585,79 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode { self.beginCustomDismiss(false) self.statusNode.isHidden = true self.animateOut(toOverlay: overlayNode, completion: { [weak self] in - self?.completeCustomDismiss() + self?.completeCustomDismiss(false) }) } } } + private func setupNativePictureInPicture() { + guard let item = self.item, let videoNode = self.videoNode else { + return + } + + var hiddenMedia: (MessageId, Media)? = nil + switch item.contentInfo { + case let .message(message, _): + for media in message.media { + if let media = media as? TelegramMediaImage { + hiddenMedia = (message.id, media) + } else if let media = media as? TelegramMediaFile, media.isVideo { + hiddenMedia = (message.id, media) + } + } + default: + break + } + + if #available(iOS 15.0, *) { + let content = NativePictureInPictureContentImpl(context: self.context, mediaManager: self.context.sharedContext.mediaManager, accountId: self.context.account.id, hiddenMedia: hiddenMedia, videoNode: videoNode, canSkip: true, willBegin: { [weak self] content in + guard let self, let controller = self.galleryController(), let navigationController = self.baseNavigationController() else { + return + } + self.activePictureInPictureNavigationController = navigationController + self.activePictureInPictureController = controller + + controller.view.alpha = 0.0 + controller.view.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, completion: { [weak self] _ in + self?.completeCustomDismiss(true) + }) + }, didBegin: { [weak self] _ in + guard let self else { + return + } + let _ = self + }, expand: { [weak self] completion in + guard let self, let activePictureInPictureController = self.activePictureInPictureController, let activePictureInPictureNavigationController = self.activePictureInPictureNavigationController else { + completion() + return + } + + self.activePictureInPictureController = nil + self.activePictureInPictureNavigationController = nil + + activePictureInPictureController.presentationArguments = nil + activePictureInPictureNavigationController.currentWindow?.present(activePictureInPictureController, on: .root, blockInteraction: false, completion: { + }) + + activePictureInPictureController.view.alpha = 1.0 + activePictureInPictureController.view.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2, completion: { _ in + completion() + }) + }) + + self.nativePictureInPictureContent = content + } + } + @objc func pictureInPictureButtonPressed() { + if #available(iOS 15.0, *) { + if let nativePictureInPictureContent = self.nativePictureInPictureContent as? NativePictureInPictureContentImpl { + nativePictureInPictureContent.beginPictureInPicture() + return + } + } + var isNativePictureInPictureSupported = false switch self.item?.contentInfo { case let .message(message, _): @@ -2391,7 +2712,7 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode { guard let strongSelf = self else { return } - strongSelf.completeCustomDismiss() + strongSelf.completeCustomDismiss(false) }, expand: { [weak baseNavigationController] completion in guard let contentInfo = item.contentInfo else { return @@ -2524,7 +2845,7 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode { self.beginCustomDismiss(false) self.statusNode.isHidden = true self.animateOut(toOverlay: overlayNode, completion: { [weak self] in - self?.completeCustomDismiss() + self?.completeCustomDismiss(false) }) } } @@ -2847,7 +3168,7 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode { context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: context, chatLocation: .peer(peer), subject: .message(id: .id(message.id), highlight: ChatControllerSubject.MessageHighlight(quote: nil), timecode: nil, setupReply: false))) Queue.mainQueue().after(0.3) { - strongSelf.completeCustomDismiss() + strongSelf.completeCustomDismiss(false) } } f(.default) @@ -2928,7 +3249,7 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode { context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: context, chatLocation: .peer(peer), subject: .message(id: .id(message.id), highlight: ChatControllerSubject.MessageHighlight(quote: nil), timecode: nil, setupReply: true))) Queue.mainQueue().after(0.3) { - self.completeCustomDismiss() + self.completeCustomDismiss(false) } } f(.default) diff --git a/submodules/GalleryUI/Sources/SecretMediaPreviewController.swift b/submodules/GalleryUI/Sources/SecretMediaPreviewController.swift index 0360571fe4..2d5c388ef5 100644 --- a/submodules/GalleryUI/Sources/SecretMediaPreviewController.swift +++ b/submodules/GalleryUI/Sources/SecretMediaPreviewController.swift @@ -300,7 +300,7 @@ public final class SecretMediaPreviewController: ViewController { } } - self.controllerNode.completeCustomDismiss = { [weak self] in + self.controllerNode.completeCustomDismiss = { [weak self] _ in self?._hiddenMedia.set(.single(nil)) self?.presentingViewController?.dismiss(animated: false, completion: nil) } diff --git a/submodules/InstantPageUI/Sources/InstantPageGalleryController.swift b/submodules/InstantPageUI/Sources/InstantPageGalleryController.swift index 2a3040ef38..3735bdd4b8 100644 --- a/submodules/InstantPageUI/Sources/InstantPageGalleryController.swift +++ b/submodules/InstantPageUI/Sources/InstantPageGalleryController.swift @@ -398,7 +398,7 @@ public class InstantPageGalleryController: ViewController, StandalonePresentable self?.presentingViewController?.dismiss(animated: false, completion: nil) } - self.galleryNode.completeCustomDismiss = { [weak self] in + self.galleryNode.completeCustomDismiss = { [weak self] _ in self?._hiddenMedia.set(.single(nil)) self?.presentingViewController?.dismiss(animated: false, completion: nil) } diff --git a/submodules/TelegramCallsUI/Sources/VideoChatActionButtonComponent.swift b/submodules/TelegramCallsUI/Sources/VideoChatActionButtonComponent.swift index 656494c344..f89a6bda96 100644 --- a/submodules/TelegramCallsUI/Sources/VideoChatActionButtonComponent.swift +++ b/submodules/TelegramCallsUI/Sources/VideoChatActionButtonComponent.swift @@ -160,7 +160,7 @@ final class VideoChatActionButtonComponent: Component { case .unmuted: backgroundColor = !isActive ? UIColor(rgb: 0x124B21) : UIColor(rgb: 0x34C659) case .raiseHand, .scheduled: - backgroundColor = UIColor(rgb: 0x3252EF) + backgroundColor = !isActive ? UIColor(rgb: 0x23306B) : UIColor(rgb: 0x3252EF) } iconDiameter = 60.0 case let .video(isActive): @@ -282,6 +282,7 @@ final class VideoChatActionButtonComponent: Component { } self.isEnabled = isEnabled + self.isUserInteractionEnabled = isEnabled return size } diff --git a/submodules/TelegramCallsUI/Sources/VideoChatScreen.swift b/submodules/TelegramCallsUI/Sources/VideoChatScreen.swift index 35c8beec52..a6a0564e8c 100644 --- a/submodules/TelegramCallsUI/Sources/VideoChatScreen.swift +++ b/submodules/TelegramCallsUI/Sources/VideoChatScreen.swift @@ -1022,7 +1022,7 @@ final class VideoChatScreenComponent: Component { if case let .channel(channel) = self.peer, case .broadcast = channel.info { displayEvent = false } - if members.totalCount < 250 { + if members.totalCount < 40 { displayEvent = true } else if event.peer.isVerified { displayEvent = true diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift index 8a36726273..ed93377cda 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift @@ -6203,17 +6203,13 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI return } + let effectiveMediaVisibility = self.visibility + var isPlaying = true - if case let .visible(_, subRect) = self.visibility { - if subRect.minY > 32.0 { - isPlaying = false - } - } else { - isPlaying = false - } if !item.controllerInteraction.canReadHistory { isPlaying = false } + if self.forceStopAnimations { isPlaying = false } @@ -6228,7 +6224,19 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI } for contentNode in self.contentNodes { - contentNode.visibility = mapVisibility(effectiveVisibility, boundsSize: self.bounds.size, insets: self.insets, to: contentNode) + if contentNode is ChatMessageMediaBubbleContentNode { + contentNode.visibility = mapVisibility(effectiveMediaVisibility, boundsSize: self.bounds.size, insets: self.insets, to: contentNode) + } else { + contentNode.visibility = mapVisibility(effectiveVisibility, boundsSize: self.bounds.size, insets: self.insets, to: contentNode) + } + } + + if case let .visible(_, subRect) = self.visibility { + if subRect.minY > 32.0 { + isPlaying = false + } + } else { + isPlaying = false } if let threadInfoNode = self.threadInfoNode { diff --git a/submodules/TelegramUniversalVideoContent/Sources/HLSVideoContent.swift b/submodules/TelegramUniversalVideoContent/Sources/HLSVideoContent.swift index 77b46d4398..de44e0b2c6 100644 --- a/submodules/TelegramUniversalVideoContent/Sources/HLSVideoContent.swift +++ b/submodules/TelegramUniversalVideoContent/Sources/HLSVideoContent.swift @@ -25,9 +25,22 @@ public final class HLSQualitySet { if let alternativeFile = alternativeRepresentation as? TelegramMediaFile { for attribute in alternativeFile.attributes { if case let .Video(_, size, _, _, _, videoCodec) = attribute { - let _ = size if let videoCodec, NativeVideoContent.isVideoCodecSupported(videoCodec: videoCodec) { - qualityFiles[Int(size.height)] = baseFile.withMedia(alternativeFile) + let key = Int(size.height) + if let currentFile = qualityFiles[key] { + var currentCodec: String? + for attribute in currentFile.media.attributes { + if case let .Video(_, _, _, _, _, videoCodec) = attribute { + currentCodec = videoCodec + } + } + if let currentCodec, currentCodec == "av1" { + } else { + qualityFiles[key] = baseFile.withMedia(alternativeFile) + } + } else { + qualityFiles[key] = baseFile.withMedia(alternativeFile) + } } } } diff --git a/submodules/TelegramUniversalVideoContent/Sources/NativeVideoContent.swift b/submodules/TelegramUniversalVideoContent/Sources/NativeVideoContent.swift index 07909d446f..f8e38da318 100644 --- a/submodules/TelegramUniversalVideoContent/Sources/NativeVideoContent.swift +++ b/submodules/TelegramUniversalVideoContent/Sources/NativeVideoContent.swift @@ -11,6 +11,7 @@ import AccountContext import PhotoResources import UIKitRuntimeUtils import RangeSet +import VideoToolbox private extension CGRect { var center: CGPoint { @@ -25,6 +26,11 @@ public enum NativeVideoContentId: Hashable { case profileVideo(Int64, String?) } +private let isAv1Supported: Bool = { + let value = VTIsHardwareDecodeSupported(kCMVideoCodecType_AV1) + return value +}() + public final class NativeVideoContent: UniversalVideoContent { public let id: AnyHashable public let nativeId: NativeVideoContentId @@ -58,7 +64,17 @@ public final class NativeVideoContent: UniversalVideoContent { let hasSentFramesToDisplay: (() -> Void)? public static func isVideoCodecSupported(videoCodec: String) -> Bool { - return videoCodec == "h264" || videoCodec == "h265" || videoCodec == "avc" || videoCodec == "hevc" + if videoCodec == "h264" || videoCodec == "h265" || videoCodec == "avc" || videoCodec == "hevc" { + return true + } + + if videoCodec == "av1" { + if isAv1Supported { + return true + } + } + + return false } public static func isHLSVideo(file: TelegramMediaFile) -> Bool {