shaka-player 4.0.0 → 4.0.3

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.
Files changed (84) hide show
  1. package/.github/config.yml +1 -0
  2. package/.github/workflows/release-please.yaml +6 -11
  3. package/AUTHORS +1 -0
  4. package/CHANGELOG.md +39 -0
  5. package/CONTRIBUTORS +1 -0
  6. package/README.md +4 -4
  7. package/build/misspellings.txt +1 -0
  8. package/build/test.py +1 -1
  9. package/demo/common/assets.js +1 -1
  10. package/demo/demo.less +0 -1
  11. package/demo/main.js +3 -3
  12. package/dist/controls.css +3 -3
  13. package/dist/controls.css.map +1 -1
  14. package/dist/demo.compiled.debug.js +3 -3
  15. package/dist/demo.compiled.debug.map +2 -2
  16. package/dist/demo.compiled.js +3 -3
  17. package/dist/demo.compiled.map +2 -2
  18. package/dist/demo.css +1 -1
  19. package/dist/demo.css.map +1 -1
  20. package/dist/deps.js +1 -1
  21. package/dist/locales.js +33 -33
  22. package/dist/receiver.compiled.debug.js +1 -1
  23. package/dist/receiver.compiled.debug.map +1 -1
  24. package/dist/receiver.compiled.js +1 -1
  25. package/dist/receiver.compiled.map +1 -1
  26. package/dist/shaka-player.compiled.d.ts +2 -1
  27. package/dist/shaka-player.compiled.debug.d.ts +2 -1
  28. package/dist/shaka-player.compiled.debug.externs.js +4 -2
  29. package/dist/shaka-player.compiled.debug.js +278 -267
  30. package/dist/shaka-player.compiled.debug.map +4 -4
  31. package/dist/shaka-player.compiled.externs.js +4 -2
  32. package/dist/shaka-player.compiled.js +815 -806
  33. package/dist/shaka-player.compiled.map +4 -4
  34. package/dist/shaka-player.ui.d.ts +2 -1
  35. package/dist/shaka-player.ui.debug.d.ts +2 -1
  36. package/dist/shaka-player.ui.debug.externs.js +4 -2
  37. package/dist/shaka-player.ui.debug.js +305 -293
  38. package/dist/shaka-player.ui.debug.map +4 -4
  39. package/dist/shaka-player.ui.externs.js +4 -2
  40. package/dist/shaka-player.ui.js +966 -959
  41. package/dist/shaka-player.ui.map +4 -4
  42. package/docs/tutorials/upgrade.md +4 -2
  43. package/externs/shaka/player.js +15 -2
  44. package/karma.conf.js +4 -0
  45. package/lib/abr/simple_abr_manager.js +2 -1
  46. package/lib/dash/dash_parser.js +2 -2
  47. package/lib/hls/hls_parser.js +211 -82
  48. package/lib/media/drm_engine.js +19 -0
  49. package/lib/media/media_source_engine.js +91 -37
  50. package/lib/media/segment_index.js +26 -1
  51. package/lib/media/segment_reference.js +10 -3
  52. package/lib/media/streaming_engine.js +2 -3
  53. package/lib/offline/indexeddb/storage_mechanism.js +4 -2
  54. package/lib/player.js +165 -107
  55. package/lib/text/vtt_text_parser.js +7 -7
  56. package/lib/util/config_utils.js +25 -0
  57. package/lib/util/error.js +7 -0
  58. package/lib/util/manifest_parser_utils.js +1 -1
  59. package/lib/util/mime_utils.js +62 -6
  60. package/lib/util/periods.js +5 -4
  61. package/lib/util/player_configuration.js +14 -14
  62. package/lib/util/stream_utils.js +5 -2
  63. package/package.json +6 -3
  64. package/test/hls/hls_live_unit.js +79 -7
  65. package/test/hls/hls_parser_unit.js +102 -8
  66. package/test/hls/manifest_text_parser_unit.js +19 -0
  67. package/test/offline/indexeddb_storage_unit.js +1 -1
  68. package/test/offline/storage_compatibility_unit.js +1 -1
  69. package/test/player_external.js +7 -1
  70. package/test/player_integration.js +6 -10
  71. package/test/player_src_equals_integration.js +81 -83
  72. package/test/player_unit.js +98 -1
  73. package/test/test/boot.js +9 -0
  74. package/test/test/externs/filters.js +4 -0
  75. package/test/test/util/simple_fakes.js +31 -2
  76. package/test/text/vtt_text_parser_unit.js +64 -5
  77. package/test/util/mime_utils_unit.js +54 -0
  78. package/test/util/periods_unit.js +79 -0
  79. package/ui/less/containers.less +1 -1
  80. package/ui/less/general.less +1 -1
  81. package/ui/less/range_elements.less +1 -0
  82. package/ui/resolution_selection.js +1 -1
  83. package/.github/pull_request_template.md +0 -10
  84. package/docs/tutorials/gsoc-ideas-list.md +0 -3
@@ -106,6 +106,12 @@ shaka.media.MediaSourceEngine = class {
106
106
 
107
107
  /** @private {string} */
108
108
  this.url_ = '';
109
+
110
+ /** @private {boolean} */
111
+ this.sequenceMode_ = false;
112
+
113
+ /** @private {!shaka.util.PublicPromise.<number>} */
114
+ this.textSequenceModeOffset_ = new shaka.util.PublicPromise();
109
115
  }
110
116
 
111
117
  /**
@@ -331,6 +337,8 @@ shaka.media.MediaSourceEngine = class {
331
337
 
332
338
  await this.mediaSourceOpen_;
333
339
 
340
+ this.sequenceMode_ = sequenceMode;
341
+
334
342
  for (const contentType of streamsByType.keys()) {
335
343
  const stream = streamsByType.get(contentType);
336
344
  goog.asserts.assert(
@@ -348,11 +356,9 @@ shaka.media.MediaSourceEngine = class {
348
356
  mimeType =
349
357
  shaka.media.Transmuxer.convertTsCodecs(contentType, mimeType);
350
358
  }
359
+
351
360
  const sourceBuffer = this.mediaSource_.addSourceBuffer(mimeType);
352
- if (sequenceMode) {
353
- sourceBuffer.mode =
354
- shaka.media.MediaSourceEngine.SourceBufferMode_.SEQUENCE;
355
- }
361
+
356
362
  this.eventManager_.listen(
357
363
  sourceBuffer, 'error',
358
364
  () => this.onError_(contentType));
@@ -515,35 +521,29 @@ shaka.media.MediaSourceEngine = class {
515
521
  * @param {?boolean} hasClosedCaptions True if the buffer contains CEA closed
516
522
  * captions
517
523
  * @param {boolean=} seeked True if we just seeked
518
- * @param {boolean=} sequenceMode True if sequence mode
519
524
  * @return {!Promise}
520
525
  */
