@gcorevideo/player 2.28.36 → 2.30.0

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.
Files changed (60) hide show
  1. package/README.md +108 -0
  2. package/assets/media-control/media-control.scss +8 -6
  3. package/assets/multi-camera/multicamera.ejs +27 -23
  4. package/assets/multi-camera/style.scss +7 -34
  5. package/assets/style/main.scss +2 -2
  6. package/dist/core.js +24 -7
  7. package/dist/index.css +324 -346
  8. package/dist/index.embed.js +24 -46
  9. package/dist/index.js +471 -245
  10. package/docs/api/player.md +22 -9
  11. package/docs/api/player.mediacontrol.setkeepvisible.md +56 -0
  12. package/docs/api/player.multicamera.md +0 -28
  13. package/docs/api/player.multiccamerasourceinfo.md +27 -0
  14. package/docs/api/{player.multicamera.unbindevents.md → player.multisourcesmode.md} +4 -7
  15. package/docs/api/player.sourcecontroller.md +0 -37
  16. package/lib/Player.d.ts +9 -0
  17. package/lib/Player.d.ts.map +1 -1
  18. package/lib/Player.js +11 -0
  19. package/lib/index.plugins.d.ts +1 -0
  20. package/lib/index.plugins.d.ts.map +1 -1
  21. package/lib/index.plugins.js +1 -0
  22. package/lib/playback/dash-playback/DashPlayback.d.ts +2 -1
  23. package/lib/playback/dash-playback/DashPlayback.d.ts.map +1 -1
  24. package/lib/playback/dash-playback/DashPlayback.js +5 -1
  25. package/lib/playback/hls-playback/HlsPlayback.d.ts +2 -1
  26. package/lib/playback/hls-playback/HlsPlayback.d.ts.map +1 -1
  27. package/lib/playback/types.d.ts +9 -0
  28. package/lib/playback/types.d.ts.map +1 -1
  29. package/lib/playback.types.d.ts +0 -6
  30. package/lib/playback.types.d.ts.map +1 -1
  31. package/lib/plugins/multi-camera/MultiCamera.d.ts +21 -4
  32. package/lib/plugins/multi-camera/MultiCamera.d.ts.map +1 -1
  33. package/lib/plugins/multi-camera/MultiCamera.js +70 -134
  34. package/lib/plugins/source-controller/SourceController.d.ts +0 -39
  35. package/lib/plugins/source-controller/SourceController.d.ts.map +1 -1
  36. package/lib/plugins/source-controller/SourceController.js +0 -39
  37. package/lib/plugins/token-refresh/TokenRefreshPlugin.d.ts +119 -0
  38. package/lib/plugins/token-refresh/TokenRefreshPlugin.d.ts.map +1 -0
  39. package/lib/plugins/token-refresh/TokenRefreshPlugin.js +318 -0
  40. package/lib/plugins/token-refresh/index.d.ts +2 -0
  41. package/lib/plugins/token-refresh/index.d.ts.map +1 -0
  42. package/lib/plugins/token-refresh/index.js +1 -0
  43. package/lib/utils/mediaSources.d.ts +4 -0
  44. package/lib/utils/mediaSources.d.ts.map +1 -1
  45. package/lib/utils/mediaSources.js +8 -6
  46. package/package.json +1 -1
  47. package/src/Player.ts +12 -0
  48. package/src/index.plugins.ts +1 -0
  49. package/src/playback/dash-playback/DashPlayback.ts +7 -3
  50. package/src/playback/hls-playback/HlsPlayback.ts +1 -1
  51. package/src/playback/types.ts +10 -0
  52. package/src/playback.types.ts +0 -6
  53. package/src/plugins/multi-camera/MultiCamera.ts +103 -166
  54. package/src/plugins/source-controller/SourceController.ts +0 -39
  55. package/src/plugins/subtitles/ClosedCaptions.ts +1 -1
  56. package/src/plugins/token-refresh/TokenRefreshPlugin.ts +425 -0
  57. package/src/plugins/token-refresh/index.ts +5 -0
  58. package/src/utils/mediaSources.ts +10 -6
  59. package/tsconfig.tsbuildinfo +1 -1
  60. package/docs/api/player.multicamera.activebyid.md +0 -67
package/dist/index.js CHANGED
@@ -12778,6 +12778,9 @@ var PlayerEvent;
12778
12778
  PlayerEvent["VolumeUpdate"] = "volumeupdate";
12779
12779
  })(PlayerEvent || (PlayerEvent = {}));
12780
12780
 
12781
+ const MIME_TYPES_HLS = ['application/x-mpegurl', 'application/vnd.apple.mpegurl'];
12782
+ const MIME_TYPE_HLS = MIME_TYPES_HLS[0];
12783
+ const MIME_TYPE_DASH = 'application/dash+xml';
12781
12784
  // TODO rewrite using the Playback classes and canPlay static methods
