aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMiguel Ángel Moreno <mail@migalmoreno.com>2024-12-01 13:16:35 +0100
committerMiguel Ángel Moreno <mail@migalmoreno.com>2024-12-01 13:26:43 +0100
commitc6c5f094c41b0a024f052ce2350872490f60a73a (patch)
tree69514eb15bcc9f89f4c8e0ec564344121fe388a5
parent3951173441bcb511c8f8ae383002e6992e6409a8 (diff)
feat: replace vidstack with Media Chrome
-rw-r--r--package-lock.json75
-rw-r--r--package.json4
-rw-r--r--postcss.config.js8
-rw-r--r--resources/src/styles/index.scss16
-rw-r--r--shadow-cljs.edn7
-rw-r--r--src/frontend/tubo/bg_player/events.cljs24
-rw-r--r--src/frontend/tubo/bg_player/views.cljs72
-rw-r--r--src/frontend/tubo/main_player/events.cljs2
-rw-r--r--src/frontend/tubo/player/events.cljs10
-rw-r--r--src/frontend/tubo/player/views.cljs161
-rw-r--r--src/frontend/tubo/queue/events.cljs18
-rw-r--r--tailwind.config.js10
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")],
+};