diff options
-rw-r--r-- | html/voice-memos/app.js | 140 |
1 files changed, 125 insertions, 15 deletions
diff --git a/html/voice-memos/app.js b/html/voice-memos/app.js index d9a300e..0436494 100644 --- a/html/voice-memos/app.js +++ b/html/voice-memos/app.js @@ -9,6 +9,7 @@ * @property {boolean} isPlaying - Playback state flag * @property {number} recordingStartTime - Timestamp when recording started * @property {string} lastError - Last error message + * @property {string} mimeType - MIME type for recording */ /** @@ -23,7 +24,8 @@ const initialState = { countdown: 0, isPlaying: false, recordingStartTime: 0, - lastError: '' + lastError: '', + mimeType: null }; /** @@ -244,6 +246,40 @@ const setupWaveformVisualization = (analyser) => { }; /** + * Determines the best supported MIME type for the current browser + * @returns {string} The best supported MIME type or null if none found + */ +const getSupportedMimeType = () => { + const types = [ + 'audio/webm', + 'audio/mp4', + 'audio/ogg', + 'audio/wav', + 'audio/mpeg' + ]; + + // Add codec options for better compatibility + const typesWithCodecs = [ + 'audio/webm;codecs=opus', + 'audio/webm;codecs=pcm', + 'audio/mp4;codecs=mp4a.40.2' + ]; + + // Combine all types to check + const allTypes = [...typesWithCodecs, ...types]; + + for (const type of allTypes) { + if (MediaRecorder.isTypeSupported(type)) { + console.log(`Browser supports recording with MIME type: ${type}`); + return type; + } + } + + console.warn('No supported MIME types found for MediaRecorder'); + return null; +}; + +/** * Starts the recording process with a countdown */ const startRecording = async () => { @@ -270,7 +306,12 @@ const startRecording = async () => { const source = audioContext.createMediaStreamSource(stream); source.connect(analyser); - const mediaRecorder = new MediaRecorder(stream); + // Get supported MIME type + const mimeType = getSupportedMimeType(); + + // Create MediaRecorder with options if mimeType is supported + const options = mimeType ? { mimeType } : undefined; + const mediaRecorder = new MediaRecorder(stream, options); const audioChunks = []; mediaRecorder.ondataavailable = (event) => { @@ -299,7 +340,8 @@ const startRecording = async () => { audioChunks, isRecording: true, countdown: 0, - recordingStartTime: Date.now() + recordingStartTime: Date.now(), + mimeType: mediaRecorder.mimeType }); setupWaveformVisualization(analyser); @@ -328,41 +370,109 @@ const stopRecording = () => { * Plays back the recorded audio */ const playRecording = () => { - const { audioChunks } = stateManager.getState(); - const audioBlob = new Blob(audioChunks, { type: 'audio/webm' }); + const { audioChunks, mimeType } = stateManager.getState(); + + // Use the detected MIME type or fallback to a generic audio type + const blobType = mimeType || 'audio/webm'; + const audioBlob = new Blob(audioChunks, { type: blobType }); const audioUrl = URL.createObjectURL(audioBlob); const audio = new Audio(audioUrl); stateManager.setState({ isPlaying: true }); - audio.play(); + // Add error handling before playing + const playPromise = audio.play(); + + if (playPromise !== undefined) { + playPromise + .then(() => { + console.log('Audio playback started successfully'); + }) + .catch(error => { + console.error('Error playing audio:', error); + URL.revokeObjectURL(audioUrl); + stateManager.setState({ + isPlaying: false, + lastError: `Playback error: ${error.message || 'Could not play recording in this browser'}` + }); + + // Try alternative playback method for Safari + if (error.name === 'NotSupportedError') { + tryAlternativePlayback(audioBlob); + } + }); + } audio.onended = () => { URL.revokeObjectURL(audioUrl); stateManager.setState({ isPlaying: false }); }; - - audio.onerror = () => { - URL.revokeObjectURL(audioUrl); +}; + +/** + * Attempts alternative playback methods for Safari + * @param {Blob} audioBlob - The audio blob to play + */ +const tryAlternativePlayback = async (audioBlob) => { + try { + // Create a new audio context + const audioContext = new (window.AudioContext || window.webkitAudioContext)(); + + // Convert blob to array buffer + const arrayBuffer = await audioBlob.arrayBuffer(); + + // Decode the audio data + const audioBuffer = await audioContext.decodeAudioData(arrayBuffer); + + // Create a buffer source + const source = audioContext.createBufferSource(); + source.buffer = audioBuffer; + source.connect(audioContext.destination); + + // Play the audio + source.start(0); + + stateManager.setState({ isPlaying: true }); + + // Handle playback completion + source.onended = () => { + stateManager.setState({ isPlaying: false }); + }; + + console.log('Using alternative playback method for Safari'); + } catch (error) { + console.error('Alternative playback failed:', error); stateManager.setState({ isPlaying: false, - lastError: 'Error playing back the recording' + lastError: 'Could not play recording in this browser. Try saving and playing externally.' }); - }; + } }; /** - * Saves the recording as an MP3 file + * Saves the recording as an audio file */ const saveRecording = () => { - const { audioChunks } = stateManager.getState(); - const audioBlob = new Blob(audioChunks, { type: 'audio/webm' }); + const { audioChunks, mimeType } = stateManager.getState(); + + // Use the detected MIME type or fallback to a generic audio type + const blobType = mimeType || 'audio/webm'; + const audioBlob = new Blob(audioChunks, { type: blobType }); const audioUrl = URL.createObjectURL(audioBlob); + // Determine file extension based on MIME type + let fileExtension = 'webm'; + if (mimeType) { + if (mimeType.includes('mp4')) fileExtension = 'mp4'; + else if (mimeType.includes('mp3') || mimeType.includes('mpeg')) fileExtension = 'mp3'; + else if (mimeType.includes('ogg')) fileExtension = 'ogg'; + else if (mimeType.includes('wav')) fileExtension = 'wav'; + } + const a = document.createElement('a'); a.href = audioUrl; - a.download = `voice-memo-${new Date().toISOString()}.webm`; + a.download = `voice-memo-${new Date().toISOString()}.${fileExtension}`; document.body.appendChild(a); a.click(); document.body.removeChild(a); |