1
0

youtube.cljs 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153
  1. (ns frontend.extensions.video.youtube
  2. (:require [rum.core :as rum]
  3. [cljs.core.async :refer [<! chan go] :as a]
  4. [frontend.components.svg :as svg]
  5. [frontend.state :as state]
  6. [frontend.util :as util]
  7. [goog.object :as gobj]
  8. [clojure.string :as str]
  9. [frontend.mobile.util :as mobile-util]
  10. [frontend.handler.notification :as notification]))
  11. (defn- load-yt-script []
  12. (js/console.log "load yt script")
  13. (let [tag (js/document.createElement "script")
  14. first-script-tag (first (js/document.getElementsByTagName "script"))
  15. parent-node (.-parentNode first-script-tag)]
  16. (set! (.-src tag) "https://www.youtube.com/iframe_api")
  17. (.insertBefore parent-node tag first-script-tag)))
  18. (defn load-youtube-api []
  19. (let [c (chan)]
  20. (if js/window.YT
  21. (a/close! c)
  22. (do
  23. (set! js/window.onYouTubeIframeAPIReady #(a/close! c))
  24. (load-yt-script)))
  25. c))
  26. (defn register-player [state]
  27. (try
  28. (let [id (first (:rum/args state))
  29. node (rum/dom-node state)]
  30. (when node
  31. (let [player (js/window.YT.Player.
  32. node
  33. (clj->js
  34. {:events
  35. {"onReady" (fn [_e] (js/console.log id " ready"))}}))]
  36. (state/update-state! [:youtube/players]
  37. (fn [players]
  38. (assoc players id player))))))
  39. (catch :default _e
  40. nil)))
  41. (rum/defcs youtube-video <
  42. rum/reactive
  43. (rum/local nil ::player)
  44. {:did-mount
  45. (fn [state]
  46. (go
  47. (<! (load-youtube-api))
  48. (register-player state))
  49. state)}
  50. [state id {:keys [width height] :as _opts}]
  51. (let [width (or width (min (- (util/get-width) 96)
  52. 560))
  53. height (or height (int (* width (/ 315 560))))]
  54. [:iframe
  55. {:id (str "youtube-player-" id)
  56. :allow-full-screen "allowfullscreen"
  57. :allow "accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope"
  58. :frame-border "0"
  59. :src (str "https://www.youtube.com/embed/" id "?enablejsapi=1")
  60. :height height
  61. :width width}]))
  62. (defn seconds->display [seconds]
  63. (let [seconds (int seconds)
  64. hours (quot seconds 3600)
  65. minutes (mod (quot seconds 60) 60)
  66. seconds (mod seconds 60)]
  67. (->> [hours minutes seconds]
  68. (map (fn [v] (if (< v 10) (str "0" v) (str v))))
  69. (keep-indexed (fn [idx v]
  70. (when (or (> idx 0)
  71. (not= v "00"))
  72. v)))
  73. (str/join ":"))))
  74. (defn dom-after-video-node? [video-node target]
  75. (not (zero?
  76. (bit-and
  77. (.compareDocumentPosition video-node target)
  78. js/Node.DOCUMENT_POSITION_FOLLOWING))))
  79. (defn get-player [target]
  80. (when-let [iframe (->> (js/document.getElementsByTagName "iframe")
  81. (filter
  82. (fn [node]
  83. (let [src (gobj/get node "src" "")]
  84. (str/includes? src "youtube.com"))))
  85. (filter #(dom-after-video-node? % target))
  86. last)]
  87. (let [id (gobj/get iframe "id" "")
  88. id (str/replace-first id #"youtube-player-" "")]
  89. (get (get @state/state :youtube/players) id))))
  90. (rum/defc timestamp
  91. [seconds]
  92. [:a.svg-small.youtube-timestamp
  93. {:on-click (fn [e]
  94. (util/stop e)
  95. (when-let [player (get-player (.-target e))]
  96. (.seekTo ^js player seconds true)))}
  97. svg/clock
  98. (seconds->display seconds)])
  99. (defn gen-youtube-ts-macro []
  100. (if-let [player (get-player (state/get-input))]
  101. (util/format "{{youtube-timestamp %s}}" (Math/floor (.getCurrentTime ^js player)))
  102. (when (mobile-util/native-platform?)
  103. (notification/show!
  104. "Please embed a YouTube video at first, then use this icon.
  105. Remember: You can paste a raw YouTube url as embedded video on mobile."
  106. :warning
  107. false)
  108. nil)))
  109. (defn parse-timestamp [timestamp]
  110. (let [reg #"^(?:(\d+):)?([0-5]?\d):([0-5]?\d)$"
  111. reg-number #"^\d+$"
  112. timestamp (str timestamp)
  113. total-seconds (some-> (re-matches reg-number timestamp)
  114. util/safe-parse-int)
  115. [_ hours minutes seconds] (re-matches reg timestamp)
  116. [hours minutes seconds] (map #(if (nil? %) 0 (util/safe-parse-int %)) [hours minutes seconds])]
  117. (cond
  118. total-seconds
  119. total-seconds
  120. (and minutes seconds)
  121. (+ (* 3600 hours) (* 60 minutes) seconds)
  122. :else
  123. nil)))
  124. (comment
  125. ;; hh:mm:ss
  126. (re-matches #"^(?:(\d+):)?([0-5]?\d):([0-5]?\d)$" "123:22:23") ;; => ["123:22:23" "123" "22" "23"]
  127. (re-matches #"^(?:(\d+):)?([0-5]?\d):([0-5]?\d)$" "30:23") ;; => ["30:23" nil "30" "23"]
  128. (parse-timestamp "01:23") ;; => 83
  129. (parse-timestamp "01:01:23") ;; => 3683
  130. ;; seconds->display
  131. ;; https://stackoverflow.com/questions/1322732/convert-seconds-to-hh-mm-ss-with-javascript
  132. (seconds->display 129600) ;; => "36:00:00"
  133. (seconds->display 13545) ;; => "03:45:45"
  134. (seconds->display 18) ;; => "00:18"
  135. )