diff --git a/src/components/TTSPlayer.tsx b/src/components/TTSPlayer.tsx
index 9850eb1..7ec0831 100644
--- a/src/components/TTSPlayer.tsx
+++ b/src/components/TTSPlayer.tsx
@@ -34,7 +34,20 @@ const speedOptions = [
export default function TTSPlayer() {
const [isVisible, setIsVisible] = useState(true);
- const { isPlaying, togglePlay, skipForward, skipBackward, isProcessing, speed, setSpeedAndRestart } = useTTS();
+ const {
+ isPlaying,
+ togglePlay,
+ skipForward,
+ skipBackward,
+ isProcessing,
+ speed,
+ setSpeedAndRestart,
+ voice,
+ setVoiceAndRestart,
+ availableVoices,
+ } = useTTS();
+
+ //console.log(availableVoices);
return (
+
+
+
+
+ {voice}
+
+
+
+ {availableVoices.map((voiceId) => (
+
+ `relative cursor-pointer select-none py-2 px-3 ${
+ active ? 'bg-offbase' : ''
+ } ${selected ? 'font-medium' : ''}`
+ }
+ >
+ {voiceId}
+
+ ))}
+
+
+
);
diff --git a/src/contexts/TTSContext.tsx b/src/contexts/TTSContext.tsx
index 1790d1f..e039b97 100644
--- a/src/contexts/TTSContext.tsx
+++ b/src/contexts/TTSContext.tsx
@@ -41,6 +41,10 @@ interface TTSContextType {
speed: number;
setSpeed: (speed: number) => void;
setSpeedAndRestart: (speed: number) => void;
+ voice: string;
+ setVoice: (voice: string) => void;
+ setVoiceAndRestart: (voice: string) => void;
+ availableVoices: string[];
}
const TTSContext = createContext(undefined);
@@ -60,6 +64,8 @@ export function TTSProvider({ children }: { children: React.ReactNode }) {
const skipTimeoutRef = useRef(null);
const isPausingRef = useRef(false);
const [speed, setSpeed] = useState(1);
+ const [voice, setVoice] = useState('alloy');
+ const [availableVoices, setAvailableVoices] = useState([]);
// Create OpenAI instance
const [openai] = useState(
@@ -95,6 +101,31 @@ export function TTSProvider({ children }: { children: React.ReactNode }) {
}
}, [audioContext]);
+ useEffect(() => {
+ // Fetch available voices when component mounts
+ const fetchVoices = async () => {
+ try {
+ const response = await fetch(`${openai.baseURL}/audio/voices`, {
+ headers: {
+ 'Authorization': `Bearer ${process.env.NEXT_PUBLIC_OPENAI_API_KEY}`,
+ 'Content-Type': 'application/json',
+ },
+ });
+ if (!response.ok) throw new Error('Failed to fetch voices');
+ const data = await response.json();
+ setAvailableVoices(data.voices || []);
+ } catch (error) {
+ console.error('Error fetching voices:', error);
+
+ // Set available voices to default openai voices
+ // Supported voices are alloy, ash, coral, echo, fable, onyx, nova, sage and shimmer
+ setAvailableVoices(['alloy', 'ash', 'coral', 'echo', 'fable', 'onyx', 'nova', 'sage', 'shimmer']);
+ }
+ };
+
+ fetchVoices();
+ }, [openai.baseURL]);
+
// Text preprocessing function to clean and normalize text
const preprocessText = (text: string): string => {
return text
@@ -164,7 +195,7 @@ export function TTSProvider({ children }: { children: React.ReactNode }) {
const startTime = Date.now();
const response = await openai.audio.speech.create({
model: 'tts-1',
- voice: 'alloy',
+ voice: voice as "alloy",
input: cleanedSentence,
speed: speed,
});
@@ -391,7 +422,7 @@ export function TTSProvider({ children }: { children: React.ReactNode }) {
const startTime = Date.now();
const response = await openai.audio.speech.create({
model: 'tts-1',
- voice: 'alloy',
+ voice: voice as "alloy",
input: sentence,
speed: speed,
});
@@ -494,6 +525,22 @@ export function TTSProvider({ children }: { children: React.ReactNode }) {
}
}, [isPlaying, currentIndex, stop]);
+ const setVoiceAndRestart = useCallback((newVoice: string) => {
+ setVoice(newVoice);
+ // Clear the audio cache since it contains audio with the old voice
+ audioCacheRef.current.clear();
+
+ if (isPlaying) {
+ const currentIdx = currentIndex;
+ stop();
+ // Small delay to ensure audio is fully stopped
+ setTimeout(() => {
+ setCurrentIndex(currentIdx);
+ setIsPlaying(true);
+ }, 50);
+ }
+ }, [isPlaying, currentIndex, stop]);
+
const value = {
isPlaying,
currentText,
@@ -512,6 +559,10 @@ export function TTSProvider({ children }: { children: React.ReactNode }) {
speed,
setSpeed,
setSpeedAndRestart,
+ voice,
+ setVoice,
+ setVoiceAndRestart,
+ availableVoices,
};
return (