TM-SGNL-iOS/Signal/Calls/UserInterface/LocalVideoView.swift
TeleMessage developers dde0620daf initial commit
2025-05-03 12:28:28 -07:00

125 lines
3.7 KiB
Swift

//
// Copyright 2020 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
//
import AVFoundation
import SignalRingRTC
import SignalServiceKit
public import WebRTC
class LocalVideoView: UIView {
private let localVideoCapturePreview = RTCCameraPreviewView()
var captureSession: AVCaptureSession? {
get { localVideoCapturePreview.captureSession }
set { localVideoCapturePreview.captureSession = newValue }
}
override var contentMode: UIView.ContentMode {
didSet { localVideoCapturePreview.contentMode = contentMode }
}
init() {
super.init(frame: .zero)
addSubview(localVideoCapturePreview)
if Platform.isSimulator {
backgroundColor = .brown
}
NotificationCenter.default.addObserver(
self,
selector: #selector(updateLocalVideoOrientation),
name: UIDevice.orientationDidChangeNotification,
object: nil
)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override var frame: CGRect {
didSet {
updateLocalVideoOrientation()
}
}
@objc
private func updateLocalVideoOrientation() {
defer {
localVideoCapturePreview.frame = bounds
}
// iPad supports rotating this view controller directly,
// so we don't need to do anything here.
guard !UIDevice.current.isIPad else { return }
// We lock this view to portrait only on phones, but the
// local video capture will rotate with the device's
// orientation (so the remote party will render your video
// in the correct orientation). As such, we need to rotate
// the local video preview layer so it *looks* like we're
// also always capturing in portrait.
switch UIDevice.current.orientation {
case .portrait:
localVideoCapturePreview.transform = .identity
case .portraitUpsideDown:
localVideoCapturePreview.transform = .init(rotationAngle: .pi)
case .landscapeLeft:
localVideoCapturePreview.transform = .init(rotationAngle: .halfPi)
case .landscapeRight:
localVideoCapturePreview.transform = .init(rotationAngle: .pi + .halfPi)
case .faceUp, .faceDown, .unknown:
break
@unknown default:
break
}
}
}
extension RTCCameraPreviewView {
var previewLayer: AVCaptureVideoPreviewLayer? {
return layer as? AVCaptureVideoPreviewLayer
}
open override var contentMode: UIView.ContentMode {
get {
guard let previewLayer = previewLayer else {
owsFailDebug("missing preview layer")
return .scaleToFill
}
switch previewLayer.videoGravity {
case .resizeAspectFill:
return .scaleAspectFill
case .resizeAspect:
return .scaleAspectFit
case .resize:
return .scaleToFill
default:
owsFailDebug("Unexpected contentMode")
return .scaleToFill
}
}
set {
guard let previewLayer = previewLayer else {
return owsFailDebug("missing preview layer")
}
switch newValue {
case .scaleAspectFill:
previewLayer.videoGravity = .resizeAspectFill
case .scaleAspectFit:
previewLayer.videoGravity = .resizeAspect
case .scaleToFill:
previewLayer.videoGravity = .resize
default:
owsFailDebug("Unexpected contentMode")
}
}
}
}