mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-07-25 20:50:47 +00:00
Merge commit '8bcc38c24e25d27697235a93d046e78320075276'
This commit is contained in:
commit
e17b8380a2
@ -57,29 +57,29 @@ final class CameraDeviceContext {
|
|||||||
self.output = CameraOutput(exclusive: exclusive, ciContext: ciContext, use32BGRA: use32BGRA)
|
self.output = CameraOutput(exclusive: exclusive, ciContext: ciContext, use32BGRA: use32BGRA)
|
||||||
}
|
}
|
||||||
|
|
||||||
func configure(position: Camera.Position, previewView: CameraSimplePreviewView?, audio: Bool, photo: Bool, metadata: Bool, preferWide: Bool = false, preferLowerFramerate: Bool = false) {
|
func configure(position: Camera.Position, previewView: CameraSimplePreviewView?, audio: Bool, photo: Bool, metadata: Bool, preferWide: Bool = false, preferLowerFramerate: Bool = false, switchAudio: Bool = true) {
|
||||||
guard let session = self.session else {
|
guard let session = self.session else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
self.previewView = previewView
|
self.previewView = previewView
|
||||||
|
|
||||||
self.device.configure(for: session, position: position, dual: !exclusive || additional)
|
self.device.configure(for: session, position: position, dual: !self.exclusive || self.additional, switchAudio: switchAudio)
|
||||||
self.device.configureDeviceFormat(maxDimensions: self.maxDimensions(additional: self.additional, preferWide: preferWide), maxFramerate: self.preferredMaxFrameRate(useLower: preferLowerFramerate))
|
self.device.configureDeviceFormat(maxDimensions: self.maxDimensions(additional: self.additional, preferWide: preferWide), maxFramerate: self.preferredMaxFrameRate(useLower: preferLowerFramerate))
|
||||||
self.input.configure(for: session, device: self.device, audio: audio)
|
self.input.configure(for: session, device: self.device, audio: audio && switchAudio)
|
||||||
self.output.configure(for: session, device: self.device, input: self.input, previewView: previewView, audio: audio, photo: photo, metadata: metadata)
|
self.output.configure(for: session, device: self.device, input: self.input, previewView: previewView, audio: audio && switchAudio, photo: photo, metadata: metadata)
|
||||||
|
|
||||||
self.output.configureVideoStabilization()
|
self.output.configureVideoStabilization()
|
||||||
|
|
||||||
self.device.resetZoom(neutral: self.exclusive || !self.additional)
|
self.device.resetZoom(neutral: self.exclusive || !self.additional)
|
||||||
}
|
}
|
||||||
|
|
||||||
func invalidate() {
|
func invalidate(switchAudio: Bool = true) {
|
||||||
guard let session = self.session else {
|
guard let session = self.session else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
self.output.invalidate(for: session)
|
self.output.invalidate(for: session, switchAudio: switchAudio)
|
||||||
self.input.invalidate(for: session)
|
self.input.invalidate(for: session, switchAudio: switchAudio)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func maxDimensions(additional: Bool, preferWide: Bool) -> CMVideoDimensions {
|
private func maxDimensions(additional: Bool, preferWide: Bool) -> CMVideoDimensions {
|
||||||
@ -248,7 +248,8 @@ private final class CameraContext {
|
|||||||
mainDeviceContext.output.markPositionChange(position: targetPosition)
|
mainDeviceContext.output.markPositionChange(position: targetPosition)
|
||||||
} else {
|
} else {
|
||||||
self.configure {
|
self.configure {
|
||||||
self.mainDeviceContext?.invalidate()
|
let isRoundVideo = self.initialConfiguration.isRoundVideo
|
||||||
|
self.mainDeviceContext?.invalidate(switchAudio: !isRoundVideo)
|
||||||
|
|
||||||
let targetPosition: Camera.Position
|
let targetPosition: Camera.Position
|
||||||
if case .back = mainDeviceContext.device.position {
|
if case .back = mainDeviceContext.device.position {
|
||||||
@ -260,7 +261,14 @@ private final class CameraContext {
|
|||||||
self._positionPromise.set(targetPosition)
|
self._positionPromise.set(targetPosition)
|
||||||
self.modeChange = .position
|
self.modeChange = .position
|
||||||
|
|
||||||
mainDeviceContext.configure(position: targetPosition, previewView: self.simplePreviewView, audio: self.initialConfiguration.audio, photo: self.initialConfiguration.photo, metadata: self.initialConfiguration.metadata, preferWide: self.initialConfiguration.preferWide, preferLowerFramerate: self.initialConfiguration.preferLowerFramerate)
|
|
||||||
|
let preferWide = self.initialConfiguration.preferWide || isRoundVideo
|
||||||
|
let preferLowerFramerate = self.initialConfiguration.preferLowerFramerate || isRoundVideo
|
||||||
|
|
||||||
|
mainDeviceContext.configure(position: targetPosition, previewView: self.simplePreviewView, audio: self.initialConfiguration.audio, photo: self.initialConfiguration.photo, metadata: self.initialConfiguration.metadata, preferWide: preferWide, preferLowerFramerate: preferLowerFramerate, switchAudio: !isRoundVideo)
|
||||||
|
if isRoundVideo {
|
||||||
|
mainDeviceContext.output.markPositionChange(position: targetPosition)
|
||||||
|
}
|
||||||
|
|
||||||
self.queue.after(0.5) {
|
self.queue.after(0.5) {
|
||||||
self.modeChange = .none
|
self.modeChange = .none
|
||||||
@ -277,7 +285,10 @@ private final class CameraContext {
|
|||||||
self.positionValue = position
|
self.positionValue = position
|
||||||
self.modeChange = .position
|
self.modeChange = .position
|
||||||
|
|
||||||
self.mainDeviceContext?.configure(position: position, previewView: self.simplePreviewView, audio: self.initialConfiguration.audio, photo: self.initialConfiguration.photo, metadata: self.initialConfiguration.metadata)
|
let preferWide = self.initialConfiguration.preferWide || (self.positionValue == .front && self.initialConfiguration.isRoundVideo)
|
||||||
|
let preferLowerFramerate = self.initialConfiguration.preferLowerFramerate || self.initialConfiguration.isRoundVideo
|
||||||
|
|
||||||
|
self.mainDeviceContext?.configure(position: position, previewView: self.simplePreviewView, audio: self.initialConfiguration.audio, photo: self.initialConfiguration.photo, metadata: self.initialConfiguration.metadata, preferWide: preferWide, preferLowerFramerate: preferLowerFramerate)
|
||||||
|
|
||||||
self.queue.after(0.5) {
|
self.queue.after(0.5) {
|
||||||
self.modeChange = .none
|
self.modeChange = .none
|
||||||
@ -342,8 +353,11 @@ private final class CameraContext {
|
|||||||
self.additionalDeviceContext?.invalidate()
|
self.additionalDeviceContext?.invalidate()
|
||||||
self.additionalDeviceContext = nil
|
self.additionalDeviceContext = nil
|
||||||
|
|
||||||
self.mainDeviceContext = CameraDeviceContext(session: self.session, exclusive: true, additional: false, ciContext: self.ciContext, use32BGRA: false)
|
let preferWide = self.initialConfiguration.preferWide || self.initialConfiguration.isRoundVideo
|
||||||
self.mainDeviceContext?.configure(position: self.positionValue, previewView: self.simplePreviewView, audio: self.initialConfiguration.audio, photo: self.initialConfiguration.photo, metadata: self.initialConfiguration.metadata, preferWide: self.initialConfiguration.preferWide, preferLowerFramerate: self.initialConfiguration.preferLowerFramerate)
|
let preferLowerFramerate = self.initialConfiguration.preferLowerFramerate || self.initialConfiguration.isRoundVideo
|
||||||
|
|
||||||
|
self.mainDeviceContext = CameraDeviceContext(session: self.session, exclusive: true, additional: false, ciContext: self.ciContext, use32BGRA: self.initialConfiguration.isRoundVideo)
|
||||||
|
self.mainDeviceContext?.configure(position: self.positionValue, previewView: self.simplePreviewView, audio: self.initialConfiguration.audio, photo: self.initialConfiguration.photo, metadata: self.initialConfiguration.metadata, preferWide: preferWide, preferLowerFramerate: preferLowerFramerate)
|
||||||
}
|
}
|
||||||
self.mainDeviceContext?.output.processSampleBuffer = { [weak self] sampleBuffer, pixelBuffer, connection in
|
self.mainDeviceContext?.output.processSampleBuffer = { [weak self] sampleBuffer, pixelBuffer, connection in
|
||||||
guard let self, let mainDeviceContext = self.mainDeviceContext else {
|
guard let self, let mainDeviceContext = self.mainDeviceContext else {
|
||||||
|
@ -29,7 +29,7 @@ final class CameraDevice {
|
|||||||
|
|
||||||
public private(set) var audioDevice: AVCaptureDevice? = nil
|
public private(set) var audioDevice: AVCaptureDevice? = nil
|
||||||
|
|
||||||
func configure(for session: CameraSession, position: Camera.Position, dual: Bool) {
|
func configure(for session: CameraSession, position: Camera.Position, dual: Bool, switchAudio: Bool) {
|
||||||
self.position = position
|
self.position = position
|
||||||
|
|
||||||
var selectedDevice: AVCaptureDevice?
|
var selectedDevice: AVCaptureDevice?
|
||||||
@ -57,8 +57,10 @@ final class CameraDevice {
|
|||||||
self.videoDevice = selectedDevice
|
self.videoDevice = selectedDevice
|
||||||
self.videoDevicePromise.set(.single(selectedDevice))
|
self.videoDevicePromise.set(.single(selectedDevice))
|
||||||
|
|
||||||
|
if switchAudio {
|
||||||
self.audioDevice = AVCaptureDevice.default(for: .audio)
|
self.audioDevice = AVCaptureDevice.default(for: .audio)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func configureDeviceFormat(maxDimensions: CMVideoDimensions, maxFramerate: Double) {
|
func configureDeviceFormat(maxDimensions: CMVideoDimensions, maxFramerate: Double) {
|
||||||
guard let device = self.videoDevice else {
|
guard let device = self.videoDevice else {
|
||||||
|
@ -14,8 +14,11 @@ class CameraInput {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func invalidate(for session: CameraSession) {
|
func invalidate(for session: CameraSession, switchAudio: Bool = true) {
|
||||||
for input in session.session.inputs {
|
for input in session.session.inputs {
|
||||||
|
if !switchAudio && input === self.audioInput {
|
||||||
|
continue
|
||||||
|
}
|
||||||
session.session.removeInput(input)
|
session.session.removeInput(input)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -190,7 +190,7 @@ final class CameraOutput: NSObject {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func invalidate(for session: CameraSession) {
|
func invalidate(for session: CameraSession, switchAudio: Bool = true) {
|
||||||
if #available(iOS 13.0, *) {
|
if #available(iOS 13.0, *) {
|
||||||
if let previewConnection = self.previewConnection {
|
if let previewConnection = self.previewConnection {
|
||||||
if session.session.connections.contains(where: { $0 === previewConnection }) {
|
if session.session.connections.contains(where: { $0 === previewConnection }) {
|
||||||
@ -214,7 +214,7 @@ final class CameraOutput: NSObject {
|
|||||||
if session.session.outputs.contains(where: { $0 === self.videoOutput }) {
|
if session.session.outputs.contains(where: { $0 === self.videoOutput }) {
|
||||||
session.session.removeOutput(self.videoOutput)
|
session.session.removeOutput(self.videoOutput)
|
||||||
}
|
}
|
||||||
if session.session.outputs.contains(where: { $0 === self.audioOutput }) {
|
if switchAudio, session.session.outputs.contains(where: { $0 === self.audioOutput }) {
|
||||||
session.session.removeOutput(self.audioOutput)
|
session.session.removeOutput(self.audioOutput)
|
||||||
}
|
}
|
||||||
if session.session.outputs.contains(where: { $0 === self.photoOutput }) {
|
if session.session.outputs.contains(where: { $0 === self.photoOutput }) {
|
||||||
@ -409,6 +409,14 @@ final class CameraOutput: NSObject {
|
|||||||
private weak var masterOutput: CameraOutput?
|
private weak var masterOutput: CameraOutput?
|
||||||
|
|
||||||
private var lastSampleTimestamp: CMTime?
|
private var lastSampleTimestamp: CMTime?
|
||||||
|
|
||||||
|
private var needsCrossfadeTransition = false
|
||||||
|
private var crossfadeTransitionStart: Double = 0.0
|
||||||
|
|
||||||
|
private var needsSwitchSampleOffset = false
|
||||||
|
private var lastAudioSampleTime: CMTime?
|
||||||
|
private var videoSwitchSampleTimeOffset: CMTime?
|
||||||
|
|
||||||
func processVideoRecording(_ sampleBuffer: CMSampleBuffer, fromAdditionalOutput: Bool) {
|
func processVideoRecording(_ sampleBuffer: CMSampleBuffer, fromAdditionalOutput: Bool) {
|
||||||
guard let formatDescriptor = CMSampleBufferGetFormatDescription(sampleBuffer) else {
|
guard let formatDescriptor = CMSampleBufferGetFormatDescription(sampleBuffer) else {
|
||||||
return
|
return
|
||||||
@ -417,9 +425,10 @@ final class CameraOutput: NSObject {
|
|||||||
|
|
||||||
if let videoRecorder = self.videoRecorder, videoRecorder.isRecording {
|
if let videoRecorder = self.videoRecorder, videoRecorder.isRecording {
|
||||||
if case .roundVideo = self.currentMode, type == kCMMediaType_Video {
|
if case .roundVideo = self.currentMode, type == kCMMediaType_Video {
|
||||||
var transitionFactor: CGFloat = 0.0
|
|
||||||
let currentTimestamp = CACurrentMediaTime()
|
let currentTimestamp = CACurrentMediaTime()
|
||||||
let duration: Double = 0.2
|
let duration: Double = 0.2
|
||||||
|
if !self.exclusive {
|
||||||
|
var transitionFactor: CGFloat = 0.0
|
||||||
if case .front = self.currentPosition {
|
if case .front = self.currentPosition {
|
||||||
transitionFactor = 1.0
|
transitionFactor = 1.0
|
||||||
if self.lastSwitchTimestamp > 0.0, currentTimestamp - self.lastSwitchTimestamp < duration {
|
if self.lastSwitchTimestamp > 0.0, currentTimestamp - self.lastSwitchTimestamp < duration {
|
||||||
@ -445,6 +454,51 @@ final class CameraOutput: NSObject {
|
|||||||
videoRecorder.appendSampleBuffer(sampleBuffer)
|
videoRecorder.appendSampleBuffer(sampleBuffer)
|
||||||
}
|
}
|
||||||
} else {
|
} 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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)
|
videoRecorder.appendSampleBuffer(sampleBuffer)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -486,6 +540,11 @@ final class CameraOutput: NSObject {
|
|||||||
var sampleTimingInfo: CMSampleTimingInfo = .invalid
|
var sampleTimingInfo: CMSampleTimingInfo = .invalid
|
||||||
CMSampleBufferGetSampleTimingInfo(sampleBuffer, at: 0, timingInfoOut: &sampleTimingInfo)
|
CMSampleBufferGetSampleTimingInfo(sampleBuffer, at: 0, timingInfoOut: &sampleTimingInfo)
|
||||||
|
|
||||||
|
if let videoSwitchSampleTimeOffset = self.videoSwitchSampleTimeOffset {
|
||||||
|
sampleTimingInfo.decodeTimeStamp = sampleTimingInfo.decodeTimeStamp - videoSwitchSampleTimeOffset
|
||||||
|
sampleTimingInfo.presentationTimeStamp = sampleTimingInfo.presentationTimeStamp - videoSwitchSampleTimeOffset
|
||||||
|
}
|
||||||
|
|
||||||
var newSampleBuffer: CMSampleBuffer?
|
var newSampleBuffer: CMSampleBuffer?
|
||||||
status = CMSampleBufferCreateForImageBuffer(
|
status = CMSampleBufferCreateForImageBuffer(
|
||||||
allocator: kCFAllocatorDefault,
|
allocator: kCFAllocatorDefault,
|
||||||
|
@ -21,6 +21,29 @@ private extension UIInterfaceOrientation {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private class SimpleCapturePreviewLayer: AVCaptureVideoPreviewLayer {
|
||||||
|
public var didEnterHierarchy: (() -> Void)?
|
||||||
|
public var didExitHierarchy: (() -> Void)?
|
||||||
|
|
||||||
|
override open func action(forKey event: String) -> CAAction? {
|
||||||
|
if event == kCAOnOrderIn {
|
||||||
|
self.didEnterHierarchy?()
|
||||||
|
} else if event == kCAOnOrderOut {
|
||||||
|
self.didExitHierarchy?()
|
||||||
|
}
|
||||||
|
return nullAction
|
||||||
|
}
|
||||||
|
|
||||||
|
override public init(layer: Any) {
|
||||||
|
super.init(layer: layer)
|
||||||
|
}
|
||||||
|
|
||||||
|
required public init?(coder: NSCoder) {
|
||||||
|
fatalError("init(coder:) has not been implemented")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public class CameraSimplePreviewView: UIView {
|
public class CameraSimplePreviewView: UIView {
|
||||||
func updateOrientation() {
|
func updateOrientation() {
|
||||||
guard self.videoPreviewLayer.connection?.isVideoOrientationSupported == true else {
|
guard self.videoPreviewLayer.connection?.isVideoOrientationSupported == true else {
|
||||||
@ -72,11 +95,16 @@ public class CameraSimplePreviewView: UIView {
|
|||||||
private var previewingDisposable: Disposable?
|
private var previewingDisposable: Disposable?
|
||||||
private let placeholderView = UIImageView()
|
private let placeholderView = UIImageView()
|
||||||
|
|
||||||
public init(frame: CGRect, main: Bool) {
|
public init(frame: CGRect, main: Bool, roundVideo: Bool = false) {
|
||||||
super.init(frame: frame)
|
super.init(frame: frame)
|
||||||
|
|
||||||
|
if roundVideo {
|
||||||
|
self.videoPreviewLayer.videoGravity = .resizeAspectFill
|
||||||
|
self.placeholderView.contentMode = .scaleAspectFill
|
||||||
|
} else {
|
||||||
self.videoPreviewLayer.videoGravity = main ? .resizeAspectFill : .resizeAspect
|
self.videoPreviewLayer.videoGravity = main ? .resizeAspectFill : .resizeAspect
|
||||||
self.placeholderView.contentMode = main ? .scaleAspectFill : .scaleAspectFit
|
self.placeholderView.contentMode = main ? .scaleAspectFill : .scaleAspectFit
|
||||||
|
}
|
||||||
|
|
||||||
self.addSubview(self.placeholderView)
|
self.addSubview(self.placeholderView)
|
||||||
}
|
}
|
||||||
@ -567,35 +595,29 @@ public class CameraPreviewView: MTKView {
|
|||||||
var scaleX: CGFloat
|
var scaleX: CGFloat
|
||||||
var scaleY: CGFloat
|
var scaleY: CGFloat
|
||||||
|
|
||||||
// Rotate the layer into screen orientation.
|
|
||||||
switch UIDevice.current.orientation {
|
switch UIDevice.current.orientation {
|
||||||
case .portraitUpsideDown:
|
case .portraitUpsideDown:
|
||||||
rotation = 180
|
rotation = 180
|
||||||
scaleX = videoPreviewRect.width / captureDeviceResolution.width
|
scaleX = videoPreviewRect.width / captureDeviceResolution.width
|
||||||
scaleY = videoPreviewRect.height / captureDeviceResolution.height
|
scaleY = videoPreviewRect.height / captureDeviceResolution.height
|
||||||
|
|
||||||
case .landscapeLeft:
|
case .landscapeLeft:
|
||||||
rotation = 90
|
rotation = 90
|
||||||
scaleX = videoPreviewRect.height / captureDeviceResolution.width
|
scaleX = videoPreviewRect.height / captureDeviceResolution.width
|
||||||
scaleY = scaleX
|
scaleY = scaleX
|
||||||
|
|
||||||
case .landscapeRight:
|
case .landscapeRight:
|
||||||
rotation = -90
|
rotation = -90
|
||||||
scaleX = videoPreviewRect.height / captureDeviceResolution.width
|
scaleX = videoPreviewRect.height / captureDeviceResolution.width
|
||||||
scaleY = scaleX
|
scaleY = scaleX
|
||||||
|
|
||||||
default:
|
default:
|
||||||
rotation = 0
|
rotation = 0
|
||||||
scaleX = videoPreviewRect.width / captureDeviceResolution.width
|
scaleX = videoPreviewRect.width / captureDeviceResolution.width
|
||||||
scaleY = videoPreviewRect.height / captureDeviceResolution.height
|
scaleY = videoPreviewRect.height / captureDeviceResolution.height
|
||||||
}
|
}
|
||||||
|
|
||||||
// Scale and mirror the image to ensure upright presentation.
|
|
||||||
let affineTransform = CGAffineTransform(rotationAngle: radiansForDegrees(rotation))
|
let affineTransform = CGAffineTransform(rotationAngle: radiansForDegrees(rotation))
|
||||||
.scaledBy(x: scaleX, y: -scaleY)
|
.scaledBy(x: scaleX, y: -scaleY)
|
||||||
overlayLayer.setAffineTransform(affineTransform)
|
overlayLayer.setAffineTransform(affineTransform)
|
||||||
|
|
||||||
// Cover entire screen UI.
|
|
||||||
let rootLayerBounds = self.bounds
|
let rootLayerBounds = self.bounds
|
||||||
overlayLayer.position = CGPoint(x: rootLayerBounds.midX, y: rootLayerBounds.midY)
|
overlayLayer.position = CGPoint(x: rootLayerBounds.midX, y: rootLayerBounds.midY)
|
||||||
}
|
}
|
||||||
|
@ -146,7 +146,7 @@ private final class VideoRecorderImpl {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if failed {
|
if failed {
|
||||||
print("error")
|
print("append video error")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -256,7 +256,7 @@ private final class VideoRecorderImpl {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if failed {
|
if failed {
|
||||||
print("error")
|
print("append audio error")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -88,6 +88,8 @@ public final class PlainButtonComponent: Component {
|
|||||||
override init(frame: CGRect) {
|
override init(frame: CGRect) {
|
||||||
super.init(frame: frame)
|
super.init(frame: frame)
|
||||||
|
|
||||||
|
self.isExclusiveTouch = true
|
||||||
|
|
||||||
self.contentContainer.isUserInteractionEnabled = false
|
self.contentContainer.isUserInteractionEnabled = false
|
||||||
self.addSubview(self.contentContainer)
|
self.addSubview(self.contentContainer)
|
||||||
|
|
||||||
|
@ -329,7 +329,10 @@ final class ThemeGridControllerNode: ASDisplayNode {
|
|||||||
if let strongSelf = self, !strongSelf.currentState.editing {
|
if let strongSelf = self, !strongSelf.currentState.editing {
|
||||||
let entries = previousEntries.with { $0 }
|
let entries = previousEntries.with { $0 }
|
||||||
if let entries = entries, !entries.isEmpty {
|
if let entries = entries, !entries.isEmpty {
|
||||||
let wallpapers = entries.map { $0.wallpaper }.filter { !$0.isColorOrGradient }
|
var wallpapers = entries.map { $0.wallpaper }
|
||||||
|
if case .peer = mode {
|
||||||
|
wallpapers = wallpapers.filter { !$0.isColorOrGradient }
|
||||||
|
}
|
||||||
|
|
||||||
var options = WallpaperPresentationOptions()
|
var options = WallpaperPresentationOptions()
|
||||||
if wallpaper == strongSelf.presentationData.chatWallpaper, let settings = wallpaper.settings {
|
if wallpaper == strongSelf.presentationData.chatWallpaper, let settings = wallpaper.settings {
|
||||||
@ -575,7 +578,14 @@ final class ThemeGridControllerNode: ASDisplayNode {
|
|||||||
transition.updateFrame(node: strongSelf.bottomBackgroundNode, frame: CGRect(origin: CGPoint(x: 0.0, y: gridLayout.contentSize.height), size: CGSize(width: layout.size.width, height: 500.0)))
|
transition.updateFrame(node: strongSelf.bottomBackgroundNode, frame: CGRect(origin: CGPoint(x: 0.0, y: gridLayout.contentSize.height), size: CGSize(width: layout.size.width, height: 500.0)))
|
||||||
transition.updateFrame(node: strongSelf.bottomSeparatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: gridLayout.contentSize.height), size: CGSize(width: layout.size.width, height: UIScreenPixel)))
|
transition.updateFrame(node: strongSelf.bottomSeparatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: gridLayout.contentSize.height), size: CGSize(width: layout.size.width, height: UIScreenPixel)))
|
||||||
|
|
||||||
let params = ListViewItemLayoutParams(width: layout.size.width, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, availableHeight: layout.size.height)
|
let sideInset = max(16.0, floor((layout.size.width - 674.0) / 2.0))
|
||||||
|
var listInsets = layout.safeInsets
|
||||||
|
if layout.size.width >= 375.0 {
|
||||||
|
listInsets.left = sideInset
|
||||||
|
listInsets.right = sideInset
|
||||||
|
}
|
||||||
|
|
||||||
|
let params = ListViewItemLayoutParams(width: layout.size.width, leftInset: listInsets.left, rightInset: listInsets.right, availableHeight: layout.size.height)
|
||||||
|
|
||||||
let makeResetLayout = strongSelf.resetItemNode.asyncLayout()
|
let makeResetLayout = strongSelf.resetItemNode.asyncLayout()
|
||||||
let makeResetDescriptionLayout = strongSelf.resetDescriptionItemNode.asyncLayout()
|
let makeResetDescriptionLayout = strongSelf.resetDescriptionItemNode.asyncLayout()
|
||||||
@ -588,8 +598,8 @@ final class ThemeGridControllerNode: ASDisplayNode {
|
|||||||
transition.updateFrame(node: strongSelf.resetItemNode, frame: CGRect(origin: CGPoint(x: 0.0, y: gridLayout.contentSize.height + 35.0), size: resetLayout.contentSize))
|
transition.updateFrame(node: strongSelf.resetItemNode, frame: CGRect(origin: CGPoint(x: 0.0, y: gridLayout.contentSize.height + 35.0), size: resetLayout.contentSize))
|
||||||
transition.updateFrame(node: strongSelf.resetDescriptionItemNode, frame: CGRect(origin: CGPoint(x: 0.0, y: gridLayout.contentSize.height + 35.0 + resetLayout.contentSize.height), size: resetDescriptionLayout.contentSize))
|
transition.updateFrame(node: strongSelf.resetDescriptionItemNode, frame: CGRect(origin: CGPoint(x: 0.0, y: gridLayout.contentSize.height + 35.0 + resetLayout.contentSize.height), size: resetDescriptionLayout.contentSize))
|
||||||
|
|
||||||
let sideInset = strongSelf.leftOverlayNode.frame.maxX
|
let maskSideInset = strongSelf.leftOverlayNode.frame.maxX
|
||||||
strongSelf.maskNode.frame = CGRect(origin: CGPoint(x: sideInset, y: strongSelf.separatorNode.frame.minY + UIScreenPixel + 4.0), size: CGSize(width: layout.size.width - sideInset * 2.0, height: gridLayout.contentSize.height + 6.0))
|
strongSelf.maskNode.frame = CGRect(origin: CGPoint(x: maskSideInset, y: strongSelf.separatorNode.frame.minY + UIScreenPixel + 4.0), size: CGSize(width: layout.size.width - sideInset * 2.0, height: gridLayout.contentSize.height + 6.0))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -934,7 +944,7 @@ final class ThemeGridControllerNode: ASDisplayNode {
|
|||||||
let (resetDescriptionLayout, _) = makeResetDescriptionLayout(self.resetDescriptionItem, params, ItemListNeighbors(top: .none, bottom: .none))
|
let (resetDescriptionLayout, _) = makeResetDescriptionLayout(self.resetDescriptionItem, params, ItemListNeighbors(top: .none, bottom: .none))
|
||||||
|
|
||||||
if !isChannel {
|
if !isChannel {
|
||||||
insets.bottom += buttonHeight + 35.0 + resetDescriptionLayout.contentSize.height + 32.0
|
listInsets.bottom += buttonHeight + 35.0 + resetDescriptionLayout.contentSize.height + 32.0
|
||||||
}
|
}
|
||||||
|
|
||||||
self.gridNode.frame = CGRect(x: 0.0, y: 0.0, width: layout.size.width, height: layout.size.height)
|
self.gridNode.frame = CGRect(x: 0.0, y: 0.0, width: layout.size.width, height: layout.size.height)
|
||||||
|
@ -241,15 +241,21 @@ private final class VideoMessageCameraScreenComponent: CombinedComponent {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let currentTimestamp = CACurrentMediaTime()
|
||||||
|
if let lastActionTimestamp = controller.lastActionTimestamp, currentTimestamp - lastActionTimestamp < 0.5 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
controller.lastActionTimestamp = currentTimestamp
|
||||||
|
|
||||||
let initialDuration = controller.node.previewState?.composition.duration.seconds ?? 0.0
|
let initialDuration = controller.node.previewState?.composition.duration.seconds ?? 0.0
|
||||||
|
let isFirstRecording = initialDuration.isZero
|
||||||
|
controller.node.resumeCameraCapture()
|
||||||
|
|
||||||
controller.updatePreviewState({ _ in return nil}, transition: .spring(duration: 0.4))
|
controller.updatePreviewState({ _ in return nil}, transition: .spring(duration: 0.4))
|
||||||
|
|
||||||
controller.node.dismissAllTooltips()
|
controller.node.dismissAllTooltips()
|
||||||
controller.updateCameraState({ $0.updatedRecording(pressing ? .holding : .handsFree).updatedDuration(initialDuration) }, transition: .spring(duration: 0.4))
|
controller.updateCameraState({ $0.updatedRecording(pressing ? .holding : .handsFree).updatedDuration(initialDuration) }, transition: .spring(duration: 0.4))
|
||||||
|
|
||||||
let isFirstRecording = initialDuration.isZero
|
|
||||||
controller.node.resumeCameraCapture()
|
|
||||||
|
|
||||||
controller.node.withReadyCamera(isFirstTime: !controller.node.cameraIsActive) {
|
controller.node.withReadyCamera(isFirstTime: !controller.node.cameraIsActive) {
|
||||||
self.resultDisposable.set((camera.startRecording()
|
self.resultDisposable.set((camera.startRecording()
|
||||||
|> deliverOnMainQueue).start(next: { [weak self] recordingData in
|
|> deliverOnMainQueue).start(next: { [weak self] recordingData in
|
||||||
@ -275,6 +281,11 @@ private final class VideoMessageCameraScreenComponent: CombinedComponent {
|
|||||||
guard let controller = self.getController(), let camera = controller.camera else {
|
guard let controller = self.getController(), let camera = controller.camera else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
let currentTimestamp = CACurrentMediaTime()
|
||||||
|
if let lastActionTimestamp = controller.lastActionTimestamp, currentTimestamp - lastActionTimestamp < 0.5 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
controller.lastActionTimestamp = currentTimestamp
|
||||||
|
|
||||||
self.resultDisposable.set((camera.stopRecording()
|
self.resultDisposable.set((camera.stopRecording()
|
||||||
|> deliverOnMainQueue).start(next: { [weak self] result in
|
|> deliverOnMainQueue).start(next: { [weak self] result in
|
||||||
@ -601,8 +612,8 @@ public class VideoMessageCameraScreen: ViewController {
|
|||||||
let isDualCameraEnabled = Camera.isDualCameraSupported
|
let isDualCameraEnabled = Camera.isDualCameraSupported
|
||||||
let isFrontPosition = "".isEmpty
|
let isFrontPosition = "".isEmpty
|
||||||
|
|
||||||
self.mainPreviewView = CameraSimplePreviewView(frame: .zero, main: true)
|
self.mainPreviewView = CameraSimplePreviewView(frame: .zero, main: true, roundVideo: true)
|
||||||
self.additionalPreviewView = CameraSimplePreviewView(frame: .zero, main: false)
|
self.additionalPreviewView = CameraSimplePreviewView(frame: .zero, main: false, roundVideo: true)
|
||||||
|
|
||||||
self.progressView = RecordingProgressView(frame: .zero)
|
self.progressView = RecordingProgressView(frame: .zero)
|
||||||
|
|
||||||
@ -638,7 +649,9 @@ public class VideoMessageCameraScreen: ViewController {
|
|||||||
self.containerView.addSubview(self.previewContainerView)
|
self.containerView.addSubview(self.previewContainerView)
|
||||||
|
|
||||||
self.previewContainerView.addSubview(self.mainPreviewView)
|
self.previewContainerView.addSubview(self.mainPreviewView)
|
||||||
|
if isDualCameraEnabled {
|
||||||
self.previewContainerView.addSubview(self.additionalPreviewView)
|
self.previewContainerView.addSubview(self.additionalPreviewView)
|
||||||
|
}
|
||||||
self.previewContainerView.addSubview(self.progressView)
|
self.previewContainerView.addSubview(self.progressView)
|
||||||
self.previewContainerView.addSubview(self.previewBlurView)
|
self.previewContainerView.addSubview(self.previewBlurView)
|
||||||
self.previewContainerView.addSubview(self.loadingView)
|
self.previewContainerView.addSubview(self.loadingView)
|
||||||
@ -652,7 +665,7 @@ public class VideoMessageCameraScreen: ViewController {
|
|||||||
self.mainPreviewView.removePlaceholder(delay: 0.0)
|
self.mainPreviewView.removePlaceholder(delay: 0.0)
|
||||||
}
|
}
|
||||||
self.withReadyCamera(isFirstTime: true, {
|
self.withReadyCamera(isFirstTime: true, {
|
||||||
if isDualCameraEnabled {
|
if !isDualCameraEnabled {
|
||||||
self.mainPreviewView.removePlaceholder(delay: 0.0)
|
self.mainPreviewView.removePlaceholder(delay: 0.0)
|
||||||
}
|
}
|
||||||
self.loadingView.alpha = 0.0
|
self.loadingView.alpha = 0.0
|
||||||
@ -675,7 +688,7 @@ public class VideoMessageCameraScreen: ViewController {
|
|||||||
|
|
||||||
func withReadyCamera(isFirstTime: Bool = false, _ f: @escaping () -> Void) {
|
func withReadyCamera(isFirstTime: Bool = false, _ f: @escaping () -> Void) {
|
||||||
if #available(iOS 13.0, *) {
|
if #available(iOS 13.0, *) {
|
||||||
let _ = (self.additionalPreviewView.isPreviewing
|
let _ = ((self.cameraState.isDualCameraEnabled ? self.additionalPreviewView.isPreviewing : self.mainPreviewView.isPreviewing)
|
||||||
|> filter { $0 }
|
|> filter { $0 }
|
||||||
|> take(1)).startStandalone(next: { _ in
|
|> take(1)).startStandalone(next: { _ in
|
||||||
f()
|
f()
|
||||||
@ -733,6 +746,11 @@ public class VideoMessageCameraScreen: ViewController {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
self.cameraState = self.cameraState.updatedPosition(position)
|
self.cameraState = self.cameraState.updatedPosition(position)
|
||||||
|
|
||||||
|
if !self.cameraState.isDualCameraEnabled {
|
||||||
|
self.animatePositionChange()
|
||||||
|
}
|
||||||
|
|
||||||
self.requestUpdateLayout(transition: .easeInOut(duration: 0.2))
|
self.requestUpdateLayout(transition: .easeInOut(duration: 0.2))
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -794,6 +812,31 @@ public class VideoMessageCameraScreen: ViewController {
|
|||||||
self.previewContainerView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false)
|
self.previewContainerView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func animatePositionChange() {
|
||||||
|
if let snapshotView = self.mainPreviewView.snapshotView(afterScreenUpdates: false) {
|
||||||
|
self.previewContainerView.insertSubview(snapshotView, belowSubview: self.progressView)
|
||||||
|
self.previewSnapshotView = snapshotView
|
||||||
|
|
||||||
|
let action = { [weak self] in
|
||||||
|
guard let self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
UIView.animate(withDuration: 0.2, animations: {
|
||||||
|
self.previewSnapshotView?.alpha = 0.0
|
||||||
|
}, completion: { _ in
|
||||||
|
self.previewSnapshotView?.removeFromSuperview()
|
||||||
|
self.previewSnapshotView = nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
Queue.mainQueue().after(1.0) {
|
||||||
|
action()
|
||||||
|
}
|
||||||
|
|
||||||
|
self.requestUpdateLayout(transition: .immediate)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func pauseCameraCapture() {
|
func pauseCameraCapture() {
|
||||||
self.mainPreviewView.isEnabled = false
|
self.mainPreviewView.isEnabled = false
|
||||||
self.additionalPreviewView.isEnabled = false
|
self.additionalPreviewView.isEnabled = false
|
||||||
@ -805,7 +848,7 @@ public class VideoMessageCameraScreen: ViewController {
|
|||||||
|
|
||||||
func resumeCameraCapture() {
|
func resumeCameraCapture() {
|
||||||
if !self.mainPreviewView.isEnabled {
|
if !self.mainPreviewView.isEnabled {
|
||||||
if let snapshotView = self.previewContainerView.snapshotView(afterScreenUpdates: false) {
|
if let snapshotView = self.resultPreviewView?.snapshotView(afterScreenUpdates: false) {
|
||||||
self.previewContainerView.insertSubview(snapshotView, belowSubview: self.previewBlurView)
|
self.previewContainerView.insertSubview(snapshotView, belowSubview: self.previewBlurView)
|
||||||
self.previewSnapshotView = snapshotView
|
self.previewSnapshotView = snapshotView
|
||||||
}
|
}
|
||||||
@ -1071,21 +1114,37 @@ public class VideoMessageCameraScreen: ViewController {
|
|||||||
}
|
}
|
||||||
transition.setCornerRadius(layer: self.previewContainerView.layer, cornerRadius: previewSide / 2.0)
|
transition.setCornerRadius(layer: self.previewContainerView.layer, cornerRadius: previewSide / 2.0)
|
||||||
|
|
||||||
let previewInnerFrame = CGRect(origin: .zero, size: previewFrame.size)
|
let previewBounds = CGRect(origin: .zero, size: previewFrame.size)
|
||||||
|
|
||||||
let additionalPreviewSize = CGSize(width: previewFrame.size.width, height: previewFrame.size.width / 3.0 * 4.0)
|
let previewInnerSize: CGSize
|
||||||
let additionalPreviewInnerFrame = CGRect(origin: CGPoint(x: 0.0, y: floorToScreenPixels((previewFrame.height - additionalPreviewSize.height) / 2.0)), size: additionalPreviewSize)
|
let additionalPreviewInnerSize: CGSize
|
||||||
|
|
||||||
|
if self.cameraState.isDualCameraEnabled {
|
||||||
|
previewInnerSize = CGSize(width: previewFrame.size.width, height: previewFrame.size.width / 9.0 * 16.0)
|
||||||
|
additionalPreviewInnerSize = CGSize(width: previewFrame.size.width, height: previewFrame.size.width / 3.0 * 4.0)
|
||||||
|
} else {
|
||||||
|
previewInnerSize = CGSize(width: previewFrame.size.width, height: previewFrame.size.width / 3.0 * 4.0)
|
||||||
|
additionalPreviewInnerSize = CGSize(width: previewFrame.size.width, height: previewFrame.size.width / 3.0 * 4.0)
|
||||||
|
}
|
||||||
|
|
||||||
|
let previewInnerFrame = CGRect(origin: CGPoint(x: 0.0, y: floorToScreenPixels((previewFrame.height - previewInnerSize.height) / 2.0)), size: previewInnerSize)
|
||||||
|
|
||||||
|
let additionalPreviewInnerFrame = CGRect(origin: CGPoint(x: 0.0, y: floorToScreenPixels((previewFrame.height - additionalPreviewInnerSize.height) / 2.0)), size: additionalPreviewInnerSize)
|
||||||
|
if self.cameraState.isDualCameraEnabled {
|
||||||
self.mainPreviewView.frame = previewInnerFrame
|
self.mainPreviewView.frame = previewInnerFrame
|
||||||
self.additionalPreviewView.frame = additionalPreviewInnerFrame
|
self.additionalPreviewView.frame = additionalPreviewInnerFrame
|
||||||
|
} else {
|
||||||
|
self.mainPreviewView.frame = self.cameraState.position == .front ? additionalPreviewInnerFrame : previewInnerFrame
|
||||||
|
}
|
||||||
|
|
||||||
self.progressView.frame = previewInnerFrame
|
self.progressView.frame = previewBounds
|
||||||
self.progressView.value = CGFloat(self.cameraState.duration / 60.0)
|
self.progressView.value = CGFloat(self.cameraState.duration / 60.0)
|
||||||
|
|
||||||
transition.setAlpha(view: self.additionalPreviewView, alpha: self.cameraState.position == .front ? 1.0 : 0.0)
|
transition.setAlpha(view: self.additionalPreviewView, alpha: self.cameraState.position == .front ? 1.0 : 0.0)
|
||||||
|
|
||||||
self.previewBlurView.frame = previewInnerFrame
|
self.previewBlurView.frame = previewBounds
|
||||||
self.previewSnapshotView?.frame = previewInnerFrame
|
self.previewSnapshotView?.center = previewBounds.center
|
||||||
self.loadingView.update(size: previewInnerFrame.size, transition: .immediate)
|
self.loadingView.update(size: previewBounds.size, transition: .immediate)
|
||||||
|
|
||||||
let componentSize = self.componentHost.update(
|
let componentSize = self.componentHost.update(
|
||||||
transition: transition,
|
transition: transition,
|
||||||
@ -1155,7 +1214,7 @@ public class VideoMessageCameraScreen: ViewController {
|
|||||||
|
|
||||||
resultPreviewView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.resultTapped)))
|
resultPreviewView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.resultTapped)))
|
||||||
}
|
}
|
||||||
resultPreviewView.frame = previewInnerFrame
|
resultPreviewView.frame = previewBounds
|
||||||
} else if let resultPreviewView = self.resultPreviewView {
|
} else if let resultPreviewView = self.resultPreviewView {
|
||||||
self.resultPreviewView = nil
|
self.resultPreviewView = nil
|
||||||
resultPreviewView.removeFromSuperview()
|
resultPreviewView.removeFromSuperview()
|
||||||
@ -1177,7 +1236,7 @@ public class VideoMessageCameraScreen: ViewController {
|
|||||||
fileprivate var allowLiveUpload: Bool
|
fileprivate var allowLiveUpload: Bool
|
||||||
fileprivate var viewOnceAvailable: Bool
|
fileprivate var viewOnceAvailable: Bool
|
||||||
|
|
||||||
fileprivate let completion: (EnqueueMessage?) -> Void
|
fileprivate let completion: (EnqueueMessage?, Bool?, Int32?) -> Void
|
||||||
|
|
||||||
private var audioSessionDisposable: Disposable?
|
private var audioSessionDisposable: Disposable?
|
||||||
|
|
||||||
@ -1320,15 +1379,16 @@ public class VideoMessageCameraScreen: ViewController {
|
|||||||
public init(
|
public init(
|
||||||
context: AccountContext,
|
context: AccountContext,
|
||||||
updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)?,
|
updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)?,
|
||||||
peerId: EnginePeer.Id,
|
allowLiveUpload: Bool,
|
||||||
|
viewOnceAvailable: Bool,
|
||||||
inputPanelFrame: CGRect,
|
inputPanelFrame: CGRect,
|
||||||
completion: @escaping (EnqueueMessage?) -> Void
|
completion: @escaping (EnqueueMessage?, Bool?, Int32?) -> Void
|
||||||
) {
|
) {
|
||||||
self.context = context
|
self.context = context
|
||||||
self.updatedPresentationData = updatedPresentationData
|
self.updatedPresentationData = updatedPresentationData
|
||||||
self.inputPanelFrame = inputPanelFrame
|
self.inputPanelFrame = inputPanelFrame
|
||||||
self.allowLiveUpload = peerId.namespace != Namespaces.Peer.SecretChat
|
self.allowLiveUpload = allowLiveUpload
|
||||||
self.viewOnceAvailable = peerId.namespace == Namespaces.Peer.CloudUser && peerId != context.account.peerId
|
self.viewOnceAvailable = viewOnceAvailable
|
||||||
self.completion = completion
|
self.completion = completion
|
||||||
|
|
||||||
self.recordingStatus = RecordingStatus(micLevel: self.micLevelValue.get(), duration: self.durationValue.get())
|
self.recordingStatus = RecordingStatus(micLevel: self.micLevelValue.get(), duration: self.durationValue.get())
|
||||||
@ -1360,10 +1420,21 @@ public class VideoMessageCameraScreen: ViewController {
|
|||||||
super.displayNodeDidLoad()
|
super.displayNodeDidLoad()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fileprivate var didSend = false
|
||||||
|
fileprivate var lastActionTimestamp: Double?
|
||||||
fileprivate var isSendingImmediately = false
|
fileprivate var isSendingImmediately = false
|
||||||
public func sendVideoRecording() {
|
public func sendVideoRecording(silentPosting: Bool? = nil, scheduleTime: Int32? = nil) {
|
||||||
|
guard !self.didSend else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let currentTimestamp = CACurrentMediaTime()
|
||||||
|
if let lastActionTimestamp = self.lastActionTimestamp, currentTimestamp - lastActionTimestamp < 0.5 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if case .none = self.cameraState.recording, self.node.results.isEmpty {
|
if case .none = self.cameraState.recording, self.node.results.isEmpty {
|
||||||
self.completion(nil)
|
self.completion(nil, nil, nil)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1374,6 +1445,8 @@ public class VideoMessageCameraScreen: ViewController {
|
|||||||
self.node.stopRecording.invoke(Void())
|
self.node.stopRecording.invoke(Void())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.didSend = true
|
||||||
|
|
||||||
let _ = (self.currentResults
|
let _ = (self.currentResults
|
||||||
|> take(1)
|
|> take(1)
|
||||||
|> deliverOnMainQueue).startStandalone(next: { [weak self] results in
|
|> deliverOnMainQueue).startStandalone(next: { [weak self] results in
|
||||||
@ -1393,7 +1466,7 @@ public class VideoMessageCameraScreen: ViewController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if duration < 1.0 {
|
if duration < 1.0 {
|
||||||
self.completion(nil)
|
self.completion(nil, nil, nil)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1467,12 +1540,16 @@ public class VideoMessageCameraScreen: ViewController {
|
|||||||
localGroupingKey: nil,
|
localGroupingKey: nil,
|
||||||
correlationId: nil,
|
correlationId: nil,
|
||||||
bubbleUpEmojiOrStickersets: []
|
bubbleUpEmojiOrStickersets: []
|
||||||
))
|
), silentPosting, scheduleTime)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
private var waitingForNextResult = false
|
private var waitingForNextResult = false
|
||||||
public func stopVideoRecording() -> Bool {
|
public func stopVideoRecording() -> Bool {
|
||||||
|
guard !self.didSend else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
self.node.dismissAllTooltips()
|
self.node.dismissAllTooltips()
|
||||||
|
|
||||||
self.waitingForNextResult = true
|
self.waitingForNextResult = true
|
||||||
@ -1487,6 +1564,10 @@ public class VideoMessageCameraScreen: ViewController {
|
|||||||
fileprivate var recordingStartTime: Double?
|
fileprivate var recordingStartTime: Double?
|
||||||
fileprivate var scheduledLock = false
|
fileprivate var scheduledLock = false
|
||||||
public func lockVideoRecording() {
|
public func lockVideoRecording() {
|
||||||
|
guard !self.didSend else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if case .none = self.cameraState.recording {
|
if case .none = self.cameraState.recording {
|
||||||
self.scheduledLock = true
|
self.scheduledLock = true
|
||||||
self.node.requestUpdateLayout(transition: .spring(duration: 0.4))
|
self.node.requestUpdateLayout(transition: .spring(duration: 0.4))
|
||||||
|
@ -683,6 +683,10 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let _ = strongSelf.videoRecorderValue {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
strongSelf.chatDisplayNode.messageTransitionNode.dismissMessageReactionContexts()
|
strongSelf.chatDisplayNode.messageTransitionNode.dismissMessageReactionContexts()
|
||||||
|
|
||||||
if strongSelf.presentVoiceMessageDiscardAlert(action: action, performAction: false) {
|
if strongSelf.presentVoiceMessageDiscardAlert(action: action, performAction: false) {
|
||||||
@ -15374,14 +15378,18 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
isScheduledMessages = true
|
isScheduledMessages = true
|
||||||
}
|
}
|
||||||
|
|
||||||
let _ = isScheduledMessages
|
var isBot = false
|
||||||
|
if let user = self.presentationInterfaceState.renderedPeer?.peer as? TelegramUser, user.botInfo != nil {
|
||||||
|
isBot = true
|
||||||
|
}
|
||||||
|
|
||||||
let controller = VideoMessageCameraScreen(
|
let controller = VideoMessageCameraScreen(
|
||||||
context: self.context,
|
context: self.context,
|
||||||
updatedPresentationData: self.updatedPresentationData,
|
updatedPresentationData: self.updatedPresentationData,
|
||||||
peerId: peerId,
|
allowLiveUpload: peerId.namespace != Namespaces.Peer.SecretChat,
|
||||||
|
viewOnceAvailable: !isScheduledMessages && peerId.namespace == Namespaces.Peer.CloudUser && peerId != self.context.account.peerId && !isBot,
|
||||||
inputPanelFrame: currentInputPanelFrame,
|
inputPanelFrame: currentInputPanelFrame,
|
||||||
completion: { [weak self] message in
|
completion: { [weak self] message, silentPosting, scheduleTime in
|
||||||
guard let self, let videoController = self.videoRecorderValue else {
|
guard let self, let videoController = self.videoRecorderValue else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -15400,8 +15408,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
.withUpdatedCorrelationId(correlationId)
|
.withUpdatedCorrelationId(correlationId)
|
||||||
|
|
||||||
var usedCorrelationId = false
|
var usedCorrelationId = false
|
||||||
|
if scheduleTime == nil, self.chatDisplayNode.shouldAnimateMessageTransition, let extractedView = videoController.extractVideoSnapshot() {
|
||||||
if self.chatDisplayNode.shouldAnimateMessageTransition, let extractedView = videoController.extractVideoSnapshot() {
|
|
||||||
usedCorrelationId = true
|
usedCorrelationId = true
|
||||||
self.chatDisplayNode.messageTransitionNode.add(correlationId: correlationId, source: .videoMessage(ChatMessageTransitionNodeImpl.Source.VideoMessage(view: extractedView)), initiated: { [weak videoController, weak self] in
|
self.chatDisplayNode.messageTransitionNode.add(correlationId: correlationId, source: .videoMessage(ChatMessageTransitionNodeImpl.Source.VideoMessage(view: extractedView)), initiated: { [weak videoController, weak self] in
|
||||||
videoController?.hideVideoSnapshot()
|
videoController?.hideVideoSnapshot()
|
||||||
@ -15424,7 +15431,17 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
}
|
}
|
||||||
}, usedCorrelationId ? correlationId : nil)
|
}, usedCorrelationId ? correlationId : nil)
|
||||||
|
|
||||||
self.sendMessages([message])
|
let messages = [message]
|
||||||
|
let transformedMessages: [EnqueueMessage]
|
||||||
|
if let silentPosting {
|
||||||
|
transformedMessages = self.transformEnqueueMessages(messages, silentPosting: silentPosting)
|
||||||
|
} else if let scheduleTime {
|
||||||
|
transformedMessages = self.transformEnqueueMessages(messages, silentPosting: false, scheduleTime: scheduleTime)
|
||||||
|
} else {
|
||||||
|
transformedMessages = self.transformEnqueueMessages(messages)
|
||||||
|
}
|
||||||
|
|
||||||
|
self.sendMessages(transformedMessages)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
controller.onResume = { [weak self] in
|
controller.onResume = { [weak self] in
|
||||||
@ -15634,6 +15651,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
}
|
}
|
||||||
|
|
||||||
func resumeMediaRecorder() {
|
func resumeMediaRecorder() {
|
||||||
|
self.context.sharedContext.mediaManager.playlistControl(.playback(.pause), type: nil)
|
||||||
|
|
||||||
if let audioRecorderValue = self.audioRecorderValue {
|
if let audioRecorderValue = self.audioRecorderValue {
|
||||||
audioRecorderValue.resume()
|
audioRecorderValue.resume()
|
||||||
|
|
||||||
@ -15743,7 +15762,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
|
|
||||||
donateSendMessageIntent(account: self.context.account, sharedContext: self.context.sharedContext, intentContext: .chat, peerIds: [peerId])
|
donateSendMessageIntent(account: self.context.account, sharedContext: self.context.sharedContext, intentContext: .chat, peerIds: [peerId])
|
||||||
case .video:
|
case .video:
|
||||||
self.videoRecorderValue?.sendVideoRecording()
|
self.videoRecorderValue?.sendVideoRecording(silentPosting: silentPosting, scheduleTime: scheduleTime)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3729,6 +3729,9 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
|||||||
if self.chatPresentationInterfaceState.inputTextPanelState.mediaRecordingState != nil {
|
if self.chatPresentationInterfaceState.inputTextPanelState.mediaRecordingState != nil {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
if self.chatPresentationInterfaceState.recordedMediaPreview != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
if let inputPanelNode = self.inputPanelNode as? ChatTextInputPanelNode {
|
if let inputPanelNode = self.inputPanelNode as? ChatTextInputPanelNode {
|
||||||
if inputPanelNode.isFocused {
|
if inputPanelNode.isFocused {
|
||||||
return false
|
return false
|
||||||
|
@ -107,6 +107,7 @@ final class ChatRecordingPreviewInputPanelNode: ChatInputPanelNode {
|
|||||||
|
|
||||||
self.sendButton = HighlightTrackingButtonNode()
|
self.sendButton = HighlightTrackingButtonNode()
|
||||||
self.sendButton.displaysAsynchronously = false
|
self.sendButton.displaysAsynchronously = false
|
||||||
|
self.sendButton.isExclusiveTouch = true
|
||||||
self.sendButton.setImage(PresentationResourcesChat.chatInputPanelSendButtonImage(theme), for: [])
|
self.sendButton.setImage(PresentationResourcesChat.chatInputPanelSendButtonImage(theme), for: [])
|
||||||
|
|
||||||
self.viewOnceButton = ChatRecordingViewOnceButtonNode(icon: .viewOnce)
|
self.viewOnceButton = ChatRecordingViewOnceButtonNode(icon: .viewOnce)
|
||||||
@ -195,6 +196,12 @@ final class ChatRecordingPreviewInputPanelNode: ChatInputPanelNode {
|
|||||||
let gestureRecognizer = ContextGesture(target: nil, action: nil)
|
let gestureRecognizer = ContextGesture(target: nil, action: nil)
|
||||||
self.sendButton.view.addGestureRecognizer(gestureRecognizer)
|
self.sendButton.view.addGestureRecognizer(gestureRecognizer)
|
||||||
self.gestureRecognizer = gestureRecognizer
|
self.gestureRecognizer = gestureRecognizer
|
||||||
|
gestureRecognizer.shouldBegin = { [weak self] _ in
|
||||||
|
if let self, self.viewOnce {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
gestureRecognizer.activated = { [weak self] gesture, _ in
|
gestureRecognizer.activated = { [weak self] gesture, _ in
|
||||||
guard let strongSelf = self else {
|
guard let strongSelf = self else {
|
||||||
return
|
return
|
||||||
|
@ -972,7 +972,7 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate, Ch
|
|||||||
interfaceInteraction.finishMediaRecording(.dismiss)
|
interfaceInteraction.finishMediaRecording(.dismiss)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
interfaceInteraction.finishMediaRecording(.dismiss)
|
// interfaceInteraction.finishMediaRecording(.dismiss)
|
||||||
}
|
}
|
||||||
strongSelf.viewOnce = false
|
strongSelf.viewOnce = false
|
||||||
strongSelf.tooltipController?.dismiss()
|
strongSelf.tooltipController?.dismiss()
|
||||||
|
Loading…
x
Reference in New Issue
Block a user