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 (