(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")))