mirror of
https://github.com/TelegramMessenger/Telegram-iOS.git
synced 2026-07-06 03:33:41 +02:00
Various improvements
This commit is contained in:
parent
a05ef5e022
commit
22945ebb31
22 changed files with 662 additions and 363 deletions
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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<EnvironmentType>, 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<Empty>, 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)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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<Bool>(false)
|
||||
private let animateFlipAction = ActionSlot<Void>()
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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<Empty>()
|
||||
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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<CGFloat>(value: 0.63)
|
||||
private var primaryOffsetX = AnimatableProperty<CGFloat>(value: 0.0)
|
||||
private var primaryOffsetY = AnimatableProperty<CGFloat>(value: 0.0)
|
||||
private var primaryRedness = AnimatableProperty<CGFloat>(value: 0.0)
|
||||
private var primaryColor = AnimatableProperty<CGRect>(value: CGRect(x: 1.0, y: 1.0, width: 1.0, height: 1.0))
|
||||
private var primaryCornerRadius = AnimatableProperty<CGFloat>(value: 0.63)
|
||||
|
||||
private var secondarySize = AnimatableProperty<CGFloat>(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<simd_float3>.size, index: 1)
|
||||
|
|
@ -327,17 +346,20 @@ final class ShutterBlobView: UIView {
|
|||
)
|
||||
renderEncoder.setFragmentBytes(&primaryOffset, length: MemoryLayout<simd_float2>.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<simd_float3>.stride, index: 3)
|
||||
|
||||
var secondaryParameters = simd_float2(
|
||||
Float(self.secondarySize.presentationValue),
|
||||
Float(self.secondaryRedness.presentationValue)
|
||||
)
|
||||
renderEncoder.setFragmentBytes(&secondaryParameters, length: MemoryLayout<simd_float4>.size, index: 3)
|
||||
renderEncoder.setFragmentBytes(&secondaryParameters, length: MemoryLayout<simd_float4>.size, index: 4)
|
||||
|
||||
var secondaryOffset = simd_float2(
|
||||
Float(self.secondaryOffsetX.presentationValue),
|
||||
Float(self.secondaryOffsetY.presentationValue)
|
||||
)
|
||||
renderEncoder.setFragmentBytes(&secondaryOffset, length: MemoryLayout<simd_float2>.size, index: 4)
|
||||
renderEncoder.setFragmentBytes(&secondaryOffset, length: MemoryLayout<simd_float2>.size, index: 5)
|
||||
|
||||
renderEncoder.drawPrimitives(type: .triangle, vertexStart: 0, vertexCount: 6, instanceCount: 1)
|
||||
renderEncoder.endEncoding()
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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?
|
||||
|
|
|
|||
|
|
@ -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<MediaEditorStoredState?, NoError> {
|
||||
public func mediaEditorStoredState(engine: TelegramEngine) -> Signal<MediaEditorStoredState?, NoError> {
|
||||
let key = EngineDataBuffer(length: 4)
|
||||
key.setInt32(0, value: 0)
|
||||
|
||||
|
|
@ -121,7 +120,7 @@ func mediaEditorStoredState(engine: TelegramEngine) -> Signal<MediaEditorStoredS
|
|||
}
|
||||
}
|
||||
|
||||
func updateMediaEditorStoredStateInteractively(engine: TelegramEngine, _ f: @escaping (MediaEditorStoredState?) -> MediaEditorStoredState?) -> Signal<Never, NoError> {
|
||||
public func updateMediaEditorStoredStateInteractively(engine: TelegramEngine, _ f: @escaping (MediaEditorStoredState?) -> MediaEditorStoredState?) -> Signal<Never, NoError> {
|
||||
let key = EngineDataBuffer(length: 4)
|
||||
key.setInt32(0, value: 0)
|
||||
|
||||
|
|
@ -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<Double>?) -> MediaEditorValues {
|
||||
return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, 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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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?
|
||||
|
|
|
|||
|
|
@ -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: {},
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue