eb-player 2.1.3 → 2.1.5
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.5";
|
|
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):
|
|
@@ -4420,6 +4421,11 @@
|
|
|
4420
4421
|
return this._container;
|
|
4421
4422
|
}
|
|
4422
4423
|
constructor(config) {
|
|
4424
|
+
// Captured BEFORE merging with DEFAULT_CONFIG: DEFAULT_CONFIG.primaryColor is a
|
|
4425
|
+
// non-empty fallback ('#1FA9DD'), unlike skinColor/skinColors.* which default to
|
|
4426
|
+
// ''. Without this flag, mount() cannot tell "caller explicitly set primaryColor"
|
|
4427
|
+
// apart from "value is just the default surviving the merge" — see mount() below.
|
|
4428
|
+
this.hasExplicitPrimaryColor = config.primaryColor !== undefined;
|
|
4423
4429
|
this.config = mergeConfig(DEFAULT_CONFIG, config);
|
|
4424
4430
|
this.state = createPlayerState();
|
|
4425
4431
|
this.bus = new TypedEventBus();
|
|
@@ -4440,7 +4446,9 @@
|
|
|
4440
4446
|
* managers based on config flags.
|
|
4441
4447
|
*
|
|
4442
4448
|
* CSS application order (later wins):
|
|
4443
|
-
* 1. primaryColor → --eb-color-primary
|
|
4449
|
+
* 1. primaryColor → --eb-color-primary (only when the caller explicitly set it —
|
|
4450
|
+
* an unset primaryColor falls back to DEFAULT_CONFIG's default and must not
|
|
4451
|
+
* override an active theme's own colors)
|
|
4444
4452
|
* 2. skinColors.general → --eb-color-primary (overrides primaryColor)
|
|
4445
4453
|
* 3. skinColors.progressBar → --eb-color-progress
|
|
4446
4454
|
* 4. skinColors.volumeBar → --eb-color-volume
|
|
@@ -4463,7 +4471,7 @@
|
|
|
4463
4471
|
}
|
|
4464
4472
|
// Apply runtime CSS custom properties for backward compat with primaryColor / skinColors
|
|
4465
4473
|
const skinColors = this.config.skinColors;
|
|
4466
|
-
if (this.config.primaryColor) {
|
|
4474
|
+
if (this.hasExplicitPrimaryColor && this.config.primaryColor) {
|
|
4467
4475
|
container.style.setProperty('--eb-color-primary', this.config.primaryColor);
|
|
4468
4476
|
container.style.setProperty('--eb-color-progress', this.config.primaryColor);
|
|
4469
4477
|
container.style.setProperty('--eb-accent', this.config.primaryColor);
|
|
@@ -4483,21 +4491,32 @@
|
|
|
4483
4491
|
// Set config-derived state flags
|
|
4484
4492
|
this.state.isRtl = this.config.lang === 'ar';
|
|
4485
4493
|
this.state.isRadio = this.config.radio === true;
|
|
4486
|
-
// Step 3: Create SkinRoot
|
|
4494
|
+
// Step 3: Create SkinRoot. This runs on EVERY mount, including noUi —
|
|
4495
|
+
// SkinRoot is the only creator of the <video> element, and its own
|
|
4496
|
+
// template() already branches to a headless template that renders just
|
|
4497
|
+
// the bare element (see renderNoUiTemplate() in skin-root.ts). This block
|
|
4498
|
+
// used to be gated on `!this.config.noUi`, which left that headless
|
|
4499
|
+
// branch unreachable from the real player: a noUi mount produced an
|
|
4500
|
+
// empty container, no <video> was ever handed to the engine, and
|
|
4501
|
+
// therefore start() issued no CDN token, no manifest and no segment
|
|
4502
|
+
// request at all.
|
|
4503
|
+
const skinRoot = new SkinRoot();
|
|
4504
|
+
this.skinRoot = skinRoot;
|
|
4505
|
+
skinRoot.connect(container, this.state, this.bus, this.config, this.i18n);
|
|
4506
|
+
// Sync initial muted/volume from the actual video element so UI matches
|
|
4507
|
+
// reality. The volumechange event only fires on *changes*, not on initial
|
|
4508
|
+
// attribute values set during element creation. The browser is the source
|
|
4509
|
+
// of truth — it may override the muted attribute based on autoplay policy.
|
|
4510
|
+
const videoEl = skinRoot.getVideoElement();
|
|
4511
|
+
if (videoEl !== null) {
|
|
4512
|
+
this.state.muted = videoEl.muted;
|
|
4513
|
+
this.state.volume = videoEl.volume;
|
|
4514
|
+
}
|
|
4515
|
+
// Step 4: Wire activity and keyboard controllers — chrome-only, so they
|
|
4516
|
+
// stay gated on noUi (D-03). AutoHide drives visibility for controls that
|
|
4517
|
+
// don't exist headlessly; Keyboard would claim page-level key handling
|
|
4518
|
+
// that a headless integrator expects to own itself.
|
|
4487
4519
|
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
4520
|
new AutoHideController(container, this.state, this.signal);
|
|
4502
4521
|
new KeyboardController(container, this.state, this.bus, this.config, this.signal);
|
|
4503
4522
|
}
|
|
@@ -4592,12 +4611,16 @@
|
|
|
4592
4611
|
const playlistManager = new PlaylistManager();
|
|
4593
4612
|
playlistManager.init(config, state, bus, signal);
|
|
4594
4613
|
}
|
|
4595
|
-
// AdsManager: skip when config.ad is undefined (D-09)
|
|
4614
|
+
// AdsManager: skip when config.ad is undefined (D-09), or when no ads
|
|
4615
|
+
// container exists — the headless template (D-04) renders none, so a
|
|
4616
|
+
// noUi mount must not construct AdsManager at all, not merely skip
|
|
4617
|
+
// init(). Constructing it unconditionally on skinRoot !== null would
|
|
4618
|
+
// regress now that SkinRoot is created in noUi mode too (D-01).
|
|
4596
4619
|
if (config.ad && this.skinRoot !== null) {
|
|
4597
|
-
const adsManager = new AdsManager();
|
|
4598
4620
|
const video = this.skinRoot.getVideoElement();
|
|
4599
4621
|
const adsContainer = this.skinRoot.getAdsContainer();
|
|
4600
4622
|
if (video !== null && adsContainer !== null) {
|
|
4623
|
+
const adsManager = new AdsManager();
|
|
4601
4624
|
adsManager.init(config, state, bus, video, adsContainer, signal).catch((error) => {
|
|
4602
4625
|
console.error('EBPlayer: AdsManager init failed:', error);
|
|
4603
4626
|
});
|
|
@@ -7531,10 +7554,11 @@
|
|
|
7531
7554
|
* (20s) so that more precise, better-logged inner mechanism wins first in a
|
|
7532
7555
|
* live player — this is the outer safety net, not the primary signal. Covers
|
|
7533
7556
|
* two cases the inner mechanism cannot: an AdsManager that was never
|
|
7534
|
-
* constructed at all (
|
|
7535
|
-
*
|
|
7536
|
-
*
|
|
7537
|
-
*
|
|
7557
|
+
* constructed at all (a headless player renders no ads container, so the
|
|
7558
|
+
* ads branch in lifecycle.ts is still skipped for that reason (D-04) — this
|
|
7559
|
+
* backstop applies unchanged to noUi + ad configurations), and an AdsManager
|
|
7560
|
+
* that obtained its manager from the loader (disarming its own bound) whose
|
|
7561
|
+
* creative then never reached the screen.
|
|
7538
7562
|
*/
|
|
7539
7563
|
const MAIN_OPEN_FALLBACK_MS = 30000;
|
|
7540
7564
|
// ---------------------------------------------------------------------------
|
|
@@ -7569,7 +7593,13 @@
|
|
|
7569
7593
|
// so the config-level swap is legitimate for that field only). NEVER mutate
|
|
7570
7594
|
// `mergedConfig.muted` — it is consumed only at video element creation in
|
|
7571
7595
|
// skin-root.ts:141/318 and is a no-op for runtime mute.
|
|
7572
|
-
|
|
7596
|
+
//
|
|
7597
|
+
// `mergedConfig.muted` is typed `boolean` (required, non-optional) on
|
|
7598
|
+
// PlayerConfig, and DEFAULT_CONFIG.muted is the boolean literal `true` —
|
|
7599
|
+
// mergeConfig()'s deepMerge always falls back to the base value for an
|
|
7600
|
+
// undefined override (see config.ts), so this field can never be nullish
|
|
7601
|
+
// through the real merge. No `?? false` fallback needed.
|
|
7602
|
+
const originalMuted = mergedConfig.muted;
|
|
7573
7603
|
const originalManager = mergedConfig.manager;
|
|
7574
7604
|
const originalStartAt = mergedConfig.startAt;
|
|
7575
7605
|
const controller = new PlayerController(runtimeConfig);
|
|
@@ -7748,6 +7778,21 @@
|
|
|
7748
7778
|
controller.bus.on('error-fatal', () => {
|
|
7749
7779
|
inst.p2p?.stop();
|
|
7750
7780
|
}, { signal: controller.signal });
|
|
7781
|
+
// Fail loud when mount() produced no video element (D-06). Without one,
|
|
7782
|
+
// engine.setVideo() is skipped inside open() (see the `video !== null`
|
|
7783
|
+
// guard above), so no engine ever attaches media and nothing is requested
|
|
7784
|
+
// at all — no CDN token, no manifest, no segments. No watchdog covers
|
|
7785
|
+
// this: the load-never-started watchdog lives inside the engine and is
|
|
7786
|
+
// only armed once a driver exists. state.error (not a bus emit) is the
|
|
7787
|
+
// channel here because start() has not returned the reference yet, so no
|
|
7788
|
+
// consumer can have subscribed to the bus at this point, whereas the
|
|
7789
|
+
// reactive state store is readable at any time afterwards.
|
|
7790
|
+
if (video === null) {
|
|
7791
|
+
const message = 'EBPlayer: no <video> element found in the container after mount() — playback cannot start.';
|
|
7792
|
+
console.error(message);
|
|
7793
|
+
controller.state.error = message;
|
|
7794
|
+
return reference;
|
|
7795
|
+
}
|
|
7751
7796
|
// Phase 7/8 — pre-main leg orchestration.
|
|
7752
7797
|
// When mergedConfig.preroll is set, open the intro stream first, then on
|
|
7753
7798
|
// bus 'intro-stream-complete' (emitted by IntroStreamManager on video.ended,
|
|
@@ -7784,9 +7829,11 @@
|
|
|
7784
7829
|
// same tick. complete() is idempotent, so on the normal ordering (intro
|
|
7785
7830
|
// already finished) this is a no-op; the listener is already torn down.
|
|
7786
7831
|
controller.bus.emit('force-intro-complete');
|
|
7787
|
-
|
|
7788
|
-
|
|
7789
|
-
|
|
7832
|
+
// D-06: `video` is guaranteed non-null here — the guard above (line ~415)
|
|
7833
|
+
// already returned early when mount() produced no <video>, and `video`
|
|
7834
|
+
// is a `const`, so TypeScript's control-flow narrowing carries the
|
|
7835
|
+
// non-null guarantee into this closure without a redundant re-check.
|
|
7836
|
+
video.muted = originalMuted;
|
|
7790
7837
|
controller.state.muted = originalMuted;
|
|
7791
7838
|
// CR-05: keep mergedConfig.manager and controller.config.manager in sync.
|
|
7792
7839
|
// start() builds mergedConfig and PlayerController builds its own merged
|
|
@@ -7871,7 +7918,9 @@
|
|
|
7871
7918
|
return;
|
|
7872
7919
|
deferredPlaybackStarted = true;
|
|
7873
7920
|
const engine = inst.engine;
|
|
7874
|
-
|
|
7921
|
+
// D-06: `video === null` is unreachable here — see the comment on the
|
|
7922
|
+
// equivalent guard in openMainStream() above.
|
|
7923
|
+
if (engine === null)
|
|
7875
7924
|
return;
|
|
7876
7925
|
// Honor the user's click intent. CommandHandler's earlier bus.on('play')
|
|
7877
7926
|
// listener fired before us with no source attached, so its video.play()
|
|
@@ -7941,9 +7990,9 @@
|
|
|
7941
7990
|
}, { signal: controller.signal });
|
|
7942
7991
|
// Force runtime mute off for the intro pass — applied to the LIVE <video>
|
|
7943
7992
|
// element and PlayerState, NOT to mergedConfig.muted (no-op for runtime).
|
|
7944
|
-
|
|
7945
|
-
|
|
7946
|
-
|
|
7993
|
+
// D-06: `video` is guaranteed non-null here — see the comment on the
|
|
7994
|
+
// equivalent restore in openMainStream() above.
|
|
7995
|
+
video.muted = false;
|
|
7947
7996
|
controller.state.muted = false;
|
|
7948
7997
|
// Skip P2P attach for the intro stream (D-08); restored before main open.
|
|
7949
7998
|
// CR-05: keep the dual-merged configs in sync (see openMainStream).
|