Files
Claper/assets/js/audio_capture.js
Alex Lion 20abccf384 Merge branch 'feature/real-time-transcription' into v3
# Conflicts:
#	lib/claper_web/live/admin_live/event_live.ex
#	lib/claper_web/live/event_live/manage.ex
#	lib/claper_web/live/event_live/manage.html.heex
#	lib/claper_web/live/event_live/manager_settings_component.ex
#	lib/claper_web/router.ex
#	priv/gettext/de/LC_MESSAGES/default.po
#	priv/gettext/default.pot
#	priv/gettext/en/LC_MESSAGES/default.po
#	priv/gettext/es/LC_MESSAGES/default.po
#	priv/gettext/fr/LC_MESSAGES/default.po
#	priv/gettext/hu/LC_MESSAGES/default.po
#	priv/gettext/it/LC_MESSAGES/default.po
#	priv/gettext/lv/LC_MESSAGES/default.po
#	priv/gettext/nl/LC_MESSAGES/default.po
2026-04-28 17:50:19 +02:00

88 lines
2.3 KiB
JavaScript

import { Socket } from "phoenix";
export class AudioCapture {
constructor(eventUuid, audioToken) {
this.eventUuid = eventUuid;
this.audioToken = audioToken;
this.socket = null;
this.channel = null;
this.stream = null;
this.audioContext = null;
this.workletNode = null;
}
async start(deviceId) {
try {
const audioConstraints = deviceId
? { deviceId: { exact: deviceId } }
: true;
this.stream = await navigator.mediaDevices.getUserMedia({
audio: audioConstraints,
});
this.socket = new Socket("/audio", {
params: { token: this.audioToken },
});
this.socket.connect();
this.channel = this.socket.channel(`audio:${this.eventUuid}`, {});
this.channel
.join()
.receive("ok", () => {
console.log("Joined audio channel");
this.startPCMCapture();
})
.receive("error", (resp) => {
console.error("Unable to join audio channel", resp);
});
} catch (err) {
console.error("Failed to start audio capture:", err);
}
}
async startPCMCapture() {
this.audioContext = new AudioContext({ sampleRate: 48000 });
const source = this.audioContext.createMediaStreamSource(this.stream);
await this.audioContext.audioWorklet.addModule("/worklets/pcm-processor.js");
this.workletNode = new AudioWorkletNode(
this.audioContext,
"pcm-processor",
);
this.workletNode.port.onmessage = (event) => {
if (this.channel) {
const bytes = new Uint8Array(event.data);
const base64 = btoa(String.fromCharCode(...bytes));
this.channel.push("audio_chunk", { data: base64 });
}
};
source.connect(this.workletNode);
this.workletNode.connect(this.audioContext.destination);
}
stop() {
if (this.workletNode) {
this.workletNode.disconnect();
this.workletNode = null;
}
if (this.audioContext) {
this.audioContext.close();
this.audioContext = null;
}
if (this.stream) {
this.stream.getTracks().forEach((track) => track.stop());
this.stream = null;
}
if (this.channel) {
this.channel.leave();
this.channel = null;
}
if (this.socket) {
this.socket.disconnect();
this.socket = null;
}
}
}