/*
* VideoKit
* Copyright © 2026 Yusuf Olokoba. All Rights Reserved.
*/
#nullable enable
namespace VideoKit {
using System;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using UnityEngine;
using AOT;
using Internal;
using MediaDeviceFlags = Internal.VideoKit.MediaDeviceFlags;
using PermissionType = Internal.VideoKit.PermissionType;
using Status = Internal.VideoKit.Status;
///
/// Media device which provides media sample buffers.
///
public abstract class MediaDevice {
#region --Enumerations--
///
/// Device location.
///
public enum Location : int { // CHECK // Must match `VideoKit.h`
///
/// Device type is unknown.
///
Unknown = 0,
///
/// Device is internal.
///
Internal = 1 << 0,
///
/// Device is external.
///
External = 1 << 1,
}
///
/// Device permissions status.
///
public enum PermissionStatus : int { // CHECK // Must match `VideoKit.h`
///
/// User has not authorized or denied access to media device.
///
Unknown = 0,
///
/// User has denied access to media device.
///
Denied = 2,
///
/// User has authorized access to media device.
///
Authorized = 3
}
#endregion
#region --Properties--
///
/// Device unique ID.
///
public string uniqueId { get; protected set; }
///
/// Display friendly device name.
///
public string name { get; protected set; }
///
/// Device location.
///
public virtual Location location => handle.GetMediaDeviceFlags(out var flags).Throw() == Status.Ok ? (Location)((int)flags & 0x3) : default;
///
/// Device is the default device for its media type.
///
public virtual bool defaultForMediaType => handle.GetMediaDeviceFlags(out var flags).Throw() == Status.Ok ? flags.HasFlag(MediaDeviceFlags.Default) : default;
#endregion
#region --Events--
///
/// Event raised when the device is disconnected.
///
public event Action? onDisconnected;
#endregion
#region --Streaming--
///
/// Whether the device is running.
///
public virtual bool running => handle.GetMediaDeviceIsRunning(out var running).Throw() == Status.Ok ? running : default;
///
/// Stop running.
///
public virtual void StopRunning() {
if (running)
handle.StopRunning().Throw();
if (streamHandle != default)
streamHandle.Free();
streamHandle = default;
}
#endregion
#region --Operations--
protected readonly IntPtr handle;
protected readonly GCHandle weakSelf;
private readonly bool strong;
private GCHandle streamHandle;
internal MediaDevice(IntPtr handle, bool strong = true) {
this.handle = handle;
this.strong = strong;
this.weakSelf = GCHandle.Alloc(this, GCHandleType.Weak);
// Cache UID
var buffer = new StringBuilder(2048);
handle.GetMediaDeviceUniqueID(buffer, buffer.Capacity);
this.uniqueId = buffer.ToString();
// Cache name
buffer.Clear();
handle.GetMediaDeviceName(buffer, buffer.Capacity);
this.name = buffer.ToString();
// Register handlers
handle.SetDisconnectHandler(OnDeviceDisconnect, (IntPtr)weakSelf);
}
~MediaDevice() {
if (strong)
handle.ReleaseMediaDevice();
weakSelf.Free();
}
protected virtual void StartRunning(Action handler) {
streamHandle = GCHandle.Alloc(handler, GCHandleType.Normal);
try {
handle.StartRunning(OnSampleBuffer, (IntPtr)streamHandle).Throw();
} catch {
streamHandle.Free();
streamHandle = default;
throw;
}
}
protected static Task CheckPermissions(PermissionType type, bool request) {
var tcs = new TaskCompletionSource();
var handle = GCHandle.Alloc(tcs, GCHandleType.Normal);
try {
VideoKit.CheckPermissions(type, request, OnPermissionResult, (IntPtr)handle).Throw();
} catch (Exception ex) {
handle.Free();
tcs.SetException(ex);
}
return tcs.Task;
}
public static implicit operator IntPtr(MediaDevice device) => device.handle;
#endregion
#region --Callbacks--
[MonoPInvokeCallback(typeof(VideoKit.MediaDeviceDisconnectHandler))]
private static void OnDeviceDisconnect(IntPtr context, IntPtr _) {
// Check
if (!VideoKit.IsAppDomainLoaded)
return;
try {
// Invoke
var handle = (GCHandle)context;
var device = handle.Target as MediaDevice;
device?.onDisconnected?.Invoke();
} catch (Exception ex) {
Debug.LogException(ex);
}
}
[MonoPInvokeCallback(typeof(VideoKit.MediaDevicePermissionResultHandler))]
private static void OnPermissionResult(IntPtr context, PermissionStatus status) {
// Check
if (!VideoKit.IsAppDomainLoaded)
return;
// Get tcs
TaskCompletionSource? tcs;
try {
var handle = (GCHandle)context;
tcs = handle.Target as TaskCompletionSource;
handle.Free();
} catch (Exception ex) {
Debug.LogException(ex);
return;
}
// Invoke
tcs?.SetResult(status);
}
[MonoPInvokeCallback(typeof(VideoKit.SampleBufferHandler))]
private static void OnSampleBuffer(IntPtr context, IntPtr sampleBuffer) {
// Check
if (!VideoKit.IsAppDomainLoaded)
return;
// Invoke
try {
var handle = (GCHandle)context;
var handler = handle.Target as Action;
handler?.Invoke(sampleBuffer);
} catch (Exception ex) {
Debug.LogException(ex);
}
}
#endregion
}
}