Camera fixes

This commit is contained in:
Ilya Laktyushin 2023-10-19 20:44:16 +04:00
parent ec51634dcc
commit deaf722d85
10 changed files with 154 additions and 138 deletions

View File

@ -119,12 +119,6 @@ private final class CameraContext {
private let detectedCodesPipe = ValuePipe<[CameraCode]>()
fileprivate let modeChangePromise = ValuePromise<Camera.ModeChange>(.none)
var previewNode: CameraPreviewNode? {
didSet {
self.previewNode?.prepare()
}
}
var previewView: CameraPreviewView?
var simplePreviewView: CameraSimplePreviewView?
@ -310,9 +304,7 @@ private final class CameraContext {
self.mainDeviceContext?.output.processSampleBuffer = { [weak self] sampleBuffer, pixelBuffer, connection in
guard let self, let mainDeviceContext = self.mainDeviceContext else {
return
}
self.previewNode?.enqueue(sampleBuffer)
}
let timestamp = CACurrentMediaTime()
if timestamp > self.lastSnapshotTimestamp + 2.5, !mainDeviceContext.output.isRecording {
var front = false
@ -350,8 +342,6 @@ private final class CameraContext {
guard let self, let mainDeviceContext = self.mainDeviceContext else {
return
}
self.previewNode?.enqueue(sampleBuffer)
let timestamp = CACurrentMediaTime()
if timestamp > self.lastSnapshotTimestamp + 2.5, !mainDeviceContext.output.isRecording {
var front = false
@ -814,20 +804,6 @@ public final class Camera {
return disposable
}
}
public func attachPreviewNode(_ node: CameraPreviewNode) {
let nodeRef: Unmanaged<CameraPreviewNode> = Unmanaged.passRetained(node)
self.queue.async {
if let context = self.contextRef?.takeUnretainedValue() {
context.previewNode = nodeRef.takeUnretainedValue()
nodeRef.release()
} else {
Queue.mainQueue().async {
nodeRef.release()
}
}
}
}
public func attachPreviewView(_ view: CameraPreviewView) {
self.previewView = view

View File

@ -245,7 +245,7 @@ final class CameraOutput: NSObject {
}
let settings = AVCapturePhotoSettings(format: [kCVPixelBufferPixelFormatTypeKey as String: Int(kCVPixelFormatType_32BGRA)])
settings.flashMode = flashMode
settings.flashMode = mirror ? .off : flashMode
if let previewPhotoPixelFormatType = settings.availablePreviewPhotoPixelFormatTypes.first {
settings.previewPhotoFormat = [kCVPixelBufferPixelFormatTypeKey as String: previewPhotoPixelFormatType]
}

View File

@ -1,69 +0,0 @@
import Foundation
import AsyncDisplayKit
import Display
import AVFoundation
import SwiftSignalKit
private final class CameraPreviewNodeLayerNullAction: NSObject, CAAction {
@objc func run(forKey event: String, object anObject: Any, arguments dict: [AnyHashable : Any]?) {
}
}
private final class CameraPreviewNodeLayer: AVSampleBufferDisplayLayer {
override func action(forKey event: String) -> CAAction? {
return CameraPreviewNodeLayerNullAction()
}
}
public final class CameraPreviewNode: ASDisplayNode {
private var displayLayer: AVSampleBufferDisplayLayer
private let fadeNode: ASDisplayNode
private var fadedIn = false
public override init() {
self.displayLayer = AVSampleBufferDisplayLayer()
self.displayLayer.videoGravity = .resizeAspectFill
self.fadeNode = ASDisplayNode()
self.fadeNode.backgroundColor = .black
self.fadeNode.isUserInteractionEnabled = false
super.init()
self.clipsToBounds = true
self.layer.addSublayer(self.displayLayer)
self.addSubnode(self.fadeNode)
}
func prepare() {
DispatchQueue.main.async {
self.displayLayer.flushAndRemoveImage()
}
}
func enqueue(_ sampleBuffer: CMSampleBuffer) {
self.displayLayer.enqueue(sampleBuffer)
if !self.fadedIn {
self.fadedIn = true
Queue.mainQueue().after(0.2) {
self.fadeNode.alpha = 0.0
self.fadeNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3)
}
}
}
override public func layout() {
super.layout()
var transform = CGAffineTransform(rotationAngle: CGFloat.pi / 2.0)
transform = transform.scaledBy(x: 1.0, y: 1.0)
self.displayLayer.setAffineTransform(transform)
self.displayLayer.frame = self.bounds
self.fadeNode.frame = self.bounds
}
}

View File

@ -20,6 +20,7 @@
@property (nonatomic, assign) bool alwaysSetFlash;
@property (nonatomic, assign) PGCameraMode currentMode;
@property (nonatomic, assign) PGCameraFlashMode currentFlashMode;
@property (nonatomic, readonly) AVCaptureFlashMode currentDeviceFlashMode;
@property (nonatomic, assign) PGCameraPosition currentCameraPosition;
@property (nonatomic, readonly) PGCameraPosition preferredCameraPosition;

View File

@ -391,6 +391,7 @@ NSString *const PGCameraAdjustingFocusKey = @"adjustingFocus";
_currentPhotoOrientation = orientation;
AVCapturePhotoSettings *photoSettings = [AVCapturePhotoSettings photoSettings];
photoSettings.flashMode = self.captureSession.currentDeviceFlashMode;
[self.captureSession.imageOutput capturePhotoWithSettings:photoSettings delegate:self];
}];
};

View File

@ -703,6 +703,10 @@ const NSInteger PGCameraFrameRate = 30;
#pragma mark - Flash
- (AVCaptureFlashMode)currentDeviceFlashMode {
return [PGCameraCaptureSession _deviceFlashModeForCameraFlashMode:self.currentFlashMode];
}
- (PGCameraFlashMode)currentFlashMode
{
switch (self.currentMode)

View File

@ -390,7 +390,7 @@ private final class QrCodeScanScreenNode: ViewControllerTracingNode, UIScrollVie
private weak var controller: QrCodeScanScreen?
private let subject: QrCodeScanScreen.Subject
private let previewNode: CameraPreviewNode
private let previewView: CameraSimplePreviewView
private let fadeNode: ASDisplayNode
private let topDimNode: ASDisplayNode
private let bottomDimNode: ASDisplayNode
@ -436,8 +436,8 @@ private final class QrCodeScanScreenNode: ViewControllerTracingNode, UIScrollVie
self.controller = controller
self.subject = subject
self.previewNode = CameraPreviewNode()
self.previewNode.backgroundColor = .black
self.previewView = CameraSimplePreviewView(frame: .zero, main: true)
self.previewView.backgroundColor = .black
self.fadeNode = ASDisplayNode()
self.fadeNode.alpha = 0.0
@ -513,7 +513,7 @@ private final class QrCodeScanScreenNode: ViewControllerTracingNode, UIScrollVie
self.errorTextNode.textAlignment = .center
self.errorTextNode.isHidden = true
self.camera = Camera(configuration: .init(preset: .hd1920x1080, position: .back, audio: false, photo: true, metadata: true, preferredFps: 60))
self.camera = Camera(configuration: .init(preset: .hd1920x1080, position: .back, audio: false, photo: true, metadata: true, preferredFps: 60), previewView: self.previewView)
super.init()
@ -526,7 +526,6 @@ private final class QrCodeScanScreenNode: ViewControllerTracingNode, UIScrollVie
}
})
self.addSubnode(self.previewNode)
self.addSubnode(self.fadeNode)
self.addSubnode(self.topDimNode)
self.addSubnode(self.bottomDimNode)
@ -544,6 +543,20 @@ private final class QrCodeScanScreenNode: ViewControllerTracingNode, UIScrollVie
self.galleryButtonNode.addTarget(self, action: #selector(self.galleryPressed), forControlEvents: .touchUpInside)
self.torchButtonNode.addTarget(self, action: #selector(self.torchPressed), forControlEvents: .touchUpInside)
self.previewView.resetPlaceholder(front: false)
if #available(iOS 13.0, *) {
let _ = (self.previewView.isPreviewing
|> filter { $0 }
|> take(1)
|> deliverOnMainQueue).startStandalone(next: { [weak self] _ in
self?.previewView.removePlaceholder(delay: 0.15)
})
} else {
Queue.mainQueue().after(0.35) {
self.previewView.removePlaceholder(delay: 0.15)
}
}
}
deinit {
@ -564,7 +577,7 @@ private final class QrCodeScanScreenNode: ViewControllerTracingNode, UIScrollVie
override func didLoad() {
super.didLoad()
self.camera.attachPreviewNode(self.previewNode)
self.view.insertSubview(self.previewView, at: 0)
self.camera.startCapture()
let throttledSignal = self.camera.detectedCodes
@ -671,14 +684,14 @@ private final class QrCodeScanScreenNode: ViewControllerTracingNode, UIScrollVie
if case .tablet = layout.deviceMetrics.type {
if UIDevice.current.orientation == .landscapeLeft {
self.previewNode.transform = CATransform3DMakeRotation(-CGFloat.pi / 2.0, 0.0, 0.0, 1.0)
self.previewView.layer.transform = CATransform3DMakeRotation(-CGFloat.pi / 2.0, 0.0, 0.0, 1.0)
} else if UIDevice.current.orientation == .landscapeRight {
self.previewNode.transform = CATransform3DMakeRotation(CGFloat.pi / 2.0, 0.0, 0.0, 1.0)
self.previewView.layer.transform = CATransform3DMakeRotation(CGFloat.pi / 2.0, 0.0, 0.0, 1.0)
} else {
self.previewNode.transform = CATransform3DIdentity
self.previewView.layer.transform = CATransform3DIdentity
}
}
transition.updateFrame(node: self.previewNode, frame: bounds)
transition.updateFrame(view: self.previewView, frame: bounds)
transition.updateFrame(node: self.fadeNode, frame: bounds)
let frameSide = max(240.0, layout.size.width - sideInset * 2.0)

View File

@ -409,17 +409,23 @@ private final class CameraScreenComponent: CombinedComponent {
controller.updateCameraState({ $0.updatedMode(mode) }, transition: .spring(duration: 0.3))
var flashOn = controller.cameraState.flashMode == .on
if case .video = mode, case .auto = controller.cameraState.flashMode {
camera.setFlashMode(.on)
flashOn = true
}
self.updateScreenBrightness(flashOn: flashOn)
}
func toggleFlashMode() {
guard let controller = self.getController(), let camera = controller.camera else {
return
}
var flashOn = false
switch controller.cameraState.flashMode {
case .off:
flashOn = true
camera.setFlashMode(.on)
case .on:
if controller.cameraState.mode == .video {
@ -431,6 +437,8 @@ private final class CameraScreenComponent: CombinedComponent {
camera.setFlashMode(.off)
}
self.hapticFeedback.impact(.light)
self.updateScreenBrightness(flashOn: flashOn)
}
func updateFlashTint(_ tint: CameraState.FlashTint?) {
@ -441,6 +449,7 @@ private final class CameraScreenComponent: CombinedComponent {
controller.updateCameraState({ $0.updatedFlashTint(tint) }, transition: .easeInOut(duration: 0.2))
} else {
camera.setFlashMode(.off)
self.updateScreenBrightness(flashOn: false)
}
}
@ -460,6 +469,8 @@ private final class CameraScreenComponent: CombinedComponent {
self.displayingFlashTint = true
self.updated(transition: .immediate)
self.updateScreenBrightness(flashOn: true)
}
private var lastFlipTimestamp: Double?
@ -515,7 +526,7 @@ private final class CameraScreenComponent: CombinedComponent {
self.updated(transition: .easeInOut(duration: 0.2))
}
private var isTakingPhoto = false
var isTakingPhoto = false
func takePhoto() {
guard let controller = self.getController(), let camera = controller.camera else {
return
@ -527,20 +538,47 @@ private final class CameraScreenComponent: CombinedComponent {
controller.node.dismissAllTooltips()
let takePhoto = camera.takePhoto()
|> mapToSignal { value -> Signal<CameraScreen.Result, NoError> in
switch value {
case .began:
return .single(.pendingImage)
case let .finished(image, additionalImage, _):
return .single(.image(CameraScreen.Result.Image(image: image, additionalImage: additionalImage, additionalImagePosition: .topRight)))
case .failed:
return .complete()
let takePhoto = {
let takePhoto = camera.takePhoto()
|> mapToSignal { value -> Signal<CameraScreen.Result, NoError> in
switch value {
case .began:
return .single(.pendingImage)
case let .finished(image, additionalImage, _):
return .single(.image(CameraScreen.Result.Image(image: image, additionalImage: additionalImage, additionalImagePosition: .topRight)))
case .failed:
return .complete()
}
}
self.completion.invoke(takePhoto)
}
self.completion.invoke(takePhoto)
Queue.mainQueue().after(1.0) {
self.isTakingPhoto = false
let isFrontCamera = controller.cameraState.position == .front
let isFlashOn = controller.cameraState.flashMode == .on
if isFrontCamera && isFlashOn {
let previousBrightness = UIScreen.main.brightness
UIScreen.main.brightness = 1.0
let flashController = CameraFrontFlashOverlayController(color: controller.cameraState.flashTint.color)
controller.presentInGlobalOverlay(flashController)
Queue.mainQueue().after(0.1, {
takePhoto()
Queue.mainQueue().after(0.5, {
self.isTakingPhoto = false
self.brightnessArguments = (CACurrentMediaTime(), 0.25, UIScreen.main.brightness, previousBrightness)
self.animateBrightnessChange()
flashController.dismissAnimated()
})
})
} else {
takePhoto()
Queue.mainQueue().after(1.0) {
self.isTakingPhoto = false
}
}
}
@ -548,10 +586,33 @@ private final class CameraScreenComponent: CombinedComponent {
private var brightnessArguments: (Double, Double, CGFloat, CGFloat)?
private var brightnessAnimator: ConstantDisplayLinkAnimator?
private func updateBrightness() {
func updateScreenBrightness(flashOn: Bool?) {
guard let controller = self.getController() else {
return
}
let isFrontCamera = controller.cameraState.position == .front
let isVideo = controller.cameraState.mode == .video
let isFlashOn = flashOn ?? (controller.cameraState.flashMode == .on)
if isFrontCamera && isVideo && isFlashOn {
if self.initialBrightness == nil {
self.initialBrightness = UIScreen.main.brightness
self.brightnessArguments = (CACurrentMediaTime(), 0.2, UIScreen.main.brightness, 1.0)
self.animateBrightnessChange()
}
} else {
if let initialBrightness = self.initialBrightness {
self.initialBrightness = nil
self.brightnessArguments = (CACurrentMediaTime(), 0.2, UIScreen.main.brightness, initialBrightness)
self.animateBrightnessChange()
}
}
}
private func animateBrightnessChange() {
if self.brightnessAnimator == nil {
self.brightnessAnimator = ConstantDisplayLinkAnimator(update: { [weak self] in
self?.updateBrightness()
self?.animateBrightnessChange()
})
self.brightnessAnimator?.isPaused = true
}
@ -601,16 +662,7 @@ private final class CameraScreenComponent: CombinedComponent {
controller.updateCameraState({ $0.updatedRecording(pressing ? .holding : .handsFree).updatedDuration(0.0) }, transition: .spring(duration: 0.4))
if case .front = controller.cameraState.position {
self.initialBrightness = UIScreen.main.brightness
UIScreen.main.brightness = 1.0
Queue.mainQueue().after(0.2, {
startRecording()
})
} else {
startRecording()
}
startRecording()
}
func stopVideoRecording() {
@ -635,7 +687,7 @@ private final class CameraScreenComponent: CombinedComponent {
if case .front = controller.cameraState.position, let initialBrightness = self.initialBrightness {
self.initialBrightness = nil
self.brightnessArguments = (CACurrentMediaTime(), 0.2, UIScreen.main.brightness, initialBrightness)
self.updateBrightness()
self.animateBrightnessChange()
}
}
@ -776,7 +828,9 @@ private final class CameraScreenComponent: CombinedComponent {
.disappear(.default(alpha: true))
)
controlsTintColor = .black
if !state.isTakingPhoto {
controlsTintColor = .black
}
}
let shutterState: ShutterButtonState
@ -1190,6 +1244,7 @@ private final class CameraScreenComponent: CombinedComponent {
.disappear(.default(alpha: true))
)
}
return availableSize
}
}
@ -1564,7 +1619,7 @@ public class CameraScreen: ViewController {
self.additionalPreviewView.isPreviewing
)
|> filter { $0 && $1 }
|> take(1)).start(next: { [weak self] _, _ in
|> take(1)).startStandalone(next: { [weak self] _, _ in
self?.mainPreviewView.removePlaceholder(delay: 0.35)
self?.additionalPreviewView.removePlaceholder(delay: 0.35)
})
@ -1572,7 +1627,7 @@ public class CameraScreen: ViewController {
let _ = (self.mainPreviewView.isPreviewing
|> filter { $0 }
|> take(1)
|> deliverOnMainQueue).start(next: { [weak self] _ in
|> deliverOnMainQueue).startStandalone(next: { [weak self] _ in
self?.mainPreviewView.removePlaceholder(delay: 0.35)
})
}

View File

@ -1,6 +1,7 @@
import Foundation
import UIKit
import Display
import AsyncDisplayKit
import ComponentFlow
import RoundedRectWithTailPath
@ -431,3 +432,35 @@ private final class SliderView: UIView {
func performAction() {
}
}
final class CameraFrontFlashOverlayController: ViewController {
class Node: ASDisplayNode {
init(color: UIColor) {
super.init()
self.backgroundColor = color
}
}
private let color: UIColor
init(color: UIColor) {
self.color = color
super.init(navigationBarPresentationData: nil)
}
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func loadDisplayNode() {
self.displayNode = Node(color: self.color)
self.displayNodeDidLoad()
}
func dismissAnimated() {
self.displayNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false, completion: { _ in
self.dismiss()
})
}
}

View File

@ -158,15 +158,17 @@ public final class ChatMessageAttachedContentNode: ASDisplayNode {
}
let messageTheme = incoming ? presentationData.theme.theme.chat.message.incoming : presentationData.theme.theme.chat.message.outgoing
let author = message.author
let mainColor: UIColor
var secondaryColor: UIColor?
if !incoming {
mainColor = messageTheme.accentTextColor
if let _ = author?.nameColor?.dashColors.1 {
secondaryColor = messageTheme.accentTextColor
}
} else {
var authorNameColor: UIColor?
let author = message.author
if [Namespaces.Peer.CloudGroup, Namespaces.Peer.CloudChannel].contains(message.id.peerId.namespace), author?.id.namespace == Namespaces.Peer.CloudUser {
// if [Namespaces.Peer.CloudGroup, Namespaces.Peer.CloudChannel].contains(message.id.peerId.namespace), author?.id.namespace == Namespaces.Peer.CloudUser {
authorNameColor = author?.nameColor?.color
secondaryColor = author?.nameColor?.dashColors.1
@ -186,7 +188,7 @@ public final class ChatMessageAttachedContentNode: ASDisplayNode {
// authorNameColor = UIColor(hue: hue, saturation: saturation * 0.7, brightness: min(1.0, brightness * 1.2), alpha: 1.0)
// }
// }
}
// }
if let authorNameColor {
mainColor = authorNameColor