@corti/dictation-web 0.3.0-rc → 0.4.0-rc
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 +9 -4
- package/dist/bundle.js +3098 -1630
- package/dist/components/audio-visualiser.d.ts +5 -3
- package/dist/components/audio-visualiser.js +38 -46
- package/dist/components/audio-visualiser.js.map +1 -1
- package/dist/components/corti-dictation.d.ts +122 -0
- package/dist/components/corti-dictation.js +234 -0
- package/dist/components/corti-dictation.js.map +1 -0
- package/dist/components/device-selector.d.ts +14 -0
- package/dist/components/device-selector.js +75 -0
- package/dist/components/device-selector.js.map +1 -0
- package/dist/components/language-selector.d.ts +15 -0
- package/dist/components/language-selector.js +74 -0
- package/dist/components/language-selector.js.map +1 -0
- package/dist/components/recording-button.d.ts +29 -0
- package/dist/components/recording-button.js +217 -0
- package/dist/components/recording-button.js.map +1 -0
- package/dist/components/settings-menu.d.ts +9 -15
- package/dist/components/settings-menu.js +48 -161
- package/dist/components/settings-menu.js.map +1 -1
- package/dist/constants.d.ts +8 -2
- package/dist/constants.js +38 -12
- package/dist/constants.js.map +1 -1
- package/dist/contexts/dictation-context.d.ts +99 -0
- package/dist/contexts/dictation-context.js +257 -0
- package/dist/contexts/dictation-context.js.map +1 -0
- package/dist/controllers/DictationController.d.ts +35 -0
- package/dist/controllers/DictationController.js +130 -0
- package/dist/controllers/DictationController.js.map +1 -0
- package/dist/controllers/MediaController.d.ts +31 -0
- package/dist/controllers/MediaController.js +99 -0
- package/dist/controllers/MediaController.js.map +1 -0
- package/dist/controllers/devices-controller.d.ts +26 -0
- package/dist/controllers/devices-controller.js +99 -0
- package/dist/controllers/devices-controller.js.map +1 -0
- package/dist/controllers/dictation-controller.d.ts +28 -0
- package/dist/controllers/dictation-controller.js +141 -0
- package/dist/controllers/dictation-controller.js.map +1 -0
- package/dist/controllers/languages-controller.d.ts +26 -0
- package/dist/controllers/languages-controller.js +83 -0
- package/dist/controllers/languages-controller.js.map +1 -0
- package/dist/controllers/media-controller.d.ts +24 -0
- package/dist/controllers/media-controller.js +115 -0
- package/dist/controllers/media-controller.js.map +1 -0
- package/dist/index.d.ts +9 -1
- package/dist/index.js +29 -3
- package/dist/index.js.map +1 -1
- package/dist/package.json +14 -0
- package/dist/src/components/audio-visualiser.d.ts +14 -0
- package/dist/src/components/audio-visualiser.js +57 -0
- package/dist/src/components/audio-visualiser.js.map +1 -0
- package/dist/src/components/corti-dictation.d.ts +123 -0
- package/dist/src/components/corti-dictation.js +224 -0
- package/dist/src/components/corti-dictation.js.map +1 -0
- package/dist/src/components/device-selector.d.ts +24 -0
- package/dist/src/components/device-selector.js +106 -0
- package/dist/src/components/device-selector.js.map +1 -0
- package/dist/src/components/language-selector.d.ts +24 -0
- package/dist/src/components/language-selector.js +100 -0
- package/dist/src/components/language-selector.js.map +1 -0
- package/dist/src/components/recording-button.d.ts +37 -0
- package/dist/src/components/recording-button.js +203 -0
- package/dist/src/components/recording-button.js.map +1 -0
- package/dist/src/components/settings-menu.d.ts +16 -0
- package/dist/src/components/settings-menu.js +80 -0
- package/dist/src/components/settings-menu.js.map +1 -0
- package/dist/src/constants.d.ts +4 -0
- package/dist/src/constants.js +37 -0
- package/dist/src/constants.js.map +1 -0
- package/dist/src/contexts/dictation-context.d.ts +97 -0
- package/dist/src/contexts/dictation-context.js +208 -0
- package/dist/src/contexts/dictation-context.js.map +1 -0
- package/dist/src/controllers/DictationController.d.ts +35 -0
- package/dist/src/controllers/DictationController.js +130 -0
- package/dist/src/controllers/DictationController.js.map +1 -0
- package/dist/src/controllers/MediaController.d.ts +31 -0
- package/dist/src/controllers/MediaController.js +99 -0
- package/dist/src/controllers/MediaController.js.map +1 -0
- package/dist/src/icons/icons.d.ts +17 -0
- package/dist/src/icons/icons.js +158 -0
- package/dist/src/icons/icons.js.map +1 -0
- package/dist/src/styles/ComponentStyles.d.ts +2 -0
- package/dist/src/styles/ComponentStyles.js +18 -0
- package/dist/src/styles/ComponentStyles.js.map +1 -0
- package/dist/src/styles/audio-visualiser.d.ts +2 -0
- package/dist/src/styles/audio-visualiser.js +33 -0
- package/dist/src/styles/audio-visualiser.js.map +1 -0
- package/dist/src/styles/buttons.d.ts +2 -0
- package/dist/src/styles/buttons.js +52 -0
- package/dist/src/styles/buttons.js.map +1 -0
- package/dist/src/styles/callout.d.ts +2 -0
- package/dist/src/styles/callout.js +23 -0
- package/dist/src/styles/callout.js.map +1 -0
- package/dist/src/styles/default-theme.d.ts +2 -0
- package/dist/src/styles/default-theme.js +50 -0
- package/dist/src/styles/default-theme.js.map +1 -0
- package/dist/src/styles/recording-button.d.ts +2 -0
- package/dist/src/styles/recording-button.js +8 -0
- package/dist/src/styles/recording-button.js.map +1 -0
- package/dist/src/styles/select.d.ts +2 -0
- package/dist/src/styles/select.js +36 -0
- package/dist/src/styles/select.js.map +1 -0
- package/dist/src/styles/settings-menu.d.ts +2 -0
- package/dist/src/styles/settings-menu.js +34 -0
- package/dist/src/styles/settings-menu.js.map +1 -0
- package/dist/src/types.d.ts +7 -0
- package/dist/src/types.js +2 -0
- package/dist/src/types.js.map +1 -0
- package/dist/src/utils/auth.d.ts +9 -0
- package/dist/src/utils/auth.js +21 -0
- package/dist/src/utils/auth.js.map +1 -0
- package/dist/src/utils/converters.d.ts +4 -0
- package/dist/src/utils/converters.js +8 -0
- package/dist/src/utils/converters.js.map +1 -0
- package/dist/src/utils/devices.d.ts +26 -0
- package/dist/src/utils/devices.js +53 -0
- package/dist/src/utils/devices.js.map +1 -0
- package/dist/src/utils/events.d.ts +44 -0
- package/dist/src/utils/events.js +88 -0
- package/dist/src/utils/events.js.map +1 -0
- package/dist/src/utils/languages.d.ts +7 -0
- package/dist/src/utils/languages.js +29 -0
- package/dist/src/utils/languages.js.map +1 -0
- package/dist/src/utils/media.d.ts +6 -0
- package/dist/src/utils/media.js +39 -0
- package/dist/src/utils/media.js.map +1 -0
- package/dist/src/utils/token.d.ts +13 -0
- package/dist/src/utils/token.js +60 -0
- package/dist/src/utils/token.js.map +1 -0
- package/dist/src/utils/validation.d.ts +1 -0
- package/dist/src/utils/validation.js +7 -0
- package/dist/src/utils/validation.js.map +1 -0
- package/dist/stories/audio-visualiser.stories.d.ts +39 -0
- package/dist/stories/audio-visualiser.stories.js +71 -0
- package/dist/stories/audio-visualiser.stories.js.map +1 -0
- package/dist/stories/corti-dictation.stories.d.ts +27 -0
- package/dist/stories/corti-dictation.stories.js +129 -0
- package/dist/stories/corti-dictation.stories.js.map +1 -0
- package/dist/stories/device-selector.stories.d.ts +18 -0
- package/dist/stories/device-selector.stories.js +84 -0
- package/dist/stories/device-selector.stories.js.map +1 -0
- package/dist/stories/language-selector.stories.d.ts +18 -0
- package/dist/stories/language-selector.stories.js +53 -0
- package/dist/stories/language-selector.stories.js.map +1 -0
- package/dist/stories/recording-button.stories.d.ts +27 -0
- package/dist/stories/recording-button.stories.js +90 -0
- package/dist/stories/recording-button.stories.js.map +1 -0
- package/dist/stories/settings-menu.stories.d.ts +23 -0
- package/dist/stories/settings-menu.stories.js +156 -0
- package/dist/stories/settings-menu.stories.js.map +1 -0
- package/dist/styles/ComponentStyles.js +5 -39
- package/dist/styles/ComponentStyles.js.map +1 -1
- package/dist/styles/audio-visualiser.d.ts +2 -0
- package/dist/styles/audio-visualiser.js +33 -0
- package/dist/styles/audio-visualiser.js.map +1 -0
- package/dist/styles/buttons.js +19 -26
- package/dist/styles/buttons.js.map +1 -1
- package/dist/styles/callout.js +7 -17
- package/dist/styles/callout.js.map +1 -1
- package/dist/styles/component-styles.d.ts +2 -0
- package/dist/styles/component-styles.js +22 -0
- package/dist/styles/component-styles.js.map +1 -0
- package/dist/styles/default-theme.d.ts +2 -0
- package/dist/styles/default-theme.js +14 -0
- package/dist/styles/default-theme.js.map +1 -0
- package/dist/styles/recording-button.d.ts +2 -0
- package/dist/styles/recording-button.js +8 -0
- package/dist/styles/recording-button.js.map +1 -0
- package/dist/styles/select.js +9 -9
- package/dist/styles/select.js.map +1 -1
- package/dist/styles/settings-menu.d.ts +2 -0
- package/dist/styles/settings-menu.js +34 -0
- package/dist/styles/settings-menu.js.map +1 -0
- package/dist/tsconfig.stories.tsbuildinfo +1 -0
- package/dist/types.d.ts +6 -8
- package/dist/types.js.map +1 -1
- package/dist/utils/auth.d.ts +9 -0
- package/dist/utils/auth.js +21 -0
- package/dist/utils/auth.js.map +1 -0
- package/dist/utils/converters.d.ts +4 -0
- package/dist/utils/converters.js +8 -0
- package/dist/utils/converters.js.map +1 -0
- package/dist/utils/devices.d.ts +26 -0
- package/dist/utils/devices.js +53 -0
- package/dist/utils/devices.js.map +1 -0
- package/dist/utils/events.d.ts +44 -0
- package/dist/utils/events.js +88 -0
- package/dist/utils/events.js.map +1 -0
- package/dist/utils/languages.d.ts +8 -0
- package/dist/utils/languages.js +29 -0
- package/dist/utils/languages.js.map +1 -0
- package/dist/utils/media.d.ts +6 -0
- package/dist/utils/media.js +39 -0
- package/dist/utils/media.js.map +1 -0
- package/dist/utils/token.d.ts +13 -0
- package/dist/utils/token.js +60 -0
- package/dist/utils/token.js.map +1 -0
- package/dist/utils/validation.d.ts +1 -0
- package/dist/utils/validation.js +7 -0
- package/dist/utils/validation.js.map +1 -0
- package/package.json +29 -55
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { CortiClient, CortiWebSocketProxyClient } from "@corti/sdk";
|
|
2
|
+
import { DEFAULT_DICTATION_CONFIG } from "../constants.js";
|
|
3
|
+
export class DictationController {
|
|
4
|
+
constructor(host) {
|
|
5
|
+
this._cortiClient = null;
|
|
6
|
+
this._webSocket = null;
|
|
7
|
+
this.host = host;
|
|
8
|
+
host.addController(this);
|
|
9
|
+
}
|
|
10
|
+
hostDisconnected() {
|
|
11
|
+
this.cleanup();
|
|
12
|
+
}
|
|
13
|
+
async connect(mediaRecorder, dictationConfig = DEFAULT_DICTATION_CONFIG, callbacks = {}) {
|
|
14
|
+
if (!mediaRecorder) {
|
|
15
|
+
throw new Error("MediaRecorder is required to connect");
|
|
16
|
+
}
|
|
17
|
+
if (this._webSocket?.readyState === WebSocket.OPEN) {
|
|
18
|
+
throw new Error("Already connected. Disconnect before reconnecting.");
|
|
19
|
+
}
|
|
20
|
+
this._webSocket =
|
|
21
|
+
this.host._socketUrl || this.host._socketProxy
|
|
22
|
+
? await this.connectProxy(dictationConfig)
|
|
23
|
+
: await this.connectAuth(dictationConfig);
|
|
24
|
+
this._onNetworkActivity = callbacks.onNetworkActivity;
|
|
25
|
+
this.setupMediaRecorder(mediaRecorder);
|
|
26
|
+
this.setupWebSocketHandlers(callbacks);
|
|
27
|
+
}
|
|
28
|
+
async connectProxy(dictationConfig) {
|
|
29
|
+
const proxyOptions = this.host._socketProxy || {
|
|
30
|
+
url: this.host._socketUrl || "",
|
|
31
|
+
};
|
|
32
|
+
if (!proxyOptions.url) {
|
|
33
|
+
throw new Error("Proxy URL is required when using proxy client");
|
|
34
|
+
}
|
|
35
|
+
return await CortiWebSocketProxyClient.transcribe.connect({
|
|
36
|
+
configuration: dictationConfig,
|
|
37
|
+
proxy: proxyOptions,
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
async connectAuth(dictationConfig) {
|
|
41
|
+
if (!this.host._authConfig && !this.host._accessToken) {
|
|
42
|
+
throw new Error("Auth configuration or access token is required to connect");
|
|
43
|
+
}
|
|
44
|
+
// Use authConfig if available, otherwise create one from accessToken
|
|
45
|
+
const auth = this.host._authConfig || {
|
|
46
|
+
accessToken: this.host._accessToken || "",
|
|
47
|
+
refreshAccessToken: () => ({
|
|
48
|
+
accessToken: this.host._accessToken || "",
|
|
49
|
+
}),
|
|
50
|
+
};
|
|
51
|
+
this._cortiClient = new CortiClient({
|
|
52
|
+
auth,
|
|
53
|
+
environment: this.host._region,
|
|
54
|
+
tenantName: this.host._tenantName,
|
|
55
|
+
});
|
|
56
|
+
return await this._cortiClient.transcribe.connect({
|
|
57
|
+
configuration: dictationConfig,
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
setupWebSocketHandlers(callbacks) {
|
|
61
|
+
if (!this._webSocket) {
|
|
62
|
+
throw new Error("WebSocket not initialized");
|
|
63
|
+
}
|
|
64
|
+
this._webSocket.on("message", (message) => {
|
|
65
|
+
this._onNetworkActivity?.("received", message);
|
|
66
|
+
if (callbacks.onMessage) {
|
|
67
|
+
callbacks.onMessage(message);
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
this._webSocket.on("error", (event) => {
|
|
71
|
+
if (callbacks.onError) {
|
|
72
|
+
callbacks.onError(event);
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
this._webSocket.on("close", (event) => {
|
|
76
|
+
if (callbacks.onClose) {
|
|
77
|
+
callbacks.onClose(event);
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
setupMediaRecorder(mediaRecorder) {
|
|
82
|
+
mediaRecorder.ondataavailable = (event) => {
|
|
83
|
+
this._webSocket?.sendAudio(event.data);
|
|
84
|
+
this._onNetworkActivity?.("sent", {
|
|
85
|
+
size: event.data.size,
|
|
86
|
+
type: "audio",
|
|
87
|
+
});
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
async disconnect(onClose) {
|
|
91
|
+
await new Promise((resolve, reject) => {
|
|
92
|
+
if (!this._webSocket || this._webSocket.readyState !== WebSocket.OPEN) {
|
|
93
|
+
resolve();
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
this._webSocket.on("close", (event) => {
|
|
97
|
+
if (this._closeTimeout) {
|
|
98
|
+
clearTimeout(this._closeTimeout);
|
|
99
|
+
this._closeTimeout = undefined;
|
|
100
|
+
}
|
|
101
|
+
if (onClose) {
|
|
102
|
+
onClose(event);
|
|
103
|
+
}
|
|
104
|
+
resolve();
|
|
105
|
+
});
|
|
106
|
+
this._webSocket.sendEnd({ type: "end" });
|
|
107
|
+
this._onNetworkActivity?.("sent", { type: "end" });
|
|
108
|
+
this._closeTimeout = window.setTimeout(() => {
|
|
109
|
+
// Reject the promise before closing the web socket, so the promise rejects before close event fires
|
|
110
|
+
reject(new Error("WebSocket close timeout"));
|
|
111
|
+
if (this._webSocket?.readyState === WebSocket.OPEN) {
|
|
112
|
+
this._webSocket.close();
|
|
113
|
+
}
|
|
114
|
+
}, 10000);
|
|
115
|
+
});
|
|
116
|
+
this.cleanup();
|
|
117
|
+
}
|
|
118
|
+
cleanup() {
|
|
119
|
+
if (this._closeTimeout) {
|
|
120
|
+
clearTimeout(this._closeTimeout);
|
|
121
|
+
this._closeTimeout = undefined;
|
|
122
|
+
}
|
|
123
|
+
if (this._webSocket?.readyState === WebSocket.OPEN) {
|
|
124
|
+
this._webSocket.close();
|
|
125
|
+
}
|
|
126
|
+
this._webSocket = null;
|
|
127
|
+
this._cortiClient = null;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
//# sourceMappingURL=DictationController.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"DictationController.js","sourceRoot":"","sources":["../../src/controllers/DictationController.ts"],"names":[],"mappings":"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","sourcesContent":["import { type Corti, CortiClient, CortiWebSocketProxyClient } from \"@corti/sdk\";\nimport type { ReactiveController, ReactiveControllerHost } from \"lit\";\nimport { DEFAULT_DICTATION_CONFIG } from \"../constants.js\";\nimport type { ProxyOptions } from \"../types.js\";\n\ntype TranscribeSocket = Awaited<\n ReturnType<CortiClient[\"transcribe\"][\"connect\"]>\n>;\n\ninterface DictationControllerHost extends ReactiveControllerHost {\n _accessToken?: string;\n _authConfig?: Corti.BearerOptions;\n _region?: string;\n _tenantName?: string;\n _socketUrl?: string;\n _socketProxy?: ProxyOptions;\n}\n\nexport type TranscribeMessage =\n | Corti.TranscribeConfigStatusMessage\n | Corti.TranscribeUsageMessage\n | Corti.TranscribeEndedMessage\n | Corti.TranscribeErrorMessage\n | Corti.TranscribeTranscriptMessage\n | Corti.TranscribeCommandMessage\n | Corti.TranscribeFlushedMessage;\n\ninterface WebSocketCallbacks {\n onMessage?: (message: TranscribeMessage) => void;\n onError?: (error: Error) => void;\n onClose?: (event: unknown) => void;\n onNetworkActivity?: (direction: \"sent\" | \"received\", data: unknown) => void;\n}\n\nexport class DictationController implements ReactiveController {\n host: DictationControllerHost;\n\n private _cortiClient: CortiClient | null = null;\n private _webSocket: TranscribeSocket | null = null;\n private _closeTimeout?: number;\n private _onNetworkActivity?: WebSocketCallbacks[\"onNetworkActivity\"];\n\n constructor(host: DictationControllerHost) {\n this.host = host;\n host.addController(this);\n }\n\n hostDisconnected(): void {\n this.cleanup();\n }\n\n async connect(\n mediaRecorder: MediaRecorder | null,\n dictationConfig: Corti.TranscribeConfig = DEFAULT_DICTATION_CONFIG,\n callbacks: WebSocketCallbacks = {},\n ): Promise<void> {\n if (!mediaRecorder) {\n throw new Error(\"MediaRecorder is required to connect\");\n }\n\n if (this._webSocket?.readyState === WebSocket.OPEN) {\n throw new Error(\"Already connected. Disconnect before reconnecting.\");\n }\n\n this._webSocket =\n this.host._socketUrl || this.host._socketProxy\n ? await this.connectProxy(dictationConfig)\n : await this.connectAuth(dictationConfig);\n\n this._onNetworkActivity = callbacks.onNetworkActivity;\n this.setupMediaRecorder(mediaRecorder);\n this.setupWebSocketHandlers(callbacks);\n }\n\n private async connectProxy(\n dictationConfig: Corti.TranscribeConfig,\n ): Promise<TranscribeSocket> {\n const proxyOptions = this.host._socketProxy || {\n url: this.host._socketUrl || \"\",\n };\n\n if (!proxyOptions.url) {\n throw new Error(\"Proxy URL is required when using proxy client\");\n }\n\n return await CortiWebSocketProxyClient.transcribe.connect({\n configuration: dictationConfig,\n proxy: proxyOptions,\n });\n }\n\n private async connectAuth(\n dictationConfig: Corti.TranscribeConfig,\n ): Promise<TranscribeSocket> {\n if (!this.host._authConfig && !this.host._accessToken) {\n throw new Error(\n \"Auth configuration or access token is required to connect\",\n );\n }\n\n // Use authConfig if available, otherwise create one from accessToken\n const auth: Corti.BearerOptions = this.host._authConfig || {\n accessToken: this.host._accessToken || \"\",\n refreshAccessToken: () => ({\n accessToken: this.host._accessToken || \"\",\n }),\n };\n\n this._cortiClient = new CortiClient({\n auth,\n environment: this.host._region,\n tenantName: this.host._tenantName,\n });\n\n return await this._cortiClient.transcribe.connect({\n configuration: dictationConfig,\n });\n }\n\n private setupWebSocketHandlers(callbacks: WebSocketCallbacks): void {\n if (!this._webSocket) {\n throw new Error(\"WebSocket not initialized\");\n }\n\n this._webSocket.on(\"message\", (message: TranscribeMessage) => {\n this._onNetworkActivity?.(\"received\", message);\n\n if (callbacks.onMessage) {\n callbacks.onMessage(message);\n }\n });\n\n this._webSocket.on(\"error\", (event: Error) => {\n if (callbacks.onError) {\n callbacks.onError(event);\n }\n });\n\n this._webSocket.on(\"close\", (event: unknown) => {\n if (callbacks.onClose) {\n callbacks.onClose(event);\n }\n });\n }\n\n private setupMediaRecorder(mediaRecorder: MediaRecorder): void {\n mediaRecorder.ondataavailable = (event) => {\n this._webSocket?.sendAudio(event.data);\n this._onNetworkActivity?.(\"sent\", {\n size: event.data.size,\n type: \"audio\",\n });\n };\n }\n\n async disconnect(onClose?: (event: unknown) => void): Promise<void> {\n await new Promise<void>((resolve, reject) => {\n if (!this._webSocket || this._webSocket.readyState !== WebSocket.OPEN) {\n resolve();\n return;\n }\n\n this._webSocket.on(\"close\", (event) => {\n if (this._closeTimeout) {\n clearTimeout(this._closeTimeout);\n this._closeTimeout = undefined;\n }\n\n if (onClose) {\n onClose(event);\n }\n\n resolve();\n });\n\n this._webSocket.sendEnd({ type: \"end\" });\n this._onNetworkActivity?.(\"sent\", { type: \"end\" });\n\n this._closeTimeout = window.setTimeout(() => {\n // Reject the promise before closing the web socket, so the promise rejects before close event fires\n reject(new Error(\"WebSocket close timeout\"));\n\n if (this._webSocket?.readyState === WebSocket.OPEN) {\n this._webSocket.close();\n }\n }, 10000);\n });\n\n this.cleanup();\n }\n\n cleanup(): void {\n if (this._closeTimeout) {\n clearTimeout(this._closeTimeout);\n this._closeTimeout = undefined;\n }\n\n if (this._webSocket?.readyState === WebSocket.OPEN) {\n this._webSocket.close();\n }\n\n this._webSocket = null;\n this._cortiClient = null;\n }\n}\n"]}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { ReactiveController, ReactiveControllerHost } from "lit";
|
|
2
|
+
interface MediaControllerHost extends ReactiveControllerHost {
|
|
3
|
+
_selectedDevice?: MediaDeviceInfo;
|
|
4
|
+
_debug_displayAudio?: boolean;
|
|
5
|
+
}
|
|
6
|
+
export declare class MediaController implements ReactiveController {
|
|
7
|
+
host: MediaControllerHost;
|
|
8
|
+
private _mediaStream;
|
|
9
|
+
private _audioContext;
|
|
10
|
+
private _analyser;
|
|
11
|
+
private _mediaRecorder;
|
|
12
|
+
private _visualiserInterval?;
|
|
13
|
+
private _audioLevel;
|
|
14
|
+
private _onTrackEnded?;
|
|
15
|
+
private _onAudioLevelChange?;
|
|
16
|
+
constructor(host: MediaControllerHost);
|
|
17
|
+
hostDisconnected(): void;
|
|
18
|
+
initialize(onTrackEnded?: () => void): Promise<void>;
|
|
19
|
+
getAudioLevel(): number;
|
|
20
|
+
startAudioLevelMonitoring(onAudioLevelChange?: (level: number) => void): void;
|
|
21
|
+
stopAudioLevelMonitoring(): void;
|
|
22
|
+
cleanup(): Promise<void>;
|
|
23
|
+
/**
|
|
24
|
+
* Stops the media recorder and waits for all buffered data to be flushed.
|
|
25
|
+
* This ensures the final ondataavailable event fires before resolving.
|
|
26
|
+
*/
|
|
27
|
+
stopRecording(): Promise<void>;
|
|
28
|
+
get mediaRecorder(): MediaRecorder | null;
|
|
29
|
+
get audioLevel(): number;
|
|
30
|
+
}
|
|
31
|
+
export {};
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { calculateAudioLevel, createAudioAnalyzer, getMediaStream, } from "../utils/media.js";
|
|
2
|
+
export class MediaController {
|
|
3
|
+
constructor(host) {
|
|
4
|
+
this._mediaStream = null;
|
|
5
|
+
this._audioContext = null;
|
|
6
|
+
this._analyser = null;
|
|
7
|
+
this._mediaRecorder = null;
|
|
8
|
+
this._audioLevel = 0;
|
|
9
|
+
this.host = host;
|
|
10
|
+
host.addController(this);
|
|
11
|
+
}
|
|
12
|
+
hostDisconnected() {
|
|
13
|
+
this.cleanup();
|
|
14
|
+
}
|
|
15
|
+
async initialize(onTrackEnded) {
|
|
16
|
+
await this.cleanup();
|
|
17
|
+
this._onTrackEnded = onTrackEnded;
|
|
18
|
+
this._mediaStream = await getMediaStream(this.host._selectedDevice?.deviceId, this.host._debug_displayAudio);
|
|
19
|
+
this._mediaStream.getTracks().forEach((track) => {
|
|
20
|
+
track.addEventListener("ended", () => {
|
|
21
|
+
if (this._onTrackEnded) {
|
|
22
|
+
this._onTrackEnded();
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
});
|
|
26
|
+
const { audioContext, analyser } = createAudioAnalyzer(this._mediaStream);
|
|
27
|
+
this._audioContext = audioContext;
|
|
28
|
+
this._analyser = analyser;
|
|
29
|
+
this._mediaRecorder = new MediaRecorder(this._mediaStream);
|
|
30
|
+
}
|
|
31
|
+
getAudioLevel() {
|
|
32
|
+
return this._analyser ? calculateAudioLevel(this._analyser) : 0;
|
|
33
|
+
}
|
|
34
|
+
startAudioLevelMonitoring(onAudioLevelChange) {
|
|
35
|
+
this.stopAudioLevelMonitoring();
|
|
36
|
+
this._onAudioLevelChange = onAudioLevelChange;
|
|
37
|
+
this._visualiserInterval = window.setInterval(() => {
|
|
38
|
+
this._audioLevel = this.getAudioLevel() * 3;
|
|
39
|
+
this.host.requestUpdate();
|
|
40
|
+
if (this._onAudioLevelChange) {
|
|
41
|
+
this._onAudioLevelChange(this._audioLevel);
|
|
42
|
+
}
|
|
43
|
+
}, 150);
|
|
44
|
+
}
|
|
45
|
+
stopAudioLevelMonitoring() {
|
|
46
|
+
if (this._visualiserInterval) {
|
|
47
|
+
clearInterval(this._visualiserInterval);
|
|
48
|
+
this._visualiserInterval = undefined;
|
|
49
|
+
}
|
|
50
|
+
this._audioLevel = 0;
|
|
51
|
+
this.host.requestUpdate();
|
|
52
|
+
if (this._onAudioLevelChange) {
|
|
53
|
+
this._onAudioLevelChange(this._audioLevel);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
async cleanup() {
|
|
57
|
+
this.stopAudioLevelMonitoring();
|
|
58
|
+
if (this._mediaRecorder?.state === "recording") {
|
|
59
|
+
this._mediaRecorder.stop();
|
|
60
|
+
}
|
|
61
|
+
if (this._mediaStream) {
|
|
62
|
+
this._mediaStream.getTracks().forEach((track) => {
|
|
63
|
+
track.stop();
|
|
64
|
+
});
|
|
65
|
+
this._mediaStream = null;
|
|
66
|
+
}
|
|
67
|
+
if (this._audioContext && this._audioContext.state !== "closed") {
|
|
68
|
+
await this._audioContext.close();
|
|
69
|
+
}
|
|
70
|
+
this._audioContext = null;
|
|
71
|
+
this._analyser = null;
|
|
72
|
+
this._mediaRecorder = null;
|
|
73
|
+
this._onTrackEnded = undefined;
|
|
74
|
+
this._onAudioLevelChange = undefined;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Stops the media recorder and waits for all buffered data to be flushed.
|
|
78
|
+
* This ensures the final ondataavailable event fires before resolving.
|
|
79
|
+
*/
|
|
80
|
+
async stopRecording() {
|
|
81
|
+
return new Promise((resolve) => {
|
|
82
|
+
if (!this._mediaRecorder || this._mediaRecorder.state !== "recording") {
|
|
83
|
+
resolve();
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
this._mediaRecorder.onstop = () => {
|
|
87
|
+
resolve();
|
|
88
|
+
};
|
|
89
|
+
this._mediaRecorder.stop();
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
get mediaRecorder() {
|
|
93
|
+
return this._mediaRecorder;
|
|
94
|
+
}
|
|
95
|
+
get audioLevel() {
|
|
96
|
+
return this._audioLevel;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
//# sourceMappingURL=MediaController.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"MediaController.js","sourceRoot":"","sources":["../../src/controllers/MediaController.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ReactiveController, ReactiveControllerHost } from \"lit\";\nimport {\n calculateAudioLevel,\n createAudioAnalyzer,\n getMediaStream,\n} from \"../utils/media.js\";\n\ninterface MediaControllerHost extends ReactiveControllerHost {\n _selectedDevice?: MediaDeviceInfo;\n _debug_displayAudio?: boolean;\n}\n\nexport class MediaController implements ReactiveController {\n host: MediaControllerHost;\n\n private _mediaStream: MediaStream | null = null;\n private _audioContext: AudioContext | null = null;\n private _analyser: AnalyserNode | null = null;\n private _mediaRecorder: MediaRecorder | null = null;\n private _visualiserInterval?: number;\n private _audioLevel: number = 0;\n private _onTrackEnded?: () => void;\n private _onAudioLevelChange?: (level: number) => void;\n\n constructor(host: MediaControllerHost) {\n this.host = host;\n host.addController(this);\n }\n\n hostDisconnected(): void {\n this.cleanup();\n }\n\n async initialize(onTrackEnded?: () => void): Promise<void> {\n await this.cleanup();\n\n this._onTrackEnded = onTrackEnded;\n this._mediaStream = await getMediaStream(\n this.host._selectedDevice?.deviceId,\n this.host._debug_displayAudio,\n );\n\n this._mediaStream.getTracks().forEach((track: MediaStreamTrack) => {\n track.addEventListener(\"ended\", () => {\n if (this._onTrackEnded) {\n this._onTrackEnded();\n }\n });\n });\n\n const { audioContext, analyser } = createAudioAnalyzer(this._mediaStream);\n\n this._audioContext = audioContext;\n this._analyser = analyser;\n\n this._mediaRecorder = new MediaRecorder(this._mediaStream);\n }\n\n getAudioLevel(): number {\n return this._analyser ? calculateAudioLevel(this._analyser) : 0;\n }\n\n startAudioLevelMonitoring(\n onAudioLevelChange?: (level: number) => void,\n ): void {\n this.stopAudioLevelMonitoring();\n\n this._onAudioLevelChange = onAudioLevelChange;\n\n this._visualiserInterval = window.setInterval(() => {\n this._audioLevel = this.getAudioLevel() * 3;\n this.host.requestUpdate();\n\n if (this._onAudioLevelChange) {\n this._onAudioLevelChange(this._audioLevel);\n }\n }, 150);\n }\n\n stopAudioLevelMonitoring(): void {\n if (this._visualiserInterval) {\n clearInterval(this._visualiserInterval);\n this._visualiserInterval = undefined;\n }\n\n this._audioLevel = 0;\n this.host.requestUpdate();\n\n if (this._onAudioLevelChange) {\n this._onAudioLevelChange(this._audioLevel);\n }\n }\n\n async cleanup(): Promise<void> {\n this.stopAudioLevelMonitoring();\n\n if (this._mediaRecorder?.state === \"recording\") {\n this._mediaRecorder.stop();\n }\n\n if (this._mediaStream) {\n this._mediaStream.getTracks().forEach((track) => {\n track.stop();\n });\n this._mediaStream = null;\n }\n\n if (this._audioContext && this._audioContext.state !== \"closed\") {\n await this._audioContext.close();\n }\n\n this._audioContext = null;\n\n this._analyser = null;\n this._mediaRecorder = null;\n this._onTrackEnded = undefined;\n this._onAudioLevelChange = undefined;\n }\n\n /**\n * Stops the media recorder and waits for all buffered data to be flushed.\n * This ensures the final ondataavailable event fires before resolving.\n */\n async stopRecording(): Promise<void> {\n return new Promise<void>((resolve) => {\n if (!this._mediaRecorder || this._mediaRecorder.state !== \"recording\") {\n resolve();\n return;\n }\n\n this._mediaRecorder.onstop = () => {\n resolve();\n };\n\n this._mediaRecorder.stop();\n });\n }\n\n get mediaRecorder(): MediaRecorder | null {\n return this._mediaRecorder;\n }\n\n get audioLevel(): number {\n return this._audioLevel;\n }\n}\n"]}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { ReactiveController, ReactiveControllerHost } from "lit";
|
|
2
|
+
interface DevicesControllerHost extends ReactiveControllerHost {
|
|
3
|
+
devices?: MediaDeviceInfo[];
|
|
4
|
+
selectedDevice?: MediaDeviceInfo;
|
|
5
|
+
dispatchEvent(event: CustomEvent): boolean;
|
|
6
|
+
requestUpdate(): void;
|
|
7
|
+
_devices?: MediaDeviceInfo[];
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Controller that manages automatic device loading.
|
|
11
|
+
* Loads devices when they're not present and handles device changes.
|
|
12
|
+
* Reacts to updates and automatically loads devices when needed.
|
|
13
|
+
*/
|
|
14
|
+
export declare class DevicesController implements ReactiveController {
|
|
15
|
+
#private;
|
|
16
|
+
host: DevicesControllerHost;
|
|
17
|
+
constructor(host: DevicesControllerHost);
|
|
18
|
+
initialize(): void;
|
|
19
|
+
hostDisconnected(): void;
|
|
20
|
+
hostUpdate(): void;
|
|
21
|
+
/**
|
|
22
|
+
* Clear the auto-loaded flag (when devices are set externally)
|
|
23
|
+
*/
|
|
24
|
+
clearAutoLoadedFlag(): void;
|
|
25
|
+
}
|
|
26
|
+
export {};
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
|
|
2
|
+
if (kind === "m") throw new TypeError("Private method is not writable");
|
|
3
|
+
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
|
|
4
|
+
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
|
|
5
|
+
return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
|
|
6
|
+
};
|
|
7
|
+
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
|
|
8
|
+
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
|
|
9
|
+
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
|
|
10
|
+
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
|
|
11
|
+
};
|
|
12
|
+
var _DevicesController_instances, _DevicesController_autoLoadedDevices, _DevicesController_loadingDevices, _DevicesController_deviceChangeHandler, _DevicesController_initialized, _DevicesController_setupDeviceChangeListener, _DevicesController_removeDeviceChangeListener, _DevicesController_loadDevices;
|
|
13
|
+
import { getAudioDevices } from "../utils/devices.js";
|
|
14
|
+
import { errorEvent, readyEvent, recordingDevicesChangedEvent, } from "../utils/events.js";
|
|
15
|
+
/**
|
|
16
|
+
* Controller that manages automatic device loading.
|
|
17
|
+
* Loads devices when they're not present and handles device changes.
|
|
18
|
+
* Reacts to updates and automatically loads devices when needed.
|
|
19
|
+
*/
|
|
20
|
+
export class DevicesController {
|
|
21
|
+
constructor(host) {
|
|
22
|
+
_DevicesController_instances.add(this);
|
|
23
|
+
_DevicesController_autoLoadedDevices.set(this, false);
|
|
24
|
+
_DevicesController_loadingDevices.set(this, false);
|
|
25
|
+
_DevicesController_deviceChangeHandler.set(this, void 0);
|
|
26
|
+
_DevicesController_initialized.set(this, false);
|
|
27
|
+
this.host = host;
|
|
28
|
+
host.addController(this);
|
|
29
|
+
}
|
|
30
|
+
initialize() {
|
|
31
|
+
__classPrivateFieldSet(this, _DevicesController_initialized, true, "f");
|
|
32
|
+
if (this.host.devices === undefined) {
|
|
33
|
+
__classPrivateFieldGet(this, _DevicesController_instances, "m", _DevicesController_loadDevices).call(this);
|
|
34
|
+
__classPrivateFieldGet(this, _DevicesController_instances, "m", _DevicesController_setupDeviceChangeListener).call(this);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
hostDisconnected() {
|
|
38
|
+
__classPrivateFieldGet(this, _DevicesController_instances, "m", _DevicesController_removeDeviceChangeListener).call(this);
|
|
39
|
+
}
|
|
40
|
+
hostUpdate() {
|
|
41
|
+
// Only react to updates after initialization
|
|
42
|
+
if (!__classPrivateFieldGet(this, _DevicesController_initialized, "f")) {
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
// When devices are accessed but not present, load them
|
|
46
|
+
if (this.host.devices === undefined) {
|
|
47
|
+
__classPrivateFieldGet(this, _DevicesController_instances, "m", _DevicesController_loadDevices).call(this);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Clear the auto-loaded flag (when devices are set externally)
|
|
52
|
+
*/
|
|
53
|
+
clearAutoLoadedFlag() {
|
|
54
|
+
__classPrivateFieldSet(this, _DevicesController_autoLoadedDevices, false, "f");
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
_DevicesController_autoLoadedDevices = new WeakMap(), _DevicesController_loadingDevices = new WeakMap(), _DevicesController_deviceChangeHandler = new WeakMap(), _DevicesController_initialized = new WeakMap(), _DevicesController_instances = new WeakSet(), _DevicesController_setupDeviceChangeListener = function _DevicesController_setupDeviceChangeListener() {
|
|
58
|
+
if (__classPrivateFieldGet(this, _DevicesController_deviceChangeHandler, "f")) {
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
__classPrivateFieldSet(this, _DevicesController_deviceChangeHandler, () => {
|
|
62
|
+
if (__classPrivateFieldGet(this, _DevicesController_autoLoadedDevices, "f")) {
|
|
63
|
+
__classPrivateFieldGet(this, _DevicesController_instances, "m", _DevicesController_loadDevices).call(this);
|
|
64
|
+
}
|
|
65
|
+
}, "f");
|
|
66
|
+
navigator.mediaDevices.addEventListener("devicechange", __classPrivateFieldGet(this, _DevicesController_deviceChangeHandler, "f"));
|
|
67
|
+
}, _DevicesController_removeDeviceChangeListener = function _DevicesController_removeDeviceChangeListener() {
|
|
68
|
+
if (!__classPrivateFieldGet(this, _DevicesController_deviceChangeHandler, "f")) {
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
navigator.mediaDevices.removeEventListener("devicechange", __classPrivateFieldGet(this, _DevicesController_deviceChangeHandler, "f"));
|
|
72
|
+
__classPrivateFieldSet(this, _DevicesController_deviceChangeHandler, undefined, "f");
|
|
73
|
+
}, _DevicesController_loadDevices = async function _DevicesController_loadDevices() {
|
|
74
|
+
if (__classPrivateFieldGet(this, _DevicesController_loadingDevices, "f")) {
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
__classPrivateFieldSet(this, _DevicesController_loadingDevices, true, "f");
|
|
78
|
+
try {
|
|
79
|
+
const { devices, defaultDevice } = await getAudioDevices();
|
|
80
|
+
__classPrivateFieldSet(this, _DevicesController_autoLoadedDevices, true, "f");
|
|
81
|
+
this.host._devices = devices;
|
|
82
|
+
// Use selected device if it still exists, otherwise fall back to default
|
|
83
|
+
const previousDevice = this.host.selectedDevice;
|
|
84
|
+
const selectedDevice = (previousDevice &&
|
|
85
|
+
devices.find((d) => d.deviceId === previousDevice.deviceId)) ??
|
|
86
|
+
defaultDevice;
|
|
87
|
+
this.host.selectedDevice = selectedDevice;
|
|
88
|
+
this.host.requestUpdate();
|
|
89
|
+
this.host.dispatchEvent(recordingDevicesChangedEvent(devices, selectedDevice));
|
|
90
|
+
this.host.dispatchEvent(readyEvent());
|
|
91
|
+
}
|
|
92
|
+
catch (error) {
|
|
93
|
+
this.host.dispatchEvent(errorEvent(error));
|
|
94
|
+
}
|
|
95
|
+
finally {
|
|
96
|
+
__classPrivateFieldSet(this, _DevicesController_loadingDevices, false, "f");
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
//# sourceMappingURL=devices-controller.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"devices-controller.js","sourceRoot":"","sources":["../../src/controllers/devices-controller.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ReactiveController, ReactiveControllerHost } from \"lit\";\nimport { getAudioDevices } from \"../utils/devices.js\";\nimport {\n errorEvent,\n readyEvent,\n recordingDevicesChangedEvent,\n} from \"../utils/events.js\";\n\ninterface DevicesControllerHost extends ReactiveControllerHost {\n devices?: MediaDeviceInfo[];\n selectedDevice?: MediaDeviceInfo;\n dispatchEvent(event: CustomEvent): boolean;\n requestUpdate(): void;\n _devices?: MediaDeviceInfo[];\n}\n\n/**\n * Controller that manages automatic device loading.\n * Loads devices when they're not present and handles device changes.\n * Reacts to updates and automatically loads devices when needed.\n */\nexport class DevicesController implements ReactiveController {\n host: DevicesControllerHost;\n #autoLoadedDevices: boolean = false;\n #loadingDevices: boolean = false;\n #deviceChangeHandler?: () => void;\n #initialized: boolean = false;\n\n constructor(host: DevicesControllerHost) {\n this.host = host;\n host.addController(this);\n }\n\n initialize(): void {\n this.#initialized = true;\n if (this.host.devices === undefined) {\n this.#loadDevices();\n this.#setupDeviceChangeListener();\n }\n }\n\n hostDisconnected(): void {\n this.#removeDeviceChangeListener();\n }\n\n hostUpdate(): void {\n // Only react to updates after initialization\n if (!this.#initialized) {\n return;\n }\n\n // When devices are accessed but not present, load them\n if (this.host.devices === undefined) {\n this.#loadDevices();\n }\n }\n\n #setupDeviceChangeListener(): void {\n if (this.#deviceChangeHandler) {\n return;\n }\n\n this.#deviceChangeHandler = () => {\n if (this.#autoLoadedDevices) {\n this.#loadDevices();\n }\n };\n\n navigator.mediaDevices.addEventListener(\n \"devicechange\",\n this.#deviceChangeHandler,\n );\n }\n\n #removeDeviceChangeListener(): void {\n if (!this.#deviceChangeHandler) {\n return;\n }\n\n navigator.mediaDevices.removeEventListener(\n \"devicechange\",\n this.#deviceChangeHandler,\n );\n this.#deviceChangeHandler = undefined;\n }\n\n async #loadDevices(): Promise<void> {\n if (this.#loadingDevices) {\n return;\n }\n\n this.#loadingDevices = true;\n\n try {\n const { devices, defaultDevice } = await getAudioDevices();\n\n this.#autoLoadedDevices = true;\n this.host._devices = devices;\n\n // Use selected device if it still exists, otherwise fall back to default\n const previousDevice = this.host.selectedDevice;\n const selectedDevice =\n (previousDevice &&\n devices.find((d) => d.deviceId === previousDevice.deviceId)) ??\n defaultDevice;\n\n this.host.selectedDevice = selectedDevice;\n\n this.host.requestUpdate();\n this.host.dispatchEvent(\n recordingDevicesChangedEvent(devices, selectedDevice),\n );\n this.host.dispatchEvent(readyEvent());\n } catch (error) {\n this.host.dispatchEvent(errorEvent(error));\n } finally {\n this.#loadingDevices = false;\n }\n }\n\n /**\n * Clear the auto-loaded flag (when devices are set externally)\n */\n clearAutoLoadedFlag(): void {\n this.#autoLoadedDevices = false;\n }\n}\n"]}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { type Corti } from "@corti/sdk";
|
|
2
|
+
import type { ReactiveController, ReactiveControllerHost } from "lit";
|
|
3
|
+
import type { ProxyOptions } from "../types.js";
|
|
4
|
+
interface DictationControllerHost extends ReactiveControllerHost {
|
|
5
|
+
_accessToken?: string;
|
|
6
|
+
_authConfig?: Corti.BearerOptions;
|
|
7
|
+
_region?: string;
|
|
8
|
+
_tenantName?: string;
|
|
9
|
+
_socketUrl?: string;
|
|
10
|
+
_socketProxy?: ProxyOptions;
|
|
11
|
+
}
|
|
12
|
+
export type TranscribeMessage = Corti.TranscribeConfigStatusMessage | Corti.TranscribeUsageMessage | Corti.TranscribeEndedMessage | Corti.TranscribeErrorMessage | Corti.TranscribeTranscriptMessage | Corti.TranscribeCommandMessage | Corti.TranscribeFlushedMessage;
|
|
13
|
+
interface WebSocketCallbacks {
|
|
14
|
+
onMessage?: (message: TranscribeMessage) => void;
|
|
15
|
+
onError?: (error: Error) => void;
|
|
16
|
+
onClose?: (event: unknown) => void;
|
|
17
|
+
onNetworkActivity?: (direction: "sent" | "received", data: unknown) => void;
|
|
18
|
+
}
|
|
19
|
+
export declare class DictationController implements ReactiveController {
|
|
20
|
+
#private;
|
|
21
|
+
host: DictationControllerHost;
|
|
22
|
+
constructor(host: DictationControllerHost);
|
|
23
|
+
hostDisconnected(): void;
|
|
24
|
+
connect(mediaRecorder: MediaRecorder | null, dictationConfig?: Corti.TranscribeConfig, callbacks?: WebSocketCallbacks): Promise<void>;
|
|
25
|
+
disconnect(onClose?: (event: unknown) => void): Promise<void>;
|
|
26
|
+
cleanup(): void;
|
|
27
|
+
}
|
|
28
|
+
export {};
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
|
|
2
|
+
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
|
|
3
|
+
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
|
|
4
|
+
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
|
|
5
|
+
};
|
|
6
|
+
var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
|
|
7
|
+
if (kind === "m") throw new TypeError("Private method is not writable");
|
|
8
|
+
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
|
|
9
|
+
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
|
|
10
|
+
return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
|
|
11
|
+
};
|
|
12
|
+
var _DictationController_instances, _DictationController_cortiClient, _DictationController_webSocket, _DictationController_closeTimeout, _DictationController_onNetworkActivity, _DictationController_connectProxy, _DictationController_connectAuth, _DictationController_setupWebSocketHandlers, _DictationController_setupMediaRecorder;
|
|
13
|
+
import { CortiClient, CortiWebSocketProxyClient } from "@corti/sdk";
|
|
14
|
+
import { DEFAULT_DICTATION_CONFIG } from "../constants.js";
|
|
15
|
+
export class DictationController {
|
|
16
|
+
constructor(host) {
|
|
17
|
+
_DictationController_instances.add(this);
|
|
18
|
+
_DictationController_cortiClient.set(this, null);
|
|
19
|
+
_DictationController_webSocket.set(this, null);
|
|
20
|
+
_DictationController_closeTimeout.set(this, void 0);
|
|
21
|
+
_DictationController_onNetworkActivity.set(this, void 0);
|
|
22
|
+
this.host = host;
|
|
23
|
+
host.addController(this);
|
|
24
|
+
}
|
|
25
|
+
hostDisconnected() {
|
|
26
|
+
this.cleanup();
|
|
27
|
+
}
|
|
28
|
+
async connect(mediaRecorder, dictationConfig = DEFAULT_DICTATION_CONFIG, callbacks = {}) {
|
|
29
|
+
if (!mediaRecorder) {
|
|
30
|
+
throw new Error("MediaRecorder is required to connect");
|
|
31
|
+
}
|
|
32
|
+
if (__classPrivateFieldGet(this, _DictationController_webSocket, "f")?.readyState === WebSocket.OPEN) {
|
|
33
|
+
throw new Error("Already connected. Disconnect before reconnecting.");
|
|
34
|
+
}
|
|
35
|
+
__classPrivateFieldSet(this, _DictationController_webSocket, this.host._socketUrl || this.host._socketProxy
|
|
36
|
+
? await __classPrivateFieldGet(this, _DictationController_instances, "m", _DictationController_connectProxy).call(this, dictationConfig)
|
|
37
|
+
: await __classPrivateFieldGet(this, _DictationController_instances, "m", _DictationController_connectAuth).call(this, dictationConfig), "f");
|
|
38
|
+
__classPrivateFieldSet(this, _DictationController_onNetworkActivity, callbacks.onNetworkActivity, "f");
|
|
39
|
+
__classPrivateFieldGet(this, _DictationController_instances, "m", _DictationController_setupMediaRecorder).call(this, mediaRecorder);
|
|
40
|
+
__classPrivateFieldGet(this, _DictationController_instances, "m", _DictationController_setupWebSocketHandlers).call(this, callbacks);
|
|
41
|
+
}
|
|
42
|
+
async disconnect(onClose) {
|
|
43
|
+
await new Promise((resolve, reject) => {
|
|
44
|
+
if (!__classPrivateFieldGet(this, _DictationController_webSocket, "f") || __classPrivateFieldGet(this, _DictationController_webSocket, "f").readyState !== WebSocket.OPEN) {
|
|
45
|
+
resolve();
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
__classPrivateFieldGet(this, _DictationController_webSocket, "f").on("close", (event) => {
|
|
49
|
+
if (__classPrivateFieldGet(this, _DictationController_closeTimeout, "f")) {
|
|
50
|
+
clearTimeout(__classPrivateFieldGet(this, _DictationController_closeTimeout, "f"));
|
|
51
|
+
__classPrivateFieldSet(this, _DictationController_closeTimeout, undefined, "f");
|
|
52
|
+
}
|
|
53
|
+
if (onClose) {
|
|
54
|
+
onClose(event);
|
|
55
|
+
}
|
|
56
|
+
resolve();
|
|
57
|
+
});
|
|
58
|
+
__classPrivateFieldGet(this, _DictationController_webSocket, "f").sendEnd({ type: "end" });
|
|
59
|
+
__classPrivateFieldGet(this, _DictationController_onNetworkActivity, "f")?.call(this, "sent", { type: "end" });
|
|
60
|
+
__classPrivateFieldSet(this, _DictationController_closeTimeout, window.setTimeout(() => {
|
|
61
|
+
// Reject the promise before closing the web socket, so the promise rejects before close event fires
|
|
62
|
+
reject(new Error("WebSocket close timeout"));
|
|
63
|
+
if (__classPrivateFieldGet(this, _DictationController_webSocket, "f")?.readyState === WebSocket.OPEN) {
|
|
64
|
+
__classPrivateFieldGet(this, _DictationController_webSocket, "f").close();
|
|
65
|
+
}
|
|
66
|
+
}, 10000), "f");
|
|
67
|
+
});
|
|
68
|
+
this.cleanup();
|
|
69
|
+
}
|
|
70
|
+
cleanup() {
|
|
71
|
+
if (__classPrivateFieldGet(this, _DictationController_closeTimeout, "f")) {
|
|
72
|
+
clearTimeout(__classPrivateFieldGet(this, _DictationController_closeTimeout, "f"));
|
|
73
|
+
__classPrivateFieldSet(this, _DictationController_closeTimeout, undefined, "f");
|
|
74
|
+
}
|
|
75
|
+
if (__classPrivateFieldGet(this, _DictationController_webSocket, "f")?.readyState === WebSocket.OPEN) {
|
|
76
|
+
__classPrivateFieldGet(this, _DictationController_webSocket, "f").close();
|
|
77
|
+
}
|
|
78
|
+
__classPrivateFieldSet(this, _DictationController_webSocket, null, "f");
|
|
79
|
+
__classPrivateFieldSet(this, _DictationController_cortiClient, null, "f");
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
_DictationController_cortiClient = new WeakMap(), _DictationController_webSocket = new WeakMap(), _DictationController_closeTimeout = new WeakMap(), _DictationController_onNetworkActivity = new WeakMap(), _DictationController_instances = new WeakSet(), _DictationController_connectProxy = async function _DictationController_connectProxy(dictationConfig) {
|
|
83
|
+
const proxyOptions = this.host._socketProxy || {
|
|
84
|
+
url: this.host._socketUrl || "",
|
|
85
|
+
};
|
|
86
|
+
if (!proxyOptions.url) {
|
|
87
|
+
throw new Error("Proxy URL is required when using proxy client");
|
|
88
|
+
}
|
|
89
|
+
return await CortiWebSocketProxyClient.transcribe.connect({
|
|
90
|
+
configuration: dictationConfig,
|
|
91
|
+
proxy: proxyOptions,
|
|
92
|
+
});
|
|
93
|
+
}, _DictationController_connectAuth = async function _DictationController_connectAuth(dictationConfig) {
|
|
94
|
+
if (!this.host._authConfig && !this.host._accessToken) {
|
|
95
|
+
throw new Error("Auth configuration or access token is required to connect");
|
|
96
|
+
}
|
|
97
|
+
// Use authConfig if available, otherwise create one from accessToken
|
|
98
|
+
const auth = this.host._authConfig || {
|
|
99
|
+
accessToken: this.host._accessToken || "",
|
|
100
|
+
refreshAccessToken: () => ({
|
|
101
|
+
accessToken: this.host._accessToken || "",
|
|
102
|
+
}),
|
|
103
|
+
};
|
|
104
|
+
__classPrivateFieldSet(this, _DictationController_cortiClient, new CortiClient({
|
|
105
|
+
auth,
|
|
106
|
+
environment: this.host._region,
|
|
107
|
+
tenantName: this.host._tenantName,
|
|
108
|
+
}), "f");
|
|
109
|
+
return await __classPrivateFieldGet(this, _DictationController_cortiClient, "f").transcribe.connect({
|
|
110
|
+
configuration: dictationConfig,
|
|
111
|
+
});
|
|
112
|
+
}, _DictationController_setupWebSocketHandlers = function _DictationController_setupWebSocketHandlers(callbacks) {
|
|
113
|
+
if (!__classPrivateFieldGet(this, _DictationController_webSocket, "f")) {
|
|
114
|
+
throw new Error("WebSocket not initialized");
|
|
115
|
+
}
|
|
116
|
+
__classPrivateFieldGet(this, _DictationController_webSocket, "f").on("message", (message) => {
|
|
117
|
+
__classPrivateFieldGet(this, _DictationController_onNetworkActivity, "f")?.call(this, "received", message);
|
|
118
|
+
if (callbacks.onMessage) {
|
|
119
|
+
callbacks.onMessage(message);
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
__classPrivateFieldGet(this, _DictationController_webSocket, "f").on("error", (event) => {
|
|
123
|
+
if (callbacks.onError) {
|
|
124
|
+
callbacks.onError(event);
|
|
125
|
+
}
|
|
126
|
+
});
|
|
127
|
+
__classPrivateFieldGet(this, _DictationController_webSocket, "f").on("close", (event) => {
|
|
128
|
+
if (callbacks.onClose) {
|
|
129
|
+
callbacks.onClose(event);
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
}, _DictationController_setupMediaRecorder = function _DictationController_setupMediaRecorder(mediaRecorder) {
|
|
133
|
+
mediaRecorder.ondataavailable = (event) => {
|
|
134
|
+
__classPrivateFieldGet(this, _DictationController_webSocket, "f")?.sendAudio(event.data);
|
|
135
|
+
__classPrivateFieldGet(this, _DictationController_onNetworkActivity, "f")?.call(this, "sent", {
|
|
136
|
+
size: event.data.size,
|
|
137
|
+
type: "audio",
|
|
138
|
+
});
|
|
139
|
+
};
|
|
140
|
+
};
|
|
141
|
+
//# sourceMappingURL=dictation-controller.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dictation-controller.js","sourceRoot":"","sources":["../../src/controllers/dictation-controller.ts"],"names":[],"mappings":"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","sourcesContent":["import { type Corti, CortiClient, CortiWebSocketProxyClient } from \"@corti/sdk\";\nimport type { ReactiveController, ReactiveControllerHost } from \"lit\";\nimport { DEFAULT_DICTATION_CONFIG } from \"../constants.js\";\nimport type { ProxyOptions } from \"../types.js\";\n\ntype TranscribeSocket = Awaited<\n ReturnType<CortiClient[\"transcribe\"][\"connect\"]>\n>;\n\ninterface DictationControllerHost extends ReactiveControllerHost {\n _accessToken?: string;\n _authConfig?: Corti.BearerOptions;\n _region?: string;\n _tenantName?: string;\n _socketUrl?: string;\n _socketProxy?: ProxyOptions;\n}\n\nexport type TranscribeMessage =\n | Corti.TranscribeConfigStatusMessage\n | Corti.TranscribeUsageMessage\n | Corti.TranscribeEndedMessage\n | Corti.TranscribeErrorMessage\n | Corti.TranscribeTranscriptMessage\n | Corti.TranscribeCommandMessage\n | Corti.TranscribeFlushedMessage;\n\ninterface WebSocketCallbacks {\n onMessage?: (message: TranscribeMessage) => void;\n onError?: (error: Error) => void;\n onClose?: (event: unknown) => void;\n onNetworkActivity?: (direction: \"sent\" | \"received\", data: unknown) => void;\n}\n\nexport class DictationController implements ReactiveController {\n host: DictationControllerHost;\n\n #cortiClient: CortiClient | null = null;\n #webSocket: TranscribeSocket | null = null;\n #closeTimeout?: number;\n #onNetworkActivity?: WebSocketCallbacks[\"onNetworkActivity\"];\n\n constructor(host: DictationControllerHost) {\n this.host = host;\n host.addController(this);\n }\n\n hostDisconnected(): void {\n this.cleanup();\n }\n\n async connect(\n mediaRecorder: MediaRecorder | null,\n dictationConfig: Corti.TranscribeConfig = DEFAULT_DICTATION_CONFIG,\n callbacks: WebSocketCallbacks = {},\n ): Promise<void> {\n if (!mediaRecorder) {\n throw new Error(\"MediaRecorder is required to connect\");\n }\n\n if (this.#webSocket?.readyState === WebSocket.OPEN) {\n throw new Error(\"Already connected. Disconnect before reconnecting.\");\n }\n\n this.#webSocket =\n this.host._socketUrl || this.host._socketProxy\n ? await this.#connectProxy(dictationConfig)\n : await this.#connectAuth(dictationConfig);\n\n this.#onNetworkActivity = callbacks.onNetworkActivity;\n this.#setupMediaRecorder(mediaRecorder);\n this.#setupWebSocketHandlers(callbacks);\n }\n\n async #connectProxy(\n dictationConfig: Corti.TranscribeConfig,\n ): Promise<TranscribeSocket> {\n const proxyOptions = this.host._socketProxy || {\n url: this.host._socketUrl || \"\",\n };\n\n if (!proxyOptions.url) {\n throw new Error(\"Proxy URL is required when using proxy client\");\n }\n\n return await CortiWebSocketProxyClient.transcribe.connect({\n configuration: dictationConfig,\n proxy: proxyOptions,\n });\n }\n\n async #connectAuth(\n dictationConfig: Corti.TranscribeConfig,\n ): Promise<TranscribeSocket> {\n if (!this.host._authConfig && !this.host._accessToken) {\n throw new Error(\n \"Auth configuration or access token is required to connect\",\n );\n }\n\n // Use authConfig if available, otherwise create one from accessToken\n const auth: Corti.BearerOptions = this.host._authConfig || {\n accessToken: this.host._accessToken || \"\",\n refreshAccessToken: () => ({\n accessToken: this.host._accessToken || \"\",\n }),\n };\n\n this.#cortiClient = new CortiClient({\n auth,\n environment: this.host._region,\n tenantName: this.host._tenantName,\n });\n\n return await this.#cortiClient.transcribe.connect({\n configuration: dictationConfig,\n });\n }\n\n #setupWebSocketHandlers(callbacks: WebSocketCallbacks): void {\n if (!this.#webSocket) {\n throw new Error(\"WebSocket not initialized\");\n }\n\n this.#webSocket.on(\"message\", (message: TranscribeMessage) => {\n this.#onNetworkActivity?.(\"received\", message);\n\n if (callbacks.onMessage) {\n callbacks.onMessage(message);\n }\n });\n\n this.#webSocket.on(\"error\", (event: Error) => {\n if (callbacks.onError) {\n callbacks.onError(event);\n }\n });\n\n this.#webSocket.on(\"close\", (event: unknown) => {\n if (callbacks.onClose) {\n callbacks.onClose(event);\n }\n });\n }\n\n #setupMediaRecorder(mediaRecorder: MediaRecorder): void {\n mediaRecorder.ondataavailable = (event) => {\n this.#webSocket?.sendAudio(event.data);\n this.#onNetworkActivity?.(\"sent\", {\n size: event.data.size,\n type: \"audio\",\n });\n };\n }\n\n async disconnect(onClose?: (event: unknown) => void): Promise<void> {\n await new Promise<void>((resolve, reject) => {\n if (!this.#webSocket || this.#webSocket.readyState !== WebSocket.OPEN) {\n resolve();\n return;\n }\n\n this.#webSocket.on(\"close\", (event) => {\n if (this.#closeTimeout) {\n clearTimeout(this.#closeTimeout);\n this.#closeTimeout = undefined;\n }\n\n if (onClose) {\n onClose(event);\n }\n\n resolve();\n });\n\n this.#webSocket.sendEnd({ type: \"end\" });\n this.#onNetworkActivity?.(\"sent\", { type: \"end\" });\n\n this.#closeTimeout = window.setTimeout(() => {\n // Reject the promise before closing the web socket, so the promise rejects before close event fires\n reject(new Error(\"WebSocket close timeout\"));\n\n if (this.#webSocket?.readyState === WebSocket.OPEN) {\n this.#webSocket.close();\n }\n }, 10000);\n });\n\n this.cleanup();\n }\n\n cleanup(): void {\n if (this.#closeTimeout) {\n clearTimeout(this.#closeTimeout);\n this.#closeTimeout = undefined;\n }\n\n if (this.#webSocket?.readyState === WebSocket.OPEN) {\n this.#webSocket.close();\n }\n\n this.#webSocket = null;\n this.#cortiClient = null;\n }\n}\n"]}
|