Story covers

This commit is contained in:
Ilya Laktyushin 2024-07-19 17:43:17 +04:00
parent d8d68722ae
commit 3e448e833e
57 changed files with 1196 additions and 152 deletions

View File

@ -1707,7 +1707,7 @@ private final class NotificationServiceHandler {
} else if let file = media as? TelegramMediaFile {
resource = file.resource
for attribute in file.attributes {
if case let .Video(_, _, _, preloadSize) = attribute {
if case let .Video(_, _, _, preloadSize, _) = attribute {
fetchSize = preloadSize.flatMap(Int64.init)
}
}

View File

@ -18,7 +18,7 @@ public func isMediaStreamable(message: Message, media: TelegramMediaFile) -> Boo
return false
}
for attribute in media.attributes {
if case let .Video(_, _, flags, _) = attribute {
if case let .Video(_, _, flags, _, _) = attribute {
if flags.contains(.supportsStreaming) {
return true
}
@ -41,7 +41,7 @@ public func isMediaStreamable(media: TelegramMediaFile) -> Bool {
return false
}
for attribute in media.attributes {
if case let .Video(_, _, flags, _) = attribute {
if case let .Video(_, _, flags, _, _) = attribute {
if flags.contains(.supportsStreaming) {
return true
}

View File

@ -206,7 +206,7 @@ public final class AvatarVideoNode: ASDisplayNode {
self.backgroundNode.image = nil
let videoId = photo.id?.id ?? peer.id.id._internalGetInt64Value()
let videoFileReference = FileMediaReference.avatarList(peer: peerReference, media: TelegramMediaFile(fileId: EngineMedia.Id(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: [], preloadSize: nil)]))
let videoFileReference = FileMediaReference.avatarList(peer: peerReference, media: TelegramMediaFile(fileId: EngineMedia.Id(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: [], preloadSize: nil, coverTime: nil)]))
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()

View File

@ -460,7 +460,7 @@ public final class ChatImportActivityScreen: ViewController {
if let path = getAppBundle().path(forResource: "BlankVideo", ofType: "m4v"), let size = fileSize(path) {
let decoration = ChatBubbleVideoDecoration(corners: ImageCorners(), nativeSize: CGSize(width: 100.0, height: 100.0), contentMode: .aspectFit, backgroundColor: .black)
let dummyFile = TelegramMediaFile(fileId: EngineMedia.Id(namespace: 0, id: 1), partialReference: nil, resource: LocalFileReferenceMediaResource(localFilePath: path, randomId: 12345), previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "video/mp4", size: size, attributes: [.Video(duration: 1, size: PixelDimensions(width: 100, height: 100), flags: [], preloadSize: nil)])
let dummyFile = TelegramMediaFile(fileId: EngineMedia.Id(namespace: 0, id: 1), partialReference: nil, resource: LocalFileReferenceMediaResource(localFilePath: path, randomId: 12345), previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "video/mp4", size: size, attributes: [.Video(duration: 1, size: PixelDimensions(width: 100, height: 100), flags: [], preloadSize: nil, coverTime: nil)])
let videoContent = NativeVideoContent(id: .message(1, EngineMedia.Id(namespace: 0, id: 1)), userLocation: .other, fileReference: .standalone(media: dummyFile), streamVideo: .none, loopVideo: true, enableSound: false, fetchAutomatically: true, onlyFullSizeThumbnail: false, continuePlayingWithoutSoundOnLostAudioSession: false, placeholderColor: .black, storeAfterDownload: nil)

View File

@ -2576,7 +2576,7 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode {
case let .preview(dimensions, immediateThumbnailData, videoDuration):
if let immediateThumbnailData {
if let videoDuration {
let thumbnailMedia = TelegramMediaFile(fileId: MediaId(namespace: 0, id: index), partialReference: nil, resource: EmptyMediaResource(), previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: immediateThumbnailData, mimeType: "video/mp4", size: nil, attributes: [.Video(duration: Double(videoDuration), size: dimensions ?? PixelDimensions(width: 1, height: 1), flags: [], preloadSize: nil)])
let thumbnailMedia = TelegramMediaFile(fileId: MediaId(namespace: 0, id: index), partialReference: nil, resource: EmptyMediaResource(), previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: immediateThumbnailData, mimeType: "video/mp4", size: nil, attributes: [.Video(duration: Double(videoDuration), size: dimensions ?? PixelDimensions(width: 1, height: 1), flags: [], preloadSize: nil, coverTime: nil)])
contentImageSpecs.append(ContentImageSpec(message: message, media: .file(thumbnailMedia), size: fitSize))
} else {
let thumbnailMedia = TelegramMediaImage(imageId: MediaId(namespace: 0, id: index), representations: [], immediateThumbnailData: immediateThumbnailData, reference: nil, partialReference: nil, flags: [])

View File

@ -246,7 +246,7 @@ public func chatListItemStrings(strings: PresentationStrings, nameDisplayOrder:
processed = true
break inner
}
case let .Video(_, _, flags, _):
case let .Video(_, _, flags, _, _):
if flags.contains(.instantRoundVideo) {
messageText = strings.Message_VideoMessage
processed = true

View File

@ -836,7 +836,7 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, ASScroll
} else if let media = media as? TelegramMediaFile, !media.isAnimated {
for attribute in media.attributes {
switch attribute {
case let .Video(_, dimensions, _, _):
case let .Video(_, dimensions, _, _, _):
isVideo = true
if dimensions.height > 0 {
if CGFloat(dimensions.width) / CGFloat(dimensions.height) > 1.33 {

View File

@ -1235,7 +1235,7 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
}
if let file = file {
for attribute in file.attributes {
if case let .Video(duration, _, _, _) = attribute, duration >= 30 {
if case let .Video(duration, _, _, _, _) = attribute, duration >= 30 {
hintSeekable = true
break
}

View File

@ -53,7 +53,7 @@ final class InstantPageMediaPlaylistItem: SharedMediaPlaylistItem {
} else {
return SharedMediaPlaybackData(type: .music, source: .telegramFile(reference: .webPage(webPage: WebpageReference(self.webPage), media: file), isCopyProtected: false, isViewOnce: false))
}
case let .Video(_, _, flags, _):
case let .Video(_, _, flags, _, _):
if flags.contains(.instantRoundVideo) {
return SharedMediaPlaybackData(type: .instantVideo, source: .telegramFile(reference: .webPage(webPage: WebpageReference(self.webPage), media: file), isCopyProtected: false, isViewOnce: false))
} else {
@ -99,7 +99,7 @@ final class InstantPageMediaPlaylistItem: SharedMediaPlaylistItem {
return SharedMediaPlaybackDisplayData.music(title: updatedTitle, performer: updatedPerformer, albumArt: albumArt, long: false, caption: nil)
}
case let .Video(_, _, flags, _):
case let .Video(_, _, flags, _, _):
if flags.contains(.instantRoundVideo) {
return SharedMediaPlaybackDisplayData.instantVideo(author: nil, peer: nil, timestamp: 0)
} else {

View File

@ -294,7 +294,7 @@ public func legacyEnqueueGifMessage(account: Account, data: Data, correlationId:
let finalDimensions = TGMediaVideoConverter.dimensions(for: dimensions, adjustments: nil, preset: TGMediaVideoConversionPresetAnimation)
var fileAttributes: [TelegramMediaFileAttribute] = []
fileAttributes.append(.Video(duration: 0.0, size: PixelDimensions(finalDimensions), flags: [.supportsStreaming], preloadSize: nil))
fileAttributes.append(.Video(duration: 0.0, size: PixelDimensions(finalDimensions), flags: [.supportsStreaming], preloadSize: nil, coverTime: nil))
fileAttributes.append(.FileName(fileName: fileName))
fileAttributes.append(.Animated)
@ -336,7 +336,7 @@ public func legacyEnqueueVideoMessage(account: Account, data: Data, correlationI
let finalDimensions = TGMediaVideoConverter.dimensions(for: dimensions, adjustments: nil, preset: TGMediaVideoConversionPresetAnimation)
var fileAttributes: [TelegramMediaFileAttribute] = []
fileAttributes.append(.Video(duration: 0.0, size: PixelDimensions(finalDimensions), flags: [.supportsStreaming], preloadSize: nil))
fileAttributes.append(.Video(duration: 0.0, size: PixelDimensions(finalDimensions), flags: [.supportsStreaming], preloadSize: nil, coverTime: nil))
fileAttributes.append(.FileName(fileName: fileName))
fileAttributes.append(.Animated)
@ -857,7 +857,7 @@ public func legacyAssetPickerEnqueueMessages(context: AccountContext, account: A
fileAttributes.append(.Animated)
}
if !asFile {
fileAttributes.append(.Video(duration: finalDuration, size: PixelDimensions(finalDimensions), flags: [.supportsStreaming], preloadSize: nil))
fileAttributes.append(.Video(duration: finalDuration, size: PixelDimensions(finalDimensions), flags: [.supportsStreaming], preloadSize: nil, coverTime: nil))
if let adjustments = adjustments {
if adjustments.sendAsGif {
fileAttributes.append(.Animated)

View File

@ -187,7 +187,7 @@ final class PeerAvatarImageGalleryItemNode: ZoomableContentGalleryItemNode {
let subject: ShareControllerSubject
var actionCompletionText: String?
if let video = entry.videoRepresentations.last, let peerReference = PeerReference(peer._asPeer()) {
let videoFileReference = FileMediaReference.avatarList(peer: peerReference, media: TelegramMediaFile(fileId: EngineMedia.Id(namespace: Namespaces.Media.LocalFile, id: 0), partialReference: nil, resource: video.representation.resource, previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "video/mp4", size: nil, attributes: [.Animated, .Video(duration: 0, size: video.representation.dimensions, flags: [], preloadSize: nil)]))
let videoFileReference = FileMediaReference.avatarList(peer: peerReference, media: TelegramMediaFile(fileId: EngineMedia.Id(namespace: Namespaces.Media.LocalFile, id: 0), partialReference: nil, resource: video.representation.resource, previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "video/mp4", size: nil, attributes: [.Animated, .Video(duration: 0, size: video.representation.dimensions, flags: [], preloadSize: nil, coverTime: nil)]))
subject = .media(videoFileReference.abstract)
actionCompletionText = strongSelf.presentationData.strings.Gallery_VideoSaved
} else {
@ -279,7 +279,7 @@ final class PeerAvatarImageGalleryItemNode: ZoomableContentGalleryItemNode {
if let video = entry.videoRepresentations.last, let peerReference = PeerReference(self.peer._asPeer()) {
if video != previousVideoRepresentations?.last {
let mediaManager = self.context.sharedContext.mediaManager
let videoFileReference = FileMediaReference.avatarList(peer: peerReference, media: TelegramMediaFile(fileId: EngineMedia.Id(namespace: Namespaces.Media.LocalFile, id: 0), partialReference: nil, resource: video.representation.resource, previewRepresentations: representations.map { $0.representation }, videoThumbnails: [], immediateThumbnailData: entry.immediateThumbnailData, mimeType: "video/mp4", size: nil, attributes: [.Animated, .Video(duration: 0, size: video.representation.dimensions, flags: [], preloadSize: nil)]))
let videoFileReference = FileMediaReference.avatarList(peer: peerReference, media: TelegramMediaFile(fileId: EngineMedia.Id(namespace: Namespaces.Media.LocalFile, id: 0), partialReference: nil, resource: video.representation.resource, previewRepresentations: representations.map { $0.representation }, videoThumbnails: [], immediateThumbnailData: entry.immediateThumbnailData, mimeType: "video/mp4", size: nil, attributes: [.Animated, .Video(duration: 0, size: video.representation.dimensions, flags: [], preloadSize: nil, coverTime: nil)]))
let videoContent = NativeVideoContent(id: .profileVideo(id, category), userLocation: .other, fileReference: videoFileReference, streamVideo: isMediaStreamable(resource: video.representation.resource) ? .conservative : .none, loopVideo: true, enableSound: false, fetchAutomatically: true, onlyFullSizeThumbnail: true, useLargeThumbnail: true, continuePlayingWithoutSoundOnLostAudioSession: false, placeholderColor: .clear, storeAfterDownload: nil)
let videoNode = UniversalVideoNode(postbox: self.context.account.postbox, audioSession: mediaManager.audioSession, manager: mediaManager.universalVideoManager, decoration: GalleryVideoDecoration(), content: videoContent, priority: .overlay)
videoNode.isUserInteractionEnabled = false

View File

@ -515,7 +515,7 @@ public final class PeerInfoAvatarListItemNode: ASDisplayNode {
self.isReady.set(.single(true))
}
} else if let video = videoRepresentations.last, let peerReference = PeerReference(self.peer._asPeer()) {
let videoFileReference = FileMediaReference.avatarList(peer: peerReference, media: TelegramMediaFile(fileId: EngineMedia.Id(namespace: Namespaces.Media.LocalFile, id: 0), partialReference: nil, resource: video.representation.resource, previewRepresentations: representations.map { $0.representation }, videoThumbnails: [], immediateThumbnailData: immediateThumbnailData, mimeType: "video/mp4", size: nil, attributes: [.Animated, .Video(duration: 0, size: video.representation.dimensions, flags: [], preloadSize: nil)]))
let videoFileReference = FileMediaReference.avatarList(peer: peerReference, media: TelegramMediaFile(fileId: EngineMedia.Id(namespace: Namespaces.Media.LocalFile, id: 0), partialReference: nil, resource: video.representation.resource, previewRepresentations: representations.map { $0.representation }, videoThumbnails: [], immediateThumbnailData: immediateThumbnailData, mimeType: "video/mp4", size: nil, attributes: [.Animated, .Video(duration: 0, size: video.representation.dimensions, flags: [], preloadSize: nil, coverTime: nil)]))
let videoContent = NativeVideoContent(id: .profileVideo(id, nil), userLocation: .other, fileReference: videoFileReference, streamVideo: isMediaStreamable(resource: video.representation.resource) ? .conservative : .none, loopVideo: true, enableSound: false, fetchAutomatically: true, onlyFullSizeThumbnail: fullSizeOnly, useLargeThumbnail: true, autoFetchFullSizeThumbnail: true, startTimestamp: video.representation.startTimestamp, continuePlayingWithoutSoundOnLostAudioSession: false, placeholderColor: .clear, storeAfterDownload: nil)
if videoContent.id != self.videoContent?.id {

View File

@ -279,7 +279,7 @@ public final class ShareProlongedLoadingContainerNode: ASDisplayNode, ShareConte
if let postbox, let mediaManager = environment.mediaManager, let path = getAppBundle().path(forResource: "BlankVideo", ofType: "m4v"), let size = fileSize(path) {
let decoration = ChatBubbleVideoDecoration(corners: ImageCorners(), nativeSize: CGSize(width: 100.0, height: 100.0), contentMode: .aspectFit, backgroundColor: .black)
let dummyFile = TelegramMediaFile(fileId: EngineMedia.Id(namespace: 0, id: 1), partialReference: nil, resource: LocalFileReferenceMediaResource(localFilePath: path, randomId: 12345), previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "video/mp4", size: size, attributes: [.Video(duration: 1, size: PixelDimensions(width: 100, height: 100), flags: [], preloadSize: nil)])
let dummyFile = TelegramMediaFile(fileId: EngineMedia.Id(namespace: 0, id: 1), partialReference: nil, resource: LocalFileReferenceMediaResource(localFilePath: path, randomId: 12345), previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "video/mp4", size: size, attributes: [.Video(duration: 1, size: PixelDimensions(width: 100, height: 100), flags: [], preloadSize: nil, coverTime: nil)])
let videoContent = NativeVideoContent(id: .message(1, EngineMedia.Id(namespace: 0, id: 1)), userLocation: .other, fileReference: .standalone(media: dummyFile), streamVideo: .none, loopVideo: true, enableSound: false, fetchAutomatically: true, onlyFullSizeThumbnail: false, continuePlayingWithoutSoundOnLostAudioSession: false, placeholderColor: .black, storeAfterDownload: nil)

View File

@ -144,7 +144,7 @@ private func preparedShareItem(postbox: Postbox, network: Network, to peerId: Pe
let estimatedSize = TGMediaVideoConverter.estimatedSize(for: preset, duration: finalDuration, hasAudio: true)
let resource = LocalFileVideoMediaResource(randomId: Int64.random(in: Int64.min ... Int64.max), path: asset.url.path, adjustments: resourceAdjustments)
return standaloneUploadedFile(postbox: postbox, network: network, peerId: peerId, text: "", source: .resource(.standalone(resource: resource)), mimeType: "video/mp4", attributes: [.Video(duration: finalDuration, size: PixelDimensions(width: Int32(finalDimensions.width), height: Int32(finalDimensions.height)), flags: flags, preloadSize: nil)], hintFileIsLarge: estimatedSize > 10 * 1024 * 1024)
return standaloneUploadedFile(postbox: postbox, network: network, peerId: peerId, text: "", source: .resource(.standalone(resource: resource)), mimeType: "video/mp4", attributes: [.Video(duration: finalDuration, size: PixelDimensions(width: Int32(finalDimensions.width), height: Int32(finalDimensions.height)), flags: flags, preloadSize: nil, coverTime: nil)], hintFileIsLarge: estimatedSize > 10 * 1024 * 1024)
|> mapError { _ -> PreparedShareItemError in
return .generic
}
@ -210,7 +210,7 @@ private func preparedShareItem(postbox: Postbox, network: Network, to peerId: Pe
let mimeType: String
if converted {
mimeType = "video/mp4"
attributes = [.Video(duration: duration, size: PixelDimensions(width: Int32(dimensions.width), height: Int32(dimensions.height)), flags: [.supportsStreaming], preloadSize: nil), .Animated, .FileName(fileName: "animation.mp4")]
attributes = [.Video(duration: duration, size: PixelDimensions(width: Int32(dimensions.width), height: Int32(dimensions.height)), flags: [.supportsStreaming], preloadSize: nil, coverTime: nil), .Animated, .FileName(fileName: "animation.mp4")]
} else {
mimeType = "animation/gif"
attributes = [.ImageSize(size: PixelDimensions(width: Int32(dimensions.width), height: Int32(dimensions.height))), .Animated, .FileName(fileName: fileName ?? "animation.gif")]

View File

@ -1677,7 +1677,7 @@ private func monetizationEntries(
}
}
if isCreator {
if isCreator && canViewRevenue {
var switchOffAdds: Bool? = nil
if let boostData, boostData.level >= premiumConfiguration.minChannelRestrictAdsLevel {
switchOffAdds = adsRestricted

View File

@ -50,7 +50,7 @@ public func tagsForStoreMessage(incoming: Bool, attributes: [MessageAttribute],
var isAnimated = false
inner: for attribute in file.attributes {
switch attribute {
case let .Video(_, _, flags, _):
case let .Video(_, _, flags, _, _):
if flags.contains(.instantRoundVideo) {
refinedTag = .voiceOrInstantVideo
} else {

View File

@ -6,7 +6,7 @@ import TelegramApi
func dimensionsForFileAttributes(_ attributes: [TelegramMediaFileAttribute]) -> PixelDimensions? {
for attribute in attributes {
switch attribute {
case let .Video(_, size, _, _):
case let .Video(_, size, _, _, _):
return size
case let .ImageSize(size):
return size
@ -20,7 +20,7 @@ func dimensionsForFileAttributes(_ attributes: [TelegramMediaFileAttribute]) ->
func durationForFileAttributes(_ attributes: [TelegramMediaFileAttribute]) -> Double? {
for attribute in attributes {
switch attribute {
case let .Video(duration, _, _, _):
case let .Video(duration, _, _, _, _):
return duration
case let .Audio(_, duration, _, _, _):
return Double(duration)
@ -99,7 +99,7 @@ func telegramMediaFileAttributesFromApiAttributes(_ attributes: [Api.DocumentAtt
result.append(.ImageSize(size: PixelDimensions(width: w, height: h)))
case .documentAttributeAnimated:
result.append(.Animated)
case let .documentAttributeVideo(flags, duration, w, h, preloadSize, _):
case let .documentAttributeVideo(flags, duration, w, h, preloadSize, videoStart):
var videoFlags = TelegramMediaVideoFlags()
if (flags & (1 << 0)) != 0 {
videoFlags.insert(.instantRoundVideo)
@ -110,7 +110,7 @@ func telegramMediaFileAttributesFromApiAttributes(_ attributes: [Api.DocumentAtt
if (flags & (1 << 3)) != 0 {
videoFlags.insert(.isSilent)
}
result.append(.Video(duration: Double(duration), size: PixelDimensions(width: w, height: h), flags: videoFlags, preloadSize: preloadSize))
result.append(.Video(duration: Double(duration), size: PixelDimensions(width: w, height: h), flags: videoFlags, preloadSize: preloadSize, coverTime: videoStart))
case let .documentAttributeAudio(flags, duration, title, performer, waveform):
let isVoice = (flags & (1 << 10)) != 0
let waveformBuffer: Data? = waveform?.makeData()

View File

@ -701,7 +701,7 @@ func inputDocumentAttributesFromFileAttributes(_ fileAttributes: [TelegramMediaF
attributes.append(.documentAttributeSticker(flags: flags, alt: displayText, stickerset: stickerSet, maskCoords: inputMaskCoords))
case .HasLinkedStickers:
attributes.append(.documentAttributeHasStickers)
case let .Video(duration, size, videoFlags, preloadSize):
case let .Video(duration, size, videoFlags, preloadSize, coverTime):
var flags: Int32 = 0
if videoFlags.contains(.instantRoundVideo) {
flags |= (1 << 0)
@ -715,8 +715,10 @@ func inputDocumentAttributesFromFileAttributes(_ fileAttributes: [TelegramMediaF
if videoFlags.contains(.isSilent) {
flags |= (1 << 3)
}
attributes.append(.documentAttributeVideo(flags: flags, duration: duration, w: Int32(size.width), h: Int32(size.height), preloadPrefixSize: preloadSize, videoStartTs: nil))
if let coverTime = coverTime, coverTime > 0.0 {
flags |= (1 << 4)
}
attributes.append(.documentAttributeVideo(flags: flags, duration: duration, w: Int32(size.width), h: Int32(size.height), preloadPrefixSize: preloadSize, videoStartTs: coverTime))
case let .Audio(isVoice, duration, title, performer, waveform):
var flags: Int32 = 0
if isVoice {
@ -786,7 +788,7 @@ public func statsCategoryForFileWithAttributes(_ attributes: [TelegramMediaFileA
} else {
return .audio
}
case let .Video(_, _, flags, _):
case let .Video(_, _, flags, _, _):
if flags.contains(TelegramMediaVideoFlags.instantRoundVideo) {
return .voiceMessages
} else {

View File

@ -553,7 +553,7 @@ private func decryptedAttributes46(_ attributes: [TelegramMediaFileAttribute], t
result.append(.documentAttributeSticker(alt: displayText, stickerset: stickerSet))
case let .ImageSize(size):
result.append(.documentAttributeImageSize(w: Int32(size.width), h: Int32(size.height)))
case let .Video(duration, size, _, _):
case let .Video(duration, size, _, _, _):
result.append(.documentAttributeVideo(duration: Int32(duration), w: Int32(size.width), h: Int32(size.height)))
case let .Audio(isVoice, duration, title, performer, waveform):
var flags: Int32 = 0
@ -612,7 +612,7 @@ private func decryptedAttributes73(_ attributes: [TelegramMediaFileAttribute], t
result.append(.documentAttributeSticker(alt: displayText, stickerset: stickerSet))
case let .ImageSize(size):
result.append(.documentAttributeImageSize(w: Int32(size.width), h: Int32(size.height)))
case let .Video(duration, size, videoFlags, _):
case let .Video(duration, size, videoFlags, _, _):
var flags: Int32 = 0
if videoFlags.contains(.instantRoundVideo) {
flags |= 1 << 0
@ -675,7 +675,7 @@ private func decryptedAttributes101(_ attributes: [TelegramMediaFileAttribute],
result.append(.documentAttributeSticker(alt: displayText, stickerset: stickerSet))
case let .ImageSize(size):
result.append(.documentAttributeImageSize(w: Int32(size.width), h: Int32(size.height)))
case let .Video(duration, size, videoFlags, _):
case let .Video(duration, size, videoFlags, _, _):
var flags: Int32 = 0
if videoFlags.contains(.instantRoundVideo) {
flags |= 1 << 0
@ -738,7 +738,7 @@ private func decryptedAttributes144(_ attributes: [TelegramMediaFileAttribute],
result.append(.documentAttributeSticker(alt: displayText, stickerset: stickerSet))
case let .ImageSize(size):
result.append(.documentAttributeImageSize(w: Int32(size.width), h: Int32(size.height)))
case let .Video(duration, size, videoFlags, _):
case let .Video(duration, size, videoFlags, _, _):
var flags: Int32 = 0
if videoFlags.contains(.instantRoundVideo) {
flags |= 1 << 0

View File

@ -610,7 +610,7 @@ extension TelegramMediaFileAttribute {
}
self = .Sticker(displayText: alt, packReference: packReference, maskData: nil)
case let .documentAttributeVideo(duration, w, h):
self = .Video(duration: Double(duration), size: PixelDimensions(width: w, height: h), flags: [], preloadSize: nil)
self = .Video(duration: Double(duration), size: PixelDimensions(width: w, height: h), flags: [], preloadSize: nil, coverTime: nil)
}
}
}
@ -642,7 +642,7 @@ extension TelegramMediaFileAttribute {
if (flags & (1 << 0)) != 0 {
videoFlags.insert(.instantRoundVideo)
}
self = .Video(duration: Double(duration), size: PixelDimensions(width: w, height: h), flags: videoFlags, preloadSize: nil)
self = .Video(duration: Double(duration), size: PixelDimensions(width: w, height: h), flags: videoFlags, preloadSize: nil, coverTime: nil)
}
}
}
@ -674,7 +674,7 @@ extension TelegramMediaFileAttribute {
if (flags & (1 << 0)) != 0 {
videoFlags.insert(.instantRoundVideo)
}
self = .Video(duration: Double(duration), size: PixelDimensions(width: w, height: h), flags: videoFlags, preloadSize: nil)
self = .Video(duration: Double(duration), size: PixelDimensions(width: w, height: h), flags: videoFlags, preloadSize: nil, coverTime: nil)
}
}
}
@ -706,7 +706,7 @@ extension TelegramMediaFileAttribute {
if (flags & (1 << 0)) != 0 {
videoFlags.insert(.instantRoundVideo)
}
self = .Video(duration: Double(duration), size: PixelDimensions(width: w, height: h), flags: videoFlags, preloadSize: nil)
self = .Video(duration: Double(duration), size: PixelDimensions(width: w, height: h), flags: videoFlags, preloadSize: nil, coverTime: nil)
}
}
}
@ -821,7 +821,7 @@ private func parseMessage(peerId: PeerId, authorId: PeerId, tagLocalIndex: Int32
text = caption
}
if let file = file {
let parsedAttributes: [TelegramMediaFileAttribute] = [.Video(duration: Double(duration), size: PixelDimensions(width: w, height: h), flags: [], preloadSize: nil), .FileName(fileName: "video.mov")]
let parsedAttributes: [TelegramMediaFileAttribute] = [.Video(duration: Double(duration), size: PixelDimensions(width: w, height: h), flags: [], preloadSize: nil, coverTime: nil), .FileName(fileName: "video.mov")]
var previewRepresentations: [TelegramMediaImageRepresentation] = []
if thumb.size != 0 {
let resource = LocalFileMediaResource(fileId: Int64.random(in: Int64.min ... Int64.max))
@ -1021,7 +1021,7 @@ private func parseMessage(peerId: PeerId, authorId: PeerId, tagLocalIndex: Int32
loop: for attr in parsedAttributes {
switch attr {
case let .Video(_, _, flags, _):
case let .Video(_, _, flags, _, _):
if flags.contains(.instantRoundVideo) {
attributes.append(ConsumableContentMessageAttribute(consumed: false))
}
@ -1040,7 +1040,7 @@ private func parseMessage(peerId: PeerId, authorId: PeerId, tagLocalIndex: Int32
text = caption
}
if let file = file {
let parsedAttributes: [TelegramMediaFileAttribute] = [.Video(duration: Double(duration), size: PixelDimensions(width: w, height: h), flags: [], preloadSize: nil), .FileName(fileName: "video.mov")]
let parsedAttributes: [TelegramMediaFileAttribute] = [.Video(duration: Double(duration), size: PixelDimensions(width: w, height: h), flags: [], preloadSize: nil, coverTime: nil), .FileName(fileName: "video.mov")]
var previewRepresentations: [TelegramMediaImageRepresentation] = []
if thumb.size != 0 {
let resource = LocalFileMediaResource(fileId: Int64.random(in: Int64.min ... Int64.max))
@ -1300,7 +1300,7 @@ private func parseMessage(peerId: PeerId, authorId: PeerId, tagLocalIndex: Int32
loop: for attr in parsedAttributes {
switch attr {
case let .Video(_, _, flags, _):
case let .Video(_, _, flags, _, _):
if flags.contains(.instantRoundVideo) {
attributes.append(ConsumableContentMessageAttribute(consumed: false))
}
@ -1319,7 +1319,7 @@ private func parseMessage(peerId: PeerId, authorId: PeerId, tagLocalIndex: Int32
text = caption
}
if let file = file {
let parsedAttributes: [TelegramMediaFileAttribute] = [.Video(duration: Double(duration), size: PixelDimensions(width: w, height: h), flags: [], preloadSize: nil), .FileName(fileName: "video.mov")]
let parsedAttributes: [TelegramMediaFileAttribute] = [.Video(duration: Double(duration), size: PixelDimensions(width: w, height: h), flags: [], preloadSize: nil, coverTime: nil), .FileName(fileName: "video.mov")]
var previewRepresentations: [TelegramMediaImageRepresentation] = []
if thumb.size != 0 {
let resource = LocalFileMediaResource(fileId: Int64.random(in: Int64.min ... Int64.max))
@ -1501,7 +1501,7 @@ private func parseMessage(peerId: PeerId, authorId: PeerId, tagLocalIndex: Int32
loop: for attr in parsedAttributes {
switch attr {
case let .Video(_, _, flags, _):
case let .Video(_, _, flags, _, _):
if flags.contains(.instantRoundVideo) {
attributes.append(ConsumableContentMessageAttribute(consumed: false))
}
@ -1520,7 +1520,7 @@ private func parseMessage(peerId: PeerId, authorId: PeerId, tagLocalIndex: Int32
text = caption
}
if let file = file {
let parsedAttributes: [TelegramMediaFileAttribute] = [.Video(duration: Double(duration), size: PixelDimensions(width: w, height: h), flags: [], preloadSize: nil), .FileName(fileName: "video.mov")]
let parsedAttributes: [TelegramMediaFileAttribute] = [.Video(duration: Double(duration), size: PixelDimensions(width: w, height: h), flags: [], preloadSize: nil, coverTime: nil), .FileName(fileName: "video.mov")]
var previewRepresentations: [TelegramMediaImageRepresentation] = []
if thumb.size != 0 {
let resource = LocalFileMediaResource(fileId: Int64.random(in: Int64.min ... Int64.max))

View File

@ -235,7 +235,7 @@ public enum TelegramMediaFileAttribute: PostboxCoding, Equatable {
case Sticker(displayText: String, packReference: StickerPackReference?, maskData: StickerMaskCoords?)
case ImageSize(size: PixelDimensions)
case Animated
case Video(duration: Double, size: PixelDimensions, flags: TelegramMediaVideoFlags, preloadSize: Int32?)
case Video(duration: Double, size: PixelDimensions, flags: TelegramMediaVideoFlags, preloadSize: Int32?, coverTime: Double?)
case Audio(isVoice: Bool, duration: Int, title: String?, performer: String?, waveform: Data?)
case HasLinkedStickers
case hintFileIsLarge
@ -262,7 +262,7 @@ public enum TelegramMediaFileAttribute: PostboxCoding, Equatable {
duration = Double(decoder.decodeInt32ForKey("du", orElse: 0))
}
self = .Video(duration: duration, size: PixelDimensions(width: decoder.decodeInt32ForKey("w", orElse: 0), height: decoder.decodeInt32ForKey("h", orElse: 0)), flags: TelegramMediaVideoFlags(rawValue: decoder.decodeInt32ForKey("f", orElse: 0)), preloadSize: decoder.decodeOptionalInt32ForKey("prs"))
self = .Video(duration: duration, size: PixelDimensions(width: decoder.decodeInt32ForKey("w", orElse: 0), height: decoder.decodeInt32ForKey("h", orElse: 0)), flags: TelegramMediaVideoFlags(rawValue: decoder.decodeInt32ForKey("f", orElse: 0)), preloadSize: decoder.decodeOptionalInt32ForKey("prs"), coverTime: decoder.decodeOptionalDoubleForKey("ct"))
case typeAudio:
let waveformBuffer = decoder.decodeBytesForKeyNoCopy("wf")
var waveform: Data?
@ -309,7 +309,7 @@ public enum TelegramMediaFileAttribute: PostboxCoding, Equatable {
encoder.encodeInt32(Int32(size.height), forKey: "h")
case .Animated:
encoder.encodeInt32(typeAnimated, forKey: "t")
case let .Video(duration, size, flags, preloadSize):
case let .Video(duration, size, flags, preloadSize, coverTime):
encoder.encodeInt32(typeVideo, forKey: "t")
encoder.encodeDouble(duration, forKey: "dur")
encoder.encodeInt32(Int32(size.width), forKey: "w")
@ -320,6 +320,11 @@ public enum TelegramMediaFileAttribute: PostboxCoding, Equatable {
} else {
encoder.encodeNil(forKey: "prs")
}
if let coverTime = coverTime {
encoder.encodeDouble(coverTime, forKey: "ct")
} else {
encoder.encodeNil(forKey: "ct")
}
case let .Audio(isVoice, duration, title, performer, waveform):
encoder.encodeInt32(typeAudio, forKey: "t")
encoder.encodeInt32(isVoice ? 1 : 0, forKey: "iv")
@ -592,7 +597,7 @@ public final class TelegramMediaFile: Media, Equatable, Codable {
public var isInstantVideo: Bool {
for attribute in self.attributes {
if case .Video(_, _, let flags, _) = attribute {
if case .Video(_, _, let flags, _, _) = attribute {
return flags.contains(.instantRoundVideo)
}
}
@ -601,7 +606,7 @@ public final class TelegramMediaFile: Media, Equatable, Codable {
public var preloadSize: Int32? {
for attribute in self.attributes {
if case .Video(_, _, _, let preloadSize) = attribute {
if case .Video(_, _, _, let preloadSize, _) = attribute {
return preloadSize
}
}

View File

@ -118,7 +118,7 @@ func _internal_outgoingMessageWithChatContextResult(to peerId: PeerId, threadId:
if let dimensions = externalReference.content?.dimensions {
fileAttributes.append(.ImageSize(size: dimensions))
if externalReference.type == "gif" {
fileAttributes.append(.Video(duration: externalReference.content?.duration ?? 0.0, size: dimensions, flags: [], preloadSize: nil))
fileAttributes.append(.Video(duration: externalReference.content?.duration ?? 0.0, size: dimensions, flags: [], preloadSize: nil, coverTime: nil))
}
}

View File

@ -5,12 +5,12 @@ import TelegramApi
public enum EngineStoryInputMedia {
case image(dimensions: PixelDimensions, data: Data, stickers: [TelegramMediaFile])
case video(dimensions: PixelDimensions, duration: Double, resource: TelegramMediaResource, firstFrameFile: TempBoxFile?, stickers: [TelegramMediaFile])
case video(dimensions: PixelDimensions, duration: Double, resource: TelegramMediaResource, firstFrameFile: TempBoxFile?, stickers: [TelegramMediaFile], coverTime: Double?)
case existing(media: Media)
var embeddedStickers: [TelegramMediaFile] {
switch self {
case let .image(_, _, stickers), let .video(_, _, _, _, stickers):
case let .image(_, _, stickers), let .video(_, _, _, _, stickers, _):
return stickers
case .existing:
return []
@ -849,7 +849,7 @@ private func prepareUploadStoryContent(account: Account, media: EngineStoryInput
flags: []
)
return imageMedia
case let .video(dimensions, duration, resource, firstFrameFile, _):
case let .video(dimensions, duration, resource, firstFrameFile, _, coverTime):
var previewRepresentations: [TelegramMediaImageRepresentation] = []
if let firstFrameFile = firstFrameFile {
account.postbox.mediaBox.storeCachedResourceRepresentation(resource.id.stringRepresentation, representationId: "first-frame", keepDuration: .general, tempFile: firstFrameFile)
@ -871,7 +871,7 @@ private func prepareUploadStoryContent(account: Account, media: EngineStoryInput
mimeType: "video/mp4",
size: nil,
attributes: [
TelegramMediaFileAttribute.Video(duration: duration, size: dimensions, flags: .supportsStreaming, preloadSize: nil)
TelegramMediaFileAttribute.Video(duration: duration, size: dimensions, flags: .supportsStreaming, preloadSize: nil, coverTime: coverTime)
]
)

View File

@ -144,7 +144,7 @@ public extension ImportSticker {
fileAttributes.append(.FileName(fileName: "sticker.webm"))
fileAttributes.append(.Animated)
fileAttributes.append(.Sticker(displayText: "", packReference: nil, maskData: nil))
fileAttributes.append(.Video(duration: self.duration ?? 3.0, size: self.dimensions, flags: [], preloadSize: nil))
fileAttributes.append(.Video(duration: self.duration ?? 3.0, size: self.dimensions, flags: [], preloadSize: nil, coverTime: nil))
} else if self.mimeType == "application/x-tgsticker" {
fileAttributes.append(.FileName(fileName: "sticker.tgs"))
fileAttributes.append(.Animated)

View File

@ -330,7 +330,7 @@ public func mediaContentKind(_ media: EngineMedia, message: EngineMessage? = nil
return .file(performer)
}
}
case let .Video(_, _, flags, _):
case let .Video(_, _, flags, _, _):
if file.isAnimated {
result = .animation
} else {

View File

@ -235,7 +235,7 @@ public func universalServiceMessageString(presentationData: (PresentationTheme,
} else {
for attribute in file.attributes {
switch attribute {
case let .Video(_, _, flags, _):
case let .Video(_, _, flags, _, _):
if flags.contains(.instantRoundVideo) {
type = .round
} else {

View File

@ -172,7 +172,7 @@ private final class ChatContextResultPeekNode: ASDisplayNode, PeekControllerCont
imageDimensions = externalReference.content?.dimensions?.cgSize
if let content = externalReference.content, externalReference.type == "gif", let thumbnailResource = imageResource
, let dimensions = content.dimensions {
videoFileReference = .standalone(media: TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: 0), partialReference: nil, resource: content.resource, previewRepresentations: [TelegramMediaImageRepresentation(dimensions: dimensions, resource: thumbnailResource, progressiveSizes: [], immediateThumbnailData: nil, hasVideo: false, isPersonal: false)], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "video/mp4", size: nil, attributes: [.Animated, .Video(duration: 0, size: dimensions, flags: [], preloadSize: nil)]))
videoFileReference = .standalone(media: TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: 0), partialReference: nil, resource: content.resource, previewRepresentations: [TelegramMediaImageRepresentation(dimensions: dimensions, resource: thumbnailResource, progressiveSizes: [], immediateThumbnailData: nil, hasVideo: false, isPersonal: false)], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "video/mp4", size: nil, attributes: [.Animated, .Video(duration: 0, size: dimensions, flags: [], preloadSize: nil, coverTime: nil)]))
imageResource = nil
}
case let .internalReference(internalReference):

View File

@ -272,7 +272,7 @@ public class ChatMessageActionBubbleContentNode: ChatMessageBubbleContentNode {
strongSelf.mediaBackgroundNode.image = backgroundImage
if let image = image, let video = image.videoRepresentations.last, let id = image.id?.id {
let videoFileReference = FileMediaReference.message(message: MessageReference(item.message), 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: [], preloadSize: nil)]))
let videoFileReference = FileMediaReference.message(message: MessageReference(item.message), 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: [], preloadSize: nil, coverTime: nil)]))
let videoContent = NativeVideoContent(id: .profileVideo(id, "action"), userLocation: .peer(item.message.id.peerId), 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, storeAfterDownload: nil)
if videoContent.id != strongSelf.videoContent?.id {
let mediaManager = item.context.sharedContext.mediaManager

View File

@ -650,7 +650,7 @@ public final class ChatMessageInteractiveFileNode: ASDisplayNode {
let messageTheme = arguments.incoming ? arguments.presentationData.theme.theme.chat.message.incoming : arguments.presentationData.theme.theme.chat.message.outgoing
let isInstantVideo = arguments.file.isInstantVideo
for attribute in arguments.file.attributes {
if case let .Video(videoDuration, _, flags, _) = attribute, flags.contains(.instantRoundVideo) {
if case let .Video(videoDuration, _, flags, _, _) = attribute, flags.contains(.instantRoundVideo) {
isAudio = true
isVoice = true
@ -1558,7 +1558,7 @@ public final class ChatMessageInteractiveFileNode: ASDisplayNode {
var isVoice = false
var audioDuration: Int32?
for attribute in file.attributes {
if case let .Video(duration, _, flags, _) = attribute, flags.contains(.instantRoundVideo) {
if case let .Video(duration, _, flags, _, _) = attribute, flags.contains(.instantRoundVideo) {
isAudio = true
isVoice = true
audioDuration = Int32(duration)

View File

@ -27,7 +27,7 @@ private func mediaMergeableStyle(_ media: Media) -> ChatMessageMerge {
switch attribute {
case .Sticker:
return .semanticallyMerged
case let .Video(_, _, flags, _):
case let .Video(_, _, flags, _, _):
if flags.contains(.instantRoundVideo) {
return .none
}
@ -423,7 +423,7 @@ public final class ChatMessageItemImpl: ChatMessageItem, CustomStringConvertible
viewClassName = ChatMessageStickerItemNode.self
}
break loop
case let .Video(_, _, flags, _):
case let .Video(_, _, flags, _, _):
if flags.contains(.instantRoundVideo) {
viewClassName = ChatMessageBubbleItemNode.self
break loop

View File

@ -203,7 +203,7 @@ public final class ChatMessageAccessibilityData {
text = item.presentationData.strings.VoiceOver_Chat_MusicTitle(title, performer).string
text.append(item.presentationData.strings.VoiceOver_Chat_Duration(durationString).string)
}
case let .Video(duration, _, flags, _):
case let .Video(duration, _, flags, _, _):
isSpecialFile = true
if isSelected == nil {
hint = item.presentationData.strings.VoiceOver_Chat_PlayHint

View File

@ -218,7 +218,7 @@ public class ChatMessageProfilePhotoSuggestionContentNode: ChatMessageBubbleCont
}
if let photo = photo, let video = photo.videoRepresentations.last, let id = photo.id?.id {
let videoFileReference = FileMediaReference.message(message: MessageReference(item.message), 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: [], preloadSize: nil)]))
let videoFileReference = FileMediaReference.message(message: MessageReference(item.message), 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: [], preloadSize: nil, coverTime: nil)]))
let videoContent = NativeVideoContent(id: .profileVideo(id, "action"), userLocation: .peer(item.message.id.peerId), 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, storeAfterDownload: nil)
if videoContent.id != strongSelf.videoContent?.id {
let mediaManager = item.context.sharedContext.mediaManager

View File

@ -126,7 +126,7 @@ public func paneGifSearchForQuery(context: AccountContext, query: String, offset
))
}
}
let file = TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: uniqueId ?? 0), partialReference: nil, resource: resource, previewRepresentations: previews, videoThumbnails: videoThumbnails, immediateThumbnailData: nil, mimeType: "video/mp4", size: nil, attributes: [.Animated, .Video(duration: 0, size: dimensions, flags: [], preloadSize: nil)])
let file = TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: uniqueId ?? 0), partialReference: nil, resource: resource, previewRepresentations: previews, videoThumbnails: videoThumbnails, immediateThumbnailData: nil, mimeType: "video/mp4", size: nil, attributes: [.Animated, .Video(duration: 0, size: dimensions, flags: [], preloadSize: nil, coverTime: nil)])
references.append(MultiplexedVideoNodeFile(file: FileMediaReference.standalone(media: file), contextResult: (collection, result)))
}
case let .internalReference(internalReference):

View File

@ -231,7 +231,7 @@ public func legacyInstantVideoController(theme: PresentationTheme, forStory: Boo
}
}
let media = TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: Int64.random(in: Int64.min ... Int64.max)), partialReference: nil, resource: resource, previewRepresentations: previewRepresentations, videoThumbnails: [], immediateThumbnailData: nil, mimeType: "video/mp4", size: nil, attributes: [.FileName(fileName: "video.mp4"), .Video(duration: finalDuration, size: PixelDimensions(finalDimensions), flags: [.instantRoundVideo], preloadSize: nil)])
let media = TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: Int64.random(in: Int64.min ... Int64.max)), partialReference: nil, resource: resource, previewRepresentations: previewRepresentations, videoThumbnails: [], immediateThumbnailData: nil, mimeType: "video/mp4", size: nil, attributes: [.FileName(fileName: "video.mp4"), .Video(duration: finalDuration, size: PixelDimensions(finalDimensions), flags: [.instantRoundVideo], preloadSize: nil, coverTime: nil)])
var message: EnqueueMessage = .message(text: "", attributes: [], inlineStickers: [:], mediaReference: .standalone(media: media), threadId: nil, replyToMessageId: nil, replyToStoryId: nil, localGroupingKey: nil, correlationId: nil, bubbleUpEmojiOrStickersets: [])
let scheduleTime: Int32? = scheduleTimestamp > 0 ? scheduleTimestamp : nil

View File

@ -476,6 +476,7 @@ public final class MediaEditor {
audioTrackOffset: nil,
audioTrackVolume: nil,
audioTrackSamples: nil,
coverImageTimestamp: nil,
qualityPreset: nil
)
}
@ -1733,6 +1734,12 @@ public final class MediaEditor {
}
}
public func setCoverImageTimestamp(_ coverImageTimestamp: Double?) {
self.updateValues(mode: .skipRendering) { values in
return values.withUpdatedCoverImageTimestamp(coverImageTimestamp)
}
}
public func setDrawingAndEntities(data: Data?, image: UIImage?, entities: [CodableDrawingEntity]) {
self.updateValues(mode: .skipRendering) { values in
return values.withUpdatedDrawingAndEntities(drawing: image, entities: entities)

View File

@ -324,6 +324,9 @@ public final class MediaEditorValues: Codable, Equatable {
if lhs.audioTrackSamples != rhs.audioTrackSamples {
return false
}
if lhs.coverImageTimestamp != rhs.coverImageTimestamp {
return false
}
if lhs.nightTheme != rhs.nightTheme {
return false
}
@ -394,6 +397,7 @@ public final class MediaEditorValues: Codable, Equatable {
case audioTrackTrimRange
case audioTrackOffset
case audioTrackVolume
case coverImageTimestamp
case qualityPreset
}
@ -438,6 +442,8 @@ public final class MediaEditorValues: Codable, Equatable {
public let audioTrackVolume: CGFloat?
public let audioTrackSamples: MediaAudioTrackSamples?
public let coverImageTimestamp: Double?
public let qualityPreset: MediaQualityPreset?
var isStory: Bool {
@ -486,6 +492,7 @@ public final class MediaEditorValues: Codable, Equatable {
audioTrackOffset: Double?,
audioTrackVolume: CGFloat?,
audioTrackSamples: MediaAudioTrackSamples?,
coverImageTimestamp: Double?,
qualityPreset: MediaQualityPreset?
) {
self.peerId = peerId
@ -521,6 +528,7 @@ public final class MediaEditorValues: Codable, Equatable {
self.audioTrackOffset = audioTrackOffset
self.audioTrackVolume = audioTrackVolume
self.audioTrackSamples = audioTrackSamples
self.coverImageTimestamp = coverImageTimestamp
self.qualityPreset = qualityPreset
}
@ -591,6 +599,8 @@ public final class MediaEditorValues: Codable, Equatable {
self.audioTrackSamples = nil
self.coverImageTimestamp = try container.decodeIfPresent(Double.self, forKey: .coverImageTimestamp)
self.qualityPreset = (try container.decodeIfPresent(Int32.self, forKey: .qualityPreset)).flatMap { MediaQualityPreset(rawValue: $0) }
}
@ -652,109 +662,115 @@ public final class MediaEditorValues: Codable, Equatable {
try container.encodeIfPresent(self.audioTrackOffset, forKey: .audioTrackOffset)
try container.encodeIfPresent(self.audioTrackVolume, forKey: .audioTrackVolume)
try container.encodeIfPresent(self.coverImageTimestamp, forKey: .coverImageTimestamp)
try container.encodeIfPresent(self.qualityPreset?.rawValue, forKey: .qualityPreset)
}
public func makeCopy() -> MediaEditorValues {
return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, qualityPreset: self.qualityPreset)
return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, coverImageTimestamp: self.coverImageTimestamp, qualityPreset: self.qualityPreset)
}
func withUpdatedCrop(offset: CGPoint, scale: CGFloat, rotation: CGFloat, mirroring: Bool) -> MediaEditorValues {
return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: offset, cropRect: self.cropRect, cropScale: scale, cropRotation: rotation, cropMirroring: mirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, qualityPreset: self.qualityPreset)
return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: offset, cropRect: self.cropRect, cropScale: scale, cropRotation: rotation, cropMirroring: mirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, coverImageTimestamp: self.coverImageTimestamp, qualityPreset: self.qualityPreset)
}
public func withUpdatedCropRect(cropRect: CGRect, rotation: CGFloat, mirroring: Bool) -> MediaEditorValues {
return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: .zero, cropRect: cropRect, cropScale: 1.0, cropRotation: rotation, cropMirroring: mirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, qualityPreset: self.qualityPreset)
return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: .zero, cropRect: cropRect, cropScale: 1.0, cropRotation: rotation, cropMirroring: mirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, coverImageTimestamp: self.coverImageTimestamp, qualityPreset: self.qualityPreset)
}
func withUpdatedGradientColors(gradientColors: [UIColor]) -> MediaEditorValues {
return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: gradientColors, videoTrimRange: self.videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, qualityPreset: self.qualityPreset)
return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: gradientColors, videoTrimRange: self.videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, coverImageTimestamp: self.coverImageTimestamp, qualityPreset: self.qualityPreset)
}
func withUpdatedVideoIsMuted(_ videoIsMuted: Bool) -> MediaEditorValues {
return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoIsMuted: videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, qualityPreset: self.qualityPreset)
return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoIsMuted: videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, coverImageTimestamp: self.coverImageTimestamp, qualityPreset: self.qualityPreset)
}
func withUpdatedVideoIsFullHd(_ videoIsFullHd: Bool) -> MediaEditorValues {
return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, qualityPreset: self.qualityPreset)
return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, coverImageTimestamp: self.coverImageTimestamp, qualityPreset: self.qualityPreset)
}
func withUpdatedVideoIsMirrored(_ videoIsMirrored: Bool) -> MediaEditorValues {
return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, qualityPreset: self.qualityPreset)
return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, coverImageTimestamp: self.coverImageTimestamp, qualityPreset: self.qualityPreset)
}
func withUpdatedVideoVolume(_ videoVolume: CGFloat?) -> MediaEditorValues {
return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoIsMuted: videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, qualityPreset: self.qualityPreset)
return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoIsMuted: videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, coverImageTimestamp: self.coverImageTimestamp, qualityPreset: self.qualityPreset)
}
func withUpdatedAdditionalVideo(path: String?, isDual: Bool, positionChanges: [VideoPositionChange]) -> MediaEditorValues {
return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: path, additionalVideoIsDual: isDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: positionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, qualityPreset: self.qualityPreset)
return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: path, additionalVideoIsDual: isDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: positionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, coverImageTimestamp: self.coverImageTimestamp, qualityPreset: self.qualityPreset)
}
func withUpdatedAdditionalVideo(position: CGPoint, scale: CGFloat, rotation: CGFloat) -> MediaEditorValues {
return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: position, additionalVideoScale: scale, additionalVideoRotation: rotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, qualityPreset: self.qualityPreset)
return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: position, additionalVideoScale: scale, additionalVideoRotation: rotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, coverImageTimestamp: self.coverImageTimestamp, qualityPreset: self.qualityPreset)
}
func withUpdatedAdditionalVideoTrimRange(_ additionalVideoTrimRange: Range<Double>?) -> MediaEditorValues {
return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, qualityPreset: self.qualityPreset)
return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, coverImageTimestamp: self.coverImageTimestamp, qualityPreset: self.qualityPreset)
}
func withUpdatedAdditionalVideoOffset(_ additionalVideoOffset: Double?) -> MediaEditorValues {
return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, qualityPreset: self.qualityPreset)
return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, coverImageTimestamp: self.coverImageTimestamp, qualityPreset: self.qualityPreset)
}
func withUpdatedAdditionalVideoVolume(_ additionalVideoVolume: CGFloat?) -> MediaEditorValues {
return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: additionalVideoVolume, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, qualityPreset: self.qualityPreset)
return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: additionalVideoVolume, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, coverImageTimestamp: self.coverImageTimestamp, qualityPreset: self.qualityPreset)
}
func withUpdatedVideoTrimRange(_ videoTrimRange: Range<Double>) -> MediaEditorValues {
return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, qualityPreset: self.qualityPreset)
return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, coverImageTimestamp: self.coverImageTimestamp, qualityPreset: self.qualityPreset)
}
func withUpdatedDrawingAndEntities(drawing: UIImage?, entities: [CodableDrawingEntity]) -> MediaEditorValues {
return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, nightTheme: self.nightTheme, drawing: drawing, maskDrawing: self.maskDrawing, entities: entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, qualityPreset: self.qualityPreset)
return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, nightTheme: self.nightTheme, drawing: drawing, maskDrawing: self.maskDrawing, entities: entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, coverImageTimestamp: self.coverImageTimestamp, qualityPreset: self.qualityPreset)
}
public func withUpdatedMaskDrawing(maskDrawing: UIImage?) -> MediaEditorValues {
return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, qualityPreset: self.qualityPreset)
return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, coverImageTimestamp: self.coverImageTimestamp, qualityPreset: self.qualityPreset)
}
func withUpdatedToolValues(_ toolValues: [EditorToolKey: Any]) -> MediaEditorValues {
return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, qualityPreset: self.qualityPreset)
return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, coverImageTimestamp: self.coverImageTimestamp, qualityPreset: self.qualityPreset)
}
func withUpdatedAudioTrack(_ audioTrack: MediaAudioTrack?) -> MediaEditorValues {
return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, qualityPreset: self.qualityPreset)
return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, coverImageTimestamp: self.coverImageTimestamp, qualityPreset: self.qualityPreset)
}
func withUpdatedAudioTrackTrimRange(_ audioTrackTrimRange: Range<Double>?) -> MediaEditorValues {
return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, qualityPreset: self.qualityPreset)
return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, coverImageTimestamp: self.coverImageTimestamp, qualityPreset: self.qualityPreset)
}
func withUpdatedAudioTrackOffset(_ audioTrackOffset: Double?) -> MediaEditorValues {
return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, qualityPreset: self.qualityPreset)
return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, coverImageTimestamp: self.coverImageTimestamp, qualityPreset: self.qualityPreset)
}
func withUpdatedAudioTrackVolume(_ audioTrackVolume: CGFloat?) -> MediaEditorValues {
return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: audioTrackVolume, audioTrackSamples: self.audioTrackSamples, qualityPreset: self.qualityPreset)
return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: audioTrackVolume, audioTrackSamples: self.audioTrackSamples, coverImageTimestamp: self.coverImageTimestamp, qualityPreset: self.qualityPreset)
}
func withUpdatedAudioTrackSamples(_ audioTrackSamples: MediaAudioTrackSamples?) -> MediaEditorValues {
return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: audioTrackSamples, qualityPreset: self.qualityPreset)
return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: audioTrackSamples, coverImageTimestamp: self.coverImageTimestamp, qualityPreset: self.qualityPreset)
}
func withUpdatedNightTheme(_ nightTheme: Bool) -> MediaEditorValues {
return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, nightTheme: nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, qualityPreset: self.qualityPreset)
return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, nightTheme: nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, coverImageTimestamp: self.coverImageTimestamp, qualityPreset: self.qualityPreset)
}
public func withUpdatedEntities(_ entities: [CodableDrawingEntity]) -> MediaEditorValues {
return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, qualityPreset: self.qualityPreset)
return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, coverImageTimestamp: self.coverImageTimestamp, qualityPreset: self.qualityPreset)
}
public func withUpdatedCoverImageTimestamp(_ coverImageTimestamp: Double?) -> MediaEditorValues {
return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, coverImageTimestamp: coverImageTimestamp, qualityPreset: self.qualityPreset)
}
public func withUpdatedQualityPreset(_ qualityPreset: MediaQualityPreset?) -> MediaEditorValues {
return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, qualityPreset: qualityPreset)
return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, coverImageTimestamp: self.coverImageTimestamp, qualityPreset: qualityPreset)
}
public var resultDimensions: PixelDimensions {

View File

@ -216,7 +216,7 @@ public extension MediaEditorScreen {
}
}
update((context.engine.messages.editStory(peerId: peer.id, id: storyItem.id, media: .video(dimensions: dimensions, duration: duration, resource: resource, firstFrameFile: firstFrameFile, stickers: result.stickers), mediaAreas: result.mediaAreas, text: updatedText, entities: updatedEntities, privacy: nil)
update((context.engine.messages.editStory(peerId: peer.id, id: storyItem.id, media: .video(dimensions: dimensions, duration: duration, resource: resource, firstFrameFile: firstFrameFile, stickers: result.stickers, coverTime: nil), mediaAreas: result.mediaAreas, text: updatedText, entities: updatedEntities, privacy: nil)
|> deliverOnMainQueue).startStrict(next: { result in
switch result {
case let .progress(progress):

View File

@ -0,0 +1,589 @@
import Foundation
import UIKit
import Display
import AsyncDisplayKit
import ComponentFlow
import SwiftSignalKit
import ViewControllerComponent
import ComponentDisplayAdapters
import TelegramPresentationData
import AccountContext
import TelegramCore
import MultilineTextComponent
import MediaEditor
import MediaScrubberComponent
import ButtonComponent
private final class MediaCoverScreenComponent: Component {
typealias EnvironmentType = ViewControllerComponentContainer.Environment
let context: AccountContext
let mediaEditor: MediaEditor
init(
context: AccountContext,
mediaEditor: MediaEditor
) {
self.context = context
self.mediaEditor = mediaEditor
}
static func ==(lhs: MediaCoverScreenComponent, rhs: MediaCoverScreenComponent) -> Bool {
if lhs.context !== rhs.context {
return false
}
return true
}
final class State: ComponentState {
enum ImageKey: Hashable {
case done
}
private var cachedImages: [ImageKey: UIImage] = [:]
func image(_ key: ImageKey) -> UIImage {
if let image = self.cachedImages[key] {
return image
} else {
var image: UIImage
switch key {
case .done:
image = generateTintedImage(image: UIImage(bundleImageName: "Media Editor/Done"), color: .white)!
}
cachedImages[key] = image
return image
}
}
var playerStateDisposable: Disposable?
var playerState: MediaEditorPlayerState?
init(mediaEditor: MediaEditor) {
super.init()
self.playerStateDisposable = (mediaEditor.playerState(framesCount: 16)
|> deliverOnMainQueue).start(next: { [weak self] playerState in
if let self {
if self.playerState != playerState {
self.playerState = playerState
self.updated()
}
}
})
}
deinit {
self.playerStateDisposable?.dispose()
}
}
func makeState() -> State {
return State(mediaEditor: self.mediaEditor)
}
public final class View: UIView {
private let buttonsContainerView = UIView()
private let buttonsBackgroundView = UIImageView()
private let previewContainerView = UIView()
private let cancelButton = ComponentView<Empty>()
private let label = ComponentView<Empty>()
private let doneButton = ComponentView<Empty>()
private let scrubber = ComponentView<Empty>()
private let fadeView = UIView()
private var component: MediaCoverScreenComponent?
private weak var state: State?
private var environment: ViewControllerComponentContainer.Environment?
override init(frame: CGRect) {
self.buttonsContainerView.clipsToBounds = true
self.fadeView.alpha = 0.0
self.fadeView.backgroundColor = UIColor(rgb: 0x000000, alpha: 0.7)
self.buttonsBackgroundView.image = generateImage(CGSize(width: 22.0, height: 22.0), rotatedContext: { size, context in
context.setFillColor(UIColor.black.cgColor)
context.fill(CGRect(origin: .zero, size: size))
context.setBlendMode(.clear)
context.setFillColor(UIColor.clear.cgColor)
context.addPath(CGPath(roundedRect: CGRect(x: 0.0, y: -11.0, width: size.width, height: 22.0), cornerWidth: 11.0, cornerHeight: 11.0, transform: nil))
context.fillPath()
})?.stretchableImage(withLeftCapWidth: 11, topCapHeight: 11)
super.init(frame: frame)
self.backgroundColor = .clear
self.addSubview(self.buttonsContainerView)
self.buttonsContainerView.addSubview(self.buttonsBackgroundView)
self.addSubview(self.fadeView)
self.addSubview(self.previewContainerView)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func animateInFromEditor() {
self.buttonsBackgroundView.layer.animatePosition(from: CGPoint(x: 0.0, y: 44.0), to: .zero, duration: 0.2, additive: true)
self.label.view?.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
if let view = self.doneButton.view {
view.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
view.layer.animateScale(from: 0.1, to: 1.0, duration: 0.2)
}
}
private var animatingOut = false
func animateOutToEditor(completion: @escaping () -> Void) {
self.animatingOut = true
self.fadeView.layer.animateAlpha(from: self.fadeView.alpha, to: 0.0, duration: 0.2, removeOnCompletion: false)
self.buttonsBackgroundView.layer.animatePosition(from: .zero, to: CGPoint(x: 0.0, y: 44.0), duration: 0.2, removeOnCompletion: false, additive: true)
self.label.view?.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false)
if let view = self.scrubber.view {
view.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { _ in
completion()
})
view.layer.animateScale(from: 1.0, to: 0.1, duration: 0.2)
}
if let view = self.cancelButton.view {
view.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false)
view.layer.animateScale(from: 1.0, to: 0.1, duration: 0.2)
}
if let view = self.doneButton.view {
view.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false)
view.layer.animateScale(from: 1.0, to: 0.1, duration: 0.2)
}
self.state?.updated()
}
// override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
// let result = super.hitTest(point, with: event)
// if let controller = self.environment?.controller() as? MediaCoverScreen, [.erase, .restore].contains(controller.mode), result == self.previewContainerView {
// return nil
// }
// return result
// }
func update(component: MediaCoverScreenComponent, availableSize: CGSize, state: State, environment: Environment<ViewControllerComponentContainer.Environment>, transition: ComponentTransition) -> CGSize {
let environment = environment[ViewControllerComponentContainer.Environment.self].value
self.environment = environment
guard let controller = environment.controller() as? MediaCoverScreen else {
return .zero
}
// let isFirstTime = self.component == nil
self.component = component
self.state = state
let isTablet: Bool
if case .regular = environment.metrics.widthClass {
isTablet = true
} else {
isTablet = false
}
let buttonSideInset: CGFloat = 16.0
var controlsBottomInset: CGFloat = 0.0
let previewSize: CGSize
var topInset: CGFloat = environment.statusBarHeight + 5.0
if isTablet {
let previewHeight = availableSize.height - topInset - 75.0
previewSize = CGSize(width: floorToScreenPixels(previewHeight / 1.77778), height: previewHeight)
} else {
previewSize = CGSize(width: availableSize.width, height: floorToScreenPixels(availableSize.width * 1.77778))
if availableSize.height < previewSize.height + 30.0 {
topInset = 0.0
controlsBottomInset = -75.0
}
}
let previewContainerFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - previewSize.width) / 2.0), y: environment.safeInsets.top), size: CGSize(width: previewSize.width, height: availableSize.height - environment.safeInsets.top - environment.safeInsets.bottom + controlsBottomInset))
let buttonsContainerFrame = CGRect(origin: CGPoint(x: 0.0, y: availableSize.height - environment.safeInsets.bottom + controlsBottomInset - 31.0), size: CGSize(width: availableSize.width, height: environment.safeInsets.bottom - controlsBottomInset))
let cancelButtonSize = self.cancelButton.update(
transition: transition,
component: AnyComponent(Button(
content: AnyComponent(
MultilineTextComponent(text: .plain(NSAttributedString(string: "Cancel", font: Font.regular(17.0), textColor: .white)))
),
action: { [weak controller] in
controller?.requestDismiss(animated: true)
}
)),
environment: {},
containerSize: CGSize(width: 120.0, height: 44.0)
)
let cancelButtonFrame = CGRect(
origin: CGPoint(x: 16.0, y: 80.0),
size: cancelButtonSize
)
if let cancelButtonView = self.cancelButton.view {
if cancelButtonView.superview == nil {
self.addSubview(cancelButtonView)
setupButtonShadow(cancelButtonView)
}
transition.setFrame(view: cancelButtonView, frame: cancelButtonFrame)
}
let doneButtonSize = self.doneButton.update(
transition: transition,
component: AnyComponent(
ButtonComponent(
background: ButtonComponent.Background(
color: environment.theme.list.itemCheckColors.fillColor,
foreground: environment.theme.list.itemCheckColors.foregroundColor,
pressedColor: environment.theme.list.itemCheckColors.fillColor.withMultipliedAlpha(0.9)
),
content: AnyComponentWithIdentity(
id: AnyHashable(0),
component: AnyComponent(ButtonTextContentComponent(
text: "Save Cover",
badge: 0,
textColor: environment.theme.list.itemCheckColors.foregroundColor,
badgeBackground: .clear,
badgeForeground: .clear
))
),
isEnabled: true,
displaysProgress: false,
action: { [weak controller, weak self] in
if let playerState = self?.state?.playerState, let mediaEditor = self?.component?.mediaEditor, let image = mediaEditor.resultImage {
mediaEditor.setCoverImageTimestamp(playerState.position)
controller?.completed(playerState.position, image)
}
controller?.requestDismiss(animated: true)
}
)
),
environment: {},
containerSize: CGSize(width: availableSize.width - buttonSideInset * 2.0, height: 50.0)
)
let doneButtonFrame = CGRect(
origin: CGPoint(x: floor((availableSize.width - doneButtonSize.width) / 2.0), y: availableSize.height - 99.0),
size: doneButtonSize
)
if let doneButtonView = self.doneButton.view {
if doneButtonView.superview == nil {
self.addSubview(doneButtonView)
}
transition.setFrame(view: doneButtonView, frame: doneButtonFrame)
}
let labelSize = self.label.update(
transition: transition,
component: AnyComponent(Text(text: "Story Cover", font: Font.semibold(17.0), color: UIColor(rgb: 0xffffff))),
environment: {},
containerSize: CGSize(width: availableSize.width - 88.0, height: 44.0)
)
let labelFrame = CGRect(
origin: CGPoint(x: floorToScreenPixels((availableSize.width - labelSize.width) / 2.0), y: 80.0),
size: labelSize
)
if let labelView = self.label.view {
if labelView.superview == nil {
self.addSubview(labelView)
setupButtonShadow(labelView)
}
if labelView.bounds.width > 0.0 && labelFrame.width != labelView.bounds.width {
if let snapshotView = labelView.snapshotView(afterScreenUpdates: false) {
snapshotView.center = labelView.center
self.buttonsContainerView.addSubview(snapshotView)
labelView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25)
snapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false, completion: { _ in
snapshotView.removeFromSuperview()
})
}
}
labelView.bounds = CGRect(origin: .zero, size: labelFrame.size)
transition.setPosition(view: labelView, position: labelFrame.center)
}
transition.setFrame(view: self.buttonsContainerView, frame: buttonsContainerFrame)
transition.setFrame(view: self.buttonsBackgroundView, frame: CGRect(origin: .zero, size: buttonsContainerFrame.size))
transition.setFrame(view: self.previewContainerView, frame: previewContainerFrame)
if let playerState = state.playerState {
let visibleTracks = playerState.tracks.filter { $0.id == 0 }.map { MediaScrubberComponent.Track($0) }
let mediaEditor = component.mediaEditor
let scrubberInset: CGFloat = buttonSideInset
let scrubberSize = self.scrubber.update(
transition: transition,
component: AnyComponent(MediaScrubberComponent(
context: component.context,
style: .cover,
theme: environment.theme,
generationTimestamp: playerState.generationTimestamp,
position: playerState.position,
minDuration: 1.0,
maxDuration: storyMaxVideoDuration,
isPlaying: playerState.isPlaying,
tracks: visibleTracks,
portalView: controller.portalView,
positionUpdated: { [weak mediaEditor] position, apply in
if let mediaEditor {
mediaEditor.seek(position, andPlay: false)
}
},
trackTrimUpdated: { _, _, _, _, _ in
},
trackOffsetUpdated: { _, _, _ in
},
trackLongPressed: { _, _ in
}
)),
environment: {},
containerSize: CGSize(width: previewSize.width - scrubberInset * 2.0, height: availableSize.height)
)
let scrubberFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - scrubberSize.width) / 2.0), y: availableSize.height - environment.safeInsets.bottom - scrubberSize.height + controlsBottomInset + 3.0 - 40.0), size: scrubberSize)
if let scrubberView = self.scrubber.view {
var animateIn = false
if scrubberView.superview == nil {
animateIn = true
self.addSubview(scrubberView)
}
if animateIn {
scrubberView.frame = scrubberFrame
} else {
transition.setFrame(view: scrubberView, frame: scrubberFrame)
}
if animateIn {
scrubberView.layer.animatePosition(from: CGPoint(x: 0.0, y: 44.0), to: .zero, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, additive: true)
scrubberView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
scrubberView.layer.animateScale(from: 0.6, to: 1.0, duration: 0.2)
}
}
}
return availableSize
}
}
func makeView() -> View {
return View()
}
public func update(view: View, availableSize: CGSize, state: State, environment: Environment<ViewControllerComponentContainer.Environment>, transition: ComponentTransition) -> CGSize {
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
}
}
final class MediaCoverScreen: ViewController {
fileprivate final class Node: ViewControllerTracingNode, ASGestureRecognizerDelegate {
private weak var controller: MediaCoverScreen?
private let context: AccountContext
fileprivate let componentHost: ComponentView<ViewControllerComponentContainer.Environment>
private var presentationData: PresentationData
private var validLayout: ContainerViewLayout?
init(controller: MediaCoverScreen) {
self.controller = controller
self.context = controller.context
self.presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
self.componentHost = ComponentView<ViewControllerComponentContainer.Environment>()
super.init()
self.backgroundColor = .clear
}
override func didLoad() {
super.didLoad()
self.view.disablesInteractiveModalDismiss = true
self.view.disablesInteractiveKeyboardGestureRecognizer = true
}
@objc func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return true
}
func animateInFromEditor() {
if let view = self.componentHost.view as? MediaCoverScreenComponent.View {
view.animateInFromEditor()
}
}
func animateOutToEditor(completion: @escaping () -> Void) {
if let mediaEditor = self.controller?.mediaEditor {
mediaEditor.play()
}
if let view = self.componentHost.view as? MediaCoverScreenComponent.View {
view.animateOutToEditor(completion: completion)
}
}
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
let result = super.hitTest(point, with: event)
if result === self.view {
return nil
}
return result
}
func requestLayout(transition: ComponentTransition) {
if let layout = self.validLayout {
self.containerLayoutUpdated(layout: layout, forceUpdate: true, transition: transition)
}
}
func containerLayoutUpdated(layout: ContainerViewLayout, forceUpdate: Bool = false, animateOut: Bool = false, transition: ComponentTransition) {
guard let controller = self.controller else {
return
}
let isFirstTime = self.validLayout == nil
self.validLayout = layout
let isTablet = layout.metrics.isTablet
let previewSize: CGSize
let topInset: CGFloat = (layout.statusBarHeight ?? 0.0) + 5.0
if isTablet {
let previewHeight = layout.size.height - topInset - 75.0
previewSize = CGSize(width: floorToScreenPixels(previewHeight / 1.77778), height: previewHeight)
} else {
previewSize = CGSize(width: layout.size.width, height: floorToScreenPixels(layout.size.width * 1.77778))
}
let bottomInset = layout.size.height - previewSize.height - topInset
let environment = ViewControllerComponentContainer.Environment(
statusBarHeight: layout.statusBarHeight ?? 0.0,
navigationHeight: 0.0,
safeInsets: UIEdgeInsets(
top: topInset,
left: layout.safeInsets.left,
bottom: bottomInset,
right: layout.safeInsets.right
),
additionalInsets: layout.additionalInsets,
inputHeight: layout.inputHeight ?? 0.0,
metrics: layout.metrics,
deviceMetrics: layout.deviceMetrics,
orientation: nil,
isVisible: true,
theme: self.presentationData.theme,
strings: self.presentationData.strings,
dateTimeFormat: self.presentationData.dateTimeFormat,
controller: { [weak self] in
return self?.controller
}
)
let componentSize = self.componentHost.update(
transition: transition,
component: AnyComponent(
MediaCoverScreenComponent(
context: self.context,
mediaEditor: controller.mediaEditor
)
),
environment: {
environment
},
forceUpdate: forceUpdate || animateOut,
containerSize: layout.size
)
if let componentView = self.componentHost.view {
if componentView.superview == nil {
self.view.insertSubview(componentView, at: 3)
componentView.clipsToBounds = true
}
let componentFrame = CGRect(origin: .zero, size: componentSize)
transition.setFrame(view: componentView, frame: CGRect(origin: componentFrame.origin, size: CGSize(width: componentFrame.width, height: componentFrame.height)))
}
if isFirstTime {
self.animateInFromEditor()
}
}
}
fileprivate var node: Node {
return self.displayNode as! Node
}
fileprivate let context: AccountContext
fileprivate let mediaEditor: MediaEditor
fileprivate let previewView: MediaEditorPreviewView
fileprivate let portalView: PortalView
var completed: (Double, UIImage) -> Void = { _, _ in }
var dismissed: () -> Void = {}
private var initialValues: MediaEditorValues
init(
context: AccountContext,
mediaEditor: MediaEditor,
previewView: MediaEditorPreviewView,
portalView: PortalView
) {
self.context = context
self.mediaEditor = mediaEditor
self.previewView = previewView
self.portalView = portalView
self.initialValues = mediaEditor.values.makeCopy()
super.init(navigationBarPresentationData: nil)
self.navigationPresentation = .flatModal
self.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .portrait)
self.statusBar.statusBarStyle = .White
if let coverImageTimestamp = mediaEditor.values.coverImageTimestamp {
mediaEditor.seek(coverImageTimestamp, andPlay: false)
} else {
mediaEditor.seek(0.0, andPlay: false)
}
}
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func loadDisplayNode() {
self.displayNode = Node(controller: self)
super.displayNodeDidLoad()
}
func requestDismiss(animated: Bool) {
self.dismissed()
self.node.animateOutToEditor(completion: {
self.dismiss()
})
}
override func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
super.containerLayoutUpdated(layout, transition: transition)
(self.displayNode as! Node).containerLayoutUpdated(layout: layout, transition: ComponentTransition(transition))
}
}
private func setupButtonShadow(_ view: UIView, radius: CGFloat = 2.0) {
view.layer.shadowOffset = CGSize(width: 0.0, height: 0.0)
view.layer.shadowRadius = radius
view.layer.shadowColor = UIColor.black.cgColor
view.layer.shadowOpacity = 0.35
}

View File

@ -76,6 +76,7 @@ final class MediaEditorScreenComponent: Component {
case cutout
case cutoutErase
case cutoutRestore
case cover
}
let context: AccountContext
@ -827,7 +828,6 @@ final class MediaEditorScreenComponent: Component {
doneButtonTitle = nil
doneButtonIcon = generateTintedImage(image: UIImage(bundleImageName: "Media Editor/Apply"), color: .white)!
case .botPreview:
//TODO:localize
doneButtonTitle = environment.strings.Story_Editor_Add
doneButtonIcon = nil
}
@ -841,31 +841,7 @@ final class MediaEditorScreenComponent: Component {
title: doneButtonTitle)),
effectAlignment: .center,
action: { [weak controller] in
guard let controller else {
return
}
switch controller.mode {
case .storyEditor:
guard !controller.node.recording.isActive else {
return
}
guard controller.checkCaptionLimit() else {
return
}
if controller.isEditingStory {
controller.requestStoryCompletion(animated: true)
} else {
if controller.checkIfCompletionIsAllowed() {
controller.openPrivacySettings(completion: { [weak controller] in
controller?.requestStoryCompletion(animated: true)
})
}
}
case .stickerEditor:
controller.requestStickerCompletion(animated: true)
case .botPreview:
controller.requestStoryCompletion(animated: true)
}
controller?.node.requestCompletion()
}
)),
environment: {},
@ -2555,6 +2531,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
fileprivate var drawingScreen: DrawingScreen?
fileprivate var stickerScreen: StickerPickerScreen?
fileprivate weak var cutoutScreen: MediaCutoutScreen?
fileprivate weak var coverScreen: MediaCoverScreen?
private var defaultToEmoji = false
private var previousDrawingData: Data?
@ -4614,6 +4591,75 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
)
}
func requestCompletion() {
guard let controller = self.controller else {
return
}
switch controller.mode {
case .storyEditor:
guard !controller.node.recording.isActive else {
return
}
guard controller.checkCaptionLimit() else {
return
}
if controller.isEditingStory {
controller.requestStoryCompletion(animated: true)
} else {
if controller.checkIfCompletionIsAllowed() {
controller.openPrivacySettings(completion: { [weak controller] in
controller?.requestStoryCompletion(animated: true)
})
}
}
case .stickerEditor:
controller.requestStickerCompletion(animated: true)
case .botPreview:
controller.requestStoryCompletion(animated: true)
}
}
func openCoverSelection() {
guard let mediaEditor = self.mediaEditor else {
return
}
guard let portalView = PortalView(matchPosition: false) else {
return
}
portalView.view.layer.rasterizationScale = UIScreenScale
self.previewContentContainerView.addPortal(view: portalView)
let scale = 48.0 / self.previewContentContainerView.frame.height
portalView.view.transform = CGAffineTransformMakeScale(scale, scale)
if self.entitiesView.hasSelection {
self.entitiesView.selectEntity(nil)
}
let coverController = MediaCoverScreen(
context: self.context,
mediaEditor: mediaEditor,
previewView: self.previewView,
portalView: portalView
)
coverController.dismissed = { [weak self] in
if let self {
self.animateInFromTool()
Queue.mainQueue().after(0.1) {
self.requestCompletion()
}
}
}
coverController.completed = { [weak self] position, image in
if let self {
self.controller?.currentCoverImage = image
}
}
self.controller?.present(coverController, in: .window(.root))
self.coverScreen = coverController
self.animateOutToTool(tool: .cover)
}
func updateModalTransitionFactor(_ value: CGFloat, transition: ContainedViewLayoutTransition) {
guard let layout = self.validLayout, case .compact = layout.metrics.widthClass else {
return
@ -5077,6 +5123,8 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
}
self.controller?.present(controller, in: .window(.root))
self.animateOutToTool(tool: .tools)
case .cover:
self.openCoverSelection()
}
}
},
@ -5652,8 +5700,12 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
return self.isEditingStory || self.forwardSource != nil
}
private var currentCoverImage: UIImage?
func openPrivacySettings(_ privacy: MediaEditorResultPrivacy? = nil, completion: @escaping () -> Void = {}) {
self.node.mediaEditor?.maybePauseVideo()
guard let mediaEditor = self.node.mediaEditor else {
return
}
mediaEditor.maybePauseVideo()
self.hapticFeedback.impact(.light)
@ -5662,6 +5714,8 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
let text = self.getCaption().string
let mentions = generateTextEntities(text, enabledTypes: [.mention], currentEntities: []).map { (text as NSString).substring(with: NSRange(location: $0.range.lowerBound + 1, length: $0.range.upperBound - $0.range.lowerBound - 1)) }
let coverImage = self.currentCoverImage ?? mediaEditor.resultImage
let stateContext = ShareWithPeersScreen.StateContext(
context: self.context,
subject: .stories(editing: false),
@ -5679,6 +5733,8 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
let initialPrivacy = privacy.privacy
let timeout = privacy.timeout
var editCoverImpl: (() -> Void)?
let controller = ShareWithPeersScreen(
context: self.context,
initialPrivacy: initialPrivacy,
@ -5687,6 +5743,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
pin: privacy.pin,
timeout: privacy.timeout,
mentions: mentions,
coverImage: coverImage,
stateContext: stateContext,
completion: { [weak self] sendAsPeerId, privacy, allowScreenshots, pin, _, completed in
guard let self else {
@ -5736,6 +5793,9 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
pin: pin
), completion: completion)
})
},
editCover: {
editCoverImpl?()
}
)
controller.customModalStyleOverlayTransitionFactorUpdated = { [weak self, weak controller] transition in
@ -5748,6 +5808,17 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
self.node.mediaEditor?.play()
}
self.push(controller)
editCoverImpl = { [weak self, weak controller] in
if let self {
Queue.mainQueue().after(0.25, {
self.node.openCoverSelection()
})
}
if let controller {
controller.dismiss()
}
}
})
}
@ -8074,7 +8145,7 @@ private func stickerFile(resource: TelegramMediaResource, thumbnailResource: Tel
fileAttributes.append(.FileName(fileName: isVideo ? "sticker.webm" : "sticker.webp"))
fileAttributes.append(.Sticker(displayText: "", packReference: nil, maskData: nil))
if isVideo {
fileAttributes.append(.Video(duration: duration ?? 3.0, size: dimensions, flags: [], preloadSize: nil))
fileAttributes.append(.Video(duration: duration ?? 3.0, size: dimensions, flags: [], preloadSize: nil, coverTime: nil))
} else {
fileAttributes.append(.ImageSize(size: dimensions))
}

View File

@ -70,6 +70,7 @@ public final class MediaScrubberComponent: Component {
public enum Style {
case editor
case videoMessage
case cover
}
let context: AccountContext
@ -84,6 +85,7 @@ public final class MediaScrubberComponent: Component {
let isPlaying: Bool
let tracks: [Track]
let portalView: PortalView?
let positionUpdated: (Double, Bool) -> Void
let trackTrimUpdated: (Int32, Double, Double, Bool, Bool) -> Void
@ -100,6 +102,7 @@ public final class MediaScrubberComponent: Component {
maxDuration: Double,
isPlaying: Bool,
tracks: [Track],
portalView: PortalView? = nil,
positionUpdated: @escaping (Double, Bool) -> Void,
trackTrimUpdated: @escaping (Int32, Double, Double, Bool, Bool) -> Void,
trackOffsetUpdated: @escaping (Int32, Double, Bool) -> Void,
@ -114,6 +117,7 @@ public final class MediaScrubberComponent: Component {
self.maxDuration = maxDuration
self.isPlaying = isPlaying
self.tracks = tracks
self.portalView = portalView
self.positionUpdated = positionUpdated
self.trackTrimUpdated = trackTrimUpdated
self.trackOffsetUpdated = trackOffsetUpdated
@ -152,6 +156,7 @@ public final class MediaScrubberComponent: Component {
private var trackViews: [Int32: TrackView] = [:]
private let trimView: TrimView
private let ghostTrimView: TrimView
private let cursorContentView: UIView
private let cursorView: HandleView
private var cursorDisplayLink: SharedDisplayLinkDriver.Link?
@ -169,6 +174,7 @@ public final class MediaScrubberComponent: Component {
self.trimView = TrimView(frame: .zero)
self.ghostTrimView = TrimView(frame: .zero)
self.ghostTrimView.isHollow = true
self.cursorContentView = UIView()
self.cursorView = HandleView()
super.init(frame: frame)
@ -178,6 +184,10 @@ public final class MediaScrubberComponent: Component {
self.disablesInteractiveModalDismiss = true
self.disablesInteractiveKeyboardGestureRecognizer = true
self.cursorContentView.isUserInteractionEnabled = false
self.cursorContentView.clipsToBounds = true
self.cursorContentView.layer.cornerRadius = 10.0
let positionImage = generateImage(CGSize(width: handleWidth, height: 50.0), rotatedContext: { size, context in
context.clear(CGRect(origin: .zero, size: size))
context.setFillColor(UIColor.white.cgColor)
@ -187,13 +197,13 @@ public final class MediaScrubberComponent: Component {
context.addPath(path.cgPath)
context.fillPath()
})?.stretchableImage(withLeftCapWidth: Int(handleWidth / 2.0), topCapHeight: 25)
self.cursorView.image = positionImage
self.cursorView.isUserInteractionEnabled = true
self.cursorView.hitTestSlop = UIEdgeInsets(top: -8.0, left: -9.0, bottom: -8.0, right: -9.0)
self.addSubview(self.ghostTrimView)
self.addSubview(self.trimView)
self.addSubview(self.cursorContentView)
self.addSubview(self.cursorView)
self.cursorView.addGestureRecognizer(UIPanGestureRecognizer(target: self, action: #selector(self.handleCursorPan(_:))))
@ -328,10 +338,23 @@ public final class MediaScrubberComponent: Component {
}
private func cursorFrame(size: CGSize, height: CGFloat, position: Double, duration : Double) -> CGRect {
var cursorWidth = handleWidth
var cursorMargin = handleWidth
var height = height
var isCover = false
var y: CGFloat = -5.0 - UIScreenPixel
if let component = self.component, case .cover = component.style {
cursorWidth = 30.0 + 12.0
cursorMargin = 0.0
height = 50.0
isCover = true
y += 1.0
}
let cursorPadding: CGFloat = 8.0
let cursorPositionFraction = duration > 0.0 ? position / duration : 0.0
let cursorPosition = floorToScreenPixels(handleWidth - 1.0 + (size.width - handleWidth * 2.0 + 2.0) * cursorPositionFraction)
var cursorFrame = CGRect(origin: CGPoint(x: cursorPosition - handleWidth / 2.0, y: -5.0 - UIScreenPixel), size: CGSize(width: handleWidth, height: height))
let cursorPosition = floorToScreenPixels(cursorMargin - 1.0 + (size.width - handleWidth * 2.0 + 2.0) * cursorPositionFraction)
var cursorFrame = CGRect(origin: CGPoint(x: cursorPosition - cursorWidth / 2.0, y: y), size: CGSize(width: cursorWidth, height: height))
var leftEdge = self.ghostTrimView.leftHandleView.frame.maxX
var rightEdge = self.ghostTrimView.rightHandleView.frame.minX
@ -339,9 +362,13 @@ public final class MediaScrubberComponent: Component {
leftEdge = self.trimView.leftHandleView.frame.maxX
rightEdge = self.trimView.rightHandleView.frame.minX
}
if isCover {
leftEdge = 0.0
rightEdge = size.width
}
cursorFrame.origin.x = max(leftEdge - cursorPadding, cursorFrame.origin.x)
cursorFrame.origin.x = min(rightEdge - handleWidth + cursorPadding, cursorFrame.origin.x)
cursorFrame.origin.x = min(rightEdge - cursorWidth + cursorPadding, cursorFrame.origin.x)
return cursorFrame
}
@ -377,6 +404,7 @@ public final class MediaScrubberComponent: Component {
updatedPosition = max(self.startPosition, min(self.endPosition, position + advance))
}
self.cursorView.frame = cursorFrame(size: scrubberSize, height: self.effectiveCursorHeight, position: updatedPosition, duration: self.trimDuration)
self.cursorContentView.frame = self.cursorView.frame.insetBy(dx: 6.0, dy: 2.0).offsetBy(dx: -1.0 - UIScreenPixel, dy: 0.0)
}
public func update(component: MediaScrubberComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<EnvironmentType>, transition: ComponentTransition) -> CGSize {
@ -384,11 +412,36 @@ public final class MediaScrubberComponent: Component {
self.component = component
self.state = state
if let portalView = component.portalView, portalView.view.superview == nil {
portalView.view.frame = CGRect(x: 0.0, y: 0.0, width: 30.0, height: 48.0)
portalView.view.clipsToBounds = true
self.cursorContentView.addSubview(portalView.view)
}
switch component.style {
case .editor:
self.cursorView.isHidden = false
case .videoMessage:
self.cursorView.isHidden = true
case .cover:
self.cursorView.isHidden = false
self.trimView.isHidden = true
self.ghostTrimView.isHidden = true
if isFirstTime {
let positionImage = generateImage(CGSize(width: 30.0 + 12.0, height: 50.0), rotatedContext: { size, context in
context.clear(CGRect(origin: .zero, size: size))
context.setStrokeColor(UIColor.white.cgColor)
let lineWidth = 2.0 - UIScreenPixel
context.setLineWidth(lineWidth)
context.setShadow(offset: .zero, blur: 2.0, color: UIColor(rgb: 0x000000, alpha: 0.55).cgColor)
let path = UIBezierPath(roundedRect: CGRect(origin: CGPoint(x: 6.0 - lineWidth / 2.0, y: 2.0 - lineWidth / 2.0), size: CGSize(width: 30.0 - lineWidth, height: 48.0 - lineWidth)), cornerRadius: 9.0)
context.addPath(path.cgPath)
context.strokePath()
})
self.cursorView.image = positionImage
}
}
var totalHeight: CGFloat = 0.0
@ -520,7 +573,7 @@ public final class MediaScrubberComponent: Component {
let fullTrackHeight: CGFloat
switch component.style {
case .editor:
case .editor, .cover:
fullTrackHeight = trackHeight
case .videoMessage:
fullTrackHeight = 33.0
@ -610,7 +663,9 @@ public final class MediaScrubberComponent: Component {
if let offset = self.mainAudioTrackOffset {
cursorPosition -= offset
}
transition.setFrame(view: self.cursorView, frame: cursorFrame(size: scrubberSize, height: self.effectiveCursorHeight, position: cursorPosition, duration: trimDuration))
let cursorFrame = cursorFrame(size: scrubberSize, height: self.effectiveCursorHeight, position: cursorPosition, duration: trimDuration)
transition.setFrame(view: self.cursorView, frame: cursorFrame)
transition.setFrame(view: self.cursorContentView, frame: cursorFrame.insetBy(dx: 6.0, dy: 2.0).offsetBy(dx: -1.0 - UIScreenPixel, dy: 0.0))
} else {
if let (_, _, end, ended) = self.cursorPositionAnimation {
if ended, component.position >= self.startPosition && component.position < end - 1.0 {
@ -839,7 +894,7 @@ private class TrackView: UIView, UIScrollViewDelegate, UIGestureRecognizerDelega
let fullTrackHeight: CGFloat
let framesCornerRadius: CGFloat
switch style {
case .editor:
case .editor, .cover:
fullTrackHeight = trackHeight
framesCornerRadius = 9.0
case .videoMessage:
@ -1362,7 +1417,7 @@ private class TrimView: UIView {
let highlightColor: UIColor
switch style {
case .editor:
case .editor, .cover:
effectiveHandleWidth = handleWidth
fullTrackHeight = trackHeight
capsuleOffset = 5.0 - UIScreenPixel

View File

@ -327,7 +327,7 @@ final class PeerInfoAvatarTransformContainerNode: ASDisplayNode {
markupNode.update(markup: markup, size: CGSize(width: 320.0, height: 320.0))
markupNode.updateVisibility(true)
} else if threadInfo == nil, let video = videoRepresentations.last, let peerReference = PeerReference(peer) {
let videoFileReference = FileMediaReference.avatarList(peer: peerReference, media: TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: 0), partialReference: nil, resource: video.representation.resource, previewRepresentations: representations.map { $0.representation }, videoThumbnails: [], immediateThumbnailData: immediateThumbnailData, mimeType: "video/mp4", size: nil, attributes: [.Animated, .Video(duration: 0, size: video.representation.dimensions, flags: [], preloadSize: nil)]))
let videoFileReference = FileMediaReference.avatarList(peer: peerReference, media: TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: 0), partialReference: nil, resource: video.representation.resource, previewRepresentations: representations.map { $0.representation }, videoThumbnails: [], immediateThumbnailData: immediateThumbnailData, mimeType: "video/mp4", size: nil, attributes: [.Animated, .Video(duration: 0, size: video.representation.dimensions, flags: [], preloadSize: nil, coverTime: nil)]))
let videoContent = NativeVideoContent(id: .profileVideo(videoId, nil), userLocation: .other, fileReference: videoFileReference, streamVideo: isMediaStreamable(resource: video.representation.resource) ? .conservative : .none, loopVideo: true, enableSound: false, fetchAutomatically: true, onlyFullSizeThumbnail: false, useLargeThumbnail: true, autoFetchFullSizeThumbnail: true, startTimestamp: video.representation.startTimestamp, continuePlayingWithoutSoundOnLostAudioSession: false, placeholderColor: .clear, captureProtected: peer.isCopyProtectionEnabled, storeAfterDownload: nil)
if videoContent.id != self.videoContent?.id {
self.videoNode?.removeFromSupernode()

View File

@ -162,7 +162,7 @@ final class PeerInfoEditingAvatarNode: ASDisplayNode {
markupNode.removeFromSupernode()
}
let videoFileReference = FileMediaReference.avatarList(peer: peerReference, media: TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: 0), partialReference: nil, resource: video.representation.resource, previewRepresentations: representations.map { $0.representation }, videoThumbnails: [], immediateThumbnailData: immediateThumbnailData, mimeType: "video/mp4", size: nil, attributes: [.Animated, .Video(duration: 0, size: video.representation.dimensions, flags: [], preloadSize: nil)]))
let videoFileReference = FileMediaReference.avatarList(peer: peerReference, media: TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: 0), partialReference: nil, resource: video.representation.resource, previewRepresentations: representations.map { $0.representation }, videoThumbnails: [], immediateThumbnailData: immediateThumbnailData, mimeType: "video/mp4", size: nil, attributes: [.Animated, .Video(duration: 0, size: video.representation.dimensions, flags: [], preloadSize: nil, coverTime: nil)]))
let videoContent = NativeVideoContent(id: .profileVideo(videoId, nil), userLocation: .other, fileReference: videoFileReference, streamVideo: isMediaStreamable(resource: video.representation.resource) ? .conservative : .none, loopVideo: true, enableSound: false, fetchAutomatically: true, onlyFullSizeThumbnail: false, useLargeThumbnail: true, autoFetchFullSizeThumbnail: true, startTimestamp: video.representation.startTimestamp, continuePlayingWithoutSoundOnLostAudioSession: false, placeholderColor: .clear, captureProtected: peer.isCopyProtectionEnabled, storeAfterDownload: nil)
if videoContent.id != self.videoContent?.id {
self.videoNode?.removeFromSupernode()

View File

@ -910,6 +910,7 @@ private extension MediaEditorValues {
audioTrackOffset: nil,
audioTrackVolume: nil,
audioTrackSamples: nil,
coverImageTimestamp: nil,
qualityPreset: qualityPreset
)
}
@ -1053,6 +1054,7 @@ private extension MediaEditorValues {
audioTrackOffset: nil,
audioTrackVolume: nil,
audioTrackSamples: nil,
coverImageTimestamp: nil,
qualityPreset: qualityPreset
)
}

View File

@ -0,0 +1,144 @@
import Foundation
import UIKit
import Display
import ComponentFlow
import MultilineTextComponent
import TelegramPresentationData
import SwitchComponent
final class CoverListItemComponent: Component {
let theme: PresentationTheme
let title: String
let image: UIImage?
let hasNext: Bool
let action: () -> Void
init(
theme: PresentationTheme,
title: String,
image: UIImage?,
hasNext: Bool,
action: @escaping () -> Void
) {
self.theme = theme
self.title = title
self.image = image
self.hasNext = hasNext
self.action = action
}
static func ==(lhs: CoverListItemComponent, rhs: CoverListItemComponent) -> Bool {
if lhs.theme !== rhs.theme {
return false
}
if lhs.title != rhs.title {
return false
}
if lhs.image !== rhs.image {
return false
}
if lhs.hasNext != rhs.hasNext {
return false
}
return true
}
final class View: UIView {
private let containerButton: HighlightTrackingButton
private let title = ComponentView<Empty>()
private let icon = ComponentView<Empty>()
private let separatorLayer: SimpleLayer
private var component: CoverListItemComponent?
private weak var state: EmptyComponentState?
override init(frame: CGRect) {
self.separatorLayer = SimpleLayer()
self.containerButton = HighlightTrackingButton()
super.init(frame: frame)
self.layer.addSublayer(self.separatorLayer)
self.addSubview(self.containerButton)
self.containerButton.addTarget(self, action: #selector(self.pressed), for: .touchUpInside)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
@objc private func pressed() {
guard let component = self.component else {
return
}
component.action()
}
func update(component: CoverListItemComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
let themeUpdated = self.component?.theme !== component.theme
self.component = component
self.state = state
let height: CGFloat = 44.0
let verticalInset: CGFloat = 0.0
let leftInset: CGFloat = 16.0
let rightInset: CGFloat = 16.0
let iconSize = self.icon.update(
transition: .immediate,
component: AnyComponent(Image(image: component.image, contentMode: .scaleAspectFill)),
environment: {},
containerSize: CGSize(width: 30.0, height: 30.0)
)
let titleSize = self.title.update(
transition: .immediate,
component: AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString(string: component.title, font: Font.regular(17.0), textColor: component.theme.list.itemPrimaryTextColor))
)),
environment: {},
containerSize: CGSize(width: availableSize.width - leftInset - rightInset, height: 100.0)
)
let titleFrame = CGRect(origin: CGPoint(x: leftInset, y: floorToScreenPixels((height - titleSize.height) / 2.0)), size: titleSize)
if let titleView = self.title.view {
if titleView.superview == nil {
titleView.isUserInteractionEnabled = false
self.containerButton.addSubview(titleView)
}
titleView.frame = titleFrame
}
if let iconView = self.icon.view {
if iconView.superview == nil {
iconView.clipsToBounds = true
iconView.layer.cornerRadius = 5.0
self.containerButton.addSubview(iconView)
}
transition.setFrame(view: iconView, frame: CGRect(origin: CGPoint(x: availableSize.width - rightInset - iconSize.width, y: floorToScreenPixels((height - iconSize.height) / 2.0)), size: iconSize))
}
if themeUpdated {
self.separatorLayer.backgroundColor = component.theme.list.itemPlainSeparatorColor.cgColor
}
transition.setFrame(layer: self.separatorLayer, frame: CGRect(origin: CGPoint(x: leftInset, y: height), size: CGSize(width: availableSize.width - leftInset, height: UIScreenPixel)))
self.separatorLayer.isHidden = !component.hasNext
let containerFrame = CGRect(origin: CGPoint(x: 0.0, y: verticalInset), size: CGSize(width: availableSize.width, height: height - verticalInset * 2.0))
transition.setFrame(view: self.containerButton, frame: containerFrame)
return CGSize(width: availableSize.width, height: height)
}
}
func makeView() -> View {
return View(frame: CGRect())
}
func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
}
}

View File

@ -38,9 +38,11 @@ final class ShareWithPeersScreenComponent: Component {
let mentions: [String]
let categoryItems: [CategoryItem]
let optionItems: [OptionItem]
let coverItem: CoverItem?
let completion: (EnginePeer.Id?, EngineStoryPrivacy, Bool, Bool, [EnginePeer], Bool) -> Void
let editCategory: (EngineStoryPrivacy, Bool, Bool) -> Void
let editBlockedPeers: (EngineStoryPrivacy, Bool, Bool) -> Void
let editCover: () -> Void
let peerCompletion: (EnginePeer.Id) -> Void
init(
@ -54,9 +56,11 @@ final class ShareWithPeersScreenComponent: Component {
mentions: [String],
categoryItems: [CategoryItem],
optionItems: [OptionItem],
coverItem: CoverItem?,
completion: @escaping (EnginePeer.Id?, EngineStoryPrivacy, Bool, Bool, [EnginePeer], Bool) -> Void,
editCategory: @escaping (EngineStoryPrivacy, Bool, Bool) -> Void,
editBlockedPeers: @escaping (EngineStoryPrivacy, Bool, Bool) -> Void,
editCover: @escaping () -> Void,
peerCompletion: @escaping (EnginePeer.Id) -> Void
) {
self.context = context
@ -69,9 +73,11 @@ final class ShareWithPeersScreenComponent: Component {
self.mentions = mentions
self.categoryItems = categoryItems
self.optionItems = optionItems
self.coverItem = coverItem
self.completion = completion
self.editCategory = editCategory
self.editBlockedPeers = editBlockedPeers
self.editCover = editCover
self.peerCompletion = peerCompletion
}
@ -106,6 +112,9 @@ final class ShareWithPeersScreenComponent: Component {
if lhs.optionItems != rhs.optionItems {
return false
}
if lhs.coverItem != rhs.coverItem {
return false
}
return true
}
@ -258,6 +267,33 @@ final class ShareWithPeersScreenComponent: Component {
return false
}
}
enum CoverId: Int, Hashable {
case choose = 0
}
final class CoverItem: Equatable {
let id: CoverId
let title: String
let image: UIImage?
init(
id: CoverId,
title: String,
image: UIImage?
) {
self.id = id
self.title = title
self.image = image
}
static func ==(lhs: CoverItem, rhs: CoverItem) -> Bool {
if lhs === rhs {
return true
}
return false
}
}
final class View: UIView, UIScrollViewDelegate {
private let dimView: UIView
@ -1607,6 +1643,90 @@ final class ShareWithPeersScreenComponent: Component {
footerText = isSendAsGroup ? environment.strings.Story_Privacy_KeepOnGroupPageInfo(footerValue).string : environment.strings.Story_Privacy_KeepOnChannelPageInfo(footerValue).string
}
let footerSize = sectionFooter.update(
transition: sectionFooterTransition,
component: AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString(string: footerText, font: Font.regular(13.0), textColor: environment.theme.list.freeTextColor)),
maximumNumberOfLines: 0,
lineSpacing: 0.2
)),
environment: {},
containerSize: CGSize(width: itemLayout.containerSize.width - 16.0 * 2.0, height: itemLayout.contentHeight)
)
let footerFrame = CGRect(origin: CGPoint(x: itemLayout.sideInset + 16.0, y: sectionOffset + section.totalHeight + 7.0), size: footerSize)
if let footerView = sectionFooter.view {
if footerView.superview == nil {
self.itemContainerView.addSubview(footerView)
}
sectionFooterTransition.setFrame(view: footerView, frame: footerFrame)
}
sectionOffset += footerSize.height
} else if section.id == 4 && section.itemCount > 0 {
if let item = component.coverItem {
let itemFrame = CGRect(origin: CGPoint(x: itemLayout.sideInset, y: sectionOffset + section.insets.top + CGFloat(0) * section.itemHeight), size: CGSize(width: itemLayout.containerSize.width, height: section.itemHeight))
if !visibleBounds.intersects(itemFrame) {
continue
}
let itemId = AnyHashable(item.id)
validIds.append(itemId)
var itemTransition = transition
let visibleItem: ComponentView<Empty>
if let current = self.visibleItems[itemId] {
visibleItem = current
} else {
visibleItem = ComponentView()
if !transition.animation.isImmediate {
itemTransition = .immediate
}
self.visibleItems[itemId] = visibleItem
}
let _ = visibleItem.update(
transition: itemTransition,
component: AnyComponent(CoverListItemComponent(
theme: environment.theme,
title: item.title,
image: item.image,
hasNext: false,
action: {
component.editCover()
}
)),
environment: {},
containerSize: itemFrame.size
)
if let itemView = visibleItem.view {
if itemView.superview == nil {
if let minSectionHeader {
self.itemContainerView.insertSubview(itemView, belowSubview: minSectionHeader)
} else {
self.itemContainerView.addSubview(itemView)
}
}
itemTransition.setFrame(view: itemView, frame: itemFrame)
}
}
let sectionFooter: ComponentView<Empty>
var sectionFooterTransition = transition
if let current = self.visibleSectionFooters[section.id] {
sectionFooter = current
} else {
if !transition.animation.isImmediate {
sectionFooterTransition = .immediate
}
sectionFooter = ComponentView()
self.visibleSectionFooters[section.id] = sectionFooter
}
var footerText = "Choose a frame from the story to show in your Profile."
if let sendAsPeerId = self.sendAsPeerId, sendAsPeerId.isGroupOrChannel == true {
footerText = isSendAsGroup ? "Choose a frame from the story to show in group profile.": "Choose a frame from the story to show in channel profile."
}
let footerSize = sectionFooter.update(
transition: sectionFooterTransition,
component: AnyComponent(MultilineTextComponent(
@ -1625,7 +1745,6 @@ final class ShareWithPeersScreenComponent: Component {
sectionFooterTransition.setFrame(view: footerView, frame: footerFrame)
}
}
sectionOffset += section.totalHeight
}
@ -1873,6 +1992,8 @@ final class ShareWithPeersScreenComponent: Component {
}
private var currentHasChannels: Bool?
private var currentHasCover: Bool?
func update(component: ShareWithPeersScreenComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<ViewControllerComponentContainer.Environment>, transition: ComponentTransition) -> CGSize {
guard !self.isDismissed else {
return availableSize
@ -1886,19 +2007,28 @@ final class ShareWithPeersScreenComponent: Component {
var hasCategories = false
var hasChannels = false
var hasCover = false
if case .stories = component.stateContext.subject {
if let peerId = self.sendAsPeerId, peerId.isGroupOrChannel {
} else {
hasCategories = true
}
let sendAsPeersCount = component.stateContext.stateValue?.sendAsPeers.count ?? 1
if sendAsPeersCount > 1 {
if sendAsPeersCount > 1 && !"".isEmpty {
hasChannels = true
}
if let currentHasChannels = self.currentHasChannels, currentHasChannels != hasChannels {
contentTransition = .spring(duration: 0.4)
}
self.currentHasChannels = hasChannels
if self.selectedOptions.contains(.pin) {
hasCover = true
}
if let currentHasCover = self.currentHasCover, currentHasCover != hasCover {
contentTransition = .spring(duration: 0.4)
}
self.currentHasCover = hasCover
} else if case .members = component.stateContext.subject {
self.dismissPanGesture?.isEnabled = false
} else if case .channels = component.stateContext.subject {
@ -2306,6 +2436,15 @@ final class ShareWithPeersScreenComponent: Component {
itemHeight: optionItemSize.height,
itemCount: component.optionItems.count
))
if hasCover {
sections.append(ItemLayout.Section(
id: 4,
insets: UIEdgeInsets(top: 28.0, left: 0.0, bottom: 0.0, right: 0.0),
itemHeight: optionItemSize.height,
itemCount: 1
))
}
} else {
sections.append(ItemLayout.Section(
id: 1,
@ -2489,8 +2628,12 @@ final class ShareWithPeersScreenComponent: Component {
inset = 1000.0
}
} else {
inset = 464.0
inset += 10.0 + environment.safeInsets.bottom + 50.0 + footersTotalHeight
if self.selectedOptions.contains(.pin) {
inset = 1000.0
} else {
inset = 464.0
inset += 10.0 + environment.safeInsets.bottom + 50.0 + footersTotalHeight
}
}
}
}
@ -2849,10 +2992,12 @@ public class ShareWithPeersScreen: ViewControllerComponentContainer {
pin: Bool = false,
timeout: Int = 0,
mentions: [String] = [],
coverImage: UIImage? = nil,
stateContext: StateContext,
completion: @escaping (EnginePeer.Id?, EngineStoryPrivacy, Bool, Bool, [EnginePeer], Bool) -> Void,
editCategory: @escaping (EngineStoryPrivacy, Bool, Bool) -> Void = { _, _, _ in },
editBlockedPeers: @escaping (EngineStoryPrivacy, Bool, Bool) -> Void = { _, _, _ in },
editCover: @escaping () -> Void = { },
peerCompletion: @escaping (EnginePeer.Id) -> Void = { _ in }
) {
self.context = context
@ -2861,6 +3006,7 @@ public class ShareWithPeersScreen: ViewControllerComponentContainer {
var categoryItems: [ShareWithPeersScreenComponent.CategoryItem] = []
var optionItems: [ShareWithPeersScreenComponent.OptionItem] = []
var coverItem: ShareWithPeersScreenComponent.CoverItem?
if case let .stories(editing) = stateContext.subject {
var everyoneSubtitle = presentationData.strings.Story_Privacy_ExcludePeople
if (stateContext.stateValue?.savedSelectedPeers[.everyone]?.count ?? 0) > 0 {
@ -2994,6 +3140,10 @@ public class ShareWithPeersScreen: ViewControllerComponentContainer {
title: presentationData.strings.Story_Privacy_KeepOnMyPage
))
}
if !editing || pin {
coverItem = ShareWithPeersScreenComponent.CoverItem(id: .choose, title: "Choose Story Cover", image: coverImage)
}
}
var theme: ViewControllerComponentContainer.Theme = .dark
@ -3013,9 +3163,11 @@ public class ShareWithPeersScreen: ViewControllerComponentContainer {
mentions: mentions,
categoryItems: categoryItems,
optionItems: optionItems,
coverItem: coverItem,
completion: completion,
editCategory: editCategory,
editBlockedPeers: editBlockedPeers,
editCover: editCover,
peerCompletion: peerCompletion
), navigationBarAppearance: .none, theme: theme)

View File

@ -1820,7 +1820,7 @@ public func preloadStoryMedia(context: AccountContext, info: StoryPreloadInfo) -
case let .file(file):
var fetchRange: (Range<Int64>, MediaBoxFetchPriority)?
for attribute in file.attributes {
if case let .Video(_, _, _, preloadSize) = attribute {
if case let .Video(_, _, _, preloadSize, _) = attribute {
if let preloadSize {
fetchRange = (0 ..< Int64(preloadSize), .default)
}
@ -2045,7 +2045,7 @@ public func waitUntilStoryMediaPreloaded(context: AccountContext, peerId: Engine
case let .file(file):
var fetchRange: (Range<Int64>, MediaBoxFetchPriority)?
for attribute in file.attributes {
if case let .Video(_, _, _, preloadSize) = attribute {
if case let .Video(_, _, _, preloadSize, _) = attribute {
if let preloadSize {
fetchRange = (0 ..< Int64(preloadSize), .default)
}

View File

@ -1136,7 +1136,7 @@ private final class StoryContainerScreenComponent: Component {
var isSilentVideo = false
if case let .file(file) = slice.item.storyItem.media {
for attribute in file.attributes {
if case let .Video(_, _, flags, _) = attribute {
if case let .Video(_, _, flags, _, _) = attribute {
if flags.contains(.isSilent) {
isSilentVideo = true
}

View File

@ -3798,7 +3798,7 @@ public final class StoryItemSetContainerComponent: Component {
isVideo = true
soundAlpha = 1.0
for attribute in file.attributes {
if case let .Video(_, _, flags, _) = attribute {
if case let .Video(_, _, flags, _, _) = attribute {
if flags.contains(.isSilent) {
isSilentVideo = true
soundAlpha = 0.5
@ -3831,7 +3831,7 @@ public final class StoryItemSetContainerComponent: Component {
var isSilentVideo = false
if case let .file(file) = component.slice.item.storyItem.media {
for attribute in file.attributes {
if case let .Video(_, _, flags, _) = attribute {
if case let .Video(_, _, flags, _, _) = attribute {
if flags.contains(.isSilent) {
isSilentVideo = true
}

View File

@ -1793,7 +1793,7 @@ public class VideoMessageCameraScreen: ViewController {
guard let self else {
return
}
let values = MediaEditorValues(peerId: self.context.account.peerId, originalDimensions: dimensions, cropOffset: .zero, cropRect: CGRect(origin: .zero, size: dimensions.cgSize), cropScale: 1.0, cropRotation: 0.0, cropMirroring: false, cropOrientation: nil, gradientColors: nil, videoTrimRange: self.node.previewState?.trimRange, videoIsMuted: false, videoIsFullHd: false, videoIsMirrored: false, videoVolume: nil, additionalVideoPath: nil, additionalVideoIsDual: false, additionalVideoPosition: nil, additionalVideoScale: nil, additionalVideoRotation: nil, additionalVideoPositionChanges: [], additionalVideoTrimRange: nil, additionalVideoOffset: nil, additionalVideoVolume: nil, nightTheme: false, drawing: nil, maskDrawing: nil, entities: [], toolValues: [:], audioTrack: nil, audioTrackTrimRange: nil, audioTrackOffset: nil, audioTrackVolume: nil, audioTrackSamples: nil, qualityPreset: .videoMessage)
let values = MediaEditorValues(peerId: self.context.account.peerId, originalDimensions: dimensions, cropOffset: .zero, cropRect: CGRect(origin: .zero, size: dimensions.cgSize), cropScale: 1.0, cropRotation: 0.0, cropMirroring: false, cropOrientation: nil, gradientColors: nil, videoTrimRange: self.node.previewState?.trimRange, videoIsMuted: false, videoIsFullHd: false, videoIsMirrored: false, videoVolume: nil, additionalVideoPath: nil, additionalVideoIsDual: false, additionalVideoPosition: nil, additionalVideoScale: nil, additionalVideoRotation: nil, additionalVideoPositionChanges: [], additionalVideoTrimRange: nil, additionalVideoOffset: nil, additionalVideoVolume: nil, nightTheme: false, drawing: nil, maskDrawing: nil, entities: [], toolValues: [:], audioTrack: nil, audioTrackTrimRange: nil, audioTrackOffset: nil, audioTrackVolume: nil, audioTrackSamples: nil, coverImageTimestamp: nil, qualityPreset: .videoMessage)
var resourceAdjustments: VideoMediaResourceAdjustments? = nil
if let valuesData = try? JSONEncoder().encode(values) {
@ -1833,7 +1833,7 @@ public class VideoMessageCameraScreen: ViewController {
context.account.postbox.mediaBox.storeCachedResourceRepresentation(resource, representation: CachedVideoFirstFrameRepresentation(), data: data)
}
let media = TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: Int64.random(in: Int64.min ... Int64.max)), partialReference: nil, resource: resource, previewRepresentations: previewRepresentations, videoThumbnails: [], immediateThumbnailData: nil, mimeType: "video/mp4", size: nil, attributes: [.FileName(fileName: "video.mp4"), .Video(duration: finalDuration, size: video.dimensions, flags: [.instantRoundVideo], preloadSize: nil)])
let media = TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: Int64.random(in: Int64.min ... Int64.max)), partialReference: nil, resource: resource, previewRepresentations: previewRepresentations, videoThumbnails: [], immediateThumbnailData: nil, mimeType: "video/mp4", size: nil, attributes: [.FileName(fileName: "video.mp4"), .Video(duration: finalDuration, size: video.dimensions, flags: [.instantRoundVideo], preloadSize: nil, coverTime: nil)])
var attributes: [MessageAttribute] = []
if self.cameraState.isViewOnceEnabled {

View File

@ -186,6 +186,7 @@ extension ChatControllerImpl {
audioTrackOffset: nil,
audioTrackVolume: nil,
audioTrackSamples: nil,
coverImageTimestamp: nil,
qualityPreset: nil
)
@ -210,7 +211,7 @@ extension ChatControllerImpl {
var fileAttributes: [TelegramMediaFileAttribute] = []
fileAttributes.append(.FileName(fileName: "sticker.webm"))
fileAttributes.append(.Sticker(displayText: "", packReference: nil, maskData: nil))
fileAttributes.append(.Video(duration: animatedImage.duration, size: PixelDimensions(width: 512, height: 512), flags: [], preloadSize: nil))
fileAttributes.append(.Video(duration: animatedImage.duration, size: PixelDimensions(width: 512, height: 512), flags: [], preloadSize: nil, coverTime: nil))
let previewRepresentations: [TelegramMediaImageRepresentation] = []
// if let thumbnailResource {

View File

@ -397,7 +397,7 @@ func messageMediaEditingOptions(message: Message) -> MessageMediaEditingOptions
return []
case .Animated:
break
case let .Video(_, _, flags, _):
case let .Video(_, _, flags, _, _):
if flags.contains(.instantRoundVideo) {
return []
} else {

View File

@ -260,7 +260,7 @@ final class HorizontalListContextResultsChatInputPanelItemNode: ListViewItemNode
}
imageDimensions = externalReference.content?.dimensions?.cgSize
if externalReference.type == "gif", let thumbnailResource = externalReference.thumbnail?.resource, let content = externalReference.content, let dimensions = content.dimensions {
videoFile = TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: 0), partialReference: nil, resource: thumbnailResource, previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "video/mp4", size: nil, attributes: [.Animated, .Video(duration: 0, size: dimensions, flags: [], preloadSize: nil)])
videoFile = TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: 0), partialReference: nil, resource: thumbnailResource, previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "video/mp4", size: nil, attributes: [.Animated, .Video(duration: 0, size: dimensions, flags: [], preloadSize: nil, coverTime: nil)])
imageResource = nil
}

View File

@ -72,7 +72,7 @@ final class MessageMediaPlaylistItem: SharedMediaPlaylistItem {
} else {
return SharedMediaPlaybackData(type: .music, source: source)
}
case let .Video(_, _, flags, _):
case let .Video(_, _, flags, _, _):
if flags.contains(.instantRoundVideo) {
return SharedMediaPlaybackData(type: .instantVideo, source: source)
} else {
@ -129,7 +129,7 @@ final class MessageMediaPlaylistItem: SharedMediaPlaylistItem {
displayData = SharedMediaPlaybackDisplayData.music(title: updatedTitle, performer: updatedPerformer, albumArt: albumArt, long: CGFloat(duration) > 10.0 * 60.0, caption: caption)
}
return displayData
case let .Video(_, _, flags, _):
case let .Video(_, _, flags, _, _):
if flags.contains(.instantRoundVideo) {
return SharedMediaPlaybackDisplayData.instantVideo(author: self.message.effectiveAuthor.flatMap(EnginePeer.init), peer: self.message.peers[self.message.id.peerId].flatMap(EnginePeer.init), timestamp: self.message.timestamp)
} else {

View File

@ -671,7 +671,7 @@ public final class TelegramRootController: NavigationController, TelegramRootCon
return nil
}
}
media = .video(dimensions: dimensions, duration: duration, resource: resource, firstFrameFile: firstFrameFile, stickers: result.stickers)
media = .video(dimensions: dimensions, duration: duration, resource: resource, firstFrameFile: firstFrameFile, stickers: result.stickers, coverTime: values.coverImageTimestamp)
}
default:
break

View File

@ -172,7 +172,7 @@ func makeBridgeMedia(message: Message, strings: PresentationStrings, chatPeer: P
for attribute in file.attributes {
switch attribute {
case let .Video(duration, size, flags, _):
case let .Video(duration, size, flags, _, _):
bridgeVideo.duration = Int32(duration)
bridgeVideo.dimensions = size.cgSize
bridgeVideo.round = flags.contains(.instantRoundVideo)

View File

@ -37,7 +37,7 @@ struct WebSearchGalleryEntry: Equatable {
switch self.result {
case let .externalReference(externalReference):
if let content = externalReference.content, externalReference.type == "gif", let thumbnailResource = externalReference.thumbnail?.resource, let dimensions = content.dimensions {
let fileReference = FileMediaReference.standalone(media: TelegramMediaFile(fileId: EngineMedia.Id(namespace: Namespaces.Media.LocalFile, id: 0), partialReference: nil, resource: content.resource, previewRepresentations: [TelegramMediaImageRepresentation(dimensions: dimensions, resource: thumbnailResource, progressiveSizes: [], immediateThumbnailData: nil, hasVideo: false, isPersonal: false)], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "video/mp4", size: nil, attributes: [.Animated, .Video(duration: 0, size: dimensions, flags: [], preloadSize: nil)]))
let fileReference = FileMediaReference.standalone(media: TelegramMediaFile(fileId: EngineMedia.Id(namespace: Namespaces.Media.LocalFile, id: 0), partialReference: nil, resource: content.resource, previewRepresentations: [TelegramMediaImageRepresentation(dimensions: dimensions, resource: thumbnailResource, progressiveSizes: [], immediateThumbnailData: nil, hasVideo: false, isPersonal: false)], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "video/mp4", size: nil, attributes: [.Animated, .Video(duration: 0, size: dimensions, flags: [], preloadSize: nil, coverTime: nil)]))
return WebSearchVideoGalleryItem(context: context, presentationData: presentationData, index: self.index, result: self.result, content: NativeVideoContent(id: .contextResult(self.result.queryId, self.result.id), userLocation: .other, fileReference: fileReference, loopVideo: true, enableSound: false, fetchAutomatically: true, storeAfterDownload: nil), controllerInteraction: controllerInteraction)
}
case let .internalReference(internalReference):

View File

@ -22,7 +22,7 @@ public extension WidgetDataPeer.Message {
switch attribute {
case let .Sticker(altText, _, _):
content = .sticker(WidgetDataPeer.Message.Content.Sticker(altText: altText))
case let .Video(duration, _, flags, _):
case let .Video(duration, _, flags, _, _):
if flags.contains(.instantRoundVideo) {
content = .videoMessage(WidgetDataPeer.Message.Content.VideoMessage(duration: Int32(duration)))
} else {