mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
HLS video improvements
This commit is contained in:
parent
9f11ee1c5a
commit
9e1dc11997
@ -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)
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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 }
|
||||
|
@ -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 }
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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<Bool>(false, ignoreRepeated: true)
|
||||
private let hasExpandedCaptionPromise = Promise<Bool>()
|
||||
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<Void, NoError> {
|
||||
@ -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)
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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 {
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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 {
|
||||
|
Loading…
x
Reference in New Issue
Block a user