eb-player 2.0.1 → 2.0.4

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 (42) hide show
  1. package/dist/build/eb-player.css +792 -9
  2. package/dist/build/ebplayer.bundle.js +215 -60
  3. package/dist/build/ebplayer.bundle.js.map +1 -1
  4. package/dist/build/theme-forja.css +1 -1
  5. package/dist/build/theme-lequipe.css +767 -0
  6. package/dist/build/theme-modern.css +1 -1
  7. package/dist/build/theme-v2.css +1 -1
  8. package/dist/build/types/core/config.d.ts +14 -2
  9. package/dist/build/types/core/config.d.ts.map +1 -1
  10. package/dist/build/types/core/i18n.d.ts.map +1 -1
  11. package/dist/build/types/core/index.d.ts +1 -1
  12. package/dist/build/types/core/index.d.ts.map +1 -1
  13. package/dist/build/types/core/lifecycle.d.ts.map +1 -1
  14. package/dist/build/types/eb-player-standalone.d.ts +14 -3
  15. package/dist/build/types/eb-player-standalone.d.ts.map +1 -1
  16. package/dist/build/types/engines/hls.d.ts +1 -0
  17. package/dist/build/types/engines/hls.d.ts.map +1 -1
  18. package/dist/build/types/engines/snapshot/hls.d.ts +1 -1
  19. package/dist/build/types/engines/snapshot/hls.d.ts.map +1 -1
  20. package/dist/build/types/integrations/p2p-manager.d.ts.map +1 -1
  21. package/dist/build/types/skin/controls/seekbar.d.ts.map +1 -1
  22. package/dist/build/types/skin/controls/settings-panel.d.ts +8 -0
  23. package/dist/build/types/skin/controls/settings-panel.d.ts.map +1 -1
  24. package/dist/dev/default.js +734 -508
  25. package/dist/dev/default.js.map +1 -1
  26. package/dist/dev/easybroadcast.js +266 -88
  27. package/dist/dev/easybroadcast.js.map +1 -1
  28. package/dist/dev/equipe.js +6683 -0
  29. package/dist/dev/equipe.js.map +1 -0
  30. package/dist/eb-player.css +792 -9
  31. package/dist/players/easybroadcast/easybroadcast.js +397 -0
  32. package/dist/players/easybroadcast/index.html +1 -0
  33. package/dist/players/equipe/EB_lequipe-preprod.js +385 -0
  34. package/dist/players/equipe/equipe.js +385 -0
  35. package/dist/players/equipe/index.html +1 -0
  36. package/dist/players/forja/forja.js +198 -111
  37. package/dist/players/forja/index.html +1 -1
  38. package/dist/theme-forja.css +1 -1
  39. package/dist/theme-lequipe.css +767 -0
  40. package/dist/theme-modern.css +1 -1
  41. package/dist/theme-v2.css +1 -1
  42. package/package.json +8 -73
@@ -4,6 +4,8 @@
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.4";
8
+
7
9
  /**
8
10
  * Finite State Machine for player playback state transitions.
9
11
  *
@@ -274,28 +276,61 @@
274
276
  right: ['forward']
275
277
  }
276
278
  };
279
+ /**
280
+ * Available themes — single source of truth for all theme names and labels.
281
+ * The test player reads this to populate the skin selector dynamically.
282
+ * Add new entries here when creating a new theme-*.css file.
283
+ */
284
+ const AVAILABLE_THEMES = [
285
+ { value: 'default', label: 'Default' },
286
+ { value: 'forja', label: 'Forja', primaryColor: '#FC013B' },
287
+ { value: 'radio', label: 'Radio', primaryColor: '#F4A261' },
288
+ { value: 'snrt', label: 'SNRT', primaryColor: '#006633' },
289
+ { value: 'modern', label: 'Modern', primaryColor: '#7c3aed' },
290
+ { value: 'v2', label: 'V2', primaryColor: '#ff841f' },
291
+ { value: 'lequipe', label: "L'Equipe", primaryColor: '#d61e00' },
292
+ ];
277
293
  /**
278
294
  * Theme-specific default layouts.
279
295
  * Used when config.layout is not explicitly provided but a theme is set.
280
296
  */
