summaryrefslogtreecommitdiff
path: root/public
diff options
context:
space:
mode:
authorilotterytea <iltsu@alright.party>2025-06-15 16:14:16 +0400
committerilotterytea <iltsu@alright.party>2025-06-15 16:14:16 +0400
commit648c126b070fc83ddb66b0353d03c4ad31b87551 (patch)
tree3a83989f5eac1ef21e58d06fb293e14b2be636b4 /public
parentd152925126af0d311746518a20f6a409869ae02d (diff)
feat: audio recording
Diffstat (limited to 'public')
-rw-r--r--public/index.php12
-rwxr-xr-xpublic/static/img/icons/pause.pngbin0 -> 721 bytes
-rwxr-xr-xpublic/static/img/icons/play.pngbin0 -> 717 bytes
-rwxr-xr-xpublic/static/img/icons/repeat.pngbin0 -> 750 bytes
-rw-r--r--public/static/scripts/audiorecorder.js210
-rw-r--r--public/static/style.css11
6 files changed, 230 insertions, 3 deletions
diff --git a/public/index.php b/public/index.php
index 9e6ccf1..c3eea01 100644
--- a/public/index.php
+++ b/public/index.php
@@ -233,7 +233,7 @@ $privacy_exists = is_file($_SERVER['DOCUMENT_ROOT'] . '/static/PRIVACY.txt');
</section>
<section class="box column">
- <div class="tabs">
+ <div class="tabs" id="form-upload-tabs">
<div class="form-upload-tab tab" id="form-tab-file">
<button onclick="showUploadType('file')" class="transparent">
<p>File Upload</p>
@@ -288,6 +288,8 @@ $privacy_exists = is_file($_SERVER['DOCUMENT_ROOT'] . '/static/PRIVACY.txt');
<textarea name="paste" placeholder="Enter your text here..."></textarea>
</div>
+ <div class="column" id="form-record-upload" style="display: none;"></div>
+
<table class="vertical left" id="form-upload-options">
<tr>
<th>Title:</th>
@@ -336,6 +338,7 @@ $privacy_exists = is_file($_SERVER['DOCUMENT_ROOT'] . '/static/PRIVACY.txt');
}
</script>
<?php elseif (!$file): ?>
+ <script src="/static/scripts/audiorecorder.js"></script>
<script>
const formDetails = document.getElementById('form-upload-options');
@@ -591,8 +594,13 @@ $privacy_exists = is_file($_SERVER['DOCUMENT_ROOT'] . '/static/PRIVACY.txt');
}
function showUploadType(type) {
- document.getElementById('form-upload-wrapper').style.display = type == 'text' ? 'none' : 'flex';
+ if (document.getElementById('form-upload-tabs').hasAttribute('disabled')) {
+ return;
+ }
+
+ document.getElementById('form-upload-wrapper').style.display = type == 'file' ? 'flex' : 'none';
document.getElementById('form-text-upload').style.display = type == 'text' ? 'flex' : 'none';
+ document.getElementById('form-record-upload').style.display = type === 'audio' ? 'flex' : 'none';
const tabs = document.querySelectorAll('.form-upload-tab');
diff --git a/public/static/img/icons/pause.png b/public/static/img/icons/pause.png
new file mode 100755
index 0000000..ec61099
--- /dev/null
+++ b/public/static/img/icons/pause.png
Binary files differ
diff --git a/public/static/img/icons/play.png b/public/static/img/icons/play.png
new file mode 100755
index 0000000..f8c8ec6
--- /dev/null
+++ b/public/static/img/icons/play.png
Binary files differ
diff --git a/public/static/img/icons/repeat.png b/public/static/img/icons/repeat.png
new file mode 100755
index 0000000..406ec33
--- /dev/null
+++ b/public/static/img/icons/repeat.png
Binary files 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 += `
+<div class="form-upload-tab tab disabled" id="form-tab-audio">
+ <button onclick="showUploadType('audio')" class="transparent">
+ <p>Record</p>
+ </button>
+</div>
+`;
+
+function generateAudioRecorderHTML() {
+ return `
+ <div class="column gap-8">
+ <button type="button" class="big-upload-button" onclick="startRecording()" id="record-start-btn">
+ <h1>Click here to start audio recording</h1>
+ </button>
+ <button type="button" class="big-upload-button" onclick="stopRecording()" id="record-stop-btn" style="display: none">
+ <h1>Recording...</h1>
+ <p>Click here to stop audio recording</p>
+ </button>
+ <div class="row align-center justify-center">
+ <div class="box row align-center gap-8 pad-8" id="record-player" style="display:none">
+ <button type="button" onclick="removeRecording()">
+ <img src="/static/img/icons/cross.png" alt="Delete">
+ </button>
+ <button type="button" onclick="startRecording()">
+ <img src="/static/img/icons/repeat.png" alt="Retry">
+ </button>
+ <button type="button" onclick="playRecord()" id="record-play-btn">
+ <img src="/static/img/icons/play.png" alt="Play">
+ </button>
+ <button type="button" onclick="pauseRecord()" id="record-pause-btn" style="display: none;">
+ <img src="/static/img/icons/pause.png" alt="Pause">
+ </button>
+ <div class="column gap-8">
+ <input type="range" min="0" max="100" value="0" step="0.01" class="audio-slider" id="record-slider" oninput="rewindRecord()">
+ <div class="row justify-between">
+ <p id="record-currentsecond"></p>
+ <p id="record-duration"></p>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+`;
+}
+
+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;
}