mirror of
https://github.com/heyarne/airsonic-ui.git
synced 2026-05-07 02:33:39 +02:00
Implement progress bar with html elements, fixes #39
This commit is contained in:
parent
afef13082e
commit
a9fbe5c741
2 changed files with 81 additions and 79 deletions
|
|
@ -1,81 +1,34 @@
|
||||||
(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]]
|
||||||
[airsonic-ui.routes :as routes]
|
[airsonic-ui.routes :as routes]
|
||||||
[airsonic-ui.components.highres-canvas.views :refer [canvas]]
|
|
||||||
[airsonic-ui.helpers :refer [add-classes muted-dispatch]]
|
[airsonic-ui.helpers :refer [add-classes muted-dispatch]]
|
||||||
[airsonic-ui.views.cover :refer [cover]]
|
[airsonic-ui.views.cover :refer [cover]]
|
||||||
[airsonic-ui.views.icon :refer [icon]]))
|
[airsonic-ui.views.icon :refer [icon]]))
|
||||||
|
|
||||||
;; currently playing / coming next / audio controls...
|
;; currently playing / coming next / audio controls...
|
||||||
;; FIXME: Sometimes items don't have a duration
|
|
||||||
|
|
||||||
(def progress-bar-color "rgb(93,93,93)")
|
|
||||||
(def progress-bar-color-buffered "rgb(143,143,143)")
|
|
||||||
(def progress-bar-color-active "whitesmoke")
|
|
||||||
|
|
||||||
(defn draw-progress [ctx current-time buffered duration]
|
|
||||||
(let [width (.. ctx -canvas -clientWidth)
|
|
||||||
height (.. ctx -canvas -clientHeight)
|
|
||||||
padding 5
|
|
||||||
buffered-x (+ padding (* (- width (* 2 padding)) (min 1 (/ buffered duration))))
|
|
||||||
current-x (+ padding (* (- width (* 2 padding)) (min 1 (/ current-time duration))))]
|
|
||||||
;; vertically center everything
|
|
||||||
(.translate ctx 0.5 (+ (Math/ceil (/ height 2)) 0.5))
|
|
||||||
;; draw complete bar
|
|
||||||
(set! (.-strokeStyle ctx) progress-bar-color)
|
|
||||||
(doto ctx
|
|
||||||
(.beginPath)
|
|
||||||
(.moveTo padding 0)
|
|
||||||
(.lineTo (- width (* 2 padding)) 0)
|
|
||||||
(.stroke))
|
|
||||||
;; draw the buffered part
|
|
||||||
(set! (.-strokeStyle ctx) progress-bar-color-buffered)
|
|
||||||
(doto ctx
|
|
||||||
(.beginPath)
|
|
||||||
(.moveTo padding 0)
|
|
||||||
(.lineTo buffered-x 0)
|
|
||||||
(.stroke))
|
|
||||||
;; draw the part that's already played
|
|
||||||
(set! (.-strokeStyle ctx) progress-bar-color-active)
|
|
||||||
(doto ctx
|
|
||||||
(.beginPath)
|
|
||||||
(.moveTo padding 0)
|
|
||||||
(.lineTo current-x 0)
|
|
||||||
(.stroke))
|
|
||||||
;; draw a dot marking the current time
|
|
||||||
(set! (.-fillStyle ctx) progress-bar-color-active)
|
|
||||||
(doto ctx
|
|
||||||
(.beginPath)
|
|
||||||
(.arc current-x 0 (/ padding 2) 0 (* Math/PI 2))
|
|
||||||
(.fill))))
|
|
||||||
|
|
||||||
(defn current-progress [current-time buffered duration]
|
|
||||||
[canvas {:class "current-progress-canvas"
|
|
||||||
:draw #(draw-progress % current-time buffered duration)}])
|
|
||||||
|
|
||||||
;; FIXME: It's ugly to have the canvas padding and styling scattered everywhere (sass, drawing code above, and here)
|
|
||||||
|
|
||||||
(defn seek
|
(defn seek
|
||||||
"Calculates the position of the click and sets current playback accordingly"
|
"Calculates the position of the click and sets current playback accordingly"
|
||||||
[ev]
|
[ev]
|
||||||
(let [x (- (.. ev -nativeEvent -pageX)
|
(let [x-ratio (/ (.. ev -nativeEvent -layerX)
|
||||||
(.. ev -target getBoundingClientRect -left))
|
(.. ev -target -parentElement getBoundingClientRect -width))]
|
||||||
width (- (.. ev -target -nextElementSibling -clientWidth) 10)] ;; <- 10 = 2 * canvas-padding
|
(dispatch [:audio-player/seek x-ratio])))
|
||||||
(dispatch [:audio-player/seek (/ x width)])))
|
|
||||||
|
|
||||||
(defn buffered-part
|
(defn- ratio->width [ratio]
|
||||||
[buffered duration]
|
(str (.toFixed (* 100 ratio) 2) "%"))
|
||||||
(let [width (min 100 (* (/ buffered duration) 100))]
|
|
||||||
[:div.buffered-part {:on-click seek
|
|
||||||
:style {:width (str "calc(" width "% - 1rem - 10px)")}}]))
|
|
||||||
|
|
||||||
(defn progress-bar [song status]
|
(defn progress-bar [song status]
|
||||||
(let [current-time (:current-time status)
|
(let [current-time (:current-time status)
|
||||||
buffered (:buffered status)
|
buffered (:buffered status)
|
||||||
duration (:duration song)]
|
duration (:duration song)
|
||||||
[:article.progress-bar
|
buffered-width (ratio->width (/ buffered duration))
|
||||||
[buffered-part buffered duration]
|
played-width (ratio->width (/ current-time duration))]
|
||||||
[current-progress current-time buffered duration]]))
|
[:article.progress-bar {:aria-hidden "true"}
|
||||||
|
[:div.complete-song]
|
||||||
|
[:div.buffered-part {:style {:width buffered-width}
|
||||||
|
:on-click seek}]
|
||||||
|
[:div.played-back {:style {:width played-width}}
|
||||||
|
[:div.played-back-knob]]]))
|
||||||
|
|
||||||
(defn playback-info [song status]
|
(defn playback-info [song status]
|
||||||
[:a.playback-info.media
|
[:a.playback-info.media
|
||||||
|
|
@ -143,8 +96,8 @@
|
||||||
;; show song info, controls, progress bar, etc.
|
;; show song info, controls, progress bar, etc.
|
||||||
[:section.audio-interaction
|
[:section.audio-interaction
|
||||||
[playback-info current-song playback-status]
|
[playback-info current-song playback-status]
|
||||||
[playback-controls is-playing?]
|
|
||||||
[progress-bar current-song playback-status]
|
[progress-bar current-song playback-status]
|
||||||
|
[playback-controls is-playing?]
|
||||||
[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"])]]))
|
||||||
|
|
|
||||||
|
|
@ -18,11 +18,14 @@
|
||||||
+loader
|
+loader
|
||||||
|
|
||||||
// bottom bar
|
// bottom bar
|
||||||
|
.has-navbar-bottom
|
||||||
|
padding-bottom: 64px
|
||||||
|
|
||||||
.audio-player
|
.audio-player
|
||||||
// first clear some of that navigation styling
|
// first clear some of that navigation styling
|
||||||
background-color: $dark
|
background-color: $dark
|
||||||
color: $light
|
color: $light
|
||||||
min-height: 0
|
min-height: 64px
|
||||||
|
|
||||||
.navbar-menu
|
.navbar-menu
|
||||||
padding: 0
|
padding: 0
|
||||||
|
|
@ -37,6 +40,7 @@
|
||||||
// ... or with all the bells and whistles
|
// ... or with all the bells and whistles
|
||||||
.audio-interaction
|
.audio-interaction
|
||||||
display: flex
|
display: flex
|
||||||
|
flex-grow: 1
|
||||||
align-items: center
|
align-items: center
|
||||||
|
|
||||||
.playback-info
|
.playback-info
|
||||||
|
|
@ -45,39 +49,84 @@
|
||||||
flex-grow: 1
|
flex-grow: 1
|
||||||
color: inherit
|
color: inherit
|
||||||
|
|
||||||
.artist-and-title > *
|
.media-left
|
||||||
display: inline-block
|
margin-right: .6rem
|
||||||
white-space: nowrap
|
|
||||||
width: 100%
|
|
||||||
text-overflow: ellipsis
|
|
||||||
|
|
||||||
|
.artist-and-title
|
||||||
|
margin-right: .6rem
|
||||||
|
|
||||||
|
.artist,
|
||||||
|
.song-title
|
||||||
|
display: block
|
||||||
|
white-space: nowrap
|
||||||
|
width: 100%
|
||||||
|
max-width: 100%
|
||||||
|
overflow: hidden
|
||||||
|
text-overflow: ellipsis
|
||||||
|
|
||||||
.progress-bar
|
.progress-bar
|
||||||
// hide progress bar on mobile
|
// hide progress bar on mobile
|
||||||
display: none
|
display: none
|
||||||
@include =tablet
|
+tablet
|
||||||
display: block
|
display: block
|
||||||
|
|
||||||
flex-grow: 1
|
flex-grow: 3
|
||||||
position: relative
|
position: relative
|
||||||
|
height: 1rem
|
||||||
|
|
||||||
|
.complete-song,
|
||||||
|
.buffered-part,
|
||||||
|
.played-back
|
||||||
|
height: 1rem
|
||||||
|
position: absolute
|
||||||
|
top: 0
|
||||||
|
left: 0
|
||||||
|
|
||||||
|
// these are the actual bars
|
||||||
|
&:after
|
||||||
|
content: ''
|
||||||
|
display: block
|
||||||
|
height: 1px
|
||||||
|
position: relative
|
||||||
|
top: 50%
|
||||||
|
|
||||||
|
.complete-song
|
||||||
|
width: 100%
|
||||||
|
|
||||||
|
&:after
|
||||||
|
background: rgb(93,93,93)
|
||||||
|
|
||||||
.buffered-part
|
.buffered-part
|
||||||
position: absolute
|
|
||||||
top: .5rem
|
|
||||||
left: calc(.5rem + 5px)
|
|
||||||
height: 1rem
|
|
||||||
cursor: pointer
|
cursor: pointer
|
||||||
|
|
||||||
.current-progress-canvas
|
&:after
|
||||||
display: block
|
background: rgb(143,143,143)
|
||||||
height: 1rem
|
|
||||||
width: 100%
|
.played-back
|
||||||
|
pointer-events: none
|
||||||
|
|
||||||
|
&:after
|
||||||
|
background: whitesmoke
|
||||||
|
|
||||||
|
.played-back-knob
|
||||||
|
position: absolute
|
||||||
|
width: 5px
|
||||||
|
height: 5px
|
||||||
|
left: 100%
|
||||||
|
top: 50%
|
||||||
|
margin-left: -2.5px
|
||||||
|
margin-top: -2.5px
|
||||||
|
background: whitesmoke
|
||||||
|
border-radius: 100%
|
||||||
|
|
||||||
// buttons to control current playback and playlist behavior
|
// buttons to control current playback and playlist behavior
|
||||||
.playback-controls,
|
.playback-controls,
|
||||||
.playback-mode-controls
|
.playback-mode-controls
|
||||||
flex-shrink: 0
|
flex-shrink: 0
|
||||||
padding: .3rem
|
padding-right: .6rem
|
||||||
|
|
||||||
|
.playback-controls
|
||||||
|
padding-left: .6rem
|
||||||
|
|
||||||
// preview card for album or artist listings
|
// preview card for album or artist listings
|
||||||
.preview-card
|
.preview-card
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue