Source: SRGLetterbox.js

import videojs from 'video.js';

import '@videojs/http-streaming';
import 'videojs-contrib-eme';
import 'videojs-contrib-dash';
import 'videojs-contrib-quality-levels';
import './mediaPlayer/middlewares/srgssr-middleware.js';
import './mediaPlayer/middlewares/videojs-mediacomposition-middleware.js';
import './mediaPlayer/components/header-component.js';
import './mediaPlayer/components/picture-in-picture-toggle.js';
import './mediaPlayer/components/image-copyright.js';
import './mediaPlayer/components/play-toggle-component.js';
import './mediaPlayer/components/overlay-component.js';
import './mediaPlayer/components/current-time-display-component.js';
import './mediaPlayer/components/skip-credits.js';
import './mediaPlayer/components/menu/srg-menu-close-button.js';
import './mediaPlayer/components/menu/srg-menu.js';
import './mediaPlayer/components/menu/srg-menu-button.js';
import './mediaPlayer/components/menu/settings/settings-menu.js';
import './mediaPlayer/components/menu/settings/settings-button.js';
import './mediaPlayer/components/menu/subtitles/subtitles-menu.js';
import './mediaPlayer/components/menu/subtitles/subtitles-button.js';
import './mediaPlayer/components/menu/playbackRate/playback-rate-menu.js';
import './mediaPlayer/components/menu/playbackRate/playback-rate-button.js';
import './mediaPlayer/components/chromecast-toggle-component.js';
import './mediaPlayer/components/SRGSSR-button-component.js';
import './mediaPlayer/components/endScreen/end-screen-component.js';
import './mediaPlayer/components/error-display-component.js';
import './mediaPlayer/components/time-tool-tip-component.js';
import './mediaPlayer/components/subdivisions/subdivisions-container.js';
import './mediaPlayer/components/warning-message-component.js';
import './mediaPlayer/components/airplay-button.js';
import './mediaPlayer/components/backward-button.js';
import './mediaPlayer/components/forward-button.js';
import './mediaPlayer/components/text-track-display.js';
import './mediaPlayer/components/seek-bar.js';
import './mediaPlayer/components/thumbnail-seeking-component.js';
import './mediaPlayer/utils/chromecastPlayer.js';
import './mediaPlayer/scss/imports.scss';

import * as Events from './utils/Events.js';
import * as PlayerEvents from './utils/PlayerEvents.js';
import * as SRGEvents from './utils/SRGEvents.js';
import * as VideojsEvents from './utils/VideojsEvents.js';
import * as videojsDELanguage from './lang/de-CH.json';
import * as videojsENLanguage from './lang/en-US.json';
import * as videojsFRLanguage from './lang/fr-CH.json';
import * as videojsITLanguage from './lang/it-CH.json';
import * as videojsRMLanguage from './lang/rm-CH.json';

import MediaComposition from './dataProvider/model/MediaComposition.js';
import SRGLetterboxComponents from './utils/SRGLetterboxComponents.js';
import SRGLetterboxConfiguration from './utils/SRGLetterboxConfiguration.js';
import SRGPlaybackSettings from './utils/SRGPlaybackSettings.js';
import SupportedDevices from './utils/SupportedDevices.js';
import Utils from './utils/Utils.js';
import build from '../generated/build.json';
import PlayerUtils from './utils/PlayerUtils.js';
import { version } from '../package.json';
import SRGStreamType from './utils/SRGStreamType.js';
import TextTracksUtils from './utils/TextTracksUtils.js';
import AudioTracksUtils from './utils/AudioTracksUtils.js';

videojs.addLanguage('fr', videojsFRLanguage);
videojs.addLanguage('en', videojsENLanguage);
videojs.addLanguage('it', videojsITLanguage);
videojs.addLanguage('rm', videojsRMLanguage);
videojs.addLanguage('de', videojsDELanguage);

videojs.hook('beforesetup', (videoEl, options) => {
  if (!options.SRGPlayerConfiguration.noUI) {
    return options;
  }

  videoEl.classList.add('srgssr-no-ui');

  const noUIOptions = videojs.mergeOptions(options, {
    children: ['mediaLoader', 'textTrackDisplay'],
    controlBar: false,
    html5: {
      nativeTextTracks: videojs.browser.IS_SAFARI,
      vhs: {
        overrideNative: !videojs.browser.IS_SAFARI,
      },
    },
  });

  return noUIOptions;
});

