@meuuhdia/video-player 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,429 @@
1
+ import { meuuhdiaDisclosure } from "./disclosure.js";
2
+ import { MeuuhdiaIcon } from "./icon.js";
3
+ import { MeuuhdiaTranscription } from "./transcription.js";
4
+
5
+ class MeuuhdiaCaption {
6
+ /**
7
+ * @type {Object}
8
+ */
9
+ locale;
10
+
11
+ /**
12
+ * @type {Node}
13
+ */
14
+ controls;
15
+
16
+ /**
17
+ * @type {Node}
18
+ */
19
+ wrapper;
20
+
21
+ /**
22
+ * @type {Node}
23
+ */
24
+ video;
25
+
26
+ /**
27
+ * ? MeuuhdiaCaption constructor
28
+ *
29
+ * @param {Object} locale
30
+ * @param {Node} wrapper
31
+ * @param {Node} video
32
+ * @param {Node} controls
33
+ */
34
+ constructor(locale, wrapper, video, controls) {
35
+ this.locale = locale;
36
+ this.wrapper = wrapper;
37
+ this.video = video;
38
+ this.controls = controls;
39
+ }
40
+
41
+ /**
42
+ * ? Create caption disclosure
43
+ *
44
+ * @see https://developer.mozilla.org/en-US/docs/Web/API/TextTrack
45
+ *
46
+ * @param {TextTrack} textTracks
47
+ * @returns {Node} <li> element
48
+ */
49
+ createCaption(textTracks) {
50
+ let selection = document.createElement("details");
51
+ selection.setAttribute("open", "true");
52
+
53
+ let legend = document.createElement("summary");
54
+ legend.textContent = this.locale.cc.selection;
55
+ selection.appendChild(legend);
56
+
57
+ let tracks_list = document.createElement("ul");
58
+ tracks_list.classList.add("list-unstyled");
59
+
60
+ /**
61
+ * ? disable caption
62
+ */
63
+ let offItem = document.createElement("li");
64
+ offItem.classList.add("meuuhdia_caption_sublist");
65
+
66
+ let offBtn = document.createElement("button");
67
+ offBtn.textContent = this.locale.words.disable;
68
+ offBtn.setAttribute("aria-pressed", "true");
69
+ offBtn.setAttribute("type", "button");
70
+ offBtn.addEventListener("click", this.#switchTrack.bind(this));
71
+
72
+ offItem.appendChild(offBtn);
73
+ tracks_list.appendChild(offItem);
74
+
75
+ /**
76
+ * ? caption types list
77
+ */
78
+ let captions = Object.values(textTracks).filter(track => "captions" === track.kind);
79
+ let subtitles = Object.values(textTracks).filter(track => "subtitles" === track.kind);
80
+ let descriptions = Object.values(textTracks).filter(track => "descriptions" === track.kind);
81
+
82
+ if(0 < captions.length) tracks_list.appendChild(this.#createTrackList(captions, this.locale.cc.caption));
83
+ if(0 < subtitles.length) tracks_list.appendChild(this.#createTrackList(subtitles, this.locale.cc.subtitles));
84
+ if(0 < descriptions.length) MeuuhdiaTranscription.addTranscription(this.locale, this.wrapper, descriptions, this.video);
85
+
86
+ selection.appendChild(tracks_list);
87
+
88
+ /**
89
+ * ? Caption customizer
90
+ */
91
+ let customize = document.createElement("details");
92
+ let customize_legend = document.createElement("summary");
93
+ customize_legend.textContent = this.locale.cc.customize;
94
+ customize.appendChild(customize_legend);
95
+
96
+ /**
97
+ * ? Text section
98
+ */
99
+
100
+ let textSection = document.createElement("fieldset");
101
+ let textSectionLegend = document.createElement("legend");
102
+ textSectionLegend.textContent = this.locale.cc.textSectionLegend;
103
+ textSection.appendChild(textSectionLegend);
104
+
105
+ let text_options_list = document.createElement("ul");
106
+ text_options_list.classList.add("meuuhdia_customize_options");
107
+ text_options_list.classList.add("list-unstyled");
108
+
109
+ //? font size
110
+ this.wrapper.style.setProperty("--meuuhdia-cue-fontsize", "100%");
111
+ text_options_list.appendChild(
112
+ this.#createRangeInput(
113
+ this.locale.cc.textSize,
114
+ "meuuhdia_caption_size",
115
+ "50",
116
+ "200",
117
+ "100",
118
+ "10",
119
+ "fontsize"
120
+ )
121
+ );
122
+
123
+ //? color
124
+ text_options_list.appendChild(
125
+ this.#createSelector(
126
+ this.locale.words.theme,
127
+ "text_color",
128
+ [
129
+ {
130
+ value: "default",
131
+ label: this.locale.cc.defaultColorOption
132
+ },
133
+ {
134
+ value: "custom",
135
+ label: this.locale.cc.customColorOption
136
+ }
137
+ ]
138
+ )
139
+ );
140
+
141
+ //? custom color
142
+ text_options_list.appendChild(
143
+ this.#createColorInput(
144
+ this.locale.words.color,
145
+ "meuuhdia_textcolor",
146
+ "#FFFF00",
147
+ "--meuuhdia-cue-color"
148
+ )
149
+ );
150
+
151
+ textSection.appendChild(text_options_list);
152
+ customize.appendChild(textSection);
153
+
154
+ /**
155
+ * ? Background section
156
+ */
157
+ let bgSection = document.createElement("fieldset");
158
+ let bgSectionLegend = document.createElement("legend");
159
+ bgSectionLegend.textContent = this.locale.cc.bgSectionLegend;
160
+ bgSection.appendChild(bgSectionLegend);
161
+
162
+ let bg_options_list = document.createElement("ul");
163
+ bg_options_list.classList.add("meuuhdia_customize_options");
164
+ bg_options_list.classList.add("list-unstyled");
165
+
166
+ //? style
167
+ bg_options_list.appendChild(
168
+ this.#createSelector(
169
+ this.locale.words.style,
170
+ "bg_style",
171
+ [
172
+ {
173
+ value: "stroke",
174
+ label: this.locale.cc.bgStyleStroke
175
+ },
176
+ {
177
+ value: "fill",
178
+ label: this.locale.cc.bgStyleFill
179
+ }
180
+ ]
181
+ )
182
+ );
183
+
184
+ //? color
185
+ bg_options_list.appendChild(
186
+ this.#createColorInput(
187
+ this.locale.words.color,
188
+ "meuuhdia_bgcolor",
189
+ "#000000",
190
+ "--meuuhdia-cue-bgcolor"
191
+ )
192
+ );
193
+
194
+ bgSection.appendChild(bg_options_list);
195
+ customize.appendChild(bgSection);
196
+
197
+ return meuuhdiaDisclosure.createDisclosure("caption", "caption_off", this.locale.controls.caption, [selection, customize], this.locale.words.disabled);
198
+ }
199
+
200
+ createCaptionContainer() {
201
+ Object.values(this.video.textTracks).forEach(track => track.mode = "hidden");
202
+
203
+ let container = document.createElement("div");
204
+ container.setAttribute("aria-hidden", "true");
205
+ container.setAttribute("hidden", "hidden");
206
+ container.classList.add("meuuhdia_caption_container");
207
+
208
+ return container;
209
+ }
210
+
211
+ /**
212
+ * ? Create tracks list item
213
+ *
214
+ * @param {Array} tracks
215
+ * @param {string} title
216
+ * @returns {Node} <li> element
217
+ */
218
+ #createTrackList(tracks, title) {
219
+ let tracksContainer = document.createElement("li");
220
+ tracksContainer.classList.add("meuuhdia_caption_sublist");
221
+
222
+ let tracksHeader = document.createElement("p");
223
+ tracksHeader.textContent = title;
224
+ tracksContainer.appendChild(tracksHeader);
225
+
226
+ if(1 < tracks.length) {
227
+ let tracksList = document.createElement("ul");
228
+ tracksList.classList.add("list-unstyled");
229
+
230
+ tracks.forEach(el => {
231
+ let item = document.createElement("li");
232
+ let btn = document.createElement("button");
233
+
234
+ btn.textContent = el.label;
235
+ btn.dataset.track = el.id;
236
+ btn.setAttribute("lang", el.language);
237
+ btn.setAttribute("type", "button");
238
+ btn.setAttribute("aria-pressed", "false");
239
+ btn.addEventListener("click", this.#switchTrack.bind(this));
240
+
241
+ item.appendChild(btn);
242
+ tracksList.appendChild(item);
243
+ });
244
+
245
+ tracksContainer.appendChild(tracksList);
246
+ } else {
247
+ let trackBtn = document.createElement("button");
248
+ trackBtn.textContent = tracks[0].label;
249
+ trackBtn.dataset.track = tracks[0].id;
250
+ trackBtn.setAttribute("lang", tracks[0].language);
251
+ trackBtn.setAttribute("type", "button");
252
+ trackBtn.setAttribute("aria-pressed", "false");
253
+ trackBtn.addEventListener("click", this.#switchTrack.bind(this));
254
+
255
+ tracksContainer.appendChild(trackBtn);
256
+ }
257
+
258
+ return tracksContainer;
259
+ }
260
+
261
+ /**
262
+ * ? Create input color item
263
+ *
264
+ * @param {string} label
265
+ * @param {string} identifiant
266
+ * @param {string} value
267
+ * @param {string} variable
268
+ * @returns {Node} <li> element
269
+ */
270
+ #createColorInput(label, identifiant, value, variable) {
271
+ let color = document.createElement("li");
272
+ let colorLabel = document.createElement("label");
273
+ colorLabel.textContent = label;
274
+ colorLabel.setAttribute("for", identifiant);
275
+
276
+ let colorInput = document.createElement("input");
277
+ colorInput.setAttribute("id", identifiant);
278
+ colorInput.setAttribute("type", "color");
279
+ colorInput.setAttribute("value", value);
280
+ colorInput.setAttribute("disabled", true);
281
+ colorInput.addEventListener("change", (evt) => {
282
+ this.wrapper.style.setProperty(variable, evt.currentTarget.value);
283
+ });
284
+
285
+ color.appendChild(colorLabel);
286
+ color.appendChild(colorInput);
287
+
288
+ return color;
289
+ }
290
+
291
+ #switchTrack(evt) {
292
+ let caption = this.controls.querySelector(".meuuhdia_caption");
293
+ caption.querySelectorAll(".meuuhdia_caption_sublist button").forEach(opt => {
294
+ if("true" == opt.getAttribute("aria-pressed")) {
295
+ opt.setAttribute("aria-pressed", "false");
296
+ let track_id = this.video.textTracks.getTrackById(opt.dataset.track);
297
+ if(track_id) track_id.oncuechange = null;
298
+ }
299
+ });
300
+
301
+ evt.currentTarget.setAttribute("aria-pressed", "true");
302
+ let caption_action = caption.querySelector(".meuuhdia_caption_action");
303
+ let caption_icon = caption_action.querySelector(".meuuhdia_caption_action svg");
304
+ let caption_container = this.wrapper.querySelector(".meuuhdia_caption_container");
305
+
306
+ if(evt.currentTarget.dataset.track) {
307
+ caption_action.replaceChild(MeuuhdiaIcon.createIcon("caption", this.locale.words.enabled), caption_icon);
308
+ caption_container.removeAttribute("hidden");
309
+ let track = this.video.textTracks.getTrackById(evt.currentTarget.dataset.track);
310
+ this.#replaceActiveCues();
311
+
312
+ track.oncuechange = this.#replaceActiveCues.bind(this);
313
+ } else {
314
+ caption_action.replaceChild(MeuuhdiaIcon.createIcon("caption_off", this.locale.words.disabled), caption_icon);
315
+ caption_container.setAttribute("hidden", "hidden");
316
+ caption_container.replaceChildren();
317
+ }
318
+ }
319
+
320
+ #replaceActiveCues() {
321
+ let active_track = this.controls.querySelector(".meuuhdia_caption button[data-track][aria-pressed='true']").dataset.track;
322
+ let track = this.video.textTracks.getTrackById(active_track);
323
+ let activeCues = track.activeCues;
324
+ let cueContainer = this.wrapper.querySelector(".meuuhdia_caption_container");
325
+
326
+ if(activeCues.length) {
327
+ let cues = Object.values(activeCues).map(cue => {
328
+ let cueHTML = cue.getCueAsHTML();
329
+ let paragraph = document.createElement("p");
330
+ if(cue.align) paragraph.dataset.align = cue.align;
331
+ if(1 == cue.line) paragraph.classList.add("align-top");
332
+ paragraph.classList.add("meuuhdia_caption_cue");
333
+ paragraph.appendChild(cueHTML);
334
+ return paragraph;
335
+ });
336
+
337
+ cueContainer.replaceChildren(...cues);
338
+ } else cueContainer.replaceChildren();
339
+ }
340
+
341
+ /**
342
+ * ? Create input range item
343
+ *
344
+ * @param {string} label
345
+ * @param {string} identifiant
346
+ * @param {string} min
347
+ * @param {string} max
348
+ * @param {string} value
349
+ * @param {string} step
350
+ * @param {string} setting
351
+ * @returns {Node} <li> element
352
+ */
353
+ #createRangeInput(label, identifiant, min, max, value, step, setting) {
354
+ let item = document.createElement("li");
355
+
356
+ let item_label = document.createElement("label");
357
+ item_label.textContent = label;
358
+ item_label.setAttribute("for", identifiant);
359
+ item.appendChild(item_label);
360
+
361
+ let item_input = document.createElement("input");
362
+ item_input.setAttribute("type", "range");
363
+ item_input.setAttribute("min", min);
364
+ item_input.setAttribute("max", max);
365
+ item_input.setAttribute("step", step);
366
+ item_input.setAttribute("value", value);
367
+ item_input.setAttribute("id", identifiant);
368
+
369
+ if("fontsize" === setting) {
370
+ item_input.classList.add("meuuhdia_range_horizontal");
371
+
372
+ item_input.addEventListener("change", (evt) => {
373
+ this.wrapper.style.setProperty("--meuuhdia-cue-fontsize", evt.currentTarget.value + "%");
374
+ });
375
+ }
376
+
377
+ item.appendChild(item_input);
378
+ return item;
379
+ }
380
+
381
+ /**
382
+ * ? Create select item
383
+ *
384
+ * @param {string} label
385
+ * @param {string} identifiant
386
+ * @param {Object} options
387
+ * @returns {Node} <li> element
388
+ */
389
+ #createSelector(label, identifiant, options) {
390
+ let item = document.createElement("li");
391
+
392
+ let item_label = document.createElement("label");
393
+ item_label.textContent = label;
394
+ item_label.setAttribute("for", identifiant);
395
+ item.appendChild(item_label);
396
+
397
+ let selector = document.createElement("select");
398
+ selector.setAttribute("id", identifiant);
399
+
400
+ options.forEach(opt => {
401
+ let newOpt = document.createElement("option");
402
+ newOpt.setAttribute("value", opt.value);
403
+ newOpt.textContent = opt.label;
404
+
405
+ selector.appendChild(newOpt);
406
+ });
407
+
408
+ switch(identifiant) {
409
+ case "text_color":
410
+ selector.addEventListener("change", (evt) => {
411
+ this.wrapper.querySelector(".meuuhdia_caption_container").classList.toggle("meuuhdia_custom_colors");
412
+
413
+ this.wrapper.querySelectorAll("input[type='color']").forEach(el => el.toggleAttribute("disabled"));
414
+ });
415
+ break;
416
+ case "bg_style":
417
+ selector.addEventListener("change", (evt) => {
418
+ this.wrapper.querySelector(".meuuhdia_caption_container").classList.toggle("meuuhdia_caption_cue--fill");
419
+ });
420
+ break;
421
+ default:
422
+ }
423
+
424
+ item.appendChild(selector);
425
+ return item;
426
+ }
427
+ }
428
+
429
+ export { MeuuhdiaCaption };