Avatar upload improvements

This commit is contained in:
Ilya Laktyushin 2023-01-28 04:42:49 +04:00
parent 8e7936bb57
commit 1b723cd2a7
6 changed files with 286 additions and 220 deletions

View File

@ -142,6 +142,15 @@ public enum AvatarGalleryEntry: Equatable {
}
}
public var emojiMarkup: TelegramMediaImage.EmojiMarkup? {
switch self {
case .topImage:
return nil
case let .image(_, _, _, _, _, _, _, _, _, _, _, markup):
return markup
}
}
public var indexData: GalleryItemIndexData? {
switch self {
case let .topImage(_, _, _, indexData, _, _):
@ -343,11 +352,16 @@ public func fetchedAvatarGalleryEntries(engine: TelegramEngine, account: Account
let indexData = GalleryItemIndexData(position: index, totalCount: Int32(photos.count))
if result.isEmpty, let first = initialEntries.first {
var videoRepresentations: [VideoRepresentationWithReference] = first.videoRepresentations
var emojiMarkup: TelegramMediaImage.EmojiMarkup? = first.emojiMarkup
let isPersonal = first.representations.first?.representation.isPersonal == true
if videoRepresentations.isEmpty, !isPersonal {
videoRepresentations = photo.image.videoRepresentations.map({ VideoRepresentationWithReference(representation: $0, reference: MediaResourceReference.avatarList(peer: peerReference, resource: $0.resource)) })
}
result.append(.image(photo.image.imageId, photo.image.reference, first.representations, videoRepresentations, peer, secondEntry != nil ? 0 : photo.date, indexData, photo.messageId, photo.image.immediateThumbnailData, nil, false, photo.image.emojiMarkup))
if emojiMarkup == nil, !isPersonal {
emojiMarkup = photo.image.emojiMarkup
}
result.append(.image(photo.image.imageId, photo.image.reference, first.representations, videoRepresentations, peer, secondEntry != nil ? 0 : photo.date, indexData, photo.messageId, first.immediateThumbnailData ?? photo.image.immediateThumbnailData, nil, false, emojiMarkup))
} else {
result.append(.image(photo.image.imageId, photo.image.reference, photo.image.representations.map({ ImageRepresentationWithReference(representation: $0, reference: MediaResourceReference.avatarList(peer: peerReference, resource: $0.resource)) }), photo.image.videoRepresentations.map({ VideoRepresentationWithReference(representation: $0, reference: MediaResourceReference.avatarList(peer: peerReference, resource: $0.resource)) }), peer, photo.date, indexData, photo.messageId, photo.image.immediateThumbnailData, nil, photo.image.id == lastEntry?.id, photo.image.emojiMarkup))
}

View File

@ -4676,6 +4676,21 @@ public extension Api.functions.messages {
})
}
}
public extension Api.functions.messages {
static func getEmojiProfilePhotoGroups(hash: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.messages.EmojiGroups>) {
let buffer = Buffer()
buffer.appendInt32(564480243)
serializeInt32(hash, buffer: buffer, boxed: false)
return (FunctionDescription(name: "messages.getEmojiProfilePhotoGroups", parameters: [("hash", String(describing: hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.EmojiGroups? in
let reader = BufferReader(buffer)
var result: Api.messages.EmojiGroups?
if let signature = reader.readInt32() {
result = Api.parse(reader, signature: signature) as? Api.messages.EmojiGroups
}
return result
})
}
}
public extension Api.functions.messages {
static func getEmojiStatusGroups(hash: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.messages.EmojiGroups>) {
let buffer = Buffer()
@ -7764,15 +7779,16 @@ public extension Api.functions.photos {
}
}
public extension Api.functions.photos {
static func uploadContactProfilePhoto(flags: Int32, userId: Api.InputUser, file: Api.InputFile?, video: Api.InputFile?, videoStartTs: Double?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.photos.Photo>) {
static func uploadContactProfilePhoto(flags: Int32, userId: Api.InputUser, file: Api.InputFile?, video: Api.InputFile?, videoStartTs: Double?, videoEmojiMarkup: Api.VideoSize?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.photos.Photo>) {
let buffer = Buffer()
buffer.appendInt32(-1189444673)
buffer.appendInt32(-515093903)
serializeInt32(flags, buffer: buffer, boxed: false)
userId.serialize(buffer, true)
if Int(flags) & Int(1 << 0) != 0 {file!.serialize(buffer, true)}
if Int(flags) & Int(1 << 1) != 0 {video!.serialize(buffer, true)}
if Int(flags) & Int(1 << 2) != 0 {serializeDouble(videoStartTs!, buffer: buffer, boxed: false)}
return (FunctionDescription(name: "photos.uploadContactProfilePhoto", parameters: [("flags", String(describing: flags)), ("userId", String(describing: userId)), ("file", String(describing: file)), ("video", String(describing: video)), ("videoStartTs", String(describing: videoStartTs))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.photos.Photo? in
if Int(flags) & Int(1 << 5) != 0 {videoEmojiMarkup!.serialize(buffer, true)}
return (FunctionDescription(name: "photos.uploadContactProfilePhoto", parameters: [("flags", String(describing: flags)), ("userId", String(describing: userId)), ("file", String(describing: file)), ("video", String(describing: video)), ("videoStartTs", String(describing: videoStartTs)), ("videoEmojiMarkup", String(describing: videoEmojiMarkup))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.photos.Photo? in
let reader = BufferReader(buffer)
var result: Api.photos.Photo?
if let signature = reader.readInt32() {

View File

@ -26,8 +26,8 @@ public extension TelegramEngine {
return _internal_updateContactName(account: self.account, peerId: peerId, firstName: firstName, lastName: lastName)
}
public func updateContactPhoto(peerId: PeerId, resource: MediaResource?, videoResource: MediaResource?, videoStartTimestamp: Double?, mode: SetCustomPeerPhotoMode, mapResourceToAvatarSizes: @escaping (MediaResource, [TelegramMediaImageRepresentation]) -> Signal<[Int: Data], NoError>) -> Signal<UpdatePeerPhotoStatus, UploadPeerPhotoError> {
return _internal_updateContactPhoto(account: self.account, peerId: peerId, resource: resource, videoResource: videoResource, videoStartTimestamp: videoStartTimestamp, mode: mode, mapResourceToAvatarSizes: mapResourceToAvatarSizes)
public func updateContactPhoto(peerId: PeerId, resource: MediaResource?, videoResource: MediaResource?, videoStartTimestamp: Double?, markup: UploadPeerPhotoMarkup?, mode: SetCustomPeerPhotoMode, mapResourceToAvatarSizes: @escaping (MediaResource, [TelegramMediaImageRepresentation]) -> Signal<[Int: Data], NoError>) -> Signal<UpdatePeerPhotoStatus, UploadPeerPhotoError> {
return _internal_updateContactPhoto(account: self.account, peerId: peerId, resource: resource, videoResource: videoResource, videoStartTimestamp: videoStartTimestamp, markup: markup, mode: mode, mapResourceToAvatarSizes: mapResourceToAvatarSizes)
}
public func deviceContactsImportedByCount(contacts: [(String, [DeviceContactNormalizedPhoneNumber])]) -> Signal<[String: Int32], NoError> {

View File

@ -19,7 +19,13 @@ public enum UploadPeerPhotoMarkup {
}
func _internal_updateAccountPhoto(account: Account, resource: MediaResource?, videoResource: MediaResource?, videoStartTimestamp: Double?, markup: UploadPeerPhotoMarkup?, fallback: Bool, mapResourceToAvatarSizes: @escaping (MediaResource, [TelegramMediaImageRepresentation]) -> Signal<[Int: Data], NoError>) -> Signal<UpdatePeerPhotoStatus, UploadPeerPhotoError> {
return _internal_updatePeerPhoto(postbox: account.postbox, network: account.network, stateManager: account.stateManager, accountPeerId: account.peerId, peerId: account.peerId, photo: resource.flatMap({ _internal_uploadedPeerPhoto(postbox: account.postbox, network: account.network, resource: $0) }), video: videoResource.flatMap({ _internal_uploadedPeerVideo(postbox: account.postbox, network: account.network, messageMediaPreuploadManager: account.messageMediaPreuploadManager, resource: $0) |> map(Optional.init) }), videoStartTimestamp: videoStartTimestamp, markup: markup, fallback: fallback, mapResourceToAvatarSizes: mapResourceToAvatarSizes)
let photo: Signal<UploadedPeerPhotoData, NoError>?
if videoResource == nil && markup != nil, let resource = resource {
photo = .single(UploadedPeerPhotoData.withResource(resource))
} else {
photo = resource.flatMap({ _internal_uploadedPeerPhoto(postbox: account.postbox, network: account.network, resource: $0) })
}
return _internal_updatePeerPhoto(postbox: account.postbox, network: account.network, stateManager: account.stateManager, accountPeerId: account.peerId, peerId: account.peerId, photo: photo, video: videoResource.flatMap({ _internal_uploadedPeerVideo(postbox: account.postbox, network: account.network, messageMediaPreuploadManager: account.messageMediaPreuploadManager, resource: $0) |> map(Optional.init) }), videoStartTimestamp: videoStartTimestamp, markup: markup, fallback: fallback, mapResourceToAvatarSizes: mapResourceToAvatarSizes)
}
public enum SetCustomPeerPhotoMode {
@ -28,13 +34,14 @@ public enum SetCustomPeerPhotoMode {
case customAndSuggest
}
func _internal_updateContactPhoto(account: Account, peerId: PeerId, resource: MediaResource?, videoResource: MediaResource?, videoStartTimestamp: Double?, mode: SetCustomPeerPhotoMode, mapResourceToAvatarSizes: @escaping (MediaResource, [TelegramMediaImageRepresentation]) -> Signal<[Int: Data], NoError>) -> Signal<UpdatePeerPhotoStatus, UploadPeerPhotoError> {
return _internal_updatePeerPhoto(postbox: account.postbox, network: account.network, stateManager: account.stateManager, accountPeerId: account.peerId, peerId: peerId, photo: resource.flatMap({ _internal_uploadedPeerPhoto(postbox: account.postbox, network: account.network, resource: $0) }), video: videoResource.flatMap({ _internal_uploadedPeerVideo(postbox: account.postbox, network: account.network, messageMediaPreuploadManager: account.messageMediaPreuploadManager, resource: $0) |> map(Optional.init) }), videoStartTimestamp: videoStartTimestamp, customPeerPhotoMode: mode, mapResourceToAvatarSizes: mapResourceToAvatarSizes)
func _internal_updateContactPhoto(account: Account, peerId: PeerId, resource: MediaResource?, videoResource: MediaResource?, videoStartTimestamp: Double?, markup: UploadPeerPhotoMarkup?, mode: SetCustomPeerPhotoMode, mapResourceToAvatarSizes: @escaping (MediaResource, [TelegramMediaImageRepresentation]) -> Signal<[Int: Data], NoError>) -> Signal<UpdatePeerPhotoStatus, UploadPeerPhotoError> {
return _internal_updatePeerPhoto(postbox: account.postbox, network: account.network, stateManager: account.stateManager, accountPeerId: account.peerId, peerId: peerId, photo: resource.flatMap({ _internal_uploadedPeerPhoto(postbox: account.postbox, network: account.network, resource: $0) }), video: videoResource.flatMap({ _internal_uploadedPeerVideo(postbox: account.postbox, network: account.network, messageMediaPreuploadManager: account.messageMediaPreuploadManager, resource: $0) |> map(Optional.init) }), videoStartTimestamp: videoStartTimestamp, markup: markup, customPeerPhotoMode: mode, mapResourceToAvatarSizes: mapResourceToAvatarSizes)
}
public struct UploadedPeerPhotoData {
fileprivate let resource: MediaResource
fileprivate let content: UploadedPeerPhotoDataContent
fileprivate let local: Bool
public var isCompleted: Bool {
if case let .result(result) = content, case .inputFile = result {
@ -43,6 +50,10 @@ public struct UploadedPeerPhotoData {
return false
}
}
static func withResource(_ resource: MediaResource) -> UploadedPeerPhotoData {
return UploadedPeerPhotoData(resource: resource, content: .result(.inputFile(.inputFile(id: 0, parts: 0, name: "", md5Checksum: ""))), local: true)
}
}
enum UploadedPeerPhotoDataContent {
@ -53,10 +64,10 @@ enum UploadedPeerPhotoDataContent {
func _internal_uploadedPeerPhoto(postbox: Postbox, network: Network, resource: MediaResource) -> Signal<UploadedPeerPhotoData, NoError> {
return multipartUpload(network: network, postbox: postbox, source: .resource(.standalone(resource: resource)), encrypt: false, tag: TelegramMediaResourceFetchTag(statsCategory: .image, userContentType: .image), hintFileSize: nil, hintFileIsLarge: false, forceNoBigParts: false)
|> map { result -> UploadedPeerPhotoData in
return UploadedPeerPhotoData(resource: resource, content: .result(result))
return UploadedPeerPhotoData(resource: resource, content: .result(result), local: false)
}
|> `catch` { _ -> Signal<UploadedPeerPhotoData, NoError> in
return .single(UploadedPeerPhotoData(resource: resource, content: .error))
return .single(UploadedPeerPhotoData(resource: resource, content: .error, local: false))
}
}
@ -64,18 +75,18 @@ func _internal_uploadedPeerVideo(postbox: Postbox, network: Network, messageMedi
if let messageMediaPreuploadManager = messageMediaPreuploadManager {
return messageMediaPreuploadManager.upload(network: network, postbox: postbox, source: .resource(.standalone(resource: resource)), encrypt: false, tag: TelegramMediaResourceFetchTag(statsCategory: .video, userContentType: .video), hintFileSize: nil, hintFileIsLarge: false)
|> map { result -> UploadedPeerPhotoData in
return UploadedPeerPhotoData(resource: resource, content: .result(result))
return UploadedPeerPhotoData(resource: resource, content: .result(result), local: false)
}
|> `catch` { _ -> Signal<UploadedPeerPhotoData, NoError> in
return .single(UploadedPeerPhotoData(resource: resource, content: .error))
return .single(UploadedPeerPhotoData(resource: resource, content: .error, local: false))
}
} else {
return multipartUpload(network: network, postbox: postbox, source: .resource(.standalone(resource: resource)), encrypt: false, tag: TelegramMediaResourceFetchTag(statsCategory: .video, userContentType: .video), hintFileSize: nil, hintFileIsLarge: false, forceNoBigParts: false)
|> map { result -> UploadedPeerPhotoData in
return UploadedPeerPhotoData(resource: resource, content: .result(result))
return UploadedPeerPhotoData(resource: resource, content: .result(result), local: false)
}
|> `catch` { _ -> Signal<UploadedPeerPhotoData, NoError> in
return .single(UploadedPeerPhotoData(resource: resource, content: .error))
return .single(UploadedPeerPhotoData(resource: resource, content: .error, local: false))
}
}
}
@ -88,6 +99,16 @@ func _internal_updatePeerPhotoInternal(postbox: Postbox, network: Network, state
return peer
|> mapError { _ -> UploadPeerPhotoError in }
|> mapToSignal { peer -> Signal<UpdatePeerPhotoStatus, UploadPeerPhotoError> in
var videoEmojiMarkup: Api.VideoSize?
if let markup = markup {
switch markup {
case let .emoji(fileId, backgroundColors):
videoEmojiMarkup = .videoSizeEmojiMarkup(emojiId: fileId, backgroundColors: backgroundColors)
case let .sticker(packReference, fileId, backgroundColors):
videoEmojiMarkup = .videoSizeStickerMarkup(stickerset: packReference.apiInputStickerSet, stickerId: fileId, backgroundColors: backgroundColors)
}
}
if let photo = photo {
let mappedPhoto = photo
|> take(until: { value in
@ -146,8 +167,16 @@ func _internal_updatePeerPhotoInternal(postbox: Postbox, network: Network, state
}
}
var photoFile: Api.InputFile?
if !photoResult.local {
photoFile = file
}
if peer is TelegramUser {
var flags: Int32 = (1 << 0)
var flags: Int32 = 0
if let _ = photoFile {
flags = (1 << 0)
}
if let _ = videoFile {
flags |= (1 << 1)
if let _ = videoStartTimestamp {
@ -155,23 +184,15 @@ func _internal_updatePeerPhotoInternal(postbox: Postbox, network: Network, state
}
}
var videoEmojiMarkup: Api.VideoSize?
if let markup = markup{
switch markup {
case let .emoji(fileId, backgroundColors):
videoEmojiMarkup = .videoSizeEmojiMarkup(emojiId: fileId, backgroundColors: backgroundColors)
case let .sticker(packReference, fileId, backgroundColors):
videoEmojiMarkup = .videoSizeStickerMarkup(stickerset: packReference.apiInputStickerSet, stickerId: fileId, backgroundColors: backgroundColors)
}
flags |= (1 << 4)
}
let request: Signal<Api.photos.Photo, MTRpcError>
if peer.id == accountPeerId {
if fallback {
flags |= (1 << 3)
}
request = network.request(Api.functions.photos.uploadProfilePhoto(flags: flags, file: file, video: videoFile, videoStartTs: videoStartTimestamp, videoEmojiMarkup: videoEmojiMarkup))
if let _ = videoEmojiMarkup {
flags |= (1 << 4)
}
request = network.request(Api.functions.photos.uploadProfilePhoto(flags: flags, file: photoFile, video: videoFile, videoStartTs: videoStartTimestamp, videoEmojiMarkup: videoEmojiMarkup))
} else if let inputUser = apiInputUser(peer) {
if let customPeerPhotoMode = customPeerPhotoMode {
switch customPeerPhotoMode {
@ -184,8 +205,10 @@ func _internal_updatePeerPhotoInternal(postbox: Postbox, network: Network, state
flags |= (1 << 4)
}
}
request = network.request(Api.functions.photos.uploadContactProfilePhoto(flags: flags, userId: inputUser, file: file, video: videoFile, videoStartTs: videoStartTimestamp))
if let _ = videoEmojiMarkup {
flags |= (1 << 5)
}
request = network.request(Api.functions.photos.uploadContactProfilePhoto(flags: flags, userId: inputUser, file: file, video: videoFile, videoStartTs: videoStartTimestamp, videoEmojiMarkup: videoEmojiMarkup))
} else {
request = .complete()
}
@ -273,6 +296,14 @@ func _internal_updatePeerPhotoInternal(postbox: Postbox, network: Network, state
return nil
}
}
} else if peer.id == accountPeerId && customPeerPhotoMode == nil {
transaction.updatePeerCachedData(peerIds: Set([peer.id])) { peerId, cachedPeerData in
if let cachedPeerData = cachedPeerData as? CachedUserData {
return cachedPeerData.withUpdatedPhoto(.known(image))
} else {
return nil
}
}
}
}
return (.complete(representations), photoResult.resource, videoResult?.resource)
@ -287,14 +318,7 @@ func _internal_updatePeerPhotoInternal(postbox: Postbox, network: Network, state
}
}
var videoEmojiMarkup: Api.VideoSize?
if let markup = markup {
switch markup {
case let .emoji(fileId, backgroundColors):
videoEmojiMarkup = .videoSizeEmojiMarkup(emojiId: fileId, backgroundColors: backgroundColors)
case let .sticker(packReference, fileId, backgroundColors):
videoEmojiMarkup = .videoSizeStickerMarkup(stickerset: packReference.apiInputStickerSet, stickerId: fileId, backgroundColors: backgroundColors)
}
if let _ = videoEmojiMarkup {
flags |= (1 << 3)
}
@ -388,7 +412,7 @@ func _internal_updatePeerPhotoInternal(postbox: Postbox, network: Network, state
request = network.request(Api.functions.photos.updateProfilePhoto(flags: flags, id: Api.InputPhoto.inputPhotoEmpty))
} else if let inputUser = apiInputUser(peer) {
let flags: Int32 = (1 << 4)
request = network.request(Api.functions.photos.uploadContactProfilePhoto(flags: flags, userId: inputUser, file: nil, video: nil, videoStartTs: nil))
request = network.request(Api.functions.photos.uploadContactProfilePhoto(flags: flags, userId: inputUser, file: nil, video: nil, videoStartTs: nil, videoEmojiMarkup: nil))
} else {
request = .complete()
}

View File

@ -14,6 +14,7 @@ import GalleryUI
import HierarchyTrackingLayer
import WallpaperBackgroundNode
import ChatControllerInteraction
import AvatarVideoNode
private let timezoneOffset: Int32 = {
let nowTimestamp = Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970)
@ -446,20 +447,17 @@ final class ChatMessageAvatarHeaderNode: ListViewItemHeaderNode {
private let containerNode: ContextControllerSourceNode
private let avatarNode: AvatarNode
private var videoNode: UniversalVideoNode?
private var avatarVideoNode: AvatarVideoNode?
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 {
if self.trackingIsInHierarchy != oldValue {
Queue.mainQueue().justDispatch {
if self.trackingIsInHierarchy {
self.videoLoopCount = 0
self.avatarVideoNode?.resetPlayback()
}
self.updateVideoVisibility()
}
@ -507,7 +505,6 @@ final class ChatMessageAvatarHeaderNode: ListViewItemHeaderNode {
deinit {
self.cachedDataDisposable.dispose()
self.playbackStartDisposable.dispose()
}
func setCustomLetters(context: AccountContext, theme: PresentationTheme, synchronousLoad: Bool, letters: [String], emptyColor: UIColor) {
@ -531,16 +528,35 @@ final class ChatMessageAvatarHeaderNode: ListViewItemHeaderNode {
guard let strongSelf = self else {
return
}
let cachedPeerData = peerView.cachedData
if let cachedPeerData = cachedPeerData as? CachedUserData, case let .known(maybePhoto) = cachedPeerData.photo {
if let photo = maybePhoto, let video = photo.videoRepresentations.last, let peerReference = PeerReference(peer) {
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, "\(Int32.random(in: 0 ..< Int32.max))"), 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
let cachedPeerData = peerView.cachedData as? CachedUserData
var personalPhoto: TelegramMediaImage?
var profilePhoto: TelegramMediaImage?
var isKnown = false
if let cachedPeerData = cachedPeerData {
if case let .known(maybePersonalPhoto) = cachedPeerData.personalPhoto {
personalPhoto = maybePersonalPhoto
isKnown = true
}
if case let .known(maybePhoto) = cachedPeerData.photo {
profilePhoto = maybePhoto
isKnown = true
}
}
if isKnown {
let photo = personalPhoto ?? profilePhoto
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: EnginePeer(peer), photo: photo, size: CGSize(width: 38.0, height: 38.0))
if strongSelf.hierarchyTrackingLayer == nil {
let hierarchyTrackingLayer = HierarchyTrackingLayer()
@ -561,23 +577,65 @@ final class ChatMessageAvatarHeaderNode: ListViewItemHeaderNode {
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()
}
// let cachedPeerData = peerView.cachedData
// if let cachedPeerData = cachedPeerData as? CachedUserData, case let .known(maybePhoto) = cachedPeerData.photo {
// if let photo = maybePhoto, let video = photo.videoRepresentations.last, let peerReference = PeerReference(peer) {
// 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, "\(Int32.random(in: 0 ..< Int32.max))"), 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 strongSelf.hierarchyTrackingLayer == nil {
// let hierarchyTrackingLayer = HierarchyTrackingLayer()
// hierarchyTrackingLayer.didEnterHierarchy = { [weak self] in
// guard let strongSelf = self else {
// return
// }
// strongSelf.trackingIsInHierarchy = true
// }
//
// hierarchyTrackingLayer.didExitHierarchy = { [weak self] in
// guard let strongSelf = self else {
// return
// }
// strongSelf.trackingIsInHierarchy = false
// }
// strongSelf.hierarchyTrackingLayer = hierarchyTrackingLayer
// strongSelf.layer.addSublayer(hierarchyTrackingLayer)
// }
// } else {
// strongSelf.videoContent = nil
//
// strongSelf.hierarchyTrackingLayer?.removeFromSuperlayer()
// strongSelf.hierarchyTrackingLayer = nil
// }
//
// strongSelf.updateVideoVisibility()
// } else {
}))
} 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
@ -659,74 +717,12 @@ final class ChatMessageAvatarHeaderNode: ListViewItemHeaderNode {
}
private func updateVideoVisibility() {
let context = self.context
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: .embedded)
videoNode.clipsToBounds = true
videoNode.isUserInteractionEnabled = false
videoNode.isHidden = true
videoNode.playbackCompleted = { [weak self] in
Queue.mainQueue().async {
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()
})
}
}
}
}
}
self.avatarVideoNode?.updateVisibility(isVisible)
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
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
}
}
}

View File

@ -6919,11 +6919,11 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
})
}
} else if case .custom = mode {
signal = self.context.engine.contacts.updateContactPhoto(peerId: self.peerId, resource: resource, videoResource: nil, videoStartTimestamp: nil, mode: .custom, mapResourceToAvatarSizes: { resource, representations in
signal = self.context.engine.contacts.updateContactPhoto(peerId: self.peerId, resource: resource, videoResource: nil, videoStartTimestamp: nil, markup: nil, mode: .custom, mapResourceToAvatarSizes: { resource, representations in
return mapResourceToAvatarSizes(postbox: postbox, resource: resource, representations: representations)
})
} else if case .suggest = mode {
signal = self.context.engine.contacts.updateContactPhoto(peerId: self.peerId, resource: resource, videoResource: nil, videoStartTimestamp: nil, mode: .suggest, mapResourceToAvatarSizes: { resource, representations in
signal = self.context.engine.contacts.updateContactPhoto(peerId: self.peerId, resource: resource, videoResource: nil, videoStartTimestamp: nil, markup: nil, mode: .suggest, mapResourceToAvatarSizes: { resource, representations in
return mapResourceToAvatarSizes(postbox: postbox, resource: resource, representations: representations)
})
} else {
@ -7030,85 +7030,109 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
videoStartTimestamp = adjustments.videoStartValue - adjustments.trimStartValue
}
var markup: UploadPeerPhotoMarkup? = nil
if let fileId = adjustments?.documentId, let backgroundColors = adjustments?.colors as? [Int32], fileId != 0 {
if let packId = adjustments?.stickerPackId, let accessHash = adjustments?.stickerPackAccessHash, packId != 0 {
markup = .sticker(packReference: .id(id: packId, accessHash: accessHash), fileId: fileId, backgroundColors: backgroundColors)
} else {
markup = .emoji(fileId: fileId, backgroundColors: backgroundColors)
}
}
var uploadVideo = true
if let _ = markup {
if let data = self.context.currentAppConfiguration.with({ $0 }).data, let uploadVideoValue = data["upload_markup_video"] as? Bool, uploadVideoValue {
uploadVideo = true
} else {
uploadVideo = false
}
}
let account = self.context.account
let context = self.context
let signal = Signal<TelegramMediaResource, UploadPeerPhotoError> { [weak self] subscriber in
let entityRenderer: LegacyPaintEntityRenderer? = adjustments.flatMap { adjustments in
if let paintingData = adjustments.paintingData, paintingData.hasAnimation {
return LegacyPaintEntityRenderer(account: account, adjustments: adjustments)
} else {
return nil
}
}
let uploadInterface = LegacyLiveUploadInterface(context: context)
let signal: SSignal
if let url = asset as? URL, url.absoluteString.hasSuffix(".jpg"), let data = try? Data(contentsOf: url, options: [.mappedRead]), let image = UIImage(data: data), let entityRenderer = entityRenderer {
let durationSignal: SSignal = SSignal(generator: { subscriber in
let disposable = (entityRenderer.duration()).start(next: { duration in
subscriber.putNext(duration)
subscriber.putCompletion()
})
return SBlockDisposable(block: {
disposable.dispose()
})
})
signal = durationSignal.map(toSignal: { duration -> SSignal in
if let duration = duration as? Double {
return TGMediaVideoConverter.renderUIImage(image, duration: duration, adjustments: adjustments, watcher: nil, entityRenderer: entityRenderer)!
let videoResource: Signal<TelegramMediaResource?, UploadPeerPhotoError>
if uploadVideo {
videoResource = Signal<TelegramMediaResource?, UploadPeerPhotoError> { [weak self] subscriber in
let entityRenderer: LegacyPaintEntityRenderer? = adjustments.flatMap { adjustments in
if let paintingData = adjustments.paintingData, paintingData.hasAnimation {
return LegacyPaintEntityRenderer(account: account, adjustments: adjustments)
} else {
return SSignal.single(nil)
}
})
} else if let asset = asset as? AVAsset {
signal = TGMediaVideoConverter.convert(asset, adjustments: adjustments, watcher: uploadInterface, entityRenderer: entityRenderer)!
} else {
signal = SSignal.complete()
}
let signalDisposable = signal.start(next: { next in
if let result = next as? TGMediaVideoConversionResult {
if let image = result.coverImage, let data = image.jpegData(compressionQuality: 0.7) {
account.postbox.mediaBox.storeResourceData(photoResource.id, data: data)
}
if let timestamp = videoStartTimestamp {
videoStartTimestamp = max(0.0, min(timestamp, result.duration - 0.05))
}
var value = stat()
if stat(result.fileURL.path, &value) == 0 {
if let data = try? Data(contentsOf: result.fileURL) {
let resource: TelegramMediaResource
if let liveUploadData = result.liveUploadData as? LegacyLiveUploadInterfaceResult {
resource = LocalFileMediaResource(fileId: liveUploadData.id)
} else {
resource = LocalFileMediaResource(fileId: Int64.random(in: Int64.min ... Int64.max))
}
account.postbox.mediaBox.storeResourceData(resource.id, data: data, synchronous: true)
subscriber.putNext(resource)
}
}
subscriber.putCompletion()
} else if let strongSelf = self, let progress = next as? NSNumber {
Queue.mainQueue().async {
strongSelf.state = strongSelf.state.withAvatarUploadProgress(CGFloat(progress.floatValue * 0.45))
if let (layout, navigationHeight) = strongSelf.validLayout {
strongSelf.containerLayoutUpdated(layout: layout, navigationHeight: navigationHeight, transition: .immediate, additive: false)
}
return nil
}
}
}, error: { _ in
}, completed: nil)
let uploadInterface = LegacyLiveUploadInterface(context: context)
let signal: SSignal
if let url = asset as? URL, url.absoluteString.hasSuffix(".jpg"), let data = try? Data(contentsOf: url, options: [.mappedRead]), let image = UIImage(data: data), let entityRenderer = entityRenderer {
let durationSignal: SSignal = SSignal(generator: { subscriber in
let disposable = (entityRenderer.duration()).start(next: { duration in
subscriber.putNext(duration)
subscriber.putCompletion()
})
let disposable = ActionDisposable {
signalDisposable?.dispose()
}
return SBlockDisposable(block: {
disposable.dispose()
})
})
signal = durationSignal.map(toSignal: { duration -> SSignal in
if let duration = duration as? Double {
return TGMediaVideoConverter.renderUIImage(image, duration: duration, adjustments: adjustments, watcher: nil, entityRenderer: entityRenderer)!
} else {
return SSignal.single(nil)
}
})
return ActionDisposable {
disposable.dispose()
} else if let asset = asset as? AVAsset {
signal = TGMediaVideoConverter.convert(asset, adjustments: adjustments, watcher: uploadInterface, entityRenderer: entityRenderer)!
} else {
signal = SSignal.complete()
}
let signalDisposable = signal.start(next: { next in
if let result = next as? TGMediaVideoConversionResult {
if let image = result.coverImage, let data = image.jpegData(compressionQuality: 0.7) {
account.postbox.mediaBox.storeResourceData(photoResource.id, data: data)
}
if let timestamp = videoStartTimestamp {
videoStartTimestamp = max(0.0, min(timestamp, result.duration - 0.05))
}
var value = stat()
if stat(result.fileURL.path, &value) == 0 {
if let data = try? Data(contentsOf: result.fileURL) {
let resource: TelegramMediaResource
if let liveUploadData = result.liveUploadData as? LegacyLiveUploadInterfaceResult {
resource = LocalFileMediaResource(fileId: liveUploadData.id)
} else {
resource = LocalFileMediaResource(fileId: Int64.random(in: Int64.min ... Int64.max))
}
account.postbox.mediaBox.storeResourceData(resource.id, data: data, synchronous: true)
subscriber.putNext(resource)
}
}
subscriber.putCompletion()
} else if let strongSelf = self, let progress = next as? NSNumber {
Queue.mainQueue().async {
strongSelf.state = strongSelf.state.withAvatarUploadProgress(CGFloat(progress.floatValue * 0.45))
if let (layout, navigationHeight) = strongSelf.validLayout {
strongSelf.containerLayoutUpdated(layout: layout, navigationHeight: navigationHeight, transition: .immediate, additive: false)
}
}
}
}, error: { _ in
}, completed: nil)
let disposable = ActionDisposable {
signalDisposable?.dispose()
}
return ActionDisposable {
disposable.dispose()
}
}
} else {
videoResource = .single(nil)
}
var dismissStatus: (() -> Void)?
@ -7131,17 +7155,9 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
let peerId = self.peerId
let isSettings = self.isSettings
self.updateAvatarDisposable.set((signal
self.updateAvatarDisposable.set((videoResource
|> mapToSignal { videoResource -> Signal<UpdatePeerPhotoStatus, UploadPeerPhotoError> in
var markup: UploadPeerPhotoMarkup? = nil
if isSettings {
if let fileId = adjustments?.documentId, let backgroundColors = adjustments?.colors as? [Int32], fileId != 0 {
if let packId = adjustments?.stickerPackId, let accessHash = adjustments?.stickerPackAccessHash, packId != 0 {
markup = .sticker(packReference: .id(id: packId, accessHash: accessHash), fileId: fileId, backgroundColors: backgroundColors)
} else {
markup = .emoji(fileId: fileId, backgroundColors: backgroundColors)
}
}
if case .fallback = mode {
return context.engine.accountData.updateFallbackPhoto(resource: photoResource, videoResource: videoResource, videoStartTimestamp: videoStartTimestamp, markup: markup, mapResourceToAvatarSizes: { resource, representations in
return mapResourceToAvatarSizes(postbox: account.postbox, resource: resource, representations: representations)
@ -7152,15 +7168,15 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
})
}
} else if case .custom = mode {
return context.engine.contacts.updateContactPhoto(peerId: peerId, resource: photoResource, videoResource: videoResource, videoStartTimestamp: videoStartTimestamp, mode: .custom, mapResourceToAvatarSizes: { resource, representations in
return context.engine.contacts.updateContactPhoto(peerId: peerId, resource: photoResource, videoResource: videoResource, videoStartTimestamp: videoStartTimestamp, markup: markup, mode: .custom, mapResourceToAvatarSizes: { resource, representations in
return mapResourceToAvatarSizes(postbox: account.postbox, resource: resource, representations: representations)
})
} else if case .suggest = mode {
return context.engine.contacts.updateContactPhoto(peerId: peerId, resource: photoResource, videoResource: videoResource, videoStartTimestamp: videoStartTimestamp, mode: .suggest, mapResourceToAvatarSizes: { resource, representations in
return context.engine.contacts.updateContactPhoto(peerId: peerId, resource: photoResource, videoResource: videoResource, videoStartTimestamp: videoStartTimestamp, markup: markup, mode: .suggest, mapResourceToAvatarSizes: { resource, representations in
return mapResourceToAvatarSizes(postbox: account.postbox, resource: resource, representations: representations)
})
} else {
return context.engine.peers.updatePeerPhoto(peerId: peerId, photo: context.engine.peers.uploadedPeerPhoto(resource: photoResource), video: context.engine.peers.uploadedPeerVideo(resource: videoResource) |> map(Optional.init), videoStartTimestamp: videoStartTimestamp, markup: markup, mapResourceToAvatarSizes: { resource, representations in
return context.engine.peers.updatePeerPhoto(peerId: peerId, photo: context.engine.peers.uploadedPeerPhoto(resource: photoResource), video: videoResource.flatMap { context.engine.peers.uploadedPeerVideo(resource: $0) |> map(Optional.init) }, videoStartTimestamp: videoStartTimestamp, markup: markup, mapResourceToAvatarSizes: { resource, representations in
return mapResourceToAvatarSizes(postbox: account.postbox, resource: resource, representations: representations)
})
}
@ -7418,7 +7434,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
let postbox = strongSelf.context.account.postbox
let signal: Signal<UpdatePeerPhotoStatus, UploadPeerPhotoError>
if case .custom = mode {
signal = strongSelf.context.engine.contacts.updateContactPhoto(peerId: strongSelf.peerId, resource: nil, videoResource: nil, videoStartTimestamp: nil, mode: .custom, mapResourceToAvatarSizes: { resource, representations in
signal = strongSelf.context.engine.contacts.updateContactPhoto(peerId: strongSelf.peerId, resource: nil, videoResource: nil, videoStartTimestamp: nil, markup: nil, mode: .custom, mapResourceToAvatarSizes: { resource, representations in
return mapResourceToAvatarSizes(postbox: postbox, resource: resource, representations: representations)
})
} else if case .fallback = mode {
@ -8507,7 +8523,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
guard let strongSelf = self else {
return
}
strongSelf.updateAvatarDisposable.set((strongSelf.context.engine.contacts.updateContactPhoto(peerId: strongSelf.peerId, resource: nil, videoResource: nil, videoStartTimestamp: nil, mode: .custom, mapResourceToAvatarSizes: { resource, representations in
strongSelf.updateAvatarDisposable.set((strongSelf.context.engine.contacts.updateContactPhoto(peerId: strongSelf.peerId, resource: nil, videoResource: nil, videoStartTimestamp: nil, markup: nil, mode: .custom, mapResourceToAvatarSizes: { resource, representations in
mapResourceToAvatarSizes(postbox: strongSelf.context.account.postbox, resource: resource, representations: representations)
})
|> deliverOnMainQueue).start(next: { [weak self] _ in