Everything is now working except recording/playback of student sound.
Ahem. I may have backed myself into a bit of a corner over that.
This commit is contained in:
parent
277833aeb2
commit
20f471236c
170
japji/index.html
170
japji/index.html
|
@ -17,12 +17,13 @@
|
|||
color: #331f16;
|
||||
padding: 5% 20%;
|
||||
font-size: x-large;
|
||||
font-family: sans-serif;
|
||||
}
|
||||
|
||||
footer {
|
||||
border-top: thin solid #331f16;
|
||||
font-size: medium;
|
||||
padding: 0.5em 0;
|
||||
padding: 4em 0 0.5em 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
|
@ -39,7 +40,12 @@
|
|||
font-size: 2em;
|
||||
}
|
||||
|
||||
#popup-record-stop {
|
||||
#popup-close-box {
|
||||
float: right;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
#popup-record-stop, #popup-close-box {
|
||||
color: red;
|
||||
}
|
||||
|
||||
|
@ -70,6 +76,114 @@
|
|||
<script crossorigin src="https://cdn.jsdelivr.net/npm/react-dom@18/umd/react-dom.production.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/scittle@0.7.27/dist/scittle.reagent.js" type="text/javascript"></script>
|
||||
|
||||
<script type="text/javascript">
|
||||
/* Yes, I should rewrite this in Scittle, especially since it depends on JQuery
|
||||
* which we otherwise don't need. However it's ugly and I don't have time.
|
||||
*/
|
||||
/**
|
||||
* 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(phrase-data) {
|
||||
console.info("Entered recordStudentSound for phrase " + phrase-data);
|
||||
|
||||
if (!!phrase-data) {
|
||||
$('#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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<script type="application/x-scittle">
|
||||
(require '[reagent.core :as r]
|
||||
'[reagent.dom :as rdom])
|
||||
|
@ -1734,26 +1848,64 @@
|
|||
(when popup
|
||||
(rdom/render "" popup))))
|
||||
|
||||
(def popup-control-style {:border "thin solid #331f16"
|
||||
:padding "0 0.5em"})
|
||||
|
||||
(def student-recordings (atom (apply vector (repeat (count data) nil))))
|
||||
|
||||
(defn play-student [phrase-no]
|
||||
(js/alert "Now you should be able to play your recording, but that does not work yet"))
|
||||
|
||||
(defn record-student [phrase-no]
|
||||
(js/alert "Now you should be able to record yourself, but that does not work yet"))
|
||||
|
||||
(defn launch-popup [phrase-no event]
|
||||
(.log js/console (str "Launching popup for phrase " phrase-no))
|
||||
;; OK, there's a problem here. The popup is not getting style from the
|
||||
;; style element, probably because it doesn't exist when the page is
|
||||
;; initially styled; so style needs to be embedded.
|
||||
(let [popup (.getElementById js/document "popup")
|
||||
phrase-data (nth data phrase-no)
|
||||
content [:div {:id "popup-content"
|
||||
:style {:border "thin solid #331f16"
|
||||
:background-color "whitesmoke"
|
||||
:left (.-pageX event)
|
||||
:left (- (.-pageX event) 100)
|
||||
:top (.-pageY event)
|
||||
:position "absolute"
|
||||
:display "block"
|
||||
:z-index 10}}
|
||||
[:div {:id "popup-closebox" :on-click #(close-popup) :title "Close popup"} "x"
|
||||
[:div {:style {:background-color "#331f16"
|
||||
:color "red"
|
||||
:height "1.2em"
|
||||
:width "100%"}}
|
||||
[:span {:id "popup-closebox" :on-click #(close-popup)
|
||||
:title "Close popup"
|
||||
:style {:float "right"}} "x"]]
|
||||
(apply vector (concat [:p {:id "popup-phrase" :class "gurmukhi"}]
|
||||
(map #(markup-word %1 phrase-no %2) (:words phrase-data) (range))))
|
||||
[:table {:id "popup-controls" :summary "Controls for audio playback and recording"}
|
||||
[:tr [:th "Tutor"][:td {:id "popup-play-tutor"} "►"]]
|
||||
[:tr [:th "You"]
|
||||
[:td {:id "popup-play-student"} "►"]
|
||||
[:td {:id "popup-record-stop"} "⏺"]]]]]]
|
||||
[:table {:id "popup-controls"
|
||||
:summary "Controls for audio playback and recording"
|
||||
:style {:border "thin solid #331f16"
|
||||
:padding "0.25em 1em"
|
||||
:width "100%"}}
|
||||
[:tr [:td "Tutor"][:td {:id "popup-play-tutor"
|
||||
:on-click #(play-segment recording
|
||||
(:lineStart phrase-data)
|
||||
(:lineEnd phrase-data))
|
||||
:style popup-control-style
|
||||
:title "Play the tutor's recording"}
|
||||
"►"]]
|
||||
[:tr [:td "You"]
|
||||
(when (@student-recordings phrase-no)
|
||||
[:td {:id "popup-play-student"
|
||||
:on-click #(play-student phrase-no)
|
||||
:style popup-control-style
|
||||
:title "Play your own recording"} "►"])
|
||||
[:td {:id "popup-record-stop"
|
||||
:on-click #(record-student phrase-no)
|
||||
:style popup-control-style
|
||||
:title "Record yourself saying this phrase"}
|
||||
"⏺"]]]]]
|
||||
(rdom/render content popup)))
|
||||
|
||||
(defn markup-phrase [record n]
|
||||
|
|
Loading…
Reference in a new issue