/* * EchoClip, a web tool to record and play back audio clips. * Copyright 2024 Pacien TRAN-GIRARD * SPDX-License-Identifier: EUPL-1.2 */ const errorContainer = document.querySelector("#error"); const recordingIndicator = document.querySelector("#recording-indicator"); const recordBtn = document.querySelector("#record"); const autoplayCheckbox = document.querySelector("#autoplay"); const clearBtn = document.querySelector("#clear"); const clips = document.querySelector("#clips"); clearBtn.addEventListener("click", _event => { clips.textContent = ""; }); function onNthClip(index, action) { if (index < 0 || clips.children.length <= index - 1) return; const clip = clips.children[index - 1].querySelector("audio"); action(clip); } document.addEventListener("keydown", event => { if (event.key.match(/[1-9]/)) onNthClip(parseInt(event.key), clip => playFromStart(clip)); }); document.addEventListener("keyup", event => { if (event.key.match(/[1-9]/)) onNthClip(parseInt(event.key), clip => clip.pause()); }); function playFromStart(player) { if (!player.paused) return; player.currentTime = 0; player.play(); } function stopOtherPlayersExcept(player) { document.querySelectorAll("audio").forEach(other => { if (other != player) other.pause(); }); } function makeExclusive(player) { player.addEventListener("play", _event => stopOtherPlayersExcept(player)); return player; } function wrapElement(wrapper, child) { const wrapperElement = document.createElement(wrapper); wrapperElement.append(child); return wrapperElement; } // TODO: fancy player element with spectrogram and spectrum analyser? function audioElementForBlob(blob) { const audioElement = document.createElement("audio"); audioElement.src = window.URL.createObjectURL(blob); audioElement.setAttribute("controls", ""); return audioElement; } function onGetDeviceSuccess(stream) { const mediaRecorder = new MediaRecorder(stream); let recordingChunks = []; mediaRecorder.addEventListener("dataavailable", event => { recordingChunks.push(event.data); }); mediaRecorder.addEventListener("start", _event => { recordingIndicator.classList.add("active"); }); mediaRecorder.addEventListener("stop", _event => { recordingIndicator.classList.remove("active"); const blob = new Blob(recordingChunks, { type: mediaRecorder.mimeType }); recordingChunks = []; const audioElement = makeExclusive(audioElementForBlob(blob)); // TODO: record blob and list to local persistent storage // TODO: buttons to clear individual clips clips.prepend(wrapElement("li", audioElement)); if (autoplayCheckbox.checked) audioElement.play(); }); recordBtn.addEventListener("mousedown", _event => { mediaRecorder.start(); }); recordBtn.addEventListener("mouseup", _event => { mediaRecorder.stop(); }); document.addEventListener("keydown", event => { if (event.key == " ") { event.preventDefault(); // prevent scroll if (mediaRecorder.state == "inactive") mediaRecorder.start(); } }); document.addEventListener("keyup", event => { if (event.key == " ") { event.preventDefault(); // prevent scroll mediaRecorder.stop(); } }); } function onGetDeviceError(error) { console.log(error); errorContainer.textContent = error; } navigator .mediaDevices .getUserMedia({ audio: true }) .then(onGetDeviceSuccess, onGetDeviceError);