From 6ca564992532973d1358363d9128adbd5a0b3a1d Mon Sep 17 00:00:00 2001 From: Bruno Torres Date: Thu, 19 Sep 2019 20:58:02 -0300 Subject: [PATCH 01/10] chore: update test pages to link between full and plain html5 versions --- public/index.html | 3 +++ public/index.plainhtml5.html | 3 +++ public/stylesheets/style.css | 2 +- 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/public/index.html b/public/index.html index 34e4accff..352bd9a35 100644 --- a/public/index.html +++ b/public/index.html @@ -70,6 +70,9 @@
+

+ Load Plain HTML5 Version +

\n * \n * ```\n * Now, create the player:\n * ```html\n * \n *
\n * \n * \n * ```\n */\nexport default class Player extends BaseObject {\n\n set loader(loader) { this._loader = loader }\n get loader() {\n if (!this._loader)\n this._loader = new Loader(this.options.plugins || {}, this.options.playerId)\n\n return this._loader\n }\n\n /**\n * Determine if the playback has ended.\n * @property ended\n * @type Boolean\n */\n get ended() {\n return this.core.activeContainer.ended\n }\n\n /**\n * Determine if the playback is having to buffer in order for\n * playback to be smooth.\n * (i.e if a live stream is playing smoothly, this will be false)\n * @property buffering\n * @type Boolean\n */\n get buffering() {\n return this.core.activeContainer.buffering\n }\n\n /*\n * determine if the player is ready.\n * @property isReady\n * @type {Boolean} `true` if the player is ready. ie PLAYER_READY event has fired\n */\n get isReady() {\n return !!this._ready\n }\n\n /**\n * An events map that allows the user to add custom callbacks in player's options.\n * @property eventsMapping\n * @type {Object}\n */\n get eventsMapping() {\n return {\n onReady: Events.PLAYER_READY,\n onResize: Events.PLAYER_RESIZE,\n onPlay: Events.PLAYER_PLAY,\n onPause: Events.PLAYER_PAUSE,\n onStop: Events.PLAYER_STOP,\n onEnded: Events.PLAYER_ENDED,\n onSeek: Events.PLAYER_SEEK,\n onError: Events.PLAYER_ERROR,\n onTimeUpdate: Events.PLAYER_TIMEUPDATE,\n onVolumeUpdate: Events.PLAYER_VOLUMEUPDATE,\n onSubtitleAvailable: Events.PLAYER_SUBTITLE_AVAILABLE\n }\n }\n\n /**\n * @typedef {Object} PlaybackConfig\n * @prop {boolean} disableContextMenu\n * disables the context menu (right click) on the video element if a HTML5Video playback is used.\n * @prop {boolean} preload\n * video will be preloaded according to `preload` attribute options **default**: `'metadata'`\n * @prop {boolean} controls\n * enabled/disables displaying controls\n * @prop {boolean} crossOrigin\n * enables cross-origin capability for media-resources\n * @prop {boolean} playInline\n * enables in-line video elements\n * @prop {boolean} audioOnly\n * enforce audio-only playback (when possible)\n * @prop {Object} externalTracks\n * pass externaly loaded track to playback\n * @prop {Number} [maxBufferLength]\n * The default behavior for the **HLS playback** is to keep buffering indefinitely, even on VoD.\n * This replicates the behavior for progressive download, which continues buffering when pausing the video, thus making the video available for playback even on slow networks.\n * To change this behavior use `maxBufferLength` where **value is in seconds**.\n * @prop {Number} [maxBackBufferLength]\n * After how much distance of the playhead data should be pruned from the buffer (influences memory consumption\n * of adaptive media-engines like Hls.js or Shaka)\n * @prop {Number} [minBufferLength]\n * After how much data in the buffer at least we attempt to consume it (influences QoS-related behavior\n * of adaptive media-engines like Hls.js or Shaka). If this is too low, and the available bandwidth is varying a lot\n * and too close to the streamed bitrate, we may continuously hit under-runs.\n * @prop {Number} [initialBandwidthEstimate]\n * define an initial bandwidth \"guess\" (or previously stored/established value) for underlying adaptive-bitreate engines\n * of adaptive playback implementations, like Hls.js or Shaka\n * @prop {Number} [maxAdaptiveBitrate]\n * Limits the streamed bitrate (for adaptive media-engines in underlying playback implementations)\n * @prop {Object} [maxAdaptiveVideoDimensions]\n * Limits the video dimensions in adaptive media-engines. Should be a literal object with `height` and `width`.\n * @prop {Boolean}[enableAutomaticABR] **default**: `true`\n * Allows to enable/disable automatic bitrate switching in adaptive media-engines\n * @prop {String} [preferredTextLanguage] **default**: `'pt-BR'`\n * Allows to set a preferred text language, that may be enabled by the media-engine if available.\n * @prop {String} [preferredAudioLanguage] **default**: `'pt-BR'`\n * Allows to set a preferred audio language, that may be enabled by the media-engine if available.\n */\n\n /**\n * ## Player's constructor\n *\n * You might pass the options object to build the player.\n * ```javascript\n * var options = {source: \"http://example.com/video.mp4\", param1: \"val1\"};\n * var player = new Clappr.Player(options);\n * ```\n *\n * @method constructor\n * @param {Object} options Data\n * options to build a player instance\n * @param {Number} [options.width]\n * player's width **default**: `640`\n * @param {Number} [options.height]\n * player's height **default**: `360`\n * @param {String} [options.parentId]\n * the id of the element on the page that the player should be inserted into\n * @param {Object} [options.parent]\n * a reference to a dom element that the player should be inserted into\n * @param {String} [options.source]\n * The media source URL, or {source: <>, mimeType: <>}\n * @param {Object} [options.sources]\n * An array of media source URL's, or an array of {source: <>, mimeType: <>}\n * @param {Boolean} [options.autoPlay]\n * automatically play after page load **default**: `false`\n * @param {Boolean} [options.loop]\n * automatically replay after it ends **default**: `false`\n * @param {Boolean} [options.chromeless]\n * player acts in chromeless mode **default**: `false`\n * @param {Boolean} [options.allowUserInteraction]\n * whether or not the player should handle click events when in chromeless mode **default**: `false` on desktops browsers, `true` on mobile.\n * @param {Boolean} [options.disableKeyboardShortcuts]\n * disable keyboard shortcuts. **default**: `false`. `true` if `allowUserInteraction` is `false`.\n * @param {Boolean} [options.mute]\n * start the video muted **default**: `false`\n * @param {String} [options.mimeType]\n * add `mimeType: \"application/vnd.apple.mpegurl\"` if you need to use a url without extension.\n * @param {Boolean} [options.actualLiveTime]\n * show duration and seek time relative to actual time.\n * @param {String} [options.actualLiveServerTime]\n * specify server time as a string, format: \"2015/11/26 06:01:03\". This option is meant to be used with actualLiveTime.\n * @param {Boolean} [options.persistConfig]\n * persist player's settings (volume) through the same domain **default**: `true`\n * @param {String} [options.preload] @deprecated\n * video will be preloaded according to `preload` attribute options **default**: `'metadata'`\n * @param {Number} [options.maxBufferLength] @deprecated\n * the default behavior for the **HLS playback** is to keep buffering indefinitely, even on VoD.\n * This replicates the behavior for progressive download, which continues buffering when pausing the video, thus making the video available for playback even on slow networks.\n * To change this behavior use `maxBufferLength` where **value is in seconds**.\n * @param {String} [options.gaAccount]\n * enable Google Analytics events dispatch **(play/pause/stop/buffering/etc)** by adding your `gaAccount`\n * @param {String} [options.gaTrackerName]\n * besides `gaAccount` you can optionally, pass your favorite trackerName as `gaTrackerName`\n * @param {Object} [options.mediacontrol]\n * customize control bar colors, example: `mediacontrol: {seekbar: \"#E113D3\", buttons: \"#66B2FF\"}`\n * @param {Boolean} [options.hideMediaControl]\n * control media control auto hide **default**: `true`\n * @param {Boolean} [options.hideVolumeBar]\n * when embedded with width less than 320, volume bar will hide. You can force this behavior for all sizes by adding `true` **default**: `false`\n * @param {String} [options.watermark]\n * put `watermark: 'http://url/img.png'` on your embed parameters to automatically add watermark on your video.\n * You can customize corner position by defining position parameter. Positions can be `bottom-left`, `bottom-right`, `top-left` and `top-right`.\n * @param {String} [options.watermarkLink]\n * `watermarkLink: 'http://example.net/'` - define URL to open when the watermark is clicked. If not provided watermark will not be clickable.\n * @param {Boolean} [options.disableVideoTagContextMenu] @deprecated\n * disables the context menu (right click) on the video element if a HTML5Video playback is used.\n * @param {Boolean} [options.autoSeekFromUrl]\n * Automatically seek to the seconds provided in the url (e.g example.com?t=100) **default**: `true`\n * @param {Boolean} [options.exitFullscreenOnEnd]\n * Automatically exit full screen when the media finishes. **default**: `true`\n * @param {String} [options.poster]\n * define a poster by adding its address `poster: 'http://url/img.png'`. It will appear after video embed, disappear on play and go back when user stops the video.\n * @param {String} [options.playbackNotSupportedMessage]\n * define a custom message to be displayed when a playback is not supported.\n * @param {Object} [options.events]\n * Specify listeners which will be registered with their corresponding player events.\n * E.g. onReady -> \"PLAYER_READY\", onTimeUpdate -> \"PLAYER_TIMEUPDATE\"\n * @param {PlaybackConfig} [options.playback]\n * Generic `Playback` component related configuration\n * @param {Boolean} [options.disableErrorScreen]\n * disables the error screen plugin.\n * @param {Number} [options.autoPlayTimeout]\n * autoplay check timeout.\n */\n\n constructor(options) {\n super(options)\n const playbackDefaultOptions = { recycleVideo : true }\n const defaultOptions = {\n playerId: uniqueId(''),\n persistConfig: true,\n width: 640,\n height: 360,\n baseUrl: baseUrl,\n allowUserInteraction: Browser.isMobile,\n playback: playbackDefaultOptions\n }\n this._options = $.extend(defaultOptions, options)\n this.options.sources = this._normalizeSources(options)\n if (!this.options.chromeless) {\n // \"allowUserInteraction\" cannot be false if not in chromeless mode.\n this.options.allowUserInteraction = true\n }\n if (!this.options.allowUserInteraction) {\n // if user iteraction is not allowed ensure keyboard shortcuts are disabled\n this.options.disableKeyboardShortcuts = true\n }\n this._registerOptionEventListeners(this.options.events)\n this._coreFactory = new CoreFactory(this)\n this.playerInfo = PlayerInfo.getInstance(this.options.playerId)\n this.playerInfo.currentSize = { width: options.width, height: options.height }\n this.playerInfo.options = this.options\n if (this.options.parentId)\n this.setParentId(this.options.parentId)\n\n else if (this.options.parent)\n this.attachTo(this.options.parent)\n\n }\n\n /**\n * Specify a `parentId` to the player.\n * @method setParentId\n * @param {String} parentId the element parent id.\n * @return {Player} itself\n */\n setParentId(parentId) {\n const el = document.querySelector(parentId)\n if (el)\n this.attachTo(el)\n\n return this\n }\n\n /**\n * You can use this method to attach the player to a given element. You don't need to do this when you specify it during the player instantiation passing the `parentId` param.\n * @method attachTo\n * @param {Object} element a given element.\n * @return {Player} itself\n */\n attachTo(element) {\n this.options.parentElement = element\n this.core = this._coreFactory.create()\n this._addEventListeners()\n return this\n }\n\n _addEventListeners() {\n if (!this.core.isReady)\n this.listenToOnce(this.core, Events.CORE_READY, this._onReady)\n else\n this._onReady()\n\n this.listenTo(this.core, Events.CORE_ACTIVE_CONTAINER_CHANGED, this._containerChanged)\n this.listenTo(this.core, Events.CORE_FULLSCREEN, this._onFullscreenChange)\n this.listenTo(this.core, Events.CORE_RESIZE, this._onResize)\n return this\n }\n\n _addContainerEventListeners() {\n const container = this.core.activeContainer\n if (container) {\n this.listenTo(container, Events.CONTAINER_PLAY, this._onPlay)\n this.listenTo(container, Events.CONTAINER_PAUSE, this._onPause)\n this.listenTo(container, Events.CONTAINER_STOP, this._onStop)\n this.listenTo(container, Events.CONTAINER_ENDED, this._onEnded)\n this.listenTo(container, Events.CONTAINER_SEEK, this._onSeek)\n this.listenTo(container, Events.CONTAINER_ERROR, this._onError)\n this.listenTo(container, Events.CONTAINER_TIMEUPDATE, this._onTimeUpdate)\n this.listenTo(container, Events.CONTAINER_VOLUME, this._onVolumeUpdate)\n this.listenTo(container, Events.CONTAINER_SUBTITLE_AVAILABLE, this._onSubtitleAvailable)\n }\n return this\n }\n\n _registerOptionEventListeners(newEvents = {}, events = {}) {\n const hasNewEvents = Object.keys(newEvents).length > 0\n hasNewEvents && Object.keys(events).forEach((userEvent) => {\n const eventType = this.eventsMapping[userEvent]\n eventType && this.off(eventType, events[userEvent])\n })\n\n Object.keys(newEvents).forEach((userEvent) => {\n const eventType = this.eventsMapping[userEvent]\n if (eventType) {\n let eventFunction = newEvents[userEvent]\n eventFunction = typeof eventFunction === 'function' && eventFunction\n eventFunction && this.on(eventType, eventFunction)\n }\n })\n return this\n }\n\n _containerChanged() {\n this.stopListening()\n this._addEventListeners()\n }\n\n _onReady() {\n this._ready = true\n this._addContainerEventListeners()\n this.trigger(Events.PLAYER_READY)\n }\n\n _onFullscreenChange(fullscreen) {\n this.trigger(Events.PLAYER_FULLSCREEN, fullscreen)\n }\n\n _onVolumeUpdate(volume) {\n this.trigger(Events.PLAYER_VOLUMEUPDATE, volume)\n }\n\n _onSubtitleAvailable() {\n this.trigger(Events.PLAYER_SUBTITLE_AVAILABLE)\n }\n\n _onResize(size) {\n this.trigger(Events.PLAYER_RESIZE, size)\n }\n\n _onPlay() {\n this.trigger(Events.PLAYER_PLAY)\n }\n\n _onPause() {\n this.trigger(Events.PLAYER_PAUSE)\n }\n\n _onStop() {\n this.trigger(Events.PLAYER_STOP, this.getCurrentTime())\n }\n\n _onEnded() {\n this.trigger(Events.PLAYER_ENDED)\n }\n\n _onSeek(time) {\n this.trigger(Events.PLAYER_SEEK, time)\n }\n\n _onTimeUpdate(timeProgress) {\n this.trigger(Events.PLAYER_TIMEUPDATE, timeProgress)\n }\n\n _onError(error) {\n this.trigger(Events.PLAYER_ERROR, error)\n }\n\n _normalizeSources(options) {\n const sources = options.sources || (options.source !== undefined? [options.source] : [])\n return sources.length === 0 ? [{ source:'', mimeType:'' }] : sources\n }\n\n /**\n * resizes the current player canvas.\n * @method resize\n * @param {Object} size should be a literal object with `height` and `width`.\n * @return {Player} itself\n * @example\n * ```javascript\n * player.resize({height: 360, width: 640})\n * ```\n */\n resize(size) {\n this.core.resize(size)\n return this\n }\n\n /**\n * loads a new source.\n * @method load\n * @param {Array|String} sources source or sources of video.\n * An array item can be a string or {source: <>, mimeType: <>}\n * @param {String} mimeType a mime type, example: `'application/vnd.apple.mpegurl'`\n * @param {Boolean} [autoPlay=false] whether playing should be started immediately\n * @return {Player} itself\n */\n load(sources, mimeType, autoPlay) {\n if (autoPlay !== undefined)\n this.configure({ autoPlay: !!autoPlay })\n\n this.core.load(sources, mimeType)\n return this\n }\n\n /**\n * destroys the current player and removes it from the DOM.\n * @method destroy\n * @return {Player} itself\n */\n destroy() {\n this.stopListening()\n this.core.destroy()\n return this\n }\n\n /**\n * Gives user consent to playback. Required by mobile device after a click event before Player.load().\n * @method consent\n * @return {Player} itself\n */\n consent() {\n this.core.getCurrentPlayback().consent()\n return this\n }\n\n /**\n * plays the current video (`source`).\n * @method play\n * @return {Player} itself\n */\n play() {\n this.core.activeContainer.play()\n return this\n }\n\n /**\n * pauses the current video (`source`).\n * @method pause\n * @return {Player} itself\n */\n pause() {\n this.core.activeContainer.pause()\n return this\n }\n\n /**\n * stops the current video (`source`).\n * @method stop\n * @return {Player} itself\n */\n stop() {\n this.core.activeContainer.stop()\n return this\n }\n\n\n /**\n * seeks the current video (`source`). For example, `player.seek(120)` will seek to second 120 (2minutes) of the current video.\n * @method seek\n * @param {Number} time should be a number between 0 and the video duration.\n * @return {Player} itself\n */\n seek(time) {\n this.core.activeContainer.seek(time)\n return this\n }\n\n /**\n * seeks the current video (`source`). For example, `player.seek(50)` will seek to the middle of the current video.\n * @method seekPercentage\n * @param {Number} time should be a number between 0 and 100.\n * @return {Player} itself\n */\n seekPercentage(percentage) {\n this.core.activeContainer.seekPercentage(percentage)\n return this\n }\n\n /**\n * mutes the current video (`source`).\n * @method mute\n * @return {Player} itself\n */\n mute() {\n this._mutedVolume = this.getVolume()\n this.setVolume(0)\n return this\n }\n\n /**\n * unmutes the current video (`source`).\n * @method unmute\n * @return {Player} itself\n */\n unmute() {\n this.setVolume(typeof this._mutedVolume === 'number' ? this._mutedVolume : 100)\n this._mutedVolume = null\n return this\n }\n\n /**\n * checks if the player is playing.\n * @method isPlaying\n * @return {Boolean} `true` if the current source is playing, otherwise `false`\n */\n isPlaying() {\n return this.core.activeContainer.isPlaying()\n }\n\n /**\n * returns `true` if DVR is enable otherwise `false`.\n * @method isDvrEnabled\n * @return {Boolean}\n */\n isDvrEnabled() {\n return this.core.activeContainer.isDvrEnabled()\n }\n\n /**\n * returns `true` if DVR is in use otherwise `false`.\n * @method isDvrInUse\n * @return {Boolean}\n */\n isDvrInUse() {\n return this.core.activeContainer.isDvrInUse()\n }\n\n /**\n * enables to configure a player after its creation\n * @method configure\n * @param {Object} options all the options to change in form of a javascript object\n * @return {Player} itself\n */\n configure(options = {}) {\n this._registerOptionEventListeners(options.events, this.options.events)\n this.core.configure(options)\n return this\n }\n\n /**\n * get a plugin by its name.\n * @method getPlugin\n * @param {String} name of the plugin.\n * @return {Object} the plugin instance\n * @example\n * ```javascript\n * var poster = player.getPlugin('poster');\n * poster.hidePlayButton();\n * ```\n */\n getPlugin(name) {\n const plugins = this.core.plugins.concat(this.core.activeContainer.plugins)\n return plugins.filter(plugin => plugin.name === name)[0]\n }\n\n /**\n * the current time in seconds.\n * @method getCurrentTime\n * @return {Number} current time (in seconds) of the current source\n */\n getCurrentTime() {\n return this.core.activeContainer.getCurrentTime()\n }\n\n /**\n * The time that \"0\" now represents relative to when playback started.\n * For a stream with a sliding window this will increase as content is\n * removed from the beginning.\n * @method getStartTimeOffset\n * @return {Number} time (in seconds) that time \"0\" represents.\n */\n getStartTimeOffset() {\n return this.core.activeContainer.getStartTimeOffset()\n }\n\n /**\n * the duration time in seconds.\n * @method getDuration\n * @return {Number} duration time (in seconds) of the current source\n */\n getDuration() {\n return this.core.activeContainer.getDuration()\n }\n}\n\nObject.assign(Player.prototype, ErrorMixin)\n","// Copyright 2014 Globo.com Player authors. All rights reserved.\n// Use of this source code is governed by a BSD-style\n// license that can be found in the LICENSE file.\n\nclass PlayerInfo {\n constructor() {\n this.options = {}\n this.playbackPlugins = []\n this.currentSize = { width: 0, height: 0 }\n }\n}\n\nPlayerInfo._players = {}\n\nPlayerInfo.getInstance = (playerId) => {\n return PlayerInfo._players[playerId] || (PlayerInfo._players[playerId] = new PlayerInfo())\n}\n\nexport default PlayerInfo\n","module.exports = \"\"","module.exports = \"\"","module.exports = \"\"","module.exports = \"\"","module.exports = \"\"","module.exports = \"\"","module.exports = \"\"","module.exports = \"\"","module.exports = \"\"","module.exports = \"\"","// Copyright 2014 Globo.com Player authors. All rights reserved.\n// Use of this source code is governed by a BSD-style\n// license that can be found in the LICENSE file.\n\nimport Player from './components/player'\nimport Utils from './base/utils'\nimport Events from './base/events'\nimport Playback from './base/playback'\nimport ContainerPlugin from './base/container_plugin'\nimport CorePlugin from './base/core_plugin'\nimport UICorePlugin from './base/ui_core_plugin'\nimport UIContainerPlugin from './base/ui_container_plugin'\nimport BaseObject from './base/base_object'\nimport UIObject from './base/ui_object'\nimport Browser from './components/browser'\nimport Container from './components/container'\nimport Core from './components/core'\nimport PlayerError from './components/error'\nimport Loader from './components/loader'\nimport Mediator from './components/mediator'\nimport PlayerInfo from './components/player_info'\nimport BaseFlashPlayback from './playbacks/base_flash_playback'\nimport Flash from './playbacks/flash'\nimport FlasHLS from './playbacks/flashls'\nimport HLS from './playbacks/hls'\nimport HTML5Audio from './playbacks/html5_audio'\nimport HTML5Video from './playbacks/html5_video'\nimport HTMLImg from './playbacks/html_img'\nimport NoOp from './playbacks/no_op'\nimport MediaControl from './plugins/media_control'\nimport ClickToPausePlugin from './plugins/click_to_pause'\nimport DVRControls from './plugins/dvr_controls'\nimport Favicon from './plugins/favicon'\nimport Log from './plugins/log'\nimport Poster from './plugins/poster'\nimport SpinnerThreeBouncePlugin from './plugins/spinner_three_bounce'\nimport WaterMarkPlugin from './plugins/watermark'\nimport Styler from './base/styler'\nimport Vendor from './vendor'\nimport template from './base/template'\n\nimport $ from 'clappr-zepto'\n\nconst version = VERSION\n\nexport default {\n Player,\n Mediator,\n Events,\n Browser,\n PlayerInfo,\n MediaControl,\n ContainerPlugin,\n UIContainerPlugin,\n CorePlugin,\n UICorePlugin,\n Playback,\n Container,\n Core,\n PlayerError,\n Loader,\n BaseObject,\n UIObject,\n Utils,\n BaseFlashPlayback,\n Flash,\n FlasHLS,\n HLS,\n HTML5Audio,\n HTML5Video,\n HTMLImg,\n NoOp,\n ClickToPausePlugin,\n DVRControls,\n Favicon,\n Log,\n Poster,\n SpinnerThreeBouncePlugin,\n WaterMarkPlugin,\n Styler,\n Vendor,\n version,\n template,\n $\n}\n","// Copyright 2015 Globo.com Player authors. All rights reserved.\n// Use of this source code is governed by a BSD-style\n// license that can be found in the LICENSE file.\n\nimport Playback from '../../base/playback'\nimport template from '../../base/template'\nimport Browser from '../../components/browser'\n\nimport flashHTML from './public/flash.html'\nimport './public/flash.scss'\n\nconst IE_CLASSID = 'clsid:d27cdb6e-ae6d-11cf-96b8-444553540000'\n\nexport default class BaseFlashPlayback extends Playback {\n get tagName() { return 'object' }\n get swfPath() { return '' }\n get wmode() { return 'transparent' }\n get template() { return template(flashHTML) }\n get attributes() {\n let type = 'application/x-shockwave-flash'\n\n if (Browser.isLegacyIE)\n type = ''\n\n\n return {\n class: 'clappr-flash-playback',\n type: type,\n width: '100%',\n height: '100%',\n data: this.swfPath,\n 'data-flash-playback': this.name\n }\n }\n\n setElement(element) {\n this.$el = element\n this.el = element[0]\n }\n\n render() {\n this.$el.attr('data', this.swfPath)\n this.$el.html(this.template({\n cid: this.cid,\n swfPath: this.swfPath,\n baseUrl: this.baseUrl,\n playbackId: this.uniqueId,\n wmode: this.wmode,\n callbackName: `window.Clappr.flashlsCallbacks.${this.cid}` })\n )\n\n if (Browser.isIE) {\n this.$('embed').remove()\n\n if (Browser.isLegacyIE)\n this.$el.attr('classid', IE_CLASSID)\n\n }\n\n this.el.id = this.cid\n\n return this\n }\n}\n","export default require('./base_flash_playback')\n","module.exports = \"\\\">\\n\\n\\n\\n\\n\\n\\\">\\n\\n&callback=<%= callbackName %>\\\">\\n\\\"\\n type=\\\"application/x-shockwave-flash\\\"\\n disabled=\\\"disabled\\\"\\n tabindex=\\\"-1\\\"\\n enablecontextmenu=\\\"false\\\"\\n allowScriptAccess=\\\"always\\\"\\n quality=\\\"autohigh\\\"\\n pluginspage=\\\"http://www.macromedia.com/go/getflashplayer\\\"\\n wmode=\\\"<%= wmode %>\\\"\\n swliveconnect=\\\"true\\\"\\n allowfullscreen=\\\"false\\\"\\n bgcolor=\\\"#000000\\\"\\n FlashVars=\\\"playbackId=<%= playbackId %>&callback=<%= callbackName %>\\\"\\n data=\\\"<%= swfPath %>\\\"\\n src=\\\"<%= swfPath %>\\\"\\n width=\\\"100%\\\"\\n height=\\\"100%\\\">\\n\\n\";","\nvar content = require(\"!!../../../../node_modules/css-loader/index.js!../../../../node_modules/postcss-loader/lib/index.js!../../../../node_modules/sass-loader/lib/loader.js?includePaths[]=/Users/bruno/workspace/clappr/clappr/src/base/scss!./flash.scss\");\n\nif(typeof content === 'string') content = [[module.id, content, '']];\n\nvar transform;\nvar insertInto;\n\n\n\nvar options = {\"singleton\":true,\"hmr\":true}\n\noptions.transform = transform\noptions.insertInto = undefined;\n\nvar update = require(\"!../../../../node_modules/style-loader/lib/addStyles.js\")(content, options);\n\nif(content.locals) module.exports = content.locals;\n\nif(module.hot) {\n\tmodule.hot.accept(\"!!../../../../node_modules/css-loader/index.js!../../../../node_modules/postcss-loader/lib/index.js!../../../../node_modules/sass-loader/lib/loader.js?includePaths[]=/Users/bruno/workspace/clappr/clappr/src/base/scss!./flash.scss\", function() {\n\t\tvar newContent = require(\"!!../../../../node_modules/css-loader/index.js!../../../../node_modules/postcss-loader/lib/index.js!../../../../node_modules/sass-loader/lib/loader.js?includePaths[]=/Users/bruno/workspace/clappr/clappr/src/base/scss!./flash.scss\");\n\n\t\tif(typeof newContent === 'string') newContent = [[module.id, newContent, '']];\n\n\t\tvar locals = (function(a, b) {\n\t\t\tvar key, idx = 0;\n\n\t\t\tfor(key in a) {\n\t\t\t\tif(!b || a[key] !== b[key]) return false;\n\t\t\t\tidx++;\n\t\t\t}\n\n\t\t\tfor(key in b) idx--;\n\n\t\t\treturn idx === 0;\n\t\t}(content.locals, newContent.locals));\n\n\t\tif(!locals) throw new Error('Aborting CSS HMR due to changed css-modules locals.');\n\n\t\tupdate(newContent);\n\t});\n\n\tmodule.hot.dispose(function() { update(); });\n}","// Copyright 2014 Globo.com Player authors. All rights reserved.\n// Use of this source code is governed by a BSD-style\n// license that can be found in the LICENSE file.\n\nimport { seekStringToSeconds } from '../../base/utils'\n\nimport BaseFlashPlayback from '../../playbacks/base_flash_playback'\nimport Browser from '../../components/browser'\nimport Mediator from '../../components/mediator'\nimport template from '../../base/template'\nimport $ from 'clappr-zepto'\nimport Events from '../../base/events'\nimport Playback from '../../base/playback'\nimport flashSwf from './public/Player.swf'\n\nconst MAX_ATTEMPTS = 60\n\nexport default class Flash extends BaseFlashPlayback {\n get name() { return 'flash' }\n get swfPath() { return template(flashSwf)({ baseUrl: this._baseUrl }) }\n\n /**\n * Determine if the playback has ended.\n * @property ended\n * @type Boolean\n */\n get ended() {\n return this._currentState === 'ENDED'\n }\n\n /**\n * Determine if the playback is buffering.\n * This is related to the PLAYBACK_BUFFERING and PLAYBACK_BUFFERFULL events\n * @property buffering\n * @type Boolean\n */\n get buffering() {\n return !!this._bufferingState && this._currentState !== 'ENDED'\n }\n\n constructor(...args) {\n super(...args)\n this._src = this.options.src\n this._baseUrl = this.options.baseUrl\n this._autoPlay = this.options.autoPlay\n this.settings = { default: ['seekbar'] }\n this.settings.left = ['playpause', 'position', 'duration']\n this.settings.right = ['fullscreen', 'volume']\n this.settings.seekEnabled = true\n this._isReadyState = false\n this._addListeners()\n }\n\n\n _bootstrap() {\n if (this.el.playerPlay) {\n this.el.width = '100%'\n this.el.height = '100%'\n if (this._currentState === 'PLAYING') { this._firstPlay() } else {\n this._currentState = 'IDLE'\n this._autoPlay && this.play()\n }\n $('
').insertAfter(this.$el)\n if (this.getDuration() > 0)\n this._metadataLoaded()\n else\n Mediator.once(this.uniqueId + ':timeupdate', this._metadataLoaded, this)\n\n } else {\n this._attempts = this._attempts || 0\n if (++this._attempts <= MAX_ATTEMPTS)\n setTimeout(() => this._bootstrap(), 50)\n else\n this.trigger(Events.PLAYBACK_ERROR, { message: 'Max number of attempts reached' }, this.name)\n\n }\n }\n\n _metadataLoaded() {\n this._isReadyState = true\n this.trigger(Events.PLAYBACK_READY, this.name)\n this.trigger(Events.PLAYBACK_SETTINGSUPDATE, this.name)\n }\n\n getPlaybackType() {\n return Playback.VOD\n }\n\n isHighDefinitionInUse() {\n return false\n }\n\n _updateTime() {\n this.trigger(Events.PLAYBACK_TIMEUPDATE, { current: this.el.getPosition(), total: this.el.getDuration() }, this.name)\n }\n\n _addListeners() {\n Mediator.on(this.uniqueId + ':progress', this._progress, this)\n Mediator.on(this.uniqueId + ':timeupdate', this._updateTime, this)\n Mediator.on(this.uniqueId + ':statechanged', this._checkState, this)\n Mediator.on(this.uniqueId + ':flashready', this._bootstrap, this)\n }\n\n stopListening() {\n super.stopListening()\n Mediator.off(this.uniqueId + ':progress')\n Mediator.off(this.uniqueId + ':timeupdate')\n Mediator.off(this.uniqueId + ':statechanged')\n Mediator.off(this.uniqueId + ':flashready')\n }\n\n _checkState() {\n if (this._isIdle || this._currentState === 'PAUSED') { return } else if (this._currentState !== 'PLAYING_BUFFERING' && this.el.getState() === 'PLAYING_BUFFERING') {\n this._bufferingState = true\n this.trigger(Events.PLAYBACK_BUFFERING, this.name)\n this._currentState = 'PLAYING_BUFFERING'\n } else if (this.el.getState() === 'PLAYING') {\n this._bufferingState = false\n this.trigger(Events.PLAYBACK_BUFFERFULL, this.name)\n this._currentState = 'PLAYING'\n } else if (this.el.getState() === 'IDLE') { this._currentState = 'IDLE' } else if (this.el.getState() === 'ENDED') {\n this.trigger(Events.PLAYBACK_ENDED, this.name)\n this.trigger(Events.PLAYBACK_TIMEUPDATE, { current: 0, total: this.el.getDuration() }, this.name)\n this._currentState = 'ENDED'\n this._isIdle = true\n }\n }\n\n _progress() {\n if (this._currentState !== 'IDLE' && this._currentState !== 'ENDED') {\n this.trigger(Events.PLAYBACK_PROGRESS,{\n start: 0,\n current: this.el.getBytesLoaded(),\n total: this.el.getBytesTotal()\n })\n }\n }\n\n _firstPlay() {\n if (this.el.playerPlay) {\n this._isIdle = false\n this.el.playerPlay(this._src)\n this.listenToOnce(this, Events.PLAYBACK_BUFFERFULL, () => this._checkInitialSeek())\n this._currentState = 'PLAYING'\n } else { this.listenToOnce(this, Events.PLAYBACK_READY, this._firstPlay) }\n\n }\n\n _checkInitialSeek() {\n let seekTime = seekStringToSeconds(window.location.href)\n if (seekTime !== 0)\n this.seekSeconds(seekTime)\n\n }\n\n play() {\n this.trigger(Events.PLAYBACK_PLAY_INTENT)\n if (this._currentState === 'PAUSED' || this._currentState === 'PLAYING_BUFFERING') {\n this._currentState = 'PLAYING'\n this.el.playerResume()\n this.trigger(Events.PLAYBACK_PLAY, this.name)\n } else if (this._currentState !== 'PLAYING') {\n this._firstPlay()\n this.trigger(Events.PLAYBACK_PLAY, this.name)\n }\n }\n\n volume(value) {\n if (this.isReady)\n this.el.playerVolume(value)\n else\n this.listenToOnce(this, Events.PLAYBACK_BUFFERFULL, () => this.volume(value))\n\n }\n\n pause() {\n this._currentState = 'PAUSED'\n this.el.playerPause()\n this.trigger(Events.PLAYBACK_PAUSE, this.name)\n }\n\n stop() {\n this.el.playerStop()\n this.trigger(Events.PLAYBACK_STOP)\n this.trigger(Events.PLAYBACK_TIMEUPDATE, { current: 0, total: 0 }, this.name)\n }\n\n isPlaying() {\n return !!(this.isReady && this._currentState.indexOf('PLAYING') > -1)\n }\n\n get isReady() {\n return this._isReadyState\n }\n\n getDuration() {\n return this.el.getDuration()\n }\n\n seekPercentage(percentage) {\n if (this.el.getDuration() > 0) {\n let seekSeconds = this.el.getDuration() * (percentage / 100)\n this.seek(seekSeconds)\n } else { this.listenToOnce(this, Events.PLAYBACK_BUFFERFULL, () => this.seekPercentage(percentage)) }\n\n }\n\n seek(time) {\n if (this.isReady && this.el.playerSeek) {\n this.el.playerSeek(time)\n this.trigger(Events.PLAYBACK_TIMEUPDATE, { current: time, total: this.el.getDuration() }, this.name)\n if (this._currentState === 'PAUSED')\n this.el.playerPause()\n\n } else { this.listenToOnce(this, Events.PLAYBACK_BUFFERFULL, () => this.seek(time)) }\n\n }\n\n destroy() {\n clearInterval(this.bootstrapId)\n super.stopListening()\n this.$el.remove()\n }\n}\n\nFlash.canPlay = function(resource) {\n if (!Browser.hasFlash || !resource || resource.constructor !== String) { return false } else {\n const resourceParts = resource.split('?')[0].match(/.*\\.(.*)$/) || []\n return resourceParts.length > 1 && !Browser.isMobile && resourceParts[1].toLowerCase().match(/^(mp4|mov|f4v|3gpp|3gp)$/)\n\n }\n}\n","import Flash from './flash'\nexport default Flash\n","module.exports = \"<%=baseUrl%>/4b76590b32dab62bc95c1b7951efae78.swf\";","// Copyright 2014 Globo.com Player authors. All rights reserved.\n// Use of this source code is governed by a BSD-style\n// license that can be found in the LICENSE file.\n\nimport BaseFlashPlayback from '../../playbacks/base_flash_playback'\nimport Events from '../../base/events'\nimport template from '../../base/template'\nimport Playback from '../../base/playback'\nimport Mediator from '../../components/mediator'\nimport Browser from '../../components/browser'\nimport PlayerError from '../../components/error'\nimport HLSEvents from './flashls_events'\nimport hlsSwf from './public/HLSPlayer.swf'\nimport $ from 'clappr-zepto'\n\n\nconst MAX_ATTEMPTS = 60\nconst AUTO = -1\n\nexport default class FlasHLS extends BaseFlashPlayback {\n get name() { return 'flashls' }\n get swfPath() { return template(hlsSwf)({ baseUrl: this._baseUrl }) }\n\n get levels() { return this._levels || [] }\n get currentLevel() {\n if (this._currentLevel === null || this._currentLevel === undefined)\n return AUTO\n else\n return this._currentLevel //0 is a valid level ID\n\n }\n set currentLevel(id) {\n this._currentLevel = id\n this.trigger(Events.PLAYBACK_LEVEL_SWITCH_START)\n this.el.playerSetCurrentLevel(id)\n }\n\n /**\n * Determine if the playback has ended.\n * @property ended\n * @type Boolean\n */\n get ended() {\n return this._hasEnded\n }\n\n /**\n * Determine if the playback is buffering.\n * This is related to the PLAYBACK_BUFFERING and PLAYBACK_BUFFERFULL events\n * @property buffering\n * @type Boolean\n */\n get buffering() {\n return !!this._bufferingState && !this._hasEnded\n }\n\n constructor(...args) {\n super(...args)\n this._src = this.options.src\n this._baseUrl = this.options.baseUrl\n this._initHlsParameters(this.options)\n // TODO can this be private?\n this.highDefinition = false\n this._autoPlay = this.options.autoPlay\n this._loop = this.options.loop\n this._defaultSettings = {\n left: ['playstop'],\n default: ['seekbar'],\n right: ['fullscreen', 'volume', 'hd-indicator'],\n seekEnabled: false\n }\n this.settings = $.extend({}, this._defaultSettings)\n this._playbackType = Playback.LIVE\n this._hasEnded = false\n this._addListeners()\n }\n\n _initHlsParameters(options) {\n this._autoStartLoad = (options.autoStartLoad === undefined) ? true : options.autoStartLoad\n this._capLevelToStage = (options.capLevelToStage === undefined) ? false : options.capLevelToStage\n this._maxLevelCappingMode = (options.maxLevelCappingMode === undefined) ? 'downscale' : options.maxLevelCappingMode\n this._minBufferLength = (options.minBufferLength === undefined) ? -1 : options.minBufferLength\n this._minBufferLengthCapping = (options.minBufferLengthCapping === undefined) ? -1 : options.minBufferLengthCapping\n this._maxBufferLength = (options.maxBufferLength === undefined) ? 120 : options.maxBufferLength\n this._maxBackBufferLength = (options.maxBackBufferLength === undefined) ? 30 : options.maxBackBufferLength\n this._lowBufferLength = (options.lowBufferLength === undefined) ? 3 : options.lowBufferLength\n this._mediaTimePeriod = (options.mediaTimePeriod === undefined) ? 100 : options.mediaTimePeriod\n this._fpsDroppedMonitoringPeriod = (options.fpsDroppedMonitoringPeriod === undefined) ? 5000 : options.fpsDroppedMonitoringPeriod\n this._fpsDroppedMonitoringThreshold = (options.fpsDroppedMonitoringThreshold === undefined) ? 0.2 : options.fpsDroppedMonitoringThreshold\n this._capLevelonFPSDrop = (options.capLevelonFPSDrop === undefined) ? false : options.capLevelonFPSDrop\n this._smoothAutoSwitchonFPSDrop = (options.smoothAutoSwitchonFPSDrop === undefined) ? this.capLevelonFPSDrop : options.smoothAutoSwitchonFPSDrop\n this._switchDownOnLevelError = (options.switchDownOnLevelError === undefined) ? true : options.switchDownOnLevelError\n this._seekMode = (options.seekMode === undefined) ? 'ACCURATE' : options.seekMode\n this._keyLoadMaxRetry = (options.keyLoadMaxRetry === undefined) ? 3 : options.keyLoadMaxRetry\n this._keyLoadMaxRetryTimeout = (options.keyLoadMaxRetryTimeout === undefined) ? 64000 : options.keyLoadMaxRetryTimeout\n this._fragmentLoadMaxRetry = (options.fragmentLoadMaxRetry === undefined) ? 3 : options.fragmentLoadMaxRetry\n this._fragmentLoadMaxRetryTimeout = (options.fragmentLoadMaxRetryTimeout === undefined) ? 4000 : options.fragmentLoadMaxRetryTimeout\n this._fragmentLoadSkipAfterMaxRetry = (options.fragmentLoadSkipAfterMaxRetry === undefined) ? true : options.fragmentLoadSkipAfterMaxRetry\n this._maxSkippedFragments = (options.maxSkippedFragments === undefined) ? 5 : options.maxSkippedFragments\n this._flushLiveURLCache = (options.flushLiveURLCache === undefined) ? false : options.flushLiveURLCache\n this._initialLiveManifestSize = (options.initialLiveManifestSize === undefined) ? 1 : options.initialLiveManifestSize\n this._manifestLoadMaxRetry = (options.manifestLoadMaxRetry === undefined) ? 3 : options.manifestLoadMaxRetry\n this._manifestLoadMaxRetryTimeout = (options.manifestLoadMaxRetryTimeout === undefined) ? 64000 : options.manifestLoadMaxRetryTimeout\n this._manifestRedundantLoadmaxRetry = (options.manifestRedundantLoadmaxRetry === undefined) ? 3 : options.manifestRedundantLoadmaxRetry\n this._startFromBitrate = (options.startFromBitrate === undefined) ? -1 : options.startFromBitrate\n this._startFromLevel = (options.startFromLevel === undefined) ? -1 : options.startFromLevel\n this._autoStartMaxDuration = (options.autoStartMaxDuration === undefined) ? -1 : options.autoStartMaxDuration\n this._seekFromLevel = (options.seekFromLevel === undefined) ? -1 : options.seekFromLevel\n this._useHardwareVideoDecoder = (options.useHardwareVideoDecoder === undefined) ? false : options.useHardwareVideoDecoder\n this._hlsLogEnabled = (options.hlsLogEnabled === undefined) ? true : options.hlsLogEnabled\n this._logDebug = (options.logDebug === undefined) ? false : options.logDebug\n this._logDebug2 = (options.logDebug2 === undefined) ? false : options.logDebug2\n this._logWarn = (options.logWarn === undefined) ? true : options.logWarn\n this._logError = (options.logError === undefined) ? true : options.logError\n this._hlsMinimumDvrSize = (options.hlsMinimumDvrSize === undefined) ? 60 : options.hlsMinimumDvrSize\n }\n\n _addListeners() {\n Mediator.on(this.cid + ':flashready', () => this._bootstrap())\n Mediator.on(this.cid + ':timeupdate', (timeMetrics) => this._updateTime(timeMetrics))\n Mediator.on(this.cid + ':playbackstate', (state) => this._setPlaybackState(state))\n Mediator.on(this.cid + ':levelchanged', (level) => this._levelChanged(level))\n Mediator.on(this.cid + ':error', (code, url, message) => this._flashPlaybackError(code, url, message))\n Mediator.on(this.cid + ':fragmentloaded',(loadmetrics) => this._onFragmentLoaded(loadmetrics))\n Mediator.on(this.cid + ':levelendlist', (level) => this._onLevelEndlist(level))\n }\n\n stopListening() {\n super.stopListening()\n Mediator.off(this.cid + ':flashready')\n Mediator.off(this.cid + ':timeupdate')\n Mediator.off(this.cid + ':playbackstate')\n Mediator.off(this.cid + ':levelchanged')\n Mediator.off(this.cid + ':playbackerror')\n Mediator.off(this.cid + ':fragmentloaded')\n Mediator.off(this.cid + ':manifestloaded')\n Mediator.off(this.cid + ':levelendlist')\n }\n\n _bootstrap() {\n if (this.el.playerLoad) {\n this.el.width = '100%'\n this.el.height = '100%'\n this._isReadyState = true\n this._srcLoaded = false\n this._currentState = 'IDLE'\n this._setFlashSettings()\n this._updatePlaybackType()\n if (this._autoPlay || this._shouldPlayOnManifestLoaded)\n this.play()\n\n this.trigger(Events.PLAYBACK_READY, this.name)\n } else {\n this._bootstrapAttempts = this._bootstrapAttempts || 0\n if (++this._bootstrapAttempts <= MAX_ATTEMPTS) {\n setTimeout(() => this._bootstrap(), 50)\n } else {\n const formattedError = this.createError({\n code: 'playerLoadFail_maxNumberAttemptsReached',\n description: `${this.name} error: Max number of attempts reached`,\n level: PlayerError.Levels.FATAL,\n raw: {},\n })\n this.trigger(Events.PLAYBACK_ERROR, formattedError)\n }\n }\n }\n\n _setFlashSettings() {\n this.el.playerSetAutoStartLoad(this._autoStartLoad)\n this.el.playerSetCapLevelToStage(this._capLevelToStage)\n this.el.playerSetMaxLevelCappingMode(this._maxLevelCappingMode)\n this.el.playerSetMinBufferLength(this._minBufferLength)\n this.el.playerSetMinBufferLengthCapping(this._minBufferLengthCapping)\n this.el.playerSetMaxBufferLength(this._maxBufferLength)\n this.el.playerSetMaxBackBufferLength(this._maxBackBufferLength)\n this.el.playerSetLowBufferLength(this._lowBufferLength)\n this.el.playerSetMediaTimePeriod(this._mediaTimePeriod)\n this.el.playerSetFpsDroppedMonitoringPeriod(this._fpsDroppedMonitoringPeriod)\n this.el.playerSetFpsDroppedMonitoringThreshold(this._fpsDroppedMonitoringThreshold)\n this.el.playerSetCapLevelonFPSDrop(this._capLevelonFPSDrop)\n this.el.playerSetSmoothAutoSwitchonFPSDrop(this._smoothAutoSwitchonFPSDrop)\n this.el.playerSetSwitchDownOnLevelError(this._switchDownOnLevelError)\n this.el.playerSetSeekMode(this._seekMode)\n this.el.playerSetKeyLoadMaxRetry(this._keyLoadMaxRetry)\n this.el.playerSetKeyLoadMaxRetryTimeout(this._keyLoadMaxRetryTimeout)\n this.el.playerSetFragmentLoadMaxRetry(this._fragmentLoadMaxRetry)\n this.el.playerSetFragmentLoadMaxRetryTimeout(this._fragmentLoadMaxRetryTimeout)\n this.el.playerSetFragmentLoadSkipAfterMaxRetry(this._fragmentLoadSkipAfterMaxRetry)\n this.el.playerSetMaxSkippedFragments(this._maxSkippedFragments)\n this.el.playerSetFlushLiveURLCache(this._flushLiveURLCache)\n this.el.playerSetInitialLiveManifestSize(this._initialLiveManifestSize)\n this.el.playerSetManifestLoadMaxRetry(this._manifestLoadMaxRetry)\n this.el.playerSetManifestLoadMaxRetryTimeout(this._manifestLoadMaxRetryTimeout)\n this.el.playerSetManifestRedundantLoadmaxRetry(this._manifestRedundantLoadmaxRetry)\n this.el.playerSetStartFromBitrate(this._startFromBitrate)\n this.el.playerSetStartFromLevel(this._startFromLevel)\n this.el.playerSetAutoStartMaxDuration(this._autoStartMaxDuration)\n this.el.playerSetSeekFromLevel(this._seekFromLevel)\n this.el.playerSetUseHardwareVideoDecoder(this._useHardwareVideoDecoder)\n this.el.playerSetLogInfo(this._hlsLogEnabled)\n this.el.playerSetLogDebug(this._logDebug)\n this.el.playerSetLogDebug2(this._logDebug2)\n this.el.playerSetLogWarn(this._logWarn)\n this.el.playerSetLogError(this._logError)\n }\n\n setAutoStartLoad(autoStartLoad) {\n this._autoStartLoad = autoStartLoad\n this.el.playerSetAutoStartLoad(this._autoStartLoad)\n }\n\n setCapLevelToStage(capLevelToStage) {\n this._capLevelToStage = capLevelToStage\n this.el.playerSetCapLevelToStage(this._capLevelToStage)\n }\n\n setMaxLevelCappingMode(maxLevelCappingMode) {\n this._maxLevelCappingMode = maxLevelCappingMode\n this.el.playerSetMaxLevelCappingMode(this._maxLevelCappingMode)\n }\n\n setSetMinBufferLength(minBufferLength) {\n this._minBufferLength = minBufferLength\n this.el.playerSetMinBufferLength(this._minBufferLength)\n }\n\n setMinBufferLengthCapping(minBufferLengthCapping) {\n this._minBufferLengthCapping = minBufferLengthCapping\n this.el.playerSetMinBufferLengthCapping(this._minBufferLengthCapping)\n }\n\n setMaxBufferLength(maxBufferLength) {\n this._maxBufferLength = maxBufferLength\n this.el.playerSetMaxBufferLength(this._maxBufferLength)\n }\n\n setMaxBackBufferLength(maxBackBufferLength) {\n this._maxBackBufferLength = maxBackBufferLength\n this.el.playerSetMaxBackBufferLength(this._maxBackBufferLength)\n }\n\n setLowBufferLength(lowBufferLength) {\n this._lowBufferLength = lowBufferLength\n this.el.playerSetLowBufferLength(this._lowBufferLength)\n }\n\n setMediaTimePeriod(mediaTimePeriod) {\n this._mediaTimePeriod = mediaTimePeriod\n this.el.playerSetMediaTimePeriod(this._mediaTimePeriod)\n }\n\n setFpsDroppedMonitoringPeriod(fpsDroppedMonitoringPeriod) {\n this._fpsDroppedMonitoringPeriod = fpsDroppedMonitoringPeriod\n this.el.playerSetFpsDroppedMonitoringPeriod(this._fpsDroppedMonitoringPeriod)\n }\n\n setFpsDroppedMonitoringThreshold(fpsDroppedMonitoringThreshold) {\n this._fpsDroppedMonitoringThreshold = fpsDroppedMonitoringThreshold\n this.el.playerSetFpsDroppedMonitoringThreshold(this._fpsDroppedMonitoringThreshold)\n }\n\n setCapLevelonFPSDrop(capLevelonFPSDrop) {\n this._capLevelonFPSDrop = capLevelonFPSDrop\n this.el.playerSetCapLevelonFPSDrop(this._capLevelonFPSDrop)\n }\n\n setSmoothAutoSwitchonFPSDrop(smoothAutoSwitchonFPSDrop) {\n this._smoothAutoSwitchonFPSDrop = smoothAutoSwitchonFPSDrop\n this.el.playerSetSmoothAutoSwitchonFPSDrop(this._smoothAutoSwitchonFPSDrop)\n }\n\n setSwitchDownOnLevelError(switchDownOnLevelError) {\n this._switchDownOnLevelError = switchDownOnLevelError\n this.el.playerSetSwitchDownOnLevelError(this._switchDownOnLevelError)\n }\n\n setSeekMode(seekMode) {\n this._seekMode = seekMode\n this.el.playerSetSeekMode(this._seekMode)\n }\n\n setKeyLoadMaxRetry(keyLoadMaxRetry) {\n this._keyLoadMaxRetry = keyLoadMaxRetry\n this.el.playerSetKeyLoadMaxRetry(this._keyLoadMaxRetry)\n }\n\n setKeyLoadMaxRetryTimeout(keyLoadMaxRetryTimeout) {\n this._keyLoadMaxRetryTimeout = keyLoadMaxRetryTimeout\n this.el.playerSetKeyLoadMaxRetryTimeout(this._keyLoadMaxRetryTimeout)\n }\n\n setFragmentLoadMaxRetry(fragmentLoadMaxRetry) {\n this._fragmentLoadMaxRetry = fragmentLoadMaxRetry\n this.el.playerSetFragmentLoadMaxRetry(this._fragmentLoadMaxRetry)\n }\n\n setFragmentLoadMaxRetryTimeout(fragmentLoadMaxRetryTimeout) {\n this._fragmentLoadMaxRetryTimeout = fragmentLoadMaxRetryTimeout\n this.el.playerSetFragmentLoadMaxRetryTimeout(this._fragmentLoadMaxRetryTimeout)\n }\n\n setFragmentLoadSkipAfterMaxRetry(fragmentLoadSkipAfterMaxRetry) {\n this._fragmentLoadSkipAfterMaxRetry = fragmentLoadSkipAfterMaxRetry\n this.el.playerSetFragmentLoadSkipAfterMaxRetry(this._fragmentLoadSkipAfterMaxRetry)\n }\n\n setMaxSkippedFragments(maxSkippedFragments) {\n this._maxSkippedFragments = maxSkippedFragments\n this.el.playerSetMaxSkippedFragments(this._maxSkippedFragments)\n }\n\n setFlushLiveURLCache(flushLiveURLCache) {\n this._flushLiveURLCache = flushLiveURLCache\n this.el.playerSetFlushLiveURLCache(this._flushLiveURLCache)\n }\n\n setInitialLiveManifestSize(initialLiveManifestSize) {\n this._initialLiveManifestSize = initialLiveManifestSize\n this.el.playerSetInitialLiveManifestSize(this._initialLiveManifestSize)\n }\n\n setManifestLoadMaxRetry(manifestLoadMaxRetry) {\n this._manifestLoadMaxRetry = manifestLoadMaxRetry\n this.el.playerSetManifestLoadMaxRetry(this._manifestLoadMaxRetry)\n }\n\n setManifestLoadMaxRetryTimeout(manifestLoadMaxRetryTimeout) {\n this._manifestLoadMaxRetryTimeout = manifestLoadMaxRetryTimeout\n this.el.playerSetManifestLoadMaxRetryTimeout(this._manifestLoadMaxRetryTimeout)\n }\n\n setManifestRedundantLoadmaxRetry(manifestRedundantLoadmaxRetry) {\n this._manifestRedundantLoadmaxRetry = manifestRedundantLoadmaxRetry\n this.el.playerSetManifestRedundantLoadmaxRetry(this._manifestRedundantLoadmaxRetry)\n }\n\n setStartFromBitrate(startFromBitrate) {\n this._startFromBitrate = startFromBitrate\n this.el.playerSetStartFromBitrate(this._startFromBitrate)\n }\n\n setStartFromLevel(startFromLevel) {\n this._startFromLevel = startFromLevel\n this.el.playerSetStartFromLevel(this._startFromLevel)\n }\n\n setAutoStartMaxDuration(autoStartMaxDuration) {\n this._autoStartMaxDuration = autoStartMaxDuration\n this.el.playerSetAutoStartMaxDuration(this._autoStartMaxDuration)\n }\n\n setSeekFromLevel(seekFromLevel) {\n this._seekFromLevel = seekFromLevel\n this.el.playerSetSeekFromLevel(this._seekFromLevel)\n }\n\n setUseHardwareVideoDecoder(useHardwareVideoDecoder) {\n this._useHardwareVideoDecoder = useHardwareVideoDecoder\n this.el.playerSetUseHardwareVideoDecoder(this._useHardwareVideoDecoder)\n }\n\n setSetLogInfo(hlsLogEnabled) {\n this._hlsLogEnabled = hlsLogEnabled\n this.el.playerSetLogInfo(this._hlsLogEnabled)\n }\n\n setLogDebug(logDebug) {\n this._logDebug = logDebug\n this.el.playerSetLogDebug(this._logDebug)\n }\n\n setLogDebug2(logDebug2) {\n this._logDebug2 = logDebug2\n this.el.playerSetLogDebug2(this._logDebug2)\n }\n\n setLogWarn(logWarn) {\n this._logWarn = logWarn\n this.el.playerSetLogWarn(this._logWarn)\n }\n\n setLogError(logError) {\n this._logError = logError\n this.el.playerSetLogError(this._logError)\n }\n\n _levelChanged(level) {\n const currentLevel = this.el.getLevels()[level]\n if (currentLevel) {\n this.highDefinition = (currentLevel.height >= 720 || (currentLevel.bitrate / 1000) >= 2000)\n this.trigger(Events.PLAYBACK_HIGHDEFINITIONUPDATE, this.highDefinition)\n\n if (!this._levels || this._levels.length === 0) this._fillLevels()\n\n this.trigger(Events.PLAYBACK_BITRATE, {\n height: currentLevel.height,\n width: currentLevel.width,\n bandwidth: currentLevel.bitrate,\n bitrate: currentLevel.bitrate,\n level: level\n })\n this.trigger(Events.PLAYBACK_LEVEL_SWITCH_END)\n }\n }\n\n _updateTime(timeMetrics) {\n if (this._currentState === 'IDLE')\n return\n\n\n const duration = this._normalizeDuration(timeMetrics.duration)\n let position = Math.min(Math.max(timeMetrics.position, 0), duration)\n const previousDVRStatus = this._dvrEnabled\n const livePlayback = (this._playbackType === Playback.LIVE)\n this._dvrEnabled = (livePlayback && duration > this._hlsMinimumDvrSize)\n\n if (duration === 100 || livePlayback === undefined)\n return\n\n\n if (this._dvrEnabled !== previousDVRStatus) {\n this._updateSettings()\n this.trigger(Events.PLAYBACK_SETTINGSUPDATE, this.name)\n }\n\n if (livePlayback && !this._dvrEnabled)\n position = duration\n\n this.trigger(Events.PLAYBACK_TIMEUPDATE, { current: position, total: duration }, this.name)\n }\n\n play() {\n this.trigger(Events.PLAYBACK_PLAY_INTENT)\n if (this._currentState === 'PAUSED')\n this.el.playerResume()\n else if (!this._srcLoaded && this._currentState !== 'PLAYING')\n this._firstPlay()\n else\n this.el.playerPlay()\n\n }\n\n getPlaybackType() {\n return this._playbackType? this._playbackType: null\n }\n\n getCurrentTime() {\n return this.el.getPosition()\n }\n\n getCurrentLevelIndex() {\n return this._currentLevel\n }\n\n getCurrentLevel() {\n return this.levels[this.currentLevel]\n }\n\n getCurrentBitrate() {\n return this.levels[this.currentLevel].bitrate\n }\n\n setCurrentLevel(level) {\n this.currentLevel = level\n }\n\n isHighDefinitionInUse() {\n return this.highDefinition\n }\n\n getLevels() {\n return this.levels\n }\n\n _setPlaybackState(state) {\n if (['PLAYING_BUFFERING', 'PAUSED_BUFFERING'].indexOf(state) >= 0) {\n this._bufferingState = true\n this.trigger(Events.PLAYBACK_BUFFERING, this.name)\n this._updateCurrentState(state)\n } else if (['PLAYING', 'PAUSED'].indexOf(state) >= 0) {\n if (['PLAYING_BUFFERING', 'PAUSED_BUFFERING', 'IDLE'].indexOf(this._currentState) >= 0) {\n this._bufferingState = false\n this.trigger(Events.PLAYBACK_BUFFERFULL, this.name)\n }\n this._updateCurrentState(state)\n } else if (state === 'IDLE') {\n this._srcLoaded = false\n if (this._loop && ['PLAYING_BUFFERING', 'PLAYING'].indexOf(this._currentState) >= 0) {\n this.play()\n this.seek(0)\n } else {\n this._updateCurrentState(state)\n this._hasEnded = true\n this.trigger(Events.PLAYBACK_TIMEUPDATE, { current: 0, total: this.getDuration() }, this.name)\n this.trigger(Events.PLAYBACK_ENDED, this.name)\n }\n }\n }\n\n _updateCurrentState(state) {\n this._currentState = state\n if (state !== 'IDLE')\n this._hasEnded = false\n\n this._updatePlaybackType()\n if (state === 'PLAYING')\n this.trigger(Events.PLAYBACK_PLAY, this.name)\n else if (state === 'PAUSED')\n this.trigger(Events.PLAYBACK_PAUSE, this.name)\n\n }\n\n _updatePlaybackType() {\n this._playbackType = this.el.getType()\n if (this._playbackType) {\n this._playbackType = this._playbackType.toLowerCase()\n if (this._playbackType === Playback.VOD)\n this._startReportingProgress()\n else\n this._stopReportingProgress()\n\n }\n this.trigger(Events.PLAYBACK_PLAYBACKSTATE, { type: this._playbackType })\n }\n\n _startReportingProgress() {\n if (!this._reportingProgress)\n this._reportingProgress = true\n\n }\n\n _stopReportingProgress() {\n this._reportingProgress = false\n }\n\n _onFragmentLoaded(loadmetrics) {\n this.trigger(Events.PLAYBACK_FRAGMENT_LOADED, loadmetrics)\n if (this._reportingProgress && this.getCurrentTime()) {\n const buffered = this.getCurrentTime() + this.el.getbufferLength()\n this.trigger(Events.PLAYBACK_PROGRESS, {\n start: this.getCurrentTime(),\n current: buffered,\n total: this.el.getDuration()\n })\n }\n }\n\n _onLevelEndlist() {\n this._updatePlaybackType()\n }\n\n _firstPlay() {\n this._shouldPlayOnManifestLoaded = true\n if (this.el.playerLoad) {\n Mediator.once(this.cid + ':manifestloaded', (duration, loadmetrics) => this._manifestLoaded(duration, loadmetrics))\n this._setFlashSettings() //ensure flushLiveURLCache will work (#327)\n this.el.playerLoad(this._src)\n this._srcLoaded = true\n }\n }\n\n volume(value) {\n if (this.isReady)\n this.el.playerVolume(value)\n else\n this.listenToOnce(this, Events.PLAYBACK_BUFFERFULL, () => this.volume(value))\n\n }\n\n pause() {\n if (this._playbackType !== Playback.LIVE || this._dvrEnabled) {\n this.el.playerPause()\n if (this._playbackType === Playback.LIVE && this._dvrEnabled)\n this._updateDvr(true)\n\n }\n }\n\n stop() {\n this._srcLoaded = false\n this.el.playerStop()\n this.trigger(Events.PLAYBACK_STOP)\n this.trigger(Events.PLAYBACK_TIMEUPDATE, { current: 0, total: 0 }, this.name)\n }\n\n isPlaying() {\n if (this._currentState)\n return !!(this._currentState.match(/playing/i))\n\n return false\n }\n\n get isReady() {\n return this._isReadyState\n }\n\n getDuration() {\n return this._normalizeDuration(this.el.getDuration())\n }\n\n _normalizeDuration(duration) {\n if (this._playbackType === Playback.LIVE) {\n // estimate 10 seconds of buffer time for live streams for seek positions\n duration = Math.max(0, duration - 10)\n }\n return duration\n }\n\n seekPercentage(percentage) {\n const duration = this.el.getDuration()\n let time = 0\n if (percentage > 0)\n time = duration * percentage / 100\n\n this.seek(time)\n }\n\n seek(time) {\n const duration = this.getDuration()\n if (this._playbackType === Playback.LIVE) {\n // seek operations to a time within 3 seconds from live stream will position playhead back to live\n const dvrInUse = duration - time > 3\n this._updateDvr(dvrInUse)\n }\n this.el.playerSeek(time)\n this.trigger(Events.PLAYBACK_TIMEUPDATE, { current: time, total: duration }, this.name)\n }\n\n _updateDvr(dvrInUse) {\n const previousDvrInUse = !!this._dvrInUse\n this._dvrInUse = dvrInUse\n if (this._dvrInUse !== previousDvrInUse) {\n this._updateSettings()\n this.trigger(Events.PLAYBACK_DVR, this._dvrInUse)\n this.trigger(Events.PLAYBACK_STATS_ADD, { 'dvr': this._dvrInUse })\n }\n }\n\n _flashPlaybackError(code, url, message) {\n const error = {\n code,\n description: message,\n level: PlayerError.Levels.FATAL,\n raw: { code, url, message },\n }\n const formattedError = this.createError(error)\n this.trigger(Events.PLAYBACK_ERROR, formattedError)\n this.trigger(Events.PLAYBACK_STOP)\n }\n\n _manifestLoaded(duration, loadmetrics) {\n if (this._shouldPlayOnManifestLoaded) {\n this._shouldPlayOnManifestLoaded = false\n // this method initialises the player (and starts playback)\n // this needs to happen before PLAYBACK_LOADEDMETADATA is fired\n // as the user may call seek() in a LOADEDMETADATA listener.\n /// when playerPlay() is called the player seeks to 0\n this.el.playerPlay()\n }\n\n this._fillLevels()\n this.trigger(Events.PLAYBACK_LOADEDMETADATA, { duration: duration, data: loadmetrics })\n }\n\n _fillLevels() {\n const levels = this.el.getLevels()\n const levelsLength = levels.length\n this._levels = []\n\n for (let index = 0 ; index < levelsLength ; index++)\n this._levels.push({ id: index, label: `${levels[index].height}p`, level: levels[index] })\n\n this.trigger(Events.PLAYBACK_LEVELS_AVAILABLE, this._levels)\n }\n\n destroy() {\n this.stopListening()\n this.$el.remove()\n }\n\n _updateSettings() {\n this.settings = $.extend({}, this._defaultSettings)\n if (this._playbackType === Playback.VOD || this._dvrInUse) {\n this.settings.left = ['playpause', 'position', 'duration']\n this.settings.seekEnabled = true\n } else if (this._dvrEnabled) {\n this.settings.left = ['playpause']\n this.settings.seekEnabled = true\n } else { this.settings.seekEnabled = false }\n\n }\n\n get dvrEnabled() {\n return !!this._dvrEnabled\n }\n\n _createCallbacks() {\n if (!window.Clappr)\n window.Clappr = {}\n\n if (!window.Clappr.flashlsCallbacks)\n window.Clappr.flashlsCallbacks = {}\n\n this.flashlsEvents = new HLSEvents(this.cid)\n window.Clappr.flashlsCallbacks[this.cid] = (eventName, args) => {\n this.flashlsEvents[eventName].apply(this.flashlsEvents, args)\n }\n }\n\n render() {\n super.render()\n this._createCallbacks()\n return this\n }\n}\n\nFlasHLS.canPlay = function(resource, mimeType) {\n const resourceParts = resource.split('?')[0].match(/.*\\.(.*)$/) || []\n return Browser.hasFlash &&\n ((resourceParts.length > 1 && resourceParts[1].toLowerCase() === 'm3u8') ||\n mimeType === 'application/x-mpegURL' || mimeType === 'application/vnd.apple.mpegurl')\n}\n","import Mediator from '../../components/mediator'\n\nexport default class HLSEvents {\n constructor(instanceId) {\n this.instanceId = instanceId\n }\n ready() {\n Mediator.trigger(`${this.instanceId}:flashready`)\n }\n videoSize(width, height) {\n Mediator.trigger(`${this.instanceId}:videosizechanged`, width, height)\n }\n complete() {\n Mediator.trigger(`${this.instanceId}:complete`)\n }\n error(code, url, message) {\n Mediator.trigger(`${this.instanceId}:error`, code, url, message)\n }\n manifest(duration, loadmetrics) {\n Mediator.trigger(`${this.instanceId}:manifestloaded`, duration, loadmetrics)\n }\n audioLevelLoaded(loadmetrics) {\n Mediator.trigger(`${this.instanceId}:audiolevelloaded`, loadmetrics)\n }\n levelLoaded(loadmetrics) {\n Mediator.trigger(`${this.instanceId}:levelloaded`, loadmetrics)\n }\n levelEndlist(level) {\n Mediator.trigger(`${this.instanceId}:levelendlist`, level)\n }\n fragmentLoaded(loadmetrics) {\n Mediator.trigger(`${this.instanceId}:fragmentloaded`, loadmetrics)\n }\n fragmentPlaying(playmetrics) {\n Mediator.trigger(`${this.instanceId}:fragmentplaying`, playmetrics)\n }\n position(timemetrics) {\n Mediator.trigger(`${this.instanceId}:timeupdate`, timemetrics)\n }\n state(newState) {\n Mediator.trigger(`${this.instanceId}:playbackstate`, newState)\n }\n seekState(newState) {\n Mediator.trigger(`${this.instanceId}:seekstate`, newState)\n }\n switch(newLevel) {\n Mediator.trigger(`${this.instanceId}:levelchanged`, newLevel)\n }\n audioTracksListChange(trackList) {\n Mediator.trigger(`${this.instanceId}:audiotracklistchanged`, trackList)\n }\n audioTrackChange(trackId) {\n Mediator.trigger(`${this.instanceId}:audiotrackchanged`, trackId)\n }\n}\n\n","import FlasHLS from './flashls'\nexport default FlasHLS\n","module.exports = \"<%=baseUrl%>/8fa12a459188502b9f0d39b8a67d9e6c.swf\";","// Copyright 2014 Globo.com Player authors. All rights reserved.\n// Use of this source code is governed by a BSD-style\n// license that can be found in the LICENSE file.\n\nimport HTML5VideoPlayback from '../../playbacks/html5_video'\nimport HLSJS from 'hls.js'\nimport Events from '../../base/events'\nimport Playback from '../../base/playback'\nimport { now, assign, listContainsIgnoreCase } from '../../base/utils'\nimport Log from '../../plugins/log'\nimport PlayerError from '../../components/error'\n\nconst AUTO = -1\n\nexport default class HLS extends HTML5VideoPlayback {\n get name() { return 'hls' }\n\n get levels() { return this._levels || [] }\n\n get currentLevel() {\n if (this._currentLevel === null || this._currentLevel === undefined)\n return AUTO\n else\n return this._currentLevel //0 is a valid level ID\n\n }\n\n get isReady() {\n return this._isReadyState\n }\n\n set currentLevel(id) {\n this._currentLevel = id\n this.trigger(Events.PLAYBACK_LEVEL_SWITCH_START)\n if (this.options.playback.hlsUseNextLevel)\n this._hls.nextLevel = this._currentLevel\n else\n this._hls.currentLevel = this._currentLevel\n }\n\n get _startTime() {\n if (this._playbackType === Playback.LIVE && this._playlistType !== 'EVENT')\n return this._extrapolatedStartTime\n\n return this._playableRegionStartTime\n }\n\n get _now() {\n return now()\n }\n\n // the time in the video element which should represent the start of the sliding window\n // extrapolated to increase in real time (instead of jumping as the early segments are removed)\n get _extrapolatedStartTime() {\n if (!this._localStartTimeCorrelation)\n return this._playableRegionStartTime\n\n let corr = this._localStartTimeCorrelation\n let timePassed = this._now - corr.local\n let extrapolatedWindowStartTime = (corr.remote + timePassed) / 1000\n // cap at the end of the extrapolated window duration\n return Math.min(extrapolatedWindowStartTime, this._playableRegionStartTime + this._extrapolatedWindowDuration)\n }\n\n // the time in the video element which should represent the end of the content\n // extrapolated to increase in real time (instead of jumping as segments are added)\n get _extrapolatedEndTime() {\n let actualEndTime = this._playableRegionStartTime + this._playableRegionDuration\n if (!this._localEndTimeCorrelation)\n return actualEndTime\n\n let corr = this._localEndTimeCorrelation\n let timePassed = this._now - corr.local\n let extrapolatedEndTime = (corr.remote + timePassed) / 1000\n return Math.max(actualEndTime - this._extrapolatedWindowDuration, Math.min(extrapolatedEndTime, actualEndTime))\n }\n\n get _duration() {\n return this._extrapolatedEndTime - this._startTime\n }\n\n // Returns the duration (seconds) of the window that the extrapolated start time is allowed\n // to move in before being capped.\n // The extrapolated start time should never reach the cap at the end of the window as the\n // window should slide as chunks are removed from the start.\n // This also applies to the extrapolated end time in the same way.\n //\n // If chunks aren't being removed for some reason that the start time will reach and remain fixed at\n // playableRegionStartTime + extrapolatedWindowDuration\n //\n // <-- window duration -->\n // I.e playableRegionStartTime |-----------------------|\n // | --> . . .\n // . --> | --> . .\n // . . --> | --> .\n // . . . --> |\n // . . . .\n // extrapolatedStartTime\n get _extrapolatedWindowDuration() {\n if (this._segmentTargetDuration === null)\n return 0\n\n return this._extrapolatedWindowNumSegments * this._segmentTargetDuration\n }\n\n static get HLSJS() {\n return HLSJS\n }\n\n constructor(...args) {\n super(...args)\n // backwards compatibility (TODO: remove on 0.3.0)\n this.options.playback = { ...this.options, ...this.options.playback }\n this._minDvrSize = typeof (this.options.hlsMinimumDvrSize) === 'undefined' ? 60 : this.options.hlsMinimumDvrSize\n // The size of the start time extrapolation window measured as a multiple of segments.\n // Should be 2 or higher, or 0 to disable. Should only need to be increased above 2 if more than one segment is\n // removed from the start of the playlist at a time. E.g if the playlist is cached for 10 seconds and new chunks are\n // added/removed every 5.\n this._extrapolatedWindowNumSegments = !this.options.playback || typeof (this.options.playback.extrapolatedWindowNumSegments) === 'undefined' ? 2 : this.options.playback.extrapolatedWindowNumSegments\n\n this._playbackType = Playback.VOD\n this._lastTimeUpdate = { current: 0, total: 0 }\n this._lastDuration = null\n // for hls streams which have dvr with a sliding window,\n // the content at the start of the playlist is removed as new\n // content is appended at the end.\n // this means the actual playable start time will increase as the\n // start content is deleted\n // For streams with dvr where the entire recording is kept from the\n // beginning this should stay as 0\n this._playableRegionStartTime = 0\n // {local, remote} remote is the time in the video element that should represent 0\n // local is the system time when the 'remote' measurment took place\n this._localStartTimeCorrelation = null\n // {local, remote} remote is the time in the video element that should represents the end\n // local is the system time when the 'remote' measurment took place\n this._localEndTimeCorrelation = null\n // if content is removed from the beginning then this empty area should\n // be ignored. \"playableRegionDuration\" excludes the empty area\n this._playableRegionDuration = 0\n // #EXT-X-PROGRAM-DATE-TIME\n this._programDateTime = 0\n // true when the actual duration is longer than hlsjs's live sync point\n // when this is false playableRegionDuration will be the actual duration\n // when this is true playableRegionDuration will exclude the time after the sync point\n this._durationExcludesAfterLiveSyncPoint = false\n // #EXT-X-TARGETDURATION\n this._segmentTargetDuration = null\n // #EXT-X-PLAYLIST-TYPE\n this._playlistType = null\n this._recoverAttemptsRemaining = this.options.hlsRecoverAttempts || 16\n }\n\n _setup() {\n this._ccIsSetup = false\n this._ccTracksUpdated = false\n this._hls = new HLSJS(assign({}, this.options.playback.hlsjsConfig))\n this._hls.on(HLSJS.Events.MEDIA_ATTACHED, () => this._hls.loadSource(this.options.src))\n this._hls.on(HLSJS.Events.LEVEL_LOADED, (evt, data) => this._updatePlaybackType(evt, data))\n this._hls.on(HLSJS.Events.LEVEL_UPDATED, (evt, data) => this._onLevelUpdated(evt, data))\n this._hls.on(HLSJS.Events.LEVEL_SWITCHING, (evt,data) => this._onLevelSwitch(evt, data))\n this._hls.on(HLSJS.Events.FRAG_LOADED, (evt, data) => this._onFragmentLoaded(evt, data))\n this._hls.on(HLSJS.Events.ERROR, (evt, data) => this._onHLSJSError(evt, data))\n this._hls.on(HLSJS.Events.SUBTITLE_TRACK_LOADED, (evt, data) => this._onSubtitleLoaded(evt, data))\n this._hls.on(HLSJS.Events.SUBTITLE_TRACKS_UPDATED, () => this._ccTracksUpdated = true)\n this._hls.attachMedia(this.el)\n }\n\n render() {\n this._ready()\n return super.render()\n }\n\n _ready() {\n this._isReadyState = true\n this.trigger(Events.PLAYBACK_READY, this.name)\n }\n\n _recover(evt, data, error) {\n if (!this._recoveredDecodingError) {\n this._recoveredDecodingError = true\n this._hls.recoverMediaError()\n } else if (!this._recoveredAudioCodecError) {\n this._recoveredAudioCodecError = true\n this._hls.swapAudioCodec()\n this._hls.recoverMediaError()\n } else {\n Log.error('hlsjs: failed to recover', { evt, data })\n error.level = PlayerError.Levels.FATAL\n const formattedError = this.createError(error)\n this.trigger(Events.PLAYBACK_ERROR, formattedError)\n this.stop()\n }\n }\n\n // override\n _setupSrc(srcUrl) { // eslint-disable-line no-unused-vars\n // this playback manages the src on the video element itself\n }\n\n _startTimeUpdateTimer() {\n if (this._timeUpdateTimer) return\n\n this._timeUpdateTimer = setInterval(() => {\n this._onDurationChange()\n this._onTimeUpdate()\n }, 100)\n }\n\n _stopTimeUpdateTimer() {\n if (!this._timeUpdateTimer) return\n\n clearInterval(this._timeUpdateTimer)\n this._timeUpdateTimer = null\n }\n\n getProgramDateTime() {\n return this._programDateTime\n }\n // the duration on the video element itself should not be used\n // as this does not necesarily represent the duration of the stream\n // https://github.com/clappr/clappr/issues/668#issuecomment-157036678\n getDuration() {\n return this._duration\n }\n\n getCurrentTime() {\n // e.g. can be < 0 if user pauses near the start\n // eventually they will then be kicked to the end by hlsjs if they run out of buffer\n // before the official start time\n return Math.max(0, this.el.currentTime - this._startTime)\n }\n\n // the time that \"0\" now represents relative to when playback started\n // for a stream with a sliding window this will increase as content is\n // removed from the beginning\n getStartTimeOffset() {\n return this._startTime\n }\n\n seekPercentage(percentage) {\n let seekTo = this._duration\n if (percentage > 0)\n seekTo = this._duration * (percentage / 100)\n\n this.seek(seekTo)\n }\n\n seek(time) {\n if (time < 0) {\n Log.warn('Attempt to seek to a negative time. Resetting to live point. Use seekToLivePoint() to seek to the live point.')\n time = this.getDuration()\n }\n // assume live if time within 3 seconds of end of stream\n this.dvrEnabled && this._updateDvr(time < this.getDuration()-3)\n time += this._startTime\n super.seek(time)\n }\n\n seekToLivePoint() {\n this.seek(this.getDuration())\n }\n\n _updateDvr(status) {\n this.trigger(Events.PLAYBACK_DVR, status)\n this.trigger(Events.PLAYBACK_STATS_ADD, { 'dvr': status })\n }\n\n _updateSettings() {\n if (this._playbackType === Playback.VOD)\n this.settings.left = ['playpause', 'position', 'duration']\n else if (this.dvrEnabled)\n this.settings.left = ['playpause']\n else\n this.settings.left = ['playstop']\n\n this.settings.seekEnabled = this.isSeekEnabled()\n this.trigger(Events.PLAYBACK_SETTINGSUPDATE)\n }\n\n _onHLSJSError(evt, data) {\n const error = {\n code: `${data.type}_${data.details}`,\n description: `${this.name} error: type: ${data.type}, details: ${data.details}`,\n raw: data,\n }\n let formattedError\n if (data.response) error.description += `, response: ${JSON.stringify(data.response)}`\n // only report/handle errors if they are fatal\n // hlsjs should automatically handle non fatal errors\n if (data.fatal) {\n if (this._recoverAttemptsRemaining > 0) {\n this._recoverAttemptsRemaining -= 1\n switch (data.type) {\n case HLSJS.ErrorTypes.NETWORK_ERROR:\n switch (data.details) {\n // The following network errors cannot be recovered with HLS.startLoad()\n // For more details, see https://github.com/video-dev/hls.js/blob/master/doc/design.md#error-detection-and-handling\n // For \"level load\" fatal errors, see https://github.com/video-dev/hls.js/issues/1138\n case HLSJS.ErrorDetails.MANIFEST_LOAD_ERROR:\n case HLSJS.ErrorDetails.MANIFEST_LOAD_TIMEOUT:\n case HLSJS.ErrorDetails.MANIFEST_PARSING_ERROR:\n case HLSJS.ErrorDetails.LEVEL_LOAD_ERROR:\n case HLSJS.ErrorDetails.LEVEL_LOAD_TIMEOUT:\n Log.error('hlsjs: unrecoverable network fatal error.', { evt, data })\n formattedError = this.createError(error)\n this.trigger(Events.PLAYBACK_ERROR, formattedError)\n this.stop()\n break\n default:\n Log.warn('hlsjs: trying to recover from network error.', { evt, data })\n error.level = PlayerError.Levels.WARN\n this.createError(error)\n this._hls.startLoad()\n break\n }\n break\n case HLSJS.ErrorTypes.MEDIA_ERROR:\n Log.warn('hlsjs: trying to recover from media error.', { evt, data })\n error.level = PlayerError.Levels.WARN\n this.createError(error)\n this._recover(evt, data, error)\n break\n default:\n Log.error('hlsjs: could not recover from error.', { evt, data })\n formattedError = this.createError(error)\n this.trigger(Events.PLAYBACK_ERROR, formattedError)\n this.stop()\n break\n }\n } else {\n Log.error('hlsjs: could not recover from error after maximum number of attempts.', { evt, data })\n formattedError = this.createError(error)\n this.trigger(Events.PLAYBACK_ERROR, formattedError)\n this.stop()\n }\n } else {\n // Transforms HLSJS.ErrorDetails.KEY_LOAD_ERROR non-fatal error to\n // playback fatal error if triggerFatalErrorOnResourceDenied playback\n // option is set. HLSJS.ErrorTypes.KEY_SYSTEM_ERROR are fatal errors\n // and therefore already handled.\n if (this.options.playback.triggerFatalErrorOnResourceDenied && this._keyIsDenied(data)) {\n Log.error('hlsjs: could not load decrypt key.', { evt, data })\n formattedError = this.createError(error)\n this.trigger(Events.PLAYBACK_ERROR, formattedError)\n this.stop()\n return\n }\n\n error.level = PlayerError.Levels.WARN\n this.createError(error)\n Log.warn('hlsjs: non-fatal error occurred', { evt, data })\n }\n }\n\n _keyIsDenied(data) {\n return data.type === HLSJS.ErrorTypes.NETWORK_ERROR\n && data.details === HLSJS.ErrorDetails.KEY_LOAD_ERROR\n && data.response\n && data.response.code >= 400\n }\n\n _onTimeUpdate() {\n let update = { current: this.getCurrentTime(), total: this.getDuration(), firstFragDateTime: this.getProgramDateTime() }\n let isSame = this._lastTimeUpdate && (\n update.current === this._lastTimeUpdate.current &&\n update.total === this._lastTimeUpdate.total)\n if (isSame)\n return\n\n this._lastTimeUpdate = update\n this.trigger(Events.PLAYBACK_TIMEUPDATE, update, this.name)\n }\n\n _onDurationChange() {\n let duration = this.getDuration()\n if (this._lastDuration === duration)\n return\n\n this._lastDuration = duration\n super._onDurationChange()\n }\n\n _onProgress() {\n if (!this.el.buffered.length)\n return\n\n let buffered = []\n let bufferedPos = 0\n for (let i = 0; i < this.el.buffered.length; i++) {\n buffered = [...buffered, {\n // for a stream with sliding window dvr something that is buffered my slide off the start of the timeline\n start: Math.max(0, this.el.buffered.start(i) - this._playableRegionStartTime),\n end: Math.max(0, this.el.buffered.end(i) - this._playableRegionStartTime)\n }]\n if (this.el.currentTime >= buffered[i].start && this.el.currentTime <= buffered[i].end)\n bufferedPos = i\n\n }\n const progress = {\n start: buffered[bufferedPos].start,\n current: buffered[bufferedPos].end,\n total: this.getDuration()\n }\n this.trigger(Events.PLAYBACK_PROGRESS, progress, buffered)\n }\n\n play() {\n if (!this._hls)\n this._setup()\n\n super.play()\n this._startTimeUpdateTimer()\n }\n\n pause() {\n if (!this._hls)\n return\n\n super.pause()\n if (this.dvrEnabled)\n this._updateDvr(true)\n\n }\n\n stop() {\n this._stopTimeUpdateTimer()\n if (this._hls) {\n super.stop()\n this._hls.destroy()\n delete this._hls\n }\n }\n\n destroy() {\n this._stopTimeUpdateTimer()\n if (this._hls) {\n this._hls.destroy()\n delete this._hls\n }\n super.destroy()\n }\n\n _updatePlaybackType(evt, data) {\n this._playbackType = data.details.live ? Playback.LIVE : Playback.VOD\n this._onLevelUpdated(evt, data)\n\n // Live stream subtitle tracks detection hack (may not immediately available)\n if (this._ccTracksUpdated && this._playbackType === Playback.LIVE && this.hasClosedCaptionsTracks)\n this._onSubtitleLoaded()\n\n }\n\n _fillLevels() {\n this._levels = this._hls.levels.map((level, index) => {\n return { id: index, level: level, label: `${level.bitrate/1000}Kbps` }\n })\n this.trigger(Events.PLAYBACK_LEVELS_AVAILABLE, this._levels)\n }\n\n _onLevelUpdated(evt, data) {\n this._segmentTargetDuration = data.details.targetduration\n this._playlistType = data.details.type || null\n\n let startTimeChanged = false\n let durationChanged = false\n let fragments = data.details.fragments\n let previousPlayableRegionStartTime = this._playableRegionStartTime\n let previousPlayableRegionDuration = this._playableRegionDuration\n\n if (fragments.length === 0)\n return\n\n\n // #EXT-X-PROGRAM-DATE-TIME\n if (fragments[0].rawProgramDateTime)\n this._programDateTime = fragments[0].rawProgramDateTime\n\n\n if (this._playableRegionStartTime !== fragments[0].start) {\n startTimeChanged = true\n this._playableRegionStartTime = fragments[0].start\n }\n\n if (startTimeChanged) {\n if (!this._localStartTimeCorrelation) {\n // set the correlation to map to middle of the extrapolation window\n this._localStartTimeCorrelation = {\n local: this._now,\n remote: (fragments[0].start + (this._extrapolatedWindowDuration/2)) * 1000\n }\n } else {\n // check if the correlation still works\n let corr = this._localStartTimeCorrelation\n let timePassed = this._now - corr.local\n // this should point to a time within the extrapolation window\n let startTime = (corr.remote + timePassed) / 1000\n if (startTime < fragments[0].start) {\n // our start time is now earlier than the first chunk\n // (maybe the chunk was removed early)\n // reset correlation so that it sits at the beginning of the first available chunk\n this._localStartTimeCorrelation = {\n local: this._now,\n remote: fragments[0].start * 1000\n }\n } else if (startTime > previousPlayableRegionStartTime + this._extrapolatedWindowDuration) {\n // start time was past the end of the old extrapolation window (so would have been capped)\n // see if now that time would be inside the window, and if it would be set the correlation\n // so that it resumes from the time it was at at the end of the old window\n // update the correlation so that the time starts counting again from the value it's on now\n this._localStartTimeCorrelation = {\n local: this._now,\n remote: Math.max(fragments[0].start, previousPlayableRegionStartTime + this._extrapolatedWindowDuration) * 1000\n }\n }\n }\n }\n\n let newDuration = data.details.totalduration\n // if it's a live stream then shorten the duration to remove access\n // to the area after hlsjs's live sync point\n // seeks to areas after this point sometimes have issues\n if (this._playbackType === Playback.LIVE) {\n let fragmentTargetDuration = data.details.targetduration\n let hlsjsConfig = this.options.playback.hlsjsConfig || {}\n let liveSyncDurationCount = hlsjsConfig.liveSyncDurationCount || HLSJS.DefaultConfig.liveSyncDurationCount\n let hiddenAreaDuration = fragmentTargetDuration * liveSyncDurationCount\n if (hiddenAreaDuration <= newDuration) {\n newDuration -= hiddenAreaDuration\n this._durationExcludesAfterLiveSyncPoint = true\n } else { this._durationExcludesAfterLiveSyncPoint = false }\n\n }\n\n if (newDuration !== this._playableRegionDuration) {\n durationChanged = true\n this._playableRegionDuration = newDuration\n }\n\n // Note the end time is not the playableRegionDuration\n // The end time will always increase even if content is removed from the beginning\n let endTime = fragments[0].start + newDuration\n let previousEndTime = previousPlayableRegionStartTime + previousPlayableRegionDuration\n let endTimeChanged = endTime !== previousEndTime\n if (endTimeChanged) {\n if (!this._localEndTimeCorrelation) {\n // set the correlation to map to the end\n this._localEndTimeCorrelation = {\n local: this._now,\n remote: endTime * 1000\n }\n } else {\n // check if the correlation still works\n let corr = this._localEndTimeCorrelation\n let timePassed = this._now - corr.local\n // this should point to a time within the extrapolation window from the end\n let extrapolatedEndTime = (corr.remote + timePassed) / 1000\n if (extrapolatedEndTime > endTime) {\n this._localEndTimeCorrelation = {\n local: this._now,\n remote: endTime * 1000\n }\n } else if (extrapolatedEndTime < endTime - this._extrapolatedWindowDuration) {\n // our extrapolated end time is now earlier than the extrapolation window from the actual end time\n // (maybe a chunk became available early)\n // reset correlation so that it sits at the beginning of the extrapolation window from the end time\n this._localEndTimeCorrelation = {\n local: this._now,\n remote: (endTime - this._extrapolatedWindowDuration) * 1000\n }\n } else if (extrapolatedEndTime > previousEndTime) {\n // end time was past the old end time (so would have been capped)\n // set the correlation so that it resumes from the time it was at at the end of the old window\n this._localEndTimeCorrelation = {\n local: this._now,\n remote: previousEndTime * 1000\n }\n }\n }\n }\n\n // now that the values have been updated call any methods that use on them so they get the updated values\n // immediately\n durationChanged && this._onDurationChange()\n startTimeChanged && this._onProgress()\n }\n\n _onFragmentLoaded(evt, data) {\n this.trigger(Events.PLAYBACK_FRAGMENT_LOADED, data)\n }\n\n _onSubtitleLoaded() {\n // This event may be triggered multiple times\n // Setup CC only once (disable CC by default)\n if (!this._ccIsSetup) {\n this.trigger(Events.PLAYBACK_SUBTITLE_AVAILABLE)\n const trackId = this._playbackType === Playback.LIVE ? -1 : this.closedCaptionsTrackId\n this.closedCaptionsTrackId = trackId\n this._ccIsSetup = true\n }\n }\n\n _onLevelSwitch(evt, data) {\n if (!this.levels.length)\n this._fillLevels()\n\n this.trigger(Events.PLAYBACK_LEVEL_SWITCH_END)\n this.trigger(Events.PLAYBACK_LEVEL_SWITCH, data)\n let currentLevel = this._hls.levels[data.level]\n if (currentLevel) {\n // TODO should highDefinition be private and maybe have a read only accessor if it's used somewhere\n this.highDefinition = (currentLevel.height >= 720 || (currentLevel.bitrate / 1000) >= 2000)\n this.trigger(Events.PLAYBACK_HIGHDEFINITIONUPDATE, this.highDefinition)\n this.trigger(Events.PLAYBACK_BITRATE, {\n height: currentLevel.height,\n width: currentLevel.width,\n bandwidth: currentLevel.bitrate,\n bitrate: currentLevel.bitrate,\n level: data.level\n })\n }\n }\n\n get dvrEnabled() {\n // enabled when:\n // - the duration does not include content after hlsjs's live sync point\n // - the playable region duration is longer than the configured duration to enable dvr after\n // - the playback type is LIVE.\n return (this._durationExcludesAfterLiveSyncPoint && this._duration >= this._minDvrSize && this.getPlaybackType() === Playback.LIVE)\n }\n\n getPlaybackType() {\n return this._playbackType\n }\n\n isSeekEnabled() {\n return (this._playbackType === Playback.VOD || this.dvrEnabled)\n }\n}\n\nHLS.canPlay = function(resource, mimeType) {\n const resourceParts = resource.split('?')[0].match(/.*\\.(.*)$/) || []\n const isHls = ((resourceParts.length > 1 && resourceParts[1].toLowerCase() === 'm3u8') || listContainsIgnoreCase(mimeType, ['application/vnd.apple.mpegurl', 'application/x-mpegURL']))\n\n return !!(HLSJS.isSupported() && isHls)\n}\n","import HLS from './hls'\nexport default HLS\n","// Copyright 2014 Globo.com Player authors. All rights reserved.\n// Use of this source code is governed by a BSD-style\n// license that can be found in the LICENSE file.\n\nimport Events from '../../base/events'\nimport Playback from '../../base/playback'\nimport HTML5Video from '../../playbacks/html5_video'\n\n// TODO: remove this playback and change HTML5Video to HTML5Playback (breaking change, only after 0.3.0)\nexport default class HTML5Audio extends HTML5Video {\n get name() { return 'html5_audio' }\n get tagName() { return 'audio' }\n\n get isAudioOnly() {\n return true\n }\n\n updateSettings() {\n this.settings.left = ['playpause', 'position', 'duration']\n this.settings.seekEnabled = this.isSeekEnabled()\n this.trigger(Events.PLAYBACK_SETTINGSUPDATE)\n }\n\n getPlaybackType() {\n return Playback.AOD\n }\n}\n\nHTML5Audio.canPlay = function(resourceUrl, mimeType) {\n const mimetypes = {\n 'wav': ['audio/wav'],\n 'mp3': ['audio/mp3', 'audio/mpeg;codecs=\"mp3\"'],\n 'aac': ['audio/mp4;codecs=\"mp4a.40.5\"'],\n 'oga': ['audio/ogg']\n }\n return HTML5Video._canPlay('audio', mimetypes, resourceUrl, mimeType)\n}\n","import HTML5Audio from './html5_audio'\nexport default HTML5Audio\n","// Copyright 2014 Globo.com Player authors. All rights reserved.\n// Use of this source code is governed by a BSD-style\n// license that can be found in the LICENSE file.\n\nimport { isNumber, seekStringToSeconds, DomRecycler, canAutoPlayMedia } from '../../base/utils'\n\nimport Playback from '../../base/playback'\nimport Browser from '../../components/browser'\nimport PlayerError from '../../components/error'\nimport Events from '../../base/events'\nimport Log from '../../plugins/log'\nimport $ from 'clappr-zepto'\nimport template from '../../base/template'\nimport tracksHTML from './public/tracks.html'\nimport './public/style.scss'\n\nconst MIMETYPES = {\n 'mp4': ['avc1.42E01E', 'avc1.58A01E', 'avc1.4D401E', 'avc1.64001E', 'mp4v.20.8', 'mp4v.20.240', 'mp4a.40.2'].map(\n (codec) => { return 'video/mp4; codecs=\"' + codec + ', mp4a.40.2\"' }),\n 'ogg': ['video/ogg; codecs=\"theora, vorbis\"', 'video/ogg; codecs=\"dirac\"', 'video/ogg; codecs=\"theora, speex\"'],\n '3gpp': ['video/3gpp; codecs=\"mp4v.20.8, samr\"'],\n 'webm': ['video/webm; codecs=\"vp8, vorbis\"'],\n 'mkv': ['video/x-matroska; codecs=\"theora, vorbis\"'],\n 'm3u8': ['application/x-mpegurl']\n}\nMIMETYPES['ogv'] = MIMETYPES['ogg']\nMIMETYPES['3gp'] = MIMETYPES['3gpp']\n\nconst AUDIO_MIMETYPES = {\n 'wav': ['audio/wav'],\n 'mp3': ['audio/mp3', 'audio/mpeg;codecs=\"mp3\"'],\n 'aac': ['audio/mp4;codecs=\"mp4a.40.5\"'],\n 'oga': ['audio/ogg']\n}\n\nconst KNOWN_AUDIO_MIMETYPES = Object.keys(AUDIO_MIMETYPES).reduce((acc, k) => [...acc, ...AUDIO_MIMETYPES[k]], [])\n\nconst UNKNOWN_ERROR = { code: 'unknown', message: 'unknown' }\n\n// TODO: rename this Playback to HTML5Playback (breaking change, only after 0.3.0)\nexport default class HTML5Video extends Playback {\n get name() { return 'html5_video' }\n get tagName() { return this.isAudioOnly ? 'audio' : 'video' }\n\n get isAudioOnly() {\n const resourceUrl = this.options.src\n let mimeTypes = HTML5Video._mimeTypesForUrl(resourceUrl, AUDIO_MIMETYPES, this.options.mimeType)\n return this.options.playback && this.options.playback.audioOnly || this.options.audioOnly || KNOWN_AUDIO_MIMETYPES.indexOf(mimeTypes[0]) >= 0\n }\n\n get attributes() {\n return {\n 'data-html5-video': ''\n }\n }\n\n get events() {\n return {\n 'canplay': '_onCanPlay',\n 'canplaythrough': '_handleBufferingEvents',\n 'durationchange': '_onDurationChange',\n 'ended': '_onEnded',\n 'error': '_onError',\n 'loadeddata': '_onLoadedData',\n 'loadedmetadata': '_onLoadedMetadata',\n 'pause': '_onPause',\n 'playing': '_onPlaying',\n 'progress': '_onProgress',\n 'seeking': '_onSeeking',\n 'seeked': '_onSeeked',\n 'stalled': '_handleBufferingEvents',\n 'timeupdate': '_onTimeUpdate',\n 'waiting': '_onWaiting'\n }\n }\n\n /**\n * Determine if the playback has ended.\n * @property ended\n * @type Boolean\n */\n get ended() {\n return this.el.ended\n }\n\n /**\n * Determine if the playback is having to buffer in order for\n * playback to be smooth.\n * This is related to the PLAYBACK_BUFFERING and PLAYBACK_BUFFERFULL events\n * @property buffering\n * @type Boolean\n */\n get buffering() {\n return this._isBuffering\n }\n\n constructor(...args) {\n super(...args)\n this._destroyed = false\n this._loadStarted = false\n this._isBuffering = false\n this._playheadMoving = false\n this._playheadMovingTimer = null\n this._stopped = false\n this._ccTrackId = -1\n this._setupSrc(this.options.src)\n // backwards compatibility (TODO: remove on 0.3.0)\n this.options.playback || (this.options.playback = this.options || {})\n this.options.playback.disableContextMenu = this.options.playback.disableContextMenu || this.options.disableVideoTagContextMenu\n\n const playbackConfig = this.options.playback\n const preload = playbackConfig.preload || (Browser.isSafari ? 'auto' : this.options.preload)\n\n let posterUrl // FIXME: poster plugin should always convert poster to object with expected properties ?\n if (this.options.poster) {\n if (typeof this.options.poster === 'string')\n posterUrl = this.options.poster\n else if (typeof this.options.poster.url === 'string')\n posterUrl = this.options.poster.url\n\n }\n\n $.extend(this.el, {\n muted: this.options.mute,\n defaultMuted: this.options.mute,\n loop: this.options.loop,\n poster: posterUrl,\n preload: preload || 'metadata',\n controls: (playbackConfig.controls || this.options.useVideoTagDefaultControls) && 'controls',\n crossOrigin: playbackConfig.crossOrigin,\n 'x-webkit-playsinline': playbackConfig.playInline\n })\n\n playbackConfig.playInline && (this.$el.attr({ playsinline: 'playsinline' }))\n playbackConfig.crossOrigin && (this.$el.attr({ crossorigin: playbackConfig.crossOrigin }))\n\n // TODO should settings be private?\n this.settings = { default: ['seekbar'] }\n this.settings.left = ['playpause', 'position', 'duration']\n this.settings.right = ['fullscreen', 'volume', 'hd-indicator']\n\n playbackConfig.externalTracks && (this._setupExternalTracks(playbackConfig.externalTracks))\n\n this.options.autoPlay && this.attemptAutoPlay()\n }\n\n configure(options) {\n super.configure(options)\n this.el.loop = !!options.loop\n }\n\n // See Playback.attemptAutoPlay()\n attemptAutoPlay() {\n this.canAutoPlay((result, error) => {\n error && Log.warn(this.name, 'autoplay error.', { result, error })\n\n // https://github.com/clappr/clappr/issues/1076\n result && process.nextTick(() => !this._destroyed && this.play())\n })\n }\n\n // See Playback.canAutoPlay()\n canAutoPlay(cb) {\n if (this.options.disableCanAutoPlay)\n cb(true, null)\n\n let opts = {\n timeout: this.options.autoPlayTimeout || 500,\n inline: this.options.playback.playInline || false,\n muted: this.options.mute || false, // Known issue: mediacontrols may asynchronously mute video\n }\n\n // Use current video element if recycling feature enabled with mobile devices\n if (Browser.isMobile && DomRecycler.options.recycleVideo)\n opts.element = this.el\n\n // Desktop browser autoplay policy may require user action\n // Mobile browser autoplay require user consent and video recycling feature enabled\n // It may returns a false positive with source-less player consent\n canAutoPlayMedia(cb, opts)\n }\n\n _setupExternalTracks(tracks) {\n this._externalTracks = tracks.map(track => {\n return {\n kind: track.kind || 'subtitles', // Default is 'subtitles'\n label: track.label,\n lang: track.lang,\n src: track.src,\n }\n })\n }\n\n /**\n * Sets the source url on the