From 648c126b070fc83ddb66b0353d03c4ad31b87551 Mon Sep 17 00:00:00 2001 From: ilotterytea Date: Sun, 15 Jun 2025 16:14:16 +0400 Subject: feat: audio recording --- public/static/img/icons/pause.png | Bin 0 -> 721 bytes public/static/img/icons/play.png | Bin 0 -> 717 bytes public/static/img/icons/repeat.png | Bin 0 -> 750 bytes public/static/scripts/audiorecorder.js | 210 +++++++++++++++++++++++++++++++++ public/static/style.css | 11 +- 5 files changed, 220 insertions(+), 1 deletion(-) create mode 100755 public/static/img/icons/pause.png create mode 100755 public/static/img/icons/play.png create mode 100755 public/static/img/icons/repeat.png create mode 100644 public/static/scripts/audiorecorder.js (limited to 'public/static') diff --git a/public/static/img/icons/pause.png b/public/static/img/icons/pause.png new file mode 100755 index 0000000..ec61099 Binary files /dev/null and b/public/static/img/icons/pause.png differ diff --git a/public/static/img/icons/play.png b/public/static/img/icons/play.png new file mode 100755 index 0000000..f8c8ec6 Binary files /dev/null and b/public/static/img/icons/play.png differ diff --git a/public/static/img/icons/repeat.png b/public/static/img/icons/repeat.png new file mode 100755 index 0000000..406ec33 Binary files /dev/null and b/public/static/img/icons/repeat.png differ diff --git a/public/static/scripts/audiorecorder.js b/public/static/scripts/audiorecorder.js new file mode 100644 index 0000000..b12a2af --- /dev/null +++ b/public/static/scripts/audiorecorder.js @@ -0,0 +1,210 @@ +// getting form upload tabs +const tabs = document.getElementById('form-upload-tabs'); + +// creating an audio button +tabs.innerHTML += ` +
+ +
+`; + +function generateAudioRecorderHTML() { + return ` +
+ + +
+ +
+
+`; +} + +const form = document.getElementById('form-record-upload'); +form.innerHTML = generateAudioRecorderHTML(); + +const startBtn = document.getElementById('record-start-btn'); +const stopBtn = document.getElementById('record-stop-btn'); +const playBtn = document.getElementById('record-play-btn'); +const pauseBtn = document.getElementById('record-pause-btn'); + +const player = document.getElementById('record-player'); +const slider = document.getElementById('record-slider'); +const currentSecond = document.getElementById('record-currentsecond'); +const duration = document.getElementById('record-duration'); + +let playback = null; + +// record functions +let mediaRecorder; +let stream; +let audioChunks = []; +let audioLength = 0; + +async function startRecording() { + // TODO: very poor sound quality + stream = await navigator.mediaDevices.getUserMedia({ audio: { sampleRate: 44100, channelCount: 1 }}); + const options = { mimeType: 'audio/ogg; codecs=opus', audioBitsPerSecond: 128000 }; + if (!MediaRecorder.isTypeSupported(options.mimeType)) { + alert("Your browser doesn't support audio/ogg recording."); + return; + } + + audioLength = 0; + + mediaRecorder = new MediaRecorder(stream, options); + audioChunks = []; + + mediaRecorder.ondataavailable = event => { + if (event.data.size > 0) { + audioChunks.push(event.data); + audioLength++; + } + }; + + mediaRecorder.start(); + startBtn.style.display = 'none'; + stopBtn.style.display = 'block'; + player.style.display = 'none'; + + setFormDetailsVisiblity(false); +} + +function stopRecording() { + mediaRecorder.stop(); + startBtn.style.display = 'block'; + stopBtn.style.display = 'none'; + + if (playback) { + form.removeChild(playback); + playback = null; + } + + mediaRecorder.onstop = () => { + const blob = new Blob(audioChunks, { type: 'audio/ogg; codecs=opus' }); + const file = new File([blob], 'recording.ogg', { type: 'audio/ogg; codecs=opus' }); + + const url = URL.createObjectURL(file); + + playback = document.createElement('audio'); + playback.src = url; + + playback.addEventListener('loadedmetadata', () => { + const d = playback.duration; + slider.max = d; + slider.value = 0; + currentSecond.textContent = formatTimestamp(slider.getAttribute('value')); + duration.textContent = formatTimestamp(d); + }); + + playback.addEventListener('timeupdate', () => { + currentSecond.textContent = formatTimestamp(playback.currentTime); + slider.value = playback.currentTime; + }); + + playback.addEventListener('ended', () => { + playBtn.style.display = 'flex'; + pauseBtn.style.display = 'none'; + currentSecond.textContent = formatTimestamp(0); + slider.value = 0; + }); + + form.appendChild(playback); + + playBtn.style.display = 'flex'; + pauseBtn.style.display = 'none'; + + startBtn.style.display = 'none'; + stopBtn.style.display = 'none'; + player.style.display = 'flex'; + + setFormDetailsVisiblity(true); + + stream.getAudioTracks().forEach(track => { + track.stop(); + }); + + stream = null; + + // attaching the file + if (formFile) { + const dt = new DataTransfer(); + dt.items.add(file); + formFile.files = dt.files; + } + + tabs.setAttribute('disabled', 'true'); + }; +} + +function removeRecording() { + startBtn.style.display = 'block'; + player.style.display = 'none'; + form.removeChild(playback); + formFile.value = ''; + setFormDetailsVisiblity(false); + + tabs.removeAttribute('disabled'); +} + +function playRecord() { + if (playback) playback.play(); + + playBtn.style.display = 'none'; + pauseBtn.style.display = 'flex'; +} + +function pauseRecord() { + if (playback) playback.pause(); + + playBtn.style.display = 'flex'; + pauseBtn.style.display = 'none'; +} + +function rewindRecord() { + currentSecond.textContent = formatTimestamp(slider.value); + if (playback) { + playback.currentTime = slider.value; + playRecord(); + } +} + +function formatTimestamp(timestamp) { + const m = Math.floor(timestamp / 60); + const s = Math.ceil(timestamp % 60); + + return (m < 10 ? '0' : '') + m + ':' + (s < 10 ? '0' : '') + s +} + +// form +document.getElementById('form-upload').addEventListener('submit', () => { + player.style.display = 'none'; + startBtn.style.display = 'block'; +}); \ No newline at end of file diff --git a/public/static/style.css b/public/static/style.css index e6dd5fd..63e5409 100644 --- a/public/static/style.css +++ b/public/static/style.css @@ -136,7 +136,8 @@ button[type=submit]:hover { cursor: pointer; } -#form-upload-wrapper>button { +#form-upload-wrapper>button, +.big-upload-button { background: var(--background-2); padding: 32px 0; color: var(--foreground); @@ -278,6 +279,10 @@ button[type=submit]:hover { justify-content: end; } +.justify-between { + justify-content: space-between; +} + .align-center { align-items: center; } @@ -290,6 +295,10 @@ button[type=submit]:hover { padding: 4px; } +.pad-8 { + padding: 8px; +} + .gap-16 { gap: 16px; } -- cgit v1.2.3