All files / hooks/use-user-media/src types.ts

100% Statements 4/4
100% Branches 0/0
100% Functions 1/1
100% Lines 4/4

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115                                                                                    7x 7x 7x 7x                                                                                                                                          
/**
 * Where a stream request currently stands.
 *
 * `prompting` is its own state because it is the one the UI must *not* treat as
 * an error: the browser is showing its permission sheet and the user has not
 * answered yet. Collapsing it into "loading" is how scanners end up flashing a
 * "camera unavailable" message over the top of the permission dialog.
 */
export type UserMediaStatus =
  /** Nothing requested yet. */
  | "idle"
  /** Waiting for the user (or the OS) to answer. */
  | "prompting"
  /** A live stream is available. */
  | "granted"
  /** The user or a policy refused. */
  | "denied"
  /** `getUserMedia` does not exist here — an insecure origin, or no media stack. */
  | "unsupported"
  /** Something else went wrong; see `error`. */
  | "error";
 
/** Why a stream request failed, normalized across browsers. */
export type UserMediaErrorReason =
  | "denied"
  | "not-found"
  | "in-use"
  | "over-constrained"
  | "unsupported"
  | "unknown";
 
/**
 * A `getUserMedia` failure with a reason worth showing a user.
 *
 * Every browser words its `DOMException` differently, and "NotReadableError"
 * means nothing to anyone. The distinction that matters in a UI is between
 * "allow access" and "close the app already using your camera", and those are
 * two different `name`s that no message string reliably distinguishes.
 */
export class UserMediaError extends Error {
  readonly reason: UserMediaErrorReason;
 
  constructor(reason: UserMediaErrorReason, message: string, readonly cause?: unknown) {
    super(message);
    this.name = "UserMediaError";
    this.reason = reason;
  }
}
 
export interface UseUserMediaOptions {
  /**
   * What to request. Passing `false`/omitting video (or audio) leaves that
   * track out entirely.
   *
   * @default { video: true }
   */
  constraints?: MediaStreamConstraints;
  /**
   * Acquire as soon as the hook mounts.
   *
   * Off by default: `getUserMedia` shows a permission prompt, and a prompt the
   * user did not ask for is the fastest way to get denied permanently.
   *
   * @default false
   */
  autoStart?: boolean;
  /**
   * Preferred camera. Merged into the video constraints, and re-applied when
   * it changes — the usual way to offer a "flip camera" button.
   */
  facingMode?: "user" | "environment";
  /** Exact device to open, from {@link UseUserMediaReturn.devices}. */
  deviceId?: string;
  /** Called whenever a new stream becomes available. */
  onStream?: (stream: MediaStream) => void;
  /** Called when acquisition fails. */
  onError?: (error: UserMediaError) => void;
}
 
export interface UseUserMediaReturn {
  /** The live stream, or `null` when there is not one. */
  stream: MediaStream | null;
  status: UserMediaStatus;
  /** The last failure, cleared by a successful `start()`. */
  error: UserMediaError | null;
  /** Whether this environment can open a stream at all. */
  isSupported: boolean;
 
  /** Request (or re-request) a stream. Resolves once the attempt settles. */
  start: () => Promise<MediaStream | null>;
  /** Stop every track and drop the stream. Safe to call when already stopped. */
  stop: () => void;
 
  /**
   * Video input devices.
   *
   * Empty until permission has been granted once: browsers hide the list —
   * and, in Safari and Firefox, even the labels — from a page that has never
   * been allowed to see a camera.
   */
  devices: MediaDeviceInfo[];
  /** The device the current stream came from, if it reported one. */
  activeDeviceId: string | null;
  /** Open a specific device. */
  selectDevice: (deviceId: string) => Promise<MediaStream | null>;
  /** Cycle to the next video input, wrapping around. */
  switchDevice: () => Promise<MediaStream | null>;
 
  /** Whether the active camera reports torch support. */
  isTorchSupported: boolean;
  torch: boolean;
  /** Turn the torch on or off. Resolves `false` when the camera refused. */
  setTorch: (on: boolean) => Promise<boolean>;
}