File size: 2,491 Bytes
888b792
 
 
 
 
 
 
a7c5777
888b792
 
 
 
a7c5777
888b792
 
 
 
 
 
 
 
 
a7c5777
 
 
 
888b792
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
a7c5777
888b792
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
a7c5777
 
 
 
 
 
 
 
 
888b792
 
 
 
 
 
a7c5777
888b792
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
import { useEffect, useRef, useState } from 'react';

type PlayerStatus = 'idle' | 'loading' | 'playing' | 'paused' | 'error';

interface AudioPlayer {
  status: PlayerStatus;
  error: string | null;
  volume: number;
  play: (src: string) => void;
  pause: () => void;
  resume: () => void;
  stop: () => void;
  setVolume: (level: number) => void;
}

/**
 * Lightweight audio controller for blob URLs returned by the TTS service.
 */
export function useAudioPlayer(): AudioPlayer {
  const audioRef = useRef<HTMLAudioElement | null>(null);
  const [status, setStatus] = useState<PlayerStatus>('idle');
  const [error, setError] = useState<string | null>(null);
  const [volume, setVolumeState] = useState<number>(() => {
    const saved = localStorage.getItem('tts-volume');
    return saved ? parseFloat(saved) : 0.7;
  });

  const cleanup = () => {
    const audio = audioRef.current;
    if (audio) {
      audio.pause();
      audio.src = '';
      audioRef.current = null;
    }
  };

  const stop = () => {
    cleanup();
    setStatus('idle');
  };

  const play = (src: string) => {
    setError(null);
    cleanup();
    setStatus('loading');
    const audio = new Audio(src);
    audio.volume = volume;
    audioRef.current = audio;

    audio.oncanplay = () => {
      audio.play().catch((err) => {
        setError(err?.message || 'Failed to play audio.');
        setStatus('error');
      });
    };
    audio.onplay = () => setStatus('playing');
    audio.onpause = () => setStatus((prev) => (prev === 'loading' ? 'loading' : 'paused'));
    audio.onended = () => setStatus('idle');
    audio.onerror = () => {
      setError('Audio playback error.');
      setStatus('error');
    };
  };

  const pause = () => {
    const audio = audioRef.current;
    if (audio && !audio.paused) {
      audio.pause();
    }
  };

  const resume = () => {
    const audio = audioRef.current;
    if (audio && audio.paused) {
      audio.play().catch((err) => {
        setError(err?.message || 'Failed to resume audio.');
        setStatus('error');
      });
    }
  };

  const setVolume = (level: number) => {
    const clamped = Math.max(0, Math.min(1, level));
    setVolumeState(clamped);
    localStorage.setItem('tts-volume', clamped.toString());
    if (audioRef.current) {
      audioRef.current.volume = clamped;
    }
  };

  useEffect(() => {
    return () => {
      cleanup();
    };
  }, []);

  return { status, error, volume, play, pause, resume, stop, setVolume };
}