Blame view

node_modules/videojs-contrib-hls/es5/playlist.js 17.3 KB
2a09d1a4   liuqimichale   添加宜春 天水 宣化
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
  /**
   * @file playlist.js
   *
   * Playlist related utilities.
   */
  'use strict';
  
  Object.defineProperty(exports, '__esModule', {
    value: true
  });
  
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
  
  var _videoJs = require('video.js');
  
  var _globalWindow = require('global/window');
  
  var _globalWindow2 = _interopRequireDefault(_globalWindow);
  
  /**
   * walk backward until we find a duration we can use
   * or return a failure
   *
   * @param {Playlist} playlist the playlist to walk through
   * @param {Number} endSequence the mediaSequence to stop walking on
   */
  
  var backwardDuration = function backwardDuration(playlist, endSequence) {
    var result = 0;
    var i = endSequence - playlist.mediaSequence;
    // if a start time is available for segment immediately following
    // the interval, use it
    var segment = playlist.segments[i];
  
    // Walk backward until we find the latest segment with timeline
    // information that is earlier than endSequence
    if (segment) {
      if (typeof segment.start !== 'undefined') {
        return { result: segment.start, precise: true };
      }
      if (typeof segment.end !== 'undefined') {
        return {
          result: segment.end - segment.duration,
          precise: true
        };
      }
    }
    while (i--) {
      segment = playlist.segments[i];
      if (typeof segment.end !== 'undefined') {
        return { result: result + segment.end, precise: true };
      }
  
      result += segment.duration;
  
      if (typeof segment.start !== 'undefined') {
        return { result: result + segment.start, precise: true };
      }
    }
    return { result: result, precise: false };
  };
  
  /**
   * walk forward until we find a duration we can use
   * or return a failure
   *
   * @param {Playlist} playlist the playlist to walk through
   * @param {Number} endSequence the mediaSequence to stop walking on
   */
  var forwardDuration = function forwardDuration(playlist, endSequence) {
    var result = 0;
    var segment = undefined;
    var i = endSequence - playlist.mediaSequence;
    // Walk forward until we find the earliest segment with timeline
    // information
  
    for (; i < playlist.segments.length; i++) {
      segment = playlist.segments[i];
      if (typeof segment.start !== 'undefined') {
        return {
          result: segment.start - result,
          precise: true
        };
      }
  
      result += segment.duration;
  
      if (typeof segment.end !== 'undefined') {
        return {
          result: segment.end - result,
          precise: true
        };
      }
    }
    // indicate we didn't find a useful duration estimate
    return { result: -1, precise: false };
  };
  
  /**
    * Calculate the media duration from the segments associated with a
    * playlist. The duration of a subinterval of the available segments
    * may be calculated by specifying an end index.
    *
    * @param {Object} playlist a media playlist object
    * @param {Number=} endSequence an exclusive upper boundary
    * for the playlist.  Defaults to playlist length.
    * @param {Number} expired the amount of time that has dropped
    * off the front of the playlist in a live scenario
    * @return {Number} the duration between the first available segment
    * and end index.
    */
  var intervalDuration = function intervalDuration(playlist, endSequence, expired) {
    var backward = undefined;
    var forward = undefined;
  
    if (typeof endSequence === 'undefined') {
      endSequence = playlist.mediaSequence + playlist.segments.length;
    }
  
    if (endSequence < playlist.mediaSequence) {
      return 0;
    }
  
    // do a backward walk to estimate the duration
    backward = backwardDuration(playlist, endSequence);
    if (backward.precise) {
      // if we were able to base our duration estimate on timing
      // information provided directly from the Media Source, return
      // it
      return backward.result;
    }
  
    // walk forward to see if a precise duration estimate can be made
    // that way
    forward = forwardDuration(playlist, endSequence);
    if (forward.precise) {
      // we found a segment that has been buffered and so it's
      // position is known precisely
      return forward.result;
    }
  
    // return the less-precise, playlist-based duration estimate
    return backward.result + expired;
  };
  
  /**
    * Calculates the duration of a playlist. If a start and end index
    * are specified, the duration will be for the subset of the media
    * timeline between those two indices. The total duration for live
    * playlists is always Infinity.
    *
    * @param {Object} playlist a media playlist object
    * @param {Number=} endSequence an exclusive upper
    * boundary for the playlist. Defaults to the playlist media
    * sequence number plus its length.
    * @param {Number=} expired the amount of time that has
    * dropped off the front of the playlist in a live scenario
    * @return {Number} the duration between the start index and end
    * index.
    */
  var duration = function duration(playlist, endSequence, expired) {
    if (!playlist) {
      return 0;
    }
  
    if (typeof expired !== 'number') {
      expired = 0;
    }
  
    // if a slice of the total duration is not requested, use
    // playlist-level duration indicators when they're present
    if (typeof endSequence === 'undefined') {
      // if present, use the duration specified in the playlist
      if (playlist.totalDuration) {
        return playlist.totalDuration;
      }
  
      // duration should be Infinity for live playlists
      if (!playlist.endList) {
        return _globalWindow2['default'].Infinity;
      }
    }
  
    // calculate the total duration based on the segment durations
    return intervalDuration(playlist, endSequence, expired);
  };
  
  exports.duration = duration;
  /**
    * Calculate the time between two indexes in the current playlist
    * neight the start- nor the end-index need to be within the current
    * playlist in which case, the targetDuration of the playlist is used
    * to approximate the durations of the segments
    *
    * @param {Object} playlist a media playlist object
    * @param {Number} startIndex
    * @param {Number} endIndex
    * @return {Number} the number of seconds between startIndex and endIndex
    */
  var sumDurations = function sumDurations(playlist, startIndex, endIndex) {
    var durations = 0;
  
    if (startIndex > endIndex) {
      var _ref = [endIndex, startIndex];
      startIndex = _ref[0];
      endIndex = _ref[1];
    }
  
    if (startIndex < 0) {
      for (var i = startIndex; i < Math.min(0, endIndex); i++) {
        durations += playlist.targetDuration;
      }
      startIndex = 0;
    }
  
    for (var i = startIndex; i < endIndex; i++) {
      durations += playlist.segments[i].duration;
    }
  
    return durations;
  };
  
  exports.sumDurations = sumDurations;
  /**
   * Determines the media index of the segment corresponding to the safe edge of the live
   * window which is the duration of the last segment plus 2 target durations from the end
   * of the playlist.
   *
   * @param {Object} playlist
   *        a media playlist object
   * @return {Number}
   *         The media index of the segment at the safe live point. 0 if there is no "safe"
   *         point.
   * @function safeLiveIndex
   */
  var safeLiveIndex = function safeLiveIndex(playlist) {
    if (!playlist.segments.length) {
      return 0;
    }
  
    var i = playlist.segments.length - 1;
    var distanceFromEnd = playlist.segments[i].duration || playlist.targetDuration;
    var safeDistance = distanceFromEnd + playlist.targetDuration * 2;
  
    while (i--) {
      distanceFromEnd += playlist.segments[i].duration;
  
      if (distanceFromEnd >= safeDistance) {
        break;
      }
    }
  
    return Math.max(0, i);
  };
  
  exports.safeLiveIndex = safeLiveIndex;
  /**
   * Calculates the playlist end time
   *
   * @param {Object} playlist a media playlist object
   * @param {Number=} expired the amount of time that has
   *                  dropped off the front of the playlist in a live scenario
   * @param {Boolean|false} useSafeLiveEnd a boolean value indicating whether or not the
   *                        playlist end calculation should consider the safe live end
   *                        (truncate the playlist end by three segments). This is normally
   *                        used for calculating the end of the playlist's seekable range.
   * @returns {Number} the end time of playlist
   * @function playlistEnd
   */
  var playlistEnd = function playlistEnd(playlist, expired, useSafeLiveEnd) {
    if (!playlist || !playlist.segments) {
      return null;
    }
    if (playlist.endList) {
      return duration(playlist);
    }
  
    if (expired === null) {
      return null;
    }
  
    expired = expired || 0;
  
    var endSequence = useSafeLiveEnd ? safeLiveIndex(playlist) : playlist.segments.length;
  
    return intervalDuration(playlist, playlist.mediaSequence + endSequence, expired);
  };
  
  exports.playlistEnd = playlistEnd;
  /**
    * Calculates the interval of time that is currently seekable in a
    * playlist. The returned time ranges are relative to the earliest
    * moment in the specified playlist that is still available. A full
    * seekable implementation for live streams would need to offset
    * these values by the duration of content that has expired from the
    * stream.
    *
    * @param {Object} playlist a media playlist object
    * dropped off the front of the playlist in a live scenario
    * @param {Number=} expired the amount of time that has
    * dropped off the front of the playlist in a live scenario
    * @return {TimeRanges} the periods of time that are valid targets
    * for seeking
    */
  var seekable = function seekable(playlist, expired) {
    var useSafeLiveEnd = true;
    var seekableStart = expired || 0;
    var seekableEnd = playlistEnd(playlist, expired, useSafeLiveEnd);
  
    if (seekableEnd === null) {
      return (0, _videoJs.createTimeRange)();
    }
    return (0, _videoJs.createTimeRange)(seekableStart, seekableEnd);
  };
  
  exports.seekable = seekable;
  var isWholeNumber = function isWholeNumber(num) {
    return num - Math.floor(num) === 0;
  };
  
  var roundSignificantDigit = function roundSignificantDigit(increment, num) {
    // If we have a whole number, just add 1 to it
    if (isWholeNumber(num)) {
      return num + increment * 0.1;
    }
  
    var numDecimalDigits = num.toString().split('.')[1].length;
  
    for (var i = 1; i <= numDecimalDigits; i++) {
      var scale = Math.pow(10, i);
      var temp = num * scale;
  
      if (isWholeNumber(temp) || i === numDecimalDigits) {
        return (temp + increment) / scale;
      }
    }
  };
  
  var ceilLeastSignificantDigit = roundSignificantDigit.bind(null, 1);
  var floorLeastSignificantDigit = roundSignificantDigit.bind(null, -1);
  
  /**
   * Determine the index and estimated starting time of the segment that
   * contains a specified playback position in a media playlist.
   *
   * @param {Object} playlist the media playlist to query
   * @param {Number} currentTime The number of seconds since the earliest
   * possible position to determine the containing segment for
   * @param {Number} startIndex
   * @param {Number} startTime
   * @return {Object}
   */
  var getMediaInfoForTime = function getMediaInfoForTime(playlist, currentTime, startIndex, startTime) {
    var i = undefined;
    var segment = undefined;
    var numSegments = playlist.segments.length;
  
    var time = currentTime - startTime;
  
    if (time < 0) {
      // Walk backward from startIndex in the playlist, adding durations
      // until we find a segment that contains `time` and return it
      if (startIndex > 0) {
        for (i = startIndex - 1; i >= 0; i--) {
          segment = playlist.segments[i];
          time += floorLeastSignificantDigit(segment.duration);
          if (time > 0) {
            return {
              mediaIndex: i,
              startTime: startTime - sumDurations(playlist, startIndex, i)
            };
          }
        }
      }
      // We were unable to find a good segment within the playlist
      // so select the first segment
      return {
        mediaIndex: 0,
        startTime: currentTime
      };
    }
  
    // When startIndex is negative, we first walk forward to first segment
    // adding target durations. If we "run out of time" before getting to
    // the first segment, return the first segment
    if (startIndex < 0) {
      for (i = startIndex; i < 0; i++) {
        time -= playlist.targetDuration;
        if (time < 0) {
          return {
            mediaIndex: 0,
            startTime: currentTime
          };
        }
      }
      startIndex = 0;
    }
  
    // Walk forward from startIndex in the playlist, subtracting durations
    // until we find a segment that contains `time` and return it
    for (i = startIndex; i < numSegments; i++) {
      segment = playlist.segments[i];
      time -= ceilLeastSignificantDigit(segment.duration);
      if (time < 0) {
        return {
          mediaIndex: i,
          startTime: startTime + sumDurations(playlist, startIndex, i)
        };
      }
    }
  
    // We are out of possible candidates so load the last one...
    return {
      mediaIndex: numSegments - 1,
      startTime: currentTime
    };
  };
  
  exports.getMediaInfoForTime = getMediaInfoForTime;
  /**
   * Check whether the playlist is blacklisted or not.
   *
   * @param {Object} playlist the media playlist object
   * @return {boolean} whether the playlist is blacklisted or not
   * @function isBlacklisted
   */
  var isBlacklisted = function isBlacklisted(playlist) {
    return playlist.excludeUntil && playlist.excludeUntil > Date.now();
  };
  
  exports.isBlacklisted = isBlacklisted;
  /**
   * Check whether the playlist is compatible with current playback configuration or has
   * been blacklisted permanently for being incompatible.
   *
   * @param {Object} playlist the media playlist object
   * @return {boolean} whether the playlist is incompatible or not
   * @function isIncompatible
   */
  var isIncompatible = function isIncompatible(playlist) {
    return playlist.excludeUntil && playlist.excludeUntil === Infinity;
  };
  
  exports.isIncompatible = isIncompatible;
  /**
   * Check whether the playlist is enabled or not.
   *
   * @param {Object} playlist the media playlist object
   * @return {boolean} whether the playlist is enabled or not
   * @function isEnabled
   */
  var isEnabled = function isEnabled(playlist) {
    var blacklisted = isBlacklisted(playlist);
  
    return !playlist.disabled && !blacklisted;
  };
  
  exports.isEnabled = isEnabled;
  /**
   * Check whether the playlist has been manually disabled through the representations api.
   *
   * @param {Object} playlist the media playlist object
   * @return {boolean} whether the playlist is disabled manually or not
   * @function isDisabled
   */
  var isDisabled = function isDisabled(playlist) {
    return playlist.disabled;
  };
  
  exports.isDisabled = isDisabled;
  /**
   * Returns whether the current playlist is an AES encrypted HLS stream
   *
   * @return {Boolean} true if it's an AES encrypted HLS stream
   */
  var isAes = function isAes(media) {
    for (var i = 0; i < media.segments.length; i++) {
      if (media.segments[i].key) {
        return true;
      }
    }
    return false;
  };
  
  exports.isAes = isAes;
  /**
   * Returns whether the current playlist contains fMP4
   *
   * @return {Boolean} true if the playlist contains fMP4
   */
  var isFmp4 = function isFmp4(media) {
    for (var i = 0; i < media.segments.length; i++) {
      if (media.segments[i].map) {
        return true;
      }
    }
    return false;
  };
  
  exports.isFmp4 = isFmp4;
  /**
   * Checks if the playlist has a value for the specified attribute
   *
   * @param {String} attr
   *        Attribute to check for
   * @param {Object} playlist
   *        The media playlist object
   * @return {Boolean}
   *         Whether the playlist contains a value for the attribute or not
   * @function hasAttribute
   */
  var hasAttribute = function hasAttribute(attr, playlist) {
    return playlist.attributes && playlist.attributes[attr];
  };
  
  exports.hasAttribute = hasAttribute;
  /**
   * Estimates the time required to complete a segment download from the specified playlist
   *
   * @param {Number} segmentDuration
   *        Duration of requested segment
   * @param {Number} bandwidth
   *        Current measured bandwidth of the player
   * @param {Object} playlist
   *        The media playlist object
   * @param {Number=} bytesReceived
   *        Number of bytes already received for the request. Defaults to 0
   * @return {Number|NaN}
   *         The estimated time to request the segment. NaN if bandwidth information for
   *         the given playlist is unavailable
   * @function estimateSegmentRequestTime
   */
  var estimateSegmentRequestTime = function estimateSegmentRequestTime(segmentDuration, bandwidth, playlist) {
    var bytesReceived = arguments.length <= 3 || arguments[3] === undefined ? 0 : arguments[3];
  
    if (!hasAttribute('BANDWIDTH', playlist)) {
      return NaN;
    }
  
    var size = segmentDuration * playlist.attributes.BANDWIDTH;
  
    return (size - bytesReceived * 8) / bandwidth;
  };
  
  exports.estimateSegmentRequestTime = estimateSegmentRequestTime;
  /*
   * Returns whether the current playlist is the lowest rendition
   *
   * @return {Boolean} true if on lowest rendition
   */
  var isLowestEnabledRendition = function isLowestEnabledRendition(master, media) {
    if (master.playlists.length === 1) {
      return true;
    }
  
    var currentBandwidth = media.attributes.BANDWIDTH || Number.MAX_VALUE;
  
    return master.playlists.filter(function (playlist) {
      if (!isEnabled(playlist)) {
        return false;
      }
  
      return (playlist.attributes.BANDWIDTH || 0) < currentBandwidth;
    }).length === 0;
  };
  
  exports.isLowestEnabledRendition = isLowestEnabledRendition;
  // exports
  exports['default'] = {
    duration: duration,
    seekable: seekable,
    safeLiveIndex: safeLiveIndex,
    getMediaInfoForTime: getMediaInfoForTime,
    isEnabled: isEnabled,
    isDisabled: isDisabled,
    isBlacklisted: isBlacklisted,
    isIncompatible: isIncompatible,
    playlistEnd: playlistEnd,
    isAes: isAes,
    isFmp4: isFmp4,
    hasAttribute: hasAttribute,
    estimateSegmentRequestTime: estimateSegmentRequestTime,
    isLowestEnabledRendition: isLowestEnabledRendition
  };