eb-player 2.1.3 → 2.1.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.
|
@@ -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.1.
|
|
7
|
+
var __EB_PLAYER_VERSION__ = "2.1.4";
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
10
|
* Finite State Machine for player playback state transitions.
|
|
@@ -4397,8 +4397,9 @@
|
|
|
4397
4397
|
* Mount order:
|
|
4398
4398
|
* 1. CSS vars / data-theme applied to container
|
|
4399
4399
|
* 2. Config-derived state (isRtl, isRadio) set
|
|
4400
|
-
* 3. SkinRoot connected (needs DOM) — creates video element
|
|
4401
|
-
*
|
|
4400
|
+
* 3. SkinRoot connected (needs DOM) — creates the video element on every
|
|
4401
|
+
* mount, including noUi; the ads container is only rendered in UI mode
|
|
4402
|
+
* 4. AutoHideController + KeyboardController wired (UI mode only)
|
|
4402
4403
|
* 5. Integrations initialized (need skin DOM for ads)
|
|
4403
4404
|
*
|
|
4404
4405
|
* Dispose order (reverse of mount):
|
|
@@ -4483,21 +4484,32 @@
|
|
|
4483
4484
|
// Set config-derived state flags
|
|
4484
4485
|
this.state.isRtl = this.config.lang === 'ar';
|
|
4485
4486
|
this.state.isRadio = this.config.radio === true;
|
|
4486
|
-
// Step 3: Create SkinRoot
|
|
4487
|
+
// Step 3: Create SkinRoot. This runs on EVERY mount, including noUi —
|
|
4488
|
+
// SkinRoot is the only creator of the <video> element, and its own
|
|
4489
|
+
// template() already branches to a headless template that renders just
|
|
4490
|
+
// the bare element (see renderNoUiTemplate() in skin-root.ts). This block
|
|
4491
|
+
// used to be gated on `!this.config.noUi`, which left that headless
|
|
4492
|
+
// branch unreachable from the real player: a noUi mount produced an
|
|
4493
|
+
// empty container, no <video> was ever handed to the engine, and
|
|
4494
|
+
// therefore start() issued no CDN token, no manifest and no segment
|
|
4495
|
+
// request at all.
|
|
4496
|
+
const skinRoot = new SkinRoot();
|
|
4497
|
+
this.skinRoot = skinRoot;
|
|
4498
|
+
skinRoot.connect(container, this.state, this.bus, this.config, this.i18n);
|
|
4499
|
+
// Sync initial muted/volume from the actual video element so UI matches
|
|
4500
|
+
// reality. The volumechange event only fires on *changes*, not on initial
|
|
4501
|
+
// attribute values set during element creation. The browser is the source
|
|
4502
|
+
// of truth — it may override the muted attribute based on autoplay policy.
|
|
4503
|
+
const videoEl = skinRoot.getVideoElement();
|
|
4504
|
+
if (videoEl !== null) {
|
|
4505
|
+
this.state.muted = videoEl.muted;
|
|
4506
|
+
this.state.volume = videoEl.volume;
|
|
4507
|
+
}
|
|
4508
|
+
// Step 4: Wire activity and keyboard controllers — chrome-only, so they
|
|
4509
|
+
// stay gated on noUi (D-03). AutoHide drives visibility for controls that
|
|
4510
|
+
// don't exist headlessly; Keyboard would claim page-level key handling
|
|
4511
|
+
// that a headless integrator expects to own itself.
|
|
4487
4512
|
if (!this.config.noUi) {
|
|
4488
|
-
const skinRoot = new SkinRoot();
|
|
4489
|
-
this.skinRoot = skinRoot;
|
|
4490
|
-
skinRoot.connect(container, this.state, this.bus, this.config, this.i18n);
|
|
4491
|
-
// Sync initial muted/volume from the actual video element so UI matches
|
|
4492
|
-
// reality. The volumechange event only fires on *changes*, not on initial
|
|
4493
|
-
// attribute values set during element creation. The browser is the source
|
|
4494
|
-
// of truth — it may override the muted attribute based on autoplay policy.
|
|
4495
|
-
const videoEl = skinRoot.getVideoElement();
|
|
4496
|
-
if (videoEl !== null) {
|
|
4497
|
-
this.state.muted = videoEl.muted;
|
|
4498
|
-
this.state.volume = videoEl.volume;
|
|
4499
|
-
}
|
|
4500
|
-
// Step 4: Wire activity and keyboard controllers
|
|
4501
4513
|
new AutoHideController(container, this.state, this.signal);
|
|
4502
4514
|
new KeyboardController(container, this.state, this.bus, this.config, this.signal);
|
|
4503
4515
|
}
|
|
@@ -4592,12 +4604,16 @@
|
|
|
4592
4604
|
const playlistManager = new PlaylistManager();
|
|
4593
4605
|
playlistManager.init(config, state, bus, signal);
|
|
4594
4606
|
}
|
|
4595
|
-
// AdsManager: skip when config.ad is undefined (D-09)
|
|
4607
|
+
// AdsManager: skip when config.ad is undefined (D-09), or when no ads
|
|
4608
|
+
// container exists — the headless template (D-04) renders none, so a
|
|
4609
|
+
// noUi mount must not construct AdsManager at all, not merely skip
|
|
4610
|
+
// init(). Constructing it unconditionally on skinRoot !== null would
|
|
4611
|
+
// regress now that SkinRoot is created in noUi mode too (D-01).
|
|
4596
4612
|
if (config.ad && this.skinRoot !== null) {
|
|
4597
|
-
const adsManager = new AdsManager();
|
|
4598
4613
|
const video = this.skinRoot.getVideoElement();
|
|
4599
4614
|
const adsContainer = this.skinRoot.getAdsContainer();
|
|
4600
4615
|
if (video !== null && adsContainer !== null) {
|
|
4616
|
+
const adsManager = new AdsManager();
|
|
4601
4617
|
adsManager.init(config, state, bus, video, adsContainer, signal).catch((error) => {
|
|
4602
4618
|
console.error('EBPlayer: AdsManager init failed:', error);
|
|
4603
4619
|
});
|
|
@@ -7531,10 +7547,11 @@
|
|
|
7531
7547
|
* (20s) so that more precise, better-logged inner mechanism wins first in a
|
|
7532
7548
|
* live player — this is the outer safety net, not the primary signal. Covers
|
|
7533
7549
|
* two cases the inner mechanism cannot: an AdsManager that was never
|
|
7534
|
-
* constructed at all (
|
|
7535
|
-
*
|
|
7536
|
-
*
|
|
7537
|
-
*
|
|
7550
|
+
* constructed at all (a headless player renders no ads container, so the
|
|
7551
|
+
* ads branch in lifecycle.ts is still skipped for that reason (D-04) — this
|
|
7552
|
+
* backstop applies unchanged to noUi + ad configurations), and an AdsManager
|
|
7553
|
+
* that obtained its manager from the loader (disarming its own bound) whose
|
|
7554
|
+
* creative then never reached the screen.
|
|
7538
7555
|
*/
|
|
7539
7556
|
const MAIN_OPEN_FALLBACK_MS = 30000;
|
|
7540
7557
|
// ---------------------------------------------------------------------------
|
|
@@ -7569,7 +7586,13 @@
|
|
|
7569
7586
|
// so the config-level swap is legitimate for that field only). NEVER mutate
|
|
7570
7587
|
// `mergedConfig.muted` — it is consumed only at video element creation in
|
|
7571
7588
|
// skin-root.ts:141/318 and is a no-op for runtime mute.
|
|
7572
|
-
|
|
7589
|
+
//
|
|
7590
|
+
// `mergedConfig.muted` is typed `boolean` (required, non-optional) on
|
|
7591
|
+
// PlayerConfig, and DEFAULT_CONFIG.muted is the boolean literal `true` —
|
|
7592
|
+
// mergeConfig()'s deepMerge always falls back to the base value for an
|
|
7593
|
+
// undefined override (see config.ts), so this field can never be nullish
|
|
7594
|
+
// through the real merge. No `?? false` fallback needed.
|
|
7595
|
+
const originalMuted = mergedConfig.muted;
|
|
7573
7596
|
const originalManager = mergedConfig.manager;
|
|
7574
7597
|
const originalStartAt = mergedConfig.startAt;
|
|
7575
7598
|
const controller = new PlayerController(runtimeConfig);
|
|
@@ -7748,6 +7771,21 @@
|
|
|
7748
7771
|
controller.bus.on('error-fatal', () => {
|
|
7749
7772
|
inst.p2p?.stop();
|
|
7750
7773
|
}, { signal: controller.signal });
|
|
7774
|
+
// Fail loud when mount() produced no video element (D-06). Without one,
|
|
7775
|
+
// engine.setVideo() is skipped inside open() (see the `video !== null`
|
|
7776
|
+
// guard above), so no engine ever attaches media and nothing is requested
|
|
7777
|
+
// at all — no CDN token, no manifest, no segments. No watchdog covers
|
|
7778
|
+
// this: the load-never-started watchdog lives inside the engine and is
|
|
7779
|
+
// only armed once a driver exists. state.error (not a bus emit) is the
|
|
7780
|
+
// channel here because start() has not returned the reference yet, so no
|
|
7781
|
+
// consumer can have subscribed to the bus at this point, whereas the
|
|
7782
|
+
// reactive state store is readable at any time afterwards.
|
|
7783
|
+
if (video === null) {
|
|
7784
|
+
const message = 'EBPlayer: no <video> element found in the container after mount() — playback cannot start.';
|
|
7785
|
+
console.error(message);
|
|
7786
|
+
controller.state.error = message;
|
|
7787
|
+
return reference;
|
|
7788
|
+
}
|
|
7751
7789
|
// Phase 7/8 — pre-main leg orchestration.
|
|
7752
7790
|
// When mergedConfig.preroll is set, open the intro stream first, then on
|
|
7753
7791
|
// bus 'intro-stream-complete' (emitted by IntroStreamManager on video.ended,
|
|
@@ -7784,9 +7822,11 @@
|
|
|
7784
7822
|
// same tick. complete() is idempotent, so on the normal ordering (intro
|
|
7785
7823
|
// already finished) this is a no-op; the listener is already torn down.
|
|
7786
7824
|
controller.bus.emit('force-intro-complete');
|
|
7787
|
-
|
|
7788
|
-
|
|
7789
|
-
|
|
7825
|
+
// D-06: `video` is guaranteed non-null here — the guard above (line ~415)
|
|
7826
|
+
// already returned early when mount() produced no <video>, and `video`
|
|
7827
|
+
// is a `const`, so TypeScript's control-flow narrowing carries the
|
|
7828
|
+
// non-null guarantee into this closure without a redundant re-check.
|
|
7829
|
+
video.muted = originalMuted;
|
|
7790
7830
|
controller.state.muted = originalMuted;
|
|
7791
7831
|
// CR-05: keep mergedConfig.manager and controller.config.manager in sync.
|
|
7792
7832
|
// start() builds mergedConfig and PlayerController builds its own merged
|
|
@@ -7871,7 +7911,9 @@
|
|
|
7871
7911
|
return;
|
|
7872
7912
|
deferredPlaybackStarted = true;
|
|
7873
7913
|
const engine = inst.engine;
|
|
7874
|
-
|
|
7914
|
+
// D-06: `video === null` is unreachable here — see the comment on the
|
|
7915
|
+
// equivalent guard in openMainStream() above.
|
|
7916
|
+
if (engine === null)
|
|
7875
7917
|
return;
|
|
7876
7918
|
// Honor the user's click intent. CommandHandler's earlier bus.on('play')
|
|
7877
7919
|
// listener fired before us with no source attached, so its video.play()
|
|
@@ -7941,9 +7983,9 @@
|
|
|
7941
7983
|
}, { signal: controller.signal });
|
|
7942
7984
|
// Force runtime mute off for the intro pass — applied to the LIVE <video>
|
|
7943
7985
|
// element and PlayerState, NOT to mergedConfig.muted (no-op for runtime).
|
|
7944
|
-
|
|
7945
|
-
|
|
7946
|
-
|
|
7986
|
+
// D-06: `video` is guaranteed non-null here — see the comment on the
|
|
7987
|
+
// equivalent restore in openMainStream() above.
|
|
7988
|
+
video.muted = false;
|
|
7947
7989
|
controller.state.muted = false;
|
|
7948
7990
|
// Skip P2P attach for the intro stream (D-08); restored before main open.
|
|
7949
7991
|
// CR-05: keep the dual-merged configs in sync (see openMainStream).
|