diff options
-rw-r--r-- | package-lock.json | 75 | ||||
-rw-r--r-- | package.json | 4 | ||||
-rw-r--r-- | postcss.config.js | 8 | ||||
-rw-r--r-- | resources/src/styles/index.scss | 16 | ||||
-rw-r--r-- | shadow-cljs.edn | 7 | ||||
-rw-r--r-- | src/frontend/tubo/bg_player/events.cljs | 24 | ||||
-rw-r--r-- | src/frontend/tubo/bg_player/views.cljs | 72 | ||||
-rw-r--r-- | src/frontend/tubo/main_player/events.cljs | 2 | ||||
-rw-r--r-- | src/frontend/tubo/player/events.cljs | 10 | ||||
-rw-r--r-- | src/frontend/tubo/player/views.cljs | 161 | ||||
-rw-r--r-- | src/frontend/tubo/queue/events.cljs | 18 | ||||
-rw-r--r-- | tailwind.config.js | 10 |
12 files changed, 210 insertions, 197 deletions
diff --git a/package-lock.json b/package-lock.json index db7de25..699e9bb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6,7 +6,9 @@ "": { "dependencies": { "@fortawesome/fontawesome-free": "^6.4.2", - "@vidstack/react": "^1.11.24", + "hls-video-element": "^1.2.11", + "hls.js": "^1.5.17", + "media-chrome": "^4.2.3", "react": "^18.2.0", "react-dom": "^18.2.0", "timeago.js": "^4.0.2" @@ -287,37 +289,6 @@ "undici-types": "~5.26.4" } }, - "node_modules/@types/prop-types": { - "version": "15.7.12", - "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.12.tgz", - "integrity": "sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q==", - "peer": true - }, - "node_modules/@types/react": { - "version": "18.3.3", - "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.3.tgz", - "integrity": "sha512-hti/R0pS0q1/xx+TsI73XIqk26eBsISZ2R0wUijXIngRK9R/e7Xw/cXVxQK7R5JjW+SV4zGcn5hXjudkN/pLIw==", - "peer": true, - "dependencies": { - "@types/prop-types": "*", - "csstype": "^3.0.2" - } - }, - "node_modules/@vidstack/react": { - "version": "1.11.24", - "resolved": "https://registry.npmjs.org/@vidstack/react/-/react-1.11.24.tgz", - "integrity": "sha512-7tgJUJdIKIidzziZ9QqpU73g0BI3BvgYnIim3FrHsxFMRUgZbLKLbSJP1c16NQijS6kljUTWPJErCOm+n27xPg==", - "dependencies": { - "media-captions": "^1.0.1" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@types/react": "^18.0.0", - "react": "^18.0.0" - } - }, "node_modules/@webassemblyjs/ast": { "version": "1.12.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.12.1.tgz", @@ -1510,11 +1481,10 @@ "integrity": "sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g==", "dev": true }, - "node_modules/csstype": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", - "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", - "peer": true + "node_modules/custom-media-element": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/custom-media-element/-/custom-media-element-1.4.1.tgz", + "integrity": "sha512-kWPRk+6tEebklkd9QWbeCAEOzgL72Uhra4ZSmjN3R9mxg4emqh/0vKyKgxzLtuak76SiaSstzCY6zFrBZ8kyJg==" }, "node_modules/define-data-property": { "version": "1.1.4", @@ -2184,6 +2154,21 @@ "node": ">= 0.4" } }, + "node_modules/hls-video-element": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/hls-video-element/-/hls-video-element-1.2.11.tgz", + "integrity": "sha512-QaUPnGcJj9uW9US7+XtUMda/i6Wvf9EOTPRCMYzgQKU0h65QgiOpMnfKQDVDZbTanc7333nf2H0ubKyL0umyRg==", + "dependencies": { + "custom-media-element": "^1.4.1", + "hls.js": "^1.5.11", + "media-tracks": "^0.3.3" + } + }, + "node_modules/hls.js": { + "version": "1.5.17", + "resolved": "https://registry.npmjs.org/hls.js/-/hls.js-1.5.17.tgz", + "integrity": "sha512-wA66nnYFvQa1o4DO/BFgLNRKnBTVXpNeldGRBJ2Y0SvFtdwvFKCbqa9zhHoZLoxHhZ+jYsj3aIBkWQQCPNOhMw==" + }, "node_modules/hmac-drbg": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", @@ -2591,13 +2576,15 @@ "integrity": "sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==", "dev": true }, - "node_modules/media-captions": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/media-captions/-/media-captions-1.0.4.tgz", - "integrity": "sha512-cyDNmuZvvO4H27rcBq2Eudxo9IZRDCOX/I7VEyqbxsEiD2Ei7UYUhG/Sc5fvMZjmathgz3fEK7iAKqvpY+Ux1w==", - "engines": { - "node": ">=16" - } + "node_modules/media-chrome": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/media-chrome/-/media-chrome-4.2.3.tgz", + "integrity": "sha512-gzwFy2b+RLsEtnPzUzqzf2L5XkaTLQr8POOyLOcoebWSAWg31cPy2vfXNiUnd93sc5IxwJ8OAwkKxnaJNZ8Gjg==" + }, + "node_modules/media-tracks": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/media-tracks/-/media-tracks-0.3.3.tgz", + "integrity": "sha512-9P2FuUHnZZ3iji+2RQk7Zkh5AmZTnOG5fODACnjhCVveX1McY3jmCRHofIEI+yTBqplz7LXy48c7fQ3Uigp88w==" }, "node_modules/merge-stream": { "version": "2.0.0", diff --git a/package.json b/package.json index 9a720e3..0c4fe0e 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,9 @@ }, "dependencies": { "@fortawesome/fontawesome-free": "^6.4.2", - "@vidstack/react": "^1.11.24", + "hls-video-element": "^1.2.11", + "hls.js": "^1.5.17", + "media-chrome": "^4.2.3", "react": "^18.2.0", "react-dom": "^18.2.0", "timeago.js": "^4.0.2" diff --git a/postcss.config.js b/postcss.config.js index 5c7bdd0..29594c9 100644 --- a/postcss.config.js +++ b/postcss.config.js @@ -1,8 +1,8 @@ module.exports = { plugins: { - 'postcss-import': {}, + "postcss-import": {}, tailwindcss: {}, autoprefixer: {}, - cssnano: process.env.NODE_ENV === 'production' ? {} : false - } -} + cssnano: process.env.NODE_ENV === "production" ? {} : false, + }, +}; diff --git a/resources/src/styles/index.scss b/resources/src/styles/index.scss index 582e733..c7e71c0 100644 --- a/resources/src/styles/index.scss +++ b/resources/src/styles/index.scss @@ -1,14 +1,22 @@ @use "@fontsource/nunito-sans/scss/mixins" as NunitoSans; $fontsourceDir: "~@fontsource"; -@include NunitoSans.faces($weights: (200, 300, 400, 500, 600, 700, 800, 900)); +@include NunitoSans.faces( + $weights: ( + 200, + 300, + 400, + 500, + 600, + 700, + 800, + 900, + ) +); $fa-font-path: "~@fortawesome/fontawesome-free/webfonts"; @import "@fortawesome/fontawesome-free/scss/brands"; @import "@fortawesome/fontawesome-free/scss/regular"; @import "@fortawesome/fontawesome-free/scss/solid"; @import "@fortawesome/fontawesome-free/scss/fontawesome"; -@import '@vidstack/react/player/styles/default/theme.css'; -@import '@vidstack/react/player/styles/default/layouts/video.css'; -@import '@vidstack/react/player/styles/default/layouts/audio.css'; @import "tailwindcss/base"; @import "tailwindcss/components"; @import "tailwindcss/utilities"; diff --git a/shadow-cljs.edn b/shadow-cljs.edn index 5937f86..7efe42d 100644 --- a/shadow-cljs.edn +++ b/shadow-cljs.edn @@ -3,7 +3,10 @@ :proxy-url "http://localhost:3000"}} :builds {:tubo - {:target :browser + {:target :browser :output-dir "resources/public/js" :asset-path "/js" - :modules {:main {:init-fn tubo.core/init}}}}} + :js-options {:entry-keys ["module" "browser" "main"] + :export-conditions ["import" "module" "browser" "require" + "default"]} + :modules {:main {:init-fn tubo.core/init}}}}} diff --git a/src/frontend/tubo/bg_player/events.cljs b/src/frontend/tubo/bg_player/events.cljs index 75f3a8c..7a0f22b 100644 --- a/src/frontend/tubo/bg_player/events.cljs +++ b/src/frontend/tubo/bg_player/events.cljs @@ -19,36 +19,20 @@ :bg-player/pause [(rf/inject-cofx ::inject/sub [:bg-player])] (fn [{:keys [bg-player]} [_ paused?]] - {:player/pause {:paused? paused? + {:player/pause {:paused? (not 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]} _] + (fn [{:keys [db bg-player elapsed-time]} _] {:fx [[:dispatch [:bg-player/set-paused true]] + [:dispatch [:bg-player/seek @elapsed-time]] [:dispatch [:bg-player/pause false]] [:dispatch [:player/change-volume (:player/volume db) - bg-player]] - ]})) + bg-player]]]})) (rf/reg-event-fx :bg-player/mute diff --git a/src/frontend/tubo/bg_player/views.cljs b/src/frontend/tubo/bg_player/views.cljs index c5f336b..6be514e 100644 --- a/src/frontend/tubo/bg_player/views.cljs +++ b/src/frontend/tubo/bg_player/views.cljs @@ -2,14 +2,12 @@ (:require [clojure.string :as str] [re-frame.core :as rf] + [reagent.dom :as rdom] [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)])) + [tubo.utils :as utils])) (defonce base-slider-classes ["h-2" "cursor-pointer" "appearance-none" "bg-neutral-300" @@ -180,8 +178,7 @@ [: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))]) + :on-click #(rf/dispatch [:bg-player/pause (not (.-paused @!player))]) :show-on-mobile? true :extra-classes ["lg:text-2xl"]] [button @@ -258,52 +255,31 @@ :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)] + [_] + (let [!elapsed-time @(rf/subscribe [:elapsed-time]) + queue-pos @(rf/subscribe [:queue/position]) + stream @(rf/subscribe [:queue/current])] (r/create-class {:component-will-unmount #(rf/dispatch [:bg-player/ready false]) + :component-did-mount + (fn [this] + (set! (.-onended (rdom/dom-node this)) + #(rf/dispatch [:queue/change-pos (inc queue-pos)])) + (when stream + (set! (.-src (rdom/dom-node this)) + (:content (nth (:audio-streams stream) 0))))) :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 %) + (fn [!player] + [: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}]])}))) + :on-can-play #(rf/dispatch [:bg-player/ready true]) + :on-seeked #(reset! !elapsed-time (.-currentTime @!player)) + :on-time-update #(reset! !elapsed-time (.-currentTime @!player)) + :on-play #(rf/dispatch [:bg-player/set-paused false]) + :on-pause #(rf/dispatch [:bg-player/set-paused true]) + :on-loaded-data #(rf/dispatch [:bg-player/start])}])}))) (defn player [] @@ -335,7 +311,7 @@ :background-position "center" :background-repeat "no-repeat"}} [:div.flex.items-center - [audio-player stream !player] + [audio-player !player] [metadata stream] [main-controls !player color] [extra-controls !player stream color]]]))) diff --git a/src/frontend/tubo/main_player/events.cljs b/src/frontend/tubo/main_player/events.cljs index a5e8414..da9676e 100644 --- a/src/frontend/tubo/main_player/events.cljs +++ b/src/frontend/tubo/main_player/events.cljs @@ -14,7 +14,7 @@ [(rf/inject-cofx ::inject/sub [:main-player])] (fn [{:keys [db main-player]} [_ paused?]] (when (:main-player/ready db) - {:player/pause {:paused? paused? + {:player/pause {:paused? (not paused?) :player main-player}}))) (rf/reg-event-fx diff --git a/src/frontend/tubo/player/events.cljs b/src/frontend/tubo/player/events.cljs index 7246a7f..6350029 100644 --- a/src/frontend/tubo/player/events.cljs +++ b/src/frontend/tubo/player/events.cljs @@ -17,8 +17,10 @@ (rf/reg-fx :player/src - (fn [{:keys [player src]}] - (set! (.-source @player) (clj->js src)))) + (fn [{:keys [player src current-pos]}] + (set! (.-src @player) (clj->js src)) + (set! (.-onended @player) + #(rf/dispatch [:queue/change-pos (inc current-pos)])))) (rf/reg-fx :player/loop @@ -35,7 +37,9 @@ :player/pause (fn [{:keys [paused? player]}] (when (and player @player) - (set! (.-paused @player) paused?)))) + (if paused? + (.play @player) + (.pause @player))))) (rf/reg-fx :media-session-metadata diff --git a/src/frontend/tubo/player/views.cljs b/src/frontend/tubo/player/views.cljs index e1c5549..2a5e02c 100644 --- a/src/frontend/tubo/player/views.cljs +++ b/src/frontend/tubo/player/views.cljs @@ -2,27 +2,17 @@ (:require [re-frame.core :as rf] [reagent.core :as r] - ["@vidstack/react" :refer (MediaPlayer MediaProvider Poster)] - ["@vidstack/react/player/layouts/default" :refer - (defaultLayoutIcons DefaultVideoLayout)])) - -(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)) - [])) + ["media-chrome/dist/react" :refer + (MediaController + MediaControlBar + MediaTimeRange + MediaTimeDisplay + MediaVolumeRange + MediaFullscreenButton + MediaPipButton + MediaPlayButton + MediaPlaybackRateButton + MediaMuteButton)])) (defn video-player [_stream _!player] @@ -31,41 +21,96 @@ (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]} + (fn [{:keys [video-streams audio-streams thumbnail-url]} !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}]]))}))) + (let [show-main-player? @(rf/subscribe [:main-player/show]) + service-color @(rf/subscribe [:service-color])] + [:div + {:class "w-full h-80 md:h-[450px] lg:h-[600px]"} + [:> MediaController + {:style {"--media-secondary-color" "transparent" + "--media-primary-color" "white" + "aspectRatio" "16/9" + "height" "100%" + "width" "100%"}} + [:video + {:style {"max-height" "100%" + "min-height" "100%" + "min-width" "100%" + "max-width" "100%"} + :ref #(reset! !player %) + :poster thumbnail-url + :loop (when show-main-player? + (= @(rf/subscribe [:player/loop]) :stream)) + :on-can-play #(rf/dispatch [:main-player/ready true]) + :on-ended #(when show-main-player? + (rf/dispatch [:queue/change-pos + (inc @(rf/subscribe + [:queue/position]))]) + (reset! !elapsed-time 0)) + :on-play #(rf/dispatch [:main-player/play]) + :on-loaded-data (fn [] + (when show-main-player? + (rf/dispatch [:main-player/start])) + (when (and @!main-player-first? + show-main-player?) + (reset! !main-player-first? false))) + :on-time-update (when show-main-player? + #(reset! !elapsed-time (.-currentTime + @!player))) + :on-seeked (when show-main-player? + #(reset! !elapsed-time (.-currentTime + @!player))) + :slot "media" + :src (:content (nth (into video-streams audio-streams) + 0)) + :preload "auto" + :muted @(rf/subscribe [:player/muted]) + :crossOrigin ""}] + [:div.ytp-gradient-bottom.absolute.w-full.bottom-0.pointer-events-none.bg-bottom.bg-repeat-x + {:style + {"paddingTop" "37px" + "height" "170px" + "backgroundImage" + "url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAACqCAYAAABsziWkAAAAAXNSR0IArs4c6QAAAQVJREFUOE9lyNdHBQAAhfHb3nvvuu2997jNe29TJJEkkkgSSSSJJJJEEkkiifRH5jsP56Xz8PM5gcC/xfDEmjhKxEOCSaREEiSbFEqkQppJpzJMJiWyINvkUCIX8kw+JQqg0BRRxaaEEqVQZsopUQGVpooS1VBjglStqaNEPTSYRko0QbNpoUQrtJl2qsN0UqILuk0PJXqhz/RTYgAGzRA1bEYoMQpjZpwSExAyk5SYgmkzQ82aOUqEIWKilJiHBbNIiSVYhhVYhTVYhw3YhC3Yhh3YhT3YhwM4hCM4hhM4hTM4hwu4hCu4hhu4hTu4hwd4hCd4hhd4hTd4hw/4hC/4hh/4/QM2/id28uIEJAAAAABJRU5ErkJggg==')"}}] + [:> MediaTimeRange + {:class "w-full h-[5px]" + :style + {"--media-control-hover-background" "transparent" + "--media-range-track-transition" "height 0.1s linear" + "--media-range-track-background" "rgba(255,255,255,.2)" + "--media-range-track-pointer-background" "rgba(255,255,255,.5)" + "--media-time-range-buffered-color" "rgba(255,255,255,.4)" + "--media-range-bar-color" service-color + "--media-range-thumb-border-radius" "13px" + "--media-range-thumb-background" service-color + "--media-range-thumb-transition" "transform 0.1s linear" + "--media-range-thumb-transform" "scale(0) translate(0%, 0%)"}}] + [:> MediaControlBar + {:class "relative pl-[10px] pr-[5px]" + :style + {"--media-control-hover-background" "transparent" + "--media-range-track-height" "3px" + "--media-range-thumb-height" "13px" + "--media-range-thumb-width" "13px" + "--media-range-thumb-border-radius" "13px"}} + [:> MediaPlayButton + {:class "py-[6px] px-[10px]" + :style + {"--media-button-icon-width" "30px"}}] + [:> MediaMuteButton + {:class "peer/mute"}] + [:> MediaVolumeRange + {:class + ["w-0" "overflow-hidden" "transition-[width]" + "transition-200" "ease-in" "peer-hover/mute:w-[70px]" + "peer-focus/mute:w-[70px]" "hover:w-[70px]" "focus:w-[70px]"] + :style + {"--media-range-track-background" "rgba(255,255,255,.2)" + "--media-range-bar-color" service-color + "--media-range-thumb-background" service-color}}] + [:> MediaTimeDisplay {:showDuration true}] + [:span.control-spacer.grow] + [:> MediaPlaybackRateButton] + [:> MediaPipButton] + [:> MediaFullscreenButton]]]]))}))) diff --git a/src/frontend/tubo/queue/events.cljs b/src/frontend/tubo/queue/events.cljs index 04502fd..2765af2 100644 --- a/src/frontend/tubo/queue/events.cljs +++ b/src/frontend/tubo/queue/events.cljs @@ -1,6 +1,7 @@ (ns tubo.queue.events (:require - [re-frame.core :as rf])) + [re-frame.core :as rf] + [vimsical.re-frame.cofx.inject :as inject])) (rf/reg-event-fx :queue/show @@ -104,8 +105,15 @@ (rf/reg-event-fx :queue/change-stream - [(rf/inject-cofx :store)] - (fn [{:keys [db store]} [_ stream idx]] + [(rf/inject-cofx :store) + (rf/inject-cofx ::inject/sub [:bg-player])] + (fn [{:keys [db store bg-player]} [_ stream idx]] (let [update-entry (fn [x] (update-in x [:queue idx] #(merge % stream)))] - {:db (assoc (update-entry db) :queue/position idx) - :store (assoc (update-entry store) :queue/position idx)}))) + {:db (assoc (update-entry db) :queue/position idx) + :store (assoc (update-entry store) :queue/position idx) + :player/src {:player bg-player + :src (-> stream + :audio-streams + first + :content) + :current-pos idx}}))) diff --git a/tailwind.config.js b/tailwind.config.js index b695cbb..06e1f0e 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -1,4 +1,3 @@ -/** @type {import('tailwindcss').Config} */ module.exports = { content: ["./src/**/*.{html,js,cljs}"], darkMode: "class", @@ -8,12 +7,9 @@ module.exports = { "nunito-sans": ["Nunito Sans", "sans-serif"], }, screens: { - "xs": "480px", + xs: "480px", }, }, }, - plugins: [ - require("@tailwindcss/forms"), - require("@vidstack/react/tailwind.cjs") - ], -} + plugins: [require("@tailwindcss/forms")], +}; |