audio.ts 1.5 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253
  1. import { Audio, type AudioErrorContext, type AudioPlayOptions, type AudioSound, type AudioVoice } from "@opentui/core"
  2. import { readFile } from "node:fs/promises"
  3. let audio: Audio | null | undefined
  4. const sounds = new Map<string, Promise<AudioSound | null>>()
  5. function getAudio() {
  6. if (audio !== undefined) return audio
  7. try {
  8. const next = Audio.create({ autoStart: false })
  9. next.on("error", (error: Error, context: AudioErrorContext) => {
  10. console.debug("tui audio error", { error, context })
  11. })
  12. audio = next
  13. return next
  14. } catch (error) {
  15. console.debug("failed to create tui audio", { error })
  16. audio = null
  17. return null
  18. }
  19. }
  20. export function loadSoundFile(file: string) {
  21. const current = getAudio()
  22. if (!current) return Promise.resolve(null)
  23. const cached = sounds.get(file)
  24. if (cached) return cached
  25. const task = readFile(file)
  26. .then((bytes) => current.loadSound(bytes))
  27. .catch((error) => {
  28. console.debug("failed to load tui sound", { file, error })
  29. return null
  30. })
  31. sounds.set(file, task)
  32. return task
  33. }
  34. export function play(sound: AudioSound, options?: AudioPlayOptions) {
  35. const current = getAudio()
  36. if (!current) return null
  37. if (!current.isStarted() && !current.start()) return null
  38. return current.play(sound, options)
  39. }
  40. export function stopVoice(voice: AudioVoice) {
  41. return audio?.stopVoice(voice) ?? false
  42. }
  43. export function dispose() {
  44. audio?.dispose()
  45. audio = undefined
  46. sounds.clear()
  47. }