Skip to content

Commit

Permalink
2181: Styling recording page
Browse files Browse the repository at this point in the history
  • Loading branch information
yepzdk committed Aug 23, 2024
1 parent a904f1c commit 755aeb7
Show file tree
Hide file tree
Showing 6 changed files with 160 additions and 177 deletions.
270 changes: 122 additions & 148 deletions web/modules/custom/giv_din_stemme/js/giv_din_stemme.js
Original file line number Diff line number Diff line change
@@ -1,158 +1,132 @@
// Set up basic variables for app
const record = document.querySelector(".record");
const stop = document.querySelector(".stop");
const canvas = document.querySelector(".visualizer");
const mainSection = document.querySelector(".main-controls");
const soundClips = document.querySelector(".sound-clips");
const submitButton = document.querySelector("#read_submit_button");
const fileElement = document.querySelector("#audio_input");
const durationElement = document.querySelector("#recording_duration");

// Disable stop button while not recording
stop.disabled = true;
submitButton.disabled = true;

// Visualiser setup - create web audio api context and canvas
let audioCtx;
const canvasCtx = canvas.getContext("2d");

// Main block for doing the audio recording
if (navigator.mediaDevices.getUserMedia) {
const constraints = { audio: true };
let chunks = [];
let startTime;
let endTime;

let onSuccess = function (stream) {
const mediaRecorder = new MediaRecorder(stream);

visualize(stream);

record.onclick = function () {
startTime = Date.now();
mediaRecorder.start();
record.style.background = "red";

stop.disabled = false;
record.disabled = true;
};

stop.onclick = function () {
mediaRecorder.stop();
record.style.background = "";
record.style.color = "";

stop.disabled = true;
record.disabled = false;
endTime = Date.now();
(async () => {

let volumeCallback = null;
let volumeInterval = null;
const volumeVisualizer = document.getElementById('btn-microphone-toggle');
const toggleButton = document.getElementById('btn-microphone-toggle');
const mainSection = document.querySelector(".main-controls");
const soundClips = document.querySelector(".sound-clips");
const submitButton = document.querySelector("#read_submit_button");
const fileElement = document.querySelector("#audio_input");
const durationElement = document.querySelector("#recording_duration");

// Initialize
try {
const audioStream = await navigator.mediaDevices.getUserMedia({
audio: {
echoCancellation: true
}
});
const audioContext = new AudioContext();
const audioSource = audioContext.createMediaStreamSource(audioStream);
const analyser = audioContext.createAnalyser();
analyser.fftSize = 512;
analyser.minDecibels = -127;
analyser.maxDecibels = 0;
analyser.smoothingTimeConstant = 0.4;
audioSource.connect(analyser);
const volumes = new Uint8Array(analyser.frequencyBinCount);

volumeCallback = () => {
analyser.getByteFrequencyData(volumes);
let volumeSum = 0;
for(const volume of volumes)
volumeSum += volume;
const averageVolume = volumeSum / volumes.length;
// Value range: 127 = analyser.maxDecibels - analyser.minDecibels;
volumeVisualizer.style.setProperty('--volume', Math.ceil((averageVolume * 100 / 127)) + 'px');
};
} catch(e) {
console.error('Failed to initialize volume visualizer.', e);
}

mediaRecorder.onstop = function (e) {
const clipContainer = document.createElement("article");
const clipLabel = document.createElement("p");
const audio = document.createElement("audio");
const deleteButton = document.createElement("button");

clipContainer.classList.add("clip");
audio.setAttribute("controls", "");
deleteButton.textContent = "Delete";

clipContainer.appendChild(audio);
clipContainer.appendChild(clipLabel);
clipContainer.appendChild(deleteButton);
soundClips.appendChild(clipContainer);

const blob = new Blob(chunks, { type: "audio/mp3" });
chunks = [];

audio.src = window.URL.createObjectURL(blob);

// Convert blob to file and attach to file element.
let file = new File([blob], "audio_recording.mp3", {type:blob.type, lastModified:new Date().getTime()});
let container = new DataTransfer();
container.items.add(file);
fileElement.files = container.files;

// Set duration time
durationElement.value = Math.round((endTime - startTime) / 1000);

deleteButton.onclick = (e) => {
let evtTgt = e.target;
evtTgt.parentNode.parentNode.removeChild(evtTgt.parentNode);
record.disabled = false;
submitButton.disabled = true;
fileElement.files = new DataTransfer().files;
startTime = null;
endTime = null;
// Disable next button while not recording
submitButton.disabled = true;

// Main block for doing the audio recording
if (navigator.mediaDevices.getUserMedia) {
const constraints = { audio: true };
let chunks = [];
let startTime;
let endTime;

let onSuccess = function (stream) {
const mediaRecorder = new MediaRecorder(stream);

// visualize(stream);

toggleButton.addEventListener('click', () => {
if (toggleButton.classList.contains('active')) {
if (volumeInterval !== null) {
clearInterval(volumeInterval);
volumeInterval = null;
}
mediaRecorder.stop();
endTime = Date.now();
toggleButton.classList.remove('active')
}
else {
toggleButton.classList.add('active');
startTime = Date.now();
mediaRecorder.start();
if (volumeCallback !== null && volumeInterval === null) {
volumeInterval = setInterval(volumeCallback, 100);
}
}
});

mediaRecorder.onstop = function (e) {
const clipContainer = document.createElement("article");
// TODO: Do we use this clipLabel?
const clipLabel = document.createElement("p");
const audio = document.createElement("audio");
const deleteButton = document.createElement("button");

clipContainer.classList.add("clip", "flex");
deleteButton.classList.add("btn-primary");
audio.setAttribute("controls", "");
// TODO: This should be translateable
deleteButton.textContent = "Delete recording";

clipContainer.appendChild(audio);
clipContainer.appendChild(clipLabel);
clipContainer.appendChild(deleteButton);
soundClips.appendChild(clipContainer);

const blob = new Blob(chunks, { type: "audio/mp3" });
chunks = [];

audio.src = window.URL.createObjectURL(blob);

// Convert blob to file and attach to file element.
let file = new File([blob], "audio_recording.mp3", {type:blob.type, lastModified:new Date().getTime()});
let container = new DataTransfer();
container.items.add(file);
fileElement.files = container.files;

// Set duration time
durationElement.value = Math.round((endTime - startTime) / 1000);

deleteButton.onclick = (e) => {
let evtTgt = e.target;
evtTgt.parentNode.parentNode.removeChild(evtTgt.parentNode);
submitButton.disabled = true;
fileElement.files = new DataTransfer().files;
startTime = null;
endTime = null;
};

submitButton.disabled = false;
};

submitButton.disabled = false;
record.disabled = true;
};

mediaRecorder.ondataavailable = function (e) {
chunks.push(e.data);
mediaRecorder.ondataavailable = function (e) {
chunks.push(e.data);
};
};
};

navigator.mediaDevices.getUserMedia(constraints).then(onSuccess);
}

function visualize(stream) {
if (!audioCtx) {
audioCtx = new AudioContext();
navigator.mediaDevices.getUserMedia(constraints).then(onSuccess);
}

const source = audioCtx.createMediaStreamSource(stream);

const analyser = audioCtx.createAnalyser();
analyser.fftSize = 2048;
const bufferLength = analyser.frequencyBinCount;
const dataArray = new Uint8Array(bufferLength);

source.connect(analyser);

draw();

function draw() {
const WIDTH = canvas.width;
const HEIGHT = canvas.height;

requestAnimationFrame(draw);

analyser.getByteTimeDomainData(dataArray);

canvasCtx.fillStyle = "rgb(200, 200, 200)";
canvasCtx.fillRect(0, 0, WIDTH, HEIGHT);

canvasCtx.lineWidth = 2;
canvasCtx.strokeStyle = "rgb(0, 0, 0)";

canvasCtx.beginPath();

let sliceWidth = (WIDTH * 1.0) / bufferLength;
let x = 0;

for (let i = 0; i < bufferLength; i++) {
let v = dataArray[i] / 128.0;
let y = (v * HEIGHT) / 2;

if (i === 0) {
canvasCtx.moveTo(x, y);
} else {
canvasCtx.lineTo(x, y);
}

x += sliceWidth;
}

canvasCtx.lineTo(canvas.width, canvas.height / 2);
canvasCtx.stroke();
}
}

window.onresize = function () {
canvas.width = mainSection.offsetWidth;
};
})();

window.onresize();
4 changes: 2 additions & 2 deletions web/modules/custom/giv_din_stemme/js/testMicrophone.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
(async () => {
let volumeCallback = null;
let volumeInterval = null;
const volumeVisualizer = document.getElementById('volume-test-toggle');
const toggleButton = document.getElementById('volume-test-toggle');
const volumeVisualizer = document.getElementById('btn-microphone-toggle');
const toggleButton = document.getElementById('btn-microphone-toggle');

// Initialize
try {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<div id="btn-microphone-toggle" class="btn-microphone-toggle rounded-full flex place-content-center w-20 h-20 cursor-pointer text-center bg-brand visualizer-shadow">
<i class="text-white text-3xl fa-solid fa-microphone-slash place-self-center"></i>
<i class="text-white text-3xl fa-solid fa-microphone place-self-center"></i>
</div>
33 changes: 16 additions & 17 deletions web/modules/custom/giv_din_stemme/templates/read-page.html.twig
Original file line number Diff line number Diff line change
@@ -1,24 +1,23 @@
{% extends "@giv_din_stemme/gds-page-wrapper.html.twig" %}

{% block gds_content %}
<div class="py-5">
<div class="consent-intro text-lg">
<p>
{{ "Activate the microphone and read the displayed text. Press 'Next Section' to continue"|t }}
</p>
</div>
<div class="mb-5 pb-5 border-b-2"></div>
<div class="consent-text max-h-52 overflow-y-scroll">
<p>
{{ textToRead }}
</p>
<div class="py-5">
<div class="consent-intro text-lg">
<p>
{{ "Activate the microphone and read the displayed text. Press 'Next Section' to continue"|t }}
</p>
</div>
<div class="mb-5 pb-5 border-b-2"></div>
</div>
</div>
<div class="py-5">
<canvas class="visualizer" height="60px"></canvas>
<div id="buttons">
<button class="record">Record</button>
<button class="stop">Stop</button>

<div class="relative flex flex-col md:flex-row md:gap-5 md:min-h-36">
<div class="consent-text max-h-52 overflow-y-scroll flex align-content-center">
<p class="text-lg">
{{ textToRead }}
</p>
</div>
<div class="">
{% include "@giv_din_stemme/components/button-microphone-toggle.html.twig" %}
</div>
</div>
<section class="sound-clips">
Expand Down
12 changes: 5 additions & 7 deletions web/modules/custom/giv_din_stemme/templates/test-page.html.twig
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,12 @@
</p>
</div>
<div class="-mx-5 mb-5 pb-5 border-b-2"></div>
<div class="text-center text-lg">{{ 'Try to say'|t }}:</div>
<div class="text-center text-xl">"{{ 'She sells seashells by the seashore'|t }}"</div>
<div id="volume-test-toggle" class="my-16 mx-auto rounded-full table w-20 h-20 cursor-pointer text-center">
<div class="align-middle table-cell">
<i class="text-white text-3xl fa-solid fa-microphone"></i>
</div>
<div class="text-center flex flex-col items-center">
<p class="text-lg">{{ 'Try to say'|t }}:</p>
<p class="text-xl">"{{ 'She sells seashells by the seashore'|t }}"</p>
<span class="my-10">{% include "@giv_din_stemme/components/button-microphone-toggle.html.twig" %}</span>
<p class="text-lg">{{ 'If the button is not vibrating you must adjust your device input volume.'|t }}</p>
</div>
<div class="text-center text-lg">{{ 'If the button is not vibrating you must adjust your device input volume.'|t }}</div>
</div>
{% endblock %}
{% block gds_footer %}
Expand Down
14 changes: 11 additions & 3 deletions web/themes/custom/giv_din_stemme_theme/styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,15 @@
}
}

#volume-test-toggle {
background-color: red;
box-shadow: 0 0 var(--volume, 5px) var(--volume, 5px) #EF4123;
.btn-microphone-toggle.active.visualizer-shadow {
box-shadow: 0 0 var(--volume, 5px) var(--volume, 5px) hsla(9, 86%, 54%, 50%);
}
.btn-microphone-toggle.active .fa-microphone {
display: none;
}
.btn-microphone-toggle.active .fa-microphone-slash {
display: inline;
}
.btn-microphone-toggle .fa-microphone-slash {
display: none;
}

0 comments on commit 755aeb7

Please sign in to comment.