diff --git a/submodules/Camera/Sources/Camera.swift b/submodules/Camera/Sources/Camera.swift index d879212c91..e2353ce62b 100644 --- a/submodules/Camera/Sources/Camera.swift +++ b/submodules/Camera/Sources/Camera.swift @@ -278,7 +278,6 @@ private final class CameraContext { self._positionPromise.set(targetPosition) self.modeChange = .position - let preferWide = self.initialConfiguration.preferWide || isRoundVideo let preferLowerFramerate = self.initialConfiguration.preferLowerFramerate || isRoundVideo @@ -567,6 +566,11 @@ private final class CameraContext { return .finished(mainImage, additionalImage, CACurrentMediaTime()) } } else { + if case .failed = main { + return .failed + } else if case .failed = additional { + return .failed + } return .began } } |> distinctUntilChanged @@ -585,6 +589,10 @@ private final class CameraContext { mainDeviceContext.device.setTorchMode(self._flashMode) } + let timestamp = CACurrentMediaTime() + 2.0 + self.lastSnapshotTimestamp = timestamp + self.lastAdditionalSnapshotTimestamp = timestamp + let orientation = self.simplePreviewView?.videoPreviewLayer.connection?.videoOrientation ?? .portrait if self.initialConfiguration.isRoundVideo { return mainDeviceContext.output.startRecording(mode: .roundVideo, orientation: DeviceModel.current.isIpad ? orientation : .portrait, additionalOutput: self.additionalDeviceContext?.output) @@ -790,6 +798,11 @@ public final class Camera { secondaryPreviewView.setSession(session.session, autoConnect: false) } + if #available(iOS 14.5, *), configuration.isRoundVideo { + AVCaptureDevice.centerStageControlMode = .app + AVCaptureDevice.isCenterStageEnabled = false + } + self.queue.async { let context = CameraContext(queue: self.queue, session: session, configuration: configuration, metrics: self.metrics, previewView: previewView, secondaryPreviewView: secondaryPreviewView) self.contextRef = Unmanaged.passRetained(context) @@ -803,6 +816,10 @@ public final class Camera { self.queue.async { contextRef?.release() } + + if #available(iOS 14.5, *) { + AVCaptureDevice.centerStageControlMode = .user + } } public func startCapture() { diff --git a/submodules/Camera/Sources/CameraDevice.swift b/submodules/Camera/Sources/CameraDevice.swift index e07d634d41..c1d3eefbc6 100644 --- a/submodules/Camera/Sources/CameraDevice.swift +++ b/submodules/Camera/Sources/CameraDevice.swift @@ -156,7 +156,7 @@ final class CameraDevice { if device.isLowLightBoostSupported { device.automaticallyEnablesLowLightBoostWhenAvailable = true } - + if device.isExposureModeSupported(.continuousAutoExposure) { device.exposureMode = .continuousAutoExposure } @@ -185,7 +185,7 @@ final class CameraDevice { private(set) var fps: Double = defaultFPS func setFps(_ fps: Double) { - guard let device = self.videoDevice, let targetFPS = device.actualFPS(Double(self.fps)) else { + guard let device = self.videoDevice, let targetFPS = device.actualFPS(Double(fps)) else { return } self.fps = targetFPS.fps @@ -305,7 +305,8 @@ final class CameraDevice { return } self.transaction(device) { device in - device.videoZoomFactor = max(device.neutralZoomFactor, min(10.0, device.neutralZoomFactor + zoomLevel)) + let target = device.neutralZoomFactor + zoomLevel + device.videoZoomFactor = self.clampedZoomFactor(target, for: device) } } @@ -314,7 +315,8 @@ final class CameraDevice { return } self.transaction(device) { device in - device.videoZoomFactor = max(1.0, min(10.0, device.videoZoomFactor * zoomDelta)) + let target = device.videoZoomFactor * zoomDelta + device.videoZoomFactor = self.clampedZoomFactor(target, for: device) } } @@ -323,7 +325,8 @@ final class CameraDevice { return } self.transaction(device) { device in - device.ramp(toVideoZoomFactor: zoomLevel, withRate: Float(rate)) + let target = self.clampedZoomFactor(zoomLevel, for: device) + device.ramp(toVideoZoomFactor: target, withRate: Float(rate)) } } @@ -332,7 +335,14 @@ final class CameraDevice { return } self.transaction(device) { device in - device.videoZoomFactor = neutral ? device.neutralZoomFactor : device.minAvailableVideoZoomFactor + let target = neutral ? device.neutralZoomFactor : device.minAvailableVideoZoomFactor + device.videoZoomFactor = self.clampedZoomFactor(target, for: device) } } + + private func clampedZoomFactor(_ value: CGFloat, for device: AVCaptureDevice) -> CGFloat { + let minimum = max(1.0, device.minAvailableVideoZoomFactor) + let maximum = max(minimum, device.maxAvailableVideoZoomFactor) + return min(maximum, max(minimum, value)) + } } diff --git a/submodules/Camera/Sources/CameraInput.swift b/submodules/Camera/Sources/CameraInput.swift index 29adeca209..f9114e2db0 100644 --- a/submodules/Camera/Sources/CameraInput.swift +++ b/submodules/Camera/Sources/CameraInput.swift @@ -15,11 +15,14 @@ class CameraInput { } func invalidate(for session: CameraSession, switchAudio: Bool = true) { - for input in session.session.inputs { - if !switchAudio && input === self.audioInput { - continue - } - session.session.removeInput(input) + if let videoInput = self.videoInput, session.session.inputs.contains(where: { $0 === videoInput }) { + session.session.removeInput(videoInput) + } + self.videoInput = nil + + if switchAudio, let audioInput = self.audioInput, session.session.inputs.contains(where: { $0 === audioInput }) { + session.session.removeInput(audioInput) + self.audioInput = nil } } diff --git a/submodules/Camera/Sources/CameraOutput.swift b/submodules/Camera/Sources/CameraOutput.swift index 4872e7635b..75dce41a14 100644 --- a/submodules/Camera/Sources/CameraOutput.swift +++ b/submodules/Camera/Sources/CameraOutput.swift @@ -192,9 +192,9 @@ final class CameraOutput: NSObject { } if #available(iOS 13.0, *), session.hasMultiCam { - if let device = device.videoDevice, let ports = input.videoInput?.ports(for: AVMediaType.video, sourceDeviceType: device.deviceType, sourceDevicePosition: device.position) { + if let device = device.videoDevice, let ports = input.videoInput?.ports(for: AVMediaType.video, sourceDeviceType: device.deviceType, sourceDevicePosition: device.position), let firstPort = ports.first { if let previewView { - let previewConnection = AVCaptureConnection(inputPort: ports.first!, videoPreviewLayer: previewView.videoPreviewLayer) + let previewConnection = AVCaptureConnection(inputPort: firstPort, videoPreviewLayer: previewView.videoPreviewLayer) if session.session.canAddConnection(previewConnection) { session.session.addConnection(previewConnection) self.previewConnection = previewConnection @@ -447,7 +447,7 @@ final class CameraOutput: NSObject { } return Signal { subscriber in - let timer = SwiftSignalKit.Timer(timeout: 0.1, repeat: true, completion: { [weak videoRecorder] in + let timer = SwiftSignalKit.Timer(timeout: 0.09, repeat: true, completion: { [weak videoRecorder] in let recordingData = CameraRecordingData(duration: videoRecorder?.duration ?? 0.0, filePath: outputFilePath) subscriber.putNext(recordingData) }, queue: Queue.mainQueue()) @@ -473,11 +473,7 @@ final class CameraOutput: NSObject { } var transitionImage: UIImage? { - var result: UIImage? - self.videoQueue.sync { - result = self.videoRecorder?.transitionImage - } - return result + return self.videoRecorder?.transitionImage } private weak var masterOutput: CameraOutput? @@ -705,7 +701,7 @@ extension CameraOutput: AVCaptureVideoDataOutputSampleBufferDelegate, AVCaptureA func captureOutput(_ output: AVCaptureOutput, didDrop sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) { if #available(iOS 13.0, *) { - Logger.shared.log("VideoRecorder", "Dropped sample buffer \(sampleBuffer.attachments)") + Logger.shared.log("Camera", "Dropped sample buffer \(sampleBuffer.attachments)") } } } diff --git a/submodules/Camera/Sources/VideoRecorder.swift b/submodules/Camera/Sources/VideoRecorder.swift index 5748938a75..e3772d446f 100644 --- a/submodules/Camera/Sources/VideoRecorder.swift +++ b/submodules/Camera/Sources/VideoRecorder.swift @@ -232,7 +232,9 @@ private final class VideoRecorderImpl { } else if self.orientation == .portraitUpsideDown { orientation = .left } - self.transitionImage = UIImage(cgImage: cgImage, scale: 1.0, orientation: orientation) + Queue.mainQueue().async { + self.transitionImage = UIImage(cgImage: cgImage, scale: 1.0, orientation: orientation) + } } else { self.savedTransitionImage = false } @@ -375,21 +377,21 @@ private final class VideoRecorderImpl { dispatchPrecondition(condition: .onQueue(self.queue)) let completion = self.completion if self.recordingStopSampleTime == .invalid { - DispatchQueue.main.async { + Queue.mainQueue().async { completion(false, nil, nil) } return } if let _ = self.error.with({ $0 }) { - DispatchQueue.main.async { + Queue.mainQueue().async { completion(false, nil, nil) } return } if !self.tryAppendingPendingAudioBuffers() { - DispatchQueue.main.async { + Queue.mainQueue().async { completion(false, nil, nil) } return @@ -398,21 +400,21 @@ private final class VideoRecorderImpl { if self.assetWriter.status == .writing { self.assetWriter.finishWriting { if let _ = self.assetWriter.error { - DispatchQueue.main.async { + Queue.mainQueue().async { completion(false, nil, nil) } } else { - DispatchQueue.main.async { + Queue.mainQueue().async { completion(true, self.transitionImage, self.positionChangeTimestamps) } } } } else if let _ = self.assetWriter.error { - DispatchQueue.main.async { + Queue.mainQueue().async { completion(false, nil, nil) } } else { - DispatchQueue.main.async { + Queue.mainQueue().async { completion(false, nil, nil) } } diff --git a/submodules/LegacyComponents/Sources/TGCameraController.m b/submodules/LegacyComponents/Sources/TGCameraController.m index 3d9f789212..ee9724b20e 100644 --- a/submodules/LegacyComponents/Sources/TGCameraController.m +++ b/submodules/LegacyComponents/Sources/TGCameraController.m @@ -2751,6 +2751,8 @@ static CGPoint TGCameraControllerClampPointToScreenSize(__unused id self, __unus return CGRectMake(0, 82, screenSize.width, screenSize.height - 82 - 83); else if (widescreenWidth == 926.0f) return CGRectMake(0, 82, screenSize.width, screenSize.height - 82 - 83); + else if (widescreenWidth == 912.0f) + return CGRectMake(0, 82, screenSize.width, screenSize.height - 82 - 83); else if (widescreenWidth == 896.0f) return CGRectMake(0, 77, screenSize.width, screenSize.height - 77 - 83); else if (widescreenWidth == 874.0f) @@ -2784,6 +2786,8 @@ static CGPoint TGCameraControllerClampPointToScreenSize(__unused id self, __unus return CGRectMake(0, 136, screenSize.width, screenSize.height - 136 - 223); else if (widescreenWidth == 926.0f) return CGRectMake(0, 121, screenSize.width, screenSize.height - 121 - 234); + else if (widescreenWidth == 912.0f) + return CGRectMake(0, 136, screenSize.width, screenSize.height - 136 - 216); else if (widescreenWidth == 896.0f) return CGRectMake(0, 121, screenSize.width, screenSize.height - 121 - 223); else if (widescreenWidth == 874.0f) diff --git a/submodules/TelegramUI/Components/VideoMessageCameraScreen/Sources/VideoMessageCameraScreen.swift b/submodules/TelegramUI/Components/VideoMessageCameraScreen/Sources/VideoMessageCameraScreen.swift index 8e663ee071..97e52bec43 100644 --- a/submodules/TelegramUI/Components/VideoMessageCameraScreen/Sources/VideoMessageCameraScreen.swift +++ b/submodules/TelegramUI/Components/VideoMessageCameraScreen/Sources/VideoMessageCameraScreen.swift @@ -575,14 +575,18 @@ private final class VideoMessageCameraScreenComponent: CombinedComponent { if !component.isPreviewing { if case .on = component.cameraState.flashMode, case .front = component.cameraState.position { + let frontFlashPosition = CGPoint(x: component.previewFrame.midX, y: component.previewFrame.midY) + let frontFlashSize = CGSize( + width: max(abs(frontFlashPosition.x), abs(context.availableSize.width - frontFlashPosition.x)) * 2.0, + height: max(abs(frontFlashPosition.y), abs(context.availableSize.height - frontFlashPosition.y)) * 2.0 + ) let frontFlash = frontFlash.update( - component: Image(image: state.image(.flashImage, theme: environment.theme), tintColor: component.cameraState.flashTint.color), - availableSize: context.availableSize, + component: Image(image: state.image(.flashImage, theme: environment.theme), tintColor: component.cameraState.flashTint.color, contentMode: .scaleAspectFill), + availableSize: frontFlashSize, transition: .easeInOut(duration: 0.2) ) - let frontFlashFrame = CGRect(origin: CGPoint(), size: context.availableSize) context.add(frontFlash - .position(frontFlashFrame.center) + .position(frontFlashPosition) .scale(1.5 - component.cameraState.flashTintSize * 0.5) .appear(.default(alpha: true)) .disappear(ComponentTransition.Disappear({ view, transition, completion in