From cdcda05f954e445201bd3579f81057122fe9d442 Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Mon, 22 Jan 2024 11:49:54 +0400 Subject: [PATCH 1/5] Cherry-pick various fixes --- submodules/Camera/Sources/Camera.swift | 43 ++++++++++++------- submodules/Camera/Sources/CameraOutput.swift | 6 ++- .../Sources/CameraRoundVideoFilter.swift | 8 ++-- submodules/Camera/Sources/VideoRecorder.swift | 40 +++++++++++------ .../Sources/VideoMessageCameraScreen.swift | 27 +++++++++--- 5 files changed, 85 insertions(+), 39 deletions(-) diff --git a/submodules/Camera/Sources/Camera.swift b/submodules/Camera/Sources/Camera.swift index c966c7d1f5..ca7d149b09 100644 --- a/submodules/Camera/Sources/Camera.swift +++ b/submodules/Camera/Sources/Camera.swift @@ -45,16 +45,18 @@ final class CameraDeviceContext { private let exclusive: Bool private let additional: Bool + private let isRoundVideo: Bool let device = CameraDevice() let input = CameraInput() let output: CameraOutput - init(session: CameraSession, exclusive: Bool, additional: Bool, ciContext: CIContext, use32BGRA: Bool = false) { + init(session: CameraSession, exclusive: Bool, additional: Bool, ciContext: CIContext, colorSpace: CGColorSpace, isRoundVideo: Bool = false) { self.session = session self.exclusive = exclusive self.additional = additional - self.output = CameraOutput(exclusive: exclusive, ciContext: ciContext, use32BGRA: use32BGRA) + self.isRoundVideo = isRoundVideo + self.output = CameraOutput(exclusive: exclusive, ciContext: ciContext, colorSpace: colorSpace, use32BGRA: isRoundVideo) } func configure(position: Camera.Position, previewView: CameraSimplePreviewView?, audio: Bool, photo: Bool, metadata: Bool, preferWide: Bool = false, preferLowerFramerate: Bool = false, switchAudio: Bool = true) { @@ -63,7 +65,7 @@ final class CameraDeviceContext { } self.previewView = previewView - + self.device.configure(for: session, position: position, dual: !self.exclusive || self.additional, switchAudio: switchAudio) self.device.configureDeviceFormat(maxDimensions: self.maxDimensions(additional: self.additional, preferWide: preferWide), maxFramerate: self.preferredMaxFrameRate(useLower: preferLowerFramerate)) self.input.configure(for: session, device: self.device, audio: audio && switchAudio) @@ -83,11 +85,19 @@ final class CameraDeviceContext { } private func maxDimensions(additional: Bool, preferWide: Bool) -> CMVideoDimensions { - if additional || preferWide { - return CMVideoDimensions(width: 1920, height: 1440) - } else { - return CMVideoDimensions(width: 1920, height: 1080) - } +// if self.isRoundVideo { +// if additional { +// return CMVideoDimensions(width: 640, height: 480) +// } else { +// return CMVideoDimensions(width: 1280, height: 720) +// } +// } else { + if additional || preferWide { + return CMVideoDimensions(width: 1920, height: 1440) + } else { + return CMVideoDimensions(width: 1920, height: 1080) + } +// } } private func preferredMaxFrameRate(useLower: Bool) -> Double { @@ -108,14 +118,13 @@ final class CameraDeviceContext { private final class CameraContext { private let queue: Queue - private let session: CameraSession + private let ciContext: CIContext + private let colorSpace: CGColorSpace private var mainDeviceContext: CameraDeviceContext? private var additionalDeviceContext: CameraDeviceContext? - private let ciContext = CIContext() - private let initialConfiguration: Camera.Configuration private var invalidated = false @@ -139,7 +148,7 @@ private final class CameraContext { transform = CGAffineTransformTranslate(transform, 0.0, -size.height) ciImage = ciImage.transformed(by: transform) } - ciImage = ciImage.clampedToExtent().applyingGaussianBlur(sigma: 40.0).cropped(to: CGRect(origin: .zero, size: size)) + ciImage = ciImage.clampedToExtent().applyingGaussianBlur(sigma: 100.0).cropped(to: CGRect(origin: .zero, size: size)) if let cgImage = self.ciContext.createCGImage(ciImage, from: ciImage.extent) { let uiImage = UIImage(cgImage: cgImage, scale: 1.0, orientation: .right) if front { @@ -156,6 +165,10 @@ private final class CameraContext { self.queue = queue self.session = session + + self.colorSpace = CGColorSpaceCreateDeviceRGB() + self.ciContext = CIContext(options: [.workingColorSpace : self.colorSpace]) + self.initialConfiguration = configuration self.simplePreviewView = previewView self.secondaryPreviewView = secondaryPreviewView @@ -313,10 +326,10 @@ private final class CameraContext { if enabled { self.configure { self.mainDeviceContext?.invalidate() - self.mainDeviceContext = CameraDeviceContext(session: self.session, exclusive: false, additional: false, ciContext: self.ciContext, use32BGRA: self.initialConfiguration.isRoundVideo) + self.mainDeviceContext = CameraDeviceContext(session: self.session, exclusive: false, additional: false, ciContext: self.ciContext, colorSpace: self.colorSpace, isRoundVideo: self.initialConfiguration.isRoundVideo) self.mainDeviceContext?.configure(position: .back, previewView: self.simplePreviewView, audio: self.initialConfiguration.audio, photo: self.initialConfiguration.photo, metadata: self.initialConfiguration.metadata) - self.additionalDeviceContext = CameraDeviceContext(session: self.session, exclusive: false, additional: true, ciContext: self.ciContext, use32BGRA: self.initialConfiguration.isRoundVideo) + self.additionalDeviceContext = CameraDeviceContext(session: self.session, exclusive: false, additional: true, ciContext: self.ciContext, colorSpace: self.colorSpace, isRoundVideo: self.initialConfiguration.isRoundVideo) self.additionalDeviceContext?.configure(position: .front, previewView: self.secondaryPreviewView, audio: false, photo: true, metadata: false) } self.mainDeviceContext?.output.processSampleBuffer = { [weak self] sampleBuffer, pixelBuffer, connection in @@ -356,7 +369,7 @@ private final class CameraContext { let preferWide = self.initialConfiguration.preferWide || self.initialConfiguration.isRoundVideo let preferLowerFramerate = self.initialConfiguration.preferLowerFramerate || self.initialConfiguration.isRoundVideo - self.mainDeviceContext = CameraDeviceContext(session: self.session, exclusive: true, additional: false, ciContext: self.ciContext, use32BGRA: self.initialConfiguration.isRoundVideo) + self.mainDeviceContext = CameraDeviceContext(session: self.session, exclusive: true, additional: false, ciContext: self.ciContext, colorSpace: self.colorSpace, isRoundVideo: self.initialConfiguration.isRoundVideo) self.mainDeviceContext?.configure(position: self.positionValue, previewView: self.simplePreviewView, audio: self.initialConfiguration.audio, photo: self.initialConfiguration.photo, metadata: self.initialConfiguration.metadata, preferWide: preferWide, preferLowerFramerate: preferLowerFramerate) } self.mainDeviceContext?.output.processSampleBuffer = { [weak self] sampleBuffer, pixelBuffer, connection in diff --git a/submodules/Camera/Sources/CameraOutput.swift b/submodules/Camera/Sources/CameraOutput.swift index f07f8cbee7..a53045d829 100644 --- a/submodules/Camera/Sources/CameraOutput.swift +++ b/submodules/Camera/Sources/CameraOutput.swift @@ -80,6 +80,7 @@ public struct CameraCode: Equatable { final class CameraOutput: NSObject { let exclusive: Bool let ciContext: CIContext + let colorSpace: CGColorSpace let isVideoMessage: Bool let photoOutput = AVCapturePhotoOutput() @@ -104,9 +105,10 @@ final class CameraOutput: NSObject { var processAudioBuffer: ((CMSampleBuffer) -> Void)? var processCodes: (([CameraCode]) -> Void)? - init(exclusive: Bool, ciContext: CIContext, use32BGRA: Bool = false) { + init(exclusive: Bool, ciContext: CIContext, colorSpace: CGColorSpace, use32BGRA: Bool = false) { self.exclusive = exclusive self.ciContext = ciContext + self.colorSpace = colorSpace self.isVideoMessage = use32BGRA super.init() @@ -530,7 +532,7 @@ final class CameraOutput: NSObject { if let current = self.roundVideoFilter { filter = current } else { - filter = CameraRoundVideoFilter(ciContext: self.ciContext) + filter = CameraRoundVideoFilter(ciContext: self.ciContext, colorSpace: self.colorSpace) self.roundVideoFilter = filter } if !filter.isPrepared { diff --git a/submodules/Camera/Sources/CameraRoundVideoFilter.swift b/submodules/Camera/Sources/CameraRoundVideoFilter.swift index 6e0a4252fd..5a58548393 100644 --- a/submodules/Camera/Sources/CameraRoundVideoFilter.swift +++ b/submodules/Camera/Sources/CameraRoundVideoFilter.swift @@ -89,8 +89,9 @@ private func preallocateBuffers(pool: CVPixelBufferPool, allocationThreshold: In pixelBuffers.removeAll() } -class CameraRoundVideoFilter { +final class CameraRoundVideoFilter { private let ciContext: CIContext + private let colorSpace: CGColorSpace private var resizeFilter: CIFilter? private var overlayFilter: CIFilter? @@ -104,8 +105,9 @@ class CameraRoundVideoFilter { private(set) var isPrepared = false - init(ciContext: CIContext) { + init(ciContext: CIContext, colorSpace: CGColorSpace) { self.ciContext = ciContext + self.colorSpace = colorSpace } func prepare(with formatDescription: CMFormatDescription, outputRetainedBufferCountHint: Int) { @@ -158,7 +160,7 @@ class CameraRoundVideoFilter { return nil } - var sourceImage = CIImage(cvImageBuffer: pixelBuffer) + var sourceImage = CIImage(cvImageBuffer: pixelBuffer, options: [.colorSpace: self.colorSpace]) sourceImage = sourceImage.oriented(additional ? .leftMirrored : .right) let scale = CGFloat(videoMessageDimensions.width) / min(sourceImage.extent.width, sourceImage.extent.height) diff --git a/submodules/Camera/Sources/VideoRecorder.swift b/submodules/Camera/Sources/VideoRecorder.swift index 1b1d11900f..1d5f9352cc 100644 --- a/submodules/Camera/Sources/VideoRecorder.swift +++ b/submodules/Camera/Sources/VideoRecorder.swift @@ -112,7 +112,7 @@ private final class VideoRecorderImpl { } } } - + public func appendVideoSampleBuffer(_ sampleBuffer: CMSampleBuffer) { if let _ = self.hasError() { return @@ -129,6 +129,8 @@ private final class VideoRecorderImpl { } var failed = false if self.videoInput == nil { + Logger.shared.log("VideoRecorder", "Try adding video input") + let videoSettings = self.configuration.videoSettings if self.assetWriter.canApply(outputSettings: videoSettings, forMediaType: .video) { let videoInput = AVAssetWriterInput(mediaType: .video, outputSettings: videoSettings, sourceFormatHint: formatDescription) @@ -137,6 +139,8 @@ private final class VideoRecorderImpl { if self.assetWriter.canAdd(videoInput) { self.assetWriter.add(videoInput) self.videoInput = videoInput + + Logger.shared.log("VideoRecorder", "Successfully added video input") } else { failed = true } @@ -146,26 +150,32 @@ private final class VideoRecorderImpl { } if failed { - print("append video error") + Logger.shared.log("VideoRecorder", "Failed to append video buffer") return } - + if self.assetWriter.status == .unknown { if sampleBuffer.presentationTimestamp < self.recordingStartSampleTime { return } - if !self.assetWriter.startWriting() { - if let error = self.assetWriter.error { - self.transitionToFailedStatus(error: .avError(error)) - return + if self.videoInput != nil && (self.audioInput != nil || !self.configuration.hasAudio) { + if !self.assetWriter.startWriting() { + if let error = self.assetWriter.error { + self.transitionToFailedStatus(error: .avError(error)) + return + } } + + self.assetWriter.startSession(atSourceTime: presentationTime) + self.recordingStartSampleTime = presentationTime + self.lastVideoSampleTime = presentationTime } - - self.assetWriter.startSession(atSourceTime: presentationTime) - self.recordingStartSampleTime = presentationTime - self.lastVideoSampleTime = presentationTime } + if self.recordingStartSampleTime == .invalid || sampleBuffer.presentationTimestamp < self.recordingStartSampleTime { + return + } + if self.assetWriter.status == .writing { if self.recordingStopSampleTime != .invalid && sampleBuffer.presentationTimestamp > self.recordingStopSampleTime { self.hasAllVideoBuffers = true @@ -225,6 +235,8 @@ private final class VideoRecorderImpl { var failed = false if self.audioInput == nil { + Logger.shared.log("VideoRecorder", "Try adding audio input") + var audioSettings = self.configuration.audioSettings if let currentAudioStreamBasicDescription = CMAudioFormatDescriptionGetStreamBasicDescription(formatDescription) { audioSettings[AVSampleRateKey] = currentAudioStreamBasicDescription.pointee.mSampleRate @@ -247,6 +259,8 @@ private final class VideoRecorderImpl { if self.assetWriter.canAdd(audioInput) { self.assetWriter.add(audioInput) self.audioInput = audioInput + + Logger.shared.log("VideoRecorder", "Successfully added audio input") } else { failed = true } @@ -256,11 +270,11 @@ private final class VideoRecorderImpl { } if failed { - print("append audio error") + Logger.shared.log("VideoRecorder", "Failed to append audio buffer") return } - if self.assetWriter.status == .writing { + if self.recordingStartSampleTime != .invalid { //self.assetWriter.status == .writing { if sampleBuffer.presentationTimestamp < self.recordingStartSampleTime { return } diff --git a/submodules/TelegramUI/Components/VideoMessageCameraScreen/Sources/VideoMessageCameraScreen.swift b/submodules/TelegramUI/Components/VideoMessageCameraScreen/Sources/VideoMessageCameraScreen.swift index 0bcd43ba73..8342a6e194 100644 --- a/submodules/TelegramUI/Components/VideoMessageCameraScreen/Sources/VideoMessageCameraScreen.swift +++ b/submodules/TelegramUI/Components/VideoMessageCameraScreen/Sources/VideoMessageCameraScreen.swift @@ -584,7 +584,7 @@ public class VideoMessageCameraScreen: ViewController { if self.cameraState.isViewOnceEnabled != oldValue.isViewOnceEnabled { if self.cameraState.isViewOnceEnabled { let presentationData = self.context.sharedContext.currentPresentationData.with { $0 } - self.displayViewOnceTooltip(text: presentationData.strings.Chat_PlayVideoMessageOnceTooltip, hasIcon: false) + self.displayViewOnceTooltip(text: presentationData.strings.Chat_PlayVideoMessageOnceTooltip, hasIcon: true) let _ = ApplicationSpecificNotice.incrementVideoMessagesPlayOnceSuggestion(accountManager: self.context.sharedContext.accountManager, count: 3).startStandalone() } else { @@ -1439,9 +1439,10 @@ public class VideoMessageCameraScreen: ViewController { return } + var skipAction = false let currentTimestamp = CACurrentMediaTime() if let lastActionTimestamp = self.lastActionTimestamp, currentTimestamp - lastActionTimestamp < 0.5 { - return + skipAction = true } if case .none = self.cameraState.recording, self.node.results.isEmpty { @@ -1451,9 +1452,21 @@ public class VideoMessageCameraScreen: ViewController { if case .none = self.cameraState.recording { } else { - self.isSendingImmediately = true - self.waitingForNextResult = true - self.node.stopRecording.invoke(Void()) + if self.cameraState.duration > 0.5 { + if skipAction { + return + } + self.isSendingImmediately = true + self.waitingForNextResult = true + self.node.stopRecording.invoke(Void()) + } else { + self.completion(nil, nil, nil) + return + } + } + + guard !skipAction else { + return } self.didSend = true @@ -1630,7 +1643,9 @@ public class VideoMessageCameraScreen: ViewController { try? AVAudioSession.sharedInstance().setAllowHapticsAndSystemSoundsDuringRecording(true) } if let self { - self.node.setupCamera() + Queue.mainQueue().async { + self.node.setupCamera() + } } }, deactivate: { _ in return .single(Void()) From 1fb7544b52e978f31ec8cbd5e7655f78bd8b1358 Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Mon, 22 Jan 2024 15:09:25 +0400 Subject: [PATCH 2/5] Cherry-pick various fixes --- submodules/Camera/Sources/Camera.swift | 17 +++++------------ submodules/Camera/Sources/CameraOutput.swift | 2 +- .../Sources/CameraRoundVideoFilter.swift | 19 +++++++++++++------ 3 files changed, 19 insertions(+), 19 deletions(-) diff --git a/submodules/Camera/Sources/Camera.swift b/submodules/Camera/Sources/Camera.swift index ca7d149b09..9263e55924 100644 --- a/submodules/Camera/Sources/Camera.swift +++ b/submodules/Camera/Sources/Camera.swift @@ -85,26 +85,19 @@ final class CameraDeviceContext { } private func maxDimensions(additional: Bool, preferWide: Bool) -> CMVideoDimensions { -// if self.isRoundVideo { -// if additional { -// return CMVideoDimensions(width: 640, height: 480) -// } else { -// return CMVideoDimensions(width: 1280, height: 720) -// } -// } else { + if self.isRoundVideo && !Camera.isDualCameraSupported { + return CMVideoDimensions(width: 640, height: 480) + } else { if additional || preferWide { return CMVideoDimensions(width: 1920, height: 1440) } else { return CMVideoDimensions(width: 1920, height: 1080) } -// } + } } private func preferredMaxFrameRate(useLower: Bool) -> Double { - if !self.exclusive { - return 30.0 - } - if useLower { + if !self.exclusive || self.isRoundVideo || useLower { return 30.0 } switch DeviceModel.current { diff --git a/submodules/Camera/Sources/CameraOutput.swift b/submodules/Camera/Sources/CameraOutput.swift index a53045d829..cc4f1de5d7 100644 --- a/submodules/Camera/Sources/CameraOutput.swift +++ b/submodules/Camera/Sources/CameraOutput.swift @@ -532,7 +532,7 @@ final class CameraOutput: NSObject { if let current = self.roundVideoFilter { filter = current } else { - filter = CameraRoundVideoFilter(ciContext: self.ciContext, colorSpace: self.colorSpace) + filter = CameraRoundVideoFilter(ciContext: self.ciContext, colorSpace: self.colorSpace, simple: self.exclusive) self.roundVideoFilter = filter } if !filter.isPrepared { diff --git a/submodules/Camera/Sources/CameraRoundVideoFilter.swift b/submodules/Camera/Sources/CameraRoundVideoFilter.swift index 5a58548393..11584991d8 100644 --- a/submodules/Camera/Sources/CameraRoundVideoFilter.swift +++ b/submodules/Camera/Sources/CameraRoundVideoFilter.swift @@ -92,6 +92,7 @@ private func preallocateBuffers(pool: CVPixelBufferPool, allocationThreshold: In final class CameraRoundVideoFilter { private let ciContext: CIContext private let colorSpace: CGColorSpace + private let simple: Bool private var resizeFilter: CIFilter? private var overlayFilter: CIFilter? @@ -105,9 +106,10 @@ final class CameraRoundVideoFilter { private(set) var isPrepared = false - init(ciContext: CIContext, colorSpace: CGColorSpace) { + init(ciContext: CIContext, colorSpace: CGColorSpace, simple: Bool) { self.ciContext = ciContext self.colorSpace = colorSpace + self.simple = simple } func prepare(with formatDescription: CMFormatDescription, outputRetainedBufferCountHint: Int) { @@ -164,14 +166,19 @@ final class CameraRoundVideoFilter { sourceImage = sourceImage.oriented(additional ? .leftMirrored : .right) let scale = CGFloat(videoMessageDimensions.width) / min(sourceImage.extent.width, sourceImage.extent.height) - resizeFilter.setValue(sourceImage, forKey: kCIInputImageKey) - resizeFilter.setValue(scale, forKey: kCIInputScaleKey) - - if let resizedImage = resizeFilter.outputImage { - sourceImage = resizedImage + if !self.simple { + resizeFilter.setValue(sourceImage, forKey: kCIInputImageKey) + resizeFilter.setValue(scale, forKey: kCIInputScaleKey) + + if let resizedImage = resizeFilter.outputImage { + sourceImage = resizedImage + } else { + sourceImage = sourceImage.transformed(by: CGAffineTransformMakeScale(scale, scale), highQualityDownsample: true) + } } else { sourceImage = sourceImage.transformed(by: CGAffineTransformMakeScale(scale, scale), highQualityDownsample: true) } + sourceImage = sourceImage.transformed(by: CGAffineTransformMakeTranslation(0.0, -(sourceImage.extent.height - sourceImage.extent.width) / 2.0)) sourceImage = sourceImage.cropped(to: CGRect(x: 0.0, y: 0.0, width: sourceImage.extent.width, height: sourceImage.extent.width)) From 7d8de2a865a7aa19d86f1e7345c26bac30323132 Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Mon, 22 Jan 2024 17:12:48 +0400 Subject: [PATCH 3/5] Cherry-pick various fixes --- submodules/Camera/Sources/Camera.swift | 16 +- submodules/Camera/Sources/CameraOutput.swift | 5 +- .../Sources/CameraRoundVideoFilter.swift | 30 ++- .../Components/VideoMessageCameraScreen/BUILD | 1 + .../Sources/VideoMessageCameraScreen.swift | 182 +++++++++++------- .../ChatRecordingPreviewInputPanelNode.swift | 11 +- 6 files changed, 160 insertions(+), 85 deletions(-) diff --git a/submodules/Camera/Sources/Camera.swift b/submodules/Camera/Sources/Camera.swift index 9263e55924..609f9c2608 100644 --- a/submodules/Camera/Sources/Camera.swift +++ b/submodules/Camera/Sources/Camera.swift @@ -131,7 +131,10 @@ private final class CameraContext { var secondaryPreviewView: CameraSimplePreviewView? private var lastSnapshotTimestamp: Double = CACurrentMediaTime() + private var savedSnapshot = false private var lastAdditionalSnapshotTimestamp: Double = CACurrentMediaTime() + private var savedAdditionalSnapshot = false + private func savePreviewSnapshot(pixelBuffer: CVPixelBuffer, front: Bool) { Queue.concurrentDefaultQueue().async { var ciImage = CIImage(cvImageBuffer: pixelBuffer) @@ -141,7 +144,7 @@ private final class CameraContext { transform = CGAffineTransformTranslate(transform, 0.0, -size.height) ciImage = ciImage.transformed(by: transform) } - ciImage = ciImage.clampedToExtent().applyingGaussianBlur(sigma: 100.0).cropped(to: CGRect(origin: .zero, size: size)) + ciImage = ciImage.clampedToExtent().applyingGaussianBlur(sigma: Camera.isDualCameraSupported ? 100.0 : 40.0).cropped(to: CGRect(origin: .zero, size: size)) if let cgImage = self.ciContext.createCGImage(ciImage, from: ciImage.extent) { let uiImage = UIImage(cgImage: cgImage, scale: 1.0, orientation: .right) if front { @@ -330,13 +333,14 @@ private final class CameraContext { return } let timestamp = CACurrentMediaTime() - if timestamp > self.lastSnapshotTimestamp + 2.5, !mainDeviceContext.output.isRecording { + if timestamp > self.lastSnapshotTimestamp + 2.5, !mainDeviceContext.output.isRecording || !self.savedSnapshot { var front = false if #available(iOS 13.0, *) { front = connection.inputPorts.first?.sourceDevicePosition == .front } self.savePreviewSnapshot(pixelBuffer: pixelBuffer, front: front) self.lastSnapshotTimestamp = timestamp + self.savedSnapshot = true } } self.additionalDeviceContext?.output.processSampleBuffer = { [weak self] sampleBuffer, pixelBuffer, connection in @@ -344,13 +348,14 @@ private final class CameraContext { return } let timestamp = CACurrentMediaTime() - if timestamp > self.lastAdditionalSnapshotTimestamp + 2.5, !additionalDeviceContext.output.isRecording { + if timestamp > self.lastAdditionalSnapshotTimestamp + 2.5, !additionalDeviceContext.output.isRecording || !self.savedAdditionalSnapshot { var front = false if #available(iOS 13.0, *) { front = connection.inputPorts.first?.sourceDevicePosition == .front } self.savePreviewSnapshot(pixelBuffer: pixelBuffer, front: front) self.lastAdditionalSnapshotTimestamp = timestamp + self.savedAdditionalSnapshot = true } } } else { @@ -370,13 +375,14 @@ private final class CameraContext { return } let timestamp = CACurrentMediaTime() - if timestamp > self.lastSnapshotTimestamp + 2.5, !mainDeviceContext.output.isRecording { + if timestamp > self.lastSnapshotTimestamp + 2.5, !mainDeviceContext.output.isRecording || !self.savedSnapshot { var front = false if #available(iOS 13.0, *) { front = connection.inputPorts.first?.sourceDevicePosition == .front } self.savePreviewSnapshot(pixelBuffer: pixelBuffer, front: front) self.lastSnapshotTimestamp = timestamp + self.savedSnapshot = true } } if self.initialConfiguration.reportAudioLevel { @@ -557,7 +563,7 @@ private final class CameraContext { let orientation = self.simplePreviewView?.videoPreviewLayer.connection?.videoOrientation ?? .portrait if self.initialConfiguration.isRoundVideo { - return mainDeviceContext.output.startRecording(mode: .roundVideo, orientation: .portrait, additionalOutput: self.additionalDeviceContext?.output) + return mainDeviceContext.output.startRecording(mode: .roundVideo, orientation: DeviceModel.current.isIpad ? orientation : .portrait, additionalOutput: self.additionalDeviceContext?.output) } else { if let additionalDeviceContext = self.additionalDeviceContext { return combineLatest( diff --git a/submodules/Camera/Sources/CameraOutput.swift b/submodules/Camera/Sources/CameraOutput.swift index cc4f1de5d7..ab889bf248 100644 --- a/submodules/Camera/Sources/CameraOutput.swift +++ b/submodules/Camera/Sources/CameraOutput.swift @@ -100,6 +100,8 @@ final class CameraOutput: NSObject { private var photoCaptureRequests: [Int64: PhotoCaptureContext] = [:] private var videoRecorder: VideoRecorder? + + private var captureOrientation: AVCaptureVideoOrientation = .portrait var processSampleBuffer: ((CMSampleBuffer, CVImageBuffer, AVCaptureConnection) -> Void)? var processAudioBuffer: ((CMSampleBuffer) -> Void)? @@ -305,6 +307,7 @@ final class CameraOutput: NSObject { self.currentMode = mode self.lastSampleTimestamp = nil + self.captureOrientation = orientation var orientation = orientation let dimensions: CGSize @@ -538,7 +541,7 @@ final class CameraOutput: NSObject { if !filter.isPrepared { filter.prepare(with: newFormatDescription, outputRetainedBufferCountHint: 3) } - guard let newPixelBuffer = filter.render(pixelBuffer: videoPixelBuffer, additional: additional, transitionFactor: transitionFactor) else { + guard let newPixelBuffer = filter.render(pixelBuffer: videoPixelBuffer, additional: additional, captureOrientation: self.captureOrientation, transitionFactor: transitionFactor) else { self.semaphore.signal() return nil } diff --git a/submodules/Camera/Sources/CameraRoundVideoFilter.swift b/submodules/Camera/Sources/CameraRoundVideoFilter.swift index 11584991d8..3ce2d80c05 100644 --- a/submodules/Camera/Sources/CameraRoundVideoFilter.swift +++ b/submodules/Camera/Sources/CameraRoundVideoFilter.swift @@ -1,5 +1,6 @@ import Foundation import UIKit +import AVFoundation import CoreImage import CoreMedia import CoreVideo @@ -157,13 +158,29 @@ final class CameraRoundVideoFilter { private var lastMainSourceImage: CIImage? private var lastAdditionalSourceImage: CIImage? - func render(pixelBuffer: CVPixelBuffer, additional: Bool, transitionFactor: CGFloat) -> CVPixelBuffer? { + func render(pixelBuffer: CVPixelBuffer, additional: Bool, captureOrientation: AVCaptureVideoOrientation, transitionFactor: CGFloat) -> CVPixelBuffer? { guard let resizeFilter = self.resizeFilter, let overlayFilter = self.overlayFilter, let compositeFilter = self.compositeFilter, let borderFilter = self.borderFilter, self.isPrepared else { return nil } var sourceImage = CIImage(cvImageBuffer: pixelBuffer, options: [.colorSpace: self.colorSpace]) - sourceImage = sourceImage.oriented(additional ? .leftMirrored : .right) + var sourceOrientation: CGImagePropertyOrientation + var sourceIsLandscape = false + switch captureOrientation { + case .portrait: + sourceOrientation = additional ? .leftMirrored : .right + case .landscapeLeft: + sourceOrientation = additional ? .upMirrored : .down + sourceIsLandscape = true + case .landscapeRight: + sourceOrientation = additional ? .downMirrored : .up + sourceIsLandscape = true + case .portraitUpsideDown: + sourceOrientation = additional ? .rightMirrored : .left + @unknown default: + sourceOrientation = additional ? .leftMirrored : .right + } + sourceImage = sourceImage.oriented(sourceOrientation) let scale = CGFloat(videoMessageDimensions.width) / min(sourceImage.extent.width, sourceImage.extent.height) if !self.simple { @@ -179,8 +196,13 @@ final class CameraRoundVideoFilter { sourceImage = sourceImage.transformed(by: CGAffineTransformMakeScale(scale, scale), highQualityDownsample: true) } - sourceImage = sourceImage.transformed(by: CGAffineTransformMakeTranslation(0.0, -(sourceImage.extent.height - sourceImage.extent.width) / 2.0)) - sourceImage = sourceImage.cropped(to: CGRect(x: 0.0, y: 0.0, width: sourceImage.extent.width, height: sourceImage.extent.width)) + if sourceIsLandscape { + sourceImage = sourceImage.transformed(by: CGAffineTransformMakeTranslation(-(sourceImage.extent.width - sourceImage.extent.height) / 2.0, 0.0)) + sourceImage = sourceImage.cropped(to: CGRect(x: 0.0, y: 0.0, width: sourceImage.extent.height, height: sourceImage.extent.height)) + } else { + sourceImage = sourceImage.transformed(by: CGAffineTransformMakeTranslation(0.0, -(sourceImage.extent.height - sourceImage.extent.width) / 2.0)) + sourceImage = sourceImage.cropped(to: CGRect(x: 0.0, y: 0.0, width: sourceImage.extent.width, height: sourceImage.extent.width)) + } if additional { self.lastAdditionalSourceImage = sourceImage diff --git a/submodules/TelegramUI/Components/VideoMessageCameraScreen/BUILD b/submodules/TelegramUI/Components/VideoMessageCameraScreen/BUILD index c872752a69..9044bfadc1 100644 --- a/submodules/TelegramUI/Components/VideoMessageCameraScreen/BUILD +++ b/submodules/TelegramUI/Components/VideoMessageCameraScreen/BUILD @@ -38,6 +38,7 @@ swift_library( "//submodules/DeviceAccess", "//submodules/TelegramUI/Components/MediaEditor", "//submodules/LegacyMediaPickerUI", + "//submodules/TelegramAudio", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/VideoMessageCameraScreen/Sources/VideoMessageCameraScreen.swift b/submodules/TelegramUI/Components/VideoMessageCameraScreen/Sources/VideoMessageCameraScreen.swift index 8342a6e194..ad68560a44 100644 --- a/submodules/TelegramUI/Components/VideoMessageCameraScreen/Sources/VideoMessageCameraScreen.swift +++ b/submodules/TelegramUI/Components/VideoMessageCameraScreen/Sources/VideoMessageCameraScreen.swift @@ -26,6 +26,7 @@ import MediaResources import LocalMediaResources import ImageCompression import LegacyMediaPickerUI +import TelegramAudio struct CameraState: Equatable { enum Recording: Equatable { @@ -694,7 +695,7 @@ public class VideoMessageCameraScreen: ViewController { func withReadyCamera(isFirstTime: Bool = false, _ f: @escaping () -> Void) { let previewReady: Signal if #available(iOS 13.0, *) { - previewReady = self.cameraState.isDualCameraEnabled ? self.additionalPreviewView.isPreviewing : self.mainPreviewView.isPreviewing + previewReady = self.cameraState.isDualCameraEnabled ? self.additionalPreviewView.isPreviewing : self.mainPreviewView.isPreviewing |> delay(0.2, queue: Queue.mainQueue()) } else { previewReady = .single(true) |> delay(0.35, queue: Queue.mainQueue()) } @@ -1116,9 +1117,22 @@ public class VideoMessageCameraScreen: ViewController { let previewSide = min(369.0, layout.size.width - 24.0) let previewFrame: CGRect if layout.metrics.isTablet { - previewFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((layout.size.width - previewSide) / 2.0), y: max(layout.statusBarHeight ?? 0.0 + 24.0, availableHeight * 0.2 - previewSide / 2.0)), size: CGSize(width: previewSide, height: previewSide)) + let statusBarOrientation: UIInterfaceOrientation + if #available(iOS 13.0, *) { + statusBarOrientation = UIApplication.shared.windows.first?.windowScene?.interfaceOrientation ?? .portrait + } else { + statusBarOrientation = UIApplication.shared.statusBarOrientation + } + + if statusBarOrientation == .landscapeLeft { + previewFrame = CGRect(origin: CGPoint(x: layout.size.width - 44.0 - previewSide, y: floorToScreenPixels((layout.size.height - previewSide) / 2.0)), size: CGSize(width: previewSide, height: previewSide)) + } else if statusBarOrientation == .landscapeRight { + previewFrame = CGRect(origin: CGPoint(x: 44.0, y: floorToScreenPixels((layout.size.height - previewSide) / 2.0)), size: CGSize(width: previewSide, height: previewSide)) + } else { + previewFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((layout.size.width - previewSide) / 2.0), y: max(layout.statusBarHeight ?? 0.0 + 24.0, availableHeight * 0.2 - previewSide / 2.0)), size: CGSize(width: previewSide, height: previewSide)) + } } else { - previewFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((layout.size.width - previewSide) / 2.0), y: max(layout.statusBarHeight ?? 0.0 + 16.0, availableHeight * 0.4 - previewSide / 2.0)), size: CGSize(width: previewSide, height: previewSide)) + previewFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((layout.size.width - previewSide) / 2.0), y: max(layout.statusBarHeight ?? 0.0 + 24.0, availableHeight * 0.4 - previewSide / 2.0)), size: CGSize(width: previewSide, height: previewSide)) } if !self.animatingIn { transition.setFrame(view: self.previewContainerView, frame: previewFrame) @@ -1321,7 +1335,7 @@ public class VideoMessageCameraScreen: ViewController { public func takenRecordedData() -> Signal { let previewState = self.node.previewStatePromise.get() - let count = 12 + let count = 13 let initialPlaceholder: Signal if let firstResult = self.node.results.first { @@ -1508,78 +1522,97 @@ public class VideoMessageCameraScreen: ViewController { let dimensions = PixelDimensions(width: 400, height: 400) - var thumbnailImage = video.thumbnail + let thumbnailImage: Signal if startTime > 0.0 { - let composition = composition(with: results) - let imageGenerator = AVAssetImageGenerator(asset: composition) - imageGenerator.maximumSize = dimensions.cgSize - imageGenerator.appliesPreferredTrackTransform = true - - if let cgImage = try? imageGenerator.copyCGImage(at: CMTime(seconds: startTime, preferredTimescale: composition.duration.timescale), actualTime: nil) { - thumbnailImage = UIImage(cgImage: cgImage) + thumbnailImage = Signal { subscriber in + let composition = composition(with: results) + let imageGenerator = AVAssetImageGenerator(asset: composition) + imageGenerator.maximumSize = dimensions.cgSize + imageGenerator.appliesPreferredTrackTransform = true + + imageGenerator.generateCGImagesAsynchronously(forTimes: [NSValue(time: CMTime(seconds: startTime, preferredTimescale: composition.duration.timescale))], completionHandler: { _, image, _, _, _ in + if let image { + subscriber.putNext(UIImage(cgImage: image)) + } else { + subscriber.putNext(video.thumbnail) + } + subscriber.putCompletion() + }) + + return ActionDisposable { + imageGenerator.cancelAllCGImageGeneration() + } } - } - - 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, entities: [], toolValues: [:], audioTrack: nil, audioTrackTrimRange: nil, audioTrackOffset: nil, audioTrackVolume: nil, audioTrackSamples: nil, qualityPreset: .videoMessage) - - var resourceAdjustments: VideoMediaResourceAdjustments? = nil - if let valuesData = try? JSONEncoder().encode(values) { - let data = MemoryBuffer(data: valuesData) - let digest = MemoryBuffer(data: data.md5Digest()) - resourceAdjustments = VideoMediaResourceAdjustments(data: data, digest: digest, isStory: false) - } - - let resource: TelegramMediaResource - let liveUploadData: LegacyLiveUploadInterfaceResult? - if let current = self.node.currentLiveUploadData { - liveUploadData = current } else { - liveUploadData = self.node.liveUploadInterface?.fileUpdated(true) as? LegacyLiveUploadInterfaceResult - } - if !hasAdjustments, let liveUploadData, let data = try? Data(contentsOf: URL(fileURLWithPath: video.videoPath)) { - resource = LocalFileMediaResource(fileId: liveUploadData.id) - self.context.account.postbox.mediaBox.storeResourceData(resource.id, data: data, synchronous: true) - } else { - resource = LocalFileVideoMediaResource(randomId: Int64.random(in: Int64.min ... Int64.max), paths: videoPaths, adjustments: resourceAdjustments) + thumbnailImage = .single(video.thumbnail) } - var previewRepresentations: [TelegramMediaImageRepresentation] = [] - - let thumbnailResource = LocalFileMediaResource(fileId: Int64.random(in: Int64.min ... Int64.max)) - let thumbnailSize = video.dimensions.cgSize.aspectFitted(CGSize(width: 320.0, height: 320.0)) - if let thumbnailData = scaleImageToPixelSize(image: thumbnailImage, size: thumbnailSize)?.jpegData(compressionQuality: 0.4) { - self.context.account.postbox.mediaBox.storeResourceData(thumbnailResource.id, data: thumbnailData) - previewRepresentations.append(TelegramMediaImageRepresentation(dimensions: PixelDimensions(thumbnailSize), resource: thumbnailResource, progressiveSizes: [], immediateThumbnailData: nil, hasVideo: false, isPersonal: false)) - } - - let tempFile = TempBox.shared.tempFile(fileName: "file") - defer { - TempBox.shared.dispose(tempFile) - } - if let data = compressImageToJPEG(thumbnailImage, quality: 0.7, tempFilePath: tempFile.path) { - context.account.postbox.mediaBox.storeCachedResourceRepresentation(resource, representation: CachedVideoFirstFrameRepresentation(), data: data) - } + let _ = (thumbnailImage + |> deliverOnMainQueue).startStandalone(next: { [weak self] thumbnailImage in + guard let self else { + return + } + let values = MediaEditorValues(peerId: self.context.account.peerId, originalDimensions: dimensions, cropOffset: .zero, cropRect: CGRect(origin: .zero, size: dimensions.cgSize), cropScale: 1.0, cropRotation: 0.0, cropMirroring: false, cropOrientation: nil, gradientColors: nil, videoTrimRange: self.node.previewState?.trimRange, videoIsMuted: false, videoIsFullHd: false, videoIsMirrored: false, videoVolume: nil, additionalVideoPath: nil, additionalVideoIsDual: false, additionalVideoPosition: nil, additionalVideoScale: nil, additionalVideoRotation: nil, additionalVideoPositionChanges: [], additionalVideoTrimRange: nil, additionalVideoOffset: nil, additionalVideoVolume: nil, nightTheme: false, drawing: nil, entities: [], toolValues: [:], audioTrack: nil, audioTrackTrimRange: nil, audioTrackOffset: nil, audioTrackVolume: nil, audioTrackSamples: nil, qualityPreset: .videoMessage) + + var resourceAdjustments: VideoMediaResourceAdjustments? = nil + if let valuesData = try? JSONEncoder().encode(values) { + let data = MemoryBuffer(data: valuesData) + let digest = MemoryBuffer(data: data.md5Digest()) + resourceAdjustments = VideoMediaResourceAdjustments(data: data, digest: digest, isStory: false) + } + + let resource: TelegramMediaResource + let liveUploadData: LegacyLiveUploadInterfaceResult? + if let current = self.node.currentLiveUploadData { + liveUploadData = current + } else { + liveUploadData = self.node.liveUploadInterface?.fileUpdated(true) as? LegacyLiveUploadInterfaceResult + } + if !hasAdjustments, let liveUploadData, let data = try? Data(contentsOf: URL(fileURLWithPath: video.videoPath)) { + resource = LocalFileMediaResource(fileId: liveUploadData.id) + self.context.account.postbox.mediaBox.storeResourceData(resource.id, data: data, synchronous: true) + } else { + resource = LocalFileVideoMediaResource(randomId: Int64.random(in: Int64.min ... Int64.max), paths: videoPaths, adjustments: resourceAdjustments) + } + + var previewRepresentations: [TelegramMediaImageRepresentation] = [] + + let thumbnailResource = LocalFileMediaResource(fileId: Int64.random(in: Int64.min ... Int64.max)) + let thumbnailSize = video.dimensions.cgSize.aspectFitted(CGSize(width: 320.0, height: 320.0)) + if let thumbnailData = scaleImageToPixelSize(image: thumbnailImage, size: thumbnailSize)?.jpegData(compressionQuality: 0.4) { + self.context.account.postbox.mediaBox.storeResourceData(thumbnailResource.id, data: thumbnailData) + previewRepresentations.append(TelegramMediaImageRepresentation(dimensions: PixelDimensions(thumbnailSize), resource: thumbnailResource, progressiveSizes: [], immediateThumbnailData: nil, hasVideo: false, isPersonal: false)) + } + + let tempFile = TempBox.shared.tempFile(fileName: "file") + defer { + TempBox.shared.dispose(tempFile) + } + if let data = compressImageToJPEG(thumbnailImage, quality: 0.7, tempFilePath: tempFile.path) { + 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)]) - - - var attributes: [MessageAttribute] = [] - if self.cameraState.isViewOnceEnabled { - attributes.append(AutoremoveTimeoutMessageAttribute(timeout: viewOnceTimeout, countdownBeginTime: nil)) - } - - self.completion(.message( - text: "", - attributes: attributes, - inlineStickers: [:], - mediaReference: .standalone(media: media), - threadId: nil, - replyToMessageId: nil, - replyToStoryId: nil, - localGroupingKey: nil, - correlationId: nil, - bubbleUpEmojiOrStickersets: [] - ), silentPosting, scheduleTime) + 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)]) + + + var attributes: [MessageAttribute] = [] + if self.cameraState.isViewOnceEnabled { + attributes.append(AutoremoveTimeoutMessageAttribute(timeout: viewOnceTimeout, countdownBeginTime: nil)) + } + + self.completion(.message( + text: "", + attributes: attributes, + inlineStickers: [:], + mediaReference: .standalone(media: media), + threadId: nil, + replyToMessageId: nil, + replyToStoryId: nil, + localGroupingKey: nil, + correlationId: nil, + bubbleUpEmojiOrStickersets: [] + ), silentPosting, scheduleTime) + }) }) } @@ -1638,7 +1671,14 @@ public class VideoMessageCameraScreen: ViewController { } private func requestAudioSession() { - self.audioSessionDisposable = self.context.sharedContext.mediaManager.audioSession.push(audioSessionType: .recordWithOthers, activate: { [weak self] _ in + let audioSessionType: ManagedAudioSessionType + if self.context.sharedContext.currentMediaInputSettings.with({ $0 }).pauseMusicOnRecording { + audioSessionType = .record(speaker: false, withOthers: false) + } else { + audioSessionType = .recordWithOthers + } + + self.audioSessionDisposable = self.context.sharedContext.mediaManager.audioSession.push(audioSessionType: audioSessionType, activate: { [weak self] _ in if #available(iOS 13.0, *) { try? AVAudioSession.sharedInstance().setAllowHapticsAndSystemSoundsDuringRecording(true) } diff --git a/submodules/TelegramUI/Sources/ChatRecordingPreviewInputPanelNode.swift b/submodules/TelegramUI/Sources/ChatRecordingPreviewInputPanelNode.swift index 8a6c84f630..b29d44420a 100644 --- a/submodules/TelegramUI/Sources/ChatRecordingPreviewInputPanelNode.swift +++ b/submodules/TelegramUI/Sources/ChatRecordingPreviewInputPanelNode.swift @@ -337,19 +337,22 @@ final class ChatRecordingPreviewInputPanelNode: ChatInputPanelNode { ), environment: {}, forceUpdate: false, - containerSize: CGSize(width: width - leftInset - rightInset - 45.0 * 2.0, height: 33.0) + containerSize: CGSize(width: min(424, width - leftInset - rightInset - 45.0 * 2.0), height: 33.0) ) - + if let view = self.scrubber.view { if view.superview == nil { self.view.addSubview(view) } - - view.frame = CGRect(origin: CGPoint(x: leftInset + 45.0, y: 7.0 - UIScreenPixel), size: scrubberSize) + view.bounds = CGRect(origin: .zero, size: scrubberSize) } } } } + + if let view = self.scrubber.view { + view.frame = CGRect(origin: CGPoint(x: max(leftInset + 45.0, floorToScreenPixels((width - view.bounds.width) / 2.0)), y: 7.0 - UIScreenPixel), size: view.bounds.size) + } let panelHeight = defaultHeight(metrics: metrics) From bc99a483e1c8a9f3ea1c55c93c7fa11ae0c4c0ee Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Mon, 22 Jan 2024 17:13:06 +0400 Subject: [PATCH 4/5] Bump version --- versions.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/versions.json b/versions.json index 0c3c3d9dce..2009ebc4aa 100644 --- a/versions.json +++ b/versions.json @@ -1,5 +1,5 @@ { - "app": "10.6.2", + "app": "10.6.3", "bazel": "6.4.0", "xcode": "15.1", "macos": "13.0" From 79f167d01da01584e70e39b7212176810a63dd1a Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Sat, 27 Jan 2024 18:43:10 +0400 Subject: [PATCH 5/5] Various video/audio recording improvements --- submodules/Camera/Sources/VideoRecorder.swift | 78 +++++++++---------- .../Sources/ManagedAudioSession.swift | 11 +++ .../Sources/ManagedAudioRecorder.swift | 19 ++--- versions.json | 2 +- 4 files changed, 56 insertions(+), 54 deletions(-) diff --git a/submodules/Camera/Sources/VideoRecorder.swift b/submodules/Camera/Sources/VideoRecorder.swift index 1d5f9352cc..bed6803b69 100644 --- a/submodules/Camera/Sources/VideoRecorder.swift +++ b/submodules/Camera/Sources/VideoRecorder.swift @@ -274,7 +274,7 @@ private final class VideoRecorderImpl { return } - if self.recordingStartSampleTime != .invalid { //self.assetWriter.status == .writing { + if self.recordingStartSampleTime != .invalid { if sampleBuffer.presentationTimestamp < self.recordingStartSampleTime { return } @@ -336,7 +336,7 @@ private final class VideoRecorderImpl { public func maybeFinish() { self.queue.async { - guard self.hasAllVideoBuffers && self.hasAllVideoBuffers else { + guard self.hasAllVideoBuffers && self.hasAllVideoBuffers && !self.stopped else { return } self.stopped = true @@ -345,49 +345,47 @@ private final class VideoRecorderImpl { } public func finish() { - self.queue.async { - let completion = self.completion - if self.recordingStopSampleTime == .invalid { - DispatchQueue.main.async { - completion(false, nil, nil) - } - return + let completion = self.completion + if self.recordingStopSampleTime == .invalid { + DispatchQueue.main.async { + completion(false, nil, nil) } - - if let _ = self.error.with({ $0 }) { - DispatchQueue.main.async { - completion(false, nil, nil) - } - return + return + } + + if let _ = self.error.with({ $0 }) { + DispatchQueue.main.async { + completion(false, nil, nil) } - - if !self.tryAppendingPendingAudioBuffers() { - DispatchQueue.main.async { - completion(false, nil, nil) - } - return + return + } + + if !self.tryAppendingPendingAudioBuffers() { + DispatchQueue.main.async { + completion(false, nil, nil) } - - if self.assetWriter.status == .writing { - self.assetWriter.finishWriting { - if let _ = self.assetWriter.error { - DispatchQueue.main.async { - completion(false, nil, nil) - } - } else { - DispatchQueue.main.async { - completion(true, self.transitionImage, self.positionChangeTimestamps) - } + return + } + + if self.assetWriter.status == .writing { + self.assetWriter.finishWriting { + if let _ = self.assetWriter.error { + DispatchQueue.main.async { + completion(false, nil, nil) + } + } else { + DispatchQueue.main.async { + completion(true, self.transitionImage, self.positionChangeTimestamps) } } - } else if let _ = self.assetWriter.error { - DispatchQueue.main.async { - completion(false, nil, nil) - } - } else { - DispatchQueue.main.async { - completion(false, nil, nil) - } + } + } else if let _ = self.assetWriter.error { + DispatchQueue.main.async { + completion(false, nil, nil) + } + } else { + DispatchQueue.main.async { + completion(false, nil, nil) } } } diff --git a/submodules/TelegramAudio/Sources/ManagedAudioSession.swift b/submodules/TelegramAudio/Sources/ManagedAudioSession.swift index 7b1431d82f..0c9564781f 100644 --- a/submodules/TelegramAudio/Sources/ManagedAudioSession.swift +++ b/submodules/TelegramAudio/Sources/ManagedAudioSession.swift @@ -960,6 +960,17 @@ public final class ManagedAudioSession: NSObject { try AVAudioSession.sharedInstance().overrideOutputAudioPort(.none) } + if case let .record(speaker, _) = type, !speaker, let input = AVAudioSession.sharedInstance().availableInputs?.first { + if let dataSources = input.dataSources { + for source in dataSources { + if source.dataSourceName.contains("Front") { + try? input.setPreferredDataSource(source) + break + } + } + } + } + if resetToBuiltin { var updatedType = type if case .record(false, let withOthers) = updatedType, self.isHeadsetPluggedInValue { diff --git a/submodules/TelegramUI/Sources/ManagedAudioRecorder.swift b/submodules/TelegramUI/Sources/ManagedAudioRecorder.swift index cb8f91145c..18cb29e417 100644 --- a/submodules/TelegramUI/Sources/ManagedAudioRecorder.swift +++ b/submodules/TelegramUI/Sources/ManagedAudioRecorder.swift @@ -150,7 +150,6 @@ final class ManagedAudioRecorderContext { private let beganWithTone: (Bool) -> Void private var paused = true - private var manuallyPaused = false private let queue: Queue private let mediaManager: MediaManager @@ -414,11 +413,9 @@ final class ManagedAudioRecorderContext { return Signal { subscriber in queue.async { if let strongSelf = self { - if !strongSelf.manuallyPaused { - strongSelf.hasAudioSession = false - strongSelf.stop() - strongSelf.recordingState.set(.stopped) - } + strongSelf.hasAudioSession = false + strongSelf.stop() + strongSelf.recordingState.set(.stopped) subscriber.putCompletion() } } @@ -453,17 +450,13 @@ final class ManagedAudioRecorderContext { func pause() { assert(self.queue.isCurrent()) - self.manuallyPaused = true + self.stop() } func resume() { assert(self.queue.isCurrent()) - if self.manuallyPaused { - self.manuallyPaused = false - } else if self.paused { - self.start() - } + self.start() } func stop() { @@ -507,7 +500,7 @@ final class ManagedAudioRecorderContext { free(buffer.mData) } - if !self.processSamples || self.manuallyPaused { + if !self.processSamples { return } diff --git a/versions.json b/versions.json index 2009ebc4aa..0e59f5f696 100644 --- a/versions.json +++ b/versions.json @@ -1,5 +1,5 @@ { - "app": "10.6.3", + "app": "10.6.4", "bazel": "6.4.0", "xcode": "15.1", "macos": "13.0"