aboutsummaryrefslogtreecommitdiff
path: root/app.js
blob: ad5dcb0eb655b110bf71b666a14df93fffd7de80 (plain)
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
/*
 * 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");

function stopPlayer(player) {
  player.pause();
  player.currentTime = 0;
}

function stopOtherPlayersExcept(player) {
  document.querySelectorAll("audio").forEach(other => {
    if (other != player)
      stopPlayer(other);
  });
}

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: "clear all" button to clear all clips
    // TODO: buttons to clear individual clips
    // TODO: keyboard shortcut to play clips for the ten last indexes

    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);