From a911b403ca28a1f46180a113b0d2bbfc5c60798c Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Fri, 17 Jul 2020 12:34:40 +0300 Subject: [PATCH] Video avatar fixes --- .../Sources/TGMediaVideoConverter.m | 2 +- .../Sources/ChatMessageActionItemNode.swift | 4 +- ...atMessageInteractiveInstantVideoNode.swift | 2 +- .../Sources/PeerInfo/PeerInfoHeaderNode.swift | 102 ++++++++++++++++-- 4 files changed, 97 insertions(+), 13 deletions(-) diff --git a/submodules/LegacyComponents/Sources/TGMediaVideoConverter.m b/submodules/LegacyComponents/Sources/TGMediaVideoConverter.m index a42bb2737a..f2387de000 100644 --- a/submodules/LegacyComponents/Sources/TGMediaVideoConverter.m +++ b/submodules/LegacyComponents/Sources/TGMediaVideoConverter.m @@ -1356,7 +1356,7 @@ static CGFloat progressOfSampleBufferInTimeRange(CMSampleBufferRef sampleBuffer, return 2000; case TGMediaVideoConversionPresetProfileVeryHigh: - return 2500; + return 2300; default: return 900; diff --git a/submodules/TelegramUI/Sources/ChatMessageActionItemNode.swift b/submodules/TelegramUI/Sources/ChatMessageActionItemNode.swift index 00fae31474..e12c9fc369 100644 --- a/submodules/TelegramUI/Sources/ChatMessageActionItemNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageActionItemNode.swift @@ -147,7 +147,7 @@ class ChatMessageActionBubbleContentNode: ChatMessageBubbleContentNode { } } - let imageSize = layoutConstants.instantVideo.dimensions + let imageSize = CGSize(width: 212.0, height: 212.0) let (labelLayout, apply) = makeLabelLayout(TextNodeLayoutArguments(attributedString: attributedString, backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: constrainedSize.width - 32.0, height: CGFloat.greatestFiniteMagnitude), alignment: .center, cutout: nil, insets: UIEdgeInsets())) @@ -222,7 +222,7 @@ class ChatMessageActionBubbleContentNode: ChatMessageBubbleContentNode { if let image = image, let video = image.videoRepresentations.last, let id = image.id?.id { let videoFileReference = FileMediaReference.standalone(media: TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: 0), partialReference: nil, resource: video.resource, previewRepresentations: image.representations, videoThumbnails: [], immediateThumbnailData: image.immediateThumbnailData, mimeType: "video/mp4", size: nil, attributes: [.Animated, .Video(duration: 0, size: video.dimensions, flags: [])])) - let videoContent = NativeVideoContent(id: .profileVideo(id, "action"), fileReference: videoFileReference, streamVideo: isMediaStreamable(resource: video.resource) ? .conservative : .none, loopVideo: true, enableSound: false, fetchAutomatically: true, onlyFullSizeThumbnail: false, autoFetchFullSizeThumbnail: true, continuePlayingWithoutSoundOnLostAudioSession: false, placeholderColor: .clear) + let videoContent = NativeVideoContent(id: .profileVideo(id, "action"), 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) if videoContent.id != strongSelf.videoContent?.id { let mediaManager = item.context.sharedContext.mediaManager let videoNode = UniversalVideoNode(postbox: item.context.account.postbox, audioSession: mediaManager.audioSession, manager: mediaManager.universalVideoManager, decoration: GalleryVideoDecoration(), content: videoContent, priority: .secondaryOverlay) diff --git a/submodules/TelegramUI/Sources/ChatMessageInteractiveInstantVideoNode.swift b/submodules/TelegramUI/Sources/ChatMessageInteractiveInstantVideoNode.swift index 7b34b53c9e..87c3688d60 100644 --- a/submodules/TelegramUI/Sources/ChatMessageInteractiveInstantVideoNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageInteractiveInstantVideoNode.swift @@ -519,7 +519,7 @@ class ChatMessageInteractiveInstantVideoNode: ASDisplayNode { } var isBuffering: Bool? - if let message = self.item?.message, let media = self.media, let size = media.size, (isMediaStreamable(message: message, media: media) || size <= 256 * 1024) && (self.automaticDownload ?? false) { + if let message = self.item?.message, let media = self.media, isMediaStreamable(message: message, media: media) && (self.automaticDownload ?? false) { if let playerStatus = self.playerStatus, case .buffering = playerStatus.status { isBuffering = true } else { diff --git a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoHeaderNode.swift b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoHeaderNode.swift index 567095af9c..b60fabd78a 100644 --- a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoHeaderNode.swift +++ b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoHeaderNode.swift @@ -198,8 +198,13 @@ final class PeerInfoAvatarListItemNode: ASDisplayNode { private var videoNode: UniversalVideoNode? private var videoContent: NativeVideoContent? private var videoStartTimestamp: Double? - private let playbackStatusDisposable = MetaDisposable() + private let playbackStartDisposable = MetaDisposable() + private let statusDisposable = MetaDisposable() private let preloadDisposable = MetaDisposable() + private var statusNode: RadialStatusNode? + + private var fetchStatus: MediaResourceStatus? + private var playerStatus: MediaPlayerStatus? let isReady = Promise() private var didSetReady: Bool = false @@ -229,7 +234,7 @@ final class PeerInfoAvatarListItemNode: ASDisplayNode { self.preloadDisposable.set(nil) } else { if let videoNode = self.videoNode { - self.playbackStatusDisposable.set(nil) + self.playbackStartDisposable.set(nil) self.statusPromise.set(.single(nil)) self.videoNode = nil if self.delayCentralityLose { @@ -262,10 +267,76 @@ final class PeerInfoAvatarListItemNode: ASDisplayNode { } deinit { - self.playbackStatusDisposable.dispose() + self.statusDisposable.dispose() + self.playbackStartDisposable.dispose() self.preloadDisposable.dispose() } + private func updateStatus() { + guard let videoContent = self.videoContent, let fetchStatus = self.fetchStatus else { + return + } + + var isBuffering: Bool? + var isPlaying = false + if isMediaStreamable(resource: videoContent.fileReference.media.resource) { + if let playerStatus = self.playerStatus { + if case .buffering = playerStatus.status { + isBuffering = true + } else if case .playing = playerStatus.status { + isPlaying = true + } + } else { + isBuffering = false + } + } + + var progressRequired = false + if case .Local = fetchStatus { + } else if isBuffering ?? false { + progressRequired = true + } else if case .Fetching = fetchStatus, !isPlaying { + progressRequired = true + } else if case .Remote = fetchStatus, !isPlaying { + progressRequired = true + } + + if progressRequired { + if self.statusNode == nil { + let statusNode = RadialStatusNode(backgroundNodeColor: UIColor(rgb: 0x000000, alpha: 0.3)) + statusNode.isUserInteractionEnabled = false + statusNode.frame = CGRect(origin: CGPoint(x: floor((self.frame.size.width - 50.0) / 2.0), y: floor((self.frame.size.height - 50.0) / 2.0)), size: CGSize(width: 50.0, height: 50.0)) + self.statusNode = statusNode + self.addSubnode(statusNode) + } + } else { + if let statusNode = self.statusNode { + statusNode.transitionToState(.none, completion: { [weak statusNode] in + statusNode?.removeFromSupernode() + }) + self.statusNode = nil + } + } + + var state: RadialStatusNodeState + if progressRequired { + state = .progress(color: .white, lineWidth: nil, value: nil, cancelEnabled: false) + } else { + state = .none + } + + if let statusNode = self.statusNode { + if state == .none { + self.statusNode = nil + } + statusNode.transitionToState(state, completion: { [weak statusNode] in + if state == .none { + statusNode?.removeFromSupernode() + } + }) + } + } + func updateTransitionFraction(_ fraction: CGFloat, transition: ContainedViewLayoutTransition) { if let videoNode = self.videoNode { if case .immediate = transition, fraction == 1.0 { @@ -287,7 +358,7 @@ final class PeerInfoAvatarListItemNode: ASDisplayNode { videoNode.isHidden = true if let _ = self.videoStartTimestamp { - self.playbackStatusDisposable.set((videoNode.status + self.playbackStartDisposable.set((videoNode.status |> map { status -> Bool in if let status = status, case .playing = status.status { return true @@ -307,7 +378,7 @@ final class PeerInfoAvatarListItemNode: ASDisplayNode { } })) } else { - self.playbackStatusDisposable.set(nil) + self.playbackStartDisposable.set(nil) videoNode.isHidden = false } videoNode.play() @@ -316,6 +387,17 @@ final class PeerInfoAvatarListItemNode: ASDisplayNode { let videoStartTimestamp = self.videoStartTimestamp self.statusPromise.set(videoNode.status |> map { ($0, videoStartTimestamp) }) + self.statusDisposable.set((combineLatest(self.mediaStatus, self.context.account.postbox.mediaBox.resourceStatus(videoContent.fileReference.media.resource)) + |> deliverOnMainQueue).start(next: { [weak self] mediaStatus, fetchStatus in + if let strongSelf = self { + if let mediaStatusAndStartTimestamp = mediaStatus { + strongSelf.playerStatus = mediaStatusAndStartTimestamp.0 + } + strongSelf.fetchStatus = fetchStatus + strongSelf.updateStatus() + } + })) + self.addSubnode(videoNode) self.isReady.set(videoNode.ready |> map { return true }) @@ -369,6 +451,8 @@ final class PeerInfoAvatarListItemNode: ASDisplayNode { self.statusPromise.set(.single(nil)) + self.statusDisposable.set(nil) + self.imageNode.imageUpdated = { [weak self] _ in guard let strongSelf = self else { return @@ -1186,7 +1270,7 @@ final class PeerInfoAvatarTransformContainerNode: ASDisplayNode { private var isFirstAvatarLoading = true var item: PeerInfoAvatarListItem? - private let playbackStatusDisposable = MetaDisposable() + private let playbackStartDisposable = MetaDisposable() init(context: AccountContext) { self.context = context @@ -1202,7 +1286,7 @@ final class PeerInfoAvatarTransformContainerNode: ASDisplayNode { } deinit { - self.playbackStatusDisposable.dispose() + self.playbackStartDisposable.dispose() } @objc private func tapGesture(_ recognizer: UITapGestureRecognizer) { @@ -1289,7 +1373,7 @@ final class PeerInfoAvatarTransformContainerNode: ASDisplayNode { if let startTimestamp = video.representation.startTimestamp { self.videoStartTimestamp = startTimestamp - self.playbackStatusDisposable.set((videoNode.status + self.playbackStartDisposable.set((videoNode.status |> map { status -> Bool in if let status = status, case .playing = status.status { return true @@ -1310,7 +1394,7 @@ final class PeerInfoAvatarTransformContainerNode: ASDisplayNode { })) } else { self.videoStartTimestamp = nil - self.playbackStatusDisposable.set(nil) + self.playbackStartDisposable.set(nil) videoNode.isHidden = false }