From ebc8176850ac45f956dc3f964bfb64673afaf717 Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Sat, 13 Jan 2024 21:46:02 +0400 Subject: [PATCH] Video message recording improvements --- submodules/Camera/Sources/CameraOutput.swift | 12 +++++-- .../Sources/CameraRoundVideoFilter.swift | 2 +- .../Sources/VideoMessageCameraScreen.swift | 36 +++++++++++++++---- .../TelegramUI/Sources/ChatController.swift | 4 +-- .../ChatRecordingPreviewInputPanelNode.swift | 3 +- 5 files changed, 44 insertions(+), 13 deletions(-) diff --git a/submodules/Camera/Sources/CameraOutput.swift b/submodules/Camera/Sources/CameraOutput.swift index a97b804706..9b6787b011 100644 --- a/submodules/Camera/Sources/CameraOutput.swift +++ b/submodules/Camera/Sources/CameraOutput.swift @@ -302,6 +302,7 @@ final class CameraOutput: NSObject { } self.currentMode = mode + self.lastSampleTimestamp = nil let codecType: AVVideoCodecType if case .roundVideo = mode { @@ -407,6 +408,7 @@ final class CameraOutput: NSObject { private weak var masterOutput: CameraOutput? + private var lastSampleTimestamp: CMTime? func processVideoRecording(_ sampleBuffer: CMSampleBuffer, fromAdditionalOutput: Bool) { guard let formatDescriptor = CMSampleBufferGetFormatDescription(sampleBuffer) else { return @@ -430,8 +432,14 @@ final class CameraOutput: NSObject { } } if let processedSampleBuffer = self.processRoundVideoSampleBuffer(sampleBuffer, additional: fromAdditionalOutput, transitionFactor: transitionFactor) { - if (transitionFactor == 1.0 && fromAdditionalOutput) || (transitionFactor == 0.0 && !fromAdditionalOutput) || (transitionFactor > 0.0 && transitionFactor < 1.0) { - videoRecorder.appendSampleBuffer(processedSampleBuffer) + let presentationTime = CMSampleBufferGetPresentationTimeStamp(processedSampleBuffer) + if let lastSampleTimestamp = self.lastSampleTimestamp, lastSampleTimestamp > presentationTime { + + } else { + if (transitionFactor == 1.0 && fromAdditionalOutput) || (transitionFactor == 0.0 && !fromAdditionalOutput) || (transitionFactor > 0.0 && transitionFactor < 1.0) { + videoRecorder.appendSampleBuffer(processedSampleBuffer) + self.lastSampleTimestamp = presentationTime + } } } else { videoRecorder.appendSampleBuffer(sampleBuffer) diff --git a/submodules/Camera/Sources/CameraRoundVideoFilter.swift b/submodules/Camera/Sources/CameraRoundVideoFilter.swift index 93e3849800..83d8d9f1b6 100644 --- a/submodules/Camera/Sources/CameraRoundVideoFilter.swift +++ b/submodules/Camera/Sources/CameraRoundVideoFilter.swift @@ -121,7 +121,7 @@ class CameraRoundVideoFilter { context.setFillColor(UIColor.white.cgColor) context.fill(bounds) context.setBlendMode(.clear) - context.fillEllipse(in: bounds) + context.fillEllipse(in: bounds.insetBy(dx: -2.0, dy: -2.0)) })! self.resizeFilter = CIFilter(name: "CILanczosScaleTransform") diff --git a/submodules/TelegramUI/Components/VideoMessageCameraScreen/Sources/VideoMessageCameraScreen.swift b/submodules/TelegramUI/Components/VideoMessageCameraScreen/Sources/VideoMessageCameraScreen.swift index f06e18a375..33d0f6f75a 100644 --- a/submodules/TelegramUI/Components/VideoMessageCameraScreen/Sources/VideoMessageCameraScreen.swift +++ b/submodules/TelegramUI/Components/VideoMessageCameraScreen/Sources/VideoMessageCameraScreen.swift @@ -71,7 +71,7 @@ enum CameraScreenTransition { private let viewOnceButtonTag = GenericComponentViewTag() -private final class CameraScreenComponent: CombinedComponent { +private final class VideoMessageCameraScreenComponent: CombinedComponent { typealias EnvironmentType = ViewControllerComponentContainer.Environment let context: AccountContext @@ -109,7 +109,7 @@ private final class CameraScreenComponent: CombinedComponent { self.completion = completion } - static func ==(lhs: CameraScreenComponent, rhs: CameraScreenComponent) -> Bool { + static func ==(lhs: VideoMessageCameraScreenComponent, rhs: VideoMessageCameraScreenComponent) -> Bool { if lhs.context !== rhs.context { return false } @@ -184,6 +184,9 @@ private final class CameraScreenComponent: CombinedComponent { if let self, let controller = getController() { self.startVideoRecording(pressing: !controller.scheduledLock) controller.scheduledLock = false + if controller.recordingStartTime == nil { + controller.recordingStartTime = CACurrentMediaTime() + } } }) self.stopRecording.connect({ [weak self] _ in @@ -241,7 +244,7 @@ private final class CameraScreenComponent: CombinedComponent { let duration = initialDuration + recordingData.duration if let self, let controller = self.getController() { controller.updateCameraState({ $0.updatedDuration(duration) }, transition: .easeInOut(duration: 0.1)) - if recordingData.duration > 59.0 { + if duration > 59.0 { self.stopVideoRecording() } if isFirstRecording { @@ -323,6 +326,10 @@ private final class CameraScreenComponent: CombinedComponent { showViewOnce = true } + if let controller = component.getController(), !controller.viewOnceAvailable { + showViewOnce = false + } + if !component.isPreviewing { let flipButton = flipButton.update( component: CameraButton( @@ -942,7 +949,7 @@ public class VideoMessageCameraScreen: ViewController { let componentSize = self.componentHost.update( transition: transition, component: AnyComponent( - CameraScreenComponent( + VideoMessageCameraScreenComponent( context: self.context, cameraState: self.cameraState, isPreviewing: self.previewState != nil || self.transitioningToPreview, @@ -1056,6 +1063,7 @@ public class VideoMessageCameraScreen: ViewController { private let updatedPresentationData: (initial: PresentationData, signal: Signal)? private let inputPanelFrame: CGRect fileprivate var allowLiveUpload: Bool + fileprivate var viewOnceAvailable: Bool fileprivate let completion: (EnqueueMessage?) -> Void @@ -1147,13 +1155,20 @@ public class VideoMessageCameraScreen: ViewController { initialPlaceholder = self.camera?.transitionImage ?? .single(nil) } + var approximateDuration: Double + if let recordingStartTime = self.recordingStartTime { + approximateDuration = CACurrentMediaTime() - recordingStartTime + } else { + approximateDuration = 1.0 + } + let immediateResult: Signal = initialPlaceholder |> take(1) |> mapToSignal { initialPlaceholder in return videoFrames(asset: nil, count: count, initialPlaceholder: initialPlaceholder) |> map { framesAndUpdateTimestamp in return RecordedVideoData( - duration: 1.0, + duration: approximateDuration, frames: framesAndUpdateTimestamp.0, framesUpdateTimestamp: framesAndUpdateTimestamp.1, trimRange: nil @@ -1196,14 +1211,15 @@ public class VideoMessageCameraScreen: ViewController { public init( context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)?, + peerId: EnginePeer.Id, inputPanelFrame: CGRect, - allowLiveUpload: Bool, completion: @escaping (EnqueueMessage?) -> Void ) { self.context = context self.updatedPresentationData = updatedPresentationData self.inputPanelFrame = inputPanelFrame - self.allowLiveUpload = allowLiveUpload + self.allowLiveUpload = peerId.namespace != Namespaces.Peer.SecretChat + self.viewOnceAvailable = peerId.namespace == Namespaces.Peer.CloudUser self.completion = completion self.recordingStatus = RecordingStatus(micLevel: self.micLevelValue.get(), duration: self.durationValue.get()) @@ -1265,6 +1281,11 @@ public class VideoMessageCameraScreen: ViewController { } } + if duration < 1.0 { + self.completion(nil) + return + } + let finalDuration: Double if let trimRange = self.node.previewState?.trimRange { finalDuration = trimRange.upperBound - trimRange.lowerBound @@ -1350,6 +1371,7 @@ public class VideoMessageCameraScreen: ViewController { return true } + fileprivate var recordingStartTime: Double? fileprivate var scheduledLock = false public func lockVideoRecording() { if case .none = self.cameraState.recording { diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index 4d0a16d6bc..74eb0bf136 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -15381,8 +15381,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G let controller = VideoMessageCameraScreen( context: self.context, updatedPresentationData: self.updatedPresentationData, + peerId: peerId, inputPanelFrame: currentInputPanelFrame, - allowLiveUpload: peerId.namespace != Namespaces.Peer.SecretChat, completion: { [weak self] message in guard let self, let videoController = self.videoRecorderValue else { return @@ -15574,7 +15574,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G self.recorderDataDisposable.set((videoRecorderValue.takenRecordedData() |> deliverOnMainQueue).startStrict(next: { [weak self] data in if let strongSelf = self, let data = data { - if data.duration < 0.5 { + if data.duration < 1.0 { strongSelf.recorderFeedback?.error() strongSelf.recorderFeedback = nil strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: true, { diff --git a/submodules/TelegramUI/Sources/ChatRecordingPreviewInputPanelNode.swift b/submodules/TelegramUI/Sources/ChatRecordingPreviewInputPanelNode.swift index e81e3ba61c..7b490f9bdb 100644 --- a/submodules/TelegramUI/Sources/ChatRecordingPreviewInputPanelNode.swift +++ b/submodules/TelegramUI/Sources/ChatRecordingPreviewInputPanelNode.swift @@ -492,10 +492,11 @@ final class ChatRecordingPreviewInputPanelNode: ChatInputPanelNode { } @objc func sendPressed() { - self.viewOnce = false self.tooltipController?.dismiss() self.interfaceInteraction?.sendRecordedMedia(false, self.viewOnce) + + self.viewOnce = false } private weak var tooltipController: TooltipScreen?