mirror of
https://github.com/heyarne/airsonic-ui.git
synced 2026-05-06 18:33:38 +02:00
More improvements to the audio player
* Expose shuffle / repeat in interface * Make sure next song gets properly called when a song has ended
This commit is contained in:
parent
840c7edb79
commit
f7042e9ecc
6 changed files with 93 additions and 55 deletions
|
|
@ -180,7 +180,6 @@
|
||||||
; sets up the db, starts to play a song and adds the rest to a playlist
|
; sets up the db, starts to play a song and adds the rest to a playlist
|
||||||
::play-songs
|
::play-songs
|
||||||
(fn [{:keys [db]} [_ songs start-idx]]
|
(fn [{:keys [db]} [_ songs start-idx]]
|
||||||
(println "play-songs called with" start-idx songs)
|
|
||||||
(let [playlist (-> (playlist/->playlist songs :playback-mode :linear :repeat-mode :repeat-all)
|
(let [playlist (-> (playlist/->playlist songs :playback-mode :linear :repeat-mode :repeat-all)
|
||||||
(playlist/set-current-song start-idx))]
|
(playlist/set-current-song start-idx))]
|
||||||
{:audio/play (song-url db (playlist/peek playlist))
|
{:audio/play (song-url db (playlist/peek playlist))
|
||||||
|
|
@ -229,11 +228,14 @@
|
||||||
(fn [_ _]
|
(fn [_ _]
|
||||||
{:audio/toggle-play-pause nil}))
|
{:audio/toggle-play-pause nil}))
|
||||||
|
|
||||||
(re-frame/reg-event-db
|
(defn audio-update
|
||||||
:audio/update
|
"Reacts to audio events fired by the HTML5 audio player and plays the next
|
||||||
(fn [db [_ status]]
|
track if necessary."
|
||||||
; this is coming from HTML5 Audio events
|
[{:keys [db]} [_ status]]
|
||||||
(assoc-in db [:audio :playback-status] status)))
|
(cond-> {:db (assoc-in db [:audio :playback-status] status)}
|
||||||
|
(:ended? status) (assoc :dispatch [::next-song])))
|
||||||
|
|
||||||
|
(re-frame/reg-event-fx :audio/update audio-update)
|
||||||
|
|
||||||
;; ---
|
;; ---
|
||||||
;; routing
|
;; routing
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@
|
||||||
|
|
||||||
[airsonic-ui.views.notifications :refer [notification-list]]
|
[airsonic-ui.views.notifications :refer [notification-list]]
|
||||||
[airsonic-ui.views.breadcrumbs :refer [breadcrumbs]]
|
[airsonic-ui.views.breadcrumbs :refer [breadcrumbs]]
|
||||||
[airsonic-ui.views.bottom-bar :refer [bottom-bar]]
|
[airsonic-ui.views.audio-player :refer [audio-player]]
|
||||||
[airsonic-ui.views.login :refer [login-form]]
|
[airsonic-ui.views.login :refer [login-form]]
|
||||||
[airsonic-ui.views.album :as album]
|
[airsonic-ui.views.album :as album]
|
||||||
[airsonic-ui.views.song :as song]))
|
[airsonic-ui.views.song :as song]))
|
||||||
|
|
@ -61,7 +61,7 @@
|
||||||
::routes/main [most-recent content]
|
::routes/main [most-recent content]
|
||||||
::routes/artist-view [artist-detail content]
|
::routes/artist-view [artist-detail content]
|
||||||
::routes/album-view [album-detail content])]]]
|
::routes/album-view [album-detail content])]]]
|
||||||
[bottom-bar]]))
|
[audio-player]]))
|
||||||
|
|
||||||
(defn main-panel []
|
(defn main-panel []
|
||||||
(let [notifications @(subscribe [::subs/notifications])
|
(let [notifications @(subscribe [::subs/notifications])
|
||||||
|
|
|
||||||
77
src/cljs/airsonic_ui/views/audio_player.cljs
Normal file
77
src/cljs/airsonic_ui/views/audio_player.cljs
Normal file
|
|
@ -0,0 +1,77 @@
|
||||||
|
(ns airsonic-ui.views.audio-player
|
||||||
|
(:require [re-frame.core :refer [subscribe]]
|
||||||
|
[airsonic-ui.utils.helpers :refer [dispatch]]
|
||||||
|
[airsonic-ui.events :as events]
|
||||||
|
[airsonic-ui.views.cover :refer [cover]]
|
||||||
|
[airsonic-ui.views.icon :refer [icon]]))
|
||||||
|
|
||||||
|
;; currently playing / coming next / audio controls...
|
||||||
|
|
||||||
|
(defn current-song-info [song status]
|
||||||
|
[:article
|
||||||
|
[:div (:artist song) " - " (:title song)]
|
||||||
|
;; FIXME: Sometimes items don't have a duration
|
||||||
|
[:progress.progress.is-tiny {:value (:current-time status)
|
||||||
|
:max (:duration song)}]])
|
||||||
|
|
||||||
|
(defn song-controls [is-playing?]
|
||||||
|
[:div.field.has-addons
|
||||||
|
(let [buttons [[:media-step-backward ::events/previous-song]
|
||||||
|
[(if is-playing? :media-pause :media-play) ::events/toggle-play-pause]
|
||||||
|
[:media-step-forward ::events/next-song]]]
|
||||||
|
(map (fn [[icon-glyph event]]
|
||||||
|
^{:key icon-glyph} [:p.control>button.button.is-light
|
||||||
|
{:on-click (dispatch [event])}
|
||||||
|
[icon icon-glyph]])
|
||||||
|
buttons))])
|
||||||
|
|
||||||
|
(defn- add-classes
|
||||||
|
"Adds one or more classes to a hiccup keyword"
|
||||||
|
[elem & classes]
|
||||||
|
(keyword (apply str (name elem) (->> (filter identity classes)
|
||||||
|
(map #(str "." (name %)))))))
|
||||||
|
|
||||||
|
(defn- toggle-shuffle [playback-mode]
|
||||||
|
(dispatch [::events/set-playback-mode (if (= playback-mode :shuffled)
|
||||||
|
:linear :shuffled)]))
|
||||||
|
|
||||||
|
(defn- advance-repeat-mode [current-mode]
|
||||||
|
(let [modes (cycle '(:repeat-none :repeat-all :repeat-single))
|
||||||
|
next-mode (->> (drop-while (partial not= current-mode) modes)
|
||||||
|
(second))]
|
||||||
|
(dispatch [::events/set-repeat-mode next-mode])))
|
||||||
|
|
||||||
|
(defn playback-mode-controls [playlist]
|
||||||
|
(let [{:keys [repeat-mode playback-mode]} playlist
|
||||||
|
button :p.control>button.button.is-light
|
||||||
|
shuffle-button (add-classes button (when (= playback-mode :shuffled) :is-primary))
|
||||||
|
repeat-button (add-classes button (case repeat-mode
|
||||||
|
:repeat-single :is-info
|
||||||
|
:repeat-all :is-primary
|
||||||
|
nil))]
|
||||||
|
[:div.field.has-addons
|
||||||
|
^{:key :shuffle-button} [shuffle-button {:on-click (toggle-shuffle playback-mode)} [icon :random]]
|
||||||
|
^{:key :repeat-button} [repeat-button {:on-click (advance-repeat-mode repeat-mode)} [icon :loop]]]))
|
||||||
|
|
||||||
|
(def logo-url "https://airsonic.github.io/airsonic-ui/assets/images/logo/airsonic-light-350x100.png")
|
||||||
|
|
||||||
|
(defn audio-player []
|
||||||
|
(let [current-song @(subscribe [:audio/current-song])
|
||||||
|
playlist @(subscribe [:audio/playlist])
|
||||||
|
playback-status @(subscribe [:audio/playback-status])
|
||||||
|
is-playing? @(subscribe [:audio/is-playing?])]
|
||||||
|
[:nav.navbar.is-fixed-bottom.playback-area
|
||||||
|
[:div.navbar-brand
|
||||||
|
[:div.navbar-item
|
||||||
|
[:img {:src logo-url}]]]
|
||||||
|
[:div.navbar-menu.is-active
|
||||||
|
(if current-song
|
||||||
|
;; show song info
|
||||||
|
[:section.level.audio-interaction
|
||||||
|
[:div.level-left>article.media
|
||||||
|
[:div.media-left [cover current-song 48]]
|
||||||
|
[:div.media-content [current-song-info current-song playback-status]]]
|
||||||
|
[:div.level-right [song-controls is-playing?]]
|
||||||
|
[:div.level-right [playback-mode-controls playlist]]]
|
||||||
|
;; not playing anything
|
||||||
|
[:p.idle-notification "Currently no song selected"])]]))
|
||||||
|
|
@ -1,46 +0,0 @@
|
||||||
(ns airsonic-ui.views.bottom-bar
|
|
||||||
(:require [re-frame.core :refer [dispatch subscribe]]
|
|
||||||
[airsonic-ui.events :as events]
|
|
||||||
[airsonic-ui.views.cover :refer [cover]]
|
|
||||||
[airsonic-ui.views.icon :refer [icon]]))
|
|
||||||
|
|
||||||
;; currently playing / coming next / audio controls...
|
|
||||||
|
|
||||||
(defn current-song-info [song status]
|
|
||||||
[:article
|
|
||||||
[:div (:artist song) " - " (:title song)]
|
|
||||||
;; FIXME: Sometimes items don't have a duration
|
|
||||||
[:progress.progress.is-tiny {:value (:current-time status)
|
|
||||||
:max (:duration song)}]])
|
|
||||||
|
|
||||||
(defn playback-controls [is-playing?]
|
|
||||||
[:div.field.has-addons
|
|
||||||
(let [buttons [[:media-step-backward ::events/previous-song]
|
|
||||||
[(if is-playing? :media-pause :media-play) ::events/toggle-play-pause]
|
|
||||||
[:media-step-forward ::events/next-song]]]
|
|
||||||
(map (fn [[icon-glyph event]]
|
|
||||||
^{:key icon-glyph} [:p.control>button.button.is-light
|
|
||||||
{:on-click #(dispatch [event])}
|
|
||||||
[icon icon-glyph]])
|
|
||||||
buttons))])
|
|
||||||
|
|
||||||
(def logo-url "https://airsonic.github.io/airsonic-ui/assets/images/logo/airsonic-light-350x100.png")
|
|
||||||
|
|
||||||
(defn bottom-bar []
|
|
||||||
(let [current-song @(subscribe [:audio/current-song])
|
|
||||||
playback-status @(subscribe [:audio/playback-status])
|
|
||||||
is-playing? @(subscribe [:audio/is-playing?])]
|
|
||||||
[:nav.navbar.is-fixed-bottom.playback-area
|
|
||||||
[:div.navbar-brand
|
|
||||||
[:div.navbar-item
|
|
||||||
[:img {:src logo-url}]]]
|
|
||||||
[:div.navbar-menu.is-active
|
|
||||||
(if current-song
|
|
||||||
;; show song info
|
|
||||||
[:section.level.audio-interaction
|
|
||||||
[:div.level-left>article.media
|
|
||||||
[:div.media-left [cover current-song 48]]
|
|
||||||
[:div.media-content [current-song-info current-song playback-status]]]
|
|
||||||
[:div.level-right [playback-controls is-playing?]]]
|
|
||||||
;; not playing anything
|
|
||||||
[:p.idle-notification "Currently no song selected"])]]))
|
|
||||||
|
|
@ -28,4 +28,4 @@
|
||||||
[:td>a {:title "Play last"
|
[:td>a {:title "Play last"
|
||||||
:href "#"
|
:href "#"
|
||||||
:on-click (dispatch [::events/enqueue-last song])}
|
:on-click (dispatch [::events/enqueue-last song])}
|
||||||
[icon :arrow-thick-right]]])])
|
[icon :caret-right]]])])
|
||||||
|
|
|
||||||
|
|
@ -136,3 +136,8 @@
|
||||||
(testing "Should automatically remove a message after a while"
|
(testing "Should automatically remove a message after a while"
|
||||||
(let [fx (events/show-notification {} [:_ :info "This is a notification"])]
|
(let [fx (events/show-notification {} [:_ :info "This is a notification"])]
|
||||||
(is (= :notification/hide (-> (:dispatch-later fx) first :dispatch first))))))
|
(is (= :notification/hide (-> (:dispatch-later fx) first :dispatch first))))))
|
||||||
|
|
||||||
|
(deftest song-has-ended
|
||||||
|
(testing "Should play the next song when current song has ended"
|
||||||
|
(is (not (dispatches? (events/audio-update {} [:audio/update {:ended? false}]) ::events/next-song))))
|
||||||
|
(is (dispatches? (events/audio-update {} [:audio/update {:ended? true}]) ::events/next-song)))
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue