UNPKG

react-native-audio-recorder-player

Version:
365 lines (344 loc) 11.4 kB
"use strict"; export * from "./AudioRecorderPlayer.nitro.js"; class AudioRecorderPlayerWebImpl { mediaRecorder = null; audioContext = null; audio = null; recordedChunks = []; recordingStartTime = 0; actualRecordedDuration = 0; recordBackListener = null; playBackListener = null; playbackEndListener = null; recordingInterval = null; playbackInterval = null; subscriptionDuration = 10; // Default 10ms for faster updates currentVolume = 1.0; recordingUrl = null; mediaStream = null; // Recording methods async startRecorder(_uri, audioSets, meteringEnabled) { try { // Get user media const stream = await navigator.mediaDevices.getUserMedia({ audio: { channelCount: audioSets?.AudioChannels || 1, sampleRate: audioSets?.AudioSamplingRate || 44100, echoCancellation: true, noiseSuppression: true } }); // Setup audio context for metering if (meteringEnabled && !this.audioContext) { this.audioContext = new AudioContext(); } // Create MediaRecorder const options = { mimeType: this.getMimeType(audioSets), audioBitsPerSecond: audioSets?.AudioEncodingBitRate || 128000 }; this.mediaRecorder = new MediaRecorder(stream, options); this.recordedChunks = []; this.mediaRecorder.ondataavailable = event => { if (event.data.size > 0) { this.recordedChunks.push(event.data); } }; // Store stream reference for cleanup this.mediaStream = stream; // Start recording with timeslice to get more accurate chunks this.mediaRecorder.start(100); // Capture data every 100ms this.recordingStartTime = Date.now(); // Start progress updates this.startRecordingProgress(meteringEnabled || false); // Return a placeholder until recording is stopped return 'recording_in_progress'; } catch (error) { console.error('Failed to start recording:', error); throw new Error(`Failed to start recording: ${error}`); } } async pauseRecorder() { if (this.mediaRecorder && this.mediaRecorder.state === 'recording') { this.mediaRecorder.pause(); this.stopRecordingProgress(); return 'paused'; } throw new Error('No active recording to pause'); } async resumeRecorder() { if (this.mediaRecorder && this.mediaRecorder.state === 'paused') { this.mediaRecorder.resume(); this.startRecordingProgress(false); return 'resumed'; } throw new Error('No paused recording to resume'); } async stopRecorder() { if (this.mediaRecorder) { return new Promise(resolve => { const mimeType = this.mediaRecorder.mimeType || 'audio/webm'; // Calculate actual duration before stopping this.actualRecordedDuration = Date.now() - this.recordingStartTime; this.mediaRecorder.onstop = () => { const blob = new Blob(this.recordedChunks, { type: mimeType }); const url = URL.createObjectURL(blob); this.recordingUrl = url; // Clean up the stream if (this.mediaStream) { this.mediaStream.getTracks().forEach(track => track.stop()); this.mediaStream = null; } // Clean up media recorder this.mediaRecorder = null; resolve(url); }; this.mediaRecorder.stop(); this.stopRecordingProgress(); }); } throw new Error('No active recording to stop'); } // Playback methods async startPlayer(uri, httpHeaders) { try { this.audio = new Audio(); this.audio.volume = this.currentVolume; if (uri) { // For remote URLs with headers, we might need to use fetch if (httpHeaders && Object.keys(httpHeaders).length > 0) { const response = await fetch(uri, { headers: httpHeaders }); const blob = await response.blob(); const url = URL.createObjectURL(blob); this.audio.src = url; } else { this.audio.src = uri; } } else if (this.recordingUrl) { this.audio.src = this.recordingUrl; } else { throw new Error('No audio URI provided'); } // Add ended event listener this.audio.onended = () => { this.stopPlaybackProgress(); const finalDuration = this.actualRecordedDuration > 0 ? this.actualRecordedDuration : this.audio.duration * 1000; if (this.playBackListener) { // Send final update with exact duration this.playBackListener({ isMuted: this.audio.muted, duration: finalDuration, currentPosition: finalDuration }); } if (this.playbackEndListener) { // Send playback end event this.playbackEndListener({ duration: finalDuration, currentPosition: finalDuration }); } }; await this.audio.play(); this.startPlaybackProgress(); return uri || this.recordingUrl || ''; } catch (error) { console.error('Failed to start playback:', error); throw new Error(`Failed to start playback: ${error}`); } } async stopPlayer() { if (this.audio) { this.audio.pause(); this.audio.currentTime = 0; this.stopPlaybackProgress(); this.audio = null; return 'stopped'; } throw new Error('No active playback to stop'); } async pausePlayer() { if (this.audio && !this.audio.paused) { this.audio.pause(); this.stopPlaybackProgress(); return 'paused'; } throw new Error('No active playback to pause'); } async resumePlayer() { if (this.audio && this.audio.paused) { await this.audio.play(); this.startPlaybackProgress(); return 'resumed'; } throw new Error('No paused playback to resume'); } async seekToPlayer(time) { if (this.audio) { this.audio.currentTime = time / 1000; // Convert ms to seconds return `${time}`; } throw new Error('No active playback to seek'); } async setVolume(volume) { this.currentVolume = Math.max(0, Math.min(1, volume)); if (this.audio) { this.audio.volume = this.currentVolume; } return `${this.currentVolume}`; } async setPlaybackSpeed(playbackSpeed) { if (this.audio) { this.audio.playbackRate = playbackSpeed; return `${playbackSpeed}`; } throw new Error('No active playback to set speed'); } // Subscription setSubscriptionDuration(sec) { // For web, use milliseconds directly for faster updates this.subscriptionDuration = Math.max(10, sec * 1000); // Convert to ms, minimum 10ms } // Listeners addRecordBackListener(callback) { this.recordBackListener = callback; } removeRecordBackListener() { this.recordBackListener = null; } addPlayBackListener(callback) { this.playBackListener = callback; } removePlayBackListener() { this.playBackListener = null; } addPlaybackEndListener(callback) { this.playbackEndListener = callback; } removePlaybackEndListener() { this.playbackEndListener = null; } // Utility methods mmss(secs) { const minutes = Math.floor(secs / 60); const seconds = Math.floor(secs % 60); return `${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`; } mmssss(milisecs) { const totalSeconds = Math.floor(milisecs / 1000); const minutes = Math.floor(totalSeconds / 60); const seconds = totalSeconds % 60; const milliseconds = Math.floor(milisecs % 1000 / 10); return `${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}:${milliseconds.toString().padStart(2, '0')}`; } // Private helper methods getMimeType(_audioSets) { // Try to use webm/opus for best browser support if (MediaRecorder.isTypeSupported('audio/webm;codecs=opus')) { return 'audio/webm;codecs=opus'; } if (MediaRecorder.isTypeSupported('audio/webm')) { return 'audio/webm'; } if (MediaRecorder.isTypeSupported('audio/mp4')) { return 'audio/mp4'; } return 'audio/wav'; } startRecordingProgress(meteringEnabled) { if (this.recordingInterval) { clearInterval(this.recordingInterval); } const startTime = performance.now(); this.recordingInterval = setInterval(() => { if (this.recordBackListener && this.mediaRecorder?.state === 'recording') { const currentPosition = performance.now() - startTime; const recordSecs = currentPosition / 1000; this.recordBackListener({ isRecording: true, currentPosition, currentMetering: meteringEnabled ? this.getCurrentMetering() : undefined, recordSecs }); } }, this.subscriptionDuration); } stopRecordingProgress() { if (this.recordingInterval) { clearInterval(this.recordingInterval); this.recordingInterval = null; } } startPlaybackProgress() { if (this.playbackInterval) { clearInterval(this.playbackInterval); } this.playbackInterval = setInterval(() => { if (this.playBackListener && this.audio && !this.audio.paused) { // Use actual recorded duration if available and audio duration is not accurate const duration = this.actualRecordedDuration > 0 ? this.actualRecordedDuration : this.audio.duration * 1000; const currentPosition = this.audio.currentTime * 1000; // Ensure we don't exceed duration const safePosition = Math.min(currentPosition, duration); this.playBackListener({ isMuted: this.audio.muted, duration: duration, currentPosition: safePosition }); // Check if playback has ended if (this.audio.ended || currentPosition >= duration) { this.stopPlaybackProgress(); if (this.playBackListener) { // Send final update with 100% progress this.playBackListener({ isMuted: this.audio.muted, duration: duration, currentPosition: duration }); } } } }, this.subscriptionDuration); } stopPlaybackProgress() { if (this.playbackInterval) { clearInterval(this.playbackInterval); this.playbackInterval = null; } } getCurrentMetering() { // Simplified metering - in a real implementation, you'd analyze the audio stream return Math.random() * 40 - 40; // Random value between -40 and 0 dB } // Required by HybridObject interface but not used in web get name() { return 'AudioRecorderPlayer'; } equals(other) { return other === this; } get hashCode() { return 0; } toString() { return 'AudioRecorderPlayer (Web)'; } dispose() { this.stopRecorder().catch(() => {}); this.stopPlayer().catch(() => {}); this.removeRecordBackListener(); this.removePlayBackListener(); if (this.audioContext) { this.audioContext.close(); this.audioContext = null; } } } // Create singleton instance const AudioRecorderPlayer = new AudioRecorderPlayerWebImpl(); export default AudioRecorderPlayer; //# sourceMappingURL=index.web.js.map