| import init, { |
| MoshiASRDecoder, |
| initThreadPool, |
| } from "./build/wasm_speech_streaming.js"; |
|
|
| async function fetchArrayBuffer(url) { |
| const cacheName = "whisper-candle-cache"; |
| const cache = await caches.open(cacheName); |
| const cachedResponse = await cache.match(url); |
| if (cachedResponse) { |
| const data = await cachedResponse.arrayBuffer(); |
| return new Uint8Array(data); |
| } |
| const res = await fetch(url, { cache: "force-cache" }); |
| cache.put(url, res.clone()); |
| return new Uint8Array(await res.arrayBuffer()); |
| } |
|
|
| class MoshiASR { |
| static decoder = null; |
|
|
| |
| static async initialize(params) { |
| const { weightsURL, tokenizerURL, mimiURL, configURL } = params; |
|
|
| if (this.decoder) { |
| self.postMessage({ status: "model_ready" }); |
| return; |
| } |
|
|
| try { |
| await init(); |
| const numThreads = navigator.hardwareConcurrency || 4; |
| await initThreadPool(numThreads); |
|
|
| self.postMessage({ |
| status: "loading", |
| message: `Loading Model (~950 MB)`, |
| }); |
|
|
| const [weightsArrayU8, tokenizerArrayU8, mimiArrayU8, configArrayU8] = |
| await Promise.all([ |
| fetchArrayBuffer(weightsURL), |
| fetchArrayBuffer(tokenizerURL), |
| fetchArrayBuffer(mimiURL), |
| fetchArrayBuffer(configURL), |
| ]); |
|
|
| this.decoder = new MoshiASRDecoder( |
| weightsArrayU8, |
| tokenizerArrayU8, |
| mimiArrayU8, |
| configArrayU8 |
| ); |
|
|
| self.postMessage({ status: "model_ready" }); |
| } catch (error) { |
| self.postMessage({ error: error.message }); |
| } |
| } |
|
|
| static startStream() { |
| if (this.decoder) { |
| this.decoder.start_streaming(); |
| } |
| } |
|
|
| static stopStream() { |
| if (this.decoder) { |
| this.decoder.stop_streaming(); |
| } |
| } |
|
|
| static processAudio(audioData) { |
| if (this.decoder) { |
| this.decoder.process_audio_chunk(audioData, (word) => { |
| self.postMessage({ |
| status: "streaming", |
| word: word, |
| }); |
| }); |
| self.postMessage({ |
| status: "chunk_processed", |
| }); |
| } |
| } |
| } |
|
|
| self.addEventListener("message", async (event) => { |
| const { command } = event.data; |
|
|
| try { |
| switch (command) { |
| case "initialize": |
| const { weightsURL, modelID, tokenizerURL, mimiURL, configURL } = |
| event.data; |
| await MoshiASR.initialize({ |
| weightsURL, |
| modelID, |
| tokenizerURL, |
| mimiURL, |
| configURL, |
| }); |
| break; |
|
|
| case "start_stream": |
| MoshiASR.startStream(); |
| break; |
|
|
| case "stop_stream": |
| MoshiASR.stopStream(); |
| break; |
|
|
| case "process_audio": |
| const { audioData } = event.data; |
| MoshiASR.processAudio(audioData); |
| break; |
|
|
| default: |
| self.postMessage({ error: "Unknown command: " + command }); |
| } |
| } catch (e) { |
| self.postMessage({ error: e.message }); |
| } |
| }); |
|
|