const LETTERBOX_INSTANCES = [];

/**
 * @class SRGLetterbox
 *
 * The SRGLetterbox class provides a simple way to create a web player.
 */
class SRGLetterbox {
  /**
   * @constructor
   *
   * @param {LetterboxConfiguration} options Letterbox configuration object. The parameter object is merged with the default options.
   * @param {String} options.container query selector of the HTMLElement where the player will be created.
   *
   * @see SRGLetterbox.LETTERBOX_DEFAULT_OPTIONS and its documentation for available options.
   */
  constructor(options = {}, ConfigurationHandler = SRGLetterboxConfiguration) {
    this.ConfigurationHandler = ConfigurationHandler;

    this.player = this.ConfigurationHandler.initializePlayer(options, version);

    this.playbackSettings = new SRGPlaybackSettings(
      this.player,
      ConfigurationHandler,
    );

    this.player.options({
      SRGProviders: {
        letterbox: this,
      },
    });

    SRGLetterbox.addInstance(this);
  }

  /**
   * A getter/setter for the `Player`'s aspect ratio.
   *
   * @param {string} [ratio]
   *        The value to set the Player's aspect ratio to.
   *
   * @see fill
   * @see https://docs.videojs.com/player#aspectRatio
   * @see https://docs.videojs.com/tutorial-layout.html
   *
   * @return {string|undefined}
   *         - The current aspect ratio of the `Player` when getting.
   *         - undefined when setting
  */
  aspectRatio(ratio) {
    return this.player.aspectRatio(ratio);
  }

  /**
   * Get or set the audio track language.
   * The value should be the language code of the audio track
   * or an object containing at least the language property
   *
   * @param {undefined|string|object} value
   *
   * @example audioTrackLanguage()
   * @example audioTrackLanguage('de')
   * @example audioTrackLanguage({language: 'de'})
   * @example audioTrackLanguage({language: 'de', description: true})
   * @example audioTrackLanguage({language: 'de', description: false})
   *
   * @returns {object}
   */
  audioTrackLanguage(value) {
    const audioTrackValue = AudioTracksUtils.sanitizeParameter(value);

    return PlayerUtils.audioTrackLanguage(audioTrackValue, this.player);
  }

  /**
  * Get the AudioTrackList.
  *
  * @see https://docs.videojs.com/player#audioTracks
  * @see https://docs.videojs.com/audiotracklist
  * @see https://html.spec.whatwg.org/multipage/media.html#dom-media-audiotracks
  *
  * @param {*} safety
  *        Anything passed in to silence the warning.
  *
  * @returns {AudioTrackList} The current audio track list.
  */
  audioTracks(safety) {
    if (!safety) {
      console.warn( // eslint-disable-line no-console
        'If you want to get or set the current audio track prefer the use of %caudioTrackLanguage',
        'font-weight:bold; font-size:1.1em; background: #ff0000; color:#fff;',
        'https://letterbox-web.srgssr.ch/production/api/SRGLetterbox.html#audioTrackLanguage',
      );
    }

    return this.player.audioTracks();
  }

  /**
   * Get or set the autoplay option.
   * - true: autoplay using the browser behavior
   * - false: do not autoplay
   * - 'play': call play() on every loadstart
   * - 'muted': call muted() then play() on every loadstart
   * - 'any': call play() on every loadstart. if that fails call muted() then play().
   * - *: values other than those listed here will be set `autoplay` to true
   *
   * @param {boolean|string} value
   *
   * @see https://docs.videojs.com/player#autoplay
   */
  autoplay(value) {
    return PlayerUtils.autoplay(this.player, value);
  }

  /**
   * Get or set the current date.
   *
   * It allows to seek in a DVR live stream using a date as an absolute position.
   *
   * @param {Date} [date] The date to seek to
   *
   * @return {Date} The current date when getting
   */
  currentDate(date) {
    if (!SRGStreamType.isDvr(this.player.currentSource().streamType)) {
      throw new Error('The streamType must be DVR');
    }

    if (date !== undefined && !(date instanceof Date)) {
      throw new Error('The date parameter must be an instance of Date');
    }

    return PlayerUtils.currentDate(date, this.player);
  }

  /**
   * Get or set the current time (in seconds)
   *
   * @param {number|string} [seconds] The time to seek to in seconds
   *
   * @return {number} The current time in seconds when getting
   */
  currentTime(seconds) {
    return this.player.currentTime(seconds);
  }

  /**
   * Disable Picture-in-Picture mode.
   *
   * __FYI: Firefox doesn't provide any PiP API at the moment.__
   *
   * @param {boolean} value
   *                  - true will disable Picture-in-Picture mode
   *                  - false will enable Picture-in-Picture mode
   */
  disablePictureInPicture(value) {
    this.player.disablePictureInPicture(value);
  }

  /**
   * Destroys the video player and does any necessary cleanup.
   * This is especially helpful if you are dynamically adding and removing videos to/from the DOM.
   *
   * @see https://docs.videojs.com/player#dispose
   *
   * @fires videojs#event:dispose
   */
  dispose() {
    this.player.dispose();

    SRGLetterbox.removeInstance(this);
  }

  /**
   * Return letterbox duration of the currently playing stream. This is videojs duration
   * ( @see https://docs.videojs.com/player#paused ) when the player is initialized,
   * or the mediacomposition duration if the player is not yet playing.
   *
   * @returns {number | undefined} duration in seconds
   */
  /** TODO: Investigate the use case : Why do we return duration from mediacomposition */
  duration() {
    const playerDuration = this.player.duration();
    if (playerDuration) {
      return playerDuration;
    }
    const mediaComposition = this.getMediaComposition();
    if (mediaComposition) {
      const mainChapter = mediaComposition.getMainChapter();
      return Utils.millisecondsToSeconds(mainChapter.duration);
    }
    return undefined;
  }

  /**
   * Get the value of the `error` from the media element.
   * `error` indicates any MediaError that may have occurred during playback.
   * If error returns null there is no current error.
   *
   * __FYI__: video.js error method allows a `MediaError` object as a parameter, Letterbox don't.
   *
   * @returns MediaError | null
   *
   * @see https://docs.videojs.com/html5#error
   */
  error() {
    return this.player.error();
  }

  /**
   * Exit Picture-in-Picture mode.
   *
   * __FYI: Firefox doesn't provide any PiP API at the moment.__
   *
   * @see [Spec]{@link https://wicg.github.io/picture-in-picture}
   *
   * @fires Player#leavepictureinpicture
   *
   * @return {Promise}
   *         A promise.
   */
  exitPictureInPicture() {
    return this.player.exitPictureInPicture();
  }

  /**
   * A getter/setter/toggler for the vjs-fill `className` on the `Player`.
   *
   * Turning this on will turn off fluid mode.
   *
   * By default Letterbox uses the fill mode.
   * This means the player will take all the available space in his container.
   *
   * @see https://docs.videojs.com/player#fill
   *
   * @param {boolean} [bool]
   *        - A value of true adds the class.
   *        - A value of false removes the class.
   *        - No value will be a getter.
   *
   * @return {boolean|undefined}
   *         - The value of fluid when getting.
   *         - `undefined` when setting.
   */
  fill(bool) {
    return this.player.fill(bool);
  }

  /**
   * A getter/setter/toggler for the vjs-fluid `className` on the `Player`.
   *
   * Turning this on will turn off fill mode.
   *
   * @see https://docs.videojs.com/player#fluid
   *
   * @param {boolean} [bool]
   *        - A value of true adds the class.
   *        - A value of false removes the class.
   *        - No value will be a getter.
   *
   * @return {boolean|undefined}
   *         - The value of fluid when getting.
   *         - `undefined` when setting.
   */
  fluid(bool) {
    return this.player.fluid(bool);
  }

  /**
  * Return the video to its normal size after having been in full screen mode
  *
  * @fires Player#fullscreenchange
  */
  exitFullscreen() {
    return this.player.exitFullscreen();
  }

  /**
   * Get the current player configuration object
   *
   * @see SRGLetterboxConfiguration.getPlayerConfiguration
   *
   * @return {PlayerConfiguration} SRG player configuration
   */
  getConfiguration() {
    return this.ConfigurationHandler.getPlayerConfiguration(this.player);
  }

  /**
   * Get the debug information.
   *
   * @returns {Object}
   */
  getDebugInformation() {
    return PlayerUtils.getDebugInformation(this.player);
  }

  /**
   * Get the currently playing Media Composition.
   *
   * @returns {MediaComposition}
   */
  getMediaComposition() {
    return this.player.options().SRGProviders.mediaComposition;
  }

