aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorpacien2024-01-05 00:12:47 +0100
committerpacien2024-01-05 00:12:47 +0100
commitc5cc11f56f3afdb8508dff32f7f76b417fd2ba9a (patch)
tree59f1483bf9607d627066ba5abe1417d28b306de1
parent82f193b00ec364c591553a3815834d273d99d9c1 (diff)
downloadechoclip-c5cc11f56f3afdb8508dff32f7f76b417fd2ba9a.tar.gz
app prototype
Inspired by the MDN example for playing with mediaDevices.
-rw-r--r--app.css9
-rw-r--r--app.js65
-rw-r--r--index.html41
3 files changed, 114 insertions, 1 deletions
diff --git a/app.css b/app.css
new file mode 100644
index 0000000..c32fa83
--- /dev/null
+++ b/app.css
@@ -0,0 +1,9 @@
1/*
2 * EchoClip, a web tool to record and play back audio clips.
3 * Copyright 2024 Pacien TRAN-GIRARD
4 * SPDX-License-Identifier: EUPL-1.2
5 */
6
7#error {
8 color: red;
9} \ No newline at end of file
diff --git a/app.js b/app.js
new file mode 100644
index 0000000..762a72f
--- /dev/null
+++ b/app.js
@@ -0,0 +1,65 @@
1/*
2 * EchoClip, a web tool to record and play back audio clips.
3 * Copyright 2024 Pacien TRAN-GIRARD
4 * SPDX-License-Identifier: EUPL-1.2
5 */
6
7const errorContainer = document.querySelector("#error");
8const recordBtn = document.querySelector("#record");
9const autoplayCheckbox = document.querySelector("#autoplay");
10const clearBtn = document.querySelector("#clear");
11const clips = document.querySelector("#clips");
12
13function wrapElement(wrapper, child) {
14 const wrapperElement = document.createElement(wrapper);
15 wrapperElement.append(child);
16 return wrapperElement;
17}
18
19// TODO: fancy player element with spectrogram and spectrum analyser?
20function audioElementForBlob(blob) {
21 const audioElement = document.createElement("audio");
22 audioElement.src = window.URL.createObjectURL(blob);
23 audioElement.setAttribute("controls", "");
24 return audioElement;
25}
26
27function onGetDeviceSuccess(stream) {
28 const mediaRecorder = new MediaRecorder(stream);
29 let recordingChunks = [];
30
31 mediaRecorder.addEventListener("dataavailable", event => {
32 recordingChunks.push(event.data);
33 });
34
35 mediaRecorder.addEventListener("stop", _event => {
36 const blob = new Blob(recordingChunks, { type: mediaRecorder.mimeType });
37 recordingChunks = [];
38 const audioElement = audioElementForBlob(blob);
39 // TODO: autoplay audioElement if checkbox enabled
40 // TODO: record blob and list to local persistent storage
41 // TODO: "clear all" button to clear all clips
42 // TODO: buttons to clear individual clips
43 // TODO: keyboard shortcut to play clips for the ten last indexes
44 clips.prepend(wrapElement("li", audioElement));
45 });
46
47 // TODO: handle "space" key hold the same as holding the "record" button
48 recordBtn.addEventListener("mousedown", _event => {
49 mediaRecorder.start();
50 });
51
52 recordBtn.addEventListener("mouseup", _event => {
53 mediaRecorder.stop();
54 });
55}
56
57function onGetDeviceError(error) {
58 console.log(error);
59 errorContainer.innerHTML = error;
60}
61
62navigator
63 .mediaDevices
64 .getUserMedia({ audio: true })
65 .then(onGetDeviceSuccess, onGetDeviceError);
diff --git a/index.html b/index.html
index 71b17c4..751187d 100644
--- a/index.html
+++ b/index.html
@@ -16,6 +16,8 @@
16 <link rel="mask-icon" href="favicon.svg" color="#000000"> 16 <link rel="mask-icon" href="favicon.svg" color="#000000">
17 <link rel="apple-touch-icon" href="favicon.svg"> 17 <link rel="apple-touch-icon" href="favicon.svg">
18 18
19 <link rel="stylesheet" href="app.css" type="text/css">
20
19 <title>EchoClip</title> 21 <title>EchoClip</title>
20</head> 22</head>
21 23
@@ -25,13 +27,50 @@
25 <p>A web tool to record and play back audio clips.</p> 27 <p>A web tool to record and play back audio clips.</p>
26 </header> 28 </header>
27 29
30 <section id="error">
31 <noscript>
32 This web application requires JavaScript support to be functional.
33 </noscript>
34 </section>
35
36 <section>
37 <fieldset>
38 <legend>Record a new clip</legend>
39
40 <button id="record">Hold (space) to record</button>
41
42 <input type="checkbox" id="autoplay" name="autoplay" checked>
43 <label for="autoplay">Autoplay</label>
44 </fieldset>
45 </section>
46
47 <section>
48 <fieldset>
49 <legend>Saved clips</legend>
50
51 <p>
52 Latest on top.
53 Hold a number key (1-9) for quick replay.
54 </p>
55
56 <button id="clear">Clear all</button>
57
58 <ol id="clips">
59 </ol>
60 </fieldset>
61 </section>
62
28 <footer> 63 <footer>
64 <p>Speak, sing, practice, have fun! Nothing is sent out to the network.</p>
65
29 <ul> 66 <ul>
30 <li><a href="https://pacien.org">&copy; 2024 Pacien</a></li> 67 <li><a href="https://pacien.org">&copy; 2024 Pacien</a></li>
31 <li><a href="https://cgit.pacien.net/echoclip">Source code (EUPL)</a></li> 68 <li><a href="https://cgit.pacien.net/echoclip">Source code (EUPL)</a></li>
32 <li><a href="https://www.paypal.com/paypalme/pacien/10">Donate</a></li> 69 <li><a href="https://www.paypal.com/paypalme/pacien/10">Donate</a></li>
33 </ul> 70 </ul>
34 </footer> 71 </footer>
72
73 <script src="app.js"></script>
35</body> 74</body>
36 75
37</html> 76</html> \ No newline at end of file