Spaces:
Running
Running
<html> | |
<head> | |
<title>Audio Recorder with Silence Detection</title> | |
<style> | |
body { | |
font-family: system-ui, sans-serif; | |
max-width: 800px; | |
margin: 0 auto; | |
padding: 20px; | |
} | |
.controls { | |
margin: 20px 0; | |
} | |
button { | |
padding: 10px 20px; | |
margin: 5px; | |
cursor: pointer; | |
} | |
#recordingsList { | |
margin-top: 20px; | |
border: 1px solid #ccc; | |
padding: 10px; | |
min-height: 100px; | |
} | |
.recording-item { | |
display: flex; | |
justify-content: space-between; | |
align-items: center; | |
padding: 10px; | |
border-bottom: 1px solid #eee; | |
} | |
#timer { | |
font-size: 1.2em; | |
margin: 10px 0; | |
} | |
#volumeMeter { | |
width: 300px; | |
height: 20px; | |
border: 1px solid #ccc; | |
margin: 10px 0; | |
} | |
#volumeBar { | |
height: 100%; | |
width: 0%; | |
background-color: #4CAF50; | |
transition: width 0.1s; | |
} | |
</style> | |
</head> | |
<body> | |
<h1>Audio Recorder with Silence Detection</h1> | |
<div class="controls"> | |
<button id="startButton">Start Recording</button> | |
<button id="stopButton" disabled>Stop Recording</button> | |
</div> | |
<div id="timer">00:00</div> | |
<div id="volumeMeter"> | |
<div id="volumeBar"></div> | |
</div> | |
<div id="recordingsList"></div> | |
<script> | |
let mediaRecorder; | |
let audioChunks = []; | |
let recordings = []; | |
let startTime; | |
let timerInterval; | |
let silenceTimeout; | |
let audioContext; | |
let analyser; | |
let isRecording = false; | |
let totalDuration = 0; | |
const SILENCE_THRESHOLD = -50; // dB | |
const SILENCE_DURATION = 1000; // 1 second | |
document.getElementById('startButton').addEventListener('click', startRecording); | |
document.getElementById('stopButton').addEventListener('click', stopRecording); | |
async function startRecording() { | |
try { | |
const stream = await navigator.mediaDevices.getUserMedia({ audio: true }); | |
setupAudioAnalysis(stream); | |
mediaRecorder = new MediaRecorder(stream); | |
audioChunks = []; | |
isRecording = true; | |
startTime = Date.now(); | |
mediaRecorder.ondataavailable = event => { | |
audioChunks.push(event.data); | |
}; | |
mediaRecorder.onstop = () => { | |
if (audioChunks.length > 0) { | |
saveRecording(); | |
} | |
}; | |
mediaRecorder.start(); | |
updateUI(true); | |
startTimer(); | |
} catch (err) { | |
console.error('Error:', err); | |
alert('Error accessing microphone'); | |
} | |
} | |
function setupAudioAnalysis(stream) { | |
audioContext = new AudioContext(); | |
analyser = audioContext.createAnalyser(); | |
const source = audioContext.createMediaStreamSource(stream); | |
source.connect(analyser); | |
analyser.fftSize = 2048; | |
const bufferLength = analyser.frequencyBinCount; | |
const dataArray = new Float32Array(bufferLength); | |
function checkAudioLevel() { | |
if (!isRecording) return; | |
analyser.getFloatTimeDomainData(dataArray); | |
let sum = 0; | |
for (let i = 0; i < bufferLength; i++) { | |
sum += Math.abs(dataArray[i]); | |
} | |
const average = sum / bufferLength; | |
const db = 20 * Math.log10(average); | |
// Update volume meter | |
const volumeBar = document.getElementById('volumeBar'); | |
const normalizedVolume = Math.max(0, (db + 90) / 90) * 100; | |
volumeBar.style.width = `${normalizedVolume}%`; | |
if (db < SILENCE_THRESHOLD) { | |
if (!silenceTimeout) { | |
silenceTimeout = setTimeout(() => { | |
if (mediaRecorder.state === 'recording') { | |
mediaRecorder.stop(); | |
startNewRecording(); | |
} | |
}, SILENCE_DURATION); | |
} | |
} else { | |
if (silenceTimeout) { | |
clearTimeout(silenceTimeout); | |
silenceTimeout = null; | |
} | |
if (mediaRecorder.state === 'inactive' && isRecording) { | |
startNewRecording(); | |
} | |
} | |
requestAnimationFrame(checkAudioLevel); | |
} | |
checkAudioLevel(); | |
} | |
function startNewRecording() { | |
if (isRecording) { | |
audioChunks = []; | |
mediaRecorder.start(); | |
} | |
} | |
function stopRecording() { | |
isRecording = false; | |
if (mediaRecorder.state === 'recording') { | |
mediaRecorder.stop(); | |
} | |
clearInterval(timerInterval); | |
updateUI(false); | |
if (audioContext) { | |
audioContext.close(); | |
} | |
if (silenceTimeout) { | |
clearTimeout(silenceTimeout); | |
} | |
} | |
function saveRecording() { | |
const blob = new Blob(audioChunks, { type: 'audio/webm' }); | |
const reader = new FileReader(); | |
reader.onload = function() { | |
const base64String = reader.result.split(',')[1]; | |
const timestamp = new Date().toISOString().replace(/[:.]/g, '-'); | |
const filename = `recording-${timestamp}.webm`; | |
recordings.push({ | |
filename, | |
base64: base64String, | |
duration: (Date.now() - startTime) / 1000 | |
}); | |
updateRecordingsList(); | |
}; | |
reader.readAsDataURL(blob); | |
} | |
function updateRecordingsList() { | |
const list = document.getElementById('recordingsList'); | |
list.innerHTML = ''; | |
recordings.forEach((recording, index) => { | |
const item = document.createElement('div'); | |
item.className = 'recording-item'; | |
const info = document.createElement('span'); | |
info.textContent = `${recording.filename} (${recording.duration.toFixed(1)}s)`; | |
const downloadBtn = document.createElement('button'); | |
downloadBtn.textContent = 'Download'; | |
downloadBtn.onclick = () => downloadRecording(recording); | |
item.appendChild(info); | |
item.appendChild(downloadBtn); | |
list.appendChild(item); | |
}); | |
} | |
function downloadRecording(recording) { | |
const link = document.createElement('a'); | |
link.href = `data:audio/webm;base64,${recording.base64}`; | |
link.download = recording.filename; | |
document.body.appendChild(link); | |
link.click(); | |
document.body.removeChild(link); | |
} | |
function startTimer() { | |
const timerElement = document.getElementById('timer'); | |
const startTime = Date.now(); | |
timerInterval = setInterval(() => { | |
const elapsed = Date.now() - startTime; | |
const seconds = Math.floor(elapsed / 1000); | |
const minutes = Math.floor(seconds / 60); | |
const remainingSeconds = seconds % 60; | |
timerElement.textContent = `${String(minutes).padStart(2, '0')}:${String(remainingSeconds).padStart(2, '0')}`; | |
}, 1000); | |
} | |
function updateUI(recording) { | |
document.getElementById('startButton').disabled = recording; | |
document.getElementById('stopButton').disabled = !recording; | |
} | |
</script> | |
</body> | |
</html> |