  /**
   * Get the current playback settings.
   *
   * @returns {Object}
   * TODO: Check playbackSettings that shouldn't be merged in the mediaComposition in the middleware
   */
  getPlaybackSettings() {
    return this.player.currentSource().playbackSettings;
  }

  /**
   * @returns {String} currently playing URN
   */
  getUrn() {
    return PlayerUtils.getUrn(this.player);
  }

  /**
   * Check if the MediaComposition has subdivisions.
   *
   * @returns {Boolean}
   */
  hasSubdivisions() {
    const mediaComposition = this.getMediaComposition();
    return mediaComposition
      ? mediaComposition.getSubdivisions().length > 0 : false;
  }

  /**
   * Check if the player is in fullscreen mode or tell the player that it is or is not in fullscreen mode.
   *
   * @see https://docs.videojs.com/player#isFullscreen
   *
   * @returns {Boolean}
   */
  isFullscreen() {
    return this.player.isFullscreen();
  }

  /**
   * Check if the player is in Picture-in-Picture mode or tell the player that it
   * is or is not in Picture-in-Picture mode.
   *
   * __FYI: Firefox doesn't provide any PiP API at the moment.__
   *
   * @param  {boolean} [isPiP]
   *         Set the players current Picture-in-Picture state
   *
   * @return {boolean}
   *         - true if Picture-in-Picture is on and getting
   *         - false if Picture-in-Picture is off and getting
   */
  isInPictureInPicture(isPiP) {
    return this.player.isInPictureInPicture(isPiP);
  }

  /**
   * Load a media provided as a mediacomposition.
   *
   * @param {Object} mediaComposition data or mediaComposition object
   * @param {Object} param1
   * @param {Number} [param1.pendingSeek=undefined] position to play at in seconds or undefined to play at stream default position
   * @param {Object} [param1.playbackSettings=undefined] options to be stored with the playback context,
   *                 can be used for application specific information
   *                 they can be read back in getPlaybackSettings
   * @param {boolean|string} [param1.autoplay=undefined] value
   *
   * @see autoplay autoplay value
   * @see getPlaybackSettings
   * @see getMediaComposition
   */
  loadMediaComposition(
    mediaComposition,
    {
      autoplay = undefined,
      pendingSeek = undefined,
      playbackSettings = undefined,
    } = {},
  ) {
    PlayerUtils.loadMedia(this.player, {
      autoplay,
      mediaComposition: MediaComposition.cleanCache(mediaComposition),
      pendingSeek,
      playbackSettings,
      urn: mediaComposition.segmentUrn || mediaComposition.chapterUrn,
    });
  }

  /**
   * Load an URN.
   *
   * @param {String} urn string
   * @param {Object} param1
   * @param {Number} [param1.pendingSeek=undefined] position to play at in seconds or undefined to play at stream default position
   * @param {Object} [param1.playbackSettings=undefined] options to be stored with the playback context,
   *                 can be used for application specific information
   *                 they can be read back in getPlaybackSettings
   * @param {boolean|string} [param1.autoplay=undefined] value
   * @param {bolean} [param1.standalone=false] allow to switch between onlyChapters true/false.
   *                 The default value is false.
   *
   * @see autoplay autoplay value
   * @see getPlaybackSettings
   * @see getMediaComposition
   * */
  loadUrn(urn, {
    autoplay = undefined,
    pendingSeek = undefined,
    playbackSettings = undefined,
    standalone = false,
  } = {}) {
    PlayerUtils.loadMedia(this.player, {
      autoplay,
      pendingSeek,
      playbackSettings,
      standalone,
      urn,
    });
  }

  /**
   * Get or set the loop attribute on the video element.
   *
   * @see https://docs.videojs.com/player#loop
   *
   * @param {boolean} [value]
   *        - true means that we should loop the video
   *        - false means that we should not loop the video
   *
   * @return {boolean}
   *         The current value of loop when getting
   */
  loop(value) {
    return this.player.loop(value);
  }

  /**
   * Get the current muted state, or turn mute on or off
   *
   * @see https://docs.videojs.com/player#muted
   *
   * @param {boolean} [muted]
   *        - true to mute
   *        - false to unmute
   *
   * @return {boolean}
   *         - true if mute is on and getting
   *         - false if mute is off and getting
   */
  muted(muted) {
    return this.player.muted(muted);
  }

