eb-player 2.0.4 → 2.0.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/build/eb-player.css +47 -12
- package/dist/build/ebplayer.bundle.js +174 -27
- package/dist/build/ebplayer.bundle.js.map +1 -1
- package/dist/build/types/eb-player.d.ts.map +1 -1
- package/dist/build/types/engines/hls.d.ts +7 -0
- package/dist/build/types/engines/hls.d.ts.map +1 -1
- package/dist/build/types/engines/snapshot/hls.d.ts.map +1 -1
- package/dist/build/types/skin/controls/fullscreen-button.d.ts +14 -1
- package/dist/build/types/skin/controls/fullscreen-button.d.ts.map +1 -1
- package/dist/build/types/skin/controls/seekbar.d.ts +6 -1
- package/dist/build/types/skin/controls/seekbar.d.ts.map +1 -1
- package/dist/dev/easybroadcast.js +169 -27
- package/dist/dev/easybroadcast.js.map +1 -1
- package/dist/eb-player.css +47 -12
- package/dist/players/default/default.js +148 -165
- package/dist/players/equipe/EB_lequipe-preprod.js +70 -72
- package/dist/players/equipe/equipe.js +70 -72
- package/package.json +2 -1
package/dist/build/eb-player.css
CHANGED
|
@@ -2049,7 +2049,12 @@
|
|
|
2049
2049
|
user-select: none;
|
|
2050
2050
|
}
|
|
2051
2051
|
|
|
2052
|
-
/* Video element fills the container
|
|
2052
|
+
/* Video element fills the container.
|
|
2053
|
+
filter: brightness(1) is visually identical to no filter but forces Chrome to
|
|
2054
|
+
composite the video through the GPU filter pipeline instead of using a hardware
|
|
2055
|
+
overlay plane. Without this, DRM-protected video (Widevine) renders on a separate
|
|
2056
|
+
hardware overlay that sits above ALL HTML layers — making seekbar snapshot preview
|
|
2057
|
+
and other overlay elements invisible behind the main video. */
|
|
2053
2058
|
.eb-player video.eb-video {
|
|
2054
2059
|
position: absolute;
|
|
2055
2060
|
top: 0;
|
|
@@ -2059,6 +2064,7 @@
|
|
|
2059
2064
|
display: block;
|
|
2060
2065
|
object-fit: contain;
|
|
2061
2066
|
z-index: 1;
|
|
2067
|
+
filter: brightness(1);
|
|
2062
2068
|
}
|
|
2063
2069
|
|
|
2064
2070
|
/* ============================================================
|
|
@@ -2362,23 +2368,29 @@
|
|
|
2362
2368
|
padding: 4px 0;
|
|
2363
2369
|
}
|
|
2364
2370
|
|
|
2365
|
-
.eb-settings-submenu
|
|
2371
|
+
.eb-settings-submenu {
|
|
2366
2372
|
max-height: 200px;
|
|
2367
2373
|
overflow-y: auto;
|
|
2368
2374
|
}
|
|
2369
2375
|
|
|
2370
|
-
.eb-settings-submenu
|
|
2376
|
+
.eb-settings-submenu::-webkit-scrollbar {
|
|
2371
2377
|
width: 4px;
|
|
2372
2378
|
}
|
|
2373
2379
|
|
|
2374
|
-
.eb-settings-submenu
|
|
2380
|
+
.eb-settings-submenu::-webkit-scrollbar-thumb {
|
|
2375
2381
|
background: rgba(255, 255, 255, 0.3);
|
|
2376
2382
|
border-radius: 2px;
|
|
2377
2383
|
}
|
|
2378
2384
|
|
|
2385
|
+
.eb-settings-header {
|
|
2386
|
+
display: flex;
|
|
2387
|
+
align-items: center;
|
|
2388
|
+
gap: 4px;
|
|
2389
|
+
padding: 5px 12px;
|
|
2390
|
+
}
|
|
2391
|
+
|
|
2379
2392
|
.eb-settings-category,
|
|
2380
|
-
.eb-settings-item
|
|
2381
|
-
.eb-settings-back {
|
|
2393
|
+
.eb-settings-item {
|
|
2382
2394
|
display: flex;
|
|
2383
2395
|
align-items: center;
|
|
2384
2396
|
justify-content: space-between;
|
|
@@ -2392,9 +2404,18 @@
|
|
|
2392
2404
|
text-align: left;
|
|
2393
2405
|
}
|
|
2394
2406
|
|
|
2407
|
+
.eb-settings-back {
|
|
2408
|
+
display: flex;
|
|
2409
|
+
align-items: center;
|
|
2410
|
+
border: none;
|
|
2411
|
+
background: none;
|
|
2412
|
+
color: #fff;
|
|
2413
|
+
cursor: pointer;
|
|
2414
|
+
padding: 0;
|
|
2415
|
+
}
|
|
2416
|
+
|
|
2395
2417
|
.eb-settings-category:hover,
|
|
2396
|
-
.eb-settings-item:hover
|
|
2397
|
-
.eb-settings-back:hover {
|
|
2418
|
+
.eb-settings-item:hover {
|
|
2398
2419
|
background: rgba(255, 255, 255, 0.1);
|
|
2399
2420
|
}
|
|
2400
2421
|
|
|
@@ -2477,13 +2498,27 @@
|
|
|
2477
2498
|
white-space: nowrap;
|
|
2478
2499
|
pointer-events: none;
|
|
2479
2500
|
text-align: center;
|
|
2501
|
+
z-index: 50;
|
|
2502
|
+
overflow: hidden;
|
|
2480
2503
|
}
|
|
2481
2504
|
|
|
2482
2505
|
.eb-seekbar-preview {
|
|
2506
|
+
position: static !important;
|
|
2483
2507
|
width: 120px;
|
|
2484
|
-
height:
|
|
2485
|
-
|
|
2486
|
-
|
|
2508
|
+
height: 68px;
|
|
2509
|
+
min-width: 0 !important;
|
|
2510
|
+
min-height: 0 !important;
|
|
2511
|
+
max-width: none !important;
|
|
2512
|
+
max-height: none !important;
|
|
2513
|
+
display: block !important;
|
|
2514
|
+
object-fit: contain;
|
|
2515
|
+
background: #000;
|
|
2516
|
+
margin: 0 !important;
|
|
2517
|
+
padding: 0 !important;
|
|
2518
|
+
border: none !important;
|
|
2519
|
+
inset: auto !important;
|
|
2520
|
+
transform: none !important;
|
|
2521
|
+
z-index: auto !important;
|
|
2487
2522
|
}
|
|
2488
2523
|
|
|
2489
2524
|
.eb-chapter-marker {
|
|
@@ -2843,7 +2878,7 @@
|
|
|
2843
2878
|
Responsive: ensure container fills parent
|
|
2844
2879
|
============================================================ */
|
|
2845
2880
|
.eb-player,
|
|
2846
|
-
.eb-player video {
|
|
2881
|
+
.eb-player video.eb-video {
|
|
2847
2882
|
max-width: 100%;
|
|
2848
2883
|
max-height: 100%;
|
|
2849
2884
|
}
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.EBPlayer = {}));
|
|
5
5
|
})(this, (function (exports) { 'use strict';
|
|
6
6
|
|
|
7
|
-
var __EB_PLAYER_VERSION__ = "2.0.
|
|
7
|
+
var __EB_PLAYER_VERSION__ = "2.0.7";
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
10
|
* Finite State Machine for player playback state transitions.
|
|
@@ -1312,6 +1312,8 @@
|
|
|
1312
1312
|
this.tooltipX = 0;
|
|
1313
1313
|
this.previewVideoEl = null;
|
|
1314
1314
|
this.snapshotTake = null;
|
|
1315
|
+
this.trackEl = null;
|
|
1316
|
+
this.boundDocPointerMove = null;
|
|
1315
1317
|
}
|
|
1316
1318
|
onConnect() {
|
|
1317
1319
|
// Subscribe to snapshot handler readiness (emitted by eb-player.ts)
|
|
@@ -1332,6 +1334,10 @@
|
|
|
1332
1334
|
this.state.on('epgPrograms', () => this.scheduleRender(), { signal: this.signal });
|
|
1333
1335
|
this.render();
|
|
1334
1336
|
}
|
|
1337
|
+
disconnect() {
|
|
1338
|
+
this.stopDocumentTracking();
|
|
1339
|
+
super.disconnect();
|
|
1340
|
+
}
|
|
1335
1341
|
// ---- rAF batching ----
|
|
1336
1342
|
scheduleRender() {
|
|
1337
1343
|
if (this.rafPending)
|
|
@@ -1354,11 +1360,47 @@
|
|
|
1354
1360
|
const clamped = Math.min(1, Math.max(0, percent));
|
|
1355
1361
|
return clamped * this.state.duration;
|
|
1356
1362
|
}
|
|
1363
|
+
// ---- Document-level pointer tracking ----
|
|
1364
|
+
// pointerleave/mouseleave on the track are unreliable when the tooltip (a DOM child)
|
|
1365
|
+
// is absolutely positioned above the track — some browsers consider the pointer still
|
|
1366
|
+
// "inside" the track's subtree. Instead, we track pointer position at the document level
|
|
1367
|
+
// and hide the tooltip when the pointer moves outside the track's bounding rect.
|
|
1368
|
+
startDocumentTracking() {
|
|
1369
|
+
if (this.boundDocPointerMove)
|
|
1370
|
+
return;
|
|
1371
|
+
this.boundDocPointerMove = (event) => this.onDocPointerMove(event);
|
|
1372
|
+
document.addEventListener('pointermove', this.boundDocPointerMove);
|
|
1373
|
+
document.addEventListener('mouseleave', this.boundDocPointerMove);
|
|
1374
|
+
}
|
|
1375
|
+
stopDocumentTracking() {
|
|
1376
|
+
if (!this.boundDocPointerMove)
|
|
1377
|
+
return;
|
|
1378
|
+
document.removeEventListener('pointermove', this.boundDocPointerMove);
|
|
1379
|
+
document.removeEventListener('mouseleave', this.boundDocPointerMove);
|
|
1380
|
+
this.boundDocPointerMove = null;
|
|
1381
|
+
}
|
|
1382
|
+
onDocPointerMove(event) {
|
|
1383
|
+
if (this.isDragging)
|
|
1384
|
+
return;
|
|
1385
|
+
if (!this.trackEl)
|
|
1386
|
+
return;
|
|
1387
|
+
const rect = this.trackEl.getBoundingClientRect();
|
|
1388
|
+
const inBounds = event.clientX >= rect.left
|
|
1389
|
+
&& event.clientX <= rect.right
|
|
1390
|
+
&& event.clientY >= rect.top
|
|
1391
|
+
&& event.clientY <= rect.bottom;
|
|
1392
|
+
if (!inBounds) {
|
|
1393
|
+
this.tooltipVisible = false;
|
|
1394
|
+
this.stopDocumentTracking();
|
|
1395
|
+
this.render();
|
|
1396
|
+
}
|
|
1397
|
+
}
|
|
1357
1398
|
// ---- Drag handlers ----
|
|
1358
1399
|
handlePointerDown(event) {
|
|
1359
1400
|
if (this.state.adPlaying)
|
|
1360
1401
|
return;
|
|
1361
1402
|
const trackEl = event.currentTarget;
|
|
1403
|
+
this.trackEl = trackEl;
|
|
1362
1404
|
// setPointerCapture ensures events continue firing even if pointer leaves the element
|
|
1363
1405
|
if (typeof trackEl.setPointerCapture === 'function') {
|
|
1364
1406
|
trackEl.setPointerCapture(event.pointerId);
|
|
@@ -1389,20 +1431,16 @@
|
|
|
1389
1431
|
const seekTime = this.eventToTime(event, trackEl);
|
|
1390
1432
|
this.isDragging = false;
|
|
1391
1433
|
this.bus.emit('seek', { time: seekTime });
|
|
1392
|
-
//
|
|
1393
|
-
|
|
1394
|
-
if (event.clientX < rect.left || event.clientX > rect.right) {
|
|
1395
|
-
this.tooltipVisible = false;
|
|
1396
|
-
}
|
|
1397
|
-
this.render();
|
|
1398
|
-
}
|
|
1399
|
-
handlePointerLeave() {
|
|
1434
|
+
// Always hide tooltip on pointer up — it reappears naturally via pointermove
|
|
1435
|
+
// if the pointer is still over the track.
|
|
1400
1436
|
this.tooltipVisible = false;
|
|
1437
|
+
this.stopDocumentTracking();
|
|
1401
1438
|
this.render();
|
|
1402
1439
|
}
|
|
1403
1440
|
// ---- Tooltip ----
|
|
1404
1441
|
updateTooltip(event) {
|
|
1405
1442
|
const trackEl = event.currentTarget;
|
|
1443
|
+
this.trackEl = trackEl;
|
|
1406
1444
|
const rect = trackEl.getBoundingClientRect();
|
|
1407
1445
|
// Compute hover time (use LTR calculation for tooltip position regardless of RTL)
|
|
1408
1446
|
const rawPercent = (event.clientX - rect.left) / rect.width;
|
|
@@ -1413,6 +1451,8 @@
|
|
|
1413
1451
|
// Position tooltip at pointer X relative to track, clamped to track edges
|
|
1414
1452
|
this.tooltipX = Math.min(rect.width, Math.max(0, event.clientX - rect.left));
|
|
1415
1453
|
this.tooltipVisible = true;
|
|
1454
|
+
// Start document-level tracking to detect when pointer leaves the track
|
|
1455
|
+
this.startDocumentTracking();
|
|
1416
1456
|
// Request snapshot frame for seekbar preview thumbnail
|
|
1417
1457
|
if (this.snapshotTake !== null) {
|
|
1418
1458
|
this.snapshotTake(this.tooltipTime);
|
|
@@ -1505,11 +1545,10 @@
|
|
|
1505
1545
|
const tooltip = b `
|
|
1506
1546
|
<div
|
|
1507
1547
|
class="eb-seekbar-tooltip"
|
|
1508
|
-
style="left: ${this.tooltipX}px"
|
|
1509
|
-
?hidden="${!this.tooltipVisible}"
|
|
1548
|
+
style="left: ${this.tooltipX}px; visibility: ${this.tooltipVisible ? 'visible' : 'hidden'}"
|
|
1510
1549
|
>
|
|
1511
|
-
${tooltipTimeText}
|
|
1512
1550
|
${this.previewVideoEl !== null ? this.previewVideoEl : b ``}
|
|
1551
|
+
${tooltipTimeText}
|
|
1513
1552
|
</div>
|
|
1514
1553
|
`;
|
|
1515
1554
|
return b `
|
|
@@ -1522,7 +1561,6 @@
|
|
|
1522
1561
|
@pointerdown="${(event) => this.handlePointerDown(event)}"
|
|
1523
1562
|
@pointermove="${(event) => this.handlePointerMove(event)}"
|
|
1524
1563
|
@pointerup="${(event) => this.handlePointerUp(event)}"
|
|
1525
|
-
@pointerleave="${() => this.handlePointerLeave()}"
|
|
1526
1564
|
>
|
|
1527
1565
|
<div class="eb-seekbar-buffered" style="width: ${bufferedPercent.toFixed(2)}%"></div>
|
|
1528
1566
|
<div class="eb-seekbar-progress" style="width: ${progressPercent.toFixed(2)}%">
|
|
@@ -2220,13 +2258,24 @@
|
|
|
2220
2258
|
*
|
|
2221
2259
|
* - Calls screenfull.toggle() with the closest .eb-player ancestor on click
|
|
2222
2260
|
* - Subscribes to screenfull 'change' event to update state.isFullscreen
|
|
2223
|
-
* -
|
|
2261
|
+
* - Falls back to video-element fullscreen on mobile (iOS Safari webkitEnterFullscreen,
|
|
2262
|
+
* Android Chrome video.requestFullscreen) when the Fullscreen API is not available
|
|
2263
|
+
* for arbitrary elements
|
|
2224
2264
|
* - Re-renders when state.isFullscreen changes
|
|
2225
2265
|
*/
|
|
2226
2266
|
class FullscreenButton extends BaseComponent {
|
|
2227
2267
|
constructor() {
|
|
2228
2268
|
super(...arguments);
|
|
2229
2269
|
this.changeHandler = null;
|
|
2270
|
+
this.videoEl = null;
|
|
2271
|
+
this.videoFullscreenBeginHandler = null;
|
|
2272
|
+
this.videoFullscreenEndHandler = null;
|
|
2273
|
+
this.useVideoFallback = false;
|
|
2274
|
+
this.handleDocFullscreenChange = () => {
|
|
2275
|
+
const isFullscreen = document.fullscreenElement === this.videoEl;
|
|
2276
|
+
this.state.isFullscreen = isFullscreen;
|
|
2277
|
+
this.render();
|
|
2278
|
+
};
|
|
2230
2279
|
}
|
|
2231
2280
|
onConnect() {
|
|
2232
2281
|
this.state.on('isFullscreen', () => this.render(), { signal: this.signal });
|
|
@@ -2236,25 +2285,90 @@
|
|
|
2236
2285
|
this.render();
|
|
2237
2286
|
};
|
|
2238
2287
|
screenfull$1.on('change', this.changeHandler);
|
|
2239
|
-
// Unsubscribe when signal aborts (on disconnect)
|
|
2240
2288
|
this.signal.addEventListener('abort', () => {
|
|
2241
2289
|
screenfull$1.off('change', this.changeHandler);
|
|
2242
2290
|
this.changeHandler = null;
|
|
2243
2291
|
});
|
|
2244
2292
|
}
|
|
2293
|
+
else {
|
|
2294
|
+
// Fullscreen API not available for elements — try video-element fallback (mobile)
|
|
2295
|
+
this.initVideoFallback();
|
|
2296
|
+
}
|
|
2245
2297
|
this.render();
|
|
2246
2298
|
}
|
|
2299
|
+
/**
|
|
2300
|
+
* On mobile browsers the Fullscreen API may not work on arbitrary elements,
|
|
2301
|
+
* but the <video> element itself supports fullscreen via webkitEnterFullscreen
|
|
2302
|
+
* (iOS Safari) or video.requestFullscreen() (Android Chrome).
|
|
2303
|
+
*/
|
|
2304
|
+
initVideoFallback() {
|
|
2305
|
+
const playerRoot = this.el?.closest('.eb-player');
|
|
2306
|
+
if (!playerRoot)
|
|
2307
|
+
return;
|
|
2308
|
+
const video = playerRoot.querySelector('video.eb-video');
|
|
2309
|
+
if (!video)
|
|
2310
|
+
return;
|
|
2311
|
+
// iOS Safari: webkitSupportsFullscreen + webkitEnterFullscreen
|
|
2312
|
+
const hasWebkitFullscreen = typeof video.webkitEnterFullscreen === 'function';
|
|
2313
|
+
// Android Chrome: standard requestFullscreen on the video element
|
|
2314
|
+
const hasRequestFullscreen = typeof video.requestFullscreen === 'function';
|
|
2315
|
+
if (!hasWebkitFullscreen && !hasRequestFullscreen)
|
|
2316
|
+
return;
|
|
2317
|
+
this.videoEl = video;
|
|
2318
|
+
this.useVideoFallback = true;
|
|
2319
|
+
// Track fullscreen state changes on the video element
|
|
2320
|
+
this.videoFullscreenBeginHandler = () => {
|
|
2321
|
+
this.state.isFullscreen = true;
|
|
2322
|
+
this.render();
|
|
2323
|
+
};
|
|
2324
|
+
this.videoFullscreenEndHandler = () => {
|
|
2325
|
+
this.state.isFullscreen = false;
|
|
2326
|
+
this.render();
|
|
2327
|
+
};
|
|
2328
|
+
// iOS Safari fires these events on the video element
|
|
2329
|
+
video.addEventListener('webkitbeginfullscreen', this.videoFullscreenBeginHandler);
|
|
2330
|
+
video.addEventListener('webkitendfullscreen', this.videoFullscreenEndHandler);
|
|
2331
|
+
// Android Chrome fires standard fullscreenchange on the document
|
|
2332
|
+
document.addEventListener('fullscreenchange', this.handleDocFullscreenChange);
|
|
2333
|
+
this.signal.addEventListener('abort', () => {
|
|
2334
|
+
video.removeEventListener('webkitbeginfullscreen', this.videoFullscreenBeginHandler);
|
|
2335
|
+
video.removeEventListener('webkitendfullscreen', this.videoFullscreenEndHandler);
|
|
2336
|
+
document.removeEventListener('fullscreenchange', this.handleDocFullscreenChange);
|
|
2337
|
+
this.videoFullscreenBeginHandler = null;
|
|
2338
|
+
this.videoFullscreenEndHandler = null;
|
|
2339
|
+
this.videoEl = null;
|
|
2340
|
+
});
|
|
2341
|
+
}
|
|
2247
2342
|
handleClick() {
|
|
2248
|
-
if (
|
|
2343
|
+
if (screenfull$1.isEnabled) {
|
|
2344
|
+
const container = (this.el.closest('.eb-player') ?? this.el);
|
|
2345
|
+
screenfull$1.toggle(container);
|
|
2249
2346
|
return;
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
|
|
2347
|
+
}
|
|
2348
|
+
if (this.useVideoFallback && this.videoEl) {
|
|
2349
|
+
if (this.state.isFullscreen) {
|
|
2350
|
+
// Exit fullscreen
|
|
2351
|
+
if (typeof this.videoEl.webkitExitFullscreen === 'function') {
|
|
2352
|
+
this.videoEl.webkitExitFullscreen();
|
|
2353
|
+
}
|
|
2354
|
+
else if (document.exitFullscreen) {
|
|
2355
|
+
document.exitFullscreen();
|
|
2356
|
+
}
|
|
2357
|
+
}
|
|
2358
|
+
else {
|
|
2359
|
+
// Enter fullscreen
|
|
2360
|
+
if (typeof this.videoEl.webkitEnterFullscreen === 'function') {
|
|
2361
|
+
this.videoEl.webkitEnterFullscreen();
|
|
2362
|
+
}
|
|
2363
|
+
else if (typeof this.videoEl.requestFullscreen === 'function') {
|
|
2364
|
+
this.videoEl.requestFullscreen();
|
|
2365
|
+
}
|
|
2366
|
+
}
|
|
2367
|
+
}
|
|
2255
2368
|
}
|
|
2256
2369
|
template() {
|
|
2257
|
-
|
|
2370
|
+
const canFullscreen = screenfull$1.isEnabled || this.useVideoFallback;
|
|
2371
|
+
if (!canFullscreen) {
|
|
2258
2372
|
return b `<button class="eb-fullscreen" hidden aria-hidden="true">${icon('fullscreen')}</button>`;
|
|
2259
2373
|
}
|
|
2260
2374
|
const isFullscreen = this.state.isFullscreen;
|
|
@@ -4998,6 +5112,14 @@
|
|
|
4998
5112
|
getDriver() {
|
|
4999
5113
|
return this.driver;
|
|
5000
5114
|
}
|
|
5115
|
+
/**
|
|
5116
|
+
* Returns the CDN token manager used by this engine.
|
|
5117
|
+
* Allows the snapshot handler to share the same manager (avoids duplicate token requests).
|
|
5118
|
+
* Returns null when no token URL is configured or before init().
|
|
5119
|
+
*/
|
|
5120
|
+
getTokenManager() {
|
|
5121
|
+
return this.tokenManager;
|
|
5122
|
+
}
|
|
5001
5123
|
// -------------------------------------------------------------------------
|
|
5002
5124
|
// BaseEngine hooks
|
|
5003
5125
|
// -------------------------------------------------------------------------
|
|
@@ -5880,7 +6002,7 @@
|
|
|
5880
6002
|
init(HlsConstructor) {
|
|
5881
6003
|
// Create an off-screen video element for the snapshot player
|
|
5882
6004
|
const offscreenVideo = document.createElement('video');
|
|
5883
|
-
offscreenVideo.preload = '
|
|
6005
|
+
offscreenVideo.preload = 'metadata';
|
|
5884
6006
|
this.offscreenVideo = offscreenVideo;
|
|
5885
6007
|
// Capture tokenManager via closure (Pitfall 6)
|
|
5886
6008
|
const tokenManager = this.tokenManager;
|
|
@@ -5911,11 +6033,17 @@
|
|
|
5911
6033
|
}
|
|
5912
6034
|
PLoader = SnapshotPLoader;
|
|
5913
6035
|
}
|
|
6036
|
+
// Strip player-specific keys that are NOT hls.js config options — they contain
|
|
6037
|
+
// non-serializable functions that cause DataCloneError when hls.js posts config to its worker.
|
|
6038
|
+
const rawSettings = { ...(this.config.engineSettings ?? {}) };
|
|
6039
|
+
delete rawSettings['extraParamsCallback'];
|
|
6040
|
+
delete rawSettings['onCDNTokenError'];
|
|
5914
6041
|
const driverConfig = {
|
|
5915
6042
|
startLevel: 0,
|
|
5916
6043
|
enableWebVTT: false,
|
|
6044
|
+
enableWorker: false,
|
|
5917
6045
|
maxBufferLength: 1,
|
|
5918
|
-
...
|
|
6046
|
+
...rawSettings,
|
|
5919
6047
|
...(PLoader ? { pLoader: PLoader } : {})
|
|
5920
6048
|
};
|
|
5921
6049
|
const driver = new HlsConstructor(driverConfig);
|
|
@@ -6246,7 +6374,13 @@
|
|
|
6246
6374
|
else {
|
|
6247
6375
|
const win = window;
|
|
6248
6376
|
if (win.Hls) {
|
|
6249
|
-
|
|
6377
|
+
// Share the main engine's token manager with the snapshot handler
|
|
6378
|
+
// to avoid duplicate token requests (one CDNTokenManager per player instance)
|
|
6379
|
+
const sharedTokenManager = engine.getTokenManager();
|
|
6380
|
+
// Build DRM config (emeEnabled, drmSystems, licenseXhrSetup) for the snapshot hls.js instance
|
|
6381
|
+
const snapshotDrmConfigurator = new DrmConfigurator(sharedTokenManager);
|
|
6382
|
+
const snapshotDrmConfig = snapshotDrmConfigurator.buildHlsConfig(mergedConfig.engineSettings);
|
|
6383
|
+
const handler = new HlsSnapshotHandler({ src, engineSettings: { ...mergedConfig.engineSettings, ...snapshotDrmConfig } }, sharedTokenManager);
|
|
6250
6384
|
handler.init(win.Hls)
|
|
6251
6385
|
.then(() => {
|
|
6252
6386
|
activeSnapshotDestroy = () => handler.destroy();
|
|
@@ -6304,9 +6438,22 @@
|
|
|
6304
6438
|
}, 100);
|
|
6305
6439
|
});
|
|
6306
6440
|
// Auto-open the stream if src is provided in config (matches legacy player behaviour
|
|
6307
|
-
// where consumers call start({ src: '...' }) and expect playback to begin immediately)
|
|
6441
|
+
// where consumers call start({ src: '...' }) and expect playback to begin immediately).
|
|
6442
|
+
// When autoplay is false, defer open() until the user requests play — this avoids
|
|
6443
|
+
// fetching CDN tokens and loading manifests before playback is actually needed.
|
|
6308
6444
|
if (mergedConfig.src) {
|
|
6309
|
-
|
|
6445
|
+
if (mergedConfig.autoplay) {
|
|
6446
|
+
reference.open(mergedConfig.src);
|
|
6447
|
+
}
|
|
6448
|
+
else {
|
|
6449
|
+
let deferredOpen = true;
|
|
6450
|
+
controller.bus.on('play', () => {
|
|
6451
|
+
if (deferredOpen) {
|
|
6452
|
+
deferredOpen = false;
|
|
6453
|
+
reference.open(mergedConfig.src);
|
|
6454
|
+
}
|
|
6455
|
+
}, { signal: controller.signal });
|
|
6456
|
+
}
|
|
6310
6457
|
}
|
|
6311
6458
|
return reference;
|
|
6312
6459
|
}
|