521
- async appendBuffer(contentType, data, startTime, endTime, hasClosedCaptions,
522
- seeked, sequenceMode) {
526
+ async appendBuffer(
527
+ contentType, data, startTime, endTime, hasClosedCaptions, seeked) {
523
528
  const ContentType = shaka.util.ManifestParserUtils.ContentType;
524
529
 
525
- if (startTime != null && sequenceMode && contentType != ContentType.TEXT) {
526
- // If we just cleared buffer and is on an unbuffered seek, we need to set
527
- // the new timestampOffset of the sourceBuffer.
528
- // Don't do this for text streams, though, since they don't use
529
- // MediaSource anyway.
530
- if (seeked) {
531
- const timestampOffset = /** @type {number} */ (startTime);
532
- this.enqueueOperation_(
533
- contentType,
534
- () => this.setTimestampOffset_(contentType, timestampOffset));
530
+ if (contentType == ContentType.TEXT) {
531
+ if (this.sequenceMode_) {
532
+ // This won't be known until the first video segment is appended.
533
+ const offset = await this.textSequenceModeOffset_;
534
+ this.textEngine_.setTimestampOffset(offset);
535
535
  }
536
+ await this.textEngine_.appendBuffer(data, startTime, endTime);
537
+ return;
536
538
  }
537
539
 
538
- if (contentType == ContentType.TEXT) {
539
- await this.textEngine_.appendBuffer(data, startTime, endTime);
540
- } else if (this.transmuxers_[contentType]) {
540
+ if (this.transmuxers_[contentType]) {
541
541
  const transmuxedData =
542
542
  await this.transmuxers_[contentType].transmux(data);
543
543
  // For HLS CEA-608/708 CLOSED-CAPTIONS, text data is embedded in
544
544
  // the video stream, so textEngine may not have been initialized.
545
545
  if (!this.textEngine_) {
546
- this.reinitText('text/vtt', sequenceMode || false);
546
+ this.reinitText('text/vtt', this.sequenceMode_);
547
547
  }
548
548
 
549
549
  if (transmuxedData.metadata) {
@@ -562,15 +562,10 @@ shaka.media.MediaSourceEngine = class {
562
562
  closedCaptions, startTime, endTime, videoOffset);
563
563
  }
564
564
 
565
- let transmuxedSegment = transmuxedData.data;
566
- transmuxedSegment = this.workAroundBrokenPlatforms_(
567
- transmuxedSegment, startTime, contentType);
568
-
569
- await this.enqueueOperation_(
570
- contentType, () => this.append_(contentType, transmuxedSegment));
565
+ data = transmuxedData.data;
571
566
  } else if (hasClosedCaptions) {
572
567
  if (!this.textEngine_) {
573
- this.reinitText('text/vtt', sequenceMode || false);
568
+ this.reinitText('text/vtt', this.sequenceMode_);
574
569
  }
575
570
  // If it is the init segment for closed captions, initialize the closed
576
571
  // caption parser.
@@ -585,19 +580,78 @@ shaka.media.MediaSourceEngine = class {
585
580
  closedCaptions, startTime, endTime, videoOffset);
586
581
  }
587
582
  }
583
+ }
588
584
 
589
- data = this.workAroundBrokenPlatforms_(data, startTime, contentType);
585
+ data = this.workAroundBrokenPlatforms_(data, startTime, contentType);
586
+
587
+ const sourceBuffer = this.sourceBuffers_[contentType];
588
+ const SEQUENCE = shaka.media.MediaSourceEngine.SourceBufferMode_.SEQUENCE;
589
+
590
+ if (this.sequenceMode_ && sourceBuffer.mode != SEQUENCE &&
591
+ startTime != null) {
592
+ // This is the first media segment to be appended to a SourceBuffer in
593
+ // sequence mode. We set the mode late so that we can trick MediaSource
594
+ // into extracting a timestamp for us to align text segments in sequence
595
+ // mode.
596
+
597
+ // Timestamps can only be reliably extracted from video, not audio.
598
+ // Packed audio formats do not have internal timestamps at all.
599
+ // Prefer video for this when available.
600
+ const isBestSourceBufferForTimestamps =
601
+ contentType == ContentType.VIDEO ||
602
+ !(ContentType.VIDEO in this.sourceBuffers_);
603
+ if (isBestSourceBufferForTimestamps) {
604
+ // Append the segment in segments mode first, with offset of 0 and an
605
+ // open append window.
606
+ const originalRange =
607
+ [sourceBuffer.appendWindowStart, sourceBuffer.appendWindowEnd];
608
+ sourceBuffer.appendWindowStart = 0;
609
+ sourceBuffer.appendWindowEnd = Infinity;
610
+
611
+ const originalOffset = sourceBuffer.timestampOffset;
612
+ sourceBuffer.timestampOffset = 0;
613
+
614
+ await this.enqueueOperation_(
615
+ contentType, () => this.append_(contentType, data));
616
+
617
+ // Reset the offset and append window.
618
+ sourceBuffer.timestampOffset = originalOffset;
619
+ sourceBuffer.appendWindowStart = originalRange[0];
620
+ sourceBuffer.appendWindowEnd = originalRange[1];
621
+
622
+ // Now get the timestamp of the segment and compute the offset for text
623
+ // segments.
624
+ const mediaStartTime = shaka.media.TimeRangesUtils.bufferStart(
625
+ this.getBuffered_(contentType));
626
+ const textOffset = (startTime || 0) - (mediaStartTime || 0);
627
+ this.textSequenceModeOffset_.resolve(textOffset);
628
+
629
+ // Finally, clear the buffer.
630
+ await this.enqueueOperation_(
631
+ contentType,
632
+ () => this.remove_(contentType, 0, this.mediaSource_.duration));
633
+ }
590
634
 
591
- await this.enqueueOperation_(
592
- contentType,
593
- () => this.append_(contentType, data));
594
- } else {
595
- data = this.workAroundBrokenPlatforms_(data, startTime, contentType);
635
+ // Now switch to sequence mode and fall through to our normal operations.
636
+ sourceBuffer.mode = SEQUENCE;
637
+ }
596
638
 
597
- await this.enqueueOperation_(
598
- contentType,
599
- () => this.append_(contentType, data));
639
+ if (startTime != null && this.sequenceMode_ &&
640
+ contentType != ContentType.TEXT) {
641
+ // In sequence mode, for non-text streams, if we just cleared the buffer
642
+ // and are performing an unbuffered seek, we need to set a new
643
+ // timestampOffset on the sourceBuffer.
644
+ if (seeked) {
645
+ const timestampOffset = /** @type {number} */ (startTime);
646
+ this.enqueueOperation_(
647
+ contentType,
648
+ () => this.setTimestampOffset_(contentType, timestampOffset));
649
+ }
600
650
  }
651
+
652
+ await this.enqueueOperation_(
653
+ contentType,
654
+ () => this.append_(contentType, data));
601
655
  }
602
656
 
603
657
  /**
@@ -94,6 +94,25 @@ shaka.media.SegmentIndex = class {
94
94
  }
95
95
 
96
96
 
97
+ /**
98
+ * Return the earliest reference, or null if empty.
99
+ * @return {shaka.media.SegmentReference}
100
+ */
101
+ earliestReference() {
102
+ return this.references[0] || null;
103
+ }
104
+
105
+
106
+ /**
107
+ * Drop the first N references.
108
+ * Used in early HLS synchronization, and does not count as eviction.
109
+ * @param {number} n
110
+ */
111
+ dropFirstReferences(n) {
112
+ this.references.splice(0, n);
113
+ }
114
+
115
+
97
116
  /**
98
117
  * Finds the position of the segment for the given time, in seconds, relative
99
118
  * to the start of the presentation. Returns the position of the segment
@@ -166,7 +185,13 @@ shaka.media.SegmentIndex = class {
166
185
  for (const ref of this.references) {
167
186
  ref.startTime += offset;
168
187
  ref.endTime += offset;
169
- ref.timestampOffset += offset;
188
+ ref.trueEndTime += offset;
189
+
190
+ for (const partial of ref.partialReferences) {
191
+ partial.startTime += offset;
192
+ partial.endTime += offset;
193
+ partial.trueEndTime += offset;
194
+ }
170
195
  }
171
196
  }
172
197
  }
@@ -160,8 +160,9 @@ shaka.media.SegmentReference = class {
160
160
  * If not provided, the duration should be automatically calculated based on
161
161
  * the duration of the reference.
162
162
  * @param {?number=} syncTime
163
- * A time value, expressed in the same scale as the start and end time, which
164
- * is used to synchronize between streams.
163
+ * A time value, expressed in seconds since 1970, which is used to
164
+ * synchronize between streams. Both produced and consumed by the HLS
165
+ * parser. Other components should not need this value.
165
166
  * @param {shaka.media.SegmentReference.Status=} status
166
167
  * The segment status is used to indicate that a segment does not exist or is
167
168
  * not available.
@@ -221,7 +222,13 @@ shaka.media.SegmentReference = class {
221
222
  /** @type {?number} */
222
223
  this.tileDuration = tileDuration;
223
224
 
224
- /** @type {?number} */
225
+ /**
226
+ * A time value, expressed in seconds since 1970, which is used to
227
+ * synchronize between streams. Both produced and consumed by the HLS
228
+ * parser. Other components should not need this value.
229
+ *
230
+ * @type {?number}
231
+ */
225
232
  this.syncTime = syncTime;
226
233
 
227
234
  /** @type {shaka.media.SegmentReference.Status} */
@@ -1602,11 +1602,10 @@ shaka.media.StreamingEngine = class {
1602
1602
  await this.playerInterface_.mediaSourceEngine.appendBuffer(
1603
1603
  mediaState.type,
1604
1604
  segment,
1605
- reference.syncTime == null ? reference.startTime : reference.syncTime,
1605
+ reference.startTime,
1606
1606
  reference.endTime,
1607
1607
  hasClosedCaptions,
1608
- seeked,
1609
- this.manifest_.sequenceMode);
1608
+ seeked);
1610
1609
  this.destroyer_.ensureNotDestroyed();
1611
1610
  shaka.log.v2(logPrefix, 'appended media segment');
1612
1611
  }
@@ -368,8 +368,10 @@ shaka.offline.indexeddb.StorageMechanism.SESSION_ID_STORE = 'session-ids';
368
368
  shaka.offline.StorageMuxer.register(
369
369
  'idb',
370
370
  () => {
371
- // Offline storage is not supported on the Chromecast platform.
372
- if (shaka.util.Platform.isChromecast()) {
371
+ // Offline storage is not supported on the Chromecast or Xbox One
372
+ // platforms.
373
+ if (shaka.util.Platform.isChromecast() ||
374
+ shaka.util.Platform.isXboxOne()) {
373
375
  return null;
374
376
  }
375
377
  // Offline storage requires the IndexedDB API.