vidply 1.0.2 → 1.0.4
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/README.md +6 -4
- package/dist/vidply.css +24 -0
- package/dist/vidply.esm.js +374 -396
- package/dist/vidply.esm.js.map +3 -3
- package/dist/vidply.esm.min.js +7 -6
- package/dist/vidply.esm.min.meta.json +11 -45
- package/dist/vidply.js +374 -396
- package/dist/vidply.js.map +3 -3
- package/dist/vidply.min.css +1 -1
- package/dist/vidply.min.js +7 -6
- package/dist/vidply.min.meta.json +11 -45
- package/package.json +57 -54
- package/src/controls/CaptionManager.js +216 -218
- package/src/controls/ControlBar.js +2026 -1870
- package/src/controls/KeyboardManager.js +50 -13
- package/src/core/Player.js +1017 -991
- package/src/features/PlaylistManager.js +611 -437
- package/src/styles/vidply.css +24 -0
package/dist/vidply.esm.js
CHANGED
|
@@ -1212,6 +1212,42 @@ var ControlBar = class {
|
|
|
1212
1212
|
document.addEventListener("keydown", handleEscape);
|
|
1213
1213
|
}, 100);
|
|
1214
1214
|
}
|
|
1215
|
+
// Helper method to add keyboard navigation to menus (arrow keys)
|
|
1216
|
+
attachMenuKeyboardNavigation(menu) {
|
|
1217
|
+
const menuItems = Array.from(menu.querySelectorAll(`.${this.player.options.classPrefix}-menu-item`));
|
|
1218
|
+
if (menuItems.length === 0) return;
|
|
1219
|
+
const handleKeyDown = (e) => {
|
|
1220
|
+
const currentIndex = menuItems.indexOf(document.activeElement);
|
|
1221
|
+
switch (e.key) {
|
|
1222
|
+
case "ArrowDown":
|
|
1223
|
+
e.preventDefault();
|
|
1224
|
+
const nextIndex = (currentIndex + 1) % menuItems.length;
|
|
1225
|
+
menuItems[nextIndex].focus();
|
|
1226
|
+
break;
|
|
1227
|
+
case "ArrowUp":
|
|
1228
|
+
e.preventDefault();
|
|
1229
|
+
const prevIndex = (currentIndex - 1 + menuItems.length) % menuItems.length;
|
|
1230
|
+
menuItems[prevIndex].focus();
|
|
1231
|
+
break;
|
|
1232
|
+
case "Home":
|
|
1233
|
+
e.preventDefault();
|
|
1234
|
+
menuItems[0].focus();
|
|
1235
|
+
break;
|
|
1236
|
+
case "End":
|
|
1237
|
+
e.preventDefault();
|
|
1238
|
+
menuItems[menuItems.length - 1].focus();
|
|
1239
|
+
break;
|
|
1240
|
+
case "Enter":
|
|
1241
|
+
case " ":
|
|
1242
|
+
e.preventDefault();
|
|
1243
|
+
if (document.activeElement && menuItems.includes(document.activeElement)) {
|
|
1244
|
+
document.activeElement.click();
|
|
1245
|
+
}
|
|
1246
|
+
break;
|
|
1247
|
+
}
|
|
1248
|
+
};
|
|
1249
|
+
menu.addEventListener("keydown", handleKeyDown);
|
|
1250
|
+
}
|
|
1215
1251
|
createElement() {
|
|
1216
1252
|
this.element = DOMUtils.createElement("div", {
|
|
1217
1253
|
className: `${this.player.options.classPrefix}-controls`,
|
|
@@ -1615,12 +1651,26 @@ var ControlBar = class {
|
|
|
1615
1651
|
}
|
|
1616
1652
|
createTimeDisplay() {
|
|
1617
1653
|
const container = DOMUtils.createElement("div", {
|
|
1618
|
-
className: `${this.player.options.classPrefix}-time
|
|
1654
|
+
className: `${this.player.options.classPrefix}-time`,
|
|
1655
|
+
attributes: {
|
|
1656
|
+
"role": "group",
|
|
1657
|
+
"aria-label": "Time display"
|
|
1658
|
+
}
|
|
1619
1659
|
});
|
|
1620
1660
|
this.controls.currentTimeDisplay = DOMUtils.createElement("span", {
|
|
1621
1661
|
className: `${this.player.options.classPrefix}-current-time`,
|
|
1622
|
-
|
|
1662
|
+
attributes: {
|
|
1663
|
+
"aria-label": "0 seconds"
|
|
1664
|
+
}
|
|
1665
|
+
});
|
|
1666
|
+
const currentTimeVisual = DOMUtils.createElement("span", {
|
|
1667
|
+
textContent: "00:00",
|
|
1668
|
+
attributes: {
|
|
1669
|
+
"aria-hidden": "true"
|
|
1670
|
+
}
|
|
1623
1671
|
});
|
|
1672
|
+
this.controls.currentTimeDisplay.appendChild(currentTimeVisual);
|
|
1673
|
+
this.controls.currentTimeVisual = currentTimeVisual;
|
|
1624
1674
|
const separator = DOMUtils.createElement("span", {
|
|
1625
1675
|
textContent: " / ",
|
|
1626
1676
|
attributes: {
|
|
@@ -1629,8 +1679,18 @@ var ControlBar = class {
|
|
|
1629
1679
|
});
|
|
1630
1680
|
this.controls.durationDisplay = DOMUtils.createElement("span", {
|
|
1631
1681
|
className: `${this.player.options.classPrefix}-duration`,
|
|
1632
|
-
|
|
1682
|
+
attributes: {
|
|
1683
|
+
"aria-label": "Duration: 0 seconds"
|
|
1684
|
+
}
|
|
1685
|
+
});
|
|
1686
|
+
const durationVisual = DOMUtils.createElement("span", {
|
|
1687
|
+
textContent: "00:00",
|
|
1688
|
+
attributes: {
|
|
1689
|
+
"aria-hidden": "true"
|
|
1690
|
+
}
|
|
1633
1691
|
});
|
|
1692
|
+
this.controls.durationDisplay.appendChild(durationVisual);
|
|
1693
|
+
this.controls.durationVisual = durationVisual;
|
|
1634
1694
|
container.appendChild(this.controls.currentTimeDisplay);
|
|
1635
1695
|
container.appendChild(separator);
|
|
1636
1696
|
container.appendChild(this.controls.durationDisplay);
|
|
@@ -1706,7 +1766,8 @@ var ControlBar = class {
|
|
|
1706
1766
|
className: `${this.player.options.classPrefix}-menu-item`,
|
|
1707
1767
|
attributes: {
|
|
1708
1768
|
"type": "button",
|
|
1709
|
-
"role": "menuitem"
|
|
1769
|
+
"role": "menuitem",
|
|
1770
|
+
"tabindex": "-1"
|
|
1710
1771
|
}
|
|
1711
1772
|
});
|
|
1712
1773
|
const timeLabel = DOMUtils.createElement("span", {
|
|
@@ -1726,6 +1787,13 @@ var ControlBar = class {
|
|
|
1726
1787
|
});
|
|
1727
1788
|
menu.appendChild(item);
|
|
1728
1789
|
}
|
|
1790
|
+
this.attachMenuKeyboardNavigation(menu);
|
|
1791
|
+
setTimeout(() => {
|
|
1792
|
+
const firstItem = menu.querySelector(`.${this.player.options.classPrefix}-menu-item`);
|
|
1793
|
+
if (firstItem) {
|
|
1794
|
+
firstItem.focus();
|
|
1795
|
+
}
|
|
1796
|
+
}, 0);
|
|
1729
1797
|
}
|
|
1730
1798
|
}
|
|
1731
1799
|
button.appendChild(menu);
|
|
@@ -1779,19 +1847,22 @@ var ControlBar = class {
|
|
|
1779
1847
|
});
|
|
1780
1848
|
menu.appendChild(noQualityItem);
|
|
1781
1849
|
} else {
|
|
1850
|
+
let activeItem = null;
|
|
1782
1851
|
if (isHLS) {
|
|
1783
1852
|
const autoItem = DOMUtils.createElement("button", {
|
|
1784
1853
|
className: `${this.player.options.classPrefix}-menu-item`,
|
|
1785
1854
|
textContent: i18n.t("player.auto"),
|
|
1786
1855
|
attributes: {
|
|
1787
1856
|
"type": "button",
|
|
1788
|
-
"role": "menuitem"
|
|
1857
|
+
"role": "menuitem",
|
|
1858
|
+
"tabindex": "-1"
|
|
1789
1859
|
}
|
|
1790
1860
|
});
|
|
1791
1861
|
const isAuto = this.player.renderer.hls && this.player.renderer.hls.currentLevel === -1;
|
|
1792
1862
|
if (isAuto) {
|
|
1793
1863
|
autoItem.classList.add(`${this.player.options.classPrefix}-menu-item-active`);
|
|
1794
1864
|
autoItem.appendChild(createIconElement("check"));
|
|
1865
|
+
activeItem = autoItem;
|
|
1795
1866
|
}
|
|
1796
1867
|
autoItem.addEventListener("click", () => {
|
|
1797
1868
|
if (this.player.renderer.switchQuality) {
|
|
@@ -1807,12 +1878,14 @@ var ControlBar = class {
|
|
|
1807
1878
|
textContent: quality.name || `${quality.height}p`,
|
|
1808
1879
|
attributes: {
|
|
1809
1880
|
"type": "button",
|
|
1810
|
-
"role": "menuitem"
|
|
1881
|
+
"role": "menuitem",
|
|
1882
|
+
"tabindex": "-1"
|
|
1811
1883
|
}
|
|
1812
1884
|
});
|
|
1813
1885
|
if (quality.index === currentQuality) {
|
|
1814
1886
|
item.classList.add(`${this.player.options.classPrefix}-menu-item-active`);
|
|
1815
1887
|
item.appendChild(createIconElement("check"));
|
|
1888
|
+
activeItem = item;
|
|
1816
1889
|
}
|
|
1817
1890
|
item.addEventListener("click", () => {
|
|
1818
1891
|
if (this.player.renderer.switchQuality) {
|
|
@@ -1822,6 +1895,13 @@ var ControlBar = class {
|
|
|
1822
1895
|
});
|
|
1823
1896
|
menu.appendChild(item);
|
|
1824
1897
|
});
|
|
1898
|
+
this.attachMenuKeyboardNavigation(menu);
|
|
1899
|
+
setTimeout(() => {
|
|
1900
|
+
const focusTarget = activeItem || menu.querySelector(`.${this.player.options.classPrefix}-menu-item`);
|
|
1901
|
+
if (focusTarget) {
|
|
1902
|
+
focusTarget.focus();
|
|
1903
|
+
}
|
|
1904
|
+
}, 0);
|
|
1825
1905
|
}
|
|
1826
1906
|
} else {
|
|
1827
1907
|
const noSupportItem = DOMUtils.createElement("div", {
|
|
@@ -1915,6 +1995,12 @@ var ControlBar = class {
|
|
|
1915
1995
|
menu.style.minWidth = "220px";
|
|
1916
1996
|
button.appendChild(menu);
|
|
1917
1997
|
this.attachMenuCloseHandler(menu, button, true);
|
|
1998
|
+
setTimeout(() => {
|
|
1999
|
+
const firstSelect = menu.querySelector("select");
|
|
2000
|
+
if (firstSelect) {
|
|
2001
|
+
firstSelect.focus();
|
|
2002
|
+
}
|
|
2003
|
+
}, 0);
|
|
1918
2004
|
}
|
|
1919
2005
|
createStyleControl(label, property, options) {
|
|
1920
2006
|
const group = DOMUtils.createElement("div", {
|
|
@@ -2127,18 +2213,21 @@ var ControlBar = class {
|
|
|
2127
2213
|
}
|
|
2128
2214
|
});
|
|
2129
2215
|
const speeds = [0.25, 0.5, 0.75, 1, 1.25, 1.5, 1.75, 2];
|
|
2216
|
+
let activeItem = null;
|
|
2130
2217
|
speeds.forEach((speed) => {
|
|
2131
2218
|
const item = DOMUtils.createElement("button", {
|
|
2132
2219
|
className: `${this.player.options.classPrefix}-menu-item`,
|
|
2133
2220
|
textContent: this.formatSpeedLabel(speed),
|
|
2134
2221
|
attributes: {
|
|
2135
2222
|
"type": "button",
|
|
2136
|
-
"role": "menuitem"
|
|
2223
|
+
"role": "menuitem",
|
|
2224
|
+
"tabindex": "-1"
|
|
2137
2225
|
}
|
|
2138
2226
|
});
|
|
2139
2227
|
if (speed === this.player.state.playbackSpeed) {
|
|
2140
2228
|
item.classList.add(`${this.player.options.classPrefix}-menu-item-active`);
|
|
2141
2229
|
item.appendChild(createIconElement("check"));
|
|
2230
|
+
activeItem = item;
|
|
2142
2231
|
}
|
|
2143
2232
|
item.addEventListener("click", () => {
|
|
2144
2233
|
this.player.setPlaybackSpeed(speed);
|
|
@@ -2147,7 +2236,14 @@ var ControlBar = class {
|
|
|
2147
2236
|
menu.appendChild(item);
|
|
2148
2237
|
});
|
|
2149
2238
|
button.appendChild(menu);
|
|
2239
|
+
this.attachMenuKeyboardNavigation(menu);
|
|
2150
2240
|
this.attachMenuCloseHandler(menu, button);
|
|
2241
|
+
setTimeout(() => {
|
|
2242
|
+
const focusTarget = activeItem || menu.querySelector(`.${this.player.options.classPrefix}-menu-item`);
|
|
2243
|
+
if (focusTarget) {
|
|
2244
|
+
focusTarget.focus();
|
|
2245
|
+
}
|
|
2246
|
+
}, 0);
|
|
2151
2247
|
}
|
|
2152
2248
|
createCaptionsButton() {
|
|
2153
2249
|
const button = DOMUtils.createElement("button", {
|
|
@@ -2190,17 +2286,20 @@ var ControlBar = class {
|
|
|
2190
2286
|
this.attachMenuCloseHandler(menu, button);
|
|
2191
2287
|
return;
|
|
2192
2288
|
}
|
|
2289
|
+
let activeItem = null;
|
|
2193
2290
|
const offItem = DOMUtils.createElement("button", {
|
|
2194
2291
|
className: `${this.player.options.classPrefix}-menu-item`,
|
|
2195
2292
|
textContent: i18n.t("captions.off"),
|
|
2196
2293
|
attributes: {
|
|
2197
2294
|
"type": "button",
|
|
2198
|
-
"role": "menuitem"
|
|
2295
|
+
"role": "menuitem",
|
|
2296
|
+
"tabindex": "-1"
|
|
2199
2297
|
}
|
|
2200
2298
|
});
|
|
2201
2299
|
if (!this.player.state.captionsEnabled) {
|
|
2202
2300
|
offItem.classList.add(`${this.player.options.classPrefix}-menu-item-active`);
|
|
2203
2301
|
offItem.appendChild(createIconElement("check"));
|
|
2302
|
+
activeItem = offItem;
|
|
2204
2303
|
}
|
|
2205
2304
|
offItem.addEventListener("click", () => {
|
|
2206
2305
|
this.player.disableCaptions();
|
|
@@ -2216,12 +2315,14 @@ var ControlBar = class {
|
|
|
2216
2315
|
attributes: {
|
|
2217
2316
|
"type": "button",
|
|
2218
2317
|
"role": "menuitem",
|
|
2219
|
-
"lang": track.language
|
|
2318
|
+
"lang": track.language,
|
|
2319
|
+
"tabindex": "-1"
|
|
2220
2320
|
}
|
|
2221
2321
|
});
|
|
2222
2322
|
if (this.player.state.captionsEnabled && this.player.captionManager.currentTrack === this.player.captionManager.tracks[track.index]) {
|
|
2223
2323
|
item.classList.add(`${this.player.options.classPrefix}-menu-item-active`);
|
|
2224
2324
|
item.appendChild(createIconElement("check"));
|
|
2325
|
+
activeItem = item;
|
|
2225
2326
|
}
|
|
2226
2327
|
item.addEventListener("click", () => {
|
|
2227
2328
|
this.player.captionManager.switchTrack(track.index);
|
|
@@ -2231,7 +2332,14 @@ var ControlBar = class {
|
|
|
2231
2332
|
menu.appendChild(item);
|
|
2232
2333
|
});
|
|
2233
2334
|
button.appendChild(menu);
|
|
2335
|
+
this.attachMenuKeyboardNavigation(menu);
|
|
2234
2336
|
this.attachMenuCloseHandler(menu, button);
|
|
2337
|
+
setTimeout(() => {
|
|
2338
|
+
const focusTarget = activeItem || menu.querySelector(`.${this.player.options.classPrefix}-menu-item`);
|
|
2339
|
+
if (focusTarget) {
|
|
2340
|
+
focusTarget.focus();
|
|
2341
|
+
}
|
|
2342
|
+
}, 0);
|
|
2235
2343
|
}
|
|
2236
2344
|
updateCaptionsButton() {
|
|
2237
2345
|
if (!this.controls.captions) return;
|
|
@@ -2406,36 +2514,43 @@ var ControlBar = class {
|
|
|
2406
2514
|
const percent = this.player.state.currentTime / this.player.state.duration * 100;
|
|
2407
2515
|
this.controls.played.style.width = `${percent}%`;
|
|
2408
2516
|
this.controls.progress.setAttribute("aria-valuenow", String(Math.round(percent)));
|
|
2409
|
-
if (this.controls.
|
|
2410
|
-
|
|
2517
|
+
if (this.controls.currentTimeVisual) {
|
|
2518
|
+
const currentTime = this.player.state.currentTime;
|
|
2519
|
+
this.controls.currentTimeVisual.textContent = TimeUtils.formatTime(currentTime);
|
|
2520
|
+
this.controls.currentTimeDisplay.setAttribute("aria-label", TimeUtils.formatDuration(currentTime));
|
|
2411
2521
|
}
|
|
2412
2522
|
}
|
|
2413
2523
|
updateDuration() {
|
|
2414
|
-
if (this.controls.
|
|
2415
|
-
|
|
2524
|
+
if (this.controls.durationVisual) {
|
|
2525
|
+
const duration = this.player.state.duration;
|
|
2526
|
+
this.controls.durationVisual.textContent = TimeUtils.formatTime(duration);
|
|
2527
|
+
this.controls.durationDisplay.setAttribute("aria-label", "Duration: " + TimeUtils.formatDuration(duration));
|
|
2416
2528
|
}
|
|
2417
2529
|
}
|
|
2418
2530
|
updateVolumeDisplay() {
|
|
2419
|
-
if (!this.controls.volumeFill) return;
|
|
2420
2531
|
const percent = this.player.state.volume * 100;
|
|
2421
|
-
this.controls.volumeFill
|
|
2532
|
+
if (this.controls.volumeFill) {
|
|
2533
|
+
this.controls.volumeFill.style.height = `${percent}%`;
|
|
2534
|
+
}
|
|
2422
2535
|
if (this.controls.mute) {
|
|
2423
2536
|
const icon = this.controls.mute.querySelector(".vidply-icon");
|
|
2424
|
-
|
|
2425
|
-
|
|
2426
|
-
|
|
2427
|
-
|
|
2428
|
-
|
|
2429
|
-
|
|
2430
|
-
|
|
2431
|
-
|
|
2432
|
-
|
|
2537
|
+
if (icon) {
|
|
2538
|
+
let iconName;
|
|
2539
|
+
if (this.player.state.muted || this.player.state.volume === 0) {
|
|
2540
|
+
iconName = "volumeMuted";
|
|
2541
|
+
} else if (this.player.state.volume < 0.3) {
|
|
2542
|
+
iconName = "volumeLow";
|
|
2543
|
+
} else if (this.player.state.volume < 0.7) {
|
|
2544
|
+
iconName = "volumeMedium";
|
|
2545
|
+
} else {
|
|
2546
|
+
iconName = "volumeHigh";
|
|
2547
|
+
}
|
|
2548
|
+
icon.innerHTML = createIconElement(iconName).innerHTML;
|
|
2549
|
+
this.controls.mute.setAttribute(
|
|
2550
|
+
"aria-label",
|
|
2551
|
+
this.player.state.muted ? i18n.t("player.unmute") : i18n.t("player.mute")
|
|
2552
|
+
);
|
|
2433
2553
|
}
|
|
2434
|
-
icon.innerHTML = createIconElement(iconName).innerHTML;
|
|
2435
|
-
this.controls.mute.setAttribute(
|
|
2436
|
-
"aria-label",
|
|
2437
|
-
this.player.state.muted ? i18n.t("player.unmute") : i18n.t("player.mute")
|
|
2438
|
-
);
|
|
2439
2554
|
}
|
|
2440
2555
|
if (this.controls.volumeSlider) {
|
|
2441
2556
|
this.controls.volumeSlider.setAttribute("aria-valuenow", String(Math.round(percent)));
|
|
@@ -2620,7 +2735,6 @@ var CaptionManager = class {
|
|
|
2620
2735
|
this.updateCaptions();
|
|
2621
2736
|
};
|
|
2622
2737
|
selectedTrack.track.addEventListener("cuechange", this.cueChangeHandler);
|
|
2623
|
-
this.element.style.display = "block";
|
|
2624
2738
|
this.player.emit("captionsenabled", selectedTrack);
|
|
2625
2739
|
}
|
|
2626
2740
|
}
|
|
@@ -2763,6 +2877,9 @@ var KeyboardManager = class {
|
|
|
2763
2877
|
}
|
|
2764
2878
|
}
|
|
2765
2879
|
}
|
|
2880
|
+
if (!handled && this.player.options.debug) {
|
|
2881
|
+
console.log("[VidPly] Unhandled key:", e.key, "code:", e.code, "shiftKey:", e.shiftKey);
|
|
2882
|
+
}
|
|
2766
2883
|
}
|
|
2767
2884
|
executeAction(action, event) {
|
|
2768
2885
|
switch (action) {
|
|
@@ -2781,12 +2898,6 @@ var KeyboardManager = class {
|
|
|
2781
2898
|
case "seek-backward":
|
|
2782
2899
|
this.player.seekBackward();
|
|
2783
2900
|
return true;
|
|
2784
|
-
case "seek-forward-large":
|
|
2785
|
-
this.player.seekForward(this.player.options.seekIntervalLarge);
|
|
2786
|
-
return true;
|
|
2787
|
-
case "seek-backward-large":
|
|
2788
|
-
this.player.seekBackward(this.player.options.seekIntervalLarge);
|
|
2789
|
-
return true;
|
|
2790
2901
|
case "mute":
|
|
2791
2902
|
this.player.toggleMute();
|
|
2792
2903
|
return true;
|
|
@@ -2795,14 +2906,22 @@ var KeyboardManager = class {
|
|
|
2795
2906
|
return true;
|
|
2796
2907
|
case "captions":
|
|
2797
2908
|
if (this.player.captionManager && this.player.captionManager.tracks.length > 1) {
|
|
2798
|
-
const captionsButton =
|
|
2799
|
-
if (captionsButton
|
|
2909
|
+
const captionsButton = this.player.controlBar && this.player.controlBar.controls.captions;
|
|
2910
|
+
if (captionsButton) {
|
|
2800
2911
|
this.player.controlBar.showCaptionsMenu(captionsButton);
|
|
2912
|
+
} else {
|
|
2913
|
+
this.player.toggleCaptions();
|
|
2801
2914
|
}
|
|
2802
2915
|
} else {
|
|
2803
2916
|
this.player.toggleCaptions();
|
|
2804
2917
|
}
|
|
2805
2918
|
return true;
|
|
2919
|
+
case "caption-style-menu":
|
|
2920
|
+
if (this.player.controlBar && this.player.controlBar.controls.captionStyle) {
|
|
2921
|
+
this.player.controlBar.showCaptionStyleMenu(this.player.controlBar.controls.captionStyle);
|
|
2922
|
+
return true;
|
|
2923
|
+
}
|
|
2924
|
+
return false;
|
|
2806
2925
|
case "speed-up":
|
|
2807
2926
|
this.player.setPlaybackSpeed(
|
|
2808
2927
|
Math.min(2, this.player.state.playbackSpeed + 0.25)
|
|
@@ -2813,9 +2932,30 @@ var KeyboardManager = class {
|
|
|
2813
2932
|
Math.max(0.25, this.player.state.playbackSpeed - 0.25)
|
|
2814
2933
|
);
|
|
2815
2934
|
return true;
|
|
2816
|
-
case "
|
|
2817
|
-
this.player.
|
|
2818
|
-
|
|
2935
|
+
case "speed-menu":
|
|
2936
|
+
if (this.player.controlBar && this.player.controlBar.controls.speed) {
|
|
2937
|
+
this.player.controlBar.showSpeedMenu(this.player.controlBar.controls.speed);
|
|
2938
|
+
return true;
|
|
2939
|
+
}
|
|
2940
|
+
return false;
|
|
2941
|
+
case "quality-menu":
|
|
2942
|
+
if (this.player.controlBar && this.player.controlBar.controls.quality) {
|
|
2943
|
+
this.player.controlBar.showQualityMenu(this.player.controlBar.controls.quality);
|
|
2944
|
+
return true;
|
|
2945
|
+
}
|
|
2946
|
+
return false;
|
|
2947
|
+
case "chapters-menu":
|
|
2948
|
+
if (this.player.controlBar && this.player.controlBar.controls.chapters) {
|
|
2949
|
+
this.player.controlBar.showChaptersMenu(this.player.controlBar.controls.chapters);
|
|
2950
|
+
return true;
|
|
2951
|
+
}
|
|
2952
|
+
return false;
|
|
2953
|
+
case "transcript-toggle":
|
|
2954
|
+
if (this.player.transcriptManager) {
|
|
2955
|
+
this.player.transcriptManager.toggleTranscript();
|
|
2956
|
+
return true;
|
|
2957
|
+
}
|
|
2958
|
+
return false;
|
|
2819
2959
|
default:
|
|
2820
2960
|
return false;
|
|
2821
2961
|
}
|
|
@@ -2882,317 +3022,6 @@ var KeyboardManager = class {
|
|
|
2882
3022
|
}
|
|
2883
3023
|
};
|
|
2884
3024
|
|
|
2885
|
-
// src/controls/SettingsDialog.js
|
|
2886
|
-
var SettingsDialog = class {
|
|
2887
|
-
constructor(player) {
|
|
2888
|
-
this.player = player;
|
|
2889
|
-
this.element = null;
|
|
2890
|
-
this.isOpen = false;
|
|
2891
|
-
this.init();
|
|
2892
|
-
}
|
|
2893
|
-
init() {
|
|
2894
|
-
this.createElement();
|
|
2895
|
-
}
|
|
2896
|
-
createElement() {
|
|
2897
|
-
this.overlay = DOMUtils.createElement("div", {
|
|
2898
|
-
className: `${this.player.options.classPrefix}-settings-overlay`,
|
|
2899
|
-
attributes: {
|
|
2900
|
-
"role": "dialog",
|
|
2901
|
-
"aria-modal": "true",
|
|
2902
|
-
"aria-label": i18n.t("settings.title")
|
|
2903
|
-
}
|
|
2904
|
-
});
|
|
2905
|
-
this.overlay.style.display = "none";
|
|
2906
|
-
this.element = DOMUtils.createElement("div", {
|
|
2907
|
-
className: `${this.player.options.classPrefix}-settings-dialog`
|
|
2908
|
-
});
|
|
2909
|
-
const header = DOMUtils.createElement("div", {
|
|
2910
|
-
className: `${this.player.options.classPrefix}-settings-header`
|
|
2911
|
-
});
|
|
2912
|
-
const title = DOMUtils.createElement("h2", {
|
|
2913
|
-
textContent: i18n.t("settings.title"),
|
|
2914
|
-
attributes: {
|
|
2915
|
-
"id": `${this.player.options.classPrefix}-settings-title`
|
|
2916
|
-
}
|
|
2917
|
-
});
|
|
2918
|
-
const closeButton = DOMUtils.createElement("button", {
|
|
2919
|
-
className: `${this.player.options.classPrefix}-button ${this.player.options.classPrefix}-settings-close`,
|
|
2920
|
-
attributes: {
|
|
2921
|
-
"type": "button",
|
|
2922
|
-
"aria-label": i18n.t("settings.close")
|
|
2923
|
-
}
|
|
2924
|
-
});
|
|
2925
|
-
closeButton.appendChild(createIconElement("close"));
|
|
2926
|
-
closeButton.addEventListener("click", () => this.hide());
|
|
2927
|
-
header.appendChild(title);
|
|
2928
|
-
header.appendChild(closeButton);
|
|
2929
|
-
const content = DOMUtils.createElement("div", {
|
|
2930
|
-
className: `${this.player.options.classPrefix}-settings-content`
|
|
2931
|
-
});
|
|
2932
|
-
content.appendChild(this.createSpeedSettings());
|
|
2933
|
-
if (this.player.captionManager && this.player.captionManager.tracks.length > 0) {
|
|
2934
|
-
content.appendChild(this.createCaptionSettings());
|
|
2935
|
-
}
|
|
2936
|
-
const footer = DOMUtils.createElement("div", {
|
|
2937
|
-
className: `${this.player.options.classPrefix}-settings-footer`
|
|
2938
|
-
});
|
|
2939
|
-
const resetButton = DOMUtils.createElement("button", {
|
|
2940
|
-
className: `${this.player.options.classPrefix}-button`,
|
|
2941
|
-
textContent: i18n.t("settings.reset"),
|
|
2942
|
-
attributes: {
|
|
2943
|
-
"type": "button"
|
|
2944
|
-
}
|
|
2945
|
-
});
|
|
2946
|
-
resetButton.addEventListener("click", () => this.resetSettings());
|
|
2947
|
-
footer.appendChild(resetButton);
|
|
2948
|
-
this.element.appendChild(header);
|
|
2949
|
-
this.element.appendChild(content);
|
|
2950
|
-
this.element.appendChild(footer);
|
|
2951
|
-
this.overlay.appendChild(this.element);
|
|
2952
|
-
this.player.container.appendChild(this.overlay);
|
|
2953
|
-
this.overlay.addEventListener("click", (e) => {
|
|
2954
|
-
if (e.target === this.overlay) {
|
|
2955
|
-
this.hide();
|
|
2956
|
-
}
|
|
2957
|
-
});
|
|
2958
|
-
document.addEventListener("keydown", (e) => {
|
|
2959
|
-
if (e.key === "Escape" && this.isOpen) {
|
|
2960
|
-
this.hide();
|
|
2961
|
-
}
|
|
2962
|
-
});
|
|
2963
|
-
}
|
|
2964
|
-
formatSpeedLabel(speed) {
|
|
2965
|
-
if (speed === 1) {
|
|
2966
|
-
return i18n.t("speeds.normal");
|
|
2967
|
-
}
|
|
2968
|
-
const speedStr = speed.toLocaleString(i18n.getLanguage(), {
|
|
2969
|
-
minimumFractionDigits: 0,
|
|
2970
|
-
maximumFractionDigits: 2
|
|
2971
|
-
});
|
|
2972
|
-
return `${speedStr}\xD7`;
|
|
2973
|
-
}
|
|
2974
|
-
createSpeedSettings() {
|
|
2975
|
-
const section = DOMUtils.createElement("div", {
|
|
2976
|
-
className: `${this.player.options.classPrefix}-settings-section`
|
|
2977
|
-
});
|
|
2978
|
-
const label = DOMUtils.createElement("label", {
|
|
2979
|
-
textContent: i18n.t("settings.speed"),
|
|
2980
|
-
attributes: {
|
|
2981
|
-
"for": `${this.player.options.classPrefix}-speed-select`
|
|
2982
|
-
}
|
|
2983
|
-
});
|
|
2984
|
-
const select = DOMUtils.createElement("select", {
|
|
2985
|
-
className: `${this.player.options.classPrefix}-settings-select`,
|
|
2986
|
-
attributes: {
|
|
2987
|
-
"id": `${this.player.options.classPrefix}-speed-select`
|
|
2988
|
-
}
|
|
2989
|
-
});
|
|
2990
|
-
const speeds = [0.25, 0.5, 0.75, 1, 1.25, 1.5, 1.75, 2];
|
|
2991
|
-
speeds.forEach((speed) => {
|
|
2992
|
-
const option = DOMUtils.createElement("option", {
|
|
2993
|
-
textContent: this.formatSpeedLabel(speed),
|
|
2994
|
-
attributes: {
|
|
2995
|
-
"value": String(speed)
|
|
2996
|
-
}
|
|
2997
|
-
});
|
|
2998
|
-
if (speed === this.player.state.playbackSpeed) {
|
|
2999
|
-
option.selected = true;
|
|
3000
|
-
}
|
|
3001
|
-
select.appendChild(option);
|
|
3002
|
-
});
|
|
3003
|
-
select.addEventListener("change", (e) => {
|
|
3004
|
-
this.player.setPlaybackSpeed(parseFloat(e.target.value));
|
|
3005
|
-
});
|
|
3006
|
-
section.appendChild(label);
|
|
3007
|
-
section.appendChild(select);
|
|
3008
|
-
return section;
|
|
3009
|
-
}
|
|
3010
|
-
createCaptionSettings() {
|
|
3011
|
-
const section = DOMUtils.createElement("div", {
|
|
3012
|
-
className: `${this.player.options.classPrefix}-settings-section`
|
|
3013
|
-
});
|
|
3014
|
-
const heading = DOMUtils.createElement("h3", {
|
|
3015
|
-
textContent: i18n.t("settings.captions")
|
|
3016
|
-
});
|
|
3017
|
-
section.appendChild(heading);
|
|
3018
|
-
const trackLabel = DOMUtils.createElement("label", {
|
|
3019
|
-
textContent: i18n.t("captions.select"),
|
|
3020
|
-
attributes: {
|
|
3021
|
-
"for": `${this.player.options.classPrefix}-caption-track-select`
|
|
3022
|
-
}
|
|
3023
|
-
});
|
|
3024
|
-
const trackSelect = DOMUtils.createElement("select", {
|
|
3025
|
-
className: `${this.player.options.classPrefix}-settings-select`,
|
|
3026
|
-
attributes: {
|
|
3027
|
-
"id": `${this.player.options.classPrefix}-caption-track-select`
|
|
3028
|
-
}
|
|
3029
|
-
});
|
|
3030
|
-
const offOption = DOMUtils.createElement("option", {
|
|
3031
|
-
textContent: i18n.t("captions.off"),
|
|
3032
|
-
attributes: { "value": "-1" }
|
|
3033
|
-
});
|
|
3034
|
-
trackSelect.appendChild(offOption);
|
|
3035
|
-
const tracks = this.player.captionManager.getAvailableTracks();
|
|
3036
|
-
tracks.forEach((track) => {
|
|
3037
|
-
const option = DOMUtils.createElement("option", {
|
|
3038
|
-
textContent: track.label,
|
|
3039
|
-
attributes: { "value": String(track.index) }
|
|
3040
|
-
});
|
|
3041
|
-
trackSelect.appendChild(option);
|
|
3042
|
-
});
|
|
3043
|
-
trackSelect.addEventListener("change", (e) => {
|
|
3044
|
-
const index = parseInt(e.target.value);
|
|
3045
|
-
if (index === -1) {
|
|
3046
|
-
this.player.disableCaptions();
|
|
3047
|
-
} else {
|
|
3048
|
-
this.player.captionManager.switchTrack(index);
|
|
3049
|
-
}
|
|
3050
|
-
});
|
|
3051
|
-
section.appendChild(trackLabel);
|
|
3052
|
-
section.appendChild(trackSelect);
|
|
3053
|
-
section.appendChild(this.createCaptionStyleControl("fontSize", i18n.t("captions.fontSize"), [
|
|
3054
|
-
{ label: i18n.t("fontSizes.small"), value: "80%" },
|
|
3055
|
-
{ label: i18n.t("fontSizes.medium"), value: "100%" },
|
|
3056
|
-
{ label: i18n.t("fontSizes.large"), value: "120%" },
|
|
3057
|
-
{ label: i18n.t("fontSizes.xlarge"), value: "150%" }
|
|
3058
|
-
]));
|
|
3059
|
-
section.appendChild(this.createCaptionStyleControl("fontFamily", i18n.t("captions.fontFamily"), [
|
|
3060
|
-
{ label: i18n.t("fontFamilies.sansSerif"), value: "sans-serif" },
|
|
3061
|
-
{ label: i18n.t("fontFamilies.serif"), value: "serif" },
|
|
3062
|
-
{ label: i18n.t("fontFamilies.monospace"), value: "monospace" }
|
|
3063
|
-
]));
|
|
3064
|
-
section.appendChild(this.createColorControl("color", i18n.t("captions.color")));
|
|
3065
|
-
section.appendChild(this.createColorControl("backgroundColor", i18n.t("captions.backgroundColor")));
|
|
3066
|
-
section.appendChild(this.createRangeControl("opacity", i18n.t("captions.opacity"), 0, 1, 0.1));
|
|
3067
|
-
return section;
|
|
3068
|
-
}
|
|
3069
|
-
createCaptionStyleControl(property, label, options) {
|
|
3070
|
-
const wrapper = DOMUtils.createElement("div", {
|
|
3071
|
-
className: `${this.player.options.classPrefix}-settings-control`
|
|
3072
|
-
});
|
|
3073
|
-
const labelEl = DOMUtils.createElement("label", {
|
|
3074
|
-
textContent: label,
|
|
3075
|
-
attributes: {
|
|
3076
|
-
"for": `${this.player.options.classPrefix}-caption-${property}`
|
|
3077
|
-
}
|
|
3078
|
-
});
|
|
3079
|
-
const select = DOMUtils.createElement("select", {
|
|
3080
|
-
className: `${this.player.options.classPrefix}-settings-select`,
|
|
3081
|
-
attributes: {
|
|
3082
|
-
"id": `${this.player.options.classPrefix}-caption-${property}`
|
|
3083
|
-
}
|
|
3084
|
-
});
|
|
3085
|
-
options.forEach((opt) => {
|
|
3086
|
-
const option = DOMUtils.createElement("option", {
|
|
3087
|
-
textContent: opt.label,
|
|
3088
|
-
attributes: { "value": opt.value }
|
|
3089
|
-
});
|
|
3090
|
-
if (opt.value === this.player.options[`captions${property.charAt(0).toUpperCase() + property.slice(1)}`]) {
|
|
3091
|
-
option.selected = true;
|
|
3092
|
-
}
|
|
3093
|
-
select.appendChild(option);
|
|
3094
|
-
});
|
|
3095
|
-
select.addEventListener("change", (e) => {
|
|
3096
|
-
this.player.captionManager.setCaptionStyle(property, e.target.value);
|
|
3097
|
-
});
|
|
3098
|
-
wrapper.appendChild(labelEl);
|
|
3099
|
-
wrapper.appendChild(select);
|
|
3100
|
-
return wrapper;
|
|
3101
|
-
}
|
|
3102
|
-
createColorControl(property, label) {
|
|
3103
|
-
const wrapper = DOMUtils.createElement("div", {
|
|
3104
|
-
className: `${this.player.options.classPrefix}-settings-control`
|
|
3105
|
-
});
|
|
3106
|
-
const labelEl = DOMUtils.createElement("label", {
|
|
3107
|
-
textContent: label,
|
|
3108
|
-
attributes: {
|
|
3109
|
-
"for": `${this.player.options.classPrefix}-caption-${property}`
|
|
3110
|
-
}
|
|
3111
|
-
});
|
|
3112
|
-
const input = DOMUtils.createElement("input", {
|
|
3113
|
-
className: `${this.player.options.classPrefix}-settings-color`,
|
|
3114
|
-
attributes: {
|
|
3115
|
-
"type": "color",
|
|
3116
|
-
"id": `${this.player.options.classPrefix}-caption-${property}`,
|
|
3117
|
-
"value": this.player.options[`captions${property.charAt(0).toUpperCase() + property.slice(1)}`]
|
|
3118
|
-
}
|
|
3119
|
-
});
|
|
3120
|
-
input.addEventListener("change", (e) => {
|
|
3121
|
-
this.player.captionManager.setCaptionStyle(property, e.target.value);
|
|
3122
|
-
});
|
|
3123
|
-
wrapper.appendChild(labelEl);
|
|
3124
|
-
wrapper.appendChild(input);
|
|
3125
|
-
return wrapper;
|
|
3126
|
-
}
|
|
3127
|
-
createRangeControl(property, label, min, max, step) {
|
|
3128
|
-
const wrapper = DOMUtils.createElement("div", {
|
|
3129
|
-
className: `${this.player.options.classPrefix}-settings-control`
|
|
3130
|
-
});
|
|
3131
|
-
const labelEl = DOMUtils.createElement("label", {
|
|
3132
|
-
textContent: label,
|
|
3133
|
-
attributes: {
|
|
3134
|
-
"for": `${this.player.options.classPrefix}-caption-${property}`
|
|
3135
|
-
}
|
|
3136
|
-
});
|
|
3137
|
-
const input = DOMUtils.createElement("input", {
|
|
3138
|
-
className: `${this.player.options.classPrefix}-settings-range`,
|
|
3139
|
-
attributes: {
|
|
3140
|
-
"type": "range",
|
|
3141
|
-
"id": `${this.player.options.classPrefix}-caption-${property}`,
|
|
3142
|
-
"min": String(min),
|
|
3143
|
-
"max": String(max),
|
|
3144
|
-
"step": String(step),
|
|
3145
|
-
"value": String(this.player.options[`captions${property.charAt(0).toUpperCase() + property.slice(1)}`])
|
|
3146
|
-
}
|
|
3147
|
-
});
|
|
3148
|
-
const valueDisplay = DOMUtils.createElement("span", {
|
|
3149
|
-
className: `${this.player.options.classPrefix}-settings-value`,
|
|
3150
|
-
textContent: String(this.player.options[`captions${property.charAt(0).toUpperCase() + property.slice(1)}`])
|
|
3151
|
-
});
|
|
3152
|
-
input.addEventListener("input", (e) => {
|
|
3153
|
-
const value = parseFloat(e.target.value);
|
|
3154
|
-
valueDisplay.textContent = value.toFixed(1);
|
|
3155
|
-
this.player.captionManager.setCaptionStyle(property, value);
|
|
3156
|
-
});
|
|
3157
|
-
wrapper.appendChild(labelEl);
|
|
3158
|
-
wrapper.appendChild(input);
|
|
3159
|
-
wrapper.appendChild(valueDisplay);
|
|
3160
|
-
return wrapper;
|
|
3161
|
-
}
|
|
3162
|
-
resetSettings() {
|
|
3163
|
-
this.player.setPlaybackSpeed(1);
|
|
3164
|
-
if (this.player.captionManager) {
|
|
3165
|
-
this.player.captionManager.setCaptionStyle("fontSize", "100%");
|
|
3166
|
-
this.player.captionManager.setCaptionStyle("fontFamily", "sans-serif");
|
|
3167
|
-
this.player.captionManager.setCaptionStyle("color", "#FFFFFF");
|
|
3168
|
-
this.player.captionManager.setCaptionStyle("backgroundColor", "#000000");
|
|
3169
|
-
this.player.captionManager.setCaptionStyle("opacity", 0.8);
|
|
3170
|
-
}
|
|
3171
|
-
this.hide();
|
|
3172
|
-
setTimeout(() => this.show(), 100);
|
|
3173
|
-
}
|
|
3174
|
-
show() {
|
|
3175
|
-
this.overlay.style.display = "flex";
|
|
3176
|
-
this.isOpen = true;
|
|
3177
|
-
const closeButton = this.element.querySelector(`.${this.player.options.classPrefix}-settings-close`);
|
|
3178
|
-
if (closeButton) {
|
|
3179
|
-
closeButton.focus();
|
|
3180
|
-
}
|
|
3181
|
-
this.player.emit("settingsopen");
|
|
3182
|
-
}
|
|
3183
|
-
hide() {
|
|
3184
|
-
this.overlay.style.display = "none";
|
|
3185
|
-
this.isOpen = false;
|
|
3186
|
-
this.player.container.focus();
|
|
3187
|
-
this.player.emit("settingsclose");
|
|
3188
|
-
}
|
|
3189
|
-
destroy() {
|
|
3190
|
-
if (this.overlay && this.overlay.parentNode) {
|
|
3191
|
-
this.overlay.parentNode.removeChild(this.overlay);
|
|
3192
|
-
}
|
|
3193
|
-
}
|
|
3194
|
-
};
|
|
3195
|
-
|
|
3196
3025
|
// src/controls/TranscriptManager.js
|
|
3197
3026
|
var TranscriptManager = class {
|
|
3198
3027
|
constructor(player) {
|
|
@@ -4480,16 +4309,18 @@ var Player = class extends EventEmitter {
|
|
|
4480
4309
|
"play-pause": [" ", "p", "k"],
|
|
4481
4310
|
"volume-up": ["ArrowUp"],
|
|
4482
4311
|
"volume-down": ["ArrowDown"],
|
|
4483
|
-
"seek-forward": ["ArrowRight"
|
|
4484
|
-
"seek-backward": ["ArrowLeft"
|
|
4485
|
-
"seek-forward-large": ["l"],
|
|
4486
|
-
"seek-backward-large": ["j"],
|
|
4312
|
+
"seek-forward": ["ArrowRight"],
|
|
4313
|
+
"seek-backward": ["ArrowLeft"],
|
|
4487
4314
|
"mute": ["m"],
|
|
4488
4315
|
"fullscreen": ["f"],
|
|
4489
4316
|
"captions": ["c"],
|
|
4317
|
+
"caption-style-menu": ["a"],
|
|
4490
4318
|
"speed-up": [">"],
|
|
4491
4319
|
"speed-down": ["<"],
|
|
4492
|
-
"
|
|
4320
|
+
"speed-menu": ["s"],
|
|
4321
|
+
"quality-menu": ["q"],
|
|
4322
|
+
"chapters-menu": ["j"],
|
|
4323
|
+
"transcript-toggle": ["t"]
|
|
4493
4324
|
},
|
|
4494
4325
|
// Accessibility
|
|
4495
4326
|
ariaLabels: {},
|
|
@@ -4578,9 +4409,6 @@ var Player = class extends EventEmitter {
|
|
|
4578
4409
|
if (this.options.keyboard) {
|
|
4579
4410
|
this.keyboardManager = new KeyboardManager(this);
|
|
4580
4411
|
}
|
|
4581
|
-
if (this.options.settingsButton) {
|
|
4582
|
-
this.settingsDialog = new SettingsDialog(this);
|
|
4583
|
-
}
|
|
4584
4412
|
this.setupResponsiveHandlers();
|
|
4585
4413
|
if (this.options.startTime > 0) {
|
|
4586
4414
|
this.seek(this.options.startTime);
|
|
@@ -4758,6 +4586,17 @@ var Player = class extends EventEmitter {
|
|
|
4758
4586
|
this.captionManager.destroy();
|
|
4759
4587
|
this.captionManager = new CaptionManager(this);
|
|
4760
4588
|
}
|
|
4589
|
+
if (this.transcriptManager) {
|
|
4590
|
+
const wasVisible = this.transcriptManager.isVisible;
|
|
4591
|
+
this.transcriptManager.destroy();
|
|
4592
|
+
this.transcriptManager = new TranscriptManager(this);
|
|
4593
|
+
if (wasVisible) {
|
|
4594
|
+
this.transcriptManager.showTranscript();
|
|
4595
|
+
}
|
|
4596
|
+
}
|
|
4597
|
+
if (this.controlBar) {
|
|
4598
|
+
this.updateControlBar();
|
|
4599
|
+
}
|
|
4761
4600
|
this.emit("sourcechange", config);
|
|
4762
4601
|
this.log("Media loaded successfully");
|
|
4763
4602
|
} catch (error) {
|
|
@@ -4769,6 +4608,17 @@ var Player = class extends EventEmitter {
|
|
|
4769
4608
|
* @param {string} src - New source URL
|
|
4770
4609
|
* @returns {boolean}
|
|
4771
4610
|
*/
|
|
4611
|
+
/**
|
|
4612
|
+
* Update control bar to refresh button visibility based on available features
|
|
4613
|
+
*/
|
|
4614
|
+
updateControlBar() {
|
|
4615
|
+
if (!this.controlBar) return;
|
|
4616
|
+
const controlBar = this.controlBar;
|
|
4617
|
+
controlBar.element.innerHTML = "";
|
|
4618
|
+
controlBar.createControls();
|
|
4619
|
+
controlBar.attachEvents();
|
|
4620
|
+
controlBar.setupAutoHide();
|
|
4621
|
+
}
|
|
4772
4622
|
shouldChangeRenderer(src) {
|
|
4773
4623
|
if (!this.renderer) return true;
|
|
4774
4624
|
const isYouTube = src.includes("youtube.com") || src.includes("youtu.be");
|
|
@@ -4833,12 +4683,14 @@ var Player = class extends EventEmitter {
|
|
|
4833
4683
|
this.renderer.setMuted(true);
|
|
4834
4684
|
}
|
|
4835
4685
|
this.state.muted = true;
|
|
4686
|
+
this.emit("volumechange");
|
|
4836
4687
|
}
|
|
4837
4688
|
unmute() {
|
|
4838
4689
|
if (this.renderer) {
|
|
4839
4690
|
this.renderer.setMuted(false);
|
|
4840
4691
|
}
|
|
4841
4692
|
this.state.muted = false;
|
|
4693
|
+
this.emit("volumechange");
|
|
4842
4694
|
}
|
|
4843
4695
|
toggleMute() {
|
|
4844
4696
|
if (this.state.muted) {
|
|
@@ -5072,15 +4924,11 @@ var Player = class extends EventEmitter {
|
|
|
5072
4924
|
}
|
|
5073
4925
|
}
|
|
5074
4926
|
// Settings
|
|
4927
|
+
// Settings dialog removed - using individual control buttons instead
|
|
5075
4928
|
showSettings() {
|
|
5076
|
-
|
|
5077
|
-
this.settingsDialog.show();
|
|
5078
|
-
}
|
|
4929
|
+
console.warn("[VidPly] Settings dialog has been removed. Use individual control buttons (speed, captions, etc.)");
|
|
5079
4930
|
}
|
|
5080
4931
|
hideSettings() {
|
|
5081
|
-
if (this.settingsDialog) {
|
|
5082
|
-
this.settingsDialog.hide();
|
|
5083
|
-
}
|
|
5084
4932
|
}
|
|
5085
4933
|
// Utility methods
|
|
5086
4934
|
getCurrentTime() {
|
|
@@ -5177,9 +5025,6 @@ var Player = class extends EventEmitter {
|
|
|
5177
5025
|
if (this.keyboardManager) {
|
|
5178
5026
|
this.keyboardManager.destroy();
|
|
5179
5027
|
}
|
|
5180
|
-
if (this.settingsDialog) {
|
|
5181
|
-
this.settingsDialog.destroy();
|
|
5182
|
-
}
|
|
5183
5028
|
if (this.transcriptManager) {
|
|
5184
5029
|
this.transcriptManager.destroy();
|
|
5185
5030
|
}
|
|
@@ -5233,7 +5078,9 @@ var PlaylistManager = class {
|
|
|
5233
5078
|
this.trackInfoElement = null;
|
|
5234
5079
|
this.handleTrackEnd = this.handleTrackEnd.bind(this);
|
|
5235
5080
|
this.handleTrackError = this.handleTrackError.bind(this);
|
|
5081
|
+
this.player.playlistManager = this;
|
|
5236
5082
|
this.init();
|
|
5083
|
+
this.updatePlayerControls();
|
|
5237
5084
|
}
|
|
5238
5085
|
init() {
|
|
5239
5086
|
this.player.on("ended", this.handleTrackEnd);
|
|
@@ -5242,6 +5089,17 @@ var PlaylistManager = class {
|
|
|
5242
5089
|
this.createUI();
|
|
5243
5090
|
}
|
|
5244
5091
|
}
|
|
5092
|
+
/**
|
|
5093
|
+
* Update player controls to add playlist navigation buttons
|
|
5094
|
+
*/
|
|
5095
|
+
updatePlayerControls() {
|
|
5096
|
+
if (!this.player.controlBar) return;
|
|
5097
|
+
const controlBar = this.player.controlBar;
|
|
5098
|
+
controlBar.element.innerHTML = "";
|
|
5099
|
+
controlBar.createControls();
|
|
5100
|
+
controlBar.attachEvents();
|
|
5101
|
+
controlBar.setupAutoHide();
|
|
5102
|
+
}
|
|
5245
5103
|
/**
|
|
5246
5104
|
* Load a playlist
|
|
5247
5105
|
* @param {Array} tracks - Array of track objects
|
|
@@ -5262,8 +5120,9 @@ var PlaylistManager = class {
|
|
|
5262
5120
|
/**
|
|
5263
5121
|
* Play a specific track
|
|
5264
5122
|
* @param {number} index - Track index
|
|
5123
|
+
* @param {boolean} userInitiated - Whether this was triggered by user action (default: false)
|
|
5265
5124
|
*/
|
|
5266
|
-
play(index) {
|
|
5125
|
+
play(index, userInitiated = false) {
|
|
5267
5126
|
if (index < 0 || index >= this.tracks.length) {
|
|
5268
5127
|
console.warn("VidPly Playlist: Invalid track index", index);
|
|
5269
5128
|
return;
|
|
@@ -5283,6 +5142,9 @@ var PlaylistManager = class {
|
|
|
5283
5142
|
item: track,
|
|
5284
5143
|
total: this.tracks.length
|
|
5285
5144
|
});
|
|
5145
|
+
if (userInitiated && this.player.container) {
|
|
5146
|
+
this.player.container.focus();
|
|
5147
|
+
}
|
|
5286
5148
|
setTimeout(() => {
|
|
5287
5149
|
this.player.play();
|
|
5288
5150
|
}, 100);
|
|
@@ -5344,12 +5206,17 @@ var PlaylistManager = class {
|
|
|
5344
5206
|
return;
|
|
5345
5207
|
}
|
|
5346
5208
|
this.trackInfoElement = DOMUtils.createElement("div", {
|
|
5347
|
-
className: "vidply-track-info"
|
|
5209
|
+
className: "vidply-track-info",
|
|
5210
|
+
role: "status",
|
|
5211
|
+
"aria-live": "polite",
|
|
5212
|
+
"aria-atomic": "true"
|
|
5348
5213
|
});
|
|
5349
5214
|
this.trackInfoElement.style.display = "none";
|
|
5350
5215
|
this.container.appendChild(this.trackInfoElement);
|
|
5351
5216
|
this.playlistPanel = DOMUtils.createElement("div", {
|
|
5352
|
-
className: "vidply-playlist-panel"
|
|
5217
|
+
className: "vidply-playlist-panel",
|
|
5218
|
+
role: "region",
|
|
5219
|
+
"aria-label": "Media playlist"
|
|
5353
5220
|
});
|
|
5354
5221
|
this.playlistPanel.style.display = "none";
|
|
5355
5222
|
this.container.appendChild(this.playlistPanel);
|
|
@@ -5361,10 +5228,14 @@ var PlaylistManager = class {
|
|
|
5361
5228
|
if (!this.trackInfoElement) return;
|
|
5362
5229
|
const trackNumber = this.currentIndex + 1;
|
|
5363
5230
|
const totalTracks = this.tracks.length;
|
|
5231
|
+
const trackTitle = track.title || "Untitled";
|
|
5232
|
+
const trackArtist = track.artist || "";
|
|
5233
|
+
const announcement = `Now playing: Track ${trackNumber} of ${totalTracks}. ${trackTitle}${trackArtist ? " by " + trackArtist : ""}`;
|
|
5364
5234
|
this.trackInfoElement.innerHTML = `
|
|
5365
|
-
<
|
|
5366
|
-
<div class="vidply-track-
|
|
5367
|
-
|
|
5235
|
+
<span class="vidply-sr-only">${DOMUtils.escapeHTML(announcement)}</span>
|
|
5236
|
+
<div class="vidply-track-number" aria-hidden="true">Track ${trackNumber} of ${totalTracks}</div>
|
|
5237
|
+
<div class="vidply-track-title" aria-hidden="true">${DOMUtils.escapeHTML(trackTitle)}</div>
|
|
5238
|
+
${trackArtist ? `<div class="vidply-track-artist" aria-hidden="true">${DOMUtils.escapeHTML(trackArtist)}</div>` : ""}
|
|
5368
5239
|
`;
|
|
5369
5240
|
this.trackInfoElement.style.display = "block";
|
|
5370
5241
|
}
|
|
@@ -5374,14 +5245,29 @@ var PlaylistManager = class {
|
|
|
5374
5245
|
renderPlaylist() {
|
|
5375
5246
|
if (!this.playlistPanel) return;
|
|
5376
5247
|
this.playlistPanel.innerHTML = "";
|
|
5377
|
-
const header = DOMUtils.createElement("
|
|
5378
|
-
className: "vidply-playlist-header"
|
|
5248
|
+
const header = DOMUtils.createElement("h2", {
|
|
5249
|
+
className: "vidply-playlist-header",
|
|
5250
|
+
id: "vidply-playlist-heading"
|
|
5379
5251
|
});
|
|
5380
5252
|
header.textContent = `Playlist (${this.tracks.length})`;
|
|
5381
5253
|
this.playlistPanel.appendChild(header);
|
|
5382
|
-
const
|
|
5383
|
-
className: "vidply-
|
|
5384
|
-
|
|
5254
|
+
const instructions = DOMUtils.createElement("div", {
|
|
5255
|
+
className: "vidply-sr-only",
|
|
5256
|
+
"aria-hidden": "false"
|
|
5257
|
+
});
|
|
5258
|
+
instructions.textContent = "Use arrow keys to navigate between tracks. Press Enter or Space to play a track. Press Home or End to jump to first or last track.";
|
|
5259
|
+
this.playlistPanel.appendChild(instructions);
|
|
5260
|
+
const list = DOMUtils.createElement("ul", {
|
|
5261
|
+
className: "vidply-playlist-list",
|
|
5262
|
+
"aria-labelledby": "vidply-playlist-heading",
|
|
5263
|
+
"aria-describedby": "vidply-playlist-instructions"
|
|
5264
|
+
});
|
|
5265
|
+
const listDescription = DOMUtils.createElement("div", {
|
|
5266
|
+
className: "vidply-sr-only",
|
|
5267
|
+
id: "vidply-playlist-instructions"
|
|
5268
|
+
});
|
|
5269
|
+
listDescription.textContent = `Playlist with ${this.tracks.length} ${this.tracks.length === 1 ? "track" : "tracks"}`;
|
|
5270
|
+
this.playlistPanel.appendChild(listDescription);
|
|
5385
5271
|
this.tracks.forEach((track, index) => {
|
|
5386
5272
|
const item = this.createPlaylistItem(track, index);
|
|
5387
5273
|
list.appendChild(item);
|
|
@@ -5393,20 +5279,39 @@ var PlaylistManager = class {
|
|
|
5393
5279
|
* Create playlist item element
|
|
5394
5280
|
*/
|
|
5395
5281
|
createPlaylistItem(track, index) {
|
|
5396
|
-
const
|
|
5282
|
+
const trackPosition = `Track ${index + 1} of ${this.tracks.length}`;
|
|
5283
|
+
const trackTitle = track.title || `Track ${index + 1}`;
|
|
5284
|
+
const trackArtist = track.artist ? ` by ${track.artist}` : "";
|
|
5285
|
+
const isActive = index === this.currentIndex;
|
|
5286
|
+
const statusText = isActive ? "Currently playing" : "Not playing";
|
|
5287
|
+
const actionText = isActive ? "Press Enter to restart" : "Press Enter to play";
|
|
5288
|
+
const item = DOMUtils.createElement("li", {
|
|
5397
5289
|
className: "vidply-playlist-item",
|
|
5398
|
-
|
|
5399
|
-
|
|
5400
|
-
"aria-label":
|
|
5401
|
-
|
|
5402
|
-
|
|
5290
|
+
tabIndex: index === 0 ? 0 : -1,
|
|
5291
|
+
// Only first item is in tab order initially
|
|
5292
|
+
"aria-label": `${trackPosition}. ${trackTitle}${trackArtist}. ${statusText}. ${actionText}.`,
|
|
5293
|
+
"aria-posinset": index + 1,
|
|
5294
|
+
"aria-setsize": this.tracks.length,
|
|
5295
|
+
"data-playlist-index": index
|
|
5296
|
+
});
|
|
5297
|
+
if (isActive) {
|
|
5403
5298
|
item.classList.add("vidply-playlist-item-active");
|
|
5299
|
+
item.setAttribute("aria-current", "true");
|
|
5300
|
+
item.setAttribute("tabIndex", "0");
|
|
5404
5301
|
}
|
|
5302
|
+
const positionInfo = DOMUtils.createElement("span", {
|
|
5303
|
+
className: "vidply-sr-only"
|
|
5304
|
+
});
|
|
5305
|
+
positionInfo.textContent = `${trackPosition}: `;
|
|
5306
|
+
item.appendChild(positionInfo);
|
|
5405
5307
|
const thumbnail = DOMUtils.createElement("div", {
|
|
5406
|
-
className: "vidply-playlist-thumbnail"
|
|
5308
|
+
className: "vidply-playlist-thumbnail",
|
|
5309
|
+
"aria-hidden": "true"
|
|
5407
5310
|
});
|
|
5408
5311
|
if (track.poster) {
|
|
5409
5312
|
thumbnail.style.backgroundImage = `url(${track.poster})`;
|
|
5313
|
+
thumbnail.setAttribute("role", "img");
|
|
5314
|
+
thumbnail.setAttribute("aria-label", `${trackTitle} thumbnail`);
|
|
5410
5315
|
} else {
|
|
5411
5316
|
const icon = createIconElement("music");
|
|
5412
5317
|
icon.classList.add("vidply-playlist-thumbnail-icon");
|
|
@@ -5414,12 +5319,13 @@ var PlaylistManager = class {
|
|
|
5414
5319
|
}
|
|
5415
5320
|
item.appendChild(thumbnail);
|
|
5416
5321
|
const info = DOMUtils.createElement("div", {
|
|
5417
|
-
className: "vidply-playlist-item-info"
|
|
5322
|
+
className: "vidply-playlist-item-info",
|
|
5323
|
+
"aria-hidden": "true"
|
|
5418
5324
|
});
|
|
5419
5325
|
const title = DOMUtils.createElement("div", {
|
|
5420
5326
|
className: "vidply-playlist-item-title"
|
|
5421
5327
|
});
|
|
5422
|
-
title.textContent =
|
|
5328
|
+
title.textContent = trackTitle;
|
|
5423
5329
|
info.appendChild(title);
|
|
5424
5330
|
if (track.artist) {
|
|
5425
5331
|
const artist = DOMUtils.createElement("div", {
|
|
@@ -5429,20 +5335,64 @@ var PlaylistManager = class {
|
|
|
5429
5335
|
info.appendChild(artist);
|
|
5430
5336
|
}
|
|
5431
5337
|
item.appendChild(info);
|
|
5338
|
+
if (isActive) {
|
|
5339
|
+
const statusIndicator = DOMUtils.createElement("span", {
|
|
5340
|
+
className: "vidply-sr-only"
|
|
5341
|
+
});
|
|
5342
|
+
statusIndicator.textContent = " (Currently playing)";
|
|
5343
|
+
item.appendChild(statusIndicator);
|
|
5344
|
+
}
|
|
5432
5345
|
const playIcon = createIconElement("play");
|
|
5433
5346
|
playIcon.classList.add("vidply-playlist-item-icon");
|
|
5347
|
+
playIcon.setAttribute("aria-hidden", "true");
|
|
5434
5348
|
item.appendChild(playIcon);
|
|
5435
5349
|
item.addEventListener("click", () => {
|
|
5436
|
-
this.play(index);
|
|
5350
|
+
this.play(index, true);
|
|
5437
5351
|
});
|
|
5438
5352
|
item.addEventListener("keydown", (e) => {
|
|
5439
|
-
|
|
5440
|
-
e.preventDefault();
|
|
5441
|
-
this.play(index);
|
|
5442
|
-
}
|
|
5353
|
+
this.handlePlaylistItemKeydown(e, index);
|
|
5443
5354
|
});
|
|
5444
5355
|
return item;
|
|
5445
5356
|
}
|
|
5357
|
+
/**
|
|
5358
|
+
* Handle keyboard navigation in playlist items
|
|
5359
|
+
*/
|
|
5360
|
+
handlePlaylistItemKeydown(e, index) {
|
|
5361
|
+
const items = Array.from(this.playlistPanel.querySelectorAll(".vidply-playlist-item"));
|
|
5362
|
+
let newIndex = -1;
|
|
5363
|
+
switch (e.key) {
|
|
5364
|
+
case "Enter":
|
|
5365
|
+
case " ":
|
|
5366
|
+
e.preventDefault();
|
|
5367
|
+
this.play(index, true);
|
|
5368
|
+
break;
|
|
5369
|
+
case "ArrowDown":
|
|
5370
|
+
e.preventDefault();
|
|
5371
|
+
if (index < items.length - 1) {
|
|
5372
|
+
newIndex = index + 1;
|
|
5373
|
+
}
|
|
5374
|
+
break;
|
|
5375
|
+
case "ArrowUp":
|
|
5376
|
+
e.preventDefault();
|
|
5377
|
+
if (index > 0) {
|
|
5378
|
+
newIndex = index - 1;
|
|
5379
|
+
}
|
|
5380
|
+
break;
|
|
5381
|
+
case "Home":
|
|
5382
|
+
e.preventDefault();
|
|
5383
|
+
newIndex = 0;
|
|
5384
|
+
break;
|
|
5385
|
+
case "End":
|
|
5386
|
+
e.preventDefault();
|
|
5387
|
+
newIndex = items.length - 1;
|
|
5388
|
+
break;
|
|
5389
|
+
}
|
|
5390
|
+
if (newIndex !== -1 && newIndex !== index) {
|
|
5391
|
+
items[index].setAttribute("tabIndex", "-1");
|
|
5392
|
+
items[newIndex].setAttribute("tabIndex", "0");
|
|
5393
|
+
items[newIndex].focus();
|
|
5394
|
+
}
|
|
5395
|
+
}
|
|
5446
5396
|
/**
|
|
5447
5397
|
* Update playlist UI (highlight current track)
|
|
5448
5398
|
*/
|
|
@@ -5450,11 +5400,25 @@ var PlaylistManager = class {
|
|
|
5450
5400
|
if (!this.playlistPanel) return;
|
|
5451
5401
|
const items = this.playlistPanel.querySelectorAll(".vidply-playlist-item");
|
|
5452
5402
|
items.forEach((item, index) => {
|
|
5403
|
+
const track = this.tracks[index];
|
|
5404
|
+
const trackPosition = `Track ${index + 1} of ${this.tracks.length}`;
|
|
5405
|
+
const trackTitle = track.title || `Track ${index + 1}`;
|
|
5406
|
+
const trackArtist = track.artist ? ` by ${track.artist}` : "";
|
|
5453
5407
|
if (index === this.currentIndex) {
|
|
5454
5408
|
item.classList.add("vidply-playlist-item-active");
|
|
5409
|
+
item.setAttribute("aria-current", "true");
|
|
5410
|
+
item.setAttribute("tabIndex", "0");
|
|
5411
|
+
const statusText = "Currently playing";
|
|
5412
|
+
const actionText = "Press Enter to restart";
|
|
5413
|
+
item.setAttribute("aria-label", `${trackPosition}. ${trackTitle}${trackArtist}. ${statusText}. ${actionText}.`);
|
|
5455
5414
|
item.scrollIntoView({ behavior: "smooth", block: "nearest" });
|
|
5456
5415
|
} else {
|
|
5457
5416
|
item.classList.remove("vidply-playlist-item-active");
|
|
5417
|
+
item.removeAttribute("aria-current");
|
|
5418
|
+
item.setAttribute("tabIndex", "-1");
|
|
5419
|
+
const statusText = "Not playing";
|
|
5420
|
+
const actionText = "Press Enter to play";
|
|
5421
|
+
item.setAttribute("aria-label", `${trackPosition}. ${trackTitle}${trackArtist}. ${statusText}. ${actionText}.`);
|
|
5458
5422
|
}
|
|
5459
5423
|
});
|
|
5460
5424
|
}
|
|
@@ -5472,10 +5436,24 @@ var PlaylistManager = class {
|
|
|
5472
5436
|
currentIndex: this.currentIndex,
|
|
5473
5437
|
totalTracks: this.tracks.length,
|
|
5474
5438
|
currentTrack: this.getCurrentTrack(),
|
|
5475
|
-
hasNext: this.
|
|
5476
|
-
hasPrevious: this.
|
|
5439
|
+
hasNext: this.hasNext(),
|
|
5440
|
+
hasPrevious: this.hasPrevious()
|
|
5477
5441
|
};
|
|
5478
5442
|
}
|
|
5443
|
+
/**
|
|
5444
|
+
* Check if there is a next track
|
|
5445
|
+
*/
|
|
5446
|
+
hasNext() {
|
|
5447
|
+
if (this.options.loop) return true;
|
|
5448
|
+
return this.currentIndex < this.tracks.length - 1;
|
|
5449
|
+
}
|
|
5450
|
+
/**
|
|
5451
|
+
* Check if there is a previous track
|
|
5452
|
+
*/
|
|
5453
|
+
hasPrevious() {
|
|
5454
|
+
if (this.options.loop) return true;
|
|
5455
|
+
return this.currentIndex > 0;
|
|
5456
|
+
}
|
|
5479
5457
|
/**
|
|
5480
5458
|
* Add track to playlist
|
|
5481
5459
|
*/
|