/* * VideoKit * Copyright © 2026 Yusuf Olokoba. All Rights Reserved. */ #nullable enable namespace VideoKit { using System; using System.Linq; using System.Runtime.InteropServices; using System.Threading.Tasks; using UnityEngine; using AOT; using Internal; using Status = Internal.VideoKit.Status; /// /// Multi camera device. /// This is a device that allows for streaming pixel buffers from multiple camera devices simultaneously. /// public sealed class MultiCameraDevice : MediaDevice { #region --Client API-- /// /// Camera devices that comprise this multi-camera device. /// public readonly CameraDevice[] cameras; /// /// Get the multi-camera device normalized hardware cost in range [0.0, 1.0]. /// public float? hardwareCost => handle.GetMultiCameraDeviceHardwareCost(out var cost) == Status.Ok ? cost : default; /// /// Get the multi-camera device normalized system pressure cost in range [0.0, 1.0]. /// public float? systemPressureCost => handle.GetMultiCameraDeviceSystemPressureCost(out var cost) == Status.Ok ? cost : default; /// /// Event raised when the system pressure level changes. /// NOTE: This event is invoked on a dedicated camera thread, not on the Unity main thread. /// public event Action? onSystemPressureChange; /// /// Check whether a given camera in the multi-camera device is running. /// /// Camera device. MUST be a member of this multi-camera device. public bool IsRunning(CameraDevice camera) => handle .GetMultiCameraDeviceIsRunning(camera, out var running) .Throw() == Status.Ok && running; /// /// Start running. /// /// Delegate to receive preview frames. public void StartRunning(Action handler) => StartRunning((IntPtr sampleBuffer) => { sampleBuffer.GetMultiCameraPixelBufferCamera(out var rawCamera).Throw(); var camera = cameras.First(cam => cam == rawCamera); var pixelBuffer = new PixelBuffer(sampleBuffer); handler(camera, pixelBuffer); }); /// /// Start the camera preview from a given camera in the multi-camera device. /// /// Camera device. MUST be a member of this multi-camera device. public void StartRunning(CameraDevice camera) => handle.StartRunning(camera).Throw(); /// /// Stop the camera preview from a specific camera in the multi-camera device. /// /// Camera device. MUST be a member of this multi-camera device. public void StopRunning(CameraDevice camera) => handle.StopRunning(camera).Throw(); #endregion #region --Discovery-- /// /// Check the current camera permission status. /// This is an alias for `CameraDevice.CheckPermissions`. /// /// Request permissions if the user has not yet been asked. /// Camera permission status. public static Task CheckPermissions(bool request = true) => CameraDevice.CheckPermissions(request); /// /// Discover available multi-camera devices. /// public static async Task Discover() { // Check session await VideoKitClient.Instance!.CheckSession(); // Discover var tcs = new TaskCompletionSource(); var handle = GCHandle.Alloc(tcs, GCHandleType.Normal); try { VideoKit.DiscoverMultiCameraDevices(OnDiscoverDevices, (IntPtr)handle).Throw(); return await tcs.Task; } catch { handle.Free(); throw; } } #endregion #region --Operations-- internal MultiCameraDevice(IntPtr device) : base(device) { device.GetMultiCameraDeviceCameraCount(out var count).Throw(); this.cameras = Enumerable.Range(0, count) .Select(idx => device.GetMultiCameraDeviceCamera(idx, out var camera).Throw() == Status.Ok ? camera : default) .Select(camera => new CameraDevice(camera, strong: false)) .ToArray(); device.SetMultiCameraDeviceSystemPressureChangeHandler(OnSystemPressureChange, (IntPtr)weakSelf); } public override string ToString() => $"MultiCameraDevice(uniqueId=\"{uniqueId}\", name=\"{name}\")"; [MonoPInvokeCallback(typeof(VideoKit.MediaDeviceDiscoveryHandler))] private static unsafe void OnDiscoverDevices(IntPtr context, IntPtr devices, int count) { try { // Check if (!VideoKit.IsAppDomainLoaded) return; // Get tcs var handle = (GCHandle)context; var tcs = handle.Target as TaskCompletionSource; handle.Free(); // Complete task var cameras = Enumerable .Range(0, count) .Select(idx => ((IntPtr*)devices)[idx]) .Select(device => new MultiCameraDevice(device)) .ToArray(); tcs?.SetResult(cameras); } catch (Exception ex) { Debug.LogException(ex); } } [MonoPInvokeCallback(typeof(VideoKit.MultiCameraDeviceSystemPressureHandler))] private static void OnSystemPressureChange(IntPtr context) { var handle = (GCHandle)context; var device = handle.Target as MultiCameraDevice; // weak device?.onSystemPressureChange?.Invoke(); } #endregion } }