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 { } else if let file = media as? TelegramMediaFile {
resource = file.resource resource = file.resource
for attribute in file.attributes { for attribute in file.attributes {
if case let .Video(_, _, _, preloadSize) = attribute { if case let .Video(_, _, _, preloadSize, _) = attribute {
fetchSize = preloadSize.flatMap(Int64.init) fetchSize = preloadSize.flatMap(Int64.init)
} }
} }

View File

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

View File

@ -206,7 +206,7 @@ public final class AvatarVideoNode: ASDisplayNode {
self.backgroundNode.image = nil self.backgroundNode.image = nil
let videoId = photo.id?.id ?? peer.id.id._internalGetInt64Value() 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) 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 { if videoContent.id != self.videoContent?.id {
self.videoNode?.removeFromSupernode() 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) { 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 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) 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): case let .preview(dimensions, immediateThumbnailData, videoDuration):
if let immediateThumbnailData { if let immediateThumbnailData {
if let videoDuration { 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)) contentImageSpecs.append(ContentImageSpec(message: message, media: .file(thumbnailMedia), size: fitSize))
} else { } else {
let thumbnailMedia = TelegramMediaImage(imageId: MediaId(namespace: 0, id: index), representations: [], immediateThumbnailData: immediateThumbnailData, reference: nil, partialReference: nil, flags: []) 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 processed = true
break inner break inner
} }
case let .Video(_, _, flags, _): case let .Video(_, _, flags, _, _):
if flags.contains(.instantRoundVideo) { if flags.contains(.instantRoundVideo) {
messageText = strings.Message_VideoMessage messageText = strings.Message_VideoMessage
processed = true processed = true

View File

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

View File

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

View File

@ -53,7 +53,7 @@ final class InstantPageMediaPlaylistItem: SharedMediaPlaylistItem {
} else { } else {
return SharedMediaPlaybackData(type: .music, source: .telegramFile(reference: .webPage(webPage: WebpageReference(self.webPage), media: file), isCopyProtected: false, isViewOnce: false)) 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) { if flags.contains(.instantRoundVideo) {
return SharedMediaPlaybackData(type: .instantVideo, source: .telegramFile(reference: .webPage(webPage: WebpageReference(self.webPage), media: file), isCopyProtected: false, isViewOnce: false)) return SharedMediaPlaybackData(type: .instantVideo, source: .telegramFile(reference: .webPage(webPage: WebpageReference(self.webPage), media: file), isCopyProtected: false, isViewOnce: false))
} else { } else {
@ -99,7 +99,7 @@ final class InstantPageMediaPlaylistItem: SharedMediaPlaylistItem {
return SharedMediaPlaybackDisplayData.music(title: updatedTitle, performer: updatedPerformer, albumArt: albumArt, long: false, caption: nil) 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) { if flags.contains(.instantRoundVideo) {
return SharedMediaPlaybackDisplayData.instantVideo(author: nil, peer: nil, timestamp: 0) return SharedMediaPlaybackDisplayData.instantVideo(author: nil, peer: nil, timestamp: 0)
} else { } 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) let finalDimensions = TGMediaVideoConverter.dimensions(for: dimensions, adjustments: nil, preset: TGMediaVideoConversionPresetAnimation)
var fileAttributes: [TelegramMediaFileAttribute] = [] 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(.FileName(fileName: fileName))
fileAttributes.append(.Animated) 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) let finalDimensions = TGMediaVideoConverter.dimensions(for: dimensions, adjustments: nil, preset: TGMediaVideoConversionPresetAnimation)
var fileAttributes: [TelegramMediaFileAttribute] = [] 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(.FileName(fileName: fileName))
fileAttributes.append(.Animated) fileAttributes.append(.Animated)
@ -857,7 +857,7 @@ public func legacyAssetPickerEnqueueMessages(context: AccountContext, account: A
fileAttributes.append(.Animated) fileAttributes.append(.Animated)
} }
if !asFile { 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 let adjustments = adjustments {
if adjustments.sendAsGif { if adjustments.sendAsGif {
fileAttributes.append(.Animated) fileAttributes.append(.Animated)

View File

@ -187,7 +187,7 @@ final class PeerAvatarImageGalleryItemNode: ZoomableContentGalleryItemNode {
let subject: ShareControllerSubject let subject: ShareControllerSubject
var actionCompletionText: String? var actionCompletionText: String?
if let video = entry.videoRepresentations.last, let peerReference = PeerReference(peer._asPeer()) { 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) subject = .media(videoFileReference.abstract)
actionCompletionText = strongSelf.presentationData.strings.Gallery_VideoSaved actionCompletionText = strongSelf.presentationData.strings.Gallery_VideoSaved
} else { } else {
@ -279,7 +279,7 @@ final class PeerAvatarImageGalleryItemNode: ZoomableContentGalleryItemNode {
if let video = entry.videoRepresentations.last, let peerReference = PeerReference(self.peer._asPeer()) { if let video = entry.videoRepresentations.last, let peerReference = PeerReference(self.peer._asPeer()) {
if video != previousVideoRepresentations?.last { if video != previousVideoRepresentations?.last {
let mediaManager = self.context.sharedContext.mediaManager 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 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) let videoNode = UniversalVideoNode(postbox: self.context.account.postbox, audioSession: mediaManager.audioSession, manager: mediaManager.universalVideoManager, decoration: GalleryVideoDecoration(), content: videoContent, priority: .overlay)
videoNode.isUserInteractionEnabled = false videoNode.isUserInteractionEnabled = false

View File

@ -515,7 +515,7 @@ public final class PeerInfoAvatarListItemNode: ASDisplayNode {
self.isReady.set(.single(true)) self.isReady.set(.single(true))
} }
} else if let video = videoRepresentations.last, let peerReference = PeerReference(self.peer._asPeer()) { } 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) 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 { 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) { 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 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) 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 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) 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 |> mapError { _ -> PreparedShareItemError in
return .generic return .generic
} }
@ -210,7 +210,7 @@ private func preparedShareItem(postbox: Postbox, network: Network, to peerId: Pe
let mimeType: String let mimeType: String
if converted { if converted {
mimeType = "video/mp4" 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 { } else {
mimeType = "animation/gif" mimeType = "animation/gif"
attributes = [.ImageSize(size: PixelDimensions(width: Int32(dimensions.width), height: Int32(dimensions.height))), .Animated, .FileName(fileName: fileName ?? "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 var switchOffAdds: Bool? = nil
if let boostData, boostData.level >= premiumConfiguration.minChannelRestrictAdsLevel { if let boostData, boostData.level >= premiumConfiguration.minChannelRestrictAdsLevel {
switchOffAdds = adsRestricted switchOffAdds = adsRestricted

View File

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

View File

@ -6,7 +6,7 @@ import TelegramApi
func dimensionsForFileAttributes(_ attributes: [TelegramMediaFileAttribute]) -> PixelDimensions? { func dimensionsForFileAttributes(_ attributes: [TelegramMediaFileAttribute]) -> PixelDimensions? {
for attribute in attributes { for attribute in attributes {
switch attribute { switch attribute {
case let .Video(_, size, _, _): case let .Video(_, size, _, _, _):
return size return size
case let .ImageSize(size): case let .ImageSize(size):
return size return size
@ -20,7 +20,7 @@ func dimensionsForFileAttributes(_ attributes: [TelegramMediaFileAttribute]) ->
func durationForFileAttributes(_ attributes: [TelegramMediaFileAttribute]) -> Double? { func durationForFileAttributes(_ attributes: [TelegramMediaFileAttribute]) -> Double? {
for attribute in attributes { for attribute in attributes {
switch attribute { switch attribute {
case let .Video(duration, _, _, _): case let .Video(duration, _, _, _, _):
return duration return duration
case let .Audio(_, duration, _, _, _): case let .Audio(_, duration, _, _, _):
return Double(duration) return Double(duration)
@ -99,7 +99,7 @@ func telegramMediaFileAttributesFromApiAttributes(_ attributes: [Api.DocumentAtt
result.append(.ImageSize(size: PixelDimensions(width: w, height: h))) result.append(.ImageSize(size: PixelDimensions(width: w, height: h)))
case .documentAttributeAnimated: case .documentAttributeAnimated:
result.append(.Animated) result.append(.Animated)
case let .documentAttributeVideo(flags, duration, w, h, preloadSize, _): case let .documentAttributeVideo(flags, duration, w, h, preloadSize, videoStart):
var videoFlags = TelegramMediaVideoFlags() var videoFlags = TelegramMediaVideoFlags()
if (flags & (1 << 0)) != 0 { if (flags & (1 << 0)) != 0 {
videoFlags.insert(.instantRoundVideo) videoFlags.insert(.instantRoundVideo)
@ -110,7 +110,7 @@ func telegramMediaFileAttributesFromApiAttributes(_ attributes: [Api.DocumentAtt
if (flags & (1 << 3)) != 0 { if (flags & (1 << 3)) != 0 {
videoFlags.insert(.isSilent) 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): case let .documentAttributeAudio(flags, duration, title, performer, waveform):
let isVoice = (flags & (1 << 10)) != 0 let isVoice = (flags & (1 << 10)) != 0
let waveformBuffer: Data? = waveform?.makeData() 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)) attributes.append(.documentAttributeSticker(flags: flags, alt: displayText, stickerset: stickerSet, maskCoords: inputMaskCoords))
case .HasLinkedStickers: case .HasLinkedStickers:
attributes.append(.documentAttributeHasStickers) attributes.append(.documentAttributeHasStickers)
case let .Video(duration, size, videoFlags, preloadSize): case let .Video(duration, size, videoFlags, preloadSize, coverTime):
var flags: Int32 = 0 var flags: Int32 = 0
if videoFlags.contains(.instantRoundVideo) { if videoFlags.contains(.instantRoundVideo) {
flags |= (1 << 0) flags |= (1 << 0)
@ -715,8 +715,10 @@ func inputDocumentAttributesFromFileAttributes(_ fileAttributes: [TelegramMediaF
if videoFlags.contains(.isSilent) { if videoFlags.contains(.isSilent) {
flags |= (1 << 3) flags |= (1 << 3)
} }
if let coverTime = coverTime, coverTime > 0.0 {
attributes.append(.documentAttributeVideo(flags: flags, duration: duration, w: Int32(size.width), h: Int32(size.height), preloadPrefixSize: preloadSize, videoStartTs: nil)) 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): case let .Audio(isVoice, duration, title, performer, waveform):
var flags: Int32 = 0 var flags: Int32 = 0
if isVoice { if isVoice {
@ -786,7 +788,7 @@ public func statsCategoryForFileWithAttributes(_ attributes: [TelegramMediaFileA
} else { } else {
return .audio return .audio
} }
case let .Video(_, _, flags, _): case let .Video(_, _, flags, _, _):
if flags.contains(TelegramMediaVideoFlags.instantRoundVideo) { if flags.contains(TelegramMediaVideoFlags.instantRoundVideo) {
return .voiceMessages return .voiceMessages
} else { } else {

View File

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

View File

@ -610,7 +610,7 @@ extension TelegramMediaFileAttribute {
} }
self = .Sticker(displayText: alt, packReference: packReference, maskData: nil) self = .Sticker(displayText: alt, packReference: packReference, maskData: nil)
case let .documentAttributeVideo(duration, w, h): 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 { if (flags & (1 << 0)) != 0 {
videoFlags.insert(.instantRoundVideo) 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 { if (flags & (1 << 0)) != 0 {
videoFlags.insert(.instantRoundVideo) 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 { if (flags & (1 << 0)) != 0 {
videoFlags.insert(.instantRoundVideo) 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 text = caption
} }
if let file = file { 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] = [] var previewRepresentations: [TelegramMediaImageRepresentation] = []
if thumb.size != 0 { if thumb.size != 0 {
let resource = LocalFileMediaResource(fileId: Int64.random(in: Int64.min ... Int64.max)) 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 { loop: for attr in parsedAttributes {
switch attr { switch attr {
case let .Video(_, _, flags, _): case let .Video(_, _, flags, _, _):
if flags.contains(.instantRoundVideo) { if flags.contains(.instantRoundVideo) {
attributes.append(ConsumableContentMessageAttribute(consumed: false)) attributes.append(ConsumableContentMessageAttribute(consumed: false))
} }
@ -1040,7 +1040,7 @@ private func parseMessage(peerId: PeerId, authorId: PeerId, tagLocalIndex: Int32
text = caption text = caption
} }
if let file = file { 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] = [] var previewRepresentations: [TelegramMediaImageRepresentation] = []
if thumb.size != 0 { if thumb.size != 0 {
let resource = LocalFileMediaResource(fileId: Int64.random(in: Int64.min ... Int64.max)) 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 { loop: for attr in parsedAttributes {
switch attr { switch attr {
case let .Video(_, _, flags, _): case let .Video(_, _, flags, _, _):
if flags.contains(.instantRoundVideo) { if flags.contains(.instantRoundVideo) {
attributes.append(ConsumableContentMessageAttribute(consumed: false)) attributes.append(ConsumableContentMessageAttribute(consumed: false))
} }
@ -1319,7 +1319,7 @@ private func parseMessage(peerId: PeerId, authorId: PeerId, tagLocalIndex: Int32
text = caption text = caption
} }
if let file = file { 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] = [] var previewRepresentations: [TelegramMediaImageRepresentation] = []
if thumb.size != 0 { if thumb.size != 0 {
let resource = LocalFileMediaResource(fileId: Int64.random(in: Int64.min ... Int64.max)) 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 { loop: for attr in parsedAttributes {
switch attr { switch attr {
case let .Video(_, _, flags, _): case let .Video(_, _, flags, _, _):
if flags.contains(.instantRoundVideo) { if flags.contains(.instantRoundVideo) {
attributes.append(ConsumableContentMessageAttribute(consumed: false)) attributes.append(ConsumableContentMessageAttribute(consumed: false))
} }
@ -1520,7 +1520,7 @@ private func parseMessage(peerId: PeerId, authorId: PeerId, tagLocalIndex: Int32
text = caption text = caption
} }
if let file = file { 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] = [] var previewRepresentations: [TelegramMediaImageRepresentation] = []
if thumb.size != 0 { if thumb.size != 0 {
let resource = LocalFileMediaResource(fileId: Int64.random(in: Int64.min ... Int64.max)) 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 Sticker(displayText: String, packReference: StickerPackReference?, maskData: StickerMaskCoords?)
case ImageSize(size: PixelDimensions) case ImageSize(size: PixelDimensions)
case Animated 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 Audio(isVoice: Bool, duration: Int, title: String?, performer: String?, waveform: Data?)
case HasLinkedStickers case HasLinkedStickers
case hintFileIsLarge case hintFileIsLarge
@ -262,7 +262,7 @@ public enum TelegramMediaFileAttribute: PostboxCoding, Equatable {
duration = Double(decoder.decodeInt32ForKey("du", orElse: 0)) 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: case typeAudio:
let waveformBuffer = decoder.decodeBytesForKeyNoCopy("wf") let waveformBuffer = decoder.decodeBytesForKeyNoCopy("wf")
var waveform: Data? var waveform: Data?
@ -309,7 +309,7 @@ public enum TelegramMediaFileAttribute: PostboxCoding, Equatable {
encoder.encodeInt32(Int32(size.height), forKey: "h") encoder.encodeInt32(Int32(size.height), forKey: "h")
case .Animated: case .Animated:
encoder.encodeInt32(typeAnimated, forKey: "t") 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.encodeInt32(typeVideo, forKey: "t")
encoder.encodeDouble(duration, forKey: "dur") encoder.encodeDouble(duration, forKey: "dur")
encoder.encodeInt32(Int32(size.width), forKey: "w") encoder.encodeInt32(Int32(size.width), forKey: "w")
@ -320,6 +320,11 @@ public enum TelegramMediaFileAttribute: PostboxCoding, Equatable {
} else { } else {
encoder.encodeNil(forKey: "prs") 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): case let .Audio(isVoice, duration, title, performer, waveform):
encoder.encodeInt32(typeAudio, forKey: "t") encoder.encodeInt32(typeAudio, forKey: "t")
encoder.encodeInt32(isVoice ? 1 : 0, forKey: "iv") encoder.encodeInt32(isVoice ? 1 : 0, forKey: "iv")
@ -592,7 +597,7 @@ public final class TelegramMediaFile: Media, Equatable, Codable {
public var isInstantVideo: Bool { public var isInstantVideo: Bool {
for attribute in self.attributes { for attribute in self.attributes {
if case .Video(_, _, let flags, _) = attribute { if case .Video(_, _, let flags, _, _) = attribute {
return flags.contains(.instantRoundVideo) return flags.contains(.instantRoundVideo)
} }
} }
@ -601,7 +606,7 @@ public final class TelegramMediaFile: Media, Equatable, Codable {
public var preloadSize: Int32? { public var preloadSize: Int32? {
for attribute in self.attributes { for attribute in self.attributes {
if case .Video(_, _, _, let preloadSize) = attribute { if case .Video(_, _, _, let preloadSize, _) = attribute {
return preloadSize return preloadSize
} }
} }

View File

@ -118,7 +118,7 @@ func _internal_outgoingMessageWithChatContextResult(to peerId: PeerId, threadId:
if let dimensions = externalReference.content?.dimensions { if let dimensions = externalReference.content?.dimensions {
fileAttributes.append(.ImageSize(size: dimensions)) fileAttributes.append(.ImageSize(size: dimensions))
if externalReference.type == "gif" { 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 { public enum EngineStoryInputMedia {
case image(dimensions: PixelDimensions, data: Data, stickers: [TelegramMediaFile]) 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) case existing(media: Media)
var embeddedStickers: [TelegramMediaFile] { var embeddedStickers: [TelegramMediaFile] {
switch self { switch self {
case let .image(_, _, stickers), let .video(_, _, _, _, stickers): case let .image(_, _, stickers), let .video(_, _, _, _, stickers, _):
return stickers return stickers
case .existing: case .existing:
return [] return []
@ -849,7 +849,7 @@ private func prepareUploadStoryContent(account: Account, media: EngineStoryInput
flags: [] flags: []
) )
return imageMedia return imageMedia
case let .video(dimensions, duration, resource, firstFrameFile, _): case let .video(dimensions, duration, resource, firstFrameFile, _, coverTime):
var previewRepresentations: [TelegramMediaImageRepresentation] = [] var previewRepresentations: [TelegramMediaImageRepresentation] = []
if let firstFrameFile = firstFrameFile { if let firstFrameFile = firstFrameFile {
account.postbox.mediaBox.storeCachedResourceRepresentation(resource.id.stringRepresentation, representationId: "first-frame", keepDuration: .general, tempFile: 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", mimeType: "video/mp4",
size: nil, size: nil,
attributes: [ 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(.FileName(fileName: "sticker.webm"))
fileAttributes.append(.Animated) fileAttributes.append(.Animated)
fileAttributes.append(.Sticker(displayText: "", packReference: nil, maskData: nil)) 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" { } else if self.mimeType == "application/x-tgsticker" {
fileAttributes.append(.FileName(fileName: "sticker.tgs")) fileAttributes.append(.FileName(fileName: "sticker.tgs"))
fileAttributes.append(.Animated) fileAttributes.append(.Animated)

View File

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

View File

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

View File

@ -172,7 +172,7 @@ private final class ChatContextResultPeekNode: ASDisplayNode, PeekControllerCont
imageDimensions = externalReference.content?.dimensions?.cgSize imageDimensions = externalReference.content?.dimensions?.cgSize
if let content = externalReference.content, externalReference.type == "gif", let thumbnailResource = imageResource if let content = externalReference.content, externalReference.type == "gif", let thumbnailResource = imageResource
, let dimensions = content.dimensions { , 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 imageResource = nil
} }
case let .internalReference(internalReference): case let .internalReference(internalReference):

View File

@ -272,7 +272,7 @@ public class ChatMessageActionBubbleContentNode: ChatMessageBubbleContentNode {
strongSelf.mediaBackgroundNode.image = backgroundImage strongSelf.mediaBackgroundNode.image = backgroundImage
if let image = image, let video = image.videoRepresentations.last, let id = image.id?.id { 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) 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 { if videoContent.id != strongSelf.videoContent?.id {
let mediaManager = item.context.sharedContext.mediaManager 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 messageTheme = arguments.incoming ? arguments.presentationData.theme.theme.chat.message.incoming : arguments.presentationData.theme.theme.chat.message.outgoing
let isInstantVideo = arguments.file.isInstantVideo let isInstantVideo = arguments.file.isInstantVideo
for attribute in arguments.file.attributes { 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 isAudio = true
isVoice = true isVoice = true
@ -1558,7 +1558,7 @@ public final class ChatMessageInteractiveFileNode: ASDisplayNode {
var isVoice = false var isVoice = false
var audioDuration: Int32? var audioDuration: Int32?
for attribute in file.attributes { 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 isAudio = true
isVoice = true isVoice = true
audioDuration = Int32(duration) audioDuration = Int32(duration)

View File

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

View File

@ -203,7 +203,7 @@ public final class ChatMessageAccessibilityData {
text = item.presentationData.strings.VoiceOver_Chat_MusicTitle(title, performer).string text = item.presentationData.strings.VoiceOver_Chat_MusicTitle(title, performer).string
text.append(item.presentationData.strings.VoiceOver_Chat_Duration(durationString).string) text.append(item.presentationData.strings.VoiceOver_Chat_Duration(durationString).string)
} }
case let .Video(duration, _, flags, _): case let .Video(duration, _, flags, _, _):
isSpecialFile = true isSpecialFile = true
if isSelected == nil { if isSelected == nil {
hint = item.presentationData.strings.VoiceOver_Chat_PlayHint 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 { 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) 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 { if videoContent.id != strongSelf.videoContent?.id {
let mediaManager = item.context.sharedContext.mediaManager 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))) references.append(MultiplexedVideoNodeFile(file: FileMediaReference.standalone(media: file), contextResult: (collection, result)))
} }
case let .internalReference(internalReference): 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: []) 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 let scheduleTime: Int32? = scheduleTimestamp > 0 ? scheduleTimestamp : nil

View File

@ -476,6 +476,7 @@ public final class MediaEditor {
audioTrackOffset: nil, audioTrackOffset: nil,
audioTrackVolume: nil, audioTrackVolume: nil,
audioTrackSamples: nil, audioTrackSamples: nil,
coverImageTimestamp: nil,
qualityPreset: 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]) { public func setDrawingAndEntities(data: Data?, image: UIImage?, entities: [CodableDrawingEntity]) {
self.updateValues(mode: .skipRendering) { values in self.updateValues(mode: .skipRendering) { values in
return values.withUpdatedDrawingAndEntities(drawing: image, entities: entities) return values.withUpdatedDrawingAndEntities(drawing: image, entities: entities)

View File

@ -324,6 +324,9 @@ public final class MediaEditorValues: Codable, Equatable {
if lhs.audioTrackSamples != rhs.audioTrackSamples { if lhs.audioTrackSamples != rhs.audioTrackSamples {
return false return false
} }
if lhs.coverImageTimestamp != rhs.coverImageTimestamp {
return false
}
if lhs.nightTheme != rhs.nightTheme { if lhs.nightTheme != rhs.nightTheme {
return false return false
} }
@ -394,6 +397,7 @@ public final class MediaEditorValues: Codable, Equatable {
case audioTrackTrimRange case audioTrackTrimRange
case audioTrackOffset case audioTrackOffset
case audioTrackVolume case audioTrackVolume
case coverImageTimestamp
case qualityPreset case qualityPreset
} }
@ -438,6 +442,8 @@ public final class MediaEditorValues: Codable, Equatable {
public let audioTrackVolume: CGFloat? public let audioTrackVolume: CGFloat?
public let audioTrackSamples: MediaAudioTrackSamples? public let audioTrackSamples: MediaAudioTrackSamples?
public let coverImageTimestamp: Double?
public let qualityPreset: MediaQualityPreset? public let qualityPreset: MediaQualityPreset?
var isStory: Bool { var isStory: Bool {
@ -486,6 +492,7 @@ public final class MediaEditorValues: Codable, Equatable {
audioTrackOffset: Double?, audioTrackOffset: Double?,
audioTrackVolume: CGFloat?, audioTrackVolume: CGFloat?,
audioTrackSamples: MediaAudioTrackSamples?, audioTrackSamples: MediaAudioTrackSamples?,
coverImageTimestamp: Double?,
qualityPreset: MediaQualityPreset? qualityPreset: MediaQualityPreset?
) { ) {
self.peerId = peerId self.peerId = peerId
@ -521,6 +528,7 @@ public final class MediaEditorValues: Codable, Equatable {
self.audioTrackOffset = audioTrackOffset self.audioTrackOffset = audioTrackOffset
self.audioTrackVolume = audioTrackVolume self.audioTrackVolume = audioTrackVolume
self.audioTrackSamples = audioTrackSamples self.audioTrackSamples = audioTrackSamples
self.coverImageTimestamp = coverImageTimestamp
self.qualityPreset = qualityPreset self.qualityPreset = qualityPreset
} }
@ -591,6 +599,8 @@ public final class MediaEditorValues: Codable, Equatable {
self.audioTrackSamples = nil 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) } 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.audioTrackOffset, forKey: .audioTrackOffset)
try container.encodeIfPresent(self.audioTrackVolume, forKey: .audioTrackVolume) try container.encodeIfPresent(self.audioTrackVolume, forKey: .audioTrackVolume)
try container.encodeIfPresent(self.coverImageTimestamp, forKey: .coverImageTimestamp)
try container.encodeIfPresent(self.qualityPreset?.rawValue, forKey: .qualityPreset) try container.encodeIfPresent(self.qualityPreset?.rawValue, forKey: .qualityPreset)
} }
public func makeCopy() -> MediaEditorValues { 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 { 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 { 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 { 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 { 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 { 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 { 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 { 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 { 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 { 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 { 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 { 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 { 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 { 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 { 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 { 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 { 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 { 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 { 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 { 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 { 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 { 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 { 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 { 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 { 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 { 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 |> deliverOnMainQueue).startStrict(next: { result in
switch result { switch result {
case let .progress(progress): 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 cutout
case cutoutErase case cutoutErase
case cutoutRestore case cutoutRestore
case cover
} }
let context: AccountContext let context: AccountContext
@ -827,7 +828,6 @@ final class MediaEditorScreenComponent: Component {
doneButtonTitle = nil doneButtonTitle = nil
doneButtonIcon = generateTintedImage(image: UIImage(bundleImageName: "Media Editor/Apply"), color: .white)! doneButtonIcon = generateTintedImage(image: UIImage(bundleImageName: "Media Editor/Apply"), color: .white)!
case .botPreview: case .botPreview:
//TODO:localize
doneButtonTitle = environment.strings.Story_Editor_Add doneButtonTitle = environment.strings.Story_Editor_Add
doneButtonIcon = nil doneButtonIcon = nil
} }
@ -841,31 +841,7 @@ final class MediaEditorScreenComponent: Component {
title: doneButtonTitle)), title: doneButtonTitle)),
effectAlignment: .center, effectAlignment: .center,
action: { [weak controller] in action: { [weak controller] in
guard let controller else { controller?.node.requestCompletion()
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)
}
} }
)), )),
environment: {}, environment: {},
@ -2555,6 +2531,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
fileprivate var drawingScreen: DrawingScreen? fileprivate var drawingScreen: DrawingScreen?
fileprivate var stickerScreen: StickerPickerScreen? fileprivate var stickerScreen: StickerPickerScreen?
fileprivate weak var cutoutScreen: MediaCutoutScreen? fileprivate weak var cutoutScreen: MediaCutoutScreen?
fileprivate weak var coverScreen: MediaCoverScreen?
private var defaultToEmoji = false private var defaultToEmoji = false
private var previousDrawingData: Data? 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) { func updateModalTransitionFactor(_ value: CGFloat, transition: ContainedViewLayoutTransition) {
guard let layout = self.validLayout, case .compact = layout.metrics.widthClass else { guard let layout = self.validLayout, case .compact = layout.metrics.widthClass else {
return return
@ -5077,6 +5123,8 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
} }
self.controller?.present(controller, in: .window(.root)) self.controller?.present(controller, in: .window(.root))
self.animateOutToTool(tool: .tools) self.animateOutToTool(tool: .tools)
case .cover:
self.openCoverSelection()
} }
} }
}, },
@ -5652,8 +5700,12 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
return self.isEditingStory || self.forwardSource != nil return self.isEditingStory || self.forwardSource != nil
} }
private var currentCoverImage: UIImage?
func openPrivacySettings(_ privacy: MediaEditorResultPrivacy? = nil, completion: @escaping () -> Void = {}) { 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) self.hapticFeedback.impact(.light)
@ -5662,6 +5714,8 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
let text = self.getCaption().string 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 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( let stateContext = ShareWithPeersScreen.StateContext(
context: self.context, context: self.context,
subject: .stories(editing: false), subject: .stories(editing: false),
@ -5679,6 +5733,8 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
let initialPrivacy = privacy.privacy let initialPrivacy = privacy.privacy
let timeout = privacy.timeout let timeout = privacy.timeout
var editCoverImpl: (() -> Void)?
let controller = ShareWithPeersScreen( let controller = ShareWithPeersScreen(
context: self.context, context: self.context,
initialPrivacy: initialPrivacy, initialPrivacy: initialPrivacy,
@ -5687,6 +5743,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
pin: privacy.pin, pin: privacy.pin,
timeout: privacy.timeout, timeout: privacy.timeout,
mentions: mentions, mentions: mentions,
coverImage: coverImage,
stateContext: stateContext, stateContext: stateContext,
completion: { [weak self] sendAsPeerId, privacy, allowScreenshots, pin, _, completed in completion: { [weak self] sendAsPeerId, privacy, allowScreenshots, pin, _, completed in
guard let self else { guard let self else {
@ -5736,6 +5793,9 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
pin: pin pin: pin
), completion: completion) ), completion: completion)
}) })
},
editCover: {
editCoverImpl?()
} }
) )
controller.customModalStyleOverlayTransitionFactorUpdated = { [weak self, weak controller] transition in controller.customModalStyleOverlayTransitionFactorUpdated = { [weak self, weak controller] transition in
@ -5748,6 +5808,17 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
self.node.mediaEditor?.play() self.node.mediaEditor?.play()
} }
self.push(controller) 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(.FileName(fileName: isVideo ? "sticker.webm" : "sticker.webp"))
fileAttributes.append(.Sticker(displayText: "", packReference: nil, maskData: nil)) fileAttributes.append(.Sticker(displayText: "", packReference: nil, maskData: nil))
if isVideo { 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 { } else {
fileAttributes.append(.ImageSize(size: dimensions)) fileAttributes.append(.ImageSize(size: dimensions))
} }

View File

@ -70,6 +70,7 @@ public final class MediaScrubberComponent: Component {
public enum Style { public enum Style {
case editor case editor
case videoMessage case videoMessage
case cover
} }
let context: AccountContext let context: AccountContext
@ -84,6 +85,7 @@ public final class MediaScrubberComponent: Component {
let isPlaying: Bool let isPlaying: Bool
let tracks: [Track] let tracks: [Track]
let portalView: PortalView?
let positionUpdated: (Double, Bool) -> Void let positionUpdated: (Double, Bool) -> Void
let trackTrimUpdated: (Int32, Double, Double, Bool, Bool) -> Void let trackTrimUpdated: (Int32, Double, Double, Bool, Bool) -> Void
@ -100,6 +102,7 @@ public final class MediaScrubberComponent: Component {
maxDuration: Double, maxDuration: Double,
isPlaying: Bool, isPlaying: Bool,
tracks: [Track], tracks: [Track],
portalView: PortalView? = nil,
positionUpdated: @escaping (Double, Bool) -> Void, positionUpdated: @escaping (Double, Bool) -> Void,
trackTrimUpdated: @escaping (Int32, Double, Double, Bool, Bool) -> Void, trackTrimUpdated: @escaping (Int32, Double, Double, Bool, Bool) -> Void,
trackOffsetUpdated: @escaping (Int32, Double, Bool) -> Void, trackOffsetUpdated: @escaping (Int32, Double, Bool) -> Void,
@ -114,6 +117,7 @@ public final class MediaScrubberComponent: Component {
self.maxDuration = maxDuration self.maxDuration = maxDuration
self.isPlaying = isPlaying self.isPlaying = isPlaying
self.tracks = tracks self.tracks = tracks
self.portalView = portalView
self.positionUpdated = positionUpdated self.positionUpdated = positionUpdated
self.trackTrimUpdated = trackTrimUpdated self.trackTrimUpdated = trackTrimUpdated
self.trackOffsetUpdated = trackOffsetUpdated self.trackOffsetUpdated = trackOffsetUpdated
@ -152,6 +156,7 @@ public final class MediaScrubberComponent: Component {
private var trackViews: [Int32: TrackView] = [:] private var trackViews: [Int32: TrackView] = [:]
private let trimView: TrimView private let trimView: TrimView
private let ghostTrimView: TrimView private let ghostTrimView: TrimView
private let cursorContentView: UIView
private let cursorView: HandleView private let cursorView: HandleView
private var cursorDisplayLink: SharedDisplayLinkDriver.Link? private var cursorDisplayLink: SharedDisplayLinkDriver.Link?
@ -169,6 +174,7 @@ public final class MediaScrubberComponent: Component {
self.trimView = TrimView(frame: .zero) self.trimView = TrimView(frame: .zero)
self.ghostTrimView = TrimView(frame: .zero) self.ghostTrimView = TrimView(frame: .zero)
self.ghostTrimView.isHollow = true self.ghostTrimView.isHollow = true
self.cursorContentView = UIView()
self.cursorView = HandleView() self.cursorView = HandleView()
super.init(frame: frame) super.init(frame: frame)
@ -178,6 +184,10 @@ public final class MediaScrubberComponent: Component {
self.disablesInteractiveModalDismiss = true self.disablesInteractiveModalDismiss = true
self.disablesInteractiveKeyboardGestureRecognizer = 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 let positionImage = generateImage(CGSize(width: handleWidth, height: 50.0), rotatedContext: { size, context in
context.clear(CGRect(origin: .zero, size: size)) context.clear(CGRect(origin: .zero, size: size))
context.setFillColor(UIColor.white.cgColor) context.setFillColor(UIColor.white.cgColor)
@ -187,13 +197,13 @@ public final class MediaScrubberComponent: Component {
context.addPath(path.cgPath) context.addPath(path.cgPath)
context.fillPath() context.fillPath()
})?.stretchableImage(withLeftCapWidth: Int(handleWidth / 2.0), topCapHeight: 25) })?.stretchableImage(withLeftCapWidth: Int(handleWidth / 2.0), topCapHeight: 25)
self.cursorView.image = positionImage self.cursorView.image = positionImage
self.cursorView.isUserInteractionEnabled = true self.cursorView.isUserInteractionEnabled = true
self.cursorView.hitTestSlop = UIEdgeInsets(top: -8.0, left: -9.0, bottom: -8.0, right: -9.0) self.cursorView.hitTestSlop = UIEdgeInsets(top: -8.0, left: -9.0, bottom: -8.0, right: -9.0)
self.addSubview(self.ghostTrimView) self.addSubview(self.ghostTrimView)
self.addSubview(self.trimView) self.addSubview(self.trimView)
self.addSubview(self.cursorContentView)
self.addSubview(self.cursorView) self.addSubview(self.cursorView)
self.cursorView.addGestureRecognizer(UIPanGestureRecognizer(target: self, action: #selector(self.handleCursorPan(_:)))) 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 { 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 cursorPadding: CGFloat = 8.0
let cursorPositionFraction = duration > 0.0 ? position / duration : 0.0 let cursorPositionFraction = duration > 0.0 ? position / duration : 0.0
let cursorPosition = floorToScreenPixels(handleWidth - 1.0 + (size.width - handleWidth * 2.0 + 2.0) * cursorPositionFraction) let cursorPosition = floorToScreenPixels(cursorMargin - 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)) 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 leftEdge = self.ghostTrimView.leftHandleView.frame.maxX
var rightEdge = self.ghostTrimView.rightHandleView.frame.minX var rightEdge = self.ghostTrimView.rightHandleView.frame.minX
@ -339,9 +362,13 @@ public final class MediaScrubberComponent: Component {
leftEdge = self.trimView.leftHandleView.frame.maxX leftEdge = self.trimView.leftHandleView.frame.maxX
rightEdge = self.trimView.rightHandleView.frame.minX 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 = 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 return cursorFrame
} }
@ -377,6 +404,7 @@ public final class MediaScrubberComponent: Component {
updatedPosition = max(self.startPosition, min(self.endPosition, position + advance)) updatedPosition = max(self.startPosition, min(self.endPosition, position + advance))
} }
self.cursorView.frame = cursorFrame(size: scrubberSize, height: self.effectiveCursorHeight, position: updatedPosition, duration: self.trimDuration) 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 { 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.component = component
self.state = state 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 { switch component.style {
case .editor: case .editor:
self.cursorView.isHidden = false self.cursorView.isHidden = false
case .videoMessage: case .videoMessage:
self.cursorView.isHidden = true 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 var totalHeight: CGFloat = 0.0
@ -520,7 +573,7 @@ public final class MediaScrubberComponent: Component {
let fullTrackHeight: CGFloat let fullTrackHeight: CGFloat
switch component.style { switch component.style {
case .editor: case .editor, .cover:
fullTrackHeight = trackHeight fullTrackHeight = trackHeight
case .videoMessage: case .videoMessage:
fullTrackHeight = 33.0 fullTrackHeight = 33.0
@ -610,7 +663,9 @@ public final class MediaScrubberComponent: Component {
if let offset = self.mainAudioTrackOffset { if let offset = self.mainAudioTrackOffset {
cursorPosition -= offset 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 { } else {
if let (_, _, end, ended) = self.cursorPositionAnimation { if let (_, _, end, ended) = self.cursorPositionAnimation {
if ended, component.position >= self.startPosition && component.position < end - 1.0 { 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 fullTrackHeight: CGFloat
let framesCornerRadius: CGFloat let framesCornerRadius: CGFloat
switch style { switch style {
case .editor: case .editor, .cover:
fullTrackHeight = trackHeight fullTrackHeight = trackHeight
framesCornerRadius = 9.0 framesCornerRadius = 9.0
case .videoMessage: case .videoMessage:
@ -1362,7 +1417,7 @@ private class TrimView: UIView {
let highlightColor: UIColor let highlightColor: UIColor
switch style { switch style {
case .editor: case .editor, .cover:
effectiveHandleWidth = handleWidth effectiveHandleWidth = handleWidth
fullTrackHeight = trackHeight fullTrackHeight = trackHeight
capsuleOffset = 5.0 - UIScreenPixel 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.update(markup: markup, size: CGSize(width: 320.0, height: 320.0))
markupNode.updateVisibility(true) markupNode.updateVisibility(true)
} else if threadInfo == nil, let video = videoRepresentations.last, let peerReference = PeerReference(peer) { } 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) 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 { if videoContent.id != self.videoContent?.id {
self.videoNode?.removeFromSupernode() self.videoNode?.removeFromSupernode()

View File

@ -162,7 +162,7 @@ final class PeerInfoEditingAvatarNode: ASDisplayNode {
markupNode.removeFromSupernode() 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) 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 { if videoContent.id != self.videoContent?.id {
self.videoNode?.removeFromSupernode() self.videoNode?.removeFromSupernode()

View File

@ -910,6 +910,7 @@ private extension MediaEditorValues {
audioTrackOffset: nil, audioTrackOffset: nil,
audioTrackVolume: nil, audioTrackVolume: nil,
audioTrackSamples: nil, audioTrackSamples: nil,
coverImageTimestamp: nil,
qualityPreset: qualityPreset qualityPreset: qualityPreset
) )
} }
@ -1053,6 +1054,7 @@ private extension MediaEditorValues {
audioTrackOffset: nil, audioTrackOffset: nil,
audioTrackVolume: nil, audioTrackVolume: nil,
audioTrackSamples: nil, audioTrackSamples: nil,
coverImageTimestamp: nil,
qualityPreset: qualityPreset 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 mentions: [String]
let categoryItems: [CategoryItem] let categoryItems: [CategoryItem]
let optionItems: [OptionItem] let optionItems: [OptionItem]
let coverItem: CoverItem?
let completion: (EnginePeer.Id?, EngineStoryPrivacy, Bool, Bool, [EnginePeer], Bool) -> Void let completion: (EnginePeer.Id?, EngineStoryPrivacy, Bool, Bool, [EnginePeer], Bool) -> Void
let editCategory: (EngineStoryPrivacy, Bool, Bool) -> Void let editCategory: (EngineStoryPrivacy, Bool, Bool) -> Void
let editBlockedPeers: (EngineStoryPrivacy, Bool, Bool) -> Void let editBlockedPeers: (EngineStoryPrivacy, Bool, Bool) -> Void
let editCover: () -> Void
let peerCompletion: (EnginePeer.Id) -> Void let peerCompletion: (EnginePeer.Id) -> Void
init( init(
@ -54,9 +56,11 @@ final class ShareWithPeersScreenComponent: Component {
mentions: [String], mentions: [String],
categoryItems: [CategoryItem], categoryItems: [CategoryItem],
optionItems: [OptionItem], optionItems: [OptionItem],
coverItem: CoverItem?,
completion: @escaping (EnginePeer.Id?, EngineStoryPrivacy, Bool, Bool, [EnginePeer], Bool) -> Void, completion: @escaping (EnginePeer.Id?, EngineStoryPrivacy, Bool, Bool, [EnginePeer], Bool) -> Void,
editCategory: @escaping (EngineStoryPrivacy, Bool, Bool) -> Void, editCategory: @escaping (EngineStoryPrivacy, Bool, Bool) -> Void,
editBlockedPeers: @escaping (EngineStoryPrivacy, Bool, Bool) -> Void, editBlockedPeers: @escaping (EngineStoryPrivacy, Bool, Bool) -> Void,
editCover: @escaping () -> Void,
peerCompletion: @escaping (EnginePeer.Id) -> Void peerCompletion: @escaping (EnginePeer.Id) -> Void
) { ) {
self.context = context self.context = context
@ -69,9 +73,11 @@ final class ShareWithPeersScreenComponent: Component {
self.mentions = mentions self.mentions = mentions
self.categoryItems = categoryItems self.categoryItems = categoryItems
self.optionItems = optionItems self.optionItems = optionItems
self.coverItem = coverItem
self.completion = completion self.completion = completion
self.editCategory = editCategory self.editCategory = editCategory
self.editBlockedPeers = editBlockedPeers self.editBlockedPeers = editBlockedPeers
self.editCover = editCover
self.peerCompletion = peerCompletion self.peerCompletion = peerCompletion
} }
@ -106,6 +112,9 @@ final class ShareWithPeersScreenComponent: Component {
if lhs.optionItems != rhs.optionItems { if lhs.optionItems != rhs.optionItems {
return false return false
} }
if lhs.coverItem != rhs.coverItem {
return false
}
return true return true
} }
@ -258,6 +267,33 @@ final class ShareWithPeersScreenComponent: Component {
return false 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 { final class View: UIView, UIScrollViewDelegate {
private let dimView: UIView 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 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( let footerSize = sectionFooter.update(
transition: sectionFooterTransition, transition: sectionFooterTransition,
component: AnyComponent(MultilineTextComponent( component: AnyComponent(MultilineTextComponent(
@ -1625,7 +1745,6 @@ final class ShareWithPeersScreenComponent: Component {
sectionFooterTransition.setFrame(view: footerView, frame: footerFrame) sectionFooterTransition.setFrame(view: footerView, frame: footerFrame)
} }
} }
sectionOffset += section.totalHeight sectionOffset += section.totalHeight
} }
@ -1873,6 +1992,8 @@ final class ShareWithPeersScreenComponent: Component {
} }
private var currentHasChannels: Bool? private var currentHasChannels: Bool?
private var currentHasCover: Bool?
func update(component: ShareWithPeersScreenComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<ViewControllerComponentContainer.Environment>, transition: ComponentTransition) -> CGSize { func update(component: ShareWithPeersScreenComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<ViewControllerComponentContainer.Environment>, transition: ComponentTransition) -> CGSize {
guard !self.isDismissed else { guard !self.isDismissed else {
return availableSize return availableSize
@ -1886,19 +2007,28 @@ final class ShareWithPeersScreenComponent: Component {
var hasCategories = false var hasCategories = false
var hasChannels = false var hasChannels = false
var hasCover = false
if case .stories = component.stateContext.subject { if case .stories = component.stateContext.subject {
if let peerId = self.sendAsPeerId, peerId.isGroupOrChannel { if let peerId = self.sendAsPeerId, peerId.isGroupOrChannel {
} else { } else {
hasCategories = true hasCategories = true
} }
let sendAsPeersCount = component.stateContext.stateValue?.sendAsPeers.count ?? 1 let sendAsPeersCount = component.stateContext.stateValue?.sendAsPeers.count ?? 1
if sendAsPeersCount > 1 { if sendAsPeersCount > 1 && !"".isEmpty {
hasChannels = true hasChannels = true
} }
if let currentHasChannels = self.currentHasChannels, currentHasChannels != hasChannels { if let currentHasChannels = self.currentHasChannels, currentHasChannels != hasChannels {
contentTransition = .spring(duration: 0.4) contentTransition = .spring(duration: 0.4)
} }
self.currentHasChannels = hasChannels 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 { } else if case .members = component.stateContext.subject {
self.dismissPanGesture?.isEnabled = false self.dismissPanGesture?.isEnabled = false
} else if case .channels = component.stateContext.subject { } else if case .channels = component.stateContext.subject {
@ -2306,6 +2436,15 @@ final class ShareWithPeersScreenComponent: Component {
itemHeight: optionItemSize.height, itemHeight: optionItemSize.height,
itemCount: component.optionItems.count 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 { } else {
sections.append(ItemLayout.Section( sections.append(ItemLayout.Section(
id: 1, id: 1,
@ -2489,8 +2628,12 @@ final class ShareWithPeersScreenComponent: Component {
inset = 1000.0 inset = 1000.0
} }
} else { } else {
inset = 464.0 if self.selectedOptions.contains(.pin) {
inset += 10.0 + environment.safeInsets.bottom + 50.0 + footersTotalHeight 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, pin: Bool = false,
timeout: Int = 0, timeout: Int = 0,
mentions: [String] = [], mentions: [String] = [],
coverImage: UIImage? = nil,
stateContext: StateContext, stateContext: StateContext,
completion: @escaping (EnginePeer.Id?, EngineStoryPrivacy, Bool, Bool, [EnginePeer], Bool) -> Void, completion: @escaping (EnginePeer.Id?, EngineStoryPrivacy, Bool, Bool, [EnginePeer], Bool) -> Void,
editCategory: @escaping (EngineStoryPrivacy, Bool, Bool) -> Void = { _, _, _ in }, editCategory: @escaping (EngineStoryPrivacy, Bool, Bool) -> Void = { _, _, _ in },
editBlockedPeers: @escaping (EngineStoryPrivacy, Bool, Bool) -> Void = { _, _, _ in }, editBlockedPeers: @escaping (EngineStoryPrivacy, Bool, Bool) -> Void = { _, _, _ in },
editCover: @escaping () -> Void = { },
peerCompletion: @escaping (EnginePeer.Id) -> Void = { _ in } peerCompletion: @escaping (EnginePeer.Id) -> Void = { _ in }
) { ) {
self.context = context self.context = context
@ -2861,6 +3006,7 @@ public class ShareWithPeersScreen: ViewControllerComponentContainer {
var categoryItems: [ShareWithPeersScreenComponent.CategoryItem] = [] var categoryItems: [ShareWithPeersScreenComponent.CategoryItem] = []
var optionItems: [ShareWithPeersScreenComponent.OptionItem] = [] var optionItems: [ShareWithPeersScreenComponent.OptionItem] = []
var coverItem: ShareWithPeersScreenComponent.CoverItem?
if case let .stories(editing) = stateContext.subject { if case let .stories(editing) = stateContext.subject {
var everyoneSubtitle = presentationData.strings.Story_Privacy_ExcludePeople var everyoneSubtitle = presentationData.strings.Story_Privacy_ExcludePeople
if (stateContext.stateValue?.savedSelectedPeers[.everyone]?.count ?? 0) > 0 { if (stateContext.stateValue?.savedSelectedPeers[.everyone]?.count ?? 0) > 0 {
@ -2994,6 +3140,10 @@ public class ShareWithPeersScreen: ViewControllerComponentContainer {
title: presentationData.strings.Story_Privacy_KeepOnMyPage 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 var theme: ViewControllerComponentContainer.Theme = .dark
@ -3013,9 +3163,11 @@ public class ShareWithPeersScreen: ViewControllerComponentContainer {
mentions: mentions, mentions: mentions,
categoryItems: categoryItems, categoryItems: categoryItems,
optionItems: optionItems, optionItems: optionItems,
coverItem: coverItem,
completion: completion, completion: completion,
editCategory: editCategory, editCategory: editCategory,
editBlockedPeers: editBlockedPeers, editBlockedPeers: editBlockedPeers,
editCover: editCover,
peerCompletion: peerCompletion peerCompletion: peerCompletion
), navigationBarAppearance: .none, theme: theme) ), navigationBarAppearance: .none, theme: theme)

View File

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

View File

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

View File

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

View File

@ -1793,7 +1793,7 @@ public class VideoMessageCameraScreen: ViewController {
guard let self else { guard let self else {
return 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 var resourceAdjustments: VideoMediaResourceAdjustments? = nil
if let valuesData = try? JSONEncoder().encode(values) { 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) 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] = [] var attributes: [MessageAttribute] = []
if self.cameraState.isViewOnceEnabled { if self.cameraState.isViewOnceEnabled {

View File

@ -186,6 +186,7 @@ extension ChatControllerImpl {
audioTrackOffset: nil, audioTrackOffset: nil,
audioTrackVolume: nil, audioTrackVolume: nil,
audioTrackSamples: nil, audioTrackSamples: nil,
coverImageTimestamp: nil,
qualityPreset: nil qualityPreset: nil
) )
@ -210,7 +211,7 @@ extension ChatControllerImpl {
var fileAttributes: [TelegramMediaFileAttribute] = [] var fileAttributes: [TelegramMediaFileAttribute] = []
fileAttributes.append(.FileName(fileName: "sticker.webm")) fileAttributes.append(.FileName(fileName: "sticker.webm"))
fileAttributes.append(.Sticker(displayText: "", packReference: nil, maskData: nil)) 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] = [] let previewRepresentations: [TelegramMediaImageRepresentation] = []
// if let thumbnailResource { // if let thumbnailResource {

View File

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

View File

@ -260,7 +260,7 @@ final class HorizontalListContextResultsChatInputPanelItemNode: ListViewItemNode
} }
imageDimensions = externalReference.content?.dimensions?.cgSize imageDimensions = externalReference.content?.dimensions?.cgSize
if externalReference.type == "gif", let thumbnailResource = externalReference.thumbnail?.resource, let content = externalReference.content, let dimensions = content.dimensions { 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 imageResource = nil
} }

View File

@ -72,7 +72,7 @@ final class MessageMediaPlaylistItem: SharedMediaPlaylistItem {
} else { } else {
return SharedMediaPlaybackData(type: .music, source: source) return SharedMediaPlaybackData(type: .music, source: source)
} }
case let .Video(_, _, flags, _): case let .Video(_, _, flags, _, _):
if flags.contains(.instantRoundVideo) { if flags.contains(.instantRoundVideo) {
return SharedMediaPlaybackData(type: .instantVideo, source: source) return SharedMediaPlaybackData(type: .instantVideo, source: source)
} else { } 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) displayData = SharedMediaPlaybackDisplayData.music(title: updatedTitle, performer: updatedPerformer, albumArt: albumArt, long: CGFloat(duration) > 10.0 * 60.0, caption: caption)
} }
return displayData return displayData
case let .Video(_, _, flags, _): case let .Video(_, _, flags, _, _):
if flags.contains(.instantRoundVideo) { 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) 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 { } else {

View File

@ -671,7 +671,7 @@ public final class TelegramRootController: NavigationController, TelegramRootCon
return nil 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: default:
break break

View File

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

View File

@ -37,7 +37,7 @@ struct WebSearchGalleryEntry: Equatable {
switch self.result { switch self.result {
case let .externalReference(externalReference): case let .externalReference(externalReference):
if let content = externalReference.content, externalReference.type == "gif", let thumbnailResource = externalReference.thumbnail?.resource, let dimensions = content.dimensions { 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) 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): case let .internalReference(internalReference):

View File

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