@corti/dictation-web 0.1.0 → 0.1.2
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 -17
- package/dist/CortiDictation.d.ts +4 -4
- package/dist/CortiDictation.js +9 -9
- package/dist/CortiDictation.js.map +1 -1
- package/dist/DictationService.d.ts +3 -1
- package/dist/DictationService.js +21 -25
- package/dist/DictationService.js.map +1 -1
- package/dist/RecorderManager.d.ts +2 -1
- package/dist/RecorderManager.js +27 -26
- package/dist/RecorderManager.js.map +1 -1
- package/dist/bundle.js +1691 -0
- package/dist/components/settings-menu.js +5 -5
- package/dist/components/settings-menu.js.map +1 -1
- package/dist/constants.d.ts +1 -1
- package/dist/constants.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/utils.d.ts +1 -4
- package/dist/utils.js +3 -5
- package/dist/utils.js.map +1 -1
- package/package.json +92 -92
package/README.md
CHANGED
|
@@ -5,7 +5,6 @@ The **Corti Dictation SDK** is a web component that enables real-time speech-to-
|
|
|
5
5
|
|
|
6
6
|
> **Note:** OAuth 2.0 authentication is not handled by this SDK. The client must provide an API key or authorization token before using the component.
|
|
7
7
|
|
|
8
|
-
---
|
|
9
8
|
|
|
10
9
|
## Installation
|
|
11
10
|
|
|
@@ -15,10 +14,14 @@ Include the SDK in your project by importing the JavaScript module:
|
|
|
15
14
|
npm i @corti/dictation-web
|
|
16
15
|
```
|
|
17
16
|
|
|
18
|
-
---
|
|
19
17
|
|
|
20
18
|
## Usage
|
|
21
19
|
|
|
20
|
+
### Demo
|
|
21
|
+
|
|
22
|
+
🚀 [Hosted Demo](https://codepen.io/hccullen/pen/OPJmxQR)
|
|
23
|
+
|
|
24
|
+
|
|
22
25
|
### Basic Example
|
|
23
26
|
|
|
24
27
|
```html
|
|
@@ -29,12 +32,12 @@ npm i @corti/dictation-web
|
|
|
29
32
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
30
33
|
</head>
|
|
31
34
|
<body>
|
|
35
|
+
<script src="https://cdn.jsdelivr.net/npm/@corti/dictation-web/dist/index.min.js"></script>
|
|
32
36
|
<corti-dictation authToken="xyz"></corti-dictation>
|
|
33
37
|
<textarea id="transcript" placeholder="Transcript will appear here..."></textarea>
|
|
34
38
|
|
|
35
|
-
<script
|
|
36
|
-
|
|
37
|
-
|
|
39
|
+
<script>
|
|
40
|
+
const dictationEl = document.getElementById('transcript')
|
|
38
41
|
// Listen for events
|
|
39
42
|
dictationEl.addEventListener('transcript', (e) => {
|
|
40
43
|
document.getElementById('transcript').value += e.detail.data.text + ' ';
|
|
@@ -44,7 +47,6 @@ npm i @corti/dictation-web
|
|
|
44
47
|
</html>
|
|
45
48
|
```
|
|
46
49
|
|
|
47
|
-
---
|
|
48
50
|
|
|
49
51
|
## API Reference
|
|
50
52
|
|
|
@@ -72,30 +74,20 @@ npm i @corti/dictation-web
|
|
|
72
74
|
| `transcript` | Fired when a new transcript is received. `detail.data.text` contains the transcribed text. |
|
|
73
75
|
| `audio-level-changed` | Fired when the input audio level changes. `detail.audioLevel` contains the new level. |
|
|
74
76
|
|
|
75
|
-
---
|
|
76
77
|
|
|
77
78
|
## Authentication
|
|
78
79
|
|
|
79
80
|
This SDK does not handle OAuth 2.0 authentication. The client must provide an API key or access token as a string in `authToken`.
|
|
80
81
|
|
|
81
|
-
---
|
|
82
82
|
|
|
83
83
|
## Notes
|
|
84
|
-
- Ensure the provided API token is valid.
|
|
85
|
-
- The component requires microphone access permissions.
|
|
86
84
|
- Works in modern browsers that support Web Components and MediaRecorder API.
|
|
85
|
+
- Supports dark and light mode based on browser preference.
|
|
87
86
|
|
|
88
|
-
---
|
|
89
87
|
|
|
90
88
|
## License
|
|
91
89
|
This SDK is not licensed.
|
|
92
90
|
|
|
93
|
-
---
|
|
94
|
-
|
|
95
|
-
## Developer Guide
|
|
96
|
-
See [Developer Setup](docs/DEV_README.md) for installation and development details.
|
|
97
|
-
|
|
98
|
-
---
|
|
99
91
|
|
|
100
92
|
## Support
|
|
101
93
|
For issues or questions, contact **Corti Support** at [support@corti.ai](mailto:support@corti.ai).
|
package/dist/CortiDictation.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { LitElement } from 'lit';
|
|
2
|
-
import './components/settings-menu';
|
|
3
|
-
import './components/audio-visualiser';
|
|
4
|
-
import './icons/icons';
|
|
5
|
-
import { DictationConfig } from './types';
|
|
2
|
+
import './components/settings-menu.js';
|
|
3
|
+
import './components/audio-visualiser.js';
|
|
4
|
+
import './icons/icons.js';
|
|
5
|
+
import type { DictationConfig } from './types.js';
|
|
6
6
|
export declare class CortiDictation extends LitElement {
|
|
7
7
|
static styles: import("lit").CSSResult[];
|
|
8
8
|
devices: MediaDeviceInfo[];
|
package/dist/CortiDictation.js
CHANGED
|
@@ -2,15 +2,15 @@ import { __decorate } from "tslib";
|
|
|
2
2
|
// corti-dictation.ts
|
|
3
3
|
import { html, LitElement } from 'lit';
|
|
4
4
|
import { property, state } from 'lit/decorators.js';
|
|
5
|
-
import { RecorderManager } from './RecorderManager';
|
|
6
|
-
import './components/settings-menu';
|
|
7
|
-
import './components/audio-visualiser';
|
|
8
|
-
import './icons/icons';
|
|
9
|
-
import ThemeStyles from './styles/theme';
|
|
10
|
-
import ButtonStyles from './styles/buttons';
|
|
11
|
-
import ComponentStyles from './styles/ComponentStyles';
|
|
12
|
-
import { DEFAULT_DICTATION_CONFIG } from './constants';
|
|
13
|
-
import CalloutStyles from './styles/callout';
|
|
5
|
+
import { RecorderManager } from './RecorderManager.js';
|
|
6
|
+
import './components/settings-menu.js';
|
|
7
|
+
import './components/audio-visualiser.js';
|
|
8
|
+
import './icons/icons.js';
|
|
9
|
+
import ThemeStyles from './styles/theme.js';
|
|
10
|
+
import ButtonStyles from './styles/buttons.js';
|
|
11
|
+
import ComponentStyles from './styles/ComponentStyles.js';
|
|
12
|
+
import { DEFAULT_DICTATION_CONFIG } from './constants.js';
|
|
13
|
+
import CalloutStyles from './styles/callout.js';
|
|
14
14
|
export class CortiDictation extends LitElement {
|
|
15
15
|
constructor() {
|
|
16
16
|
super(...arguments);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CortiDictation.js","sourceRoot":"","sources":["../src/CortiDictation.ts"],"names":[],"mappings":";AAAA,qBAAqB;AACrB,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,MAAM,KAAK,CAAC;AACvC,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,EAAE,eAAe,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"CortiDictation.js","sourceRoot":"","sources":["../src/CortiDictation.ts"],"names":[],"mappings":"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","sourcesContent":["// corti-dictation.ts\nimport { html, LitElement } from 'lit';\nimport { property, state } from 'lit/decorators.js';\nimport { RecorderManager } from './RecorderManager.js';\nimport './components/settings-menu.js';\nimport './components/audio-visualiser.js';\nimport './icons/icons.js';\nimport ThemeStyles from './styles/theme.js';\nimport ButtonStyles from './styles/buttons.js';\nimport ComponentStyles from './styles/ComponentStyles.js';\n\nimport type { DictationConfig } from './types.js';\nimport { DEFAULT_DICTATION_CONFIG } from './constants.js';\nimport CalloutStyles from './styles/callout.js';\n\nexport class CortiDictation extends LitElement {\n static styles = [ButtonStyles, ThemeStyles, ComponentStyles, CalloutStyles];\n\n @property({ type: Array })\n devices: MediaDeviceInfo[] = [];\n\n @property({ type: String, reflect: true })\n recordingState = 'stopped';\n\n @property({ type: Object })\n dictationConfig: DictationConfig = DEFAULT_DICTATION_CONFIG;\n\n @property({ type: String })\n authToken: string | undefined;\n\n @state()\n private _audioLevel = 0;\n\n private recorderManager = new RecorderManager();\n\n async connectedCallback() {\n super.connectedCallback();\n await this.recorderManager.initialize();\n this.devices = this.recorderManager.devices;\n\n // Map event names to any extra handling logic\n const eventHandlers: Record<string, (e: CustomEvent) => void> = {\n 'recording-state-changed': e => {\n this.recordingState = e.detail.state;\n },\n 'audio-level-changed': e => {\n this._audioLevel = e.detail.audioLevel;\n this.requestUpdate();\n },\n };\n\n const eventsToRelay = [\n 'recording-state-changed',\n 'audio-level-changed',\n 'error',\n 'transcript',\n ];\n\n eventsToRelay.forEach(eventName => {\n this.recorderManager.addEventListener(eventName, (e: Event) => {\n const customEvent = e as CustomEvent;\n // Perform any additional handling if defined\n if (eventHandlers[eventName]) {\n eventHandlers[eventName](customEvent);\n }\n // Re-dispatch the event from the component\n this.dispatchEvent(\n new CustomEvent(eventName, {\n detail: customEvent.detail,\n bubbles: true,\n composed: true,\n }),\n );\n });\n });\n }\n\n public toggleRecording() {\n this._toggleRecording();\n }\n\n _toggleRecording() {\n if (!this.authToken) return;\n if (this.recordingState === 'recording') {\n this.recorderManager.stopRecording();\n } else if (this.recordingState === 'stopped') {\n this.recorderManager.startRecording({\n dictationConfig: this.dictationConfig,\n authToken: this.authToken,\n });\n }\n }\n\n // Handle device change events if needed\n async _onRecordingDeviceChanged(event: Event) {\n const customEvent = event as CustomEvent;\n if (!this.authToken) return;\n this.recorderManager.selectedDevice = customEvent.detail.deviceId;\n if (this.recordingState === 'recording') {\n await this.recorderManager.stopRecording();\n await this.recorderManager.startRecording({\n dictationConfig: this.dictationConfig,\n authToken: this.authToken,\n });\n }\n }\n\n render() {\n const isConfigured = this.authToken;\n if (!isConfigured) {\n return html`\n <div class=\"wrapper\">\n <div class=\"callout red tiny\">\n Please configure the server settings in the parent component.\n </div>\n </div>\n `;\n }\n\n const isLoading =\n this.recordingState === 'initializing' ||\n this.recordingState === 'stopping';\n const isRecording = this.recordingState === 'recording';\n return html`\n <div class=\"wrapper\">\n <button\n @click=${this._toggleRecording}\n class=${isRecording ? 'red' : 'accent'}\n >\n ${isLoading\n ? html`<icon-loading-spinner></icon-loading-spinner>`\n : isRecording\n ? html`<icon-recording></icon-recording>`\n : html`<icon-mic-on></icon-mic-on>`}\n <audio-visualiser\n .level=${this._audioLevel}\n .active=${isRecording}\n ></audio-visualiser>\n </button>\n\n <settings-menu\n .devices=${this.devices}\n .selectedDevice=${this.recorderManager.selectedDevice}\n ?settingsDisabled=${this.recordingState !== 'stopped'}\n @recording-device-changed=${this._onRecordingDeviceChanged}\n ></settings-menu>\n </div>\n `;\n }\n}\n\nexport default CortiDictation;\n"]}
|
|
@@ -1,13 +1,15 @@
|
|
|
1
|
-
import { DictationConfig } from './types';
|
|
1
|
+
import type { DictationConfig } from './types.js';
|
|
2
2
|
export declare class DictationService extends EventTarget {
|
|
3
3
|
private mediaRecorder;
|
|
4
4
|
private webSocket;
|
|
5
5
|
private authToken;
|
|
6
6
|
private dictationConfig;
|
|
7
|
+
private serverConfig;
|
|
7
8
|
constructor(mediaStream: MediaStream, { dictationConfig, authToken, }: {
|
|
8
9
|
dictationConfig: DictationConfig;
|
|
9
10
|
authToken: string;
|
|
10
11
|
});
|
|
12
|
+
private dispatchCustomEvent;
|
|
11
13
|
startRecording(): void;
|
|
12
14
|
stopRecording(): Promise<void>;
|
|
13
15
|
}
|
package/dist/DictationService.js
CHANGED
|
@@ -1,20 +1,31 @@
|
|
|
1
|
-
import { decodeToken } from './utils';
|
|
1
|
+
import { decodeToken } from './utils.js';
|
|
2
2
|
export class DictationService extends EventTarget {
|
|
3
3
|
constructor(mediaStream, { dictationConfig, authToken, }) {
|
|
4
4
|
super();
|
|
5
5
|
this.mediaRecorder = new MediaRecorder(mediaStream);
|
|
6
6
|
this.authToken = authToken;
|
|
7
7
|
this.dictationConfig = dictationConfig;
|
|
8
|
+
// Decode token during construction.
|
|
9
|
+
const config = decodeToken(this.authToken);
|
|
10
|
+
if (!config) {
|
|
11
|
+
throw new Error('Invalid token');
|
|
12
|
+
}
|
|
13
|
+
this.serverConfig = config;
|
|
8
14
|
this.mediaRecorder.ondataavailable = event => {
|
|
9
|
-
// if webSocket is open, send the data
|
|
10
15
|
if (this.webSocket?.readyState === WebSocket.OPEN) {
|
|
11
16
|
this.webSocket.send(event.data);
|
|
12
17
|
}
|
|
13
18
|
};
|
|
14
19
|
}
|
|
20
|
+
dispatchCustomEvent(eventName, detail) {
|
|
21
|
+
this.dispatchEvent(new CustomEvent(eventName, {
|
|
22
|
+
detail,
|
|
23
|
+
bubbles: true,
|
|
24
|
+
composed: true,
|
|
25
|
+
}));
|
|
26
|
+
}
|
|
15
27
|
startRecording() {
|
|
16
|
-
const
|
|
17
|
-
const url = `wss://api.${serverConfig.environment}.corti.app/audio-bridge/v2/transcribe?tenant-name=${serverConfig.tenant}&token=Bearer%20${this.authToken}`;
|
|
28
|
+
const url = `wss://api.${this.serverConfig.environment}.corti.app/audio-bridge/v2/transcribe?tenant-name=${this.serverConfig.tenant}&token=Bearer%20${this.authToken}`;
|
|
18
29
|
this.webSocket = new WebSocket(url);
|
|
19
30
|
this.webSocket.onopen = () => {
|
|
20
31
|
this.webSocket.send(JSON.stringify({
|
|
@@ -28,44 +39,29 @@ export class DictationService extends EventTarget {
|
|
|
28
39
|
this.mediaRecorder.start(250);
|
|
29
40
|
}
|
|
30
41
|
else if (message.type === 'transcript') {
|
|
31
|
-
this.
|
|
32
|
-
detail: message,
|
|
33
|
-
bubbles: true,
|
|
34
|
-
composed: true,
|
|
35
|
-
}));
|
|
42
|
+
this.dispatchCustomEvent('transcript', message);
|
|
36
43
|
}
|
|
37
44
|
};
|
|
38
45
|
this.webSocket.onerror = event => {
|
|
39
|
-
this.
|
|
40
|
-
detail: event,
|
|
41
|
-
bubbles: true,
|
|
42
|
-
composed: true,
|
|
43
|
-
}));
|
|
46
|
+
this.dispatchCustomEvent('error', event);
|
|
44
47
|
};
|
|
45
48
|
this.webSocket.onclose = event => {
|
|
46
|
-
this.
|
|
47
|
-
detail: event,
|
|
48
|
-
bubbles: true,
|
|
49
|
-
composed: true,
|
|
50
|
-
}));
|
|
49
|
+
this.dispatchCustomEvent('stream-closed', event);
|
|
51
50
|
};
|
|
52
51
|
}
|
|
53
52
|
async stopRecording() {
|
|
54
53
|
this.mediaRecorder.stop();
|
|
55
54
|
if (this.webSocket?.readyState === WebSocket.OPEN) {
|
|
56
|
-
this.webSocket.send(JSON.stringify({
|
|
57
|
-
type: 'end',
|
|
58
|
-
}));
|
|
55
|
+
this.webSocket.send(JSON.stringify({ type: 'end' }));
|
|
59
56
|
}
|
|
60
|
-
const
|
|
57
|
+
const timeout = setTimeout(() => {
|
|
61
58
|
if (this.webSocket?.readyState === WebSocket.OPEN) {
|
|
62
59
|
this.webSocket.close();
|
|
63
60
|
}
|
|
64
61
|
}, 10000);
|
|
65
|
-
// This implementation should be replaced by handling a proper 'ended' message from the server
|
|
66
62
|
this.webSocket.onclose = () => {
|
|
67
63
|
this.webSocket?.close();
|
|
68
|
-
clearTimeout(
|
|
64
|
+
clearTimeout(timeout);
|
|
69
65
|
};
|
|
70
66
|
}
|
|
71
67
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DictationService.js","sourceRoot":"","sources":["../src/DictationService.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,WAAW,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"DictationService.js","sourceRoot":"","sources":["../src/DictationService.ts"],"names":[],"mappings":"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","sourcesContent":["import type { DictationConfig, ServerConfig } from './types.js';\nimport { decodeToken } from './utils.js';\n\nexport class DictationService extends EventTarget {\n private mediaRecorder: MediaRecorder;\n private webSocket!: WebSocket;\n private authToken: string;\n private dictationConfig: DictationConfig;\n private serverConfig: ServerConfig;\n\n constructor(\n mediaStream: MediaStream,\n {\n dictationConfig,\n authToken,\n }: { dictationConfig: DictationConfig; authToken: string },\n ) {\n super();\n this.mediaRecorder = new MediaRecorder(mediaStream);\n this.authToken = authToken;\n this.dictationConfig = dictationConfig;\n\n // Decode token during construction.\n const config = decodeToken(this.authToken);\n if (!config) {\n throw new Error('Invalid token');\n }\n this.serverConfig = config;\n\n this.mediaRecorder.ondataavailable = event => {\n if (this.webSocket?.readyState === WebSocket.OPEN) {\n this.webSocket.send(event.data);\n }\n };\n }\n\n private dispatchCustomEvent(eventName: string, detail: unknown): void {\n this.dispatchEvent(\n new CustomEvent(eventName, {\n detail,\n bubbles: true,\n composed: true,\n }),\n );\n }\n\n public startRecording(): void {\n const url = `wss://api.${this.serverConfig.environment}.corti.app/audio-bridge/v2/transcribe?tenant-name=${this.serverConfig.tenant}&token=Bearer%20${this.authToken}`;\n this.webSocket = new WebSocket(url);\n\n this.webSocket.onopen = () => {\n this.webSocket.send(\n JSON.stringify({\n type: 'config',\n configuration: this.dictationConfig,\n }),\n );\n };\n\n this.webSocket.onmessage = event => {\n const message = JSON.parse(event.data);\n if (message.type === 'config') {\n this.mediaRecorder.start(250);\n } else if (message.type === 'transcript') {\n this.dispatchCustomEvent('transcript', message);\n }\n };\n\n this.webSocket.onerror = event => {\n this.dispatchCustomEvent('error', event);\n };\n\n this.webSocket.onclose = event => {\n this.dispatchCustomEvent('stream-closed', event);\n };\n }\n\n public async stopRecording(): Promise<void> {\n this.mediaRecorder.stop();\n\n if (this.webSocket?.readyState === WebSocket.OPEN) {\n this.webSocket.send(JSON.stringify({ type: 'end' }));\n }\n\n const timeout = setTimeout(() => {\n if (this.webSocket?.readyState === WebSocket.OPEN) {\n this.webSocket.close();\n }\n }, 10000);\n\n this.webSocket.onclose = () => {\n this.webSocket?.close();\n clearTimeout(timeout);\n };\n }\n}\n"]}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { DictationConfig, RecordingState } from './types';
|
|
1
|
+
import type { DictationConfig, RecordingState } from './types.js';
|
|
2
2
|
export declare class RecorderManager extends EventTarget {
|
|
3
3
|
devices: MediaDeviceInfo[];
|
|
4
4
|
selectedDevice: string;
|
|
@@ -11,6 +11,7 @@ export declare class RecorderManager extends EventTarget {
|
|
|
11
11
|
devices: MediaDeviceInfo[];
|
|
12
12
|
defaultDeviceId?: string;
|
|
13
13
|
}>;
|
|
14
|
+
private dispatchCustomEvent;
|
|
14
15
|
startRecording(params: {
|
|
15
16
|
dictationConfig: DictationConfig;
|
|
16
17
|
authToken: string;
|
package/dist/RecorderManager.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { getAudioDevices } from './utils';
|
|
2
|
-
import { AudioService } from './audioService';
|
|
3
|
-
import { DictationService } from './DictationService';
|
|
1
|
+
import { getAudioDevices } from './utils.js';
|
|
2
|
+
import { AudioService } from './audioService.js';
|
|
3
|
+
import { DictationService } from './DictationService.js';
|
|
4
4
|
export class RecorderManager extends EventTarget {
|
|
5
5
|
constructor() {
|
|
6
6
|
super(...arguments);
|
|
@@ -17,47 +17,48 @@ export class RecorderManager extends EventTarget {
|
|
|
17
17
|
this.selectedDevice = deviceResponse.defaultDeviceId || '';
|
|
18
18
|
return deviceResponse;
|
|
19
19
|
}
|
|
20
|
+
dispatchCustomEvent(eventName, detail) {
|
|
21
|
+
this.dispatchEvent(new CustomEvent(eventName, {
|
|
22
|
+
detail,
|
|
23
|
+
bubbles: true,
|
|
24
|
+
composed: true,
|
|
25
|
+
}));
|
|
26
|
+
}
|
|
20
27
|
async startRecording(params) {
|
|
21
28
|
this._updateRecordingState('initializing');
|
|
29
|
+
// Get media stream and initialize audio service.
|
|
22
30
|
try {
|
|
23
31
|
this._mediaStream = await navigator.mediaDevices.getUserMedia({
|
|
24
32
|
audio: { deviceId: this.selectedDevice },
|
|
25
33
|
});
|
|
26
34
|
this._audioService = new AudioService(this._mediaStream);
|
|
35
|
+
}
|
|
36
|
+
catch (error) {
|
|
37
|
+
this.dispatchCustomEvent('error', error);
|
|
38
|
+
this._updateRecordingState('stopped');
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
// Initialize dictation service.
|
|
42
|
+
try {
|
|
27
43
|
this._dictationService = new DictationService(this._mediaStream, params);
|
|
28
|
-
// Forward custom events from dictation service
|
|
29
|
-
this._dictationService.addEventListener('error', e => this.dispatchEvent(new CustomEvent('error', {
|
|
30
|
-
detail: e.detail,
|
|
31
|
-
bubbles: true,
|
|
32
|
-
composed: true,
|
|
33
|
-
})));
|
|
34
|
-
this._dictationService.addEventListener('stream-closed', () => this.stopRecording());
|
|
35
|
-
this._dictationService.addEventListener('transcript', e => this.dispatchEvent(new CustomEvent('transcript', {
|
|
36
|
-
detail: e.detail,
|
|
37
|
-
bubbles: true,
|
|
38
|
-
composed: true,
|
|
39
|
-
})));
|
|
40
44
|
}
|
|
41
45
|
catch (error) {
|
|
42
|
-
this.
|
|
43
|
-
detail: error,
|
|
44
|
-
bubbles: true,
|
|
45
|
-
composed: true,
|
|
46
|
-
}));
|
|
46
|
+
this.dispatchCustomEvent('error', error);
|
|
47
47
|
this._updateRecordingState('stopped');
|
|
48
48
|
return;
|
|
49
49
|
}
|
|
50
|
-
|
|
50
|
+
// Forward dictation service events.
|
|
51
|
+
this._dictationService.addEventListener('error', e => this.dispatchCustomEvent('error', e.detail));
|
|
52
|
+
this._dictationService.addEventListener('stream-closed', () => this.stopRecording());
|
|
53
|
+
this._dictationService.addEventListener('transcript', e => this.dispatchCustomEvent('transcript', e.detail));
|
|
54
|
+
this._dictationService.startRecording();
|
|
51
55
|
this._updateRecordingState('recording');
|
|
56
|
+
// Update audio level visualization.
|
|
52
57
|
this._visualiserInterval = window.setInterval(() => {
|
|
53
58
|
const level = this._audioService
|
|
54
59
|
? this._audioService.getAudioLevel() * 3
|
|
55
60
|
: 0;
|
|
56
|
-
this.
|
|
57
|
-
detail: { audioLevel: level },
|
|
58
|
-
bubbles: true,
|
|
59
|
-
composed: true,
|
|
60
|
-
}));
|
|
61
|
+
this.dispatchCustomEvent('audio-level-changed', { audioLevel: level });
|
|
61
62
|
}, 150);
|
|
62
63
|
}
|
|
63
64
|
async stopRecording() {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"RecorderManager.js","sourceRoot":"","sources":["../src/RecorderManager.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"RecorderManager.js","sourceRoot":"","sources":["../src/RecorderManager.ts"],"names":[],"mappings":"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","sourcesContent":["import { getAudioDevices } from './utils.js';\nimport { AudioService } from './audioService.js';\nimport { DictationService } from './DictationService.js';\nimport type { DictationConfig, RecordingState } from './types.js';\n\nexport class RecorderManager extends EventTarget {\n public devices: MediaDeviceInfo[] = [];\n\n public selectedDevice: string = '';\n\n public recordingState: RecordingState = 'stopped';\n\n private _mediaStream: MediaStream | null = null;\n\n private _audioService: AudioService | null = null;\n\n private _dictationService: DictationService | null = null;\n\n private _visualiserInterval?: number;\n\n async initialize() {\n const deviceResponse = await getAudioDevices();\n this.devices = deviceResponse.devices;\n this.selectedDevice = deviceResponse.defaultDeviceId || '';\n return deviceResponse;\n }\n private dispatchCustomEvent(eventName: string, detail: unknown): void {\n this.dispatchEvent(\n new CustomEvent(eventName, {\n detail,\n bubbles: true,\n composed: true,\n }),\n );\n }\n\n async startRecording(params: {\n dictationConfig: DictationConfig;\n authToken: string;\n }): Promise<void> {\n this._updateRecordingState('initializing');\n\n // Get media stream and initialize audio service.\n try {\n this._mediaStream = await navigator.mediaDevices.getUserMedia({\n audio: { deviceId: this.selectedDevice },\n });\n this._audioService = new AudioService(this._mediaStream);\n } catch (error) {\n this.dispatchCustomEvent('error', error);\n this._updateRecordingState('stopped');\n return;\n }\n\n // Initialize dictation service.\n try {\n this._dictationService = new DictationService(this._mediaStream, params);\n } catch (error) {\n this.dispatchCustomEvent('error', error);\n this._updateRecordingState('stopped');\n return;\n }\n\n // Forward dictation service events.\n this._dictationService.addEventListener('error', e =>\n this.dispatchCustomEvent('error', (e as CustomEvent).detail),\n );\n this._dictationService.addEventListener('stream-closed', () =>\n this.stopRecording(),\n );\n this._dictationService.addEventListener('transcript', e =>\n this.dispatchCustomEvent('transcript', (e as CustomEvent).detail),\n );\n\n this._dictationService.startRecording();\n this._updateRecordingState('recording');\n\n // Update audio level visualization.\n this._visualiserInterval = window.setInterval(() => {\n const level = this._audioService\n ? this._audioService.getAudioLevel() * 3\n : 0;\n this.dispatchCustomEvent('audio-level-changed', { audioLevel: level });\n }, 150);\n }\n\n async stopRecording() {\n this._updateRecordingState('stopping');\n if (this._visualiserInterval) {\n clearInterval(this._visualiserInterval);\n this._visualiserInterval = undefined;\n }\n if (this._mediaStream) {\n this._mediaStream.getTracks().forEach(track => track.stop());\n this._mediaStream = null;\n }\n await this._dictationService?.stopRecording();\n this._updateRecordingState('stopped');\n }\n\n private _updateRecordingState(state: RecordingState) {\n this.recordingState = state;\n this.dispatchEvent(\n new CustomEvent('recording-state-changed', {\n detail: { state },\n bubbles: true,\n composed: true,\n }),\n );\n }\n}\n"]}
|