muharni/resources/public/scripts/muharni.js
Simon Brooke 353e37cff5
Some user interface improvements
Close control on popup, progress bar when recording.
2022-09-18 18:27:53 +01:00

176 lines
6.7 KiB
JavaScript

/**
* muharni.js
*
* Custom JavaScript functions, some dependent on JQuery, used in the `muharni`
* page.
*/
/**
* An array for client side sound recordings made by the student. We don't
* strictly need one for each short and long character, but it will give a
* better experience for people who really get into it.
*
* There are two tables each of 39 rows and 12 columns, but we're going to
* pack both tables into the same array. So the long sounds will be rows 1
* to 39 inclusive, and the short from 41 to 79 inclusive.
*/
const studentSounds = Array(80).fill(0).map(x => Array(13).fill(null));
/**
* Creates a progressbar. Adapted from
* https://stackoverflow.com/questions/31109581/javascript-timer-progress-bar
*
* @param id the id of the div we want to transform in a progressbar
* @param duration the duration of the timer example: '10s'
* @param callback, optional function which is called when the progressbar reaches 0.
*/
function createProgressbar(id, duration) {
// We select the div that we want to turn into a progressbar
try {
const progressbar = document.getElementById(id);
progressbar.className = 'progressbar';
// We create the div that changes width to show progress
let progressbarinner = progressbar.querySelector('.inner');
if (progressbarinner == null) {
progressbarinner = document.createElement('div');
progressbarinner.className = 'inner';
// Now we set the animation parameters
progressbarinner.style.animationDuration = duration;
// Append the progressbar to the main progressbardiv
progressbar.appendChild(progressbarinner);
}
progressbarinner.addEventListener('animationend', () => {
while (progressbar.hasChildNodes()) {
progressbar.removeChild(progressbar.lastChild);
}
});
// When everything is set up we start the animation
progressbarinner.style.animationPlayState = 'running';
} catch (e) {
console.warn("Failed to create progress bar because " +
e.message +
". This does not, cosmically speaking, matter.");
}
}
function recordStudentSound(r, c) {
console.info("Entered recordStudentSound for row " + r + ", column " + c);
if (Number.isInteger(r) && Number.isInteger(c)) {
$('#record-stop').css('color', 'green');
try {
createProgressbar('progress', '5s');
navigator.mediaDevices.getUserMedia({ audio: true })
.then(stream => {
const mediaRecorder = new MediaRecorder(stream);
const audioChunks = [];
mediaRecorder.start();
mediaRecorder.onerror = function (e) {
console.log("An error has occurred: " + e.message);
};
mediaRecorder.addEventListener("dataavailable", event => {
console.info("Audio recorded...")
audioChunks.push(event.data);
});
mediaRecorder.onstop = function (e) {
console.log("data available after MediaRecorder.stop() called.");
if (audioChunks.length > 0) {
studentSounds[r][c] = new Blob(audioChunks);
$('#play-student').prop('disabled', false);
$('#play-student').css('color', 'black');
$("#play-student").on("click", function (e) {
console.log("Playing student sound for row " + r + ", column " + c);
new Audio(URL.createObjectURL(studentSounds[r][c])).play();
});
console.log("Successfully recorded student sound for row " + r + ", column " + c);
} else {
console.warn("Failed to record student sound for row " + r + ", column " + c);
window.alert("No sound detected. Check your microphone?");
}
$('#record-stop').css('color', 'red');
};
setTimeout(() => {
mediaRecorder.requestData();
mediaRecorder.stop();
}, 5000);
});
} catch (error) {
console.error(error);
if (error instanceof TypeError) {
window.alert("Sound recording is only possible on secure connections.");
}
else if (error instanceof DOMException) {
window.alert("No microphone detected? " + error.message);
}
}
}
}
$(document).ready(function () {
$(".entry-text").on("click", function (e) {
let cellId = e.currentTarget ? e.currentTarget.id : null;
if (cellId) {
let row = parseInt(cellId.slice(1, 3));
let colChar = cellId.slice(3);
if (colChar == colChar.toLowerCase()) {
row += 40; /* we're wrapping lower case into the bottom half of the array */
}
let col = cellId.charCodeAt(3) - 65;
if (col > 26) {
col -= 32; /* lower case */
}
let studentAudio = studentSounds[row][col];
$("#popup").css({
'left': e.pageX,
'top': e.pageY
});
$("#character").text(e.currentTarget.innerText.substring(0, 1));
$("#play-tutor").off("click"); /* trying to remove any previous click handler */
$("#play-tutor").on("click", function (e) {
let audioUrl = "audio/" + cellId.slice(1) + ".mp3";
console.log("Playing tutor audio " + audioUrl);
new Audio(audioUrl).play();
});
$("#record-stop").off("click");
$("#record-stop").on("click", function (e) {
console.log("Recording student sound for row " + row + ", column " + col);
recordStudentSound(row, col);
});
$("#play-student").off("click");
$('#play-student').css('color', 'gray');
if (studentAudio != null) {
$('#play-student').css('color', 'black');
$("#play-student").on("click", function (e) {
console.log("Playing student sound for row " + row + ", column " + col);
new Audio(URL.createObjectURL(studentAudio)).play();
});
}
$("#play-student").prop("disabled", studentAudio == null);
$("#popup").show();
}
});
})