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.
@@ -1,7 +1,7 @@
1
1
  var EBPlayerBundle = (function (exports) {
2
2
  'use strict';
3
3
 
4
- var __EB_PLAYER_VERSION__ = "2.0.6";
4
+ var __EB_PLAYER_VERSION__ = "2.0.9";
5
5
 
6
6
  function styleInject(css, ref) {
7
7
  if ( ref === void 0 ) ref = {};
@@ -4332,6 +4332,15 @@ var EBPlayerBundle = (function (exports) {
4332
4332
  video.addEventListener('ended', () => {
4333
4333
  state.playbackState = 'ended';
4334
4334
  }, { signal });
4335
+ // Safety net: clear stale 'buffering' state when playback is clearly advancing.
4336
+ // In some edge cases (live stream segment boundaries), the browser fires 'waiting'
4337
+ // but never fires 'playing' even though video resumes from buffer. The timeupdate
4338
+ // event fires reliably while time advances, so we use it to recover.
4339
+ video.addEventListener('timeupdate', () => {
4340
+ if (state.playbackState === 'buffering' && !video.paused && video.readyState >= 3) {
4341
+ state.playbackState = 'playing';
4342
+ }
4343
+ }, { signal });
4335
4344
  }
4336
4345
  /**
4337
4346
  * Seek to a specific time. Default implementation sets video.currentTime.
@@ -4526,6 +4535,7 @@ var EBPlayerBundle = (function (exports) {
4526
4535
  this.expirationMarginInSeconds = expirationMarginInSeconds;
4527
4536
  this.lastTokenResponse = null;
4528
4537
  this.resetAttemptCounterTimeout = null;
4538
+ this.inFlightFetch = null;
4529
4539
  }
4530
4540
  resetAttemptCounter() {
4531
4541
  if (this.resetAttemptCounterTimeout) {
@@ -4769,21 +4779,35 @@ var EBPlayerBundle = (function (exports) {
4769
4779
  console.warn('CDNToken: Missing src to tokenize');
4770
4780
  return null;
4771
4781
  }
4772
- switch (this.tokenType) {
4773
- case TOKEN_TYPES.BUNNY:
4774
- this.lastTokenResponse = await this._fetchBunnyToken(src);
4775
- break;
4776
- case TOKEN_TYPES.AKAMAI:
4777
- this.lastTokenResponse = await this._fetchAkamaiToken(src);
4778
- break;
4779
- case TOKEN_TYPES.VENOM:
4780
- case TOKEN_TYPES.EASY_B:
4781
- this.lastTokenResponse = await this._fetchEasyBToken(src);
4782
- break;
4783
- default:
4784
- this.lastTokenResponse = await this._fetchDefaultToken(src);
4782
+ // Deduplicate concurrent fetches: if a fetch is already in flight,
4783
+ // return the same promise instead of firing a second network request.
4784
+ // This prevents the main player and snapshot handler from both triggering
4785
+ // separate generate calls when their manifest refreshes overlap.
4786
+ if (this.inFlightFetch) {
4787
+ return this.inFlightFetch;
4785
4788
  }
4786
- return this.lastTokenResponse;
4789
+ const doFetch = async () => {
4790
+ switch (this.tokenType) {
4791
+ case TOKEN_TYPES.BUNNY:
4792
+ return this._fetchBunnyToken(src);
4793
+ case TOKEN_TYPES.AKAMAI:
4794
+ return this._fetchAkamaiToken(src);
4795
+ case TOKEN_TYPES.VENOM:
4796
+ case TOKEN_TYPES.EASY_B:
4797
+ return this._fetchEasyBToken(src);
4798
+ default:
4799
+ return this._fetchDefaultToken(src);
4800
+ }
4801
+ };
4802
+ this.inFlightFetch = doFetch().then((response) => {
4803
+ this.lastTokenResponse = response;
4804
+ this.inFlightFetch = null;
4805
+ return response;
4806
+ }).catch((error) => {
4807
+ this.inFlightFetch = null;
4808
+ throw error;
4809
+ });
4810
+ return this.inFlightFetch;
4787
4811
  }
4788
4812
  // -------------------------------------------------------------------------
4789
4813
  // Public: updateUrlWithTokenParams
@@ -4886,6 +4910,7 @@ var EBPlayerBundle = (function (exports) {
4886
4910
  }
4887
4911
  this.attempt = 0;
4888
4912
  this.lastTokenResponse = null;
4913
+ this.inFlightFetch = null;
4889
4914
  console.log('CDNTokenManager: destroyed');
4890
4915
  }
4891
4916
  // -------------------------------------------------------------------------
@@ -5160,6 +5185,14 @@ var EBPlayerBundle = (function (exports) {
5160
5185
  getDriver() {
5161
5186
  return this.driver;
5162
5187
  }
5188
+ /**
5189
+ * Returns the CDN token manager used by this engine.
5190
+ * Allows the snapshot handler to share the same manager (avoids duplicate token requests).
5191
+ * Returns null when no token URL is configured or before init().
5192
+ */
5193
+ getTokenManager() {
5194
+ return this.tokenManager;
5195
+ }
5163
5196
  // -------------------------------------------------------------------------
5164
5197
  // BaseEngine hooks
5165
5198
  // -------------------------------------------------------------------------
@@ -5275,7 +5308,9 @@ var EBPlayerBundle = (function (exports) {
5275
5308
  if (!hlsjsUrl) {
5276
5309
  throw new Error('HlsEngine: config.hlsjs URL is required');
5277
5310
  }
5278
- // Create token manager if a token URL is configured
5311
+ // Create token manager if a token URL is configured.
5312
+ // No initial fetchToken() here — updateUrlWithTokenParams() below handles it
5313
+ // and populates lastTokenResponse (used lazily by DRM licenseXhrSetup).
5279
5314
  if (config.token) {
5280
5315
  this.tokenManager = new CDNTokenManager({
5281
5316
  token: config.token,
@@ -5284,13 +5319,6 @@ var EBPlayerBundle = (function (exports) {
5284
5319
  extraParamsCallback: (config.engineSettings.extraParamsCallback ?? config.extraParamsCallback),
5285
5320
  onCDNTokenError: config.engineSettings.onCDNTokenError
5286
5321
  });
5287
- // Fetch initial token
5288
- if (config.src) {
5289
- await this.tokenManager.fetchToken({ src: config.src });
5290
- }
5291
- // Guard: abort if detached during token fetch
5292
- if (this.detached)
5293
- return;
5294
5322
  }
5295
5323
  // console.info('HlsEngine: loading hls.js from', hlsjsUrl)
5296
5324
  const Hls = await loadScript(hlsjsUrl, 'Hls');
@@ -5373,7 +5401,6 @@ var EBPlayerBundle = (function (exports) {
5373
5401
  // Create the driver (NEVER stored in state)
5374
5402
  const driver = new Hls(driverConfig);
5375
5403
  this.driver = driver;
5376
- this.resolveDriverReady();
5377
5404
  // Pitfall 4: apply discontinuity workaround BEFORE attachMedia/loadSource
5378
5405
  applyDiscontinuityWorkaround(driver, Hls.Events);
5379
5406
  // Wire retry handler
@@ -5396,8 +5423,27 @@ var EBPlayerBundle = (function (exports) {
5396
5423
  }
5397
5424
  }
5398
5425
  driver.loadSource(src);
5426
+ // Resolve driverReady AFTER loadSource so consumers (P2P, snapshot handler)
5427
+ // don't start making token requests before the main engine's initial token
5428
+ // fetch completes and populates the cache.
5429
+ this.resolveDriverReady();
5399
5430
  // Register driver event handlers
5400
5431
  this.registerDriverEvents(Hls, state);
5432
+ // Pause/resume loading on video pause/play to stop manifest refreshes
5433
+ // (and thus CDN token requests) while the player is paused.
5434
+ // Only applies to live streams where hls.js continuously refreshes the manifest.
5435
+ const driverRef = driver;
5436
+ video.addEventListener('pause', () => {
5437
+ if (this.state?.isLive) {
5438
+ driverRef.stopLoad();
5439
+ }
5440
+ }, { signal });
5441
+ video.addEventListener('play', () => {
5442
+ if (this.state?.isLive) {
5443
+ // Defer startLoad to avoid re-entrancy (Pitfall 3)
5444
+ setTimeout(() => driverRef.startLoad(-1), 0);
5445
+ }
5446
+ }, { signal });
5401
5447
  // Start stall watchdog
5402
5448
  this.startWatchdog();
5403
5449
  }
@@ -6131,6 +6177,24 @@ var EBPlayerBundle = (function (exports) {
6131
6177
  getVideo() {
6132
6178
  return this.offscreenVideo;
6133
6179
  }
6180
+ /**
6181
+ * Stop manifest refreshes (and thus CDN token requests) while the main player is paused.
6182
+ * For live streams, the snapshot hls.js instance continuously refreshes the manifest
6183
+ * on the playlist interval — this prevents unnecessary generate calls during pause.
6184
+ */
6185
+ stopLoad() {
6186
+ if (this.driver && typeof this.driver['stopLoad'] === 'function') {
6187
+ this.driver['stopLoad']();
6188
+ }
6189
+ }
6190
+ /**
6191
+ * Resume manifest refreshes when the main player resumes playback.
6192
+ */
6193
+ startLoad() {
6194
+ if (this.driver && typeof this.driver['startLoad'] === 'function') {
6195
+ this.driver['startLoad'](-1);
6196
+ }
6197
+ }
6134
6198
  /**
6135
6199
  * Destroy the snapshot Hls instance and clean up resources.
6136
6200
  */
@@ -6414,36 +6478,35 @@ var EBPlayerBundle = (function (exports) {
6414
6478
  else {
6415
6479
  const win = window;
6416
6480
  if (win.Hls) {
6417
- // Create a dedicated token manager for the snapshot handler (DRM license + manifest tokens)
6418
- let snapshotTokenManager = null;
6419
- if (mergedConfig.token) {
6420
- snapshotTokenManager = new CDNTokenManager({
6421
- token: mergedConfig.token,
6422
- tokenType: mergedConfig.tokenType,
6423
- srcInTokenRequest: mergedConfig.srcInTokenRequest,
6424
- extraParamsCallback: (mergedConfig.engineSettings.extraParamsCallback ?? mergedConfig.extraParamsCallback),
6425
- onCDNTokenError: mergedConfig.engineSettings.onCDNTokenError
6426
- });
6427
- }
6481
+ // Share the main engine's token manager with the snapshot handler
6482
+ // to avoid duplicate token requests (one CDNTokenManager per player instance)
6483
+ const sharedTokenManager = engine.getTokenManager();
6428
6484
  // Build DRM config (emeEnabled, drmSystems, licenseXhrSetup) for the snapshot hls.js instance
6429
- const snapshotDrmConfigurator = new DrmConfigurator(snapshotTokenManager);
6485
+ const snapshotDrmConfigurator = new DrmConfigurator(sharedTokenManager);
6430
6486
  const snapshotDrmConfig = snapshotDrmConfigurator.buildHlsConfig(mergedConfig.engineSettings);
6431
- const handler = new HlsSnapshotHandler({ src, engineSettings: { ...mergedConfig.engineSettings, ...snapshotDrmConfig } }, snapshotTokenManager);
6432
- // Fetch initial token before init (needed for manifest request)
6433
- const tokenReady = snapshotTokenManager && src
6434
- ? snapshotTokenManager.fetchToken({ src }).catch((error) => {
6435
- console.warn('EBPlayer: Snapshot token fetch failed:', error);
6436
- })
6437
- : Promise.resolve();
6438
- tokenReady.then(() => {
6439
- return handler.init(win.Hls);
6440
- })
6487
+ const handler = new HlsSnapshotHandler({ src, engineSettings: { ...mergedConfig.engineSettings, ...snapshotDrmConfig } }, sharedTokenManager);
6488
+ handler.init(win.Hls)
6441
6489
  .then(() => {
6442
6490
  activeSnapshotDestroy = () => handler.destroy();
6443
6491
  const snapshotVideo = handler.getVideo();
6444
6492
  if (snapshotVideo !== null) {
6445
6493
  controller.bus.emit('snapshot-handler-ready', { take: (time) => handler.take(time), video: snapshotVideo });
6446
6494
  }
6495
+ // Stop/start snapshot manifest refreshes on pause/play to prevent
6496
+ // CDN token generate calls while the player is paused (live streams only).
6497
+ // Mirrors the same logic in hls.ts for the main engine.
6498
+ if (video) {
6499
+ video.addEventListener('pause', () => {
6500
+ if (controller.state?.isLive) {
6501
+ handler.stopLoad();
6502
+ }
6503
+ }, { signal: controller.signal });
6504
+ video.addEventListener('play', () => {
6505
+ if (controller.state?.isLive) {
6506
+ setTimeout(() => handler.startLoad(), 0);
6507
+ }
6508
+ }, { signal: controller.signal });
6509
+ }
6447
6510
  })
6448
6511
  .catch((error) => {
6449
6512
  console.warn('EBPlayer: HlsSnapshotHandler init failed:', error);
@@ -6494,9 +6557,22 @@ var EBPlayerBundle = (function (exports) {
6494
6557
  }, 100);
6495
6558
  });
6496
6559
  // Auto-open the stream if src is provided in config (matches legacy player behaviour
6497
- // where consumers call start({ src: '...' }) and expect playback to begin immediately)
6560
+ // where consumers call start({ src: '...' }) and expect playback to begin immediately).
6561
+ // When autoplay is false, defer open() until the user requests play — this avoids
6562
+ // fetching CDN tokens and loading manifests before playback is actually needed.
6498
6563
  if (mergedConfig.src) {
6499
- reference.open(mergedConfig.src);
6564
+ if (mergedConfig.autoplay) {
6565
+ reference.open(mergedConfig.src);
6566
+ }
6567
+ else {
6568
+ let deferredOpen = true;
6569
+ controller.bus.on('play', () => {
6570
+ if (deferredOpen) {
6571
+ deferredOpen = false;
6572
+ reference.open(mergedConfig.src);
6573
+ }
6574
+ }, { signal: controller.signal });
6575
+ }
6500
6576
  }
6501
6577
  return reference;
6502
6578
  }
@@ -6538,7 +6614,7 @@ var EBPlayerBundle = (function (exports) {
6538
6614
  window.EBPlayer = { start: start$1, stop, destroy, AVAILABLE_THEMES, THEME_LAYOUTS, version: VERSION };
6539
6615
  }
6540
6616
 
6541
- var brandConfig = {"loading":false,"start":true,"autoplay":true,"muted":true,"image":"https://www.easybroadcast.fr/img/logo_blue.svg","statsOrigin":"easybroadcast.fr","ad":"https://pubads.g.doubleclick.net/gampad/live/ads?iu=/21671680130/FORJA/PLAYER_VIDEO&description_url=[placeholder]&tfcd=0&npa=0&sz=640x480%7C1280x720%7C1920x1080&gdfp_req=1&output=vast&unviewed_position_start=1&env=vp&impl=s&correlator=[timestamp]&cust_params=","socialMediaList":{"facebook":"https://www.facebook.com/sharer/sharer.php?u=https://www.easybroadcast.fr","twitter":"https://twitter.com/Easy_Broadcast","pinterest":"http://pinterest.com/pin/create/link/?url=https://www.easybroadcast.fr","blogger":"https://www.blogger.com/blog_this.pyra?t&u=https://www.easybroadcast.fr","tumblr":"http://www.tumblr.com/share/link?url=https://www.easybroadcast.fr","reddit":"http://reddit.com/submit?url=https://www.easybroadcast.fr"},"socials":{"facebook":"https://www.facebook.com/sharer/sharer.php?u=https://www.easybroadcast.fr","twitter":"https://twitter.com/Easy_Broadcast","pinterest":"http://pinterest.com/pin/create/link/?url=https://www.easybroadcast.fr","reddit":"http://reddit.com/submit?url=https://www.easybroadcast.fr"},"liveButton":true,"src":"https://live.easybroadcast.fr/hls/live/playlist.m3u8","disableCustomAbr":false,"aboutText":"\n\n <img src=\"https://www.easybroadcast.fr/img/logo_blue.svg\" width=\"40px\"/>\n <p> Copyright 2019 EasyBroadcast </p>\n \n\n <a style=\"color:white\" href=\"http://www.easybroadcast.fr\" target=\"_blank\">\n easybroadcast.fr\n </a>\n ","template":"/Volumes/Dev-ext/dev/players/web/player-master/configs/videos/easybroadcast/demo/index.ejs","content":"_99999","lang":"FR","epgContentId":"Alaoula","epgPolling":20,"showProgressThumb":true,"engineSettings":{"liveSyncDurationCount":2,"lowLatencyMode":false,"maxLiveSyncPlaybackRate":1}};
6617
+ var brandConfig = {"start":true,"autoplay":true,"muted":true,"statsOrigin":"easybroadcast.fr","prerollLink":false,"socialMediaList":{"facebook":"https://www.facebook.com/sharer/sharer.php?u=https://www.easybroadcast.fr","twitter":"https://twitter.com/Easy_Broadcast","pinterest":"http://pinterest.com/pin/create/link/?url=https://www.easybroadcast.fr","blogger":"https://www.blogger.com/blog_this.pyra?t&u=https://www.easybroadcast.fr","tumblr":"http://www.tumblr.com/share/link?url=https://www.easybroadcast.fr","reddit":"http://reddit.com/submit?url=https://www.easybroadcast.fr"},"srcInTokenRequest":true,"manager":"wss://manager.kube.easybroadcast.fr/easybroadcast/Lyf4JVGAmWJpTyN","src":"https://cdn.live.easybroadcast.io/abr_corp/73_aloula_w1dqfwm/playlist_dvr.m3u8","token":"https://token.easybroadcast.io/all","tokenType":"easy_b","lib":"https://libs.easybroadcast.io/assets/js/eblib/hls1/latest/eblib.bundle.js","aboutText":"\n\n <img src=\"https://www.easybroadcast.fr/img/logo_blue.svg\" width=\"40px\"/>\n <p> Copyright 2019 EasyBroadcast </p>\n \n\n <a style=\"color:white\" href=\"http://www.easybroadcast.fr\" target=\"_blank\">\n easybroadcast.fr\n </a>\n ","template":"/Volumes/Dev-ext/dev/players/web/player-master/configs/videos/easybroadcast/demo-token/index.ejs","content":"_99999"};
6542
6618
 
6543
6619
  /**
6544
6620
  * Standalone entry point — used for the generated player bundle (not the npm UMD build).