From 0979f55aecc6768e324b2b175e0a75467b22414f Mon Sep 17 00:00:00 2001 From: Richard Roberson Date: Mon, 20 Jan 2025 18:24:59 -0700 Subject: [PATCH] Fix ' bug --- src/components/TTSPlayer.tsx | 43 +++++++++++++++++++++++++++++++--- src/components/icons/Icons.tsx | 20 ++++++++++++++++ src/contexts/TTSContext.tsx | 27 ++++++++++++++++++++- 3 files changed, 86 insertions(+), 4 deletions(-) diff --git a/src/components/TTSPlayer.tsx b/src/components/TTSPlayer.tsx index 22664a5..9850eb1 100644 --- a/src/components/TTSPlayer.tsx +++ b/src/components/TTSPlayer.tsx @@ -2,12 +2,13 @@ import React, { useState } from 'react'; import { useTTS } from '@/contexts/TTSContext'; -import { Button } from '@headlessui/react'; +import { Button, Listbox, ListboxButton, ListboxOptions, ListboxOption } from '@headlessui/react'; import { PlayIcon, PauseIcon, SkipForwardIcon, SkipBackwardIcon, + ChevronUpDownIcon, } from './icons/Icons'; // Loading spinner component @@ -19,9 +20,21 @@ function LoadingSpinner() { ); } +const speedOptions = [ + { value: 0.5, label: '0.5x' }, + { value: 0.75, label: '0.75x' }, + { value: 1, label: '1x' }, + { value: 1.25, label: '1.25x' }, + { value: 1.5, label: '1.5x' }, + { value: 1.75, label: '1.75x' }, + { value: 2, label: '2x' }, + { value: 2.5, label: '2.5x' }, + { value: 3, label: '3x' }, +]; + export default function TTSPlayer() { const [isVisible, setIsVisible] = useState(true); - const { isPlaying, togglePlay, skipForward, skipBackward, isProcessing } = useTTS(); + const { isPlaying, togglePlay, skipForward, skipBackward, isProcessing, speed, setSpeedAndRestart } = useTTS(); return (
-
+
+ +
+ + + {speed}x + + + + {speedOptions.map((option) => ( + + `relative cursor-pointer select-none py-2 px-3 ${ + active ? 'bg-offbase' : '' + } ${selected ? 'font-medium' : ''}` + } + > + {option.label} + + ))} + + +
); diff --git a/src/components/icons/Icons.tsx b/src/components/icons/Icons.tsx index 7095729..784906f 100644 --- a/src/components/icons/Icons.tsx +++ b/src/components/icons/Icons.tsx @@ -108,3 +108,23 @@ export const SkipBackwardIcon = () => ( /> ); + +export function ChevronUpDownIcon(props: React.SVGProps) { + return ( + + + + ); +} diff --git a/src/contexts/TTSContext.tsx b/src/contexts/TTSContext.tsx index 3f887a4..1790d1f 100644 --- a/src/contexts/TTSContext.tsx +++ b/src/contexts/TTSContext.tsx @@ -38,6 +38,9 @@ interface TTSContextType { stopAndPlayFromIndex: (index: number) => void; sentences: string[]; isProcessing: boolean; + speed: number; + setSpeed: (speed: number) => void; + setSpeedAndRestart: (speed: number) => void; } const TTSContext = createContext(undefined); @@ -56,6 +59,7 @@ export function TTSProvider({ children }: { children: React.ReactNode }) { const skipTriggeredRef = useRef(false); const skipTimeoutRef = useRef(null); const isPausingRef = useRef(false); + const [speed, setSpeed] = useState(1); // Create OpenAI instance const [openai] = useState( @@ -97,7 +101,7 @@ export function TTSProvider({ children }: { children: React.ReactNode }) { // Replace URLs with descriptive text including domain .replace(/\S*(?:https?:\/\/|www\.)([^\/\s]+)(?:\/\S*)?/gi, '- (link to $1) -') // Remove special characters except basic punctuation - .replace(/[^\w\s.,!?;:'"()-]/g, ' ') + //.replace(/[^\w\s.,!?;:'"()-]/g, ' ') // Fix hyphenated words at line breaks (word- word -> wordword) .replace(/(\w+)-\s+(\w+)/g, '$1$2') // Replace multiple spaces with single space @@ -162,6 +166,7 @@ export function TTSProvider({ children }: { children: React.ReactNode }) { model: 'tts-1', voice: 'alloy', input: cleanedSentence, + speed: speed, }); const duration = Date.now() - startTime; @@ -388,6 +393,7 @@ export function TTSProvider({ children }: { children: React.ReactNode }) { model: 'tts-1', voice: 'alloy', input: sentence, + speed: speed, }); const duration = Date.now() - startTime; @@ -472,6 +478,22 @@ export function TTSProvider({ children }: { children: React.ReactNode }) { skipTriggeredRef.current = false; }, [activeSource]); + const setSpeedAndRestart = useCallback((newSpeed: number) => { + setSpeed(newSpeed); + // Clear the audio cache since it contains audio at the old speed + 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, @@ -487,6 +509,9 @@ export function TTSProvider({ children }: { children: React.ReactNode }) { stopAndPlayFromIndex, sentences, isProcessing, + speed, + setSpeed, + setSpeedAndRestart, }; return (