mirror of
https://github.com/heyarne/airsonic-ui.git
synced 2026-05-07 02:33:39 +02:00
Add a real playlist (#20)
* Start implementing playlist Done so far: * Creation * Changing playback mode * Changing repeat mode * Add skipping for linear playlists with all repeat modes * Complete implementation for playlist/next-song * Implement all the playlist skipping functionality * Add functions to enqueue songs * Remove start-idx parameter when constructing playlists * Use namespaced keywords only when modifying external data E.g. songs in the queue
This commit is contained in:
parent
2638378064
commit
1888c3023c
11 changed files with 456 additions and 46 deletions
|
|
@ -1,4 +1,7 @@
|
|||
(ns airsonic-ui.audio
|
||||
(ns airsonic-ui.audio.core
|
||||
"This namespace contains some JS interop code to interact with an audio player
|
||||
and receive information about the current playback status so we can use it in
|
||||
our re-frame app."
|
||||
(:require [re-frame.core :as re-frame]))
|
||||
|
||||
;; TODO: Manage buffering
|
||||
140
src/cljs/airsonic_ui/audio/playlist.cljs
Normal file
140
src/cljs/airsonic_ui/audio/playlist.cljs
Normal file
|
|
@ -0,0 +1,140 @@
|
|||
(ns airsonic-ui.audio.playlist
|
||||
"Implements playlist queues that support different kinds of repetition and
|
||||
song ordering."
|
||||
(:refer-clojure :exclude [peek])
|
||||
(:require [airsonic-ui.utils.helpers :refer [find-where]]
|
||||
[debux.cs.core :refer-macros [dbg]]))
|
||||
|
||||
(defrecord Playlist [queue playback-mode repeat-mode]
|
||||
cljs.core/ICounted
|
||||
(-count [this]
|
||||
(count (:queue this))))
|
||||
|
||||
(defmulti ->playlist
|
||||
"Creates a new playlist that behaves according to the given playback- and
|
||||
repeat-mode parameters."
|
||||
(fn [queue & {:keys [playback-mode #_repeat-mode]}]
|
||||
playback-mode))
|
||||
|
||||
(defn- mark-first-song [queue]
|
||||
(let [[first-idx _] (find-where #(= 0 (:playlist/order %)) queue)]
|
||||
(assoc-in queue [first-idx :playlist/currently-playing??] true)))
|
||||
|
||||
(defmethod ->playlist :linear
|
||||
[queue & {:keys [playback-mode repeat-mode]}]
|
||||
(let [queue (-> (mapv (fn [order song] (assoc song :playlist/order order)) (range) queue)
|
||||
(mark-first-song))]
|
||||
(->Playlist queue playback-mode repeat-mode)))
|
||||
|
||||
(defn- -shuffle-songs [queue]
|
||||
(->> (shuffle (range (count queue)))
|
||||
(mapv (fn [song order] (assoc song :playlist/order order)) queue)))
|
||||
|
||||
(defmethod ->playlist :shuffled
|
||||
[queue & {:keys [playback-mode repeat-mode]}]
|
||||
(let [queue (conj (mapv #(update % :playlist/order inc) (-shuffle-songs (rest queue)))
|
||||
(assoc (first queue) :playlist/order 0 :playlist/currently-playing?? true))]
|
||||
(->Playlist queue playback-mode repeat-mode)))
|
||||
|
||||
(defn set-current-song
|
||||
"Marks a song in the queue as currently playing, given its ID"
|
||||
[playlist next-idx]
|
||||
(let [[current-idx _] (find-where :playlist/currently-playing?? (:queue playlist))]
|
||||
(-> (if current-idx
|
||||
(update-in playlist [:queue current-idx] dissoc :playlist/currently-playing??)
|
||||
playlist)
|
||||
(assoc-in [:queue next-idx :playlist/currently-playing??] true))))
|
||||
|
||||
(defn set-playback-mode
|
||||
"Changes the playback mode of a playlist and re-shuffles it if necessary"
|
||||
[playlist playback-mode]
|
||||
(if (= playback-mode :shuffled)
|
||||
;; for shuffled playlists we reorder the songs make sure that the currently
|
||||
;; playing song has order 0
|
||||
(let [playlist (->playlist (:queue playlist) :playback-mode playback-mode :repeat-mode (:repeat-mode playlist))
|
||||
[current-idx current-song] (find-where :playlist/currently-playing?? (:queue playlist))
|
||||
[swap-idx _] (find-where #(= 0 (:playlist/order %)) (:queue playlist))]
|
||||
(-> (assoc-in playlist [:queue current-idx :playlist/order] 0)
|
||||
(assoc-in [:queue swap-idx :playlist/order] (:playlist/order current-song))))
|
||||
;; for linear songs we just make sure that the current does not change
|
||||
(let [[current-idx _] (find-where :playlist/currently-playing?? (:queue playlist))]
|
||||
(-> (->playlist (:queue playlist) :playback-mode playback-mode :repeat-mode (:repeat-mode playlist))
|
||||
(set-current-song current-idx)))))
|
||||
|
||||
(defn set-repeat-mode
|
||||
"Allows to change the way the next and previous song of a playlist is selected"
|
||||
[playlist repeat-mode]
|
||||
(assoc playlist :repeat-mode repeat-mode))
|
||||
|
||||
(defn peek
|
||||
"Returns the song in a playlist that is currently playing"
|
||||
[playlist]
|
||||
(->> (:queue playlist)
|
||||
(filter :playlist/currently-playing??)
|
||||
(first)))
|
||||
|
||||
(defmulti next-song "Advances the currently playing song" :repeat-mode)
|
||||
|
||||
(defmethod next-song :repeat-none
|
||||
[playlist]
|
||||
;; this is pretty easy; get the next song and stop playing at the at
|
||||
(let [[current-idx current-song] (find-where :playlist/currently-playing?? (:queue playlist))
|
||||
[next-idx _] (find-where #(= (:playlist/order %) (inc (:playlist/order current-song))) (:queue playlist))]
|
||||
(update playlist :queue
|
||||
(fn [queue]
|
||||
(cond-> queue
|
||||
current-idx (update current-idx dissoc :playlist/currently-playing??)
|
||||
next-idx (assoc-in [next-idx :playlist/currently-playing??] true))))))
|
||||
|
||||
(defmethod next-song :repeat-single [playlist] playlist)
|
||||
|
||||
(defmethod next-song :repeat-all
|
||||
[playlist]
|
||||
(let [[current-idx current-song] (find-where :playlist/currently-playing?? (:queue playlist))
|
||||
[next-idx _] (find-where #(= (:playlist/order %) (inc (:playlist/order current-song))) (:queue playlist))]
|
||||
(-> (update-in playlist [:queue current-idx] dissoc :playlist/currently-playing??)
|
||||
(update :queue
|
||||
(fn [queue]
|
||||
;; we need special treatment here if we're playing the last song and
|
||||
;; have a shuffled playlist because we need to re-shuffle
|
||||
(if next-idx
|
||||
(assoc-in queue [next-idx :playlist/currently-playing??] true)
|
||||
(case (:playback-mode playlist)
|
||||
:linear (assoc-in queue [0 :playlist/currently-playing??] true)
|
||||
:shuffled (let [queue' (-shuffle-songs queue)
|
||||
[next-idx _] (find-where #(= (:playlist/order %) 0) queue')]
|
||||
(assoc-in queue' [next-idx :playlist/currently-playing??] true)))))))))
|
||||
|
||||
(defmulti previous-song "Goes back along the playback queue" :repeat-mode)
|
||||
|
||||
(defmethod previous-song :repeat-single [playlist] playlist)
|
||||
|
||||
(defmethod previous-song :repeat-none [playlist]
|
||||
(let [[current-idx current-song] (find-where :playlist/currently-playing?? (:queue playlist))
|
||||
[next-idx _] (find-where #(= (:playlist/order %) (dec (:playlist/order current-song))) (:queue playlist))]
|
||||
(set-current-song playlist (or next-idx current-idx))))
|
||||
|
||||
(defmethod previous-song :repeat-all [playlist]
|
||||
(let [[_ current-song] (find-where :playlist/currently-playing?? (:queue playlist))
|
||||
[next-idx _] (find-where #(= (:playlist/order %)
|
||||
(rem (dec (:playlist/order current-song)) (count playlist)))
|
||||
(:queue playlist))]
|
||||
(if next-idx
|
||||
(set-current-song playlist next-idx)
|
||||
(if (= :shuffled (:playback-mode playlist))
|
||||
(let [highest-order (dec (count playlist))
|
||||
playlist (update playlist :queue -shuffle-songs)
|
||||
[last-idx _] (find-where #(= (:playlist/order %) highest-order) (:queue playlist))]
|
||||
(set-current-song playlist last-idx))
|
||||
(set-current-song playlist (mod (dec (:playlist/order current-song)) (count playlist)))))))
|
||||
|
||||
(defn enqueue-last [playlist song]
|
||||
(let [highest-order (last (sort (map :playlist/order (:queue playlist))))]
|
||||
(update playlist :queue conj (assoc song :playlist/order (inc highest-order)))))
|
||||
|
||||
(defn enqueue-next [playlist song]
|
||||
(let [[_ current-song] (find-where :playlist/currently-playing?? (:queue playlist))]
|
||||
(update playlist :queue
|
||||
(fn [queue]
|
||||
(-> (mapv #(if (> (:playlist/order %) (:playlist/order current-song)) (update % :playlist/order inc) %) queue)
|
||||
(conj (assoc song :playlist/order (inc (:playlist/order current-song)))))))))
|
||||
|
|
@ -5,7 +5,7 @@
|
|||
[day8.re-frame.http-fx]
|
||||
[akiroz.re-frame.storage :as storage]
|
||||
;; our app
|
||||
[airsonic-ui.audio] ; <- just registers effects here
|
||||
[airsonic-ui.audio.core] ; <- just registers effects here
|
||||
[airsonic-ui.events :as events]
|
||||
[airsonic-ui.views :as views]
|
||||
[airsonic-ui.config :as config]))
|
||||
|
|
|
|||
|
|
@ -3,8 +3,7 @@
|
|||
[ajax.core :as ajax]
|
||||
[airsonic-ui.routes :as routes]
|
||||
[airsonic-ui.db :as db]
|
||||
[airsonic-ui.utils.api :as api]
|
||||
[day8.re-frame.tracing :refer-macros [fn-traced defn-traced]])) ; <- useful to debug handlers
|
||||
[airsonic-ui.utils.api :as api]))
|
||||
|
||||
(re-frame/reg-fx
|
||||
;; a simple effect to keep println statements out of our event handlers
|
||||
|
|
|
|||
10
src/cljs/airsonic_ui/utils/helpers.cljs
Normal file
10
src/cljs/airsonic_ui/utils/helpers.cljs
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
(ns airsonic-ui.utils.helpers
|
||||
"Assorted helper functions")
|
||||
|
||||
(defn find-where
|
||||
"Returns the the first item in `coll` with its index for which `(p song)`
|
||||
is truthy"
|
||||
[p coll]
|
||||
(->> (map-indexed vector coll)
|
||||
(reduce (fn [_ [idx song]]
|
||||
(when (p song) (reduced [idx song]))) nil)))
|
||||
Loading…
Add table
Add a link
Reference in a new issue