diff --git a/shadow-cljs.edn b/shadow-cljs.edn index 85d832b..c645180 100644 --- a/shadow-cljs.edn +++ b/shadow-cljs.edn @@ -9,6 +9,7 @@ [funcool/bide "1.6.0"] ;; debugging [day8.re-frame/re-frame-10x "0.3.2-react16"] + [day8.re-frame/tracing "0.5.1"] ;; for CIDER [cider/cider-nrepl "0.16.0-snapshot"] [refactor-nrepl "2.3.1"]] @@ -17,7 +18,8 @@ {:app {:target :browser :output-dir "public/app/js" :asset-path "/app/js" - :closure-defines {"re_frame.trace.trace_enabled_QMARK_" true} + :closure-defines {"re_frame.trace.trace_enabled_QMARK_" true + "day8.re_frame.tracing.trace_enabled_QMARK_" true} :modules {:main {:entries [airsonic-ui.core]}} :devtools {:http-root "public" :http-port 8080 diff --git a/src/airsonic_ui/audio.cljs b/src/airsonic_ui/audio.cljs index 7ce4ae5..08842b9 100644 --- a/src/airsonic_ui/audio.cljs +++ b/src/airsonic_ui/audio.cljs @@ -1,9 +1,9 @@ (ns airsonic-ui.audio (:require [re-frame.core :as re-frame])) -;; TODO: Manage multiple songs, buffering, stopping, progress notification... +;; TODO: Manage buffering -(defonce current-audio (atom nil)) +(defonce audio (atom nil)) (defn ->status "Takes an audio object and returns a map describing its current status" @@ -23,16 +23,17 @@ (re-frame/reg-fx :play-song (fn [song-url] - (some-> @current-audio .pause) - (let [audio (js/Audio. song-url)] - (reset! current-audio audio) - (attach-listeners! audio) - (.play audio)))) + (when-not @audio + (reset! audio (js/Audio.)) + (attach-listeners! @audio)) + (.pause @audio) + (set! (.-src @audio) song-url) + (.play @audio))) (re-frame/reg-fx :toggle-play-pause (fn [_] - (when-let [a @current-audio] + (let [a @audio] (if (.-paused a) (.play a) (.pause a))))) diff --git a/src/airsonic_ui/events.cljs b/src/airsonic_ui/events.cljs index 1da7ebe..9a4e395 100644 --- a/src/airsonic_ui/events.cljs +++ b/src/airsonic_ui/events.cljs @@ -3,7 +3,8 @@ [ajax.core :as ajax] [airsonic-ui.routes :as routes] [airsonic-ui.db :as db] - [airsonic-ui.api :as api])) + [airsonic-ui.api :as api] + [day8.re-frame.tracing :refer-macros [fn-traced]])) ; <- useful to debug handlers ;; this is where all of the event handling takes place; the names put the events into ;; the following categories: @@ -75,19 +76,43 @@ ;; musique +(defn ->song-url [song credentials] + (api/url "stream" (merge {:id (:id song)} credentials))) + +; TODO: Make play, next and previous a bit prettier and more DRY + (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)))] - {:play-song song-url - :db (assoc-in db [:currently-playing :item] song)}))) + ; sets up the db, starts to play a song and adds the rest to a playlist + ::play-songs + (fn [{:keys [db]} [_ songs song]] + {:play-song (->song-url song (:login db)) + :db (-> db + (assoc-in [:currently-playing :item] song) + (assoc-in [:currently-playing :playlist] songs))})) + +(re-frame/reg-event-fx + ::next-song + (fn [{:keys [db]} _] + (let [playlist (-> db :currently-playing :playlist) + current (-> db :currently-playing :item) + next (first (rest (drop-while #(not= % current) playlist)))] + (when next + {:play-song (->song-url next (:login db)) + :db (assoc-in db [:currently-playing :item] next)})))) + +(re-frame/reg-event-fx + ::previous-song + (fn [{:keys [db]} _] + (let [playlist (-> db :currently-playing :playlist) + current (-> db :currently-playing :item) + previous (last (take-while #(not= % current) playlist))] + (when previous + {:play-song (->song-url previous (:login db)) + :db (assoc-in db [:currently-playing :item] previous)})))) (re-frame/reg-event-fx ::toggle-play-pause (fn [_ _] - ; pauses the current song {:toggle-play-pause nil})) (re-frame/reg-event-db diff --git a/src/airsonic_ui/views.cljs b/src/airsonic_ui/views.cljs index c5f17e2..eb3fbe2 100644 --- a/src/airsonic_ui/views.cljs +++ b/src/airsonic_ui/views.cljs @@ -1,5 +1,5 @@ (ns airsonic-ui.views - (:require [re-frame.core :as re-frame] + (:require [re-frame.core :refer [dispatch subscribe]] [reagent.core :as r] [airsonic-ui.routes :as routes] [airsonic-ui.events :as events] @@ -21,7 +21,7 @@ [:span "Password"] [:input {:type "password" :name "pass" :on-change #(reset! pass (-> % .-target .-value))}]] [:div - [:button {:on-click #(re-frame/dispatch [::events/authenticate @user @pass])} "Submit"]]]))) + [:button {:on-click #(dispatch [::events/authenticate @user @pass])} "Submit"]]]))) ;; album list (start page) @@ -43,15 +43,15 @@ ;; single album -(defn song-item [song] +(defn song-item [songs song] [:div (str (:artist song) " - ") [:a - {:on-click #(re-frame/dispatch [::events/play-song song])} + {:on-click #(dispatch [::events/play-songs songs song])} (:title song)]]) (defn song-list [songs] [:ul (for [[idx song] (map-indexed vector songs)] - [:li {:key idx} [song-item song]])]) + [:li {:key idx} [song-item songs song]])]) (defn album-detail [content] [:div @@ -68,15 +68,15 @@ (defn playback-controls [] [:div - [:button "previous"] - [:button {:on-click #(re-frame/dispatch [::events/toggle-play-pause])} "play / pause"] - [:button "next"] + [:button {:on-click #(dispatch [::events/previous-song])} "previous"] + [:button {:on-click #(dispatch [::events/toggle-play-pause])} "play / pause"] + [:button {:on-click #(dispatch [::events/next-song])} "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])] + (if-let [currently-playing @(subscribe [::subs/currently-playing])] [current-song-info currently-playing] [:span "Currently no song selected"]) [playback-controls]]) @@ -84,18 +84,18 @@ ;; putting everything together (defn app [route params query] - (let [login @(re-frame/subscribe [::subs/login]) - content @(re-frame/subscribe [::subs/current-content])] + (let [login @(subscribe [::subs/login]) + content @(subscribe [::subs/current-content])] [:div [:span (str "Currently logged in as " (:u login))] (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 #(dispatch [::events/initialize-db]) :href "#"} "Logout"] [bottom-bar]])) (defn main-panel [] - (let [[route params query] @(re-frame/subscribe [::subs/current-route])] + (let [[route params query] @(subscribe [::subs/current-route])] [:div [:h1 "Airsonic"] (case route