  /**
   * Removes listener(s) from event(s) on an evented object.
   *
   * @param  {string|Array|Element|Object} [targetOrType]
   *         If this is a string or array, it represents the event type(s).
   *
   *         Another evented object can be passed here instead, in which case
   *         ALL 3 arguments are _required_.
   *
   * @param  {string|Array|Function} [typeOrListener]
   *         If the first argument was a string or array, this may be the
   *         listener function. Otherwise, this is a string or array of event
   *         type(s).
   *
   * @param  {Function} [listener]
   *         If the first argument was another evented object, this will be
   *         the listener function; otherwise, _all_ listeners bound to the
   *         event type(s) will be removed.
   *
   * @see https://github.com/videojs/video.js/blob/v7.7.4/src/js/mixins/evented.js#L337
   */
  off(elem, typeopt, fnopt) {
    this.player.off(elem, typeopt, fnopt);
  }

  /**
   * Add a listener to an event (or events) on this object or another evented
   * object.
   *
   * @param  {string|Array|Element|Object} targetOrType
   *         If this is a string or array, it represents the event type(s)
   *         that will trigger the listener.
   *
   *         Another evented object can be passed here instead, which will
   *         cause the listener to listen for events on _that_ object.
   *
   *         In either case, the listener's `this` value will be bound to
   *         this object.
   *
   * @param  {string|Array|Function} typeOrListener
   *         If the first argument was a string or array, this should be the
   *         listener function. Otherwise, this is a string or array of event
   *         type(s).
   *
   * @param  {Function} [listener]
   *         If the first argument was another evented object, this will be
   *         the listener function.
   *
   * @see https://github.com/videojs/video.js/blob/v7.7.4/src/js/mixins/evented.js#L192
   */
  on(...args) {
    this.player.on(...args);
  }

  /**
   * Add a listener to an event (or events) on this object or another evented
   * object. The listener will be called once per event and then removed.
   *
   * @param  {string|Array|Element|Object} targetOrType
   *         If this is a string or array, it represents the event type(s)
   *         that will trigger the listener.
   *
   *         Another evented object can be passed here instead, which will
   *         cause the listener to listen for events on _that_ object.
   *
   *         In either case, the listener's `this` value will be bound to
   *         this object.
   *
   * @param  {string|Array|Function} typeOrListener
   *         If the first argument was a string or array, this should be the
   *         listener function. Otherwise, this is a string or array of event
   *         type(s).
   *
   * @param  {Function} [listener]
   *         If the first argument was another evented object, this will be
   *         the listener function.
   *
   * @see https://github.com/videojs/video.js/blob/v7.7.4/src/js/mixins/evented.js#L244
   */
  one(...args) {
    this.player.one(...args);
  }

  /**
   * Pause the video playback.
   *
   * @see https://docs.videojs.com/player#pause
   */
  pause() {
    this.player.pause();
  }

  /**
   * Check if the player is paused.
   *
   * @see https://docs.videojs.com/player#paused
   *
   * @returns {Boolean}
   */
  paused() {
    return this.player.paused();
  }

  /**
   * Attempt to begin playback.
   *
   * @see https://docs.videojs.com/player#play
   *
   * @return {Promise|undefined}
   */
  play() {
    return this.player.play();
  }

  /**
   * Gets or sets the current playback rate. A playback rate of 1.0 represents normal speed and 0.5 would indicate half-speed playback, for instance.
   *
   * @param {Number} rateopt New playback rate to set.
   *
   * @see https://docs.videojs.com/player#playbackRate
   *
   * @returns {Number} The current playback rate when getting or 1.0
   */
  playbackRate(rateopt) {
    return this.player.playbackRate(rateopt);
  }

  /**
   * Restart the playback session.
   *
   * @param autoplay autoplay property
   *
   * @see autoplay
   */
  restart(autoplay = true) {
    this.loadUrn(this.getUrn(), { autoplay });
  }

  /**
   * Increase the size of the video to full screen
   * In some browsers, full screen is not supported natively, so it enters
   * "full window mode", where the video fills the browser window.
   * In browsers and devices that support native full screen, sometimes the
   * browser's default controls will be shown, and not the Video.js custom skin.
   * This includes most mobile devices (iOS, Android) and older versions of
   * Safari.
   *
   * __FYI__: Be aware of the potential error "Request for fullscreen was denied
   * because Element.requestFullscreen() was not called from inside a short running user-generated event handler."
   *
   * @param  {Object} [fullscreenOptions]
   *         Override the player fullscreen options
   *
   * @fires Player#fullscreenchange
   *
   * @see https://docs.videojs.com/player#requestFullscreen
   * @see https://docs.videojs.com/tutorial-options.html#fullscreen
   * @see https://fullscreen.spec.whatwg.org/#dom-element-requestfullscreen
   * @see https://fullscreen.spec.whatwg.org/#dom-fullscreenoptions-navigationui
   */
  requestFullscreen(fullscreenOptions) {
    return this.player.requestFullscreen(fullscreenOptions);
  }

