(function (f) { if (typeof exports === 'object' && typeof module !== 'undefined') { module.exports = f() } else if (typeof define === 'function' && define.amd) { define([], f) } else { var g; if (typeof window !== 'undefined') { g = window } else if (typeof global !== 'undefined') { g = global } else if (typeof self !== 'undefined') { g = self } else { g = this }g.jwplayer_hls_provider = f() } })(function () { var define, module, exports; return (function e (t, n, r) { function s (o, u) { if (!n[o]) { if (!t[o]) { var a = typeof require === 'function' && require; if (!u && a) return a(o, !0); if (i) return i(o, !0); var f = new Error("Cannot find module '" + o + "'"); throw f.code = 'MODULE_NOT_FOUND', f } var l = n[o] = { exports: {} }; t[o][0].call(l.exports, function (e) { var n = t[o][1][e]; return s(n || e) }, l, l.exports, e, t, n, r) } return n[o].exports } var i = typeof require === 'function' && require; for (var o = 0; o < r.length; o++)s(r[o]); return s })({ 1: [function (_dereq_, module, exports) { 'use strict' var has = Object.prototype.hasOwnProperty var prefix = '~' /** * Constructor to create a storage for our `EE` objects. * An `Events` instance is a plain object whose properties are event names. * * @constructor * @api private */ function Events () {} // // We try to not inherit from `Object.prototype`. In some engines creating an // instance in this way is faster than calling `Object.create(null)` directly. // If `Object.create(null)` is not supported we prefix the event names with a // character to make sure that the built-in object properties are not // overridden or used as an attack vector. // if (Object.create) { Events.prototype = Object.create(null) // // This hack is needed because the `__proto__` property is still inherited in // some old browsers like Android 4, iPhone 5.1, Opera 11 and Safari 5. // if (!new Events().__proto__) prefix = false } /** * Representation of a single event listener. * * @param {Function} fn The listener function. * @param {Mixed} context The context to invoke the listener with. * @param {Boolean} [once=false] Specify if the listener is a one-time listener. * @constructor * @api private */ function EE (fn, context, once) { this.fn = fn this.context = context this.once = once || false } /** * Minimal `EventEmitter` interface that is molded against the Node.js * `EventEmitter` interface. * * @constructor * @api public */ function EventEmitter () { this._events = new Events() this._eventsCount = 0 } /** * Return an array listing the events for which the emitter has registered * listeners. * * @returns {Array} * @api public */ EventEmitter.prototype.eventNames = function eventNames () { var names = [] var events var name if (this._eventsCount === 0) return names for (name in (events = this._events)) { if (has.call(events, name)) names.push(prefix ? name.slice(1) : name) } if (Object.getOwnPropertySymbols) { return names.concat(Object.getOwnPropertySymbols(events)) } return names } /** * Return the listeners registered for a given event. * * @param {String|Symbol} event The event name. * @param {Boolean} exists Only check if there are listeners. * @returns {Array|Boolean} * @api public */ EventEmitter.prototype.listeners = function listeners (event, exists) { var evt = prefix ? prefix + event : event var available = this._events[evt] if (exists) return !!available if (!available) return [] if (available.fn) return [available.fn] for (var i = 0, l = available.length, ee = new Array(l); i < l; i++) { ee[i] = available[i].fn } return ee } /** * Calls each of the listeners registered for a given event. * * @param {String|Symbol} event The event name. * @returns {Boolean} `true` if the event had listeners, else `false`. * @api public */ EventEmitter.prototype.emit = function emit (event, a1, a2, a3, a4, a5) { var evt = prefix ? prefix + event : event if (!this._events[evt]) return false var listeners = this._events[evt] var len = arguments.length var args var i if (listeners.fn) { if (listeners.once) this.removeListener(event, listeners.fn, undefined, true) switch (len) { case 1: return listeners.fn.call(listeners.context), true case 2: return listeners.fn.call(listeners.context, a1), true case 3: return listeners.fn.call(listeners.context, a1, a2), true case 4: return listeners.fn.call(listeners.context, a1, a2, a3), true case 5: return listeners.fn.call(listeners.context, a1, a2, a3, a4), true case 6: return listeners.fn.call(listeners.context, a1, a2, a3, a4, a5), true } for (i = 1, args = new Array(len - 1); i < len; i++) { args[i - 1] = arguments[i] } listeners.fn.apply(listeners.context, args) } else { var length = listeners.length var j for (i = 0; i < length; i++) { if (listeners[i].once) this.removeListener(event, listeners[i].fn, undefined, true) switch (len) { case 1: listeners[i].fn.call(listeners[i].context); break case 2: listeners[i].fn.call(listeners[i].context, a1); break case 3: listeners[i].fn.call(listeners[i].context, a1, a2); break case 4: listeners[i].fn.call(listeners[i].context, a1, a2, a3); break default: if (!args) { for (j = 1, args = new Array(len - 1); j < len; j++) { args[j - 1] = arguments[j] } } listeners[i].fn.apply(listeners[i].context, args) } } } return true } /** * Add a listener for a given event. * * @param {String|Symbol} event The event name. * @param {Function} fn The listener function. * @param {Mixed} [context=this] The context to invoke the listener with. * @returns {EventEmitter} `this`. * @api public */ EventEmitter.prototype.on = function on (event, fn, context) { var listener = new EE(fn, context || this) var evt = prefix ? prefix + event : event if (!this._events[evt]) this._events[evt] = listener, this._eventsCount++ else if (!this._events[evt].fn) this._events[evt].push(listener) else this._events[evt] = [this._events[evt], listener] return this } /** * Add a one-time listener for a given event. * * @param {String|Symbol} event The event name. * @param {Function} fn The listener function. * @param {Mixed} [context=this] The context to invoke the listener with. * @returns {EventEmitter} `this`. * @api public */ EventEmitter.prototype.once = function once (event, fn, context) { var listener = new EE(fn, context || this, true) var evt = prefix ? prefix + event : event if (!this._events[evt]) this._events[evt] = listener, this._eventsCount++ else if (!this._events[evt].fn) this._events[evt].push(listener) else this._events[evt] = [this._events[evt], listener] return this } /** * Remove the listeners of a given event. * * @param {String|Symbol} event The event name. * @param {Function} fn Only remove the listeners that match this function. * @param {Mixed} context Only remove the listeners that have this context. * @param {Boolean} once Only remove one-time listeners. * @returns {EventEmitter} `this`. * @api public */ EventEmitter.prototype.removeListener = function removeListener (event, fn, context, once) { var evt = prefix ? prefix + event : event if (!this._events[evt]) return this if (!fn) { if (--this._eventsCount === 0) this._events = new Events() else delete this._events[evt] return this } var listeners = this._events[evt] if (listeners.fn) { if ( listeners.fn === fn && (!once || listeners.once) && (!context || listeners.context === context) ) { if (--this._eventsCount === 0) this._events = new Events() else delete this._events[evt] } } else { for (var i = 0, events = [], length = listeners.length; i < length; i++) { if ( listeners[i].fn !== fn || (once && !listeners[i].once) || (context && listeners[i].context !== context) ) { events.push(listeners[i]) } } // // Reset the array, or remove it completely if we have no more listeners. // if (events.length) this._events[evt] = events.length === 1 ? events[0] : events else if (--this._eventsCount === 0) this._events = new Events() else delete this._events[evt] } return this } /** * Remove all listeners, or those of the specified event. * * @param {String|Symbol} [event] The event name. * @returns {EventEmitter} `this`. * @api public */ EventEmitter.prototype.removeAllListeners = function removeAllListeners (event) { var evt if (event) { evt = prefix ? prefix + event : event if (this._events[evt]) { if (--this._eventsCount === 0) this._events = new Events() else delete this._events[evt] } } else { this._events = new Events() this._eventsCount = 0 } return this } // // Alias methods names because people roll like that. // EventEmitter.prototype.off = EventEmitter.prototype.removeListener EventEmitter.prototype.addListener = EventEmitter.prototype.on // // This function doesn't apply anymore. // EventEmitter.prototype.setMaxListeners = function setMaxListeners () { return this } // // Expose the prefix. // EventEmitter.prefixed = prefix // // Allow `EventEmitter` to be imported as module namespace. // EventEmitter.EventEmitter = EventEmitter // // Expose the module. // if (typeof module !== 'undefined') { module.exports = EventEmitter } }, {}], 2: [function (_dereq_, module, exports) { 'use strict' var EventEmitter = _dereq_('eventemitter3') var E = module.exports = HlsProv var provider_attached = false // jwplayer v8+ dropped exposing jwplayer.events var jwe = { JWPLAYER_MEDIA_BEFORECOMPLETE: 'beforeComplete', JWPLAYER_MEDIA_BUFFER: 'bufferChange', JWPLAYER_MEDIA_BUFFER_FULL: 'bufferFull', JWPLAYER_MEDIA_COMPLETE: 'complete', JWPLAYER_MEDIA_ERROR: 'mediaError', JWPLAYER_MEDIA_LEVELS: 'levels', JWPLAYER_MEDIA_LEVEL_CHANGED: 'levelsChanged', JWPLAYER_MEDIA_META: 'meta', JWPLAYER_MEDIA_SEEK: 'seek', JWPLAYER_MEDIA_SEEKED: 'seeked', JWPLAYER_MEDIA_TIME: 'time', JWPLAYER_MEDIA_TYPE: 'mediaType', JWPLAYER_PLAYER_STATE: 'state', JWPLAYER_PROVIDER_FIRST_FRAME: 'providerFirstFrame' } // XXX arik: protect against exceptions in api. currently jwplayer will be // stuck + add test function HlsProv (id) { var jwplayer = E.jwplayer || window.jwplayer; var Hls = E.Hls || window.Hls var jw = id && jwplayer(id) console.log('init hola/hls provider v' + E.VERSION + ' hls v' + Hls.version + (E.version ? ' hap v' + E.version : '')) jw.provider = this function empty_fn (name) { return function () {} } var _this = this this.hls_restore_pos = function () { var new_pos = this.hls_queued.seek var old_pos = video.currentTime if (hls.streamController.state == 'STOPPED') { // XXX pavelki: hack to use our start position hls.streamController.startPosition = 0 hls.startLoad(new_pos || 0) } if (!new_pos) { return } this._in_seek = true video.currentTime = new_pos this.trigger(jwe.JWPLAYER_MEDIA_SEEK, { position: old_pos, offset: new_pos }) // XXX pavelki: hack to override W3 algorithm of media // seeking: when video element has HAVE_NOTHING state, // seeking event doesn't fire and loading doesn't start if (!video.readyState) { video.dispatchEvent(new Event('seeking')) } this.hls_queued.seek = 0 } function check_playback_started (video) { if (video.paused) { return void hls_log('video play refused') } function rm_listeners () { video.removeEventListener('playing', listener) video.removeEventListener('pause', listener) video.removeEventListener('abort', listener) video.removeEventListener('error', listener) } function listener (e) { rm_listeners() if (e.type != 'playing') { hls_log('play() was interrupted by a "' + e.type + '" event') } } video.addEventListener('playing', listener) video.addEventListener('abort', listener) video.addEventListener('error', listener) video.addEventListener('pause', listener) } function video_play () { var promise = video.play() || check_playback_started(video) if (promise && promise.catch) { promise.catch(function (err) { hls_log('video_play failed with ' + err) console.warn(err) _this.trigger('checkAutoplayFailed', {err}) // user gesture required to start playback if (err.name == 'NotAllowedError' && video.hasAttribute('jw-gesture-required')) { _this.trigger('autoplayFailed') video.setAttribute('autoplay-failed', 'failed') } }) } else if (video.hasAttribute('jw-gesture-required')) { // autoplay isn't supported in older versions of Safari (<10) // and Chrome (<53) _this.trigger('autoplayFailed') video.setAttribute('autoplay-failed', 'failed') } } function hls_log (msg) { var dbg // if (dbg = hls_params.debug) { dbg.log(msg) } } function hls_play () { hls_log('hls_play state: ' + _this.hls_state + ' att:' + _this.attached) if (!(_this.hls_queued.play = _this.hls_state != 'ready') && _this.attached) { _this.hls_restore_pos() video_play() } } function hls_load (src) { if (!src) { return } if (_this.hls_state == 'ready') { _this.hls_state = 'idle' } if (_this.level_cb) { hls.off(Hls.Events.LEVEL_LOADED, _this.level_cb) } _this.level_cb = function () { hls_log('hls play queued on level_cb:' + _this.hls_queued.play) hls.off(Hls.Events.LEVEL_LOADED, _this.level_cb) _this.level_cb = undefined _this.hls_state = 'ready' if (_this.hls_queued.play) { hls_play() } _this.trigger(jwe.JWPLAYER_MEDIA_BUFFER_FULL) } hls.on(Hls.Events.LEVEL_LOADED, _this.level_cb) hls.loadSource(src) if (!hls.media) { _this.attachMedia() } } function on_video_src_change (video, allow_cb) { var o = video for (; o && !o.hasOwnProperty('src'); o = Object.getPrototypeOf(o)); if (!o) { return } var prop = Object.getOwnPropertyDescriptor(o, 'src') // XXX volodymyr: some browsers (e.g. safari9) may lock access to src, // solution copied from polyfill.video_source_access in html5.js if (!prop.get && !prop.set) { prop.get = function () { var src = video.getAttribute('src') return src != null ? src : '' } prop.set = function (s) { var el = document.createElement('source') el.src = s || '' // will convert relative path into absolute uri video.setAttribute('src', el.src) } } Object.defineProperty(video, 'src', { configurable: true, enumerable: false, set: function (src) { if (allow_cb(prop.get.call(video), src)) { prop.set.call(video, src) } }, get: prop.get }) } function get_default_src (sources) { return sources && sources.find(function (s) { return s.default }) || sources[0] } function _log (method, message) { if (_this.hls.holaLog && _this.hls.holaLog[method]) { _this.hls.holaLog[method].call(_this.hls.holaLog, message) } } // XXX marka: jwplayer inherits provider from DefaultProvider, so it will // override our inheritance from EventEmitter, do it manually this.events = new EventEmitter() this.addEventListener = this.on = this.events.on.bind(this.events) this.once = this.events.once.bind(this.events) this.removeEventListener = this.off = this.events.off.bind(this.events) this.trigger = this.emit = function (e) { if (!_this.attached && !_this.before_complete) { return } var args = [].slice.call(arguments) _this.events.emit.apply(this.events, args) if (e != 'all') { args.unshift('all') _this.events.emit.apply(this.events, args) } } this.removeAllListeners = function (e) { this.events.removeAllListeners(e) } this.hls_queued = { play: false, seek: 0 } this.attached = true this.hls_state = 'idle' this.is_mobile = function () { var ios; var and; var ua = navigator.userAgent if ((ios = /iP(hone|ad|od)/i.test(ua)) || (and = /Android/i.test(ua))) { return { is_ios: ios, is_android: and } } } this.supports_captions = function () { var ua = navigator.userAgent return /(iPhone|iPad|iPod|iPod touch);.*?OS/.test(ua) || / (Chrome|Version)\/\d+(\.\d+)+.* Safari\/\d+(\.\d+)+/.test(ua) || /Firefox\/(\d+(?:\.\d+)+)/.test(ua) } var element = document.getElementById(id); var container var video = element ? element.querySelector('video') : undefined; var hls var try_play; var can_play; var _is_mobile = this.is_mobile() var visual_quality = { reason: 'initial choice', mode: 'auto' } if (!video) { video = document.createElement('video') if (_is_mobile) { video.setAttribute('jw-gesture-required', '') } } video.className = 'jw-video jw-reset' // XXX marka: mark html5 element to skip autodetection of dm/hls video.hola_dm_hls_attached = true // XXX pavelki: hack to override ozee's wrong src set on_video_src_change(video, function (from, to) { return to != from + '?' }) var hls_params = E.hls_params || {}; var hola_log this.ad_count = 0 if (jw) { jw.on('captionsList', caption_track) jw.on('captionsChanged', caption_track) // XXX pavelki: counters for ad, need to make loading deferred jw.on('adImpression', function () { _this.ad_count++ }) jw.on('adComplete', function () { _this.ad_count-- }) jw.on('adSkipped', function () { _this.ad_count-- }) Object.assign(hls_params, jw.hola_config) hls_params.xhrSetup = window.customXHR hls_params.liveSyncDurationCount = 5 hls_params.fragLoadingRetryDelay = 500 hls_params.autoStartLoad = true hls_params.maxBufferLength = 300 if (hls_params.debug != 'undefined') { hola_log = hls_params.debug delete hls_params.debug } } hls_params.debug = {}; ['debug', 'info', 'log', 'warn', 'error'].forEach(function (method) { hls_params.debug[method] = _log.bind(null, method) }) this.hls = hls = new Hls(hls_params) hls.holaLog = hola_log if (jw) { jw.hls = hls } var _buffered, _duration, _position function caption_track (cc) { if (!_this.renderNatively) { return } var tracks = video.textTracks; var new_id = cc.tracks[cc.track].id for (var i = 0; i < tracks.length; i++) { tracks[i].mode = tracks[i]._id == new_id ? 'showing' : 'hidden' } } function get_seekable_end () { var i; var end; var len = video.seekable ? video.seekable.length : 0 for (end = 0, i = 0; i < len; i++) { end = Math.max(end, video.seekable.end(i)) } return end } function get_duration () { var duration = video.duration; var end = get_seekable_end() if (duration == Infinity && end) { var seekable_dur = end - video.seekable.start(0) if (seekable_dur != Infinity && seekable_dur > 120) { duration = -seekable_dur } } return duration } function get_duration_inf () { return is_live() ? 1 / 0 : get_duration() } function is_live () { var live, levels try { if (!(levels = hls.streamController.levels)) { return } var loaded_lvl = levels.find(function (lvl) { return lvl.details }) live = loaded_lvl && !!loaded_lvl.details.live } catch (e) { hls_log('is_live failed with ' + e) } return live } function get_buffered () { var buf = video.buffered; var dur = video.duration if (!buf || !buf.length || dur <= 0 || dur == Infinity) { return 0 } return Math.min(buf.end(buf.length - 1) / dur, 1.0) } function set_buffered (buffered, pos, duration) { if (buffered == _buffered && duration == _duration) { return } _buffered = buffered _this.trigger(jwe.JWPLAYER_MEDIA_BUFFER, { bufferPercent: buffered * 100, position: pos, duration: get_duration_inf() }) } function playback_complete () { _this.setState('complete') _this.trigger(jwe.JWPLAYER_MEDIA_COMPLETE) _this.before_complete = false } var video_listeners = { durationchange: function () { _duration = get_duration() set_buffered(get_buffered(), _position, _duration) }, ended: function () { if (_this.state == 'idle' || _this.state == 'complete') { return } _this.before_complete = true _this.trigger(jwe.JWPLAYER_MEDIA_BEFORECOMPLETE) if (_this.attached) { playback_complete() } }, error: function () { _this.trigger(jwe.JWPLAYER_MEDIA_ERROR, { message: 'Error loading media: File could not be played' }) }, loadstart: function () { video.setAttribute('jw-loaded', 'started') }, loadeddata: function () { video.setAttribute('jw-loaded', 'data') if (!_this.supports_captions()) { return } video.textTracks.onaddtrack = function () { _this.renderNatively = true _this.trigger('subtitlesTracks', { tracks: video.textTracks }) } // XXX pavelki: add checking of playlist if (video.textTracks.length) { video.textTracks.onaddtrack() } }, loadedmetadata: function () { if (video.muted) { video.muted = false video.muted = true } video.setAttribute('jw-loaded', 'meta') _duration = get_duration() _this.trigger(jwe.JWPLAYER_MEDIA_META, { duration: get_duration_inf(), height: video.videoHeight, width: video.videoWidth }) }, canplay: function () { can_play = true _this.trigger(jwe.JWPLAYER_MEDIA_BUFFER_FULL) }, playing: function () { _this.setState('playing') if (!video.hasAttribute('jw-played')) { video.setAttribute('jw-played', '') } if (video.hasAttribute('jw-gesture-required')) { video.removeAttribute('jw-gesture-required') video.removeAttribute('autoplay-failed') } _this.trigger(jwe.JWPLAYER_PROVIDER_FIRST_FRAME, {}) }, pause: function () { if (_this.state == 'complete' || video.currentTime == video.duration) { return } _this.setState('paused') }, seeking: function () { if (!_this._in_seek) { _this._in_seek = true _this.trigger(jwe.JWPLAYER_MEDIA_SEEK, { position: _position, offset: video.currentTime }) } }, seeked: function () { _this._in_seek = false _this.trigger(jwe.JWPLAYER_MEDIA_SEEKED) }, progress: function () { set_buffered(get_buffered(), _position, _duration) }, timeupdate: function () { _duration = get_duration() _position = _duration < 0 ? -(get_seekable_end() - video.currentTime) : video.currentTime var a = { position: _position, duration: _duration, currentTime: video.currentTime, metadata: {currentTime: video.currentTime} }; set_buffered(get_buffered(), _position, _duration); if (_this.state=='playing') { _this.trigger(jwe.JWPLAYER_MEDIA_TIME, a); } // set_buffered(get_buffered(), _position, _duration) // if (_this.state == 'playing') { // _this.trigger(jwe.JWPLAYER_MEDIA_TIME, { position: _position, // duration: get_duration_inf() }) // } } } function wrap_gen (e) { return function () { if (!_this.attached) { return } video_listeners[e]() } } for (var e in video_listeners) { video.addEventListener(e, wrap_gen(e), false) } function scaled_number (num) { if (num === undefined) { return '' } if (!num) { return '0' } var k = 1024 var sizes = ['', 'K', 'M', 'G', 'T', 'P'] var i = Math.floor(Math.log(num) / Math.log(k)) num /= Math.pow(k, i) if (num < 0.001) { return '0' } if (num >= k - 1) { num = Math.trunc(num) } var str = num.toFixed(num < 1 ? 3 : num < 10 ? 2 : num < 100 ? 1 : 0) return str.replace(/\.0*$/, '') + sizes[i] } // XXX yurij: duplicate from videojs5-hlsjs to avoid deps function level_label (level) { if (level.height) { return level.height + 'p' } if (level.width) { return Math.round(level.width * 9 / 16) + 'p' } if (level.bitrate) { return scaled_number(level.bitrate) + 'bps' } return 0 } function get_levels () { var levels = hls.levels || []; var res = [] // level 0 mimics native jw's hls provider behavior if (levels.length > 1) { res.push({ label: 'Auto' }) } levels.forEach(function (level) { res.push({ bitrate: level.bitrate, height: level.height, label: level_label(level), width: level.width }) }) return res } function get_level (level) { var levels = hls.levels || [] level = level || hls.currentLevel return { // level 0 is dummy for 'Auto' option in jwplayer's UI jw: hls.manual_level == -1 || levels.length < 2 ? 0 : level + 1, real: levels.length < 2 ? 0 : level + 1 } } hls.on(Hls.Events.ERROR, function (event, data) { if (!data.fatal) { return } var msg switch (data.details) { case Hls.ErrorDetails.MANIFEST_LOAD_ERROR: case Hls.ErrorDetails.MANIFEST_LOAD_TIMEOUT: msg = 'Cannot load M3U8: ' + data.response.statusText break default: msg = 'Error loading media: ' + data.details break } var a = {key: 'cantPlayVideo', code: data.details, recoverable: false, stalling: false, suppressLevel: false, fatal: true, error: data}; _this.trigger(jwe.JWPLAYER_MEDIA_ERROR, a) // var seen = []; // // var raw_error = JSON.stringify(data, function(key, val) { // if (typeof val == "object") { // if (seen.indexOf(val) >= 0) // return; // seen.push(val) // } // return val; // }); // // var time = getPlaying(video_id, episode_id); // // $.ajax({ // type: 'POST', // url: '/play-trunk-error', // data: { // 'video_id': video_id, // 'episode_id': episode_id, // 'message': msg, // 'raw_error' : raw_error, // 'time': parseInt(time) // }, // dataType: 'json', // success: function (data) { // }, // error: function (data) { // } // }); }) hls.on(Hls.Events.MANIFEST_LOADED, function () { _this.trigger(jwe.JWPLAYER_MEDIA_LEVELS, { currentQuality: get_level().jw, levels: get_levels() }) var levels; var is_video = 0 if (!(levels = hls.levels)) { return } levels.forEach(function (level) { is_video += +!!(level.videoCodec || !level.audioCodec && (level.bitrate > 64000 || level.width || level.height)) }) _this.trigger(jwe.JWPLAYER_MEDIA_TYPE, { mediaType: is_video ? 'video' : 'audio' }) }) hls.on(Hls.Events.LEVEL_SWITCH, function (e, data) { var levels = get_levels(); var level_id = get_level(data.level) _this.trigger(jwe.JWPLAYER_MEDIA_LEVEL_CHANGED, { currentQuality: level_id.jw, levels: levels }) var level = levels[level_id.real] visual_quality.level = level visual_quality.level.index = level_id.real visual_quality.level.label = hls.manual_level == -1 && levels.length > 1 ? 'auto' : level.label visual_quality.reason = visual_quality.reason || 'auto' _this.trigger('visualQuality', visual_quality) visual_quality.reason = '' }) this.init = function (item) { try_play = false video.setAttribute('jw-loaded', 'init') } this.load = function (item) { if (!this.attached) { return } var newsource = get_default_src(item.sources).file var video_state = video.getAttribute('jw-loaded') var hq = this.hls_queued; var played = video.hasAttribute('jw-played') if (!_is_mobile || played) { // don't change state on mobile before user initiates playback this.setState('loading') } hq.seek = Math.max(item.starttime - (hq.rw_sec || 0), 0) if (this.hls_state != 'ready' || (this.source || '') != newsource || ['init', 'started'].includes(video_state)) { var sc try_play = false video.load() hls.stopLoad(hls.media && this.hls_state == 'ready' && video_state == 'init') hls_load(this.source = newsource) video.setAttribute('jw-loaded', 'init') } else { hls_play() } if (_is_mobile && !played) { if (!try_play && (!_is_mobile.is_ios || can_play)) { try_play = true can_play = false video_play() } if (!video.paused && this.state != 'playing') { this.setState('loading') } } } this.play = function () { hls_play() } this.pause = function () { video.pause() _this.setState('paused') } this.stop = function () { hls.stopLoad() _this.setState('idle') } this.volume = function (vol) { video.volume = Math.min(vol / 100, 1.0) } this.mute = function (state) { video.muted = !!state } this.seek = function (pos) { this._in_seek = true if (this.hls_state == 'ready') { var sv = video.currentTime video.currentTime = pos this.trigger(jwe.JWPLAYER_MEDIA_SEEK, { position: sv, offset: pos }) } else { this.hls_queued.seek = pos } } // XXX arik: todo, without it video resize will be wrong. // eg. http://www.ozee.com/shows/muddha-mandaram#hola_mode=cdn&hola_zone=ozee_hap this.resize = function (width, height, stretching) {} this.remove = function () { this.in_container = false hls.stopLoad() this.source = undefined if (container === video.parentNode) { container.removeChild(video) } } this.destroy = function () { for (var e in video_listeners) { video.removeEventListener(e, video_listeners[e], false) } this.removeAllListeners() } this.setVisibility = function (state) { container.style.visibility = state ? 'visible' : '' container.style.opacity = state ? 1 : 0 } this.setFullscreen = function () { return false } this.getFullscreen = empty_fn('getFullscreen') this.getContainer = function () { return container } this.setContainer = function (element) { container = element container.appendChild(video) this.in_container = true } hls.manual_level = -1 this.setCurrentQuality = function (level) { if (level == hls.manual_level + 1) { return } hls.manual_level = level - 1 if (!hls.hola_adaptive) { hls.loadLevel = hls.manual_level } _this.trigger(jwe.JWPLAYER_MEDIA_LEVEL_CHANGED, { currentQuality: level, levels: get_levels() }) visual_quality.reason = 'api' } this.getName = function () { return { name: 'hola/hls' } } this.get_position = function () { return video.currentTime } this.getQualityLevels = function () { return get_levels() } this.getCurrentQuality = function () { return get_level(hls.loadLevel).jw } this.getAudioTracks = empty_fn('getAudioTracks') this.getCurrentAudioTrack = empty_fn('getCurrentAudioTrack') this.setCurrentAudioTrack = empty_fn('setCurrentAudioTrack') this.checkComplete = function () { return !!this.before_complete } this.setControls = empty_fn('setControls') this.attachMedia = function () { if (this.before_complete) { return playback_complete() } if (this.ad_count) { hls_log('jwprovider attach inside ad ' + this.ad_count) } this.attached = true hls.attachMedia(video) } this.detachMedia = function () { hls.trigger(Hls.Events.BUFFER_RESET) hls.detachMedia() if (this.level_cb) { hls.off(Hls.Events.LEVEL_LOADED, this.level_cb) this.level_cb = undefined } // XXX pavelki: hack to remove pending segments try{ delete hls.bufferController.segments }catch(err){ } this.attached = false return video } this.setState = function (state) { var oldState = this.state || 'idle' this.state = state if (state == oldState) { return } this.trigger(jwe.JWPLAYER_PLAYER_STATE, { newstate: state }) } this.sendMediaType = function (levels) { var is_audio = ['oga', 'aac', 'mp3', 'mpeg', 'vorbis'] .includes(levels[0].type) this.trigger(jwe.JWPLAYER_MEDIA_TYPE, { mediaType: is_audio ? 'audio' : 'video' }) } } E.getName = function () { return { name: 'hola/hls' } } // XXX yurij: copied from zjwplayer3.js to not depend on our code function get_player_instances () { var jwplayer = E.jwplayer || window.jwplayer var i = 0; var res = []; var jw // XXX marka/vadiml: a real instance will contain pause(), otherwise it // will be {registerPlugin: ...} with anything the customer adds while ((jw = jwplayer(i++)) && jw.pause) { res.push(jw) } return res } function src_supported (src) { if (src.type == 'hls') { return true } return (src.file || '').match(/\.m3u8$/) } E.supports = function (src) { var Hls = E.Hls || window.Hls var is_ad = get_player_instances().every(function (j) { // XXX yurij: jw.getPlaylist returns playlist item on early call var pl = j.getPlaylist() return (pl.every ? pl : [{ sources: [pl] }]).every(function (p) { // XXX pavlo: playlist item can be w/o sources/allSources return (p.allSources || p.sources || [{ file: p.file }]) .every(function (s) { return s.file != src.file }) }) }) if (is_ad) // XXX yurij: we are not supporting adaptive ads { return false } return !E.disabled && src_supported(src) && Hls && Hls.isSupported() } E.attach = function () { var jwplayer = E.jwplayer || window.jwplayer E.disabled = false if (!provider_attached) { provider_attached = true // XXX arik: unregister on error/fallback jwplayer.api.registerProvider(this) } } E.detach = function (hp) { // we don't remove provider from list, just set it as disabled so it will // return false in supports() E.disabled = true if (!hp || !hp.attached) { return } hp.setState('idle') hp.detachMedia() } // XXX vadiml copied from loader.js&zjwplayer3.js to not depend on our code. // For use by HolaCDN Onboarding Tool extension E.reload_jwplayer_instances = function () { get_player_instances().forEach(function (jw) { var c = jw.getConfig() if (!c) { return } // XXX marka: JW removes conf.advertising.client, try to restore it if (c.advertising && !c.advertising.client && c.plugins) { for (var url in c.plugins) { if (c.plugins[url] !== c.advertising) { continue } var m = url.match(/\/(\w+)\.js$/) c.advertising.client = m && m[1] break } } jw.setup(c) }) } E.VERSION = '0.0.79' }, { 'eventemitter3': 1 }] }, {}, [2])(2) }) // # sourceMappingURL=jwplayer.hlsjs.js.map