How GyroCam Handles Orientation

GyroCam continuously monitors device motion and adapts the video orientation. Below are simplified snippets from the Swift implementation.

startOrientationUpdates()

@MainActor func startOrientationUpdates() {
    guard motionManager.isDeviceMotionAvailable else {
        showError("Motion data unavailable")
        return
    }
    if lockLandscape {
        if previousOrientation == .portrait {
            previousOrientation = .landscapeLeft
        }
    }
    motionManager.deviceMotionUpdateInterval = 0.1
    motionManager.startDeviceMotionUpdates(to: .main) { [weak self] motion, error in
        guard let self = self else { return }
        guard let motion = motion, error == nil else {
            self.showError(error?.localizedDescription ?? "Motion updates failed")
            return
        }
        let newOrientation = OrientationHelper.getOrientation(from: motion, currentOrientation: self.previousOrientation, cameraManager: self)
        if newOrientation != self.previousOrientation && newOrientation != .unknown {
            self.handleOrientationChange(newOrientation: newOrientation)
            self.previousOrientation = newOrientation
        }
        DispatchQueue.main.async {
            self.currentOrientation = newOrientation.description
            self.updateVideoOrientation(newOrientation)
        }
    }
}

OrientationHelper.getOrientation()

struct OrientationHelper {
    @MainActor static func getOrientation(from motion: CMDeviceMotion, currentOrientation: UIDeviceOrientation, cameraManager: CameraManager) -> UIDeviceOrientation {
        let gravity = motion.gravity
        let absX = abs(gravity.x)
        let absY = abs(gravity.y)
        let absZ = abs(gravity.z)

        // Determine the real device orientation first
        var realOrientation: UIDeviceOrientation = currentOrientation

        // Check for landscape/portrait
        if absX > absY {
            // Landscape orientation
            realOrientation = gravity.x > 0 ? .landscapeRight : .landscapeLeft
        } else if absY > absX {
            // Portrait orientation
            realOrientation = gravity.y > 0 ? .portraitUpsideDown : .portrait
        }

        if cameraManager.useRealOrientation {
            cameraManager.realOrientation = realOrientation.description
        }

        // If landscape lock is on, we need to determine what orientation to return
        if cameraManager.lockLandscape {
            // Skip face up/down check if locked to landscape
            if absZ > max(absX, absY) {
                if !cameraManager.useRealOrientation {
                    cameraManager.realOrientation = currentOrientation.description
                }
                return currentOrientation
            }

            // For landscape orientations, return the detected orientation
            if absX > absY {
                if !cameraManager.useRealOrientation {
                    cameraManager.realOrientation = gravity.x > 0 ? "Landscape Right" : "Landscape Left"
                }
                return gravity.x > 0 ? .landscapeRight : .landscapeLeft
            } else if absY > absX {
                if !cameraManager.useRealOrientation {
                    cameraManager.realOrientation = currentOrientation.description
                }
                return currentOrientation
            } else {
                if !cameraManager.useRealOrientation {
                    cameraManager.realOrientation = currentOrientation.description
                }
                return currentOrientation
            }
        } else {
            cameraManager.realOrientation = realOrientation.description
            return realOrientation
        }
    }
}

extension UIDeviceOrientation {
    var description: String {
        switch self {
        case .portrait: return "Portrait"
        case .portraitUpsideDown: return "Upside Down"
        case .landscapeLeft: return "Landscape Left"
        case .landscapeRight: return "Landscape Right"
        case .faceUp: return "Face Up"
        case .faceDown: return "Face Down"
        default: return "Unknown"
        }
    }
}