  /**
   * Create a floating video window always on top of other windows so that users may
   * continue consuming media while they interact with other content sites, or
   * applications on their device.
   *
   * __FYI: Firefox doesn't provide any PiP API at the moment.__
   *
   * @see [Spec]{@link https://wicg.github.io/picture-in-picture}
   *
   * @fires Player#enterpictureinpicture
   *
   * @return {Promise}
   *         A promise with a Picture-in-Picture window.
   */
  requestPictureInPicture() {
    return this.player.requestPictureInPicture();
  }

  /**
   * Switch URN. Play a different URN in the same context.
   *
   * - Switch to segment if urn is within segment list of the current mediacomposition
   * (e.g.: full DVR highlight or live). Will start at the beginning of the segment,
   * or at the edge of live for live.
   * - Switch to chapter if urn is within chapter list of the current mediacomposition
   * (e.g.: limited DVR highlight or live). Will start at the beginning of the segment,
   * or at the edge of live for live.
   *
   * This will start playing if player is not currently playing.
   *
   * @param {String} urn to play
   */
  switchToUrn(urn) {
    PlayerUtils.switchToUrn(this.player, urn);
  }

  /**
   * Get or set the text track language.
   * The value should be the language code of the text track
   * Or an object containing at least the language property
   *
   * @param {undefined|string|object} value
   *
   * @example textTrackLanguage()
   * @example textTrackLanguage('de')
   * @example textTrackLanguage({language: 'de'})
   * @example textTrackLanguage({language: 'de', caption: true})
   * @example textTrackLanguage({language: 'de', caption: false})
   *
   * @returns {object}
   */
  textTrackLanguage(value) {
    const textTrackValue = TextTracksUtils.sanitizeParameter(value);

    return PlayerUtils.textTrackLanguage(textTrackValue, this.player);
  }

  /**
   * Get the TextTrackList.
   *
   * @see https://docs.videojs.com/player#textTracks
   * @see https://docs.videojs.com/texttracklist
   * @see http://www.w3.org/html/wg/drafts/html/master/embedded-content-0.html#dom-media-texttracks
   *
   * @param {*} safety
   *        Anything passed in to silence the warning.
   *
   * @example
   * //Allows to filter the metadata type tracks
   * Array.from(letterboxInstance.textTracks()).filter(({kind}) => kind !== 'metadata');
   *
   * @return {TextTrackList}
   *         The current text track list.
   */
  textTracks(safety) {
    if (!safety) {
      console.warn( // eslint-disable-line no-console
        'If you want to get or set the current text track prefer the use of %ctextTrackLanguage',
        'font-weight:bold; font-size:1.1em; background: #ff0000; color:#fff;',
        'https://letterbox-web.srgssr.ch/production/api/SRGLetterbox.html#textTrackLanguage',
      );
    }

    return this.player.textTracks();
  }

  /**
 * Get or set the text track font size.
 *
 * @param {Number} sizeLevel is a size level between 1 and 8.
 * @returns {Number|undefined} The font size in percent.
 */
  textTrackSizeLevel(sizeLevel) {
    return PlayerUtils.textTrackSizeLevel(sizeLevel, this.player);
  }

  /**
   * Fire an event on this evented object, causing its listeners to be called.
   *
   * @param   {string|Object} event
   *          An event type or an object with a type property.
   *
   * @param   {Object} [hash]
   *          An additional object to pass along to listeners.
   *
   * @return {boolean}
   *          Whether or not the default behavior was prevented.
   *
   * @see https://github.com/videojs/video.js/blob/v7.7.4/src/js/mixins/evented.js#L389
   */
  trigger(event, hash) {
    this.player.trigger(event, hash);
  }

