KokoroWorker.ts 2.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106
  1. import WorkerInstance from '$lib/workers/kokoro.worker?worker';
  2. export class KokoroWorker {
  3. private worker: Worker | null = null;
  4. private initialized: boolean = false;
  5. private dtype: string;
  6. private requestQueue: Array<{
  7. text: string;
  8. voice: string;
  9. resolve: (value: string) => void;
  10. reject: (reason: any) => void;
  11. }> = [];
  12. private processing = false; // To track if a request is being processed
  13. constructor(dtype: string = 'fp32') {
  14. this.dtype = dtype;
  15. }
  16. public async init() {
  17. if (this.worker) {
  18. console.warn('KokoroWorker is already initialized.');
  19. return;
  20. }
  21. this.worker = new WorkerInstance();
  22. // Handle worker messages
  23. this.worker.onmessage = (event) => {
  24. const { status, error, audioUrl } = event.data;
  25. if (status === 'init:complete') {
  26. this.initialized = true;
  27. } else if (status === 'init:error') {
  28. console.error(error);
  29. this.initialized = false;
  30. } else if (status === 'generate:complete') {
  31. // Resolve promise from queue
  32. const request = this.requestQueue.shift();
  33. if (request) {
  34. request.resolve(audioUrl);
  35. this.processNextRequest(); // Process next request in queue
  36. }
  37. } else if (status === 'generate:error') {
  38. const request = this.requestQueue.shift();
  39. if (request) {
  40. request.reject(new Error(error));
  41. this.processNextRequest(); // Continue processing next in queue
  42. }
  43. }
  44. };
  45. return new Promise<void>((resolve, reject) => {
  46. this.worker!.postMessage({
  47. type: 'init',
  48. payload: { dtype: this.dtype }
  49. });
  50. const handleMessage = (event: MessageEvent) => {
  51. if (event.data.status === 'init:complete') {
  52. this.worker!.removeEventListener('message', handleMessage);
  53. this.initialized = true;
  54. resolve();
  55. } else if (event.data.status === 'init:error') {
  56. this.worker!.removeEventListener('message', handleMessage);
  57. reject(new Error(event.data.error));
  58. }
  59. };
  60. this.worker!.addEventListener('message', handleMessage);
  61. });
  62. }
  63. public async generate({ text, voice }: { text: string; voice: string }): Promise<string> {
  64. if (!this.initialized || !this.worker) {
  65. throw new Error('KokoroTTS Worker is not initialized yet.');
  66. }
  67. return new Promise<string>((resolve, reject) => {
  68. this.requestQueue.push({ text, voice, resolve, reject });
  69. if (!this.processing) {
  70. this.processNextRequest();
  71. }
  72. });
  73. }
  74. private processNextRequest() {
  75. if (this.requestQueue.length === 0) {
  76. this.processing = false;
  77. return;
  78. }
  79. this.processing = true;
  80. const { text, voice } = this.requestQueue[0]; // Get first request but don't remove yet
  81. this.worker!.postMessage({ type: 'generate', payload: { text, voice } });
  82. }
  83. public terminate() {
  84. if (this.worker) {
  85. this.worker.terminate();
  86. this.worker = null;
  87. this.initialized = false;
  88. this.requestQueue = [];
  89. this.processing = false;
  90. }
  91. }
  92. }