@clappr/hlsjs-playback 1.9.11 → 1.9.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -4,1021 +4,726 @@
4
4
  (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.HlsjsPlayback = factory(global.Clappr, global.Hls));
5
5
  })(this, (function (core, HLSJS) { 'use strict';
6
6
 
7
- function _arrayLikeToArray(r, a) {
8
- (null == a || a > r.length) && (a = r.length);
9
- for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e];
10
- return n;
11
- }
12
- function _arrayWithoutHoles(r) {
13
- if (Array.isArray(r)) return _arrayLikeToArray(r);
14
- }
15
- function _assertThisInitialized(e) {
16
- if (void 0 === e) throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
17
- return e;
18
- }
19
- function _callSuper(t, o, e) {
20
- return o = _getPrototypeOf(o), _possibleConstructorReturn(t, _isNativeReflectConstruct() ? Reflect.construct(o, e || [], _getPrototypeOf(t).constructor) : o.apply(t, e));
21
- }
22
- function _classCallCheck(a, n) {
23
- if (!(a instanceof n)) throw new TypeError("Cannot call a class as a function");
24
- }
25
- function _defineProperties(e, r) {
26
- for (var t = 0; t < r.length; t++) {
27
- var o = r[t];
28
- o.enumerable = o.enumerable || !1, o.configurable = !0, "value" in o && (o.writable = !0), Object.defineProperty(e, _toPropertyKey(o.key), o);
7
+ // Copyright 2014 Globo.com Player authors. All rights reserved.
8
+ // Use of this source code is governed by a BSD-style
9
+ // license that can be found in the LICENSE file.
10
+
11
+ const {
12
+ now,
13
+ listContainsIgnoreCase
14
+ } = core.Utils;
15
+ const AUTO = -1;
16
+ const DEFAULT_RECOVER_ATTEMPTS = 16;
17
+ core.Events.register('PLAYBACK_FRAGMENT_CHANGED');
18
+ core.Events.register('PLAYBACK_FRAGMENT_PARSING_METADATA');
19
+ class HlsjsPlayback extends core.HTML5Video {
20
+ get name() {
21
+ return 'hls';
29
22
  }
30
- }
31
- function _createClass(e, r, t) {
32
- return r && _defineProperties(e.prototype, r), t && _defineProperties(e, t), Object.defineProperty(e, "prototype", {
33
- writable: !1
34
- }), e;
35
- }
36
- function _defineProperty(e, r, t) {
37
- return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, {
38
- value: t,
39
- enumerable: !0,
40
- configurable: !0,
41
- writable: !0
42
- }) : e[r] = t, e;
43
- }
44
- function _get() {
45
- return _get = "undefined" != typeof Reflect && Reflect.get ? Reflect.get.bind() : function (e, t, r) {
46
- var p = _superPropBase(e, t);
47
- if (p) {
48
- var n = Object.getOwnPropertyDescriptor(p, t);
49
- return n.get ? n.get.call(arguments.length < 3 ? e : r) : n.value;
23
+ get supportedVersion() {
24
+ return {
25
+ min: "0.14.8"
26
+ };
27
+ }
28
+ get levels() {
29
+ return this._levels || [];
30
+ }
31
+ get currentLevel() {
32
+ if (this._currentLevel === null || this._currentLevel === undefined) {
33
+ return AUTO;
34
+ } else {
35
+ return this._currentLevel;
36
+ } // 0 is a valid level ID
37
+ }
38
+ get isReady() {
39
+ return this._isReadyState;
40
+ }
41
+ set currentLevel(id) {
42
+ this._currentLevel = id;
43
+ this.trigger(core.Events.PLAYBACK_LEVEL_SWITCH_START);
44
+ if (this.options.playback.hlsUseNextLevel) {
45
+ this._hls.nextLevel = this._currentLevel;
46
+ } else {
47
+ this._hls.currentLevel = this._currentLevel;
50
48
  }
51
- }, _get.apply(null, arguments);
52
- }
53
- function _getPrototypeOf(t) {
54
- return _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf.bind() : function (t) {
55
- return t.__proto__ || Object.getPrototypeOf(t);
56
- }, _getPrototypeOf(t);
57
- }
58
- function _inherits(t, e) {
59
- if ("function" != typeof e && null !== e) throw new TypeError("Super expression must either be null or a function");
60
- t.prototype = Object.create(e && e.prototype, {
61
- constructor: {
62
- value: t,
63
- writable: !0,
64
- configurable: !0
49
+ }
50
+ get latency() {
51
+ return this._hls.latency;
52
+ }
53
+ get liveSyncPosition() {
54
+ return this._hls.liveSyncPosition;
55
+ }
56
+ get currentProgramDateTime() {
57
+ return this._hls.playingDate;
58
+ }
59
+ get _startTime() {
60
+ if (this._playbackType === core.Playback.LIVE && this._playlistType !== 'EVENT') {
61
+ return this._extrapolatedStartTime;
65
62
  }
66
- }), Object.defineProperty(t, "prototype", {
67
- writable: !1
68
- }), e && _setPrototypeOf(t, e);
69
- }
70
- function _isNativeReflectConstruct() {
71
- try {
72
- var t = !Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {}));
73
- } catch (t) {}
74
- return (_isNativeReflectConstruct = function () {
75
- return !!t;
76
- })();
77
- }
78
- function _iterableToArray(r) {
79
- if ("undefined" != typeof Symbol && null != r[Symbol.iterator] || null != r["@@iterator"]) return Array.from(r);
80
- }
81
- function _nonIterableSpread() {
82
- throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
83
- }
84
- function ownKeys(e, r) {
85
- var t = Object.keys(e);
86
- if (Object.getOwnPropertySymbols) {
87
- var o = Object.getOwnPropertySymbols(e);
88
- r && (o = o.filter(function (r) {
89
- return Object.getOwnPropertyDescriptor(e, r).enumerable;
90
- })), t.push.apply(t, o);
91
- }
92
- return t;
93
- }
94
- function _objectSpread2(e) {
95
- for (var r = 1; r < arguments.length; r++) {
96
- var t = null != arguments[r] ? arguments[r] : {};
97
- r % 2 ? ownKeys(Object(t), !0).forEach(function (r) {
98
- _defineProperty(e, r, t[r]);
99
- }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) {
100
- Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r));
101
- });
63
+ return this._playableRegionStartTime;
102
64
  }
103
- return e;
104
- }
105
- function _possibleConstructorReturn(t, e) {
106
- if (e && ("object" == typeof e || "function" == typeof e)) return e;
107
- if (void 0 !== e) throw new TypeError("Derived constructors may only return object or undefined");
108
- return _assertThisInitialized(t);
109
- }
110
- function _setPrototypeOf(t, e) {
111
- return _setPrototypeOf = Object.setPrototypeOf ? Object.setPrototypeOf.bind() : function (t, e) {
112
- return t.__proto__ = e, t;
113
- }, _setPrototypeOf(t, e);
114
- }
115
- function _superPropBase(t, o) {
116
- for (; !{}.hasOwnProperty.call(t, o) && null !== (t = _getPrototypeOf(t)););
117
- return t;
118
- }
119
- function _superPropGet(t, o, e, r) {
120
- var p = _get(_getPrototypeOf(1 & r ? t.prototype : t), o, e);
121
- return 2 & r && "function" == typeof p ? function (t) {
122
- return p.apply(e, t);
123
- } : p;
124
- }
125
- function _toConsumableArray(r) {
126
- return _arrayWithoutHoles(r) || _iterableToArray(r) || _unsupportedIterableToArray(r) || _nonIterableSpread();
127
- }
128
- function _toPrimitive(t, r) {
129
- if ("object" != typeof t || !t) return t;
130
- var e = t[Symbol.toPrimitive];
131
- if (void 0 !== e) {
132
- var i = e.call(t, r || "default");
133
- if ("object" != typeof i) return i;
134
- throw new TypeError("@@toPrimitive must return a primitive value.");
135
- }
136
- return ("string" === r ? String : Number)(t);
137
- }
138
- function _toPropertyKey(t) {
139
- var i = _toPrimitive(t, "string");
140
- return "symbol" == typeof i ? i : i + "";
141
- }
142
- function _unsupportedIterableToArray(r, a) {
143
- if (r) {
144
- if ("string" == typeof r) return _arrayLikeToArray(r, a);
145
- var t = {}.toString.call(r).slice(8, -1);
146
- return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0;
65
+ get _now() {
66
+ return now();
147
67
  }
148
- }
149
68
 
150
- var now = core.Utils.now,
151
- listContainsIgnoreCase = core.Utils.listContainsIgnoreCase;
152
- var AUTO = -1;
153
- var DEFAULT_RECOVER_ATTEMPTS = 16;
154
- core.Events.register('PLAYBACK_FRAGMENT_CHANGED');
155
- core.Events.register('PLAYBACK_FRAGMENT_PARSING_METADATA');
156
- var HlsjsPlayback = /*#__PURE__*/function (_HTML5Video) {
157
- function HlsjsPlayback() {
158
- var _this;
159
- _classCallCheck(this, HlsjsPlayback);
160
- for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
161
- args[_key] = arguments[_key];
162
- }
163
- _this = _callSuper(this, HlsjsPlayback, [].concat(args));
164
- _this.options.hlsPlayback = _objectSpread2(_objectSpread2({}, _this.defaultOptions), _this.options.hlsPlayback);
165
- _this._timeUpdateThrottleDelay = 200;
166
- _this._timeUpdateFiringRate = 0.2;
167
- _this._durationChangeMinOffset = 0.5;
168
- _this._setInitialState();
169
- return _this;
170
- }
171
- _inherits(HlsjsPlayback, _HTML5Video);
172
- return _createClass(HlsjsPlayback, [{
173
- key: "name",
174
- get: function get() {
175
- return 'hls';
176
- }
177
- }, {
178
- key: "supportedVersion",
179
- get: function get() {
180
- return {
181
- min: "0.14.6"
182
- };
183
- }
184
- }, {
185
- key: "levels",
186
- get: function get() {
187
- return this._levels || [];
188
- }
189
- }, {
190
- key: "currentLevel",
191
- get: function get() {
192
- if (this._currentLevel === null || this._currentLevel === undefined) {
193
- return AUTO;
194
- } else {
195
- return this._currentLevel;
196
- } // 0 is a valid level ID
197
- },
198
- set: function set(id) {
199
- this._currentLevel = id;
200
- this.trigger(core.Events.PLAYBACK_LEVEL_SWITCH_START);
201
- if (this.options.playback.hlsUseNextLevel) {
202
- this._hls.nextLevel = this._currentLevel;
203
- } else {
204
- this._hls.currentLevel = this._currentLevel;
205
- }
206
- }
207
- }, {
208
- key: "isReady",
209
- get: function get() {
210
- return this._isReadyState;
211
- }
212
- }, {
213
- key: "latency",
214
- get: function get() {
215
- return this._hls.latency;
216
- }
217
- }, {
218
- key: "liveSyncPosition",
219
- get: function get() {
220
- return this._hls.liveSyncPosition;
221
- }
222
- }, {
223
- key: "currentProgramDateTime",
224
- get: function get() {
225
- return this._hls.playingDate;
226
- }
227
- }, {
228
- key: "_startTime",
229
- get: function get() {
230
- if (this._playbackType === core.Playback.LIVE && this._playlistType !== 'EVENT') {
231
- return this._extrapolatedStartTime;
232
- }
69
+ // the time in the video element which should represent the start of the sliding window
70
+ // extrapolated to increase in real time (instead of jumping as the early segments are removed)
71
+ get _extrapolatedStartTime() {
72
+ if (!this._localStartTimeCorrelation) {
233
73
  return this._playableRegionStartTime;
234
74
  }
235
- }, {
236
- key: "_now",
237
- get: function get() {
238
- return now();
239
- }
240
-
241
- // the time in the video element which should represent the start of the sliding window
242
- // extrapolated to increase in real time (instead of jumping as the early segments are removed)
243
- }, {
244
- key: "_extrapolatedStartTime",
245
- get: function get() {
246
- if (!this._localStartTimeCorrelation) {
247
- return this._playableRegionStartTime;
248
- }
249
- var corr = this._localStartTimeCorrelation;
250
- var timePassed = this._now - corr.local;
251
- var extrapolatedWindowStartTime = (corr.remote + timePassed) / 1000;
252
- // cap at the end of the extrapolated window duration
253
- return Math.min(extrapolatedWindowStartTime, this._playableRegionStartTime + this._extrapolatedWindowDuration);
254
- }
75
+ const corr = this._localStartTimeCorrelation;
76
+ const timePassed = this._now - corr.local;
77
+ const extrapolatedWindowStartTime = (corr.remote + timePassed) / 1000;
78
+ // cap at the end of the extrapolated window duration
79
+ return Math.min(extrapolatedWindowStartTime, this._playableRegionStartTime + this._extrapolatedWindowDuration);
80
+ }
255
81
 
256
- // the time in the video element which should represent the end of the content
257
- // extrapolated to increase in real time (instead of jumping as segments are added)
258
- }, {
259
- key: "_extrapolatedEndTime",
260
- get: function get() {
261
- var actualEndTime = this._playableRegionStartTime + this._playableRegionDuration;
262
- if (!this._localEndTimeCorrelation) return actualEndTime;
263
- var correlation = this._localEndTimeCorrelation;
264
- var timePassed = this._now - correlation.local;
265
- var extrapolatedEndTime = (correlation.remote + timePassed) / 1000;
266
- return Math.max(actualEndTime - this._extrapolatedWindowDuration, Math.min(extrapolatedEndTime, actualEndTime));
267
- }
268
- }, {
269
- key: "_duration",
270
- get: function get() {
271
- return this._extrapolatedEndTime - this._startTime;
272
- }
82
+ // the time in the video element which should represent the end of the content
83
+ // extrapolated to increase in real time (instead of jumping as segments are added)
84
+ get _extrapolatedEndTime() {
85
+ const actualEndTime = this._playableRegionStartTime + this._playableRegionDuration;
86
+ if (!this._localEndTimeCorrelation) return actualEndTime;
87
+ const correlation = this._localEndTimeCorrelation;
88
+ const timePassed = this._now - correlation.local;
89
+ const extrapolatedEndTime = (correlation.remote + timePassed) / 1000;
90
+ return Math.max(actualEndTime - this._extrapolatedWindowDuration, Math.min(extrapolatedEndTime, actualEndTime));
91
+ }
92
+ get _duration() {
93
+ return this._extrapolatedEndTime - this._startTime;
94
+ }
273
95
 
274
- // Returns the duration (seconds) of the window that the extrapolated start time is allowed
275
- // to move in before being capped.
276
- // The extrapolated start time should never reach the cap at the end of the window as the
277
- // window should slide as chunks are removed from the start.
278
- // This also applies to the extrapolated end time in the same way.
279
- //
280
- // If chunks aren't being removed for some reason that the start time will reach and remain fixed at
281
- // playableRegionStartTime + extrapolatedWindowDuration
282
- //
283
- // <-- window duration -->
284
- // I.e playableRegionStartTime |-----------------------|
285
- // | --> . . .
286
- // . --> | --> . .
287
- // . . --> | --> .
288
- // . . . --> |
289
- // . . . .
290
- // extrapolatedStartTime
291
- }, {
292
- key: "_extrapolatedWindowDuration",
293
- get: function get() {
294
- if (this._segmentTargetDuration === null) {
295
- return 0;
296
- }
297
- return this._extrapolatedWindowNumSegments * this._segmentTargetDuration;
298
- }
299
- }, {
300
- key: "bandwidthEstimate",
301
- get: function get() {
302
- return this._hls && this._hls.bandwidthEstimate;
303
- }
304
- }, {
305
- key: "defaultOptions",
306
- get: function get() {
307
- return {
308
- preload: true
309
- };
310
- }
311
- }, {
312
- key: "customListeners",
313
- get: function get() {
314
- return this.options.hlsPlayback && this.options.hlsPlayback.customListeners || [];
315
- }
316
- }, {
317
- key: "sourceMedia",
318
- get: function get() {
319
- return this.options.src;
320
- }
321
- }, {
322
- key: "currentTimestamp",
323
- get: function get() {
324
- if (!this._currentFragment) return null;
325
- var startTime = this._currentFragment.programDateTime;
326
- var playbackTime = this.el.currentTime;
327
- var playTimeOffSet = playbackTime - this._currentFragment.start;
328
- var currentTimestampInMs = startTime + playTimeOffSet * 1000;
329
- return currentTimestampInMs / 1000;
330
- }
331
- }, {
332
- key: "_setInitialState",
333
- value: function _setInitialState() {
334
- this._minDvrSize = typeof this.options.hlsMinimumDvrSize === 'undefined' ? 60 : this.options.hlsMinimumDvrSize;
335
- // The size of the start time extrapolation window measured as a multiple of segments.
336
- // Should be 2 or higher, or 0 to disable. Should only need to be increased above 2 if more than one segment is
337
- // removed from the start of the playlist at a time. E.g if the playlist is cached for 10 seconds and new chunks are
338
- // added/removed every 5.
339
- this._extrapolatedWindowNumSegments = !this.options.playback || typeof this.options.playback.extrapolatedWindowNumSegments === 'undefined' ? 2 : this.options.playback.extrapolatedWindowNumSegments;
340
- this._playbackType = core.Playback.VOD;
341
- this._lastTimeUpdate = {
342
- current: 0,
343
- total: 0,
344
- firstFragDateTime: 0
345
- };
346
- this._lastTimeUpdateFiredTime = 0;
347
- this._lastDuration = null;
348
- // for hls streams which have dvr with a sliding window,
349
- // the content at the start of the playlist is removed as new
350
- // content is appended at the end.
351
- // this means the actual playable start time will increase as the
352
- // start content is deleted
353
- // For streams with dvr where the entire recording is kept from the
354
- // beginning this should stay as 0
355
- this._playableRegionStartTime = 0;
356
- // {local, remote} remote is the time in the video element that should represent 0
357
- // local is the system time when the 'remote' measurment took place
358
- this._localStartTimeCorrelation = null;
359
- // {local, remote} remote is the time in the video element that should represents the end
360
- // local is the system time when the 'remote' measurment took place
361
- this._localEndTimeCorrelation = null;
362
- // if content is removed from the beginning then this empty area should
363
- // be ignored. "playableRegionDuration" excludes the empty area
364
- this._playableRegionDuration = 0;
365
- // #EXT-X-PROGRAM-DATE-TIME
366
- this._programDateTime = 0;
367
- // true when the actual duration is longer than hlsjs's live sync point
368
- // when this is false playableRegionDuration will be the actual duration
369
- // when this is true playableRegionDuration will exclude the time after the sync point
370
- this._durationExcludesAfterLiveSyncPoint = false;
371
- // #EXT-X-TARGETDURATION
372
- this._segmentTargetDuration = null;
373
- // #EXT-X-PLAYLIST-TYPE
374
- this._playlistType = null;
375
- this._recoverAttemptsRemaining = this.options.hlsRecoverAttempts || DEFAULT_RECOVER_ATTEMPTS;
376
- }
377
- }, {
378
- key: "_setup",
379
- value: function _setup() {
380
- this._destroyHLSInstance();
381
- this._createHLSInstance();
382
- this._listenHLSEvents();
383
- this._attachHLSMedia();
384
- }
385
- }, {
386
- key: "_destroyHLSInstance",
387
- value: function _destroyHLSInstance() {
388
- if (!this._hls) return;
389
- this._manifestLoading = false;
390
- this._ccIsSetup = false;
391
- this._ccTracksUpdated = false;
392
- this._setInitialState();
393
- this._hls.destroy();
394
- this._hls = null;
395
- }
396
- }, {
397
- key: "_createHLSInstance",
398
- value: function _createHLSInstance() {
399
- var config = _objectSpread2({}, this.options.playback.hlsjsConfig);
400
- this._hls = new HLSJS(config);
401
- }
402
- }, {
403
- key: "_attachHLSMedia",
404
- value: function _attachHLSMedia() {
405
- if (!this._hls) return;
406
- this._hls.attachMedia(this.el);
407
- }
408
- }, {
409
- key: "_listenHLSEvents",
410
- value: function _listenHLSEvents() {
411
- var _this2 = this;
412
- if (!this._hls) return;
413
- this._hls.once(HLSJS.Events.MEDIA_ATTACHED, function () {
414
- _this2.options.hlsPlayback.preload && _this2._hls.loadSource(_this2.options.src);
415
- });
416
- this._hls.on(HLSJS.Events.MANIFEST_LOADING, function () {
417
- _this2._manifestLoading = true;
418
- });
419
- this._hls.on(HLSJS.Events.LEVEL_LOADED, function (evt, data) {
420
- return _this2._updatePlaybackType(evt, data);
421
- });
422
- this._hls.on(HLSJS.Events.LEVEL_UPDATED, function (evt, data) {
423
- return _this2._onLevelUpdated(evt, data);
424
- });
425
- this._hls.on(HLSJS.Events.LEVEL_SWITCHED, function (evt, data) {
426
- return _this2._onLevelSwitch(evt, data);
427
- });
428
- this._hls.on(HLSJS.Events.FRAG_CHANGED, function (evt, data) {
429
- return _this2._onFragmentChanged(evt, data);
430
- });
431
- this._hls.on(HLSJS.Events.FRAG_LOADED, function (evt, data) {
432
- return _this2._onFragmentLoaded(evt, data);
433
- });
434
- this._hls.on(HLSJS.Events.FRAG_BUFFERED, function (evt, data) {
435
- return _this2._onFragmentBuffered(evt, data);
436
- });
437
- this._hls.on(HLSJS.Events.FRAG_PARSING_METADATA, function (evt, data) {
438
- return _this2._onFragmentParsingMetadata(evt, data);
439
- });
440
- this._hls.on(HLSJS.Events.ERROR, function (evt, data) {
441
- return _this2._onHLSJSError(evt, data);
442
- });
443
- this._hls.on(HLSJS.Events.SUBTITLE_TRACK_LOADED, function (evt, data) {
444
- return _this2._onSubtitleLoaded(evt, data);
445
- });
446
- this._hls.on(HLSJS.Events.SUBTITLE_TRACKS_UPDATED, function () {
447
- return _this2._ccTracksUpdated = true;
448
- });
449
- this.bindCustomListeners();
450
- }
451
- }, {
452
- key: "bindCustomListeners",
453
- value: function bindCustomListeners() {
454
- var _this3 = this;
455
- this.customListeners.forEach(function (item) {
456
- var requestedEventName = item.eventName;
457
- var typeOfListener = item.once ? 'once' : 'on';
458
- requestedEventName && _this3._hls["".concat(typeOfListener)](requestedEventName, item.callback);
459
- });
460
- }
461
- }, {
462
- key: "unbindCustomListeners",
463
- value: function unbindCustomListeners() {
464
- var _this4 = this;
465
- this.customListeners.forEach(function (item) {
466
- var requestedEventName = item.eventName;
467
- requestedEventName && _this4._hls.off(requestedEventName, item.callback);
468
- });
469
- }
470
- }, {
471
- key: "_onFragmentParsingMetadata",
472
- value: function _onFragmentParsingMetadata(evt, data) {
473
- this.trigger(core.Events.Custom.PLAYBACK_FRAGMENT_PARSING_METADATA, {
474
- evt: evt,
475
- data: data
96
+ // Returns the duration (seconds) of the window that the extrapolated start time is allowed
97
+ // to move in before being capped.
98
+ // The extrapolated start time should never reach the cap at the end of the window as the
99
+ // window should slide as chunks are removed from the start.
100
+ // This also applies to the extrapolated end time in the same way.
101
+ //
102
+ // If chunks aren't being removed for some reason that the start time will reach and remain fixed at
103
+ // playableRegionStartTime + extrapolatedWindowDuration
104
+ //
105
+ // <-- window duration -->
106
+ // I.e playableRegionStartTime |-----------------------|
107
+ // | --> . . .
108
+ // . --> | --> . .
109
+ // . . --> | --> .
110
+ // . . . --> |
111
+ // . . . .
112
+ // extrapolatedStartTime
113
+ get _extrapolatedWindowDuration() {
114
+ if (this._segmentTargetDuration === null) {
115
+ return 0;
116
+ }
117
+ return this._extrapolatedWindowNumSegments * this._segmentTargetDuration;
118
+ }
119
+ get bandwidthEstimate() {
120
+ return this._hls && this._hls.bandwidthEstimate;
121
+ }
122
+ get defaultOptions() {
123
+ return {
124
+ preload: true
125
+ };
126
+ }
127
+ get customListeners() {
128
+ return this.options.hlsPlayback && this.options.hlsPlayback.customListeners || [];
129
+ }
130
+ get sourceMedia() {
131
+ return this.options.src;
132
+ }
133
+ get currentTimestamp() {
134
+ if (!this._currentFragment) return null;
135
+ const startTime = this._currentFragment.programDateTime;
136
+ const playbackTime = this.el.currentTime;
137
+ const playTimeOffSet = playbackTime - this._currentFragment.start;
138
+ const currentTimestampInMs = startTime + playTimeOffSet * 1000;
139
+ return currentTimestampInMs / 1000;
140
+ }
141
+ static get HLSJS() {
142
+ return HLSJS;
143
+ }
144
+ constructor(...args) {
145
+ super(...args);
146
+ this.options.hlsPlayback = {
147
+ ...this.defaultOptions,
148
+ ...this.options.hlsPlayback
149
+ };
150
+ this._timeUpdateThrottleDelay = 200;
151
+ this._timeUpdateFiringRate = 0.2;
152
+ this._durationChangeMinOffset = 0.5;
153
+ this._setInitialState();
154
+ }
155
+ _setInitialState() {
156
+ this._minDvrSize = typeof this.options.hlsMinimumDvrSize === 'undefined' ? 60 : this.options.hlsMinimumDvrSize;
157
+ // The size of the start time extrapolation window measured as a multiple of segments.
158
+ // Should be 2 or higher, or 0 to disable. Should only need to be increased above 2 if more than one segment is
159
+ // removed from the start of the playlist at a time. E.g if the playlist is cached for 10 seconds and new chunks are
160
+ // added/removed every 5.
161
+ this._extrapolatedWindowNumSegments = !this.options.playback || typeof this.options.playback.extrapolatedWindowNumSegments === 'undefined' ? 2 : this.options.playback.extrapolatedWindowNumSegments;
162
+ this._playbackType = core.Playback.VOD;
163
+ this._lastTimeUpdate = {
164
+ current: 0,
165
+ total: 0,
166
+ firstFragDateTime: 0
167
+ };
168
+ this._lastTimeUpdateFiredTime = 0;
169
+ this._lastDuration = null;
170
+ // for hls streams which have dvr with a sliding window,
171
+ // the content at the start of the playlist is removed as new
172
+ // content is appended at the end.
173
+ // this means the actual playable start time will increase as the
174
+ // start content is deleted
175
+ // For streams with dvr where the entire recording is kept from the
176
+ // beginning this should stay as 0
177
+ this._playableRegionStartTime = 0;
178
+ // {local, remote} remote is the time in the video element that should represent 0
179
+ // local is the system time when the 'remote' measurment took place
180
+ this._localStartTimeCorrelation = null;
181
+ // {local, remote} remote is the time in the video element that should represents the end
182
+ // local is the system time when the 'remote' measurment took place
183
+ this._localEndTimeCorrelation = null;
184
+ // if content is removed from the beginning then this empty area should
185
+ // be ignored. "playableRegionDuration" excludes the empty area
186
+ this._playableRegionDuration = 0;
187
+ // #EXT-X-PROGRAM-DATE-TIME
188
+ this._programDateTime = 0;
189
+ // true when the actual duration is longer than hlsjs's live sync point
190
+ // when this is false playableRegionDuration will be the actual duration
191
+ // when this is true playableRegionDuration will exclude the time after the sync point
192
+ this._durationExcludesAfterLiveSyncPoint = false;
193
+ // #EXT-X-TARGETDURATION
194
+ this._segmentTargetDuration = null;
195
+ // #EXT-X-PLAYLIST-TYPE
196
+ this._playlistType = null;
197
+ this._recoverAttemptsRemaining = this.options.hlsRecoverAttempts || DEFAULT_RECOVER_ATTEMPTS;
198
+ }
199
+ _setup() {
200
+ this._destroyHLSInstance();
201
+ this._createHLSInstance();
202
+ this._listenHLSEvents();
203
+ this._attachHLSMedia();
204
+ }
205
+ _destroyHLSInstance() {
206
+ if (!this._hls) return;
207
+ this._manifestLoading = false;
208
+ this._ccIsSetup = false;
209
+ this._ccTracksUpdated = false;
210
+ this._setInitialState();
211
+ this._hls.destroy();
212
+ this._hls = null;
213
+ }
214
+ _createHLSInstance() {
215
+ const config = {
216
+ ...this.options.playback.hlsjsConfig
217
+ };
218
+ this._hls = new HLSJS(config);
219
+ }
220
+ _attachHLSMedia() {
221
+ if (!this._hls) return;
222
+ this._hls.attachMedia(this.el);
223
+ }
224
+ _listenHLSEvents() {
225
+ if (!this._hls) return;
226
+ this._hls.once(HLSJS.Events.MEDIA_ATTACHED, () => {
227
+ this.options.hlsPlayback.preload && this._hls.loadSource(this.options.src);
228
+ });
229
+ this._hls.on(HLSJS.Events.MANIFEST_LOADING, () => {
230
+ this._manifestLoading = true;
231
+ });
232
+ this._hls.on(HLSJS.Events.LEVEL_LOADED, (evt, data) => this._updatePlaybackType(evt, data));
233
+ this._hls.on(HLSJS.Events.LEVEL_UPDATED, (evt, data) => this._onLevelUpdated(evt, data));
234
+ this._hls.on(HLSJS.Events.LEVEL_SWITCHED, (evt, data) => this._onLevelSwitch(evt, data));
235
+ this._hls.on(HLSJS.Events.FRAG_CHANGED, (evt, data) => this._onFragmentChanged(evt, data));
236
+ this._hls.on(HLSJS.Events.FRAG_LOADED, (evt, data) => this._onFragmentLoaded(evt, data));
237
+ this._hls.on(HLSJS.Events.FRAG_BUFFERED, (evt, data) => this._onFragmentBuffered(evt, data));
238
+ this._hls.on(HLSJS.Events.FRAG_PARSING_METADATA, (evt, data) => this._onFragmentParsingMetadata(evt, data));
239
+ this._hls.on(HLSJS.Events.ERROR, (evt, data) => this._onHLSJSError(evt, data));
240
+ this._hls.on(HLSJS.Events.SUBTITLE_TRACK_LOADED, (evt, data) => this._onSubtitleLoaded(evt, data));
241
+ this._hls.on(HLSJS.Events.SUBTITLE_TRACKS_UPDATED, () => this._ccTracksUpdated = true);
242
+ this.bindCustomListeners();
243
+ }
244
+ bindCustomListeners() {
245
+ this.customListeners.forEach(item => {
246
+ const requestedEventName = item.eventName;
247
+ const typeOfListener = item.once ? 'once' : 'on';
248
+ requestedEventName && this._hls[`${typeOfListener}`](requestedEventName, item.callback);
249
+ });
250
+ }
251
+ unbindCustomListeners() {
252
+ this.customListeners.forEach(item => {
253
+ const requestedEventName = item.eventName;
254
+ requestedEventName && this._hls.off(requestedEventName, item.callback);
255
+ });
256
+ }
257
+ _onFragmentParsingMetadata(evt, data) {
258
+ this.trigger(core.Events.Custom.PLAYBACK_FRAGMENT_PARSING_METADATA, {
259
+ evt,
260
+ data
261
+ });
262
+ }
263
+ render() {
264
+ this._ready();
265
+ return super.render();
266
+ }
267
+ _ready() {
268
+ if (this._isReadyState) return;
269
+ !this._hls && this._setup();
270
+ this._isReadyState = true;
271
+ this.trigger(core.Events.PLAYBACK_READY, this.name);
272
+ }
273
+ _recover(evt, data, error) {
274
+ if (!this._recoveredDecodingError) {
275
+ this._recoveredDecodingError = true;
276
+ this._hls.recoverMediaError();
277
+ this.play();
278
+ } else if (!this._recoveredAudioCodecError) {
279
+ this._recoveredAudioCodecError = true;
280
+ this._hls.swapAudioCodec();
281
+ this._hls.recoverMediaError();
282
+ this.play();
283
+ } else {
284
+ core.Log.error('hlsjs: failed to recover', {
285
+ evt,
286
+ data
476
287
  });
288
+ error.level = core.PlayerError.Levels.FATAL;
289
+ const formattedError = this.createError(error);
290
+ this.trigger(core.Events.PLAYBACK_ERROR, formattedError);
291
+ this.stop();
477
292
  }
478
- }, {
479
- key: "render",
480
- value: function render() {
481
- this._ready();
482
- return _superPropGet(HlsjsPlayback, "render", this, 3)([]);
483
- }
484
- }, {
485
- key: "_ready",
486
- value: function _ready() {
487
- if (this._isReadyState) return;
488
- !this._hls && this._setup();
489
- this._isReadyState = true;
490
- this.trigger(core.Events.PLAYBACK_READY, this.name);
491
- }
492
- }, {
493
- key: "_recover",
494
- value: function _recover(evt, data, error) {
495
- if (!this._recoveredDecodingError) {
496
- this._recoveredDecodingError = true;
497
- this._hls.recoverMediaError();
498
- this.play();
499
- } else if (!this._recoveredAudioCodecError) {
500
- this._recoveredAudioCodecError = true;
501
- this._hls.swapAudioCodec();
502
- this._hls.recoverMediaError();
503
- this.play();
504
- } else {
505
- core.Log.error('hlsjs: failed to recover', {
506
- evt: evt,
507
- data: data
508
- });
509
- error.level = core.PlayerError.Levels.FATAL;
510
- var formattedError = this.createError(error);
511
- this.trigger(core.Events.PLAYBACK_ERROR, formattedError);
512
- this.stop();
513
- }
514
- }
293
+ }
515
294
 
516
- // override
517
- // this playback manages the src on the video element itself
518
- }, {
519
- key: "_setupSrc",
520
- value: function _setupSrc(srcUrl) {} // eslint-disable-line no-unused-vars
521
- }, {
522
- key: "_startTimeUpdateTimer",
523
- value: function _startTimeUpdateTimer() {
524
- var _this5 = this;
525
- if (this._timeUpdateTimer) return;
526
- this._timeUpdateTimer = setInterval(function () {
527
- _this5._onDurationChange();
528
- _this5._onTimeUpdate();
529
- }, 100);
530
- }
531
- }, {
532
- key: "_stopTimeUpdateTimer",
533
- value: function _stopTimeUpdateTimer() {
534
- if (!this._timeUpdateTimer) return;
535
- clearInterval(this._timeUpdateTimer);
536
- this._timeUpdateTimer = null;
537
- }
538
- }, {
539
- key: "getProgramDateTime",
540
- value: function getProgramDateTime() {
541
- return this._programDateTime;
542
- }
295
+ // override
296
+ // this playback manages the src on the video element itself
297
+ _setupSrc(srcUrl) {} // eslint-disable-line no-unused-vars
543
298
 
544
- // the duration on the video element itself should not be used
545
- // as this does not necesarily represent the duration of the stream
546
- // https://github.com/clappr/clappr/issues/668#issuecomment-157036678
547
- }, {
548
- key: "getDuration",
549
- value: function getDuration() {
550
- return this._duration;
551
- }
299
+ _startTimeUpdateTimer() {
300
+ if (this._timeUpdateTimer) return;
301
+ this._timeUpdateTimer = setInterval(() => {
302
+ this._onDurationChange();
303
+ this._onTimeUpdate();
304
+ }, 100);
305
+ }
306
+ _stopTimeUpdateTimer() {
307
+ if (!this._timeUpdateTimer) return;
308
+ clearInterval(this._timeUpdateTimer);
309
+ this._timeUpdateTimer = null;
310
+ }
311
+ getProgramDateTime() {
312
+ return this._programDateTime;
313
+ }
552
314
 
553
- // the frame rate should be retrieved from the HLS.js level information
554
- // as it is not necessarily exposed directly by the video element
555
- }, {
556
- key: "getFrameRate",
557
- value: function getFrameRate() {
558
- if (this._hls && this._hls.levels && this._hls.currentLevel >= 0) {
559
- var level = this._hls.levels[this._hls.currentLevel];
560
- return level && level.frameRate ? level.frameRate : null;
561
- }
562
- return null;
563
- }
564
- }, {
565
- key: "getCurrentTime",
566
- value: function getCurrentTime() {
567
- // e.g. can be < 0 if user pauses near the start
568
- // eventually they will then be kicked to the end by hlsjs if they run out of buffer
569
- // before the official start time
570
- return Math.max(0, this.el.currentTime - this._startTime);
571
- }
315
+ // the duration on the video element itself should not be used
316
+ // as this does not necesarily represent the duration of the stream
317
+ // https://github.com/clappr/clappr/issues/668#issuecomment-157036678
318
+ getDuration() {
319
+ return this._duration;
320
+ }
572
321
 
573
- // the time that "0" now represents relative to when playback started
574
- // for a stream with a sliding window this will increase as content is
575
- // removed from the beginning
576
- }, {
577
- key: "getStartTimeOffset",
578
- value: function getStartTimeOffset() {
579
- return this._startTime;
580
- }
581
- }, {
582
- key: "seekPercentage",
583
- value: function seekPercentage(percentage) {
584
- var seekTo = percentage > 0 ? this._duration * (percentage / 100) : this._duration;
585
- this.seek(seekTo);
586
- }
587
- }, {
588
- key: "seek",
589
- value: function seek(time) {
590
- if (time < 0) {
591
- core.Log.warn('Attempt to seek to a negative time. Resetting to live point. Use seekToLivePoint() to seek to the live point.');
592
- time = this.getDuration();
593
- }
594
- time += this._startTime;
595
- this.el.currentTime = time;
596
- }
597
- }, {
598
- key: "seekToLivePoint",
599
- value: function seekToLivePoint() {
600
- this.seek(this.getDuration());
601
- }
602
- }, {
603
- key: "_updateSettings",
604
- value: function _updateSettings() {
605
- if (this._playbackType === core.Playback.VOD) {
606
- this.settings.left = ['playpause', 'position', 'duration'];
607
- } else if (this.dvrEnabled) {
608
- this.settings.left = ['playpause'];
609
- } else {
610
- this.settings.left = ['playstop'];
611
- }
612
- this.settings.seekEnabled = this.isSeekEnabled();
613
- this.trigger(core.Events.PLAYBACK_SETTINGSUPDATE);
322
+ // the frame rate should be retrieved from the HLS.js level information
323
+ // as it is not necessarily exposed directly by the video element
324
+ getFrameRate() {
325
+ if (this._hls && this._hls.levels && this._hls.currentLevel >= 0) {
326
+ const level = this._hls.levels[this._hls.currentLevel];
327
+ return level && level.frameRate ? level.frameRate : null;
328
+ }
329
+ return null;
330
+ }
331
+ getCurrentTime() {
332
+ // e.g. can be < 0 if user pauses near the start
333
+ // eventually they will then be kicked to the end by hlsjs if they run out of buffer
334
+ // before the official start time
335
+ return Math.max(0, this.el.currentTime - this._startTime);
336
+ }
337
+
338
+ // the time that "0" now represents relative to when playback started
339
+ // for a stream with a sliding window this will increase as content is
340
+ // removed from the beginning
341
+ getStartTimeOffset() {
342
+ return this._startTime;
343
+ }
344
+ seekPercentage(percentage) {
345
+ const seekTo = percentage > 0 ? this._duration * (percentage / 100) : this._duration;
346
+ this.seek(seekTo);
347
+ }
348
+ seek(time) {
349
+ if (time < 0) {
350
+ core.Log.warn('Attempt to seek to a negative time. Resetting to live point. Use seekToLivePoint() to seek to the live point.');
351
+ time = this.getDuration();
614
352
  }
615
- }, {
616
- key: "_onHLSJSError",
617
- value: function _onHLSJSError(evt, data) {
618
- var error = {
619
- code: "".concat(data.type, "_").concat(data.details),
620
- description: "".concat(this.name, " error: type: ").concat(data.type, ", details: ").concat(data.details),
621
- raw: data
622
- };
623
- var formattedError;
624
- if (data.response) error.description += ", response: ".concat(JSON.stringify(data.response));
625
- // only report/handle errors if they are fatal
626
- // hlsjs should automatically handle non fatal errors
627
- if (data.fatal) {
628
- if (this._recoverAttemptsRemaining > 0) {
629
- this._recoverAttemptsRemaining -= 1;
630
- switch (data.type) {
631
- case HLSJS.ErrorTypes.NETWORK_ERROR:
632
- switch (data.details) {
633
- // The following network errors cannot be recovered with HLS.startLoad()
634
- // For more details, see https://github.com/video-dev/hls.js/blob/master/doc/design.md#error-detection-and-handling
635
- // For "level load" fatal errors, see https://github.com/video-dev/hls.js/issues/1138
636
- case HLSJS.ErrorDetails.MANIFEST_LOAD_ERROR:
637
- case HLSJS.ErrorDetails.MANIFEST_LOAD_TIMEOUT:
638
- case HLSJS.ErrorDetails.MANIFEST_PARSING_ERROR:
639
- case HLSJS.ErrorDetails.LEVEL_LOAD_ERROR:
640
- case HLSJS.ErrorDetails.LEVEL_LOAD_TIMEOUT:
641
- core.Log.error('hlsjs: unrecoverable network fatal error.', {
642
- evt: evt,
643
- data: data
644
- });
645
- formattedError = this.createError(error);
646
- this.trigger(core.Events.PLAYBACK_ERROR, formattedError);
647
- this.stop();
648
- break;
649
- default:
650
- core.Log.warn('hlsjs: trying to recover from network error.', {
651
- evt: evt,
652
- data: data
653
- });
654
- error.level = core.PlayerError.Levels.WARN;
655
- this._hls.startLoad();
656
- break;
657
- }
658
- break;
659
- case HLSJS.ErrorTypes.MEDIA_ERROR:
660
- core.Log.warn('hlsjs: trying to recover from media error.', {
661
- evt: evt,
662
- data: data
663
- });
664
- error.level = core.PlayerError.Levels.WARN;
665
- this._recover(evt, data, error);
666
- break;
667
- default:
668
- core.Log.error('hlsjs: could not recover from error.', {
669
- evt: evt,
670
- data: data
671
- });
672
- formattedError = this.createError(error);
673
- this.trigger(core.Events.PLAYBACK_ERROR, formattedError);
674
- this.stop();
675
- break;
676
- }
677
- } else {
678
- core.Log.error('hlsjs: could not recover from error after maximum number of attempts.', {
679
- evt: evt,
680
- data: data
681
- });
682
- formattedError = this.createError(error);
683
- this.trigger(core.Events.PLAYBACK_ERROR, formattedError);
684
- this.stop();
353
+ time += this._startTime;
354
+ this.el.currentTime = time;
355
+ }
356
+ seekToLivePoint() {
357
+ this.seek(this.getDuration());
358
+ }
359
+ _updateSettings() {
360
+ if (this._playbackType === core.Playback.VOD) {
361
+ this.settings.left = ['playpause', 'position', 'duration'];
362
+ } else if (this.dvrEnabled) {
363
+ this.settings.left = ['playpause'];
364
+ } else {
365
+ this.settings.left = ['playstop'];
366
+ }
367
+ this.settings.seekEnabled = this.isSeekEnabled();
368
+ this.trigger(core.Events.PLAYBACK_SETTINGSUPDATE);
369
+ }
370
+ _onHLSJSError(evt, data) {
371
+ const error = {
372
+ code: `${data.type}_${data.details}`,
373
+ description: `${this.name} error: type: ${data.type}, details: ${data.details}`,
374
+ raw: data
375
+ };
376
+ let formattedError;
377
+ if (data.response) error.description += `, response: ${JSON.stringify(data.response)}`;
378
+ // only report/handle errors if they are fatal
379
+ // hlsjs should automatically handle non fatal errors
380
+ if (data.fatal) {
381
+ if (this._recoverAttemptsRemaining > 0) {
382
+ this._recoverAttemptsRemaining -= 1;
383
+ switch (data.type) {
384
+ case HLSJS.ErrorTypes.NETWORK_ERROR:
385
+ switch (data.details) {
386
+ // The following network errors cannot be recovered with HLS.startLoad()
387
+ // For more details, see https://github.com/video-dev/hls.js/blob/master/doc/design.md#error-detection-and-handling
388
+ // For "level load" fatal errors, see https://github.com/video-dev/hls.js/issues/1138
389
+ case HLSJS.ErrorDetails.MANIFEST_LOAD_ERROR:
390
+ case HLSJS.ErrorDetails.MANIFEST_LOAD_TIMEOUT:
391
+ case HLSJS.ErrorDetails.MANIFEST_PARSING_ERROR:
392
+ case HLSJS.ErrorDetails.LEVEL_LOAD_ERROR:
393
+ case HLSJS.ErrorDetails.LEVEL_LOAD_TIMEOUT:
394
+ core.Log.error('hlsjs: unrecoverable network fatal error.', {
395
+ evt,
396
+ data
397
+ });
398
+ formattedError = this.createError(error);
399
+ this.trigger(core.Events.PLAYBACK_ERROR, formattedError);
400
+ this.stop();
401
+ break;
402
+ default:
403
+ core.Log.warn('hlsjs: trying to recover from network error.', {
404
+ evt,
405
+ data
406
+ });
407
+ error.level = core.PlayerError.Levels.WARN;
408
+ this._hls.startLoad();
409
+ break;
410
+ }
411
+ break;
412
+ case HLSJS.ErrorTypes.MEDIA_ERROR:
413
+ core.Log.warn('hlsjs: trying to recover from media error.', {
414
+ evt,
415
+ data
416
+ });
417
+ error.level = core.PlayerError.Levels.WARN;
418
+ this._recover(evt, data, error);
419
+ break;
420
+ default:
421
+ core.Log.error('hlsjs: could not recover from error.', {
422
+ evt,
423
+ data
424
+ });
425
+ formattedError = this.createError(error);
426
+ this.trigger(core.Events.PLAYBACK_ERROR, formattedError);
427
+ this.stop();
428
+ break;
685
429
  }
686
430
  } else {
687
- // Transforms HLSJS.ErrorDetails.KEY_LOAD_ERROR non-fatal error to
688
- // playback fatal error if triggerFatalErrorOnResourceDenied playback
689
- // option is set. HLSJS.ErrorTypes.KEY_SYSTEM_ERROR are fatal errors
690
- // and therefore already handled.
691
- if (this.options.playback.triggerFatalErrorOnResourceDenied && this._keyIsDenied(data)) {
692
- core.Log.error('hlsjs: could not load decrypt key.', {
693
- evt: evt,
694
- data: data
695
- });
696
- formattedError = this.createError(error);
697
- this.trigger(core.Events.PLAYBACK_ERROR, formattedError);
698
- this.stop();
699
- return;
700
- }
701
- error.level = core.PlayerError.Levels.WARN;
702
- core.Log.warn('hlsjs: non-fatal error occurred', {
703
- evt: evt,
704
- data: data
431
+ core.Log.error('hlsjs: could not recover from error after maximum number of attempts.', {
432
+ evt,
433
+ data
705
434
  });
435
+ formattedError = this.createError(error);
436
+ this.trigger(core.Events.PLAYBACK_ERROR, formattedError);
437
+ this.stop();
706
438
  }
707
- }
708
- }, {
709
- key: "_keyIsDenied",
710
- value: function _keyIsDenied(data) {
711
- return data.type === HLSJS.ErrorTypes.NETWORK_ERROR && data.details === HLSJS.ErrorDetails.KEY_LOAD_ERROR && data.response && data.response.code >= 400;
712
- }
713
- }, {
714
- key: "_onTimeUpdate",
715
- value: function _onTimeUpdate() {
716
- var update = {
717
- current: this.getCurrentTime(),
718
- total: this.getDuration(),
719
- firstFragDateTime: this.getProgramDateTime()
720
- };
721
- var shouldThrottle = this._shouldThrottleTimeUpdate(update);
722
- if (shouldThrottle) return;
723
- this._lastTimeUpdate = update;
724
- this._lastTimeUpdateFiredTime = this._now;
725
- this.trigger(core.Events.PLAYBACK_TIMEUPDATE, update, this.name);
726
- }
727
- }, {
728
- key: "_shouldThrottleTimeUpdate",
729
- value: function _shouldThrottleTimeUpdate(update) {
730
- var isSameTime = Math.abs(update.current - this._lastTimeUpdate.current) < this._timeUpdateFiringRate;
731
- var isSameDuration = Math.abs(update.total - this._lastTimeUpdate.total) < this._durationChangeMinOffset;
732
- var isSameFirstFragDateTime = update.firstFragDateTime === this._lastTimeUpdate.firstFragDateTime;
733
- var isSameEventPayload = isSameTime && isSameDuration && isSameFirstFragDateTime;
734
- var isThrottled = this._now - this._lastTimeUpdateFiredTime < this._timeUpdateThrottleDelay;
735
- return isSameEventPayload && isThrottled;
736
- }
737
- }, {
738
- key: "_onDurationChange",
739
- value: function _onDurationChange() {
740
- var duration = this.getDuration();
741
- var isSameDuration = Math.abs(this._lastDuration - duration) < this._durationChangeMinOffset;
742
- if (isSameDuration) return;
743
- this._lastDuration = duration;
744
- _superPropGet(HlsjsPlayback, "_onDurationChange", this, 3)([]);
745
- }
746
- }, {
747
- key: "_onProgress",
748
- value: function _onProgress() {
749
- if (!this.el.buffered.length) return;
750
- var buffered = [];
751
- var bufferedPos = 0;
752
- for (var i = 0; i < this.el.buffered.length; i++) {
753
- buffered = [].concat(_toConsumableArray(buffered), [{
754
- // for a stream with sliding window dvr something that is buffered my slide off the start of the timeline
755
- start: Math.max(0, this.el.buffered.start(i) - this._playableRegionStartTime),
756
- end: Math.max(0, this.el.buffered.end(i) - this._playableRegionStartTime)
757
- }]);
758
- if (this.el.currentTime >= buffered[i].start && this.el.currentTime <= buffered[i].end) {
759
- bufferedPos = i;
760
- }
439
+ } else {
440
+ // Transforms HLSJS.ErrorDetails.KEY_LOAD_ERROR non-fatal error to
441
+ // playback fatal error if triggerFatalErrorOnResourceDenied playback
442
+ // option is set. HLSJS.ErrorTypes.KEY_SYSTEM_ERROR are fatal errors
443
+ // and therefore already handled.
444
+ if (this.options.playback.triggerFatalErrorOnResourceDenied && this._keyIsDenied(data)) {
445
+ core.Log.error('hlsjs: could not load decrypt key.', {
446
+ evt,
447
+ data
448
+ });
449
+ formattedError = this.createError(error);
450
+ this.trigger(core.Events.PLAYBACK_ERROR, formattedError);
451
+ this.stop();
452
+ return;
761
453
  }
762
- var progress = {
763
- start: buffered[bufferedPos].start,
764
- current: buffered[bufferedPos].end,
765
- total: this.getDuration()
766
- };
767
- this.trigger(core.Events.PLAYBACK_PROGRESS, progress, buffered);
768
- }
769
- }, {
770
- key: "load",
771
- value: function load(url) {
772
- this._stopTimeUpdateTimer();
773
- this.options.src = url;
774
- this._setup();
775
- }
776
- }, {
777
- key: "play",
778
- value: function play() {
779
- !this._hls && this._setup();
780
- !this._manifestLoading && !this.options.hlsPlayback.preload && this._hls.loadSource(this.options.src);
781
- _superPropGet(HlsjsPlayback, "play", this, 3)([]);
782
- this._startTimeUpdateTimer();
783
- }
784
- }, {
785
- key: "pause",
786
- value: function pause() {
787
- if (!this._hls) return;
788
- this.el.pause();
789
- }
790
- }, {
791
- key: "stop",
792
- value: function stop() {
793
- this._stopTimeUpdateTimer();
794
- if (this._hls) _superPropGet(HlsjsPlayback, "stop", this, 3)([]);
795
- this._destroyHLSInstance();
796
- }
797
- }, {
798
- key: "destroy",
799
- value: function destroy() {
800
- this._stopTimeUpdateTimer();
801
- this._destroyHLSInstance();
802
- _superPropGet(HlsjsPlayback, "destroy", this, 3)([]);
454
+ error.level = core.PlayerError.Levels.WARN;
455
+ core.Log.warn('hlsjs: non-fatal error occurred', {
456
+ evt,
457
+ data
458
+ });
803
459
  }
804
- }, {
805
- key: "_updatePlaybackType",
806
- value: function _updatePlaybackType(evt, data) {
807
- this._playbackType = data.details.live ? core.Playback.LIVE : core.Playback.VOD;
808
- this._onLevelUpdated(evt, data);
809
- // Live stream subtitle tracks detection hack (may not immediately available)
810
- if (this._ccTracksUpdated && this._playbackType === core.Playback.LIVE && this.hasClosedCaptionsTracks) {
811
- this._onSubtitleLoaded();
460
+ }
461
+ _keyIsDenied(data) {
462
+ return data.type === HLSJS.ErrorTypes.NETWORK_ERROR && data.details === HLSJS.ErrorDetails.KEY_LOAD_ERROR && data.response && data.response.code >= 400;
463
+ }
464
+ _onTimeUpdate() {
465
+ const update = {
466
+ current: this.getCurrentTime(),
467
+ total: this.getDuration(),
468
+ firstFragDateTime: this.getProgramDateTime()
469
+ };
470
+ const shouldThrottle = this._shouldThrottleTimeUpdate(update);
471
+ if (shouldThrottle) return;
472
+ this._lastTimeUpdate = update;
473
+ this._lastTimeUpdateFiredTime = this._now;
474
+ this.trigger(core.Events.PLAYBACK_TIMEUPDATE, update, this.name);
475
+ }
476
+ _shouldThrottleTimeUpdate(update) {
477
+ const isSameTime = Math.abs(update.current - this._lastTimeUpdate.current) < this._timeUpdateFiringRate;
478
+ const isSameDuration = Math.abs(update.total - this._lastTimeUpdate.total) < this._durationChangeMinOffset;
479
+ const isSameFirstFragDateTime = update.firstFragDateTime === this._lastTimeUpdate.firstFragDateTime;
480
+ const isSameEventPayload = isSameTime && isSameDuration && isSameFirstFragDateTime;
481
+ const isThrottled = this._now - this._lastTimeUpdateFiredTime < this._timeUpdateThrottleDelay;
482
+ return isSameEventPayload && isThrottled;
483
+ }
484
+ _onDurationChange() {
485
+ const duration = this.getDuration();
486
+ const isSameDuration = Math.abs(this._lastDuration - duration) < this._durationChangeMinOffset;
487
+ if (isSameDuration) return;
488
+ this._lastDuration = duration;
489
+ super._onDurationChange();
490
+ }
491
+ _onProgress() {
492
+ if (!this.el.buffered.length) return;
493
+ let buffered = [];
494
+ let bufferedPos = 0;
495
+ for (let i = 0; i < this.el.buffered.length; i++) {
496
+ buffered = [...buffered, {
497
+ // for a stream with sliding window dvr something that is buffered my slide off the start of the timeline
498
+ start: Math.max(0, this.el.buffered.start(i) - this._playableRegionStartTime),
499
+ end: Math.max(0, this.el.buffered.end(i) - this._playableRegionStartTime)
500
+ }];
501
+ if (this.el.currentTime >= buffered[i].start && this.el.currentTime <= buffered[i].end) {
502
+ bufferedPos = i;
812
503
  }
813
504
  }
814
- }, {
815
- key: "_fillLevels",
816
- value: function _fillLevels() {
817
- this._levels = this._hls.levels.map(function (level, index) {
818
- return {
819
- id: index,
820
- level: level,
821
- label: "".concat(level.bitrate / 1000, "Kbps")
822
- };
823
- });
824
- this.trigger(core.Events.PLAYBACK_LEVELS_AVAILABLE, this._levels);
505
+ const progress = {
506
+ start: buffered[bufferedPos].start,
507
+ current: buffered[bufferedPos].end,
508
+ total: this.getDuration()
509
+ };
510
+ this.trigger(core.Events.PLAYBACK_PROGRESS, progress, buffered);
511
+ }
512
+ load(url) {
513
+ this._stopTimeUpdateTimer();
514
+ this.options.src = url;
515
+ this._setup();
516
+ }
517
+ play() {
518
+ !this._hls && this._setup();
519
+ !this._manifestLoading && !this.options.hlsPlayback.preload && this._hls.loadSource(this.options.src);
520
+ super.play();
521
+ this._startTimeUpdateTimer();
522
+ }
523
+ pause() {
524
+ if (!this._hls) return;
525
+ this.el.pause();
526
+ }
527
+ stop() {
528
+ this._stopTimeUpdateTimer();
529
+ if (this._hls) super.stop();
530
+ this._destroyHLSInstance();
531
+ }
532
+ destroy() {
533
+ this._stopTimeUpdateTimer();
534
+ this._destroyHLSInstance();
535
+ super.destroy();
536
+ }
537
+ _updatePlaybackType(evt, data) {
538
+ this._playbackType = data.details.live ? core.Playback.LIVE : core.Playback.VOD;
539
+ this._onLevelUpdated(evt, data);
540
+ // Live stream subtitle tracks detection hack (may not immediately available)
541
+ if (this._ccTracksUpdated && this._playbackType === core.Playback.LIVE && this.hasClosedCaptionsTracks) {
542
+ this._onSubtitleLoaded();
825
543
  }
826
- }, {
827
- key: "_onLevelUpdated",
828
- value: function _onLevelUpdated(evt, data) {
829
- this._segmentTargetDuration = data.details.targetduration;
830
- this._playlistType = data.details.type || null;
831
- var startTimeChanged = false;
832
- var durationChanged = false;
833
- var fragments = data.details.fragments;
834
- var previousPlayableRegionStartTime = this._playableRegionStartTime;
835
- var previousPlayableRegionDuration = this._playableRegionDuration;
836
- if (fragments.length === 0) return;
837
- // #EXT-X-PROGRAM-DATE-TIME
838
- if (fragments[0].rawProgramDateTime) {
839
- this._programDateTime = fragments[0].rawProgramDateTime;
840
- }
841
- if (this._playableRegionStartTime !== fragments[0].start) {
842
- startTimeChanged = true;
843
- this._playableRegionStartTime = fragments[0].start;
844
- }
845
- if (startTimeChanged) {
846
- if (!this._localStartTimeCorrelation) {
847
- // set the correlation to map to middle of the extrapolation window
544
+ }
545
+ _fillLevels() {
546
+ this._levels = this._hls.levels.map((level, index) => {
547
+ return {
548
+ id: index,
549
+ level: level,
550
+ label: `${level.bitrate / 1000}Kbps`
551
+ };
552
+ });
553
+ this.trigger(core.Events.PLAYBACK_LEVELS_AVAILABLE, this._levels);
554
+ }
555
+ _onLevelUpdated(evt, data) {
556
+ this._segmentTargetDuration = data.details.targetduration;
557
+ this._playlistType = data.details.type || null;
558
+ let startTimeChanged = false;
559
+ let durationChanged = false;
560
+ const fragments = data.details.fragments;
561
+ const previousPlayableRegionStartTime = this._playableRegionStartTime;
562
+ const previousPlayableRegionDuration = this._playableRegionDuration;
563
+ if (fragments.length === 0) return;
564
+ // #EXT-X-PROGRAM-DATE-TIME
565
+ if (fragments[0].rawProgramDateTime) {
566
+ this._programDateTime = fragments[0].rawProgramDateTime;
567
+ }
568
+ if (this._playableRegionStartTime !== fragments[0].start) {
569
+ startTimeChanged = true;
570
+ this._playableRegionStartTime = fragments[0].start;
571
+ }
572
+ if (startTimeChanged) {
573
+ if (!this._localStartTimeCorrelation) {
574
+ // set the correlation to map to middle of the extrapolation window
575
+ this._localStartTimeCorrelation = {
576
+ local: this._now,
577
+ remote: (fragments[0].start + this._extrapolatedWindowDuration / 2) * 1000
578
+ };
579
+ } else {
580
+ // check if the correlation still works
581
+ const corr = this._localStartTimeCorrelation;
582
+ const timePassed = this._now - corr.local;
583
+ // this should point to a time within the extrapolation window
584
+ const startTime = (corr.remote + timePassed) / 1000;
585
+ if (startTime < fragments[0].start) {
586
+ // our start time is now earlier than the first chunk
587
+ // (maybe the chunk was removed early)
588
+ // reset correlation so that it sits at the beginning of the first available chunk
848
589
  this._localStartTimeCorrelation = {
849
590
  local: this._now,
850
- remote: (fragments[0].start + this._extrapolatedWindowDuration / 2) * 1000
591
+ remote: fragments[0].start * 1000
592
+ };
593
+ } else if (startTime > previousPlayableRegionStartTime + this._extrapolatedWindowDuration) {
594
+ // start time was past the end of the old extrapolation window (so would have been capped)
595
+ // see if now that time would be inside the window, and if it would be set the correlation
596
+ // so that it resumes from the time it was at at the end of the old window
597
+ // update the correlation so that the time starts counting again from the value it's on now
598
+ this._localStartTimeCorrelation = {
599
+ local: this._now,
600
+ remote: Math.max(fragments[0].start, previousPlayableRegionStartTime + this._extrapolatedWindowDuration) * 1000
851
601
  };
852
- } else {
853
- // check if the correlation still works
854
- var corr = this._localStartTimeCorrelation;
855
- var timePassed = this._now - corr.local;
856
- // this should point to a time within the extrapolation window
857
- var startTime = (corr.remote + timePassed) / 1000;
858
- if (startTime < fragments[0].start) {
859
- // our start time is now earlier than the first chunk
860
- // (maybe the chunk was removed early)
861
- // reset correlation so that it sits at the beginning of the first available chunk
862
- this._localStartTimeCorrelation = {
863
- local: this._now,
864
- remote: fragments[0].start * 1000
865
- };
866
- } else if (startTime > previousPlayableRegionStartTime + this._extrapolatedWindowDuration) {
867
- // start time was past the end of the old extrapolation window (so would have been capped)
868
- // see if now that time would be inside the window, and if it would be set the correlation
869
- // so that it resumes from the time it was at at the end of the old window
870
- // update the correlation so that the time starts counting again from the value it's on now
871
- this._localStartTimeCorrelation = {
872
- local: this._now,
873
- remote: Math.max(fragments[0].start, previousPlayableRegionStartTime + this._extrapolatedWindowDuration) * 1000
874
- };
875
- }
876
- }
877
- }
878
- var newDuration = data.details.totalduration;
879
- // if it's a live stream then shorten the duration to remove access
880
- // to the area after hlsjs's live sync point
881
- // seeks to areas after this point sometimes have issues
882
- if (this._playbackType === core.Playback.LIVE) {
883
- var fragmentTargetDuration = data.details.targetduration;
884
- var hlsjsConfig = this.options.playback.hlsjsConfig || {};
885
- var liveSyncDurationCount = hlsjsConfig.liveSyncDurationCount || HLSJS.DefaultConfig.liveSyncDurationCount;
886
- var hiddenAreaDuration = fragmentTargetDuration * liveSyncDurationCount;
887
- if (hiddenAreaDuration <= newDuration) {
888
- newDuration -= hiddenAreaDuration;
889
- this._durationExcludesAfterLiveSyncPoint = true;
890
- } else {
891
- this._durationExcludesAfterLiveSyncPoint = false;
892
602
  }
893
603
  }
894
- if (newDuration !== this._playableRegionDuration) {
895
- durationChanged = true;
896
- this._playableRegionDuration = newDuration;
604
+ }
605
+ let newDuration = data.details.totalduration;
606
+ // if it's a live stream then shorten the duration to remove access
607
+ // to the area after hlsjs's live sync point
608
+ // seeks to areas after this point sometimes have issues
609
+ if (this._playbackType === core.Playback.LIVE) {
610
+ const fragmentTargetDuration = data.details.targetduration;
611
+ const hlsjsConfig = this.options.playback.hlsjsConfig || {};
612
+ const liveSyncDurationCount = hlsjsConfig.liveSyncDurationCount || HLSJS.DefaultConfig.liveSyncDurationCount;
613
+ const hiddenAreaDuration = fragmentTargetDuration * liveSyncDurationCount;
614
+ if (hiddenAreaDuration <= newDuration) {
615
+ newDuration -= hiddenAreaDuration;
616
+ this._durationExcludesAfterLiveSyncPoint = true;
617
+ } else {
618
+ this._durationExcludesAfterLiveSyncPoint = false;
897
619
  }
898
- // Note the end time is not the playableRegionDuration
899
- // The end time will always increase even if content is removed from the beginning
900
- var endTime = fragments[0].start + newDuration;
901
- var previousEndTime = previousPlayableRegionStartTime + previousPlayableRegionDuration;
902
- var endTimeChanged = endTime !== previousEndTime;
903
- if (endTimeChanged) {
904
- if (!this._localEndTimeCorrelation) {
905
- // set the correlation to map to the end
620
+ }
621
+ if (newDuration !== this._playableRegionDuration) {
622
+ durationChanged = true;
623
+ this._playableRegionDuration = newDuration;
624
+ }
625
+ // Note the end time is not the playableRegionDuration
626
+ // The end time will always increase even if content is removed from the beginning
627
+ const endTime = fragments[0].start + newDuration;
628
+ const previousEndTime = previousPlayableRegionStartTime + previousPlayableRegionDuration;
629
+ const endTimeChanged = endTime !== previousEndTime;
630
+ if (endTimeChanged) {
631
+ if (!this._localEndTimeCorrelation) {
632
+ // set the correlation to map to the end
633
+ this._localEndTimeCorrelation = {
634
+ local: this._now,
635
+ remote: endTime * 1000
636
+ };
637
+ } else {
638
+ // check if the correlation still works
639
+ const corr = this._localEndTimeCorrelation;
640
+ const timePassed = this._now - corr.local;
641
+ // this should point to a time within the extrapolation window from the end
642
+ const extrapolatedEndTime = (corr.remote + timePassed) / 1000;
643
+ if (extrapolatedEndTime > endTime) {
906
644
  this._localEndTimeCorrelation = {
907
645
  local: this._now,
908
646
  remote: endTime * 1000
909
647
  };
910
- } else {
911
- // check if the correlation still works
912
- var _corr = this._localEndTimeCorrelation;
913
- var _timePassed = this._now - _corr.local;
914
- // this should point to a time within the extrapolation window from the end
915
- var extrapolatedEndTime = (_corr.remote + _timePassed) / 1000;
916
- if (extrapolatedEndTime > endTime) {
917
- this._localEndTimeCorrelation = {
918
- local: this._now,
919
- remote: endTime * 1000
920
- };
921
- } else if (extrapolatedEndTime < endTime - this._extrapolatedWindowDuration) {
922
- // our extrapolated end time is now earlier than the extrapolation window from the actual end time
923
- // (maybe a chunk became available early)
924
- // reset correlation so that it sits at the beginning of the extrapolation window from the end time
925
- this._localEndTimeCorrelation = {
926
- local: this._now,
927
- remote: (endTime - this._extrapolatedWindowDuration) * 1000
928
- };
929
- } else if (extrapolatedEndTime > previousEndTime) {
930
- // end time was past the old end time (so would have been capped)
931
- // set the correlation so that it resumes from the time it was at at the end of the old window
932
- this._localEndTimeCorrelation = {
933
- local: this._now,
934
- remote: previousEndTime * 1000
935
- };
936
- }
648
+ } else if (extrapolatedEndTime < endTime - this._extrapolatedWindowDuration) {
649
+ // our extrapolated end time is now earlier than the extrapolation window from the actual end time
650
+ // (maybe a chunk became available early)
651
+ // reset correlation so that it sits at the beginning of the extrapolation window from the end time
652
+ this._localEndTimeCorrelation = {
653
+ local: this._now,
654
+ remote: (endTime - this._extrapolatedWindowDuration) * 1000
655
+ };
656
+ } else if (extrapolatedEndTime > previousEndTime) {
657
+ // end time was past the old end time (so would have been capped)
658
+ // set the correlation so that it resumes from the time it was at at the end of the old window
659
+ this._localEndTimeCorrelation = {
660
+ local: this._now,
661
+ remote: previousEndTime * 1000
662
+ };
937
663
  }
938
664
  }
939
-
940
- // now that the values have been updated call any methods that use on them so they get the updated values
941
- // immediately
942
- durationChanged && this._onDurationChange();
943
- startTimeChanged && this._onProgress();
944
- }
945
- }, {
946
- key: "_onFragmentChanged",
947
- value: function _onFragmentChanged(evt, data) {
948
- this._currentFragment = data.frag;
949
- this.trigger(core.Events.Custom.PLAYBACK_FRAGMENT_CHANGED, data);
950
- }
951
- }, {
952
- key: "_onFragmentLoaded",
953
- value: function _onFragmentLoaded(evt, data) {
954
- this.trigger(core.Events.PLAYBACK_FRAGMENT_LOADED, data);
955
- }
956
- }, {
957
- key: "_onFragmentBuffered",
958
- value: function _onFragmentBuffered(evt, data) {
959
- this.trigger(core.Events.PLAYBACK_FRAGMENT_BUFFERED, data);
960
- }
961
- }, {
962
- key: "_onSubtitleLoaded",
963
- value: function _onSubtitleLoaded() {
964
- // This event may be triggered multiple times
965
- // Setup CC only once (disable CC by default)
966
- if (!this._ccIsSetup) {
967
- this.trigger(core.Events.PLAYBACK_SUBTITLE_AVAILABLE);
968
- var trackId = this._playbackType === core.Playback.LIVE ? -1 : this.closedCaptionsTrackId;
969
- this.closedCaptionsTrackId = trackId;
970
- this._ccIsSetup = true;
971
- }
972
- }
973
- }, {
974
- key: "_onLevelSwitch",
975
- value: function _onLevelSwitch(evt, data) {
976
- if (!this.levels.length) this._fillLevels();
977
- this.trigger(core.Events.PLAYBACK_LEVEL_SWITCH_END);
978
- this.trigger(core.Events.PLAYBACK_LEVEL_SWITCH, data);
979
- var currentLevel = this._hls.levels[data.level];
980
- if (currentLevel) {
981
- // TODO should highDefinition be private and maybe have a read only accessor if it's used somewhere
982
- this.highDefinition = currentLevel.height >= 720 || currentLevel.bitrate / 1000 >= 2000;
983
- this.trigger(core.Events.PLAYBACK_HIGHDEFINITIONUPDATE, this.highDefinition);
984
- this.trigger(core.Events.PLAYBACK_BITRATE, {
985
- height: currentLevel.height,
986
- width: currentLevel.width,
987
- bandwidth: currentLevel.bitrate,
988
- bitrate: currentLevel.bitrate,
989
- level: data.level
990
- });
991
- }
992
665
  }
993
- }, {
994
- key: "dvrEnabled",
995
- get: function get() {
996
- // enabled when:
997
- // - the duration does not include content after hlsjs's live sync point
998
- // - the playable region duration is longer than the configured duration to enable dvr after
999
- // - the playback type is LIVE.
1000
- return this._durationExcludesAfterLiveSyncPoint && this._duration >= this._minDvrSize && this.getPlaybackType() === core.Playback.LIVE;
1001
- }
1002
- }, {
1003
- key: "getPlaybackType",
1004
- value: function getPlaybackType() {
1005
- return this._playbackType;
1006
- }
1007
- }, {
1008
- key: "isSeekEnabled",
1009
- value: function isSeekEnabled() {
1010
- return this._playbackType === core.Playback.VOD || this.dvrEnabled;
666
+
667
+ // now that the values have been updated call any methods that use on them so they get the updated values
668
+ // immediately
669
+ durationChanged && this._onDurationChange();
670
+ startTimeChanged && this._onProgress();
671
+ }
672
+ _onFragmentChanged(evt, data) {
673
+ this._currentFragment = data.frag;
674
+ this.trigger(core.Events.Custom.PLAYBACK_FRAGMENT_CHANGED, data);
675
+ }
676
+ _onFragmentLoaded(evt, data) {
677
+ this.trigger(core.Events.PLAYBACK_FRAGMENT_LOADED, data);
678
+ }
679
+ _onFragmentBuffered(evt, data) {
680
+ this.trigger(core.Events.PLAYBACK_FRAGMENT_BUFFERED, data);
681
+ }
682
+ _onSubtitleLoaded() {
683
+ // This event may be triggered multiple times
684
+ // Setup CC only once (disable CC by default)
685
+ if (!this._ccIsSetup) {
686
+ this.trigger(core.Events.PLAYBACK_SUBTITLE_AVAILABLE);
687
+ const trackId = this._playbackType === core.Playback.LIVE ? -1 : this.closedCaptionsTrackId;
688
+ this.closedCaptionsTrackId = trackId;
689
+ this._ccIsSetup = true;
1011
690
  }
1012
- }], [{
1013
- key: "HLSJS",
1014
- get: function get() {
1015
- return HLSJS;
691
+ }
692
+ _onLevelSwitch(evt, data) {
693
+ if (!this.levels.length) this._fillLevels();
694
+ this.trigger(core.Events.PLAYBACK_LEVEL_SWITCH_END);
695
+ this.trigger(core.Events.PLAYBACK_LEVEL_SWITCH, data);
696
+ const currentLevel = this._hls.levels[data.level];
697
+ if (currentLevel) {
698
+ // TODO should highDefinition be private and maybe have a read only accessor if it's used somewhere
699
+ this.highDefinition = currentLevel.height >= 720 || currentLevel.bitrate / 1000 >= 2000;
700
+ this.trigger(core.Events.PLAYBACK_HIGHDEFINITIONUPDATE, this.highDefinition);
701
+ this.trigger(core.Events.PLAYBACK_BITRATE, {
702
+ height: currentLevel.height,
703
+ width: currentLevel.width,
704
+ bandwidth: currentLevel.bitrate,
705
+ bitrate: currentLevel.bitrate,
706
+ level: data.level
707
+ });
1016
708
  }
1017
- }]);
1018
- }(core.HTML5Video);
709
+ }
710
+ get dvrEnabled() {
711
+ // enabled when:
712
+ // - the duration does not include content after hlsjs's live sync point
713
+ // - the playable region duration is longer than the configured duration to enable dvr after
714
+ // - the playback type is LIVE.
715
+ return this._durationExcludesAfterLiveSyncPoint && this._duration >= this._minDvrSize && this.getPlaybackType() === core.Playback.LIVE;
716
+ }
717
+ getPlaybackType() {
718
+ return this._playbackType;
719
+ }
720
+ isSeekEnabled() {
721
+ return this._playbackType === core.Playback.VOD || this.dvrEnabled;
722
+ }
723
+ }
1019
724
  HlsjsPlayback.canPlay = function (resource, mimeType) {
1020
- var resourceParts = resource.split('?')[0].match(/.*\.(.*)$/) || [];
1021
- var isHls = resourceParts.length > 1 && resourceParts[1].toLowerCase() === 'm3u8' || listContainsIgnoreCase(mimeType, ['application/vnd.apple.mpegurl', 'application/x-mpegURL']);
725
+ const resourceParts = resource.split('?')[0].match(/.*\.(.*)$/) || [];
726
+ const isHls = resourceParts.length > 1 && resourceParts[1].toLowerCase() === 'm3u8' || listContainsIgnoreCase(mimeType, ['application/vnd.apple.mpegurl', 'application/x-mpegURL']);
1022
727
  return !!(HLSJS.isSupported() && isHls);
1023
728
  };
1024
729