From 8f876636684c2c401a673d76b7b3c6f88ae2d61b Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Wed, 1 Jul 2020 05:39:35 +0300 Subject: [PATCH] Video avatar fixes --- .../MediaPlayer/Sources/MediaPlayer.swift | 8 +-- .../Sources/PeerAvatarImageGalleryItem.swift | 40 +++++++++--- .../Sources/EditSettingsController.swift | 64 +++++++++++++++---- .../Sources/PeerInfo/PeerInfoHeaderNode.swift | 1 + 4 files changed, 90 insertions(+), 23 deletions(-) diff --git a/submodules/MediaPlayer/Sources/MediaPlayer.swift b/submodules/MediaPlayer/Sources/MediaPlayer.swift index 9f3ab99e65..0bd6b3bc18 100644 --- a/submodules/MediaPlayer/Sources/MediaPlayer.swift +++ b/submodules/MediaPlayer/Sources/MediaPlayer.swift @@ -292,12 +292,12 @@ private final class MediaPlayerContext { loadedDuration = duration let status = MediaPlayerStatus(generationTimestamp: CACurrentMediaTime(), duration: duration, dimensions: CGSize(), timestamp: min(max(timestamp, 0.0), duration), baseRate: self.baseRate, seekId: self.seekId, status: .buffering(initial: false, whilePlaying: action == .play), soundEnabled: self.enableSound) self.playerStatus.set(.single(status)) - self.playerStatusValue.swap(status) + let _ = self.playerStatusValue.swap(status) } else { let duration = seekState?.duration ?? 0.0 let status = MediaPlayerStatus(generationTimestamp: CACurrentMediaTime(), duration: duration, dimensions: CGSize(), timestamp: min(max(timestamp, 0.0), duration), baseRate: self.baseRate, seekId: self.seekId, status: .buffering(initial: false, whilePlaying: action == .play), soundEnabled: self.enableSound) self.playerStatus.set(.single(status)) - self.playerStatusValue.swap(status) + let _ = self.playerStatusValue.swap(status) } let frameSource = FFMpegMediaFrameSource(queue: self.queue, postbox: self.postbox, resourceReference: self.resourceReference, tempFilePath: self.tempFilePath, streamable: self.streamable.enabled, video: self.video, preferSoftwareDecoding: self.preferSoftwareDecoding, fetchAutomatically: self.fetchAutomatically, stallDuration: self.streamable.parameters.0, lowWaterDuration: self.streamable.parameters.1, highWaterDuration: self.streamable.parameters.2) @@ -586,7 +586,7 @@ private final class MediaPlayerContext { if case .seeking = self.state, let status = self.playerStatusValue.with({ $0 }) { let status = MediaPlayerStatus(generationTimestamp: CACurrentMediaTime(), duration: status.duration, dimensions: status.dimensions, timestamp: status.timestamp, baseRate: self.baseRate, seekId: self.seekId, status: status.status, soundEnabled: status.soundEnabled) self.playerStatus.set(.single(status)) - self.playerStatusValue.swap(status) + let _ = self.playerStatusValue.swap(status) } } @@ -877,7 +877,7 @@ private final class MediaPlayerContext { } let status = MediaPlayerStatus(generationTimestamp: statusTimestamp, duration: duration, dimensions: CGSize(), timestamp: min(max(reportTimestamp, 0.0), duration), baseRate: self.baseRate, seekId: self.seekId, status: playbackStatus, soundEnabled: self.enableSound) self.playerStatus.set(.single(status)) - self.playerStatusValue.swap(status) + let _ = self.playerStatusValue.swap(status) } if performActionAtEndNow { diff --git a/submodules/PeerAvatarGalleryUI/Sources/PeerAvatarImageGalleryItem.swift b/submodules/PeerAvatarGalleryUI/Sources/PeerAvatarImageGalleryItem.swift index ccefa69e97..99f516a150 100644 --- a/submodules/PeerAvatarGalleryUI/Sources/PeerAvatarImageGalleryItem.swift +++ b/submodules/PeerAvatarGalleryUI/Sources/PeerAvatarImageGalleryItem.swift @@ -137,6 +137,7 @@ final class PeerAvatarImageGalleryItemNode: ZoomableContentGalleryItemNode { private let fetchDisposable = MetaDisposable() private let statusDisposable = MetaDisposable() private var status: MediaResourceStatus? + private let playbackStatusDisposable = MetaDisposable() init(context: AccountContext, presentationData: PresentationData, peer: Peer, sourceHasRoundCorners: Bool) { self.context = context @@ -183,6 +184,7 @@ final class PeerAvatarImageGalleryItemNode: ZoomableContentGalleryItemNode { deinit { self.fetchDisposable.dispose() self.statusDisposable.dispose() + self.playbackStatusDisposable.dispose() } override func ready() -> Signal { @@ -240,22 +242,44 @@ final class PeerAvatarImageGalleryItemNode: ZoomableContentGalleryItemNode { let videoContent = NativeVideoContent(id: .profileVideo(id), fileReference: videoFileReference, streamVideo: isMediaStreamable(resource: video.resource) ? .conservative : .none, loopVideo: true, enableSound: false, fetchAutomatically: true, onlyFullSizeThumbnail: true, continuePlayingWithoutSoundOnLostAudioSession: false, placeholderColor: .clear) let videoNode = UniversalVideoNode(postbox: self.context.account.postbox, audioSession: mediaManager.audioSession, manager: mediaManager.universalVideoManager, decoration: GalleryVideoDecoration(), content: videoContent, priority: .embedded) videoNode.isUserInteractionEnabled = false - videoNode.ownsContentNodeUpdated = { [weak self] owns in - if let strongSelf = self { - strongSelf.videoNode?.isHidden = !owns + videoNode.isHidden = true + + if let _ = video.startTimestamp { + self.playbackStatusDisposable.set((videoNode.status + |> map { status -> Bool in + if let status = status, case .playing = status.status { + return true + } else { + return false + } } + |> filter { playing in + return playing + } + |> take(1) + |> deliverOnMainQueue).start(completed: { [weak self] in + if let strongSelf = self { + Queue.mainQueue().after(0.03) { + strongSelf.videoNode?.isHidden = false + } + } + })) + } else { + self.playbackStatusDisposable.set(nil) + videoNode.isHidden = false } + videoNode.canAttachContent = true - if let startTimestamp = video.startTimestamp { - videoNode.seek(startTimestamp) - } if videoNode.hasAttachedContext { + if let startTimestamp = video.startTimestamp { + videoNode.seek(startTimestamp) + } videoNode.play() } + videoNode.updateLayout(size: largestSize.dimensions.cgSize, transition: .immediate) + self.videoContent = videoContent self.videoNode = videoNode - - videoNode.updateLayout(size: largestSize.dimensions.cgSize, transition: .immediate) self.contentNode.addSubnode(videoNode) diff --git a/submodules/SettingsUI/Sources/EditSettingsController.swift b/submodules/SettingsUI/Sources/EditSettingsController.swift index 1641ad38f4..3b98300ed9 100644 --- a/submodules/SettingsUI/Sources/EditSettingsController.swift +++ b/submodules/SettingsUI/Sources/EditSettingsController.swift @@ -353,6 +353,7 @@ func editSettingsController(context: AccountContext, currentName: ItemListAvatar actionsDisposable.add(hiddenAvatarRepresentationDisposable) let currentAvatarMixin = Atomic(value: nil) + let cachedAvatarEntries = Atomic?>(value: nil) var avatarGalleryTransitionArguments: ((AvatarGalleryEntry) -> GalleryTransitionArguments?)? let avatarAndNameInfoContext = ItemListAvatarAndNameInfoItemContext() @@ -457,6 +458,19 @@ func editSettingsController(context: AccountContext, currentName: ItemListAvatar }) } + let peer = peerViewMainPeer(view) + if let peer = peer { + let _ = cachedAvatarEntries.modify { value in + if value != nil { + return value + } else { + let promise = Promise<[AvatarGalleryEntry]>() + promise.set(fetchedAvatarGalleryEntries(account: context.account, peer: peer)) + return promise + } + } + } + let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: .text(presentationData.strings.EditProfile_Title), leftNavigationButton: nil, rightNavigationButton: rightNavigationButton, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back)) let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: editSettingsEntries(presentationData: presentationData, state: state, view: view, canAddAccounts: canAddAccounts), style: .blocks, ensureVisibleItemTag: focusOnItemTag) @@ -500,9 +514,20 @@ func editSettingsController(context: AccountContext, currentName: ItemListAvatar } } changeProfilePhotoImpl = { [weak controller] in - let _ = (context.account.postbox.transaction { transaction -> (Peer?, SearchBotsConfiguration) in + let avatarEntries = (cachedAvatarEntries.with({ $0 })?.get()) ?? .single([]) + let _ = (combineLatest(context.account.postbox.transaction { transaction -> (Peer?, SearchBotsConfiguration) in return (transaction.getPeer(context.account.peerId), currentSearchBotsConfiguration(transaction: transaction)) - } |> deliverOnMainQueue).start(next: { peer, searchBotsConfiguration in + }, avatarEntries |> take(1)) |> deliverOnMainQueue).start(next: { peerAndSearchBotsConfiguration, avatarEntries in + let peer = peerAndSearchBotsConfiguration.0 + let searchBotsConfiguration = peerAndSearchBotsConfiguration.1 + + let mainIsVideo: Bool + if let main = avatarEntries.first, case let .image(image) = main { + mainIsVideo = !image.3.isEmpty + } else { + mainIsVideo = false + } + controller?.view.endEditing(true) let presentationData = context.sharedContext.currentPresentationData.with { $0 } @@ -540,6 +565,14 @@ func editSettingsController(context: AccountContext, currentName: ItemListAvatar updateState { $0.withUpdatedUpdatingAvatar(nil) } + + if let peer = peer { + let _ = cachedAvatarEntries.modify { value in + let promise = Promise<[AvatarGalleryEntry]>() + promise.set(fetchedAvatarGalleryEntries(account: context.account, peer: peer)) + return promise + } + } case .progress: break } @@ -610,9 +643,7 @@ func editSettingsController(context: AccountContext, currentName: ItemListAvatar disposable.dispose() } } - - - + updateAvatarDisposable.set((signal |> mapToSignal { videoResource in return updateAccountPhoto(account: context.account, resource: photoResource, videoResource: videoResource, videoStartTimestamp: videoStartTimestamp, mapResourceToAvatarSizes: { resource, representations in @@ -624,6 +655,14 @@ func editSettingsController(context: AccountContext, currentName: ItemListAvatar updateState { $0.withUpdatedUpdatingAvatar(nil) } + + if let peer = peer { + let _ = cachedAvatarEntries.modify { value in + let promise = Promise<[AvatarGalleryEntry]>() + promise.set(fetchedAvatarGalleryEntries(account: context.account, peer: peer)) + return promise + } + } case .progress: break } @@ -631,7 +670,7 @@ func editSettingsController(context: AccountContext, currentName: ItemListAvatar } } - let mixin = TGMediaAvatarMenuMixin(context: legacyController.context, parentController: emptyController, hasSearchButton: true, hasDeleteButton: hasPhotos, hasViewButton: hasPhotos, personalPhoto: true, isVideo: false, saveEditedPhotos: false, saveCapturedMedia: false, signup: false)! + let mixin = TGMediaAvatarMenuMixin(context: legacyController.context, parentController: emptyController, hasSearchButton: true, hasDeleteButton: hasPhotos, hasViewButton: hasPhotos, personalPhoto: true, isVideo: mainIsVideo, saveEditedPhotos: false, saveCapturedMedia: false, signup: false)! let _ = currentAvatarMixin.swap(mixin) mixin.requestSearchController = { assetsController in let controller = WebSearchController(context: context, peer: peer, configuration: searchBotsConfiguration, mode: .avatar(initialQuery: nil, completion: { result in @@ -667,6 +706,13 @@ func editSettingsController(context: AccountContext, currentName: ItemListAvatar updateState { $0.withUpdatedUpdatingAvatar(nil) } + if let peer = peer { + let _ = cachedAvatarEntries.modify { value in + let promise = Promise<[AvatarGalleryEntry]>() + promise.set(fetchedAvatarGalleryEntries(account: context.account, peer: peer)) + return promise + } + } case .progress: break } @@ -679,12 +725,8 @@ func editSettingsController(context: AccountContext, currentName: ItemListAvatar |> take(1) |> deliverOnMainQueue).start(next: { peer in if peer.smallProfileImage != nil { - let galleryController = AvatarGalleryController(context: context, peer: peer, replaceRootController: { controller, ready in + let galleryController = AvatarGalleryController(context: context, peer: peer, remoteEntries: cachedAvatarEntries.with { $0 }, replaceRootController: { controller, ready in }) - /*hiddenAvatarRepresentationDisposable.set((galleryController.hiddenMedia |> deliverOnMainQueue).start(next: { entry in - avatarAndNameInfoContext.hiddenAvatarRepresentation = entry?.representations.first - updateHiddenAvatarImpl?() - }))*/ presentControllerImpl?(galleryController, AvatarGalleryControllerPresentationArguments(transitionArguments: { entry in return nil })) diff --git a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoHeaderNode.swift b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoHeaderNode.swift index 6b8899be17..6676680c6b 100644 --- a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoHeaderNode.swift +++ b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoHeaderNode.swift @@ -239,6 +239,7 @@ final class PeerInfoAvatarListItemNode: ASDisplayNode { let videoContent = NativeVideoContent(id: .profileVideo(id), fileReference: videoFileReference, streamVideo: isMediaStreamable(resource: video.resource) ? .conservative : .none, loopVideo: true, enableSound: false, fetchAutomatically: true, onlyFullSizeThumbnail: true, autoFetchFullSizeThumbnail: true, continuePlayingWithoutSoundOnLostAudioSession: false, placeholderColor: .clear) let videoNode = UniversalVideoNode(postbox: self.context.account.postbox, audioSession: mediaManager.audioSession, manager: mediaManager.universalVideoManager, decoration: GalleryVideoDecoration(), content: videoContent, priority: .embedded) videoNode.isUserInteractionEnabled = false + videoNode.isHidden = true videoNode.ownsContentNodeUpdated = { [weak self] owns in if let strongSelf = self { strongSelf.videoNode?.isHidden = !owns