Add audio level meter functionality and update UI elements
This commit is contained in:
+58
-1
@@ -2,7 +2,7 @@ const DB_NAME = 'faerro-kb-local';
|
||||
const STORE_NAME = 'recordings';
|
||||
const MAX_RECORDING_MS = 30 * 60 * 1000;
|
||||
const elements = Object.fromEntries([
|
||||
'record-button', 'record-title', 'record-help', 'record-time', 'record-meter-fill', 'capture-message',
|
||||
'record-button', 'record-title', 'record-help', 'record-time', 'record-meter-fill', 'audio-level', 'audio-level-fill', 'capture-message',
|
||||
'queue-list', 'queue-empty', 'queue-count', 'sync-button', 'archive-list', 'archive-empty',
|
||||
'network-state', 'storage-status',
|
||||
].map((id) => [id, document.getElementById(id)]));
|
||||
@@ -10,6 +10,8 @@ const elements = Object.fromEntries([
|
||||
let databasePromise;
|
||||
let mediaRecorder;
|
||||
let activeStream;
|
||||
let audioContext;
|
||||
let audioLevelFrame;
|
||||
let recordingChunks = [];
|
||||
let recordingStartedAt = 0;
|
||||
let recordingTimer;
|
||||
@@ -64,12 +66,62 @@ function preferredMimeType() {
|
||||
.find((type) => window.MediaRecorder?.isTypeSupported(type));
|
||||
}
|
||||
|
||||
function prepareAudioLevelMeter() {
|
||||
const AudioContextClass = window.AudioContext || window.webkitAudioContext;
|
||||
if (!AudioContextClass) return;
|
||||
|
||||
try {
|
||||
audioContext = new AudioContextClass();
|
||||
audioContext.resume().catch(() => {});
|
||||
} catch {
|
||||
audioContext = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
function startAudioLevelMeter(stream) {
|
||||
if (!audioContext) return;
|
||||
|
||||
try {
|
||||
const analyser = audioContext.createAnalyser();
|
||||
analyser.fftSize = 512;
|
||||
audioContext.createMediaStreamSource(stream).connect(analyser);
|
||||
const samples = new Uint8Array(analyser.fftSize);
|
||||
elements['audio-level'].hidden = false;
|
||||
|
||||
const updateLevel = () => {
|
||||
analyser.getByteTimeDomainData(samples);
|
||||
const sumSquares = samples.reduce((sum, sample) => sum + (sample - 128) ** 2, 0);
|
||||
const level = Math.min(100, Math.round(Math.sqrt(sumSquares / samples.length) / 32 * 100));
|
||||
elements['audio-level-fill'].style.width = `${level}%`;
|
||||
elements['audio-level'].setAttribute('aria-valuenow', level);
|
||||
audioLevelFrame = requestAnimationFrame(updateLevel);
|
||||
};
|
||||
audioLevelFrame = requestAnimationFrame(updateLevel);
|
||||
} catch {
|
||||
audioContext?.close().catch(() => {});
|
||||
audioContext = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
function stopAudioLevelMeter() {
|
||||
cancelAnimationFrame(audioLevelFrame);
|
||||
audioLevelFrame = undefined;
|
||||
if (audioContext) {
|
||||
audioContext.close().catch(() => {});
|
||||
audioContext = undefined;
|
||||
}
|
||||
elements['audio-level'].hidden = true;
|
||||
elements['audio-level-fill'].style.width = '0';
|
||||
elements['audio-level'].setAttribute('aria-valuenow', '0');
|
||||
}
|
||||
|
||||
async function startRecording() {
|
||||
if (!navigator.mediaDevices?.getUserMedia || !window.MediaRecorder) {
|
||||
setMessage('This browser cannot record audio. Open the app in current iOS Safari.', true);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
prepareAudioLevelMeter();
|
||||
activeStream = await navigator.mediaDevices.getUserMedia({ audio: true });
|
||||
const mimeType = preferredMimeType();
|
||||
mediaRecorder = new MediaRecorder(activeStream, mimeType ? { mimeType } : undefined);
|
||||
@@ -77,6 +129,7 @@ async function startRecording() {
|
||||
mediaRecorder.ondataavailable = (event) => { if (event.data.size) recordingChunks.push(event.data); };
|
||||
mediaRecorder.onstop = saveRecording;
|
||||
mediaRecorder.start(1000);
|
||||
startAudioLevelMeter(activeStream);
|
||||
recordingStartedAt = Date.now();
|
||||
elements['record-button'].classList.add('recording');
|
||||
elements['record-button'].setAttribute('aria-label', 'Stop recording');
|
||||
@@ -90,12 +143,16 @@ async function startRecording() {
|
||||
if (elapsed >= MAX_RECORDING_MS) stopRecording();
|
||||
}, 250);
|
||||
} catch (error) {
|
||||
stopAudioLevelMeter();
|
||||
activeStream?.getTracks().forEach((track) => track.stop());
|
||||
activeStream = undefined;
|
||||
setMessage(error.name === 'NotAllowedError' ? 'Microphone access was denied.' : 'Could not start microphone recording.', true);
|
||||
}
|
||||
}
|
||||
|
||||
function stopRecording() {
|
||||
if (mediaRecorder?.state === 'recording') mediaRecorder.stop();
|
||||
stopAudioLevelMeter();
|
||||
activeStream?.getTracks().forEach((track) => track.stop());
|
||||
activeStream = undefined;
|
||||
clearInterval(recordingTimer);
|
||||
|
||||
Reference in New Issue
Block a user