diff --git a/src/cljs/airsonic_ui/audio/playlist.cljs b/src/cljs/airsonic_ui/audio/playlist.cljs index 18f19a1..0afc648 100644 --- a/src/cljs/airsonic_ui/audio/playlist.cljs +++ b/src/cljs/airsonic_ui/audio/playlist.cljs @@ -13,16 +13,16 @@ (defmulti ->playlist "Creates a new playlist that behaves according to the given playback- and repeat-mode parameters." - (fn [queue playing-idx playback-mode repeat-mode] playback-mode)) + (fn [queue playback-mode repeat-mode] playback-mode)) -(defn- playlist-queue - [queue playing-idx] - (assoc-in (vec queue) [playing-idx :currently-playing?] true)) +(defn- mark-first-song [queue] + (let [[first-idx _] (find-where #(= 0 (:order %)) queue)] + (assoc-in queue [first-idx :currently-playing?] true))) (defmethod ->playlist :playback-mode/linear - [queue playing-idx playback-mode repeat-mode] - (let [queue (->> (playlist-queue queue playing-idx) - (mapv (fn [order song] (assoc song :order order)) (range)))] + [queue playback-mode repeat-mode] + (let [queue (-> (mapv (fn [order song] (assoc song :order order)) (range) queue) + (mark-first-song))] (->Playlist queue playback-mode repeat-mode))) (defn- -shuffle-songs [queue] @@ -30,16 +30,35 @@ (mapv (fn [song order] (assoc song :order order)) queue))) (defmethod ->playlist :playback-mode/shuffled - [queue playing-idx playback-mode repeat-mode] - (let [queue (->> (playlist-queue queue playing-idx) - (-shuffle-songs))] + [queue playback-mode repeat-mode] + (let [queue (conj (mapv #(update % :order inc) (-shuffle-songs (rest queue))) + (assoc (first queue) :order 0 :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 :currently-playing? (:queue playlist))] + (-> (if current-idx + (update-in playlist [:queue current-idx] dissoc :currently-playing?) + playlist) + (assoc-in [:queue next-idx :currently-playing?] true)))) + (defn set-playback-mode "Changes the playback mode of a playlist and re-shuffles it if necessary" [playlist playback-mode] - (let [[current-idx _] (find-where :currently-playing? (:queue playlist))] - (->playlist (:queue playlist) current-idx playback-mode (:repeat-mode playlist)))) + (if (= playback-mode :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 (:repeat-mode playlist)) + [current-idx current-song] (find-where :currently-playing? (:queue playlist)) + [swap-idx _] (find-where #(= 0 (:order %)) (:queue playlist))] + (-> (assoc-in playlist [:queue current-idx :order] 0) + (assoc-in [:queue swap-idx :order] (:order current-song)))) + ;; for linear songs we just make sure that the current does not change + (let [[current-idx _] (find-where :currently-playing? (:queue playlist))] + (-> (->playlist (:queue playlist) playback-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" @@ -53,15 +72,6 @@ (filter :currently-playing?) (first))) -(defn set-current-song - "Marks a song in the queue as currently playing, given its ID" - [playlist next-idx] - (let [[current-idx _] (find-where :currently-playing? (:queue playlist))] - (-> (if current-idx - (update-in playlist [:queue current-idx] dissoc :currently-playing?) - playlist) - (assoc-in [:queue next-idx :currently-playing?] true)))) - (defmulti next-song "Advances the currently playing song" :repeat-mode) (defmethod next-song :repeat-mode/none @@ -111,7 +121,10 @@ (if next-idx (set-current-song playlist next-idx) (if (= :playback-mode/shuffled (:playback-mode playlist)) - (set-current-song (update playlist :queue -shuffle-songs) 0) + (let [highest-order (dec (count playlist)) + playlist (update playlist :queue -shuffle-songs) + [last-idx _] (find-where #(= (:order %) highest-order) (:queue playlist))] + (set-current-song playlist last-idx)) (set-current-song playlist (mod (dec (:order current-song)) (count playlist))))))) (defn enqueue-last [playlist song] diff --git a/test/cljs/airsonic_ui/audio/playlist_test.cljs b/test/cljs/airsonic_ui/audio/playlist_test.cljs index f7778f2..15cb87d 100644 --- a/test/cljs/airsonic_ui/audio/playlist_test.cljs +++ b/test/cljs/airsonic_ui/audio/playlist_test.cljs @@ -34,56 +34,44 @@ (deftest playlist-creation (testing "Playlist creation" (testing "should give us the correct current song" - (let [queue (song-queue 10) - start-idx (rand-int 10)] + (let [queue (song-queue 10)] (doseq [playback-mode [:playback-mode/linear, :playback-mode/shuffled] 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) + (is (same-song? (first queue) + (-> (playlist/->playlist queue 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 (song-queue 100) - start-idx (rand-int 100)] + (let [queue (song-queue 100)] (doseq [playback-mode [:playback-mode/linear, :playback-mode/shuffled] repeat-mode [:repeat-mode/none, :repeat-mode/single, :repeat-mode/all]] (is (= (count queue) - (count (playlist/->playlist queue start-idx playback-mode repeat-mode))) + (count (playlist/->playlist queue playback-mode repeat-mode))) (str playback-mode ", " repeat-mode))))))) (deftest changing-playback-mode (testing "Changing playback mode" (testing "from linear to shuffled" (let [queue (song-queue 10) - start-idx (rand-int 10) - linear (playlist/->playlist queue start-idx :playback-mode/linear :repeat-mode/node) + linear (playlist/->playlist queue :playback-mode/linear :repeat-mode/none) shuffled (playlist/set-playback-mode linear :playback-mode/shuffled)] (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 (song-queue 10) - start-idx (rand-int 10) - shuffled (playlist/->playlist queue start-idx - :playback-mode/shuffled :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))))))))) + (is (= (:repeat-mode shuffled) (:repeat-mode linear)))))) + (testing "from shuffled to linear" + (let [queue (song-queue 10) + shuffled (playlist/->playlist queue :playback-mode/shuffled :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 queue (:queue linear)))) + (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" @@ -91,8 +79,7 @@ (doseq [playback-mode [:playback-mode/linear, :playback-mode/shuffled] 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 (song-queue 1) 0 - playback-mode repeat-mode) + (let [playlist (-> (playlist/->playlist (song-queue 1) playback-mode repeat-mode) (playlist/set-repeat-mode next-repeat-mode))] (is (= playback-mode (:playback-mode playlist))) (is (= next-repeat-mode (:repeat-mode playlist)) @@ -102,7 +89,7 @@ (testing "Should follow the same order as the queue used for creation" (doseq [repeat-mode [:repeat-mode/none :repeat-mode/all]] (let [queue (song-queue 5) - playlist (playlist/->playlist queue 0 :playback-mode/linear repeat-mode)] + playlist (playlist/->playlist queue :playback-mode/linear repeat-mode)] (is (same-song? (nth queue 1) (-> (playlist/next-song playlist) (playlist/peek))) (str repeat-mode ", skipped once")) @@ -113,81 +100,73 @@ (testing "Should go back to the first song when repeat-mode is all and we played the last song") (testing "Should always give the same track when repeat-mode is single" (let [queue (song-queue 3) - playing-idx (rand-int 3) - playlist (playlist/->playlist queue playing-idx - :playback-mode/linear :repeat-mode/single) + playlist (playlist/->playlist queue :playback-mode/linear :repeat-mode/single) played-back (map playlist/peek (iterate playlist/next-song playlist))] - (is (same-song? (nth queue playing-idx) (nth played-back 0))) - (is (same-song? (nth queue playing-idx) (nth played-back 1))) - (is (same-song? (nth queue playing-idx) (nth played-back 2))) - (is (same-song? (nth queue playing-idx) (nth played-back 3)) "wrapping around"))) + (is (same-song? (first queue) (nth played-back 0))) + (is (same-song? (first queue) (nth played-back 1))) + (is (same-song? (first queue) (nth played-back 2))) + (is (same-song? (first queue) (nth played-back 3)) "wrapping around"))) (testing "Should stop playing at the end of the queue when repeat-mode is none" (is (nil? (-> (song-queue 1) - (playlist/->playlist 0 :playback-mode/linear :repeat-mode/none) + (playlist/->playlist :playback-mode/linear :repeat-mode/none) (playlist/next-song) (playlist/peek)))))) (deftest shuffled-next-song (testing "Should play every track once when called for the entire queue" - (with-redefs [shuffle reverse] - (doseq [repeat-mode '(:repeat-mode/none :repeat-mode/all)] - (let [length 10 - playlist (playlist/->playlist (song-queue length) 9 - :playback-mode/shuffled repeat-mode) - played-tracks (->> (iterate playlist/next-song playlist) - (map playlist/peek) - (take length))] - (is (= (count played-tracks) (count (set played-tracks))) - (str repeat-mode)))))) + (doseq [repeat-mode '(:repeat-mode/none :repeat-mode/all)] + (let [length 10 + playlist (playlist/->playlist (song-queue length) :playback-mode/shuffled repeat-mode) + played-tracks (->> (iterate playlist/next-song playlist) + (map playlist/peek) + (take length))] + (is (= (count played-tracks) (count (set played-tracks))) + (str repeat-mode))))) (testing "Should re-shuffle the playlist when wrapping around and repeat-mode is all" - (let [playlist (playlist/->playlist (song-queue 100) 0 :playback-mode/shuffled :repeat-mode/all) + (let [playlist (playlist/->playlist (song-queue 100) :playback-mode/shuffled :repeat-mode/all) [last-idx _] (find-where #(= (:order %) 99) (:queue playlist))] (is (not= (map :order (:queue playlist)) (map :order (:queue (-> (playlist/set-current-song playlist last-idx) (playlist/next-song)))))))) (testing "Should always give the same track when repeat-mode is single" (let [queue (song-queue 3) - playing-idx (rand-int 3) - playlist (playlist/->playlist queue playing-idx - :playback-mode/shuffled :repeat-mode/single) + playlist (playlist/->playlist queue :playback-mode/shuffled :repeat-mode/single) played-back (map playlist/peek (iterate playlist/next-song playlist))] - (is (same-song? (nth queue playing-idx) (nth played-back 0))) - (is (same-song? (nth queue playing-idx) (nth played-back 1))) - (is (same-song? (nth queue playing-idx) (nth played-back 2))) - (is (same-song? (nth queue playing-idx) (nth played-back 3)) "wrapping around"))) + (is (same-song? (first queue) (nth played-back 0))) + (is (same-song? (first queue) (nth played-back 1))) + (is (same-song? (first queue) (nth played-back 2))) + (is (same-song? (first queue) (nth played-back 3)) "wrapping around"))) (testing "Should stop playing at the end of the queue when repeat-mode is none" (is (nil? (-> (song-queue 1) - (playlist/->playlist 0 :playback-mode/linear :repeat-mode/none) + (playlist/->playlist :playback-mode/linear :repeat-mode/none) (playlist/next-song) (playlist/peek)))))) (deftest linear-previous-song (testing "Should always give the same track when repeat-mode is single" (let [queue (song-queue 3) - playing-idx (rand-int 3) - playlist (playlist/->playlist queue playing-idx - :playback-mode/linear :repeat-mode/single) + playlist (playlist/->playlist queue :playback-mode/linear :repeat-mode/single) played-back (map playlist/peek (iterate playlist/next-song playlist))] - (is (same-song? (nth queue playing-idx) (nth played-back 0))) - (is (same-song? (nth queue playing-idx) (nth played-back 1))) - (is (same-song? (nth queue playing-idx) (nth played-back 2))) - (is (same-song? (nth queue playing-idx) (nth played-back 3)) "wrapping around"))) + (is (same-song? (first queue) (nth played-back 0))) + (is (same-song? (first queue) (nth played-back 1))) + (is (same-song? (first queue) (nth played-back 2))) + (is (same-song? (first queue) (nth played-back 3)) "wrapping around"))) (testing "Should keep the linear order when repeat-mode is not single" (doseq [repeat-mode '(:repeat-mode/none :repeat-mode/all)] (let [queue (song-queue 3) - playlist (playlist/->playlist queue 0 :playback-mode/linear repeat-mode)] + playlist (playlist/->playlist queue :playback-mode/linear repeat-mode)] (is (same-song? (nth queue 1) (-> (playlist/next-song playlist) (playlist/next-song) (playlist/previous-song) (playlist/peek))))))) (testing "Should repeatedly give the first song when repeat-mode is none" (let [queue (song-queue 3) - playlist (playlist/->playlist queue 0 :playback-mode/linear :repeat-mode/none)] + playlist (playlist/->playlist queue :playback-mode/linear :repeat-mode/none)] (is (same-song? (first queue) (-> (playlist/previous-song playlist) (playlist/peek)))))) (testing "Should wrap around to last song when repeat-mode is all" (let [queue (song-queue 3) - playlist (playlist/->playlist queue 0 :playback-mode/linear :repeat-mode/all)] + playlist (playlist/->playlist queue :playback-mode/linear :repeat-mode/all)] (is (same-song? (last queue) (-> (playlist/previous-song playlist) (playlist/peek))))))) @@ -195,20 +174,16 @@ (with-redefs [shuffle reverse] (testing "Should always give the same track when repeat-mode is single" (let [queue (song-queue 3) - playing-idx (rand-int 3) - playlist (playlist/->playlist queue playing-idx - :playback-mode/shuffled :repeat-mode/single) + playlist (playlist/->playlist queue :playback-mode/shuffled :repeat-mode/single) played-back (map playlist/peek (iterate playlist/next-song playlist))] - (is (same-song? (nth queue playing-idx) (nth played-back 0))) - (is (same-song? (nth queue playing-idx) (nth played-back 1))) - (is (same-song? (nth queue playing-idx) (nth played-back 2))) - (is (same-song? (nth queue playing-idx) (nth played-back 3)) "wrapping around"))) + (is (same-song? (first queue) (nth played-back 0))) + (is (same-song? (first queue) (nth played-back 1))) + (is (same-song? (first queue) (nth played-back 2))) + (is (same-song? (first queue) (nth played-back 3)) "wrapping around"))) (testing "Should keep the playing order when repeat-mode is not single" (doseq [repeat-mode '(:repeat-mode/none :repeat-mode/all)] (let [queue (song-queue 3) - playlist' (playlist/->playlist queue 0 :playback-mode/shuffled repeat-mode) - [first-idx _] (find-where #(= (:order %) 0) (:queue playlist')) - playlist (playlist/set-current-song playlist' first-idx)] + playlist (playlist/->playlist queue :playback-mode/shuffled repeat-mode)] (is (same-song? (playlist/peek playlist) (-> playlist (playlist/next-song) @@ -222,17 +197,17 @@ (playlist/previous-song) (playlist/peek))) (str "for repeat mode " repeat-mode))))) - (testing "Should re-shuffle when repeat-mode is all and we pass the start" - (with-redefs [shuffle identity] - (let [playlist (playlist/->playlist (song-queue 2) 0 :playback-mode/shuffled :repeat-mode/all) - playlist' (with-redefs [shuffle reverse] - (playlist/previous-song playlist))] - (is (not= (map :order (:queue playlist)) (map :order (:queue playlist'))))))))) + (testing "Should re-shuffle when repeat-mode is all and we go back to before the first track" + (let [playlist (with-redefs [shuffle identity] + (playlist/->playlist (song-queue 10) :playback-mode/shuffled :repeat-mode/all)) + playlist' (with-redefs [shuffle reverse] + (playlist/previous-song playlist))] + (is (not= (map :order (:queue playlist)) (map :order (:queue playlist')))))))) (deftest set-current-song (testing "Should correctly set the new song" (let [queue (song-queue 3) - playlist (playlist/->playlist queue 0 :playback-mode/shuffled :repeat-mode/single) + playlist (playlist/->playlist queue :playback-mode/shuffled :repeat-mode/single) current-track (first queue) next-track (-> (playlist/set-current-song playlist 1) (playlist/peek))] @@ -245,7 +220,7 @@ repeat-mode '(:repeat-mode/none :repeat-mode/all)] (let [length 5, queue (song-queue length) playlist (with-redefs [shuffle identity] - (playlist/->playlist queue 0 playback-mode repeat-mode)) + (playlist/->playlist queue playback-mode repeat-mode)) played-back (->> (iterate playlist/next-song playlist) (take (dec length)) (map #(:id (playlist/peek %))) @@ -261,10 +236,7 @@ (doseq [playback-mode '(:playback-mode/linear :playback-mode/shuffled) repeat-mode '(:repeat-mode/none :repeat-mode/all)] (let [length 5, queue (song-queue length) - playlist (playlist/->playlist queue 0 playback-mode repeat-mode) - ;; FIXME: Playlists should just start with order 0, basta - [first-song-idx _] (find-where #(= 0 (:order %)) (:queue playlist)) - playlist (playlist/set-current-song playlist first-song-idx) + playlist (playlist/->playlist queue playback-mode repeat-mode) played-back-songs (fn played-back-songs [playlist] (->> (iterate playlist/next-song playlist) (take length) @@ -280,7 +252,7 @@ (doseq [playback-mode '(:playback-mode/linear :playback-mode/shuffled) repeat-mode '(:repeat-mode/none :repeat-mode/all)] (let [length 5, queue (song-queue length) - playlist (playlist/->playlist queue 0 playback-mode repeat-mode) + playlist (playlist/->playlist queue playback-mode repeat-mode) next-song (song)] (is (same-song? next-song (-> (playlist/enqueue-next playlist next-song) (playlist/next-song)