1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
|
/*
* EchoClip, a web tool to record and play back audio clips.
* Copyright 2024 Euxane 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);
|