D'oh! Added japji.cljs
This commit is contained in:
parent
b253c1084c
commit
480032492a
32
.gitignore
vendored
32
.gitignore
vendored
|
@ -1,19 +1,21 @@
|
||||||
|
.calva/
|
||||||
|
/checkouts/
|
||||||
|
*.class
|
||||||
|
/classes/
|
||||||
|
.clj-kondo/
|
||||||
# ---> Clojure
|
# ---> Clojure
|
||||||
|
.cpcache/
|
||||||
|
japji.code-workspace
|
||||||
|
*.jar
|
||||||
|
.lein-deps-sum
|
||||||
|
.lein-failures
|
||||||
|
.lein-plugins/
|
||||||
|
.lein-repl-history
|
||||||
|
/lib/
|
||||||
|
.lsp/
|
||||||
|
.nrepl-port
|
||||||
pom.xml
|
pom.xml
|
||||||
pom.xml.asc
|
pom.xml.asc
|
||||||
*.jar
|
|
||||||
*.class
|
|
||||||
/lib/
|
|
||||||
/classes/
|
|
||||||
/target/
|
|
||||||
/checkouts/
|
|
||||||
.lein-deps-sum
|
|
||||||
.lein-repl-history
|
|
||||||
.lein-plugins/
|
|
||||||
.lein-failures
|
|
||||||
.nrepl-port
|
|
||||||
.cpcache/
|
|
||||||
.clj-kondo/
|
|
||||||
.lsp/
|
|
||||||
.portal/
|
.portal/
|
||||||
japji.code-workspace
|
.shadow-cljs/
|
||||||
|
/target/
|
||||||
|
|
184
resources/public/scripts/japji.cljs
Normal file
184
resources/public/scripts/japji.cljs
Normal file
|
@ -0,0 +1,184 @@
|
||||||
|
(ns japji
|
||||||
|
(:require [reagent.core :as r]
|
||||||
|
[reagent.dom :as rdom]))
|
||||||
|
|
||||||
|
(declare launch-popup markup-phrase data-js)
|
||||||
|
|
||||||
|
(def recording (.getElementById js/document "japji-bindranwale"))
|
||||||
|
|
||||||
|
(def data (js->clj data-js))
|
||||||
|
|
||||||
|
(.log js/console (str "Data has " (count data) " entries"))
|
||||||
|
|
||||||
|
;; bits of Scittle stuff that need to be set up for recordStudentSound to
|
||||||
|
;; work
|
||||||
|
|
||||||
|
(def student-recordings (atom (apply vector (repeat (count data) nil))))
|
||||||
|
|
||||||
|
(defn enable-play-button! [phrase-no])
|
||||||
|
|
||||||
|
(defn record-student-sound!
|
||||||
|
[phrase-no]
|
||||||
|
(.info js/console "Recording student sound for phrase " phrase-no)
|
||||||
|
|
||||||
|
(try
|
||||||
|
(.then (.getUserMedia (.mediaDevices js/navigator) {:audio true})
|
||||||
|
(fn [arg]
|
||||||
|
(let [media-recorder (js/MediaRecorder. arg)
|
||||||
|
audio-chunks (atom [])]
|
||||||
|
(.start media-recorder)
|
||||||
|
(set! (.-onerror media-recorder)
|
||||||
|
(fn [s]
|
||||||
|
(.log js/console (str "Error while recording sound: " s))))
|
||||||
|
(.addEventListener media-recorder "dataavailable"
|
||||||
|
(fn [event]
|
||||||
|
(.info js/console "Audio recorded...")
|
||||||
|
(swap! audio-chunks conj (.-data event))))
|
||||||
|
(set! (.-onstop media-recorder)
|
||||||
|
(fn [e]
|
||||||
|
(js/console.log "data available after MediaRecorder.stop() called.")
|
||||||
|
(if (> (count @audio-chunks) 0)
|
||||||
|
(do
|
||||||
|
;; Store the blob in the student-recordings data structure
|
||||||
|
(swap! student-recordings assoc phrase-no
|
||||||
|
(js/Blob. (clj->js @audio-chunks)))
|
||||||
|
(enable-play-button! phrase-no))))))))
|
||||||
|
(catch js/Error e
|
||||||
|
(.log js/console
|
||||||
|
(str "Error thrown while recording sound: " (.-message e))))
|
||||||
|
(catch :default x
|
||||||
|
(str "Unexpected object thrown while recording " x))))
|
||||||
|
|
||||||
|
(defn interval-watcher
|
||||||
|
"Returns a closure over this `audio` and this `end`,
|
||||||
|
which pauses and clears down the interval of the audio when it is
|
||||||
|
called after the current time of the `audio` exceeds `end`."
|
||||||
|
[audio end]
|
||||||
|
(fn []
|
||||||
|
(let [audio-time (.-currentTime audio)]
|
||||||
|
(.log js/console (str "interval: current time now "
|
||||||
|
audio-time "; end " end))
|
||||||
|
(when (> audio-time end)
|
||||||
|
(.log js/console (str "current time now "
|
||||||
|
(.-currentTime audio) "; end " end "; pausing recording"))
|
||||||
|
(.pause audio)
|
||||||
|
(.clearInterval js/window (.-int audio))))))
|
||||||
|
|
||||||
|
(defn play-segment [audio start end]
|
||||||
|
;; adapted from https://stackoverflow.com/questions/5932412/html5-audio-how-to-play-only-a-selected-portion-of-an-audio-file-audio-sprite
|
||||||
|
(.log js/console (str "Playing recording from " start " to " end))
|
||||||
|
(let [clone (.cloneNode audio true)]
|
||||||
|
(set! (.-currentTime clone) start)
|
||||||
|
(.log js/console (str "current time now set to " (.-currentTime clone)))
|
||||||
|
(.play clone)
|
||||||
|
(set! (.-int clone)
|
||||||
|
(.setInterval js/window
|
||||||
|
(interval-watcher clone end)
|
||||||
|
10))))
|
||||||
|
|
||||||
|
(defn markup-word [record phrase-no word-no]
|
||||||
|
[:span {:class "word, gurmukhi"
|
||||||
|
:id (str "word-" phrase-no "-" word-no)
|
||||||
|
:lang "pa"
|
||||||
|
:on-click #(play-segment recording (:start record) (:end record))}
|
||||||
|
(str " " (:text record))])
|
||||||
|
|
||||||
|
(defn close-popup []
|
||||||
|
(let [popup (.getElementById js/document "popup")]
|
||||||
|
(when popup
|
||||||
|
(rdom/render "" popup))))
|
||||||
|
|
||||||
|
(def popup-control-style {:border "thin solid #331f16"
|
||||||
|
:padding "0 0.5em"})
|
||||||
|
|
||||||
|
(defn play-student!
|
||||||
|
[phrase-no]
|
||||||
|
(.play (js/Audio. (.createObjectURL js/URL (@student-recordings phrase-no)))))
|
||||||
|
|
||||||
|
(defn animate-progress-bar!
|
||||||
|
[id duration]
|
||||||
|
(let [progress-step (int (* duration 10))]
|
||||||
|
(do
|
||||||
|
(.info js/console (str "Duration is: " duration "; step is: " progress-step))
|
||||||
|
(loop [i 1
|
||||||
|
e (.getElementById js/document id)]
|
||||||
|
(when (< i 99)
|
||||||
|
(js/setTimeout
|
||||||
|
#(do (.setAttribute e "value" i)
|
||||||
|
(.info js/console (str "progress-width updated to " i))) (* i progress-step))
|
||||||
|
(recur (inc i)))))))
|
||||||
|
|
||||||
|
(defn record-student! [phrase-no]
|
||||||
|
(let [phrase-data (nth data phrase-no)
|
||||||
|
duration (- (:lineEnd phrase-data) (:lineStart phrase-data))]
|
||||||
|
(record-student-sound! phrase-no)
|
||||||
|
(animate-progress-bar! "progress-bar" duration)))
|
||||||
|
|
||||||
|
(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)
|
||||||
|
duration (- (:lineEnd phrase-data) (:lineStart phrase-data))
|
||||||
|
progress-width (atom 0)
|
||||||
|
progress-step (int (* duration 10))
|
||||||
|
content [:div {:id "popup-content"
|
||||||
|
:style {:border "thin solid #331f16"
|
||||||
|
:background-color "whitesmoke"
|
||||||
|
:left (- (.-pageX event) 100)
|
||||||
|
:top (.-pageY event)
|
||||||
|
:position "absolute"
|
||||||
|
:display "block"
|
||||||
|
:z-index 10}}
|
||||||
|
[: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"
|
||||||
|
: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"}
|
||||||
|
"⏺"]]
|
||||||
|
[:tr [:td {:id "progress"
|
||||||
|
:colspan 3}
|
||||||
|
[:progress {:id "progress-bar"
|
||||||
|
:max 100
|
||||||
|
:value 0
|
||||||
|
:style {:width "100%"}}]]]]]]
|
||||||
|
(rdom/render content popup)))
|
||||||
|
|
||||||
|
(defn markup-phrase [record n]
|
||||||
|
[:div {:class "phrase" :id (str "phrase-" n)}
|
||||||
|
[:span {:class "play-control" :lang "en-GB"
|
||||||
|
:on-click #(play-segment recording (:lineStart record) (:lineEnd record))} "•"]
|
||||||
|
(map #(markup-word %1 n %2) (:words record) (range))
|
||||||
|
[:span {:class "popup-launcher"
|
||||||
|
:on-click #(launch-popup n %)} "..."]])
|
||||||
|
|
||||||
|
(let [content (map markup-phrase data (range))]
|
||||||
|
(rdom/render content (.getElementById js/document "content")))
|
Loading…
Reference in a new issue