Re-add AbortController API, fixes duplicate playback
This commit is contained in:
parent
cd00ad2b9b
commit
0be34a09fe
2 changed files with 67 additions and 36 deletions
|
|
@ -17,26 +17,33 @@ export async function POST(req: NextRequest) {
|
|||
return NextResponse.json({ error: 'Missing required parameters' }, { status: 400 });
|
||||
}
|
||||
|
||||
// Initialize OpenAI client
|
||||
// Initialize OpenAI client with abort signal
|
||||
const openai = new OpenAI({
|
||||
apiKey: openApiKey,
|
||||
baseURL: openApiBaseUrl,
|
||||
});
|
||||
|
||||
// Request audio from OpenAI
|
||||
// Request audio from OpenAI and pass along the abort signal
|
||||
const response = await openai.audio.speech.create({
|
||||
model: 'tts-1',
|
||||
voice: voice as "alloy",
|
||||
input: text,
|
||||
speed: speed,
|
||||
});
|
||||
}, { signal: req.signal }); // Pass the abort signal to OpenAI client
|
||||
|
||||
// Get the audio data as array buffer
|
||||
// This will also be aborted if the client cancels
|
||||
const arrayBuffer = await response.arrayBuffer();
|
||||
|
||||
// Return audio data with appropriate headers
|
||||
return new NextResponse(arrayBuffer);
|
||||
} catch (error) {
|
||||
// Check if this was an abort error
|
||||
if (error instanceof Error && error.name === 'AbortError') {
|
||||
console.log('TTS request aborted by client');
|
||||
return new Response(null, { status: 499 }); // Use 499 status for client closed request
|
||||
}
|
||||
|
||||
console.error('Error generating TTS:', error);
|
||||
return NextResponse.json(
|
||||
{ error: 'Failed to generate audio' },
|
||||
|
|
|
|||
|
|
@ -139,6 +139,8 @@ export function TTSProvider({ children }: { children: ReactNode }) {
|
|||
|
||||
// Track pending preload requests
|
||||
const preloadRequests = useRef<Map<string, Promise<string>>>(new Map());
|
||||
// Track active abort controllers for TTS requests
|
||||
const activeAbortControllers = useRef<Set<AbortController>>(new Set());
|
||||
|
||||
//console.log('page:', currDocPage, 'pages:', currDocPages);
|
||||
|
||||
|
|
@ -177,7 +179,15 @@ export function TTSProvider({ children }: { children: ReactNode }) {
|
|||
activeHowl.unload(); // Ensure Howl instance is fully cleaned up
|
||||
setActiveHowl(null);
|
||||
}
|
||||
|
||||
if (clearPending) {
|
||||
// Abort all active TTS requests
|
||||
console.log('Aborting active TTS requests');
|
||||
activeAbortControllers.current.forEach(controller => {
|
||||
controller.abort();
|
||||
});
|
||||
activeAbortControllers.current.clear();
|
||||
// Clear any pending preload requests
|
||||
preloadRequests.current.clear();
|
||||
}
|
||||
}, [activeHowl]);
|
||||
|
|
@ -222,7 +232,7 @@ export function TTSProvider({ children }: { children: ReactNode }) {
|
|||
if (!isEPUB) {
|
||||
// Handle next/previous page transitions
|
||||
if ((nextIndex >= sentences.length && currDocPageNumber < currDocPages!) ||
|
||||
(nextIndex < 0 && currDocPageNumber > 1)) {
|
||||
(nextIndex < 0 && currDocPageNumber > 1)) {
|
||||
// Pass wasPlaying to maintain playback state during page turn
|
||||
skipToLocation(currDocPageNumber + (nextIndex >= sentences.length ? 1 : -1));
|
||||
return;
|
||||
|
|
@ -396,6 +406,10 @@ export function TTSProvider({ children }: { children: ReactNode }) {
|
|||
try {
|
||||
console.log('Requesting audio for sentence:', sentence);
|
||||
|
||||
// Create an AbortController for this request
|
||||
const controller = new AbortController();
|
||||
activeAbortControllers.current.add(controller);
|
||||
|
||||
const response = await fetch('/api/tts', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
|
|
@ -408,8 +422,12 @@ export function TTSProvider({ children }: { children: ReactNode }) {
|
|||
voice: voice,
|
||||
speed: speed,
|
||||
}),
|
||||
signal: controller.signal,
|
||||
});
|
||||
|
||||
// Remove the controller once the request is complete
|
||||
activeAbortControllers.current.delete(controller);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to generate audio');
|
||||
}
|
||||
|
|
@ -422,6 +440,12 @@ export function TTSProvider({ children }: { children: ReactNode }) {
|
|||
|
||||
return arrayBuffer;
|
||||
} catch (error) {
|
||||
// Check if this was an abort error
|
||||
if (error instanceof Error && error.name === 'AbortError') {
|
||||
console.log('TTS request aborted:', sentence.substring(0, 20));
|
||||
return;
|
||||
}
|
||||
|
||||
setIsPlaying(false);
|
||||
toast.error('Failed to generate audio. Server not responding.', {
|
||||
id: 'tts-api-error',
|
||||
|
|
@ -532,7 +556,7 @@ export function TTSProvider({ children }: { children: ReactNode }) {
|
|||
}
|
||||
},
|
||||
onloaderror: (id, error) => {
|
||||
console.error('Error loading audio:', error);
|
||||
console.warn('Error loading audio:', error);
|
||||
setIsProcessing(false);
|
||||
setActiveHowl(null);
|
||||
URL.revokeObjectURL(audioUrl);
|
||||
|
|
|
|||
Loading…
Reference in a new issue