Fix: Improve audio recording playback in voice clone component

- Use explicit MIME type for MediaRecorder (audio/webm;codecs=opus)
- Add error handling for audio playback
- Copy chunks before creating blob to prevent race conditions
- Add key prop to audio elements for proper re-rendering
This commit is contained in:
ajaysi
2026-04-07 07:05:45 +05:30
parent ad97dc0d3b
commit 8dd1c13f85

View File

@@ -305,7 +305,14 @@ export const VoiceAvatarPlaceholder: React.FC<{ domainName?: string; onVoiceSet?
const stream = await navigator.mediaDevices.getUserMedia({ audio: true }); const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
streamRef.current = stream; streamRef.current = stream;
const recorder = new MediaRecorder(stream); // Use a widely supported MIME type
const mimeType = MediaRecorder.isTypeSupported('audio/webm;codecs=opus')
? 'audio/webm;codecs=opus'
: MediaRecorder.isTypeSupported('audio/webm')
? 'audio/webm'
: 'audio/mp4';
const recorder = new MediaRecorder(stream, { mimeType });
recorderRef.current = recorder; recorderRef.current = recorder;
chunksRef.current = []; chunksRef.current = [];
@@ -315,7 +322,8 @@ export const VoiceAvatarPlaceholder: React.FC<{ domainName?: string; onVoiceSet?
recorder.onstop = async () => { recorder.onstop = async () => {
try { try {
const blob = new Blob(chunksRef.current, { type: recorder.mimeType || 'audio/webm' }); const chunks = [...chunksRef.current];
const blob = new Blob(chunks, { type: mimeType });
const file = new File([blob], `voice_sample_${Date.now()}.webm`, { type: blob.type }); const file = new File([blob], `voice_sample_${Date.now()}.webm`, { type: blob.type });
if (file.size > 15 * 1024 * 1024) { if (file.size > 15 * 1024 * 1024) {
setError('Recorded file is too large. Please keep it short (520 seconds).'); setError('Recorded file is too large. Please keep it short (520 seconds).');
@@ -323,7 +331,11 @@ export const VoiceAvatarPlaceholder: React.FC<{ domainName?: string; onVoiceSet?
} }
setAudioFile(file); setAudioFile(file);
const url = URL.createObjectURL(blob); const url = URL.createObjectURL(blob);
console.log('[VoiceClone] Created audio preview URL:', url, 'size:', file.size, 'type:', blob.type);
setAudioPreviewUrl(url); setAudioPreviewUrl(url);
} catch (err) {
console.error('[VoiceClone] Error creating audio blob:', err);
setError('Failed to create audio preview. Please try again.');
} finally { } finally {
cleanupRecording(); cleanupRecording();
} }
@@ -745,7 +757,18 @@ export const VoiceAvatarPlaceholder: React.FC<{ domainName?: string; onVoiceSet?
<Typography variant="caption" sx={{ fontWeight: 700, color: '#7C3AED', whiteSpace: 'nowrap' }}> <Typography variant="caption" sx={{ fontWeight: 700, color: '#7C3AED', whiteSpace: 'nowrap' }}>
Source Sample: Source Sample:
</Typography> </Typography>
<audio controls src={audioPreviewUrl} style={{ height: '30px', width: '100%' }} /> <Box sx={{ flex: 1 }}>
<audio
key={audioPreviewUrl}
controls
src={audioPreviewUrl}
style={{ height: '30px', width: '100%' }}
onError={(e) => {
console.error('[VoiceClone] Audio playback error:', e);
setError('Failed to play recording. Please try again.');
}}
/>
</Box>
</Stack> </Stack>
) : null} ) : null}
</Box> </Box>
@@ -975,7 +998,15 @@ export const VoiceAvatarPlaceholder: React.FC<{ domainName?: string; onVoiceSet?
<Typography variant="caption" fontWeight="800" sx={{ color: '#7C3AED', textTransform: 'uppercase', mb: 0.25, display: 'block', fontSize: '0.65rem' }}> <Typography variant="caption" fontWeight="800" sx={{ color: '#7C3AED', textTransform: 'uppercase', mb: 0.25, display: 'block', fontSize: '0.65rem' }}>
Source Recording Source Recording
</Typography> </Typography>
<audio controls src={audioPreviewUrl} style={{ width: '100%', height: '28px' }} /> <audio
key={audioPreviewUrl}
controls
src={audioPreviewUrl}
style={{ width: '100%', height: '28px' }}
onError={(e) => {
console.error('[VoiceClone] Source audio playback error:', e);
}}
/>
</Box> </Box>
)} )}
{resultAudioUrl && ( {resultAudioUrl && (