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.js
CHANGED
|
@@ -1232,6 +1232,42 @@ var VidPly = (() => {
|
|
|
1232
1232
|
document.addEventListener("keydown", handleEscape);
|
|
1233
1233
|
}, 100);
|
|
1234
1234
|
}
|
|
1235
|
+
// Helper method to add keyboard navigation to menus (arrow keys)
|
|
1236
|
+
attachMenuKeyboardNavigation(menu) {
|
|
1237
|
+
const menuItems = Array.from(menu.querySelectorAll(`.${this.player.options.classPrefix}-menu-item`));
|
|
1238
|
+
if (menuItems.length === 0) return;
|
|
1239
|
+
const handleKeyDown = (e) => {
|
|
1240
|
+
const currentIndex = menuItems.indexOf(document.activeElement);
|
|
1241
|
+
switch (e.key) {
|
|
1242
|
+
case "ArrowDown":
|
|
1243
|
+
e.preventDefault();
|
|
1244
|
+
const nextIndex = (currentIndex + 1) % menuItems.length;
|
|
1245
|
+
menuItems[nextIndex].focus();
|
|
1246
|
+
break;
|
|
1247
|
+
case "ArrowUp":
|
|
1248
|
+
e.preventDefault();
|
|
1249
|
+
const prevIndex = (currentIndex - 1 + menuItems.length) % menuItems.length;
|
|
1250
|
+
menuItems[prevIndex].focus();
|
|
1251
|
+
break;
|
|
1252
|
+
case "Home":
|
|
1253
|
+
e.preventDefault();
|
|
1254
|
+
menuItems[0].focus();
|
|
1255
|
+
break;
|
|
1256
|
+
case "End":
|
|
1257
|
+
e.preventDefault();
|
|
1258
|
+
menuItems[menuItems.length - 1].focus();
|
|
1259
|
+
break;
|
|
1260
|
+
case "Enter":
|
|
1261
|
+
case " ":
|
|
1262
|
+
e.preventDefault();
|
|
1263
|
+
if (document.activeElement && menuItems.includes(document.activeElement)) {
|
|
1264
|
+
document.activeElement.click();
|
|
1265
|
+
}
|
|
1266
|
+
break;
|
|
1267
|
+
}
|
|
1268
|
+
};
|
|
1269
|
+
menu.addEventListener("keydown", handleKeyDown);
|
|
1270
|
+
}
|
|
1235
1271
|
createElement() {
|
|
1236
1272
|
this.element = DOMUtils.createElement("div", {
|
|
1237
1273
|
className: `${this.player.options.classPrefix}-controls`,
|
|
@@ -1635,12 +1671,26 @@ var VidPly = (() => {
|
|
|
1635
1671
|
}
|
|
1636
1672
|
createTimeDisplay() {
|
|
1637
1673
|
const container = DOMUtils.createElement("div", {
|
|
1638
|
-
className: `${this.player.options.classPrefix}-time
|
|
1674
|
+
className: `${this.player.options.classPrefix}-time`,
|
|
1675
|
+
attributes: {
|
|
1676
|
+
"role": "group",
|
|
1677
|
+
"aria-label": "Time display"
|
|
1678
|
+
}
|
|
1639
1679
|
});
|
|
1640
1680
|
this.controls.currentTimeDisplay = DOMUtils.createElement("span", {
|
|
1641
1681
|
className: `${this.player.options.classPrefix}-current-time`,
|
|
1642
|
-
|
|
1682
|
+
attributes: {
|
|
1683
|
+
"aria-label": "0 seconds"
|
|
1684
|
+
}
|
|
1685
|
+
});
|
|
1686
|
+
const currentTimeVisual = DOMUtils.createElement("span", {
|
|
1687
|
+
textContent: "00:00",
|
|
1688
|
+
attributes: {
|
|
1689
|
+
"aria-hidden": "true"
|
|
1690
|
+
}
|
|
1643
1691
|
});
|
|
1692
|
+
this.controls.currentTimeDisplay.appendChild(currentTimeVisual);
|
|
1693
|
+
this.controls.currentTimeVisual = currentTimeVisual;
|
|
1644
1694
|
const separator = DOMUtils.createElement("span", {
|
|
1645
1695
|
textContent: " / ",
|
|
1646
1696
|
attributes: {
|
|
@@ -1649,8 +1699,18 @@ var VidPly = (() => {
|
|
|
1649
1699
|
});
|
|
1650
1700
|
this.controls.durationDisplay = DOMUtils.createElement("span", {
|
|
1651
1701
|
className: `${this.player.options.classPrefix}-duration`,
|
|
1652
|
-
|
|
1702
|
+
attributes: {
|
|
1703
|
+
"aria-label": "Duration: 0 seconds"
|
|
1704
|
+
}
|
|
1705
|
+
});
|
|
1706
|
+
const durationVisual = DOMUtils.createElement("span", {
|
|
1707
|
+
textContent: "00:00",
|
|
1708
|
+
attributes: {
|
|
1709
|
+
"aria-hidden": "true"
|
|
1710
|
+
}
|
|
1653
1711
|
});
|
|
1712
|
+
this.controls.durationDisplay.appendChild(durationVisual);
|
|
1713
|
+
this.controls.durationVisual = durationVisual;
|
|
1654
1714
|
container.appendChild(this.controls.currentTimeDisplay);
|
|
1655
1715
|
container.appendChild(separator);
|
|
1656
1716
|
container.appendChild(this.controls.durationDisplay);
|
|
@@ -1726,7 +1786,8 @@ var VidPly = (() => {
|
|
|
1726
1786
|
className: `${this.player.options.classPrefix}-menu-item`,
|
|
1727
1787
|
attributes: {
|
|
1728
1788
|
"type": "button",
|
|
1729
|
-
"role": "menuitem"
|
|
1789
|
+
"role": "menuitem",
|
|
1790
|
+
"tabindex": "-1"
|
|
1730
1791
|
}
|
|
1731
1792
|
});
|
|
1732
1793
|
const timeLabel = DOMUtils.createElement("span", {
|
|
@@ -1746,6 +1807,13 @@ var VidPly = (() => {
|
|
|
1746
1807
|
});
|
|
1747
1808
|
menu.appendChild(item);
|
|
1748
1809
|
}
|
|
1810
|
+
this.attachMenuKeyboardNavigation(menu);
|
|
1811
|
+
setTimeout(() => {
|
|
1812
|
+
const firstItem = menu.querySelector(`.${this.player.options.classPrefix}-menu-item`);
|
|
1813
|
+
if (firstItem) {
|
|
1814
|
+
firstItem.focus();
|
|
1815
|
+
}
|
|
1816
|
+
}, 0);
|
|
1749
1817
|
}
|
|
1750
1818
|
}
|
|
1751
1819
|
button.appendChild(menu);
|
|
@@ -1799,19 +1867,22 @@ var VidPly = (() => {
|
|
|
1799
1867
|
});
|
|
1800
1868
|
menu.appendChild(noQualityItem);
|
|
1801
1869
|
} else {
|
|
1870
|
+
let activeItem = null;
|
|
1802
1871
|
if (isHLS) {
|
|
1803
1872
|
const autoItem = DOMUtils.createElement("button", {
|
|
1804
1873
|
className: `${this.player.options.classPrefix}-menu-item`,
|
|
1805
1874
|
textContent: i18n.t("player.auto"),
|
|
1806
1875
|
attributes: {
|
|
1807
1876
|
"type": "button",
|
|
1808
|
-
"role": "menuitem"
|
|
1877
|
+
"role": "menuitem",
|
|
1878
|
+
"tabindex": "-1"
|
|
1809
1879
|
}
|
|
1810
1880
|
});
|
|
1811
1881
|
const isAuto = this.player.renderer.hls && this.player.renderer.hls.currentLevel === -1;
|
|
1812
1882
|
if (isAuto) {
|
|
1813
1883
|
autoItem.classList.add(`${this.player.options.classPrefix}-menu-item-active`);
|
|
1814
1884
|
autoItem.appendChild(createIconElement("check"));
|
|
1885
|
+
activeItem = autoItem;
|
|
1815
1886
|
}
|
|
1816
1887
|
autoItem.addEventListener("click", () => {
|
|
1817
1888
|
if (this.player.renderer.switchQuality) {
|
|
@@ -1827,12 +1898,14 @@ var VidPly = (() => {
|
|
|
1827
1898
|
textContent: quality.name || `${quality.height}p`,
|
|
1828
1899
|
attributes: {
|
|
1829
1900
|
"type": "button",
|
|
1830
|
-
"role": "menuitem"
|
|
1901
|
+
"role": "menuitem",
|
|
1902
|
+
"tabindex": "-1"
|
|
1831
1903
|
}
|
|
1832
1904
|
});
|
|
1833
1905
|
if (quality.index === currentQuality) {
|
|
1834
1906
|
item.classList.add(`${this.player.options.classPrefix}-menu-item-active`);
|
|
1835
1907
|
item.appendChild(createIconElement("check"));
|
|
1908
|
+
activeItem = item;
|
|
1836
1909
|
}
|
|
1837
1910
|
item.addEventListener("click", () => {
|
|
1838
1911
|
if (this.player.renderer.switchQuality) {
|
|
@@ -1842,6 +1915,13 @@ var VidPly = (() => {
|
|
|
1842
1915
|
});
|
|
1843
1916
|
menu.appendChild(item);
|
|
1844
1917
|
});
|
|
1918
|
+
this.attachMenuKeyboardNavigation(menu);
|
|
1919
|
+
setTimeout(() => {
|
|
1920
|
+
const focusTarget = activeItem || menu.querySelector(`.${this.player.options.classPrefix}-menu-item`);
|
|
1921
|
+
if (focusTarget) {
|
|
1922
|
+
focusTarget.focus();
|
|
1923
|
+
}
|
|
1924
|
+
}, 0);
|
|
1845
1925
|
}
|
|
1846
1926
|
} else {
|
|
1847
1927
|
const noSupportItem = DOMUtils.createElement("div", {
|
|
@@ -1935,6 +2015,12 @@ var VidPly = (() => {
|
|
|
1935
2015
|
menu.style.minWidth = "220px";
|
|
1936
2016
|
button.appendChild(menu);
|
|
1937
2017
|
this.attachMenuCloseHandler(menu, button, true);
|
|
2018
|
+
setTimeout(() => {
|
|
2019
|
+
const firstSelect = menu.querySelector("select");
|
|
2020
|
+
if (firstSelect) {
|
|
2021
|
+
firstSelect.focus();
|
|
2022
|
+
}
|
|
2023
|
+
}, 0);
|
|
1938
2024
|
}
|
|
1939
2025
|
createStyleControl(label, property, options) {
|
|
1940
2026
|
const group = DOMUtils.createElement("div", {
|
|
@@ -2147,18 +2233,21 @@ var VidPly = (() => {
|
|
|
2147
2233
|
}
|
|
2148
2234
|
});
|
|
2149
2235
|
const speeds = [0.25, 0.5, 0.75, 1, 1.25, 1.5, 1.75, 2];
|
|
2236
|
+
let activeItem = null;
|
|
2150
2237
|
speeds.forEach((speed) => {
|
|
2151
2238
|
const item = DOMUtils.createElement("button", {
|
|
2152
2239
|
className: `${this.player.options.classPrefix}-menu-item`,
|
|
2153
2240
|
textContent: this.formatSpeedLabel(speed),
|
|
2154
2241
|
attributes: {
|
|
2155
2242
|
"type": "button",
|
|
2156
|
-
"role": "menuitem"
|
|
2243
|
+
"role": "menuitem",
|
|
2244
|
+
"tabindex": "-1"
|
|
2157
2245
|
}
|
|
2158
2246
|
});
|
|
2159
2247
|
if (speed === this.player.state.playbackSpeed) {
|
|
2160
2248
|
item.classList.add(`${this.player.options.classPrefix}-menu-item-active`);
|
|
2161
2249
|
item.appendChild(createIconElement("check"));
|
|
2250
|
+
activeItem = item;
|
|
2162
2251
|
}
|
|
2163
2252
|
item.addEventListener("click", () => {
|
|
2164
2253
|
this.player.setPlaybackSpeed(speed);
|
|
@@ -2167,7 +2256,14 @@ var VidPly = (() => {
|
|
|
2167
2256
|
menu.appendChild(item);
|
|
2168
2257
|
});
|
|
2169
2258
|
button.appendChild(menu);
|
|
2259
|
+
this.attachMenuKeyboardNavigation(menu);
|
|
2170
2260
|
this.attachMenuCloseHandler(menu, button);
|
|
2261
|
+
setTimeout(() => {
|
|
2262
|
+
const focusTarget = activeItem || menu.querySelector(`.${this.player.options.classPrefix}-menu-item`);
|
|
2263
|
+
if (focusTarget) {
|
|
2264
|
+
focusTarget.focus();
|
|
2265
|
+
}
|
|
2266
|
+
}, 0);
|
|
2171
2267
|
}
|
|
2172
2268
|
createCaptionsButton() {
|
|
2173
2269
|
const button = DOMUtils.createElement("button", {
|
|
@@ -2210,17 +2306,20 @@ var VidPly = (() => {
|
|
|
2210
2306
|
this.attachMenuCloseHandler(menu, button);
|
|
2211
2307
|
return;
|
|
2212
2308
|
}
|
|
2309
|
+
let activeItem = null;
|
|
2213
2310
|
const offItem = DOMUtils.createElement("button", {
|
|
2214
2311
|
className: `${this.player.options.classPrefix}-menu-item`,
|
|
2215
2312
|
textContent: i18n.t("captions.off"),
|
|
2216
2313
|
attributes: {
|
|
2217
2314
|
"type": "button",
|
|
2218
|
-
"role": "menuitem"
|
|
2315
|
+
"role": "menuitem",
|
|
2316
|
+
"tabindex": "-1"
|
|
2219
2317
|
}
|
|
2220
2318
|
});
|
|
2221
2319
|
if (!this.player.state.captionsEnabled) {
|
|
2222
2320
|
offItem.classList.add(`${this.player.options.classPrefix}-menu-item-active`);
|
|
2223
2321
|
offItem.appendChild(createIconElement("check"));
|
|
2322
|
+
activeItem = offItem;
|
|
2224
2323
|
}
|
|
2225
2324
|
offItem.addEventListener("click", () => {
|
|
2226
2325
|
this.player.disableCaptions();
|
|
@@ -2236,12 +2335,14 @@ var VidPly = (() => {
|
|
|
2236
2335
|
attributes: {
|
|
2237
2336
|
"type": "button",
|
|
2238
2337
|
"role": "menuitem",
|
|
2239
|
-
"lang": track.language
|
|
2338
|
+
"lang": track.language,
|
|
2339
|
+
"tabindex": "-1"
|
|
2240
2340
|
}
|
|
2241
2341
|
});
|
|
2242
2342
|
if (this.player.state.captionsEnabled && this.player.captionManager.currentTrack === this.player.captionManager.tracks[track.index]) {
|
|
2243
2343
|
item.classList.add(`${this.player.options.classPrefix}-menu-item-active`);
|
|
2244
2344
|
item.appendChild(createIconElement("check"));
|
|
2345
|
+
activeItem = item;
|
|
2245
2346
|
}
|
|
2246
2347
|
item.addEventListener("click", () => {
|
|
2247
2348
|
this.player.captionManager.switchTrack(track.index);
|
|
@@ -2251,7 +2352,14 @@ var VidPly = (() => {
|
|
|
2251
2352
|
menu.appendChild(item);
|
|
2252
2353
|
});
|
|
2253
2354
|
button.appendChild(menu);
|
|
2355
|
+
this.attachMenuKeyboardNavigation(menu);
|
|
2254
2356
|
this.attachMenuCloseHandler(menu, button);
|
|
2357
|
+
setTimeout(() => {
|
|
2358
|
+
const focusTarget = activeItem || menu.querySelector(`.${this.player.options.classPrefix}-menu-item`);
|
|
2359
|
+
if (focusTarget) {
|
|
2360
|
+
focusTarget.focus();
|
|
2361
|
+
}
|
|
2362
|
+
}, 0);
|
|
2255
2363
|
}
|
|
2256
2364
|
updateCaptionsButton() {
|
|
2257
2365
|
if (!this.controls.captions) return;
|
|
@@ -2426,36 +2534,43 @@ var VidPly = (() => {
|
|
|
2426
2534
|
const percent = this.player.state.currentTime / this.player.state.duration * 100;
|
|
2427
2535
|
this.controls.played.style.width = `${percent}%`;
|
|
2428
2536
|
this.controls.progress.setAttribute("aria-valuenow", String(Math.round(percent)));
|
|
2429
|
-
if (this.controls.
|
|
2430
|
-
|
|
2537
|
+
if (this.controls.currentTimeVisual) {
|
|
2538
|
+
const currentTime = this.player.state.currentTime;
|
|
2539
|
+
this.controls.currentTimeVisual.textContent = TimeUtils.formatTime(currentTime);
|
|
2540
|
+
this.controls.currentTimeDisplay.setAttribute("aria-label", TimeUtils.formatDuration(currentTime));
|
|
2431
2541
|
}
|
|
2432
2542
|
}
|
|
2433
2543
|
updateDuration() {
|
|
2434
|
-
if (this.controls.
|
|
2435
|
-
|
|
2544
|
+
if (this.controls.durationVisual) {
|
|
2545
|
+
const duration = this.player.state.duration;
|
|
2546
|
+
this.controls.durationVisual.textContent = TimeUtils.formatTime(duration);
|
|
2547
|
+
this.controls.durationDisplay.setAttribute("aria-label", "Duration: " + TimeUtils.formatDuration(duration));
|
|
2436
2548
|
}
|
|
2437
2549
|
}
|
|
2438
2550
|
updateVolumeDisplay() {
|
|
2439
|
-
if (!this.controls.volumeFill) return;
|
|
2440
2551
|
const percent = this.player.state.volume * 100;
|
|
2441
|
-
this.controls.volumeFill
|
|
2552
|
+
if (this.controls.volumeFill) {
|
|
2553
|
+
this.controls.volumeFill.style.height = `${percent}%`;
|
|
2554
|
+
}
|
|
2442
2555
|
if (this.controls.mute) {
|
|
2443
2556
|
const icon = this.controls.mute.querySelector(".vidply-icon");
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
|
|
2447
|
-
|
|
2448
|
-
|
|
2449
|
-
|
|
2450
|
-
|
|
2451
|
-
|
|
2452
|
-
|
|
2557
|
+
if (icon) {
|
|
2558
|
+
let iconName;
|
|
2559
|
+
if (this.player.state.muted || this.player.state.volume === 0) {
|
|
2560
|
+
iconName = "volumeMuted";
|
|
2561
|
+
} else if (this.player.state.volume < 0.3) {
|
|
2562
|
+
iconName = "volumeLow";
|
|
2563
|
+
} else if (this.player.state.volume < 0.7) {
|
|
2564
|
+
iconName = "volumeMedium";
|
|
2565
|
+
} else {
|
|
2566
|
+
iconName = "volumeHigh";
|
|
2567
|
+
}
|
|
2568
|
+
icon.innerHTML = createIconElement(iconName).innerHTML;
|
|
2569
|
+
this.controls.mute.setAttribute(
|
|
2570
|
+
"aria-label",
|
|
2571
|
+
this.player.state.muted ? i18n.t("player.unmute") : i18n.t("player.mute")
|
|
2572
|
+
);
|
|
2453
2573
|
}
|
|
2454
|
-
icon.innerHTML = createIconElement(iconName).innerHTML;
|
|
2455
|
-
this.controls.mute.setAttribute(
|
|
2456
|
-
"aria-label",
|
|
2457
|
-
this.player.state.muted ? i18n.t("player.unmute") : i18n.t("player.mute")
|
|
2458
|
-
);
|
|
2459
2574
|
}
|
|
2460
2575
|
if (this.controls.volumeSlider) {
|
|
2461
2576
|
this.controls.volumeSlider.setAttribute("aria-valuenow", String(Math.round(percent)));
|
|
@@ -2640,7 +2755,6 @@ var VidPly = (() => {
|
|
|
2640
2755
|
this.updateCaptions();
|
|
2641
2756
|
};
|
|
2642
2757
|
selectedTrack.track.addEventListener("cuechange", this.cueChangeHandler);
|
|
2643
|
-
this.element.style.display = "block";
|
|
2644
2758
|
this.player.emit("captionsenabled", selectedTrack);
|
|
2645
2759
|
}
|
|
2646
2760
|
}
|
|
@@ -2783,6 +2897,9 @@ var VidPly = (() => {
|
|
|
2783
2897
|
}
|
|
2784
2898
|
}
|
|
2785
2899
|
}
|
|
2900
|
+
if (!handled && this.player.options.debug) {
|
|
2901
|
+
console.log("[VidPly] Unhandled key:", e.key, "code:", e.code, "shiftKey:", e.shiftKey);
|
|
2902
|
+
}
|
|
2786
2903
|
}
|
|
2787
2904
|
executeAction(action, event) {
|
|
2788
2905
|
switch (action) {
|
|
@@ -2801,12 +2918,6 @@ var VidPly = (() => {
|
|
|
2801
2918
|
case "seek-backward":
|
|
2802
2919
|
this.player.seekBackward();
|
|
2803
2920
|
return true;
|
|
2804
|
-
case "seek-forward-large":
|
|
2805
|
-
this.player.seekForward(this.player.options.seekIntervalLarge);
|
|
2806
|
-
return true;
|
|
2807
|
-
case "seek-backward-large":
|
|
2808
|
-
this.player.seekBackward(this.player.options.seekIntervalLarge);
|
|
2809
|
-
return true;
|
|
2810
2921
|
case "mute":
|
|
2811
2922
|
this.player.toggleMute();
|
|
2812
2923
|
return true;
|
|
@@ -2815,14 +2926,22 @@ var VidPly = (() => {
|
|
|
2815
2926
|
return true;
|
|
2816
2927
|
case "captions":
|
|
2817
2928
|
if (this.player.captionManager && this.player.captionManager.tracks.length > 1) {
|
|
2818
|
-
const captionsButton =
|
|
2819
|
-
if (captionsButton
|
|
2929
|
+
const captionsButton = this.player.controlBar && this.player.controlBar.controls.captions;
|
|
2930
|
+
if (captionsButton) {
|
|
2820
2931
|
this.player.controlBar.showCaptionsMenu(captionsButton);
|
|
2932
|
+
} else {
|
|
2933
|
+
this.player.toggleCaptions();
|
|
2821
2934
|
}
|
|
2822
2935
|
} else {
|
|
2823
2936
|
this.player.toggleCaptions();
|
|
2824
2937
|
}
|
|
2825
2938
|
return true;
|
|
2939
|
+
case "caption-style-menu":
|
|
2940
|
+
if (this.player.controlBar && this.player.controlBar.controls.captionStyle) {
|
|
2941
|
+
this.player.controlBar.showCaptionStyleMenu(this.player.controlBar.controls.captionStyle);
|
|
2942
|
+
return true;
|
|
2943
|
+
}
|
|
2944
|
+
return false;
|
|
2826
2945
|
case "speed-up":
|
|
2827
2946
|
this.player.setPlaybackSpeed(
|
|
2828
2947
|
Math.min(2, this.player.state.playbackSpeed + 0.25)
|
|
@@ -2833,9 +2952,30 @@ var VidPly = (() => {
|
|
|
2833
2952
|
Math.max(0.25, this.player.state.playbackSpeed - 0.25)
|
|
2834
2953
|
);
|
|
2835
2954
|
return true;
|
|
2836
|
-
case "
|
|
2837
|
-
this.player.
|
|
2838
|
-
|
|
2955
|
+
case "speed-menu":
|
|
2956
|
+
if (this.player.controlBar && this.player.controlBar.controls.speed) {
|
|
2957
|
+
this.player.controlBar.showSpeedMenu(this.player.controlBar.controls.speed);
|
|
2958
|
+
return true;
|
|
2959
|
+
}
|
|
2960
|
+
return false;
|
|
2961
|
+
case "quality-menu":
|
|
2962
|
+
if (this.player.controlBar && this.player.controlBar.controls.quality) {
|
|
2963
|
+
this.player.controlBar.showQualityMenu(this.player.controlBar.controls.quality);
|
|
2964
|
+
return true;
|
|
2965
|
+
}
|
|
2966
|
+
return false;
|
|
2967
|
+
case "chapters-menu":
|
|
2968
|
+
if (this.player.controlBar && this.player.controlBar.controls.chapters) {
|
|
2969
|
+
this.player.controlBar.showChaptersMenu(this.player.controlBar.controls.chapters);
|
|
2970
|
+
return true;
|
|
2971
|
+
}
|
|
2972
|
+
return false;
|
|
2973
|
+
case "transcript-toggle":
|
|
2974
|
+
if (this.player.transcriptManager) {
|
|
2975
|
+
this.player.transcriptManager.toggleTranscript();
|
|
2976
|
+
return true;
|
|
2977
|
+
}
|
|
2978
|
+
return false;
|
|
2839
2979
|
default:
|
|
2840
2980
|
return false;
|
|
2841
2981
|
}
|
|
@@ -2902,317 +3042,6 @@ var VidPly = (() => {
|
|
|
2902
3042
|
}
|
|
2903
3043
|
};
|
|
2904
3044
|
|
|
2905
|
-
// src/controls/SettingsDialog.js
|
|
2906
|
-
var SettingsDialog = class {
|
|
2907
|
-
constructor(player) {
|
|
2908
|
-
this.player = player;
|
|
2909
|
-
this.element = null;
|
|
2910
|
-
this.isOpen = false;
|
|
2911
|
-
this.init();
|
|
2912
|
-
}
|
|
2913
|
-
init() {
|
|
2914
|
-
this.createElement();
|
|
2915
|
-
}
|
|
2916
|
-
createElement() {
|
|
2917
|
-
this.overlay = DOMUtils.createElement("div", {
|
|
2918
|
-
className: `${this.player.options.classPrefix}-settings-overlay`,
|
|
2919
|
-
attributes: {
|
|
2920
|
-
"role": "dialog",
|
|
2921
|
-
"aria-modal": "true",
|
|
2922
|
-
"aria-label": i18n.t("settings.title")
|
|
2923
|
-
}
|
|
2924
|
-
});
|
|
2925
|
-
this.overlay.style.display = "none";
|
|
2926
|
-
this.element = DOMUtils.createElement("div", {
|
|
2927
|
-
className: `${this.player.options.classPrefix}-settings-dialog`
|
|
2928
|
-
});
|
|
2929
|
-
const header = DOMUtils.createElement("div", {
|
|
2930
|
-
className: `${this.player.options.classPrefix}-settings-header`
|
|
2931
|
-
});
|
|
2932
|
-
const title = DOMUtils.createElement("h2", {
|
|
2933
|
-
textContent: i18n.t("settings.title"),
|
|
2934
|
-
attributes: {
|
|
2935
|
-
"id": `${this.player.options.classPrefix}-settings-title`
|
|
2936
|
-
}
|
|
2937
|
-
});
|
|
2938
|
-
const closeButton = DOMUtils.createElement("button", {
|
|
2939
|
-
className: `${this.player.options.classPrefix}-button ${this.player.options.classPrefix}-settings-close`,
|
|
2940
|
-
attributes: {
|
|
2941
|
-
"type": "button",
|
|
2942
|
-
"aria-label": i18n.t("settings.close")
|
|
2943
|
-
}
|
|
2944
|
-
});
|
|
2945
|
-
closeButton.appendChild(createIconElement("close"));
|
|
2946
|
-
closeButton.addEventListener("click", () => this.hide());
|
|
2947
|
-
header.appendChild(title);
|
|
2948
|
-
header.appendChild(closeButton);
|
|
2949
|
-
const content = DOMUtils.createElement("div", {
|
|
2950
|
-
className: `${this.player.options.classPrefix}-settings-content`
|
|
2951
|
-
});
|
|
2952
|
-
content.appendChild(this.createSpeedSettings());
|
|
2953
|
-
if (this.player.captionManager && this.player.captionManager.tracks.length > 0) {
|
|
2954
|
-
content.appendChild(this.createCaptionSettings());
|
|
2955
|
-
}
|
|
2956
|
-
const footer = DOMUtils.createElement("div", {
|
|
2957
|
-
className: `${this.player.options.classPrefix}-settings-footer`
|
|
2958
|
-
});
|
|
2959
|
-
const resetButton = DOMUtils.createElement("button", {
|
|
2960
|
-
className: `${this.player.options.classPrefix}-button`,
|
|
2961
|
-
textContent: i18n.t("settings.reset"),
|
|
2962
|
-
attributes: {
|
|
2963
|
-
"type": "button"
|
|
2964
|
-
}
|
|
2965
|
-
});
|
|
2966
|
-
resetButton.addEventListener("click", () => this.resetSettings());
|
|
2967
|
-
footer.appendChild(resetButton);
|
|
2968
|
-
this.element.appendChild(header);
|
|
2969
|
-
this.element.appendChild(content);
|
|
2970
|
-
this.element.appendChild(footer);
|
|
2971
|
-
this.overlay.appendChild(this.element);
|
|
2972
|
-
this.player.container.appendChild(this.overlay);
|
|
2973
|
-
this.overlay.addEventListener("click", (e) => {
|
|
2974
|
-
if (e.target === this.overlay) {
|
|
2975
|
-
this.hide();
|
|
2976
|
-
}
|
|
2977
|
-
});
|
|
2978
|
-
document.addEventListener("keydown", (e) => {
|
|
2979
|
-
if (e.key === "Escape" && this.isOpen) {
|
|
2980
|
-
this.hide();
|
|
2981
|
-
}
|
|
2982
|
-
});
|
|
2983
|
-
}
|
|
2984
|
-
formatSpeedLabel(speed) {
|
|
2985
|
-
if (speed === 1) {
|
|
2986
|
-
return i18n.t("speeds.normal");
|
|
2987
|
-
}
|
|
2988
|
-
const speedStr = speed.toLocaleString(i18n.getLanguage(), {
|
|
2989
|
-
minimumFractionDigits: 0,
|
|
2990
|
-
maximumFractionDigits: 2
|
|
2991
|
-
});
|
|
2992
|
-
return `${speedStr}\xD7`;
|
|
2993
|
-
}
|
|
2994
|
-
createSpeedSettings() {
|
|
2995
|
-
const section = DOMUtils.createElement("div", {
|
|
2996
|
-
className: `${this.player.options.classPrefix}-settings-section`
|
|
2997
|
-
});
|
|
2998
|
-
const label = DOMUtils.createElement("label", {
|
|
2999
|
-
textContent: i18n.t("settings.speed"),
|
|
3000
|
-
attributes: {
|
|
3001
|
-
"for": `${this.player.options.classPrefix}-speed-select`
|
|
3002
|
-
}
|
|
3003
|
-
});
|
|
3004
|
-
const select = DOMUtils.createElement("select", {
|
|
3005
|
-
className: `${this.player.options.classPrefix}-settings-select`,
|
|
3006
|
-
attributes: {
|
|
3007
|
-
"id": `${this.player.options.classPrefix}-speed-select`
|
|
3008
|
-
}
|
|
3009
|
-
});
|
|
3010
|
-
const speeds = [0.25, 0.5, 0.75, 1, 1.25, 1.5, 1.75, 2];
|
|
3011
|
-
speeds.forEach((speed) => {
|
|
3012
|
-
const option = DOMUtils.createElement("option", {
|
|
3013
|
-
textContent: this.formatSpeedLabel(speed),
|
|
3014
|
-
attributes: {
|
|
3015
|
-
"value": String(speed)
|
|
3016
|
-
}
|
|
3017
|
-
});
|
|
3018
|
-
if (speed === this.player.state.playbackSpeed) {
|
|
3019
|
-
option.selected = true;
|
|
3020
|
-
}
|
|
3021
|
-
select.appendChild(option);
|
|
3022
|
-
});
|
|
3023
|
-
select.addEventListener("change", (e) => {
|
|
3024
|
-
this.player.setPlaybackSpeed(parseFloat(e.target.value));
|
|
3025
|
-
});
|
|
3026
|
-
section.appendChild(label);
|
|
3027
|
-
section.appendChild(select);
|
|
3028
|
-
return section;
|
|
3029
|
-
}
|
|
3030
|
-
createCaptionSettings() {
|
|
3031
|
-
const section = DOMUtils.createElement("div", {
|
|
3032
|
-
className: `${this.player.options.classPrefix}-settings-section`
|
|
3033
|
-
});
|
|
3034
|
-
const heading = DOMUtils.createElement("h3", {
|
|
3035
|
-
textContent: i18n.t("settings.captions")
|
|
3036
|
-
});
|
|
3037
|
-
section.appendChild(heading);
|
|
3038
|
-
const trackLabel = DOMUtils.createElement("label", {
|
|
3039
|
-
textContent: i18n.t("captions.select"),
|
|
3040
|
-
attributes: {
|
|
3041
|
-
"for": `${this.player.options.classPrefix}-caption-track-select`
|
|
3042
|
-
}
|
|
3043
|
-
});
|
|
3044
|
-
const trackSelect = DOMUtils.createElement("select", {
|
|
3045
|
-
className: `${this.player.options.classPrefix}-settings-select`,
|
|
3046
|
-
attributes: {
|
|
3047
|
-
"id": `${this.player.options.classPrefix}-caption-track-select`
|
|
3048
|
-
}
|
|
3049
|
-
});
|
|
3050
|
-
const offOption = DOMUtils.createElement("option", {
|
|
3051
|
-
textContent: i18n.t("captions.off"),
|
|
3052
|
-
attributes: { "value": "-1" }
|
|
3053
|
-
});
|
|
3054
|
-
trackSelect.appendChild(offOption);
|
|
3055
|
-
const tracks = this.player.captionManager.getAvailableTracks();
|
|
3056
|
-
tracks.forEach((track) => {
|
|
3057
|
-
const option = DOMUtils.createElement("option", {
|
|
3058
|
-
textContent: track.label,
|
|
3059
|
-
attributes: { "value": String(track.index) }
|
|
3060
|
-
});
|
|
3061
|
-
trackSelect.appendChild(option);
|
|
3062
|
-
});
|
|
3063
|
-
trackSelect.addEventListener("change", (e) => {
|
|
3064
|
-
const index = parseInt(e.target.value);
|
|
3065
|
-
if (index === -1) {
|
|
3066
|
-
this.player.disableCaptions();
|
|
3067
|
-
} else {
|
|
3068
|
-
this.player.captionManager.switchTrack(index);
|
|
3069
|
-
}
|
|
3070
|
-
});
|
|
3071
|
-
section.appendChild(trackLabel);
|
|
3072
|
-
section.appendChild(trackSelect);
|
|
3073
|
-
section.appendChild(this.createCaptionStyleControl("fontSize", i18n.t("captions.fontSize"), [
|
|
3074
|
-
{ label: i18n.t("fontSizes.small"), value: "80%" },
|
|
3075
|
-
{ label: i18n.t("fontSizes.medium"), value: "100%" },
|
|
3076
|
-
{ label: i18n.t("fontSizes.large"), value: "120%" },
|
|
3077
|
-
{ label: i18n.t("fontSizes.xlarge"), value: "150%" }
|
|
3078
|
-
]));
|
|
3079
|
-
section.appendChild(this.createCaptionStyleControl("fontFamily", i18n.t("captions.fontFamily"), [
|
|
3080
|
-
{ label: i18n.t("fontFamilies.sansSerif"), value: "sans-serif" },
|
|
3081
|
-
{ label: i18n.t("fontFamilies.serif"), value: "serif" },
|
|
3082
|
-
{ label: i18n.t("fontFamilies.monospace"), value: "monospace" }
|
|
3083
|
-
]));
|
|
3084
|
-
section.appendChild(this.createColorControl("color", i18n.t("captions.color")));
|
|
3085
|
-
section.appendChild(this.createColorControl("backgroundColor", i18n.t("captions.backgroundColor")));
|
|
3086
|
-
section.appendChild(this.createRangeControl("opacity", i18n.t("captions.opacity"), 0, 1, 0.1));
|
|
3087
|
-
return section;
|
|
3088
|
-
}
|
|
3089
|
-
createCaptionStyleControl(property, label, options) {
|
|
3090
|
-
const wrapper = DOMUtils.createElement("div", {
|
|
3091
|
-
className: `${this.player.options.classPrefix}-settings-control`
|
|
3092
|
-
});
|
|
3093
|
-
const labelEl = DOMUtils.createElement("label", {
|
|
3094
|
-
textContent: label,
|
|
3095
|
-
attributes: {
|
|
3096
|
-
"for": `${this.player.options.classPrefix}-caption-${property}`
|
|
3097
|
-
}
|
|
3098
|
-
});
|
|
3099
|
-
const select = DOMUtils.createElement("select", {
|
|
3100
|
-
className: `${this.player.options.classPrefix}-settings-select`,
|
|
3101
|
-
attributes: {
|
|
3102
|
-
"id": `${this.player.options.classPrefix}-caption-${property}`
|
|
3103
|
-
}
|
|
3104
|
-
});
|
|
3105
|
-
options.forEach((opt) => {
|
|
3106
|
-
const option = DOMUtils.createElement("option", {
|
|
3107
|
-
textContent: opt.label,
|
|
3108
|
-
attributes: { "value": opt.value }
|
|
3109
|
-
});
|
|
3110
|
-
if (opt.value === this.player.options[`captions${property.charAt(0).toUpperCase() + property.slice(1)}`]) {
|
|
3111
|
-
option.selected = true;
|
|
3112
|
-
}
|
|
3113
|
-
select.appendChild(option);
|
|
3114
|
-
});
|
|
3115
|
-
select.addEventListener("change", (e) => {
|
|
3116
|
-
this.player.captionManager.setCaptionStyle(property, e.target.value);
|
|
3117
|
-
});
|
|
3118
|
-
wrapper.appendChild(labelEl);
|
|
3119
|
-
wrapper.appendChild(select);
|
|
3120
|
-
return wrapper;
|
|
3121
|
-
}
|
|
3122
|
-
createColorControl(property, label) {
|
|
3123
|
-
const wrapper = DOMUtils.createElement("div", {
|
|
3124
|
-
className: `${this.player.options.classPrefix}-settings-control`
|
|
3125
|
-
});
|
|
3126
|
-
const labelEl = DOMUtils.createElement("label", {
|
|
3127
|
-
textContent: label,
|
|
3128
|
-
attributes: {
|
|
3129
|
-
"for": `${this.player.options.classPrefix}-caption-${property}`
|
|
3130
|
-
}
|
|
3131
|
-
});
|
|
3132
|
-
const input = DOMUtils.createElement("input", {
|
|
3133
|
-
className: `${this.player.options.classPrefix}-settings-color`,
|
|
3134
|
-
attributes: {
|
|
3135
|
-
"type": "color",
|
|
3136
|
-
"id": `${this.player.options.classPrefix}-caption-${property}`,
|
|
3137
|
-
"value": this.player.options[`captions${property.charAt(0).toUpperCase() + property.slice(1)}`]
|
|
3138
|
-
}
|
|
3139
|
-
});
|
|
3140
|
-
input.addEventListener("change", (e) => {
|
|
3141
|
-
this.player.captionManager.setCaptionStyle(property, e.target.value);
|
|
3142
|
-
});
|
|
3143
|
-
wrapper.appendChild(labelEl);
|
|
3144
|
-
wrapper.appendChild(input);
|
|
3145
|
-
return wrapper;
|
|
3146
|
-
}
|
|
3147
|
-
createRangeControl(property, label, min, max, step) {
|
|
3148
|
-
const wrapper = DOMUtils.createElement("div", {
|
|
3149
|
-
className: `${this.player.options.classPrefix}-settings-control`
|
|
3150
|
-
});
|
|
3151
|
-
const labelEl = DOMUtils.createElement("label", {
|
|
3152
|
-
textContent: label,
|
|
3153
|
-
attributes: {
|
|
3154
|
-
"for": `${this.player.options.classPrefix}-caption-${property}`
|
|
3155
|
-
}
|
|
3156
|
-
});
|
|
3157
|
-
const input = DOMUtils.createElement("input", {
|
|
3158
|
-
className: `${this.player.options.classPrefix}-settings-range`,
|
|
3159
|
-
attributes: {
|
|
3160
|
-
"type": "range",
|
|
3161
|
-
"id": `${this.player.options.classPrefix}-caption-${property}`,
|
|
3162
|
-
"min": String(min),
|
|
3163
|
-
"max": String(max),
|
|
3164
|
-
"step": String(step),
|
|
3165
|
-
"value": String(this.player.options[`captions${property.charAt(0).toUpperCase() + property.slice(1)}`])
|
|
3166
|
-
}
|
|
3167
|
-
});
|
|
3168
|
-
const valueDisplay = DOMUtils.createElement("span", {
|
|
3169
|
-
className: `${this.player.options.classPrefix}-settings-value`,
|
|
3170
|
-
textContent: String(this.player.options[`captions${property.charAt(0).toUpperCase() + property.slice(1)}`])
|
|
3171
|
-
});
|
|
3172
|
-
input.addEventListener("input", (e) => {
|
|
3173
|
-
const value = parseFloat(e.target.value);
|
|
3174
|
-
valueDisplay.textContent = value.toFixed(1);
|
|
3175
|
-
this.player.captionManager.setCaptionStyle(property, value);
|
|
3176
|
-
});
|
|
3177
|
-
wrapper.appendChild(labelEl);
|
|
3178
|
-
wrapper.appendChild(input);
|
|
3179
|
-
wrapper.appendChild(valueDisplay);
|
|
3180
|
-
return wrapper;
|
|
3181
|
-
}
|
|
3182
|
-
resetSettings() {
|
|
3183
|
-
this.player.setPlaybackSpeed(1);
|
|
3184
|
-
if (this.player.captionManager) {
|
|
3185
|
-
this.player.captionManager.setCaptionStyle("fontSize", "100%");
|
|
3186
|
-
this.player.captionManager.setCaptionStyle("fontFamily", "sans-serif");
|
|
3187
|
-
this.player.captionManager.setCaptionStyle("color", "#FFFFFF");
|
|
3188
|
-
this.player.captionManager.setCaptionStyle("backgroundColor", "#000000");
|
|
3189
|
-
this.player.captionManager.setCaptionStyle("opacity", 0.8);
|
|
3190
|
-
}
|
|
3191
|
-
this.hide();
|
|
3192
|
-
setTimeout(() => this.show(), 100);
|
|
3193
|
-
}
|
|
3194
|
-
show() {
|
|
3195
|
-
this.overlay.style.display = "flex";
|
|
3196
|
-
this.isOpen = true;
|
|
3197
|
-
const closeButton = this.element.querySelector(`.${this.player.options.classPrefix}-settings-close`);
|
|
3198
|
-
if (closeButton) {
|
|
3199
|
-
closeButton.focus();
|
|
3200
|
-
}
|
|
3201
|
-
this.player.emit("settingsopen");
|
|
3202
|
-
}
|
|
3203
|
-
hide() {
|
|
3204
|
-
this.overlay.style.display = "none";
|
|
3205
|
-
this.isOpen = false;
|
|
3206
|
-
this.player.container.focus();
|
|
3207
|
-
this.player.emit("settingsclose");
|
|
3208
|
-
}
|
|
3209
|
-
destroy() {
|
|
3210
|
-
if (this.overlay && this.overlay.parentNode) {
|
|
3211
|
-
this.overlay.parentNode.removeChild(this.overlay);
|
|
3212
|
-
}
|
|
3213
|
-
}
|
|
3214
|
-
};
|
|
3215
|
-
|
|
3216
3045
|
// src/controls/TranscriptManager.js
|
|
3217
3046
|
var TranscriptManager = class {
|
|
3218
3047
|
constructor(player) {
|
|
@@ -4500,16 +4329,18 @@ var VidPly = (() => {
|
|
|
4500
4329
|
"play-pause": [" ", "p", "k"],
|
|
4501
4330
|
"volume-up": ["ArrowUp"],
|
|
4502
4331
|
"volume-down": ["ArrowDown"],
|
|
4503
|
-
"seek-forward": ["ArrowRight"
|
|
4504
|
-
"seek-backward": ["ArrowLeft"
|
|
4505
|
-
"seek-forward-large": ["l"],
|
|
4506
|
-
"seek-backward-large": ["j"],
|
|
4332
|
+
"seek-forward": ["ArrowRight"],
|
|
4333
|
+
"seek-backward": ["ArrowLeft"],
|
|
4507
4334
|
"mute": ["m"],
|
|
4508
4335
|
"fullscreen": ["f"],
|
|
4509
4336
|
"captions": ["c"],
|
|
4337
|
+
"caption-style-menu": ["a"],
|
|
4510
4338
|
"speed-up": [">"],
|
|
4511
4339
|
"speed-down": ["<"],
|
|
4512
|
-
"
|
|
4340
|
+
"speed-menu": ["s"],
|
|
4341
|
+
"quality-menu": ["q"],
|
|
4342
|
+
"chapters-menu": ["j"],
|
|
4343
|
+
"transcript-toggle": ["t"]
|
|
4513
4344
|
},
|
|
4514
4345
|
// Accessibility
|
|
4515
4346
|
ariaLabels: {},
|
|
@@ -4598,9 +4429,6 @@ var VidPly = (() => {
|
|
|
4598
4429
|
if (this.options.keyboard) {
|
|
4599
4430
|
this.keyboardManager = new KeyboardManager(this);
|
|
4600
4431
|
}
|
|
4601
|
-
if (this.options.settingsButton) {
|
|
4602
|
-
this.settingsDialog = new SettingsDialog(this);
|
|
4603
|
-
}
|
|
4604
4432
|
this.setupResponsiveHandlers();
|
|
4605
4433
|
if (this.options.startTime > 0) {
|
|
4606
4434
|
this.seek(this.options.startTime);
|
|
@@ -4778,6 +4606,17 @@ var VidPly = (() => {
|
|
|
4778
4606
|
this.captionManager.destroy();
|
|
4779
4607
|
this.captionManager = new CaptionManager(this);
|
|
4780
4608
|
}
|
|
4609
|
+
if (this.transcriptManager) {
|
|
4610
|
+
const wasVisible = this.transcriptManager.isVisible;
|
|
4611
|
+
this.transcriptManager.destroy();
|
|
4612
|
+
this.transcriptManager = new TranscriptManager(this);
|
|
4613
|
+
if (wasVisible) {
|
|
4614
|
+
this.transcriptManager.showTranscript();
|
|
4615
|
+
}
|
|
4616
|
+
}
|
|
4617
|
+
if (this.controlBar) {
|
|
4618
|
+
this.updateControlBar();
|
|
4619
|
+
}
|
|
4781
4620
|
this.emit("sourcechange", config);
|
|
4782
4621
|
this.log("Media loaded successfully");
|
|
4783
4622
|
} catch (error) {
|
|
@@ -4789,6 +4628,17 @@ var VidPly = (() => {
|
|
|
4789
4628
|
* @param {string} src - New source URL
|
|
4790
4629
|
* @returns {boolean}
|
|
4791
4630
|
*/
|
|
4631
|
+
/**
|
|
4632
|
+
* Update control bar to refresh button visibility based on available features
|
|
4633
|
+
*/
|
|
4634
|
+
updateControlBar() {
|
|
4635
|
+
if (!this.controlBar) return;
|
|
4636
|
+
const controlBar = this.controlBar;
|
|
4637
|
+
controlBar.element.innerHTML = "";
|
|
4638
|
+
controlBar.createControls();
|
|
4639
|
+
controlBar.attachEvents();
|
|
4640
|
+
controlBar.setupAutoHide();
|
|
4641
|
+
}
|
|
4792
4642
|
shouldChangeRenderer(src) {
|
|
4793
4643
|
if (!this.renderer) return true;
|
|
4794
4644
|
const isYouTube = src.includes("youtube.com") || src.includes("youtu.be");
|
|
@@ -4853,12 +4703,14 @@ var VidPly = (() => {
|
|
|
4853
4703
|
this.renderer.setMuted(true);
|
|
4854
4704
|
}
|
|
4855
4705
|
this.state.muted = true;
|
|
4706
|
+
this.emit("volumechange");
|
|
4856
4707
|
}
|
|
4857
4708
|
unmute() {
|
|
4858
4709
|
if (this.renderer) {
|
|
4859
4710
|
this.renderer.setMuted(false);
|
|
4860
4711
|
}
|
|
4861
4712
|
this.state.muted = false;
|
|
4713
|
+
this.emit("volumechange");
|
|
4862
4714
|
}
|
|
4863
4715
|
toggleMute() {
|
|
4864
4716
|
if (this.state.muted) {
|
|
@@ -5092,15 +4944,11 @@ var VidPly = (() => {
|
|
|
5092
4944
|
}
|
|
5093
4945
|
}
|
|
5094
4946
|
// Settings
|
|
4947
|
+
// Settings dialog removed - using individual control buttons instead
|
|
5095
4948
|
showSettings() {
|
|
5096
|
-
|
|
5097
|
-
this.settingsDialog.show();
|
|
5098
|
-
}
|
|
4949
|
+
console.warn("[VidPly] Settings dialog has been removed. Use individual control buttons (speed, captions, etc.)");
|
|
5099
4950
|
}
|
|
5100
4951
|
hideSettings() {
|
|
5101
|
-
if (this.settingsDialog) {
|
|
5102
|
-
this.settingsDialog.hide();
|
|
5103
|
-
}
|
|
5104
4952
|
}
|
|
5105
4953
|
// Utility methods
|
|
5106
4954
|
getCurrentTime() {
|
|
@@ -5197,9 +5045,6 @@ var VidPly = (() => {
|
|
|
5197
5045
|
if (this.keyboardManager) {
|
|
5198
5046
|
this.keyboardManager.destroy();
|
|
5199
5047
|
}
|
|
5200
|
-
if (this.settingsDialog) {
|
|
5201
|
-
this.settingsDialog.destroy();
|
|
5202
|
-
}
|
|
5203
5048
|
if (this.transcriptManager) {
|
|
5204
5049
|
this.transcriptManager.destroy();
|
|
5205
5050
|
}
|
|
@@ -5253,7 +5098,9 @@ var VidPly = (() => {
|
|
|
5253
5098
|
this.trackInfoElement = null;
|
|
5254
5099
|
this.handleTrackEnd = this.handleTrackEnd.bind(this);
|
|
5255
5100
|
this.handleTrackError = this.handleTrackError.bind(this);
|
|
5101
|
+
this.player.playlistManager = this;
|
|
5256
5102
|
this.init();
|
|
5103
|
+
this.updatePlayerControls();
|
|
5257
5104
|
}
|
|
5258
5105
|
init() {
|
|
5259
5106
|
this.player.on("ended", this.handleTrackEnd);
|
|
@@ -5262,6 +5109,17 @@ var VidPly = (() => {
|
|
|
5262
5109
|
this.createUI();
|
|
5263
5110
|
}
|
|
5264
5111
|
}
|
|
5112
|
+
/**
|
|
5113
|
+
* Update player controls to add playlist navigation buttons
|
|
5114
|
+
*/
|
|
5115
|
+
updatePlayerControls() {
|
|
5116
|
+
if (!this.player.controlBar) return;
|
|
5117
|
+
const controlBar = this.player.controlBar;
|
|
5118
|
+
controlBar.element.innerHTML = "";
|
|
5119
|
+
controlBar.createControls();
|
|
5120
|
+
controlBar.attachEvents();
|
|
5121
|
+
controlBar.setupAutoHide();
|
|
5122
|
+
}
|
|
5265
5123
|
/**
|
|
5266
5124
|
* Load a playlist
|
|
5267
5125
|
* @param {Array} tracks - Array of track objects
|
|
@@ -5282,8 +5140,9 @@ var VidPly = (() => {
|
|
|
5282
5140
|
/**
|
|
5283
5141
|
* Play a specific track
|
|
5284
5142
|
* @param {number} index - Track index
|
|
5143
|
+
* @param {boolean} userInitiated - Whether this was triggered by user action (default: false)
|
|
5285
5144
|
*/
|
|
5286
|
-
play(index) {
|
|
5145
|
+
play(index, userInitiated = false) {
|
|
5287
5146
|
if (index < 0 || index >= this.tracks.length) {
|
|
5288
5147
|
console.warn("VidPly Playlist: Invalid track index", index);
|
|
5289
5148
|
return;
|
|
@@ -5303,6 +5162,9 @@ var VidPly = (() => {
|
|
|
5303
5162
|
item: track,
|
|
5304
5163
|
total: this.tracks.length
|
|
5305
5164
|
});
|
|
5165
|
+
if (userInitiated && this.player.container) {
|
|
5166
|
+
this.player.container.focus();
|
|
5167
|
+
}
|
|
5306
5168
|
setTimeout(() => {
|
|
5307
5169
|
this.player.play();
|
|
5308
5170
|
}, 100);
|
|
@@ -5364,12 +5226,17 @@ var VidPly = (() => {
|
|
|
5364
5226
|
return;
|
|
5365
5227
|
}
|
|
5366
5228
|
this.trackInfoElement = DOMUtils.createElement("div", {
|
|
5367
|
-
className: "vidply-track-info"
|
|
5229
|
+
className: "vidply-track-info",
|
|
5230
|
+
role: "status",
|
|
5231
|
+
"aria-live": "polite",
|
|
5232
|
+
"aria-atomic": "true"
|
|
5368
5233
|
});
|
|
5369
5234
|
this.trackInfoElement.style.display = "none";
|
|
5370
5235
|
this.container.appendChild(this.trackInfoElement);
|
|
5371
5236
|
this.playlistPanel = DOMUtils.createElement("div", {
|
|
5372
|
-
className: "vidply-playlist-panel"
|
|
5237
|
+
className: "vidply-playlist-panel",
|
|
5238
|
+
role: "region",
|
|
5239
|
+
"aria-label": "Media playlist"
|
|
5373
5240
|
});
|
|
5374
5241
|
this.playlistPanel.style.display = "none";
|
|
5375
5242
|
this.container.appendChild(this.playlistPanel);
|
|
@@ -5381,10 +5248,14 @@ var VidPly = (() => {
|
|
|
5381
5248
|
if (!this.trackInfoElement) return;
|
|
5382
5249
|
const trackNumber = this.currentIndex + 1;
|
|
5383
5250
|
const totalTracks = this.tracks.length;
|
|
5251
|
+
const trackTitle = track.title || "Untitled";
|
|
5252
|
+
const trackArtist = track.artist || "";
|
|
5253
|
+
const announcement = `Now playing: Track ${trackNumber} of ${totalTracks}. ${trackTitle}${trackArtist ? " by " + trackArtist : ""}`;
|
|
5384
5254
|
this.trackInfoElement.innerHTML = `
|
|
5385
|
-
<
|
|
5386
|
-
<div class="vidply-track-
|
|
5387
|
-
|
|
5255
|
+
<span class="vidply-sr-only">${DOMUtils.escapeHTML(announcement)}</span>
|
|
5256
|
+
<div class="vidply-track-number" aria-hidden="true">Track ${trackNumber} of ${totalTracks}</div>
|
|
5257
|
+
<div class="vidply-track-title" aria-hidden="true">${DOMUtils.escapeHTML(trackTitle)}</div>
|
|
5258
|
+
${trackArtist ? `<div class="vidply-track-artist" aria-hidden="true">${DOMUtils.escapeHTML(trackArtist)}</div>` : ""}
|
|
5388
5259
|
`;
|
|
5389
5260
|
this.trackInfoElement.style.display = "block";
|
|
5390
5261
|
}
|
|
@@ -5394,14 +5265,29 @@ var VidPly = (() => {
|
|
|
5394
5265
|
renderPlaylist() {
|
|
5395
5266
|
if (!this.playlistPanel) return;
|
|
5396
5267
|
this.playlistPanel.innerHTML = "";
|
|
5397
|
-
const header = DOMUtils.createElement("
|
|
5398
|
-
className: "vidply-playlist-header"
|
|
5268
|
+
const header = DOMUtils.createElement("h2", {
|
|
5269
|
+
className: "vidply-playlist-header",
|
|
5270
|
+
id: "vidply-playlist-heading"
|
|
5399
5271
|
});
|
|
5400
5272
|
header.textContent = `Playlist (${this.tracks.length})`;
|
|
5401
5273
|
this.playlistPanel.appendChild(header);
|
|
5402
|
-
const
|
|
5403
|
-
className: "vidply-
|
|
5404
|
-
|
|
5274
|
+
const instructions = DOMUtils.createElement("div", {
|
|
5275
|
+
className: "vidply-sr-only",
|
|
5276
|
+
"aria-hidden": "false"
|
|
5277
|
+
});
|
|
5278
|
+
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.";
|
|
5279
|
+
this.playlistPanel.appendChild(instructions);
|
|
5280
|
+
const list = DOMUtils.createElement("ul", {
|
|
5281
|
+
className: "vidply-playlist-list",
|
|
5282
|
+
"aria-labelledby": "vidply-playlist-heading",
|
|
5283
|
+
"aria-describedby": "vidply-playlist-instructions"
|
|
5284
|
+
});
|
|
5285
|
+
const listDescription = DOMUtils.createElement("div", {
|
|
5286
|
+
className: "vidply-sr-only",
|
|
5287
|
+
id: "vidply-playlist-instructions"
|
|
5288
|
+
});
|
|
5289
|
+
listDescription.textContent = `Playlist with ${this.tracks.length} ${this.tracks.length === 1 ? "track" : "tracks"}`;
|
|
5290
|
+
this.playlistPanel.appendChild(listDescription);
|
|
5405
5291
|
this.tracks.forEach((track, index) => {
|
|
5406
5292
|
const item = this.createPlaylistItem(track, index);
|
|
5407
5293
|
list.appendChild(item);
|
|
@@ -5413,20 +5299,39 @@ var VidPly = (() => {
|
|
|
5413
5299
|
* Create playlist item element
|
|
5414
5300
|
*/
|
|
5415
5301
|
createPlaylistItem(track, index) {
|
|
5416
|
-
const
|
|
5302
|
+
const trackPosition = `Track ${index + 1} of ${this.tracks.length}`;
|
|
5303
|
+
const trackTitle = track.title || `Track ${index + 1}`;
|
|
5304
|
+
const trackArtist = track.artist ? ` by ${track.artist}` : "";
|
|
5305
|
+
const isActive = index === this.currentIndex;
|
|
5306
|
+
const statusText = isActive ? "Currently playing" : "Not playing";
|
|
5307
|
+
const actionText = isActive ? "Press Enter to restart" : "Press Enter to play";
|
|
5308
|
+
const item = DOMUtils.createElement("li", {
|
|
5417
5309
|
className: "vidply-playlist-item",
|
|
5418
|
-
|
|
5419
|
-
|
|
5420
|
-
"aria-label":
|
|
5421
|
-
|
|
5422
|
-
|
|
5310
|
+
tabIndex: index === 0 ? 0 : -1,
|
|
5311
|
+
// Only first item is in tab order initially
|
|
5312
|
+
"aria-label": `${trackPosition}. ${trackTitle}${trackArtist}. ${statusText}. ${actionText}.`,
|
|
5313
|
+
"aria-posinset": index + 1,
|
|
5314
|
+
"aria-setsize": this.tracks.length,
|
|
5315
|
+
"data-playlist-index": index
|
|
5316
|
+
});
|
|
5317
|
+
if (isActive) {
|
|
5423
5318
|
item.classList.add("vidply-playlist-item-active");
|
|
5319
|
+
item.setAttribute("aria-current", "true");
|
|
5320
|
+
item.setAttribute("tabIndex", "0");
|
|
5424
5321
|
}
|
|
5322
|
+
const positionInfo = DOMUtils.createElement("span", {
|
|
5323
|
+
className: "vidply-sr-only"
|
|
5324
|
+
});
|
|
5325
|
+
positionInfo.textContent = `${trackPosition}: `;
|
|
5326
|
+
item.appendChild(positionInfo);
|
|
5425
5327
|
const thumbnail = DOMUtils.createElement("div", {
|
|
5426
|
-
className: "vidply-playlist-thumbnail"
|
|
5328
|
+
className: "vidply-playlist-thumbnail",
|
|
5329
|
+
"aria-hidden": "true"
|
|
5427
5330
|
});
|
|
5428
5331
|
if (track.poster) {
|
|
5429
5332
|
thumbnail.style.backgroundImage = `url(${track.poster})`;
|
|
5333
|
+
thumbnail.setAttribute("role", "img");
|
|
5334
|
+
thumbnail.setAttribute("aria-label", `${trackTitle} thumbnail`);
|
|
5430
5335
|
} else {
|
|
5431
5336
|
const icon = createIconElement("music");
|
|
5432
5337
|
icon.classList.add("vidply-playlist-thumbnail-icon");
|
|
@@ -5434,12 +5339,13 @@ var VidPly = (() => {
|
|
|
5434
5339
|
}
|
|
5435
5340
|
item.appendChild(thumbnail);
|
|
5436
5341
|
const info = DOMUtils.createElement("div", {
|
|
5437
|
-
className: "vidply-playlist-item-info"
|
|
5342
|
+
className: "vidply-playlist-item-info",
|
|
5343
|
+
"aria-hidden": "true"
|
|
5438
5344
|
});
|
|
5439
5345
|
const title = DOMUtils.createElement("div", {
|
|
5440
5346
|
className: "vidply-playlist-item-title"
|
|
5441
5347
|
});
|
|
5442
|
-
title.textContent =
|
|
5348
|
+
title.textContent = trackTitle;
|
|
5443
5349
|
info.appendChild(title);
|
|
5444
5350
|
if (track.artist) {
|
|
5445
5351
|
const artist = DOMUtils.createElement("div", {
|
|
@@ -5449,20 +5355,64 @@ var VidPly = (() => {
|
|
|
5449
5355
|
info.appendChild(artist);
|
|
5450
5356
|
}
|
|
5451
5357
|
item.appendChild(info);
|
|
5358
|
+
if (isActive) {
|
|
5359
|
+
const statusIndicator = DOMUtils.createElement("span", {
|
|
5360
|
+
className: "vidply-sr-only"
|
|
5361
|
+
});
|
|
5362
|
+
statusIndicator.textContent = " (Currently playing)";
|
|
5363
|
+
item.appendChild(statusIndicator);
|
|
5364
|
+
}
|
|
5452
5365
|
const playIcon = createIconElement("play");
|
|
5453
5366
|
playIcon.classList.add("vidply-playlist-item-icon");
|
|
5367
|
+
playIcon.setAttribute("aria-hidden", "true");
|
|
5454
5368
|
item.appendChild(playIcon);
|
|
5455
5369
|
item.addEventListener("click", () => {
|
|
5456
|
-
this.play(index);
|
|
5370
|
+
this.play(index, true);
|
|
5457
5371
|
});
|
|
5458
5372
|
item.addEventListener("keydown", (e) => {
|
|
5459
|
-
|
|
5460
|
-
e.preventDefault();
|
|
5461
|
-
this.play(index);
|
|
5462
|
-
}
|
|
5373
|
+
this.handlePlaylistItemKeydown(e, index);
|
|
5463
5374
|
});
|
|
5464
5375
|
return item;
|
|
5465
5376
|
}
|
|
5377
|
+
/**
|
|
5378
|
+
* Handle keyboard navigation in playlist items
|
|
5379
|
+
*/
|
|
5380
|
+
handlePlaylistItemKeydown(e, index) {
|
|
5381
|
+
const items = Array.from(this.playlistPanel.querySelectorAll(".vidply-playlist-item"));
|
|
5382
|
+
let newIndex = -1;
|
|
5383
|
+
switch (e.key) {
|
|
5384
|
+
case "Enter":
|
|
5385
|
+
case " ":
|
|
5386
|
+
e.preventDefault();
|
|
5387
|
+
this.play(index, true);
|
|
5388
|
+
break;
|
|
5389
|
+
case "ArrowDown":
|
|
5390
|
+
e.preventDefault();
|
|
5391
|
+
if (index < items.length - 1) {
|
|
5392
|
+
newIndex = index + 1;
|
|
5393
|
+
}
|
|
5394
|
+
break;
|
|
5395
|
+
case "ArrowUp":
|
|
5396
|
+
e.preventDefault();
|
|
5397
|
+
if (index > 0) {
|
|
5398
|
+
newIndex = index - 1;
|
|
5399
|
+
}
|
|
5400
|
+
break;
|
|
5401
|
+
case "Home":
|
|
5402
|
+
e.preventDefault();
|
|
5403
|
+
newIndex = 0;
|
|
5404
|
+
break;
|
|
5405
|
+
case "End":
|
|
5406
|
+
e.preventDefault();
|
|
5407
|
+
newIndex = items.length - 1;
|
|
5408
|
+
break;
|
|
5409
|
+
}
|
|
5410
|
+
if (newIndex !== -1 && newIndex !== index) {
|
|
5411
|
+
items[index].setAttribute("tabIndex", "-1");
|
|
5412
|
+
items[newIndex].setAttribute("tabIndex", "0");
|
|
5413
|
+
items[newIndex].focus();
|
|
5414
|
+
}
|
|
5415
|
+
}
|
|
5466
5416
|
/**
|
|
5467
5417
|
* Update playlist UI (highlight current track)
|
|
5468
5418
|
*/
|
|
@@ -5470,11 +5420,25 @@ var VidPly = (() => {
|
|
|
5470
5420
|
if (!this.playlistPanel) return;
|
|
5471
5421
|
const items = this.playlistPanel.querySelectorAll(".vidply-playlist-item");
|
|
5472
5422
|
items.forEach((item, index) => {
|
|
5423
|
+
const track = this.tracks[index];
|
|
5424
|
+
const trackPosition = `Track ${index + 1} of ${this.tracks.length}`;
|
|
5425
|
+
const trackTitle = track.title || `Track ${index + 1}`;
|
|
5426
|
+
const trackArtist = track.artist ? ` by ${track.artist}` : "";
|
|
5473
5427
|
if (index === this.currentIndex) {
|
|
5474
5428
|
item.classList.add("vidply-playlist-item-active");
|
|
5429
|
+
item.setAttribute("aria-current", "true");
|
|
5430
|
+
item.setAttribute("tabIndex", "0");
|
|
5431
|
+
const statusText = "Currently playing";
|
|
5432
|
+
const actionText = "Press Enter to restart";
|
|
5433
|
+
item.setAttribute("aria-label", `${trackPosition}. ${trackTitle}${trackArtist}. ${statusText}. ${actionText}.`);
|
|
5475
5434
|
item.scrollIntoView({ behavior: "smooth", block: "nearest" });
|
|
5476
5435
|
} else {
|
|
5477
5436
|
item.classList.remove("vidply-playlist-item-active");
|
|
5437
|
+
item.removeAttribute("aria-current");
|
|
5438
|
+
item.setAttribute("tabIndex", "-1");
|
|
5439
|
+
const statusText = "Not playing";
|
|
5440
|
+
const actionText = "Press Enter to play";
|
|
5441
|
+
item.setAttribute("aria-label", `${trackPosition}. ${trackTitle}${trackArtist}. ${statusText}. ${actionText}.`);
|
|
5478
5442
|
}
|
|
5479
5443
|
});
|
|
5480
5444
|
}
|
|
@@ -5492,10 +5456,24 @@ var VidPly = (() => {
|
|
|
5492
5456
|
currentIndex: this.currentIndex,
|
|
5493
5457
|
totalTracks: this.tracks.length,
|
|
5494
5458
|
currentTrack: this.getCurrentTrack(),
|
|
5495
|
-
hasNext: this.
|
|
5496
|
-
hasPrevious: this.
|
|
5459
|
+
hasNext: this.hasNext(),
|
|
5460
|
+
hasPrevious: this.hasPrevious()
|
|
5497
5461
|
};
|
|
5498
5462
|
}
|
|
5463
|
+
/**
|
|
5464
|
+
* Check if there is a next track
|
|
5465
|
+
*/
|
|
5466
|
+
hasNext() {
|
|
5467
|
+
if (this.options.loop) return true;
|
|
5468
|
+
return this.currentIndex < this.tracks.length - 1;
|
|
5469
|
+
}
|
|
5470
|
+
/**
|
|
5471
|
+
* Check if there is a previous track
|
|
5472
|
+
*/
|
|
5473
|
+
hasPrevious() {
|
|
5474
|
+
if (this.options.loop) return true;
|
|
5475
|
+
return this.currentIndex > 0;
|
|
5476
|
+
}
|
|
5499
5477
|
/**
|
|
5500
5478
|
* Add track to playlist
|
|
5501
5479
|
*/
|