diff --git a/submodules/AccountContext/Sources/MediaManager.swift b/submodules/AccountContext/Sources/MediaManager.swift index a0e4ecc34a..4c8c0fd8e4 100644 --- a/submodules/AccountContext/Sources/MediaManager.swift +++ b/submodules/AccountContext/Sources/MediaManager.swift @@ -146,7 +146,7 @@ public protocol MediaManager: AnyObject { var musicMediaPlayerState: Signal<(Account, SharedMediaPlayerItemPlaybackStateOrLoading, MediaManagerPlayerType)?, NoError> { get } var activeGlobalMediaPlayerAccountId: Signal<(AccountRecordId, Bool)?, NoError> { get } - func setPlaylist(_ playlist: (Account, SharedMediaPlaylist)?, type: MediaManagerPlayerType, control: SharedMediaPlayerControlAction) + func setPlaylist(_ playlist: (AccountContext, SharedMediaPlaylist)?, type: MediaManagerPlayerType, control: SharedMediaPlayerControlAction) func playlistControl(_ control: SharedMediaPlayerControlAction, type: MediaManagerPlayerType?) func filteredPlaylistState(accountId: AccountRecordId, playlistId: SharedMediaPlaylistId, itemId: SharedMediaPlaylistItemId, type: MediaManagerPlayerType) -> Signal diff --git a/submodules/AccountContext/Sources/UniversalVideoNode.swift b/submodules/AccountContext/Sources/UniversalVideoNode.swift index 80273b5756..6e98693b7b 100644 --- a/submodules/AccountContext/Sources/UniversalVideoNode.swift +++ b/submodules/AccountContext/Sources/UniversalVideoNode.swift @@ -53,7 +53,7 @@ public protocol UniversalVideoContent { var dimensions: CGSize { get } var duration: Double { get } - func makeContentNode(accountId: AccountRecordId, postbox: Postbox, audioSession: ManagedAudioSession) -> UniversalVideoContentNode & ASDisplayNode + func makeContentNode(context: AccountContext, postbox: Postbox, audioSession: ManagedAudioSession) -> UniversalVideoContentNode & ASDisplayNode func isEqual(to other: UniversalVideoContent) -> Bool } @@ -95,7 +95,7 @@ public enum UniversalVideoNodeFetchControl { } public final class UniversalVideoNode: ASDisplayNode { - private let accountId: AccountRecordId + private let context: AccountContext private let postbox: Postbox private let audioSession: ManagedAudioSession private let manager: UniversalVideoManager @@ -146,12 +146,12 @@ public final class UniversalVideoNode: ASDisplayNode { if self.canAttachContent { assert(self.contentRequestIndex == nil) - let accountId = self.accountId + let context = self.context let content = self.content let postbox = self.postbox let audioSession = self.audioSession self.contentRequestIndex = self.manager.attachUniversalVideoContent(content: self.content, priority: self.priority, create: { - return content.makeContentNode(accountId: accountId, postbox: postbox, audioSession: audioSession) + return content.makeContentNode(context: context, postbox: postbox, audioSession: audioSession) }, update: { [weak self] contentNodeAndFlags in if let strongSelf = self { strongSelf.updateContentNode(contentNodeAndFlags) @@ -172,8 +172,8 @@ public final class UniversalVideoNode: ASDisplayNode { return self.contentNode != nil } - public init(accountId: AccountRecordId, postbox: Postbox, audioSession: ManagedAudioSession, manager: UniversalVideoManager, decoration: UniversalVideoDecoration, content: UniversalVideoContent, priority: UniversalVideoPriority, autoplay: Bool = false, snapshotContentWhenGone: Bool = false) { - self.accountId = accountId + public init(context: AccountContext, postbox: Postbox, audioSession: ManagedAudioSession, manager: UniversalVideoManager, decoration: UniversalVideoDecoration, content: UniversalVideoContent, priority: UniversalVideoPriority, autoplay: Bool = false, snapshotContentWhenGone: Bool = false) { + self.context = context self.postbox = postbox self.audioSession = audioSession self.manager = manager diff --git a/submodules/AvatarVideoNode/Sources/AvatarVideoNode.swift b/submodules/AvatarVideoNode/Sources/AvatarVideoNode.swift index ad45b10348..425a5f2b75 100644 --- a/submodules/AvatarVideoNode/Sources/AvatarVideoNode.swift +++ b/submodules/AvatarVideoNode/Sources/AvatarVideoNode.swift @@ -234,7 +234,7 @@ public final class AvatarVideoNode: ASDisplayNode { if self.videoNode == nil { let context = self.context let mediaManager = context.sharedContext.mediaManager - let videoNode = UniversalVideoNode(accountId: context.account.id, postbox: context.account.postbox, audioSession: mediaManager.audioSession, manager: mediaManager.universalVideoManager, decoration: VideoDecoration(), content: videoContent, priority: .embedded) + let videoNode = UniversalVideoNode(context: context, postbox: context.account.postbox, audioSession: mediaManager.audioSession, manager: mediaManager.universalVideoManager, decoration: VideoDecoration(), content: videoContent, priority: .embedded) videoNode.clipsToBounds = true videoNode.isUserInteractionEnabled = false videoNode.isHidden = true diff --git a/submodules/BrowserUI/Sources/BrowserInstantPageContent.swift b/submodules/BrowserUI/Sources/BrowserInstantPageContent.swift index f00b84aedb..918d09dfd0 100644 --- a/submodules/BrowserUI/Sources/BrowserInstantPageContent.swift +++ b/submodules/BrowserUI/Sources/BrowserInstantPageContent.swift @@ -1076,7 +1076,7 @@ final class BrowserInstantPageContent: UIView, BrowserContent, UIScrollViewDeleg } } } - self.context.sharedContext.mediaManager.setPlaylist((self.context.account, InstantPageMediaPlaylist(webPage: webPage, items: medias, initialItemIndex: initialIndex)), type: file.isVoice ? .voice : .music, control: .playback(.play)) + self.context.sharedContext.mediaManager.setPlaylist((self.context, InstantPageMediaPlaylist(webPage: webPage, items: medias, initialItemIndex: initialIndex)), type: file.isVoice ? .voice : .music, control: .playback(.play)) return } diff --git a/submodules/ChatImportUI/Sources/ChatImportActivityScreen.swift b/submodules/ChatImportUI/Sources/ChatImportActivityScreen.swift index 9634edd0a8..7919d85b1e 100644 --- a/submodules/ChatImportUI/Sources/ChatImportActivityScreen.swift +++ b/submodules/ChatImportUI/Sources/ChatImportActivityScreen.swift @@ -464,7 +464,7 @@ public final class ChatImportActivityScreen: ViewController { let videoContent = NativeVideoContent(id: .message(1, EngineMedia.Id(namespace: 0, id: 1)), userLocation: .other, fileReference: .standalone(media: dummyFile), streamVideo: .none, loopVideo: true, enableSound: false, fetchAutomatically: true, onlyFullSizeThumbnail: false, continuePlayingWithoutSoundOnLostAudioSession: false, placeholderColor: .black, storeAfterDownload: nil) - let videoNode = UniversalVideoNode(accountId: context.account.id, postbox: context.account.postbox, audioSession: context.sharedContext.mediaManager.audioSession, manager: context.sharedContext.mediaManager.universalVideoManager, decoration: decoration, content: videoContent, priority: .embedded) + let videoNode = UniversalVideoNode(context: context, postbox: context.account.postbox, audioSession: context.sharedContext.mediaManager.audioSession, manager: context.sharedContext.mediaManager.universalVideoManager, decoration: decoration, content: videoContent, priority: .embedded) videoNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: 2.0, height: 2.0)) videoNode.alpha = 0.01 self.videoNode = videoNode diff --git a/submodules/DebugSettingsUI/Sources/DebugController.swift b/submodules/DebugSettingsUI/Sources/DebugController.swift index 2650b1ca4f..e1547a1427 100644 --- a/submodules/DebugSettingsUI/Sources/DebugController.swift +++ b/submodules/DebugSettingsUI/Sources/DebugController.swift @@ -106,6 +106,7 @@ private enum DebugControllerEntry: ItemListNodeEntry { case disableCallV2(Bool) case experimentalCallMute(Bool) case conferenceCalls(Bool) + case playerV2(Bool) case benchmarkReflectors case enableLocalTranslation(Bool) case preferredVideoCodec(Int, String, String?, Bool) @@ -132,7 +133,7 @@ private enum DebugControllerEntry: ItemListNodeEntry { return DebugControllerSection.web.rawValue case .keepChatNavigationStack, .skipReadHistory, .dustEffect, .crashOnSlowQueries, .crashOnMemoryPressure: return DebugControllerSection.experiments.rawValue - case .clearTips, .resetNotifications, .crash, .fillLocalSavedMessageCache, .resetDatabase, .resetDatabaseAndCache, .resetHoles, .resetTagHoles, .reindexUnread, .resetCacheIndex, .reindexCache, .resetBiometricsData, .optimizeDatabase, .photoPreview, .knockoutWallpaper, .storiesExperiment, .storiesJpegExperiment, .playlistPlayback, .enableQuickReactionSwitch, .experimentalCompatibility, .enableDebugDataDisplay, .rippleEffect, .browserExperiment, .localTranscription, .enableReactionOverrides, .restorePurchases, .disableReloginTokens, .disableCallV2, .experimentalCallMute, .conferenceCalls, .benchmarkReflectors, .enableLocalTranslation: + case .clearTips, .resetNotifications, .crash, .fillLocalSavedMessageCache, .resetDatabase, .resetDatabaseAndCache, .resetHoles, .resetTagHoles, .reindexUnread, .resetCacheIndex, .reindexCache, .resetBiometricsData, .optimizeDatabase, .photoPreview, .knockoutWallpaper, .storiesExperiment, .storiesJpegExperiment, .playlistPlayback, .enableQuickReactionSwitch, .experimentalCompatibility, .enableDebugDataDisplay, .rippleEffect, .browserExperiment, .localTranscription, .enableReactionOverrides, .restorePurchases, .disableReloginTokens, .disableCallV2, .experimentalCallMute, .conferenceCalls, .playerV2, .benchmarkReflectors, .enableLocalTranslation: return DebugControllerSection.experiments.rawValue case .logTranslationRecognition, .resetTranslationStates: return DebugControllerSection.translation.rawValue @@ -251,12 +252,14 @@ private enum DebugControllerEntry: ItemListNodeEntry { return 52 case .conferenceCalls: return 53 - case .benchmarkReflectors: + case .playerV2: return 54 - case .enableLocalTranslation: + case .benchmarkReflectors: return 55 + case .enableLocalTranslation: + return 56 case let .preferredVideoCodec(index, _, _, _): - return 56 + index + return 57 + index case .disableVideoAspectScaling: return 100 case .enableNetworkFramework: @@ -1355,6 +1358,16 @@ private enum DebugControllerEntry: ItemListNodeEntry { }) }).start() }) + case let .playerV2(value): + return ItemListSwitchItem(presentationData: presentationData, title: "PlayerV2", value: value, sectionId: self.section, style: .blocks, updated: { value in + let _ = arguments.sharedContext.accountManager.transaction ({ transaction in + transaction.updateSharedData(ApplicationSpecificSharedDataKeys.experimentalUISettings, { settings in + var settings = settings?.get(ExperimentalUISettings.self) ?? ExperimentalUISettings.defaultSettings + settings.playerV2 = value + return PreferencesEntry(settings) + }) + }).start() + }) case .benchmarkReflectors: return ItemListActionItem(presentationData: presentationData, title: "Benchmark Reflectors", kind: .generic, alignment: .natural, sectionId: self.section, style: .blocks, action: { guard let context = arguments.context else { @@ -1578,6 +1591,7 @@ private func debugControllerEntries(sharedContext: SharedAccountContext, present entries.append(.experimentalCallMute(experimentalSettings.experimentalCallMute)) entries.append(.conferenceCalls(experimentalSettings.conferenceCalls)) + entries.append(.playerV2(experimentalSettings.playerV2)) entries.append(.benchmarkReflectors) entries.append(.enableLocalTranslation(experimentalSettings.enableLocalTranslation)) diff --git a/submodules/DrawingUI/Sources/DrawingStickerEntityView.swift b/submodules/DrawingUI/Sources/DrawingStickerEntityView.swift index e73f00da42..99f3629b04 100644 --- a/submodules/DrawingUI/Sources/DrawingStickerEntityView.swift +++ b/submodules/DrawingUI/Sources/DrawingStickerEntityView.swift @@ -330,7 +330,7 @@ public class DrawingStickerEntityView: DrawingEntityView { private func setupWithVideo(_ file: TelegramMediaFile) { let videoNode = UniversalVideoNode( - accountId: self.context.account.id, + context: self.context, postbox: self.context.account.postbox, audioSession: self.context.sharedContext.mediaManager.audioSession, manager: self.context.sharedContext.mediaManager.universalVideoManager, diff --git a/submodules/GalleryUI/Sources/Items/UniversalVideoGalleryItem.swift b/submodules/GalleryUI/Sources/Items/UniversalVideoGalleryItem.swift index 40c8ed7a7e..47da141849 100644 --- a/submodules/GalleryUI/Sources/Items/UniversalVideoGalleryItem.swift +++ b/submodules/GalleryUI/Sources/Items/UniversalVideoGalleryItem.swift @@ -1762,7 +1762,7 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode { let mediaManager = item.context.sharedContext.mediaManager - let videoNode = UniversalVideoNode(accountId: item.context.account.id, postbox: item.context.account.postbox, audioSession: mediaManager.audioSession, manager: mediaManager.universalVideoManager, decoration: GalleryVideoDecoration(), content: item.content, priority: .gallery) + let videoNode = UniversalVideoNode(context: item.context, postbox: item.context.account.postbox, audioSession: mediaManager.audioSession, manager: mediaManager.universalVideoManager, decoration: GalleryVideoDecoration(), content: item.content, priority: .gallery) let videoScale: CGFloat if item.content is WebEmbedVideoContent { videoScale = 1.0 @@ -2849,7 +2849,7 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode { let baseNavigationController = self.baseNavigationController() let mediaManager = self.context.sharedContext.mediaManager var expandImpl: (() -> Void)? - let overlayNode = OverlayUniversalVideoNode(accountId: self.context.account.id, postbox: self.context.account.postbox, audioSession: context.sharedContext.mediaManager.audioSession, manager: context.sharedContext.mediaManager.universalVideoManager, content: item.content, expand: { + let overlayNode = OverlayUniversalVideoNode(context: self.context, postbox: self.context.account.postbox, audioSession: context.sharedContext.mediaManager.audioSession, manager: context.sharedContext.mediaManager.universalVideoManager, content: item.content, expand: { expandImpl?() }, close: { [weak mediaManager] in mediaManager?.setOverlayVideoNode(nil) @@ -3073,7 +3073,7 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode { self.disablePictureInPicturePlaceholder = true - let overlayVideoNode = UniversalVideoNode(accountId: self.context.account.id, postbox: self.context.account.postbox, audioSession: self.context.sharedContext.mediaManager.audioSession, manager: self.context.sharedContext.mediaManager.universalVideoManager, decoration: GalleryVideoDecoration(), content: item.content, priority: .overlay) + let overlayVideoNode = UniversalVideoNode(context: self.context, postbox: self.context.account.postbox, audioSession: self.context.sharedContext.mediaManager.audioSession, manager: self.context.sharedContext.mediaManager.universalVideoManager, decoration: GalleryVideoDecoration(), content: item.content, priority: .overlay) let absoluteRect = videoNode.view.convert(videoNode.view.bounds, to: nil) overlayVideoNode.frame = absoluteRect overlayVideoNode.updateLayout(size: absoluteRect.size, transition: .immediate) @@ -3156,7 +3156,7 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode { shouldBeDismissed = .single(false) } - let overlayNode = OverlayUniversalVideoNode(accountId: self.context.account.id, postbox: self.context.account.postbox, audioSession: context.sharedContext.mediaManager.audioSession, manager: context.sharedContext.mediaManager.universalVideoManager, content: item.content, shouldBeDismissed: shouldBeDismissed, expand: { + let overlayNode = OverlayUniversalVideoNode(context: self.context, postbox: self.context.account.postbox, audioSession: context.sharedContext.mediaManager.audioSession, manager: context.sharedContext.mediaManager.universalVideoManager, content: item.content, shouldBeDismissed: shouldBeDismissed, expand: { expandImpl?() }, close: { [weak mediaManager] in mediaManager?.setOverlayVideoNode(nil) diff --git a/submodules/InstantPageUI/Sources/InstantPageControllerNode.swift b/submodules/InstantPageUI/Sources/InstantPageControllerNode.swift index 92ea483bbb..54846e2e14 100644 --- a/submodules/InstantPageUI/Sources/InstantPageControllerNode.swift +++ b/submodules/InstantPageUI/Sources/InstantPageControllerNode.swift @@ -1449,7 +1449,7 @@ final class InstantPageControllerNode: ASDisplayNode, ASScrollViewDelegate { } } } - self.context.sharedContext.mediaManager.setPlaylist((self.context.account, InstantPageMediaPlaylist(webPage: webPage, items: medias, initialItemIndex: initialIndex)), type: file.isVoice ? .voice : .music, control: .playback(.play)) + self.context.sharedContext.mediaManager.setPlaylist((self.context, InstantPageMediaPlaylist(webPage: webPage, items: medias, initialItemIndex: initialIndex)), type: file.isVoice ? .voice : .music, control: .playback(.play)) return } diff --git a/submodules/InstantPageUI/Sources/InstantPagePlayableVideoNode.swift b/submodules/InstantPageUI/Sources/InstantPagePlayableVideoNode.swift index cdbc22dc5f..12efc18acd 100644 --- a/submodules/InstantPageUI/Sources/InstantPagePlayableVideoNode.swift +++ b/submodules/InstantPageUI/Sources/InstantPagePlayableVideoNode.swift @@ -58,7 +58,7 @@ final class InstantPagePlayableVideoNode: ASDisplayNode, InstantPageNode, Galler fileValue = file } - self.videoNode = UniversalVideoNode(accountId: context.account.id, postbox: context.account.postbox, audioSession: context.sharedContext.mediaManager.audioSession, manager: context.sharedContext.mediaManager.universalVideoManager, decoration: GalleryVideoDecoration(), content: NativeVideoContent(id: .instantPage(webPage.webpageId, media.media.id!), userLocation: userLocation, fileReference: .webPage(webPage: WebpageReference(webPage), media: fileValue!), imageReference: imageReference, streamVideo: streamVideo ? .conservative : .none, loopVideo: true, enableSound: false, fetchAutomatically: true, placeholderColor: theme.pageBackgroundColor, storeAfterDownload: nil), priority: .embedded, autoplay: true) + self.videoNode = UniversalVideoNode(context: context, postbox: context.account.postbox, audioSession: context.sharedContext.mediaManager.audioSession, manager: context.sharedContext.mediaManager.universalVideoManager, decoration: GalleryVideoDecoration(), content: NativeVideoContent(id: .instantPage(webPage.webpageId, media.media.id!), userLocation: userLocation, fileReference: .webPage(webPage: WebpageReference(webPage), media: fileValue!), imageReference: imageReference, streamVideo: streamVideo ? .conservative : .none, loopVideo: true, enableSound: false, fetchAutomatically: true, placeholderColor: theme.pageBackgroundColor, storeAfterDownload: nil), priority: .embedded, autoplay: true) self.videoNode.isUserInteractionEnabled = false self.statusNode = RadialStatusNode(backgroundNodeColor: UIColor(white: 0.0, alpha: 0.6)) diff --git a/submodules/MediaPlayer/Sources/ChunkMediaPlayer.swift b/submodules/MediaPlayer/Sources/ChunkMediaPlayer.swift index 70e2969fef..68bf351ac8 100644 --- a/submodules/MediaPlayer/Sources/ChunkMediaPlayer.swift +++ b/submodules/MediaPlayer/Sources/ChunkMediaPlayer.swift @@ -80,12 +80,39 @@ public final class ChunkMediaPlayerPart { } public final class ChunkMediaPlayerPartsState { - public let duration: Double? - public let parts: [ChunkMediaPlayerPart] + public final class DirectReader { + public final class Impl { + public let video: MediaDataReader? + public let audio: MediaDataReader? + + public init(video: MediaDataReader?, audio: MediaDataReader?) { + self.video = video + self.audio = audio + } + } + + public let seekPosition: Double + public let availableUntilPosition: Double + public let impl: QueueLocalObject + + public init(seekPosition: Double, availableUntilPosition: Double, impl: QueueLocalObject) { + self.seekPosition = seekPosition + self.availableUntilPosition = availableUntilPosition + self.impl = impl + } + } - public init(duration: Double?, parts: [ChunkMediaPlayerPart]) { + public enum Content { + case parts([ChunkMediaPlayerPart]) + case directReader(DirectReader) + } + + public let duration: Double? + public let content: Content + + public init(duration: Double?, content: Content) { self.duration = duration - self.parts = parts + self.content = content } } diff --git a/submodules/MediaPlayer/Sources/ChunkMediaPlayerDirectFetchSourceImpl.swift b/submodules/MediaPlayer/Sources/ChunkMediaPlayerDirectFetchSourceImpl.swift index 104868c3ad..9cb579b44b 100644 --- a/submodules/MediaPlayer/Sources/ChunkMediaPlayerDirectFetchSourceImpl.swift +++ b/submodules/MediaPlayer/Sources/ChunkMediaPlayerDirectFetchSourceImpl.swift @@ -393,6 +393,7 @@ private func extractFFMpegFrameSegmentInfo(path: String) -> FFMpegFrameSegmentIn } final class ChunkMediaPlayerDirectFetchSourceImpl: ChunkMediaPlayerSourceImpl { + private let dataQueue: Queue private let resource: ChunkMediaPlayerV2.SourceDescription.ResourceDescription private let partsStateValue = Promise() @@ -403,7 +404,8 @@ final class ChunkMediaPlayerDirectFetchSourceImpl: ChunkMediaPlayerSourceImpl { private var completeFetchDisposable: Disposable? private var dataDisposable: Disposable? - init(resource: ChunkMediaPlayerV2.SourceDescription.ResourceDescription) { + init(dataQueue: Queue, resource: ChunkMediaPlayerV2.SourceDescription.ResourceDescription) { + self.dataQueue = dataQueue self.resource = resource if resource.fetchAutomatically { @@ -458,18 +460,18 @@ final class ChunkMediaPlayerDirectFetchSourceImpl: ChunkMediaPlayerSourceImpl { self.partsStateValue.set(.single(ChunkMediaPlayerPartsState( duration: mainTrack.duration.seconds, - parts: parts + content: .parts(parts) ))) } else { self.partsStateValue.set(.single(ChunkMediaPlayerPartsState( duration: nil, - parts: [] + content: .parts([]) ))) } } else { self.partsStateValue.set(.single(ChunkMediaPlayerPartsState( duration: nil, - parts: [] + content: .parts([]) ))) } }) diff --git a/submodules/MediaPlayer/Sources/ChunkMediaPlayerV2.swift b/submodules/MediaPlayer/Sources/ChunkMediaPlayerV2.swift index 1eb9b58a6a..3e1d8bfe9f 100644 --- a/submodules/MediaPlayer/Sources/ChunkMediaPlayerV2.swift +++ b/submodules/MediaPlayer/Sources/ChunkMediaPlayerV2.swift @@ -132,7 +132,7 @@ public final class ChunkMediaPlayerV2: ChunkMediaPlayer { private var videoRenderer: AVSampleBufferDisplayLayer private var audioRenderer: AVSampleBufferAudioRenderer? - private var partsState = ChunkMediaPlayerPartsState(duration: nil, parts: []) + private var partsState = ChunkMediaPlayerPartsState(duration: nil, content: .parts([])) private var loadedParts: [LoadedPart] = [] private var loadedPartsMediaData: QueueLocalObject private var hasSound: Bool = false @@ -161,6 +161,7 @@ public final class ChunkMediaPlayerV2: ChunkMediaPlayer { private var isMuted: Bool private var seekId: Int = 0 + private var seekTimestamp: Double = 0.0 private var pendingSeekTimestamp: Double? private var pendingContinuePlaybackAfterSeekToTimestamp: Double? private var shouldNotifySeeked: Bool = false @@ -219,7 +220,7 @@ public final class ChunkMediaPlayerV2: ChunkMediaPlayer { case let .externalParts(partsState): self.source = ChunkMediaPlayerExternalSourceImpl(partsState: partsState) case let .directFetch(resource): - self.source = ChunkMediaPlayerDirectFetchSourceImpl(resource: resource) + self.source = ChunkMediaPlayerDirectFetchSourceImpl(dataQueue: self.dataQueue, resource: resource) } self.updateTimer = Foundation.Timer.scheduledTimer(withTimeInterval: 1.0 / 60.0, repeats: true, block: { [weak self] _ in @@ -347,7 +348,6 @@ public final class ChunkMediaPlayerV2: ChunkMediaPlayer { } var validParts: [ChunkMediaPlayerPart] = [] - var minStartTime: Double = 0.0 for i in 0 ..< self.partsState.parts.count { let part = self.partsState.parts[i] @@ -672,6 +672,7 @@ public final class ChunkMediaPlayerV2: ChunkMediaPlayer { private func seek(timestamp: Double, play: Bool?, notify: Bool) { self.seekId += 1 + self.seekTimestamp = timestamp let seekId = self.seekId self.pendingSeekTimestamp = timestamp self.pendingContinuePlaybackAfterSeekToTimestamp = timestamp diff --git a/submodules/PeerAvatarGalleryUI/Sources/PeerAvatarImageGalleryItem.swift b/submodules/PeerAvatarGalleryUI/Sources/PeerAvatarImageGalleryItem.swift index 759746fba5..de69497b10 100644 --- a/submodules/PeerAvatarGalleryUI/Sources/PeerAvatarImageGalleryItem.swift +++ b/submodules/PeerAvatarGalleryUI/Sources/PeerAvatarImageGalleryItem.swift @@ -281,7 +281,7 @@ final class PeerAvatarImageGalleryItemNode: ZoomableContentGalleryItemNode { let mediaManager = self.context.sharedContext.mediaManager let videoFileReference = FileMediaReference.avatarList(peer: peerReference, media: TelegramMediaFile(fileId: EngineMedia.Id(namespace: Namespaces.Media.LocalFile, id: 0), partialReference: nil, resource: video.representation.resource, previewRepresentations: representations.map { $0.representation }, videoThumbnails: [], immediateThumbnailData: entry.immediateThumbnailData, mimeType: "video/mp4", size: nil, attributes: [.Animated, .Video(duration: 0, size: video.representation.dimensions, flags: [], preloadSize: nil, coverTime: nil, videoCodec: nil)], alternativeRepresentations: [])) let videoContent = NativeVideoContent(id: .profileVideo(id, category), userLocation: .other, fileReference: videoFileReference, streamVideo: isMediaStreamable(resource: video.representation.resource) ? .conservative : .none, loopVideo: true, enableSound: false, fetchAutomatically: true, onlyFullSizeThumbnail: true, useLargeThumbnail: true, continuePlayingWithoutSoundOnLostAudioSession: false, placeholderColor: .clear, storeAfterDownload: nil) - let videoNode = UniversalVideoNode(accountId: self.context.account.id, postbox: self.context.account.postbox, audioSession: mediaManager.audioSession, manager: mediaManager.universalVideoManager, decoration: GalleryVideoDecoration(), content: videoContent, priority: .overlay) + let videoNode = UniversalVideoNode(context: self.context, postbox: self.context.account.postbox, audioSession: mediaManager.audioSession, manager: mediaManager.universalVideoManager, decoration: GalleryVideoDecoration(), content: videoContent, priority: .overlay) videoNode.isUserInteractionEnabled = false videoNode.isHidden = true self.videoStartTimestamp = video.representation.startTimestamp diff --git a/submodules/PeerInfoAvatarListNode/Sources/PeerInfoAvatarListNode.swift b/submodules/PeerInfoAvatarListNode/Sources/PeerInfoAvatarListNode.swift index dfb059861b..34b97983cf 100644 --- a/submodules/PeerInfoAvatarListNode/Sources/PeerInfoAvatarListNode.swift +++ b/submodules/PeerInfoAvatarListNode/Sources/PeerInfoAvatarListNode.swift @@ -366,7 +366,7 @@ public final class PeerInfoAvatarListItemNode: ASDisplayNode { } let mediaManager = self.context.sharedContext.mediaManager - let videoNode = UniversalVideoNode(accountId: self.context.account.id, postbox: self.context.account.postbox, audioSession: mediaManager.audioSession, manager: mediaManager.universalVideoManager, decoration: GalleryVideoDecoration(), content: videoContent, priority: .secondaryOverlay) + let videoNode = UniversalVideoNode(context: self.context, postbox: self.context.account.postbox, audioSession: mediaManager.audioSession, manager: mediaManager.universalVideoManager, decoration: GalleryVideoDecoration(), content: videoContent, priority: .secondaryOverlay) videoNode.isUserInteractionEnabled = false videoNode.canAttachContent = true videoNode.isHidden = true diff --git a/submodules/PremiumUI/Sources/PhoneDemoComponent.swift b/submodules/PremiumUI/Sources/PhoneDemoComponent.swift index 8b963e3336..7f9f46566c 100644 --- a/submodules/PremiumUI/Sources/PhoneDemoComponent.swift +++ b/submodules/PremiumUI/Sources/PhoneDemoComponent.swift @@ -233,7 +233,7 @@ private final class PhoneView: UIView { hintDimensions: CGSize(width: 1170, height: 1754), storeAfterDownload: nil ) - let videoNode = UniversalVideoNode(accountId: context.account.id, postbox: context.account.postbox, audioSession: context.sharedContext.mediaManager.audioSession, manager: context.sharedContext.mediaManager.universalVideoManager, decoration: VideoDecoration(), content: videoContent, priority: .embedded) + let videoNode = UniversalVideoNode(context: context, postbox: context.account.postbox, audioSession: context.sharedContext.mediaManager.audioSession, manager: context.sharedContext.mediaManager.universalVideoManager, decoration: VideoDecoration(), content: videoContent, priority: .embedded) videoNode.canAttachContent = true self.videoNode = videoNode diff --git a/submodules/ShareController/Sources/ShareLoadingContainerNode.swift b/submodules/ShareController/Sources/ShareLoadingContainerNode.swift index eb958dece8..e6bd8fd778 100644 --- a/submodules/ShareController/Sources/ShareLoadingContainerNode.swift +++ b/submodules/ShareController/Sources/ShareLoadingContainerNode.swift @@ -277,20 +277,25 @@ public final class ShareProlongedLoadingContainerNode: ASDisplayNode, ShareConte })) if let postbox, let mediaManager = environment.mediaManager, let path = getAppBundle().path(forResource: "BlankVideo", ofType: "m4v"), let size = fileSize(path) { + let _ = postbox + let _ = mediaManager + let decoration = ChatBubbleVideoDecoration(corners: ImageCorners(), nativeSize: CGSize(width: 100.0, height: 100.0), contentMode: .aspectFit, backgroundColor: .black) + let _ = decoration let dummyFile = TelegramMediaFile(fileId: EngineMedia.Id(namespace: 0, id: 1), partialReference: nil, resource: LocalFileReferenceMediaResource(localFilePath: path, randomId: 12345), previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "video/mp4", size: size, attributes: [.Video(duration: 1, size: PixelDimensions(width: 100, height: 100), flags: [], preloadSize: nil, coverTime: nil, videoCodec: nil)], alternativeRepresentations: []) let videoContent = NativeVideoContent(id: .message(1, EngineMedia.Id(namespace: 0, id: 1)), userLocation: .other, fileReference: .standalone(media: dummyFile), streamVideo: .none, loopVideo: true, enableSound: false, fetchAutomatically: true, onlyFullSizeThumbnail: false, continuePlayingWithoutSoundOnLostAudioSession: false, placeholderColor: .black, storeAfterDownload: nil) + let _ = videoContent - let videoNode = UniversalVideoNode(accountId: AccountRecordId(rawValue: 0), postbox: postbox, audioSession: mediaManager.audioSession, manager: mediaManager.universalVideoManager, decoration: decoration, content: videoContent, priority: .embedded) + /*let videoNode = UniversalVideoNode(accountId: AccountRecordId(rawValue: 0), postbox: postbox, audioSession: mediaManager.audioSession, manager: mediaManager.universalVideoManager, decoration: decoration, content: videoContent, priority: .embedded) videoNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: 2.0, height: 2.0)) videoNode.alpha = 0.01 self.videoNode = videoNode self.addSubnode(videoNode) videoNode.canAttachContent = true - videoNode.play() + videoNode.play()*/ } } diff --git a/submodules/TelegramUI/Components/Chat/ChatBotInfoItem/Sources/ChatBotInfoItem.swift b/submodules/TelegramUI/Components/Chat/ChatBotInfoItem/Sources/ChatBotInfoItem.swift index 1769e8e2d3..99e9bfecb4 100644 --- a/submodules/TelegramUI/Components/Chat/ChatBotInfoItem/Sources/ChatBotInfoItem.swift +++ b/submodules/TelegramUI/Components/Chat/ChatBotInfoItem/Sources/ChatBotInfoItem.swift @@ -151,7 +151,7 @@ public final class ChatBotInfoItemNode: ListViewItemNode { continuePlayingWithoutSoundOnLostAudioSession: false, storeAfterDownload: nil ) - let videoNode = UniversalVideoNode(accountId: context.account.id, postbox: context.account.postbox, audioSession: context.sharedContext.mediaManager.audioSession, manager: context.sharedContext.mediaManager.universalVideoManager, decoration: VideoDecoration(), content: videoContent, priority: .embedded) + let videoNode = UniversalVideoNode(context: context, postbox: context.account.postbox, audioSession: context.sharedContext.mediaManager.audioSession, manager: context.sharedContext.mediaManager.universalVideoManager, decoration: VideoDecoration(), content: videoContent, priority: .embedded) videoNode.canAttachContent = true self.videoNode = videoNode diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageActionBubbleContentNode/Sources/ChatMessageActionBubbleContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageActionBubbleContentNode/Sources/ChatMessageActionBubbleContentNode.swift index cc0cd28113..b5976a5a1b 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageActionBubbleContentNode/Sources/ChatMessageActionBubbleContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageActionBubbleContentNode/Sources/ChatMessageActionBubbleContentNode.swift @@ -277,7 +277,7 @@ public class ChatMessageActionBubbleContentNode: ChatMessageBubbleContentNode { let videoContent = NativeVideoContent(id: .profileVideo(id, "action"), userLocation: .peer(item.message.id.peerId), fileReference: videoFileReference, streamVideo: isMediaStreamable(resource: video.resource) ? .conservative : .none, loopVideo: true, enableSound: false, fetchAutomatically: true, onlyFullSizeThumbnail: false, useLargeThumbnail: true, autoFetchFullSizeThumbnail: true, continuePlayingWithoutSoundOnLostAudioSession: false, placeholderColor: .clear, storeAfterDownload: nil) if videoContent.id != strongSelf.videoContent?.id { let mediaManager = item.context.sharedContext.mediaManager - let videoNode = UniversalVideoNode(accountId: item.context.account.id, postbox: item.context.account.postbox, audioSession: mediaManager.audioSession, manager: mediaManager.universalVideoManager, decoration: GalleryVideoDecoration(), content: videoContent, priority: .secondaryOverlay) + let videoNode = UniversalVideoNode(context: item.context, postbox: item.context.account.postbox, audioSession: mediaManager.audioSession, manager: mediaManager.universalVideoManager, decoration: GalleryVideoDecoration(), content: videoContent, priority: .secondaryOverlay) videoNode.isUserInteractionEnabled = false videoNode.ownsContentNodeUpdated = { [weak self] owns in if let strongSelf = self { diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveInstantVideoNode/Sources/ChatMessageInteractiveInstantVideoNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveInstantVideoNode/Sources/ChatMessageInteractiveInstantVideoNode.swift index 9bd38d928d..a55f886df6 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveInstantVideoNode/Sources/ChatMessageInteractiveInstantVideoNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveInstantVideoNode/Sources/ChatMessageInteractiveInstantVideoNode.swift @@ -763,7 +763,7 @@ public class ChatMessageInteractiveInstantVideoNode: ASDisplayNode { }) } let mediaManager = item.context.sharedContext.mediaManager - let videoNode = UniversalVideoNode(accountId: item.context.account.id, postbox: item.context.account.postbox, audioSession: mediaManager.audioSession, manager: mediaManager.universalVideoManager, decoration: ChatBubbleInstantVideoDecoration(inset: 2.0, backgroundImage: instantVideoBackgroundImage, tapped: { + let videoNode = UniversalVideoNode(context: item.context, postbox: item.context.account.postbox, audioSession: mediaManager.audioSession, manager: mediaManager.universalVideoManager, decoration: ChatBubbleInstantVideoDecoration(inset: 2.0, backgroundImage: instantVideoBackgroundImage, tapped: { if let strongSelf = self { if let item = strongSelf.item { if strongSelf.infoBackgroundNode.alpha.isZero { diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveMediaNode/Sources/ChatMessageInteractiveMediaNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveMediaNode/Sources/ChatMessageInteractiveMediaNode.swift index 0df17ee073..26da378aa8 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveMediaNode/Sources/ChatMessageInteractiveMediaNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveMediaNode/Sources/ChatMessageInteractiveMediaNode.swift @@ -1830,7 +1830,7 @@ public final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTr } ) } - let videoNode = UniversalVideoNode(accountId: context.account.id, postbox: context.account.postbox, audioSession: mediaManager.audioSession, manager: mediaManager.universalVideoManager, decoration: decoration, content: videoContent, priority: .embedded) + let videoNode = UniversalVideoNode(context: context, postbox: context.account.postbox, audioSession: mediaManager.audioSession, manager: mediaManager.universalVideoManager, decoration: decoration, content: videoContent, priority: .embedded) videoNode.isUserInteractionEnabled = false videoNode.ownsContentNodeUpdated = { [weak self] owns in if let strongSelf = self { diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageProfilePhotoSuggestionContentNode/Sources/ChatMessageProfilePhotoSuggestionContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageProfilePhotoSuggestionContentNode/Sources/ChatMessageProfilePhotoSuggestionContentNode.swift index cbcbc37fc4..11008ec607 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageProfilePhotoSuggestionContentNode/Sources/ChatMessageProfilePhotoSuggestionContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageProfilePhotoSuggestionContentNode/Sources/ChatMessageProfilePhotoSuggestionContentNode.swift @@ -222,7 +222,7 @@ public class ChatMessageProfilePhotoSuggestionContentNode: ChatMessageBubbleCont let videoContent = NativeVideoContent(id: .profileVideo(id, "action"), userLocation: .peer(item.message.id.peerId), fileReference: videoFileReference, streamVideo: isMediaStreamable(resource: video.resource) ? .conservative : .none, loopVideo: true, enableSound: false, fetchAutomatically: true, onlyFullSizeThumbnail: false, useLargeThumbnail: true, autoFetchFullSizeThumbnail: true, continuePlayingWithoutSoundOnLostAudioSession: false, placeholderColor: .clear, storeAfterDownload: nil) if videoContent.id != strongSelf.videoContent?.id { let mediaManager = item.context.sharedContext.mediaManager - let videoNode = UniversalVideoNode(accountId: item.context.account.id, postbox: item.context.account.postbox, audioSession: mediaManager.audioSession, manager: mediaManager.universalVideoManager, decoration: GalleryVideoDecoration(), content: videoContent, priority: .secondaryOverlay) + let videoNode = UniversalVideoNode(context: item.context, postbox: item.context.account.postbox, audioSession: mediaManager.audioSession, manager: mediaManager.universalVideoManager, decoration: GalleryVideoDecoration(), content: videoContent, priority: .secondaryOverlay) videoNode.isUserInteractionEnabled = false videoNode.ownsContentNodeUpdated = { [weak self] owns in if let strongSelf = self { diff --git a/submodules/TelegramUI/Components/Chat/ChatQrCodeScreen/Sources/ChatQrCodeScreen.swift b/submodules/TelegramUI/Components/Chat/ChatQrCodeScreen/Sources/ChatQrCodeScreen.swift index 9cef08d2d2..fe0ddd4798 100644 --- a/submodules/TelegramUI/Components/Chat/ChatQrCodeScreen/Sources/ChatQrCodeScreen.swift +++ b/submodules/TelegramUI/Components/Chat/ChatQrCodeScreen/Sources/ChatQrCodeScreen.swift @@ -2286,7 +2286,7 @@ private class MessageContentNode: ASDisplayNode, ContentNode { } } else { let videoContent = NativeVideoContent(id: .message(message.stableId, video.fileId), userLocation: .peer(message.id.peerId), fileReference: .message(message: MessageReference(message), media: video), streamVideo: .conservative, loopVideo: true, enableSound: false, fetchAutomatically: false, onlyFullSizeThumbnail: self.isStatic, continuePlayingWithoutSoundOnLostAudioSession: true, placeholderColor: .clear, captureProtected: false, storeAfterDownload: nil) - let videoNode = UniversalVideoNode(accountId: self.context.account.id, postbox: self.context.account.postbox, audioSession: self.context.sharedContext.mediaManager.audioSession, manager: self.context.sharedContext.mediaManager.universalVideoManager, decoration: GalleryVideoDecoration(), content: videoContent, priority: .overlay, autoplay: !self.isStatic) + let videoNode = UniversalVideoNode(context: self.context, postbox: self.context.account.postbox, audioSession: self.context.sharedContext.mediaManager.audioSession, manager: self.context.sharedContext.mediaManager.universalVideoManager, decoration: GalleryVideoDecoration(), content: videoContent, priority: .overlay, autoplay: !self.isStatic) self.videoStatusDisposable.set((videoNode.status |> deliverOnMainQueue).startStrict(next: { [weak self] status in diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoAvatarTransformContainerNode.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoAvatarTransformContainerNode.swift index a07718b06f..248f757820 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoAvatarTransformContainerNode.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoAvatarTransformContainerNode.swift @@ -333,7 +333,7 @@ final class PeerInfoAvatarTransformContainerNode: ASDisplayNode { self.videoNode?.removeFromSupernode() let mediaManager = self.context.sharedContext.mediaManager - let videoNode = UniversalVideoNode(accountId: self.context.account.id, postbox: self.context.account.postbox, audioSession: mediaManager.audioSession, manager: mediaManager.universalVideoManager, decoration: GalleryVideoDecoration(), content: videoContent, priority: .embedded) + let videoNode = UniversalVideoNode(context: self.context, postbox: self.context.account.postbox, audioSession: mediaManager.audioSession, manager: mediaManager.universalVideoManager, decoration: GalleryVideoDecoration(), content: videoContent, priority: .embedded) videoNode.isUserInteractionEnabled = false videoNode.isHidden = true diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoEditingAvatarNode.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoEditingAvatarNode.swift index 9d34dd106c..83e663631e 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoEditingAvatarNode.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoEditingAvatarNode.swift @@ -168,7 +168,7 @@ final class PeerInfoEditingAvatarNode: ASDisplayNode { self.videoNode?.removeFromSupernode() let mediaManager = self.context.sharedContext.mediaManager - let videoNode = UniversalVideoNode(accountId: self.context.account.id, postbox: self.context.account.postbox, audioSession: mediaManager.audioSession, manager: mediaManager.universalVideoManager, decoration: GalleryVideoDecoration(), content: videoContent, priority: .gallery) + let videoNode = UniversalVideoNode(context: self.context, postbox: self.context.account.postbox, audioSession: mediaManager.audioSession, manager: mediaManager.universalVideoManager, decoration: GalleryVideoDecoration(), content: videoContent, priority: .gallery) videoNode.isUserInteractionEnabled = false self.videoStartTimestamp = video.representation.startTimestamp self.videoContent = videoContent diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemContentComponent.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemContentComponent.swift index c0a5f2c7c6..f7adeea7cd 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemContentComponent.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemContentComponent.swift @@ -200,7 +200,7 @@ final class StoryItemContentComponent: Component { if case let .file(file) = currentMessageMedia, let peerReference = PeerReference(component.peer._asPeer()) { if self.videoNode == nil { let videoNode = UniversalVideoNode( - accountId: component.context.account.id, + context: component.context, postbox: component.context.account.postbox, audioSession: component.context.sharedContext.mediaManager.audioSession, manager: component.context.sharedContext.mediaManager.universalVideoManager, diff --git a/submodules/TelegramUI/Sources/MediaManager.swift b/submodules/TelegramUI/Sources/MediaManager.swift index ec6001bee7..d8c6c1d87a 100644 --- a/submodules/TelegramUI/Sources/MediaManager.swift +++ b/submodules/TelegramUI/Sources/MediaManager.swift @@ -464,22 +464,22 @@ public final class MediaManagerImpl: NSObject, MediaManager { } } - public func setPlaylist(_ playlist: (Account, SharedMediaPlaylist)?, type: MediaManagerPlayerType, control: SharedMediaPlayerControlAction) { + public func setPlaylist(_ playlist: (AccountContext, SharedMediaPlaylist)?, type: MediaManagerPlayerType, control: SharedMediaPlayerControlAction) { assert(Queue.mainQueue().isCurrent()) - let inputData: Signal<(Account, SharedMediaPlaylist, MusicPlaybackSettings, MediaPlaybackStoredState?)?, NoError> - if let (account, playlist) = playlist { + let inputData: Signal<(AccountContext, SharedMediaPlaylist, MusicPlaybackSettings, MediaPlaybackStoredState?)?, NoError> + if let (context, playlist) = playlist { inputData = self.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.musicPlaybackSettings]) |> take(1) - |> mapToSignal { sharedData -> Signal<(Account, SharedMediaPlaylist, MusicPlaybackSettings, MediaPlaybackStoredState?)?, NoError> in + |> mapToSignal { sharedData -> Signal<(AccountContext, SharedMediaPlaylist, MusicPlaybackSettings, MediaPlaybackStoredState?)?, NoError> in let settings = sharedData.entries[ApplicationSpecificSharedDataKeys.musicPlaybackSettings]?.get(MusicPlaybackSettings.self) ?? MusicPlaybackSettings.defaultSettings if let location = playlist.location as? PeerMessagesPlaylistLocation, let messageId = location.messageId { - return mediaPlaybackStoredState(engine: TelegramEngine(account: account), messageId: messageId) + return mediaPlaybackStoredState(engine: context.engine, messageId: messageId) |> map { storedState in - return (account, playlist, settings, storedState) + return (context, playlist, settings, storedState) } } else { - return .single((account, playlist, settings, nil)) + return .single((context, playlist, settings, nil)) } } } else { @@ -494,7 +494,7 @@ public final class MediaManagerImpl: NSObject, MediaManager { switch type { case .voice: strongSelf.musicMediaPlayer?.control(.playback(.pause)) - if let (account, playlist, settings, storedState) = inputData { + if let (context, playlist, settings, storedState) = inputData { if areSharedMediaPlaylistsEqual(playlist, strongSelf.voiceMediaPlayer?.playlist), case .seek = control { strongSelf.voiceMediaPlayer?.control(control) } else { @@ -506,7 +506,7 @@ public final class MediaManagerImpl: NSObject, MediaManager { controlPlaybackWithProximity = playlist.context.sharedContext.currentMediaInputSettings.with({ $0.enableRaiseToSpeak }) } - let voiceMediaPlayer = SharedMediaPlayer(mediaManager: strongSelf, inForeground: strongSelf.inForeground, account: account, audioSession: strongSelf.audioSession, overlayMediaManager: strongSelf.overlayMediaManager, playlist: playlist, initialOrder: .reversed, initialLooping: .none, initialPlaybackRate: settings.voicePlaybackRate, playerIndex: nextPlayerIndex, controlPlaybackWithProximity: controlPlaybackWithProximity, type: type, continueInstantVideoLoopAfterFinish: continueInstantVideoLoopAfterFinish) + let voiceMediaPlayer = SharedMediaPlayer(context: context, mediaManager: strongSelf, inForeground: strongSelf.inForeground, account: context.account, audioSession: strongSelf.audioSession, overlayMediaManager: strongSelf.overlayMediaManager, playlist: playlist, initialOrder: .reversed, initialLooping: .none, initialPlaybackRate: settings.voicePlaybackRate, playerIndex: nextPlayerIndex, controlPlaybackWithProximity: controlPlaybackWithProximity, type: type, continueInstantVideoLoopAfterFinish: continueInstantVideoLoopAfterFinish) strongSelf.voiceMediaPlayer = voiceMediaPlayer voiceMediaPlayer.playedToEnd = { [weak voiceMediaPlayer] in if let strongSelf = self, let voiceMediaPlayer = voiceMediaPlayer, voiceMediaPlayer === strongSelf.voiceMediaPlayer { @@ -535,12 +535,12 @@ public final class MediaManagerImpl: NSObject, MediaManager { } case .music, .file: strongSelf.voiceMediaPlayer?.control(.playback(.pause)) - if let (account, playlist, settings, storedState) = inputData { + if let (context, playlist, settings, storedState) = inputData { if areSharedMediaPlaylistsEqual(playlist, strongSelf.musicMediaPlayer?.playlist), case .seek = control { strongSelf.musicMediaPlayer?.control(control) } else { strongSelf.musicMediaPlayer?.stop() - let musicMediaPlayer = SharedMediaPlayer(mediaManager: strongSelf, inForeground: strongSelf.inForeground, account: account, audioSession: strongSelf.audioSession, overlayMediaManager: strongSelf.overlayMediaManager, playlist: playlist, initialOrder: settings.order, initialLooping: settings.looping, initialPlaybackRate: storedState?.playbackRate ?? .x1, playerIndex: nextPlayerIndex, controlPlaybackWithProximity: false, type: type, continueInstantVideoLoopAfterFinish: true) + let musicMediaPlayer = SharedMediaPlayer(context: context, mediaManager: strongSelf, inForeground: strongSelf.inForeground, account: context.account, audioSession: strongSelf.audioSession, overlayMediaManager: strongSelf.overlayMediaManager, playlist: playlist, initialOrder: settings.order, initialLooping: settings.looping, initialPlaybackRate: storedState?.playbackRate ?? .x1, playerIndex: nextPlayerIndex, controlPlaybackWithProximity: false, type: type, continueInstantVideoLoopAfterFinish: true) strongSelf.musicMediaPlayer = musicMediaPlayer musicMediaPlayer.cancelled = { [weak musicMediaPlayer] in if let strongSelf = self, let musicMediaPlayer = musicMediaPlayer, musicMediaPlayer === strongSelf.musicMediaPlayer { diff --git a/submodules/TelegramUI/Sources/OpenChatMessage.swift b/submodules/TelegramUI/Sources/OpenChatMessage.swift index daced6e649..86aa4c4867 100644 --- a/submodules/TelegramUI/Sources/OpenChatMessage.swift +++ b/submodules/TelegramUI/Sources/OpenChatMessage.swift @@ -308,7 +308,7 @@ func openChatMessageImpl(_ params: OpenChatMessageParams) -> Bool { } playerType = (file.isVoice || file.isInstantVideo) ? .voice : .file } - params.context.sharedContext.mediaManager.setPlaylist((params.context.account, PeerMessagesMediaPlaylist(context: params.context, location: location, chatLocationContextHolder: params.chatLocationContextHolder)), type: playerType, control: control) + params.context.sharedContext.mediaManager.setPlaylist((params.context, PeerMessagesMediaPlaylist(context: params.context, location: location, chatLocationContextHolder: params.chatLocationContextHolder)), type: playerType, control: control) return true case let .story(storyController): params.dismissInput() diff --git a/submodules/TelegramUI/Sources/OverlayInstantVideoNode.swift b/submodules/TelegramUI/Sources/OverlayInstantVideoNode.swift index 6e801b2667..e4c763d9ca 100644 --- a/submodules/TelegramUI/Sources/OverlayInstantVideoNode.swift +++ b/submodules/TelegramUI/Sources/OverlayInstantVideoNode.swift @@ -40,14 +40,14 @@ final class OverlayInstantVideoNode: OverlayMediaItemNode { var playbackEnded: (() -> Void)? - init(accountId: AccountRecordId, postbox: Postbox, audioSession: ManagedAudioSession, manager: UniversalVideoManager, content: UniversalVideoContent, close: @escaping () -> Void) { + init(context: AccountContext, postbox: Postbox, audioSession: ManagedAudioSession, manager: UniversalVideoManager, content: UniversalVideoContent, close: @escaping () -> Void) { self.close = close self.content = content var togglePlayPauseImpl: (() -> Void)? let decoration = OverlayInstantVideoDecoration(tapped: { togglePlayPauseImpl?() }) - self.videoNode = UniversalVideoNode(accountId: accountId, postbox: postbox, audioSession: audioSession, manager: manager, decoration: decoration, content: content, priority: .secondaryOverlay, snapshotContentWhenGone: true) + self.videoNode = UniversalVideoNode(context: context, postbox: postbox, audioSession: audioSession, manager: manager, decoration: decoration, content: content, priority: .secondaryOverlay, snapshotContentWhenGone: true) self.decoration = decoration super.init() diff --git a/submodules/TelegramUI/Sources/SharedMediaPlayer.swift b/submodules/TelegramUI/Sources/SharedMediaPlayer.swift index 6c6229f201..709dfaeab8 100644 --- a/submodules/TelegramUI/Sources/SharedMediaPlayer.swift +++ b/submodules/TelegramUI/Sources/SharedMediaPlayer.swift @@ -111,6 +111,7 @@ private enum SharedMediaPlaybackItem: Equatable { } final class SharedMediaPlayer { + private weak var context: AccountContext? private weak var mediaManager: MediaManager? let account: Account private let audioSession: ManagedAudioSession @@ -179,7 +180,8 @@ final class SharedMediaPlayer { let type: MediaManagerPlayerType - init(mediaManager: MediaManager, inForeground: Signal, account: Account, audioSession: ManagedAudioSession, overlayMediaManager: OverlayMediaManager, playlist: SharedMediaPlaylist, initialOrder: MusicPlaybackSettingsOrder, initialLooping: MusicPlaybackSettingsLooping, initialPlaybackRate: AudioPlaybackRate, playerIndex: Int32, controlPlaybackWithProximity: Bool, type: MediaManagerPlayerType, continueInstantVideoLoopAfterFinish: Bool) { + init(context: AccountContext, mediaManager: MediaManager, inForeground: Signal, account: Account, audioSession: ManagedAudioSession, overlayMediaManager: OverlayMediaManager, playlist: SharedMediaPlaylist, initialOrder: MusicPlaybackSettingsOrder, initialLooping: MusicPlaybackSettingsLooping, initialPlaybackRate: AudioPlaybackRate, playerIndex: Int32, controlPlaybackWithProximity: Bool, type: MediaManagerPlayerType, continueInstantVideoLoopAfterFinish: Bool) { + self.context = context self.mediaManager = mediaManager self.account = account self.audioSession = audioSession @@ -233,10 +235,10 @@ final class SharedMediaPlayer { strongSelf.playbackItem = .audio(MediaPlayer(audioSessionManager: strongSelf.audioSession, postbox: strongSelf.account.postbox, userLocation: .other, userContentType: .audio, resourceReference: fileReference.resourceReference(fileReference.media.resource), streamable: playbackData.type == .music ? .conservative : .none, video: false, preferSoftwareDecoding: false, enableSound: true, baseRate: rateValue, fetchAutomatically: true, playAndRecord: controlPlaybackWithProximity, isAudioVideoMessage: playbackData.type == .voice)) } case .instantVideo: - if let mediaManager = strongSelf.mediaManager, let item = item as? MessageMediaPlaylistItem { + if let mediaManager = strongSelf.mediaManager, let context = strongSelf.context, let item = item as? MessageMediaPlaylistItem { switch playbackData.source { case let .telegramFile(fileReference, _, _): - let videoNode = OverlayInstantVideoNode(accountId: strongSelf.account.id, postbox: strongSelf.account.postbox, audioSession: strongSelf.audioSession, manager: mediaManager.universalVideoManager, content: NativeVideoContent(id: .message(item.message.stableId, fileReference.media.fileId), userLocation: .peer(item.message.id.peerId), fileReference: fileReference, enableSound: false, baseRate: rateValue, isAudioVideoMessage: true, captureProtected: item.message.isCopyProtected(), storeAfterDownload: nil), close: { [weak mediaManager] in + let videoNode = OverlayInstantVideoNode(context: context, postbox: strongSelf.account.postbox, audioSession: strongSelf.audioSession, manager: mediaManager.universalVideoManager, content: NativeVideoContent(id: .message(item.message.stableId, fileReference.media.fileId), userLocation: .peer(item.message.id.peerId), fileReference: fileReference, enableSound: false, baseRate: rateValue, isAudioVideoMessage: true, captureProtected: item.message.isCopyProtected(), storeAfterDownload: nil), close: { [weak mediaManager] in mediaManager?.setPlaylist(nil, type: .voice, control: .playback(.pause)) }) strongSelf.playbackItem = .instantVideo(videoNode) diff --git a/submodules/TelegramUIPreferences/Sources/ExperimentalUISettings.swift b/submodules/TelegramUIPreferences/Sources/ExperimentalUISettings.swift index db8fa85144..d009392ddb 100644 --- a/submodules/TelegramUIPreferences/Sources/ExperimentalUISettings.swift +++ b/submodules/TelegramUIPreferences/Sources/ExperimentalUISettings.swift @@ -63,6 +63,7 @@ public struct ExperimentalUISettings: Codable, Equatable { public var enableLocalTranslation: Bool public var autoBenchmarkReflectors: Bool? public var conferenceCalls: Bool + public var playerV2: Bool public static var defaultSettings: ExperimentalUISettings { return ExperimentalUISettings( @@ -103,7 +104,8 @@ public struct ExperimentalUISettings: Codable, Equatable { dynamicStreaming: false, enableLocalTranslation: false, autoBenchmarkReflectors: nil, - conferenceCalls: false + conferenceCalls: false, + playerV2: false ) } @@ -145,7 +147,8 @@ public struct ExperimentalUISettings: Codable, Equatable { dynamicStreaming: Bool, enableLocalTranslation: Bool, autoBenchmarkReflectors: Bool?, - conferenceCalls: Bool + conferenceCalls: Bool, + playerV2: Bool ) { self.keepChatNavigationStack = keepChatNavigationStack self.skipReadHistory = skipReadHistory @@ -185,6 +188,7 @@ public struct ExperimentalUISettings: Codable, Equatable { self.enableLocalTranslation = enableLocalTranslation self.autoBenchmarkReflectors = autoBenchmarkReflectors self.conferenceCalls = conferenceCalls + self.playerV2 = playerV2 } public init(from decoder: Decoder) throws { @@ -228,6 +232,7 @@ public struct ExperimentalUISettings: Codable, Equatable { self.enableLocalTranslation = try container.decodeIfPresent(Bool.self, forKey: "enableLocalTranslation") ?? false self.autoBenchmarkReflectors = try container.decodeIfPresent(Bool.self, forKey: "autoBenchmarkReflectors") self.conferenceCalls = try container.decodeIfPresent(Bool.self, forKey: "conferenceCalls") ?? false + self.playerV2 = try container.decodeIfPresent(Bool.self, forKey: "playerV2") ?? false } public func encode(to encoder: Encoder) throws { @@ -271,6 +276,7 @@ public struct ExperimentalUISettings: Codable, Equatable { try container.encode(self.enableLocalTranslation, forKey: "enableLocalTranslation") try container.encodeIfPresent(self.autoBenchmarkReflectors, forKey: "autoBenchmarkReflectors") try container.encodeIfPresent(self.conferenceCalls, forKey: "conferenceCalls") + try container.encodeIfPresent(self.playerV2, forKey: "playerV2") } } diff --git a/submodules/TelegramUniversalVideoContent/Sources/HLSVideoContent.swift b/submodules/TelegramUniversalVideoContent/Sources/HLSVideoContent.swift index 2feecdd919..5b4532cb4e 100644 --- a/submodules/TelegramUniversalVideoContent/Sources/HLSVideoContent.swift +++ b/submodules/TelegramUniversalVideoContent/Sources/HLSVideoContent.swift @@ -264,8 +264,8 @@ public final class HLSVideoContent: UniversalVideoContent { self.codecConfiguration = codecConfiguration } - public func makeContentNode(accountId: AccountRecordId, postbox: Postbox, audioSession: ManagedAudioSession) -> UniversalVideoContentNode & ASDisplayNode { - return HLSVideoJSNativeContentNode(accountId: accountId, postbox: postbox, audioSessionManager: audioSession, userLocation: self.userLocation, fileReference: self.fileReference, streamVideo: self.streamVideo, loopVideo: self.loopVideo, enableSound: self.enableSound, baseRate: self.baseRate, fetchAutomatically: self.fetchAutomatically, onlyFullSizeThumbnail: self.onlyFullSizeThumbnail, useLargeThumbnail: self.useLargeThumbnail, autoFetchFullSizeThumbnail: self.autoFetchFullSizeThumbnail, codecConfiguration: self.codecConfiguration) + public func makeContentNode(context: AccountContext, postbox: Postbox, audioSession: ManagedAudioSession) -> UniversalVideoContentNode & ASDisplayNode { + return HLSVideoJSNativeContentNode(accountId: context.account.id, postbox: postbox, audioSessionManager: audioSession, userLocation: self.userLocation, fileReference: self.fileReference, streamVideo: self.streamVideo, loopVideo: self.loopVideo, enableSound: self.enableSound, baseRate: self.baseRate, fetchAutomatically: self.fetchAutomatically, onlyFullSizeThumbnail: self.onlyFullSizeThumbnail, useLargeThumbnail: self.useLargeThumbnail, autoFetchFullSizeThumbnail: self.autoFetchFullSizeThumbnail, codecConfiguration: self.codecConfiguration) } public func isEqual(to other: UniversalVideoContent) -> Bool { diff --git a/submodules/TelegramUniversalVideoContent/Sources/HLSVideoJSNativeContentNode.swift b/submodules/TelegramUniversalVideoContent/Sources/HLSVideoJSNativeContentNode.swift index 04f7b518e0..969ca736a7 100644 --- a/submodules/TelegramUniversalVideoContent/Sources/HLSVideoJSNativeContentNode.swift +++ b/submodules/TelegramUniversalVideoContent/Sources/HLSVideoJSNativeContentNode.swift @@ -1022,7 +1022,7 @@ final class HLSVideoJSNativeContentNode: ASDisplayNode, UniversalVideoContentNod private var didBecomeActiveObserver: NSObjectProtocol? private var willResignActiveObserver: NSObjectProtocol? - private let chunkPlayerPartsState = Promise(ChunkMediaPlayerPartsState(duration: nil, parts: [])) + private let chunkPlayerPartsState = Promise(ChunkMediaPlayerPartsState(duration: nil, content: .parts([]))) private var sourceBufferStateDisposable: Disposable? private var playerStatusDisposable: Disposable? @@ -1333,7 +1333,7 @@ final class HLSVideoJSNativeContentNode: ASDisplayNode, UniversalVideoContentNod return } - self.chunkPlayerPartsState.set(.single(ChunkMediaPlayerPartsState(duration: mediaSource.duration, parts: sourceBuffer.items))) + self.chunkPlayerPartsState.set(.single(ChunkMediaPlayerPartsState(duration: mediaSource.duration, content: .parts(sourceBuffer.items)))) } fileprivate func onMediaSourceBuffersUpdated() { @@ -1347,7 +1347,7 @@ final class HLSVideoJSNativeContentNode: ASDisplayNode, UniversalVideoContentNod return } - self.chunkPlayerPartsState.set(.single(ChunkMediaPlayerPartsState(duration: mediaSource.duration, parts: sourceBuffer.items))) + self.chunkPlayerPartsState.set(.single(ChunkMediaPlayerPartsState(duration: mediaSource.duration, content: .parts(sourceBuffer.items)))) if self.sourceBufferStateDisposable == nil { self.sourceBufferStateDisposable = (sourceBuffer.updated.signal() |> deliverOnMainQueue).startStrict(next: { [weak self, weak sourceBuffer] _ in @@ -1357,7 +1357,7 @@ final class HLSVideoJSNativeContentNode: ASDisplayNode, UniversalVideoContentNod guard let mediaSource = SharedHLSVideoJSContext.shared.mediaSources[sourceBuffer.mediaSourceId] else { return } - self.chunkPlayerPartsState.set(.single(ChunkMediaPlayerPartsState(duration: mediaSource.duration, parts: sourceBuffer.items))) + self.chunkPlayerPartsState.set(.single(ChunkMediaPlayerPartsState(duration: mediaSource.duration, content: .parts(sourceBuffer.items)))) self.updateBuffered() }) diff --git a/submodules/TelegramUniversalVideoContent/Sources/NativeVideoContent.swift b/submodules/TelegramUniversalVideoContent/Sources/NativeVideoContent.swift index 249633cdce..606524a610 100644 --- a/submodules/TelegramUniversalVideoContent/Sources/NativeVideoContent.swift +++ b/submodules/TelegramUniversalVideoContent/Sources/NativeVideoContent.swift @@ -128,8 +128,8 @@ public final class NativeVideoContent: UniversalVideoContent { self.hasSentFramesToDisplay = hasSentFramesToDisplay } - public func makeContentNode(accountId: AccountRecordId, postbox: Postbox, audioSession: ManagedAudioSession) -> UniversalVideoContentNode & ASDisplayNode { - return NativeVideoContentNode(postbox: postbox, audioSessionManager: audioSession, userLocation: self.userLocation, fileReference: self.fileReference, previewSourceFileReference: self.previewSourceFileReference, limitedFileRange: self.limitedFileRange, imageReference: self.imageReference, streamVideo: self.streamVideo, loopVideo: self.loopVideo, enableSound: self.enableSound, soundMuted: self.soundMuted, beginWithAmbientSound: self.beginWithAmbientSound, mixWithOthers: self.mixWithOthers, baseRate: self.baseRate, fetchAutomatically: self.fetchAutomatically, onlyFullSizeThumbnail: self.onlyFullSizeThumbnail, useLargeThumbnail: self.useLargeThumbnail, autoFetchFullSizeThumbnail: self.autoFetchFullSizeThumbnail, startTimestamp: self.startTimestamp, endTimestamp: self.endTimestamp, continuePlayingWithoutSoundOnLostAudioSession: self.continuePlayingWithoutSoundOnLostAudioSession, placeholderColor: self.placeholderColor, tempFilePath: self.tempFilePath, isAudioVideoMessage: self.isAudioVideoMessage, captureProtected: self.captureProtected, hintDimensions: self.hintDimensions, storeAfterDownload: self.storeAfterDownload, displayImage: self.displayImage, hasSentFramesToDisplay: self.hasSentFramesToDisplay) + public func makeContentNode(context: AccountContext, postbox: Postbox, audioSession: ManagedAudioSession) -> UniversalVideoContentNode & ASDisplayNode { + return NativeVideoContentNode(context: context, postbox: postbox, audioSessionManager: audioSession, userLocation: self.userLocation, fileReference: self.fileReference, previewSourceFileReference: self.previewSourceFileReference, limitedFileRange: self.limitedFileRange, imageReference: self.imageReference, streamVideo: self.streamVideo, loopVideo: self.loopVideo, enableSound: self.enableSound, soundMuted: self.soundMuted, beginWithAmbientSound: self.beginWithAmbientSound, mixWithOthers: self.mixWithOthers, baseRate: self.baseRate, fetchAutomatically: self.fetchAutomatically, onlyFullSizeThumbnail: self.onlyFullSizeThumbnail, useLargeThumbnail: self.useLargeThumbnail, autoFetchFullSizeThumbnail: self.autoFetchFullSizeThumbnail, startTimestamp: self.startTimestamp, endTimestamp: self.endTimestamp, continuePlayingWithoutSoundOnLostAudioSession: self.continuePlayingWithoutSoundOnLostAudioSession, placeholderColor: self.placeholderColor, tempFilePath: self.tempFilePath, isAudioVideoMessage: self.isAudioVideoMessage, captureProtected: self.captureProtected, hintDimensions: self.hintDimensions, storeAfterDownload: self.storeAfterDownload, displayImage: self.displayImage, hasSentFramesToDisplay: self.hasSentFramesToDisplay) } public func isEqual(to other: UniversalVideoContent) -> Bool { @@ -296,7 +296,7 @@ private final class NativeVideoContentNode: ASDisplayNode, UniversalVideoContent private let continuePlayingWithoutSoundOnLostAudioSession: Bool private let displayImage: Bool - private var player: PlayerImpl + private var player: PlayerImpl? private var thumbnailPlayer: MediaPlayer? private let imageNode: TransformImageNode private let playerNode: MediaPlayerNode @@ -338,6 +338,7 @@ private final class NativeVideoContentNode: ASDisplayNode, UniversalVideoContent return self._ready.get() } + private var initializePlayerDisposable: Disposable? private let fetchDisposable = MetaDisposable() private let fetchStatusDisposable = MetaDisposable() @@ -347,10 +348,19 @@ private final class NativeVideoContentNode: ASDisplayNode, UniversalVideoContent private var validLayout: (size: CGSize, actualSize: CGSize)? private var shouldPlay: Bool = false + private var pendingSetSoundEnabled: Bool? + private var pendingSeek: Double? + private var pendingPlayOnceWithSound: (playAndRecord: Bool, seek: MediaPlayerSeek, actionAtEnd: MediaPlayerPlayOnceWithSoundActionAtEnd)? + private var pendingForceAudioToSpeaker: Bool? + private var pendingSetSoundMuted: Bool? + private var pendingContinueWithOverridingAmbientMode: Bool? + private var pendingSetBaseRate: Double? + private var pendingContinuePlayingWithoutSound: MediaPlayerPlayOnceWithSoundActionAtEnd? + private var pendingSetContinuePlayingWithoutSoundOnLostAudioSession: Bool? private let hasSentFramesToDisplay: (() -> Void)? - init(postbox: Postbox, audioSessionManager: ManagedAudioSession, userLocation: MediaResourceUserLocation, fileReference: FileMediaReference, previewSourceFileReference: FileMediaReference?, limitedFileRange: Range?, imageReference: ImageMediaReference?, streamVideo: MediaPlayerStreaming, loopVideo: Bool, enableSound: Bool, soundMuted: Bool, beginWithAmbientSound: Bool, mixWithOthers: Bool, baseRate: Double, fetchAutomatically: Bool, onlyFullSizeThumbnail: Bool, useLargeThumbnail: Bool, autoFetchFullSizeThumbnail: Bool, startTimestamp: Double?, endTimestamp: Double?, continuePlayingWithoutSoundOnLostAudioSession: Bool = false, placeholderColor: UIColor, tempFilePath: String?, isAudioVideoMessage: Bool, captureProtected: Bool, hintDimensions: CGSize?, storeAfterDownload: (() -> Void)? = nil, displayImage: Bool, hasSentFramesToDisplay: (() -> Void)?) { + init(context: AccountContext, postbox: Postbox, audioSessionManager: ManagedAudioSession, userLocation: MediaResourceUserLocation, fileReference: FileMediaReference, previewSourceFileReference: FileMediaReference?, limitedFileRange: Range?, imageReference: ImageMediaReference?, streamVideo: MediaPlayerStreaming, loopVideo: Bool, enableSound: Bool, soundMuted: Bool, beginWithAmbientSound: Bool, mixWithOthers: Bool, baseRate: Double, fetchAutomatically: Bool, onlyFullSizeThumbnail: Bool, useLargeThumbnail: Bool, autoFetchFullSizeThumbnail: Bool, startTimestamp: Double?, endTimestamp: Double?, continuePlayingWithoutSoundOnLostAudioSession: Bool = false, placeholderColor: UIColor, tempFilePath: String?, isAudioVideoMessage: Bool, captureProtected: Bool, hintDimensions: CGSize?, storeAfterDownload: (() -> Void)? = nil, displayImage: Bool, hasSentFramesToDisplay: (() -> Void)?) { self.postbox = postbox self.userLocation = userLocation self.fileReference = fileReference @@ -385,67 +395,6 @@ private final class NativeVideoContentNode: ASDisplayNode, UniversalVideoContent self.playerNode = MediaPlayerNode(backgroundThread: false, captureProtected: captureProtected) - if !"".isEmpty { - let mediaPlayer = MediaPlayer( - audioSessionManager: audioSessionManager, - postbox: postbox, - userLocation: userLocation, - userContentType: userContentType, - resourceReference: fileReference.resourceReference(selectedFile.resource), - tempFilePath: tempFilePath, - limitedFileRange: limitedFileRange, - streamable: streamVideo, - video: true, - preferSoftwareDecoding: false, - playAutomatically: false, - enableSound: enableSound, - baseRate: baseRate, - fetchAutomatically: fetchAutomatically, - soundMuted: soundMuted, - ambient: beginWithAmbientSound, - mixWithOthers: mixWithOthers, - continuePlayingWithoutSoundOnLostAudioSession: continuePlayingWithoutSoundOnLostAudioSession, - storeAfterDownload: storeAfterDownload, - isAudioVideoMessage: isAudioVideoMessage - ) - self.player = .legacy(mediaPlayer) - mediaPlayer.attachPlayerNode(self.playerNode) - } else { - let mediaPlayer = ChunkMediaPlayerV2( - audioSessionManager: audioSessionManager, - source: .directFetch(ChunkMediaPlayerV2.SourceDescription.ResourceDescription( - postbox: postbox, - reference: fileReference.resourceReference(selectedFile.resource), - userLocation: userLocation, - userContentType: userContentType, - statsCategory: statsCategoryForFileWithAttributes(fileReference.media.attributes), - fetchAutomatically: fetchAutomatically - )), - video: true, - playAutomatically: false, - enableSound: enableSound, - baseRate: baseRate, - soundMuted: soundMuted, - ambient: beginWithAmbientSound, - mixWithOthers: mixWithOthers, - continuePlayingWithoutSoundOnLostAudioSession: continuePlayingWithoutSoundOnLostAudioSession, - isAudioVideoMessage: isAudioVideoMessage, - playerNode: self.playerNode - ) - self.player = .chunked(mediaPlayer) - } - - var actionAtEndImpl: (() -> Void)? - if enableSound && !loopVideo { - self.player.actionAtEnd = .action({ - actionAtEndImpl?() - }) - } else { - self.player.actionAtEnd = .loop({ - actionAtEndImpl?() - }) - } - self.dimensions = fileReference.media.dimensions?.cgSize if let dimensions = self.dimensions { self.dimensionsPromise.set(dimensions) @@ -469,10 +418,6 @@ private final class NativeVideoContentNode: ASDisplayNode, UniversalVideoContent self.dimensionsPromise.set(dimensions) } - actionAtEndImpl = { [weak self] in - self?.performActionAtEnd() - } - if displayImage { if captureProtected { setLayerDisableScreenshots(self.imageNode.layer, captureProtected) @@ -497,10 +442,6 @@ private final class NativeVideoContentNode: ASDisplayNode, UniversalVideoContent } self.addSubnode(self.playerNode) - self._status.set(combineLatest(self.dimensionsPromise.get(), self.player.status) - |> map { dimensions, status in - return MediaPlayerStatus(generationTimestamp: status.generationTimestamp, duration: status.duration, dimensions: dimensions, timestamp: status.timestamp, baseRate: status.baseRate, seekId: status.seekId, status: status.status, soundEnabled: status.soundEnabled) - }) self.fetchStatusDisposable.set((postbox.mediaBox.resourceStatus(selectedFile.resource) |> deliverOnMainQueue).start(next: { [weak self] status in @@ -536,15 +477,138 @@ private final class NativeVideoContentNode: ASDisplayNode, UniversalVideoContent if let startTimestamp = startTimestamp { self.seek(startTimestamp) } + + var useLegacyImplementation = !context.sharedContext.immediateExperimentalUISettings.playerV2 + if let data = context.currentAppConfiguration.with({ $0 }).data, let value = data["ios_video_legacyplayer"] as? Double { + useLegacyImplementation = value != 0.0 + } + + if useLegacyImplementation { + let mediaPlayer = MediaPlayer( + audioSessionManager: audioSessionManager, + postbox: postbox, + userLocation: userLocation, + userContentType: userContentType, + resourceReference: fileReference.resourceReference(selectedFile.resource), + tempFilePath: tempFilePath, + limitedFileRange: limitedFileRange, + streamable: streamVideo, + video: true, + preferSoftwareDecoding: false, + playAutomatically: false, + enableSound: enableSound, + baseRate: baseRate, + fetchAutomatically: fetchAutomatically, + soundMuted: soundMuted, + ambient: beginWithAmbientSound, + mixWithOthers: mixWithOthers, + continuePlayingWithoutSoundOnLostAudioSession: continuePlayingWithoutSoundOnLostAudioSession, + storeAfterDownload: storeAfterDownload, + isAudioVideoMessage: isAudioVideoMessage + ) + mediaPlayer.attachPlayerNode(self.playerNode) + self.initializePlayer(player: .legacy(mediaPlayer)) + } else { + let mediaPlayer = ChunkMediaPlayerV2( + audioSessionManager: audioSessionManager, + source: .directFetch(ChunkMediaPlayerV2.SourceDescription.ResourceDescription( + postbox: postbox, + reference: fileReference.resourceReference(selectedFile.resource), + userLocation: userLocation, + userContentType: userContentType, + statsCategory: statsCategoryForFileWithAttributes(fileReference.media.attributes), + fetchAutomatically: fetchAutomatically + )), + video: true, + playAutomatically: false, + enableSound: enableSound, + baseRate: baseRate, + soundMuted: soundMuted, + ambient: beginWithAmbientSound, + mixWithOthers: mixWithOthers, + continuePlayingWithoutSoundOnLostAudioSession: continuePlayingWithoutSoundOnLostAudioSession, + isAudioVideoMessage: isAudioVideoMessage, + playerNode: self.playerNode + ) + self.initializePlayer(player: .chunked(mediaPlayer)) + } } deinit { - self.player.pause() + self.initializePlayerDisposable?.dispose() + self.player?.pause() self.thumbnailPlayer?.pause() self.fetchDisposable.dispose() self.fetchStatusDisposable.dispose() } + private func initializePlayer(player: PlayerImpl) { + var player = player + self.player = player + + var actionAtEndImpl: (() -> Void)? + if self.enableSound && !self.loopVideo { + player.actionAtEnd = .action({ + actionAtEndImpl?() + }) + } else { + player.actionAtEnd = .loop({ + actionAtEndImpl?() + }) + } + actionAtEndImpl = { [weak self] in + self?.performActionAtEnd() + } + + self._status.set(combineLatest(self.dimensionsPromise.get(), player.status) + |> map { dimensions, status in + return MediaPlayerStatus(generationTimestamp: status.generationTimestamp, duration: status.duration, dimensions: dimensions, timestamp: status.timestamp, baseRate: status.baseRate, seekId: status.seekId, status: status.status, soundEnabled: status.soundEnabled) + }) + + if self.shouldPlay { + player.play() + } else { + player.pause() + } + + if let pendingSeek = self.pendingSeek { + self.pendingSeek = nil + self.seek(pendingSeek) + } + if let pendingSetSoundEnabled = self.pendingSetSoundEnabled { + self.pendingSetSoundEnabled = nil + self.setSoundEnabled(pendingSetSoundEnabled) + } + if let pendingPlayOnceWithSound = self.pendingPlayOnceWithSound { + self.pendingPlayOnceWithSound = nil + self.playOnceWithSound(playAndRecord: pendingPlayOnceWithSound.playAndRecord, seek: pendingPlayOnceWithSound.seek, actionAtEnd: pendingPlayOnceWithSound.actionAtEnd) + } + if let pendingForceAudioToSpeaker = self.pendingForceAudioToSpeaker { + self.pendingForceAudioToSpeaker = nil + self.setForceAudioToSpeaker(pendingForceAudioToSpeaker) + } + if let pendingSetSoundMuted = self.pendingSetSoundMuted { + self.pendingSetSoundMuted = nil + self.setSoundMuted(soundMuted: pendingSetSoundMuted) + } + if let pendingContinueWithOverridingAmbientMode = self.pendingContinueWithOverridingAmbientMode { + self.pendingContinueWithOverridingAmbientMode = nil + self.continueWithOverridingAmbientMode(isAmbient: pendingContinueWithOverridingAmbientMode) + } + if let pendingSetBaseRate = self.pendingSetBaseRate { + self.pendingSetBaseRate = nil + self.setBaseRate(pendingSetBaseRate) + } + if let pendingContinuePlayingWithoutSound = self.pendingContinuePlayingWithoutSound { + self.pendingContinuePlayingWithoutSound = nil + self.continuePlayingWithoutSound(actionAtEnd: pendingContinuePlayingWithoutSound) + } + if let pendingSetContinuePlayingWithoutSoundOnLostAudioSession = self.pendingSetContinuePlayingWithoutSoundOnLostAudioSession { + self.pendingSetContinuePlayingWithoutSoundOnLostAudioSession = nil + self.setContinuePlayingWithoutSoundOnLostAudioSession(pendingSetContinuePlayingWithoutSoundOnLostAudioSession) + } + } + private func createThumbnailPlayer() { guard let videoThumbnail = self.fileReference.media.videoThumbnails.first else { return @@ -639,41 +703,55 @@ private final class NativeVideoContentNode: ASDisplayNode, UniversalVideoContent func play() { assert(Queue.mainQueue().isCurrent()) - self.player.play() + self.player?.play() self.shouldPlay = true self.thumbnailPlayer?.play() } func pause() { assert(Queue.mainQueue().isCurrent()) - self.player.pause() + self.player?.pause() self.shouldPlay = false self.thumbnailPlayer?.pause() } func togglePlayPause() { assert(Queue.mainQueue().isCurrent()) - self.player.togglePlayPause() + self.player?.togglePlayPause() self.shouldPlay = !self.shouldPlay self.thumbnailPlayer?.togglePlayPause() } func setSoundEnabled(_ value: Bool) { assert(Queue.mainQueue().isCurrent()) - if value { - self.player.playOnceWithSound(playAndRecord: false, seek: .none) + if let player = self.player { + if value { + player.playOnceWithSound(playAndRecord: false, seek: .none) + } else { + player.continuePlayingWithoutSound(seek: .none) + } } else { - self.player.continuePlayingWithoutSound(seek: .none) + self.pendingSetSoundEnabled = value } } func seek(_ timestamp: Double) { assert(Queue.mainQueue().isCurrent()) - self.player.seek(timestamp: timestamp) + if let player = self.player { + player.seek(timestamp: timestamp) + } else { + self.pendingSeek = timestamp + } } func playOnceWithSound(playAndRecord: Bool, seek: MediaPlayerSeek, actionAtEnd: MediaPlayerPlayOnceWithSoundActionAtEnd) { assert(Queue.mainQueue().isCurrent()) + + guard var player = self.player else { + self.pendingPlayOnceWithSound = (playAndRecord, seek, actionAtEnd) + return + } + let action = { [weak self] in Queue.mainQueue().async { self?.performActionAtEnd() @@ -681,49 +759,65 @@ private final class NativeVideoContentNode: ASDisplayNode, UniversalVideoContent } switch actionAtEnd { case .loop: - self.player.actionAtEnd = .loop({}) + player.actionAtEnd = .loop({}) case .loopDisablingSound: - self.player.actionAtEnd = .loopDisablingSound(action) + player.actionAtEnd = .loopDisablingSound(action) case .stop: - self.player.actionAtEnd = .action(action) + player.actionAtEnd = .action(action) case .repeatIfNeeded: - let _ = (self.player.status + let _ = (player.status |> deliverOnMainQueue |> take(1)).start(next: { [weak self] status in - guard let strongSelf = self else { + guard let strongSelf = self, var player = strongSelf.player else { return } if status.timestamp > status.duration * 0.1 { - strongSelf.player.actionAtEnd = .loop({ [weak self] in - guard let strongSelf = self else { + player.actionAtEnd = .loop({ [weak self] in + guard let strongSelf = self, var player = strongSelf.player else { return } - strongSelf.player.actionAtEnd = .loopDisablingSound(action) + player.actionAtEnd = .loopDisablingSound(action) }) } else { - strongSelf.player.actionAtEnd = .loopDisablingSound(action) + player.actionAtEnd = .loopDisablingSound(action) } }) } - self.player.playOnceWithSound(playAndRecord: playAndRecord, seek: seek) + player.playOnceWithSound(playAndRecord: playAndRecord, seek: seek) } func setForceAudioToSpeaker(_ forceAudioToSpeaker: Bool) { assert(Queue.mainQueue().isCurrent()) - self.player.setForceAudioToSpeaker(forceAudioToSpeaker) + if let player = self.player { + player.setForceAudioToSpeaker(forceAudioToSpeaker) + } else { + self.pendingForceAudioToSpeaker = forceAudioToSpeaker + } } func setSoundMuted(soundMuted: Bool) { - self.player.setSoundMuted(soundMuted: soundMuted) + if let player = self.player { + player.setSoundMuted(soundMuted: soundMuted) + } else { + self.pendingSetSoundMuted = soundMuted + } } func continueWithOverridingAmbientMode(isAmbient: Bool) { - self.player.continueWithOverridingAmbientMode(isAmbient: isAmbient) + if let player = self.player { + player.continueWithOverridingAmbientMode(isAmbient: isAmbient) + } else { + self.pendingContinueWithOverridingAmbientMode = isAmbient + } } func setBaseRate(_ baseRate: Double) { - self.player.setBaseRate(baseRate) + if let player = self.player { + player.setBaseRate(baseRate) + } else { + self.pendingSetBaseRate = baseRate + } } func setVideoQuality(_ quality: UniversalVideoContentVideoQuality) { @@ -739,24 +833,34 @@ private final class NativeVideoContentNode: ASDisplayNode, UniversalVideoContent func continuePlayingWithoutSound(actionAtEnd: MediaPlayerPlayOnceWithSoundActionAtEnd) { assert(Queue.mainQueue().isCurrent()) + + guard var player = self.player else { + self.pendingContinuePlayingWithoutSound = actionAtEnd + return + } + let action = { [weak self] in Queue.mainQueue().async { self?.performActionAtEnd() } } switch actionAtEnd { - case .loop: - self.player.actionAtEnd = .loop({}) - case .loopDisablingSound, .repeatIfNeeded: - self.player.actionAtEnd = .loopDisablingSound(action) - case .stop: - self.player.actionAtEnd = .action(action) + case .loop: + player.actionAtEnd = .loop({}) + case .loopDisablingSound, .repeatIfNeeded: + player.actionAtEnd = .loopDisablingSound(action) + case .stop: + player.actionAtEnd = .action(action) } - self.player.continuePlayingWithoutSound() + player.continuePlayingWithoutSound() } func setContinuePlayingWithoutSoundOnLostAudioSession(_ value: Bool) { - self.player.setContinuePlayingWithoutSoundOnLostAudioSession(value) + if let player = self.player { + player.setContinuePlayingWithoutSoundOnLostAudioSession(value) + } else { + self.pendingSetContinuePlayingWithoutSoundOnLostAudioSession = value + } } func addPlaybackCompleted(_ f: @escaping () -> Void) -> Int { diff --git a/submodules/TelegramUniversalVideoContent/Sources/OverlayUniversalVideoNode.swift b/submodules/TelegramUniversalVideoContent/Sources/OverlayUniversalVideoNode.swift index d71b1eec99..0da0605cdc 100644 --- a/submodules/TelegramUniversalVideoContent/Sources/OverlayUniversalVideoNode.swift +++ b/submodules/TelegramUniversalVideoContent/Sources/OverlayUniversalVideoNode.swift @@ -41,7 +41,7 @@ public final class OverlayUniversalVideoNode: OverlayMediaItemNode, AVPictureInP private var statusDisposable: Disposable? private var status: MediaPlayerStatus? - public init(accountId: AccountRecordId, postbox: Postbox, audioSession: ManagedAudioSession, manager: UniversalVideoManager, content: UniversalVideoContent, shouldBeDismissed: Signal = .single(false), expand: @escaping () -> Void, close: @escaping () -> Void) { + public init(context: AccountContext, postbox: Postbox, audioSession: ManagedAudioSession, manager: UniversalVideoManager, content: UniversalVideoContent, shouldBeDismissed: Signal = .single(false), expand: @escaping () -> Void, close: @escaping () -> Void) { self.content = content self.defaultExpand = expand @@ -62,7 +62,7 @@ public final class OverlayUniversalVideoNode: OverlayMediaItemNode, AVPictureInP }, controlsAreShowingUpdated: { value in controlsAreShowingUpdatedImpl?(value) }) - self.videoNode = UniversalVideoNode(accountId: accountId, postbox: postbox, audioSession: audioSession, manager: manager, decoration: decoration, content: content, priority: .overlay) + self.videoNode = UniversalVideoNode(context: context, postbox: postbox, audioSession: audioSession, manager: manager, decoration: decoration, content: content, priority: .overlay) self.decoration = decoration super.init() diff --git a/submodules/TelegramUniversalVideoContent/Sources/PlatformVideoContent.swift b/submodules/TelegramUniversalVideoContent/Sources/PlatformVideoContent.swift index 2481eb2a85..924d67dc0b 100644 --- a/submodules/TelegramUniversalVideoContent/Sources/PlatformVideoContent.swift +++ b/submodules/TelegramUniversalVideoContent/Sources/PlatformVideoContent.swift @@ -95,7 +95,7 @@ public final class PlatformVideoContent: UniversalVideoContent { self.fetchAutomatically = fetchAutomatically } - public func makeContentNode(accountId: AccountRecordId, postbox: Postbox, audioSession: ManagedAudioSession) -> UniversalVideoContentNode & ASDisplayNode { + public func makeContentNode(context: AccountContext, postbox: Postbox, audioSession: ManagedAudioSession) -> UniversalVideoContentNode & ASDisplayNode { return PlatformVideoContentNode(postbox: postbox, audioSessionManager: audioSession, userLocation: self.userLocation, content: self.content, streamVideo: self.streamVideo, loopVideo: self.loopVideo, enableSound: self.enableSound, baseRate: self.baseRate, fetchAutomatically: self.fetchAutomatically) } diff --git a/submodules/TelegramUniversalVideoContent/Sources/SystemVideoContent.swift b/submodules/TelegramUniversalVideoContent/Sources/SystemVideoContent.swift index fae099ae4c..32ee7caeda 100644 --- a/submodules/TelegramUniversalVideoContent/Sources/SystemVideoContent.swift +++ b/submodules/TelegramUniversalVideoContent/Sources/SystemVideoContent.swift @@ -29,7 +29,7 @@ public final class SystemVideoContent: UniversalVideoContent { self.duration = duration } - public func makeContentNode(accountId: AccountRecordId, postbox: Postbox, audioSession: ManagedAudioSession) -> UniversalVideoContentNode & ASDisplayNode { + public func makeContentNode(context: AccountContext, postbox: Postbox, audioSession: ManagedAudioSession) -> UniversalVideoContentNode & ASDisplayNode { return SystemVideoContentNode(postbox: postbox, audioSessionManager: audioSession, userLocation: self.userLocation, url: self.url, imageReference: self.imageReference, intrinsicDimensions: self.dimensions, approximateDuration: self.duration) } } diff --git a/submodules/TelegramUniversalVideoContent/Sources/WebEmbedVideoContent.swift b/submodules/TelegramUniversalVideoContent/Sources/WebEmbedVideoContent.swift index ebe1c8b9ac..5a74e611e7 100644 --- a/submodules/TelegramUniversalVideoContent/Sources/WebEmbedVideoContent.swift +++ b/submodules/TelegramUniversalVideoContent/Sources/WebEmbedVideoContent.swift @@ -36,7 +36,7 @@ public final class WebEmbedVideoContent: UniversalVideoContent { self.openUrl = openUrl } - public func makeContentNode(accountId: AccountRecordId, postbox: Postbox, audioSession: ManagedAudioSession) -> UniversalVideoContentNode & ASDisplayNode { + public func makeContentNode(context: AccountContext, postbox: Postbox, audioSession: ManagedAudioSession) -> UniversalVideoContentNode & ASDisplayNode { return WebEmbedVideoContentNode(postbox: postbox, audioSessionManager: audioSession, userLocation: self.userLocation, webPage: self.webPage, webpageContent: self.webpageContent, forcedTimestamp: self.forcedTimestamp, openUrl: self.openUrl) } } diff --git a/submodules/WebSearchUI/Sources/WebSearchVideoGalleryItem.swift b/submodules/WebSearchUI/Sources/WebSearchVideoGalleryItem.swift index d3696d5d0d..676cce6782 100644 --- a/submodules/WebSearchUI/Sources/WebSearchVideoGalleryItem.swift +++ b/submodules/WebSearchUI/Sources/WebSearchVideoGalleryItem.swift @@ -165,7 +165,7 @@ final class WebSearchVideoGalleryItemNode: ZoomableContentGalleryItemNode { let mediaManager = item.context.sharedContext.mediaManager - let videoNode = UniversalVideoNode(accountId: item.context.account.id, postbox: item.context.account.postbox, audioSession: mediaManager.audioSession, manager: mediaManager.universalVideoManager, decoration: GalleryVideoDecoration(), content: item.content, priority: .gallery) + let videoNode = UniversalVideoNode(context: item.context, postbox: item.context.account.postbox, audioSession: mediaManager.audioSession, manager: mediaManager.universalVideoManager, decoration: GalleryVideoDecoration(), content: item.content, priority: .gallery) let videoSize = CGSize(width: item.content.dimensions.width * 2.0, height: item.content.dimensions.height * 2.0) videoNode.updateLayout(size: videoSize, transition: .immediate) self.videoNode = videoNode