@openplayerjs/player 3.0.1 → 3.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +69 -16
- package/dist/.tsbuildinfo +1 -1
- package/dist/index.js +86 -28
- package/dist/index.js.map +1 -1
- package/dist/openplayer.css +1 -1
- package/dist/openplayer.js +1 -1
- package/dist/openplayer.js.map +1 -1
- package/dist/types/configuration.d.ts +1 -0
- package/dist/types/configuration.d.ts.map +1 -1
- package/dist/types/controls/captions.d.ts +2 -0
- package/dist/types/controls/captions.d.ts.map +1 -1
- package/dist/types/controls/fullscreen.d.ts.map +1 -1
- package/dist/types/controls/play.d.ts.map +1 -1
- package/dist/types/controls/settings.d.ts.map +1 -1
- package/dist/types/playback.d.ts +2 -2
- package/dist/types/playback.d.ts.map +1 -1
- package/dist/types/ui.d.ts.map +1 -1
- package/package.json +11 -7
- package/LICENSE +0 -21
package/dist/index.js
CHANGED
|
@@ -168,13 +168,13 @@ function getActiveMedia(core) {
|
|
|
168
168
|
try {
|
|
169
169
|
const hasOverlayMgr = typeof getOverlayManager === 'function';
|
|
170
170
|
if (!hasOverlayMgr)
|
|
171
|
-
return core.
|
|
171
|
+
return core.surface;
|
|
172
172
|
const active = getOverlayManager(core)?.active;
|
|
173
173
|
const v = active?.fullscreenVideoEl;
|
|
174
|
-
return v && typeof v.play === 'function' ? v : core.
|
|
174
|
+
return v && typeof v.play === 'function' ? v : core.surface;
|
|
175
175
|
}
|
|
176
176
|
catch {
|
|
177
|
-
return core.
|
|
177
|
+
return core.surface;
|
|
178
178
|
}
|
|
179
179
|
}
|
|
180
180
|
async function togglePlayback(core) {
|
|
@@ -736,6 +736,7 @@ function createUI(core, media, controls, options = {}) {
|
|
|
736
736
|
const KEYBOARD_SHOW_MS = 6500;
|
|
737
737
|
let hideTimer;
|
|
738
738
|
let lastInteraction = 'pointer';
|
|
739
|
+
let menuOpen = false;
|
|
739
740
|
const controlsHaveFocus = () => controlsRoot.contains(document.activeElement);
|
|
740
741
|
const showControls = () => {
|
|
741
742
|
wrapper.classList.remove('op-controls--hidden');
|
|
@@ -743,9 +744,10 @@ function createUI(core, media, controls, options = {}) {
|
|
|
743
744
|
if (hideTimer)
|
|
744
745
|
window.clearTimeout(hideTimer);
|
|
745
746
|
controlsRoot.setAttribute('aria-hidden', 'false');
|
|
747
|
+
core.events.emit('ui:controls:show');
|
|
746
748
|
};
|
|
747
749
|
const hideControls = () => {
|
|
748
|
-
if (core.
|
|
750
|
+
if (core.surface.paused || core.surface.ended)
|
|
749
751
|
return;
|
|
750
752
|
if (controlsHaveFocus()) {
|
|
751
753
|
if (lastInteraction === 'keyboard') {
|
|
@@ -758,11 +760,14 @@ function createUI(core, media, controls, options = {}) {
|
|
|
758
760
|
wrapper.classList.add('op-controls--hidden');
|
|
759
761
|
mediaContainer.classList.add('op-media--controls-hidden');
|
|
760
762
|
controlsRoot.setAttribute('aria-hidden', 'true');
|
|
763
|
+
core.events.emit('ui:controls:hide');
|
|
761
764
|
};
|
|
762
765
|
const scheduleHide = (ms) => {
|
|
763
766
|
if (alwaysVisible)
|
|
764
767
|
return;
|
|
765
|
-
if (
|
|
768
|
+
if (menuOpen)
|
|
769
|
+
return;
|
|
770
|
+
if (core.surface.paused || core.surface.ended)
|
|
766
771
|
return;
|
|
767
772
|
if (hideTimer)
|
|
768
773
|
window.clearTimeout(hideTimer);
|
|
@@ -853,7 +858,7 @@ function createUI(core, media, controls, options = {}) {
|
|
|
853
858
|
// Clicks on interactive elements (buttons, links) are handled by those elements.
|
|
854
859
|
if (target && target !== wrapper && target.closest('button, [role="button"], a'))
|
|
855
860
|
return;
|
|
856
|
-
const isPlaying = !core.
|
|
861
|
+
const isPlaying = !core.surface.paused && !core.surface.ended;
|
|
857
862
|
if (isPlaying) {
|
|
858
863
|
overlay?.flashPause(350);
|
|
859
864
|
core.pause();
|
|
@@ -865,6 +870,15 @@ function createUI(core, media, controls, options = {}) {
|
|
|
865
870
|
const offPlaying = core.events.on('playing', () => scheduleHide(POINTER_SHOW_MS));
|
|
866
871
|
const offPause = core.events.on('pause', () => showControls());
|
|
867
872
|
const offEnded = core.events.on('ended', () => showControls());
|
|
873
|
+
const offMenuOpen = core.events.on('ui:menu:open', () => {
|
|
874
|
+
menuOpen = true;
|
|
875
|
+
if (hideTimer)
|
|
876
|
+
window.clearTimeout(hideTimer);
|
|
877
|
+
});
|
|
878
|
+
const offMenuClose = core.events.on('ui:menu:close', () => {
|
|
879
|
+
menuOpen = false;
|
|
880
|
+
scheduleHide();
|
|
881
|
+
});
|
|
868
882
|
const offDestroy = core.events.on('player:destroy', () => {
|
|
869
883
|
try {
|
|
870
884
|
createdControls.forEach((c) => c.destroy?.());
|
|
@@ -888,6 +902,8 @@ function createUI(core, media, controls, options = {}) {
|
|
|
888
902
|
offPlaying?.();
|
|
889
903
|
offPause?.();
|
|
890
904
|
offEnded?.();
|
|
905
|
+
offMenuOpen?.();
|
|
906
|
+
offMenuClose?.();
|
|
891
907
|
offAddElement();
|
|
892
908
|
offAddControl();
|
|
893
909
|
offDestroy();
|
|
@@ -1219,7 +1235,7 @@ class CurrentTimeControl extends BaseControl {
|
|
|
1219
1235
|
const update = () => {
|
|
1220
1236
|
if (this.activeOverlay) {
|
|
1221
1237
|
el.setAttribute('aria-hidden', 'false');
|
|
1222
|
-
el.innerText = formatTime(this.activeOverlay.value);
|
|
1238
|
+
el.innerText = formatTime(Math.ceil(this.activeOverlay.value));
|
|
1223
1239
|
return;
|
|
1224
1240
|
}
|
|
1225
1241
|
if (core.isLive) {
|
|
@@ -1484,6 +1500,8 @@ class PlayControl extends BaseControl {
|
|
|
1484
1500
|
const labels = resolveUIConfig(core).labels;
|
|
1485
1501
|
const playLabel = labels.play;
|
|
1486
1502
|
const pauseLabel = labels.pause;
|
|
1503
|
+
const restartLabel = labels.restart;
|
|
1504
|
+
let isEnded = false;
|
|
1487
1505
|
const btn = document.createElement('button');
|
|
1488
1506
|
btn.tabIndex = 0;
|
|
1489
1507
|
btn.type = 'button';
|
|
@@ -1492,20 +1510,24 @@ class PlayControl extends BaseControl {
|
|
|
1492
1510
|
btn.setAttribute('aria-pressed', 'false');
|
|
1493
1511
|
this.listen(btn, 'click', async (e) => {
|
|
1494
1512
|
const me = e;
|
|
1495
|
-
await togglePlayback(core);
|
|
1496
1513
|
me.preventDefault();
|
|
1497
1514
|
me.stopPropagation();
|
|
1515
|
+
if (isEnded) {
|
|
1516
|
+
const media = getActiveMedia(core);
|
|
1517
|
+
media.currentTime = 0;
|
|
1518
|
+
}
|
|
1519
|
+
await togglePlayback(core);
|
|
1498
1520
|
}, EVENT_OPTIONS);
|
|
1499
1521
|
const setPlaying = (playing) => {
|
|
1500
1522
|
btn.classList.toggle('op-controls__playpause--pause', playing);
|
|
1523
|
+
btn.classList.toggle('op-controls__playpause--replay', isEnded && !playing);
|
|
1501
1524
|
btn.setAttribute('aria-pressed', playing ? 'true' : 'false');
|
|
1502
|
-
setA11yLabel(btn, playing ? pauseLabel : playLabel);
|
|
1525
|
+
setA11yLabel(btn, isEnded && !playing ? restartLabel : playing ? pauseLabel : playLabel);
|
|
1503
1526
|
};
|
|
1504
|
-
this.onPlayer('play', () => setPlaying(true));
|
|
1527
|
+
this.onPlayer('play', () => { isEnded = false; setPlaying(true); });
|
|
1528
|
+
this.onPlayer('playing', () => { isEnded = false; setPlaying(true); });
|
|
1505
1529
|
this.onPlayer('pause', () => setPlaying(false));
|
|
1506
|
-
this.onPlayer('
|
|
1507
|
-
this.onPlayer('pause', () => setPlaying(false));
|
|
1508
|
-
this.onPlayer('ended', () => setPlaying(false));
|
|
1530
|
+
this.onPlayer('ended', () => { isEnded = true; setPlaying(false); });
|
|
1509
1531
|
return btn;
|
|
1510
1532
|
}
|
|
1511
1533
|
}
|
|
@@ -1607,12 +1629,12 @@ class ProgressControl extends BaseControl {
|
|
|
1607
1629
|
const getDuration = () => {
|
|
1608
1630
|
if (this.activeOverlay)
|
|
1609
1631
|
return this.activeOverlay.duration;
|
|
1610
|
-
return core.
|
|
1632
|
+
return core.surface?.duration ?? core.duration;
|
|
1611
1633
|
};
|
|
1612
1634
|
const getValue = () => {
|
|
1613
1635
|
if (this.activeOverlay)
|
|
1614
1636
|
return this.activeOverlay.value;
|
|
1615
|
-
return core.
|
|
1637
|
+
return core.surface?.currentTime ?? core.currentTime;
|
|
1616
1638
|
};
|
|
1617
1639
|
const getMode = () => (this.activeOverlay ? this.activeOverlay.mode : 'normal');
|
|
1618
1640
|
const updateUI = () => {
|
|
@@ -1634,7 +1656,7 @@ class ProgressControl extends BaseControl {
|
|
|
1634
1656
|
if (this.activeOverlay)
|
|
1635
1657
|
setSeekEnabled(this.activeOverlay.canSeek);
|
|
1636
1658
|
else
|
|
1637
|
-
setSeekEnabled(!core.isLive && core.
|
|
1659
|
+
setSeekEnabled(!core.isLive && core.surface?.duration !== Infinity);
|
|
1638
1660
|
if (slider.classList.contains('op-progress--pressed'))
|
|
1639
1661
|
return;
|
|
1640
1662
|
const d = Number.isFinite(duration) && duration > 0 ? duration : 0;
|
|
@@ -1741,7 +1763,7 @@ class ProgressControl extends BaseControl {
|
|
|
1741
1763
|
slider.classList.remove('loading');
|
|
1742
1764
|
if (slider.classList.contains('error'))
|
|
1743
1765
|
slider.classList.remove('error');
|
|
1744
|
-
if (!core.isLive && core.
|
|
1766
|
+
if (!core.isLive && core.surface.duration !== Infinity) {
|
|
1745
1767
|
progress.removeAttribute('aria-valuenow');
|
|
1746
1768
|
progress.removeAttribute('aria-valuetext');
|
|
1747
1769
|
}
|
|
@@ -1873,6 +1895,7 @@ class SettingsControl extends BaseControl {
|
|
|
1873
1895
|
this.panel = document.createElement('div');
|
|
1874
1896
|
this.panel.className = 'op-menu';
|
|
1875
1897
|
this.panel.setAttribute('role', 'menu');
|
|
1898
|
+
this.panel.tabIndex = -1;
|
|
1876
1899
|
this.panel.style.display = 'none';
|
|
1877
1900
|
this.view = document.createElement('div');
|
|
1878
1901
|
this.view.className = 'op-menu__submenu';
|
|
@@ -1899,11 +1922,38 @@ class SettingsControl extends BaseControl {
|
|
|
1899
1922
|
if (ke.key === 'Escape')
|
|
1900
1923
|
this.close();
|
|
1901
1924
|
}, EVENT_OPTIONS);
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1925
|
+
// Close the menu when focus moves outside the player (matching YouTube behaviour).
|
|
1926
|
+
this.listen(document, 'focusout', () => {
|
|
1927
|
+
if (!this.isOpen)
|
|
1928
|
+
return;
|
|
1929
|
+
window.setTimeout(() => {
|
|
1930
|
+
const playerEl = this.button.closest('.op-player');
|
|
1931
|
+
if (playerEl && !playerEl.contains(document.activeElement))
|
|
1932
|
+
this.close();
|
|
1933
|
+
}, 0);
|
|
1934
|
+
}, EVENT_OPTIONS);
|
|
1935
|
+
let knownOverlayId = null;
|
|
1936
|
+
this.dispose.add(this.overlayMgr.bus.on('overlay:changed', (ov) => {
|
|
1937
|
+
const newId = ov?.id ?? null;
|
|
1938
|
+
// Only reset submenu navigation when the overlay identity changes
|
|
1939
|
+
// (e.g. ad starts or ends). update() fires overlay:changed on every
|
|
1940
|
+
// timeupdate — we must not reset the user's submenu position then.
|
|
1941
|
+
if (newId !== knownOverlayId) {
|
|
1942
|
+
knownOverlayId = newId;
|
|
1943
|
+
this.activeSubmenuId = null;
|
|
1944
|
+
// Dismiss the menu on any context switch (ad↔content) so the user
|
|
1945
|
+
// never sees a stale track list from the previous context. It is safe
|
|
1946
|
+
// to rebuild here because the ad / content transition has completed.
|
|
1947
|
+
if (this.isOpen)
|
|
1948
|
+
this.close();
|
|
1949
|
+
else
|
|
1950
|
+
this.render();
|
|
1951
|
+
}
|
|
1952
|
+
else if (!this.isOpen) {
|
|
1953
|
+
// Periodic timeupdate ticks: only re-render visibility when closed
|
|
1954
|
+
// to avoid destroying buttons the user is actively clicking.
|
|
1955
|
+
this.render();
|
|
1956
|
+
}
|
|
1907
1957
|
}));
|
|
1908
1958
|
getSettingsRegistry(this.core).register({
|
|
1909
1959
|
id: 'speed',
|
|
@@ -1912,7 +1962,7 @@ class SettingsControl extends BaseControl {
|
|
|
1912
1962
|
const ov = this.overlayMgr.active;
|
|
1913
1963
|
if (ov?.id === 'ads')
|
|
1914
1964
|
return null;
|
|
1915
|
-
const rates = [0.5, 0.75, 1, 1.25, 1.5, 2];
|
|
1965
|
+
const rates = [0.25, 0.5, 0.75, 1, 1.25, 1.5, 2, 2.5];
|
|
1916
1966
|
const current = core.playbackRate || 1;
|
|
1917
1967
|
return {
|
|
1918
1968
|
id: 'speed',
|
|
@@ -1946,14 +1996,22 @@ class SettingsControl extends BaseControl {
|
|
|
1946
1996
|
this.button.setAttribute('aria-expanded', 'true');
|
|
1947
1997
|
this.panel.style.display = 'block';
|
|
1948
1998
|
this.render();
|
|
1999
|
+
this.core.events.emit('ui:menu:open');
|
|
1949
2000
|
}
|
|
1950
2001
|
close() {
|
|
1951
2002
|
this.isOpen = false;
|
|
1952
2003
|
this.activeSubmenuId = null;
|
|
1953
2004
|
this.button.setAttribute('aria-expanded', 'false');
|
|
1954
2005
|
this.panel.style.display = 'none';
|
|
2006
|
+
this.core.events.emit('ui:menu:close');
|
|
1955
2007
|
}
|
|
1956
2008
|
render() {
|
|
2009
|
+
// If focus is inside the menu container, move it to the panel before clearing
|
|
2010
|
+
// the DOM so the browser doesn't relocate focus to <body>, which would cause
|
|
2011
|
+
// the focusout handler to close the menu mid-navigation.
|
|
2012
|
+
if (this.isOpen && this.root.contains(document.activeElement)) {
|
|
2013
|
+
this.panel.focus({ preventScroll: true });
|
|
2014
|
+
}
|
|
1957
2015
|
const reg = getSettingsRegistry(this.core);
|
|
1958
2016
|
const providers = reg.list();
|
|
1959
2017
|
const available = providers
|
|
@@ -2164,7 +2222,7 @@ class VolumeControl extends BaseControl {
|
|
|
2164
2222
|
core.volume = v;
|
|
2165
2223
|
core.muted = v === 0;
|
|
2166
2224
|
const el = getActiveMedia(core);
|
|
2167
|
-
if (el && el !== core.
|
|
2225
|
+
if (el && el !== core.surface) {
|
|
2168
2226
|
try {
|
|
2169
2227
|
el.volume = v;
|
|
2170
2228
|
el.muted = v === 0;
|
|
@@ -2184,7 +2242,7 @@ class VolumeControl extends BaseControl {
|
|
|
2184
2242
|
lastVolume = core.volume;
|
|
2185
2243
|
core.volume = 0;
|
|
2186
2244
|
core.muted = true;
|
|
2187
|
-
if (el && el !== core.
|
|
2245
|
+
if (el && el !== core.surface) {
|
|
2188
2246
|
try {
|
|
2189
2247
|
el.volume = 0;
|
|
2190
2248
|
el.muted = true;
|
|
@@ -2200,7 +2258,7 @@ class VolumeControl extends BaseControl {
|
|
|
2200
2258
|
const restore = lastVolume > 0 ? lastVolume : 1;
|
|
2201
2259
|
core.volume = restore;
|
|
2202
2260
|
core.muted = false;
|
|
2203
|
-
if (el && el !== core.
|
|
2261
|
+
if (el && el !== core.surface) {
|
|
2204
2262
|
try {
|
|
2205
2263
|
el.volume = restore;
|
|
2206
2264
|
el.muted = false;
|
|
@@ -2224,7 +2282,7 @@ class VolumeControl extends BaseControl {
|
|
|
2224
2282
|
updateSlider(muted ? 0 : vol);
|
|
2225
2283
|
updateBtn(muted ? 0 : vol);
|
|
2226
2284
|
const el = getActiveMedia(core);
|
|
2227
|
-
if (el && el !== core.
|
|
2285
|
+
if (el && el !== core.surface) {
|
|
2228
2286
|
try {
|
|
2229
2287
|
el.muted = muted;
|
|
2230
2288
|
if (!muted)
|
|
@@ -2245,7 +2303,7 @@ class VolumeControl extends BaseControl {
|
|
|
2245
2303
|
updateBtn(muted ? 0 : vol);
|
|
2246
2304
|
btn.setAttribute('aria-pressed', muted ? 'true' : 'false');
|
|
2247
2305
|
const el = getActiveMedia(core);
|
|
2248
|
-
if (el && el !== core.
|
|
2306
|
+
if (el && el !== core.surface) {
|
|
2249
2307
|
try {
|
|
2250
2308
|
el.muted = muted;
|
|
2251
2309
|
if (!muted)
|