281
- const THEME_LAYOUTS = {
282
- v2: {
283
- topBar: {
284
- left: [],
285
- right: ['settings', 'pip', 'cast']
286
- },
287
- bottomBar: {
288
- left: ['play-pause', 'live-sync', 'time'],
289
- center: ['seekbar'],
290
- right: ['volume', 'fullscreen']
291
- },
292
- middleBar: {
293
- left: ['rewind'],
294
- center: [],
295
- right: ['forward']
296
- }
297
+ const V2_LAYOUT = {
298
+ topBar: {
299
+ left: [],
300
+ right: ['settings', 'pip', 'cast']
301
+ },
302
+ bottomBar: {
303
+ left: ['play-pause', 'live-sync', 'time'],
304
+ center: ['seekbar'],
305
+ right: ['volume', 'fullscreen']
306
+ },
307
+ middleBar: {
308
+ left: ['rewind'],
309
+ center: [],
310
+ right: ['forward']
311
+ }
312
+ };
313
+ const LEQUIPE_LAYOUT = {
314
+ topBar: {
315
+ left: [],
316
+ right: ['pip', 'settings']
317
+ },
318
+ bottomBar: {
319
+ left: ['play-pause', 'live-sync', 'time'],
320
+ center: ['seekbar'],
321
+ right: ['volume', 'fullscreen']
322
+ },
323
+ middleBar: {
324
+ left: ['rewind'],
325
+ center: [],
326
+ right: ['forward']
297
327
  }
298
328
  };
329
+ const THEME_LAYOUTS = {
330
+ v2: V2_LAYOUT,
331
+ lequipe: LEQUIPE_LAYOUT,
332
+ modern: V2_LAYOUT,
333
+ };
299
334
  /**
300
335
  * Returns the effective layout for a given config.
301
336
  * Priority: explicit config.layout > theme default > DEFAULT_LAYOUT
@@ -515,6 +550,30 @@
515
550
  fr: 'La diffusion a échoué. Reprise de la lecture locale.',
516
551
  ar: 'Casting failed. Resuming local playback.',
517
552
  es: 'Error en la transmisión. Reanudando la reproducción local.'
553
+ },
554
+ 'settings.audio': {
555
+ en: 'Audio track', fr: 'Piste audio', ar: 'مسار صوتي', es: 'Pista de audio'
556
+ },
557
+ 'settings.subtitles': {
558
+ en: 'Subtitles', fr: 'Sous-titres', ar: 'ترجمات', es: 'Subtítulos'
559
+ },
560
+ 'settings.speed': {
561
+ en: 'Playback speed', fr: 'Vitesse de lecture', ar: 'سرعة التشغيل', es: 'Velocidad'
562
+ },
563
+ 'settings.quality': {
564
+ en: 'Quality', fr: 'Qualité', ar: 'الجودة', es: 'Calidad'
565
+ },
566
+ 'settings.default': {
567
+ en: 'Default', fr: 'Défaut', ar: 'افتراضي', es: 'Predeterminado'
568
+ },
569
+ 'settings.off': {
570
+ en: 'Off', fr: 'Désactivés', ar: 'إيقاف', es: 'Desactivado'
571
+ },
572
+ 'settings.normal': {
573
+ en: 'Normal', fr: 'Normale', ar: 'عادي', es: 'Normal'
574
+ },
575
+ 'settings.auto': {
576
+ en: 'Auto', fr: 'Auto', ar: 'تلقائي', es: 'Auto'
518
577
  }
519
578
  };
520
579
  /**
@@ -1313,8 +1372,14 @@
1313
1372
  const trackEl = event.currentTarget;
1314
1373
  this.dragValue = this.eventToTime(event, trackEl);
1315
1374
  this.scheduleRender();
1375
+ // During drag with pointer capture, only update tooltip when pointer is within track bounds
1376
+ const rect = trackEl.getBoundingClientRect();
1377
+ if (event.clientX >= rect.left && event.clientX <= rect.right) {
1378
+ this.updateTooltip(event);
1379
+ }
1380
+ return;
1316
1381
  }
1317
- // Always update tooltip on pointermove over the track
1382
+ // Always update tooltip on pointermove over the track when not dragging
1318
1383
  this.updateTooltip(event);
1319
1384
  }
1320
1385
  handlePointerUp(event) {
@@ -1324,6 +1389,11 @@
1324
1389
  const seekTime = this.eventToTime(event, trackEl);
1325
1390
  this.isDragging = false;
1326
1391
  this.bus.emit('seek', { time: seekTime });
1392
+ // Hide tooltip if pointer is outside the track bounds after drag ends
1393
+ const rect = trackEl.getBoundingClientRect();
1394
+ if (event.clientX < rect.left || event.clientX > rect.right) {
1395
+ this.tooltipVisible = false;
1396
+ }
1327
1397
  this.render();
1328
1398
  }
1329
1399
  handlePointerLeave() {
@@ -1638,16 +1708,20 @@
1638
1708
  this.mode = 'root';
1639
1709
  this.verticalDir = 'up';
1640
1710
  this.horizontalDir = 'right';
1711
+ this.outsideClickHandler = null;
1712
+ this.outsideClickTimer = null;
1641
1713
  }
1642
1714
  onConnect() {
1643
1715
  this.state.on('settingsOpen', () => {
1644
1716
  // When settings close, reset to root mode
1645
1717
  if (!this.state.settingsOpen) {
1646
1718
  this.mode = 'root';
1719
+ this.removeOutsideClickListener();
1647
1720
  }
1648
1721
  else {
1649
1722
  // Compute placement when opening
1650
1723
  this.computePlacement();
1724
+ this.addOutsideClickListener();
1651
1725
  }
1652
1726
  this.render();
1653
1727
  }, { signal: this.signal });
@@ -1658,6 +1732,8 @@
1658
1732
  this.state.on('subtitleTracks', () => this.render(), { signal: this.signal });
1659
1733
  this.state.on('currentSubtitleTrack', () => this.render(), { signal: this.signal });
1660
1734
  this.state.on('playbackRate', () => this.render(), { signal: this.signal });
1735
+ // Clean up outside-click listener when component disconnects
1736
+ this.signal.addEventListener('abort', () => this.removeOutsideClickListener());
1661
1737
  this.render();
1662
1738
  }
1663
1739
  /**
@@ -1685,53 +1761,76 @@
1685
1761
  this.mode = 'root';
1686
1762
  this.render();
1687
1763
  }
1764
+ /**
1765
+ * Returns the display label for the currently selected value in a category.
1766
+ */
1767
+ currentValueLabel(mode) {
1768
+ const i18n = this.i18n;
1769
+ if (mode === 'quality') {
1770
+ const levels = this.state.qualityLevels;
1771
+ const current = this.state.currentQuality;
1772
+ if (current === -1)
1773
+ return i18n.t('settings.auto');
1774
+ const level = levels[current];
1775
+ return level?.height ? `${level.height}p` : i18n.t('settings.auto');
1776
+ }
1777
+ if (mode === 'speed') {
1778
+ const rate = this.state.playbackRate;
1779
+ return rate === 1 ? i18n.t('settings.normal') : `${rate}x`;
1780
+ }
1781
+ if (mode === 'audio') {
1782
+ const tracks = this.state.audioTracks;
1783
+ const current = this.state.currentAudioTrack;
1784
+ const track = tracks[current];
1785
+ return track?.name || track?.lang || i18n.t('settings.default');
1786
+ }
1787
+ if (mode === 'subtitles') {
1788
+ const current = this.state.currentSubtitleTrack;
1789
+ if (current === -1)
1790
+ return i18n.t('settings.off');
1791
+ const tracks = this.state.subtitleTracks;
1792
+ const track = tracks[current];
1793
+ return track?.name || track?.lang || `Track ${current}`;
1794
+ }
1795
+ return '';
1796
+ }
1688
1797
  renderRootMenu() {
1689
1798
  const qualityLevels = this.state.qualityLevels;
1690
1799
  const audioTracks = this.state.audioTracks;
1691
1800
  const subtitleTracks = this.state.subtitleTracks;
1801
+ const i18n = this.i18n;
1692
1802
  const showQuality = qualityLevels.length > 0;
1693
1803
  const showSpeed = this.config.speed === true;
1694
1804
  const showAudio = audioTracks.length > 1;
1695
1805
  const showSubtitles = subtitleTracks.length > 0;
1806
+ const row = (iconName, label, value, mode) => b `
1807
+ <li>
1808
+ <button class="eb-settings-category" @click="${() => this.navigateTo(mode)}">
1809
+ <span class="eb-settings-category__icon">${icon(iconName)}</span>
1810
+ <span class="eb-settings-category__label">${label}</span>
1811
+ <span class="eb-settings-category__value">${value}</span>
1812
+ <span class="eb-settings-category__chevron">${icon('chevron-right')}</span>
1813
+ </button>
1814
+ </li>
1815
+ `;
1696
1816
  return b `
1697
1817
  <ul class="eb-settings-menu eb-settings-root">
1698
- ${showQuality ? b `
1699
- <li>
1700
- <button class="eb-settings-category" @click="${() => this.navigateTo('quality')}">
1701
- Quality
1702
- </button>
1703
- </li>
1704
- ` : ''}
1705
- ${showSpeed ? b `
1706
- <li>
1707
- <button class="eb-settings-category" @click="${() => this.navigateTo('speed')}">
1708
- Speed
1709
- </button>
1710
- </li>
1711
- ` : ''}
1712
- ${showAudio ? b `
1713
- <li>
1714
- <button class="eb-settings-category" @click="${() => this.navigateTo('audio')}">
1715
- Audio
1716
- </button>
1717
- </li>
1718
- ` : ''}
1719
- ${showSubtitles ? b `
1720
- <li>
1721
- <button class="eb-settings-category" @click="${() => this.navigateTo('subtitles')}">
1722
- Subtitles
1723
- </button>
1724
- </li>
1725
- ` : ''}
1818
+ ${showAudio ? row('audio', i18n.t('settings.audio'), this.currentValueLabel('audio'), 'audio') : ''}
1819
+ ${showSubtitles ? row('subtitle', i18n.t('settings.subtitles'), this.currentValueLabel('subtitles'), 'subtitles') : ''}
1820
+ ${showSpeed ? row('speed', i18n.t('settings.speed'), this.currentValueLabel('speed'), 'speed') : ''}
1821
+ ${showQuality ? row('quality', i18n.t('settings.quality'), this.currentValueLabel('quality'), 'quality') : ''}
1726
1822
  </ul>
1727
1823
  `;
1728
1824
  }
1729
1825
  renderSubMenu(title, items, onSelect) {
1730
1826
  return b `
1731
1827
  <div class="eb-settings-submenu">
1732
- <button class="eb-settings-back" @click="${() => this.navigateBack()}">
1733
- ${title}
1734
- </button>
1828
+ <div class="eb-settings-header">
1829
+ <button class="eb-settings-back" @click="${() => this.navigateBack()}">
1830
+ ${icon('chevron-left')}
1831
+ </button>
1832
+ <span class="eb-settings-header__title">${title}</span>
1833
+ </div>
1735
1834
  <ul class="eb-settings-menu">
1736
1835
  ${items.map((item) => b `
1737
1836
  <li>
@@ -1751,14 +1850,14 @@
1751
1850
  const levels = this.state.qualityLevels;
1752
1851
  const currentQuality = this.state.currentQuality;
1753
1852
  const items = getQualityItems(levels, currentQuality);
1754
- return this.renderSubMenu('Quality', items, (item) => {
1853
+ return this.renderSubMenu(this.i18n.t('settings.quality'), items, (item) => {
1755
1854
  this.bus.emit('settings-select-quality', { index: item.value });
1756
1855
  });
1757
1856
  }
1758
1857
  renderSpeedMenu() {
1759
1858
  const currentRate = this.state.playbackRate;
1760
1859
  const items = getSpeedItems(currentRate);
1761
- return this.renderSubMenu('Speed', items, (item) => {
1860
+ return this.renderSubMenu(this.i18n.t('settings.speed'), items, (item) => {
1762
1861
  this.bus.emit('settings-select-speed', { rate: item.value });
1763
1862
  });
1764
1863
  }
@@ -1766,7 +1865,7 @@
1766
1865
  const tracks = this.state.audioTracks;
1767
1866
  const currentTrack = this.state.currentAudioTrack;
1768
1867
  const items = getAudioItems(tracks, currentTrack);
1769
- return this.renderSubMenu('Audio', items, (item) => {
1868
+ return this.renderSubMenu(this.i18n.t('settings.audio'), items, (item) => {
1770
1869
  this.bus.emit('settings-select-audio', { index: item.value });
1771
1870
  });
1772
1871
  }
@@ -1774,10 +1873,37 @@
1774
1873
  const tracks = this.state.subtitleTracks;
1775
1874
  const currentTrack = this.state.currentSubtitleTrack;
1776
1875
  const items = getSubtitleItems(tracks, currentTrack);
1777
- return this.renderSubMenu('Subtitles', items, (item) => {
1876
+ return this.renderSubMenu(this.i18n.t('settings.subtitles'), items, (item) => {
1778
1877
  this.bus.emit('settings-select-subtitle', { index: item.value });
1779
1878
  });
1780
1879
  }
1880
+ addOutsideClickListener() {
1881
+ this.removeOutsideClickListener();
1882
+ // Defer to next tick so the opening click doesn't immediately close
1883
+ this.outsideClickTimer = setTimeout(() => {
1884
+ this.outsideClickTimer = null;
1885
+ // Guard: panel may have closed before this timer fires
1886
+ if (!this.state?.settingsOpen)
1887
+ return;
1888
+ this.outsideClickHandler = (event) => {
1889
+ const wrapper = this.el?.querySelector('.eb-settings-wrapper');
1890
+ if (wrapper && !wrapper.contains(event.target)) {
1891
+ this.state.settingsOpen = false;
1892
+ }
1893
+ };
1894
+ document.addEventListener('click', this.outsideClickHandler, { capture: true });
1895
+ }, 0);
1896
+ }
1897
+ removeOutsideClickListener() {
1898
+ if (this.outsideClickTimer !== null) {
1899
+ clearTimeout(this.outsideClickTimer);
1900
+ this.outsideClickTimer = null;
1901
+ }
1902
+ if (this.outsideClickHandler !== null) {
1903
+ document.removeEventListener('click', this.outsideClickHandler, { capture: true });
1904
+ this.outsideClickHandler = null;
1905
+ }
1906
+ }
1781
1907
  toggleSettings() {
1782
1908
  this.state.settingsOpen = !this.state.settingsOpen;
1783
1909
  }
@@ -3642,6 +3768,13 @@
3642
3768
  */
3643
3769
  mount(container) {
3644
3770
  this._container = container;
3771
+ // Clear container DOM so a fresh <video> element is created on each mount.
3772
+ // This prevents EME/MediaKeys conflicts — setMediaKeys(null) is async and
3773
+ // hls.js destroy() doesn't wait for it, so reusing the same <video> element
3774
+ // causes "The existing ContentDecryptor" errors on skin switch.
3775
+ container.textContent = '';
3776
+ // Clear lit-html's internal render state so render() treats the container as fresh
3777
+ delete container['_$litPart$'];
3645
3778
  // Clear any stale theme/style from a previous mount cycle
3646
3779
  container.removeAttribute('data-theme');
3647
3780
  container.removeAttribute('style');
@@ -4857,6 +4990,7 @@
4857
4990
  // Private — NEVER in PlayerState (Pitfall 2)
4858
4991
  this.driver = null;
4859
4992
  this.tokenManager = null;
4993
+ this.autoQuality = true;
4860
4994
  // Holds state reference for named driver event handlers
4861
4995
  this.eventState = null;
4862
4996
  this.liveSyncDisabled = false;
@@ -4916,6 +5050,14 @@
4916
5050
  if (this.driver === null)
4917
5051
  return;
4918
5052
  this.driver.currentLevel = index;
5053
+ // When index is -1 (Auto/ABR), hls.js will fire LEVEL_SWITCHED with the
5054
+ // actual resolved level. We need to keep currentQuality as -1 so the UI
5055
+ // shows "Auto" as selected. Track ABR mode to prevent LEVEL_SWITCHED
5056
+ // from overwriting it.
5057
+ this.autoQuality = index === -1;
5058
+ if (this.state !== null) {
5059
+ this.state.currentQuality = index;
5060
+ }
4919
5061
  }
4920
5062
  setAudioTrack(index) {
4921
5063
  if (this.driver === null)
@@ -4977,7 +5119,7 @@
4977
5119
  token: config.token,
4978
5120
  tokenType: config.tokenType,
4979
5121
  srcInTokenRequest: config.srcInTokenRequest,
4980
- extraParamsCallback: config.extraParamsCallback,
5122
+ extraParamsCallback: (config.engineSettings.extraParamsCallback ?? config.extraParamsCallback),
4981
5123
  onCDNTokenError: config.engineSettings.onCDNTokenError
4982
5124
  });
4983
5125
  // Fetch initial token
@@ -4988,7 +5130,7 @@
4988
5130
  if (this.detached)
4989
5131
  return;
4990
5132
  }
4991
- console.info('HlsEngine: loading hls.js from', hlsjsUrl);
5133
+ // console.info('HlsEngine: loading hls.js from', hlsjsUrl)
4992
5134
  const Hls = await loadScript(hlsjsUrl, 'Hls');
4993
5135
  // Guard: abort if detached during CDN script load
4994
5136
  if (this.detached)
@@ -5001,7 +5143,7 @@
5001
5143
  // Build driver config — only spread known engineSettings keys that hls.js recognises,
5002
5144
  // not the entire engineSettings bag (which may contain player-specific keys like
5003
5145
  // extraParamsCallback that should NOT leak into the hls.js constructor config).
5004
- const { emeEnabled, drmSystems, ...hlsEngineSettings } = config.engineSettings;
5146
+ const { emeEnabled: _emeEnabled, drmSystems: _drmSystems, ...hlsEngineSettings } = config.engineSettings;
5005
5147
  // Remove player-specific keys that are NOT hls.js config options
5006
5148
  const hlsSafeSettings = { ...hlsEngineSettings };
5007
5149
  delete hlsSafeSettings['extraParamsCallback'];
@@ -5145,8 +5287,12 @@
5145
5287
  const state = this.eventState;
5146
5288
  if (!state)
5147
5289
  return;
5148
- const switchedData = data;
5149
- state.currentQuality = switchedData.level;
5290
+ // In ABR mode (autoQuality), keep currentQuality as -1 so the UI shows "Auto".
5291
+ // Only update to the actual level index when the user picked a specific quality.
5292
+ if (!this.autoQuality) {
5293
+ const switchedData = data;
5294
+ state.currentQuality = switchedData.level;
5295
+ }
5150
5296
  }
5151
5297
  _onAudioTracksUpdated(_event, data) {
5152
5298
  const state = this.eventState;
@@ -5525,7 +5671,7 @@
5525
5671
  };
5526
5672
  // Register with AbortSignal so it auto-removes when engine is detached
5527
5673
  window.addEventListener('unhandledrejection', this.dvrErrorHandler, { signal });
5528
- console.info('DashEngine: loading dashjs from', dashjsUrl);
5674
+ // console.info('DashEngine: loading dashjs from', dashjsUrl)
5529
5675
  const dashjs = await loadScript(dashjsUrl, 'dashjs');
5530
5676
  const player = dashjs.MediaPlayer().create();
5531
5677
  if (!player) {
@@ -5691,7 +5837,9 @@
5691
5837
  this.lib.start();
5692
5838
  // Clean up on abort
5693
5839
  signal.addEventListener('abort', () => {
5694
- this.lib.stop();
5840
+ if (this.lib !== null && typeof this.lib.stop === 'function') {
5841
+ this.lib.stop();
5842
+ }
5695
5843
  this.lib = null;
5696
5844
  }, { once: true });
5697
5845
  }
@@ -6187,10 +6335,17 @@
6187
6335
  }
6188
6336
  }
6189
6337
  // ---------------------------------------------------------------------------
6338
+ // Version
6339
+ // ---------------------------------------------------------------------------
6340
+ // Injected at build time by Rollup's @rollup/plugin-virtual or replaced by bundler.
6341
+ // Falls back to 'dev' when running unbundled (tests, dev server).
6342
+ const VERSION = typeof __EB_PLAYER_VERSION__ !== 'undefined' ? __EB_PLAYER_VERSION__ : 'dev';
6343
+ // ---------------------------------------------------------------------------
6190
6344
  // window.EBPlayer assignment
6191
6345
  // ---------------------------------------------------------------------------
6192
6346
  if (typeof window !== 'undefined') {
6193
- window.EBPlayer = { start, stop, destroy };
6347
+ console.info(`%cEBPlayer v${VERSION}`, 'color: #1FA9DD; font-weight: bold');
6348
+ window.EBPlayer = { start, stop, destroy, AVAILABLE_THEMES, THEME_LAYOUTS, version: VERSION };
6194
6349
  }
6195
6350
 
6196
6351
  /**