@ohm_studio/sdk 0.1.0 → 0.3.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.
package/README.md CHANGED
@@ -1,8 +1,10 @@
1
- # @ohm_studio/sdk
1
+ # OHM SDK · `@ohm_studio/sdk`
2
+
3
+ > The **short name** is `OHM SDK`. The **full npm name** is `@ohm_studio/sdk` — that's what you install. Both names refer to the same package.
2
4
 
3
5
  OHM Studio SDK for **JavaScript / TypeScript / React** — turn voice into structured clinical JSON, FHIR-ready, multi-language. Works in the browser, Node 18+, and Next.js (server actions, route handlers, edge runtime).
4
6
 
5
- > For React Native, install [`@ohm_studio/sdk-react-native`](https://www.npmjs.com/package/@ohm_studio/sdk-react-native) instead.
7
+ > For React Native, install [`@ohm_studio/sdk-react-native`](https://www.npmjs.com/package/@ohm_studio/sdk-react-native) (**OHM RN SDK**) instead.
6
8
 
7
9
  ## Install
8
10
 
package/dist/index.d.ts CHANGED
@@ -1,6 +1,8 @@
1
1
  import { OHMCoreClient } from "@ohm_studio/sdk-core";
2
- import type { OHMClientOptions } from "@ohm_studio/sdk-core";
2
+ import type { OHMInit } from "@ohm_studio/sdk-core";
3
3
  export * from "@ohm_studio/sdk-core";
4
+ export { Recorder, RecorderError, isRecordingSupported } from "./recorder";
5
+ export type { RecorderOptions, RecorderState, RecorderErrorCode, MicrophoneInfo, } from "./recorder";
4
6
  /**
5
7
  * OHM Studio SDK for JavaScript / TypeScript.
6
8
  *
@@ -20,12 +22,22 @@ export * from "@ohm_studio/sdk-core";
20
22
  * });
21
23
  */
22
24
  export declare class OHM extends OHMCoreClient {
23
- constructor(opts: OHMClientOptions);
25
+ constructor(init: OHMInit);
24
26
  protected runMultipart<T>(opts: {
25
27
  path: string;
26
28
  file: any;
27
29
  fields?: Record<string, string>;
28
30
  }): Promise<T>;
31
+ /**
32
+ * Build a browser/Node FormData from one of:
33
+ * - browser File
34
+ * - browser Blob
35
+ * - Node-style `{ buffer, name?, type? }`
36
+ *
37
+ * Used by both `runMultipart` (one-shot) and the streaming path
38
+ * (`audio.extract.stream`).
39
+ */
40
+ protected buildMultipartBody(file: unknown, fields?: Record<string, string>): Promise<FormData>;
29
41
  }
30
42
  export default OHM;
31
43
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAE7D,cAAc,sBAAsB,CAAC;AAErC;;;;;;;;;;;;;;;;;GAiBG;AACH,qBAAa,GAAI,SAAQ,aAAa;gBACxB,IAAI,EAAE,gBAAgB;cAIlB,YAAY,CAAC,CAAC,EAAE,IAAI,EAAE;QACpC,IAAI,EAAE,MAAM,CAAC;QACb,IAAI,EAAE,GAAG,CAAC;QACV,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;KACjC,GAAG,OAAO,CAAC,CAAC,CAAC;CAuBf;AAED,eAAe,GAAG,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,sBAAsB,CAAC;AAEpD,cAAc,sBAAsB,CAAC;AACrC,OAAO,EAAE,QAAQ,EAAE,aAAa,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAC;AAC3E,YAAY,EACV,eAAe,EACf,aAAa,EACb,iBAAiB,EACjB,cAAc,GACf,MAAM,YAAY,CAAC;AAEpB;;;;;;;;;;;;;;;;;GAiBG;AACH,qBAAa,GAAI,SAAQ,aAAa;gBACxB,IAAI,EAAE,OAAO;cAIT,YAAY,CAAC,CAAC,EAAE,IAAI,EAAE;QACpC,IAAI,EAAE,MAAM,CAAC;QACb,IAAI,EAAE,GAAG,CAAC;QACV,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;KACjC,GAAG,OAAO,CAAC,CAAC,CAAC;IAKd;;;;;;;;OAQG;cACa,kBAAkB,CAChC,IAAI,EAAE,OAAO,EACb,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAC9B,OAAO,CAAC,QAAQ,CAAC;CAuBrB;AAED,eAAe,GAAG,CAAC"}
package/dist/index.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import { OHMCoreClient } from "@ohm_studio/sdk-core";
2
2
  export * from "@ohm_studio/sdk-core";
3
+ export { Recorder, RecorderError, isRecordingSupported } from "./recorder";
3
4
  /**
4
5
  * OHM Studio SDK for JavaScript / TypeScript.
5
6
  *
@@ -19,32 +20,45 @@ export * from "@ohm_studio/sdk-core";
19
20
  * });
20
21
  */
21
22
  export class OHM extends OHMCoreClient {
22
- constructor(opts) {
23
- super(opts);
23
+ constructor(init) {
24
+ super(init);
24
25
  }
25
26
  async runMultipart(opts) {
27
+ const fd = await this.buildMultipartBody(opts.file, opts.fields);
28
+ return this.requestRaw("POST", opts.path, { body: fd });
29
+ }
30
+ /**
31
+ * Build a browser/Node FormData from one of:
32
+ * - browser File
33
+ * - browser Blob
34
+ * - Node-style `{ buffer, name?, type? }`
35
+ *
36
+ * Used by both `runMultipart` (one-shot) and the streaming path
37
+ * (`audio.extract.stream`).
38
+ */
39
+ async buildMultipartBody(file, fields) {
26
40
  const fd = new FormData();
27
- // Browser File / Blob, or a Node Buffer (which the user must wrap as Blob).
28
- if (typeof File !== "undefined" && opts.file instanceof File) {
29
- fd.append("file", opts.file);
41
+ if (typeof File !== "undefined" && file instanceof File) {
42
+ fd.append("file", file);
30
43
  }
31
- else if (typeof Blob !== "undefined" && opts.file instanceof Blob) {
32
- fd.append("file", opts.file, "audio.bin");
44
+ else if (typeof Blob !== "undefined" && file instanceof Blob) {
45
+ fd.append("file", file, "audio.bin");
33
46
  }
34
- else if (opts.file && typeof opts.file === "object" && "buffer" in opts.file) {
47
+ else if (file && typeof file === "object" && "buffer" in file) {
35
48
  // Node-style { buffer, name?, type? }
36
- const blob = new Blob([opts.file.buffer], {
37
- type: opts.file.type || "application/octet-stream",
49
+ const f = file;
50
+ const blob = new Blob([f.buffer], {
51
+ type: f.type || "application/octet-stream",
38
52
  });
39
- fd.append("file", blob, opts.file.name || "audio.bin");
53
+ fd.append("file", blob, f.name || "audio.bin");
40
54
  }
41
55
  else {
42
56
  throw new Error("Unsupported file input — pass a File, Blob, or { buffer, name?, type? }");
43
57
  }
44
- for (const [k, v] of Object.entries(opts.fields || {})) {
58
+ for (const [k, v] of Object.entries(fields || {})) {
45
59
  fd.append(k, v);
46
60
  }
47
- return this.requestRaw("POST", opts.path, { body: fd });
61
+ return fd;
48
62
  }
49
63
  }
50
64
  export default OHM;
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAGrD,cAAc,sBAAsB,CAAC;AAErC;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,OAAO,GAAI,SAAQ,aAAa;IACpC,YAAY,IAAsB;QAChC,KAAK,CAAC,IAAI,CAAC,CAAC;IACd,CAAC;IAES,KAAK,CAAC,YAAY,CAAI,IAI/B;QACC,MAAM,EAAE,GAAG,IAAI,QAAQ,EAAE,CAAC;QAC1B,4EAA4E;QAC5E,IAAI,OAAO,IAAI,KAAK,WAAW,IAAI,IAAI,CAAC,IAAI,YAAY,IAAI,EAAE,CAAC;YAC7D,EAAE,CAAC,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC;QAC/B,CAAC;aAAM,IAAI,OAAO,IAAI,KAAK,WAAW,IAAI,IAAI,CAAC,IAAI,YAAY,IAAI,EAAE,CAAC;YACpE,EAAE,CAAC,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,IAAI,EAAE,WAAW,CAAC,CAAC;QAC5C,CAAC;aAAM,IAAI,IAAI,CAAC,IAAI,IAAI,OAAO,IAAI,CAAC,IAAI,KAAK,QAAQ,IAAI,QAAQ,IAAI,IAAI,CAAC,IAAI,EAAE,CAAC;YAC/E,sCAAsC;YACtC,MAAM,IAAI,GAAG,IAAI,IAAI,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE;gBACxC,IAAI,EAAE,IAAI,CAAC,IAAI,CAAC,IAAI,IAAI,0BAA0B;aACnD,CAAC,CAAC;YACH,EAAE,CAAC,MAAM,CAAC,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,CAAC,IAAI,IAAI,WAAW,CAAC,CAAC;QACzD,CAAC;aAAM,CAAC;YACN,MAAM,IAAI,KAAK,CACb,yEAAyE,CAC1E,CAAC;QACJ,CAAC;QACD,KAAK,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,MAAM,IAAI,EAAE,CAAC,EAAE,CAAC;YACvD,EAAE,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;QAClB,CAAC;QACD,OAAO,IAAI,CAAC,UAAU,CAAI,MAAM,EAAE,IAAI,CAAC,IAAI,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC,CAAC;IAC7D,CAAC;CACF;AAED,eAAe,GAAG,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAGrD,cAAc,sBAAsB,CAAC;AACrC,OAAO,EAAE,QAAQ,EAAE,aAAa,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAC;AAQ3E;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,OAAO,GAAI,SAAQ,aAAa;IACpC,YAAY,IAAa;QACvB,KAAK,CAAC,IAAI,CAAC,CAAC;IACd,CAAC;IAES,KAAK,CAAC,YAAY,CAAI,IAI/B;QACC,MAAM,EAAE,GAAG,MAAM,IAAI,CAAC,kBAAkB,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,MAAM,CAAa,CAAC;QAC7E,OAAO,IAAI,CAAC,UAAU,CAAI,MAAM,EAAE,IAAI,CAAC,IAAI,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC,CAAC;IAC7D,CAAC;IAED;;;;;;;;OAQG;IACO,KAAK,CAAC,kBAAkB,CAChC,IAAa,EACb,MAA+B;QAE/B,MAAM,EAAE,GAAG,IAAI,QAAQ,EAAE,CAAC;QAC1B,IAAI,OAAO,IAAI,KAAK,WAAW,IAAI,IAAI,YAAY,IAAI,EAAE,CAAC;YACxD,EAAE,CAAC,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;QAC1B,CAAC;aAAM,IAAI,OAAO,IAAI,KAAK,WAAW,IAAI,IAAI,YAAY,IAAI,EAAE,CAAC;YAC/D,EAAE,CAAC,MAAM,CAAC,MAAM,EAAE,IAAI,EAAE,WAAW,CAAC,CAAC;QACvC,CAAC;aAAM,IAAI,IAAI,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,QAAQ,IAAK,IAAY,EAAE,CAAC;YACzE,sCAAsC;YACtC,MAAM,CAAC,GAAG,IAA6D,CAAC;YACxE,MAAM,IAAI,GAAG,IAAI,IAAI,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,EAAE;gBAChC,IAAI,EAAE,CAAC,CAAC,IAAI,IAAI,0BAA0B;aAC3C,CAAC,CAAC;YACH,EAAE,CAAC,MAAM,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,IAAI,WAAW,CAAC,CAAC;QACjD,CAAC;aAAM,CAAC;YACN,MAAM,IAAI,KAAK,CACb,yEAAyE,CAC1E,CAAC;QACJ,CAAC;QACD,KAAK,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,IAAI,EAAE,CAAC,EAAE,CAAC;YAClD,EAAE,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;QAClB,CAAC;QACD,OAAO,EAAE,CAAC;IACZ,CAAC;CACF;AAED,eAAe,GAAG,CAAC"}
@@ -1,6 +1,7 @@
1
1
  import { type ReactNode } from "react";
2
2
  import type { AudioExtractInput, AudioExtractResult, ExtractInput, ExtractResult, SummarizeInput, SummarizeResult } from "@ohm_studio/sdk-core";
3
3
  import { OHM } from "../index";
4
+ import { RecorderError, type RecorderOptions, type RecorderState } from "../recorder";
4
5
  export declare function OhmProvider({ client, children, }: {
5
6
  client: OHM;
6
7
  children: ReactNode;
@@ -33,4 +34,49 @@ export declare function useOhmSummarize(): {
33
34
  error: Error | null;
34
35
  isPending: boolean;
35
36
  };
37
+ export interface UseRecorderOptions extends Omit<RecorderOptions, "onStateChange" | "onLevel" | "onError"> {
38
+ /**
39
+ * If supplied, on stop the recorded blob is automatically sent to
40
+ * `ohm.audio.extract({ apiSlug, file })` and exposed on `data`/`transcript`.
41
+ * Requires <OhmProvider client={...}> above.
42
+ */
43
+ apiSlug?: string;
44
+ /** Pass-through inputs for auto-extract. */
45
+ extractInputs?: Record<string, unknown>;
46
+ /** Optional language hint. */
47
+ extractLanguage?: string;
48
+ }
49
+ export interface UseRecorderReturn<T> {
50
+ state: RecorderState;
51
+ isRecording: boolean;
52
+ isPaused: boolean;
53
+ /** Linear RMS 0–1 — wire to a VU meter. */
54
+ level: number;
55
+ /** Recorded duration in seconds (live, ticks every 250ms). */
56
+ durationSec: number;
57
+ /** Resulting blob after stop. */
58
+ blob: Blob | null;
59
+ /** Recorder error, if any. */
60
+ error: RecorderError | null;
61
+ start: () => Promise<void>;
62
+ stop: () => Promise<Blob | null>;
63
+ pause: () => void;
64
+ resume: () => void;
65
+ cancel: () => void;
66
+ reset: () => void;
67
+ extracting: boolean;
68
+ transcript: string | null;
69
+ data: T | null;
70
+ extractError: Error | null;
71
+ }
72
+ /**
73
+ * `useRecorder` — the one-call recorder hook for clinical apps.
74
+ *
75
+ * const r = useRecorder({ apiSlug: "opd-clinic", silenceAutoStop: { ms: 6000 } });
76
+ * <button onClick={r.isRecording ? r.stop : r.start}>
77
+ * {r.isRecording ? `Stop (${r.durationSec.toFixed(0)}s)` : "Record"}
78
+ * </button>
79
+ * {r.transcript && <pre>{r.transcript}</pre>}
80
+ */
81
+ export declare function useRecorder<T = Record<string, unknown>>(opts?: UseRecorderOptions): UseRecorderReturn<T>;
36
82
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/react/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAML,KAAK,SAAS,EACf,MAAM,OAAO,CAAC;AACf,OAAO,KAAK,EACV,iBAAiB,EACjB,kBAAkB,EAClB,YAAY,EACZ,aAAa,EACb,cAAc,EACd,eAAe,EAChB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,GAAG,EAAE,MAAM,UAAU,CAAC;AAa/B,wBAAgB,WAAW,CAAC,EAC1B,MAAM,EACN,QAAQ,GACT,EAAE;IACD,MAAM,EAAE,GAAG,CAAC;IACZ,QAAQ,EAAE,SAAS,CAAC;CACrB,uFAEA;AAoDD,wBAAgB,aAAa,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,IAAI,EAAE;IAC/D,OAAO,EAAE,MAAM,CAAC;CACjB;;;;;WAxCQ,KAAK,GAAG,IAAI;eACR,OAAO;EA4CnB;AAED,wBAAgB,kBAAkB,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,IAAI,EAAE;IACpE,OAAO,EAAE,MAAM,CAAC;CACjB;;;;;WAjDQ,KAAK,GAAG,IAAI;eACR,OAAO;EAqDnB;AAED,wBAAgB,eAAe;;;;;WAxDtB,KAAK,GAAG,IAAI;eACR,OAAO;EA2DnB"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/react/index.ts"],"names":[],"mappings":"AAAA,OAAO,EASL,KAAK,SAAS,EACf,MAAM,OAAO,CAAC;AACf,OAAO,KAAK,EACV,iBAAiB,EACjB,kBAAkB,EAClB,YAAY,EACZ,aAAa,EACb,cAAc,EACd,eAAe,EAChB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,GAAG,EAAE,MAAM,UAAU,CAAC;AAC/B,OAAO,EAEL,aAAa,EACb,KAAK,eAAe,EACpB,KAAK,aAAa,EACnB,MAAM,aAAa,CAAC;AAarB,wBAAgB,WAAW,CAAC,EAC1B,MAAM,EACN,QAAQ,GACT,EAAE;IACD,MAAM,EAAE,GAAG,CAAC;IACZ,QAAQ,EAAE,SAAS,CAAC;CACrB,uFAEA;AAoDD,wBAAgB,aAAa,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,IAAI,EAAE;IAC/D,OAAO,EAAE,MAAM,CAAC;CACjB;;;;;WAxCQ,KAAK,GAAG,IAAI;eACR,OAAO;EA4CnB;AAED,wBAAgB,kBAAkB,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,IAAI,EAAE;IACpE,OAAO,EAAE,MAAM,CAAC;CACjB;;;;;WAjDQ,KAAK,GAAG,IAAI;eACR,OAAO;EAqDnB;AAED,wBAAgB,eAAe;;;;;WAxDtB,KAAK,GAAG,IAAI;eACR,OAAO;EA2DnB;AAID,MAAM,WAAW,kBACf,SAAQ,IAAI,CAAC,eAAe,EAAE,eAAe,GAAG,SAAS,GAAG,SAAS,CAAC;IACtE;;;;OAIG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,4CAA4C;IAC5C,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACxC,8BAA8B;IAC9B,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,iBAAiB,CAAC,CAAC;IAClC,KAAK,EAAE,aAAa,CAAC;IACrB,WAAW,EAAE,OAAO,CAAC;IACrB,QAAQ,EAAE,OAAO,CAAC;IAClB,2CAA2C;IAC3C,KAAK,EAAE,MAAM,CAAC;IACd,8DAA8D;IAC9D,WAAW,EAAE,MAAM,CAAC;IACpB,iCAAiC;IACjC,IAAI,EAAE,IAAI,GAAG,IAAI,CAAC;IAClB,8BAA8B;IAC9B,KAAK,EAAE,aAAa,GAAG,IAAI,CAAC;IAC5B,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC3B,IAAI,EAAE,MAAM,OAAO,CAAC,IAAI,GAAG,IAAI,CAAC,CAAC;IACjC,KAAK,EAAE,MAAM,IAAI,CAAC;IAClB,MAAM,EAAE,MAAM,IAAI,CAAC;IACnB,MAAM,EAAE,MAAM,IAAI,CAAC;IACnB,KAAK,EAAE,MAAM,IAAI,CAAC;IAElB,UAAU,EAAE,OAAO,CAAC;IACpB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,IAAI,EAAE,CAAC,GAAG,IAAI,CAAC;IACf,YAAY,EAAE,KAAK,GAAG,IAAI,CAAC;CAC5B;AAED;;;;;;;;GAQG;AACH,wBAAgB,WAAW,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACrD,IAAI,GAAE,kBAAuB,GAC5B,iBAAiB,CAAC,CAAC,CAAC,CAmJtB"}
@@ -1,4 +1,5 @@
1
- import { createContext, createElement, useCallback, useContext, useState, } from "react";
1
+ import { createContext, createElement, useCallback, useContext, useEffect, useMemo, useRef, useState, } from "react";
2
+ import { Recorder, } from "../recorder";
2
3
  /**
3
4
  * React hooks for @ohm_studio/sdk. Mirrors `@ohm_studio/sdk-react-native/react` so a
4
5
  * snippet copies cleanly between web and mobile codebases.
@@ -55,4 +56,157 @@ export function useOhmAudioExtract(opts) {
55
56
  export function useOhmSummarize() {
56
57
  return useOhmMutation((c, input) => c.summarize(input));
57
58
  }
59
+ /**
60
+ * `useRecorder` — the one-call recorder hook for clinical apps.
61
+ *
62
+ * const r = useRecorder({ apiSlug: "opd-clinic", silenceAutoStop: { ms: 6000 } });
63
+ * <button onClick={r.isRecording ? r.stop : r.start}>
64
+ * {r.isRecording ? `Stop (${r.durationSec.toFixed(0)}s)` : "Record"}
65
+ * </button>
66
+ * {r.transcript && <pre>{r.transcript}</pre>}
67
+ */
68
+ export function useRecorder(opts = {}) {
69
+ const client = useContext(OhmContext); // optional; only used for auto-extract
70
+ const recRef = useRef(null);
71
+ const [state, setState] = useState("idle");
72
+ const [level, setLevel] = useState(0);
73
+ const [durationSec, setDurationSec] = useState(0);
74
+ const [blob, setBlob] = useState(null);
75
+ const [error, setError] = useState(null);
76
+ const [extracting, setExtracting] = useState(false);
77
+ const [transcript, setTranscript] = useState(null);
78
+ const [data, setData] = useState(null);
79
+ const [extractError, setExtractError] = useState(null);
80
+ // Stable options ref so the recorder is built once.
81
+ const optsRef = useRef(opts);
82
+ optsRef.current = opts;
83
+ const ensureRecorder = useCallback(() => {
84
+ if (recRef.current)
85
+ return recRef.current;
86
+ const r = new Recorder({
87
+ ...optsRef.current,
88
+ onStateChange: (s) => setState(s),
89
+ onLevel: (rms) => setLevel(rms),
90
+ onError: (e) => setError(e),
91
+ });
92
+ recRef.current = r;
93
+ return r;
94
+ }, []);
95
+ // Tick duration while recording.
96
+ useEffect(() => {
97
+ if (state !== "recording")
98
+ return;
99
+ const id = setInterval(() => {
100
+ const r = recRef.current;
101
+ if (r)
102
+ setDurationSec(r.getDuration() / 1000);
103
+ }, 250);
104
+ return () => clearInterval(id);
105
+ }, [state]);
106
+ // Cleanup on unmount.
107
+ useEffect(() => {
108
+ return () => {
109
+ try {
110
+ recRef.current?.cancel();
111
+ }
112
+ catch {
113
+ /* ignore */
114
+ }
115
+ recRef.current = null;
116
+ };
117
+ }, []);
118
+ const start = useCallback(async () => {
119
+ setError(null);
120
+ setBlob(null);
121
+ setTranscript(null);
122
+ setData(null);
123
+ setExtractError(null);
124
+ setDurationSec(0);
125
+ const r = ensureRecorder();
126
+ await r.start();
127
+ }, [ensureRecorder]);
128
+ const stop = useCallback(async () => {
129
+ const r = recRef.current;
130
+ if (!r)
131
+ return null;
132
+ const out = await r.stop();
133
+ setBlob(out);
134
+ setDurationSec(r.getDuration() / 1000);
135
+ if (optsRef.current.apiSlug && client) {
136
+ setExtracting(true);
137
+ try {
138
+ const res = await client.audio.extract({
139
+ apiSlug: optsRef.current.apiSlug,
140
+ file: out,
141
+ inputs: optsRef.current.extractInputs,
142
+ language: optsRef.current.extractLanguage,
143
+ });
144
+ setTranscript(res.transcript ?? null);
145
+ setData(res.data);
146
+ }
147
+ catch (e) {
148
+ setExtractError(e);
149
+ }
150
+ finally {
151
+ setExtracting(false);
152
+ }
153
+ }
154
+ return out;
155
+ }, [client]);
156
+ const pause = useCallback(() => recRef.current?.pause(), []);
157
+ const resume = useCallback(() => recRef.current?.resume(), []);
158
+ const cancel = useCallback(() => recRef.current?.cancel(), []);
159
+ const reset = useCallback(() => {
160
+ try {
161
+ recRef.current?.cancel();
162
+ }
163
+ catch {
164
+ /* ignore */
165
+ }
166
+ recRef.current = null;
167
+ setState("idle");
168
+ setLevel(0);
169
+ setDurationSec(0);
170
+ setBlob(null);
171
+ setError(null);
172
+ setTranscript(null);
173
+ setData(null);
174
+ setExtractError(null);
175
+ }, []);
176
+ return useMemo(() => ({
177
+ state,
178
+ isRecording: state === "recording",
179
+ isPaused: state === "paused",
180
+ level,
181
+ durationSec,
182
+ blob,
183
+ error,
184
+ start,
185
+ stop,
186
+ pause,
187
+ resume,
188
+ cancel,
189
+ reset,
190
+ extracting,
191
+ transcript,
192
+ data,
193
+ extractError,
194
+ }), [
195
+ state,
196
+ level,
197
+ durationSec,
198
+ blob,
199
+ error,
200
+ start,
201
+ stop,
202
+ pause,
203
+ resume,
204
+ cancel,
205
+ reset,
206
+ extracting,
207
+ transcript,
208
+ data,
209
+ extractError,
210
+ ]);
211
+ }
58
212
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/react/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,aAAa,EACb,aAAa,EACb,WAAW,EACX,UAAU,EACV,QAAQ,GAET,MAAM,OAAO,CAAC;AAWf;;;;;;;GAOG;AAEH,MAAM,UAAU,GAAG,aAAa,CAAa,IAAI,CAAC,CAAC;AAEnD,MAAM,UAAU,WAAW,CAAC,EAC1B,MAAM,EACN,QAAQ,GAIT;IACC,OAAO,aAAa,CAAC,UAAU,CAAC,QAAQ,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE,QAAQ,CAAC,CAAC;AACzE,CAAC;AAED,SAAS,SAAS;IAChB,MAAM,CAAC,GAAG,UAAU,CAAC,UAAU,CAAC,CAAC;IACjC,IAAI,CAAC,CAAC,EAAE,CAAC;QACP,MAAM,IAAI,KAAK,CACb,4DAA4D,CAC7D,CAAC;IACJ,CAAC;IACD,OAAO,CAAC,CAAC;AACX,CAAC;AAQD,SAAS,cAAc,CACrB,EAAoD;IAEpD,MAAM,MAAM,GAAG,SAAS,EAAE,CAAC;IAC3B,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,GAAG,QAAQ,CAAyB;QACzD,IAAI,EAAE,IAAI;QACV,KAAK,EAAE,IAAI;QACX,SAAS,EAAE,KAAK;KACjB,CAAC,CAAC;IACH,MAAM,WAAW,GAAG,WAAW,CAC7B,KAAK,EAAE,KAAa,EAAE,EAAE;QACtB,QAAQ,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QACvD,IAAI,CAAC;YACH,MAAM,IAAI,GAAG,MAAM,EAAE,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;YACrC,QAAQ,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC,CAAC;YAClD,OAAO,IAAI,CAAC;QACd,CAAC;QAAC,OAAO,GAAQ,EAAE,CAAC;YAClB,QAAQ,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC,CAAC;YACvD,MAAM,GAAG,CAAC;QACZ,CAAC;IACH,CAAC,EACD,CAAC,MAAM,EAAE,EAAE,CAAC,CACb,CAAC;IACF,OAAO;QACL,GAAG,KAAK;QACR,WAAW;QACX,MAAM,EAAE,CAAC,KAAa,EAAE,EAAE;YACxB,KAAK,WAAW,CAAC,KAAK,CAAC,CAAC;QAC1B,CAAC;QACD,KAAK,EAAE,GAAG,EAAE,CACV,QAAQ,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC;KAC1D,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,aAAa,CAA8B,IAE1D;IACC,OAAO,cAAc,CAGnB,CAAC,CAAC,EAAE,KAAK,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAI,EAAE,GAAG,KAAK,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;AACrE,CAAC;AAED,MAAM,UAAU,kBAAkB,CAA8B,IAE/D;IACC,OAAO,cAAc,CAGnB,CAAC,CAAC,EAAE,KAAK,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAI,EAAE,GAAG,KAAK,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;AAC3E,CAAC;AAED,MAAM,UAAU,eAAe;IAC7B,OAAO,cAAc,CAAkC,CAAC,CAAC,EAAE,KAAK,EAAE,EAAE,CAClE,CAAC,CAAC,SAAS,CAAC,KAAK,CAAC,CACnB,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/react/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,aAAa,EACb,aAAa,EACb,WAAW,EACX,UAAU,EACV,SAAS,EACT,OAAO,EACP,MAAM,EACN,QAAQ,GAET,MAAM,OAAO,CAAC;AAUf,OAAO,EACL,QAAQ,GAIT,MAAM,aAAa,CAAC;AAErB;;;;;;;GAOG;AAEH,MAAM,UAAU,GAAG,aAAa,CAAa,IAAI,CAAC,CAAC;AAEnD,MAAM,UAAU,WAAW,CAAC,EAC1B,MAAM,EACN,QAAQ,GAIT;IACC,OAAO,aAAa,CAAC,UAAU,CAAC,QAAQ,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE,QAAQ,CAAC,CAAC;AACzE,CAAC;AAED,SAAS,SAAS;IAChB,MAAM,CAAC,GAAG,UAAU,CAAC,UAAU,CAAC,CAAC;IACjC,IAAI,CAAC,CAAC,EAAE,CAAC;QACP,MAAM,IAAI,KAAK,CACb,4DAA4D,CAC7D,CAAC;IACJ,CAAC;IACD,OAAO,CAAC,CAAC;AACX,CAAC;AAQD,SAAS,cAAc,CACrB,EAAoD;IAEpD,MAAM,MAAM,GAAG,SAAS,EAAE,CAAC;IAC3B,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,GAAG,QAAQ,CAAyB;QACzD,IAAI,EAAE,IAAI;QACV,KAAK,EAAE,IAAI;QACX,SAAS,EAAE,KAAK;KACjB,CAAC,CAAC;IACH,MAAM,WAAW,GAAG,WAAW,CAC7B,KAAK,EAAE,KAAa,EAAE,EAAE;QACtB,QAAQ,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QACvD,IAAI,CAAC;YACH,MAAM,IAAI,GAAG,MAAM,EAAE,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;YACrC,QAAQ,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC,CAAC;YAClD,OAAO,IAAI,CAAC;QACd,CAAC;QAAC,OAAO,GAAQ,EAAE,CAAC;YAClB,QAAQ,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC,CAAC;YACvD,MAAM,GAAG,CAAC;QACZ,CAAC;IACH,CAAC,EACD,CAAC,MAAM,EAAE,EAAE,CAAC,CACb,CAAC;IACF,OAAO;QACL,GAAG,KAAK;QACR,WAAW;QACX,MAAM,EAAE,CAAC,KAAa,EAAE,EAAE;YACxB,KAAK,WAAW,CAAC,KAAK,CAAC,CAAC;QAC1B,CAAC;QACD,KAAK,EAAE,GAAG,EAAE,CACV,QAAQ,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC;KAC1D,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,aAAa,CAA8B,IAE1D;IACC,OAAO,cAAc,CAGnB,CAAC,CAAC,EAAE,KAAK,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAI,EAAE,GAAG,KAAK,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;AACrE,CAAC;AAED,MAAM,UAAU,kBAAkB,CAA8B,IAE/D;IACC,OAAO,cAAc,CAGnB,CAAC,CAAC,EAAE,KAAK,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAI,EAAE,GAAG,KAAK,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;AAC3E,CAAC;AAED,MAAM,UAAU,eAAe;IAC7B,OAAO,cAAc,CAAkC,CAAC,CAAC,EAAE,KAAK,EAAE,EAAE,CAClE,CAAC,CAAC,SAAS,CAAC,KAAK,CAAC,CACnB,CAAC;AACJ,CAAC;AA2CD;;;;;;;;GAQG;AACH,MAAM,UAAU,WAAW,CACzB,OAA2B,EAAE;IAE7B,MAAM,MAAM,GAAG,UAAU,CAAC,UAAU,CAAC,CAAC,CAAC,uCAAuC;IAC9E,MAAM,MAAM,GAAG,MAAM,CAAkB,IAAI,CAAC,CAAC;IAC7C,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,GAAG,QAAQ,CAAgB,MAAM,CAAC,CAAC;IAC1D,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAC;IACtC,MAAM,CAAC,WAAW,EAAE,cAAc,CAAC,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAC;IAClD,MAAM,CAAC,IAAI,EAAE,OAAO,CAAC,GAAG,QAAQ,CAAc,IAAI,CAAC,CAAC;IACpD,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,GAAG,QAAQ,CAAuB,IAAI,CAAC,CAAC;IAC/D,MAAM,CAAC,UAAU,EAAE,aAAa,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC;IACpD,MAAM,CAAC,UAAU,EAAE,aAAa,CAAC,GAAG,QAAQ,CAAgB,IAAI,CAAC,CAAC;IAClE,MAAM,CAAC,IAAI,EAAE,OAAO,CAAC,GAAG,QAAQ,CAAW,IAAI,CAAC,CAAC;IACjD,MAAM,CAAC,YAAY,EAAE,eAAe,CAAC,GAAG,QAAQ,CAAe,IAAI,CAAC,CAAC;IAErE,oDAAoD;IACpD,MAAM,OAAO,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC;IAC7B,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC;IAEvB,MAAM,cAAc,GAAG,WAAW,CAAC,GAAG,EAAE;QACtC,IAAI,MAAM,CAAC,OAAO;YAAE,OAAO,MAAM,CAAC,OAAO,CAAC;QAC1C,MAAM,CAAC,GAAG,IAAI,QAAQ,CAAC;YACrB,GAAG,OAAO,CAAC,OAAO;YAClB,aAAa,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC;YACjC,OAAO,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,QAAQ,CAAC,GAAG,CAAC;YAC/B,OAAO,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC;SAC5B,CAAC,CAAC;QACH,MAAM,CAAC,OAAO,GAAG,CAAC,CAAC;QACnB,OAAO,CAAC,CAAC;IACX,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,iCAAiC;IACjC,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,KAAK,KAAK,WAAW;YAAE,OAAO;QAClC,MAAM,EAAE,GAAG,WAAW,CAAC,GAAG,EAAE;YAC1B,MAAM,CAAC,GAAG,MAAM,CAAC,OAAO,CAAC;YACzB,IAAI,CAAC;gBAAE,cAAc,CAAC,CAAC,CAAC,WAAW,EAAE,GAAG,IAAI,CAAC,CAAC;QAChD,CAAC,EAAE,GAAG,CAAC,CAAC;QACR,OAAO,GAAG,EAAE,CAAC,aAAa,CAAC,EAAE,CAAC,CAAC;IACjC,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC;IAEZ,sBAAsB;IACtB,SAAS,CAAC,GAAG,EAAE;QACb,OAAO,GAAG,EAAE;YACV,IAAI,CAAC;gBACH,MAAM,CAAC,OAAO,EAAE,MAAM,EAAE,CAAC;YAC3B,CAAC;YAAC,MAAM,CAAC;gBACP,YAAY;YACd,CAAC;YACD,MAAM,CAAC,OAAO,GAAG,IAAI,CAAC;QACxB,CAAC,CAAC;IACJ,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,MAAM,KAAK,GAAG,WAAW,CAAC,KAAK,IAAI,EAAE;QACnC,QAAQ,CAAC,IAAI,CAAC,CAAC;QACf,OAAO,CAAC,IAAI,CAAC,CAAC;QACd,aAAa,CAAC,IAAI,CAAC,CAAC;QACpB,OAAO,CAAC,IAAI,CAAC,CAAC;QACd,eAAe,CAAC,IAAI,CAAC,CAAC;QACtB,cAAc,CAAC,CAAC,CAAC,CAAC;QAClB,MAAM,CAAC,GAAG,cAAc,EAAE,CAAC;QAC3B,MAAM,CAAC,CAAC,KAAK,EAAE,CAAC;IAClB,CAAC,EAAE,CAAC,cAAc,CAAC,CAAC,CAAC;IAErB,MAAM,IAAI,GAAG,WAAW,CAAC,KAAK,IAA0B,EAAE;QACxD,MAAM,CAAC,GAAG,MAAM,CAAC,OAAO,CAAC;QACzB,IAAI,CAAC,CAAC;YAAE,OAAO,IAAI,CAAC;QACpB,MAAM,GAAG,GAAG,MAAM,CAAC,CAAC,IAAI,EAAE,CAAC;QAC3B,OAAO,CAAC,GAAG,CAAC,CAAC;QACb,cAAc,CAAC,CAAC,CAAC,WAAW,EAAE,GAAG,IAAI,CAAC,CAAC;QAEvC,IAAI,OAAO,CAAC,OAAO,CAAC,OAAO,IAAI,MAAM,EAAE,CAAC;YACtC,aAAa,CAAC,IAAI,CAAC,CAAC;YACpB,IAAI,CAAC;gBACH,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,KAAK,CAAC,OAAO,CAAI;oBACxC,OAAO,EAAE,OAAO,CAAC,OAAO,CAAC,OAAO;oBAChC,IAAI,EAAE,GAAG;oBACT,MAAM,EAAE,OAAO,CAAC,OAAO,CAAC,aAAa;oBACrC,QAAQ,EAAE,OAAO,CAAC,OAAO,CAAC,eAAe;iBAC1C,CAAC,CAAC;gBACH,aAAa,CAAC,GAAG,CAAC,UAAU,IAAI,IAAI,CAAC,CAAC;gBACtC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;YACpB,CAAC;YAAC,OAAO,CAAM,EAAE,CAAC;gBAChB,eAAe,CAAC,CAAC,CAAC,CAAC;YACrB,CAAC;oBAAS,CAAC;gBACT,aAAa,CAAC,KAAK,CAAC,CAAC;YACvB,CAAC;QACH,CAAC;QACD,OAAO,GAAG,CAAC;IACb,CAAC,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC;IAEb,MAAM,KAAK,GAAG,WAAW,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,OAAO,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC,CAAC;IAC7D,MAAM,MAAM,GAAG,WAAW,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC,CAAC;IAC/D,MAAM,MAAM,GAAG,WAAW,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC,CAAC;IAC/D,MAAM,KAAK,GAAG,WAAW,CAAC,GAAG,EAAE;QAC7B,IAAI,CAAC;YACH,MAAM,CAAC,OAAO,EAAE,MAAM,EAAE,CAAC;QAC3B,CAAC;QAAC,MAAM,CAAC;YACP,YAAY;QACd,CAAC;QACD,MAAM,CAAC,OAAO,GAAG,IAAI,CAAC;QACtB,QAAQ,CAAC,MAAM,CAAC,CAAC;QACjB,QAAQ,CAAC,CAAC,CAAC,CAAC;QACZ,cAAc,CAAC,CAAC,CAAC,CAAC;QAClB,OAAO,CAAC,IAAI,CAAC,CAAC;QACd,QAAQ,CAAC,IAAI,CAAC,CAAC;QACf,aAAa,CAAC,IAAI,CAAC,CAAC;QACpB,OAAO,CAAC,IAAI,CAAC,CAAC;QACd,eAAe,CAAC,IAAI,CAAC,CAAC;IACxB,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,OAAO,OAAO,CACZ,GAAG,EAAE,CAAC,CAAC;QACL,KAAK;QACL,WAAW,EAAE,KAAK,KAAK,WAAW;QAClC,QAAQ,EAAE,KAAK,KAAK,QAAQ;QAC5B,KAAK;QACL,WAAW;QACX,IAAI;QACJ,KAAK;QACL,KAAK;QACL,IAAI;QACJ,KAAK;QACL,MAAM;QACN,MAAM;QACN,KAAK;QACL,UAAU;QACV,UAAU;QACV,IAAI;QACJ,YAAY;KACb,CAAC,EACF;QACE,KAAK;QACL,KAAK;QACL,WAAW;QACX,IAAI;QACJ,KAAK;QACL,KAAK;QACL,IAAI;QACJ,KAAK;QACL,MAAM;QACN,MAAM;QACN,KAAK;QACL,UAAU;QACV,UAAU;QACV,IAAI;QACJ,YAAY;KACb,CACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,171 @@
1
+ /**
2
+ * Browser audio recorder for clinical consults.
3
+ *
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
+ * });
16
+ * await rec.start();
17
+ * const blob = await rec.stop(); // → ohm.audio.extract({ file: blob, ... })
18
+ *
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.
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
+ }
29
+ export interface RecorderOptions {
30
+ /**
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.
35
+ */
36
+ mimeType?: string;
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
+ */
51
+ audioConstraints?: MediaTrackConstraints;
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. */
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;
101
+ }
102
+ /** True if the runtime supports recording (browser with MediaRecorder + getUserMedia). */
103
+ export declare function isRecordingSupported(): boolean;
104
+ export declare class Recorder {
105
+ private rec;
106
+ private chunks;
107
+ private stream;
108
+ private state;
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;
123
+ constructor(opts?: RecorderOptions);
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. */
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;
144
+ /**
145
+ * Request microphone access and begin recording. Resolves once the
146
+ * underlying MediaRecorder has fired `start`.
147
+ */
148
+ start(): Promise<void>;
149
+ /** Pause recording. Resumable via `resume()`. */
150
+ pause(): void;
151
+ /** Resume after `pause()`. */
152
+ resume(): void;
153
+ /**
154
+ * Stop recording and return the assembled Blob, ready to pass to
155
+ * `ohm.audio.extract({ file: blob, … })`. Releases mic, wake lock, AudioContext.
156
+ */
157
+ stop(): Promise<Blob>;
158
+ /** Stop after `ms` from now. Returns the same promise as `stop()`. */
159
+ stopAfter(ms: number): Promise<Blob>;
160
+ /** Abort — releases everything, no Blob. */
161
+ cancel(): void;
162
+ private startLevelMeter;
163
+ private acquireWakeLock;
164
+ private releaseWakeLock;
165
+ private attachVisibility;
166
+ private detachVisibility;
167
+ private cleanupStream;
168
+ private cleanup;
169
+ private setState;
170
+ }
171
+ //# sourceMappingURL=recorder.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"recorder.d.ts","sourceRoot":"","sources":["../src/recorder.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,508 @@
1
+ /**
2
+ * Browser audio recorder for clinical consults.
3
+ *
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
+ * });
16
+ * await rec.start();
17
+ * const blob = await rec.stop(); // → ohm.audio.extract({ file: blob, ... })
18
+ *
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.
21
+ */
22
+ export class RecorderError extends Error {
23
+ code;
24
+ cause;
25
+ constructor(code, message, cause) {
26
+ super(message);
27
+ this.name = "RecorderError";
28
+ this.code = code;
29
+ this.cause = cause;
30
+ }
31
+ }
32
+ const MIME_CASCADE = [
33
+ "audio/webm;codecs=opus",
34
+ "audio/mp4;codecs=mp4a.40.2",
35
+ "audio/ogg;codecs=opus",
36
+ "audio/mp4",
37
+ "audio/webm",
38
+ ];
39
+ function clinicalConstraints(deviceId) {
40
+ const base = {
41
+ echoCancellation: true,
42
+ noiseSuppression: true,
43
+ autoGainControl: true,
44
+ channelCount: { ideal: 1 },
45
+ sampleRate: { ideal: 16_000 },
46
+ sampleSize: { ideal: 16 },
47
+ };
48
+ if (deviceId)
49
+ base.deviceId = { exact: deviceId };
50
+ return base;
51
+ }
52
+ function pickMimeType(requested) {
53
+ if (typeof MediaRecorder === "undefined")
54
+ return undefined;
55
+ const probe = (m) => {
56
+ try {
57
+ return MediaRecorder.isTypeSupported(m);
58
+ }
59
+ catch {
60
+ return false;
61
+ }
62
+ };
63
+ if (requested && probe(requested))
64
+ return requested;
65
+ for (const m of MIME_CASCADE)
66
+ if (probe(m))
67
+ return m;
68
+ return undefined; // browser will pick its own fallback
69
+ }
70
+ function mapDomError(err) {
71
+ const name = (err && err.name) || "";
72
+ switch (name) {
73
+ case "NotAllowedError":
74
+ case "SecurityError":
75
+ return new RecorderError("PermissionDenied", "Microphone access was denied. Ask the user to allow it in browser settings.", err);
76
+ case "NotFoundError":
77
+ case "DevicesNotFoundError":
78
+ return new RecorderError("NoMicrophone", "No microphone was found.", err);
79
+ case "NotReadableError":
80
+ case "TrackStartError":
81
+ return new RecorderError("MicrophoneBusy", "The microphone is in use by another app.", err);
82
+ case "OverconstrainedError":
83
+ case "ConstraintNotSatisfiedError":
84
+ return new RecorderError("OverConstrained", "The requested audio constraints can't be satisfied by this device.", err);
85
+ default:
86
+ return new RecorderError("Unknown", (err && err.message) || "Recorder error", err);
87
+ }
88
+ }
89
+ /** True if the runtime supports recording (browser with MediaRecorder + getUserMedia). */
90
+ export function isRecordingSupported() {
91
+ if (typeof window === "undefined")
92
+ return false;
93
+ if (typeof navigator === "undefined" || !navigator.mediaDevices)
94
+ return false;
95
+ if (typeof navigator.mediaDevices.getUserMedia !== "function")
96
+ return false;
97
+ if (typeof globalThis.MediaRecorder === "undefined")
98
+ return false;
99
+ return true;
100
+ }
101
+ export class Recorder {
102
+ rec = null;
103
+ chunks = [];
104
+ stream = null;
105
+ state = "idle";
106
+ opts;
107
+ mime;
108
+ startedAt = 0;
109
+ pausedAccumMs = 0;
110
+ pauseStartedAt = 0;
111
+ maxStopTimer = null;
112
+ // Level metering
113
+ audioCtx = null;
114
+ analyser = null;
115
+ levelSource = null;
116
+ levelRafId = null;
117
+ levelBuffer = null;
118
+ // Silence
119
+ silenceSinceMs = 0;
120
+ // Wake lock
121
+ wakeLockSentinel = null;
122
+ // Visibility
123
+ visibilityHandler = null;
124
+ constructor(opts = {}) {
125
+ this.opts = opts;
126
+ }
127
+ /** Static support check. */
128
+ static isSupported() {
129
+ return isRecordingSupported();
130
+ }
131
+ /** Pick the best supported MIME type for this browser. */
132
+ static getSupportedMimeType(prefer) {
133
+ return pickMimeType(prefer);
134
+ }
135
+ /**
136
+ * Probe microphone permission *without* requesting it. Returns:
137
+ * "granted" — already allowed
138
+ * "denied" — user has blocked access; calling start() will reject
139
+ * "prompt" — browser will show the dialog on next start()
140
+ * "unknown" — browser doesn't support the Permissions API
141
+ */
142
+ static async probePermission() {
143
+ if (typeof navigator === "undefined" ||
144
+ !navigator.permissions ||
145
+ typeof navigator.permissions.query !== "function") {
146
+ return "unknown";
147
+ }
148
+ try {
149
+ const status = await navigator.permissions.query({
150
+ name: "microphone",
151
+ });
152
+ return status.state;
153
+ }
154
+ catch {
155
+ return "unknown";
156
+ }
157
+ }
158
+ /** Enumerate microphones. Empty `label` until permission is granted at least once. */
159
+ static async listMicrophones() {
160
+ if (typeof navigator === "undefined" ||
161
+ !navigator.mediaDevices ||
162
+ typeof navigator.mediaDevices.enumerateDevices !== "function") {
163
+ return [];
164
+ }
165
+ const devices = await navigator.mediaDevices.enumerateDevices();
166
+ return devices
167
+ .filter((d) => d.kind === "audioinput")
168
+ .map((d) => ({ deviceId: d.deviceId, label: d.label, groupId: d.groupId }));
169
+ }
170
+ /** Current state. */
171
+ get currentState() {
172
+ return this.state;
173
+ }
174
+ /** Final MIME type the browser is producing (set after `start`). */
175
+ get mimeType() {
176
+ return this.rec?.mimeType || this.mime;
177
+ }
178
+ /** Recorded duration in ms (excluding paused time). 0 when idle. */
179
+ getDuration() {
180
+ if (this.state === "idle")
181
+ return 0;
182
+ const now = Date.now();
183
+ if (this.state === "paused") {
184
+ return this.pauseStartedAt - this.startedAt - this.pausedAccumMs;
185
+ }
186
+ return now - this.startedAt - this.pausedAccumMs;
187
+ }
188
+ /**
189
+ * Request microphone access and begin recording. Resolves once the
190
+ * underlying MediaRecorder has fired `start`.
191
+ */
192
+ async start() {
193
+ if (!isRecordingSupported()) {
194
+ throw new RecorderError("NotSupported", "Recording isn't supported in this runtime — MediaRecorder or getUserMedia is missing.");
195
+ }
196
+ if (this.state !== "idle") {
197
+ throw new RecorderError("InvalidState", `Cannot start while recorder is "${this.state}".`);
198
+ }
199
+ this.setState("starting");
200
+ const constraints = this.opts.audioConstraints ??
201
+ (this.opts.clinicalDefaults === false
202
+ ? true
203
+ : clinicalConstraints(this.opts.deviceId));
204
+ try {
205
+ this.stream = await navigator.mediaDevices.getUserMedia({
206
+ audio: constraints,
207
+ });
208
+ }
209
+ catch (e) {
210
+ this.setState("idle");
211
+ const err = mapDomError(e);
212
+ this.opts.onError?.(err);
213
+ throw err;
214
+ }
215
+ // Detect mic disconnect / OS-level revoke.
216
+ for (const track of this.stream.getAudioTracks()) {
217
+ track.addEventListener("ended", () => {
218
+ if (this.state === "recording" || this.state === "paused") {
219
+ this.opts.onDeviceLost?.();
220
+ this.opts.onError?.(new RecorderError("DeviceLost", "Microphone was disconnected."));
221
+ this.cancel();
222
+ }
223
+ });
224
+ }
225
+ this.mime = pickMimeType(this.opts.mimeType);
226
+ let rec;
227
+ try {
228
+ rec = new MediaRecorder(this.stream, {
229
+ mimeType: this.mime,
230
+ audioBitsPerSecond: this.opts.audioBitsPerSecond ?? 32_000,
231
+ });
232
+ }
233
+ catch (e) {
234
+ this.cleanupStream();
235
+ this.setState("idle");
236
+ const err = mapDomError(e);
237
+ this.opts.onError?.(err);
238
+ throw err;
239
+ }
240
+ this.chunks = [];
241
+ rec.ondataavailable = (e) => {
242
+ if (e.data && e.data.size > 0) {
243
+ this.chunks.push(e.data);
244
+ this.opts.onChunk?.(e.data);
245
+ }
246
+ };
247
+ rec.onerror = (e) => {
248
+ const err = mapDomError(e?.error || e);
249
+ this.opts.onError?.(err);
250
+ };
251
+ this.rec = rec;
252
+ this.startLevelMeter();
253
+ if (this.opts.wakeLock)
254
+ await this.acquireWakeLock();
255
+ if (this.opts.pauseOnHidden)
256
+ this.attachVisibility();
257
+ return new Promise((resolve, reject) => {
258
+ rec.onstart = () => {
259
+ this.startedAt = Date.now();
260
+ this.pausedAccumMs = 0;
261
+ this.silenceSinceMs = 0;
262
+ this.setState("recording");
263
+ if (this.opts.maxDurationMs && this.opts.maxDurationMs > 0) {
264
+ this.maxStopTimer = setTimeout(() => {
265
+ this.stop().catch(() => {
266
+ /* swallow — handled via onError */
267
+ });
268
+ }, this.opts.maxDurationMs);
269
+ }
270
+ resolve();
271
+ };
272
+ const onceErr = (e) => {
273
+ const err = mapDomError(e?.error || e);
274
+ this.opts.onError?.(err);
275
+ reject(err);
276
+ };
277
+ rec.addEventListener("error", onceErr, { once: true });
278
+ try {
279
+ if (this.opts.timesliceMs && this.opts.timesliceMs > 0) {
280
+ rec.start(this.opts.timesliceMs);
281
+ }
282
+ else {
283
+ rec.start();
284
+ }
285
+ }
286
+ catch (e) {
287
+ const err = mapDomError(e);
288
+ this.opts.onError?.(err);
289
+ reject(err);
290
+ }
291
+ });
292
+ }
293
+ /** Pause recording. Resumable via `resume()`. */
294
+ pause() {
295
+ if (this.state !== "recording" || !this.rec) {
296
+ throw new RecorderError("InvalidState", `Cannot pause while "${this.state}".`);
297
+ }
298
+ this.rec.pause();
299
+ this.pauseStartedAt = Date.now();
300
+ this.setState("paused");
301
+ }
302
+ /** Resume after `pause()`. */
303
+ resume() {
304
+ if (this.state !== "paused" || !this.rec) {
305
+ throw new RecorderError("InvalidState", `Cannot resume while "${this.state}".`);
306
+ }
307
+ this.pausedAccumMs += Date.now() - this.pauseStartedAt;
308
+ this.rec.resume();
309
+ this.setState("recording");
310
+ }
311
+ /**
312
+ * Stop recording and return the assembled Blob, ready to pass to
313
+ * `ohm.audio.extract({ file: blob, … })`. Releases mic, wake lock, AudioContext.
314
+ */
315
+ async stop() {
316
+ if (!this.rec || (this.state !== "recording" && this.state !== "paused")) {
317
+ throw new RecorderError("InvalidState", `Cannot stop while "${this.state}".`);
318
+ }
319
+ this.setState("stopping");
320
+ return new Promise((resolve, reject) => {
321
+ const rec = this.rec;
322
+ rec.onstop = () => {
323
+ const type = rec.mimeType || this.mime || "audio/webm";
324
+ const blob = new Blob(this.chunks, { type });
325
+ this.cleanup();
326
+ resolve(blob);
327
+ };
328
+ rec.addEventListener("error", (e) => {
329
+ this.cleanup();
330
+ const err = mapDomError(e?.error || e);
331
+ this.opts.onError?.(err);
332
+ reject(err);
333
+ }, { once: true });
334
+ try {
335
+ rec.stop();
336
+ }
337
+ catch (e) {
338
+ this.cleanup();
339
+ const err = mapDomError(e);
340
+ this.opts.onError?.(err);
341
+ reject(err);
342
+ }
343
+ });
344
+ }
345
+ /** Stop after `ms` from now. Returns the same promise as `stop()`. */
346
+ stopAfter(ms) {
347
+ return new Promise((resolve, reject) => {
348
+ setTimeout(() => {
349
+ this.stop().then(resolve, reject);
350
+ }, ms);
351
+ });
352
+ }
353
+ /** Abort — releases everything, no Blob. */
354
+ cancel() {
355
+ if (this.rec && (this.state === "recording" || this.state === "paused")) {
356
+ try {
357
+ this.rec.stop();
358
+ }
359
+ catch {
360
+ /* ignore */
361
+ }
362
+ }
363
+ this.cleanup();
364
+ }
365
+ // ─── internals ──────────────────────────────────────────────────────
366
+ startLevelMeter() {
367
+ if (!this.stream || !this.opts.onLevel)
368
+ return;
369
+ if (typeof AudioContext === "undefined")
370
+ return;
371
+ try {
372
+ const Ctx = globalThis.AudioContext ||
373
+ globalThis.webkitAudioContext;
374
+ this.audioCtx = new Ctx();
375
+ this.levelSource = this.audioCtx.createMediaStreamSource(this.stream);
376
+ this.analyser = this.audioCtx.createAnalyser();
377
+ this.analyser.fftSize = 1024;
378
+ this.analyser.smoothingTimeConstant = 0.6;
379
+ this.levelSource.connect(this.analyser);
380
+ this.levelBuffer = new Uint8Array(new ArrayBuffer(this.analyser.fftSize));
381
+ const silenceMs = this.opts.silenceAutoStop?.ms ?? 6000;
382
+ const silenceThr = this.opts.silenceAutoStop?.threshold ?? 0.012;
383
+ const tick = () => {
384
+ if (!this.analyser || !this.levelBuffer)
385
+ return;
386
+ this.analyser.getByteTimeDomainData(this.levelBuffer);
387
+ // RMS in 0..1
388
+ let sum = 0;
389
+ for (let i = 0; i < this.levelBuffer.length; i++) {
390
+ const v = (this.levelBuffer[i] - 128) / 128;
391
+ sum += v * v;
392
+ }
393
+ const rms = Math.sqrt(sum / this.levelBuffer.length);
394
+ this.opts.onLevel?.(rms);
395
+ if (this.opts.silenceAutoStop && this.state === "recording") {
396
+ if (rms < silenceThr) {
397
+ if (this.silenceSinceMs === 0)
398
+ this.silenceSinceMs = Date.now();
399
+ else if (Date.now() - this.silenceSinceMs >= silenceMs) {
400
+ this.silenceSinceMs = 0;
401
+ this.stop().catch(() => { });
402
+ return;
403
+ }
404
+ }
405
+ else {
406
+ this.silenceSinceMs = 0;
407
+ }
408
+ }
409
+ this.levelRafId = requestAnimationFrame(tick);
410
+ };
411
+ this.levelRafId = requestAnimationFrame(tick);
412
+ }
413
+ catch {
414
+ /* metering is best-effort; never block recording */
415
+ }
416
+ }
417
+ async acquireWakeLock() {
418
+ try {
419
+ const wl = navigator.wakeLock;
420
+ if (wl && typeof wl.request === "function") {
421
+ this.wakeLockSentinel = await wl.request("screen");
422
+ }
423
+ }
424
+ catch {
425
+ /* best-effort */
426
+ }
427
+ }
428
+ releaseWakeLock() {
429
+ try {
430
+ this.wakeLockSentinel?.release?.();
431
+ }
432
+ catch {
433
+ /* ignore */
434
+ }
435
+ this.wakeLockSentinel = null;
436
+ }
437
+ attachVisibility() {
438
+ if (typeof document === "undefined")
439
+ return;
440
+ this.visibilityHandler = () => {
441
+ if (document.visibilityState === "hidden" && this.state === "recording") {
442
+ try {
443
+ this.pause();
444
+ }
445
+ catch {
446
+ /* ignore */
447
+ }
448
+ }
449
+ };
450
+ document.addEventListener("visibilitychange", this.visibilityHandler);
451
+ }
452
+ detachVisibility() {
453
+ if (this.visibilityHandler && typeof document !== "undefined") {
454
+ document.removeEventListener("visibilitychange", this.visibilityHandler);
455
+ }
456
+ this.visibilityHandler = null;
457
+ }
458
+ cleanupStream() {
459
+ this.stream?.getTracks().forEach((t) => {
460
+ try {
461
+ t.stop();
462
+ }
463
+ catch {
464
+ /* ignore */
465
+ }
466
+ });
467
+ this.stream = null;
468
+ }
469
+ cleanup() {
470
+ if (this.maxStopTimer) {
471
+ clearTimeout(this.maxStopTimer);
472
+ this.maxStopTimer = null;
473
+ }
474
+ if (this.levelRafId !== null) {
475
+ cancelAnimationFrame(this.levelRafId);
476
+ this.levelRafId = null;
477
+ }
478
+ try {
479
+ this.levelSource?.disconnect();
480
+ this.analyser?.disconnect();
481
+ this.audioCtx?.close().catch(() => { });
482
+ }
483
+ catch {
484
+ /* ignore */
485
+ }
486
+ this.levelSource = null;
487
+ this.analyser = null;
488
+ this.audioCtx = null;
489
+ this.levelBuffer = null;
490
+ this.releaseWakeLock();
491
+ this.detachVisibility();
492
+ this.cleanupStream();
493
+ this.rec = null;
494
+ this.chunks = [];
495
+ this.startedAt = 0;
496
+ this.pausedAccumMs = 0;
497
+ this.pauseStartedAt = 0;
498
+ this.silenceSinceMs = 0;
499
+ this.setState("idle");
500
+ }
501
+ setState(next) {
502
+ if (this.state === next)
503
+ return;
504
+ this.state = next;
505
+ this.opts.onStateChange?.(next);
506
+ }
507
+ }
508
+ //# sourceMappingURL=recorder.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"recorder.js","sourceRoot":"","sources":["../src/recorder.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ohm_studio/sdk",
3
- "version": "0.1.0",
3
+ "version": "0.3.0",
4
4
  "description": "OHM Studio SDK for JavaScript / TypeScript / React. Voice-to-structured-JSON clinical extraction APIs.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -17,10 +17,7 @@
17
17
  "import": "./dist/react/index.js"
18
18
  }
19
19
  },
20
- "files": [
21
- "dist",
22
- "README.md"
23
- ],
20
+ "files": ["dist", "README.md"],
24
21
  "keywords": [
25
22
  "ohm",
26
23
  "clinical",
@@ -41,24 +38,22 @@
41
38
  "publishConfig": {
42
39
  "access": "public"
43
40
  },
41
+ "scripts": {
42
+ "build": "tsc -p tsconfig.json",
43
+ "typecheck": "tsc -p tsconfig.json --noEmit"
44
+ },
44
45
  "dependencies": {
45
- "@ohm_studio/sdk-core": "0.1.0"
46
+ "@ohm_studio/sdk-core": "workspace:*"
46
47
  },
47
48
  "peerDependencies": {
48
49
  "react": ">=17"
49
50
  },
50
51
  "peerDependenciesMeta": {
51
- "react": {
52
- "optional": true
53
- }
52
+ "react": { "optional": true }
54
53
  },
55
54
  "devDependencies": {
56
55
  "@types/react": "^18.3.23",
57
56
  "react": "^18.3.1",
58
57
  "typescript": "^5.8.3"
59
- },
60
- "scripts": {
61
- "build": "tsc -p tsconfig.json",
62
- "typecheck": "tsc -p tsconfig.json --noEmit"
63
58
  }
64
- }
59
+ }