mirror of
https://github.com/heyarne/airsonic-ui.git
synced 2026-05-06 18:33:38 +02:00
Volume Controls (#46)
* Implement always-visible volume controls * Implement toggling of volume controls * Change icon based on volume level * Add volume control keyboard shortcuts * Check left mouse button for all events fired when changing the volume
This commit is contained in:
parent
058b8377a8
commit
53748941c0
5 changed files with 152 additions and 15 deletions
|
|
@ -23,14 +23,15 @@
|
||||||
:current-src (.-currentSrc elem)
|
:current-src (.-currentSrc elem)
|
||||||
:current-time (.-currentTime elem)
|
:current-time (.-currentTime elem)
|
||||||
:seekable (normalize-time-ranges (.-seekable elem))
|
:seekable (normalize-time-ranges (.-seekable elem))
|
||||||
:buffered (normalize-time-ranges (.-buffered elem))})
|
:buffered (normalize-time-ranges (.-buffered elem))
|
||||||
|
:volume (.-volume elem)})
|
||||||
|
|
||||||
; explanation of these events: https://developer.mozilla.org/en-US/Apps/Fundamentals/Audio_and_video_delivery/Cross-browser_audio_basics
|
; explanation of these events: https://developer.mozilla.org/en-US/Apps/Fundamentals/Audio_and_video_delivery/Cross-browser_audio_basics
|
||||||
|
|
||||||
|
|
||||||
(defn attach-listeners! [el]
|
(defn attach-listeners! [el]
|
||||||
(let [emit-audio-update (throttle #(rf/dispatch [:audio/update (->status el)]) 16)]
|
(let [emit-audio-update (throttle #(rf/dispatch [:audio/update (->status el)]) 16)]
|
||||||
(doseq [event ["loadstart" "progress" "play" "timeupdate" "pause"]]
|
(doseq [event ["loadstart" "progress" "play" "timeupdate" "pause" "volumechange"]]
|
||||||
(.addEventListener el event emit-audio-update))))
|
(.addEventListener el event emit-audio-update))))
|
||||||
|
|
||||||
;; effects to be fired from event handlers
|
;; effects to be fired from event handlers
|
||||||
|
|
@ -71,6 +72,27 @@
|
||||||
(set! (. @audio -currentTime)
|
(set! (. @audio -currentTime)
|
||||||
(* percentage duration))))
|
(* percentage duration))))
|
||||||
|
|
||||||
|
(defn- set-volume! [volume]
|
||||||
|
(set! (.-volume @audio) volume))
|
||||||
|
|
||||||
|
(rf/reg-fx
|
||||||
|
:audio/set-volume
|
||||||
|
(fn [percentage]
|
||||||
|
(when @audio
|
||||||
|
(set-volume! percentage))))
|
||||||
|
|
||||||
|
(rf/reg-fx
|
||||||
|
:audio/increase-volume
|
||||||
|
(fn [_]
|
||||||
|
(when-let [vol (some-> @audio .-volume)]
|
||||||
|
(set-volume! (min 1 (+ vol 0.05))))))
|
||||||
|
|
||||||
|
(rf/reg-fx
|
||||||
|
:audio/decrease-volume
|
||||||
|
(fn [_]
|
||||||
|
(when-let [vol (some-> @audio .-volume)]
|
||||||
|
(set-volume! (max 0 (- vol 0.05))))))
|
||||||
|
|
||||||
;; subscriptions
|
;; subscriptions
|
||||||
|
|
||||||
(defn summary
|
(defn summary
|
||||||
|
|
@ -87,7 +109,7 @@
|
||||||
|
|
||||||
(rf/reg-sub
|
(rf/reg-sub
|
||||||
:audio/playlist
|
:audio/playlist
|
||||||
(fn [_ _] (rf/subscribe [:audio/summary]))
|
:<- [:audio/summary]
|
||||||
playlist)
|
playlist)
|
||||||
|
|
||||||
(defn current-song
|
(defn current-song
|
||||||
|
|
@ -98,7 +120,7 @@
|
||||||
|
|
||||||
(rf/reg-sub
|
(rf/reg-sub
|
||||||
:audio/current-song
|
:audio/current-song
|
||||||
(fn [_ _] (rf/subscribe [:audio/playlist]))
|
:<- [:audio/playlist]
|
||||||
current-song)
|
current-song)
|
||||||
|
|
||||||
(defn playback-status
|
(defn playback-status
|
||||||
|
|
@ -108,7 +130,7 @@
|
||||||
|
|
||||||
(rf/reg-sub
|
(rf/reg-sub
|
||||||
:audio/playback-status
|
:audio/playback-status
|
||||||
(fn [_ _] (rf/subscribe [:audio/summary]))
|
:<- [:audio/summary]
|
||||||
playback-status)
|
playback-status)
|
||||||
|
|
||||||
(defn is-playing?
|
(defn is-playing?
|
||||||
|
|
@ -119,5 +141,5 @@
|
||||||
|
|
||||||
(rf/reg-sub
|
(rf/reg-sub
|
||||||
:audio/is-playing?
|
:audio/is-playing?
|
||||||
(fn [_ _] (rf/subscribe [:audio/playback-status]))
|
:<- [:audio/playback-status]
|
||||||
is-playing?)
|
is-playing?)
|
||||||
|
|
|
||||||
|
|
@ -67,3 +67,18 @@
|
||||||
(fn [{:keys [db]} [_ percentage]]
|
(fn [{:keys [db]} [_ percentage]]
|
||||||
(let [duration (:duration (playlist/peek (get-in db [:audio :playlist])))]
|
(let [duration (:duration (playlist/peek (get-in db [:audio :playlist])))]
|
||||||
{:audio/seek [percentage duration]})))
|
{:audio/seek [percentage duration]})))
|
||||||
|
|
||||||
|
(rf/reg-event-fx
|
||||||
|
:audio-player/set-volume
|
||||||
|
(fn [_ [_ percentage]]
|
||||||
|
{:audio/set-volume percentage}))
|
||||||
|
|
||||||
|
(rf/reg-event-fx
|
||||||
|
:audio-player/increase-volume
|
||||||
|
(fn [_ _]
|
||||||
|
{:audio/increase-volume nil}))
|
||||||
|
|
||||||
|
(rf/reg-event-fx
|
||||||
|
:audio-player/decrease-volume
|
||||||
|
(fn [_ _]
|
||||||
|
{:audio/decrease-volume nil}))
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
(ns airsonic-ui.components.audio-player.views
|
(ns airsonic-ui.components.audio-player.views
|
||||||
(:require [re-frame.core :refer [subscribe dispatch]]
|
(:require [re-frame.core :refer [subscribe dispatch]]
|
||||||
|
[reagent.core :as r]
|
||||||
[airsonic-ui.routes :as routes]
|
[airsonic-ui.routes :as routes]
|
||||||
[airsonic-ui.helpers :as h]
|
[airsonic-ui.helpers :as h]
|
||||||
[airsonic-ui.views.cover :refer [cover]]
|
[airsonic-ui.views.cover :refer [cover]]
|
||||||
|
|
@ -53,7 +54,7 @@
|
||||||
[:span.song-title (:title song)]]]])
|
[:span.song-title (:title song)]]]])
|
||||||
|
|
||||||
(defn playback-controls [is-playing?]
|
(defn playback-controls [is-playing?]
|
||||||
[:div.playback-controls
|
[:div.button-controls.playback-controls
|
||||||
[:div.field.has-addons
|
[:div.field.has-addons
|
||||||
(let [buttons [[:media-step-backward :audio-player/previous-song]
|
(let [buttons [[:media-step-backward :audio-player/previous-song]
|
||||||
[(if is-playing? :media-pause :media-play) :audio-player/toggle-play-pause]
|
[(if is-playing? :media-pause :media-play) :audio-player/toggle-play-pause]
|
||||||
|
|
@ -78,6 +79,48 @@
|
||||||
(second))]
|
(second))]
|
||||||
(h/muted-dispatch [:audio-player/set-repeat-mode next-mode])))
|
(h/muted-dispatch [:audio-player/set-repeat-mode next-mode])))
|
||||||
|
|
||||||
|
(defn set-volume [ev]
|
||||||
|
(when (= 1 (.-buttons ev)) ;; only on left-click
|
||||||
|
(let [y-ratio (/ (.. ev -nativeEvent -offsetY)
|
||||||
|
(.. ev -target getBoundingClientRect -height))]
|
||||||
|
(dispatch [:audio-player/set-volume (- 1 y-ratio)]))))
|
||||||
|
|
||||||
|
(defonce volume-slider-visible? (r/atom false))
|
||||||
|
|
||||||
|
(defn volume-slider [volume]
|
||||||
|
(let [y-pos (* (- 1 volume) 100)]
|
||||||
|
[:svg.volume-bar {:width "100%", :height "100%"}
|
||||||
|
;; the translate(...) makes the 1px rects look smoother
|
||||||
|
[:g {:transform "translate(-0.5,0)"}
|
||||||
|
;; background line
|
||||||
|
[:rect.inactive {:x "50%", :y 0, :width 1, :height "100%"}]
|
||||||
|
;; below are the line and circle that show the current volume
|
||||||
|
[:rect.active {:x "50%", :y (str y-pos "%"),
|
||||||
|
:width 1, :height (str (- 100 y-pos) "%")}]]
|
||||||
|
[:circle.active {:cx "50%", :cy (str y-pos "%"), :r 3}]
|
||||||
|
[:rect.click-dummy {:x 0, :y 0, :width "100%", :height "100%"
|
||||||
|
:on-mouse-down set-volume
|
||||||
|
:on-mouse-up set-volume
|
||||||
|
:on-mouse-move set-volume}]]))
|
||||||
|
|
||||||
|
(def toggle-volume-slider #(swap! volume-slider-visible? not))
|
||||||
|
(def hide-volume-slider #(reset! volume-slider-visible? false))
|
||||||
|
|
||||||
|
(defn volume-controls [playback-status]
|
||||||
|
(let [volume (:volume playback-status)
|
||||||
|
volume-icon (cond
|
||||||
|
(> volume 0.66) :volume-high
|
||||||
|
(> volume 0.1) :volume-low
|
||||||
|
:else :volume-off)]
|
||||||
|
[:div.button-controls.volume-controls
|
||||||
|
(when @volume-slider-visible?
|
||||||
|
[:div.button-menu
|
||||||
|
[:div.button-menu-closer {:on-click hide-volume-slider}]
|
||||||
|
[volume-slider volume]])
|
||||||
|
[:p.control>button.button.is-light
|
||||||
|
{:on-click toggle-volume-slider}
|
||||||
|
[icon volume-icon]]]))
|
||||||
|
|
||||||
(defn playback-mode-controls [playlist]
|
(defn playback-mode-controls [playlist]
|
||||||
(let [{:keys [repeat-mode playback-mode]} playlist
|
(let [{:keys [repeat-mode playback-mode]} playlist
|
||||||
button :p.control>button.button.is-light
|
button :p.control>button.button.is-light
|
||||||
|
|
@ -90,7 +133,7 @@
|
||||||
:repeat-all "Repeating current queue, click to repeat current track"
|
:repeat-all "Repeating current queue, click to repeat current track"
|
||||||
:repeat-single "Repeating current track, click to repeat none"
|
:repeat-single "Repeating current track, click to repeat none"
|
||||||
"Click to repeat current queue")]
|
"Click to repeat current queue")]
|
||||||
[:div.playback-mode-controls
|
[:div.button-controls.playback-mode-controls
|
||||||
[:div.button-group>div.field.has-addons
|
[:div.button-group>div.field.has-addons
|
||||||
^{:key :shuffle-button} [shuffle-button {:on-click (toggle-shuffle playback-mode)
|
^{:key :shuffle-button} [shuffle-button {:on-click (toggle-shuffle playback-mode)
|
||||||
:title "Shuffle"} [icon :random]]
|
:title "Shuffle"} [icon :random]]
|
||||||
|
|
@ -109,6 +152,7 @@
|
||||||
[playback-info current-song playback-status]
|
[playback-info current-song playback-status]
|
||||||
[progress-indicators current-song playback-status]
|
[progress-indicators current-song playback-status]
|
||||||
[playback-controls is-playing?]
|
[playback-controls is-playing?]
|
||||||
|
[volume-controls playback-status]
|
||||||
[playback-mode-controls playlist]]
|
[playback-mode-controls playlist]]
|
||||||
;; not playing anything
|
;; not playing anything
|
||||||
[:p.navbar-item.idle-notification "No audio playing"])]))
|
[:p.navbar-item.idle-notification "No audio playing"])]))
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,12 @@
|
||||||
["→" "Next song"
|
["→" "Next song"
|
||||||
[:audio-player/next-song]
|
[:audio-player/next-song]
|
||||||
[{:keyCode 39}]]
|
[{:keyCode 39}]]
|
||||||
|
["+" "Increase volume"
|
||||||
|
[:audio-player/increase-volume]
|
||||||
|
[{:keyCode 171}]]
|
||||||
|
["-" "Decrease volume"
|
||||||
|
[:audio-player/decrease-volume]
|
||||||
|
[{:keyCode 173}]]
|
||||||
["?" "Show / hide keyboard shortcut help"
|
["?" "Show / hide keyboard shortcut help"
|
||||||
[:bulma.modal.events/toggle :keyboard-shortcuts-help]
|
[:bulma.modal.events/toggle :keyboard-shortcuts-help]
|
||||||
[{:keyCode 63}]]])
|
[{:keyCode 63}]]])
|
||||||
|
|
|
||||||
|
|
@ -122,14 +122,64 @@
|
||||||
fill: $dark-invert
|
fill: $dark-invert
|
||||||
|
|
||||||
// buttons to control current playback and playlist behavior
|
// buttons to control current playback and playlist behavior
|
||||||
.playback-controls,
|
.button-controls
|
||||||
.playback-mode-controls
|
position: relative
|
||||||
flex-shrink: 0
|
flex-shrink: 0
|
||||||
padding-right: .6rem
|
padding-right: .6rem
|
||||||
|
|
||||||
.playback-controls
|
&:first-of-type
|
||||||
padding-left: .6rem
|
padding-left: .6rem
|
||||||
|
|
||||||
|
.button-menu
|
||||||
|
svg.volume-bar
|
||||||
|
overflow: visible
|
||||||
|
|
||||||
|
.inactive
|
||||||
|
fill: $background
|
||||||
|
|
||||||
|
.active
|
||||||
|
fill: $link
|
||||||
|
|
||||||
|
.click-dummy
|
||||||
|
cursor: pointer
|
||||||
|
fill: transparent
|
||||||
|
|
||||||
|
.button-menu-closer
|
||||||
|
// this element is needed so we can have a "click-outside"
|
||||||
|
position: fixed
|
||||||
|
z-index: -1
|
||||||
|
top: 0
|
||||||
|
left: 0
|
||||||
|
right: 0
|
||||||
|
bottom: 0
|
||||||
|
|
||||||
|
.button-menu
|
||||||
|
position: absolute
|
||||||
|
z-index: 100
|
||||||
|
width: 36px
|
||||||
|
bottom: calc(100% + .3em)
|
||||||
|
padding: $button-padding-horizontal $button-padding-horizontal / 2
|
||||||
|
|
||||||
|
border-radius: $radius
|
||||||
|
background: $white
|
||||||
|
color: $dark
|
||||||
|
box-shadow: 0 0 2px rgba(0,0,0,.1), 0 0 4px rgba(0,0,0,.1)
|
||||||
|
|
||||||
|
// little arrow at the bottom
|
||||||
|
&::after
|
||||||
|
position: absolute
|
||||||
|
content: ''
|
||||||
|
display: block
|
||||||
|
width: 6px
|
||||||
|
height: 6px
|
||||||
|
background: inherit
|
||||||
|
top: 100%
|
||||||
|
left: 50%
|
||||||
|
margin-left: -3px
|
||||||
|
margin-top: -3px
|
||||||
|
transform: rotate(45deg)
|
||||||
|
box-shadow: 2px 2px 1px rgba(0,0,0,.1)
|
||||||
|
|
||||||
// preview card for album or artist listings
|
// preview card for album or artist listings
|
||||||
.preview-card
|
.preview-card
|
||||||
.card-content > div,
|
.card-content > div,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue