vidply 1.0.28 → 1.0.29

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.
Files changed (61) hide show
  1. package/dist/dev/{vidply.TranscriptManager-QSF2PWUN.js → vidply.TranscriptManager-T677KF4N.js} +4 -5
  2. package/dist/dev/{vidply.TranscriptManager-QSF2PWUN.js.map → vidply.TranscriptManager-T677KF4N.js.map} +2 -2
  3. package/dist/dev/{vidply.chunk-SRM7VNHG.js → vidply.chunk-GS2JX5RQ.js} +136 -95
  4. package/dist/dev/vidply.chunk-GS2JX5RQ.js.map +7 -0
  5. package/dist/dev/vidply.esm.js +1674 -310
  6. package/dist/dev/vidply.esm.js.map +4 -4
  7. package/dist/legacy/vidply.js +1776 -348
  8. package/dist/legacy/vidply.js.map +4 -4
  9. package/dist/legacy/vidply.min.js +1 -1
  10. package/dist/legacy/vidply.min.meta.json +92 -24
  11. package/dist/prod/vidply.TranscriptManager-WFZSW6NR.min.js +6 -0
  12. package/dist/prod/vidply.chunk-LGTJRPUL.min.js +6 -0
  13. package/dist/prod/vidply.esm.min.js +8 -8
  14. package/dist/vidply.esm.min.meta.json +92 -24
  15. package/package.json +1 -1
  16. package/src/controls/ControlBar.js +3 -7
  17. package/src/controls/TranscriptManager.js +7 -7
  18. package/src/core/AudioDescriptionManager.js +701 -0
  19. package/src/core/Player.js +4776 -4921
  20. package/src/core/SignLanguageManager.js +1134 -0
  21. package/src/utils/DOMUtils.js +153 -114
  22. package/src/utils/MenuFactory.js +374 -0
  23. package/dist/dev/vidply.TranscriptManager-GZKY44ON.js +0 -1744
  24. package/dist/dev/vidply.TranscriptManager-GZKY44ON.js.map +0 -7
  25. package/dist/dev/vidply.TranscriptManager-UTJBQC5B.js +0 -1744
  26. package/dist/dev/vidply.TranscriptManager-UTJBQC5B.js.map +0 -7
  27. package/dist/dev/vidply.chunk-5663PYKK.js +0 -1631
  28. package/dist/dev/vidply.chunk-5663PYKK.js.map +0 -7
  29. package/dist/dev/vidply.chunk-SRM7VNHG.js.map +0 -7
  30. package/dist/dev/vidply.chunk-UH5MTGKF.js +0 -1630
  31. package/dist/dev/vidply.chunk-UH5MTGKF.js.map +0 -7
  32. package/dist/dev/vidply.de-RXAJM5QE.js +0 -181
  33. package/dist/dev/vidply.de-RXAJM5QE.js.map +0 -7
  34. package/dist/dev/vidply.de-THBIMP4S.js +0 -180
  35. package/dist/dev/vidply.de-THBIMP4S.js.map +0 -7
  36. package/dist/dev/vidply.es-6VWDNNNL.js +0 -180
  37. package/dist/dev/vidply.es-6VWDNNNL.js.map +0 -7
  38. package/dist/dev/vidply.es-SADVLJTQ.js +0 -181
  39. package/dist/dev/vidply.es-SADVLJTQ.js.map +0 -7
  40. package/dist/dev/vidply.fr-V3VAYBBT.js +0 -181
  41. package/dist/dev/vidply.fr-V3VAYBBT.js.map +0 -7
  42. package/dist/dev/vidply.fr-WHTWCHWT.js +0 -180
  43. package/dist/dev/vidply.fr-WHTWCHWT.js.map +0 -7
  44. package/dist/dev/vidply.ja-BFQNPOFI.js +0 -180
  45. package/dist/dev/vidply.ja-BFQNPOFI.js.map +0 -7
  46. package/dist/dev/vidply.ja-KL2TLZGJ.js +0 -181
  47. package/dist/dev/vidply.ja-KL2TLZGJ.js.map +0 -7
  48. package/dist/prod/vidply.TranscriptManager-DZ2WZU3K.min.js +0 -6
  49. package/dist/prod/vidply.TranscriptManager-E5QHGFIR.min.js +0 -6
  50. package/dist/prod/vidply.TranscriptManager-UZ6DUFB6.min.js +0 -6
  51. package/dist/prod/vidply.chunk-5DWTMWEO.min.js +0 -6
  52. package/dist/prod/vidply.chunk-IBNYTGGM.min.js +0 -6
  53. package/dist/prod/vidply.chunk-MBUR3U5L.min.js +0 -6
  54. package/dist/prod/vidply.de-HGJBCLLE.min.js +0 -6
  55. package/dist/prod/vidply.de-SWFW4HYT.min.js +0 -6
  56. package/dist/prod/vidply.es-7BJ2DJAY.min.js +0 -6
  57. package/dist/prod/vidply.es-CZEBXCZN.min.js +0 -6
  58. package/dist/prod/vidply.fr-DPVR5DFY.min.js +0 -6
  59. package/dist/prod/vidply.fr-HFOL7MWA.min.js +0 -6
  60. package/dist/prod/vidply.ja-PEBVWKVH.min.js +0 -6
  61. package/dist/prod/vidply.ja-QTVU5C25.min.js +0 -6
