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