Add onloaderror retries
This commit is contained in:
parent
4e07e737f0
commit
54dea5da36
3 changed files with 163 additions and 61 deletions
|
|
@ -35,6 +35,14 @@ export default defineConfig({
|
|||
screenshot: 'only-on-failure',
|
||||
},
|
||||
|
||||
/* Run your local dev server before starting the tests */
|
||||
webServer: {
|
||||
command: process.env.CI ? 'npm run build && npm run start' : 'npm run dev',
|
||||
url: 'http://localhost:3003',
|
||||
reuseExistingServer: !process.env.CI,
|
||||
timeout: 120 * 1000,
|
||||
},
|
||||
|
||||
/* Configure projects for major browsers */
|
||||
projects: [
|
||||
{
|
||||
|
|
@ -72,12 +80,4 @@ export default defineConfig({
|
|||
// use: { ...devices['Desktop Chrome'], channel: 'chrome' },
|
||||
// },
|
||||
],
|
||||
|
||||
/* Run your local dev server before starting the tests */
|
||||
webServer: {
|
||||
command: 'npm run build && npm run start',
|
||||
url: 'http://localhost:3003',
|
||||
reuseExistingServer: !process.env.CI,
|
||||
timeout: 120 * 1000,
|
||||
},
|
||||
});
|
||||
|
|
|
|||
|
|
@ -48,10 +48,60 @@ const splitIntoSentences = (text: string): string[] => {
|
|||
};
|
||||
|
||||
export async function POST(req: NextRequest) {
|
||||
// First check if the request body is empty
|
||||
const contentLength = req.headers.get('content-length');
|
||||
if (!contentLength || parseInt(contentLength) === 0) {
|
||||
return NextResponse.json(
|
||||
{ error: 'Request body is empty' },
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
|
||||
// Check content type
|
||||
const contentType = req.headers.get('content-type');
|
||||
if (!contentType?.includes('application/json')) {
|
||||
return NextResponse.json(
|
||||
{ error: 'Content-Type must be application/json' },
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
const { text } = await req.json();
|
||||
if (!text) {
|
||||
return NextResponse.json({ error: 'No text provided' }, { status: 400 });
|
||||
// Get the raw body text first to validate it's not empty
|
||||
const rawBody = await req.text();
|
||||
if (!rawBody?.trim()) {
|
||||
return NextResponse.json(
|
||||
{ error: 'Request body is empty' },
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
|
||||
// Try to parse the JSON
|
||||
let body;
|
||||
try {
|
||||
body = JSON.parse(rawBody);
|
||||
} catch (e) {
|
||||
console.error('JSON parse error:', e);
|
||||
return NextResponse.json(
|
||||
{ error: 'Invalid JSON format' },
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
|
||||
// Validate the parsed body has the required text field
|
||||
if (!body || typeof body !== 'object') {
|
||||
return NextResponse.json(
|
||||
{ error: 'Invalid request body format' },
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
|
||||
const { text } = body;
|
||||
if (!text || typeof text !== 'string') {
|
||||
return NextResponse.json(
|
||||
{ error: 'Missing or invalid text field' },
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
|
||||
if (text.length <= MAX_BLOCK_LENGTH) {
|
||||
|
|
@ -65,6 +115,9 @@ export async function POST(req: NextRequest) {
|
|||
return NextResponse.json({ sentences });
|
||||
} catch (error) {
|
||||
console.error('Error processing text:', error);
|
||||
return NextResponse.json({ error: 'Failed to process text' }, { status: 500 });
|
||||
return NextResponse.json(
|
||||
{ error: 'Internal server error' },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -150,7 +150,7 @@ export function TTSProvider({ children }: { children: ReactNode }) {
|
|||
* @returns {Promise<string[]>} Array of processed sentences
|
||||
*/
|
||||
const processTextToSentences = useCallback(async (text: string): Promise<string[]> => {
|
||||
if (text.length === 0) {
|
||||
if (text.length < 1) {
|
||||
return [];
|
||||
}
|
||||
|
||||
|
|
@ -524,58 +524,107 @@ export function TTSProvider({ children }: { children: ReactNode }) {
|
|||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// Get the processed audio data URI directly from processSentence
|
||||
const audioDataUri = await processSentence(sentence);
|
||||
if (!audioDataUri) {
|
||||
throw new Error('No audio data generated');
|
||||
}
|
||||
const MAX_RETRIES = 3;
|
||||
const INITIAL_RETRY_DELAY = 1000; // 1 second
|
||||
|
||||
// Force unload any previous Howl instance to free up resources
|
||||
if (activeHowl) {
|
||||
activeHowl.unload();
|
||||
}
|
||||
|
||||
const howl = new Howl({
|
||||
src: [audioDataUri],
|
||||
format: ['mp3'],
|
||||
html5: true,
|
||||
preload: true,
|
||||
pool: 5,
|
||||
onplay: () => {
|
||||
setIsProcessing(false);
|
||||
if ('mediaSession' in navigator) {
|
||||
navigator.mediaSession.playbackState = 'playing';
|
||||
}
|
||||
},
|
||||
onpause: () => {
|
||||
if ('mediaSession' in navigator) {
|
||||
navigator.mediaSession.playbackState = 'paused';
|
||||
}
|
||||
},
|
||||
onend: () => {
|
||||
howl.unload();
|
||||
setActiveHowl(null);
|
||||
if (isPlaying) {
|
||||
advance();
|
||||
}
|
||||
},
|
||||
onloaderror: (id, error) => {
|
||||
console.warn('Error loading audio:', error);
|
||||
setIsProcessing(false);
|
||||
setActiveHowl(null);
|
||||
howl.unload();
|
||||
setIsPlaying(false);
|
||||
},
|
||||
onstop: () => {
|
||||
setIsProcessing(false);
|
||||
howl.unload();
|
||||
const createHowl = async (retryCount = 0): Promise<Howl | null> => {
|
||||
try {
|
||||
// Get the processed audio data URI directly from processSentence
|
||||
const audioDataUri = await processSentence(sentence);
|
||||
if (!audioDataUri) {
|
||||
throw new Error('No audio data generated');
|
||||
}
|
||||
});
|
||||
|
||||
setActiveHowl(howl);
|
||||
return howl;
|
||||
// Force unload any previous Howl instance to free up resources
|
||||
if (activeHowl) {
|
||||
activeHowl.unload();
|
||||
}
|
||||
|
||||
return new Howl({
|
||||
src: [audioDataUri],
|
||||
format: ['mp3', 'mpeg'],
|
||||
html5: true,
|
||||
preload: true,
|
||||
pool: 5,
|
||||
onplay: () => {
|
||||
setIsProcessing(false);
|
||||
if ('mediaSession' in navigator) {
|
||||
navigator.mediaSession.playbackState = 'playing';
|
||||
}
|
||||
},
|
||||
onplayerror: function(this: Howl, error) {
|
||||
console.warn('Howl playback error:', error);
|
||||
// Try to recover by forcing HTML5 audio mode
|
||||
if (this.state() === 'loaded') {
|
||||
this.unload();
|
||||
this.once('load', () => {
|
||||
this.play();
|
||||
});
|
||||
this.load();
|
||||
}
|
||||
},
|
||||
onloaderror: async function(this: Howl, error) {
|
||||
console.warn(`Error loading audio (attempt ${retryCount + 1}/${MAX_RETRIES}):`, error);
|
||||
|
||||
if (retryCount < MAX_RETRIES) {
|
||||
// Calculate exponential backoff delay
|
||||
const delay = INITIAL_RETRY_DELAY * Math.pow(2, retryCount);
|
||||
console.log(`Retrying in ${delay}ms...`);
|
||||
|
||||
// Wait for the delay
|
||||
await new Promise(resolve => setTimeout(resolve, delay));
|
||||
|
||||
// Try to create a new Howl instance
|
||||
const retryHowl = await createHowl(retryCount + 1);
|
||||
if (retryHowl) {
|
||||
setActiveHowl(retryHowl);
|
||||
retryHowl.play();
|
||||
}
|
||||
} else {
|
||||
console.error('Max retries reached, moving to next sentence');
|
||||
setIsProcessing(false);
|
||||
setActiveHowl(null);
|
||||
this.unload();
|
||||
setIsPlaying(false);
|
||||
|
||||
toast.error('Audio loading failed after retries. Moving to next sentence...', {
|
||||
id: 'audio-load-error',
|
||||
style: {
|
||||
background: 'var(--background)',
|
||||
color: 'var(--accent)',
|
||||
},
|
||||
duration: 2000,
|
||||
});
|
||||
|
||||
advance();
|
||||
}
|
||||
},
|
||||
onend: function(this: Howl) {
|
||||
this.unload();
|
||||
setActiveHowl(null);
|
||||
if (isPlaying) {
|
||||
advance();
|
||||
}
|
||||
},
|
||||
onstop: function(this: Howl) {
|
||||
setIsProcessing(false);
|
||||
this.unload();
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error creating Howl instance:', error);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
const howl = await createHowl();
|
||||
if (howl) {
|
||||
setActiveHowl(howl);
|
||||
return howl;
|
||||
}
|
||||
|
||||
throw new Error('Failed to create Howl instance');
|
||||
} catch (error) {
|
||||
console.error('Error playing TTS:', error);
|
||||
setActiveHowl(null);
|
||||
|
|
|
|||
Loading…
Reference in a new issue