From 515b8a5999f111214c2795780f15a7d0825d0a31 Mon Sep 17 00:00:00 2001 From: Miguel Ángel Moreno Date: Mon, 16 Oct 2023 00:34:05 +0200 Subject: feat(frontend): refine global player with more media controls --- src/frontend/tubo/components/player.cljs | 104 ++++++++++++++++++++++++------- src/frontend/tubo/subs.cljs | 5 ++ 2 files changed, 86 insertions(+), 23 deletions(-) diff --git a/src/frontend/tubo/components/player.cljs b/src/frontend/tubo/components/player.cljs index 430dd91..1fc01ff 100644 --- a/src/frontend/tubo/components/player.cljs +++ b/src/frontend/tubo/components/player.cljs @@ -4,48 +4,106 @@ [reagent.dom :as rdom] [re-frame.core :as rf] [reitit.frontend.easy :as rfe] + [tubo.components.loading :as loading] [tubo.events :as events] + [tubo.util :as util] ["video.js" :as videojs])) (defn global-player [] (let [!player (r/atom nil) - !loop? (r/atom nil)] + !loop? (r/atom nil) + !elapsed-time (r/atom 0) + !volume-level (r/atom 100)] (fn [] - (let [{:keys [uploader-name uploader-url name stream url service-color]} @(rf/subscribe [:global-stream]) - show-global-player? @(rf/subscribe [:show-global-player])] + (let [media-queue @(rf/subscribe [:media-queue]) + media-queue-pos @(rf/subscribe [:media-queue-pos]) + {:keys [uploader-name uploader-url name stream url service-color]} + (and (not-empty media-queue) (nth media-queue media-queue-pos)) + show-global-player? @(rf/subscribe [:show-global-player]) + show-global-player-loading? @(rf/subscribe [:show-global-player-loading])] (when show-global-player? - [:div.sticky.bottom-0.z-50.bg-white.dark:bg-neutral-900.p-5.absolute.box-border.m-0 + [:div.sticky.bottom-0.z-50.bg-white.dark:bg-neutral-900.px-3.py-5.sm:p-5.absolute.box-border.m-0 {:style {:borderColor service-color :borderTopWidth "2px" :borderStyle "solid"}} [:div.flex.items-center.justify-between [:div.flex.items-center [:div.flex.flex-col [:a.text-xs.line-clamp-1 {:href (rfe/href :tubo.routes/stream nil {:url url})} name] - [:a.text-xs.pt-2.text-neutral-600.dark:text-neutral-300 + [:a.text-xs.pt-2.text-neutral-600.dark:text-neutral-300.line-clamp-1 {:href (rfe/href :tubo.routes/channel nil {:url uploader-url})} uploader-name]] - [:div.px-2.py-0.md:pt-4 - [:audio {:src stream :ref #(reset! !player %) :loop @!loop?}]] - [:div.mx-2.flex + [:audio + {:src stream :ref #(reset! !player %) + :loop @!loop? + :autoPlay true + :on-time-update #(and @!player (> (.-readyState @!player) 0) + (reset! !elapsed-time (.-currentTime @!player))) + :on-ended #(and (< (+ media-queue-pos 1) (count media-queue)) + (rf/dispatch [::events/change-media-queue-pos + (+ media-queue-pos 1)]))}]] + [:div.mx-2.flex + (when (and media-queue (not= media-queue-pos 0)) + [:button.focus:outline-none.mx-2 + {:on-click #(rf/dispatch [::events/change-media-queue-pos + (- media-queue-pos 1)])} + [:i.fa-solid.fa-backward-step]]) + [:button.focus:outline-none.mx-2 + {:on-click #(when-let [player @!player] + (if (.-paused player) + (.play player) + (.pause player)))} + (if @!player + (if show-global-player-loading? + [loading/loading-icon service-color "text-1xl"] + (if (.-paused @!player) + [:i.fa-solid.fa-play] + [:i.fa-solid.fa-pause])) + [:i.fa-solid.fa-play])] + (when (and media-queue (< (+ media-queue-pos 1) (count media-queue))) + [:button.focus:ring-transparent.mx-2 + {:on-click #(rf/dispatch [::events/change-media-queue-pos + (+ media-queue-pos 1)])} + [:i.fa-solid.fa-forward-step]]) + [:div.flex + [:div.mx-2.hidden.sm:flex + [:span (if @!elapsed-time (util/format-duration @!elapsed-time) "00:00")] + [:span.mx-2 "/"] + [:span (when (and @!player (> (.-readyState @!player) 0)) + (util/format-duration (.-duration @!player)))]] + [:input.mx-2.w-20.ml:w-80.bg-gray-200.rounded-lg.cursor-pointer.focus:outline-none + {:type "range" + :on-input #(reset! !elapsed-time (.. % -target -value)) + :on-change #(and @!player (> (.-readyState @!player) 0) + (set! (.-currentTime @!player) @!elapsed-time)) + :style {:accentColor service-color} + :max (if (and @!player (> (.-readyState @!player) 0)) + (.floor js/Math (.-duration @!player)) + 100) + :value @!elapsed-time}] [:button.focus:ring-transparent.mx-2 {:on-click (fn [] (swap! !loop? #(not %)))} [:i.fa-solid.fa-repeat {:style {:color (when @!loop? service-color)}}]] - [:button.focus:ring-transparent.mx-2 - {:on-click #(when-let [player @!player] - (if (.-paused player) - (.play player) - (.pause player)))} - (if @!player - (if (.-paused @!player) - [:i.fa-solid.fa-play] - [:i.fa-solid.fa-pause]) - [:i.fa-solid.fa-play])]]] - [:div.px-2 - [:i.fa-solid.fa-close.cursor-pointer - {:on-click (fn [] - (rf/dispatch [::events/toggle-global-player]) - (.pause @!player))}]]]]))))) + [:div.mx-2.flex + [:button.focus:outline-none.mx-2 + {:on-click #(when-let [player @!player] + (set! (.-muted player) (not (.-muted player))))} + (if (and @!player (.-muted @!player)) + [:i.fa-solid.fa-volume-xmark] + [:i.fa-solid.fa-volume-low])] + [:input.w-20.bg-gray-200.rounded-lg.cursor-pointer.focus:outline-none.hidden.sm:block + {:type "range" + :on-input #(do (reset! !volume-level (.. % -target -value)) + (and @!player (> (.-readyState @!player) 0) + (set! (.-volume @!player) (/ @!volume-level 100)))) + :style {:accentColor service-color} + :max 100 + :value @!volume-level}]]] + [:div.ml-2 + [:i.fa-solid.fa-close.cursor-pointer + {:on-click (fn [] + (rf/dispatch [::events/toggle-global-player]) + (.pause @!player))}]]]]]))))) (defn stream-player [options url] diff --git a/src/frontend/tubo/subs.cljs b/src/frontend/tubo/subs.cljs index 7c68b0b..6e7efb2 100644 --- a/src/frontend/tubo/subs.cljs +++ b/src/frontend/tubo/subs.cljs @@ -87,6 +87,11 @@ (fn [db _] (:show-global-player db))) +(rf/reg-sub + :show-global-player-loading + (fn [db _] + (:show-global-player-loading db))) + (rf/reg-sub :show-page-loading (fn [db _] -- cgit v1.2.3