Knocking over Scittle with audio recordings

Hi all,

I’ve been writing Clojure as my main language since 2013, so I’m hardly a newbie. But I’m just diving into Michiel Borkent’s Scittle (which is great fun by the way), and I’m hitting problems with interoperation with JavaScript.

I’m trying to record audio from the user’s microphone. My problem is, that although I see there’s CLJS documentation for the MediaRecorder object, I’m failing to access it. What I’ve got is:

        (try
          (.then (.getUserMedia (.mediaDevices js/navigator) {:audio true})
            (fn [arg] 
              (let [media-recorder (MediaRecorder. arg)
                    audio-chunks (atom [])]
                  (.start media-recorder)
                  (set! media-recorder onerror 
                    (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-sounds 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)))
```
However, I'm getting 

```
1639          (try
1640            (.then (.getUserMedia (.mediaDevices js/navigator) {:audio true})
1641              (fn [arg] 
1642                (let [media-recorder (MediaRecorder. arg)
                                         ^--- Could not resolve symbol: MediaRecorder
1643                      audio-chunks (atom [])]
1644                    (.start media-recorder)
```

When I try
```
(require 'web.audio)
```

I get

```
Message:  Could not find namespace: web.audio. <anonymous code>:1:145535
Location: scittle-tag-2:1:1 <anonymous code>:1:145535
----- Context ------------------------------------ <anonymous code>:1:145535

1  (require '[reagent.core :as r]
   ^--- Could not find namespace: web.audio.
2                '[reagent.dom :as rdom]
3                'web.audio)
```
I'm guessing that the web API isn't compiled into Scittle, and that what I need to do is fall back onto plain old fashioned Clojurescript.

But can anyone think how I could work around this?
2 Likes

For examples of stuff I have got working in Scittle so far, check out these pages:

Have you tried js/MediaRecorder?

1 Like

D’oh!

I had not, and that was the right answer.

1 Like

GitHub - brianium/blah: A library for working with microphones in ClojureScript might be relevant for inspiration (but I don’t think it will work in Scittle)

I briefly looked into this and tried with:

<script src="https://cdn.jsdelivr.net/npm/scittle@0.7.27/dist/scittle.js" type="application/javascript"></script>

<script type="application/x-scittle">
  (defn array-seq [arr] (seq arr))
  (intern 'clojure.core 'array-seq array-seq)
</script>

<script src="https://raw.githubusercontent.com/brianium/blah/refs/heads/main/src/blah/transforms.cljs" type="application/x-scittle"></script>

<script src="https://raw.githubusercontent.com/brianium/blah/refs/heads/main/src/blah/impl.cljs" type="application/x-scittle"></script>

<script src="https://raw.githubusercontent.com/brianium/blah/refs/heads/main/src/blah/core.cljs" type="application/x-scittle"></script>

The first thing I ran into was array-seq not existing in scittle, a thing that can be easily fixed. But the major problem is that cljs.core.async was used in this library and this is indeed unsupported.

OK, I’m not completely out of the woods yet. What I have now is:


(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 [])]
               (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.")
                        (when (> (count @audio-chunks) 0)
                          (swap! student-recordings assoc phrase-no
                                 (js/Blob. (clj->js @audio-chunks))))))
               (.start media-recorder))))
    (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))))

What I’m getting is

Error thrown while recording sound: f is not a function

The message ‘Audio recorded…’ is never printed, so I’m guessing the f that ‘is not a function’ is the handler for dataavailable But I don’t understand why it isn’t, or that is needed.