From 22945ebb318b57ab1b4e2b0d22fc60dac2957d1b Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Fri, 7 Nov 2025 14:13:09 +0400 Subject: [PATCH] Various improvements --- submodules/Camera/Sources/Camera.swift | 48 ++--- submodules/Camera/Sources/CameraOutput.swift | 143 +++++++------- .../Sources/ButtonComponent.swift | 2 +- .../TelegramUI/Components/CameraScreen/BUILD | 1 + .../MetalResources/cameraScreen.metal | 39 ++-- .../Sources/CameraLiveStreamComponent.swift | 105 ++++++++-- .../CameraScreen/Sources/CameraScreen.swift | 123 +++++++++--- .../Sources/CameraVideoSource.swift | 182 +++++++++++------- .../Sources/CaptureControlsComponent.swift | 36 +++- .../Sources/ShutterBlobView.swift | 52 +++-- .../Sources/ChatTextInputPanelComponent.swift | 6 + .../Sources/ChatTextInputPanelNode.swift | 22 ++- .../MetalResources/EditorVideo.metal | 38 ++-- .../Sources/MediaEditorComposer.swift | 7 +- .../Sources/MediaEditorRenderer.swift | 13 -- .../Sources/MediaEditorStoredState.swift | 5 +- .../Sources/MediaEditorValues.swift | 10 +- .../MediaEditor/Sources/VideoFinishPass.swift | 34 ++++ .../Sources/LiveStreamSettingsScreen.swift | 66 +++---- .../StoryContentLiveChatComponent.swift | 10 + .../Sources/StoryItemContentComponent.swift | 3 +- .../StoryItemSetContainerComponent.swift | 80 +++++--- 22 files changed, 662 insertions(+), 363 deletions(-) rename submodules/TelegramUI/Components/{MediaEditorScreen => MediaEditor}/Sources/MediaEditorStoredState.swift (94%) diff --git a/submodules/Camera/Sources/Camera.swift b/submodules/Camera/Sources/Camera.swift index 4aa87b597e..6790ba59ec 100644 --- a/submodules/Camera/Sources/Camera.swift +++ b/submodules/Camera/Sources/Camera.swift @@ -339,43 +339,34 @@ private final class CameraContext { self.mainDeviceContext?.output.processSampleBuffer = { [weak self] sampleBuffer, pixelBuffer, connection in guard let self, let mainDeviceContext = self.mainDeviceContext else { return - } + } + + var front = false + if #available(iOS 13.0, *) { + front = connection.inputPorts.first?.sourceDevicePosition == .front + } + self.mainVideoOutput?.push(sampleBuffer, mirror: front) + let timestamp = CACurrentMediaTime() 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 - } - - if sampleBuffer.type == kCMMediaType_Video { - Queue.mainQueue().async { - self.mainVideoOutput?.push(sampleBuffer, mirror: front) - } - } - self.savePreviewSnapshot(pixelBuffer: pixelBuffer, front: front) self.lastSnapshotTimestamp = timestamp self.savedSnapshot = true } } - self.mainDeviceContext?.output.processAudioBuffer = { [weak self] sampleBuffer in - let _ = self - } self.additionalDeviceContext?.output.processSampleBuffer = { [weak self] sampleBuffer, pixelBuffer, connection in guard let self, let additionalDeviceContext = self.additionalDeviceContext else { return } + + var front = false + if #available(iOS 13.0, *) { + front = connection.inputPorts.first?.sourceDevicePosition == .front + } + self.additionalVideoOutput?.push(sampleBuffer, mirror: front) + let timestamp = CACurrentMediaTime() 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 - } - - Queue.mainQueue().async { - self.additionalVideoOutput?.push(sampleBuffer, mirror: front) - } - self.savePreviewSnapshot(pixelBuffer: pixelBuffer, front: front) self.lastAdditionalSnapshotTimestamp = timestamp self.savedAdditionalSnapshot = true @@ -402,10 +393,7 @@ private final class CameraContext { if #available(iOS 13.0, *) { front = connection.inputPorts.first?.sourceDevicePosition == .front } - - Queue.mainQueue().async { - self.mainVideoOutput?.push(sampleBuffer, mirror: front) - } + self.mainVideoOutput?.push(sampleBuffer, mirror: front) let timestamp = CACurrentMediaTime() if timestamp > self.lastSnapshotTimestamp + 2.5, !mainDeviceContext.output.isRecording || !self.savedSnapshot { @@ -414,10 +402,6 @@ private final class CameraContext { self.savedSnapshot = true } } - self.mainDeviceContext?.output.processAudioBuffer = { [weak self] sampleBuffer in - let _ = self - } - // if self.initialConfiguration.reportAudioLevel { // self.mainDeviceContext?.output.processAudioBuffer = { [weak self] sampleBuffer in // guard let self else { diff --git a/submodules/Camera/Sources/CameraOutput.swift b/submodules/Camera/Sources/CameraOutput.swift index 3e37e972ff..a1f0a3fb1c 100644 --- a/submodules/Camera/Sources/CameraOutput.swift +++ b/submodules/Camera/Sources/CameraOutput.swift @@ -139,7 +139,7 @@ final class CameraOutput: NSObject { } self.videoOutput.alwaysDiscardsLateVideoFrames = false - self.videoOutput.videoSettings = [kCVPixelBufferPixelFormatTypeKey: use32BGRA ? kCVPixelFormatType_32BGRA : kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange] as [String : Any] + self.videoOutput.videoSettings = [kCVPixelBufferPixelFormatTypeKey: use32BGRA ? kCVPixelFormatType_32BGRA : kCVPixelFormatType_420YpCbCr8BiPlanarFullRange] as [String : Any] } deinit { @@ -471,90 +471,91 @@ final class CameraOutput: NSObject { private var videoSwitchSampleTimeOffset: CMTime? func processVideoRecording(_ sampleBuffer: CMSampleBuffer, fromAdditionalOutput: Bool) { + guard let videoRecorder = self.videoRecorder, videoRecorder.isRecording else { + return + } guard let formatDescriptor = CMSampleBufferGetFormatDescription(sampleBuffer) else { return } let type = CMFormatDescriptionGetMediaType(formatDescriptor) - if let videoRecorder = self.videoRecorder, videoRecorder.isRecording { - if case .roundVideo = self.currentMode, type == kCMMediaType_Video { - let currentTimestamp = CACurrentMediaTime() - let duration: Double = 0.2 - if !self.exclusive { - var transitionFactor: CGFloat = 0.0 - if case .front = self.currentPosition { - transitionFactor = 1.0 - if self.lastSwitchTimestamp > 0.0, currentTimestamp - self.lastSwitchTimestamp < duration { - transitionFactor = max(0.0, (currentTimestamp - self.lastSwitchTimestamp) / duration) - } - } else { - transitionFactor = 0.0 - if self.lastSwitchTimestamp > 0.0, currentTimestamp - self.lastSwitchTimestamp < duration { - transitionFactor = 1.0 - max(0.0, (currentTimestamp - self.lastSwitchTimestamp) / duration) - } - } - - if (transitionFactor == 1.0 && fromAdditionalOutput) - || (transitionFactor == 0.0 && !fromAdditionalOutput) - || (transitionFactor > 0.0 && transitionFactor < 1.0) { - if let processedSampleBuffer = self.processRoundVideoSampleBuffer(sampleBuffer, additional: fromAdditionalOutput, transitionFactor: transitionFactor) { - let presentationTime = CMSampleBufferGetPresentationTimeStamp(processedSampleBuffer) - if let lastSampleTimestamp = self.lastSampleTimestamp, lastSampleTimestamp > presentationTime { - - } else { - videoRecorder.appendSampleBuffer(processedSampleBuffer) - self.lastSampleTimestamp = presentationTime - } - } + if case .roundVideo = self.currentMode, type == kCMMediaType_Video { + let currentTimestamp = CACurrentMediaTime() + let duration: Double = 0.2 + if !self.exclusive { + var transitionFactor: CGFloat = 0.0 + if case .front = self.currentPosition { + transitionFactor = 1.0 + if self.lastSwitchTimestamp > 0.0, currentTimestamp - self.lastSwitchTimestamp < duration { + transitionFactor = max(0.0, (currentTimestamp - self.lastSwitchTimestamp) / duration) } } else { - var additional = self.currentPosition == .front - var transitionFactor = self.currentPosition == .front ? 1.0 : 0.0 - if self.lastSwitchTimestamp > 0.0 { - if self.needsCrossfadeTransition { - self.needsCrossfadeTransition = false - self.crossfadeTransitionStart = currentTimestamp + 0.03 - self.needsSwitchSampleOffset = true - } - if self.crossfadeTransitionStart > 0.0, currentTimestamp - self.crossfadeTransitionStart < duration { - if case .front = self.currentPosition { - transitionFactor = max(0.0, (currentTimestamp - self.crossfadeTransitionStart) / duration) - } else { - transitionFactor = 1.0 - max(0.0, (currentTimestamp - self.crossfadeTransitionStart) / duration) - } - } else if currentTimestamp - self.lastSwitchTimestamp < 0.05 { - additional = !additional - transitionFactor = 1.0 - transitionFactor - self.needsCrossfadeTransition = true - } + transitionFactor = 0.0 + if self.lastSwitchTimestamp > 0.0, currentTimestamp - self.lastSwitchTimestamp < duration { + transitionFactor = 1.0 - max(0.0, (currentTimestamp - self.lastSwitchTimestamp) / duration) } - if let processedSampleBuffer = self.processRoundVideoSampleBuffer(sampleBuffer, additional: additional, transitionFactor: transitionFactor) { - videoRecorder.appendSampleBuffer(processedSampleBuffer) - } else { - videoRecorder.appendSampleBuffer(sampleBuffer) + } + + if (transitionFactor == 1.0 && fromAdditionalOutput) + || (transitionFactor == 0.0 && !fromAdditionalOutput) + || (transitionFactor > 0.0 && transitionFactor < 1.0) { + if let processedSampleBuffer = self.processRoundVideoSampleBuffer(sampleBuffer, additional: fromAdditionalOutput, transitionFactor: transitionFactor) { + let presentationTime = CMSampleBufferGetPresentationTimeStamp(processedSampleBuffer) + if let lastSampleTimestamp = self.lastSampleTimestamp, lastSampleTimestamp > presentationTime { + + } else { + videoRecorder.appendSampleBuffer(processedSampleBuffer) + self.lastSampleTimestamp = presentationTime + } } } } else { - if type == kCMMediaType_Audio { - if self.needsSwitchSampleOffset { - self.needsSwitchSampleOffset = false - - if let lastAudioSampleTime = self.lastAudioSampleTime { - let videoSampleTime = CMSampleBufferGetPresentationTimeStamp(sampleBuffer) - let offset = videoSampleTime - lastAudioSampleTime - if let current = self.videoSwitchSampleTimeOffset { - self.videoSwitchSampleTimeOffset = current + offset - } else { - self.videoSwitchSampleTimeOffset = offset - } - self.lastAudioSampleTime = nil - } + var additional = self.currentPosition == .front + var transitionFactor = self.currentPosition == .front ? 1.0 : 0.0 + if self.lastSwitchTimestamp > 0.0 { + if self.needsCrossfadeTransition { + self.needsCrossfadeTransition = false + self.crossfadeTransitionStart = currentTimestamp + 0.03 + self.needsSwitchSampleOffset = true + } + if self.crossfadeTransitionStart > 0.0, currentTimestamp - self.crossfadeTransitionStart < duration { + if case .front = self.currentPosition { + transitionFactor = max(0.0, (currentTimestamp - self.crossfadeTransitionStart) / duration) + } else { + transitionFactor = 1.0 - max(0.0, (currentTimestamp - self.crossfadeTransitionStart) / duration) + } + } else if currentTimestamp - self.lastSwitchTimestamp < 0.05 { + additional = !additional + transitionFactor = 1.0 - transitionFactor + self.needsCrossfadeTransition = true } - - self.lastAudioSampleTime = CMSampleBufferGetPresentationTimeStamp(sampleBuffer) + CMSampleBufferGetDuration(sampleBuffer) } - videoRecorder.appendSampleBuffer(sampleBuffer) + if let processedSampleBuffer = self.processRoundVideoSampleBuffer(sampleBuffer, additional: additional, transitionFactor: transitionFactor) { + videoRecorder.appendSampleBuffer(processedSampleBuffer) + } else { + videoRecorder.appendSampleBuffer(sampleBuffer) + } } + } else { + if type == kCMMediaType_Audio { + if self.needsSwitchSampleOffset { + self.needsSwitchSampleOffset = false + + if let lastAudioSampleTime = self.lastAudioSampleTime { + let videoSampleTime = CMSampleBufferGetPresentationTimeStamp(sampleBuffer) + let offset = videoSampleTime - lastAudioSampleTime + if let current = self.videoSwitchSampleTimeOffset { + self.videoSwitchSampleTimeOffset = current + offset + } else { + self.videoSwitchSampleTimeOffset = offset + } + self.lastAudioSampleTime = nil + } + } + + self.lastAudioSampleTime = CMSampleBufferGetPresentationTimeStamp(sampleBuffer) + CMSampleBufferGetDuration(sampleBuffer) + } + videoRecorder.appendSampleBuffer(sampleBuffer) } } diff --git a/submodules/TelegramUI/Components/ButtonComponent/Sources/ButtonComponent.swift b/submodules/TelegramUI/Components/ButtonComponent/Sources/ButtonComponent.swift index 7f32707a42..805f29714e 100644 --- a/submodules/TelegramUI/Components/ButtonComponent/Sources/ButtonComponent.swift +++ b/submodules/TelegramUI/Components/ButtonComponent/Sources/ButtonComponent.swift @@ -634,7 +634,7 @@ public final class ButtonComponent: Component { chromeView.layer.compositingFilter = "overlayBlendMode" chromeView.alpha = 0.8 - chromeView.image = GlassBackgroundView.generateForegroundImage(size: CGSize(width: 26.0 * 2.0, height: 26.0 * 2.0), isDark: component.background.color.lightness < 0.4, fillColor: .clear) + chromeView.image = GlassBackgroundView.generateForegroundImage(size: CGSize(width: 26.0 * 2.0, height: 26.0 * 2.0), isDark: component.background.color.lightness < 0.36, fillColor: .clear) } chromeTransition.setFrame(view: chromeView, frame: CGRect(origin: .zero, size: availableSize)) } else if let chromeView = self.chromeView { diff --git a/submodules/TelegramUI/Components/CameraScreen/BUILD b/submodules/TelegramUI/Components/CameraScreen/BUILD index bf24d14ddf..b169af473a 100644 --- a/submodules/TelegramUI/Components/CameraScreen/BUILD +++ b/submodules/TelegramUI/Components/CameraScreen/BUILD @@ -86,6 +86,7 @@ swift_library( "//submodules/UndoUI", "//submodules/ContextUI", "//submodules/AvatarNode", + "//submodules/ActivityIndicator", "//submodules/TelegramUI/Components/Utils/AnimatableProperty", "//submodules/TelegramUI/Components/GlassBackgroundComponent", "//submodules/TelegramUI/Components/GlassBarButtonComponent", diff --git a/submodules/TelegramUI/Components/CameraScreen/MetalResources/cameraScreen.metal b/submodules/TelegramUI/Components/CameraScreen/MetalResources/cameraScreen.metal index e8ab4634fa..ddbdcaf55f 100644 --- a/submodules/TelegramUI/Components/CameraScreen/MetalResources/cameraScreen.metal +++ b/submodules/TelegramUI/Components/CameraScreen/MetalResources/cameraScreen.metal @@ -51,33 +51,36 @@ fragment half4 cameraBlobFragment(RasterizerData in[[stage_in]], constant uint2 &resolution[[buffer(0)]], constant float4 &primaryParameters[[buffer(1)]], constant float2 &primaryOffset[[buffer(2)]], - constant float3 &secondaryParameters[[buffer(3)]], - constant float2 &secondaryOffset[[buffer(4)]]) + constant float3 &primaryColor[[buffer(3)]], + constant float3 &secondaryParameters[[buffer(4)]], + constant float2 &secondaryOffset[[buffer(5)]]) { - float2 R = float2(resolution.x, resolution.y); - + float2 R = float2(resolution); float2 uv; - float offset; + float axis; if (R.x > R.y) { - uv = (2.0 * in.position.xy - R.xy) / R.y; - offset = uv.x; + uv = (2.0 * in.position.xy - R) / R.y; + axis = uv.x; } else { - uv = (2.0 * in.position.xy - R.xy) / R.x; - offset = uv.y; + uv = (2.0 * in.position.xy - R) / R.x; + axis = uv.y; } float t = AARadius / resolution.y; - float cAlpha = min(1.0, 1.0 - primaryParameters.z); - float minColor = min(1.0, 1.0 + primaryParameters.z); - float bound = max(primaryParameters.x, primaryParameters.y) + 0.05; - if (abs(offset) > bound) { - cAlpha = mix(0.0, 1.0, min(1.0, (abs(offset) - bound) * 2.4)); - } - - float c = smoothstep(t, -t, map(uv, primaryParameters, primaryOffset, secondaryParameters, secondaryOffset)); + float coverage = smoothstep(t, -t, map(uv, primaryParameters, primaryOffset, + secondaryParameters, secondaryOffset)); - return half4(min(minColor, c), min(minColor, max(cAlpha, 0.231)), min(minColor, max(cAlpha, 0.188)), c); + float bound = max(primaryParameters.x, primaryParameters.y) + 0.05; + if (abs(axis) > bound) { + float extra = min(1.0, (abs(axis) - bound) * 2.4); + coverage = mix(0.0, coverage, extra); + } + + float alpha = coverage; + float3 rgb = clamp(primaryColor, 0.0, 1.0) * alpha; + + return half4(half3(rgb), half(alpha)); } struct Rectangle { diff --git a/submodules/TelegramUI/Components/CameraScreen/Sources/CameraLiveStreamComponent.swift b/submodules/TelegramUI/Components/CameraScreen/Sources/CameraLiveStreamComponent.swift index 883878836f..dfe3034c58 100644 --- a/submodules/TelegramUI/Components/CameraScreen/Sources/CameraLiveStreamComponent.swift +++ b/submodules/TelegramUI/Components/CameraScreen/Sources/CameraLiveStreamComponent.swift @@ -24,6 +24,9 @@ final class CameraLiveStreamComponent: Component { let safeInsets: UIEdgeInsets let metrics: LayoutMetrics let deviceMetrics: DeviceMetrics + let presentController: (ViewController, Any?) -> Void + let presentInGlobalOverlay: (ViewController, Any?) -> Void + let getController: () -> ViewController? let didSetupMediaStream: (PresentationGroupCall) -> Void init( @@ -37,6 +40,9 @@ final class CameraLiveStreamComponent: Component { safeInsets: UIEdgeInsets, metrics: LayoutMetrics, deviceMetrics: DeviceMetrics, + presentController: @escaping (ViewController, Any?) -> Void, + presentInGlobalOverlay: @escaping (ViewController, Any?) -> Void, + getController: @escaping () -> ViewController?, didSetupMediaStream: @escaping (PresentationGroupCall) -> Void ) { self.context = context @@ -49,6 +55,9 @@ final class CameraLiveStreamComponent: Component { self.safeInsets = safeInsets self.metrics = metrics self.deviceMetrics = deviceMetrics + self.presentController = presentController + self.presentInGlobalOverlay = presentInGlobalOverlay + self.getController = getController self.didSetupMediaStream = didSetupMediaStream } @@ -121,6 +130,14 @@ final class CameraLiveStreamComponent: Component { } return nil } + + override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { + let result = super.hitTest(point, with: event) + if result === self { + return nil + } + return result + } func update(component: CameraLiveStreamComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { self.component = component @@ -180,21 +197,17 @@ final class CameraLiveStreamComponent: Component { isPanning: false, isCentral: true, pinchState: nil, - presentController: { c, a in - // guard let self, let environment = self.environment else { - // return - // } - // if c is UndoOverlayController || c is TooltipScreen { - // environment.controller()?.present(c, in: .current) - // } else { - // environment.controller()?.present(c, in: .window(.root), with: a) - // } + presentController: { [weak self] c, a in + guard let self, let component = self.component else { + return + } + component.presentController(c, a) }, - presentInGlobalOverlay: { c, a in - // guard let self, let environment = self.environment else { - // return - // } - // environment.controller()?.presentInGlobalOverlay(c, with: a) + presentInGlobalOverlay: { [weak self] c, a in + guard let self, let component = self.component else { + return + } + component.presentInGlobalOverlay(c, a) }, close: { }, @@ -210,8 +223,11 @@ final class CameraLiveStreamComponent: Component { }, addToFolder: { _ in }, - controller: { - return nil //self?.environment?.controller() + controller: { [weak self] in + guard let self, let component = self.component else { + return nil + } + return component.getController() }, toggleAmbientMode: { }, @@ -312,6 +328,58 @@ public final class StreamAsComponent: Component { fatalError("init(coder:) has not been implemented") } + var scheduledAnimateIn: ComponentTransition? + func animateIn(transition: ComponentTransition) { + if self.peer == nil { + self.scheduledAnimateIn = transition + self.alpha = 0.0 + return + } + self.alpha = 1.0 + + transition.animateAlpha(view: self.avatarNode.view, from: 0.0, to: 1.0) + transition.animateScale(view: self.avatarNode.view, from: 0.01, to: 1.0) + + let offset: CGFloat = 24.0 + if let titleView = self.title.view { + transition.animateAlpha(view: titleView, from: 0.0, to: 1.0) + transition.animatePosition(view: titleView, from: CGPoint(x: -titleView.bounds.width / 2.0 - offset, y: self.bounds.height / 2.0 - titleView.center.y), to: .zero, additive: true) + transition.animateScale(view: titleView, from: 0.01, to: 1.0) + } + if let subtitleView = self.subtitle.view { + transition.animateAlpha(view: subtitleView, from: 0.0, to: 1.0) + transition.animatePosition(view: subtitleView, from: CGPoint(x: -subtitleView.bounds.width / 2.0 - offset, y: self.bounds.height / 2.0 - subtitleView.center.y), to: .zero, additive: true) + transition.animateScale(view: subtitleView, from: 0.01, to: 1.0) + + transition.animateAlpha(view: self.arrow, from: 0.0, to: 1.0) + transition.animatePosition(view: self.arrow, from: CGPoint(x: -subtitleView.bounds.width / 2.0 - offset - 16.0, y: self.bounds.height / 2.0 - self.arrow.center.y), to: .zero, additive: true) + transition.animateScale(view: self.arrow, from: 0.01, to: 1.0) + } + } + + func animateOut(transition: ComponentTransition, completion: @escaping () -> Void) { + transition.setAlpha(view: self.avatarNode.view, alpha: 0.0, completion: { _ in + completion() + }) + transition.setScale(view: self.avatarNode.view, scale: 0.01) + + let offset: CGFloat = 24.0 + if let titleView = self.title.view { + transition.setAlpha(view: titleView, alpha: 0.0) + transition.setPosition(view: titleView, position: titleView.center.offsetBy(dx: -titleView.bounds.width / 2.0 - offset, dy: self.bounds.height / 2.0 - titleView.center.y)) + transition.setScale(view: titleView, scale: 0.01) + } + if let subtitleView = self.subtitle.view { + transition.setAlpha(view: subtitleView, alpha: 0.0) + transition.setPosition(view: subtitleView, position: subtitleView.center.offsetBy(dx: -subtitleView.bounds.width / 2.0 - offset, dy: self.bounds.height / 2.0 - subtitleView.center.y)) + transition.setScale(view: subtitleView, scale: 0.01) + + transition.setAlpha(view: self.arrow, alpha: 0.0) + transition.setPosition(view: self.arrow, position: self.arrow.center.offsetBy(dx: -subtitleView.bounds.width / 2.0 - offset - 16.0, dy: self.bounds.height / 2.0 - self.arrow.center.y)) + transition.setScale(view: self.arrow, scale: 0.01) + } + } + public func update(component: StreamAsComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { self.component = component self.state = state @@ -324,6 +392,11 @@ public final class StreamAsComponent: Component { } self.peer = peer self.state?.updated() + + if let scheduledAnimateIn = self.scheduledAnimateIn { + self.scheduledAnimateIn = nil + self.animateIn(transition: scheduledAnimateIn) + } }) } diff --git a/submodules/TelegramUI/Components/CameraScreen/Sources/CameraScreen.swift b/submodules/TelegramUI/Components/CameraScreen/Sources/CameraScreen.swift index b4996b8b2f..80ab2ac57c 100644 --- a/submodules/TelegramUI/Components/CameraScreen/Sources/CameraScreen.swift +++ b/submodules/TelegramUI/Components/CameraScreen/Sources/CameraScreen.swift @@ -30,6 +30,7 @@ import TelegramVoip import TelegramCallsUI import GlassBarButtonComponent import PlainButtonComponent +import StoryContainerScreen let videoRedColor = UIColor(rgb: 0xff3b30) let collageGrids: [Camera.CollageGrid] = [ @@ -87,53 +88,58 @@ struct CameraState: Equatable { let collageGrid: Camera.CollageGrid let collageProgress: Float let isStreaming: Bool + let isWaitingForStream: Bool func updatedMode(_ mode: CameraMode) -> CameraState { - return CameraState(mode: mode, position: self.position, flashMode: self.flashMode, flashModeDidChange: self.flashModeDidChange, flashTint: self.flashTint, flashTintSize: self.flashTintSize, recording: self.recording, duration: self.duration, isDualCameraEnabled: self.isDualCameraEnabled, isCollageEnabled: self.isCollageEnabled, collageGrid: self.collageGrid, collageProgress: self.collageProgress, isStreaming: self.isStreaming) + return CameraState(mode: mode, position: self.position, flashMode: self.flashMode, flashModeDidChange: self.flashModeDidChange, flashTint: self.flashTint, flashTintSize: self.flashTintSize, recording: self.recording, duration: self.duration, isDualCameraEnabled: self.isDualCameraEnabled, isCollageEnabled: self.isCollageEnabled, collageGrid: self.collageGrid, collageProgress: self.collageProgress, isStreaming: self.isStreaming, isWaitingForStream: self.isWaitingForStream) } func updatedPosition(_ position: Camera.Position) -> CameraState { - return CameraState(mode: self.mode, position: position, flashMode: self.flashMode, flashModeDidChange: self.flashModeDidChange, flashTint: self.flashTint, flashTintSize: self.flashTintSize, recording: self.recording, duration: self.duration, isDualCameraEnabled: self.isDualCameraEnabled, isCollageEnabled: self.isCollageEnabled, collageGrid: self.collageGrid, collageProgress: self.collageProgress, isStreaming: self.isStreaming) + return CameraState(mode: self.mode, position: position, flashMode: self.flashMode, flashModeDidChange: self.flashModeDidChange, flashTint: self.flashTint, flashTintSize: self.flashTintSize, recording: self.recording, duration: self.duration, isDualCameraEnabled: self.isDualCameraEnabled, isCollageEnabled: self.isCollageEnabled, collageGrid: self.collageGrid, collageProgress: self.collageProgress, isStreaming: self.isStreaming, isWaitingForStream: self.isWaitingForStream) } func updatedFlashMode(_ flashMode: Camera.FlashMode) -> CameraState { - return CameraState(mode: self.mode, position: self.position, flashMode: flashMode, flashModeDidChange: self.flashMode != flashMode, flashTint: self.flashTint, flashTintSize: self.flashTintSize, recording: self.recording, duration: self.duration, isDualCameraEnabled: self.isDualCameraEnabled, isCollageEnabled: self.isCollageEnabled, collageGrid: self.collageGrid, collageProgress: self.collageProgress, isStreaming: self.isStreaming) + return CameraState(mode: self.mode, position: self.position, flashMode: flashMode, flashModeDidChange: self.flashMode != flashMode, flashTint: self.flashTint, flashTintSize: self.flashTintSize, recording: self.recording, duration: self.duration, isDualCameraEnabled: self.isDualCameraEnabled, isCollageEnabled: self.isCollageEnabled, collageGrid: self.collageGrid, collageProgress: self.collageProgress, isStreaming: self.isStreaming, isWaitingForStream: self.isWaitingForStream) } func updatedFlashTint(_ flashTint: FlashTint) -> CameraState { - return CameraState(mode: self.mode, position: self.position, flashMode: self.flashMode, flashModeDidChange: self.flashModeDidChange, flashTint: flashTint, flashTintSize: self.flashTintSize, recording: self.recording, duration: self.duration, isDualCameraEnabled: self.isDualCameraEnabled, isCollageEnabled: self.isCollageEnabled, collageGrid: self.collageGrid, collageProgress: self.collageProgress, isStreaming: self.isStreaming) + return CameraState(mode: self.mode, position: self.position, flashMode: self.flashMode, flashModeDidChange: self.flashModeDidChange, flashTint: flashTint, flashTintSize: self.flashTintSize, recording: self.recording, duration: self.duration, isDualCameraEnabled: self.isDualCameraEnabled, isCollageEnabled: self.isCollageEnabled, collageGrid: self.collageGrid, collageProgress: self.collageProgress, isStreaming: self.isStreaming, isWaitingForStream: self.isWaitingForStream) } func updatedFlashTintSize(_ size: CGFloat) -> CameraState { - return CameraState(mode: self.mode, position: self.position, flashMode: self.flashMode, flashModeDidChange: self.flashModeDidChange, flashTint: self.flashTint, flashTintSize: size, recording: self.recording, duration: self.duration, isDualCameraEnabled: self.isDualCameraEnabled, isCollageEnabled: self.isCollageEnabled, collageGrid: self.collageGrid, collageProgress: self.collageProgress, isStreaming: self.isStreaming) + return CameraState(mode: self.mode, position: self.position, flashMode: self.flashMode, flashModeDidChange: self.flashModeDidChange, flashTint: self.flashTint, flashTintSize: size, recording: self.recording, duration: self.duration, isDualCameraEnabled: self.isDualCameraEnabled, isCollageEnabled: self.isCollageEnabled, collageGrid: self.collageGrid, collageProgress: self.collageProgress, isStreaming: self.isStreaming, isWaitingForStream: self.isWaitingForStream) } func updatedRecording(_ recording: Recording) -> CameraState { - return CameraState(mode: self.mode, position: self.position, flashMode: self.flashMode, flashModeDidChange: self.flashModeDidChange, flashTint: self.flashTint, flashTintSize: self.flashTintSize, recording: recording, duration: self.duration, isDualCameraEnabled: self.isDualCameraEnabled, isCollageEnabled: self.isCollageEnabled, collageGrid: self.collageGrid, collageProgress: self.collageProgress, isStreaming: self.isStreaming) + return CameraState(mode: self.mode, position: self.position, flashMode: self.flashMode, flashModeDidChange: self.flashModeDidChange, flashTint: self.flashTint, flashTintSize: self.flashTintSize, recording: recording, duration: self.duration, isDualCameraEnabled: self.isDualCameraEnabled, isCollageEnabled: self.isCollageEnabled, collageGrid: self.collageGrid, collageProgress: self.collageProgress, isStreaming: self.isStreaming, isWaitingForStream: self.isWaitingForStream) } func updatedDuration(_ duration: Double) -> CameraState { - return CameraState(mode: self.mode, position: self.position, flashMode: self.flashMode, flashModeDidChange: self.flashModeDidChange, flashTint: self.flashTint, flashTintSize: self.flashTintSize, recording: self.recording, duration: duration, isDualCameraEnabled: self.isDualCameraEnabled, isCollageEnabled: self.isCollageEnabled, collageGrid: self.collageGrid, collageProgress: self.collageProgress, isStreaming: self.isStreaming) + return CameraState(mode: self.mode, position: self.position, flashMode: self.flashMode, flashModeDidChange: self.flashModeDidChange, flashTint: self.flashTint, flashTintSize: self.flashTintSize, recording: self.recording, duration: duration, isDualCameraEnabled: self.isDualCameraEnabled, isCollageEnabled: self.isCollageEnabled, collageGrid: self.collageGrid, collageProgress: self.collageProgress, isStreaming: self.isStreaming, isWaitingForStream: self.isWaitingForStream) } func updatedIsDualCameraEnabled(_ isDualCameraEnabled: Bool) -> CameraState { - return CameraState(mode: self.mode, position: self.position, flashMode: self.flashMode, flashModeDidChange: self.flashModeDidChange, flashTint: self.flashTint, flashTintSize: self.flashTintSize, recording: self.recording, duration: self.duration, isDualCameraEnabled: isDualCameraEnabled, isCollageEnabled: self.isCollageEnabled, collageGrid: self.collageGrid, collageProgress: self.collageProgress, isStreaming: self.isStreaming) + return CameraState(mode: self.mode, position: self.position, flashMode: self.flashMode, flashModeDidChange: self.flashModeDidChange, flashTint: self.flashTint, flashTintSize: self.flashTintSize, recording: self.recording, duration: self.duration, isDualCameraEnabled: isDualCameraEnabled, isCollageEnabled: self.isCollageEnabled, collageGrid: self.collageGrid, collageProgress: self.collageProgress, isStreaming: self.isStreaming, isWaitingForStream: self.isWaitingForStream) } func updatedIsCollageEnabled(_ isCollageEnabled: Bool) -> CameraState { - return CameraState(mode: self.mode, position: self.position, flashMode: self.flashMode, flashModeDidChange: self.flashModeDidChange, flashTint: self.flashTint, flashTintSize: self.flashTintSize, recording: self.recording, duration: self.duration, isDualCameraEnabled: self.isDualCameraEnabled, isCollageEnabled: isCollageEnabled, collageGrid: self.collageGrid, collageProgress: self.collageProgress, isStreaming: self.isStreaming) + return CameraState(mode: self.mode, position: self.position, flashMode: self.flashMode, flashModeDidChange: self.flashModeDidChange, flashTint: self.flashTint, flashTintSize: self.flashTintSize, recording: self.recording, duration: self.duration, isDualCameraEnabled: self.isDualCameraEnabled, isCollageEnabled: isCollageEnabled, collageGrid: self.collageGrid, collageProgress: self.collageProgress, isStreaming: self.isStreaming, isWaitingForStream: self.isWaitingForStream) } func updatedCollageGrid(_ collageGrid: Camera.CollageGrid) -> CameraState { - return CameraState(mode: self.mode, position: self.position, flashMode: self.flashMode, flashModeDidChange: self.flashModeDidChange, flashTint: self.flashTint, flashTintSize: self.flashTintSize, recording: self.recording, duration: self.duration, isDualCameraEnabled: self.isDualCameraEnabled, isCollageEnabled: self.isCollageEnabled, collageGrid: collageGrid, collageProgress: self.collageProgress, isStreaming: self.isStreaming) + return CameraState(mode: self.mode, position: self.position, flashMode: self.flashMode, flashModeDidChange: self.flashModeDidChange, flashTint: self.flashTint, flashTintSize: self.flashTintSize, recording: self.recording, duration: self.duration, isDualCameraEnabled: self.isDualCameraEnabled, isCollageEnabled: self.isCollageEnabled, collageGrid: collageGrid, collageProgress: self.collageProgress, isStreaming: self.isStreaming, isWaitingForStream: self.isWaitingForStream) } func updatedCollageProgress(_ collageProgress: Float) -> CameraState { - return CameraState(mode: self.mode, position: self.position, flashMode: self.flashMode, flashModeDidChange: self.flashModeDidChange, flashTint: self.flashTint, flashTintSize: self.flashTintSize, recording: self.recording, duration: self.duration, isDualCameraEnabled: self.isDualCameraEnabled, isCollageEnabled: self.isCollageEnabled, collageGrid: self.collageGrid, collageProgress: collageProgress, isStreaming: self.isStreaming) + return CameraState(mode: self.mode, position: self.position, flashMode: self.flashMode, flashModeDidChange: self.flashModeDidChange, flashTint: self.flashTint, flashTintSize: self.flashTintSize, recording: self.recording, duration: self.duration, isDualCameraEnabled: self.isDualCameraEnabled, isCollageEnabled: self.isCollageEnabled, collageGrid: self.collageGrid, collageProgress: collageProgress, isStreaming: self.isStreaming, isWaitingForStream: self.isWaitingForStream) } func updatedIsStreaming(_ isStreaming: Bool) -> CameraState { - return CameraState(mode: self.mode, position: self.position, flashMode: self.flashMode, flashModeDidChange: self.flashModeDidChange, flashTint: self.flashTint, flashTintSize: self.flashTintSize, recording: self.recording, duration: self.duration, isDualCameraEnabled: self.isDualCameraEnabled, isCollageEnabled: self.isCollageEnabled, collageGrid: self.collageGrid, collageProgress: self.collageProgress, isStreaming: isStreaming) + return CameraState(mode: self.mode, position: self.position, flashMode: self.flashMode, flashModeDidChange: self.flashModeDidChange, flashTint: self.flashTint, flashTintSize: self.flashTintSize, recording: self.recording, duration: self.duration, isDualCameraEnabled: self.isDualCameraEnabled, isCollageEnabled: self.isCollageEnabled, collageGrid: self.collageGrid, collageProgress: self.collageProgress, isStreaming: isStreaming, isWaitingForStream: self.isWaitingForStream) + } + + func updatedIsWaitingForStream(_ isWaitingForStream: Bool) -> CameraState { + return CameraState(mode: self.mode, position: self.position, flashMode: self.flashMode, flashModeDidChange: self.flashModeDidChange, flashTint: self.flashTint, flashTintSize: self.flashTintSize, recording: self.recording, duration: self.duration, isDualCameraEnabled: self.isDualCameraEnabled, isCollageEnabled: self.isCollageEnabled, collageGrid: self.collageGrid, collageProgress: self.collageProgress, isStreaming: self.isStreaming, isWaitingForStream: isWaitingForStream) } } @@ -314,6 +320,7 @@ private final class CameraScreenComponent: CombinedComponent { private var paidMessageStars: Int64 = 0 private(set) var liveStreamStory: EngineStoryItem? + private(set) var liveStreamContent: StoryContentContext? private weak var liveStreamCall: PresentationGroupCall? private var liveStreamVideoCapturer: OngoingCallVideoCapturer? private var liveStreamVideoDisposable: Disposable? @@ -371,6 +378,13 @@ private final class CameraScreenComponent: CombinedComponent { self.sendAsPeerId = customTarget self.isCustomTarget = true } + + let _ = (mediaEditorStoredState(engine: self.context.engine) + |> deliverOnMainQueue).start(next: { [weak self] state in + if let self, let privacy = state?.privacy { + self.privacy = privacy.privacy + } + }) } } @@ -1082,13 +1096,23 @@ private final class CameraScreenComponent: CombinedComponent { controller.node.pauseCameraCapture() } - let _ = (self.context.engine.messages.beginStoryLivestream(peerId: peerId, rtmp: rtmp, privacy: self.privacy, isForwardingDisabled: self.isForwardingDisabled, messagesEnabled: self.allowComments, sendPaidMessageStars: self.paidMessageStars) + controller.updateCameraState({ $0.updatedIsWaitingForStream(true) }, transition: .spring(duration: 0.4)) + + let _ = (self.context.engine.messages.beginStoryLivestream( + peerId: peerId, + rtmp: rtmp, + privacy: self.privacy, + isForwardingDisabled: self.isForwardingDisabled, + messagesEnabled: self.allowComments, + sendPaidMessageStars: self.paidMessageStars + ) |> deliverOnMainQueue).start(next: { [weak self, weak controller] story in guard let self else { return } self.liveStreamStory = story - controller?.updateCameraState({ $0.updatedIsStreaming(true) }, transition: .spring(duration: 0.4)) + + controller?.updateCameraState({ $0.updatedIsStreaming(true).updatedIsWaitingForStream(false) }, transition: .spring(duration: 0.4)) self.updated(transition: .immediate) }) } @@ -1148,13 +1172,13 @@ private final class CameraScreenComponent: CombinedComponent { return } let _ = self.liveStreamCall?.leave(terminateIfPossible: true).startStandalone() - controller.dismiss(animated: true) + controller.requestDismiss(animated: true) }), TextAlertAction(type: .genericAction, title: "Leave", action: { [weak controller] in guard let controller else { return } - controller.dismiss(animated: true) + controller.requestDismiss(animated: true) }), TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_Cancel, action: {}) ], @@ -1175,7 +1199,7 @@ private final class CameraScreenComponent: CombinedComponent { return } let _ = self.liveStreamCall?.leave(terminateIfPossible: true).startStandalone() - controller.dismiss(animated: true) + controller.requestDismiss(animated: true) }) ] ) @@ -1184,14 +1208,18 @@ private final class CameraScreenComponent: CombinedComponent { } func setupLiveStreamCamera(call: PresentationGroupCall) { - guard self.liveStreamVideoCapturer == nil, let call = call as? PresentationGroupCallImpl, let controller = self.getController() else { + guard self.liveStreamVideoCapturer == nil, let call = call as? PresentationGroupCallImpl, let controller = self.getController(), let cameraState = self.cameraState else { return } self.liveStreamCall = call - - call.setIsMuted(action: .unmuted) - + let liveStreamMediaSource = controller.node.liveStreamMediaSource + liveStreamMediaSource.setup( + isDualCameraEnabled: cameraState.isDualCameraEnabled, + dualCameraPosition: controller.node.pipPosition, + position: cameraState.position + ) + let videoCapturer = OngoingCallVideoCapturer(keepLandscape: false, isCustom: true) self.liveStreamVideoCapturer = videoCapturer @@ -1203,8 +1231,9 @@ private final class CameraScreenComponent: CombinedComponent { videoCapturer.injectSampleBuffer(sampleBuffer, rotation: .up, completion: {}) } } - Queue.mainQueue().after(1.0) { + Queue.mainQueue().after(0.1) { call.requestVideo(capturer: videoCapturer, useFrontCamera: false) + call.setIsMuted(action: .unmuted) } } @@ -1390,7 +1419,7 @@ private final class CameraScreenComponent: CombinedComponent { case .video: shutterState = .video case .live: - shutterState = .live(active: component.cameraState.isStreaming) + shutterState = .live(active: component.cameraState.isStreaming, progress: component.cameraState.isWaitingForStream) } } } @@ -1527,6 +1556,24 @@ private final class CameraScreenComponent: CombinedComponent { safeInsets: environment.safeInsets, metrics: environment.metrics, deviceMetrics: environment.deviceMetrics, + presentController: { c, a in + if let controller = controller() { + if c is UndoOverlayController || c is TooltipScreen { + controller.present(c, in: .current, with: a) + } else { + controller.present(c, in: .window(.root), with: a) + } + } + }, + presentInGlobalOverlay: { c, a in + if let controller = controller() { + controller.presentInGlobalOverlay(c, with: a) + } + }, + getController: { + return nil + //return controller() + }, didSetupMediaStream: { [weak state] call in state?.setupLiveStreamCamera(call: call) } @@ -1568,8 +1615,18 @@ private final class CameraScreenComponent: CombinedComponent { ) context.add(streamAsButton .position(CGPoint(x: topControlSideInset + streamAsButton.size.width / 2.0 + 7.0, y: max(environment.statusBarHeight + 5.0, environment.safeInsets.top + topControlVerticalInset) + streamAsButton.size.height / 2.0 + 4.0)) - .appear(.default(scale: true)) - .disappear(.default(scale: true)) + .appear(ComponentTransition.Appear({ _, view, transition in + if let buttonView = view as? PlainButtonComponent.View, let view = buttonView.contentView as? StreamAsComponent.View, !transition.animation.isImmediate { + view.animateIn(transition: transition) + } + })) + .disappear(ComponentTransition.Disappear({ view, transition, completion in + if let buttonView = view as? PlainButtonComponent.View, let view = buttonView.contentView as? StreamAsComponent.View, !transition.animation.isImmediate { + view.animateOut(transition: transition, completion: completion) + } else { + completion() + } + })) ) } @@ -2211,7 +2268,7 @@ public class CameraScreenImpl: ViewController, CameraScreen { fileprivate var collage: CameraCollage? private var collageStateDisposable: Disposable? - private var pipPosition: PIPPosition = .topRight + fileprivate var pipPosition: PIPPosition = .topRight fileprivate var previewBlurPromise = ValuePromise(false) private let animateFlipAction = ActionSlot() @@ -2294,6 +2351,7 @@ public class CameraScreenImpl: ViewController, CameraScreen { if isDualCameraEnabled && previousPosition != currentPosition { self.animateDualCameraPositionSwitch() + self._livestreamMediaSource?.markToggleCamera(position: currentPosition) } else if dualCamWasEnabled != isDualCameraEnabled { self.requestUpdateLayout(transition: .spring(duration: 0.4)) @@ -2395,7 +2453,8 @@ public class CameraScreenImpl: ViewController, CameraScreen { isCollageEnabled: false, collageGrid: collageGrids[6], collageProgress: 0.0, - isStreaming: false + isStreaming: false, + isWaitingForStream: false ) self.previewFrameLeftDimView = UIView() @@ -2620,7 +2679,7 @@ public class CameraScreenImpl: ViewController, CameraScreen { preset: .hd1920x1080, position: self.cameraState.position, isDualEnabled: self.cameraState.isDualCameraEnabled, - audio: true, + audio: false, photo: true, metadata: true ), @@ -2820,7 +2879,7 @@ public class CameraScreenImpl: ViewController, CameraScreen { case .began: break case .changed: - if case .none = self.cameraState.recording { + if case .none = self.cameraState.recording, !self.cameraState.isStreaming && !self.cameraState.isWaitingForStream { if case .compact = layout.metrics.widthClass { switch controller.mode { case .story: @@ -2887,7 +2946,9 @@ public class CameraScreenImpl: ViewController, CameraScreen { } let location = gestureRecognizer.location(in: gestureRecognizer.view) - if self.cameraState.isDualCameraEnabled && self.additionalPreviewContainerView.frame.contains(location) { + if let layout = self.validLayout, let inputHeight = layout.inputHeight, inputHeight > 10.0 { + self.view.endEditing(true) + } else if self.cameraState.isDualCameraEnabled && self.additionalPreviewContainerView.frame.contains(location) { self.toggleCameraPositionAction.invoke(Void()) } else { let location = gestureRecognizer.location(in: self.mainPreviewView) @@ -2921,6 +2982,8 @@ public class CameraScreenImpl: ViewController, CameraScreen { self.pipPosition = pipPositionForLocation(layout: layout, position: location, velocity: velocity) self.containerLayoutUpdated(layout: layout, transition: .spring(duration: 0.4)) + self._livestreamMediaSource?.setDualCameraPosition(self.pipPosition) + UserDefaults.standard.set(self.pipPosition.rawValue as NSNumber, forKey: "TelegramStoryCameraDualPosition") default: break diff --git a/submodules/TelegramUI/Components/CameraScreen/Sources/CameraVideoSource.swift b/submodules/TelegramUI/Components/CameraScreen/Sources/CameraVideoSource.swift index b09891869d..1808012a40 100644 --- a/submodules/TelegramUI/Components/CameraScreen/Sources/CameraVideoSource.swift +++ b/submodules/TelegramUI/Components/CameraScreen/Sources/CameraVideoSource.swift @@ -83,28 +83,29 @@ final class CameraVideoSource: VideoSource { } } +private let dimensions = CGSize(width: 1080.0, height: 1920.0) final class LiveStreamMediaSource { + private let queue = Queue() private let pool: CVPixelBufferPool? private(set) var mainVideoOutput: CameraVideoOutput! private(set) var additionalVideoOutput: CameraVideoOutput! private let composer: MediaEditorComposer - private var currentMainSampleBuffer: CMSampleBuffer? - private var currentAdditionalSampleBuffer: CMSampleBuffer? + private var additionalSampleBuffer: CMSampleBuffer? public private(set) var currentVideoOutput: CVPixelBuffer? private var onVideoUpdatedListeners = Bag<() -> Void>() - public private(set) var currentAudioOutput: Data? - private var onAudioUpdatedListeners = Bag<() -> Void>() - + private var values: MediaEditorValues + private var cameraPosition: Camera.Position = .back + public init() { let width: Int32 = 720 let height: Int32 = 1280 - let dimensions = CGSize(width: CGFloat(width), height: CGFloat(height)) + let dimensions = CGSize(width: CGFloat(1080), height: CGFloat(1920)) let bufferOptions: [String: Any] = [ kCVPixelBufferPoolMinimumBufferCountKey as String: 3 as NSNumber @@ -120,52 +121,51 @@ final class LiveStreamMediaSource { CVPixelBufferPoolCreate(nil, bufferOptions as CFDictionary, pixelBufferOptions as CFDictionary, &pool) self.pool = pool - let topOffset = CGPoint(x: 267.0, y: 438.0) - let additionalVideoPosition = CGPoint(x: dimensions.width - topOffset.x, y: topOffset.y) + self.values = MediaEditorValues( + peerId: EnginePeer.Id(namespace: Namespaces.Peer.CloudUser, id: EnginePeer.Id.Id._internalFromInt64Value(0)), + originalDimensions: PixelDimensions(dimensions), + cropOffset: .zero, + cropRect: CGRect(origin: .zero, size: dimensions), + cropScale: 1.0, + cropRotation: 0.0, + cropMirroring: false, + cropOrientation: nil, + gradientColors: nil, + videoTrimRange: nil, + videoIsMuted: false, + videoIsFullHd: false, + videoIsMirrored: false, + videoVolume: nil, + additionalVideoPath: nil, + additionalVideoIsDual: true, + additionalVideoPosition: nil, + additionalVideoScale: 1.625, + additionalVideoRotation: 0.0, + additionalVideoPositionChanges: [], + additionalVideoTrimRange: nil, + additionalVideoOffset: nil, + additionalVideoVolume: nil, + collage: [], + nightTheme: false, + drawing: nil, + maskDrawing: nil, + entities: [], + toolValues: [:], + audioTrack: nil, + audioTrackTrimRange: nil, + audioTrackOffset: nil, + audioTrackVolume: nil, + audioTrackSamples: nil, + collageTrackSamples: nil, + coverImageTimestamp: nil, + coverDimensions: nil, + qualityPreset: nil + ) self.composer = MediaEditorComposer( postbox: nil, - values: MediaEditorValues( - peerId: EnginePeer.Id(namespace: Namespaces.Peer.CloudUser, id: EnginePeer.Id.Id._internalFromInt64Value(0)), - originalDimensions: PixelDimensions(dimensions), - cropOffset: .zero, - cropRect: CGRect(origin: .zero, size: dimensions), - cropScale: 1.0, - cropRotation: 0.0, - cropMirroring: false, - cropOrientation: nil, - gradientColors: nil, - videoTrimRange: nil, - videoIsMuted: false, - videoIsFullHd: false, - videoIsMirrored: false, - videoVolume: nil, - additionalVideoPath: nil, - additionalVideoIsDual: true, - additionalVideoPosition: additionalVideoPosition, - additionalVideoScale: 1.625, - additionalVideoRotation: 0.0, - additionalVideoPositionChanges: [], - additionalVideoTrimRange: nil, - additionalVideoOffset: nil, - additionalVideoVolume: nil, - collage: [], - nightTheme: false, - drawing: nil, - maskDrawing: nil, - entities: [], - toolValues: [:], - audioTrack: nil, - audioTrackTrimRange: nil, - audioTrackOffset: nil, - audioTrackVolume: nil, - audioTrackSamples: nil, - collageTrackSamples: nil, - coverImageTimestamp: nil, - coverDimensions: nil, - qualityPreset: nil - ), - dimensions: CGSize(width: 1080.0, height: 1920.0), + values: self.values, + dimensions: dimensions, outputDimensions: CGSize(width: 720.0, height: 1280.0), textScale: 1.0, videoDuration: nil, @@ -177,19 +177,77 @@ final class LiveStreamMediaSource { guard let self else { return } - self.currentMainSampleBuffer = try? CMSampleBuffer(copying: buffer) - self.push(mainSampleBuffer: buffer, additionalSampleBuffer: self.currentAdditionalSampleBuffer) + self.queue.async { + self.push(mainSampleBuffer: buffer) + } }) self.additionalVideoOutput = CameraVideoOutput(sink: { [weak self] buffer, mirror in guard let self else { return } - self.currentAdditionalSampleBuffer = try? CMSampleBuffer(copying: buffer) + self.queue.async { + self.additionalSampleBuffer = buffer + } }) } - public func addOnVideoUpdated(_ f: @escaping () -> Void) -> Disposable { + func setup(isDualCameraEnabled: Bool, dualCameraPosition: CameraScreenImpl.PIPPosition, position: Camera.Position) { + var additionalVideoPositionChanges: [VideoPositionChange] = [] + if isDualCameraEnabled && position == .front { + additionalVideoPositionChanges.append(VideoPositionChange(additional: true, timestamp: CACurrentMediaTime())) + } + var values = self.values + values = values.withUpdatedAdditionalVideoPositionChanges(additionalVideoPositionChanges: additionalVideoPositionChanges) + values = values.withUpdatedAdditionalVideo(position: self.getAdditionalVideoPosition(dualCameraPosition), scale: 1.625, rotation: 0.0) + self.values = values + self.cameraPosition = position + self.composer.values = values + } + + func markToggleCamera(position: Camera.Position) { + let timestamp = self.additionalSampleBuffer?.presentationTimeStamp.seconds ?? CACurrentMediaTime() + + var values = self.values + var additionalVideoPositionChanges = values.additionalVideoPositionChanges + additionalVideoPositionChanges.append(VideoPositionChange(additional: position == .front, timestamp: timestamp)) + values = values.withUpdatedAdditionalVideoPositionChanges(additionalVideoPositionChanges: additionalVideoPositionChanges) + self.values = values + self.cameraPosition = position + self.composer.values = self.values + } + + func setDualCameraPosition(_ pipPosition: CameraScreenImpl.PIPPosition) { + let timestamp = self.additionalSampleBuffer?.presentationTimeStamp.seconds ?? CACurrentMediaTime() + + var values = self.values + var additionalVideoPositionChanges = values.additionalVideoPositionChanges + additionalVideoPositionChanges.append(VideoPositionChange(additional: self.cameraPosition == .front, translationFrom: values.additionalVideoPosition ?? .zero, timestamp: timestamp)) + values = values.withUpdatedAdditionalVideoPositionChanges(additionalVideoPositionChanges: additionalVideoPositionChanges) + values = values.withUpdatedAdditionalVideo(position: self.getAdditionalVideoPosition(pipPosition), scale: 1.625, rotation: 0.0) + self.values = values + self.composer.values = values + } + + func getAdditionalVideoPosition(_ pipPosition: CameraScreenImpl.PIPPosition) -> CGPoint { + let topOffset = CGPoint(x: 267.0, y: 438.0) + let bottomOffset = CGPoint(x: 267.0, y: 438.0) + + let position: CGPoint + switch pipPosition { + case .topLeft: + position = CGPoint(x: topOffset.x, y: topOffset.y) + case .topRight: + position = CGPoint(x: dimensions.width - topOffset.x, y: topOffset.y) + case .bottomLeft: + position = CGPoint(x: bottomOffset.x, y: dimensions.height - bottomOffset.y) + case .bottomRight: + position = CGPoint(x: dimensions.width - bottomOffset.x, y: dimensions.height - bottomOffset.y) + } + return position + } + + func addOnVideoUpdated(_ f: @escaping () -> Void) -> Disposable { let index = self.onVideoUpdatedListeners.add(f) return ActionDisposable { [weak self] in @@ -202,19 +260,7 @@ final class LiveStreamMediaSource { } } - public func addOnAudioUpdated(_ f: @escaping () -> Void) -> Disposable { - let index = self.onAudioUpdatedListeners.add(f) - - return ActionDisposable { [weak self] in - DispatchQueue.main.async { - guard let self else { - return - } - self.onAudioUpdatedListeners.remove(index) - } - } - } - private func push(mainSampleBuffer: CMSampleBuffer, additionalSampleBuffer: CMSampleBuffer?) { + private func push(mainSampleBuffer: CMSampleBuffer) { let timestamp = mainSampleBuffer.presentationTimeStamp guard let mainPixelBuffer = CMSampleBufferGetImageBuffer(mainSampleBuffer) else { @@ -222,7 +268,7 @@ final class LiveStreamMediaSource { } let main: MediaEditorComposer.Input = .videoBuffer(VideoPixelBuffer(pixelBuffer: mainPixelBuffer, rotation: .rotate90Degrees, timestamp: timestamp), nil, 1.0, .zero) var additional: [MediaEditorComposer.Input?] = [] - if let additionalPixelBuffer = additionalSampleBuffer.flatMap({ CMSampleBufferGetImageBuffer($0) }) { + if let additionalPixelBuffer = self.additionalSampleBuffer.flatMap({ CMSampleBufferGetImageBuffer($0) }) { additional.append( .videoBuffer(VideoPixelBuffer(pixelBuffer: additionalPixelBuffer, rotation: .rotate90DegreesMirrored, timestamp: timestamp), nil, 1.0, .zero) ) @@ -236,7 +282,7 @@ final class LiveStreamMediaSource { guard let self else { return } - Queue.mainQueue().async { + self.queue.async { self.currentVideoOutput = pixelBuffer for onUpdated in self.onVideoUpdatedListeners.copyItems() { onUpdated() diff --git a/submodules/TelegramUI/Components/CameraScreen/Sources/CaptureControlsComponent.swift b/submodules/TelegramUI/Components/CameraScreen/Sources/CaptureControlsComponent.swift index 40d18576a0..64172f4a92 100644 --- a/submodules/TelegramUI/Components/CameraScreen/Sources/CaptureControlsComponent.swift +++ b/submodules/TelegramUI/Components/CameraScreen/Sources/CaptureControlsComponent.swift @@ -12,6 +12,7 @@ import AccountContext import GlassBackgroundComponent import GlassBarButtonComponent import BundleIconComponent +import ActivityIndicator enum ShutterButtonState: Equatable { case disabled @@ -20,7 +21,7 @@ enum ShutterButtonState: Equatable { case stopRecording case holdRecording(progress: Float) case transition - case live(active: Bool) + case live(active: Bool, progress: Bool) } private let maximumShutterSize = CGSize(width: 96.0, height: 96.0) @@ -109,6 +110,7 @@ private final class ShutterButtonContentComponent: Component { private let chromeView = UIImageView() private let label = ComponentView() + private var activityIndicator: ActivityIndicator? private let checkLayer = SimpleLayer() private let checkLayerMask = SimpleShapeLayer() @@ -274,6 +276,7 @@ private final class ShutterButtonContentComponent: Component { var chromeAlpha: CGFloat = 0.0 var chromeSize = CGSize(width: 60.0, height: 60.0) var labelAlpha: CGFloat = 0.0 + var hasProgress = false switch component.shutterState { case .generic, .disabled: innerColor = component.tintColor @@ -297,14 +300,15 @@ private final class ShutterButtonContentComponent: Component { innerSize = CGSize(width: 60.0, height: 60.0) ringSize = CGSize(width: 68.0, height: 68.0) recordingProgress = 0.0 - case .live: + case let .live(_, progress): innerColor = UIColor(rgb: 0xff375f) innerSize = CGSize(width: 52.0, height: 52.0) ringSize = CGSize(width: 60.0, height: 60.0) glassAlpha = 0.0 chromeAlpha = 0.65 - labelAlpha = 1.0 + labelAlpha = progress ? 0.0 : 1.0 chromeSize = CGSize(width: 326.0, height: 53.0 - UIScreenPixel) + hasProgress = progress } if component.collageProgress > 1.0 - .ulpOfOne { @@ -330,6 +334,30 @@ private final class ShutterButtonContentComponent: Component { transition.setAlpha(view: labelView, alpha: labelAlpha) } + if hasProgress { + let activityIndicator: ActivityIndicator + var activityIndicatorTransition = transition + if let current = self.activityIndicator { + activityIndicator = current + } else { + activityIndicatorTransition = .immediate + activityIndicator = ActivityIndicator(type: .custom(.white, 22.0, 2.0, true)) + activityIndicator.view.alpha = 0.0 + self.activityIndicator = activityIndicator + self.addSubview(activityIndicator.view) + } + let indicatorSize = CGSize(width: 22.0, height: 22.0) + transition.setAlpha(view: activityIndicator.view, alpha: 1.0) + + let indicatorFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((maximumShutterSize.width - indicatorSize.width) / 2.0), y: floorToScreenPixels((maximumShutterSize.height - indicatorSize.height) / 2.0)), size: indicatorSize) + activityIndicatorTransition.setFrame(view: activityIndicator.view, frame: indicatorFrame) + } else if let activityIndicator = self.activityIndicator { + self.activityIndicator = nil + transition.setAlpha(view: activityIndicator.view, alpha: 0.0, completion: { [weak activityIndicator] _ in + activityIndicator?.view.removeFromSuperview() + }) + } + let buttonFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((maximumShutterSize.width - chromeSize.width) / 2.0), y: floorToScreenPixels((maximumShutterSize.height - chromeSize.height) / 2.0)), size: chromeSize) transition.setFrame(view: self.chromeView, frame: buttonFrame) @@ -1182,7 +1210,7 @@ final class CaptureControlsComponent: Component { isHolding = true } else if case .transition = component.shutterState { isTransitioning = true - } else if case let .live(active) = component.shutterState { + } else if case let .live(active, _) = component.shutterState { isLiveStream = true isLiveActive = active } diff --git a/submodules/TelegramUI/Components/CameraScreen/Sources/ShutterBlobView.swift b/submodules/TelegramUI/Components/CameraScreen/Sources/ShutterBlobView.swift index a8effb9727..ed799fd9db 100644 --- a/submodules/TelegramUI/Components/CameraScreen/Sources/ShutterBlobView.swift +++ b/submodules/TelegramUI/Components/CameraScreen/Sources/ShutterBlobView.swift @@ -49,19 +49,27 @@ final class ShutterBlobView: UIView { } } - func primaryRedness(tintColor: UIColor) -> CGFloat { + func primaryColor(tintColor: UIColor) -> CGRect { + var color: UIColor switch self { case .generic: if tintColor.rgb == 0x000000 { - return -1.0 + color = UIColor(rgb: 0x000000) } else { - return 0.0 + color = UIColor(rgb: 0xffffff) } case .live: - return 0.7 + color = UIColor(rgb: 0xfa325a) default: - return 1.0 + color = UIColor(rgb: 0xff0b18) } + var r: CGFloat = 0.0 + var g: CGFloat = 0.0 + var b: CGFloat = 0.0 + if color.getRed(&r, green: &g, blue: &b, alpha: nil) { + return CGRect(x: r, y: g, width: b, height: 1.0) + } + return CGRect(x: 0, y: 0, width: 0, height: 1.0) } var primaryCornerRadius: CGFloat { @@ -105,7 +113,7 @@ final class ShutterBlobView: UIView { private var primaryHeight = AnimatableProperty(value: 0.63) private var primaryOffsetX = AnimatableProperty(value: 0.0) private var primaryOffsetY = AnimatableProperty(value: 0.0) - private var primaryRedness = AnimatableProperty(value: 0.0) + private var primaryColor = AnimatableProperty(value: CGRect(x: 1.0, y: 1.0, width: 1.0, height: 1.0)) private var primaryCornerRadius = AnimatableProperty(value: 0.63) private var secondarySize = AnimatableProperty(value: 0.34) @@ -145,13 +153,22 @@ final class ShutterBlobView: UIView { pipelineStateDescriptor.vertexFunction = loadedVertexProgram pipelineStateDescriptor.fragmentFunction = loadedFragmentProgram pipelineStateDescriptor.colorAttachments[0].pixelFormat = .bgra8Unorm + pipelineStateDescriptor.colorAttachments[0].isBlendingEnabled = true - pipelineStateDescriptor.colorAttachments[0].rgbBlendOperation = .add - pipelineStateDescriptor.colorAttachments[0].alphaBlendOperation = .add - pipelineStateDescriptor.colorAttachments[0].sourceRGBBlendFactor = .sourceAlpha - pipelineStateDescriptor.colorAttachments[0].sourceAlphaBlendFactor = .sourceAlpha + pipelineStateDescriptor.colorAttachments[0].sourceRGBBlendFactor = .one pipelineStateDescriptor.colorAttachments[0].destinationRGBBlendFactor = .oneMinusSourceAlpha + pipelineStateDescriptor.colorAttachments[0].rgbBlendOperation = .add + pipelineStateDescriptor.colorAttachments[0].sourceAlphaBlendFactor = .one pipelineStateDescriptor.colorAttachments[0].destinationAlphaBlendFactor = .oneMinusSourceAlpha + pipelineStateDescriptor.colorAttachments[0].alphaBlendOperation = .add + +// pipelineStateDescriptor.colorAttachments[0].isBlendingEnabled = true +// pipelineStateDescriptor.colorAttachments[0].rgbBlendOperation = .add +// pipelineStateDescriptor.colorAttachments[0].alphaBlendOperation = .add +// pipelineStateDescriptor.colorAttachments[0].sourceRGBBlendFactor = .sourceAlpha +// pipelineStateDescriptor.colorAttachments[0].sourceAlphaBlendFactor = .sourceAlpha +// pipelineStateDescriptor.colorAttachments[0].destinationRGBBlendFactor = .oneMinusSourceAlpha +// pipelineStateDescriptor.colorAttachments[0].destinationAlphaBlendFactor = .oneMinusSourceAlpha self.drawPassthroughPipelineState = try! device.makeRenderPipelineState(descriptor: pipelineStateDescriptor) @@ -184,7 +201,7 @@ final class ShutterBlobView: UIView { self.primaryWidth.update(value: state.primarySize.width, transition: transition) self.primaryHeight.update(value: state.primarySize.height, transition: transition) - self.primaryRedness.update(value: state.primaryRedness(tintColor: tintColor), transition: transition) + self.primaryColor.update(value: state.primaryColor(tintColor: tintColor), transition: transition) self.primaryCornerRadius.update(value: state.primaryCornerRadius, transition: transition) self.secondarySize.update(value: state.secondarySize, transition: transition) self.secondaryRedness.update(value: state.secondaryRedness, transition: transition) @@ -238,7 +255,6 @@ final class ShutterBlobView: UIView { self.primaryHeight, self.primaryOffsetX, self.primaryOffsetY, - self.primaryRedness, self.primaryCornerRadius, self.secondarySize, self.secondaryOffsetX, @@ -253,6 +269,9 @@ final class ShutterBlobView: UIView { hasAnimations = true } } + if self.primaryColor.tick(timestamp: timestamp) { + hasAnimations = true + } self.displayLink?.isPaused = !hasAnimations } @@ -316,7 +335,7 @@ final class ShutterBlobView: UIView { var primaryParameters = simd_float4( Float(self.primaryWidth.presentationValue), Float(self.primaryHeight.presentationValue), - Float(self.primaryRedness.presentationValue), + Float(0.0), Float(self.primaryCornerRadius.presentationValue) ) renderEncoder.setFragmentBytes(&primaryParameters, length: MemoryLayout.size, index: 1) @@ -327,17 +346,20 @@ final class ShutterBlobView: UIView { ) renderEncoder.setFragmentBytes(&primaryOffset, length: MemoryLayout.size, index: 2) + var primaryColor = simd_float3(Float(self.primaryColor.presentationValue.minX), Float(self.primaryColor.presentationValue.minY), Float(self.primaryColor.presentationValue.width)) + renderEncoder.setFragmentBytes(&primaryColor, length: MemoryLayout.stride, index: 3) + var secondaryParameters = simd_float2( Float(self.secondarySize.presentationValue), Float(self.secondaryRedness.presentationValue) ) - renderEncoder.setFragmentBytes(&secondaryParameters, length: MemoryLayout.size, index: 3) + renderEncoder.setFragmentBytes(&secondaryParameters, length: MemoryLayout.size, index: 4) var secondaryOffset = simd_float2( Float(self.secondaryOffsetX.presentationValue), Float(self.secondaryOffsetY.presentationValue) ) - renderEncoder.setFragmentBytes(&secondaryOffset, length: MemoryLayout.size, index: 4) + renderEncoder.setFragmentBytes(&secondaryOffset, length: MemoryLayout.size, index: 5) renderEncoder.drawPrimitives(type: .triangle, vertexStart: 0, vertexCount: 6, instanceCount: 1) renderEncoder.endEncoding() diff --git a/submodules/TelegramUI/Components/Chat/ChatTextInputPanelNode/Sources/ChatTextInputPanelComponent.swift b/submodules/TelegramUI/Components/Chat/ChatTextInputPanelNode/Sources/ChatTextInputPanelComponent.swift index 3822f99c74..6a442b3de7 100644 --- a/submodules/TelegramUI/Components/Chat/ChatTextInputPanelNode/Sources/ChatTextInputPanelComponent.swift +++ b/submodules/TelegramUI/Components/Chat/ChatTextInputPanelNode/Sources/ChatTextInputPanelComponent.swift @@ -57,6 +57,7 @@ public final class ChatTextInputPanelComponent: Component { public final class LeftAction: Equatable { public enum Kind: Equatable { + case empty case attach case toggleExpanded(isVisible: Bool, isExpanded: Bool, hasUnseen: Bool) } @@ -79,6 +80,7 @@ public final class ChatTextInputPanelComponent: Component { public final class RightAction: Equatable { public enum Kind: Equatable { + case empty case stars(count: Int, isFilled: Bool) } @@ -839,6 +841,8 @@ public final class ChatTextInputPanelComponent: Component { if let leftAction = component.leftAction { switch leftAction.kind { + case .empty: + panelNode.customLeftAction = .empty case .attach: panelNode.customLeftAction = nil case let .toggleExpanded(isVisible, isExpanded, hasUnseen): @@ -854,6 +858,8 @@ public final class ChatTextInputPanelComponent: Component { if let rightAction = component.rightAction { switch rightAction.kind { + case .empty: + panelNode.customRightAction = .empty case let .stars(count, isFilled): panelNode.customRightAction = .stars(count: count, isFilled: isFilled, action: { sourceView in rightAction.action(sourceView) diff --git a/submodules/TelegramUI/Components/Chat/ChatTextInputPanelNode/Sources/ChatTextInputPanelNode.swift b/submodules/TelegramUI/Components/Chat/ChatTextInputPanelNode/Sources/ChatTextInputPanelNode.swift index bbf684d140..9dbdda3121 100644 --- a/submodules/TelegramUI/Components/Chat/ChatTextInputPanelNode/Sources/ChatTextInputPanelNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatTextInputPanelNode/Sources/ChatTextInputPanelNode.swift @@ -386,10 +386,12 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg } public enum LeftAction { + case empty case toggleExpanded(isVisible: Bool, isExpanded: Bool, hasUnseen: Bool) } public enum RightAction { + case empty case stars(count: Int, isFilled: Bool, action: (UIView) -> Void, longPressAction: ((UIView) -> Void)?) } @@ -1751,7 +1753,7 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg if let customLeftAction = self.customLeftAction { switch customLeftAction { - case .toggleExpanded: + case .empty, .toggleExpanded: self.attachmentButtonIcon.image = nil self.attachmentButtonIcon.tintColor = interfaceState.theme.chat.inputPanel.panelControlColor } @@ -1789,10 +1791,9 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg } if wasEditingMedia != isEditingMedia || hadMediaDraft != hasMediaDraft || isFirstTime { - if let customLeftAction = self.customLeftAction { switch customLeftAction { - case .toggleExpanded: + case .empty, .toggleExpanded: self.attachmentButtonIcon.image = nil self.attachmentButtonIcon.tintColor = interfaceState.theme.chat.inputPanel.panelControlColor } @@ -1945,6 +1946,8 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg if let customLeftAction = self.customLeftAction { switch customLeftAction { + case .empty: + break case let .toggleExpanded(_, isExpanded, hasUnseen): let commentsButtonIcon: RasterizedCompositionMonochromeLayer if let current = self.commentsButtonIcon { @@ -2260,7 +2263,9 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg } if inputHasText || self.extendedSearchLayout || hasMediaDraft || hasForward { } else { - if let starReactionButtonSize { + if let customRightAction = self.customRightAction, case .empty = customRightAction { + textFieldInsets.right = 8.0 + } else if let starReactionButtonSize { textFieldInsets.right = 14.0 + starReactionButtonSize.width } else { textFieldInsets.right = 54.0 @@ -2269,8 +2274,13 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg if mediaRecordingState != nil { textFieldInsets.left = 8.0 } - if let customLeftAction = self.customLeftAction, case let .toggleExpanded(isVisible, _, _) = customLeftAction, !isVisible { - textFieldInsets.left = 8.0 + if let customLeftAction = self.customLeftAction { + switch customLeftAction { + case .empty, .toggleExpanded(false, _, _): + textFieldInsets.left = 8.0 + default: + break + } } var audioRecordingItemsAlpha: CGFloat = 1.0 diff --git a/submodules/TelegramUI/Components/MediaEditor/MetalResources/EditorVideo.metal b/submodules/TelegramUI/Components/MediaEditor/MetalResources/EditorVideo.metal index e254e1f707..efab4ee738 100644 --- a/submodules/TelegramUI/Components/MediaEditor/MetalResources/EditorVideo.metal +++ b/submodules/TelegramUI/Components/MediaEditor/MetalResources/EditorVideo.metal @@ -4,25 +4,25 @@ using namespace metal; -static inline float sRGBnonLinearNormToLinear(float normV) { - if (normV <= 0.04045f) { - normV *= (1.0f / 12.92f); - } else { - const float a = 0.055f; - const float gamma = 2.4f; - normV = (normV + a) * (1.0f / (1.0f + a)); - normV = pow(normV, gamma); - } - return normV; -} +//static inline float sRGBnonLinearNormToLinear(float normV) { +// if (normV <= 0.04045f) { +// normV *= (1.0f / 12.92f); +// } else { +// const float a = 0.055f; +// const float gamma = 2.4f; +// normV = (normV + a) * (1.0f / (1.0f + a)); +// normV = pow(normV, gamma); +// } +// return normV; +//} -static inline float4 sRGBGammaDecode(const float4 rgba) { - float4 result = rgba; - result.r = sRGBnonLinearNormToLinear(rgba.r); - result.g = sRGBnonLinearNormToLinear(rgba.g); - result.b = sRGBnonLinearNormToLinear(rgba.b); - return rgba; -} +//static inline float4 sRGBGammaDecode(const float4 rgba) { +// float4 result = rgba; +// result.r = sRGBnonLinearNormToLinear(rgba.r); +// result.g = sRGBnonLinearNormToLinear(rgba.g); +// result.b = sRGBnonLinearNormToLinear(rgba.b); +// return result; +//} static inline float4 BT709Decode(const float Y, const float Cb, const float Cr) { float Yn = Y; @@ -57,6 +57,6 @@ fragment float4 bt709ToRGBFragmentShader(RasterizerData in [[stage_in]], float Cr = float(uvSamples[1]); float4 pixel = BT709Decode(Y, Cb, Cr); - pixel = sRGBGammaDecode(pixel); + //pixel = sRGBGammaDecode(pixel); return pixel; } diff --git a/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditorComposer.swift b/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditorComposer.swift index eafbe03602..8d16f83166 100644 --- a/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditorComposer.swift +++ b/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditorComposer.swift @@ -92,7 +92,12 @@ public final class MediaEditorComposer { let ciContext: CIContext? private var textureCache: CVMetalTextureCache? - private let values: MediaEditorValues + public var values: MediaEditorValues { + didSet { + self.renderChain.update(values: self.values) + self.renderer.videoFinishPass.update(values: self.values, videoDuration: nil, additionalVideoDuration: nil) + } + } private let dimensions: CGSize private let outputDimensions: CGSize private let textScale: CGFloat diff --git a/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditorRenderer.swift b/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditorRenderer.swift index 976eab7acf..a4ad69c6f0 100644 --- a/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditorRenderer.swift +++ b/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditorRenderer.swift @@ -21,19 +21,6 @@ public final class VideoPixelBuffer { } } -final class RenderingContext { - let device: MTLDevice - let commandBuffer: MTLCommandBuffer - - init( - device: MTLDevice, - commandBuffer: MTLCommandBuffer - ) { - self.device = device - self.commandBuffer = commandBuffer - } -} - protocol RenderPass: AnyObject { func setup(device: MTLDevice, library: MTLLibrary) func process(input: MTLTexture, device: MTLDevice, commandBuffer: MTLCommandBuffer) -> MTLTexture? diff --git a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorStoredState.swift b/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditorStoredState.swift similarity index 94% rename from submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorStoredState.swift rename to submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditorStoredState.swift index b1fc320f57..fb8dfec786 100644 --- a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorStoredState.swift +++ b/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditorStoredState.swift @@ -3,7 +3,6 @@ import UIKit import SwiftSignalKit import TelegramCore import TelegramUIPreferences -import MediaEditor public final class MediaEditorStoredTextSettings: Codable { private enum CodingKeys: String, CodingKey { @@ -111,7 +110,7 @@ public final class MediaEditorStoredState: Codable { } } -func mediaEditorStoredState(engine: TelegramEngine) -> Signal { +public func mediaEditorStoredState(engine: TelegramEngine) -> Signal { let key = EngineDataBuffer(length: 4) key.setInt32(0, value: 0) @@ -121,7 +120,7 @@ func mediaEditorStoredState(engine: TelegramEngine) -> Signal MediaEditorStoredState?) -> Signal { +public func updateMediaEditorStoredStateInteractively(engine: TelegramEngine, _ f: @escaping (MediaEditorStoredState?) -> MediaEditorStoredState?) -> Signal { let key = EngineDataBuffer(length: 4) key.setInt32(0, value: 0) diff --git a/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditorValues.swift b/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditorValues.swift index 373483fe9b..480ffbe848 100644 --- a/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditorValues.swift +++ b/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditorValues.swift @@ -41,17 +41,21 @@ public enum EditorToolKey: Int32, CaseIterable { public struct VideoPositionChange: Codable, Equatable { private enum CodingKeys: String, CodingKey { case additional + case translationFrom case timestamp } public let additional: Bool + public let translationFrom: CGPoint? public let timestamp: Double public init( additional: Bool, + translationFrom: CGPoint? = nil, timestamp: Double ) { self.additional = additional + self.translationFrom = translationFrom self.timestamp = timestamp } } @@ -888,10 +892,14 @@ public final class MediaEditorValues: Codable, Equatable, CustomStringConvertibl 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, collage: self.collage, 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, collageTrackSamples: self.collageTrackSamples, coverImageTimestamp: self.coverImageTimestamp, coverDimensions: self.coverDimensions, qualityPreset: self.qualityPreset) } - func withUpdatedAdditionalVideo(position: CGPoint, scale: CGFloat, rotation: CGFloat) -> MediaEditorValues { + public 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, collage: self.collage, 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, collageTrackSamples: self.collageTrackSamples, coverImageTimestamp: self.coverImageTimestamp, coverDimensions: self.coverDimensions, qualityPreset: self.qualityPreset) } + public func withUpdatedAdditionalVideoPositionChanges(additionalVideoPositionChanges: [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: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, collage: self.collage, 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, collageTrackSamples: self.collageTrackSamples, coverImageTimestamp: self.coverImageTimestamp, coverDimensions: self.coverDimensions, qualityPreset: self.qualityPreset) + } + func withUpdatedAdditionalVideoTrimRange(_ additionalVideoTrimRange: Range?) -> 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, collage: self.collage, 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, collageTrackSamples: self.collageTrackSamples, coverImageTimestamp: self.coverImageTimestamp, coverDimensions: self.coverDimensions, qualityPreset: self.qualityPreset) } diff --git a/submodules/TelegramUI/Components/MediaEditor/Sources/VideoFinishPass.swift b/submodules/TelegramUI/Components/MediaEditor/Sources/VideoFinishPass.swift index 039d7d4c77..65206a070c 100644 --- a/submodules/TelegramUI/Components/MediaEditor/Sources/VideoFinishPass.swift +++ b/submodules/TelegramUI/Components/MediaEditor/Sources/VideoFinishPass.swift @@ -506,6 +506,10 @@ final class VideoFinishPass: RenderPass { let mirroring: Bool let baseScale: CGFloat + func with(position: CGPoint) -> VideoPosition { + return VideoPosition(position: position, size: self.size, scale: self.scale, rotation: self.rotation, mirroring: self.mirroring, baseScale: baseScale) + } + func with(size: CGSize, baseScale: CGFloat) -> VideoPosition { return VideoPosition(position: self.position, size: size, scale: self.scale, rotation: self.rotation, mirroring: self.mirroring, baseScale: baseScale) } @@ -568,6 +572,9 @@ final class VideoFinishPass: RenderPass { if let additionalInput { var previousChange: VideoPositionChange? for change in self.videoPositionChanges { + if let _ = change.translationFrom { + continue + } if timestamp >= change.timestamp { previousChange = change } @@ -595,6 +602,29 @@ final class VideoFinishPass: RenderPass { } } + var translationTransitionFraction = 1.0 + var previousAdditionalPosition = additionalPosition + if let _ = additionalInput { + var previousChange: VideoPositionChange? + for change in self.videoPositionChanges { + guard let _ = change.translationFrom else { + continue + } + if timestamp >= change.timestamp { + previousChange = change + } + if timestamp < change.timestamp { + break + } + } + if let previousChange, let translationFrom = previousChange.translationFrom { + previousAdditionalPosition = previousAdditionalPosition.with(position: translationFrom) + if previousChange.timestamp > 0.0 && timestamp < previousChange.timestamp + transitionDuration { + translationTransitionFraction = (timestamp - previousChange.timestamp) / transitionDuration + } + } + } + var backgroundVideoState = VideoState(texture: backgroundTexture, textureRotation: backgroundTextureRotation, position: mainPosition, roundness: 0.0, alpha: 1.0) var foregroundVideoState: VideoState? var disappearingVideoState: VideoState? @@ -615,6 +645,10 @@ final class VideoFinishPass: RenderPass { foregroundAlpha = min(1.0, max(0.0, Float(transitionFraction) * 2.5)) } + if translationTransitionFraction < 1.0 { + let springFraction = lookupSpringValue(translationTransitionFraction) + foregroundPosition = previousAdditionalPosition.mixed(with: foregroundPosition, fraction: springFraction) + } var isVisible = true var trimRangeLowerBound: Double? diff --git a/submodules/TelegramUI/Components/ShareWithPeersScreen/Sources/LiveStreamSettingsScreen.swift b/submodules/TelegramUI/Components/ShareWithPeersScreen/Sources/LiveStreamSettingsScreen.swift index e616f6feba..38c853acc6 100644 --- a/submodules/TelegramUI/Components/ShareWithPeersScreen/Sources/LiveStreamSettingsScreen.swift +++ b/submodules/TelegramUI/Components/ShareWithPeersScreen/Sources/LiveStreamSettingsScreen.swift @@ -746,38 +746,38 @@ final class LiveStreamSettingsScreenComponent: Component { action: nil )))) - var pinTitle = "Post to My Profile" - var pinInfo = "Keep this live on your profile." - if screenState.sendAsPeerId?.namespace == Namespaces.Peer.CloudChannel { - pinTitle = "Post to Channel Profile" - pinInfo = "Keep this live on channel profile." - } - - settingsSectionItems.append(AnyComponentWithIdentity(id: "pin", component: AnyComponent(ListActionItemComponent( - theme: theme, - style: .glass, - title: AnyComponent(MultilineTextComponent( - text: .plain(NSAttributedString( - string: pinTitle, - font: Font.regular(presentationData.listsFontSize.baseDisplaySize), - textColor: theme.list.itemPrimaryTextColor - )), - maximumNumberOfLines: 1 - )), - accessory: .toggle(ListActionItemComponent.Toggle(style: .regular, isOn: screenState.pin, action: { [weak self] _ in - guard let self else { - return - } - component.stateContext.pin = !component.stateContext.pin - self.state?.updated(transition: .spring(duration: 0.4)) - })), - action: nil - )))) - - let settingsSectionFooterComponent = AnyComponent(MultilineTextComponent( - text: .plain(NSAttributedString(string: pinInfo, font: footerTextFont, textColor: footerTextColor)), - maximumNumberOfLines: 0 - )) +// var pinTitle = "Post to My Profile" +// var pinInfo = "Keep this live on your profile." +// if screenState.sendAsPeerId?.namespace == Namespaces.Peer.CloudChannel { +// pinTitle = "Post to Channel Profile" +// pinInfo = "Keep this live on channel profile." +// } +// +// settingsSectionItems.append(AnyComponentWithIdentity(id: "pin", component: AnyComponent(ListActionItemComponent( +// theme: theme, +// style: .glass, +// title: AnyComponent(MultilineTextComponent( +// text: .plain(NSAttributedString( +// string: pinTitle, +// font: Font.regular(presentationData.listsFontSize.baseDisplaySize), +// textColor: theme.list.itemPrimaryTextColor +// )), +// maximumNumberOfLines: 1 +// )), +// accessory: .toggle(ListActionItemComponent.Toggle(style: .regular, isOn: screenState.pin, action: { [weak self] _ in +// guard let self else { +// return +// } +// component.stateContext.pin = !component.stateContext.pin +// self.state?.updated(transition: .spring(duration: 0.4)) +// })), +// action: nil +// )))) +// +// let settingsSectionFooterComponent = AnyComponent(MultilineTextComponent( +// text: .plain(NSAttributedString(string: pinInfo, font: footerTextFont, textColor: footerTextColor)), +// maximumNumberOfLines: 0 +// )) let settingsSectionSize = self.settingsSection.update( transition: transition, @@ -785,7 +785,7 @@ final class LiveStreamSettingsScreenComponent: Component { theme: theme, style: .glass, header: nil, - footer: settingsSectionFooterComponent, + footer: nil, items: settingsSectionItems )), environment: {}, diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryContentLiveChatComponent.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryContentLiveChatComponent.swift index 8075fd2b21..2497509730 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryContentLiveChatComponent.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryContentLiveChatComponent.swift @@ -35,6 +35,7 @@ final class StoryContentLiveChatComponent: Component { let call: PresentationGroupCall let storyPeerId: EnginePeer.Id let insets: UIEdgeInsets + let isEmbeddedInCamera: Bool let controller: () -> ViewController? init( @@ -45,6 +46,7 @@ final class StoryContentLiveChatComponent: Component { call: PresentationGroupCall, storyPeerId: EnginePeer.Id, insets: UIEdgeInsets, + isEmbeddedInCamera: Bool, controller: @escaping () -> ViewController? ) { self.external = external @@ -54,6 +56,7 @@ final class StoryContentLiveChatComponent: Component { self.call = call self.storyPeerId = storyPeerId self.insets = insets + self.isEmbeddedInCamera = isEmbeddedInCamera self.controller = controller } @@ -79,6 +82,9 @@ final class StoryContentLiveChatComponent: Component { if lhs.insets != rhs.insets { return false } + if lhs.isEmbeddedInCamera != rhs.isEmbeddedInCamera { + return false + } return true } @@ -255,6 +261,10 @@ final class StoryContentLiveChatComponent: Component { } } + if let component = self.component, component.isEmbeddedInCamera && result === self.listContainer { + return nil + } + return result } diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemContentComponent.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemContentComponent.swift index 7c38eac56f..63ac495965 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemContentComponent.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemContentComponent.swift @@ -446,7 +446,7 @@ final class StoryItemContentComponent: Component { isChannel: false, invite: nil, joinAsPeerId: nil, - isStream: true, + isStream: !(component.isEmbeddedInCamera && liveStream.kind == .rtc), keyPair: nil, conferenceSourceId: nil, isConference: false, @@ -943,6 +943,7 @@ final class StoryItemContentComponent: Component { call: mediaStreamCall, storyPeerId: component.peer.id, insets: environment.containerInsets, + isEmbeddedInCamera: component.isEmbeddedInCamera, controller: { [weak self] in guard let self, let component = self.component else { return nil diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift index eff241602f..4906ee9031 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift @@ -1342,6 +1342,14 @@ public final class StoryItemSetContainerComponent: Component { return self.itemsContainerView } + if let component = self.component, component.isEmbeddedInCamera { + for (_, visibleItem) in self.visibleItems { + if result === visibleItem.contentContainerView { + return nil + } + } + } + return result } @@ -4285,30 +4293,35 @@ public final class StoryItemSetContainerComponent: Component { let centerInfoItemSize = currentCenterInfoItem.view.update( transition: .immediate, - component: AnyComponent(PlainButtonComponent(content: currentCenterInfoItem.component, effectAlignment: .center, action: { [weak self] in - guard let self, let component = self.component else { - return - } - if let forwardInfo = component.slice.item.storyItem.forwardInfo, case let .known(peer, _, _) = forwardInfo { - if peer.id == component.context.account.peerId { - self.navigateToMyStories() - } else { - self.navigateToPeer(peer: peer, chat: false) + component: AnyComponent(PlainButtonComponent( + content: currentCenterInfoItem.component, + effectAlignment: .center, + action: { [weak self] in + guard let self, let component = self.component else { + return } - } else if let author = component.slice.item.storyItem.author { - if author.id == component.context.account.peerId { - self.navigateToMyStories() + if let forwardInfo = component.slice.item.storyItem.forwardInfo, case let .known(peer, _, _) = forwardInfo { + if peer.id == component.context.account.peerId { + self.navigateToMyStories() + } else { + self.navigateToPeer(peer: peer, chat: false) + } + } else if let author = component.slice.item.storyItem.author { + if author.id == component.context.account.peerId { + self.navigateToMyStories() + } else { + self.navigateToPeer(peer: author, chat: false) + } } else { - self.navigateToPeer(peer: author, chat: false) + if component.slice.effectivePeer.id == component.context.account.peerId { + self.navigateToMyStories() + } else { + self.navigateToPeer(peer: component.slice.effectivePeer, chat: false) + } } - } else { - if component.slice.effectivePeer.id == component.context.account.peerId { - self.navigateToMyStories() - } else { - self.navigateToPeer(peer: component.slice.effectivePeer, chat: false) - } - } - })), + }, + isEnabled: !component.isEmbeddedInCamera + )), environment: {}, containerSize: CGSize(width: headerRightOffset - 34.0, height: 44.0) ) @@ -4333,16 +4346,21 @@ public final class StoryItemSetContainerComponent: Component { let leftInfoItemSize = currentLeftInfoItem.view.update( transition: .immediate, - component: AnyComponent(PlainButtonComponent(content: currentLeftInfoItem.component, effectAlignment: .center, action: { [weak self] in - guard let self, let component = self.component else { - return - } - if component.slice.effectivePeer.id == component.context.account.peerId { - self.navigateToMyStories() - } else { - self.navigateToPeer(peer: component.slice.effectivePeer, chat: false) - } - })), + component: AnyComponent(PlainButtonComponent( + content: currentLeftInfoItem.component, + effectAlignment: .center, + action: { [weak self] in + guard let self, let component = self.component else { + return + } + if component.slice.effectivePeer.id == component.context.account.peerId { + self.navigateToMyStories() + } else { + self.navigateToPeer(peer: component.slice.effectivePeer, chat: false) + } + }, + isEnabled: !component.isEmbeddedInCamera + )), environment: {}, containerSize: CGSize(width: 32.0, height: 32.0) )