From b91a46099b7edb55bad66714a8a8251c339811e5 Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Fri, 3 Jul 2020 12:55:08 +0300 Subject: [PATCH] Video avatar fixes --- .../Sources/AvatarGalleryController.swift | 1 - .../Sources/PeerAvatarImageGalleryItem.swift | 125 +++++++++++------- .../Sources/PeerInfo/PeerInfoHeaderNode.swift | 62 ++++++++- 3 files changed, 134 insertions(+), 54 deletions(-) diff --git a/submodules/PeerAvatarGalleryUI/Sources/AvatarGalleryController.swift b/submodules/PeerAvatarGalleryUI/Sources/AvatarGalleryController.swift index 1bc1b7ec0b..62a93a71f9 100644 --- a/submodules/PeerAvatarGalleryUI/Sources/AvatarGalleryController.swift +++ b/submodules/PeerAvatarGalleryUI/Sources/AvatarGalleryController.swift @@ -646,7 +646,6 @@ public class AvatarGalleryController: ViewController, StandalonePresentableContr // } // |> runOn(Queue.mainQueue()) // |> delay(0.15, queue: Queue.mainQueue()) - self.editDisposable.set((fetchMediaData(context: self.context, postbox: self.context.account.postbox, mediaReference: mediaReference) |> deliverOnMainQueue).start(next: { [weak self] state, isImage in diff --git a/submodules/PeerAvatarGalleryUI/Sources/PeerAvatarImageGalleryItem.swift b/submodules/PeerAvatarGalleryUI/Sources/PeerAvatarImageGalleryItem.swift index e33fa63399..79c776b172 100644 --- a/submodules/PeerAvatarGalleryUI/Sources/PeerAvatarImageGalleryItem.swift +++ b/submodules/PeerAvatarGalleryUI/Sources/PeerAvatarImageGalleryItem.swift @@ -138,6 +138,7 @@ final class PeerAvatarImageGalleryItemNode: ZoomableContentGalleryItemNode { fileprivate let imageNode: TransformImageNode private var videoNode: UniversalVideoNode? private var videoContent: NativeVideoContent? + private var videoStartTimestamp: Double? fileprivate let _ready = Promise() fileprivate let _title = Promise() @@ -217,6 +218,7 @@ final class PeerAvatarImageGalleryItemNode: ZoomableContentGalleryItemNode { fileprivate func setEntry(_ entry: AvatarGalleryEntry, synchronous: Bool) { let previousRepresentations = self.entry?.representations + let previousVideoRepresentations = self.entry?.videoRepresentations if self.entry != entry { self.entry = entry @@ -257,58 +259,32 @@ final class PeerAvatarImageGalleryItemNode: ZoomableContentGalleryItemNode { id = image.0.id } if let video = entry.videoRepresentations.last, let id = id { - let mediaManager = self.context.sharedContext.mediaManager - let videoFileReference = FileMediaReference.standalone(media: TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: 0), partialReference: nil, resource: video.resource, previewRepresentations: representations.map { $0.representation }, videoThumbnails: [], immediateThumbnailData: nil, mimeType: "video/mp4", size: nil, attributes: [.Animated, .Video(duration: 0, size: video.dimensions, flags: [])])) - 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.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 + if video != previousVideoRepresentations?.last { + let mediaManager = self.context.sharedContext.mediaManager + let videoFileReference = FileMediaReference.standalone(media: TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: 0), partialReference: nil, resource: video.resource, previewRepresentations: representations.map { $0.representation }, videoThumbnails: [], immediateThumbnailData: nil, mimeType: "video/mp4", size: nil, attributes: [.Animated, .Video(duration: 0, size: video.dimensions, flags: [])])) + 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.isHidden = true + self.videoStartTimestamp = video.startTimestamp + + self.videoContent = videoContent + self.videoNode = videoNode + + self.playVideoIfCentral() + videoNode.updateLayout(size: largestSize.dimensions.cgSize, transition: .immediate) + + self.contentNode.addSubnode(videoNode) + + self._ready.set(videoNode.ready) } - - videoNode.canAttachContent = true - 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 - - self.contentNode.addSubnode(videoNode) - - self._ready.set(videoNode.ready) } else if let videoNode = self.videoNode { self.videoContent = nil self.videoNode = nil - videoNode.removeFromSupernode() + Queue.mainQueue().after(0.1) { + videoNode.removeFromSupernode() + } } self.imageNode.frame = self.contentNode.bounds @@ -319,6 +295,61 @@ final class PeerAvatarImageGalleryItemNode: ZoomableContentGalleryItemNode { } } + private func playVideoIfCentral() { + guard let videoNode = self.videoNode, self.isCentral else { + return + } + if let _ = self.videoStartTimestamp { + videoNode.isHidden = true + 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 + } + + let hadAttachedContent = videoNode.hasAttachedContext + videoNode.canAttachContent = true + if videoNode.hasAttachedContext { + if let startTimestamp = self.videoStartTimestamp, !hadAttachedContent { + videoNode.seek(startTimestamp) + } + videoNode.play() + } + } + + var isCentral = false + override func centralityUpdated(isCentral: Bool) { + super.centralityUpdated(isCentral: isCentral) + + if self.isCentral != isCentral { + self.isCentral = isCentral + + if isCentral { + self.playVideoIfCentral() + } else if let videoNode = self.videoNode { + videoNode.pause() + } + } + } + override func animateIn(from node: (ASDisplayNode, CGRect, () -> (UIView?, UIView?)), addToTransitionSurface: (UIView) -> Void, completion: @escaping () -> Void) { var transformedFrame = node.0.view.convert(node.0.view.bounds, to: self.contentNode.view) let transformedSuperFrame = node.0.view.convert(node.0.view.bounds, to: self.contentNode.view.superview) diff --git a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoHeaderNode.swift b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoHeaderNode.swift index 6676680c6b..e942bb7473 100644 --- a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoHeaderNode.swift +++ b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoHeaderNode.swift @@ -175,6 +175,7 @@ final class PeerInfoAvatarListItemNode: ASDisplayNode { let imageNode: TransformImageNode private var videoNode: UniversalVideoNode? private var videoContent: NativeVideoContent? + private let playbackStatusDisposable = MetaDisposable() let isReady = Promise() private var didSetReady: Bool = false @@ -190,6 +191,8 @@ final class PeerInfoAvatarListItemNode: ASDisplayNode { } } + var isCentral: Bool = false + init(context: AccountContext) { self.context = context self.imageNode = TransformImageNode() @@ -212,6 +215,16 @@ final class PeerInfoAvatarListItemNode: ASDisplayNode { } } + deinit { + self.playbackStatusDisposable.dispose() + } + + func updateTransitionFraction(_ fraction: CGFloat, transition: ContainedViewLayoutTransition) { + if let videoNode = self.videoNode { + transition.updateAlpha(node: videoNode, alpha: fraction) + } + } + func setup(item: PeerInfoAvatarListItem, synchronous: Bool) { let representations: [ImageRepresentationWithReference] let videoRepresentations: [TelegramMediaImage.VideoRepresentation] @@ -245,6 +258,32 @@ final class PeerInfoAvatarListItemNode: ASDisplayNode { strongSelf.videoNode?.isHidden = !owns } } + + 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 + } + if let startTimestamp = video.startTimestamp { videoNode.seek(startTimestamp) } @@ -768,6 +807,7 @@ final class PeerInfoAvatarTransformContainerNode: ASDisplayNode { fileprivate var videoNode: UniversalVideoNode? private var videoContent: NativeVideoContent? + private var videoStartTimestamp: Double? var tapped: (() -> Void)? @@ -807,6 +847,12 @@ final class PeerInfoAvatarTransformContainerNode: ASDisplayNode { } } + func updateTransitionFraction(_ fraction: CGFloat, transition: ContainedViewLayoutTransition) { + if let videoNode = self.videoNode { + transition.updateAlpha(node: videoNode, alpha: fraction) + } + } + func update(peer: Peer?, item: PeerInfoAvatarListItem?, theme: PresentationTheme, avatarSize: CGFloat, isExpanded: Bool) { if let peer = peer { var overrideImage: AvatarNodeImageOverride? @@ -857,7 +903,11 @@ final class PeerInfoAvatarTransformContainerNode: ASDisplayNode { let update = { videoNode.canAttachContent = !isExpanded if videoNode.canAttachContent { - videoNode.seek(0.0) + if let videoStartTimestamp = self.videoStartTimestamp { + videoNode.seek(videoStartTimestamp) + } else { + videoNode.seek(0.0) + } videoNode.play() } } @@ -1042,11 +1092,7 @@ final class PeerInfoAvatarListNode: ASDisplayNode { func animateAvatarCollapse(transition: ContainedViewLayoutTransition) { if let currentItemNode = self.listContainerNode.currentItemNode, case .animated = transition { if let videoNode = self.avatarContainerNode.videoNode { -// videoNode.position = currentItemNode.imageNode.position -// currentItemNode.addSubnode(videoNode) -// let scale = currentItemNode.imageNode.bounds.height / videoNode.bounds.height -// avatarCopyView.layer.transform = CATransform3DMakeScale(scale, scale, scale) -// self.avatarContainerNode.reattachVideoNode() + } else if let unroundedImage = self.avatarContainerNode.avatarNode.unroundedImage { let avatarCopyView = UIImageView() avatarCopyView.image = unroundedImage @@ -1925,6 +1971,9 @@ final class PeerInfoHeaderNode: ASDisplayNode { transition.updateAlpha(node: self.expandedBackgroundNode, alpha: backgroundTransitionFraction) } + self.avatarListNode.avatarContainerNode.updateTransitionFraction(transitionFraction, transition: transition) + self.listContainerNode.currentItemNode.updateTransitionFraction(transitionFraction, transition: transition) + self.separatorNode.backgroundColor = presentationData.theme.list.itemBlocksSeparatorColor let defaultButtonSize: CGFloat = 40.0 @@ -1992,6 +2041,7 @@ final class PeerInfoHeaderNode: ASDisplayNode { let avatarSize: CGFloat = isModalOverlay ? 200.0 : 100.0 let avatarFrame = CGRect(origin: CGPoint(x: floor((width - avatarSize) / 2.0), y: statusBarHeight + 10.0), size: CGSize(width: avatarSize, height: avatarSize)) let avatarCenter = CGPoint(x: (1.0 - transitionFraction) * avatarFrame.midX + transitionFraction * transitionSourceAvatarFrame.midX, y: (1.0 - transitionFraction) * avatarFrame.midY + transitionFraction * transitionSourceAvatarFrame.midY) + let avatarAlpha = transitionFraction let titleSize = titleNodeLayout[TitleNodeStateRegular]!.size let titleExpandedSize = titleNodeLayout[TitleNodeStateExpanded]!.size