{"version":3,"sources":["../../../../src/sensors/useSpeechSynthesis/core.ts"],"sourcesContent":["import { batch, observable, type Observable } from \"@legendapp/state\";\nimport {\n  createSupported,\n  onUnmount,\n  peek,\n  type DeepMaybeObservable,\n  type MaybeObservable,\n  type ReadonlyObservable,\n  type Supportable,\n} from \"@usels/core\";\nimport { resolveWindowSource, type ConfigurableWindow } from \"@shared/configurable\";\n\nexport type UseSpeechSynthesisStatus = \"init\" | \"play\" | \"pause\" | \"end\";\n\nexport interface UseSpeechSynthesisErrorData {\n  error: string;\n  charIndex: number;\n  elapsedTime: number;\n}\n\nexport interface UseSpeechSynthesisOptions extends ConfigurableWindow {\n  /** Language for synthesis. Default: \"en-US\" */\n  lang?: string;\n  /** Pitch (0-2). Default: 1 */\n  pitch?: number;\n  /** Rate (0.1-10). Default: 1 */\n  rate?: number;\n  /** Volume (0-1). Default: 1 */\n  volume?: number;\n  /** Voice to use */\n  voice?: SpeechSynthesisVoice;\n}\n\nexport interface UseSpeechSynthesisReturn extends Supportable {\n  /** Whether speech is currently playing */\n  isPlaying$: ReadonlyObservable<boolean>;\n  /** Current status */\n  status$: ReadonlyObservable<UseSpeechSynthesisStatus>;\n  /** Speech error */\n  error$: ReadonlyObservable<UseSpeechSynthesisErrorData | undefined>;\n  /** Start speaking the text */\n  speak: () => void;\n  /** Stop speaking */\n  stop: () => void;\n  /** Toggle speaking */\n  toggle: (value?: boolean) => void;\n}\n\n/**\n * Framework-agnostic reactive wrapper around the\n * [SpeechSynthesis API](https://developer.mozilla.org/en-US/docs/Web/API/SpeechSynthesis).\n *\n * Each `speak()` call constructs a fresh `SpeechSynthesisUtterance` using the\n * latest text and option values. Cleanup (`speechSynthesis.cancel()`) is\n * registered via `onUnmount`.\n */\n/*@__NO_SIDE_EFFECTS__*/\nexport function createSpeechSynthesis(\n  text: MaybeObservable<string>,\n  options?: DeepMaybeObservable<UseSpeechSynthesisOptions>\n): UseSpeechSynthesisReturn {\n  const text$ = observable(text);\n  const opts$ = observable(options);\n  const win$ = resolveWindowSource(opts$.window as unknown as Observable<unknown>);\n\n  const isSupported$ = createSupported(() => {\n    const win = win$.get();\n    return !!(win && \"speechSynthesis\" in win);\n  });\n\n  const isPlaying$ = observable(false);\n  const status$ = observable<UseSpeechSynthesisStatus>(\"init\");\n  const error$ = observable<UseSpeechSynthesisErrorData | undefined>(undefined);\n\n  const stop = (): void => {\n    const win = win$.peek();\n    if (win) {\n      win.speechSynthesis.cancel();\n    }\n    isPlaying$.set(false);\n  };\n\n  const speak = (): void => {\n    const win = win$.peek();\n    if (!win || !isSupported$.peek()) return;\n\n    // Cancel any ongoing speech before starting\n    win.speechSynthesis.cancel();\n\n    const currentText = text$.peek() ?? \"\";\n    const inst = new SpeechSynthesisUtterance(currentText);\n    const raw = opts$.peek();\n    inst.lang = (peek(raw?.lang) as string | undefined) ?? \"en-US\";\n    inst.pitch = (peek(raw?.pitch) as number | undefined) ?? 1;\n    inst.rate = (peek(raw?.rate) as number | undefined) ?? 1;\n    inst.volume = (peek(raw?.volume) as number | undefined) ?? 1;\n    const voice = peek(raw?.voice) as SpeechSynthesisVoice | undefined;\n    if (voice) {\n      inst.voice = voice;\n    }\n\n    inst.onstart = () => {\n      batch(() => {\n        status$.set(\"play\");\n        isPlaying$.set(true);\n      });\n    };\n\n    inst.onpause = () => {\n      batch(() => {\n        status$.set(\"pause\");\n        isPlaying$.set(false);\n      });\n    };\n\n    inst.onresume = () => {\n      batch(() => {\n        status$.set(\"play\");\n        isPlaying$.set(true);\n      });\n    };\n\n    inst.onend = () => {\n      batch(() => {\n        status$.set(\"end\");\n        isPlaying$.set(false);\n      });\n    };\n\n    inst.onerror = (event: SpeechSynthesisErrorEvent) => {\n      batch(() => {\n        error$.set({\n          error: event.error,\n          charIndex: event.charIndex,\n          elapsedTime: event.elapsedTime,\n        });\n        status$.set(\"end\");\n        isPlaying$.set(false);\n      });\n    };\n\n    win.speechSynthesis.speak(inst);\n  };\n\n  const toggle = (value?: boolean): void => {\n    const shouldPlay = value ?? !isPlaying$.peek();\n    if (shouldPlay) {\n      speak();\n    } else {\n      stop();\n    }\n  };\n\n  onUnmount(() => {\n    stop();\n  });\n\n  return {\n    isSupported$,\n    isPlaying$: isPlaying$ as ReadonlyObservable<boolean>,\n    status$: status$ as ReadonlyObservable<UseSpeechSynthesisStatus>,\n    error$: error$ as ReadonlyObservable<UseSpeechSynthesisErrorData | undefined>,\n    speak,\n    stop,\n    toggle,\n  };\n}\n"],"mappings":"AAAA,SAAS,OAAO,kBAAmC;AACnD;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OAKK;AACP,SAAS,2BAAoD;AAAA;AA+CtD,SAAS,sBACd,MACA,SAC0B;AAC1B,QAAM,QAAQ,WAAW,IAAI;AAC7B,QAAM,QAAQ,WAAW,OAAO;AAChC,QAAM,OAAO,oBAAoB,MAAM,MAAwC;AAE/E,QAAM,eAAe,gBAAgB,MAAM;AACzC,UAAM,MAAM,KAAK,IAAI;AACrB,WAAO,CAAC,EAAE,OAAO,qBAAqB;AAAA,EACxC,CAAC;AAED,QAAM,aAAa,WAAW,KAAK;AACnC,QAAM,UAAU,WAAqC,MAAM;AAC3D,QAAM,SAAS,WAAoD,MAAS;AAE5E,QAAM,OAAO,MAAY;AACvB,UAAM,MAAM,KAAK,KAAK;AACtB,QAAI,KAAK;AACP,UAAI,gBAAgB,OAAO;AAAA,IAC7B;AACA,eAAW,IAAI,KAAK;AAAA,EACtB;AAEA,QAAM,QAAQ,MAAY;AACxB,UAAM,MAAM,KAAK,KAAK;AACtB,QAAI,CAAC,OAAO,CAAC,aAAa,KAAK,EAAG;AAGlC,QAAI,gBAAgB,OAAO;AAE3B,UAAM,cAAc,MAAM,KAAK,KAAK;AACpC,UAAM,OAAO,IAAI,yBAAyB,WAAW;AACrD,UAAM,MAAM,MAAM,KAAK;AACvB,SAAK,OAAQ,KAAK,KAAK,IAAI,KAA4B;AACvD,SAAK,QAAS,KAAK,KAAK,KAAK,KAA4B;AACzD,SAAK,OAAQ,KAAK,KAAK,IAAI,KAA4B;AACvD,SAAK,SAAU,KAAK,KAAK,MAAM,KAA4B;AAC3D,UAAM,QAAQ,KAAK,KAAK,KAAK;AAC7B,QAAI,OAAO;AACT,WAAK,QAAQ;AAAA,IACf;AAEA,SAAK,UAAU,MAAM;AACnB,YAAM,MAAM;AACV,gBAAQ,IAAI,MAAM;AAClB,mBAAW,IAAI,IAAI;AAAA,MACrB,CAAC;AAAA,IACH;AAEA,SAAK,UAAU,MAAM;AACnB,YAAM,MAAM;AACV,gBAAQ,IAAI,OAAO;AACnB,mBAAW,IAAI,KAAK;AAAA,MACtB,CAAC;AAAA,IACH;AAEA,SAAK,WAAW,MAAM;AACpB,YAAM,MAAM;AACV,gBAAQ,IAAI,MAAM;AAClB,mBAAW,IAAI,IAAI;AAAA,MACrB,CAAC;AAAA,IACH;AAEA,SAAK,QAAQ,MAAM;AACjB,YAAM,MAAM;AACV,gBAAQ,IAAI,KAAK;AACjB,mBAAW,IAAI,KAAK;AAAA,MACtB,CAAC;AAAA,IACH;AAEA,SAAK,UAAU,CAAC,UAAqC;AACnD,YAAM,MAAM;AACV,eAAO,IAAI;AAAA,UACT,OAAO,MAAM;AAAA,UACb,WAAW,MAAM;AAAA,UACjB,aAAa,MAAM;AAAA,QACrB,CAAC;AACD,gBAAQ,IAAI,KAAK;AACjB,mBAAW,IAAI,KAAK;AAAA,MACtB,CAAC;AAAA,IACH;AAEA,QAAI,gBAAgB,MAAM,IAAI;AAAA,EAChC;AAEA,QAAM,SAAS,CAAC,UAA0B;AACxC,UAAM,aAAa,SAAS,CAAC,WAAW,KAAK;AAC7C,QAAI,YAAY;AACd,YAAM;AAAA,IACR,OAAO;AACL,WAAK;AAAA,IACP;AAAA,EACF;AAEA,YAAU,MAAM;AACd,SAAK;AAAA,EACP,CAAC;AAED,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;","names":[]}