diff --git a/src/airsonic_ui/audio.cljs b/src/airsonic_ui/audio.cljs index 16f79ca..973a6ae 100644 --- a/src/airsonic_ui/audio.cljs +++ b/src/airsonic_ui/audio.cljs @@ -3,11 +3,33 @@ ;; TODO: Manage multiple songs, buffering, stopping, progress notification... -(def current-audio (atom nil)) +(defonce current-audio (atom nil)) + +(defn ->status + "Takes an audio object and returns a map describing its current status" + [elem] + {:ended? (.-ended elem) + :loop? (.-loop elem) + :muted? (.-muted elem) + :paused? (.-paused elem) + :current-src (.-currentSrc elem) + :current-time (.-currentTime elem)}) + +; explanation of these events: https://developer.mozilla.org/en-US/Apps/Fundamentals/Audio_and_video_delivery/Cross-browser_audio_basics +(defn attach-listeners! [el] + (doseq [event ["loadstart" "progress" "play" "timeupdate" "pause"]] + (.addEventListener el event #(re-frame/dispatch [:audio-update (->status el)])))) + (re-frame/reg-fx :play-song (fn [song-url] (let [audio (js/Audio. song-url)] (reset! current-audio audio) + (attach-listeners! audio) (.play audio)))) + +(re-frame/reg-fx + :pause-song + (fn [_] + (some-> @current-audio .pause))) diff --git a/src/airsonic_ui/events.cljs b/src/airsonic_ui/events.cljs index 9782914..f6a3a65 100644 --- a/src/airsonic_ui/events.cljs +++ b/src/airsonic_ui/events.cljs @@ -52,8 +52,7 @@ (re-frame/reg-event-db ::api-success (fn [db [_ k response]] - (println "api response" response) - ;; we "unwrap" the responses + ; we "unwrap" the responses (assoc db :response (-> response :subsonic-response k)))) (re-frame/reg-event-db @@ -67,10 +66,23 @@ (re-frame/reg-event-fx ::play-song (fn [{:keys [db]} [_ song]] + ; sets up the db and starts to play a song (let [song-url (api/url "stream" (merge {:id (:id song)} (:login db)))] - (println "Requesting to stream song at" song-url) - {:play-song song-url}))) + {:play-song song-url + :db (assoc-in db [:currently-playing :item] song)}))) + +(re-frame/reg-event-fx + ::pause-song + (fn [_ _] + ; pauses the current song + {:pause-song nil})) + +(re-frame/reg-event-db + :audio-update + (fn [db [_ status]] + ; we receive this from the player once it's playing + (assoc-in db [:currently-playing :status] status))) ;; routing diff --git a/src/airsonic_ui/subs.cljs b/src/airsonic_ui/subs.cljs index 893c6e3..a692098 100644 --- a/src/airsonic_ui/subs.cljs +++ b/src/airsonic_ui/subs.cljs @@ -23,3 +23,9 @@ ::current-content (fn [db] (-> db :response))) + +(re-frame/reg-sub + ; returns info on the current song as is (basically the metadata you can read from the file system) + ::currently-playing + (fn [db] + (-> db :currently-playing))) diff --git a/src/airsonic_ui/views.cljs b/src/airsonic_ui/views.cljs index 8c151eb..6dd69c9 100644 --- a/src/airsonic_ui/views.cljs +++ b/src/airsonic_ui/views.cljs @@ -63,6 +63,29 @@ [:h2 (str (:artist content) " - " (:name content))] [song-list (:song content)]]) +;; currently playing / coming next / audio controls... + +(defn current-song-info [{:keys [item status]}] + [:div + [:b "Currently playing: "] + [:div (:artist item) " - " (:title item)] + [:div (:current-time status) "s / " (:duration item) "s"]]) + +(defn playback-controls [] + [:div + [:button "previous"] + [:button "play / pause"] + [:button "next"] + [:label [:input {:type "checkbox"}] "shuffle"] + [:label [:input {:type "checkbox"}] "repeat"]]) + +(defn bottom-bar [] + [:div + (if-let [currently-playing @(re-frame/subscribe [::subs/currently-playing])] + [current-song-info currently-playing] + [:span "Currently no song selected"]) + [playback-controls]]) + ;; putting everything together (defn app [route params query] @@ -73,7 +96,8 @@ (case route ::routes/main [album-list content] ::routes/album-view [album-detail content]) - [:a {:on-click #(re-frame/dispatch [::events/initialize-db]) :href "#"} "Logout"]])) + [:a {:on-click #(re-frame/dispatch [::events/initialize-db]) :href "#"} "Logout"] + [bottom-bar]])) (defn main-panel [] (let [[route params query] @(re-frame/subscribe [::subs/current-route])]