eb-player 2.0.8 → 2.0.10

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.0.7";
7
+ var __EB_PLAYER_VERSION__ = "2.0.10";
8
8
 
9
9
  /**
10
10
  * Finite State Machine for player playback state transitions.
@@ -4487,6 +4487,7 @@
4487
4487
  this.expirationMarginInSeconds = expirationMarginInSeconds;
4488
4488
  this.lastTokenResponse = null;
4489
4489
  this.resetAttemptCounterTimeout = null;
4490
+ this.inFlightFetch = null;
4490
4491
  }
4491
4492
  resetAttemptCounter() {
4492
4493
  if (this.resetAttemptCounterTimeout) {
@@ -4730,21 +4731,35 @@
4730
4731
  console.warn('CDNToken: Missing src to tokenize');
4731
4732
  return null;
4732
4733
  }
4733
- switch (this.tokenType) {
4734
- case TOKEN_TYPES.BUNNY:
4735
- this.lastTokenResponse = await this._fetchBunnyToken(src);
4736
- break;
4737
- case TOKEN_TYPES.AKAMAI:
4738
- this.lastTokenResponse = await this._fetchAkamaiToken(src);
4739
- break;
4740
- case TOKEN_TYPES.VENOM:
4741
- case TOKEN_TYPES.EASY_B:
4742
- this.lastTokenResponse = await this._fetchEasyBToken(src);
4743
- break;
4744
- default:
4745
- this.lastTokenResponse = await this._fetchDefaultToken(src);
4734
+ // Deduplicate concurrent fetches: if a fetch is already in flight,
4735
+ // return the same promise instead of firing a second network request.
4736
+ // This prevents the main player and snapshot handler from both triggering
4737
+ // separate generate calls when their manifest refreshes overlap.
4738
+ if (this.inFlightFetch) {
4739
+ return this.inFlightFetch;
4746
4740
  }
4747
- return this.lastTokenResponse;
4741
+ const doFetch = async () => {
4742
+ switch (this.tokenType) {
4743
+ case TOKEN_TYPES.BUNNY:
4744
+ return this._fetchBunnyToken(src);
4745
+ case TOKEN_TYPES.AKAMAI:
4746
+ return this._fetchAkamaiToken(src);
4747
+ case TOKEN_TYPES.VENOM:
4748
+ case TOKEN_TYPES.EASY_B:
4749
+ return this._fetchEasyBToken(src);
4750
+ default:
4751
+ return this._fetchDefaultToken(src);
4752
+ }
4753
+ };
4754
+ this.inFlightFetch = doFetch().then((response) => {
4755
+ this.lastTokenResponse = response;
4756
+ this.inFlightFetch = null;
4757
+ return response;
4758
+ }).catch((error) => {
4759
+ this.inFlightFetch = null;
4760
+ throw error;
4761
+ });
4762
+ return this.inFlightFetch;
4748
4763
  }
4749
4764
  // -------------------------------------------------------------------------
4750
4765
  // Public: updateUrlWithTokenParams
@@ -4847,6 +4862,7 @@
4847
4862
  }
4848
4863
  this.attempt = 0;
4849
4864
  this.lastTokenResponse = null;
4865
+ this.inFlightFetch = null;
4850
4866
  console.log('CDNTokenManager: destroyed');
4851
4867
  }
4852
4868
  // -------------------------------------------------------------------------
@@ -5244,7 +5260,9 @@
5244
5260
  if (!hlsjsUrl) {
5245
5261
  throw new Error('HlsEngine: config.hlsjs URL is required');
5246
5262
  }
5247
- // Create token manager if a token URL is configured
5263
+ // Create token manager if a token URL is configured.
5264
+ // No initial fetchToken() here — updateUrlWithTokenParams() below handles it
5265
+ // and populates lastTokenResponse (used lazily by DRM licenseXhrSetup).
5248
5266
  if (config.token) {
5249
5267
  this.tokenManager = new CDNTokenManager({
5250
5268
  token: config.token,
@@ -5253,13 +5271,6 @@
5253
5271
  extraParamsCallback: (config.engineSettings.extraParamsCallback ?? config.extraParamsCallback),
5254
5272
  onCDNTokenError: config.engineSettings.onCDNTokenError
5255
5273
  });
5256
- // Fetch initial token
5257
- if (config.src) {
5258
- await this.tokenManager.fetchToken({ src: config.src });
5259
- }
5260
- // Guard: abort if detached during token fetch
5261
- if (this.detached)
5262
- return;
5263
5274
  }
5264
5275
  // console.info('HlsEngine: loading hls.js from', hlsjsUrl)
5265
5276
  const Hls = await loadScript(hlsjsUrl, 'Hls');
@@ -5342,7 +5353,6 @@
5342
5353
  // Create the driver (NEVER stored in state)
5343
5354
  const driver = new Hls(driverConfig);
5344
5355
  this.driver = driver;
5345
- this.resolveDriverReady();
5346
5356
  // Pitfall 4: apply discontinuity workaround BEFORE attachMedia/loadSource
5347
5357
  applyDiscontinuityWorkaround(driver, Hls.Events);
5348
5358
  // Wire retry handler
@@ -5365,8 +5375,27 @@
5365
5375
  }
5366
5376
  }
5367
5377
  driver.loadSource(src);
5378
+ // Resolve driverReady AFTER loadSource so consumers (P2P, snapshot handler)
5379
+ // don't start making token requests before the main engine's initial token
5380
+ // fetch completes and populates the cache.
5381
+ this.resolveDriverReady();
5368
5382
  // Register driver event handlers
5369
5383
  this.registerDriverEvents(Hls, state);
5384
+ // Pause/resume loading on video pause/play to stop manifest refreshes
5385
+ // (and thus CDN token requests) while the player is paused.
5386
+ // Only applies to live streams where hls.js continuously refreshes the manifest.
5387
+ const driverRef = driver;
5388
+ video.addEventListener('pause', () => {
5389
+ if (this.state?.isLive) {
5390
+ driverRef.stopLoad();
5391
+ }
5392
+ }, { signal });
5393
+ video.addEventListener('play', () => {
5394
+ if (this.state?.isLive) {
5395
+ // Defer startLoad to avoid re-entrancy (Pitfall 3)
5396
+ setTimeout(() => driverRef.startLoad(-1), 0);
5397
+ }
5398
+ }, { signal });
5370
5399
  // Start stall watchdog
5371
5400
  this.startWatchdog();
5372
5401
  }
@@ -6100,6 +6129,24 @@
6100
6129
  getVideo() {
6101
6130
  return this.offscreenVideo;
6102
6131
  }
6132
+ /**
6133
+ * Stop manifest refreshes (and thus CDN token requests) while the main player is paused.
6134
+ * For live streams, the snapshot hls.js instance continuously refreshes the manifest
6135
+ * on the playlist interval — this prevents unnecessary generate calls during pause.
6136
+ */
6137
+ stopLoad() {
6138
+ if (this.driver && typeof this.driver['stopLoad'] === 'function') {
6139
+ this.driver['stopLoad']();
6140
+ }
6141
+ }
6142
+ /**
6143
+ * Resume manifest refreshes when the main player resumes playback.
6144
+ */
6145
+ startLoad() {
6146
+ if (this.driver && typeof this.driver['startLoad'] === 'function') {
6147
+ this.driver['startLoad'](-1);
6148
+ }
6149
+ }
6103
6150
  /**
6104
6151
  * Destroy the snapshot Hls instance and clean up resources.
6105
6152
  */
@@ -6397,6 +6444,21 @@
6397
6444
  if (snapshotVideo !== null) {
6398
6445
  controller.bus.emit('snapshot-handler-ready', { take: (time) => handler.take(time), video: snapshotVideo });
6399
6446
  }
6447
+ // Stop/start snapshot manifest refreshes on pause/play to prevent
6448
+ // CDN token generate calls while the player is paused (live streams only).
6449
+ // Mirrors the same logic in hls.ts for the main engine.
6450
+ if (video) {
6451
+ video.addEventListener('pause', () => {
6452
+ if (controller.state?.isLive) {
6453
+ handler.stopLoad();
6454
+ }
6455
+ }, { signal: controller.signal });
6456
+ video.addEventListener('play', () => {
6457
+ if (controller.state?.isLive) {
6458
+ setTimeout(() => handler.startLoad(), 0);
6459
+ }
6460
+ }, { signal: controller.signal });
6461
+ }
6400
6462
  })
6401
6463
  .catch((error) => {
6402
6464
  console.warn('EBPlayer: HlsSnapshotHandler init failed:', error);