eb-player 2.5.0 → 2.5.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 +82 -8
- package/dist/build/ebplayer.bundle.js.map +1 -1
- package/dist/build/types/src/core/fsm.d.ts +9 -3
- package/dist/build/types/src/core/fsm.d.ts.map +1 -1
- package/dist/build/types/src/eb-player.d.ts.map +1 -1
- package/dist/build/types/src/engines/base-engine.d.ts.map +1 -1
- package/dist/build/types/src/engines/hls.d.ts.map +1 -1
- package/dist/build/types/src/engines/retry/hls.d.ts +6 -0
- package/dist/build/types/src/engines/retry/hls.d.ts.map +1 -1
- package/dist/build/types/src/integrations/p2p-manager.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.5.
|
|
7
|
+
var __EB_PLAYER_VERSION__ = "2.5.2";
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
10
|
* Finite State Machine for player playback state transitions.
|
|
@@ -19,22 +19,28 @@
|
|
|
19
19
|
* - idle -> loading: initial load or post-stall reload
|
|
20
20
|
* - loading -> playing | error: stream starts or fails
|
|
21
21
|
* - playing -> paused | buffering | ended | error: normal playback events
|
|
22
|
-
* - paused -> playing | loading | idle | buffering: resume or
|
|
22
|
+
* - paused -> playing | loading | idle | buffering | ended: resume, stop, or reach end
|
|
23
23
|
* - paused -> buffering is needed when resuming a paused (live) stream where
|
|
24
24
|
* the browser fires 'waiting' before 'playing' (Safari live segment edge);
|
|
25
25
|
* without it the FSM strands at 'paused' while the video actually plays
|
|
26
|
-
*
|
|
26
|
+
* - paused -> ended is needed because the element always fires 'pause' before
|
|
27
|
+
* 'ended' when a VOD reaches its end
|
|
28
|
+
* - buffering -> playing | paused | idle | error | ended: buffer recovered, user pause, stall, or end
|
|
27
29
|
* - buffering -> idle is needed for the stall-watchdog recovery path (kick off reload)
|
|
28
30
|
* - buffering -> paused is needed for live stream support (user pauses during buffer)
|
|
31
|
+
* - buffering -> ended is needed because a tail 'waiting' firing just before the
|
|
32
|
+
* end can leave the FSM at 'buffering' at the moment 'ended' fires
|
|
29
33
|
* - error -> loading: retry after error
|
|
30
|
-
* - ended -> loading | idle: restart playback or reset
|
|
34
|
+
* - ended -> loading | idle: restart playback or reset. Recovery from 'ended'
|
|
35
|
+
* (seek-back, replay) routes explicitly through 'loading' rather than adding
|
|
36
|
+
* more direct exits here.
|
|
31
37
|
*/
|
|
32
38
|
const TRANSITIONS = {
|
|
33
39
|
idle: ['loading'],
|
|
34
40
|
loading: ['playing', 'buffering', 'paused', 'error', 'idle'],
|
|
35
41
|
playing: ['paused', 'buffering', 'ended', 'error', 'idle'],
|
|
36
|
-
paused: ['playing', 'loading', 'idle', 'buffering'],
|
|
37
|
-
buffering: ['playing', 'paused', 'idle', 'error'],
|
|
42
|
+
paused: ['playing', 'loading', 'idle', 'buffering', 'ended'],
|
|
43
|
+
buffering: ['playing', 'paused', 'idle', 'error', 'ended'],
|
|
38
44
|
error: ['loading', 'idle'],
|
|
39
45
|
ended: ['loading', 'idle']
|
|
40
46
|
};
|
|
@@ -5117,7 +5123,10 @@
|
|
|
5117
5123
|
video.addEventListener('pause', () => {
|
|
5118
5124
|
if (video.seeking)
|
|
5119
5125
|
return;
|
|
5120
|
-
|
|
5126
|
+
// Let 'ended' win: when the browser is pausing us because playback
|
|
5127
|
+
// actually reached the end (pause always fires before ended), don't
|
|
5128
|
+
// swallow it into the buffering branch below.
|
|
5129
|
+
if (state.playbackState === 'buffering' && !video.ended) {
|
|
5121
5130
|
userRequestedPause = true;
|
|
5122
5131
|
return;
|
|
5123
5132
|
}
|
|
@@ -5130,7 +5139,12 @@
|
|
|
5130
5139
|
state.currentTime = Math.round(video.currentTime);
|
|
5131
5140
|
state.isSyncWithLive = video.currentTime + syncMargin > video.duration;
|
|
5132
5141
|
}
|
|
5133
|
-
|
|
5142
|
+
// Restore 'paused' once a seek settles while the video stays paused.
|
|
5143
|
+
// Covers the existing pause-during-buffering case (userRequestedPause)
|
|
5144
|
+
// and seeking back from 'ended' (routed through 'loading' by the
|
|
5145
|
+
// engine's seek()), where no 'waiting'/'playing' event ever fires
|
|
5146
|
+
// because the video never resumes playing on its own.
|
|
5147
|
+
if ((userRequestedPause || state.playbackState === 'loading') && video.paused) {
|
|
5134
5148
|
state.playbackState = 'paused';
|
|
5135
5149
|
userRequestedPause = false;
|
|
5136
5150
|
}
|
|
@@ -5979,6 +5993,33 @@
|
|
|
5979
5993
|
const MAX_NON_FATAL_RETRIES = 3;
|
|
5980
5994
|
/** Error types that cannot be recovered by retrying (platform limitations) */
|
|
5981
5995
|
const UNRECOVERABLE_TYPES = new Set(['keySystemError']);
|
|
5996
|
+
/**
|
|
5997
|
+
* Margin (seconds) used to detect a VOD tail stall near end-of-stream.
|
|
5998
|
+
* Encoder-produced audio/video rendition length mismatches of a few tens to
|
|
5999
|
+
* ~100ms are common (observed: 27-77ms overhang) and can stall playback for
|
|
6000
|
+
* a second or two right at the end — indistinguishable from a genuinely stuck
|
|
6001
|
+
* stream by currentTime alone. 1s is a safe margin above that overhang.
|
|
6002
|
+
*/
|
|
6003
|
+
const NEAR_VOD_END_SECONDS = 1;
|
|
6004
|
+
/**
|
|
6005
|
+
* True when the driver is not live and both the playhead and the buffered
|
|
6006
|
+
* range already reach within NEAR_VOD_END_SECONDS of the media duration —
|
|
6007
|
+
* i.e. a tail stall that hls.js's own nudge/EOS handling can resolve on its
|
|
6008
|
+
* own, where a destructive recoverMediaError()/startLoad() would only race it
|
|
6009
|
+
* (observed: reload lands right as 'ended' would have fired naturally).
|
|
6010
|
+
*/
|
|
6011
|
+
function isNearVodEndStall(media, engine) {
|
|
6012
|
+
if (media === null || engine?.isLive === true)
|
|
6013
|
+
return false;
|
|
6014
|
+
const { duration, buffered, currentTime } = media;
|
|
6015
|
+
if (!Number.isFinite(duration) || duration <= 0)
|
|
6016
|
+
return false;
|
|
6017
|
+
if (duration - currentTime > NEAR_VOD_END_SECONDS)
|
|
6018
|
+
return false;
|
|
6019
|
+
if (buffered.length === 0)
|
|
6020
|
+
return false;
|
|
6021
|
+
return buffered.end(buffered.length - 1) >= duration - NEAR_VOD_END_SECONDS;
|
|
6022
|
+
}
|
|
5982
6023
|
function handleHlsRetry(driver, engine) {
|
|
5983
6024
|
/** Tracks consecutive non-fatal recovery attempts per error detail */
|
|
5984
6025
|
const nonFatalRetryCounts = new Map();
|
|
@@ -6044,6 +6085,10 @@
|
|
|
6044
6085
|
const errorTime = driver.media?.currentTime;
|
|
6045
6086
|
setTimeout(() => {
|
|
6046
6087
|
if (errorTime === driver.media?.currentTime && !engine?.chromecast_casting) {
|
|
6088
|
+
if (isNearVodEndStall(driver.media, engine)) {
|
|
6089
|
+
console.info('HLS Retry: near VOD end, letting hls.js finish naturally', event, data);
|
|
6090
|
+
return;
|
|
6091
|
+
}
|
|
6047
6092
|
console.warn('HLS Retry: Stream is stuck, trying to fix', event, data);
|
|
6048
6093
|
if (data.type === 'mediaError')
|
|
6049
6094
|
driver.recoverMediaError();
|
|
@@ -6467,7 +6512,19 @@
|
|
|
6467
6512
|
cfg.liveMaxLatencyDurationCount = Infinity;
|
|
6468
6513
|
this.liveSyncDisabled = true;
|
|
6469
6514
|
}
|
|
6515
|
+
// hls.js's StreamController parks itself in ENDED once the video reaches
|
|
6516
|
+
// its end and never resumes loading on its own — setting currentTime alone
|
|
6517
|
+
// leaves the seek stuck forever. Route through 'loading' and kick the
|
|
6518
|
+
// driver explicitly (deferred per Pitfall 3: never call startLoad
|
|
6519
|
+
// synchronously from a video event handler).
|
|
6520
|
+
const wasEnded = this.video.ended;
|
|
6470
6521
|
this.video.currentTime = time;
|
|
6522
|
+
if (wasEnded && this.driver !== null) {
|
|
6523
|
+
if (this.state)
|
|
6524
|
+
this.state.playbackState = 'loading';
|
|
6525
|
+
const driverRef = this.driver;
|
|
6526
|
+
setTimeout(() => driverRef.startLoad(time), 0);
|
|
6527
|
+
}
|
|
6471
6528
|
}
|
|
6472
6529
|
seekToLive() {
|
|
6473
6530
|
if (this.video === null)
|
|
@@ -6633,6 +6690,7 @@
|
|
|
6633
6690
|
if (config.retry) {
|
|
6634
6691
|
handleHlsRetry(driver, {
|
|
6635
6692
|
get chromecast_casting() { return state.isCasting; },
|
|
6693
|
+
get isLive() { return state.isLive; },
|
|
6636
6694
|
onUnrecoverableError: (message) => {
|
|
6637
6695
|
this.stopWatchdog();
|
|
6638
6696
|
if (this.state) {
|
|
@@ -6718,6 +6776,15 @@
|
|
|
6718
6776
|
// Defer startLoad to avoid re-entrancy (Pitfall 3)
|
|
6719
6777
|
setTimeout(() => driverRef.startLoad(-1), 0);
|
|
6720
6778
|
}
|
|
6779
|
+
else if (this.state?.playbackState === 'ended') {
|
|
6780
|
+
// Pressing Play at end-of-stream: the browser has already seeked to
|
|
6781
|
+
// the earliest position by the time this fires (evidenced live), so
|
|
6782
|
+
// video.currentTime is the correct resume point. Same StreamController
|
|
6783
|
+
// ENDED trap as seek() above — route through 'loading' and kick the driver.
|
|
6784
|
+
this.state.playbackState = 'loading';
|
|
6785
|
+
const resumeTime = this.video?.currentTime ?? 0;
|
|
6786
|
+
setTimeout(() => driverRef.startLoad(resumeTime), 0);
|
|
6787
|
+
}
|
|
6721
6788
|
}, { signal });
|
|
6722
6789
|
// Start stall watchdog
|
|
6723
6790
|
this.startWatchdog();
|
|
@@ -7435,6 +7502,12 @@
|
|
|
7435
7502
|
if (!config.lib || !config.manager)
|
|
7436
7503
|
return;
|
|
7437
7504
|
const sdk = await loadScript(config.lib, 'Easybroadcast');
|
|
7505
|
+
// The abort listener below is only registered after this await, so a
|
|
7506
|
+
// signal that already aborted during loadScript would otherwise be
|
|
7507
|
+
// missed entirely (AbortSignal only notifies listeners registered
|
|
7508
|
+
// before it fires) and lib.start() would run with no way to stop it.
|
|
7509
|
+
if (signal.aborted)
|
|
7510
|
+
return;
|
|
7438
7511
|
this.lib = new sdk.eblib.EBLibrary(config.manager);
|
|
7439
7512
|
const contentId = removeTokenAndCdnPath(config.src ?? '');
|
|
7440
7513
|
const libSettings = config.libSettings ?? {};
|
|
@@ -7820,6 +7893,7 @@
|
|
|
7820
7893
|
inst.engine.detach();
|
|
7821
7894
|
inst.engine = null;
|
|
7822
7895
|
}
|
|
7896
|
+
inst.p2p?.stop();
|
|
7823
7897
|
inst.p2p = null;
|
|
7824
7898
|
inst.controller.dispose();
|
|
7825
7899
|
}
|