vidply 1.0.27 → 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.
- package/dist/dev/{vidply.TranscriptManager-GZKY44ON.js → vidply.TranscriptManager-T677KF4N.js} +5 -6
- package/dist/dev/vidply.TranscriptManager-T677KF4N.js.map +7 -0
- package/dist/dev/{vidply.chunk-UH5MTGKF.js → vidply.chunk-GS2JX5RQ.js} +149 -100
- package/dist/dev/vidply.chunk-GS2JX5RQ.js.map +7 -0
- package/dist/dev/{vidply.de-THBIMP4S.js → vidply.de-SNL6AJ4D.js} +10 -2
- package/dist/dev/{vidply.de-THBIMP4S.js.map → vidply.de-SNL6AJ4D.js.map} +2 -2
- package/dist/dev/{vidply.es-6VWDNNNL.js → vidply.es-2QCQKZ4U.js} +10 -2
- package/dist/dev/{vidply.es-6VWDNNNL.js.map → vidply.es-2QCQKZ4U.js.map} +2 -2
- package/dist/dev/vidply.esm.js +1681 -317
- package/dist/dev/vidply.esm.js.map +4 -4
- package/dist/dev/{vidply.fr-WHTWCHWT.js → vidply.fr-FJAZRL4L.js} +10 -2
- package/dist/dev/{vidply.fr-WHTWCHWT.js.map → vidply.fr-FJAZRL4L.js.map} +2 -2
- package/dist/dev/{vidply.ja-BFQNPOFI.js → vidply.ja-2XQOW53T.js} +10 -2
- package/dist/dev/vidply.ja-2XQOW53T.js.map +7 -0
- package/dist/legacy/vidply.js +1829 -361
- package/dist/legacy/vidply.js.map +4 -4
- package/dist/legacy/vidply.min.js +1 -1
- package/dist/legacy/vidply.min.meta.json +103 -35
- package/dist/prod/vidply.TranscriptManager-WFZSW6NR.min.js +6 -0
- package/dist/prod/vidply.chunk-LGTJRPUL.min.js +6 -0
- package/dist/prod/vidply.de-FR3XX54P.min.js +6 -0
- package/dist/prod/vidply.es-3IJCQLJ7.min.js +6 -0
- package/dist/prod/vidply.esm.min.js +8 -8
- package/dist/prod/vidply.fr-NC4VEAPH.min.js +6 -0
- package/dist/prod/vidply.ja-4ZC6ZQLV.min.js +6 -0
- package/dist/vidply.esm.min.meta.json +115 -47
- package/package.json +1 -1
- package/src/controls/ControlBar.js +3 -7
- package/src/controls/TranscriptManager.js +8 -8
- package/src/core/AudioDescriptionManager.js +701 -0
- package/src/core/Player.js +4776 -4921
- package/src/core/SignLanguageManager.js +1134 -0
- package/src/features/PlaylistManager.js +12 -12
- package/src/i18n/languages/de.js +9 -1
- package/src/i18n/languages/en.js +9 -1
- package/src/i18n/languages/es.js +9 -1
- package/src/i18n/languages/fr.js +9 -1
- package/src/i18n/languages/ja.js +9 -1
- package/src/utils/DOMUtils.js +153 -114
- package/src/utils/MenuFactory.js +374 -0
- package/dist/dev/vidply.TranscriptManager-GZKY44ON.js.map +0 -7
- package/dist/dev/vidply.chunk-UH5MTGKF.js.map +0 -7
- package/dist/dev/vidply.ja-BFQNPOFI.js.map +0 -7
- package/dist/prod/vidply.TranscriptManager-UZ6DUFB6.min.js +0 -6
- package/dist/prod/vidply.chunk-MBUR3U5L.min.js +0 -6
- package/dist/prod/vidply.de-SWFW4HYT.min.js +0 -6
- package/dist/prod/vidply.es-7BJ2DJAY.min.js +0 -6
- package/dist/prod/vidply.fr-DPVR5DFY.min.js +0 -6
- package/dist/prod/vidply.ja-PEBVWKVH.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
|
+
|