audio_player.cljs 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116
  1. (ns tubo.components.audio-player
  2. (:require
  3. [goog.object :as gobj]
  4. [reagent.core :as r]
  5. [reagent.dom :as rdom]
  6. [re-frame.core :as rf]
  7. [reitit.frontend.easy :as rfe]
  8. [tubo.components.layout :as layout]
  9. [tubo.components.player :as player]
  10. [tubo.events :as events]
  11. [tubo.util :as util]))
  12. (defn audio-source
  13. [!player]
  14. (let [{:keys [stream]} @(rf/subscribe [:media-queue-stream])]
  15. (r/create-class
  16. {:display-name "AudioPlayer"
  17. :component-did-mount
  18. (fn [this]
  19. (when stream
  20. (set! (.-src (rdom/dom-node this)) stream)))
  21. :reagent-render
  22. (fn [!player]
  23. (let [!elapsed-time @(rf/subscribe [:elapsed-time])
  24. player-ready? (and @!player (> (.-readyState @!player) 0))
  25. muted? @(rf/subscribe [:muted])
  26. volume-level @(rf/subscribe [:volume-level])
  27. loop-playback @(rf/subscribe [:loop-playback])]
  28. [:audio
  29. {:ref #(reset! !player %)
  30. :loop (= loop-playback :stream)
  31. :on-loaded-data #(rf/dispatch [::events/player-start])
  32. :muted muted?
  33. :on-time-update #(reset! !elapsed-time (.-currentTime @!player))}]))})))
  34. (defn main-controls
  35. [service-color]
  36. (let [media-queue @(rf/subscribe [:media-queue])
  37. media-queue-pos @(rf/subscribe [:media-queue-pos])
  38. loading? @(rf/subscribe [:show-audio-player-loading])
  39. !elapsed-time @(rf/subscribe [:elapsed-time])
  40. !player @(rf/subscribe [:player])
  41. paused? @(rf/subscribe [:paused])
  42. player-ready? (and @!player (> (.-readyState @!player) 0))
  43. loop-playback @(rf/subscribe [:loop-playback])]
  44. [:div.flex.flex-col.items-center.ml-auto
  45. [:div.flex.justify-end
  46. [player/loop-button loop-playback service-color]
  47. [player/button
  48. [:i.fa-solid.fa-backward-step]
  49. #(when (and media-queue (not= media-queue-pos 0))
  50. (rf/dispatch [::events/change-media-queue-pos (- media-queue-pos 1)]))
  51. :disabled? (not (and media-queue (not= media-queue-pos 0)))]
  52. [player/button [:i.fa-solid.fa-backward]
  53. #(rf/dispatch [::events/set-player-time (- @!elapsed-time 5)])]
  54. [player/button
  55. (if (or loading? (not @!player))
  56. [layout/loading-icon service-color "lg:text-2xl"]
  57. (if paused?
  58. [:i.fa-solid.fa-play]
  59. [:i.fa-solid.fa-pause]))
  60. #(rf/dispatch [::events/player-paused (not paused?)])
  61. :show-on-mobile? true
  62. :extra-styles "lg:text-2xl"]
  63. [player/button [:i.fa-solid.fa-forward]
  64. #(rf/dispatch [::events/set-player-time (+ @!elapsed-time 5)])]
  65. [player/button [:i.fa-solid.fa-forward-step]
  66. #(when (and media-queue (< (+ media-queue-pos 1) (count media-queue)))
  67. (rf/dispatch [::events/change-media-queue-pos (+ media-queue-pos 1)]))
  68. :disabled? (not (and media-queue (< (+ media-queue-pos 1) (count media-queue))))]]
  69. [:div.hidden.lg:flex.items-center
  70. [:span.mx-2.text-sm
  71. (if @!elapsed-time (util/format-duration @!elapsed-time) "00:00")]
  72. [:div.w-20.lg:w-64.mx-2.flex.items-center
  73. [player/time-slider !player !elapsed-time service-color]]
  74. [:span.mx-2.text-sm
  75. (if player-ready? (util/format-duration (.-duration @!player)) "00:00")]]]))
  76. (defn player
  77. []
  78. (let [{:keys
  79. [uploader-name uploader-url thumbnail-url
  80. name stream url service-color] :as current-stream}
  81. @(rf/subscribe [:media-queue-stream])
  82. show-audio-player? @(rf/subscribe [:show-audio-player])
  83. show-media-queue? @(rf/subscribe [:show-media-queue])
  84. volume-level @(rf/subscribe [:volume-level])
  85. muted? @(rf/subscribe [:muted])
  86. !player @(rf/subscribe [:player])
  87. {:keys [current-theme]} @(rf/subscribe [:settings])
  88. bg-color (str "rgba(" (if (= current-theme "dark") "23, 23, 23" "255, 255, 255") ", 0.95)")]
  89. (when show-audio-player?
  90. [:div.sticky.bottom-0.z-40.p-3.absolute.box-border.m-0
  91. {:style
  92. {:display (when show-media-queue? "none")
  93. :background-image (str "linear-gradient(0deg, " bg-color "," bg-color "), url(\"" thumbnail-url "\")")
  94. :backgroundSize "cover"
  95. :backgroundPosition "center"
  96. :backgroundRepeat "no-repeat"}}
  97. [:div.flex.items-center.justify-between
  98. [:div.flex.items-center.lg:flex-1
  99. [:div {:style {:height "40px" :width "70px" :maxWidth "70px" :minWidth "70px"}}
  100. [:img.min-h-full.max-h-full.object-cover.min-w-full.max-w-full.w-full {:src thumbnail-url}]]
  101. [:div.flex.flex-col.px-2
  102. [:a.text-xs.line-clamp-1
  103. {:href (rfe/href :tubo.routes/stream nil {:url url})} name]
  104. [:a.text-xs.pt-2.text-neutral-600.dark:text-neutral-300.line-clamp-1
  105. {:href (rfe/href :tubo.routes/channel nil {:url uploader-url})} uploader-name]]
  106. [audio-source !player]]
  107. [main-controls service-color]
  108. [:div.flex.lg:justify-end.lg:flex-1
  109. [player/volume-slider !player volume-level muted? service-color]
  110. [player/button [:i.fa-solid.fa-list] #(rf/dispatch [::events/toggle-media-queue])
  111. :show-on-mobile? true]
  112. [player/button [:i.fa-solid.fa-close] #(rf/dispatch [::events/dispose-audio-player])
  113. :show-on-mobile? true]]]])))