@corti/dictation-web
Version:
Web component for Corti Dictation
133 lines • 5.23 kB
JavaScript
import { getAudioDevices, getMediaStream } from './utils.js';
import { AudioService } from './audioService.js';
import { DictationService } from './DictationService.js';
export class RecorderManager extends EventTarget {
constructor() {
super();
this.devices = [];
this.recordingState = 'stopped';
this._mediaStream = null;
this._audioService = null;
this._dictationService = null;
navigator.mediaDevices.addEventListener('devicechange', this.handleDevicesChange.bind(this));
}
async initialize() {
const deviceResponse = await getAudioDevices();
this.devices = deviceResponse.devices;
this.selectedDevice = deviceResponse.defaultDevice;
return { devices: this.devices, selectedDevice: this.selectedDevice };
}
dispatchCustomEvent(eventName, detail) {
this.dispatchEvent(new CustomEvent(eventName, {
detail,
bubbles: true,
composed: true,
}));
}
async handleDevicesChange() {
const deviceResponse = await getAudioDevices();
this.devices = deviceResponse.devices;
if (!this.devices.find(device => device.deviceId === this.selectedDevice?.deviceId)) {
this.selectedDevice = deviceResponse.defaultDevice;
}
this.dispatchCustomEvent('recording-devices-changed', {
devices: deviceResponse.devices,
selectedDevice: this.selectedDevice || deviceResponse.defaultDevice,
});
}
async startRecording(params) {
this._updateRecordingState('initializing');
try {
this._mediaStream = await getMediaStream(params.debug_displayAudio
? 'display_audio'
: this.selectedDevice?.deviceId);
this._mediaStream.getTracks().forEach((track) => {
track.addEventListener('ended', () => {
if (this.recordingState === 'recording') {
this.dispatchCustomEvent('error', {
message: 'Microphone access was lost.',
});
this.stopRecording();
}
});
});
this._audioService = new AudioService(this._mediaStream);
}
catch (error) {
this.dispatchCustomEvent('error', error);
this.stopRecording();
return;
}
// Initialize dictation service.
try {
this._dictationService = new DictationService(this._mediaStream, params);
// Forward custom events from dictation service
this._dictationService.addEventListener('error', e => {
this.dispatchEvent(new CustomEvent('error', {
detail: e.detail,
bubbles: true,
composed: true,
}));
this.stopRecording();
});
this._dictationService.addEventListener('stream-closed', () => this.stopRecording());
this._dictationService.addEventListener('transcript', e => this.dispatchEvent(new CustomEvent('transcript', {
detail: e.detail,
bubbles: true,
composed: true,
})));
this._dictationService.addEventListener('command', e => this.dispatchEvent(new CustomEvent('command', {
detail: e.detail,
bubbles: true,
composed: true,
})));
}
catch (error) {
this.dispatchCustomEvent('error', error);
this.stopRecording();
return;
}
try {
this._dictationService?.startRecording();
}
catch (error) {
this.dispatchEvent(new CustomEvent('error', {
detail: error,
bubbles: true,
composed: true,
}));
this.stopRecording();
return;
}
this._updateRecordingState('recording');
// Update audio level visualization.
this._visualiserInterval = window.setInterval(() => {
const level = this._audioService
? this._audioService.getAudioLevel() * 3
: 0;
this.dispatchCustomEvent('audio-level-changed', { audioLevel: level });
}, 150);
}
async stopRecording() {
this._updateRecordingState('stopping');
if (this._visualiserInterval) {
clearInterval(this._visualiserInterval);
this._visualiserInterval = undefined;
}
if (this._mediaStream) {
this._mediaStream.getTracks().forEach(track => track.stop());
}
this.dispatchCustomEvent('audio-level-changed', { audioLevel: 0 });
await this._dictationService?.stopRecording();
this._updateRecordingState('stopped');
}
_updateRecordingState(state) {
this.recordingState = state;
this.dispatchEvent(new CustomEvent('recording-state-changed', {
detail: { state },
bubbles: true,
composed: true,
}));
}
}
//# sourceMappingURL=RecorderManager.js.map