12782
12785
  function buildMediaSourcesList(sources, priorityTransport = 'dash') {
12783
12786
  const playbacks = Loader.registeredPlaybacks;
@@ -12817,22 +12820,21 @@ function wrapSource(s) {
12817
12820
  }
12818
12821
  function guessMimeType(s) {
12819
12822
  if (s.endsWith('.mpd')) {
12820
- return 'application/dash+xml';
12823
+ return MIME_TYPE_DASH;
12821
12824
  }
12822
12825
  if (s.endsWith('.m3u8')) {
12823
- // return 'application/vnd.apple.mpegurl'
12824
- return 'application/x-mpegurl';
12826
+ return MIME_TYPE_HLS;
12825
12827
  }
12826
12828
  }
12827
12829
  function isDashSource(source, mimeType) {
12828
12830
  if (mimeType) {
12829
- return mimeType === 'application/dash+xml'; // TODO consider video/mp4
12831
+ return mimeType === MIME_TYPE_DASH; // TODO consider video/mp4
12830
12832
  }
12831
12833
  return source.endsWith('.mpd');
12832
12834
  }
12833
12835
  function isHlsSource(source, mimeType) {
12834
12836
  if (mimeType) {
12835
- return ['application/vnd.apple.mpegurl', 'application/x-mpegurl'].includes(mimeType.toLowerCase());
12837
+ return MIME_TYPES_HLS.includes(mimeType.toLowerCase());
12836
12838
  }
12837
12839
  return source.endsWith('.m3u8');
12838
12840
  }
@@ -12920,7 +12922,7 @@ var PlaybackEvents;
12920
12922
  // https://github.com/clappr/clappr/blob/8752995ea439321ac7ca3cd35e8c64de7a3c3d17/LICENSE
12921
12923
  const AUTO$1 = -1;
12922
12924
  const { now: now$2 } = Utils;
12923
- const T$e = 'playback.dash';
12925
+ const T$f = 'playback.dash';
12924
12926
  class DashPlayback extends BasePlayback {
12925
12927
  _levels = [];
12926
12928
  _currentLevel = AUTO$1;
@@ -13053,6 +13055,7 @@ class DashPlayback extends BasePlayback {
13053
13055
  this._dash = dash;
13054
13056
  this._dash.initialize();
13055
13057
  if (this.options.dash) {
13058
+ const { requestInterceptor, ...dashSettings } = this.options.dash;
13056
13059
  const settings = $.extend(true, {
13057
13060
  streaming: {
13058
13061
  text: {
@@ -13064,8 +13067,11 @@ class DashPlayback extends BasePlayback {
13064
13067
  // dispatchForManualRendering: true, // TODO only when useNativeSubtitles is not true?
13065
13068
  },
13066
13069
  },
13067
- }, this.options.dash);
13070
+ }, dashSettings);
13068
13071
  this._dash.updateSettings(settings);
13072
+ if (typeof requestInterceptor === 'function') {
13073
+ this._dash.addRequestInterceptor(requestInterceptor);
13074
+ }
13069
13075
  }
13070
13076
  this._dash.attachView(this.el);
13071
13077
  this._dash.setAutoPlay(false);
@@ -13193,7 +13199,7 @@ class DashPlayback extends BasePlayback {
13193
13199
  this.trigger(Events$1.PLAYBACK_SETTINGSUPDATE);
13194
13200
  }
13195
13201
  _onPlaybackError = (event) => {
13196
- trace(`${T$e} _onPlaybackError`, { type: event.type, code: event.error.code, message: event.error.message });
13202
+ trace(`${T$f} _onPlaybackError`, { type: event.type, code: event.error.code, message: event.error.message });
13197
13203
  };
13198
13204
  _onDASHJSSError = (event) => {
13199
13205
  this._stopTimeUpdateTimer();
@@ -50093,7 +50099,7 @@ const { now } = Utils;
50093
50099
  const AUTO = -1;
50094
50100
  const DEFAULT_RECOVER_ATTEMPTS = 16;
50095
50101
  Events$1.register('PLAYBACK_FRAGMENT_PARSING_METADATA');
50096
- const T$d = 'playback.hls';
50102
+ const T$e = 'playback.hls';
50097
50103
  class HlsPlayback extends BasePlayback {
50098
50104
  _ccTracksUpdated = false;
50099
50105
  _currentFragment = null;
@@ -50418,7 +50424,7 @@ class HlsPlayback extends BasePlayback {
50418
50424
  }
50419
50425
  else {
50420
50426
  Log.error('hlsjs: failed to recover', { evt, data });
50421
- trace(`${T$d} _recover failed to recover`, {
50427
+ trace(`${T$e} _recover failed to recover`, {
50422
50428
  type: data.type,
50423
50429
  details: data.details,
50424
50430
  });
@@ -50505,7 +50511,7 @@ class HlsPlayback extends BasePlayback {
50505
50511
  this.trigger(Events$1.PLAYBACK_SETTINGSUPDATE);
50506
50512
  }
50507
50513
  _onHLSJSError(evt, data) {
50508
- trace(`${T$d} _onHLSJSError`, {
50514
+ trace(`${T$e} _onHLSJSError`, {
50509
50515
  fatal: data.fatal,
50510
50516
  type: data.type,
50511
50517
  details: data.details,
@@ -50553,7 +50559,7 @@ class HlsPlayback extends BasePlayback {
50553
50559
  evt,
50554
50560
  data,
50555
50561
  });
50556
- trace(`${T$d} _onHLSJSError trying to recover from network error`, {
50562
+ trace(`${T$e} _onHLSJSError trying to recover from network error`, {
50557
50563
  details: data.details,
50558
50564
  });
50559
50565
  error.level = PlayerError.Levels.WARN;
@@ -50566,7 +50572,7 @@ class HlsPlayback extends BasePlayback {
50566
50572
  evt,
50567
50573
  data,
50568
50574
  });
50569
- trace(`${T$d} _onHLSJSError trying to recover from media error`, {
50575
+ trace(`${T$e} _onHLSJSError trying to recover from media error`, {
50570
50576
  details: data.details,
50571
50577
  });
50572
50578
  error.level = PlayerError.Levels.WARN;
@@ -50596,7 +50602,7 @@ class HlsPlayback extends BasePlayback {
50596
50602
  return;
50597
50603
  }
50598
50604
  Log.warn('hlsjs: non-fatal error occurred', { evt, data });
50599
- trace(`${T$d} _onHLSJSError non-fatal error occurred`, {
50605
+ trace(`${T$e} _onHLSJSError non-fatal error occurred`, {
50600
50606
  type: data.type,
50601
50607
  details: data.details,
50602
50608
  });
@@ -50937,7 +50943,7 @@ class HlsPlayback extends BasePlayback {
50937
50943
  this.trigger(Events$1.PLAYBACK_AUDIO_AVAILABLE, data.audioTracks.map(toClapprTrack));
50938
50944
  }
50939
50945
  _onAudioTrackSwitched(_, data) {
50940
- trace(`${T$d} onAudioTrackSwitched`);
50946
+ trace(`${T$e} onAudioTrackSwitched`);
50941
50947
  // @ts-ignore
50942
50948
  const track = this._hls.audioTracks[data.id];
50943
50949
  this.trigger(Events$1.PLAYBACK_AUDIO_CHANGED, toClapprTrack(track));
@@ -50982,7 +50988,7 @@ function toClapprTrack(t) {
50982
50988
  };
50983
50989
  }
50984
50990
 
50985
- const T$c = 'playback.html5_video';
50991
+ const T$d = 'playback.html5_video';
50986
50992
  const STALL_TIMEOUT = 15000;
50987
50993
  class HTML5Video extends BasePlayback {
50988
50994
  stallTimerId = null;
@@ -51083,7 +51089,7 @@ class HTML5Video extends BasePlayback {
51083
51089
  switchAudioTrack(id) {
51084
51090
  const tracks = this.el.audioTracks;
51085
51091
  const supported = !!tracks;
51086
- trace(`${T$c} switchAudioTrack`, {
51092
+ trace(`${T$d} switchAudioTrack`, {
51087
51093
  supported,
51088
51094
  });
51089
51095
  if (supported) {
@@ -51102,7 +51108,7 @@ function registerPlaybacks() {
51102
51108
  Loader.registerPlayback(DashPlayback);
51103
51109
  }
51104
51110
 
51105
- const T$b = 'gplayer';
51111
+ const T$c = 'gplayer';
51106
51112
  const DEFAULT_OPTIONS = {
51107
51113
  autoPlay: false,
51108
51114
  debug: 'none',
@@ -51296,6 +51302,17 @@ class Player {
51296
51302
  }
51297
51303
  this.player?.load(ms, ms[0].mimeType ?? '');
51298
51304
  }
51305
+ /**
51306
+ * Returns a registered core plugin instance by name, or `null` if not found.
51307
+ *
51308
+ * @example
51309
+ * ```ts
51310
+ * const tokenRefresh = player.getPlugin('token_refresh') as TokenRefreshPlugin | null
51311
+ * ```
51312
+ */
51313
+ getPlugin(name) {
51314
+ return this.player?.core.getPlugin(name) ?? null;
51315
+ }
51299
51316
  /**
51300
51317
  * Mutes the sound of the video.
51301
51318
  */
@@ -51438,7 +51455,7 @@ class Player {
51438
51455
  }
51439
51456
  }
51440
51457
  triggerAutoPlay() {
51441
- trace(`${T$b} triggerAutoPlay`);
51458
+ trace(`${T$c} triggerAutoPlay`);
51442
51459
  setTimeout(() => {
51443
51460
  this.player?.play({
51444
51461
  autoPlay: true,
@@ -51456,7 +51473,7 @@ class Player {
51456
51473
  // TODO test
51457
51474
  events = {
51458
51475
  onReady: () => {
51459
- trace(`${T$b} onReady`, {
51476
+ trace(`${T$c} onReady`, {
51460
51477
  ready: this.ready,
51461
51478
  });
51462
51479
  if (this.ready) {
@@ -51490,7 +51507,7 @@ class Player {
51490
51507
  buildCoreOptions(rootNode) {
51491
51508
  const sources = this.buildMediaSourcesList();
51492
51509
  const source = sources[0];
51493
- trace(`${T$b} buildCoreOptions`, {
51510
+ trace(`${T$c} buildCoreOptions`, {
51494
51511
  source,
51495
51512
  sources,
51496
51513
  });
@@ -51567,7 +51584,7 @@ class Player {
51567
51584
  }
51568
51585
  }
51569
51586
 
51570
- var version$1 = "2.28.36";
51587
+ var version$1 = "2.30.0";
51571
51588
 
51572
51589
  var packages = {
51573
51590
  "node_modules/@clappr/core": {
@@ -52000,7 +52017,7 @@ const INITIAL_SETTINGS = {
52000
52017
  default: [],
52001
52018
  seekEnabled: false,
52002
52019
  };
52003
- const T$a = 'plugins.media_control';
52020
+ const T$b = 'plugins.media_control';
52004
52021
  /**
52005
52022
  * Extended events for the {@link MediaControl} plugin
52006
52023
  * @public
@@ -52293,7 +52310,7 @@ class MediaControl extends UICorePlugin {
52293
52310
  * Reenables the plugin disabled earlier with the {@link MediaControl.disable} method
52294
52311
  */
52295
52312
  enable() {
52296
- trace(`${T$a} enable`, {
52313
+ trace(`${T$b} enable`, {
52297
52314
  chromeless: this.options.chromeless,
52298
52315
  userDisabled: this.userDisabled,
52299
52316
  });
@@ -52450,7 +52467,7 @@ class MediaControl extends UICorePlugin {
52450
52467
  this.$el.removeClass('w370');
52451
52468
  this.$el.removeClass('w270');
52452
52469
  this.verticalVolume = false;
52453
- trace(`${T$a} playerResize`, {
52470
+ trace(`${T$b} playerResize`, {
52454
52471
  size,
52455
52472
  width: this.container.$el.width(),
52456
52473
  height: this.container.$el.height(),
@@ -53452,7 +53469,7 @@ class AudioTracks extends UICorePlugin {
53452
53469
 
53453
53470
  const templateHtml$2 = "<div class=\"big-mute-icon-wrapper\" data-big-mute id=\"gplayer-big-mute-button\">\n <div class=\"big-mute-icon gcore-skin-border-color\" data-big-mute-icon id=\"gplayer-big-mute-icon\"></div>\n</div>\n";
53454
53471
 
53455
- const T$9 = 'plugins.big_mute_button';
53472
+ const T$a = 'plugins.big_mute_button';
53456
53473
  // TODO rewrite as a container plugin
53457
53474
  /**
53458
53475
  * `PLUGIN` that displays a big mute button over the video when it's being played muted.
@@ -53515,7 +53532,7 @@ class BigMuteButton extends UICorePlugin {
53515
53532
  if (autoPlay) {
53516
53533
  this.autoPlay = true;
53517
53534
  }
53518
- trace(`${T$9} onPlay`, {
53535
+ trace(`${T$a} onPlay`, {
53519
53536
  autoPlay: this.autoPlay,
53520
53537
  wasMuted,
53521
53538
  volume,
@@ -53529,7 +53546,7 @@ class BigMuteButton extends UICorePlugin {
53529
53546
  }
53530
53547
  onStop(_, metadata) {
53531
53548
  const ui = metadata?.ui;
53532
- trace(`${T$9} onStop`, { ui });
53549
+ trace(`${T$a} onStop`, { ui });
53533
53550
  if (ui) {
53534
53551
  this.destroy();
53535
53552
  }
@@ -56493,7 +56510,7 @@ const PLAYBACK_NAMES = {
56493
56510
  hls: 'HLS.js',
56494
56511
  html5_video: 'Native',
56495
56512
  };
56496
- const T$8 = 'plugins.nerd_stats';
56513
+ const T$9 = 'plugins.nerd_stats';
56497
56514
  /**
56498
56515
  * `PLUGIN` that displays useful statistics regarding the playback as well as the network quality estimation.
56499
56516
  * @public
@@ -56630,7 +56647,7 @@ class NerdStats extends UICorePlugin {
56630
56647
  return super.destroy();
56631
56648
  }
56632
56649
  toggle = () => {
56633
- trace(`${T$8} toggle`, {
56650
+ trace(`${T$9} toggle`, {
56634
56651
  open: this.open,
56635
56652
  });
56636
56653
  if (this.open) {
@@ -56650,14 +56667,14 @@ class NerdStats extends UICorePlugin {
56650
56667
  })
56651
56668
  .catch((e) => {
56652
56669
  reportError(e);
56653
- trace(`${T$8} speedtest error`, {
56670
+ trace(`${T$9} speedtest error`, {
56654
56671
  error: e,
56655
56672
  });
56656
56673
  this.disable();
56657
56674
  });
56658
56675
  }
56659
56676
  hide() {
56660
- trace(`${T$8} hide`);
56677
+ trace(`${T$9} hide`);
56661
56678
  this.$el.hide();
56662
56679
  this.open = false;
56663
56680
  stopSpeedtest();
@@ -57390,7 +57407,7 @@ const reloadIcon = "<svg fill=\"#FFFFFF\" height=\"24\" viewBox=\"0 0 24 24\" wi
57390
57407
 
57391
57408
  const templateHtml = "<div class=\"player-error-screen__content\" data-error-screen>\n <% if (icon) { %>\n <div class=\"player-error-screen__icon\" data-error-screen><%= icon %></div>\n <% } %>\n <div class=\"player-error-screen__title\" data-error-screen><%= title %></div>\n <% if (message) { %>\n <div class=\"player-error-screen__message\" data-error-screen><%= message %></div>\n <% } %>\n <% if (code) { %>\n <div class=\"player-error-screen__code\" data-error-screen><%= i18n.t('error_code') %>: <%= code %></div>\n <% } %>\n <% if (reloadIcon) { %>\n <div class=\"player-error-screen__reload\" data-error-screen><%= reloadIcon %></div>\n <% } %>\n</div>\n";
57392
57409
 
57393
- const T$7 = 'plugins.error_screen';
57410
+ const T$8 = 'plugins.error_screen';
57394
57411
  /**
57395
57412
  * `PLUGIN` that displays fatal errors nicely in the overlay on top of the player.
57396
57413
  * @public
@@ -57442,11 +57459,11 @@ class ErrorScreen extends UICorePlugin {
57442
57459
  this.listenTo(this.core, Events$1.CORE_ACTIVE_CONTAINER_CHANGED, this.onActiveContainerChanged);
57443
57460
  }
57444
57461
  onPlay() {
57445
- trace(`${T$7} onPlay`);
57462
+ trace(`${T$8} onPlay`);
57446
57463
  this.unmount();
57447
57464
  }
57448
57465
  unmount() {
57449
- trace(`${T$7} unmount`);
57466
+ trace(`${T$8} unmount`);
57450
57467
  this.err = null;
57451
57468
  this.$el.remove();
57452
57469
  }
@@ -57459,7 +57476,7 @@ class ErrorScreen extends UICorePlugin {
57459
57476
  };
57460
57477
  }
57461
57478
  reload() {
57462
- trace(`${T$7} reload`);
57479
+ trace(`${T$8} reload`);
57463
57480
  setTimeout(() => {
57464
57481
  this.core.configure({
57465
57482
  reloading: true,
@@ -57482,7 +57499,7 @@ class ErrorScreen extends UICorePlugin {
57482
57499
  }
57483
57500
  }
57484
57501
  onError(err) {
57485
- trace(`${T$7} onError`, { err });
57502
+ trace(`${T$8} onError`, { err });
57486
57503
  if (err.UI) {
57487
57504
  if (this.err) {
57488
57505
  this.unmount();
@@ -57958,16 +57975,12 @@ class Logo extends UIContainerPlugin {
57958
57975
  }
57959
57976
  }
57960
57977
 
57961
- const pluginHtml$3 = "<button data-multicamera-button class='gcore-skin-button-color'>\n <span class=\"multicamera-icon\"></span>\n</button>\n\n<ul class=\"gcore-skin-bg-color\">\n <% for (var i = 0; i < streams.length; i++) { %>\n <% if(!streams[i].live && multisources_mode === 'only_live') { %>\n <% continue; %>\n <% } %>\n <li>\n <div class=\"multicamera-item\" data-multicamera-selector-live=\"<%= streams[i].live %>\"\n data-multicamera-selector-select=\"<%= streams[i].id %>\">\n <div class=\"multicamera-screenshot\">\n <% if (streams[i].screenshot) { %>\n <img src=\"<%= streams[i].screenshot %>\" alt=\"<%= streams[i].title %>\"/>\n <% } %>\n </div>\n <div class=\"multicamera-text gcore-skin-text-color\">\n <% if (streams[i].title) { %>\n <div class=\"multicamera-title gcore-skin-text-color\"><%= streams[i].title %></div>\n <% } %>\n <% if (streams[i].description) { %>\n <div class=\"multicamera-description gcore-skin-text-color\"><%= streams[i].description %></div>\n <% } %>\n </div>\n </div>\n </li>\n <% } %>\n</ul>\n";
57978
+ const pluginHtml$3 = "<button data-multicamera-button class='gcore-skin-button-color media-control-button'>\n <span class=\"multicamera-icon\"></span>\n</button>\n\n<ul class=\"gcore-skin-bg-color\">\n <% for (var i=0; i < streams.length; i++) { %>\n <% if(!streams[i].live && multisources_mode==='only_live' ) { %>\n <% continue; %>\n <% } %>\n <li>\n <div class=\"multicamera-item\" data-multicamera-selector-live=\"<%= streams[i].live %>\"\n data-multicamera-selector-select=\"<%= streams[i].id %>\">\n <div class=\"multicamera-screenshot\">\n <% if (streams[i].screenshot) { %>\n <img src=\"<%= streams[i].screenshot %>\" alt=\"<%= streams[i].title %>\" />\n <% } %>\n </div>\n <div class=\"multicamera-text gcore-skin-text-color\">\n <% if (streams[i].title) { %>\n <div class=\"multicamera-title gcore-skin-text-color\">\n <%= streams[i].title %>\n </div>\n <% } %>\n <% if (streams[i].description) { %>\n <div class=\"multicamera-description gcore-skin-text-color\">\n <%= streams[i].description %>\n </div>\n <% } %>\n </div>\n </div>\n </li>\n <% } %>\n</ul>";
57962
57979
 
57963
57980
  const streamsIcon = "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"20\" viewBox=\"0 0 24 20\">\n <path fill=\"#FFF\" fill-rule=\"nonzero\" d=\"M24 1.5v13a1.5 1.5 0 0 1-1.5 1.5h-1a.5.5 0 0 1-.5-.5v-10A2.5 2.5 0 0 0 18.5 3h-14a.5.5 0 0 1-.5-.5v-1A1.5 1.5 0 0 1 5.5 0h17A1.5 1.5 0 0 1 24 1.5M12.724 12.447l-5 2.5a.505.505 0 0 1-.487-.021A.503.503 0 0 1 7 14.5v-5c0-.173.09-.334.237-.426a.505.505 0 0 1 .487-.021l5 2.5a.5.5 0 0 1 0 .894M18.5 4h-17C.673 4 0 4.673 0 5.5v13c0 .827.673 1.5 1.5 1.5h17c.827 0 1.5-.673 1.5-1.5v-13c0-.827-.673-1.5-1.5-1.5\"/>\n</svg>\n";
57964
57981
 
57965
- const streamsMomentoIcon = "<svg id=\"Слой_1\" data-name=\"Слой 1\" xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 230.73 173.61\"><defs><style>.cls-1{fill:#fff;}</style></defs><path class=\"cls-1\" d=\"M71.82,16.09c11.43,0,22.87-.05,34.31,0,12,.06,19.45,7.45,19.7,19.52.23,11.31.23,11.31,11.81,11.31q24.36,0,48.73,0c12.74,0,20.21,7.3,20.27,19.88q.11,24.12,0,48.23c-.06,12.66-7.12,19.65-19.75,19.77-2,0-4,0-6,0-7.78,0-7.84.05-6,7.79a59.23,59.23,0,0,0,1.95,6.67c1.44,3.95.33,7.18-2.66,9.82-3.28,2.89-6.93,2.88-10.59.76-9.31-5.4-16.95-12.73-23.55-21.11-2.29-2.93-4.66-4.2-8.35-4-5.12.28-10.28.19-15.41,0-10.39-.37-17.64-7.65-18.09-18a62.93,62.93,0,0,0-.09-6.45c-.3-2.07,1.6-5.54-1.49-6-3.62-.48-7.92-2.08-11.07,1.89-6.09,7.7-12.82,14.76-21,20.24a42,42,0,0,1-5.09,3.08,8.16,8.16,0,0,1-9.29-1.12,8.25,8.25,0,0,1-3-8.86c1-4,2-8,3-12,.59-2.51-.34-3.66-3.07-3.59-4.14.12-8.3.14-12.43-.11-9.61-.59-16.86-7.76-17-17.42-.24-17.57-.21-35.14,0-52.71.12-10.07,8-17.46,18.43-17.58C48,16,59.88,16.09,71.82,16.09ZM56.66,120.5c2.53-.3,3.87-1.89,5.38-3.08A99.31,99.31,0,0,0,79.83,98.8a8.72,8.72,0,0,1,8-3.83c6.12.18,12.25.12,18.38,0,7.31-.12,10.6-3.39,10.64-10.72q.11-24.33,0-48.68c0-7-3.5-10.51-10.36-10.53q-35-.11-70,0c-6.34,0-9.92,3.51-9.95,9.83q-.15,25.08,0,50.18c0,6.37,3.42,9.72,9.86,9.89,5.62.14,11.26.1,16.89.06,4.37,0,6.2,2,6.25,6.4C59.55,107.88,58.17,114.1,56.66,120.5ZM167.78,152c-1.93-6.72-3.37-12.44-3.41-18.4,0-6.33,1.17-7.71,7.37-7.77,5-.05,9.94.06,14.9,0,7.6-.13,10.79-3.35,10.81-11q.08-23.85,0-47.69c0-7.61-3.38-11-10.86-11.08-18.88-.07-37.76,0-56.64-.08-3.21,0-4.29.89-4.2,4.21.24,8.27.18,16.56,0,24.83-.13,8.6-4.46,16.11-11.88,17.55-6.73,1.3-7.44,4.81-6.78,10.16.14,1.14,0,2.32,0,3.48.3,6.1,3.53,9.44,9.69,9.63,5.79.18,11.6.34,17.38,0,4.81-.31,7.94,1.3,10.83,5.22A79.08,79.08,0,0,0,167.78,152Z\"/><path class=\"cls-1\" d=\"M73.07,56c-6.4,0-12.15-.06-17.9,0-3.28,0-5.76-1-5.68-4.63s2.66-4.59,5.88-4.45c1.49.06,3-.12,4.47,0,4.79.46,8.51.12,7.44-6.47-.43-2.67,1.82-4.32,4.42-4.26s4.81,1.75,4.35,4.4c-1.18,6.75,2.76,6.72,7.44,6.34a43.43,43.43,0,0,1,6.45.09,4,4,0,0,1,4,4.17c0,2.59-1.46,4.88-4.17,4.48-7-1-7.55,5.72-11.08,8.81-1.54,1.35.36,2.28,1.27,3.06,2.13,1.83,4.38,3.53,6.52,5.36s2.74,4.28.8,6.54c-1.74,2-4,2-6.27.53-.28-.18-.52-.41-.79-.6-3.22-2.29-5.94-6.88-9.46-6.66s-6.61,4-9.91,6.17a19.94,19.94,0,0,1-2.55,1.52c-2.51,1.19-5,1.29-6.46-1.44-1.42-2.57-.6-4.82,1.91-6.34A88.31,88.31,0,0,0,68.17,62.07C69.84,60.52,71.73,59.09,73.07,56Z\"/><path class=\"cls-1\" d=\"M153,104.27a43.76,43.76,0,0,1-4.91,0c-3.27-.4-5.24.75-6.27,4-.9,2.88-2.84,5.23-6.28,3.82-3.66-1.5-3.21-4.49-1.94-7.52,4.68-11.12,9.36-22.22,13.94-33.37,1-2.36,2.28-4.1,5-4.08s3.89,2,4.85,4.25c4.66,11.11,9.4,22.2,14.14,33.28,1.3,3,1.69,6-2,7.46-3.4,1.35-5.49-.73-6.29-3.74C161.49,102,156.3,105.12,153,104.27Zm-.84-20.39a64.73,64.73,0,0,1-2.78,6.37c-2.25,3.89-1.16,4.85,3.14,4.89s4.82-1.35,2.93-4.73C154.36,88.42,154.15,86,152.18,83.88Z\"/></svg>";
57966
-
57967
- const streamsWhiteNightsIcon = "<svg xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" width=\"50\" height=\"50\" viewBox=\"0 0 50 50\">\n <defs>\n <clipPath id=\"clip-Icon\">\n <rect width=\"50\" height=\"50\"/>\n </clipPath>\n </defs>\n <g id=\"Icon\" clip-path=\"url(#clip-Icon)\">\n <g id=\"icon2\" transform=\"translate(-0.041 0)\">\n <path id=\"Контур_77\" data-name=\"Контур 77\" d=\"M6.493,13v8.266h6.275V19.74H8.31V17.714h4.006V16.3H8.31V14.53h4.365V13Zm7.5,0v8.266h1.7V15.732h.023l3.438,5.534h1.818V13h-1.7v5.545h-.023L15.8,13Z\" fill=\"#fff\"/>\n <path id=\"Контур_76\" data-name=\"Контур 76\" d=\"M29.949,29.1V26.774H31.94a1.4,1.4,0,0,1,.938.272,1.1,1.1,0,0,1,.313.874,1.155,1.155,0,0,1-.313.9,1.375,1.375,0,0,1-.938.278ZM28.132,25.36v8.266h1.817V30.4h1.818a1.353,1.353,0,0,1,.984.3,1.637,1.637,0,0,1,.394.949c.046.333.079.681.1,1.042a3.2,3.2,0,0,0,.185.938h1.819a1.218,1.218,0,0,1-.191-.423,3.611,3.611,0,0,1-.093-.527c-.019-.185-.033-.367-.041-.544s-.016-.332-.023-.463a5.052,5.052,0,0,0-.087-.625,2.109,2.109,0,0,0-.2-.573,1.586,1.586,0,0,0-.359-.451,1.414,1.414,0,0,0-.556-.284v-.023a1.926,1.926,0,0,0,1-.81,2.494,2.494,0,0,0,.307-1.262,2.308,2.308,0,0,0-.165-.88,2.128,2.128,0,0,0-.486-.724,2.3,2.3,0,0,0-.764-.492,2.67,2.67,0,0,0-1-.179ZM43.506,30.5V25.36H41.689V30.5a2.065,2.065,0,0,1-.37,1.36,1.7,1.7,0,0,1-1.343.434,2.086,2.086,0,0,1-.886-.156,1.283,1.283,0,0,1-.758-.978,3.748,3.748,0,0,1-.058-.66V25.36H36.456V30.5a3.16,3.16,0,0,0,.92,2.5,3.807,3.807,0,0,0,2.6.81,3.82,3.82,0,0,0,2.593-.816,3.132,3.132,0,0,0,.937-2.492Z\" fill=\"#fff\"/>\n <path id=\"Контур_80\" data-name=\"Контур 80\" d=\"M22.646,31.2H4.689a4.505,4.505,0,0,1-4.5-4.5V8.5A4.505,4.505,0,0,1,4.689,4h18.2a4.505,4.505,0,0,1,4.5,4.5v8.445l-.893.1a3.184,3.184,0,0,0-2.846,3.177V30.5l-.465.7ZM4.689,6a2.5,2.5,0,0,0-2.5,2.5V26.7a2.5,2.5,0,0,0,2.5,2.5H21.65V20.22a5.18,5.18,0,0,1,3.739-4.992V8.5a2.5,2.5,0,0,0-2.5-2.5Z\" fill=\"#fff\"/>\n <path id=\"Контур_81\" data-name=\"Контур 81\" d=\"M30.127,47.884a1,1,0,0,1-1-1V43.267H26.846a5.206,5.206,0,0,1-5.2-5.2V20.222a5.206,5.206,0,0,1,5.2-5.2H44.692a5.206,5.206,0,0,1,5.2,5.2V38.068a5.206,5.206,0,0,1-5.2,5.2H35.058l-4.216,4.316A1,1,0,0,1,30.127,47.884ZM26.846,17.022a3.2,3.2,0,0,0-3.2,3.2V38.067a3.2,3.2,0,0,0,3.2,3.2h3.281a1,1,0,0,1,1,1v2.162l2.8-2.86a1,1,0,0,1,.715-.3H44.692a3.2,3.2,0,0,0,3.2-3.2V20.222a3.2,3.2,0,0,0-3.2-3.2Z\" fill=\"#fff\"/>\n </g>\n </g>\n</svg>\n";
57968
-
57969
57982
  const VERSION$4 = '0.0.1';
57970
- const T$6 = 'plugins.multicamera';
57983
+ const T$7 = 'plugins.multicamera';
57971
57984
  /**
57972
57985
  * `PLUGIN` that adds support for loading multiple streams and switching between them using the media control UI.
57973
57986
  * @beta
@@ -58013,6 +58026,8 @@ class MultiCamera extends UICorePlugin {
58013
58026
  // Don't mutate the options, TODO check if some plugin observes the options.multicamera
58014
58027
  this.multicamera = this.options.multisources.map((item) => ({ ...item }));
58015
58028
  this.noActiveStreams = this.multicamera.every((item) => !item.live);
58029
+ // TODO filter out non-live
58030
+ this.core.options.sources = expandMediaSource(this.multicamera[0]);
58016
58031
  }
58017
58032
  bindEvents() {
58018
58033
  this.listenTo(this.core, Events$1.CORE_READY, this.bindPlaybackEvents);
@@ -58021,15 +58036,10 @@ class MultiCamera extends UICorePlugin {
58021
58036
  this.listenTo(this.core.mediaControl, Events$1.MEDIACONTROL_HIDE, this.hideSelectLevelMenu);
58022
58037
  }
58023
58038
  unBindEvents() {
58024
- // @ts-ignore
58025
- this.stopListening(this.core, Events$1.CORE_READY);
58026
- // @ts-ignore
58027
- this.stopListening(this.core.mediaControl, Events$1.MEDIACONTROL_CONTAINERCHANGED);
58028
- // @ts-ignore
58029
- this.stopListening(this.core.mediaControl, Events$1.MEDIACONTROL_RENDERED);
58030
- // @ts-ignore
58031
- this.stopListening(this.core.mediaControl, Events$1.MEDIACONTROL_HIDE);
58032
- // @ts-ignore
58039
+ this.stopListening(this.core, Events$1.CORE_READY, this.bindPlaybackEvents);
58040
+ this.stopListening(this.core.mediaControl, Events$1.MEDIACONTROL_CONTAINERCHANGED, this.reload);
58041
+ this.stopListening(this.core.mediaControl, Events$1.MEDIACONTROL_RENDERED, this.render);
58042
+ this.stopListening(this.core.mediaControl, Events$1.MEDIACONTROL_HIDE, this.hideSelectLevelMenu);
58033
58043
  this.stopListening(this.core.activePlayback, Events$1.PLAYBACK_PLAY, this.onPlay);
58034
58044
  }
58035
58045
  onPlay() {
@@ -58045,119 +58055,65 @@ class MultiCamera extends UICorePlugin {
58045
58055
  this.bindPlaybackEvents();
58046
58056
  }
58047
58057
  shouldRender() {
58048
- if (!this.core.activeContainer || this.noActiveStreams) {
58049
- return false;
58050
- }
58051
- if (!this.core.activePlayback) {
58058
+ if (this.noActiveStreams) {
58052
58059
  return false;
58053
58060
  }
58054
58061
  return this.multicamera.length >= 2;
58055
58062
  }
58056
58063
  render() {
58057
- if (this.shouldRender()) {
58058
- let numActiveSources = 0;
58059
- // const currentSource = this.core.options.source
58060
- const currentSource = this.core.activePlayback?.sourceMedia;
58061
- for (const item of this.multicamera) {
58062
- if (item.live) {
58063
- numActiveSources++;
58064
- }
58065
- if (!this.currentCamera && item.source === currentSource) {
58066
- this.currentCamera = item;
58067
- }
58068
- }
58069
- // const mediaControl = this.core.getPlugin('media_control')
58070
- if (this.currentTime &&
58071
- // TODO check the last active playback type instead
58072
- // !mediaControl.$el.hasClass('live') &&
58073
- this.core.getPlaybackType() !== Playback.LIVE) {
58074
- if (this.currentTime < this.core.activePlayback.getDuration()) {
58075
- this.core.activePlayback.seek(this.currentTime);
58076
- }
58077
- this.currentTime = 0;
58078
- // if (mediaControl.$el.hasClass('dvr')) {
58079
- // this.core.activeContainer.dvrInUse = true;
58080
- // }
58081
- }
58082
- // TODO current source
58083
- this.$el.html(this.template({
58084
- streams: this.multicamera,
58085
- multisources_mode: this.options.multisourcesMode,
58086
- }));
58087
- if ((numActiveSources <= 1 &&
58088
- this.options.multisourcesMode !== 'show_all') ||
58089
- this.options.multisourcesMode === 'one_first') {
58090
- this.$el.hide();
58091
- }
58092
- else {
58093
- this.$el.show();
58064
+ if (!this.core.activeContainer || !this.core.activePlayback) {
58065
+ return this;
58066
+ }
58067
+ if (!this.shouldRender()) {
58068
+ return this;
58069
+ }
58070
+ let numActiveSources = 0;
58071
+ const currentSource = this.core.activePlayback?.sourceMedia;
58072
+ for (const item of this.multicamera) {
58073
+ if (item.live) {
58074
+ numActiveSources++;
58094
58075
  }
58095
- if (this.core.mediaControl.$multiCameraSelector &&
58096
- this.core.mediaControl.$multiCameraSelector.length > 0) {
58097
- this.core.mediaControl.$multiCameraSelector.append(this.el);
58076
+ if (!this.currentCamera && item.source === currentSource) {
58077
+ this.currentCamera = item;
58098
58078
  }
58099
- else {
58100
- this.core.mediaControl.$('.media-control-right-panel').append(this.el);
58101
- }
58102
- if (Object.prototype.hasOwnProperty.call(this.core.mediaControl, '$multiCameraSelector') &&
58103
- this.core.mediaControl.$multiCameraSelector.find('span.multicamera-icon').length > 0) {
58104
- if (~window.location.href.indexOf('whitenights.gcdn.co')) {
58105
- this.core.mediaControl.$multiCameraSelector
58106
- .find('span.multicamera-icon')
58107
- .append(streamsWhiteNightsIcon);
58108
- }
58109
- else if (~window.location.href.indexOf('momentosolutions.gcdn.co')) {
58110
- this.core.mediaControl.$multiCameraSelector
58111
- .find('span.multicamera-icon')
58112
- .append(streamsMomentoIcon);
58113
- }
58114
- else {
58115
- this.core.mediaControl.$multiCameraSelector
58116
- .find('span.multicamera-icon')
58117
- .append(streamsIcon);
58118
- }
58079
+ }
58080
+ if (this.currentTime &&
58081
+ this.core.getPlaybackType() !== Playback.LIVE) {
58082
+ if (this.currentTime < this.core.activePlayback.getDuration()) {
58083
+ this.core.activePlayback.seek(this.currentTime);
58119
58084
  }
58120
- this.highlightCurrentLevel();
58085
+ this.currentTime = 0;
58086
+ }
58087
+ this.$el.html(this.template({
58088
+ streams: this.multicamera,
58089
+ multisources_mode: this.options.multisourcesMode,
58090
+ }));
58091
+ if ((numActiveSources < 2 &&
58092
+ this.options.multisourcesMode !== 'show_all') ||
58093
+ this.options.multisourcesMode === 'one_first') {
58094
+ this.$el.hide();
58095
+ }
58096
+ else {
58097
+ this.$el.show();
58121
58098
  }
58099
+ const mediaControl = this.core.getPlugin('media_control');
58100
+ mediaControl.slot('multicamera', this.$el);
58101
+ this.$el
58102
+ .find('span.multicamera-icon')
58103
+ .html(streamsIcon);
58104
+ this.highlightCurrentLevel();
58122
58105
  return this;
58123
58106
  }
58124
58107
  onCameraSelect(event) {
58125
58108
  const value = event.currentTarget.dataset
58126
58109
  .multicameraSelectorSelect;
58127
- trace(`${T$6} onCameraSelect`, { value });
58110
+ trace(`${T$7} onCameraSelect`, { value });
58128
58111
  if (value !== undefined) {
58129
58112
  this.changeById(parseInt(value, 10));
58130
58113
  }
58131
58114
  event.stopPropagation();
58132
58115
  return false;
58133
58116
  }
58134
- activeById(id, active) {
58135
- this.setLiveStatus(id, active);
58136
- if (!this.currentCamera && !this.noActiveStreams) {
58137
- return;
58138
- }
58139
- if (this.noActiveStreams && !active) {
58140
- return;
58141
- }
58142
- if (this.currentCamera) {
58143
- if (this.options.multisourcesMode === 'only_live') {
58144
- this.behaviorLive(id, active);
58145
- }
58146
- if (this.options.multisourcesMode === 'one_first') {
58147
- this.behaviorOne(id, active);
58148
- }
58149
- if (this.options.multisourcesMode === 'show_all') {
58150
- this.behaviorAll(id, active);
58151
- }
58152
- }
58153
- else {
58154
- if (this.noActiveStreams && active) {
58155
- this.changeById(id);
58156
- this.noActiveStreams = false;
58157
- }
58158
- }
58159
- this.render();
58160
- }
58161
58117
  setLiveStatus(id, active) {
58162
58118
  try {
58163
58119
  const index = this.findIndexById(id);
@@ -58220,7 +58176,6 @@ class MultiCamera extends UICorePlugin {
58220
58176
  this.currentCamera = null;
58221
58177
  this.noActiveStreams = true;
58222
58178
  this.core.trigger('core:multicamera:no_active_translation');
58223
- // this.changeById(this.multicamera[nextIndex].id);
58224
58179
  }
58225
58180
  showError() {
58226
58181
  this.core.activePlayback.pause();
@@ -58243,24 +58198,19 @@ class MultiCamera extends UICorePlugin {
58243
58198
  }
58244
58199
  hideError() {
58245
58200
  try {
58246
- this.core.mediaControl.enableControlButton();
58201
+ this.core.getPlugin('media_control')?.enableControlButton();
58247
58202
  }
58248
58203
  catch (error) {
58249
58204
  reportError(error);
58250
58205
  }
58251
58206
  }
58252
58207
  changeById(id) {
58253
- trace(`${T$6} changeById`, { id });
58208
+ trace(`${T$7} changeById`, { id });
58254
58209
  queueMicrotask(() => {
58255
58210
  const playbackOptions = this.core.options.playback || {};
58256
- // TODO figure out what this does
58211
+ // TODO figure out if it's needed
58257
58212
  playbackOptions.recycleVideo = Browser.isMobile;
58258
- this.currentCamera = this.findElementById(id) ?? null;
58259
- trace(`${T$6} changeById`, {
58260
- id,
58261
- currentCamera: this.currentCamera,
58262
- multicamera: this.multicamera,
58263
- });
58213
+ this.currentCamera = this.findElementById(id);
58264
58214
  if (!this.currentCamera) {
58265
58215
  return;
58266
58216
  }
@@ -58277,31 +58227,21 @@ class MultiCamera extends UICorePlugin {
58277
58227
  // TODO remove?
58278
58228
  // for html5 playback:
58279
58229
  this.options.dvrEnabled = this.currentCamera.dvr;
58280
- trace(`${T$6} changeById`, { currentCamera: this.currentCamera });
58230
+ trace(`${T$7} changeById`, { currentCamera: this.currentCamera });
58281
58231
  // TODO
58282
58232
  this.core.configure({
58283
58233
  playback: playbackOptions,
58284
58234
  source: this.currentCamera.source, // TODO ensure that the preferred transport is used
58285
- video360: {
58286
- // TODO
58287
- projection: this.currentCamera.projection,
58288
- },
58289
58235
  fullscreenDisable,
58290
58236
  autoPlay: this.playing,
58291
58237
  disableCanAutoPlay: true,
58292
58238
  });
58293
- this.core.activeContainer.mediaControlDisabled = false;
58239
+ this.core.activeContainer?.enableMediaControl();
58294
58240
  });
58295
58241
  this.toggleContextMenu();
58296
58242
  }
58297
- getCamerasList() {
58298
- return this.multicamera;
58299
- }
58300
- getCurrentCamera() {
58301
- return this.currentCamera;
58302
- }
58303
58243
  findElementById(id) {
58304
- return this.multicamera.find((element) => element.id === id);
58244
+ return this.multicamera.find((element) => element.id === id) ?? null;
58305
58245
  }
58306
58246
  findIndexById(id) {
58307
58247
  return this.multicamera.findIndex((element) => element.id === id);
@@ -58310,19 +58250,13 @@ class MultiCamera extends UICorePlugin {
58310
58250
  this.toggleContextMenu();
58311
58251
  }
58312
58252
  hideSelectLevelMenu() {
58313
- this.$('.multicamera ul').hide();
58253
+ this.$('ul').hide();
58314
58254
  }
58315
58255
  toggleContextMenu() {
58316
- this.$('.multicamera ul').toggle();
58256
+ this.$('ul').toggle();
58317
58257
  }
58318
- // private buttonElement(): ZeptoResult {
58319
- // return this.$('.multicamera button');
58320
- // }
58321
- // private buttonElementText(): ZeptoResult {
58322
- // return this.$('.multicamera button .quality-text');
58323
- // }
58324
58258
  levelElement(id) {
58325
- return this.$('.multicamera ul li > div' +
58259
+ return this.$('ul .multicamera-item' +
58326
58260
  (id !== undefined
58327
58261
  ? '[data-multicamera-selector-select="' + id + '"]'
58328
58262
  : ''));
@@ -58334,13 +58268,29 @@ class MultiCamera extends UICorePlugin {
58334
58268
  this.levelElement(this.currentCamera.id).addClass('multicamera-active');
58335
58269
  }
58336
58270
  }
58271
+ function expandMediaSource(source) {
58272
+ const result = [{
58273
+ source: source.source,
58274
+ mimeType: guessMimeType(source.source),
58275
+ }];
58276
+ if (source.source_dash) {
58277
+ result.push({
58278
+ source: source.source_dash,
58279
+ mimeType: MIME_TYPE_DASH,
58280
+ });
58281
+ }
58282
+ if (source.hls_mpegts_url) {
58283
+ result.push(source.hls_mpegts_url);
58284
+ }
58285
+ return result;
58286
+ }
58337
58287
 
58338
58288
  const pipIcon = "<svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n <path\n d=\"M21.05 3.00001C21.302 3.00001 21.5435 3.10003 21.7217 3.27833C21.9 3.45646 22 3.69802 22 3.95003V10.6H20.1V4.90003H4.90001V18.2H10.6V20.1H3.95001C3.69802 20.1 3.45647 20 3.27832 19.8217C3.10002 19.6436 3 19.402 3 19.15V3.95001C3 3.69802 3.10002 3.45647 3.27832 3.27832C3.45644 3.10002 3.69801 3 3.95001 3L21.05 3.00001ZM21.05 12.5C21.302 12.5 21.5435 12.6 21.7217 12.7783C21.9 12.9565 22 13.198 22 13.45V19.15C22 19.402 21.9 19.6436 21.7217 19.8217C21.5436 20 21.302 20.1 21.05 20.1H13.45C13.198 20.1 12.9564 20 12.7783 19.8217C12.6 19.6436 12.5 19.402 12.5 19.15V13.45C12.5 13.198 12.6 12.9565 12.7783 12.7783C12.9564 12.6 13.198 12.5 13.45 12.5H21.05ZM7.47178 6.12823L9.60928 8.26572L11.5501 6.32492V11.5499H6.32509L8.26589 9.60911L6.12839 7.47161L7.47178 6.12823Z\"\n fill=\"#C9C9C9\"/>\n</svg>\n";
58339
58289
 
58340
58290
  const buttonHtml$2 = "<button class=\"gplayer-lite-btn gcore-skin-button-color\">\n <%= pipIcon %>\n</button>\n";
58341
58291
 
58342
58292
  const VERSION$3 = '0.0.1';
58343
- const T$5 = `plugins.pip`;
58293
+ const T$6 = `plugins.pip`;
58344
58294
  /**
58345
58295
  * `PLUGIN` that enables picture-in-picture mode.
58346
58296
  * @public
@@ -58398,7 +58348,7 @@ class PictureInPicture extends UICorePlugin {
58398
58348
  });
58399
58349
  }
58400
58350
  isPiPSupported() {
58401
- trace(`${T$5} isPiPSupported`, {
58351
+ trace(`${T$6} isPiPSupported`, {
58402
58352
  pictureInPictureEnabled: !!document.pictureInPictureEnabled,
58403
58353
  requestPictureInPicture: !!HTMLVideoElement.prototype.requestPictureInPicture,
58404
58354
  });
@@ -58420,7 +58370,7 @@ class PictureInPicture extends UICorePlugin {
58420
58370
  return this;
58421
58371
  }
58422
58372
  togglePictureInPicture() {
58423
- trace(`${T$5} togglePictureInPicture`);
58373
+ trace(`${T$6} togglePictureInPicture`);
58424
58374
  if (this.videoElement !== document.pictureInPictureElement) {
58425
58375
  this.requestPictureInPicture();
58426
58376
  }
@@ -58429,13 +58379,13 @@ class PictureInPicture extends UICorePlugin {
58429
58379
  }
58430
58380
  }
58431
58381
  requestPictureInPicture() {
58432
- trace(`${T$5} requestPictureInPicture`, {
58382
+ trace(`${T$6} requestPictureInPicture`, {
58433
58383
  videoElement: !!this.videoElement,
58434
58384
  });
58435
58385
  this.videoElement.requestPictureInPicture();
58436
58386
  }
58437
58387
  exitPictureInPicture() {
58438
- trace(`${T$5} exitPictureInPicture`);
58388
+ trace(`${T$6} exitPictureInPicture`);
58439
58389
  document.exitPictureInPicture();
58440
58390
  }
58441
58391
  }
@@ -58462,7 +58412,7 @@ const DEFAULT_PLAYBACK_RATES = [
58462
58412
  { value: 2.0, label: '2x' },
58463
58413
  ];
58464
58414
  const DEFAULT_PLAYBACK_RATE = 1;
58465
- const T$4 = 'plugins.playback_rate';
58415
+ const T$5 = 'plugins.playback_rate';
58466
58416
  /**
58467
58417
  * `PLUGIN` that allows changing the playback speed of the video.
58468
58418
  * @public
@@ -58548,7 +58498,7 @@ class PlaybackRate extends UICorePlugin {
58548
58498
  this.listenTo(this.core, Events$1.CORE_ACTIVE_CONTAINER_CHANGED, this.onActiveContainerChange);
58549
58499
  }
58550
58500
  onCoreReady() {
58551
- trace(`${T$4} onCoreReady`);
58501
+ trace(`${T$5} onCoreReady`);
58552
58502
  const mediaControl = this.core.getPlugin('media_control');
58553
58503
  assert(mediaControl, 'media_control plugin is required');
58554
58504
  const gear = this.core.getPlugin('bottom_gear');
@@ -58557,7 +58507,7 @@ class PlaybackRate extends UICorePlugin {
58557
58507
  this.listenTo(gear, GearEvents.RENDERED, this.onGearRendered);
58558
58508
  }
58559
58509
  onActiveContainerChange() {
58560
- trace(`${T$4} onActiveContainerChange`);
58510
+ trace(`${T$5} onActiveContainerChange`);
58561
58511
  this.metadataLoaded = false;
58562
58512
  this.listenTo(this.core.activePlayback, Events$1.PLAYBACK_STOP, this.onStop);
58563
58513
  this.listenTo(this.core.activePlayback, Events$1.PLAYBACK_PLAY, this.onPlay);
@@ -58565,15 +58515,15 @@ class PlaybackRate extends UICorePlugin {
58565
58515
  this.listenTo(this.core.activeContainer, Events$1.CONTAINER_LOADEDMETADATA, this.onMetaDataLoaded);
58566
58516
  }
58567
58517
  onMediaControlRendered() {
58568
- trace(`${T$4} onMediaControlRendered`);
58518
+ trace(`${T$5} onMediaControlRendered`);
58569
58519
  this.render();
58570
58520
  }
58571
58521
  onGearRendered() {
58572
- trace(`${T$4} onGearRendered`);
58522
+ trace(`${T$5} onGearRendered`);
58573
58523
  this.mount();
58574
58524
  }
58575
58525
  mount() {
58576
- trace(`${T$4} mount`, {
58526
+ trace(`${T$5} mount`, {
58577
58527
  shouldMount: this.shouldMount(),
58578
58528
  });
58579
58529
  if (!this.shouldMount()) {
@@ -58590,7 +58540,7 @@ class PlaybackRate extends UICorePlugin {
58590
58540
  })));
58591
58541
  }
58592
58542
  onMetaDataLoaded() {
58593
- trace(`${T$4} onMetaDataLoaded`, {
58543
+ trace(`${T$5} onMetaDataLoaded`, {
58594
58544
  playbackType: this.core.activePlayback.getPlaybackType(),
58595
58545
  dvrEnabled: this.core.activePlayback.dvrEnabled,
58596
58546
  });
@@ -58612,7 +58562,7 @@ class PlaybackRate extends UICorePlugin {
58612
58562
  this.core.activePlayback?.setPlaybackRate(this.selectedRate);
58613
58563
  }
58614
58564
  else {
58615
- trace(`${T$4} onPlaybackRateChange not steering to the selected rate, it is seemingly a catchup algorithm working`, {
58565
+ trace(`${T$5} onPlaybackRateChange not steering to the selected rate, it is seemingly a catchup algorithm working`, {
58616
58566
  playbackRate,
58617
58567
  selectedRate: this.selectedRate,
58618
58568
  });
@@ -58675,13 +58625,13 @@ class PlaybackRate extends UICorePlugin {
58675
58625
  }
58676
58626
  }
58677
58627
  syncRate() {
58678
- trace(`${T$4} syncRate`, {
58628
+ trace(`${T$5} syncRate`, {
58679
58629
  selectedRate: this.selectedRate,
58680
58630
  });
58681
58631
  this.core.activePlayback?.setPlaybackRate(this.selectedRate);
58682
58632
  }
58683
58633
  resetPlaybackRate() {
58684
- trace(`${T$4} resetPlaybackRate`, {
58634
+ trace(`${T$5} resetPlaybackRate`, {
58685
58635
  selectedRate: this.selectedRate,
58686
58636
  });
58687
58637
  this.core.activePlayback?.setPlaybackRate(DEFAULT_PLAYBACK_RATE);
@@ -58716,7 +58666,7 @@ class PlaybackRate extends UICorePlugin {
58716
58666
  ?.label || `x${rate}`);
58717
58667
  }
58718
58668
  highlightCurrentRate() {
58719
- trace(`${T$4} highlightCurrentRate`, {
58669
+ trace(`${T$5} highlightCurrentRate`, {
58720
58670
  selectedRate: this.selectedRate,
58721
58671
  });
58722
58672
  this.allRateElements().removeClass('current');
@@ -59752,7 +59702,7 @@ class SpinnerThreeBounce extends UIContainerPlugin {
59752
59702
  }
59753
59703
  }
59754
59704
 
59755
- const T$3 = 'plugins.source_controller';
59705
+ const T$4 = 'plugins.source_controller';
59756
59706
  const INITIAL_RETRY_DELAY = 1000;
59757
59707
  const MAX_RETRY_DELAY = 5000;
59758
59708
  const RETRY_DELAY_BLUR = 500;
@@ -59764,45 +59714,6 @@ function noSync(cb) {
59764
59714
  * `PLUGIN` that is managing the automatic failover between media sources.
59765
59715
  * @public
59766
59716
  * @remarks
59767
- * Have a look at the {@link https://miro.com/app/board/uXjVLiN15tY=/?share_link_id=390327585787 | source failover diagram} for the details
59768
- * on how sources ordering and selection works. Below is a simplified diagram:
59769
- *
59770
- * ```markdown
59771
- * sources_list:
59772
- * - a.mpd | +--------------------+
59773
- * - b.m3u8 |--->| init |
59774
- * - ... | |--------------------|
59775
- * | current_source = 0 |
59776
- * +--------------------+
59777
- * |
59778
- * | source = a.mpd
59779
- * | playback = dash.js
59780
- * v
59781
- * +------------------+
59782
- * +-->| load source |
59783
- * | +---------|--------+
59784
- * | v
59785
- * | +------------------+
59786
- * | | play |
59787
- * | +---------|--------+
59788
- * | |
59789
- * | v
59790
- * | +-----------------------+
59791
- * | | on playback_error |
59792
- * | |-----------------------|
59793
- * | | current_source = |
59794
- * | | (current_source + 1) |
59795
- * | | % len sources_list |
59796
- * | | |
59797
- * | | delay 1..3s |
59798
- * | +---------------|-------+
59799
- * | |
59800
- * | source=b.m3u8 |
59801
- * | playback=hls.js |
59802
- * +-------------------+
59803
- *
59804
- * ```
59805
- *
59806
59717
  * @example
59807
59718
  * ```ts
59808
59719
  * import { SourceController } from '@gcorevideo/player'
@@ -59928,7 +59839,7 @@ class SourceController extends CorePlugin {
59928
59839
  }
59929
59840
  bindContainerEventListeners() {
59930
59841
  this.core.activePlayback.on(Events$1.PLAYBACK_ERROR, (error) => {
59931
- trace(`${T$3} on PLAYBACK_ERROR`, {
59842
+ trace(`${T$4} on PLAYBACK_ERROR`, {
59932
59843
  error: {
59933
59844
  code: error?.code,
59934
59845
  description: error?.description,
@@ -59952,7 +59863,7 @@ class SourceController extends CorePlugin {
59952
59863
  }
59953
59864
  });
59954
59865
  this.listenTo(this.core.activeContainer, Events$1.CONTAINER_PLAY, (_, { autoPlay }) => {
59955
- trace(`${T$3} onContainerPlay`, {
59866
+ trace(`${T$4} onContainerPlay`, {
59956
59867
  autoPlay,
59957
59868
  currentSource: this.sourcesList[this.currentSourceIndex],
59958
59869
  retrying: this.active,
@@ -59970,7 +59881,7 @@ class SourceController extends CorePlugin {
59970
59881
  this.sourcesDelay = {};
59971
59882
  }
59972
59883
  retryPlayback() {
59973
- trace(`${T$3} retryPlayback enter`, {
59884
+ trace(`${T$4} retryPlayback enter`, {
59974
59885
  currentSourceIndex: this.currentSourceIndex,
59975
59886
  currentSource: this.sourcesList[this.currentSourceIndex],
59976
59887
  });
@@ -59978,18 +59889,18 @@ class SourceController extends CorePlugin {
59978
59889
  this.switching = true;
59979
59890
  this.core.activeContainer?.getPlugin('spinner')?.show(0);
59980
59891
  this.getNextMediaSource().then((nextSource) => {
59981
- trace(`${T$3} retryPlayback syncing...`, {
59892
+ trace(`${T$4} retryPlayback syncing...`, {
59982
59893
  nextSource,
59983
59894
  });
59984
59895
  const rnd = Math.round(RETRY_DELAY_BLUR * Math.random());
59985
59896
  this.sync(() => {
59986
59897
  this.switching = false;
59987
59898
  this.core.load(nextSource.source, nextSource.mimeType);
59988
- trace(`${T$3} retryPlayback loaded`, {
59899
+ trace(`${T$4} retryPlayback loaded`, {
59989
59900
  nextSource,
59990
59901
  });
59991
59902
  setTimeout(() => {
59992
- trace(`${T$3} retryPlayback playing`, {
59903
+ trace(`${T$4} retryPlayback playing`, {
59993
59904
  autoPlay: this.autoPlay,
59994
59905
  nextSource,
59995
59906
  });
@@ -60501,7 +60412,7 @@ class ClosedCaptions extends UICorePlugin {
60501
60412
  // An example implementation of client side performancestatistics
60502
60413
  const WATCH_CUTOFF = 5;
60503
60414
  const STALL_MEASURE_PERIOD = 10;
60504
- const T$2 = 'plugins.telemetry';
60415
+ const T$3 = 'plugins.telemetry';
60505
60416
  /**
60506
60417
  * Telemetry event type
60507
60418
  * @beta
@@ -60611,7 +60522,7 @@ class Telemetry extends ContainerPlugin {
60611
60522
  }
60612
60523
  onReady() {
60613
60524
  this.sendInit();
60614
- trace(`${T$2} onReady`, {
60525
+ trace(`${T$3} onReady`, {
60615
60526
  autoPlay: this.options.autoPlay,
60616
60527
  });
60617
60528
  if (this.options.autoPlay) {
@@ -63312,7 +63223,7 @@ function loadImageDimensions(url) {
63312
63223
  });
63313
63224
  }
63314
63225
 
63315
- const T$1 = 'plugins.thumbnails';
63226
+ const T$2 = 'plugins.thumbnails';
63316
63227
  /**
63317
63228
  * `PLUGIN` that displays the thumbnails of the video when available.
63318
63229
  * @public
@@ -63430,14 +63341,14 @@ class Thumbnails extends UICorePlugin {
63430
63341
  if (!this.options.thumbnails ||
63431
63342
  !this.options.thumbnails.sprite ||
63432
63343
  !this.options.thumbnails.vtt) {
63433
- trace(`${T$1} misconfigured: options.thumbnails.sprite and options.thumbnails.vtt are required`);
63344
+ trace(`${T$2} misconfigured: options.thumbnails.sprite and options.thumbnails.vtt are required`);
63434
63345
  this.destroy();
63435
63346
  return;
63436
63347
  }
63437
63348
  const { sprite: spriteSheet, vtt } = this.options.thumbnails;
63438
63349
  this.thumbs = this.buildSpriteConfig(parseVTT(vtt), spriteSheet);
63439
63350
  if (!this.thumbs.length) {
63440
- trace(`${T$1} failed to parse the sprite sheet`);
63351
+ trace(`${T$2} failed to parse the sprite sheet`);
63441
63352
  this.destroy();
63442
63353
  return;
63443
63354
  }
@@ -63686,6 +63597,321 @@ function parseVTT(vtt) {
63686
63597
  return cues;
63687
63598
  }
63688
63599
 
63600
+ const T$1 = 'plugins.token_refresh';
63601
+ /**
63602
+ * Matches the `/{token}/{expires}/` segment in a Gcore protected-content URL.
63603
+ * Token is base64url (letters, digits, `-`, `_`); expires is a ≥10-digit Unix timestamp.
63604
+ */
63605
+ const TOKEN_SEGMENT_RE = /\/([A-Za-z0-9_-]{6,})\/(1\d{9,})\//;
63606
+ function extractTokenState(url) {
63607
+ const m = url.match(TOKEN_SEGMENT_RE);
63608
+ if (!m)
63609
+ return null;
63610
+ return { token: m[1], expires: parseInt(m[2], 10) };
63611
+ }
63612
+ /** Replaces the exact `/{oldToken}/{oldExpires}/` segment in a URL. */
63613
+ function rewriteUrl(url, from, to) {
63614
+ const oldPart = `/${from.token}/${from.expires}/`;
63615
+ const newPart = `/${to.token}/${to.expires}/`;
63616
+ return url.includes(oldPart) ? url.replace(oldPart, newPart) : url;
63617
+ }
63618
+ /**
63619
+ * Normalises a URL by removing the `/{token}/{expires}/` segment so two URLs
63620
+ * for the same stream with different token pairs compare equal.
63621
+ * Returns `null` for unparseable input.
63622
+ */
63623
+ function streamKey(url) {
63624
+ try {
63625
+ const u = new URL(url);
63626
+ return u.origin + u.pathname.replace(TOKEN_SEGMENT_RE, '/');
63627
+ }
63628
+ catch {
63629
+ return null;
63630
+ }
63631
+ }
63632
+ /**
63633
+ * Returns a custom hls.js loader class that transparently rewrites the
63634
+ * token/expires path segments in every request URL.
63635
+ *
63636
+ * The returned class extends the default hls.js XhrLoader so all native
63637
+ * hls.js behaviour (retry, timeout, range requests …) is preserved.
63638
+ */
63639
+ function createTokenRewritingLoader(getOriginal, getCurrent) {
63640
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
63641
+ const DefaultLoader = Hls.DefaultConfig.loader;
63642
+ return class TokenRewritingLoader extends DefaultLoader {
63643
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
63644
+ load(context, config, callbacks) {
63645
+ const original = getOriginal();
63646
+ const current = getCurrent();
63647
+ if (original && current && context.url) {
63648
+ context.url = rewriteUrl(context.url, original, current);
63649
+ }
63650
+ super.load(context, config, callbacks);
63651
+ }
63652
+ };
63653
+ }
63654
+ /**
63655
+ * `PLUGIN` — automatic token refresh for Gcore protected-content streams.
63656
+ *
63657
+ * Supports all three playback engines:
63658
+ *
63659
+ * | Engine | Mechanism | Interruption |
63660
+ * |--------|-----------|--------------|
63661
+ * | **hls.js** | Custom loader rewrites every request URL | None |
63662
+ * | **dash.js** | `addRequestInterceptor` rewrites every request URL | None |
63663
+ * | **Native `<video>`** (Safari ≤ iOS 14.4) | Source reload + seek restore | Brief |
63664
+ *
63665
+ * @public
63666
+ * @remarks
63667
+ * Register the plugin once before creating any player instance:
63668
+ * ```ts
63669
+ * import { Player, TokenRefreshPlugin } from '@gcorevideo/player'
63670
+ * Player.registerPlugin(TokenRefreshPlugin)
63671
+ * ```
63672
+ *
63673
+ * Then pass `tokenRefresh` in `PlayerConfig`:
63674
+ * ```ts
63675
+ * const player = new Player({
63676
+ * sources: [{ source: initialUrl, mimeType: 'application/x-mpegURL' }],
63677
+ * tokenRefresh: {
63678
+ * getToken: () => fetch('https://…/token').then(r => r.json()),
63679
+ * ipBound: false,
63680
+ * refreshLeadSeconds: 5,
63681
+ * onTokenRefreshed: (data) => console.log('new token expires', data.expires),
63682
+ * },
63683
+ * })
63684
+ * ```
63685
+ *
63686
+ * @example
63687
+ * Safari native — opt-in Service Worker for fully seamless refresh:
63688
+ * ```js
63689
+ * // Register token-refresh-sw.js (see example/ directory)
63690
+ * // and omit tokenRefresh config — the SW handles rewriting.
63691
+ * ```
63692
+ */
63693
+ class TokenRefreshPlugin extends CorePlugin {
63694
+ /** @internal */
63695
+ static get type() {
63696
+ return 'core';
63697
+ }
63698
+ /** @internal */
63699
+ get name() {
63700
+ return 'token_refresh';
63701
+ }
63702
+ /** @internal */
63703
+ get supportedVersion() {
63704
+ return { min: CLAPPR_VERSION$1 };
63705
+ }
63706
+ /** Token state extracted from the currently-managed source URL */
63707
+ originalState = null;
63708
+ /** Latest token state (updated after each refresh) */
63709
+ currentState = null;
63710
+ /** Scheduled refresh timer handle */
63711
+ refreshTimer = null;
63712
+ /** Playback time (seconds) to restore after a native-video source reload */
63713
+ savedPosition = null;
63714
+ /** True when using native HTML5 Video playback (no request interception) */
63715
+ isNativePlayback = false;
63716
+ /** Set in destroy(); short-circuits late timer callbacks and getToken() resolutions */
63717
+ destroyed = false;
63718
+ /** @internal */
63719
+ bindEvents() {
63720
+ this.listenTo(this.core, Events$1.CORE_CONTAINERS_CREATED, this.onContainersCreated);
63721
+ }
63722
+ /** @internal */
63723
+ destroy() {
63724
+ this.destroyed = true;
63725
+ this.clearTimer();
63726
+ super.destroy();
63727
+ }
63728
+ onContainersCreated() {
63729
+ const container = this.core.containers[0];
63730
+ if (!container)
63731
+ return;
63732
+ const playbackName = container.playback.name;
63733
+ const src = container.playback.options?.src ?? '';
63734
+ trace(`${T$1} onContainersCreated`, { playbackName, src: src.slice(0, 80) });
63735
+ this.isNativePlayback = playbackName !== 'hls' && playbackName !== 'dash';
63736
+ const state = extractTokenState(src);
63737
+ if (!state) {
63738
+ // Active source has no token pattern — drop any refresh state we were
63739
+ // holding for a previous stream (e.g. SourceController rotated away).
63740
+ if (this.originalState) {
63741
+ trace(`${T$1} active source has no token pattern — clearing refresh state`);
63742
+ this.clearTimer();
63743
+ this.originalState = null;
63744
+ this.currentState = null;
63745
+ }
63746
+ return;
63747
+ }
63748
+ // Adopt the new token state if this is the first source we see, or if
63749
+ // the stream has changed (SourceController rotated, consumer called
63750
+ // player.load() with a different stream, or the plugin itself reloaded
63751
+ // with a refreshed URL in the native path).
63752
+ const isNewStream = !this.originalState ||
63753
+ state.token !== this.originalState.token ||
63754
+ state.expires !== this.originalState.expires;
63755
+ if (isNewStream) {
63756
+ trace(`${T$1} adopting source token state`, {
63757
+ token: state.token.slice(0, 8) + '…',
63758
+ expires: new Date(state.expires * 1000).toISOString(),
63759
+ });
63760
+ this.originalState = { ...state };
63761
+ this.currentState = { ...state };
63762
+ this.scheduleRefresh();
63763
+ }
63764
+ // Inject the appropriate interception mechanism for this playback engine.
63765
+ switch (playbackName) {
63766
+ case 'hls':
63767
+ this.injectHlsLoader(container);
63768
+ break;
63769
+ case 'dash':
63770
+ this.injectDashInterceptor(container);
63771
+ break;
63772
+ default:
63773
+ // Native HTML5 Video — no request hooks available.
63774
+ // Seek restore after a token-triggered reload.
63775
+ this.listenToOnce(this.core, Events$1.CORE_ACTIVE_CONTAINER_CHANGED, this.onActiveContainerChangedForNative);
63776
+ break;
63777
+ }
63778
+ }
63779
+ injectHlsLoader(container) {
63780
+ const getOriginal = () => this.originalState;
63781
+ const getCurrent = () => this.currentState;
63782
+ const TokenLoader = createTokenRewritingLoader(getOriginal, getCurrent);
63783
+ $.extend(true, container.playback.options, {
63784
+ playback: {
63785
+ hlsjsConfig: {
63786
+ loader: TokenLoader,
63787
+ },
63788
+ },
63789
+ });
63790
+ trace(`${T$1} HLS custom loader injected`);
63791
+ }
63792
+ injectDashInterceptor(container) {
63793
+ $.extend(true, container.playback.options, {
63794
+ dash: {
63795
+ requestInterceptor: (request) => {
63796
+ if (this.originalState && this.currentState) {
63797
+ request.url = rewriteUrl(request.url, this.originalState, this.currentState);
63798
+ }
63799
+ return Promise.resolve(request);
63800
+ },
63801
+ },
63802
+ });
63803
+ trace(`${T$1} DASH request interceptor injected`);
63804
+ }
63805
+ async reloadNativeSource(data) {
63806
+ const container = this.core.activeContainer;
63807
+ const playback = container?.playback;
63808
+ if (!playback)
63809
+ return;
63810
+ // SourceController (or any other actor) may have switched the active
63811
+ // playback to hls/dash while getToken() was in flight. Those engines
63812
+ // have their own request-interception path; a native reload would
63813
+ // undo the switch.
63814
+ if (playback.name === 'hls' || playback.name === 'dash') {
63815
+ trace(`${T$1} skipping native reload — active playback is ${playback.name}`);
63816
+ return;
63817
+ }
63818
+ if (!this.isNativePlayback) {
63819
+ trace(`${T$1} skipping native reload — no longer in native playback mode`);
63820
+ return;
63821
+ }
63822
+ // Verify the URL we're about to load belongs to the same stream that's
63823
+ // currently active. If SourceController rotated to a different stream
63824
+ // during the getToken() await, the refreshed URL would silently swap
63825
+ // us back, undoing SourceController's decision.
63826
+ const currentSrc = playback.options?.src ?? '';
63827
+ const newUrl = this.opts.ipBound ? data.url_ip : data.url;
63828
+ const activeKey = streamKey(currentSrc);
63829
+ const nextKey = streamKey(newUrl);
63830
+ if (!activeKey || !nextKey || activeKey !== nextKey) {
63831
+ trace(`${T$1} skipping native reload — active source differs from refresh URL`, {
63832
+ activeKey,
63833
+ nextKey,
63834
+ });
63835
+ return;
63836
+ }
63837
+ // Capture current playback position before tearing down the container.
63838
+ const mediaEl = playback.el;
63839
+ const currentTime = mediaEl?.currentTime ?? 0;
63840
+ this.savedPosition = currentTime > 0 ? currentTime : null;
63841
+ trace(`${T$1} native reload`, { newUrl: newUrl.slice(0, 80), savedPosition: this.savedPosition });
63842
+ // core.load() destroys and recreates all containers.
63843
+ this.core.load([{ source: newUrl, mimeType: this.core.options.mimeType ?? 'application/x-mpegURL' }]);
63844
+ }
63845
+ onActiveContainerChangedForNative() {
63846
+ if (this.savedPosition === null)
63847
+ return;
63848
+ const pos = this.savedPosition;
63849
+ this.savedPosition = null;
63850
+ // Wait for the new container to be fully ready before seeking.
63851
+ const container = this.core.activeContainer;
63852
+ if (!container)
63853
+ return;
63854
+ this.listenToOnce(container, Events$1.CONTAINER_READY, () => {
63855
+ trace(`${T$1} native: restoring position`, { pos });
63856
+ container.seek(pos);
63857
+ container.play();
63858
+ });
63859
+ }
63860
+ scheduleRefresh() {
63861
+ this.clearTimer();
63862
+ if (this.destroyed || !this.currentState)
63863
+ return;
63864
+ const leadMs = (this.opts.refreshLeadSeconds ?? 5) * 1000;
63865
+ const msUntilRefresh = this.currentState.expires * 1000 - Date.now() - leadMs;
63866
+ trace(`${T$1} next refresh in`, {
63867
+ seconds: Math.round(msUntilRefresh / 1000),
63868
+ expires: new Date(this.currentState.expires * 1000).toISOString(),
63869
+ });
63870
+ this.refreshTimer = setTimeout(() => this.performRefresh(), Math.max(msUntilRefresh, 1000));
63871
+ }
63872
+ async performRefresh() {
63873
+ trace(`${T$1} fetching new token`);
63874
+ try {
63875
+ const data = await this.opts.getToken();
63876
+ // Plugin may have been destroyed while getToken() was in flight; drop the result.
63877
+ if (this.destroyed)
63878
+ return;
63879
+ const newToken = this.opts.ipBound ? data.token_ip : data.token;
63880
+ const newState = { token: newToken, expires: data.expires };
63881
+ if (this.isNativePlayback) {
63882
+ // Must reload source because the <video> element has no request hook.
63883
+ await this.reloadNativeSource(data);
63884
+ }
63885
+ // originalState is never changed after init — it holds the token that was
63886
+ // baked into every URL in the initial manifest. hls.js/dash.js always
63887
+ // produces request URLs based on that manifest, so every segment URL
63888
+ // still contains the original token regardless of how many refreshes
63889
+ // have already happened. The loader replaces original→current on each
63890
+ // request, so updating only currentState is sufficient.
63891
+ this.currentState = newState;
63892
+ this.opts.onTokenRefreshed?.(data);
63893
+ trace(`${T$1} token refreshed`, {
63894
+ token: newToken.slice(0, 8) + '…',
63895
+ expires: new Date(data.expires * 1000).toISOString(),
63896
+ });
63897
+ }
63898
+ catch (err) {
63899
+ trace(`${T$1} token refresh failed`, { err });
63900
+ }
63901
+ // Always reschedule, even after an error (will retry near next expiry).
63902
+ this.scheduleRefresh();
63903
+ }
63904
+ get opts() {
63905
+ return this.options.tokenRefresh;
63906
+ }
63907
+ clearTimer() {
63908
+ if (this.refreshTimer !== null) {
63909
+ clearTimeout(this.refreshTimer);
63910
+ this.refreshTimer = null;
63911
+ }
63912
+ }
63913
+ }
63914
+
63689
63915
  /**
63690
63916
  * Events emitted by the VolumeFade plugin.
63691
63917
  * @public
@@ -63794,4 +64020,4 @@ class VolumeFade extends UICorePlugin {
63794
64020
  }
63795
64021
  }
63796
64022
 
63797
- export { AudioTracks as AudioSelector, AudioTracks, BigMuteButton, BottomGear, ChainedTracer, NerdStats as ClapprNerdStats, ClapprStats, ClapprStatsChronograph, ClapprStatsCounter, ClapprStatsEvents, ClickToPause, Clips, ClosedCaptions, CmcdConfig, ContextMenu, DvrControls, ErrorScreen, ExtendedEvents, Favicon, GearEvents, GoogleAnalytics, QualityLevels as LevelSelector, LogTracer, Logger$1 as Logger, Logo, MediaControl, MultiCamera, NerdStats, PictureInPicture, PlaybackErrorCode, PlaybackRate, Player, PlayerEvent, Poster, QualityLevels, RemoteTracer, SeekTime, SentryTracer, Share, SkipTime, SourceController, SpinnerThreeBounce as Spinner, SpinnerEvents, SpinnerThreeBounce, ClosedCaptions as Subtitles, Telemetry, TelemetryEvent, Thumbnails, VolumeFade, VolumeFadeEvents, reportError, setTracer, trace, version };
64023
+ export { AudioTracks as AudioSelector, AudioTracks, BigMuteButton, BottomGear, ChainedTracer, NerdStats as ClapprNerdStats, ClapprStats, ClapprStatsChronograph, ClapprStatsCounter, ClapprStatsEvents, ClickToPause, Clips, ClosedCaptions, CmcdConfig, ContextMenu, DvrControls, ErrorScreen, ExtendedEvents, Favicon, GearEvents, GoogleAnalytics, QualityLevels as LevelSelector, LogTracer, Logger$1 as Logger, Logo, MediaControl, MultiCamera, NerdStats, PictureInPicture, PlaybackErrorCode, PlaybackRate, Player, PlayerEvent, Poster, QualityLevels, RemoteTracer, SeekTime, SentryTracer, Share, SkipTime, SourceController, SpinnerThreeBounce as Spinner, SpinnerEvents, SpinnerThreeBounce, ClosedCaptions as Subtitles, Telemetry, TelemetryEvent, Thumbnails, TokenRefreshPlugin, VolumeFade, VolumeFadeEvents, reportError, setTracer, trace, version };