  /**
   * Update the current player configration object.
   *
   * @param {object} configuration
   *
   * @example
   * // Set the continuousPlayback with a default value set to true and disable the local storage.
   * letterboxInstance.updateConfiguration({
   *    continuousPlayback : {
   *      default: true,
   *      storage : false,
   *    }
   * });
   *
   * @example
   * // Set the continuousPlayback with a default value set to false and enable the local storage.
   * letterboxInstance.updateConfiguration({
   *    continuousPlayback : {
   *      default: false,
   *      storage : true,
   *    }
   * });
   *
   * @example
   * // Activates the continuousPlayback but removes the UI component so that the user can't change it
   * letterboxInstance.updateConfiguration({
   *    continuousPlayback : true
   * });
   */
  updateConfiguration(configuration) {
    this.ConfigurationHandler.updateConfiguration(this.player, configuration);
  }

  /**
   * Get or set the current volume of the media
   *
   * @see https://docs.videojs.com/player#volume
   *
   * @param  {number} [percentAsDecimal]
   *         The new volume as a decimal percent:
   *         - 0 is muted/0%/off
   *         - 1.0 is 100%/full
   *         - 0.5 is half volume or 50%
   *
   * @return {number}
   *         The current volume as a percent when getting
   */
  volume(percentAsDecimal) {
    return this.player.volume(percentAsDecimal);
  }

  /**
   * Add Letterbox instance.
   */
  static addInstance(instance) {
    if (instance instanceof SRGLetterbox) {
      LETTERBOX_INSTANCES.push(instance);

      return;
    }

    throw new Error('The instance parameter must be a SRGLetterbox instance');
  }

  /**
   * Object with detailed build information.
   */
  static get BUILD() {
    return build;
  }

  /**
   * Aggregates all available event properties
   *
   * @type {Object}
   * @event
   * @see Events
   *
   * @linkcode SRGLetterbox#playerEvents
   * @linkcode SRGLetterbox#srgEvents
   * @linkcode SRGLetterbox#videojsEvents
   */
  static get events() {
    return Events;
  }

  /**
   * Get Letterbox instances.
   *
   * @returns {Array} array of SRGLetterbox instances
   */
  static getInstances() {
    return LETTERBOX_INSTANCES;
  }

  /**
   * Letterbox default options. Those options can be overridden in the constructor.
   *
   * @returns {LetterboxConfiguration} SRGLetterboxConfiguration.defaultLetterboxConfiguration
   */

  static get LETTERBOX_DEFAULT_OPTIONS() {
    return SRGLetterboxConfiguration.defaultLetterboxConfiguration;
  }

  /**
   * Long string with detailed version and build information.
   * @returns {string} displayable string
   */
  static get LONG_VERSION() {
    const date = new Date(parseInt(build.date, 10));

    return `${version} built at ${date} from ${build.commit} using ${build.runner} by ${build.initiator}`;
  }

  /**
   * Standard player events.
   *
   * @see https://developer.mozilla.org/en-US/docs/Web/Guide/Events/Media_events
   * @event
   * @type {Object}
   * @see PlayerEvents
   */
  static get playerEvents() {
    return PlayerEvents;
  }

  /**
   * Remove a specific Letterbox instance.
   *
   * @param {SRGLetterbox} instance
   */
  static removeInstance(instance) {
    if (instance instanceof SRGLetterbox
      && SRGLetterbox.getInstances().includes(instance)) {
      SRGLetterbox.getInstances().splice(SRGLetterbox.getInstances().indexOf(instance), 1);

      return;
    }

    throw new Error('The instance parameter must be a SRGLetterbox instance');
  }

  /**
   * SRG events.
   *
   * @event
   * @type {Object}
   * @see SRGEvents
   */
  static get srgEvents() {
    return SRGEvents;
  }

  /**
   * SupportedDevices allows to detect browser's version
   * and some features compatibilities.
   *
   * @returns {supportedDevices} supportedDevices class
   */
  static get supportedDevices() {
    return SupportedDevices;
  }

  /**
   * Represents the Letterbox version.
   *
   * @returns {String}
   */
  static get VERSION() {
    return version;
  }

  /**
   * Video.js events.
   *
   * FYI: Not all Video.js are exposed.
   *
   * @event
   * @type {Object}
   *
   * @see VideojsEvents
   */
  static get videojsEvents() {
    return VideojsEvents;
  }

  /**
   * @returns {SRGLetterboxComponents} Components list
   */
  static get srgLetterboxComponents() {
    return SRGLetterboxComponents;
  }
}

export default SRGLetterbox;

window.videojs = videojs;
window.SRGLetterbox = SRGLetterbox;
window.SRGLetterboxConfiguration = SRGLetterboxConfiguration;