eb-player 2.1.0 → 2.1.2
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/ebplayer.bundle.js +147 -34
- package/dist/build/ebplayer.bundle.js.map +1 -1
- package/dist/build/types/src/core/event-bus.d.ts +3 -0
- package/dist/build/types/src/core/event-bus.d.ts.map +1 -1
- package/dist/build/types/src/eb-player.d.ts.map +1 -1
- package/dist/build/types/src/engines/hls.d.ts +24 -0
- package/dist/build/types/src/engines/hls.d.ts.map +1 -1
- package/dist/build/types/src/engines/segment-token-loader.d.ts +16 -3
- package/dist/build/types/src/engines/segment-token-loader.d.ts.map +1 -1
- package/package.json +1 -1
|
@@ -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.2";
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
10
|
* Finite State Machine for player playback state transitions.
|
|
@@ -5852,9 +5852,22 @@
|
|
|
5852
5852
|
}
|
|
5853
5853
|
}
|
|
5854
5854
|
/**
|
|
5855
|
-
* Returns a loader
|
|
5856
|
-
*
|
|
5857
|
-
*
|
|
5855
|
+
* Returns a loader constructor extending `BaseLoader`: construct the base,
|
|
5856
|
+
* bind the inherited `load`, then assign an own `load` property that reads
|
|
5857
|
+
* `context.frag?.baseurl`.
|
|
5858
|
+
*
|
|
5859
|
+
* NOT a native `class`: this constructor is registered in the base
|
|
5860
|
+
* `config.loader` slot, which the EasyBroadcast P2P SDK's hls adapter
|
|
5861
|
+
* subclasses with transpiled ES5 inheritance — its wrapper constructor
|
|
5862
|
+
* invokes the parent as `Parent.apply(this, arguments)`, which throws
|
|
5863
|
+
* "Class constructor ... cannot be invoked without 'new'" on a native
|
|
5864
|
+
* class (hls.js's own dist loaders are function-based for the same
|
|
5865
|
+
* reason). The function below therefore supports both invocation modes:
|
|
5866
|
+
* under `new` (hls.js pLoader/fLoader/loader slots) it defers to
|
|
5867
|
+
* `Reflect.construct` so a native-class base still works; under an
|
|
5868
|
+
* `.apply()`-style super call it initializes the wrapper's `this` in
|
|
5869
|
+
* place, which requires the base to be function-based — true of every
|
|
5870
|
+
* hls.js dist build this player loads.
|
|
5858
5871
|
*
|
|
5859
5872
|
* `tokenSource` is captured by closure — never stored on `this` and never
|
|
5860
5873
|
* used to acquire or refresh a token.
|
|
@@ -5868,37 +5881,46 @@
|
|
|
5868
5881
|
// hls.js needs the error signal each time to drive its own retry/fatal
|
|
5869
5882
|
// escalation; only the console spam is deduplicated.
|
|
5870
5883
|
let unsupportedTokenTypeLogged = false;
|
|
5871
|
-
|
|
5872
|
-
|
|
5873
|
-
|
|
5874
|
-
|
|
5875
|
-
|
|
5876
|
-
|
|
5877
|
-
|
|
5878
|
-
|
|
5879
|
-
|
|
5880
|
-
|
|
5881
|
-
|
|
5882
|
-
const
|
|
5883
|
-
if (!
|
|
5884
|
-
|
|
5885
|
-
|
|
5886
|
-
unsupportedTokenTypeLogged = true;
|
|
5887
|
-
console.error(message);
|
|
5888
|
-
}
|
|
5889
|
-
callbacks.onError({ code: 0, text: message }, context, null, createEmptySegmentLoaderStats());
|
|
5890
|
-
return;
|
|
5891
|
-
}
|
|
5892
|
-
const baseUrl = context.frag?.baseurl;
|
|
5893
|
-
if (!baseUrl || !context.url) {
|
|
5894
|
-
originalLoad(context, loadConfig, callbacks);
|
|
5895
|
-
return;
|
|
5884
|
+
function attachSigningLoad(instance) {
|
|
5885
|
+
const originalLoad = instance.load.bind(instance);
|
|
5886
|
+
instance.load = function (context, loadConfig, callbacks) {
|
|
5887
|
+
// D-03/D-04: a token type outside the query-param family (akamai, or
|
|
5888
|
+
// none configured) must fail visibly rather than emit an unsigned
|
|
5889
|
+
// request. A silent passthrough here is the same undiagnosable 403
|
|
5890
|
+
// from the edge that D-04 forbids, so it gets the same treatment as
|
|
5891
|
+
// the akamai case: no delegation to the base loader, and a named
|
|
5892
|
+
// error surfaced through hls.js's own error callback.
|
|
5893
|
+
const tokenType = tokenSource.tokenType;
|
|
5894
|
+
if (!tokenType || !SEGMENT_SIGNING_TOKEN_TYPES.includes(tokenType)) {
|
|
5895
|
+
const message = segmentSigningUnsupportedMessage(tokenType);
|
|
5896
|
+
if (!unsupportedTokenTypeLogged) {
|
|
5897
|
+
unsupportedTokenTypeLogged = true;
|
|
5898
|
+
console.error(message);
|
|
5896
5899
|
}
|
|
5897
|
-
|
|
5900
|
+
callbacks.onError({ code: 0, text: message }, context, null, createEmptySegmentLoaderStats());
|
|
5901
|
+
return;
|
|
5902
|
+
}
|
|
5903
|
+
const baseUrl = context.frag?.baseurl;
|
|
5904
|
+
if (!baseUrl || !context.url) {
|
|
5898
5905
|
originalLoad(context, loadConfig, callbacks);
|
|
5899
|
-
|
|
5906
|
+
return;
|
|
5907
|
+
}
|
|
5908
|
+
context.url = signSegmentUrlFromPlaylist(context.url, baseUrl);
|
|
5909
|
+
originalLoad(context, loadConfig, callbacks);
|
|
5910
|
+
};
|
|
5911
|
+
}
|
|
5912
|
+
function SegmentTokenLoader(loaderConfig) {
|
|
5913
|
+
if (new.target) {
|
|
5914
|
+
const instance = Reflect.construct(BaseLoader, [loaderConfig], new.target);
|
|
5915
|
+
attachSigningLoad(instance);
|
|
5916
|
+
return instance;
|
|
5900
5917
|
}
|
|
5918
|
+
BaseLoader.call(this, loaderConfig);
|
|
5919
|
+
attachSigningLoad(this);
|
|
5920
|
+
return undefined;
|
|
5901
5921
|
}
|
|
5922
|
+
SegmentTokenLoader.prototype = Object.create(BaseLoader.prototype);
|
|
5923
|
+
SegmentTokenLoader.prototype.constructor = SegmentTokenLoader;
|
|
5902
5924
|
return SegmentTokenLoader;
|
|
5903
5925
|
}
|
|
5904
5926
|
|
|
@@ -5915,6 +5937,17 @@
|
|
|
5915
5937
|
* CRITICAL: Call applyDiscontinuityWorkaround BEFORE driver.attachMedia/loadSource (Pitfall 4).
|
|
5916
5938
|
* CRITICAL: Never call driver.startLoad() from video 'play' event handler (Pitfall 3).
|
|
5917
5939
|
*/
|
|
5940
|
+
/**
|
|
5941
|
+
* Bound for the "load never started" watchdog: hls.js's own manifest-loading
|
|
5942
|
+
* budget (default manifestLoadingTimeOut 10 000 ms with one retry plus
|
|
5943
|
+
* backoff) tops out around 21s before hls.js surfaces its own fatal network
|
|
5944
|
+
* error, so 30s guarantees hls.js's own error path always wins first and
|
|
5945
|
+
* this watchdog only ever sees the case hls.js is silent about — a
|
|
5946
|
+
* StreamController parked in STOPPED with no error and no event. 30s also
|
|
5947
|
+
* matches the existing MAIN_OPEN_FALLBACK_MS precedent in src/eb-player.ts
|
|
5948
|
+
* and sits well below INTRO_MAX_DURATION_MS (90 000).
|
|
5949
|
+
*/
|
|
5950
|
+
const LOAD_NEVER_STARTED_TIMEOUT_MS = 30000;
|
|
5918
5951
|
// ---------------------------------------------------------------------------
|
|
5919
5952
|
// HlsEngine
|
|
5920
5953
|
// ---------------------------------------------------------------------------
|
|
@@ -5927,6 +5960,9 @@
|
|
|
5927
5960
|
this.autoQuality = true;
|
|
5928
5961
|
// Holds state reference for named driver event handlers
|
|
5929
5962
|
this.eventState = null;
|
|
5963
|
+
// Bound "load never started" watchdog timer — 0-initialized (not null) so
|
|
5964
|
+
// clearTimeout(0) is a no-op, eliminating a null-guard phantom V8 branch.
|
|
5965
|
+
this.loadNeverStartedTimer = 0;
|
|
5930
5966
|
this.liveSyncDisabled = false;
|
|
5931
5967
|
}
|
|
5932
5968
|
getDriver() {
|
|
@@ -5960,6 +5996,7 @@
|
|
|
5960
5996
|
}
|
|
5961
5997
|
onDetach() {
|
|
5962
5998
|
this.stopWatchdog();
|
|
5999
|
+
this.clearLoadNeverStartedWatchdog();
|
|
5963
6000
|
if (this.driver !== null) {
|
|
5964
6001
|
try {
|
|
5965
6002
|
this.driver.detachMedia();
|
|
@@ -6086,8 +6123,17 @@
|
|
|
6086
6123
|
delete hlsSafeSettings['extraParamsCallback'];
|
|
6087
6124
|
delete hlsSafeSettings['onCDNTokenError'];
|
|
6088
6125
|
delete hlsSafeSettings['signSegmentsWithCdnToken'];
|
|
6126
|
+
// Do NOT set an explicit auto-start disable here. hls.js starts loading
|
|
6127
|
+
// segments on its own by default, and the resume position is already
|
|
6128
|
+
// carried by startPosition below — hls.js honours it without any help.
|
|
6129
|
+
// Nothing on the VOD path would ever restart a stopped loader: the only
|
|
6130
|
+
// two startLoad() call sites in this file are recoverFromStall() (only
|
|
6131
|
+
// reachable after playback has started) and the video 'play' listener
|
|
6132
|
+
// (gated on state.isLive, so VOD never reaches it). Disabling auto-start
|
|
6133
|
+
// here parks the StreamController in STOPPED forever for any non-zero
|
|
6134
|
+
// resume position — no variant playlist, no segments, no error, no bus
|
|
6135
|
+
// event, spinner forever.
|
|
6089
6136
|
const driverConfig = {
|
|
6090
|
-
autoStartLoad: !config.startAt,
|
|
6091
6137
|
enableWorker: true,
|
|
6092
6138
|
backBufferLength: 30,
|
|
6093
6139
|
...hlsSafeSettings,
|
|
@@ -6133,7 +6179,19 @@
|
|
|
6133
6179
|
// own parent playlist. Off by default so segment request shapes stay
|
|
6134
6180
|
// byte-identical to today's when the flag is unset.
|
|
6135
6181
|
if (config.engineSettings.signSegmentsWithCdnToken === true) {
|
|
6136
|
-
|
|
6182
|
+
const SegmentTokenLoader = createSegmentTokenLoaderClass(Hls.DefaultConfig.loader, tm);
|
|
6183
|
+
driverConfig.fLoader = SegmentTokenLoader;
|
|
6184
|
+
// The base `loader` slot must sign too: the EasyBroadcast P2P SDK's
|
|
6185
|
+
// hls adapter replaces `config.fLoader` at integrate() time with a
|
|
6186
|
+
// wrapper class that extends `config.loader` and overrides only
|
|
6187
|
+
// loadInternal(), routing segment fetches through its own
|
|
6188
|
+
// resource manager (V2V + CDN-fallback fetch of context.url
|
|
6189
|
+
// verbatim). Signing lives in load(), which the wrapper inherits,
|
|
6190
|
+
// so every URL entering that path — and hls.js's key loader, which
|
|
6191
|
+
// also instantiates `config.loader` — is signed before any request
|
|
6192
|
+
// is issued. Without this, V2V CDN fallbacks go out unsigned (403
|
|
6193
|
+
// on token-protected origins).
|
|
6194
|
+
driverConfig.loader = SegmentTokenLoader;
|
|
6137
6195
|
}
|
|
6138
6196
|
}
|
|
6139
6197
|
// Inject custom ABR controller unless disabled
|
|
@@ -6157,6 +6215,11 @@
|
|
|
6157
6215
|
this.driver = driver;
|
|
6158
6216
|
// Pitfall 4: apply discontinuity workaround BEFORE attachMedia/loadSource
|
|
6159
6217
|
applyDiscontinuityWorkaround(driver, Hls.Events);
|
|
6218
|
+
// Hoisted once, used both for the retry wiring below and for gating the
|
|
6219
|
+
// "load never started" watchdog — the intro pass already has its own
|
|
6220
|
+
// bounds (INTRO_MAX_DURATION_MS, D-04 silent fall-through) and a second
|
|
6221
|
+
// overlapping bound would only race them.
|
|
6222
|
+
const isIntroPass = Boolean(config.preroll) && this.loadSourceUrl === config.preroll;
|
|
6160
6223
|
// Wire retry handler — pass engine context so unrecoverable DRM errors
|
|
6161
6224
|
// stop the stall watchdog (prevents useless reload loops) and surface to UI.
|
|
6162
6225
|
// For the intro pass (loadSourceUrl matches config.preroll), also wire
|
|
@@ -6165,7 +6228,6 @@
|
|
|
6165
6228
|
// This callback is intentionally NOT set for the main stream so transient
|
|
6166
6229
|
// network blips still trigger the normal startLoad() recovery.
|
|
6167
6230
|
if (config.retry) {
|
|
6168
|
-
const isIntroPass = Boolean(config.preroll) && this.loadSourceUrl === config.preroll;
|
|
6169
6231
|
handleHlsRetry(driver, {
|
|
6170
6232
|
get chromecast_casting() { return state.isCasting; },
|
|
6171
6233
|
onUnrecoverableError: (message) => {
|
|
@@ -6232,6 +6294,8 @@
|
|
|
6232
6294
|
this.resolveDriverReady();
|
|
6233
6295
|
// Register driver event handlers
|
|
6234
6296
|
this.registerDriverEvents(Hls, state);
|
|
6297
|
+
// Arm the "load never started" watchdog — see LOAD_NEVER_STARTED_TIMEOUT_MS.
|
|
6298
|
+
this.armLoadNeverStartedWatchdog(driver, Hls, src, isIntroPass);
|
|
6235
6299
|
// Pause/resume loading on video pause/play to stop manifest refreshes
|
|
6236
6300
|
// (and thus CDN token requests) while the player is paused.
|
|
6237
6301
|
// Only applies to live streams where hls.js continuously refreshes the manifest.
|
|
@@ -6300,6 +6364,43 @@
|
|
|
6300
6364
|
driver.on(Events.SUBTITLE_TRACKS_UPDATED, this._onSubtitleTracksUpdated.bind(this));
|
|
6301
6365
|
driver.on(Events.SUBTITLE_TRACK_SWITCH, this._onSubtitleTrackSwitch.bind(this));
|
|
6302
6366
|
}
|
|
6367
|
+
// -------------------------------------------------------------------------
|
|
6368
|
+
// "Load never started" watchdog
|
|
6369
|
+
// -------------------------------------------------------------------------
|
|
6370
|
+
/**
|
|
6371
|
+
* Arms a one-shot bound that turns an unobservable hang into `state.error`
|
|
6372
|
+
* + a typed bus event: hls.js emits neither when its StreamController
|
|
6373
|
+
* never leaves STOPPED. Disarmed by the first FRAG_LOADING (proof that a
|
|
6374
|
+
* fragment request was actually issued, whether or not playback was
|
|
6375
|
+
* allowed to start — so autoplay-blocked playback never false-positives).
|
|
6376
|
+
* Never armed for the intro pass — it already has its own bounds.
|
|
6377
|
+
*/
|
|
6378
|
+
armLoadNeverStartedWatchdog(driver, Hls, src, isIntroPass) {
|
|
6379
|
+
if (isIntroPass)
|
|
6380
|
+
return;
|
|
6381
|
+
driver.on(Hls.Events.FRAG_LOADING, this._onFragLoading.bind(this));
|
|
6382
|
+
this.loadNeverStartedTimer = setTimeout(this._onLoadNeverStartedTimeout.bind(this, src), LOAD_NEVER_STARTED_TIMEOUT_MS);
|
|
6383
|
+
}
|
|
6384
|
+
clearLoadNeverStartedWatchdog() {
|
|
6385
|
+
clearTimeout(this.loadNeverStartedTimer);
|
|
6386
|
+
this.loadNeverStartedTimer = 0;
|
|
6387
|
+
}
|
|
6388
|
+
_onFragLoading() {
|
|
6389
|
+
this.clearLoadNeverStartedWatchdog();
|
|
6390
|
+
}
|
|
6391
|
+
_onLoadNeverStartedTimeout(src) {
|
|
6392
|
+
if (this.detached)
|
|
6393
|
+
return;
|
|
6394
|
+
// Two-signal live gate (mirrors _onLevelLoaded): a stream that never
|
|
6395
|
+
// loaded a level detail never got its isLive set from the driver, so
|
|
6396
|
+
// the config-level signal must also be checked.
|
|
6397
|
+
if (this.state?.isLive === true || this.config?.isLive === true)
|
|
6398
|
+
return;
|
|
6399
|
+
if (this.state) {
|
|
6400
|
+
this.state.error = 'HlsEngine: load never started — no fragment request was issued within the bound';
|
|
6401
|
+
}
|
|
6402
|
+
this.bus?.emit('load-never-started', { src });
|
|
6403
|
+
}
|
|
6303
6404
|
_onManifestLoaded(_event, data) {
|
|
6304
6405
|
const state = this.eventState;
|
|
6305
6406
|
const driver = this.driver;
|
|
@@ -7404,6 +7505,7 @@
|
|
|
7404
7505
|
// skin-root.ts:141/318 and is a no-op for runtime mute.
|
|
7405
7506
|
const originalMuted = mergedConfig.muted ?? false;
|
|
7406
7507
|
const originalManager = mergedConfig.manager;
|
|
7508
|
+
const originalStartAt = mergedConfig.startAt;
|
|
7407
7509
|
const controller = new PlayerController(runtimeConfig);
|
|
7408
7510
|
controller.mount(container);
|
|
7409
7511
|
// Video element is available after mount() creates the skin DOM
|
|
@@ -7629,6 +7731,10 @@
|
|
|
7629
7731
|
// during the intro pass. Synchronize both objects on every swap.
|
|
7630
7732
|
mergedConfig.manager = originalManager;
|
|
7631
7733
|
controller.config.manager = originalManager;
|
|
7734
|
+
// Restore the viewer's resume position for the main asset — see the
|
|
7735
|
+
// rationale comment at the intro-side swap below (CR-05 sync applies here too).
|
|
7736
|
+
mergedConfig.startAt = originalStartAt;
|
|
7737
|
+
controller.config.startAt = originalStartAt;
|
|
7632
7738
|
// CR-03: clear any stale error that leaked from the intro/ad pass.
|
|
7633
7739
|
// IntroStreamManager's silent fall-through (D-04) treats error-fatal as a
|
|
7634
7740
|
// normal completion but leaves state.error populated with the intro
|
|
@@ -7777,6 +7883,13 @@
|
|
|
7777
7883
|
// CR-05: keep the dual-merged configs in sync (see openMainStream).
|
|
7778
7884
|
mergedConfig.manager = false;
|
|
7779
7885
|
controller.config.manager = false;
|
|
7886
|
+
// A viewer resume position belongs to the main asset only — applying it
|
|
7887
|
+
// to a short intro clip seeks past the whole clip. This defect survives
|
|
7888
|
+
// the startAt/autoStartLoad fix: after that fix, startPosition: 300 is
|
|
7889
|
+
// still applied to the intro, which is still wrong, just differently
|
|
7890
|
+
// wrong. Cleared here and restored in openMainStream() (CR-05 sync).
|
|
7891
|
+
mergedConfig.startAt = undefined;
|
|
7892
|
+
controller.config.startAt = undefined;
|
|
7780
7893
|
reference.open(mergedConfig.preroll);
|
|
7781
7894
|
}
|
|
7782
7895
|
// D-02/D-08-02 (Phase 8): ad-only configuration — no intro stream, so this
|