mirror of
https://github.com/TelegramMessenger/Telegram-iOS.git
synced 2026-07-06 03:33:41 +02:00
Video avatar improvements
This commit is contained in:
parent
3a10dd64fc
commit
681dffbee7
7 changed files with 481 additions and 337 deletions
32
submodules/AvatarVideoNode/BUILD
Normal file
32
submodules/AvatarVideoNode/BUILD
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
|
||||
|
||||
swift_library(
|
||||
name = "AvatarVideoNode",
|
||||
module_name = "AvatarVideoNode",
|
||||
srcs = glob([
|
||||
"Sources/**/*.swift",
|
||||
]),
|
||||
copts = [
|
||||
"-warnings-as-errors",
|
||||
],
|
||||
deps = [
|
||||
"//submodules/AsyncDisplayKit:AsyncDisplayKit",
|
||||
"//submodules/Display:Display",
|
||||
"//submodules/Postbox:Postbox",
|
||||
"//submodules/TelegramCore:TelegramCore",
|
||||
"//submodules/TelegramPresentationData:TelegramPresentationData",
|
||||
"//submodules/AnimationUI:AnimationUI",
|
||||
"//submodules/AppBundle:AppBundle",
|
||||
"//submodules/AccountContext:AccountContext",
|
||||
"//submodules/MediaPlayer:UniversalMediaPlayer",
|
||||
"//submodules/TelegramUniversalVideoContent:TelegramUniversalVideoContent",
|
||||
"//submodules/ComponentFlow:ComponentFlow",
|
||||
"//submodules/GradientBackground:GradientBackground",
|
||||
"//submodules/TelegramUI/Components/EntityKeyboard:EntityKeyboard",
|
||||
"//submodules/TelegramUI/Components/AnimationCache:AnimationCache",
|
||||
"//submodules/TelegramUI/Components/MultiAnimationRenderer:MultiAnimationRenderer",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
],
|
||||
)
|
||||
395
submodules/AvatarVideoNode/Sources/AvatarVideoNode.swift
Normal file
395
submodules/AvatarVideoNode/Sources/AvatarVideoNode.swift
Normal file
|
|
@ -0,0 +1,395 @@
|
|||
import Foundation
|
||||
import UIKit
|
||||
import AsyncDisplayKit
|
||||
import Display
|
||||
import SwiftSignalKit
|
||||
import UniversalMediaPlayer
|
||||
import TelegramUniversalVideoContent
|
||||
import Postbox
|
||||
import TelegramCore
|
||||
import AccountContext
|
||||
import ComponentFlow
|
||||
import GradientBackground
|
||||
import AnimationCache
|
||||
import MultiAnimationRenderer
|
||||
import EntityKeyboard
|
||||
|
||||
private let maxVideoLoopCount = 3
|
||||
|
||||
public final class AvatarVideoNode: ASDisplayNode {
|
||||
private let context: AccountContext
|
||||
|
||||
private var backgroundNode: ASImageNode
|
||||
|
||||
private var emojiMarkup: TelegramMediaImage.EmojiMarkup?
|
||||
|
||||
private var fileDisposable: Disposable?
|
||||
private var animationFile: TelegramMediaFile?
|
||||
private var itemLayer: EmojiPagerContentComponent.View.ItemLayer?
|
||||
|
||||
private var videoNode: UniversalVideoNode?
|
||||
private var videoContent: NativeVideoContent?
|
||||
private let playbackStartDisposable = MetaDisposable()
|
||||
private var videoLoopCount = 0
|
||||
|
||||
private var size = CGSize(width: 60.0, height: 60.0)
|
||||
|
||||
public init(context: AccountContext) {
|
||||
self.context = context
|
||||
|
||||
self.backgroundNode = ASImageNode()
|
||||
self.backgroundNode.displaysAsynchronously = false
|
||||
|
||||
super.init()
|
||||
|
||||
self.clipsToBounds = true
|
||||
|
||||
self.addSubnode(self.backgroundNode)
|
||||
}
|
||||
|
||||
deinit {
|
||||
self.fileDisposable?.dispose()
|
||||
self.playbackStartDisposable.dispose()
|
||||
}
|
||||
|
||||
public override func didLoad() {
|
||||
super.didLoad()
|
||||
|
||||
if #available(iOS 13.0, *) {
|
||||
self.layer.cornerCurve = .circular
|
||||
}
|
||||
}
|
||||
|
||||
private func setupAnimation() {
|
||||
guard let animationFile = self.animationFile else {
|
||||
return
|
||||
}
|
||||
|
||||
let itemNativeFitSize = CGSize(width: 128.0, height: 128.0)
|
||||
|
||||
let size = CGSize(width: self.size.width * 0.67, height: self.size.height * 0.67)
|
||||
let itemFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((self.size.width - size.width) / 2.0), y: floorToScreenPixels((self.size.height - size.height) / 2.0)), size: size)
|
||||
|
||||
let animationData = EntityKeyboardAnimationData(file: animationFile)
|
||||
let itemLayer = EmojiPagerContentComponent.View.ItemLayer(
|
||||
item: EmojiPagerContentComponent.Item(
|
||||
animationData: animationData,
|
||||
content: .animation(animationData),
|
||||
itemFile: animationFile,
|
||||
subgroupId: nil,
|
||||
icon: .none,
|
||||
tintMode: animationData.isTemplate ? .primary : .none
|
||||
),
|
||||
context: context,
|
||||
attemptSynchronousLoad: false,
|
||||
content: .animation(animationData),
|
||||
cache: context.animationCache,
|
||||
renderer: context.animationRenderer,
|
||||
placeholderColor: .clear,
|
||||
blurredBadgeColor: .clear,
|
||||
accentIconColor: .white,
|
||||
pointSize: itemNativeFitSize,
|
||||
onUpdateDisplayPlaceholder: { displayPlaceholder, _ in
|
||||
if !displayPlaceholder {
|
||||
print()
|
||||
}
|
||||
// guard let strongSelf = self else {
|
||||
// return
|
||||
// }
|
||||
// if displayPlaceholder {
|
||||
// if let itemLayer = strongSelf.visibleItemLayers[itemId] {
|
||||
// let placeholderView: EmojiPagerContentComponent.View.ItemPlaceholderView
|
||||
// if let current = strongSelf.visibleItemPlaceholderViews[itemId] {
|
||||
// placeholderView = current
|
||||
// } else {
|
||||
// placeholderView = EmojiPagerContentComponent.View.ItemPlaceholderView(
|
||||
// context: context,
|
||||
// dimensions: item.file.dimensions?.cgSize ?? CGSize(width: 512.0, height: 512.0),
|
||||
// immediateThumbnailData: item.file.immediateThumbnailData,
|
||||
// shimmerView: nil,//strongSelf.shimmerHostView,
|
||||
// color: theme.chat.inputPanel.primaryTextColor.withMultipliedAlpha(0.08),
|
||||
// size: itemNativeFitSize
|
||||
// )
|
||||
// strongSelf.visibleItemPlaceholderViews[itemId] = placeholderView
|
||||
// strongSelf.view.insertSubview(placeholderView, at: 0)
|
||||
// }
|
||||
// placeholderView.frame = itemLayer.frame
|
||||
// placeholderView.update(size: placeholderView.bounds.size)
|
||||
//
|
||||
// strongSelf.updateShimmerIfNeeded()
|
||||
// }
|
||||
// } else {
|
||||
// if let placeholderView = strongSelf.visibleItemPlaceholderViews[itemId] {
|
||||
// strongSelf.visibleItemPlaceholderViews.removeValue(forKey: itemId)
|
||||
//
|
||||
// if duration > 0.0 {
|
||||
// placeholderView.layer.opacity = 0.0
|
||||
// placeholderView.layer.animateAlpha(from: 1.0, to: 0.0, duration: duration, completion: { [weak self, weak placeholderView] _ in
|
||||
// guard let strongSelf = self else {
|
||||
// return
|
||||
// }
|
||||
// placeholderView?.removeFromSuperview()
|
||||
// strongSelf.updateShimmerIfNeeded()
|
||||
// })
|
||||
// } else {
|
||||
// placeholderView.removeFromSuperview()
|
||||
// strongSelf.updateShimmerIfNeeded()
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
}
|
||||
)
|
||||
itemLayer.onContentsUpdate = { [weak self] in
|
||||
self?.backgroundNode.isHidden = false
|
||||
}
|
||||
itemLayer.layerTintColor = UIColor.white.cgColor
|
||||
itemLayer.frame = itemFrame
|
||||
itemLayer.isVisibleForAnimations = true
|
||||
self.itemLayer = itemLayer
|
||||
self.backgroundNode.layer.addSublayer(itemLayer)
|
||||
}
|
||||
|
||||
public func update(peer: EnginePeer, photo: TelegramMediaImage, size: CGSize) {
|
||||
self.size = size
|
||||
if let markup = photo.emojiMarkup {
|
||||
if markup != self.emojiMarkup {
|
||||
self.emojiMarkup = markup
|
||||
|
||||
let colors = markup.backgroundColors.map { UInt32(bitPattern: $0) }
|
||||
let backgroundImage: UIImage
|
||||
if colors.count == 1 {
|
||||
backgroundImage = generateSingleColorImage(size: size, color: UIColor(rgb: colors.first!))!
|
||||
} else if colors.count == 2 {
|
||||
backgroundImage = generateGradientImage(size: size, colors: colors.map { UIColor(rgb: $0) }, locations: [0.0, 1.0])!
|
||||
} else {
|
||||
backgroundImage = GradientBackgroundNode.generatePreview(size: size, colors: colors.map { UIColor(rgb: $0) })
|
||||
}
|
||||
self.backgroundNode.image = backgroundImage
|
||||
self.backgroundNode.isHidden = true
|
||||
|
||||
switch markup.content {
|
||||
case let .emoji(fileId):
|
||||
self.fileDisposable = (self.context.engine.stickers.resolveInlineStickers(fileIds: [fileId])
|
||||
|> deliverOnMainQueue).start(next: { [weak self] files in
|
||||
if let strongSelf = self, let file = files.values.first {
|
||||
strongSelf.animationFile = file
|
||||
strongSelf.setupAnimation()
|
||||
}
|
||||
})
|
||||
case let .sticker(packReference, fileId):
|
||||
self.fileDisposable = (self.context.engine.stickers.loadedStickerPack(reference: packReference, forceActualized: false)
|
||||
|> map { pack -> TelegramMediaFile? in
|
||||
if case let .result(_, items, _) = pack, let item = items.first(where: { $0.file.fileId.id == fileId }) {
|
||||
return item.file
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|> deliverOnMainQueue).start(next: { [weak self] file in
|
||||
if let strongSelf = self, let file {
|
||||
strongSelf.animationFile = file
|
||||
strongSelf.setupAnimation()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
} else if let video = smallestVideoRepresentation(photo.videoRepresentations), let peerReference = PeerReference(peer._asPeer()) {
|
||||
self.backgroundNode.image = nil
|
||||
|
||||
let videoId = photo.id?.id ?? peer.id.id._internalGetInt64Value()
|
||||
let videoFileReference = FileMediaReference.avatarList(peer: peerReference, media: TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: 0), partialReference: nil, resource: video.resource, previewRepresentations: photo.representations, videoThumbnails: [], immediateThumbnailData: photo.immediateThumbnailData, mimeType: "video/mp4", size: nil, attributes: [.Animated, .Video(duration: 0, size: video.dimensions, flags: [])]))
|
||||
let videoContent = NativeVideoContent(id: .profileVideo(videoId, nil), userLocation: .other, fileReference: videoFileReference, streamVideo: isMediaStreamable(resource: video.resource) ? .conservative : .none, loopVideo: true, enableSound: false, fetchAutomatically: true, onlyFullSizeThumbnail: false, useLargeThumbnail: true, autoFetchFullSizeThumbnail: true, startTimestamp: video.startTimestamp, continuePlayingWithoutSoundOnLostAudioSession: false, placeholderColor: .clear, captureProtected: false, storeAfterDownload: nil)
|
||||
if videoContent.id != self.videoContent?.id {
|
||||
self.videoNode?.removeFromSupernode()
|
||||
self.videoContent = videoContent
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func updateVisibility(_ isVisible: Bool) {
|
||||
if isVisible, let videoContent = self.videoContent, self.videoLoopCount != maxVideoLoopCount {
|
||||
if self.videoNode == nil {
|
||||
let context = self.context
|
||||
let mediaManager = context.sharedContext.mediaManager
|
||||
let videoNode = UniversalVideoNode(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
|
||||
videoNode.playbackCompleted = { [weak self] in
|
||||
if let strongSelf = self {
|
||||
strongSelf.videoLoopCount += 1
|
||||
if strongSelf.videoLoopCount == maxVideoLoopCount {
|
||||
if let videoNode = strongSelf.videoNode {
|
||||
strongSelf.videoNode = nil
|
||||
videoNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak videoNode] _ in
|
||||
videoNode?.removeFromSupernode()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let _ = videoContent.startTimestamp {
|
||||
self.playbackStartDisposable.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.15) {
|
||||
strongSelf.videoNode?.isHidden = false
|
||||
}
|
||||
}
|
||||
}))
|
||||
} else {
|
||||
self.playbackStartDisposable.set(nil)
|
||||
videoNode.isHidden = false
|
||||
}
|
||||
videoNode.canAttachContent = true
|
||||
videoNode.play()
|
||||
|
||||
self.addSubnode(videoNode)
|
||||
self.videoNode = videoNode
|
||||
}
|
||||
} else if let videoNode = self.videoNode {
|
||||
self.videoNode = nil
|
||||
videoNode.removeFromSupernode()
|
||||
}
|
||||
}
|
||||
|
||||
public func updateLayout(size: CGSize, cornerRadius: CGFloat, transition: ContainedViewLayoutTransition) {
|
||||
self.layer.cornerRadius = cornerRadius
|
||||
|
||||
self.backgroundNode.frame = CGRect(origin: .zero, size: size)
|
||||
|
||||
if let videoNode = self.videoNode {
|
||||
videoNode.frame = CGRect(origin: .zero, size: size)
|
||||
videoNode.updateLayout(size: size, transition: transition)
|
||||
}
|
||||
}
|
||||
|
||||
public func resetPlayback() {
|
||||
self.videoLoopCount = 0
|
||||
}
|
||||
}
|
||||
|
||||
private final class VideoDecoration: UniversalVideoDecoration {
|
||||
public let backgroundNode: ASDisplayNode? = nil
|
||||
public let contentContainerNode: ASDisplayNode
|
||||
public let foregroundNode: ASDisplayNode? = nil
|
||||
|
||||
private var contentNode: (ASDisplayNode & UniversalVideoContentNode)?
|
||||
|
||||
private var validLayoutSize: CGSize?
|
||||
|
||||
public init() {
|
||||
self.contentContainerNode = ASDisplayNode()
|
||||
}
|
||||
|
||||
public func updateContentNode(_ contentNode: (UniversalVideoContentNode & ASDisplayNode)?) {
|
||||
if self.contentNode !== contentNode {
|
||||
let previous = self.contentNode
|
||||
self.contentNode = contentNode
|
||||
|
||||
if let previous = previous {
|
||||
if previous.supernode === self.contentContainerNode {
|
||||
previous.removeFromSupernode()
|
||||
}
|
||||
}
|
||||
|
||||
if let contentNode = contentNode {
|
||||
if contentNode.supernode !== self.contentContainerNode {
|
||||
self.contentContainerNode.addSubnode(contentNode)
|
||||
if let validLayoutSize = self.validLayoutSize {
|
||||
contentNode.frame = CGRect(origin: CGPoint(), size: validLayoutSize)
|
||||
contentNode.updateLayout(size: validLayoutSize, transition: .immediate)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func updateCorners(_ corners: ImageCorners) {
|
||||
self.contentContainerNode.clipsToBounds = true
|
||||
if isRoundEqualCorners(corners) {
|
||||
self.contentContainerNode.cornerRadius = corners.topLeft.radius
|
||||
} else {
|
||||
let boundingSize: CGSize = CGSize(width: max(corners.topLeft.radius, corners.bottomLeft.radius) + max(corners.topRight.radius, corners.bottomRight.radius), height: max(corners.topLeft.radius, corners.topRight.radius) + max(corners.bottomLeft.radius, corners.bottomRight.radius))
|
||||
let size: CGSize = CGSize(width: boundingSize.width + corners.extendedEdges.left + corners.extendedEdges.right, height: boundingSize.height + corners.extendedEdges.top + corners.extendedEdges.bottom)
|
||||
let arguments = TransformImageArguments(corners: corners, imageSize: size, boundingSize: boundingSize, intrinsicInsets: UIEdgeInsets())
|
||||
guard let context = DrawingContext(size: size, clear: true) else {
|
||||
return
|
||||
}
|
||||
context.withContext { ctx in
|
||||
ctx.setFillColor(UIColor.black.cgColor)
|
||||
ctx.fill(arguments.drawingRect)
|
||||
}
|
||||
addCorners(context, arguments: arguments)
|
||||
|
||||
if let maskImage = context.generateImage() {
|
||||
let mask = CALayer()
|
||||
mask.contents = maskImage.cgImage
|
||||
mask.contentsScale = maskImage.scale
|
||||
mask.contentsCenter = CGRect(x: max(corners.topLeft.radius, corners.bottomLeft.radius) / maskImage.size.width, y: max(corners.topLeft.radius, corners.topRight.radius) / maskImage.size.height, width: (maskImage.size.width - max(corners.topLeft.radius, corners.bottomLeft.radius) - max(corners.topRight.radius, corners.bottomRight.radius)) / maskImage.size.width, height: (maskImage.size.height - max(corners.topLeft.radius, corners.topRight.radius) - max(corners.bottomLeft.radius, corners.bottomRight.radius)) / maskImage.size.height)
|
||||
|
||||
self.contentContainerNode.layer.mask = mask
|
||||
self.contentContainerNode.layer.mask?.frame = self.contentContainerNode.bounds
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func updateClippingFrame(_ frame: CGRect, completion: (() -> Void)?) {
|
||||
self.contentContainerNode.layer.animate(from: NSValue(cgRect: self.contentContainerNode.bounds), to: NSValue(cgRect: frame), keyPath: "bounds", timingFunction: kCAMediaTimingFunctionSpring, duration: 0.25, removeOnCompletion: false, completion: { _ in
|
||||
})
|
||||
|
||||
if let maskLayer = self.contentContainerNode.layer.mask {
|
||||
maskLayer.animate(from: NSValue(cgRect: self.contentContainerNode.bounds), to: NSValue(cgRect: frame), keyPath: "bounds", timingFunction: kCAMediaTimingFunctionSpring, duration: 0.25, removeOnCompletion: false, completion: { _ in
|
||||
})
|
||||
|
||||
maskLayer.animate(from: NSValue(cgPoint: maskLayer.position), to: NSValue(cgPoint: frame.center), keyPath: "position", timingFunction: kCAMediaTimingFunctionSpring, duration: 0.25, removeOnCompletion: false, completion: { _ in
|
||||
})
|
||||
}
|
||||
|
||||
if let contentNode = self.contentNode {
|
||||
contentNode.layer.animate(from: NSValue(cgPoint: contentNode.layer.position), to: NSValue(cgPoint: frame.center), keyPath: "position", timingFunction: kCAMediaTimingFunctionSpring, duration: 0.25, removeOnCompletion: false, completion: { _ in
|
||||
completion?()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
public func updateContentNodeSnapshot(_ snapshot: UIView?) {
|
||||
}
|
||||
|
||||
public func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) {
|
||||
self.validLayoutSize = size
|
||||
|
||||
let bounds = CGRect(origin: CGPoint(), size: size)
|
||||
if let backgroundNode = self.backgroundNode {
|
||||
transition.updateFrame(node: backgroundNode, frame: bounds)
|
||||
}
|
||||
if let foregroundNode = self.foregroundNode {
|
||||
transition.updateFrame(node: foregroundNode, frame: bounds)
|
||||
}
|
||||
transition.updateFrame(node: self.contentContainerNode, frame: bounds)
|
||||
if let maskLayer = self.contentContainerNode.layer.mask {
|
||||
transition.updateFrame(layer: maskLayer, frame: bounds)
|
||||
}
|
||||
if let contentNode = self.contentNode {
|
||||
transition.updateFrame(node: contentNode, frame: CGRect(origin: CGPoint(), size: size))
|
||||
contentNode.updateLayout(size: size, transition: transition)
|
||||
}
|
||||
}
|
||||
|
||||
public func setStatus(_ status: Signal<MediaPlayerStatus?, NoError>) {
|
||||
}
|
||||
|
||||
public func tap() {
|
||||
}
|
||||
}
|
||||
|
|
@ -86,6 +86,8 @@ swift_library(
|
|||
"//submodules/PeerInfoUI",
|
||||
"//submodules/TelegramUI/Components/ChatListHeaderComponent",
|
||||
"//submodules/TelegramUI/Components/ChatListTitleView",
|
||||
"//submodules/AvatarNode:AvatarNode",
|
||||
"//submodules/AvatarVideoNode:AvatarVideoNode",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
|
|
|||
|
|
@ -20,13 +20,12 @@ import ContextUI
|
|||
import ChatInterfaceState
|
||||
import TextFormat
|
||||
import InvisibleInkDustNode
|
||||
import TelegramUniversalVideoContent
|
||||
import UniversalMediaPlayer
|
||||
import GalleryUI
|
||||
import HierarchyTrackingLayer
|
||||
import TextNodeWithEntities
|
||||
import ComponentFlow
|
||||
import EmojiStatusComponent
|
||||
import AvatarVideoNode
|
||||
|
||||
public enum ChatListItemContent {
|
||||
public struct ThreadInfo: Equatable {
|
||||
|
|
@ -612,8 +611,6 @@ private final class ChatListMediaPreviewNode: ASDisplayNode {
|
|||
}
|
||||
}
|
||||
|
||||
private let maxVideoLoopCount = 3
|
||||
|
||||
class ChatListItemNode: ItemListRevealOptionsItemNode {
|
||||
final class TopicItemNode: ASDisplayNode {
|
||||
let topicTitleNode: TextNode
|
||||
|
|
@ -882,10 +879,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
|
|||
let avatarNode: AvatarNode
|
||||
var avatarIconView: ComponentHostView<Empty>?
|
||||
var avatarIconComponent: EmojiStatusComponent?
|
||||
var videoNode: UniversalVideoNode?
|
||||
private var videoContent: NativeVideoContent?
|
||||
private let playbackStartDisposable = MetaDisposable()
|
||||
private var videoLoopCount = 0
|
||||
var avatarVideoNode: AvatarVideoNode?
|
||||
|
||||
private var inlineNavigationMarkLayer: SimpleLayer?
|
||||
|
||||
|
|
@ -1084,7 +1078,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
|
|||
didSet {
|
||||
if self.visibilityStatus != oldValue {
|
||||
if self.visibilityStatus {
|
||||
self.videoLoopCount = 0
|
||||
self.avatarVideoNode?.resetPlayback()
|
||||
}
|
||||
self.updateVideoVisibility()
|
||||
|
||||
|
|
@ -1116,7 +1110,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
|
|||
if self.trackingIsInHierarchy != oldValue {
|
||||
Queue.mainQueue().justDispatch {
|
||||
if self.trackingIsInHierarchy {
|
||||
self.videoLoopCount = 0
|
||||
self.avatarVideoNode?.resetPlayback()
|
||||
}
|
||||
self.updateVideoVisibility()
|
||||
}
|
||||
|
|
@ -1249,7 +1243,6 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
|
|||
|
||||
deinit {
|
||||
self.cachedDataDisposable.dispose()
|
||||
self.playbackStartDisposable.dispose()
|
||||
}
|
||||
|
||||
override func secondaryAction(at point: CGPoint) {
|
||||
|
|
@ -1300,7 +1293,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
|
|||
}
|
||||
self.avatarNode.setPeer(context: item.context, theme: item.presentationData.theme, peer: peer, overrideImage: overrideImage, emptyColor: item.presentationData.theme.list.mediaPlaceholderColor, clipStyle: isForum ? .roundedRect : .round, synchronousLoad: synchronousLoads, displayDimensions: CGSize(width: 60.0, height: 60.0))
|
||||
|
||||
if peer.isPremium && peer.id != item.context.account.peerId {
|
||||
if (peer.isPremium || "".isEmpty) && peer.id != item.context.account.peerId {
|
||||
let context = item.context
|
||||
self.cachedDataDisposable.set((context.account.postbox.peerView(id: peer.id)
|
||||
|> deliverOnMainQueue).start(next: { [weak self] peerView in
|
||||
|
|
@ -1325,14 +1318,16 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
|
|||
|
||||
if isKnown {
|
||||
let photo = personalPhoto ?? profilePhoto
|
||||
if let photo = photo, let video = smallestVideoRepresentation(photo.videoRepresentations), let peerReference = PeerReference(peer._asPeer()) {
|
||||
let videoId = photo.id?.id ?? peer.id.id._internalGetInt64Value()
|
||||
let videoFileReference = FileMediaReference.avatarList(peer: peerReference, media: TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: 0), partialReference: nil, resource: video.resource, previewRepresentations: photo.representations, videoThumbnails: [], immediateThumbnailData: photo.immediateThumbnailData, mimeType: "video/mp4", size: nil, attributes: [.Animated, .Video(duration: 0, size: video.dimensions, flags: [])]))
|
||||
let videoContent = NativeVideoContent(id: .profileVideo(videoId, nil), userLocation: .other, fileReference: videoFileReference, streamVideo: isMediaStreamable(resource: video.resource) ? .conservative : .none, loopVideo: true, enableSound: false, fetchAutomatically: true, onlyFullSizeThumbnail: false, useLargeThumbnail: true, autoFetchFullSizeThumbnail: true, startTimestamp: video.startTimestamp, continuePlayingWithoutSoundOnLostAudioSession: false, placeholderColor: .clear, captureProtected: false, storeAfterDownload: nil)
|
||||
if videoContent.id != strongSelf.videoContent?.id {
|
||||
strongSelf.videoNode?.removeFromSupernode()
|
||||
strongSelf.videoContent = videoContent
|
||||
if let photo = photo, !photo.videoRepresentations.isEmpty || photo.emojiMarkup != nil {
|
||||
let videoNode: AvatarVideoNode
|
||||
if let current = strongSelf.avatarVideoNode {
|
||||
videoNode = current
|
||||
} else {
|
||||
videoNode = AvatarVideoNode(context: item.context)
|
||||
strongSelf.avatarNode.addSubnode(videoNode)
|
||||
strongSelf.avatarVideoNode = videoNode
|
||||
}
|
||||
videoNode.update(peer: peer, photo: photo, size: CGSize(width: 60.0, height: 60.0))
|
||||
|
||||
if strongSelf.hierarchyTrackingLayer == nil {
|
||||
let hierarchyTrackingLayer = HierarchyTrackingLayer()
|
||||
|
|
@ -1353,12 +1348,13 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
|
|||
strongSelf.layer.addSublayer(hierarchyTrackingLayer)
|
||||
}
|
||||
} else {
|
||||
strongSelf.videoContent = nil
|
||||
|
||||
if let avatarVideoNode = strongSelf.avatarVideoNode {
|
||||
avatarVideoNode.removeFromSupernode()
|
||||
strongSelf.avatarVideoNode = nil
|
||||
}
|
||||
strongSelf.hierarchyTrackingLayer?.removeFromSuperlayer()
|
||||
strongSelf.hierarchyTrackingLayer = nil
|
||||
}
|
||||
|
||||
}
|
||||
strongSelf.updateVideoVisibility()
|
||||
} else {
|
||||
let _ = context.engine.peers.fetchAndUpdateCachedPeerData(peerId: peer.id).start()
|
||||
|
|
@ -1366,10 +1362,9 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
|
|||
}))
|
||||
} else {
|
||||
self.cachedDataDisposable.set(nil)
|
||||
self.videoContent = nil
|
||||
|
||||
self.videoNode?.removeFromSupernode()
|
||||
self.videoNode = nil
|
||||
self.avatarVideoNode?.removeFromSupernode()
|
||||
self.avatarVideoNode = nil
|
||||
|
||||
self.hierarchyTrackingLayer?.removeFromSuperlayer()
|
||||
self.hierarchyTrackingLayer = nil
|
||||
|
|
@ -3435,76 +3430,11 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
|
|||
}
|
||||
|
||||
private func updateVideoVisibility() {
|
||||
guard let item = self.item else {
|
||||
return
|
||||
}
|
||||
|
||||
let isVisible = self.visibilityStatus && self.trackingIsInHierarchy
|
||||
if isVisible, let videoContent = self.videoContent, self.videoLoopCount != maxVideoLoopCount {
|
||||
if self.videoNode == nil {
|
||||
let context = item.context
|
||||
let mediaManager = context.sharedContext.mediaManager
|
||||
let videoNode = UniversalVideoNode(postbox: context.account.postbox, audioSession: mediaManager.audioSession, manager: mediaManager.universalVideoManager, decoration: GalleryVideoDecoration(), content: videoContent, priority: .embedded)
|
||||
videoNode.clipsToBounds = true
|
||||
videoNode.isUserInteractionEnabled = false
|
||||
videoNode.isHidden = true
|
||||
videoNode.playbackCompleted = { [weak self] in
|
||||
if let strongSelf = self {
|
||||
strongSelf.videoLoopCount += 1
|
||||
if strongSelf.videoLoopCount == maxVideoLoopCount {
|
||||
if let videoNode = strongSelf.videoNode {
|
||||
strongSelf.videoNode = nil
|
||||
videoNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak videoNode] _ in
|
||||
videoNode?.removeFromSupernode()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let _ = videoContent.startTimestamp {
|
||||
self.playbackStartDisposable.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.15) {
|
||||
strongSelf.videoNode?.isHidden = false
|
||||
}
|
||||
}
|
||||
}))
|
||||
} else {
|
||||
self.playbackStartDisposable.set(nil)
|
||||
videoNode.isHidden = false
|
||||
}
|
||||
videoNode.layer.cornerRadius = self.avatarNode.bounds.size.width / 2.0
|
||||
if #available(iOS 13.0, *) {
|
||||
videoNode.layer.cornerCurve = .circular
|
||||
}
|
||||
|
||||
videoNode.canAttachContent = true
|
||||
videoNode.play()
|
||||
|
||||
// self.mainContentContainerNode.insertSubnode(videoNode, aboveSubnode: self.avatarNode)
|
||||
self.avatarNode.addSubnode(videoNode)
|
||||
self.videoNode = videoNode
|
||||
}
|
||||
} else if let videoNode = self.videoNode {
|
||||
self.videoNode = nil
|
||||
videoNode.removeFromSupernode()
|
||||
}
|
||||
|
||||
if let videoNode = self.videoNode {
|
||||
videoNode.updateLayout(size: self.avatarNode.frame.size, transition: .immediate)
|
||||
self.avatarVideoNode?.updateVisibility(isVisible)
|
||||
|
||||
if let videoNode = self.avatarVideoNode {
|
||||
videoNode.updateLayout(size: self.avatarNode.frame.size, cornerRadius: self.avatarNode.frame.size.width / 2.0, transition: .immediate)
|
||||
videoNode.frame = self.avatarNode.bounds
|
||||
}
|
||||
}
|
||||
|
|
@ -3513,159 +3443,6 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
|
|||
super.updateRevealOffset(offset: offset, transition: transition)
|
||||
|
||||
transition.updateBounds(node: self.contextContainer, bounds: self.contextContainer.frame.offsetBy(dx: -offset, dy: 0.0))
|
||||
|
||||
/*if let item = self.item, let params = self.layoutParams?.5, let currentItemHeight = self.currentItemHeight, let countersSize = self.layoutParams?.6 {
|
||||
let editingOffset: CGFloat
|
||||
if let selectableControlNode = self.selectableControlNode {
|
||||
editingOffset = selectableControlNode.bounds.size.width
|
||||
var selectableControlFrame = selectableControlNode.frame
|
||||
selectableControlFrame.origin.x = params.leftInset + offset
|
||||
transition.updateFrame(node: selectableControlNode, frame: selectableControlFrame)
|
||||
} else {
|
||||
editingOffset = 0.0
|
||||
}
|
||||
|
||||
let layoutOffset: CGFloat = 0.0
|
||||
|
||||
if let reorderControlNode = self.reorderControlNode {
|
||||
var reorderControlFrame = reorderControlNode.frame
|
||||
reorderControlFrame.origin.x = params.width - params.rightInset - reorderControlFrame.size.width + offset
|
||||
transition.updateFrame(node: reorderControlNode, frame: reorderControlFrame)
|
||||
}
|
||||
|
||||
let avatarDiameter = min(60.0, floor(item.presentationData.fontSize.baseDisplaySize * 60.0 / 17.0))
|
||||
|
||||
let avatarLeftInset: CGFloat
|
||||
if case .forum = item.index {
|
||||
avatarLeftInset = 50.0
|
||||
} else {
|
||||
avatarLeftInset = 18.0 + avatarDiameter
|
||||
}
|
||||
|
||||
let leftInset: CGFloat = params.leftInset + avatarLeftInset
|
||||
|
||||
let rawContentWidth = params.width - leftInset - params.rightInset - 10.0 - editingOffset
|
||||
let rawContentRect = CGRect(origin: CGPoint(x: 2.0, y: layoutOffset + 8.0), size: CGSize(width: rawContentWidth, height: currentItemHeight - 12.0 - 9.0))
|
||||
|
||||
let contentRect = rawContentRect.offsetBy(dx: editingOffset + leftInset + offset, dy: 0.0)
|
||||
|
||||
var avatarFrame = self.avatarNode.frame
|
||||
avatarFrame.origin.x = leftInset - avatarLeftInset + editingOffset + 10.0 + offset
|
||||
transition.updateFrame(node: self.avatarNode, frame: avatarFrame)
|
||||
if let videoNode = self.videoNode {
|
||||
transition.updateFrame(node: videoNode, frame: CGRect(origin: .zero, size: avatarFrame.size))
|
||||
}
|
||||
|
||||
if let avatarIconView = self.avatarIconView {
|
||||
var avatarIconFrame = avatarIconView.frame
|
||||
avatarIconFrame.origin.x = params.leftInset + floor((leftInset - params.leftInset - avatarIconFrame.width) / 2.0) + offset
|
||||
transition.updateFrame(view: avatarIconView, frame: avatarIconFrame)
|
||||
}
|
||||
|
||||
var onlineFrame = self.onlineNode.frame
|
||||
if self.onlineIsVoiceChat {
|
||||
onlineFrame.origin.x = avatarFrame.maxX - onlineFrame.width + 1.0 - UIScreenPixel
|
||||
} else {
|
||||
onlineFrame.origin.x = avatarFrame.maxX - onlineFrame.width - 2.0
|
||||
}
|
||||
transition.updateFrame(node: self.onlineNode, frame: onlineFrame)
|
||||
|
||||
var titleOffset: CGFloat = 0.0
|
||||
if let secretIconNode = self.secretIconNode, let image = secretIconNode.image {
|
||||
transition.updateFrame(node: secretIconNode, frame: CGRect(origin: CGPoint(x: contentRect.minX, y: secretIconNode.frame.minY), size: image.size))
|
||||
titleOffset += image.size.width + 3.0
|
||||
}
|
||||
|
||||
let titleFrame = self.titleNode.frame
|
||||
transition.updateFrameAdditive(node: self.titleNode, frame: CGRect(origin: CGPoint(x: contentRect.origin.x + titleOffset, y: titleFrame.origin.y), size: titleFrame.size))
|
||||
|
||||
var authorFrame = self.authorNode.frame
|
||||
authorFrame = CGRect(origin: CGPoint(x: contentRect.origin.x - 1.0, y: authorFrame.origin.y), size: authorFrame.size)
|
||||
transition.updateFrame(node: self.authorNode, frame: authorFrame)
|
||||
|
||||
if let compoundHighlightingNode = self.compoundHighlightingNode {
|
||||
let compoundHighlightingFrame = compoundHighlightingNode.frame
|
||||
transition.updateFrame(node: compoundHighlightingNode, frame: CGRect(origin: CGPoint(x: authorFrame.minX, y: compoundHighlightingFrame.origin.y), size: compoundHighlightingFrame.size))
|
||||
}
|
||||
|
||||
transition.updateFrame(node: self.inputActivitiesNode, frame: CGRect(origin: CGPoint(x: contentRect.origin.x, y: self.inputActivitiesNode.frame.minY), size: self.inputActivitiesNode.bounds.size))
|
||||
|
||||
var textFrame = self.textNode.textNode.frame
|
||||
textFrame.origin.x = contentRect.origin.x
|
||||
transition.updateFrameAdditive(node: self.textNode.textNode, frame: textFrame)
|
||||
|
||||
if let dustNode = self.dustNode {
|
||||
transition.updateFrameAdditive(node: dustNode, frame: textFrame.insetBy(dx: -3.0, dy: -3.0).offsetBy(dx: 0.0, dy: 3.0))
|
||||
}
|
||||
|
||||
var mediaPreviewOffsetX = textFrame.origin.x
|
||||
let contentImageSpacing: CGFloat = 2.0
|
||||
for (_, media, mediaSize) in self.currentMediaPreviewSpecs {
|
||||
guard let mediaId = media.id else {
|
||||
continue
|
||||
}
|
||||
if let previewNode = self.mediaPreviewNodes[mediaId] {
|
||||
transition.updateFrameAdditive(node: previewNode, frame: CGRect(origin: CGPoint(x: mediaPreviewOffsetX, y: previewNode.frame.minY), size: mediaSize))
|
||||
}
|
||||
mediaPreviewOffsetX += mediaSize.width + contentImageSpacing
|
||||
}
|
||||
|
||||
let dateFrame = self.dateNode.frame
|
||||
transition.updateFrame(node: self.dateNode, frame: CGRect(origin: CGPoint(x: contentRect.origin.x + contentRect.size.width - dateFrame.size.width, y: dateFrame.minY), size: dateFrame.size))
|
||||
|
||||
let statusFrame = self.statusNode.frame
|
||||
|
||||
var statusOffset: CGFloat = 0.0
|
||||
if let dateStatusIconNode = self.dateStatusIconNode, let dateIconImage = dateStatusIconNode.image {
|
||||
statusOffset += 2.0 + dateIconImage.size.width + 4.0
|
||||
var dateStatusX: CGFloat = contentRect.origin.x
|
||||
dateStatusX += contentRect.size.width
|
||||
dateStatusX += -dateFrame.size.width - 4.0 - dateIconImage.size.width
|
||||
|
||||
let dateStatusY: CGFloat = dateStatusIconNode.frame.minY
|
||||
|
||||
transition.updateFrame(node: dateStatusIconNode, frame: CGRect(origin: CGPoint(x: dateStatusX, y: dateStatusY), size: dateIconImage.size))
|
||||
}
|
||||
|
||||
transition.updateFrame(node: self.statusNode, frame: CGRect(origin: CGPoint(x: contentRect.origin.x + contentRect.size.width - dateFrame.size.width - statusFrame.size.width - statusOffset, y: statusFrame.minY), size: statusFrame.size))
|
||||
|
||||
var nextTitleIconOrigin: CGFloat = contentRect.origin.x + titleFrame.size.width + 3.0 + titleOffset
|
||||
|
||||
if let credibilityIconView = self.credibilityIconView {
|
||||
transition.updateFrame(view: credibilityIconView, frame: CGRect(origin: CGPoint(x: nextTitleIconOrigin, y: credibilityIconView.frame.origin.y), size: credibilityIconView.bounds.size))
|
||||
nextTitleIconOrigin += credibilityIconView.bounds.size.width + 4.0
|
||||
}
|
||||
|
||||
let mutedIconFrame = self.mutedIconNode.frame
|
||||
transition.updateFrameAdditive(node: self.mutedIconNode, frame: CGRect(origin: CGPoint(x: nextTitleIconOrigin - 5.0, y: mutedIconFrame.minY), size: mutedIconFrame.size))
|
||||
nextTitleIconOrigin += mutedIconFrame.size.width + 3.0
|
||||
|
||||
let badgeFrame = self.badgeNode.frame
|
||||
let updatedBadgeFrame = CGRect(origin: CGPoint(x: contentRect.maxX - badgeFrame.size.width, y: contentRect.maxY - badgeFrame.size.height - 2.0), size: badgeFrame.size)
|
||||
transition.updateFrame(node: self.badgeNode, frame: updatedBadgeFrame)
|
||||
|
||||
var mentionBadgeFrame = self.mentionBadgeNode.frame
|
||||
if updatedBadgeFrame.width.isZero || self.badgeNode.isHidden {
|
||||
mentionBadgeFrame.origin.x = updatedBadgeFrame.minX - mentionBadgeFrame.width
|
||||
} else {
|
||||
mentionBadgeFrame.origin.x = updatedBadgeFrame.minX - 6.0 - mentionBadgeFrame.width
|
||||
}
|
||||
transition.updateFrame(node: self.mentionBadgeNode, frame: mentionBadgeFrame)
|
||||
|
||||
let pinnedIconSize = self.pinnedIconNode.bounds.size
|
||||
if pinnedIconSize != CGSize.zero {
|
||||
let badgeOffset: CGFloat
|
||||
if countersSize.isZero {
|
||||
badgeOffset = contentRect.maxX - pinnedIconSize.width
|
||||
} else {
|
||||
badgeOffset = contentRect.maxX - updatedBadgeFrame.size.width - 6.0 - pinnedIconSize.width
|
||||
}
|
||||
|
||||
let badgeBackgroundWidth = pinnedIconSize.width
|
||||
let badgeBackgroundFrame = CGRect(x: badgeOffset, y: self.pinnedIconNode.frame.origin.y, width: badgeBackgroundWidth, height: pinnedIconSize.height)
|
||||
transition.updateFrame(node: self.pinnedIconNode, frame: badgeBackgroundFrame)
|
||||
}
|
||||
}*/
|
||||
}
|
||||
|
||||
override func touchesToOtherItemsPrevented() {
|
||||
|
|
|
|||
|
|
@ -126,6 +126,7 @@ swift_library(
|
|||
"//submodules/TelegramBaseController:TelegramBaseController",
|
||||
"//submodules/DeviceLocationManager:DeviceLocationManager",
|
||||
"//submodules/AvatarNode:AvatarNode",
|
||||
"//submodules/AvatarVideoNode:AvatarVideoNode",
|
||||
"//submodules/OverlayStatusController:OverlayStatusController",
|
||||
"//submodules/TelegramStringFormatting:TelegramStringFormatting",
|
||||
"//submodules/LiveLocationManager:LiveLocationManager",
|
||||
|
|
|
|||
|
|
@ -3077,11 +3077,14 @@ public final class EmojiPagerContentComponent: Component {
|
|||
|
||||
override public var contents: Any? {
|
||||
didSet {
|
||||
self.onContentsUpdate()
|
||||
if let cloneLayer = self.cloneLayer {
|
||||
cloneLayer.contents = self.contents
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public var onContentsUpdate: () -> Void = {}
|
||||
|
||||
public init(
|
||||
item: Item,
|
||||
|
|
|
|||
|
|
@ -15,26 +15,22 @@ import HierarchyTrackingLayer
|
|||
import AccountContext
|
||||
import ComponentFlow
|
||||
import EmojiStatusComponent
|
||||
import AvatarVideoNode
|
||||
|
||||
private let normalFont = avatarPlaceholderFont(size: 16.0)
|
||||
private let smallFont = avatarPlaceholderFont(size: 12.0)
|
||||
|
||||
private let maxVideoLoopCount = 3
|
||||
|
||||
final class ChatAvatarNavigationNode: ASDisplayNode {
|
||||
private var context: AccountContext?
|
||||
|
||||
private let containerNode: ContextControllerSourceNode
|
||||
let avatarNode: AvatarNode
|
||||
private var videoNode: UniversalVideoNode?
|
||||
private var avatarVideoNode: AvatarVideoNode?
|
||||
|
||||
let statusView: ComponentView<Empty>
|
||||
|
||||
private var videoContent: NativeVideoContent?
|
||||
private let playbackStartDisposable = MetaDisposable()
|
||||
private var cachedDataDisposable = MetaDisposable()
|
||||
private var hierarchyTrackingLayer: HierarchyTrackingLayer?
|
||||
private var videoLoopCount = 0
|
||||
|
||||
private var trackingIsInHierarchy: Bool = false {
|
||||
didSet {
|
||||
|
|
@ -79,7 +75,6 @@ final class ChatAvatarNavigationNode: ASDisplayNode {
|
|||
|
||||
deinit {
|
||||
self.cachedDataDisposable.dispose()
|
||||
self.playbackStartDisposable.dispose()
|
||||
}
|
||||
|
||||
override func didLoad() {
|
||||
|
|
@ -116,7 +111,7 @@ final class ChatAvatarNavigationNode: ASDisplayNode {
|
|||
self.context = context
|
||||
self.avatarNode.setPeer(context: context, theme: theme, peer: peer, authorOfMessage: authorOfMessage, overrideImage: overrideImage, emptyColor: emptyColor, clipStyle: clipStyle, synchronousLoad: synchronousLoad, displayDimensions: displayDimensions, storeUnrounded: storeUnrounded)
|
||||
|
||||
if let peer = peer, peer.isPremium {
|
||||
if let peer = peer, peer.isPremium || "".isEmpty {
|
||||
self.cachedDataDisposable.set((context.account.postbox.peerView(id: peer.id)
|
||||
|> deliverOnMainQueue).start(next: { [weak self] peerView in
|
||||
guard let strongSelf = self else {
|
||||
|
|
@ -140,14 +135,16 @@ final class ChatAvatarNavigationNode: ASDisplayNode {
|
|||
|
||||
if isKnown {
|
||||
let photo = personalPhoto ?? profilePhoto
|
||||
if let photo = photo, let video = smallestVideoRepresentation(photo.videoRepresentations), let peerReference = PeerReference(peer._asPeer()) {
|
||||
let videoId = photo.id?.id ?? peer.id.id._internalGetInt64Value()
|
||||
let videoFileReference = FileMediaReference.avatarList(peer: peerReference, media: TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: 0), partialReference: nil, resource: video.resource, previewRepresentations: photo.representations, videoThumbnails: [], immediateThumbnailData: photo.immediateThumbnailData, mimeType: "video/mp4", size: nil, attributes: [.Animated, .Video(duration: 0, size: video.dimensions, flags: [])]))
|
||||
let videoContent = NativeVideoContent(id: .profileVideo(videoId, "header"), userLocation: .other, fileReference: videoFileReference, streamVideo: isMediaStreamable(resource: video.resource) ? .conservative : .none, loopVideo: true, enableSound: false, fetchAutomatically: true, onlyFullSizeThumbnail: false, useLargeThumbnail: true, autoFetchFullSizeThumbnail: true, startTimestamp: video.startTimestamp, continuePlayingWithoutSoundOnLostAudioSession: false, placeholderColor: .clear, captureProtected: false, storeAfterDownload: nil)
|
||||
if videoContent.id != strongSelf.videoContent?.id {
|
||||
strongSelf.videoNode?.removeFromSupernode()
|
||||
strongSelf.videoContent = videoContent
|
||||
if let photo = photo, !photo.videoRepresentations.isEmpty || photo.emojiMarkup != nil {
|
||||
let videoNode: AvatarVideoNode
|
||||
if let current = strongSelf.avatarVideoNode {
|
||||
videoNode = current
|
||||
} else {
|
||||
videoNode = AvatarVideoNode(context: context)
|
||||
strongSelf.avatarNode.addSubnode(videoNode)
|
||||
strongSelf.avatarVideoNode = videoNode
|
||||
}
|
||||
videoNode.update(peer: peer, photo: photo, size: CGSize(width: 37.0, height: 37.0))
|
||||
|
||||
if strongSelf.hierarchyTrackingLayer == nil {
|
||||
let hierarchyTrackingLayer = HierarchyTrackingLayer()
|
||||
|
|
@ -168,12 +165,13 @@ final class ChatAvatarNavigationNode: ASDisplayNode {
|
|||
strongSelf.layer.addSublayer(hierarchyTrackingLayer)
|
||||
}
|
||||
} else {
|
||||
strongSelf.videoContent = nil
|
||||
|
||||
if let avatarVideoNode = strongSelf.avatarVideoNode {
|
||||
avatarVideoNode.removeFromSupernode()
|
||||
strongSelf.avatarVideoNode = nil
|
||||
}
|
||||
strongSelf.hierarchyTrackingLayer?.removeFromSuperlayer()
|
||||
strongSelf.hierarchyTrackingLayer = nil
|
||||
}
|
||||
|
||||
strongSelf.updateVideoVisibility()
|
||||
} else {
|
||||
let _ = context.engine.peers.fetchAndUpdateCachedPeerData(peerId: peer.id).start()
|
||||
|
|
@ -181,10 +179,9 @@ final class ChatAvatarNavigationNode: ASDisplayNode {
|
|||
}))
|
||||
} else {
|
||||
self.cachedDataDisposable.set(nil)
|
||||
self.videoContent = nil
|
||||
|
||||
self.videoNode?.removeFromSupernode()
|
||||
self.videoNode = nil
|
||||
self.avatarVideoNode?.removeFromSupernode()
|
||||
self.avatarVideoNode = nil
|
||||
|
||||
self.hierarchyTrackingLayer?.removeFromSuperlayer()
|
||||
self.hierarchyTrackingLayer = nil
|
||||
|
|
@ -229,75 +226,12 @@ final class ChatAvatarNavigationNode: ASDisplayNode {
|
|||
}
|
||||
|
||||
private func updateVideoVisibility() {
|
||||
guard let context = self.context else {
|
||||
return
|
||||
}
|
||||
|
||||
let isVisible = self.trackingIsInHierarchy
|
||||
if isVisible, let videoContent = self.videoContent, self.videoLoopCount != maxVideoLoopCount {
|
||||
if self.videoNode == nil {
|
||||
let mediaManager = context.sharedContext.mediaManager
|
||||
let videoNode = UniversalVideoNode(postbox: context.account.postbox, audioSession: mediaManager.audioSession, manager: mediaManager.universalVideoManager, decoration: GalleryVideoDecoration(), content: videoContent, priority: .minimal)
|
||||
videoNode.clipsToBounds = true
|
||||
videoNode.isUserInteractionEnabled = false
|
||||
videoNode.isHidden = true
|
||||
videoNode.playbackCompleted = { [weak self] in
|
||||
if let strongSelf = self {
|
||||
strongSelf.videoLoopCount += 1
|
||||
if strongSelf.videoLoopCount == maxVideoLoopCount {
|
||||
if let videoNode = strongSelf.videoNode {
|
||||
strongSelf.videoNode = nil
|
||||
videoNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak videoNode] _ in
|
||||
videoNode?.removeFromSupernode()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let _ = videoContent.startTimestamp {
|
||||
self.playbackStartDisposable.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.15) {
|
||||
strongSelf.videoNode?.isHidden = false
|
||||
}
|
||||
}
|
||||
}))
|
||||
} else {
|
||||
self.playbackStartDisposable.set(nil)
|
||||
videoNode.isHidden = false
|
||||
}
|
||||
videoNode.layer.cornerRadius = self.avatarNode.frame.size.width / 2.0
|
||||
if #available(iOS 13.0, *) {
|
||||
videoNode.layer.cornerCurve = .circular
|
||||
}
|
||||
|
||||
videoNode.canAttachContent = true
|
||||
videoNode.play()
|
||||
|
||||
self.containerNode.insertSubnode(videoNode, aboveSubnode: self.avatarNode)
|
||||
self.videoNode = videoNode
|
||||
}
|
||||
} else if let videoNode = self.videoNode {
|
||||
self.videoNode = nil
|
||||
videoNode.removeFromSupernode()
|
||||
}
|
||||
|
||||
if let videoNode = self.videoNode {
|
||||
videoNode.updateLayout(size: self.avatarNode.frame.size, transition: .immediate)
|
||||
videoNode.frame = self.avatarNode.frame
|
||||
self.avatarVideoNode?.updateVisibility(isVisible)
|
||||
|
||||
if let videoNode = self.avatarVideoNode {
|
||||
videoNode.updateLayout(size: self.avatarNode.frame.size, cornerRadius: self.avatarNode.frame.size.width / 2.0, transition: .immediate)
|
||||
videoNode.frame = self.avatarNode.bounds
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue