@ohm_studio/sdk 0.2.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,30 +1,105 @@
1
1
  /**
2
- * Browser MediaRecorder wrapper. Hides the boilerplate of permissions,
3
- * stream tracks, chunk collection, and cleanup so customers can ship a
4
- * working recorder UI in three lines:
2
+ * Browser audio recorder for clinical consults.
5
3
  *
6
- * const rec = new Recorder();
4
+ * Wraps `MediaRecorder` + `getUserMedia` + `AudioContext` + `Wake Lock`
5
+ * with clinically-tuned defaults (16 kHz mono, echo-cancel, noise-suppress,
6
+ * auto-gain) and a codec cascade that works on iOS Safari, Firefox, and
7
+ * every Chromium browser.
8
+ *
9
+ * const rec = new Recorder({
10
+ * onLevel: (rms) => setVu(rms),
11
+ * onError: (e) => toast(e.message),
12
+ * maxDurationMs: 10 * 60_000, // hard cap at 10 min
13
+ * silenceAutoStop: { ms: 6000 }, // stop after 6 s of silence
14
+ * wakeLock: true,
15
+ * });
7
16
  * await rec.start();
8
- * const blob = await rec.stop(); // → ready to pass to ohm.audio.extract
17
+ * const blob = await rec.stop(); // → ohm.audio.extract({ file: blob, ... })
9
18
  *
10
- * Designed for Vite / Next.js / CRA / any modern browser. SSR-safe — the
11
- * `MediaRecorder` API is only touched inside method bodies, so importing
12
- * this module doesn't break server bundles.
19
+ * SSR-safe every browser-only API is touched inside method bodies, never
20
+ * at module scope. Importing this file from a Next.js server bundle is OK.
13
21
  */
22
+ export type RecorderState = "idle" | "starting" | "recording" | "paused" | "stopping";
23
+ export type RecorderErrorCode = "NotSupported" | "PermissionDenied" | "NoMicrophone" | "MicrophoneBusy" | "OverConstrained" | "DeviceLost" | "InvalidState" | "Unknown";
24
+ export declare class RecorderError extends Error {
25
+ code: RecorderErrorCode;
26
+ cause?: unknown;
27
+ constructor(code: RecorderErrorCode, message: string, cause?: unknown);
28
+ }
14
29
  export interface RecorderOptions {
15
30
  /**
16
- * MIME type to request from the browser. Default `audio/webm;codecs=opus`
17
- * which is universally supported and small. Pass another value if your
18
- * extraction pipeline needs a specific format.
31
+ * MIME type to request. Default: best supported from the cascade
32
+ * `audio/webm;codecs=opus` `audio/mp4;codecs=mp4a.40.2`
33
+ * `audio/ogg;codecs=opus` `audio/mp4` `audio/webm`.
34
+ * Pass an explicit value only if your pipeline needs a specific format.
19
35
  */
20
36
  mimeType?: string;
21
- /** Audio constraints passed to `getUserMedia`. */
37
+ /**
38
+ * Audio bitrate in bits-per-second. Default 32 000 (32 kbps Opus is
39
+ * plenty for clinical speech and keeps files tiny).
40
+ */
41
+ audioBitsPerSecond?: number;
42
+ /**
43
+ * Selected microphone deviceId. Use `Recorder.listMicrophones()` to enumerate.
44
+ */
45
+ deviceId?: string;
46
+ /**
47
+ * Override `getUserMedia` constraints entirely. If omitted, the SDK applies
48
+ * clinically-tuned defaults: 16 kHz mono, echo cancellation on,
49
+ * noise suppression on, auto-gain on.
50
+ */
22
51
  audioConstraints?: MediaTrackConstraints;
23
- /** Called whenever recording state changes (idle / recording / stopping). */
52
+ /**
53
+ * If true (default), sets `sampleRate: { ideal: 16000 }`, `channelCount: 1`,
54
+ * and the three voice-cleanup flags. STT models expect 16 kHz mono.
55
+ */
56
+ clinicalDefaults?: boolean;
57
+ /**
58
+ * Emit a `dataavailable` chunk every N ms. Required for streaming uploads.
59
+ * Default 0 (one final chunk on stop).
60
+ */
61
+ timesliceMs?: number;
62
+ /**
63
+ * Hard cap — auto-stop after this many ms of recording. Default off.
64
+ */
65
+ maxDurationMs?: number;
66
+ /**
67
+ * Auto-stop after sustained silence. RMS below `threshold` for `ms`
68
+ * continuous milliseconds triggers stop. Default off.
69
+ */
70
+ silenceAutoStop?: {
71
+ /** ms of continuous silence before auto-stop. Default 6000. */
72
+ ms?: number;
73
+ /** Linear-RMS threshold 0–1. Default 0.012 (≈ -38 dBFS). */
74
+ threshold?: number;
75
+ };
76
+ /**
77
+ * Hold a screen Wake Lock while recording, so phones / tablets don't dim.
78
+ * Default false. Falls back silently when the API isn't available.
79
+ */
80
+ wakeLock?: boolean;
81
+ /**
82
+ * Pause recording when the tab becomes hidden. Default false.
83
+ * (We never auto-resume — the user has to come back and tap.)
84
+ */
85
+ pauseOnHidden?: boolean;
86
+ /** Recording state changes. */
24
87
  onStateChange?: (state: RecorderState) => void;
88
+ /** Periodic linear RMS level 0–1 (≈ 60 Hz). Wire to a VU meter. */
89
+ onLevel?: (rms: number) => void;
90
+ /** Each `dataavailable` chunk. Useful for streaming uploads. */
91
+ onChunk?: (chunk: Blob) => void;
92
+ /** All recorder errors flow through here in addition to throwing on the awaited call. */
93
+ onError?: (err: RecorderError) => void;
94
+ /** Microphone disconnected (cable unplugged, OS revoked permission). */
95
+ onDeviceLost?: () => void;
96
+ }
97
+ export interface MicrophoneInfo {
98
+ deviceId: string;
99
+ label: string;
100
+ groupId: string;
25
101
  }
26
- export type RecorderState = "idle" | "recording" | "stopping";
27
- /** Returns true if the runtime supports recording (browser with MediaRecorder). */
102
+ /** True if the runtime supports recording (browser with MediaRecorder + getUserMedia). */
28
103
  export declare function isRecordingSupported(): boolean;
29
104
  export declare class Recorder {
30
105
  private rec;
@@ -32,23 +107,64 @@ export declare class Recorder {
32
107
  private stream;
33
108
  private state;
34
109
  private opts;
110
+ private mime;
111
+ private startedAt;
112
+ private pausedAccumMs;
113
+ private pauseStartedAt;
114
+ private maxStopTimer;
115
+ private audioCtx;
116
+ private analyser;
117
+ private levelSource;
118
+ private levelRafId;
119
+ private levelBuffer;
120
+ private silenceSinceMs;
121
+ private wakeLockSentinel;
122
+ private visibilityHandler;
35
123
  constructor(opts?: RecorderOptions);
36
- /** Current recorder state. */
124
+ /** Static support check. */
125
+ static isSupported(): boolean;
126
+ /** Pick the best supported MIME type for this browser. */
127
+ static getSupportedMimeType(prefer?: string): string | undefined;
128
+ /**
129
+ * Probe microphone permission *without* requesting it. Returns:
130
+ * "granted" — already allowed
131
+ * "denied" — user has blocked access; calling start() will reject
132
+ * "prompt" — browser will show the dialog on next start()
133
+ * "unknown" — browser doesn't support the Permissions API
134
+ */
135
+ static probePermission(): Promise<"granted" | "denied" | "prompt" | "unknown">;
136
+ /** Enumerate microphones. Empty `label` until permission is granted at least once. */
137
+ static listMicrophones(): Promise<MicrophoneInfo[]>;
138
+ /** Current state. */
37
139
  get currentState(): RecorderState;
140
+ /** Final MIME type the browser is producing (set after `start`). */
141
+ get mimeType(): string | undefined;
142
+ /** Recorded duration in ms (excluding paused time). 0 when idle. */
143
+ getDuration(): number;
38
144
  /**
39
145
  * Request microphone access and begin recording. Resolves once the
40
- * underlying MediaRecorder fires its `start` event. Throws on permission
41
- * denial or unsupported runtime.
146
+ * underlying MediaRecorder has fired `start`.
42
147
  */
43
148
  start(): Promise<void>;
149
+ /** Pause recording. Resumable via `resume()`. */
150
+ pause(): void;
151
+ /** Resume after `pause()`. */
152
+ resume(): void;
44
153
  /**
45
- * Stop recording and resolve with the assembled Blob. The caller can
46
- * pass this Blob directly to `ohm.audio.extract({ file: blob, ... })`.
47
- * Stops all media tracks so the browser's mic indicator clears.
154
+ * Stop recording and return the assembled Blob, ready to pass to
155
+ * `ohm.audio.extract({ file: blob, })`. Releases mic, wake lock, AudioContext.
48
156
  */
49
157
  stop(): Promise<Blob>;
50
- /** Abort the recording without surfacing the captured audio. */
158
+ /** Stop after `ms` from now. Returns the same promise as `stop()`. */
159
+ stopAfter(ms: number): Promise<Blob>;
160
+ /** Abort — releases everything, no Blob. */
51
161
  cancel(): void;
162
+ private startLevelMeter;
163
+ private acquireWakeLock;
164
+ private releaseWakeLock;
165
+ private attachVisibility;
166
+ private detachVisibility;
167
+ private cleanupStream;
52
168
  private cleanup;
53
169
  private setState;
54
170
  }
@@ -1 +1 @@
1
- {"version":3,"file":"recorder.d.ts","sourceRoot":"","sources":["../src/recorder.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,MAAM,WAAW,eAAe;IAC9B;;;;OAIG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,kDAAkD;IAClD,gBAAgB,CAAC,EAAE,qBAAqB,CAAC;IACzC,6EAA6E;IAC7E,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,CAAC;CAChD;AAED,MAAM,MAAM,aAAa,GAAG,MAAM,GAAG,WAAW,GAAG,UAAU,CAAC;AAE9D,mFAAmF;AACnF,wBAAgB,oBAAoB,IAAI,OAAO,CAK9C;AAED,qBAAa,QAAQ;IACnB,OAAO,CAAC,GAAG,CAA8B;IACzC,OAAO,CAAC,MAAM,CAAc;IAC5B,OAAO,CAAC,MAAM,CAA4B;IAC1C,OAAO,CAAC,KAAK,CAAyB;IACtC,OAAO,CAAC,IAAI,CAAkB;gBAElB,IAAI,GAAE,eAAoB;IAItC,8BAA8B;IAC9B,IAAI,YAAY,IAAI,aAAa,CAEhC;IAED;;;;OAIG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IA6B5B;;;;OAIG;IACG,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAsB3B,gEAAgE;IAChE,MAAM,IAAI,IAAI;IAWd,OAAO,CAAC,OAAO;IAQf,OAAO,CAAC,QAAQ;CAKjB"}
1
+ {"version":3,"file":"recorder.d.ts","sourceRoot":"","sources":["../src/recorder.ts"],"names":[],"mappings":"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"}