Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
signalapp
GitHub Repository: signalapp/Signal-iOS
Path: blob/main/Signal/Calls/UserInterface/LocalVideoView.swift
1 views
//
// 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
    }

    override open var contentMode: UIView.ContentMode {
        get {
            guard let 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 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")
            }
        }
    }
}