mirror of
https://github.com/heyarne/airsonic-ui.git
synced 2026-05-07 02:33:39 +02:00
Start implementing playlist
Done so far: * Creation * Changing playback mode * Changing repeat mode
This commit is contained in:
parent
80225d46b1
commit
98ddd1b9ca
7 changed files with 193 additions and 43 deletions
3
.joker
3
.joker
|
|
@ -1 +1,2 @@
|
|||
{:known-macros [cljs.test/deftest]}
|
||||
{:known-macros [cljs.test/deftest]
|
||||
:known-namespaces [cljs.core]}
|
||||
|
|
@ -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
|
||||
60
src/cljs/airsonic_ui/audio/playlist.cljs
Normal file
60
src/cljs/airsonic_ui/audio/playlist.cljs
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
(ns airsonic-ui.audio.playlist
|
||||
"Implements playlist queues that support different kinds of repetition and
|
||||
song ordering."
|
||||
(:refer-clojure :exclude [peek]))
|
||||
|
||||
(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 play-idx playback-mode repeat-mode] playback-mode))
|
||||
|
||||
(defn- playlist-queue
|
||||
[queue play-idx]
|
||||
(concat (take play-idx queue)
|
||||
[(assoc (nth queue play-idx) :currently-playing? true)]
|
||||
(drop (inc play-idx) queue)))
|
||||
|
||||
(defmethod ->playlist
|
||||
:playback-mode/linear
|
||||
[queue play-idx playback-mode repeat-mode]
|
||||
(let [queue (->> (playlist-queue queue play-idx)
|
||||
(map (fn [order song] (assoc song :order order))
|
||||
(range (count queue))))]
|
||||
(->Playlist queue playback-mode repeat-mode)))
|
||||
|
||||
(defmethod ->playlist
|
||||
:playback-mode/shuffle
|
||||
[queue play-idx playback-mode repeat-mode]
|
||||
(let [queue (->> (playlist-queue queue play-idx)
|
||||
(map (fn [order song] (assoc song :order order))
|
||||
(shuffle (range (count queue)))))]
|
||||
(->Playlist queue playback-mode repeat-mode)))
|
||||
|
||||
(defn set-playback-mode
|
||||
"Changes the playback mode of a playlist and re-suffles it if necessary"
|
||||
[playlist playback-mode]
|
||||
(let [current-idx (first (keep-indexed (fn [idx item]
|
||||
(when (:currently-playing? item)
|
||||
idx))
|
||||
(:queue playlist)))]
|
||||
(->playlist (:queue playlist) current-idx playback-mode (:repeat-mode playlist))))
|
||||
|
||||
(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 :currently-playing?)
|
||||
(first)))
|
||||
|
||||
(defmulti next-song (juxt :playback-mode :repeat-mode))
|
||||
(defmulti previous-song (juxt :playback-mode :repeat-mode))
|
||||
|
|
@ -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]))
|
||||
|
|
|
|||
23
test/cljs/airsonic_ui/audio/core_test.cljs
Normal file
23
test/cljs/airsonic_ui/audio/core_test.cljs
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
(ns airsonic-ui.audio.core-test
|
||||
(:require [airsonic-ui.audio.core :as audio]
|
||||
[airsonic-ui.audio.playlist-test :as p]
|
||||
[airsonic-ui.fixtures :as fixtures]
|
||||
[cljs.test :refer [deftest testing is]]))
|
||||
|
||||
(enable-console-print!)
|
||||
|
||||
(deftest current-song-subscription
|
||||
(letfn [(current-song [db]
|
||||
(-> (audio/summary db [:audio/summary])
|
||||
(audio/current-song [:audio/current-song])))]
|
||||
(testing "Should provide information about the song"
|
||||
(= fixtures/song (current-song p/fixture)))))
|
||||
|
||||
(deftest playback-status-subscription
|
||||
(letfn [(is-playing? [playback-status]
|
||||
(audio/is-playing? playback-status [:audio/is-playing?]))]
|
||||
(testing "Should be shown as not playing when the song is paused or has ended"
|
||||
(is (not (is-playing? {:paused? true, :ended? false})))
|
||||
(is (not (is-playing? {:paused? false, :ended? true}))))
|
||||
(testing "Should be shown as playing when the song is not paused or finished"
|
||||
(is (is-playing? {:paused? false, :ended? false})))))
|
||||
103
test/cljs/airsonic_ui/audio/playlist_test.cljs
Normal file
103
test/cljs/airsonic_ui/audio/playlist_test.cljs
Normal file
|
|
@ -0,0 +1,103 @@
|
|||
(ns airsonic-ui.audio.playlist-test
|
||||
(:require [cljs.test :refer [deftest testing is]]
|
||||
[airsonic-ui.audio.playlist :as playlist]
|
||||
[airsonic-ui.fixtures :as fixtures]
|
||||
[airsonic-ui.test-helpers :as helpers]))
|
||||
|
||||
(enable-console-print!)
|
||||
|
||||
(defn- playing-queue
|
||||
"Generates a seq of n different songs"
|
||||
[n]
|
||||
(repeatedly n #(hash-map :id (rand-int 9999)
|
||||
:coverArt (rand-int 9999)
|
||||
:year (+ 1900 (rand-int 118))
|
||||
:artist (helpers/rand-str)
|
||||
:aristId (rand-int 100000)
|
||||
:title (helpers/rand-str)
|
||||
:album (helpers/rand-str))))
|
||||
|
||||
(def fixture
|
||||
{:audio {:current-song fixtures/song
|
||||
:playlist (playing-queue 20)
|
||||
:playback-status fixtures/playback-status}})
|
||||
|
||||
(defn- same-song? [a b] (= (:id a) (:id b)))
|
||||
|
||||
(deftest playlist-creation
|
||||
(testing "Playlist creation"
|
||||
(testing "should give us the correct current song"
|
||||
(let [queue (playing-queue 10)
|
||||
start-idx (rand-int 10)]
|
||||
(doseq [playback-mode [:playback-mode/linear, :playback-mode/shuffle]
|
||||
repeat-mode [:repeat-mode/none, :repeat-mode/single, :repeat-mode/all]]
|
||||
(is (same-song? (nth queue start-idx)
|
||||
(-> (playlist/->playlist queue start-idx playback-mode repeat-mode)
|
||||
(playlist/peek)))
|
||||
(str playback-mode ", " repeat-mode)))))
|
||||
(testing "should give us a playlist with the correct number of tracks"
|
||||
(let [queue (playing-queue 100)
|
||||
start-idx (rand-int 100)]
|
||||
(doseq [playback-mode [:playback-mode/linear, :playback-mode/shuffle]
|
||||
repeat-mode [:repeat-mode/none, :repeat-mode/single, :repeat-mode/all]]
|
||||
(is (= (count queue)
|
||||
(count (playlist/->playlist queue start-idx playback-mode repeat-mode)))
|
||||
(str playback-mode ", " repeat-mode)))))))
|
||||
|
||||
(deftest changing-playback-mode
|
||||
(testing "Changing playback mode"
|
||||
(testing "from linear to shuffled"
|
||||
(let [queue (playing-queue 10)
|
||||
start-idx (rand-int 10)
|
||||
linear (playlist/->playlist queue start-idx :playback-mode/linear :repeat-mode/node)
|
||||
shuffled (playlist/set-playback-mode linear :playback-mode/shuffle)]
|
||||
(testing "should re-order the tracks"
|
||||
(is (not= (map :order (:queue shuffled)) (map :order (:queue linear)))))
|
||||
(testing "should not change the currently playing track"
|
||||
(is (same-song? (playlist/peek linear) (playlist/peek shuffled))))
|
||||
(testing "should not change the repeat mode"
|
||||
(is (= (:repeat-mode shuffled) (:repeat-mode linear)))))
|
||||
(testing "from shuffled to linear"
|
||||
(let [queue (playing-queue 10)
|
||||
start-idx (rand-int 10)
|
||||
shuffled (playlist/->playlist queue start-idx :playback-mode/shuffle :repeat-mode/none)
|
||||
linear (playlist/set-playback-mode shuffled :playback-mode/linear)]
|
||||
(testing "should set the correct order for tracks"
|
||||
(is (every? #(apply same-song? %)
|
||||
(interleave (take start-idx (:queue shuffled))
|
||||
(take start-idx (:queue linear))))
|
||||
"before")
|
||||
(is (every? #(apply same-song? %)
|
||||
(interleave (drop (inc start-idx) (:queue shuffled))
|
||||
(drop (inc start-idx) (:queue linear))))
|
||||
"after")
|
||||
(is (< (:order (first (:queue linear))) (:order (last (:queue linear))))))
|
||||
(testing "should not change the currently playing track"
|
||||
(is (same-song? (playlist/peek linear) (playlist/peek shuffled))))
|
||||
(testing "should not change the repeat mode"
|
||||
(is (= (:repeat-mode shuffled) (:repeat-mode linear)))))))))
|
||||
|
||||
(deftest chaging-repeat-mode
|
||||
(testing "Changing the repeat mode"
|
||||
(testing "should not change the playback mode"
|
||||
(doseq [playback-mode [:playback-mode/linear, :playback-mode/shuffle]
|
||||
repeat-mode [:repeat-mode/none, :repeat-mode/single, :repeat-mode/all]
|
||||
next-repeat-mode [:repeat-mode/none, :repeat-mode/single, :repeat-mode/all]]
|
||||
(let [playlist (-> (playlist/->playlist (playing-queue 1) 0 playback-mode repeat-mode)
|
||||
(playlist/set-repeat-mode next-repeat-mode))]
|
||||
(is (= playback-mode (:playback-mode playlist)))
|
||||
(is (= next-repeat-mode (:repeat-mode playlist))
|
||||
(str "from " repeat-mode " to " next-repeat-mode)))))))
|
||||
|
||||
(deftest linear-next-song
|
||||
(testing "Should follow the same order as the queue used for creation")
|
||||
(testing "Should go back to the first song when repeat-mode is all")
|
||||
(testing "Should always give the same track when repeat-mode is single"))
|
||||
|
||||
(deftest shuffled-next-song
|
||||
(testing "Should play every track once when called for the entire queue")
|
||||
(testing "Should re-shuffle the playlist when wrapping around and repeat-mode is all")
|
||||
(testing "Should always give the same track when repeat-mode is single"))
|
||||
|
||||
#_(deftest linear-previous-song)
|
||||
#_(deftest shuffled-previous-song)
|
||||
|
|
@ -1,40 +0,0 @@
|
|||
(ns airsonic-ui.audio-test
|
||||
(:require [airsonic-ui.audio :as audio]
|
||||
[airsonic-ui.fixtures :as fixtures]
|
||||
[airsonic-ui.test-helpers :as helpers]
|
||||
[cljs.test :refer [deftest testing is]]))
|
||||
|
||||
(enable-console-print!)
|
||||
|
||||
(defn- simulate-playlist [n ]
|
||||
(repeatedly n #(hash-map :id (rand-int 9999)
|
||||
:coverArt (rand-int 9999)
|
||||
:year (+ 1900 (rand-int 118))
|
||||
:artist (helpers/rand-str)
|
||||
:aristId (rand-int 100000)
|
||||
:title (helpers/rand-str)
|
||||
:album (helpers/rand-str))))
|
||||
|
||||
(def fixture
|
||||
{:audio {:current-song fixtures/song
|
||||
:playlist (simulate-playlist 20)
|
||||
:playback-status fixtures/playback-status}})
|
||||
|
||||
(deftest current-song
|
||||
(letfn [(current-song [db]
|
||||
(-> (audio/summary db [:audio/summary])
|
||||
(audio/current-song [:audio/current-song])))]
|
||||
(testing "Should provide information about the song"
|
||||
(= fixtures/song (current-song fixture)))))
|
||||
|
||||
(deftest playback-status
|
||||
(letfn [(is-playing? [playback-status]
|
||||
(audio/is-playing? playback-status [:audio/is-playing?]))]
|
||||
(testing "Should be shown as not playing when the song is paused or has ended"
|
||||
(is (not (is-playing? {:paused? true, :ended? false})))
|
||||
(is (not (is-playing? {:paused? false, :ended? true}))))
|
||||
(testing "Should be shown as playing when the song is not paused or finished"
|
||||
(is (is-playing? {:paused? false, :ended? false})))))
|
||||
|
||||
#_(deftest current-playlist
|
||||
(testing "Should show the complete playlist"))
|
||||
Loading…
Add table
Add a link
Reference in a new issue