@@ -0,0 +1,374 @@
1
+ /**
2
+ * Menu Factory - Centralized menu creation and management
3
+ * Reduces code duplication across ControlBar menu methods
4
+ */
5
+
6
+ import { DOMUtils } from './DOMUtils.js';
7
+ import { createIconElement } from '../icons/Icons.js';
8
+ import { i18n } from '../i18n/i18n.js';
9
+ import { attachMenuKeyboardNavigation, focusFirstMenuItem } from './MenuUtils.js';
10
+
11
+ /**
12
+ * Create and show a menu with standard positioning and behavior
13
+ * @param {Object} options - Menu configuration
14
+ * @returns {HTMLElement} The created menu element
15
+ */
16
+ export function createMenu({
17
+ player,
18
+ button,
19
+ menuClass,
20
+ ariaLabel,
21
+ items = [],
22
+ activeIndex = -1,
23
+ onClose = null,
24
+ insertIntoDOM = null,
25
+ positionMenu = null,
26
+ attachCloseHandler = null
27
+ }) {
28
+ const classPrefix = player.options.classPrefix;
29
+
30
+ // Remove existing menu (toggle behavior)
31
+ const existingMenu = document.querySelector(`.${classPrefix}-${menuClass}`);
32
+ if (existingMenu) {
33
+ existingMenu.remove();
34
+ button.setAttribute('aria-expanded', 'false');
35
+ return null;
36
+ }
37
+
38
+ // Create menu container
39
+ const menu = DOMUtils.createElement('div', {
40
+ className: `${classPrefix}-${menuClass} ${classPrefix}-menu`,
41
+ attributes: {
42
+ 'role': 'menu',
43
+ 'aria-label': ariaLabel
44
+ }
45
+ });
46
+
47
+ let activeItem = null;
48
+
49
+ // Create menu items
50
+ items.forEach((itemConfig, index) => {
51
+ if (itemConfig.type === 'divider') {
52
+ const divider = DOMUtils.createElement('div', {
53
+ className: `${classPrefix}-menu-divider`,
54
+ attributes: { 'role': 'separator' }
55
+ });
56
+ menu.appendChild(divider);
57
+ return;
58
+ }
59
+
60
+ if (itemConfig.type === 'header') {
61
+ const header = DOMUtils.createElement('div', {
62
+ className: `${classPrefix}-menu-header`,
63
+ textContent: itemConfig.text
64
+ });
65
+ menu.appendChild(header);
66
+ return;
67
+ }
68
+
69
+ if (itemConfig.disabled) {
70
+ const disabledItem = DOMUtils.createElement('div', {
71
+ className: `${classPrefix}-menu-item`,
72
+ textContent: itemConfig.text,
73
+ attributes: { 'role': 'menuitem' },
74
+ style: { opacity: '0.5', cursor: 'default' }
75
+ });
76
+ menu.appendChild(disabledItem);
77
+ return;
78
+ }
79
+
80
+ const item = DOMUtils.createElement('button', {
81
+ className: `${classPrefix}-menu-item`,
82
+ attributes: {
83
+ 'type': 'button',
84
+ 'role': 'menuitem',
85
+ 'tabindex': '-1'
86
+ }
87
+ });
88
+
89
+ // Add content based on item type
90
+ if (itemConfig.icon) {
91
+ item.appendChild(createIconElement(itemConfig.icon));
92
+ }
93
+
94
+ if (itemConfig.timeLabel) {
95
+ const timeSpan = DOMUtils.createElement('span', {
96
+ className: `${classPrefix}-chapter-time`,
97
+ textContent: itemConfig.timeLabel,
98
+ attributes: itemConfig.timeAriaLabel
99
+ ? { 'aria-label': itemConfig.timeAriaLabel }
100
+ : {}
101
+ });
102
+ item.appendChild(timeSpan);
103
+ item.appendChild(document.createTextNode(' '));
104
+ }
105
+
106
+ if (itemConfig.text) {
107
+ const textSpan = DOMUtils.createElement('span', {
108
+ className: itemConfig.textClass || `${classPrefix}-menu-item-text`,
109
+ textContent: itemConfig.text
110
+ });
111
+ item.appendChild(textSpan);
112
+ }
113
+
114
+ // Mark as active
115
+ const isActive = itemConfig.active || index === activeIndex;
116
+ if (isActive) {
117
+ item.classList.add(`${classPrefix}-menu-item-active`);
118
+ item.appendChild(createIconElement('check'));
119
+ activeItem = item;
120
+ }
121
+
122
+ // Click handler
123
+ if (itemConfig.onClick) {
124
+ item.addEventListener('click', () => {
125
+ itemConfig.onClick(itemConfig.value, index);
126
+ closeMenuAndReturnFocus(menu, button, onClose);
127
+ });
128
+ }
129
+
130
+ menu.appendChild(item);
131
+ });
132
+
133
+ // Position menu (hide first to prevent jumping)
134
+ menu.style.visibility = 'hidden';
135
+ menu.style.display = 'block';
136
+
137
+ // Insert into DOM
138
+ if (insertIntoDOM) {
139
+ insertIntoDOM(menu, button);
140
+ } else {
141
+ button.insertAdjacentElement('afterend', menu);
142
+ }
143
+
144
+ // Position
145
+ if (positionMenu) {
146
+ positionMenu(menu, button, true);
147
+ }
148
+
149
+ // Show menu
150
+ requestAnimationFrame(() => {
151
+ menu.style.visibility = 'visible';
152
+ });
153
+
154
+ // Add keyboard navigation
155
+ attachMenuKeyboardNavigation(menu, button, `.${classPrefix}-menu-item`, () => {
156
+ closeMenuAndReturnFocus(menu, button, onClose);
157
+ });
158
+
159
+ // Focus active or first item
160
+ setTimeout(() => {
161
+ const focusTarget = activeItem || menu.querySelector(`.${classPrefix}-menu-item`);
162
+ if (focusTarget) {
163
+ focusTarget.focus({ preventScroll: true });
164
+ }
165
+ }, 0);
166
+
167
+ // Attach close handler
168
+ if (attachCloseHandler) {
169
+ attachCloseHandler(menu, button);
170
+ }
171
+
172
+ button.setAttribute('aria-expanded', 'true');
173
+
174
+ return menu;
175
+ }
176
+
177
+ /**
178
+ * Close menu and return focus to button
179
+ */
180
+ function closeMenuAndReturnFocus(menu, button, onClose) {
181
+ if (menu) {
182
+ menu.remove();
183
+ }
184
+ button.setAttribute('aria-expanded', 'false');
185
+ button.focus({ preventScroll: true });
186
+ if (onClose) {
187
+ onClose();
188
+ }
189
+ }
190
+
191
+ /**
192
+ * Create a speed menu
193
+ */
194
+ export function createSpeedMenu({
195
+ player,
196
+ button,
197
+ currentSpeed,
198
+ speeds = [0.25, 0.5, 0.75, 1, 1.25, 1.5, 1.75, 2],
199
+ onSpeedChange,
200
+ insertIntoDOM,
201
+ positionMenu,
202
+ attachCloseHandler
203
+ }) {
204
+ const items = speeds.map(speed => ({
205
+ text: speed === 1 ? i18n.t('player.normalSpeed') : `${speed}x`,
206
+ value: speed,
207
+ active: Math.abs(currentSpeed - speed) < 0.01,
208
+ onClick: (value) => onSpeedChange(value)
209
+ }));
210
+
211
+ return createMenu({
212
+ player,
213
+ button,
214
+ menuClass: 'speed-menu',
215
+ ariaLabel: i18n.t('player.speed'),
216
+ items,
217
+ insertIntoDOM,
218
+ positionMenu,
219
+ attachCloseHandler
220
+ });
221
+ }
222
+
223
+ /**
224
+ * Create a captions menu
225
+ */
226
+ export function createCaptionsMenu({
227
+ player,
228
+ button,
229
+ tracks,
230
+ currentTrackIndex,
231
+ captionsEnabled,
232
+ onTrackSelect,
233
+ onDisable,
234
+ insertIntoDOM,
235
+ positionMenu,
236
+ attachCloseHandler
237
+ }) {
238
+ const classPrefix = player.options.classPrefix;
239
+
240
+ const items = [
241
+ {
242
+ text: i18n.t('player.captionsOff'),
243
+ active: !captionsEnabled,
244
+ onClick: () => onDisable()
245
+ }
246
+ ];
247
+
248
+ tracks.forEach((track, index) => {
249
+ items.push({
250
+ text: track.label || track.language,
251
+ value: index,
252
+ active: captionsEnabled && currentTrackIndex === index,
253
+ onClick: () => onTrackSelect(index)
254
+ });
255
+ });
256
+
257
+ return createMenu({
258
+ player,
259
+ button,
260
+ menuClass: 'captions-menu',
261
+ ariaLabel: i18n.t('player.captions'),
262
+ items,
263
+ insertIntoDOM,
264
+ positionMenu,
265
+ attachCloseHandler
266
+ });
267
+ }
268
+
269
+ /**
270
+ * Create a chapters menu
271
+ */
272
+ export function createChaptersMenu({
273
+ player,
274
+ button,
275
+ chapters,
276
+ onChapterSelect,
277
+ formatTime,
278
+ formatDuration,
279
+ insertIntoDOM,
280
+ positionMenu,
281
+ attachCloseHandler
282
+ }) {
283
+ if (!chapters || chapters.length === 0) {
284
+ return createMenu({
285
+ player,
286
+ button,
287
+ menuClass: 'chapters-menu',
288
+ ariaLabel: i18n.t('player.chapters'),
289
+ items: [{
290
+ text: i18n.t('player.noChapters'),
291
+ disabled: true
292
+ }],
293
+ insertIntoDOM,
294
+ positionMenu,
295
+ attachCloseHandler
296
+ });
297
+ }
298
+
299
+ const items = chapters.map(chapter => ({
300
+ timeLabel: formatTime(chapter.startTime),
301
+ timeAriaLabel: formatDuration(chapter.startTime),
302
+ text: chapter.text,
303
+ textClass: `${player.options.classPrefix}-chapter-title`,
304
+ value: chapter.startTime,
305
+ onClick: (value) => onChapterSelect(value)
306
+ }));
307
+
308
+ return createMenu({
309
+ player,
310
+ button,
311
+ menuClass: 'chapters-menu',
312
+ ariaLabel: i18n.t('player.chapters'),
313
+ items,
314
+ insertIntoDOM,
315
+ positionMenu,
316
+ attachCloseHandler
317
+ });
318
+ }
319
+
320
+ /**
321
+ * Create a quality menu
322
+ */
323
+ export function createQualityMenu({
324
+ player,
325
+ button,
326
+ qualities,
327
+ currentQuality,
328
+ isHLS,
329
+ onQualitySelect,
330
+ insertIntoDOM,
331
+ positionMenu,
332
+ attachCloseHandler
333
+ }) {
334
+ const items = [];
335
+
336
+ // Auto option for HLS
337
+ if (isHLS) {
338
+ items.push({
339
+ text: i18n.t('player.auto'),
340
+ value: -1,
341
+ active: currentQuality === -1,
342
+ onClick: () => onQualitySelect(-1)
343
+ });
344
+ }
345
+
346
+ // Quality options
347
+ qualities.forEach(quality => {
348
+ items.push({
349
+ text: quality.name || `${quality.height}p`,
350
+ value: quality.index,
351
+ active: quality.index === currentQuality,
352
+ onClick: () => onQualitySelect(quality.index)
353
+ });
354
+ });
355
+
356
+ if (items.length === 0) {
357
+ items.push({
358
+ text: i18n.t('player.autoQuality'),
359
+ disabled: true
360
+ });
361
+ }
362
+
363
+ return createMenu({
364
+ player,
365
+ button,
366
+ menuClass: 'quality-menu',
367
+ ariaLabel: i18n.t('player.quality'),
368
+ items,
369
+ insertIntoDOM,
370
+ positionMenu,
371
+ attachCloseHandler
372
+ });
373
+ }
374
+