/*
* VideoKit
* Copyright © 2026 Yusuf Olokoba. All Rights Reserved.
*/
#nullable enable
namespace VideoKit {
using AOT;
using System;
using System.Linq;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
using UnityEngine;
using Internal;
using MediaDeviceFlags = Internal.VideoKit.MediaDeviceFlags;
using Status = Internal.VideoKit.Status;
///
/// Camera device.
///
public sealed class CameraDevice : MediaDevice {
#region --Types--
///
/// Exposure mode.
///
public enum ExposureMode : int {
///
/// Continuous auto exposure.
///
Continuous = 0,
///
/// Locked auto exposure.
///
Locked = 1,
///
/// Manual exposure.
///
Manual = 2
}
///
/// Photo flash mode.
///
public enum FlashMode : int {
///
/// Never use flash.
///
Off = 0,
///
/// Always use flash.
///
On = 1,
///
/// Let the sensor detect if it needs flash.
///
Auto = 2
}
///
/// Focus mode.
///
public enum FocusMode : int {
///
/// Continuous autofocus.
///
Continuous = 0,
///
/// Locked focus.
///
Locked = 1,
}
///
/// Torch mode.
///
public enum TorchMode : int {
///
/// Disabled torch.
///
Off = 0,
///
/// Maximum supported torch level.
///
Maximum = 100
}
///
/// Video stabilization mode.
///
public enum VideoStabilizationMode : int {
///
/// Disabled video stabilization.
///
Off = 0,
///
/// Standard video stabilization.
///
Standard = 1
}
///
/// White balance mode.
///
public enum WhiteBalanceMode : int {
///
/// Continuous auto white balance.
///
Continuous = 0,
///
/// Locked auto white balance.
///
Locked = 1,
}
#endregion
#region --Properties--
///
/// Whether this camera is front facing.
///
public bool frontFacing =>
handle.GetMediaDeviceFlags(out var flags).Throw() == Status.Ok &&
flags.HasFlag(MediaDeviceFlags.FrontFacing);
///
/// Whether setting the flash mode for photo capture is supported.
///
public bool flashSupported =>
handle.GetMediaDeviceFlags(out var flags).Throw() == Status.Ok &&
flags.HasFlag(MediaDeviceFlags.Flash);
///
/// Whether setting the torch level is supported.
///
public bool torchSupported =>
handle.GetMediaDeviceFlags(out var flags).Throw() == Status.Ok &&
flags.HasFlag(MediaDeviceFlags.Torch);
///
/// Whether setting the exposure point is supported.
///
public bool exposurePointSupported =>
handle.GetMediaDeviceFlags(out var flags).Throw() == Status.Ok &&
flags.HasFlag(MediaDeviceFlags.ExposurePoint);
///
/// Whether setting the focus point is supported.
///
public bool focusPointSupported =>
handle.GetMediaDeviceFlags(out var flags).Throw() == Status.Ok &&
flags.HasFlag(MediaDeviceFlags.FocusPoint);
///
/// Whether depth streaming is supported.
///
public bool depthStreamingSupported =>
handle.GetMediaDeviceFlags(out var flags).Throw() == Status.Ok &&
flags.HasFlag(MediaDeviceFlags.Depth);
///
/// Field of view in degrees.
///
public (float width, float height) fieldOfView => handle.GetCameraDeviceFieldOfView(out var x, out var y) == Status.Ok ? (x, y) : default;
///
/// Exposure bias range in EV.
///
public (float min, float max) exposureBiasRange => handle.GetCameraDeviceExposureBiasRange(out var min, out var max) == Status.Ok ? (min, max) : default;
///
/// Exposure duration range in seconds.
///
public (float min, float max) exposureDurationRange => handle.GetCameraDeviceExposureDurationRange(out var min, out var max) == Status.Ok ? (min, max) : default;
///
/// Sensor sensitivity range.
///
public (float min, float max) ISORange => handle.GetCameraDeviceISORange(out var min, out var max) == Status.Ok ? (min, max) : default;
///
/// Zoom ratio range.
///
public (float min, float max) zoomRange => handle.GetCameraDeviceZoomRange(out var min, out var max) == Status.Ok ? (min, max) : (1f, 1f);
///
/// Get or set the preview resolution.
///
public (int width, int height) previewResolution {
get => handle.GetCameraDevicePreviewResolution(out var w, out var h).Throw() == Status.Ok ? (w, h) : default;
set => handle.SetCameraDevicePreviewResolution(value.width, value.height);
}
///
/// Get or set the photo resolution.
///
public (int width, int height) photoResolution {
get => handle.GetCameraDevicePhotoResolution(out var w, out var h) == Status.Ok ? (w, h) : default;
set => handle.SetCameraDevicePhotoResolution(value.width, value.height); // don't throw
}
///
/// Get or set the preview framerate.
///
public float frameRate {
get => handle.GetCameraDeviceFrameRate(out var frameRate) == Status.Ok ? frameRate : default;
set => handle.SetCameraDeviceFrameRate(value);
}
///
/// Get or set the exposure mode.
/// If the requested exposure mode is not supported, the camera device will ignore.
///
public ExposureMode exposureMode {
get => handle.GetCameraDeviceExposureMode(out var mode) == Status.Ok ? mode : default;
set => handle.SetCameraDeviceExposureMode(value).Throw();
}
///
/// Get or set the exposure bias.
/// This value must be in the range returned by `exposureRange`.
///
public float exposureBias {
get => handle.GetCameraDeviceExposureBias(out var bias) == Status.Ok ? bias : default;
set => handle.SetCameraDeviceExposureBias(value).Throw();
}
///
/// Get or set the current exposure duration in seconds.
///
public float exposureDuration => handle.GetCameraDeviceExposureDuration(out var duration) == Status.Ok ? duration : default;
///
/// Get or set the current exposure sensitivity.
///
public float ISO => handle.GetCameraDeviceISO(out var ISO) == Status.Ok ? ISO : default;
///
/// Get or set the photo flash mode.
///
public FlashMode flashMode {
get => handle.GetCameraDeviceFlashMode(out var mode) == Status.Ok ? mode : default;
set => handle.SetCameraDeviceFlashMode(value).Throw();
}
///
/// Get or set the focus mode.
///
public FocusMode focusMode {
get => handle.GetCameraDeviceFocusMode(out var mode) == Status.Ok ? mode : default;
set => handle.SetCameraDeviceFocusMode(value).Throw();
}
///
/// Get or set the torch mode.
///
public TorchMode torchMode {
get => handle.GetCameraDeviceTorchMode(out var mode) == Status.Ok ? mode : default;
set => handle.SetCameraDeviceTorchMode(value).Throw();
}
///
/// Get or set the white balance mode.
///
public WhiteBalanceMode whiteBalanceMode {
get => handle.GetCameraDeviceWhiteBalanceMode(out var mode) == Status.Ok ? mode : default;
set => handle.SetCameraDeviceWhiteBalanceMode(value).Throw();
}
///
/// Get or set the video stabilization mode.
///
public VideoStabilizationMode videoStabilizationMode {
get => handle.GetCameraDeviceVideoStabilizationMode(out var mode) == Status.Ok ? mode : default;
set => handle.SetCameraDeviceVideoStabilizationMode(value).Throw();
}
///
/// Get or set the zoom ratio.
/// This value must be in the range returned by `zoomRange`.
///
public float zoomRatio {
get => handle.GetCameraDeviceZoomRatio(out var mode) == Status.Ok ? mode : default;
set => handle.SetCameraDeviceZoomRatio(value).Throw();
}
#endregion
#region --Controls--
///
/// Check if a given exposure mode is supported by the camera device.
///
/// Exposure mode.
public bool IsExposureModeSupported(ExposureMode mode) => mode switch {
ExposureMode.Continuous => handle.GetMediaDeviceFlags(out var flags).Throw() == Status.Ok && flags.HasFlag(MediaDeviceFlags.ExposureContinuous),
ExposureMode.Locked => handle.GetMediaDeviceFlags(out var flags).Throw() == Status.Ok && flags.HasFlag(MediaDeviceFlags.ExposureLock),
ExposureMode.Manual => handle.GetMediaDeviceFlags(out var flags).Throw() == Status.Ok && flags.HasFlag(MediaDeviceFlags.ExposureManual),
_ => false
};
///
/// Check if a given focus mode is supported by the camera device.
///
/// Focus mode.
public bool IsFocusModeSupported(FocusMode mode) => mode switch {
FocusMode.Continuous => handle.GetMediaDeviceFlags(out var flags).Throw() == Status.Ok && flags.HasFlag(MediaDeviceFlags.FocusContinuous),
FocusMode.Locked => handle.GetMediaDeviceFlags(out var flags).Throw() == Status.Ok && flags.HasFlag(MediaDeviceFlags.FocusLock),
_ => false,
};
///
/// Check if a given white balance mode is supported by the camera device.
///
/// White balance mode.
public bool IsWhiteBalanceModeSupported(WhiteBalanceMode mode) => mode switch {
WhiteBalanceMode.Continuous => handle.GetMediaDeviceFlags(out var flags).Throw() == Status.Ok && flags.HasFlag(MediaDeviceFlags.WhiteBalanceContinuous),
WhiteBalanceMode.Locked => handle.GetMediaDeviceFlags(out var flags).Throw() == Status.Ok && flags.HasFlag(MediaDeviceFlags.WhiteBalanceLock),
_ => false
};
///
/// Check if a given video stabilization mode is supported by the camera device.
///
/// Video stabilization mode.
public bool IsVideoStabilizationModeSupported(VideoStabilizationMode mode) => mode switch {
VideoStabilizationMode.Off => true,
VideoStabilizationMode.Standard => handle.GetMediaDeviceFlags(out var flags).Throw() == Status.Ok && flags.HasFlag(MediaDeviceFlags.VideoStabilization),
_ => false
};
///
/// Set manual exposure.
///
/// Exposure duration in seconds. MUST be in `exposureDurationRange`.
/// Sensor sensitivity ISO value. MUST be in `ISORange`.
public void SetExposureDuration(float duration, float ISO) => handle.SetCameraDeviceExposureDuration(duration, ISO).Throw();
///
/// Set the exposure point of interest.
/// The point is specified in normalized coordinates in range [0.0, 1.0].
///
/// Normalized x coordinate.
/// Normalized y coordinate.
public void SetExposurePoint(float x, float y) => handle.SetCameraDeviceExposurePoint(x, y).Throw();
///
/// Set the focus point of interest.
/// The point is specified in normalized coordinates in range [0.0, 1.0].
///
/// Normalized x coordinate.
/// Normalized y coordinate.
public void SetFocusPoint(float x, float y) => handle.SetCameraDeviceFocusPoint(x, y).Throw();
#endregion
#region --Streaming--
///
/// Start the camera preview.
///
/// Delegate to receive preview image frames. Note that this delegate is invoked on a dedicated thread.
public void StartRunning(Action handler) => StartRunning((IntPtr sampleBuffer) => {
handler(new PixelBuffer(sampleBuffer));
});
///
/// Capture a photo.
///
/// Delegate to receive high-resolution photo. Note that this delegate is invoked on a dedicated thread.
public void CapturePhoto(Action handler) {
var handle = GCHandle.Alloc(handler, GCHandleType.Normal);
this.handle.CapturePhoto(OnCapturePhoto, (IntPtr)handle).Throw();
}
#endregion
#region --Discovery--
///
/// Check the current camera permission status.
///
/// Request permissions if the user has not yet been asked.
/// Camera permission status.
public static Task CheckPermissions(bool request = true) => CheckPermissions(
VideoKit.PermissionType.Camera,
request
);
///
/// Discover available 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.DiscoverCameraDevices(OnDiscoverDevices, (IntPtr)handle).Throw();
return await tcs.Task;
} catch {
handle.Free();
throw;
}
}
#endregion
#region --Operations--
private int priority { // #24
get {
var order = 0;
if (!defaultForMediaType)
order += 1;
if (location == Location.External)
order += 10;
if (location == Location.Unknown)
order += 100;
return order;
}
}
internal CameraDevice (IntPtr device, bool strong = true) : base(device, strong: strong) { }
public override string ToString () => $"CameraDevice(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
var cameras = Enumerable
.Range(0, count)
.Select(idx => ((IntPtr*)devices)[idx])
.Select(device => new CameraDevice(device, strong: true))
.OrderBy(device => device.priority)
.ToArray();
tcs?.SetResult(cameras);
} catch (Exception ex) {
Debug.LogException(ex);
}
}
[MonoPInvokeCallback(typeof(VideoKit.SampleBufferHandler))]
private static unsafe void OnCapturePhoto(IntPtr context, IntPtr sampleBuffer) {
try {
// Check
if (!VideoKit.IsAppDomainLoaded)
return;
// Get handler
var handle = (GCHandle)context;
var handler = handle.Target as Action;
handle.Free();
// Invoke
handler?.Invoke(new PixelBuffer(sampleBuffer));
} catch (Exception ex) {
Debug.LogException(ex);
}
}
#endregion
}
}