mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-15 13:35:19 +00:00
[Temp] video v2
This commit is contained in:
parent
4bed1703a2
commit
df9e27f4fb
@ -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<SharedMediaPlayerItemPlaybackState?, NoError>
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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))
|
||||
|
@ -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,
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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))
|
||||
|
@ -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<Impl>
|
||||
|
||||
public init(seekPosition: Double, availableUntilPosition: Double, impl: QueueLocalObject<Impl>) {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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<ChunkMediaPlayerPartsState>()
|
||||
@ -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([])
|
||||
)))
|
||||
}
|
||||
})
|
||||
|
@ -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<LoadedPartsMediaData>
|
||||
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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
@ -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()*/
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -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 {
|
||||
|
@ -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 {
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
|
@ -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 {
|
||||
|
@ -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()
|
||||
|
@ -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()
|
||||
|
@ -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<Bool, NoError>, 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<Bool, NoError>, 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)
|
||||
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -1022,7 +1022,7 @@ final class HLSVideoJSNativeContentNode: ASDisplayNode, UniversalVideoContentNod
|
||||
private var didBecomeActiveObserver: NSObjectProtocol?
|
||||
private var willResignActiveObserver: NSObjectProtocol?
|
||||
|
||||
private let chunkPlayerPartsState = Promise<ChunkMediaPlayerPartsState>(ChunkMediaPlayerPartsState(duration: nil, parts: []))
|
||||
private let chunkPlayerPartsState = Promise<ChunkMediaPlayerPartsState>(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()
|
||||
})
|
||||
|
@ -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<Int64>?, 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<Int64>?, 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 {
|
||||
|
@ -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<Bool, NoError> = .single(false), expand: @escaping () -> Void, close: @escaping () -> Void) {
|
||||
public init(context: AccountContext, postbox: Postbox, audioSession: ManagedAudioSession, manager: UniversalVideoManager, content: UniversalVideoContent, shouldBeDismissed: Signal<Bool, NoError> = .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()
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user