/* * 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 } }