@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 +4 -2
- package/dist/index.d.ts +14 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +27 -13
- package/dist/index.js.map +1 -1
- package/dist/react/index.d.ts +46 -0
- package/dist/react/index.d.ts.map +1 -1
- package/dist/react/index.js +155 -1
- package/dist/react/index.js.map +1 -1
- package/dist/recorder.d.ts +171 -0
- package/dist/recorder.d.ts.map +1 -0
- package/dist/recorder.js +508 -0
- package/dist/recorder.js.map +1 -0
- package/package.json +9 -14
package/README.md
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
|
-
#
|
|
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 {
|
|
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(
|
|
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
|
package/dist/index.d.ts.map
CHANGED
|
@@ -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,
|
|
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(
|
|
23
|
-
super(
|
|
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
|
-
|
|
28
|
-
|
|
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" &&
|
|
32
|
-
fd.append("file",
|
|
44
|
+
else if (typeof Blob !== "undefined" && file instanceof Blob) {
|
|
45
|
+
fd.append("file", file, "audio.bin");
|
|
33
46
|
}
|
|
34
|
-
else if (
|
|
47
|
+
else if (file && typeof file === "object" && "buffer" in file) {
|
|
35
48
|
// Node-style { buffer, name?, type? }
|
|
36
|
-
const
|
|
37
|
-
|
|
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,
|
|
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(
|
|
58
|
+
for (const [k, v] of Object.entries(fields || {})) {
|
|
45
59
|
fd.append(k, v);
|
|
46
60
|
}
|
|
47
|
-
return
|
|
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;
|
|
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"}
|
package/dist/react/index.d.ts
CHANGED
|
@@ -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,
|
|
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"}
|
package/dist/react/index.js
CHANGED
|
@@ -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
|
package/dist/react/index.js.map
CHANGED
|
@@ -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;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/react/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -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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"}
|
package/dist/recorder.js
ADDED
|
@@ -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.
|
|
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": "
|
|
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
|
+
}
|