|
import WorkerInstance from '$lib/workers/kokoro.worker?worker'; |
|
|
|
export class KokoroWorker { |
|
private worker: Worker | null = null; |
|
private initialized: boolean = false; |
|
private dtype: string; |
|
private requestQueue: Array<{ |
|
text: string; |
|
voice: string; |
|
resolve: (value: string) => void; |
|
reject: (reason: any) => void; |
|
}> = []; |
|
private processing = false; |
|
|
|
constructor(dtype: string = 'fp32') { |
|
this.dtype = dtype; |
|
} |
|
|
|
public async init() { |
|
if (this.worker) { |
|
console.warn('KokoroWorker is already initialized.'); |
|
return; |
|
} |
|
|
|
this.worker = new WorkerInstance(); |
|
|
|
|
|
this.worker.onmessage = (event) => { |
|
const { status, error, audioUrl } = event.data; |
|
|
|
if (status === 'init:complete') { |
|
this.initialized = true; |
|
} else if (status === 'init:error') { |
|
console.error(error); |
|
this.initialized = false; |
|
} else if (status === 'generate:complete') { |
|
|
|
const request = this.requestQueue.shift(); |
|
if (request) { |
|
request.resolve(audioUrl); |
|
this.processNextRequest(); |
|
} |
|
} else if (status === 'generate:error') { |
|
const request = this.requestQueue.shift(); |
|
if (request) { |
|
request.reject(new Error(error)); |
|
this.processNextRequest(); |
|
} |
|
} |
|
}; |
|
|
|
return new Promise<void>((resolve, reject) => { |
|
this.worker!.postMessage({ |
|
type: 'init', |
|
payload: { dtype: this.dtype } |
|
}); |
|
|
|
const handleMessage = (event: MessageEvent) => { |
|
if (event.data.status === 'init:complete') { |
|
this.worker!.removeEventListener('message', handleMessage); |
|
this.initialized = true; |
|
resolve(); |
|
} else if (event.data.status === 'init:error') { |
|
this.worker!.removeEventListener('message', handleMessage); |
|
reject(new Error(event.data.error)); |
|
} |
|
}; |
|
|
|
this.worker!.addEventListener('message', handleMessage); |
|
}); |
|
} |
|
|
|
public async generate({ text, voice }: { text: string; voice: string }): Promise<string> { |
|
if (!this.initialized || !this.worker) { |
|
throw new Error('KokoroTTS Worker is not initialized yet.'); |
|
} |
|
|
|
return new Promise<string>((resolve, reject) => { |
|
this.requestQueue.push({ text, voice, resolve, reject }); |
|
if (!this.processing) { |
|
this.processNextRequest(); |
|
} |
|
}); |
|
} |
|
|
|
private processNextRequest() { |
|
if (this.requestQueue.length === 0) { |
|
this.processing = false; |
|
return; |
|
} |
|
|
|
this.processing = true; |
|
const { text, voice } = this.requestQueue[0]; |
|
this.worker!.postMessage({ type: 'generate', payload: { text, voice } }); |
|
} |
|
|
|
public terminate() { |
|
if (this.worker) { |
|
this.worker.terminate(); |
|
this.worker = null; |
|
this.initialized = false; |
|
this.requestQueue = []; |
|
this.processing = false; |
|
} |
|
} |
|
} |
|
|