From c6c5f094c41b0a024f052ce2350872490f60a73a Mon Sep 17 00:00:00 2001 From: Miguel Ángel Moreno Date: Sun, 1 Dec 2024 13:16:35 +0100 Subject: feat: replace vidstack with Media Chrome --- src/frontend/tubo/bg_player/events.cljs | 24 +---- src/frontend/tubo/bg_player/views.cljs | 72 +++++-------- src/frontend/tubo/main_player/events.cljs | 2 +- src/frontend/tubo/player/events.cljs | 10 +- src/frontend/tubo/player/views.cljs | 161 +++++++++++++++++++----------- src/frontend/tubo/queue/events.cljs | 18 +++- 6 files changed, 152 insertions(+), 135 deletions(-) (limited to 'src') 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}}))) -- cgit v1.2.3