aboutsummaryrefslogtreecommitdiff
path: root/src/frontend/tubo
diff options
context:
space:
mode:
Diffstat (limited to 'src/frontend/tubo')
-rw-r--r--src/frontend/tubo/bg_player/events.cljs190
-rw-r--r--src/frontend/tubo/bg_player/subs.cljs25
-rw-r--r--src/frontend/tubo/bg_player/views.cljs341
-rw-r--r--src/frontend/tubo/bookmarks/modals.cljs2
-rw-r--r--src/frontend/tubo/bookmarks/views.cljs7
-rw-r--r--src/frontend/tubo/channel/events.cljs2
-rw-r--r--src/frontend/tubo/channel/views.cljs4
-rw-r--r--src/frontend/tubo/comments/views.cljs2
-rw-r--r--src/frontend/tubo/components/player.cljs244
-rw-r--r--src/frontend/tubo/events.cljs65
-rw-r--r--src/frontend/tubo/items/views.cljs (renamed from src/frontend/tubo/components/items.cljs)11
-rw-r--r--src/frontend/tubo/kiosks/events.cljs4
-rw-r--r--src/frontend/tubo/kiosks/views.cljs4
-rw-r--r--src/frontend/tubo/layout/views.cljs (renamed from src/frontend/tubo/components/layout.cljs)4
-rw-r--r--src/frontend/tubo/main_player/events.cljs53
-rw-r--r--src/frontend/tubo/main_player/subs.cljs20
-rw-r--r--src/frontend/tubo/main_player/views.cljs33
-rw-r--r--src/frontend/tubo/modals/views.cljs2
-rw-r--r--src/frontend/tubo/navigation/events.cljs55
-rw-r--r--src/frontend/tubo/navigation/subs.cljs13
-rw-r--r--src/frontend/tubo/navigation/views.cljs79
-rw-r--r--src/frontend/tubo/player/events.cljs272
-rw-r--r--src/frontend/tubo/player/subs.cljs57
-rw-r--r--src/frontend/tubo/player/views.cljs251
-rw-r--r--src/frontend/tubo/playlist/views.cljs4
-rw-r--r--src/frontend/tubo/queue/events.cljs26
-rw-r--r--src/frontend/tubo/queue/subs.cljs12
-rw-r--r--src/frontend/tubo/queue/views.cljs24
-rw-r--r--src/frontend/tubo/routes.cljs2
-rw-r--r--src/frontend/tubo/search/events.cljs20
-rw-r--r--src/frontend/tubo/search/subs.cljs12
-rw-r--r--src/frontend/tubo/search/views.cljs6
-rw-r--r--src/frontend/tubo/settings/views.cljs2
-rw-r--r--src/frontend/tubo/stream/events.cljs2
-rw-r--r--src/frontend/tubo/stream/views.cljs10
-rw-r--r--src/frontend/tubo/subs.cljs13
-rw-r--r--src/frontend/tubo/views.cljs9
37 files changed, 962 insertions, 920 deletions
diff --git a/src/frontend/tubo/bg_player/events.cljs b/src/frontend/tubo/bg_player/events.cljs
new file mode 100644
index 0000000..75f3a8c
--- /dev/null
+++ b/src/frontend/tubo/bg_player/events.cljs
@@ -0,0 +1,190 @@
+(ns tubo.bg-player.events
+ (:require
+ [re-frame.core :as rf]
+ [vimsical.re-frame.cofx.inject :as inject]))
+
+(rf/reg-event-fx
+ :bg-player/seek
+ [(rf/inject-cofx ::inject/sub [:bg-player])]
+ (fn [{:keys [db bg-player]} [_ time]]
+ (when (:bg-player/ready db)
+ {:player/time {:time time :player bg-player}})))
+
+(rf/reg-event-db
+ :bg-player/set-paused
+ (fn [db [_ val]]
+ (assoc db :player/paused val)))
+
+(rf/reg-event-fx
+ :bg-player/pause
+ [(rf/inject-cofx ::inject/sub [:bg-player])]
+ (fn [{:keys [bg-player]} [_ paused?]]
+ {:player/pause {:paused? paused?
+ :player bg-player}}))
+
+(rf/reg-event-fx
+ :bg-player/play
+ [(rf/inject-cofx ::inject/sub [:elapsed-time])
+ (rf/inject-cofx ::inject/sub [:main-player])]
+ (fn [{:keys [main-player db elapsed-time]}]
+ {:fx [[:dispatch [:bg-player/set-paused false]]
+ [:dispatch [:bg-player/seek @elapsed-time]]
+ (when (and (:main-player/ready db) main-player @main-player)
+ [:dispatch [:main-player/pause true]])]}))
+
+(rf/reg-event-fx
+ :bg-player/stop
+ (fn [_]
+ {:fx [[:dispatch [:bg-player/pause true]]
+ [:dispatch [:bg-player/seek 0]]]}))
+
+(rf/reg-event-fx
+ :bg-player/start
+ [(rf/inject-cofx ::inject/sub [:bg-player])
+ (rf/inject-cofx ::inject/sub [:elapsed-time])]
+ (fn [{:keys [db bg-player]} _]
+ {:fx [[:dispatch [:bg-player/set-paused true]]
+ [:dispatch [:bg-player/pause false]]
+ [:dispatch
+ [:player/change-volume (:player/volume db)
+ bg-player]]
+ ]}))
+
+(rf/reg-event-fx
+ :bg-player/mute
+ [(rf/inject-cofx :store)]
+ (fn [{:keys [db store]} [_ value player]]
+ {:db (assoc db :player/muted value)
+ :store (assoc store :player/muted value)
+ :player/mute {:player player :muted? value}}))
+
+(rf/reg-event-fx
+ :bg-player/hide
+ [(rf/inject-cofx :store)]
+ (fn [{:keys [db store]} _]
+ {:db (assoc db :bg-player/show false)
+ :store (assoc store :bg-player/show false)}))
+
+(rf/reg-event-fx
+ :bg-player/dispose
+ [(rf/inject-cofx :store)]
+ (fn [{:keys [db store]} _]
+ (let [remove-entries
+ (fn [elem]
+ (-> elem
+ (assoc :queue [])
+ (assoc :queue/position 0)))]
+ {:db (remove-entries db)
+ :store (remove-entries store)
+ :fx [[:dispatch [:bg-player/pause true]]
+ [:dispatch [:bg-player/seek 0]]
+ [:dispatch [:bg-player/hide]]]})))
+
+(rf/reg-event-db
+ :bg-player/ready
+ (fn [db [_ ready]]
+ (assoc db :bg-player/ready ready)))
+
+(rf/reg-event-fx
+ :bg-player/load-related-streams
+ (fn [_ [_ res]]
+ (let [{:keys [related-streams]} (js->clj res :keywordize-keys true)]
+ {:fx [[:dispatch [:queue/add-n related-streams]]]})))
+
+(rf/reg-event-fx
+ :bg-player/fetch-related-streams
+ (fn [{:keys [db]} [_ url]]
+ {:fx [[:dispatch
+ [:stream/fetch url
+ [:bg-player/load-related-streams]] [:bad-response]]]
+ :db (assoc db :bg-player/loading true)}))
+
+(rf/reg-event-fx
+ :bg-player/show
+ [(rf/inject-cofx :store)]
+ (fn [{:keys [db store]} [_ stream notify?]]
+ (let [updated-db (update db :queue conj stream)
+ idx (.indexOf (:queue updated-db) stream)]
+ {:db updated-db
+ :store (assoc store :queue (:queue updated-db))
+ :fx [[:dispatch
+ [:bg-player/fetch-stream
+ (:url stream) idx (= (count (:queue db)) 0)]]
+ (when (and notify? (not (= (count (:queue db)) 0)))
+ [:dispatch
+ [:notifications/add
+ {:status-text "Added stream to queue"
+ :failure :info}]])]})))
+
+(rf/reg-event-fx
+ :bg-player/start-radio
+ (fn [{:keys [db]} [_ stream]]
+ {:fx [[:dispatch [:bg-player/show stream]]
+ (when (not= (count (:queue db)) 0)
+ [:dispatch [:queue/change-pos (count (:queue db))]])
+ [:dispatch [:bg-player/fetch-related-streams (:url stream)]]
+ [:dispatch
+ [:notifications/add
+ {:status-text "Started stream radio"
+ :failure :info}]]]}))
+
+(rf/reg-event-fx
+ :bg-player/switch-to-main
+ [(rf/inject-cofx :store)]
+ (fn [{:keys [db]} _]
+ {:fx [[:dispatch [:main-player/show true]]]
+ :db (assoc db :bg-player/show false)
+ :scroll-to-top nil}))
+
+(rf/reg-event-fx
+ :bg-player/switch-from-main
+ (fn [{:keys [db]} _]
+ {:db (assoc db :bg-player/show true)
+ :fx [[:dispatch [:main-player/show false]]
+ [:dispatch [:main-player/pause true]]]}))
+
+(rf/reg-event-fx
+ :bg-player/load-stream
+ [(rf/inject-cofx :store)
+ (rf/inject-cofx ::inject/sub [:bg-player])]
+ (fn [{:keys [db store bg-player]} [_ idx play? res]]
+ (let [stream-res (js->clj res :keywordize-keys true)]
+ {:db (assoc db
+ :bg-player/show (not (:main-player/show db))
+ :bg-player/loading false)
+ :store (assoc store :bg-player/show (not (:main-player/show db)))
+ :fx (apply conj
+ [(when play?
+ [:dispatch [:queue/change-stream stream-res idx]])]
+ (when (and (:bg-player/ready db) play?)
+ [[:media-session-metadata
+ {:title (:name stream-res)
+ :artist (:uploader-name stream-res)
+ :artwork [{:src (:thumbnail-url stream-res)}]}]
+ [:media-session-handlers
+ {:current-pos idx
+ :player bg-player}]]))})))
+
+(rf/reg-event-fx
+ :bg-player/bad-response
+ (fn [{:keys [db]} [_ idx play? res]]
+ {:db (assoc db
+ :bg-player/loading
+ false)
+ :fx [[:dispatch [:bad-response res]]
+ (when play?
+ (if (> (-> db
+ :queue
+ count)
+ 1)
+ [:dispatch [:queue/change-pos (inc idx)]]
+ [:dispatch [:bg-player/dispose]]))]}))
+
+(rf/reg-event-fx
+ :bg-player/fetch-stream
+ (fn [{:keys [db]} [_ url idx play?]]
+ {:fx [[:dispatch
+ [:stream/fetch url
+ [:bg-player/load-stream idx play?]
+ [:bg-player/bad-response idx play?]]]]
+ :db (assoc db :bg-player/loading play?)}))
diff --git a/src/frontend/tubo/bg_player/subs.cljs b/src/frontend/tubo/bg_player/subs.cljs
new file mode 100644
index 0000000..3216391
--- /dev/null
+++ b/src/frontend/tubo/bg_player/subs.cljs
@@ -0,0 +1,25 @@
+(ns tubo.bg-player.subs
+ (:require
+ [re-frame.core :as rf]))
+
+(defonce !player (atom nil))
+
+(rf/reg-sub
+ :bg-player/ready
+ (fn [db _]
+ (:bg-player/ready db)))
+
+(rf/reg-sub
+ :bg-player/show
+ (fn [db _]
+ (:bg-player/show db)))
+
+(rf/reg-sub
+ :bg-player/loading
+ (fn [db _]
+ (:bg-player/loading db)))
+
+(rf/reg-sub
+ :bg-player
+ (fn [_ _]
+ !player))
diff --git a/src/frontend/tubo/bg_player/views.cljs b/src/frontend/tubo/bg_player/views.cljs
new file mode 100644
index 0000000..c5f336b
--- /dev/null
+++ b/src/frontend/tubo/bg_player/views.cljs
@@ -0,0 +1,341 @@
+(ns tubo.bg-player.views
+ (:require
+ [clojure.string :as str]
+ [re-frame.core :as rf]
+ [reagent.core :as r]
+ [reitit.frontend.easy :as rfe]
+ [tubo.bookmarks.modals :as modals]
+ [tubo.layout.views :as layout]
+ [tubo.utils :as utils]
+ ["@vidstack/react" :refer (MediaPlayer MediaProvider)]
+ ["@vidstack/react/player/layouts/default" :refer
+ (defaultLayoutIcons DefaultAudioLayout)]))
+
+(defonce base-slider-classes
+ ["h-2" "cursor-pointer" "appearance-none" "bg-neutral-300"
+ "dark:bg-neutral-600"
+ "rounded-full" "overflow-hidden" "focus:outline-none"
+ "[&::-webkit-slider-thumb]:appearance-none"
+ "[&::-webkit-slider-thumb]:border-0"
+ "[&::-webkit-slider-thumb]:rounded-full"
+ "[&::-webkit-slider-thumb]:h-2"
+ "[&::-webkit-slider-thumb]:w-2"
+ "[&::-webkit-slider-thumb]:shadow-[-405px_0_0_400px]"
+ "[&::-moz-range-thumb]:border-0"
+ "[&::-moz-range-thumb]:rounded-full"
+ "[&::-moz-range-thumb]:h-2"
+ "[&::-moz-range-thumb]:w-2"
+ "[&::-moz-range-thumb]:shadow-[-405px_0_0_400px]"])
+
+(defn get-slider-shadow-classes
+ [service-color]
+ (case service-color
+ "#cc0000" ["[&::-webkit-slider-thumb]:shadow-[#cc0000]"
+ "[&::-moz-range-thumb]:shadow-[#cc0000]"]
+ "#ff7700" ["[&::-webkit-slider-thumb]:shadow-[#ff7700]"
+ "[&::-moz-range-thumb]:shadow-[#ff7700]"]
+ "#333333" ["[&::-webkit-slider-thumb]:shadow-[#333333]"
+ "[&::-moz-range-thumb]:shadow-[#333333]"]
+ "#F2690D" ["[&::-webkit-slider-thumb]:shadow-[#F2690D]"
+ "[&::-moz-range-thumb]:shadow-[#F2690D]"]
+ "#629aa9" ["[&::-webkit-slider-thumb]:shadow-[#629aa9]"
+ "[&::-moz-range-thumb]:shadow-[#629aa9]"]
+ ["[&::-webkit-slider-thumb]:shadow-neutral-300"
+ "[&::-moz-range-thumb]:shadow-neutral-300"]))
+
+(defn get-slider-bg-classes
+ [service-color]
+ (case service-color
+ "#cc0000" ["[&::-webkit-slider-thumb]:bg-[#cc0000]"
+ "[&::-moz-range-thumb]:bg-[#cc0000]"]
+ "#ff7700" ["[&::-webkit-slider-thumb]:bg-[#ff7700]"
+ "[&::-moz-range-thumb]:bg-[#ff7700]"]
+ "#333333" ["[&::-webkit-slider-thumb]:bg-[#333333]"
+ "[&::-moz-range-thumb]:bg-[#333333]"]
+ "#F2690D" ["[&::-webkit-slider-thumb]:bg-[#F2690D]"
+ "[&::-moz-range-thumb]:bg-[#F2690D]"]
+ "#629aa9" ["[&::-webkit-slider-thumb]:bg-[#629aa9]"
+ "[&::-moz-range-thumb]:bg-[#629aa9]"]
+ ["[&::-webkit-slider-thumb]:bg-neutral-300"
+ "[&::-moz-range-thumb]:bg-neutral-300"]))
+
+(defn button
+ [& {:keys [icon on-click disabled? show-on-mobile? extra-classes]}]
+ [:button.outline-none.focus:ring-transparent.px-2.pt-1
+ {:class (into (into (when disabled? [:opacity-50 :cursor-auto])
+ (when-not show-on-mobile? [:hidden :lg:block]))
+ extra-classes)
+ :on-click on-click}
+ icon])
+
+(defn loop-button
+ [loop-playback color show-on-mobile?]
+ [button
+ :icon
+ [:div.relative.flex.items-center
+ [:i.fa-solid.fa-repeat
+ {:style {:color (when loop-playback color)}}]
+ (when (= loop-playback :stream)
+ [:div.absolute.w-full.h-full.flex.justify-center.items-center.font-bold
+ {:class "text-[6px]"
+ :style {:color (when loop-playback color)}}
+ "1"])]
+ :on-click #(rf/dispatch [:player/loop])
+ :extra-classes [:text-sm]
+ :show-on-mobile? show-on-mobile?])
+
+(defn shuffle-button
+ [shuffle? color show-on-mobile?]
+ [button
+ :icon
+ [:i.fa-solid.fa-shuffle {:style {:color (when shuffle? color)}}]
+ :on-click #(rf/dispatch [:queue/shuffle (not shuffle?)])
+ :extra-classes [:text-sm]
+ :show-on-mobile? show-on-mobile?])
+
+(defn time-slider
+ [!player !elapsed-time service-color]
+ (let [styles (concat base-slider-classes
+ (get-slider-bg-classes service-color)
+ (get-slider-shadow-classes service-color))
+ bg-player-ready? @(rf/subscribe [:bg-player/ready])]
+ [:input.w-full
+ {:class styles
+ :type "range"
+ :on-input #(reset! !elapsed-time (.. % -target -value))
+ :on-change #(when (and bg-player-ready? @!player)
+ (set! (.-currentTime @!player) @!elapsed-time))
+ :max (if (and bg-player-ready?
+ @!player
+ (not (js/isNaN (.-duration @!player))))
+ (.floor js/Math (.-duration @!player))
+ 100)
+ :value @!elapsed-time}]))
+
+(defn volume-slider
+ [_ _ _ _]
+ (let [show-slider? (r/atom nil)]
+ (fn [player volume-level muted? service-color]
+ (let [styles (concat ["rotate-[270deg]"]
+ base-slider-classes
+ (get-slider-bg-classes service-color)
+ (get-slider-shadow-classes service-color))]
+ [:div.relative.flex.flex-col.justify-center.items-center
+ {:on-mouse-over #(reset! show-slider? true)
+ :on-mouse-out #(reset! show-slider? false)}
+ [button
+ :icon
+ (if muted? [:i.fa-solid.fa-volume-xmark] [:i.fa-solid.fa-volume-low])
+ :on-click #(rf/dispatch [:bg-player/mute (not muted?) player])
+ :extra-classes [:pl-3 :pr-2]]
+ (when @show-slider?
+ [:input.absolute.w-24.ml-2.m-1.bottom-16
+ {:class (str/join " " styles)
+ :type "range"
+ :on-input #(rf/dispatch [:player/change-volume
+ (.. % -target -value) player])
+ :max 100
+ :value volume-level}])]))))
+
+(defn metadata
+ [{:keys [thumbnail-url url name uploader-url uploader-name]}]
+ [:div.flex.items-center.lg:flex-1
+ [:div
+ [layout/thumbnail thumbnail-url (rfe/href :stream-page nil {:url url})
+ name nil :classes [:h-14 :py-2 "w-[70px]"]]]
+ [:div.flex.flex-col.px-2
+ [:a.text-xs.line-clamp-1
+ {:href (rfe/href :stream-page nil {:url url})
+ :title name}
+ name]
+ [:a.text-xs.pt-2.text-neutral-600.dark:text-neutral-300.line-clamp-1
+ {:href (rfe/href :channel-page nil {:url uploader-url})
+ :title uploader-name}
+ uploader-name]]])
+
+(defn main-controls
+ [!player color]
+ (let [queue @(rf/subscribe [:queue])
+ queue-pos @(rf/subscribe [:queue/position])
+ loading? @(rf/subscribe [:bg-player/loading])
+ loop-playback @(rf/subscribe [:player/loop])
+ shuffle? @(rf/subscribe [:player/shuffled])
+ bg-player-ready? @(rf/subscribe [:bg-player/ready])
+ paused? @(rf/subscribe [:player/paused])
+ !elapsed-time @(rf/subscribe [:elapsed-time])]
+ [:div.flex.flex-col.items-center.ml-auto
+ [:div.flex.justify-end
+ [loop-button loop-playback color]
+ [button
+ :icon [:i.fa-solid.fa-backward-step]
+ :on-click #(rf/dispatch [:queue/change-pos (dec queue-pos)])
+ :disabled? (not (and queue (not= queue-pos 0)))]
+ [button
+ :icon [:i.fa-solid.fa-backward]
+ :on-click #(rf/dispatch [:bg-player/seek (- @!elapsed-time 5)])]
+ [button
+ :icon
+ (if (and (not loading?) @!player)
+ (if paused?
+ [:i.fa-solid.fa-play]
+ [:i.fa-solid.fa-pause])
+ [layout/loading-icon color "lg:text-2xl"])
+ :on-click
+ #(rf/dispatch [:bg-player/pause (not (.-paused @!player))])
+ :show-on-mobile? true
+ :extra-classes ["lg:text-2xl"]]
+ [button
+ :icon [:i.fa-solid.fa-forward]
+ :on-click #(rf/dispatch [:bg-player/seek (+ @!elapsed-time 5)])]
+ [button
+ :icon [:i.fa-solid.fa-forward-step]
+ :on-click #(rf/dispatch [:queue/change-pos (inc queue-pos)])
+ :disabled? (not (and queue (< (inc queue-pos) (count queue))))]
+ [shuffle-button shuffle? color]]
+ [:div.hidden.lg:flex.items-center.text-sm
+ [:span.mx-2
+ (if (and bg-player-ready? @!player @!elapsed-time)
+ (utils/format-duration @!elapsed-time)
+ "--:--")]
+ [:div.w-20.lg:w-64.mx-2.flex.items-center
+ [time-slider !player !elapsed-time color]]
+ [:span.mx-2
+ (if (and bg-player-ready? @!player)
+ (utils/format-duration (.-duration @!player))
+ "--:--")]]]))
+
+(defn extra-controls
+ [_ _ _]
+ (let [!menu-active? (r/atom nil)]
+ (fn [!player {:keys [url uploader-url] :as stream} color]
+ (let [muted? @(rf/subscribe [:player/muted])
+ volume @(rf/subscribe [:player/volume])
+ queue @(rf/subscribe [:queue])
+ queue-pos @(rf/subscribe [:queue/position])
+ bookmarks @(rf/subscribe [:bookmarks])
+ liked? (some #(= (:url %) url)
+ (-> bookmarks
+ first
+ :items))
+ bookmark #(rf/dispatch [:modals/open [modals/add-to-bookmark %]])]
+ [:div.flex.lg:justify-end.lg:flex-1
+ [volume-slider !player volume muted? color]
+ [button
+ :icon [:i.fa-solid.fa-list]
+ :on-click #(rf/dispatch [:queue/show true])
+ :show-on-mobile? true
+ :extra-classes [:!pl-4 :!pr-3]]
+ [layout/popover-menu !menu-active?
+ [{:label (if liked? "Remove favorite" "Favorite")
+ :icon [:i.fa-solid.fa-heart
+ (when liked? {:style {:color color}})]
+ :on-click #(rf/dispatch [(if liked? :likes/remove :likes/add)
+ stream])}
+ {:label "Play radio"
+ :icon [:i.fa-solid.fa-tower-cell]
+ :on-click #(rf/dispatch [:bg-player/start-radio stream])}
+ {:label "Add current to playlist"
+ :icon [:i.fa-solid.fa-plus]
+ :on-click #(bookmark stream)}
+ {:label "Add queue to playlist"
+ :icon [:i.fa-solid.fa-list]
+ :on-click #(bookmark queue)}
+ {:label "Remove from queue"
+ :icon [:i.fa-solid.fa-trash]
+ :on-click #(rf/dispatch [:queue/remove queue-pos])}
+ {:label "Switch to main"
+ :icon [:i.fa-solid.fa-display]
+ :on-click #(rf/dispatch [:bg-player/switch-to-main])}
+ {:label "Show channel details"
+ :icon [:i.fa-solid.fa-user]
+ :on-click #(rf/dispatch [:navigation/navigate
+ {:name :channel-page
+ :params {}
+ :query {:url uploader-url}}])}
+ {:label "Close player"
+ :icon [:i.fa-solid.fa-close]
+ :on-click #(rf/dispatch [:bg-player/dispose])}]
+ :menu-styles {:bottom "30px" :top nil :right "10px"}
+ :extra-classes [:pt-1 :!pl-4 :px-3]]]))))
+
+(defn get-audio-player-sources
+ [available-streams]
+ (if available-streams
+ (->> available-streams
+ (filter #(not= (:format %) "OPUS"))
+ (sort-by :bitrate)
+ (map (fn [{:keys [content]}] {:src content :type "audio/mpeg"})))
+ []))
+
+(defn audio-player
+ [_ _]
+ (let [!elapsed-time @(rf/subscribe [:elapsed-time])
+ !bg-player-first? (r/atom nil)]
+ (r/create-class
+ {:component-will-unmount #(rf/dispatch [:bg-player/ready false])
+ :reagent-render
+ (fn [{:keys [name audio-streams]} !player]
+ [:> MediaPlayer
+ {:title name
+ :class "invisible fixed"
+ :controls []
+ :src (get-audio-player-sources audio-streams)
+ :viewType "audio"
+ :ref #(reset! !player %)
+ :loop (= @(rf/subscribe [:player/loop]) :stream)
+ :onCanPlay #(rf/dispatch [:bg-player/ready true])
+ :onSeeked #(reset! !elapsed-time (.-currentTime @!player))
+ :onTimeUpdate #(reset! !elapsed-time (.-currentTime @!player))
+ :onEnded (fn []
+ (rf/dispatch [:queue/change-pos
+ (inc @(rf/subscribe
+ [:queue/position]))])
+ (reset! !elapsed-time 0))
+ :onPlay #(rf/dispatch [:bg-player/play])
+ :onReplay (fn []
+ (rf/dispatch [:bg-player/set-paused false])
+ (reset! !elapsed-time 0))
+ :onPause #(rf/dispatch [:bg-player/set-paused true])
+ :onLoadedData (fn []
+ (rf/dispatch [:bg-player/start])
+ (when-not @!bg-player-first?
+ (reset! !bg-player-first? true)))
+ :onSourceChange #(when @!bg-player-first?
+ (reset! !elapsed-time 0))}
+ [:> MediaProvider]
+ [:> DefaultAudioLayout {:icons defaultLayoutIcons}]])})))
+
+(defn player
+ []
+ (let [!player @(rf/subscribe [:bg-player])
+ stream @(rf/subscribe [:queue/current])
+ show-queue? @(rf/subscribe [:queue/show])
+ show-player? @(rf/subscribe [:bg-player/show])
+ dark-theme? @(rf/subscribe [:dark-theme])
+ color (-> stream
+ :service-id
+ utils/get-service-color)
+ bg-color (str "rgba("
+ (if dark-theme? "23,23,23" "255,255,255")
+ ",0.95)")
+ bg-image (str "linear-gradient("
+ bg-color
+ ","
+ bg-color
+ "),url("
+ (:thumbnail-url stream)
+ ")")]
+ (when show-player?
+ [:div.sticky.absolute.left-0.bottom-0.z-10.p-3.transition-all.ease-in.relative
+ {:style
+ {:visibility (when show-queue? "hidden")
+ :opacity (if show-queue? 0 1)
+ :background-image bg-image
+ :background-size "cover"
+ :background-position "center"
+ :background-repeat "no-repeat"}}
+ [:div.flex.items-center
+ [audio-player stream !player]
+ [metadata stream]
+ [main-controls !player color]
+ [extra-controls !player stream color]]])))
diff --git a/src/frontend/tubo/bookmarks/modals.cljs b/src/frontend/tubo/bookmarks/modals.cljs
index f8a1738..b6463a5 100644
--- a/src/frontend/tubo/bookmarks/modals.cljs
+++ b/src/frontend/tubo/bookmarks/modals.cljs
@@ -2,7 +2,7 @@
(:require
[reagent.core :as r]
[re-frame.core :as rf]
- [tubo.components.layout :as layout]
+ [tubo.layout.views :as layout]
[tubo.modals.views :as modals]))
(defn bookmark-item
diff --git a/src/frontend/tubo/bookmarks/views.cljs b/src/frontend/tubo/bookmarks/views.cljs
index 4bfd96e..2b3bd14 100644
--- a/src/frontend/tubo/bookmarks/views.cljs
+++ b/src/frontend/tubo/bookmarks/views.cljs
@@ -4,8 +4,8 @@
[re-frame.core :as rf]
[reitit.frontend.easy :as rfe]
[tubo.bookmarks.modals :as modals]
- [tubo.components.items :as items]
- [tubo.components.layout :as layout]))
+ [tubo.items.views :as items]
+ [tubo.layout.views :as layout]))
(defn bookmarks
[]
@@ -56,7 +56,8 @@
(let [!menu-active? (r/atom nil)]
(fn []
(let [bookmarks @(rf/subscribe [:bookmarks])
- {{:keys [id]} :query-params} @(rf/subscribe [:current-match])
+ {{:keys [id]} :query-params} @(rf/subscribe
+ [:navigation/current-match])
{:keys [items name]} (first (filter #(= (:id %) id)
bookmarks))]
[layout/content-container
diff --git a/src/frontend/tubo/channel/events.cljs b/src/frontend/tubo/channel/events.cljs
index b8e5fb0..668b21d 100644
--- a/src/frontend/tubo/channel/events.cljs
+++ b/src/frontend/tubo/channel/events.cljs
@@ -3,7 +3,7 @@
[re-frame.core :as rf]
[tubo.api :as api]
[tubo.channel.views :as channel]
- [tubo.components.layout :as layout]))
+ [tubo.layout.views :as layout]))
(rf/reg-event-fx
:channel/fetch
diff --git a/src/frontend/tubo/channel/views.cljs b/src/frontend/tubo/channel/views.cljs
index 416b787..3ee4518 100644
--- a/src/frontend/tubo/channel/views.cljs
+++ b/src/frontend/tubo/channel/views.cljs
@@ -3,8 +3,8 @@
[reagent.core :as r]
[re-frame.core :as rf]
[tubo.bookmarks.modals :as modals]
- [tubo.components.items :as items]
- [tubo.components.layout :as layout]))
+ [tubo.items.views :as items]
+ [tubo.layout.views :as layout]))
(defn metadata-popover
[_]
diff --git a/src/frontend/tubo/comments/views.cljs b/src/frontend/tubo/comments/views.cljs
index e1f6472..2b1bf7f 100644
--- a/src/frontend/tubo/comments/views.cljs
+++ b/src/frontend/tubo/comments/views.cljs
@@ -2,7 +2,7 @@
(:require
[re-frame.core :as rf]
[reitit.frontend.easy :as rfe]
- [tubo.components.layout :as layout]
+ [tubo.layout.views :as layout]
[tubo.utils :as utils]))
(defn comment-top-metadata
diff --git a/src/frontend/tubo/components/player.cljs b/src/frontend/tubo/components/player.cljs
deleted file mode 100644
index a8f3a18..0000000
--- a/src/frontend/tubo/components/player.cljs
+++ /dev/null
@@ -1,244 +0,0 @@
-(ns tubo.components.player
- (:require
- [clojure.string :as str]
- [reagent.core :as r]
- [re-frame.core :as rf]
- ["@vidstack/react" :refer (MediaPlayer MediaProvider Poster)]
- ["@vidstack/react/player/layouts/default" :refer
- (defaultLayoutIcons DefaultVideoLayout DefaultAudioLayout)]))
-
-(defn get-video-player-sources
- [available-streams service-id]
- (if available-streams
- (if (= service-id 3)
- (map (fn [{:keys [content]}] {:src content :type "video/mp4"})
- (reverse available-streams))
- (->> available-streams
- (filter #(and (not= (:format %) "WEBMA_OPUS")
- (not= (:format %) "OPUS")
- (not= (:format %) "M4A")))
- (sort-by :bitrate)
- (#(if (empty? (filter (fn [x] (= (:format x) "MP3")) %))
- (reverse %)
- %))
- (map (fn [{:keys [content]}] {:src content :type "video/mp4"}))
- first))
- []))
-
-(defn video-player
- [_stream _!player]
- (let [!elapsed-time @(rf/subscribe [:elapsed-time])
- !main-player-first? (r/atom true)]
- (r/create-class
- {:component-will-unmount #(rf/dispatch [:main-player/ready false])
- :reagent-render
- (fn [{:keys [name video-streams audio-streams thumbnail-url service-id]}
- !player]
- (let [show-main-player? @(rf/subscribe [:main-player/show])]
- [:> MediaPlayer
- {:title name
- :src (get-video-player-sources (into video-streams
- audio-streams)
- service-id)
- :poster thumbnail-url
- :class "w-full xl:w-3/5 overflow-hidden"
- :playsInline true
- :ref #(reset! !player %)
- :loop (when show-main-player?
- (= @(rf/subscribe [:loop-playback]) :stream))
- :onSeeked (when show-main-player?
- #(reset! !elapsed-time (.-currentTime @!player)))
- :onTimeUpdate (when show-main-player?
- #(reset! !elapsed-time (.-currentTime @!player)))
- :onEnded #(when show-main-player?
- (rf/dispatch [:queue/change-pos
- (inc @(rf/subscribe
- [:queue-pos]))])
- (reset! !elapsed-time 0))
- :onLoadedData (fn []
- (when show-main-player?
- (rf/dispatch [:main-player/start]))
- (when (and @!main-player-first? show-main-player?)
- (reset! !main-player-first? false)))
- :onPlay #(rf/dispatch [:main-player/play])
- :onCanPlay #(rf/dispatch [:main-player/ready true])
- :onSourceChange #(when-not @!main-player-first?
- (reset! !elapsed-time 0))}
- [:> MediaProvider
- [:> Poster
- {:src thumbnail-url
- :alt name
- :class :vds-poster}]]
- [:> DefaultVideoLayout {:icons defaultLayoutIcons}]]))})))
-
-(defn get-audio-player-sources
- [available-streams]
- (if available-streams
- (->> available-streams
- (filter #(not= (:format %) "OPUS"))
- (sort-by :bitrate)
- (map (fn [{:keys [content]}] {:src content :type "video/mp4"})))
- []))
-
-(defn audio-player
- [_stream _!player]
- (let [!elapsed-time @(rf/subscribe [:elapsed-time])
- !bg-player-first? (r/atom nil)]
- (r/create-class
- {:component-will-unmount #(rf/dispatch [:bg-player/ready false])
- :reagent-render
- (fn [{:keys [name audio-streams]} !player]
- [:> MediaPlayer
- {:title name
- :class "invisible fixed"
- :controls []
- :src (get-audio-player-sources audio-streams)
- :viewType "audio"
- :ref #(reset! !player %)
- :loop (= @(rf/subscribe [:loop-playback]) :stream)
- :onCanPlay #(rf/dispatch [:bg-player/ready true])
- :onSeeked #(reset! !elapsed-time (.-currentTime @!player))
- :onTimeUpdate #(reset! !elapsed-time (.-currentTime @!player))
- :onEnded (fn []
- (rf/dispatch [:queue/change-pos
- (inc @(rf/subscribe [:queue-pos]))])
- (reset! !elapsed-time 0))
- :onPlay #(rf/dispatch [:bg-player/play])
- :onReplay (fn []
- (rf/dispatch [:bg-player/set-paused false])
- (reset! !elapsed-time 0))
- :onPause #(rf/dispatch [:bg-player/set-paused true])
- :onLoadedData (fn []
- (rf/dispatch [:bg-player/start])
- (when-not @!bg-player-first?
- (reset! !bg-player-first? true)))
- :onSourceChange #(when @!bg-player-first?
- (reset! !elapsed-time 0))}
- [:> MediaProvider]
- [:> DefaultAudioLayout {:icons defaultLayoutIcons}]])})))
-
-(defonce base-slider-classes
- ["h-2" "cursor-pointer" "appearance-none" "bg-neutral-300"
- "dark:bg-neutral-600"
- "rounded-full" "overflow-hidden" "focus:outline-none"
- "[&::-webkit-slider-thumb]:appearance-none"
- "[&::-webkit-slider-thumb]:border-0"
- "[&::-webkit-slider-thumb]:rounded-full"
- "[&::-webkit-slider-thumb]:h-2"
- "[&::-webkit-slider-thumb]:w-2"
- "[&::-webkit-slider-thumb]:shadow-[-405px_0_0_400px]"
- "[&::-moz-range-thumb]:border-0"
- "[&::-moz-range-thumb]:rounded-full"
- "[&::-moz-range-thumb]:h-2"
- "[&::-moz-range-thumb]:w-2"
- "[&::-moz-range-thumb]:shadow-[-405px_0_0_400px]"])
-
-(defn get-slider-shadow-classes
- [service-color]
- (case service-color
- "#cc0000" ["[&::-webkit-slider-thumb]:shadow-[#cc0000]"
- "[&::-moz-range-thumb]:shadow-[#cc0000]"]
- "#ff7700" ["[&::-webkit-slider-thumb]:shadow-[#ff7700]"
- "[&::-moz-range-thumb]:shadow-[#ff7700]"]
- "#333333" ["[&::-webkit-slider-thumb]:shadow-[#333333]"
- "[&::-moz-range-thumb]:shadow-[#333333]"]
- "#F2690D" ["[&::-webkit-slider-thumb]:shadow-[#F2690D]"
- "[&::-moz-range-thumb]:shadow-[#F2690D]"]
- "#629aa9" ["[&::-webkit-slider-thumb]:shadow-[#629aa9]"
- "[&::-moz-range-thumb]:shadow-[#629aa9]"]
- ["[&::-webkit-slider-thumb]:shadow-neutral-300"
- "[&::-moz-range-thumb]:shadow-neutral-300"]))
-
-(defn get-slider-bg-classes
- [service-color]
- (case service-color
- "#cc0000" ["[&::-webkit-slider-thumb]:bg-[#cc0000]"
- "[&::-moz-range-thumb]:bg-[#cc0000]"]
- "#ff7700" ["[&::-webkit-slider-thumb]:bg-[#ff7700]"
- "[&::-moz-range-thumb]:bg-[#ff7700]"]
- "#333333" ["[&::-webkit-slider-thumb]:bg-[#333333]"
- "[&::-moz-range-thumb]:bg-[#333333]"]
- "#F2690D" ["[&::-webkit-slider-thumb]:bg-[#F2690D]"
- "[&::-moz-range-thumb]:bg-[#F2690D]"]
- "#629aa9" ["[&::-webkit-slider-thumb]:bg-[#629aa9]"
- "[&::-moz-range-thumb]:bg-[#629aa9]"]
- ["[&::-webkit-slider-thumb]:bg-neutral-300"
- "[&::-moz-range-thumb]:bg-neutral-300"]))
-
-(defn time-slider
- [!player !elapsed-time service-color]
- (let [styles (concat base-slider-classes
- (get-slider-bg-classes service-color)
- (get-slider-shadow-classes service-color))
- bg-player-ready? @(rf/subscribe [:bg-player/ready])]
- [:input.w-full
- {:class styles
- :type "range"
- :on-input #(reset! !elapsed-time (.. % -target -value))
- :on-change #(when (and bg-player-ready? @!player)
- (set! (.-currentTime @!player) @!elapsed-time))
- :max (if (and bg-player-ready?
- @!player
- (not (js/isNaN (.-duration @!player))))
- (.floor js/Math (.-duration @!player))
- 100)
- :value @!elapsed-time}]))
-
-(defn button
- [& {:keys [icon on-click disabled? show-on-mobile? extra-classes]}]
- [:button.outline-none.focus:ring-transparent.px-2.pt-1
- {:class (into (into (when disabled? [:opacity-50 :cursor-auto])
- (when-not show-on-mobile? [:hidden :lg:block]))
- extra-classes)
- :on-click on-click}
- icon])
-
-(defn loop-button
- [loop-playback color show-on-mobile?]
- [button
- :icon
- [:div.relative.flex.items-center
- [:i.fa-solid.fa-repeat
- {:style {:color (when loop-playback color)}}]
- (when (= loop-playback :stream)
- [:div.absolute.w-full.h-full.flex.justify-center.items-center.font-bold
- {:class "text-[6px]"
- :style {:color (when loop-playback color)}}
- "1"])]
- :on-click #(rf/dispatch [:player/loop])
- :extra-classes [:text-sm]
- :show-on-mobile? show-on-mobile?])
-
-(defn shuffle-button
- [shuffle? color show-on-mobile?]
- [button
- :icon
- [:i.fa-solid.fa-shuffle {:style {:color (when shuffle? color)}}]
- :on-click #(rf/dispatch [:queue/shuffle (not shuffle?)])
- :extra-classes [:text-sm]
- :show-on-mobile? show-on-mobile?])
-
-(defn volume-slider
- [_player _volume-level _muted? _service-color]
- (let [show-slider? (r/atom nil)]
- (fn [player volume-level muted? service-color]
- (let [styles (concat ["rotate-[270deg]"]
- base-slider-classes
- (get-slider-bg-classes service-color)
- (get-slider-shadow-classes service-color))]
- [:div.relative.flex.flex-col.justify-center.items-center
- {:on-mouse-over #(reset! show-slider? true)
- :on-mouse-out #(reset! show-slider? false)}
- [button
- :icon
- (if muted? [:i.fa-solid.fa-volume-xmark] [:i.fa-solid.fa-volume-low])
- :on-click #(rf/dispatch [:bg-player/mute (not muted?) player])
- :extra-classes [:pl-3 :pr-2]]
- (when @show-slider?
- [:input.absolute.w-24.ml-2.m-1.bottom-16
- {:class (str/join " " styles)
- :type "range"
- :on-input #(rf/dispatch [:player/change-volume
- (.. % -target -value) player])
- :max 100
- :value volume-level}])]))))
diff --git a/src/frontend/tubo/events.cljs b/src/frontend/tubo/events.cljs
index cfa5a78..31ecc8c 100644
--- a/src/frontend/tubo/events.cljs
+++ b/src/frontend/tubo/events.cljs
@@ -6,13 +6,14 @@
[reagent.core :as r]
[re-frame.core :as rf]
[re-promise.core]
- [reitit.frontend.easy :as rfe]
- [reitit.frontend.controllers :as rfc]
+ [tubo.bg-player.events]
[tubo.bookmarks.events]
[tubo.channel.events]
[tubo.comments.events]
[tubo.kiosks.events]
+ [tubo.main-player.events]
[tubo.modals.events]
+ [tubo.navigation.events]
[tubo.notifications.events]
[tubo.player.events]
[tubo.playlist.events]
@@ -30,13 +31,13 @@
(fn [{:keys [store]} _]
(let [if-nil #(if (nil? %1) %2 %1)]
{:db
- {:paused true
- :muted (:muted store)
+ {:player/paused true
+ :player/muted (:player/muted store)
:queue (if-nil (:queue store) [])
:service-id (if-nil (:service-id store) 0)
- :loop-playback (if-nil (:loop-playback store) :playlist)
- :queue-pos (if-nil (:queue-pos store) 0)
- :volume-level (if-nil (:volume-level store) 100)
+ :player/loop (if-nil (:player/loop store) :playlist)
+ :queue/position (if-nil (:palyer/position store) 0)
+ :player/volume (if-nil (:player/volume store) 100)
:bg-player/show (:bg-player/show store)
:bookmarks (if-nil (:bookmarks store)
[{:id (nano-id) :name "Liked Streams"}])
@@ -82,54 +83,6 @@
(fn [_ [_ element]]
{:scroll-into-view! element}))
-(rf/reg-fx
- :history-go!
- (fn [idx]
- (.go js/window.history idx)))
-
-(rf/reg-event-fx
- :history-go
- (fn [_ [_ idx]]
- {:history-go! idx}))
-
-(rf/reg-fx
- :navigate!
- (fn [{:keys [name params query]}]
- (rfe/push-state name params query)))
-
-(rf/reg-event-fx
- :navigate
- (fn [_ [_ route]]
- {:navigate! route}))
-
-(rf/reg-event-fx
- :toggle-mobile-nav
- (fn [{:keys [db]} _]
- {:db (assoc db :show-mobile-nav (not (:show-mobile-nav db)))
- :body-overflow (not (:show-mobile-nav db))}))
-
-(rf/reg-event-fx
- :navigated
- (fn [{:keys [db]} [_ new-match]]
- (let [old-match (:current-match db)
- controllers (rfc/apply-controllers (:controllers old-match) new-match)
- match (assoc new-match :controllers controllers)]
- {:db (-> db
- (assoc :current-match match)
- (assoc :show-mobile-nav false)
- (assoc :show-pagination-loading false))
- :scroll-to-top nil
- :body-overflow false
- :fx [(when (:main-player/show db)
- [:dispatch [:player/switch-from-main]])
- [:dispatch [:queue/show false]]
- [:dispatch
- [:services/fetch-all
- [:services/load] [:bad-response]]]
- [:dispatch
- [:kiosks/fetch-all (:service-id db)
- [:kiosks/load] [:bad-response]]]]})))
-
(defonce timeouts! (r/atom {}))
(rf/reg-fx
@@ -183,4 +136,4 @@
(rf/reg-event-fx
:change-view
(fn [{:keys [db]} [_ view]]
- {:db (assoc-in db [:current-match :data :view] view)}))
+ {:db (assoc-in db [:navigation/current-match :data :view] view)}))
diff --git a/src/frontend/tubo/components/items.cljs b/src/frontend/tubo/items/views.cljs
index cc8f3f1..343b3c2 100644
--- a/src/frontend/tubo/components/items.cljs
+++ b/src/frontend/tubo/items/views.cljs
@@ -1,10 +1,10 @@
-(ns tubo.components.items
+(ns tubo.items.views
(:require
[re-frame.core :as rf]
[reagent.core :as r]
[reitit.frontend.easy :as rfe]
[tubo.bookmarks.modals :as bookmarks]
- [tubo.components.layout :as layout]
+ [tubo.layout.views :as layout]
[tubo.modals.views :as modals]
[tubo.utils :as utils]))
@@ -22,11 +22,10 @@
(if (or (= type "stream") audio-streams video-streams)
[{:label "Add to queue"
:icon [:i.fa-solid.fa-headphones]
- :on-click #(rf/dispatch [:player/switch-to-background item
- true])}
+ :on-click #(rf/dispatch [:bg-player/show item true])}
{:label "Play radio"
:icon [:i.fa-solid.fa-tower-cell]
- :on-click #(rf/dispatch [:player/start-radio item])}
+ :on-click #(rf/dispatch [:bg-player/start-radio item])}
{:label (if liked? "Remove favorite" "Favorite")
:icon [:i.fa-solid.fa-heart
(when (and liked? service-id)
@@ -46,7 +45,7 @@
:on-click #(rf/dispatch [:bookmark/remove item])})
{:label "Show channel details"
:icon [:i.fa-solid.fa-user]
- :on-click #(rf/dispatch [:navigate
+ :on-click #(rf/dispatch [:navigation/navigate
{:name :channel-page
:params {}
:query {:url uploader-url}}])}]
diff --git a/src/frontend/tubo/kiosks/events.cljs b/src/frontend/tubo/kiosks/events.cljs
index 720715e..75fc9a2 100644
--- a/src/frontend/tubo/kiosks/events.cljs
+++ b/src/frontend/tubo/kiosks/events.cljs
@@ -2,7 +2,7 @@
(:require
[re-frame.core :as rf]
[tubo.api :as api]
- [tubo.components.layout :as layout]))
+ [tubo.layout.views :as layout]))
(rf/reg-event-db
:kiosks/load
@@ -93,7 +93,7 @@
(fn [_ [_ service-id]]
{:fx [[:dispatch [:services/change-id service-id]]
[:dispatch
- [:navigate
+ [:navigation/navigate
{:name :kiosk-page
:params {}
:query {:serviceId service-id}}]]]}))
diff --git a/src/frontend/tubo/kiosks/views.cljs b/src/frontend/tubo/kiosks/views.cljs
index bd6c284..e42719c 100644
--- a/src/frontend/tubo/kiosks/views.cljs
+++ b/src/frontend/tubo/kiosks/views.cljs
@@ -2,8 +2,8 @@
(:require
[re-frame.core :as rf]
[reitit.frontend.easy :as rfe]
- [tubo.components.items :as items]
- [tubo.components.layout :as layout]))
+ [tubo.items.views :as items]
+ [tubo.layout.views :as layout]))
(defn kiosk-active?
[& {:keys [kiosk kiosk-id service-id default-service default-kiosk path]}]
diff --git a/src/frontend/tubo/components/layout.cljs b/src/frontend/tubo/layout/views.cljs
index 48d4259..90dc3ef 100644
--- a/src/frontend/tubo/components/layout.cljs
+++ b/src/frontend/tubo/layout/views.cljs
@@ -1,4 +1,4 @@
-(ns tubo.components.layout
+(ns tubo.layout.views
(:require
[clojure.string :as str]
[re-frame.core :as rf]
@@ -234,5 +234,5 @@
(when parse-error
[:span (:status-text parse-error)])
[:div.flex.justify-center.gap-x-3
- [primary-button "Go Back" #(rf/dispatch [:history-go -1])]
+ [primary-button "Go Back" #(rf/dispatch [:navigation/history-go -1])]
[secondary-button "Retry" #(rf/dispatch cb)]]]])
diff --git a/src/frontend/tubo/main_player/events.cljs b/src/frontend/tubo/main_player/events.cljs
new file mode 100644
index 0000000..a5e8414
--- /dev/null
+++ b/src/frontend/tubo/main_player/events.cljs
@@ -0,0 +1,53 @@
+(ns tubo.main-player.events
+ (:require
+ [re-frame.core :as rf]
+ [vimsical.re-frame.cofx.inject :as inject]))
+
+(rf/reg-event-fx
+ :main-player/seek
+ [(rf/inject-cofx ::inject/sub [:main-player])]
+ (fn [{:keys [main-player]} [_ time]]
+ {:player/time {:time time :player main-player}}))
+
+(rf/reg-event-fx
+ :main-player/pause
+ [(rf/inject-cofx ::inject/sub [:main-player])]
+ (fn [{:keys [db main-player]} [_ paused?]]
+ (when (:main-player/ready db)
+ {:player/pause {:paused? paused?
+ :player main-player}})))
+
+(rf/reg-event-fx
+ :main-player/play
+ [(rf/inject-cofx ::inject/sub [:main-player])]
+ (fn [{:keys [db main-player]}]
+ {:fx [(when (and (:bg-player/ready db) main-player @main-player)
+ [:dispatch [:bg-player/pause true]])]}))
+
+(rf/reg-event-fx
+ :main-player/start
+ [(rf/inject-cofx ::inject/sub [:elapsed-time])]
+ (fn [{:keys [db elapsed-time]} _]
+ {:fx [[:dispatch [:main-player/pause false]]
+ (when (and (:main-player/show db) (not (:bg-player/ready db)))
+ [:dispatch [:main-player/seek @elapsed-time]])]}))
+
+(rf/reg-event-db
+ :main-player/ready
+ (fn [db [_ ready]]
+ (assoc db :main-player/ready ready)))
+
+(rf/reg-event-db
+ :main-player/toggle-layout
+ (fn [db [_ layout]]
+ (assoc-in db
+ [:queue (:queue-pos db) layout]
+ (not (get-in db [:queue (:queue/position db) layout])))))
+
+(rf/reg-event-fx
+ :main-player/show
+ (fn [{:keys [db]} [_ val]]
+ {:db (apply assoc
+ (assoc db :main-player/show val)
+ (when val [:search/show-form false]))
+ :body-overflow val}))
diff --git a/src/frontend/tubo/main_player/subs.cljs b/src/frontend/tubo/main_player/subs.cljs
new file mode 100644
index 0000000..eca94db
--- /dev/null
+++ b/src/frontend/tubo/main_player/subs.cljs
@@ -0,0 +1,20 @@
+(ns tubo.main-player.subs
+ (:require
+ [re-frame.core :as rf]))
+
+(defonce !player (atom nil))
+
+(rf/reg-sub
+ :main-player/ready
+ (fn [db _]
+ (:main-player/ready db)))
+
+(rf/reg-sub
+ :main-player/show
+ (fn [db _]
+ (:main-player/show db)))
+
+(rf/reg-sub
+ :main-player
+ (fn [_ _]
+ !player))
diff --git a/src/frontend/tubo/main_player/views.cljs b/src/frontend/tubo/main_player/views.cljs
new file mode 100644
index 0000000..be4c223
--- /dev/null
+++ b/src/frontend/tubo/main_player/views.cljs
@@ -0,0 +1,33 @@
+(ns tubo.main-player.views
+ (:require
+ [re-frame.core :as rf]
+ [tubo.layout.views :as layout]
+ [tubo.player.views :as player]
+ [tubo.queue.views :as queue]
+ [tubo.stream.views :as stream]))
+
+(defn player
+ []
+ (let [queue @(rf/subscribe [:queue])
+ queue-pos @(rf/subscribe [:queue/position])
+ bookmarks @(rf/subscribe [:bookmarks])
+ !player @(rf/subscribe [:main-player])
+ stream @(rf/subscribe [:queue/current])
+ show-player? @(rf/subscribe [:main-player/show])]
+ [:div.fixed.w-full.bg-neutral-100.dark:bg-neutral-900.overflow-auto.z-10.transition-all.ease-in-out
+ {:class ["h-[calc(100%-56px)]"
+ (if show-player? "translate-y-0" "translate-y-full")]}
+ (when (and show-player? stream)
+ [:div
+ [:div.flex.flex-col.items-center.w-full.xl:py-6
+ [player/video-player stream !player]]
+ [:div.flex.items-center.justify-center
+ [:div.flex.flex-col.gap-y-1.w-full.h-fit.max-h-64.overflow-y-auto
+ {:class ["lg:w-4/5" "xl:w-3/5"]}
+ (for [[i item] (map-indexed vector queue)]
+ ^{:key i} [queue/queue-item item queue queue-pos i bookmarks])]]
+ [layout/content-container
+ [stream/metadata stream]
+ [stream/description stream]
+ [stream/comments stream]
+ [stream/suggested stream]]])]))
diff --git a/src/frontend/tubo/modals/views.cljs b/src/frontend/tubo/modals/views.cljs
index df59034..1ff4bb1 100644
--- a/src/frontend/tubo/modals/views.cljs
+++ b/src/frontend/tubo/modals/views.cljs
@@ -1,7 +1,7 @@
(ns tubo.modals.views
(:require
[re-frame.core :as rf]
- [tubo.components.layout :as layout]))
+ [tubo.layout.views :as layout]))
(defn modal-content
[title body & extra-buttons]
diff --git a/src/frontend/tubo/navigation/events.cljs b/src/frontend/tubo/navigation/events.cljs
new file mode 100644
index 0000000..a4c961d
--- /dev/null
+++ b/src/frontend/tubo/navigation/events.cljs
@@ -0,0 +1,55 @@
+(ns tubo.navigation.events
+ (:require
+ [re-frame.core :as rf]
+ [reitit.frontend.easy :as rfe]
+ [reitit.frontend.controllers :as rfc]))
+
+(rf/reg-fx
+ :history-go!
+ (fn [idx]
+ (.go js/window.history idx)))
+
+(rf/reg-event-fx
+ :navigation/history-go
+ (fn [_ [_ idx]]
+ {:history-go! idx}))
+
+(rf/reg-fx
+ :navigate!
+ (fn [{:keys [name params query]}]
+ (rfe/push-state name params query)))
+
+(rf/reg-event-fx
+ :navigation/navigate
+ (fn [_ [_ route]]
+ {:navigate! route}))
+
+(rf/reg-event-fx
+ :navigation/toggle-mobile-menu
+ (fn [{:keys [db]} _]
+ {:db (assoc db
+ :navigation/show-mobile-menu
+ (not (:navigation/show-mobile-menu db)))
+ :body-overflow (not (:navigation/show-mobile-menu db))}))
+
+(rf/reg-event-fx
+ :navigation/navigated
+ (fn [{:keys [db]} [_ new-match]]
+ (let [old-match (:navigation/current-match db)
+ controllers (rfc/apply-controllers (:controllers old-match) new-match)
+ match (assoc new-match :controllers controllers)]
+ {:db (-> db
+ (assoc :navigation/current-match match)
+ (assoc :navigation/show-mobile-menu false)
+ (assoc :layout/show-pagination-loading false))
+ :scroll-to-top nil
+ :body-overflow false
+ :fx [(when (:main-player/show db)
+ [:dispatch [:bg-player/switch-from-main]])
+ [:dispatch [:queue/show false]]
+ [:dispatch
+ [:services/fetch-all
+ [:services/load] [:bad-response]]]
+ [:dispatch
+ [:kiosks/fetch-all (:service-id db)
+ [:kiosks/load] [:bad-response]]]]})))
diff --git a/src/frontend/tubo/navigation/subs.cljs b/src/frontend/tubo/navigation/subs.cljs
new file mode 100644
index 0000000..fc696fc
--- /dev/null
+++ b/src/frontend/tubo/navigation/subs.cljs
@@ -0,0 +1,13 @@
+(ns tubo.navigation.subs
+ (:require
+ [re-frame.core :as rf]))
+
+(rf/reg-sub
+ :navigation/show-mobile-menu
+ (fn [db _]
+ (:navigation/show-mobile-menu db)))
+
+(rf/reg-sub
+ :navigation/current-match
+ (fn [db _]
+ (:navigation/current-match db)))
diff --git a/src/frontend/tubo/navigation/views.cljs b/src/frontend/tubo/navigation/views.cljs
index b899be7..a158420 100644
--- a/src/frontend/tubo/navigation/views.cljs
+++ b/src/frontend/tubo/navigation/views.cljs
@@ -3,52 +3,51 @@
[re-frame.core :as rf]
[reagent.core :as r]
[reitit.frontend.easy :as rfe]
- [tubo.components.layout :as layout]
+ [tubo.channel.views :as channel]
[tubo.kiosks.views :as kiosks]
+ [tubo.layout.views :as layout]
[tubo.services.views :as services]
- [tubo.stream.views :as stream]
- [tubo.channel.views :as channel]))
+ [tubo.stream.views :as stream]))
(defn search-form
[]
(let [!query (r/atom "")
!input (r/atom nil)]
(fn []
- (let [search-query @(rf/subscribe [:search-query])
- show-search-form? @(rf/subscribe [:show-search-form])
+ (let [search-query @(rf/subscribe [:search/query])
+ show-search-form? @(rf/subscribe [:search/show-form])
service-id @(rf/subscribe [:service-id])]
- [:form.relative.text-white.flex.items-center.flex-auto.lg:flex-1
+ [:form.relative.text-white.flex.items-center.justify-center.flex-auto.lg:flex-1
{:class (when-not show-search-form? "hidden")
:on-submit #(do (.preventDefault %)
(when-not (empty? @!query)
- (rf/dispatch [:navigate
+ (rf/dispatch [:navigation/navigate
{:name :search-page
:params {}
:query {:q search-query
:serviceId service-id}}])))}
- [:div.flex.justify-center.flex-auto.lg:flex-1
- [:button.mx-2
- {:on-click #(rf/dispatch [:search/show-form false])}
- [:i.fa-solid.fa-arrow-left]]
- [:input.w-full.lg:w-96.bg-transparent.py-2.pl-0.pr-6.mx-2.border-none.focus:ring-transparent.placeholder-white
- {:type "text"
- :ref #(do (reset! !input %)
- (when %
- (.focus %)))
- :default-value @!query
- :on-change #(let [input (.. % -target -value)]
- (when-not (empty? input)
- (rf/dispatch [:search/change-query input]))
- (reset! !query input))
- :placeholder "Search"}]
- [:button.mx-4 {:type "submit"} [:i.fa-solid.fa-search]]
- [:button.mx-4.text-xs.absolute.right-8.top-3
- {:on-click #(when @!input
- (set! (.-value @!input) "")
- (reset! !query "")
- (.focus @!input))
- :class (when (empty? @!query) :invisible)}
- [:i.fa-solid.fa-circle-xmark]]]]))))
+ [:button.mx-2
+ {:on-click #(rf/dispatch [:search/show-form false])}
+ [:i.fa-solid.fa-arrow-left]]
+ [:input.w-full.lg:w-96.bg-transparent.py-2.pl-0.pr-6.mx-2.border-none.focus:ring-transparent.placeholder-white
+ {:type "text"
+ :ref #(do (reset! !input %)
+ (when %
+ (.focus %)))
+ :default-value @!query
+ :on-change #(let [input (.. % -target -value)]
+ (when-not (empty? input)
+ (rf/dispatch [:search/change-query input]))
+ (reset! !query input))
+ :placeholder "Search"}]
+ [:button.mx-4 {:type "submit"} [:i.fa-solid.fa-search]]
+ [:button.mx-4.text-xs.absolute.right-8.top-3
+ {:on-click #(when @!input
+ (set! (.-value @!input) "")
+ (reset! !query "")
+ (.focus @!input))
+ :class (when (empty? @!query) :invisible)}
+ [:i.fa-solid.fa-circle-xmark]]]))))
(defn mobile-nav-item
[route icon label & {:keys [new-tab? active?]}]
@@ -62,7 +61,8 @@
[show-mobile-nav? service-color services available-kiosks &
{:keys [service-id] :as kiosk-args}]
[:<>
- [layout/focus-overlay #(rf/dispatch [:toggle-mobile-nav]) show-mobile-nav?]
+ [layout/focus-overlay #(rf/dispatch [:navigation/toggle-mobile-menu])
+ show-mobile-nav?]
[:div.fixed.overflow-x-hidden.min-h-screen.w-60.top-0.transition-all.ease-in-out.delay-75.bg-white.dark:bg-neutral-900.z-20
{:class [(if show-mobile-nav? "left-0" "left-[-245px]")]}
[:div.flex.justify-center.py-4.items-center.text-white
@@ -88,8 +88,8 @@
(defn nav-left-content
[title]
- (let [show-search-form? @(rf/subscribe [:show-search-form])
- show-queue? @(rf/subscribe [:show-queue])
+ (let [show-search-form? @(rf/subscribe [:search/show-form])
+ show-queue? @(rf/subscribe [:queue/show])
show-main-player? @(rf/subscribe [:main-player/show])]
[:div.flex.items-center.gap-x-4
(when-not (or show-queue? show-main-player?)
@@ -98,17 +98,16 @@
[layout/logo :height 35 :width 35]]])
(when (and show-queue? (not show-search-form?))
[:button.text-white.mx-2
- {:on-click #(rf/dispatch [:queue/show
- false])}
+ {:on-click #(rf/dispatch [:queue/show false])}
[:i.fa-solid.fa-arrow-left]])
(when (and show-main-player? (not show-search-form?))
[:button.text-white.mx-2
- {:on-click #(rf/dispatch [:player/switch-from-main nil])}
+ {:on-click #(rf/dispatch [:bg-player/switch-from-main nil])}
[:i.fa-solid.fa-arrow-left]])
(when-not (or show-queue? show-main-player? show-search-form?)
[:button.text-white.mx-3.lg:hidden
{:on-click #(rf/dispatch
- [:toggle-mobile-nav])}
+ [:navigation/toggle-mobile-menu])}
[:i.fa-solid.fa-bars]])
(when-not (or show-queue? show-main-player? show-search-form?)
[:h1.text-white.text-lg.sm:text-xl.font-bold.line-clamp-1.lg:hidden
@@ -122,9 +121,9 @@
(defn nav-right-content
[{{:keys [kioskId]} :query-params path :path :as match}]
- (let [show-search-form? @(rf/subscribe [:show-search-form])
+ (let [show-search-form? @(rf/subscribe [:search/show-form])
show-main-player? @(rf/subscribe [:main-player/show])
- show-queue? @(rf/subscribe [:show-queue])
+ show-queue? @(rf/subscribe [:queue/show])
service-id @(rf/subscribe [:service-id])
service-color @(rf/subscribe [:service-color])
services @(rf/subscribe [:services])
@@ -167,7 +166,7 @@
(let [service-id @(rf/subscribe [:service-id])
service-color @(rf/subscribe [:service-color])
services @(rf/subscribe [:services])
- show-mobile-nav? @(rf/subscribe [:show-mobile-nav])
+ show-mobile-nav? @(rf/subscribe [:navigation/show-mobile-menu])
settings @(rf/subscribe [:settings])
kiosks @(rf/subscribe [:kiosks])]
[:nav.sticky.flex.items-center.px-2.h-14.top-0.z-20
diff --git a/src/frontend/tubo/player/events.cljs b/src/frontend/tubo/player/events.cljs
index 10195b5..7246a7f 100644
--- a/src/frontend/tubo/player/events.cljs
+++ b/src/frontend/tubo/player/events.cljs
@@ -1,116 +1,42 @@
(ns tubo.player.events
(:require
[goog.object :as gobj]
- [re-frame.core :as rf]
- [vimsical.re-frame.cofx.inject :as inject]))
+ [re-frame.core :as rf]))
(rf/reg-fx
- :volume
+ :player/volume
(fn [{:keys [player volume]}]
- (when @player
+ (when (and player @player)
(set! (.-volume @player) (/ volume 100)))))
(rf/reg-fx
- :mute
+ :player/mute
(fn [{:keys [player muted?]}]
- (when @player
+ (when (and player @player)
(set! (.-muted @player) muted?))))
(rf/reg-fx
- :src
+ :player/src
(fn [{:keys [player src]}]
(set! (.-source @player) (clj->js src))))
(rf/reg-fx
- :loop
+ :player/loop
(fn [{:keys [player loop]}]
(set! (.-loop @player) loop)))
(rf/reg-fx
- :current-time
+ :player/time
(fn [{:keys [time player]}]
- (set! (.-currentTime @player) time)))
-
-(rf/reg-event-fx
- :bg-player/seek
- [(rf/inject-cofx ::inject/sub [:player])]
- (fn [{:keys [db player]} [_ time]]
- (when (:bg-player/ready db)
- {:current-time {:time time :player player}})))
-
-(rf/reg-event-fx
- :main-player/seek
- [(rf/inject-cofx ::inject/sub [:main-player])]
- (fn [{:keys [main-player]} [_ time]]
- {:current-time {:time time :player main-player}}))
+ (when (and player @player)
+ (set! (.-currentTime @player) time))))
(rf/reg-fx
- :pause!
+ :player/pause
(fn [{:keys [paused? player]}]
- (when @player
+ (when (and player @player)
(set! (.-paused @player) paused?))))
-(rf/reg-event-db
- :bg-player/set-paused
- (fn [db [_ val]]
- (assoc db :paused val)))
-
-(rf/reg-event-fx
- :bg-player/pause
- [(rf/inject-cofx ::inject/sub [:player])]
- (fn [{:keys [player]} [_ paused?]]
- {:pause! {:paused? paused?
- :player player}}))
-
-(rf/reg-event-fx
- :main-player/pause
- [(rf/inject-cofx ::inject/sub [:main-player])]
- (fn [{:keys [db main-player]} [_ paused?]]
- (when (:main-player/ready db)
- {:pause! {:paused? paused?
- :player main-player}})))
-
-(rf/reg-event-fx
- :bg-player/play
- [(rf/inject-cofx ::inject/sub [:elapsed-time])
- (rf/inject-cofx ::inject/sub [:main-player])]
- (fn [{:keys [db elapsed-time main-player]}]
- {:fx [[:dispatch [:bg-player/set-paused false]]
- [:dispatch [:bg-player/seek @elapsed-time]]
- (when (and (:main-player/ready db) @main-player)
- [:dispatch [:main-player/pause true]])]}))
-
-(rf/reg-event-fx
- :main-player/play
- [(rf/inject-cofx ::inject/sub [:elapsed-time])
- (rf/inject-cofx ::inject/sub [:player])]
- (fn [{:keys [db player]}]
- {:fx [(when (and (:bg-player/ready db) @player)
- [:dispatch [:bg-player/pause true]])]}))
-
-(rf/reg-event-fx
- :bg-player/stop
- (fn [_]
- {:fx [[:dispatch [:bg-player/pause true]]
- [:dispatch [:bg-player/seek 0]]]}))
-
-(rf/reg-event-fx
- :bg-player/start
- [(rf/inject-cofx ::inject/sub [:player])
- (rf/inject-cofx ::inject/sub [:elapsed-time])]
- (fn [{:keys [db player]} _]
- {:fx [[:dispatch [:bg-player/set-paused true]]
- [:dispatch [:bg-player/pause false]]
- [:dispatch [:player/change-volume (:volume-level db) player]]]}))
-
-(rf/reg-event-fx
- :main-player/start
- [(rf/inject-cofx ::inject/sub [:elapsed-time])]
- (fn [{:keys [db elapsed-time]} _]
- {:fx [[:dispatch [:main-player/pause false]]
- (when (and (:main-player/show db) (not (:bg-player/ready db)))
- [:dispatch [:main-player/seek @elapsed-time]])]}))
-
(rf/reg-fx
:media-session-metadata
(fn [metadata]
@@ -122,7 +48,7 @@
:media-session-handlers
(fn [{:keys [current-pos player]}]
(when (gobj/containsKey js/navigator "mediaSession")
- (let [current-time (and @player (.-currentTime @player))
+ (let [current-time (and player @player (.-currentTime @player))
update-position
#(.setPositionState js/navigator.mediaSession
{:duration (.-duration @player)
@@ -153,177 +79,17 @@
:player/change-volume
[(rf/inject-cofx :store)]
(fn [{:keys [db store]} [_ value player]]
- {:db (assoc db :volume-level value)
- :store (assoc store :volume-level value)
- :volume {:player player :volume value}}))
-
-(rf/reg-event-fx
- :bg-player/mute
- [(rf/inject-cofx :store)]
- (fn [{:keys [db store]} [_ value player]]
- {:db (assoc db :muted value)
- :store (assoc store :muted value)
- :mute {:player player :muted? value}}))
-
-(rf/reg-event-fx
- :bg-player/hide
- [(rf/inject-cofx :store)]
- (fn [{:keys [db store]} _]
- {:db (assoc db :bg-player/show false)
- :store (assoc store :bg-player/show false)}))
+ {:db (assoc db :player/volume value)
+ :store (assoc store :player/volume value)
+ :player/volume {:player player :volume value}}))
(rf/reg-event-fx
:player/loop
[(rf/inject-cofx :store)]
(fn [{:keys [db store]} _]
- (let [loop-state (case (:loop-playback db)
+ (let [loop-state (case (:player/loop db)
:stream false
:playlist :stream
:playlist)]
- {:db (assoc db :loop-playback loop-state)
- :store (assoc store :loop-playback loop-state)})))
-
-(rf/reg-event-fx
- :bg-player/dispose
- [(rf/inject-cofx :store)]
- (fn [{:keys [db store]} _]
- (let [remove-entries
- (fn [elem]
- (-> elem
- (assoc :queue [])
- (assoc :queue-pos 0)))]
- {:db (remove-entries db)
- :store (remove-entries store)
- :fx [[:dispatch [:bg-player/pause true]]
- [:dispatch [:bg-player/seek 0]]
- [:dispatch [:bg-player/hide]]]})))
-
-(rf/reg-event-db
- :bg-player/ready
- (fn [db [_ ready]]
- (assoc db :bg-player/ready ready)))
-
-(rf/reg-event-db
- :main-player/ready
- (fn [db [_ ready]]
- (assoc db :main-player/ready ready)))
-
-(rf/reg-event-fx
- :player/switch-to-background
- [(rf/inject-cofx :store)]
- (fn [{:keys [db store]} [_ stream notify?]]
- (let [updated-db (update db :queue conj stream)
- idx (.indexOf (:queue updated-db) stream)]
- {:db updated-db
- :store (assoc store :queue (:queue updated-db))
- :fx [[:dispatch
- [:player/fetch-stream
- (:url stream) idx (= (count (:queue db)) 0)]]
- (when (and notify? (not (= (count (:queue db)) 0)))
- [:dispatch
- [:notifications/add
- {:status-text "Added stream to queue"
- :failure :info}]])]})))
-
-(rf/reg-event-fx
- :player/show-main-player
- (fn [{:keys [db]} [_ val]]
- {:db (apply assoc
- (assoc db :main-player/show val)
- (when val [:show-search-form false]))
- :body-overflow val}))
-
-(rf/reg-event-fx
- :player/switch-from-main
- [(rf/inject-cofx ::inject/sub [:elapsed-time])]
- (fn [{:keys [db]} _]
- {:db (assoc db :bg-player/show true)
- :fx [[:dispatch [:player/show-main-player false]]
- [:dispatch [:main-player/pause true]]]}))
-
-(rf/reg-event-fx
- :player/switch-to-main
- [(rf/inject-cofx :store)]
- (fn [{:keys [db]} _]
- {:fx [[:dispatch [:player/show-main-player true]]]
- :db (assoc db :bg-player/show false)
- :scroll-to-top nil}))
-
-(rf/reg-event-fx
- :player/load-related-streams
- (fn [_ [_ res]]
- (let [{:keys [related-streams]} (js->clj res :keywordize-keys true)]
- {:fx [[:dispatch [:queue/add-n related-streams]]]})))
-
-(rf/reg-event-fx
- :player/load-stream
- [(rf/inject-cofx :store)
- (rf/inject-cofx ::inject/sub [:player])]
- (fn [{:keys [db store player]} [_ idx play? res]]
- (let [stream-res (js->clj res :keywordize-keys true)]
- {:db (assoc db
- :bg-player/show (not (:main-player/show db))
- :bg-player/loading false)
- :store (assoc store :bg-player/show (not (:main-player/show db)))
- :fx (apply conj
- [(when play?
- [:dispatch [:queue/change-stream stream-res idx]])]
- (when (and (:bg-player/ready db) play?)
- [[:media-session-metadata
- {:title (:name stream-res)
- :artist (:uploader-name stream-res)
- :artwork [{:src (:thumbnail-url stream-res)}]}]
- [:media-session-handlers
- {:current-pos idx
- :player player}]]))})))
-
-(rf/reg-event-fx
- :player/bad-response
- (fn [{:keys [db]} [_ idx play? res]]
- {:db (assoc db
- :bg-player/loading
- false)
- :fx [[:dispatch [:bad-response res]]
- (when play?
- (if (> (-> db
- :queue
- count)
- 1)
- [:dispatch [:queue/change-pos (inc idx)]]
- [:dispatch [:bg-player/dispose]]))]}))
-
-(rf/reg-event-fx
- :player/fetch-related-streams
- (fn [{:keys [db]} [_ url]]
- {:fx [[:dispatch
- [:stream/fetch url
- [:player/load-related-streams]] [:bad-response]]]
- :db (assoc db :bg-player/loading true)}))
-
-(rf/reg-event-fx
- :player/fetch-stream
- (fn [{:keys [db]} [_ url idx play?]]
- {:fx [[:dispatch
- [:stream/fetch url
- [:player/load-stream idx play?]
- [:player/bad-response idx play?]]]]
- :db (assoc db :bg-player/loading play?)}))
-
-(rf/reg-event-fx
- :player/start-radio
- (fn [{:keys [db]} [_ stream]]
- {:fx [[:dispatch [:player/switch-to-background stream]]
- (when (not= (count (:queue db)) 0)
- [:dispatch [:queue/change-pos (count (:queue db))]])
- [:dispatch [:player/fetch-related-streams (:url stream)]]
- [:dispatch
- [:notifications/add
- {:status-text "Started stream radio"
- :failure :info}]]]}))
-
-(rf/reg-event-db
- :main-player/toggle-layout
- (fn [db [_ layout]]
- (assoc-in db
- [:queue (:queue-pos db) layout]
- (not (get-in db [:queue (:queue-pos db) layout])))))
+ {:db (assoc db :player/loop loop-state)
+ :store (assoc store :player/loop loop-state)})))
diff --git a/src/frontend/tubo/player/subs.cljs b/src/frontend/tubo/player/subs.cljs
index 312451d..a4f726d 100644
--- a/src/frontend/tubo/player/subs.cljs
+++ b/src/frontend/tubo/player/subs.cljs
@@ -3,71 +3,34 @@
[re-frame.core :as rf]
[reagent.core :as r]))
-(defonce !player (atom nil))
-(defonce !main-player (atom nil))
(defonce !elapsed-time (r/atom 0))
(rf/reg-sub
- :player
- (fn [_ _]
- !player))
-
-(rf/reg-sub
- :main-player
- (fn [_ _]
- !main-player))
-
-(rf/reg-sub
- :bg-player/ready
+ :player/loop
(fn [db _]
- (:bg-player/ready db)))
+ (:player/loop db)))
(rf/reg-sub
- :main-player/ready
+ :player/shuffled
(fn [db _]
- (:main-player/ready db)))
+ (:player/shuffled db)))
(rf/reg-sub
- :bg-player/show
+ :player/paused
(fn [db _]
- (:bg-player/show db)))
+ (:player/paused db)))
(rf/reg-sub
- :bg-player/loading
+ :player/volume
(fn [db _]
- (:bg-player/loading db)))
+ (:player/volume db)))
(rf/reg-sub
- :loop-playback
+ :player/muted
(fn [db _]
- (:loop-playback db)))
-
-(rf/reg-sub
- :shuffle
- (fn [db _]
- (:shuffle db)))
-
-(rf/reg-sub
- :paused
- (fn [db _]
- (:paused db)))
-
-(rf/reg-sub
- :volume-level
- (fn [db _]
- (:volume-level db)))
-
-(rf/reg-sub
- :muted
- (fn [db _]
- (:muted db)))
+ (:player/muted db)))
(rf/reg-sub
:elapsed-time
(fn [_ _]
!elapsed-time))
-
-(rf/reg-sub
- :main-player/show
- (fn [db _]
- (:main-player/show db)))
diff --git a/src/frontend/tubo/player/views.cljs b/src/frontend/tubo/player/views.cljs
index 022d882..e1c5549 100644
--- a/src/frontend/tubo/player/views.cljs
+++ b/src/frontend/tubo/player/views.cljs
@@ -2,191 +2,70 @@
(:require
[re-frame.core :as rf]
[reagent.core :as r]
- [reitit.frontend.easy :as rfe]
- [tubo.bookmarks.modals :as modals]
- [tubo.components.layout :as layout]
- [tubo.components.player :as player]
- [tubo.queue.views :as queue]
- [tubo.stream.views :as stream]
- [tubo.utils :as utils]))
+ ["@vidstack/react" :refer (MediaPlayer MediaProvider Poster)]
+ ["@vidstack/react/player/layouts/default" :refer
+ (defaultLayoutIcons DefaultVideoLayout)]))
-(defn stream-metadata
- [{:keys [thumbnail-url url name uploader-url uploader-name]}]
- [:div.flex.items-center.lg:flex-1
- [:div
- [layout/thumbnail thumbnail-url (rfe/href :stream-page nil {:url url})
- name nil :classes [:h-14 :py-2 "w-[70px]"]]]
- [:div.flex.flex-col.px-2
- [:a.text-xs.line-clamp-1
- {:href (rfe/href :stream-page nil {:url url})
- :title name}
- name]
- [:a.text-xs.pt-2.text-neutral-600.dark:text-neutral-300.line-clamp-1
- {:href (rfe/href :channel-page nil {:url uploader-url})
- :title uploader-name}
- uploader-name]]])
+(defn get-video-player-sources
+ [available-streams service-id]
+ (if available-streams
+ (if (= service-id 3)
+ (map (fn [{:keys [content]}] {:src content :type "video/mp4"})
+ (reverse available-streams))
+ (->> available-streams
+ (filter #(and (not= (:format %) "WEBMA_OPUS")
+ (not= (:format %) "OPUS")
+ (not= (:format %) "M4A")))
+ (sort-by :bitrate)
+ (#(if (empty? (filter (fn [x] (= (:format x) "MP3")) %))
+ (reverse %)
+ %))
+ (map (fn [{:keys [content]}] {:src content :type "video/mp4"}))
+ first))
+ []))
-(defn main-controls
- [!player color]
- (let [queue @(rf/subscribe [:queue])
- queue-pos @(rf/subscribe [:queue-pos])
- loading? @(rf/subscribe [:bg-player/loading])
- loop-playback @(rf/subscribe [:loop-playback])
- shuffle? @(rf/subscribe [:shuffle])
- bg-player-ready? @(rf/subscribe [:bg-player/ready])
- paused? @(rf/subscribe [:paused])
- !elapsed-time @(rf/subscribe [:elapsed-time])]
- [:div.flex.flex-col.items-center.ml-auto
- [:div.flex.justify-end
- [player/loop-button loop-playback color]
- [player/button
- :icon [:i.fa-solid.fa-backward-step]
- :on-click #(rf/dispatch [:queue/change-pos (dec queue-pos)])
- :disabled? (not (and queue (not= queue-pos 0)))]
- [player/button
- :icon [:i.fa-solid.fa-backward]
- :on-click #(rf/dispatch [:bg-player/seek (- @!elapsed-time 5)])]
- [player/button
- :icon
- (if (and (not loading?) @!player)
- (if paused?
- [:i.fa-solid.fa-play]
- [:i.fa-solid.fa-pause])
- [layout/loading-icon color "lg:text-2xl"])
- :on-click
- #(rf/dispatch [:bg-player/pause (not (.-paused @!player))])
- :show-on-mobile? true
- :extra-classes ["lg:text-2xl"]]
- [player/button
- :icon [:i.fa-solid.fa-forward]
- :on-click #(rf/dispatch [:bg-player/seek (+ @!elapsed-time 5)])]
- [player/button
- :icon [:i.fa-solid.fa-forward-step]
- :on-click #(rf/dispatch [:queue/change-pos (inc queue-pos)])
- :disabled? (not (and queue (< (inc queue-pos) (count queue))))]
- [player/shuffle-button shuffle? color]]
- [:div.hidden.lg:flex.items-center.text-sm
- [:span.mx-2
- (if (and bg-player-ready? @!player @!elapsed-time)
- (utils/format-duration @!elapsed-time)
- "--:--")]
- [:div.w-20.lg:w-64.mx-2.flex.items-center
- [player/time-slider !player !elapsed-time color]]
- [:span.mx-2
- (if (and bg-player-ready? @!player)
- (utils/format-duration (.-duration @!player))
- "--:--")]]]))
-
-(defn extra-controls
- [_!player _stream _color]
- (let [!menu-active? (r/atom nil)]
- (fn [!player {:keys [url uploader-url] :as stream} color]
- (let [muted? @(rf/subscribe [:muted])
- volume @(rf/subscribe [:volume-level])
- queue @(rf/subscribe [:queue])
- queue-pos @(rf/subscribe [:queue-pos])
- bookmarks @(rf/subscribe [:bookmarks])
- liked? (some #(= (:url %) url)
- (-> bookmarks
- first
- :items))
- bookmark #(rf/dispatch [:modals/open [modals/add-to-bookmark %]])]
- [:div.flex.lg:justify-end.lg:flex-1
- [player/volume-slider !player volume muted? color]
- [player/button
- :icon [:i.fa-solid.fa-list]
- :on-click #(rf/dispatch [:queue/show true])
- :show-on-mobile? true
- :extra-classes [:!pl-4 :!pr-3]]
- [layout/popover-menu !menu-active?
- [{:label (if liked? "Remove favorite" "Favorite")
- :icon [:i.fa-solid.fa-heart
- (when liked? {:style {:color color}})]
- :on-click #(rf/dispatch [(if liked? :likes/remove :likes/add)
- stream])}
- {:label "Play radio"
- :icon [:i.fa-solid.fa-tower-cell]
- :on-click #(rf/dispatch [:player/start-radio stream])}
- {:label "Add current to playlist"
- :icon [:i.fa-solid.fa-plus]
- :on-click #(bookmark stream)}
- {:label "Add queue to playlist"
- :icon [:i.fa-solid.fa-list]
- :on-click #(bookmark queue)}
- {:label "Remove from queue"
- :icon [:i.fa-solid.fa-trash]
- :on-click #(rf/dispatch [:queue/remove queue-pos])}
- {:label "Switch to main"
- :icon [:i.fa-solid.fa-display]
- :on-click #(rf/dispatch [:player/switch-to-main])}
- {:label "Show channel details"
- :icon [:i.fa-solid.fa-user]
- :on-click #(rf/dispatch [:navigate
- {:name :channel-page
- :params {}
- :query {:url uploader-url}}])}
- {:label "Close player"
- :icon [:i.fa-solid.fa-close]
- :on-click #(rf/dispatch [:bg-player/dispose])}]
- :menu-styles {:bottom "30px" :top nil :right "10px"}
- :extra-classes [:pt-1 :!pl-4 :px-3]]]))))
-
-(defn background-player
- []
- (let [!player @(rf/subscribe [:player])
- stream @(rf/subscribe [:queue-stream])
- show-queue? @(rf/subscribe [:show-queue])
- show-player? @(rf/subscribe [:bg-player/show])
- dark-theme? @(rf/subscribe [:dark-theme])
- color (-> stream
- :service-id
- utils/get-service-color)
- bg-color (str "rgba("
- (if dark-theme? "23,23,23" "255,255,255")
- ",0.95)")
- bg-image (str "linear-gradient("
- bg-color
- ","
- bg-color
- "),url("
- (:thumbnail-url stream)
- ")")]
- [:div.sticky.absolute.left-0.bottom-0.z-10.p-3.transition-all.ease-in.relative
- {:style
- {:visibility (when (or (not show-player?) show-queue?) "hidden")
- :opacity (if (or (not show-player?) show-queue?) 0 1)
- :background-image bg-image
- :background-size "cover"
- :background-position "center"
- :background-repeat "no-repeat"}}
- [:div.flex.items-center
- [player/audio-player stream !player]
- [stream-metadata stream]
- [main-controls !player color]
- [extra-controls !player stream color]]]))
-
-(defn main-player
- []
- (let [queue @(rf/subscribe [:queue])
- queue-pos @(rf/subscribe [:queue-pos])
- bookmarks @(rf/subscribe [:bookmarks])
- !player @(rf/subscribe [:main-player])
- stream @(rf/subscribe [:queue-stream])
- show-player? @(rf/subscribe [:main-player/show])]
- [:div.fixed.w-full.bg-neutral-100.dark:bg-neutral-900.overflow-auto.z-10.transition-all.ease-in-out
- {:class ["h-[calc(100%-56px)]"
- (if show-player? "translate-y-0" "translate-y-full")]}
- (when (and show-player? stream)
- [:div
- [:div.flex.flex-col.items-center.w-full.xl:py-6
- [player/video-player stream !player]]
- [:div.flex.items-center.justify-center
- [:div.flex.flex-col.gap-y-1.w-full.h-fit.max-h-64.overflow-y-auto
- {:class ["lg:w-4/5" "xl:w-3/5"]}
- (for [[i item] (map-indexed vector queue)]
- ^{:key i} [queue/queue-item item queue queue-pos i bookmarks])]]
- [layout/content-container
- [stream/metadata stream]
- [stream/description stream]
- [stream/comments stream]
- [stream/suggested stream]]])]))
+(defn video-player
+ [_stream _!player]
+ (let [!elapsed-time @(rf/subscribe [:elapsed-time])
+ !main-player-first? (r/atom true)]
+ (r/create-class
+ {:component-will-unmount #(rf/dispatch [:main-player/ready false])
+ :reagent-render
+ (fn [{:keys [name video-streams audio-streams thumbnail-url service-id]}
+ !player]
+ (let [show-main-player? @(rf/subscribe [:main-player/show])]
+ [:> MediaPlayer
+ {:title name
+ :src (get-video-player-sources (into video-streams
+ audio-streams)
+ service-id)
+ :poster thumbnail-url
+ :class "w-full xl:w-3/5 overflow-hidden"
+ :playsInline true
+ :ref #(reset! !player %)
+ :loop (when show-main-player?
+ (= @(rf/subscribe [:player/loop]) :stream))
+ :onSeeked (when show-main-player?
+ #(reset! !elapsed-time (.-currentTime @!player)))
+ :onTimeUpdate (when show-main-player?
+ #(reset! !elapsed-time (.-currentTime @!player)))
+ :onEnded #(when show-main-player?
+ (rf/dispatch [:queue/change-pos
+ (inc @(rf/subscribe
+ [:queue/position]))])
+ (reset! !elapsed-time 0))
+ :onLoadedData (fn []
+ (when show-main-player?
+ (rf/dispatch [:main-player/start]))
+ (when (and @!main-player-first? show-main-player?)
+ (reset! !main-player-first? false)))
+ :onPlay #(rf/dispatch [:main-player/play])
+ :onCanPlay #(rf/dispatch [:main-player/ready true])
+ :onSourceChange #(when-not @!main-player-first?
+ (reset! !elapsed-time 0))}
+ [:> MediaProvider
+ [:> Poster
+ {:src thumbnail-url
+ :alt name
+ :class :vds-poster}]]
+ [:> DefaultVideoLayout {:icons defaultLayoutIcons}]]))})))
diff --git a/src/frontend/tubo/playlist/views.cljs b/src/frontend/tubo/playlist/views.cljs
index cd31b72..ad2be19 100644
--- a/src/frontend/tubo/playlist/views.cljs
+++ b/src/frontend/tubo/playlist/views.cljs
@@ -4,8 +4,8 @@
[re-frame.core :as rf]
[reitit.frontend.easy :as rfe]
[tubo.bookmarks.modals :as modals]
- [tubo.components.items :as items]
- [tubo.components.layout :as layout]))
+ [tubo.items.views :as items]
+ [tubo.layout.views :as layout]))
(defn playlist
[{{:keys [url]} :query-params}]
diff --git a/src/frontend/tubo/queue/events.cljs b/src/frontend/tubo/queue/events.cljs
index 1cc189f..04502fd 100644
--- a/src/frontend/tubo/queue/events.cljs
+++ b/src/frontend/tubo/queue/events.cljs
@@ -6,8 +6,8 @@
:queue/show
(fn [{:keys [db]} [_ show?]]
{:db (apply assoc
- (assoc db :show-queue show?)
- (when show? [:show-search-form false]))
+ (assoc db :queue/show show?)
+ (when show? [:search/show-form false]))
:body-overflow show?}))
(rf/reg-event-fx
@@ -15,7 +15,7 @@
[(rf/inject-cofx :store)]
(fn [{:keys [db store]} [_ val]]
(let [queue (:queue db)
- queue-pos (+ 1 (:queue-pos db))
+ queue-pos (+ 1 (:queue/position db))
queue-to-end (subvec queue queue-pos)
shuffled-to-end (shuffle queue-to-end)
unshuffled-to-end (into (subvec queue 0 queue-pos) queue-to-end)
@@ -26,8 +26,10 @@
updated-db (assoc db
:queue
(if val shuffled-queue unshuffled-queue))]
- {:db (assoc updated-db :shuffle val :queue/unshuffled unshuffled-queue)
- :store (assoc store :queue (:queue updated-db) :shuffle val)})))
+ {:db (assoc updated-db
+ :player/shuffled val
+ :queue/unshuffled unshuffled-queue)
+ :store (assoc store :queue (:queue updated-db) :player/shuffled val)})))
(rf/reg-event-fx
:queue/add
@@ -49,7 +51,7 @@
(fn [{:keys [db]} [_ streams notify?]]
{:fx (into (map (fn [stream] [:dispatch [:queue/add stream]]) streams)
[[:dispatch
- [:player/fetch-stream
+ [:bg-player/fetch-stream
(-> streams
first
:url)
@@ -69,7 +71,7 @@
(let [updated-db (update db
:queue
#(into (subvec % 0 pos) (subvec % (inc pos))))
- queue-pos (:queue-pos db)
+ queue-pos (:queue/position db)
queue-length (count (:queue updated-db))]
{:db updated-db
:store (assoc store :queue (:queue updated-db))
@@ -85,7 +87,7 @@
(= pos queue-pos) pos
:else (dec queue-pos))]]]
(= (count (:queue updated-db)) 0)
- [[:dispatch [:player/dispose]]
+ [[:dispatch [:bg-player/dispose]]
[:dispatch [:queue/show false]]]
:else [])})))
@@ -95,15 +97,15 @@
(fn [{:keys [db]} [_ i]]
(let [idx (if (< i (count (:queue db)))
i
- (when (= (:loop-playback db) :playlist) 0))
+ (when (= (:player/loop db) :playlist) 0))
stream (get (:queue db) idx)]
(when stream
- {:fx [[:dispatch [:player/fetch-stream (:url stream) idx true]]]}))))
+ {:fx [[:dispatch [:bg-player/fetch-stream (:url stream) idx true]]]}))))
(rf/reg-event-fx
:queue/change-stream
[(rf/inject-cofx :store)]
(fn [{:keys [db store]} [_ stream idx]]
(let [update-entry (fn [x] (update-in x [:queue idx] #(merge % stream)))]
- {:db (assoc (update-entry db) :queue-pos idx)
- :store (assoc (update-entry store) :queue-pos idx)})))
+ {:db (assoc (update-entry db) :queue/position idx)
+ :store (assoc (update-entry store) :queue/position idx)})))
diff --git a/src/frontend/tubo/queue/subs.cljs b/src/frontend/tubo/queue/subs.cljs
index fbec406..5b37de5 100644
--- a/src/frontend/tubo/queue/subs.cljs
+++ b/src/frontend/tubo/queue/subs.cljs
@@ -13,18 +13,18 @@
(:queue/unshuffled db)))
(rf/reg-sub
- :queue-pos
+ :queue/position
(fn [db _]
- (:queue-pos db)))
+ (:queue/position db)))
(rf/reg-sub
- :show-queue
+ :queue/show
(fn [db _]
- (:show-queue db)))
+ (:queue/show db)))
(rf/reg-sub
- :queue-stream
+ :queue/current
(fn [_]
- [(rf/subscribe [:queue]) (rf/subscribe [:queue-pos])])
+ [(rf/subscribe [:queue]) (rf/subscribe [:queue/position])])
(fn [[queue pos] _]
(and (not-empty queue) (< pos (count queue)) (nth queue pos))))
diff --git a/src/frontend/tubo/queue/views.cljs b/src/frontend/tubo/queue/views.cljs
index 0947a74..df924a5 100644
--- a/src/frontend/tubo/queue/views.cljs
+++ b/src/frontend/tubo/queue/views.cljs
@@ -4,8 +4,8 @@
[re-frame.core :as rf]
[reitit.frontend.easy :as rfe]
[tubo.bookmarks.modals :as modals]
- [tubo.components.layout :as layout]
- [tubo.components.player :as player]
+ [tubo.bg-player.views :as player]
+ [tubo.layout.views :as layout]
[tubo.utils :as utils]))
(defn item-metadata
@@ -41,7 +41,7 @@
:on-click #(rf/dispatch [(if liked? :likes/remove :likes/add) item])}
{:label "Play radio"
:icon [:i.fa-solid.fa-tower-cell]
- :on-click #(rf/dispatch [:player/start-radio item])}
+ :on-click #(rf/dispatch [:bg-player/start-radio item])}
{:label "Add to playlist"
:icon [:i.fa-solid.fa-plus]
:on-click #(rf/dispatch [:modals/open [modals/add-to-bookmark item]])}
@@ -50,7 +50,7 @@
:on-click #(rf/dispatch [:queue/remove i])}
{:label "Show channel details"
:icon [:i.fa-solid.fa-user]
- :on-click #(rf/dispatch [:navigate
+ :on-click #(rf/dispatch [:navigation/navigate
{:name :channel-page
:params {}
:query {:url uploader-url}}])}]
@@ -82,15 +82,15 @@
(defn main-controls
[color]
- (let [loop-playback @(rf/subscribe [:loop-playback])
- shuffle? @(rf/subscribe [:shuffle])
- !player @(rf/subscribe [:player])
+ (let [loop-playback @(rf/subscribe [:player/loop])
+ shuffle? @(rf/subscribe [:player/shuffled])
+ !player @(rf/subscribe [:bg-player])
loading? @(rf/subscribe [:bg-player/loading])
bg-player-ready? @(rf/subscribe [:bg-player/ready])
- paused? @(rf/subscribe [:paused])
+ paused? @(rf/subscribe [:player/paused])
!elapsed-time @(rf/subscribe [:elapsed-time])
queue @(rf/subscribe [:queue])
- queue-pos @(rf/subscribe [:queue-pos])]
+ queue-pos @(rf/subscribe [:queue/position])]
[:<>
[:div.flex.flex-auto.py-2.w-full.items-center.text-sm
[:span.mr-4.whitespace-nowrap
@@ -141,10 +141,10 @@
(defn queue
[]
- (let [show-queue @(rf/subscribe [:show-queue])
- stream @(rf/subscribe [:queue-stream])
+ (let [show-queue @(rf/subscribe [:queue/show])
+ stream @(rf/subscribe [:queue/current])
bookmarks @(rf/subscribe [:bookmarks])
- queue-pos @(rf/subscribe [:queue-pos])
+ queue-pos @(rf/subscribe [:queue/position])
queue @(rf/subscribe [:queue])
color (-> stream
:service-id
diff --git a/src/frontend/tubo/routes.cljs b/src/frontend/tubo/routes.cljs
index 67de20e..2086a27 100644
--- a/src/frontend/tubo/routes.cljs
+++ b/src/frontend/tubo/routes.cljs
@@ -68,7 +68,7 @@
(defn on-navigate
[new-match]
(when new-match
- (rf/dispatch [:navigated new-match])))
+ (rf/dispatch [:navigation/navigated new-match])))
(defn start-routes!
[]
diff --git a/src/frontend/tubo/search/events.cljs b/src/frontend/tubo/search/events.cljs
index 320e5ec..a40948a 100644
--- a/src/frontend/tubo/search/events.cljs
+++ b/src/frontend/tubo/search/events.cljs
@@ -2,7 +2,7 @@
(:require
[re-frame.core :as rf]
[tubo.api :as api]
- [tubo.components.layout :as layout]))
+ [tubo.layout.views :as layout]))
(rf/reg-event-fx
:search/fetch
@@ -17,7 +17,7 @@
(fn [{:keys [db]} [_ res]]
(let [search-res (js->clj res :keywordize-keys true)]
{:db (assoc db
- :search-results search-res
+ :search/results search-res
:show-page-loading false)
:fx [[:dispatch [:services/fetch search-res]]]})))
@@ -34,8 +34,8 @@
(fn [{:keys [db]} [_ service-id query]]
{:db (assoc db
:show-page-loading true
- :show-search-form true
- :search-results nil)
+ :search/show-form true
+ :search/results nil)
:fx [[:dispatch
[:search/fetch service-id
[:search/load-page] [:search/bad-page-response service-id query]
@@ -48,13 +48,13 @@
(let [search-res (js->clj res :keywordize-keys true)]
(if (empty? (:items search-res))
(-> db
- (assoc-in [:search-results :next-page] nil)
+ (assoc-in [:search/results :next-page] nil)
(assoc :show-pagination-loading false))
(-> db
- (update-in [:search-results :items]
+ (update-in [:search/results :items]
#(apply conj %1 %2)
(:items search-res))
- (assoc-in [:search-results :next-page] (:next-page search-res))
+ (assoc-in [:search/results :next-page] (:next-page search-res))
(assoc :show-pagination-loading false))))))
(rf/reg-event-fx
@@ -73,12 +73,12 @@
:search/show-form
(fn [db [_ show?]]
(when-not (= (-> db
- :current-match
+ :navigation/current-match
:path)
"search")
- (assoc db :show-search-form show?))))
+ (assoc db :search/show-form show?))))
(rf/reg-event-db
:search/change-query
(fn [db [_ res]]
- (assoc db :search-query res)))
+ (assoc db :search/query res)))
diff --git a/src/frontend/tubo/search/subs.cljs b/src/frontend/tubo/search/subs.cljs
index 59c69f5..c64f09e 100644
--- a/src/frontend/tubo/search/subs.cljs
+++ b/src/frontend/tubo/search/subs.cljs
@@ -3,16 +3,16 @@
[re-frame.core :as rf]))
(rf/reg-sub
- :search-results
+ :search/results
(fn [db _]
- (:search-results db)))
+ (:search/results db)))
(rf/reg-sub
- :search-query
+ :search/query
(fn [db _]
- (:search-query db)))
+ (:search/query db)))
(rf/reg-sub
- :show-search-form
+ :search/show-form
(fn [db _]
- (:show-search-form db)))
+ (:search/show-form db)))
diff --git a/src/frontend/tubo/search/views.cljs b/src/frontend/tubo/search/views.cljs
index 4fdc6d6..e97a627 100644
--- a/src/frontend/tubo/search/views.cljs
+++ b/src/frontend/tubo/search/views.cljs
@@ -1,12 +1,12 @@
(ns tubo.search.views
(:require
[re-frame.core :as rf]
- [tubo.components.items :as items]
- [tubo.components.layout :as layout]))
+ [tubo.items.views :as items]
+ [tubo.layout.views :as layout]))
(defn search
[{{:keys [q serviceId]} :query-params}]
- (let [{:keys [items next-page]} @(rf/subscribe [:search-results])
+ (let [{:keys [items next-page]} @(rf/subscribe [:search/results])
next-page-url (:url next-page)
service-id (or @(rf/subscribe [:service-id]) serviceId)
scrolled-to-bottom? @(rf/subscribe [:scrolled-to-bottom])]
diff --git a/src/frontend/tubo/settings/views.cljs b/src/frontend/tubo/settings/views.cljs
index 0658bbb..51520bb 100644
--- a/src/frontend/tubo/settings/views.cljs
+++ b/src/frontend/tubo/settings/views.cljs
@@ -1,7 +1,7 @@
(ns tubo.settings.views
(:require
[re-frame.core :as rf]
- [tubo.components.layout :as layout]))
+ [tubo.layout.views :as layout]))
(defn boolean-input
[label key value]
diff --git a/src/frontend/tubo/stream/events.cljs b/src/frontend/tubo/stream/events.cljs
index 7b55e7c..709c68a 100644
--- a/src/frontend/tubo/stream/events.cljs
+++ b/src/frontend/tubo/stream/events.cljs
@@ -2,7 +2,7 @@
(:require
[re-frame.core :as rf]
[tubo.api :as api]
- [tubo.components.layout :as layout]))
+ [tubo.layout.views :as layout]))
(rf/reg-event-fx
:stream/fetch
diff --git a/src/frontend/tubo/stream/views.cljs b/src/frontend/tubo/stream/views.cljs
index 26148fc..c78cfc9 100644
--- a/src/frontend/tubo/stream/views.cljs
+++ b/src/frontend/tubo/stream/views.cljs
@@ -5,9 +5,9 @@
[reitit.frontend.easy :as rfe]
[tubo.bookmarks.modals :as modals]
[tubo.comments.views :as comments]
- [tubo.components.items :as items]
- [tubo.components.layout :as layout]
- [tubo.components.player :as player]
+ [tubo.items.views :as items]
+ [tubo.layout.views :as layout]
+ [tubo.player.views :as player]
[tubo.utils :as utils]))
(defn metadata-popover
@@ -22,10 +22,10 @@
[layout/popover-menu !menu-active?
[{:label "Add to queue"
:icon [:i.fa-solid.fa-headphones]
- :on-click #(rf/dispatch [:player/switch-to-background stream true])}
+ :on-click #(rf/dispatch [:bg-player/show stream true])}
{:label "Play radio"
:icon [:i.fa-solid.fa-tower-cell]
- :on-click #(rf/dispatch [:player/start-radio stream])}
+ :on-click #(rf/dispatch [:bg-player/start-radio stream])}
{:label (if liked? "Remove favorite" "Favorite")
:icon (if liked?
[:i.fa-solid.fa-heart
diff --git a/src/frontend/tubo/subs.cljs b/src/frontend/tubo/subs.cljs
index a789ccb..9b952e1 100644
--- a/src/frontend/tubo/subs.cljs
+++ b/src/frontend/tubo/subs.cljs
@@ -2,10 +2,13 @@
(:require
[reagent.core :as r]
[re-frame.core :as rf]
+ [tubo.bg-player.subs]
[tubo.bookmarks.subs]
[tubo.channel.subs]
[tubo.kiosks.subs]
+ [tubo.main-player.subs]
[tubo.modals.subs]
+ [tubo.navigation.subs]
[tubo.notifications.subs]
[tubo.player.subs]
[tubo.playlist.subs]
@@ -59,11 +62,6 @@
(or (and (= theme "auto") (= @!auto-theme "dark")) (= theme "dark"))))
(rf/reg-sub
- :current-match
- (fn [db _]
- (:current-match db)))
-
-(rf/reg-sub
:page-scroll
(fn [db _]
(:page-scroll db)))
@@ -77,8 +75,3 @@
:show-pagination-loading
(fn [db _]
(:show-pagination-loading db)))
-
-(rf/reg-sub
- :show-mobile-nav
- (fn [db _]
- (:show-mobile-nav db)))
diff --git a/src/frontend/tubo/views.cljs b/src/frontend/tubo/views.cljs
index a0bc34c..85f85d5 100644
--- a/src/frontend/tubo/views.cljs
+++ b/src/frontend/tubo/views.cljs
@@ -1,14 +1,15 @@
(ns tubo.views
(:require
[re-frame.core :as rf]
+ [tubo.bg-player.views :as bg-player]
+ [tubo.main-player.views :as main-player]
[tubo.navigation.views :as navigation]
[tubo.notifications.views :as notifications]
- [tubo.player.views :as player]
[tubo.queue.views :as queue]))
(defn app
[]
- (let [current-match @(rf/subscribe [:current-match])
+ (let [current-match @(rf/subscribe [:navigation/current-match])
dark-theme? @(rf/subscribe [:dark-theme])]
[:div {:class (when dark-theme? :dark)}
[:div.font-nunito-sans.min-h-screen.h-full.relative.flex.flex-col.dark:text-white.bg-neutral-100.dark:bg-neutral-900
@@ -20,5 +21,5 @@
:view)]
[view current-match])
[queue/queue]
- [player/main-player]
- [player/background-player]]]]))
+ [main-player/player]
+ [bg-player/player]]]]))