mirror of
https://github.com/heyarne/airsonic-ui.git
synced 2026-05-06 18:33:38 +02:00
Add current playlist, next and previous
Also reuse audio element so slippery stuff like volume persists
This commit is contained in:
parent
4b00c21a74
commit
d24300ad1e
4 changed files with 59 additions and 31 deletions
|
|
@ -9,6 +9,7 @@
|
||||||
[funcool/bide "1.6.0"]
|
[funcool/bide "1.6.0"]
|
||||||
;; debugging
|
;; debugging
|
||||||
[day8.re-frame/re-frame-10x "0.3.2-react16"]
|
[day8.re-frame/re-frame-10x "0.3.2-react16"]
|
||||||
|
[day8.re-frame/tracing "0.5.1"]
|
||||||
;; for CIDER
|
;; for CIDER
|
||||||
[cider/cider-nrepl "0.16.0-snapshot"]
|
[cider/cider-nrepl "0.16.0-snapshot"]
|
||||||
[refactor-nrepl "2.3.1"]]
|
[refactor-nrepl "2.3.1"]]
|
||||||
|
|
@ -17,7 +18,8 @@
|
||||||
{:app {:target :browser
|
{:app {:target :browser
|
||||||
:output-dir "public/app/js"
|
:output-dir "public/app/js"
|
||||||
:asset-path "/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]}}
|
:modules {:main {:entries [airsonic-ui.core]}}
|
||||||
:devtools {:http-root "public"
|
:devtools {:http-root "public"
|
||||||
:http-port 8080
|
:http-port 8080
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
(ns airsonic-ui.audio
|
(ns airsonic-ui.audio
|
||||||
(:require [re-frame.core :as re-frame]))
|
(: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
|
(defn ->status
|
||||||
"Takes an audio object and returns a map describing its current status"
|
"Takes an audio object and returns a map describing its current status"
|
||||||
|
|
@ -23,16 +23,17 @@
|
||||||
(re-frame/reg-fx
|
(re-frame/reg-fx
|
||||||
:play-song
|
:play-song
|
||||||
(fn [song-url]
|
(fn [song-url]
|
||||||
(some-> @current-audio .pause)
|
(when-not @audio
|
||||||
(let [audio (js/Audio. song-url)]
|
(reset! audio (js/Audio.))
|
||||||
(reset! current-audio audio)
|
(attach-listeners! @audio))
|
||||||
(attach-listeners! audio)
|
(.pause @audio)
|
||||||
(.play audio))))
|
(set! (.-src @audio) song-url)
|
||||||
|
(.play @audio)))
|
||||||
|
|
||||||
(re-frame/reg-fx
|
(re-frame/reg-fx
|
||||||
:toggle-play-pause
|
:toggle-play-pause
|
||||||
(fn [_]
|
(fn [_]
|
||||||
(when-let [a @current-audio]
|
(let [a @audio]
|
||||||
(if (.-paused a)
|
(if (.-paused a)
|
||||||
(.play a)
|
(.play a)
|
||||||
(.pause a)))))
|
(.pause a)))))
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,8 @@
|
||||||
[ajax.core :as ajax]
|
[ajax.core :as ajax]
|
||||||
[airsonic-ui.routes :as routes]
|
[airsonic-ui.routes :as routes]
|
||||||
[airsonic-ui.db :as db]
|
[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
|
;; this is where all of the event handling takes place; the names put the events into
|
||||||
;; the following categories:
|
;; the following categories:
|
||||||
|
|
@ -75,19 +76,43 @@
|
||||||
|
|
||||||
;; musique
|
;; 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
|
(re-frame/reg-event-fx
|
||||||
::play-song
|
; sets up the db, starts to play a song and adds the rest to a playlist
|
||||||
(fn [{:keys [db]} [_ song]]
|
::play-songs
|
||||||
; sets up the db and starts to play a song
|
(fn [{:keys [db]} [_ songs song]]
|
||||||
(let [song-url (api/url "stream" (merge {:id (:id song)}
|
{:play-song (->song-url song (:login db))
|
||||||
(:login db)))]
|
:db (-> db
|
||||||
{:play-song song-url
|
(assoc-in [:currently-playing :item] song)
|
||||||
:db (assoc-in db [: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
|
(re-frame/reg-event-fx
|
||||||
::toggle-play-pause
|
::toggle-play-pause
|
||||||
(fn [_ _]
|
(fn [_ _]
|
||||||
; pauses the current song
|
|
||||||
{:toggle-play-pause nil}))
|
{:toggle-play-pause nil}))
|
||||||
|
|
||||||
(re-frame/reg-event-db
|
(re-frame/reg-event-db
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
(ns airsonic-ui.views
|
(ns airsonic-ui.views
|
||||||
(:require [re-frame.core :as re-frame]
|
(:require [re-frame.core :refer [dispatch subscribe]]
|
||||||
[reagent.core :as r]
|
[reagent.core :as r]
|
||||||
[airsonic-ui.routes :as routes]
|
[airsonic-ui.routes :as routes]
|
||||||
[airsonic-ui.events :as events]
|
[airsonic-ui.events :as events]
|
||||||
|
|
@ -21,7 +21,7 @@
|
||||||
[:span "Password"]
|
[:span "Password"]
|
||||||
[:input {:type "password" :name "pass" :on-change #(reset! pass (-> % .-target .-value))}]]
|
[:input {:type "password" :name "pass" :on-change #(reset! pass (-> % .-target .-value))}]]
|
||||||
[:div
|
[: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)
|
;; album list (start page)
|
||||||
|
|
||||||
|
|
@ -43,15 +43,15 @@
|
||||||
|
|
||||||
;; single album
|
;; single album
|
||||||
|
|
||||||
(defn song-item [song]
|
(defn song-item [songs song]
|
||||||
[:div (str (:artist song) " - ")
|
[:div (str (:artist song) " - ")
|
||||||
[:a
|
[:a
|
||||||
{:on-click #(re-frame/dispatch [::events/play-song song])}
|
{:on-click #(dispatch [::events/play-songs songs song])}
|
||||||
(:title song)]])
|
(:title song)]])
|
||||||
|
|
||||||
(defn song-list [songs]
|
(defn song-list [songs]
|
||||||
[:ul (for [[idx song] (map-indexed vector 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]
|
(defn album-detail [content]
|
||||||
[:div
|
[:div
|
||||||
|
|
@ -68,15 +68,15 @@
|
||||||
|
|
||||||
(defn playback-controls []
|
(defn playback-controls []
|
||||||
[:div
|
[:div
|
||||||
[:button "previous"]
|
[:button {:on-click #(dispatch [::events/previous-song])} "previous"]
|
||||||
[:button {:on-click #(re-frame/dispatch [::events/toggle-play-pause])} "play / pause"]
|
[:button {:on-click #(dispatch [::events/toggle-play-pause])} "play / pause"]
|
||||||
[:button "next"]
|
[:button {:on-click #(dispatch [::events/next-song])} "next"]
|
||||||
[:label [:input {:type "checkbox"}] "shuffle"]
|
[:label [:input {:type "checkbox"}] "shuffle"]
|
||||||
[:label [:input {:type "checkbox"}] "repeat"]])
|
[:label [:input {:type "checkbox"}] "repeat"]])
|
||||||
|
|
||||||
(defn bottom-bar []
|
(defn bottom-bar []
|
||||||
[:div
|
[:div
|
||||||
(if-let [currently-playing @(re-frame/subscribe [::subs/currently-playing])]
|
(if-let [currently-playing @(subscribe [::subs/currently-playing])]
|
||||||
[current-song-info currently-playing]
|
[current-song-info currently-playing]
|
||||||
[:span "Currently no song selected"])
|
[:span "Currently no song selected"])
|
||||||
[playback-controls]])
|
[playback-controls]])
|
||||||
|
|
@ -84,18 +84,18 @@
|
||||||
;; putting everything together
|
;; putting everything together
|
||||||
|
|
||||||
(defn app [route params query]
|
(defn app [route params query]
|
||||||
(let [login @(re-frame/subscribe [::subs/login])
|
(let [login @(subscribe [::subs/login])
|
||||||
content @(re-frame/subscribe [::subs/current-content])]
|
content @(subscribe [::subs/current-content])]
|
||||||
[:div
|
[:div
|
||||||
[:span (str "Currently logged in as " (:u login))]
|
[:span (str "Currently logged in as " (:u login))]
|
||||||
(case route
|
(case route
|
||||||
::routes/main [album-list content]
|
::routes/main [album-list content]
|
||||||
::routes/album-view [album-detail 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]]))
|
[bottom-bar]]))
|
||||||
|
|
||||||
(defn main-panel []
|
(defn main-panel []
|
||||||
(let [[route params query] @(re-frame/subscribe [::subs/current-route])]
|
(let [[route params query] @(subscribe [::subs/current-route])]
|
||||||
[:div
|
[:div
|
||||||
[:h1 "Airsonic"]
|
[:h1 "Airsonic"]
|
||||||
(case route
|
(case route
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue