shaka-player 3.3.4 → 4.0.1

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 (201) hide show
  1. package/.github/config.yml +1 -0
  2. package/.github/workflows/build-and-test.yaml +0 -1
  3. package/.github/workflows/demo-version-index.yaml +8 -0
  4. package/.github/workflows/release-please.yaml +19 -3
  5. package/.github/workflows/selenium-lab-tests.yaml +0 -1
  6. package/.github/workflows/sync-labels.yaml +33 -0
  7. package/.github/workflows/update-issues.yaml +29 -0
  8. package/.github/workflows/validate-pr-title.yaml +35 -0
  9. package/AUTHORS +1 -0
  10. package/CHANGELOG.md +209 -28
  11. package/CONTRIBUTORS +1 -0
  12. package/README.md +10 -7
  13. package/app-engine/demo-version-index/README.md +3 -3
  14. package/app-engine/demo-version-index/app.yaml +27 -1
  15. package/app-engine/demo-version-index/generate.py +164 -0
  16. package/app-engine/demo-version-index/main.py +4 -155
  17. package/app-engine/demo-version-index/requirements.txt +0 -1
  18. package/build/README.md +2 -2
  19. package/build/compiler.py +1 -1
  20. package/build/generateTsDefs.py +0 -2
  21. package/build/misspellings.txt +1 -0
  22. package/build/shaka-lab.yaml +0 -2
  23. package/build/test.py +7 -1
  24. package/build/types/core +0 -1
  25. package/build/types/polyfill +0 -3
  26. package/demo/common/asset.js +17 -1
  27. package/demo/common/assets.js +29 -0
  28. package/demo/common/message_ids.js +6 -3
  29. package/demo/config.js +10 -11
  30. package/demo/custom.js +39 -0
  31. package/demo/demo_utils.js +0 -1
  32. package/demo/locales/en.json +6 -3
  33. package/demo/locales/source.json +22 -10
  34. package/demo/main.js +9 -6
  35. package/demo/search.js +2 -1
  36. package/dist/controls.css +1 -1
  37. package/dist/demo.compiled.debug.js +71 -64
  38. package/dist/demo.compiled.debug.map +3 -3
  39. package/dist/demo.compiled.js +71 -64
  40. package/dist/demo.compiled.map +3 -3
  41. package/dist/demo.css +1 -1
  42. package/dist/demo.css.map +1 -1
  43. package/dist/deps.js +16 -20
  44. package/dist/locales.js +33 -31
  45. package/dist/receiver.compiled.debug.js +32 -28
  46. package/dist/receiver.compiled.debug.map +3 -3
  47. package/dist/receiver.compiled.js +32 -28
  48. package/dist/receiver.compiled.map +3 -3
  49. package/dist/shaka-player.compiled.d.ts +78 -82
  50. package/dist/shaka-player.compiled.debug.d.ts +78 -82
  51. package/dist/shaka-player.compiled.debug.externs.js +127 -126
  52. package/dist/shaka-player.compiled.debug.js +471 -523
  53. package/dist/shaka-player.compiled.debug.map +4 -4
  54. package/dist/shaka-player.compiled.externs.js +127 -126
  55. package/dist/shaka-player.compiled.js +844 -858
  56. package/dist/shaka-player.compiled.map +4 -4
  57. package/dist/shaka-player.ui.d.ts +80 -82
  58. package/dist/shaka-player.ui.debug.d.ts +80 -82
  59. package/dist/shaka-player.ui.debug.externs.js +135 -126
  60. package/dist/shaka-player.ui.debug.js +531 -581
  61. package/dist/shaka-player.ui.debug.map +4 -4
  62. package/dist/shaka-player.ui.externs.js +135 -126
  63. package/dist/shaka-player.ui.js +991 -1001
  64. package/dist/shaka-player.ui.map +4 -4
  65. package/docs/tutorials/config.md +0 -2
  66. package/docs/tutorials/drm-config.md +5 -0
  67. package/docs/tutorials/fairplay.md +7 -22
  68. package/docs/tutorials/faq.md +0 -11
  69. package/docs/tutorials/index.json +2 -8
  70. package/docs/tutorials/plugins.md +1 -3
  71. package/docs/tutorials/upgrade.md +85 -11
  72. package/externs/polyfill.js +15 -0
  73. package/externs/shaka/manifest.js +5 -1
  74. package/externs/shaka/net.js +12 -0
  75. package/externs/shaka/offline.js +5 -1
  76. package/externs/shaka/player.js +39 -21
  77. package/externs/shaka/text.js +12 -12
  78. package/externs/sourcebuffer.js +15 -0
  79. package/karma.conf.js +4 -0
  80. package/lib/ads/client_side_ad.js +13 -2
  81. package/lib/ads/client_side_ad_manager.js +22 -4
  82. package/lib/cast/cast_utils.js +0 -1
  83. package/lib/dash/content_protection.js +85 -5
  84. package/lib/dash/dash_parser.js +3 -3
  85. package/lib/hls/hls_parser.js +391 -747
  86. package/lib/hls/manifest_text_parser.js +2 -3
  87. package/lib/media/drm_engine.js +102 -37
  88. package/lib/media/gap_jumping_controller.js +10 -45
  89. package/lib/media/media_source_engine.js +157 -31
  90. package/lib/media/playhead.js +2 -5
  91. package/lib/media/presentation_timeline.js +1 -2
  92. package/lib/media/segment_index.js +13 -46
  93. package/lib/media/segment_reference.js +47 -1
  94. package/lib/media/streaming_engine.js +28 -11
  95. package/lib/media/time_ranges_utils.js +4 -9
  96. package/lib/net/networking_engine.js +3 -0
  97. package/lib/offline/indexeddb/storage_mechanism.js +4 -2
  98. package/lib/offline/indexeddb/v1_storage_cell.js +1 -0
  99. package/lib/offline/indexeddb/v2_storage_cell.js +1 -0
  100. package/lib/offline/manifest_converter.js +1 -0
  101. package/lib/offline/storage.js +100 -132
  102. package/lib/player.js +181 -307
  103. package/lib/polyfill/media_capabilities.js +12 -6
  104. package/lib/polyfill/mediasource.js +0 -28
  105. package/lib/polyfill/patchedmediakeys_nop.js +2 -0
  106. package/lib/polyfill/patchedmediakeys_webkit.js +2 -0
  107. package/lib/text/cue.js +0 -6
  108. package/lib/text/lrc_text_parser.js +8 -0
  109. package/lib/text/mp4_ttml_parser.js +8 -0
  110. package/lib/text/mp4_vtt_parser.js +8 -0
  111. package/lib/text/sbv_text_parser.js +8 -0
  112. package/lib/text/simple_text_displayer.js +1 -7
  113. package/lib/text/srt_text_parser.js +8 -0
  114. package/lib/text/ssa_text_parser.js +8 -0
  115. package/lib/text/text_engine.js +19 -3
  116. package/lib/text/ttml_text_parser.js +8 -0
  117. package/lib/text/ui_text_displayer.js +1 -7
  118. package/lib/text/vtt_text_parser.js +27 -5
  119. package/lib/text/web_vtt_generator.js +1 -7
  120. package/lib/util/config_utils.js +25 -0
  121. package/lib/util/error.js +16 -13
  122. package/lib/util/fairplay_utils.js +109 -0
  123. package/lib/util/functional.js +0 -42
  124. package/lib/util/manifest_parser_utils.js +1 -1
  125. package/lib/util/mime_utils.js +50 -1
  126. package/lib/util/periods.js +5 -4
  127. package/lib/util/platform.js +45 -0
  128. package/lib/util/player_configuration.js +26 -20
  129. package/lib/util/pssh.js +23 -4
  130. package/lib/util/stream_utils.js +96 -24
  131. package/lib/util/uint8array_utils.js +0 -16
  132. package/lib/util/xml_utils.js +1 -1
  133. package/package.json +4 -1
  134. package/roadmap.md +19 -19
  135. package/shaka-player.uncompiled.js +0 -3
  136. package/test/cast/cast_utils_unit.js +1 -0
  137. package/test/dash/dash_parser_content_protection_unit.js +44 -0
  138. package/test/dash/dash_parser_manifest_unit.js +0 -24
  139. package/test/dash/dash_parser_segment_template_unit.js +3 -4
  140. package/test/demo/demo_unit.js +2 -1
  141. package/test/hls/hls_live_unit.js +121 -220
  142. package/test/hls/hls_parser_unit.js +515 -432
  143. package/test/hls/manifest_text_parser_unit.js +19 -0
  144. package/test/media/drm_engine_unit.js +86 -120
  145. package/test/media/media_source_engine_integration.js +26 -3
  146. package/test/media/media_source_engine_unit.js +7 -6
  147. package/test/media/playhead_unit.js +27 -191
  148. package/test/media/segment_index_unit.js +15 -25
  149. package/test/media/streaming_engine_integration.js +4 -39
  150. package/test/media/streaming_engine_unit.js +3 -2
  151. package/test/media/time_ranges_utils_unit.js +0 -5
  152. package/test/offline/indexeddb_storage_unit.js +1 -1
  153. package/test/offline/manifest_convert_unit.js +34 -4
  154. package/test/offline/offline_manifest_parser_unit.js +1 -0
  155. package/test/offline/storage_compatibility_unit.js +1 -1
  156. package/test/offline/storage_integration.js +5 -4
  157. package/test/player_external.js +7 -7
  158. package/test/player_integration.js +22 -10
  159. package/test/player_src_equals_integration.js +81 -83
  160. package/test/player_unit.js +124 -1
  161. package/test/test/boot.js +9 -0
  162. package/test/test/externs/filters.js +4 -0
  163. package/test/test/util/fake_media_source_engine.js +11 -4
  164. package/test/test/util/manifest_generator.js +3 -1
  165. package/test/test/util/manifest_parser_util.js +4 -1
  166. package/test/test/util/offline_utils.js +1 -0
  167. package/test/test/util/simple_fakes.js +31 -2
  168. package/test/test/util/streaming_engine_util.js +1 -0
  169. package/test/test/util/test_scheme.js +24 -1
  170. package/test/test/util/util.js +4 -4
  171. package/test/text/cue_integration.js +2 -2
  172. package/test/text/lrc_text_parser_unit.js +6 -7
  173. package/test/text/mp4_ttml_parser_unit.js +6 -4
  174. package/test/text/mp4_vtt_parser_unit.js +6 -5
  175. package/test/text/sbv_text_parser_unit.js +5 -6
  176. package/test/text/srt_text_parser_unit.js +5 -6
  177. package/test/text/ssa_text_parser_unit.js +17 -29
  178. package/test/text/text_engine_unit.js +45 -5
  179. package/test/text/ttml_text_parser_unit.js +77 -77
  180. package/test/text/vtt_text_parser_unit.js +116 -59
  181. package/test/ui/ui_integration.js +0 -16
  182. package/test/ui/ui_unit.js +27 -0
  183. package/test/util/cmcd_manager_unit.js +3 -0
  184. package/test/util/mime_utils_unit.js +54 -0
  185. package/test/util/periods_unit.js +79 -0
  186. package/test/util/stream_utils_unit.js +23 -7
  187. package/ui/controls.js +65 -40
  188. package/ui/fullscreen_button.js +26 -6
  189. package/ui/less/containers.less +3 -3
  190. package/ui/locales/en.json +1 -0
  191. package/ui/locales/source.json +5 -0
  192. package/ui/resolution_selection.js +31 -33
  193. package/ui/ui.js +0 -17
  194. package/docs/tutorials/gsoc-ideas-list.md +0 -3
  195. package/docs/tutorials/upgrade-v2-5.md +0 -441
  196. package/docs/tutorials/upgrade-v3.md +0 -38
  197. package/lib/polyfill/mathround.js +0 -49
  198. package/lib/polyfill/patchedmediakeys_apple.js +0 -744
  199. package/lib/polyfill/patchedmediakeys_ms.js +0 -698
  200. package/lib/util/mutex.js +0 -52
  201. package/test/util/functional_unit.js +0 -82
@@ -63,9 +63,17 @@ shaka.media.MediaSourceEngine = class {
63
63
  string>} */
64
64
  this.sourceBufferTypes_ = {};
65
65
 
66
+
67
+ /** @private {!Object.<shaka.util.ManifestParserUtils.ContentType,
68
+ boolean>} */
69
+ this.expectedEncryption_ = {};
70
+
66
71
  /** @private {shaka.text.TextEngine} */
67
72
  this.textEngine_ = null;
68
73
 
74
+ /** @private {boolean} */
75
+ this.segmentRelativeVttTiming_ = false;
76
+
69
77
  const onMetadataNoOp = (metadata, timestampOffset, segmentEnd) => {};
70
78
 
71
79
  /** @private {!function(!Array.<shaka.extern.ID3Metadata>,
@@ -98,6 +106,12 @@ shaka.media.MediaSourceEngine = class {
98
106
 
99
107
  /** @private {string} */
100
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();
101
115
  }
102
116
 
103
117
  /**
@@ -196,6 +210,8 @@ shaka.media.MediaSourceEngine = class {
196
210
  // TTML types
197
211
  'application/ttml+xml',
198
212
  'application/mp4; codecs="stpp"',
213
+ // Containerless types
214
+ ...shaka.media.MediaSourceEngine.RAW_FORMATS,
199
215
  ];
200
216
 
201
217
  const support = {};
@@ -310,14 +326,19 @@ shaka.media.MediaSourceEngine = class {
310
326
  * according to MediaSourceEngine.isStreamSupported.
311
327
  * @param {boolean} forceTransmuxTS
312
328
  * If true, this will transmux TS content even if it is natively supported.
329
+ * @param {boolean=} sequenceMode
330
+ * If true, the media segments are appended to the SourceBuffer in strict
331
+ * sequence.
313
332
  *
314
333
  * @return {!Promise}
315
334
  */
316
- async init(streamsByType, forceTransmuxTS) {
335
+ async init(streamsByType, forceTransmuxTS, sequenceMode=false) {
317
336
  const ContentType = shaka.util.ManifestParserUtils.ContentType;
318
337
 
319
338
  await this.mediaSourceOpen_;
320
339
 
340
+ this.sequenceMode_ = sequenceMode;
341
+
321
342
  for (const contentType of streamsByType.keys()) {
322
343
  const stream = streamsByType.get(contentType);
323
344
  goog.asserts.assert(
@@ -327,7 +348,7 @@ shaka.media.MediaSourceEngine = class {
327
348
  let mimeType = shaka.util.MimeUtils.getFullType(
328
349
  stream.mimeType, stream.codecs);
329
350
  if (contentType == ContentType.TEXT) {
330
- this.reinitText(mimeType);
351
+ this.reinitText(mimeType, sequenceMode);
331
352
  } else {
332
353
  if ((forceTransmuxTS || !MediaSource.isTypeSupported(mimeType)) &&
333
354
  shaka.media.Transmuxer.isSupported(mimeType, contentType)) {
@@ -335,7 +356,9 @@ shaka.media.MediaSourceEngine = class {
335
356
  mimeType =
336
357
  shaka.media.Transmuxer.convertTsCodecs(contentType, mimeType);
337
358
  }
359
+
338
360
  const sourceBuffer = this.mediaSource_.addSourceBuffer(mimeType);
361
+
339
362
  this.eventManager_.listen(
340
363
  sourceBuffer, 'error',
341
364
  () => this.onError_(contentType));
@@ -345,6 +368,7 @@ shaka.media.MediaSourceEngine = class {
345
368
  this.sourceBuffers_[contentType] = sourceBuffer;
346
369
  this.sourceBufferTypes_[contentType] = mimeType;
347
370
  this.queues_[contentType] = [];
371
+ this.expectedEncryption_[contentType] = !!stream.drmInfos.length;
348
372
  }
349
373
  }
350
374
  }
@@ -352,12 +376,14 @@ shaka.media.MediaSourceEngine = class {
352
376
  /**
353
377
  * Reinitialize the TextEngine for a new text type.
354
378
  * @param {string} mimeType
379
+ * @param {boolean} sequenceMode
355
380
  */
356
- reinitText(mimeType) {
381
+ reinitText(mimeType, sequenceMode) {
357
382
  if (!this.textEngine_) {
358
383
  this.textEngine_ = new shaka.text.TextEngine(this.textDisplayer_);
359
384
  }
360
- this.textEngine_.initParser(mimeType);
385
+ this.textEngine_.initParser(mimeType, sequenceMode,
386
+ this.segmentRelativeVttTiming_);
361
387
  }
362
388
 
363
389
  /**
@@ -404,17 +430,15 @@ shaka.media.MediaSourceEngine = class {
404
430
  *
405
431
  * @param {shaka.util.ManifestParserUtils.ContentType} contentType
406
432
  * @param {number} time Playhead time
407
- * @param {number=} smallGapLimit
408
433
  * @return {boolean}
409
434
  */
410
- isBuffered(contentType, time, smallGapLimit) {
435
+ isBuffered(contentType, time) {
411
436
  const ContentType = shaka.util.ManifestParserUtils.ContentType;
412
437
  if (contentType == ContentType.TEXT) {
413
438
  return this.textEngine_.isBuffered(time);
414
439
  } else {
415
440
  const buffered = this.getBuffered_(contentType);
416
- return shaka.media.TimeRangesUtils.isBuffered(
417
- buffered, time, smallGapLimit);
441
+ return shaka.media.TimeRangesUtils.isBuffered(buffered, time);
418
442
  }
419
443
  }
420
444
 
@@ -496,20 +520,30 @@ shaka.media.MediaSourceEngine = class {
496
520
  * @param {?number} endTime relative to the start of the presentation
497
521
  * @param {?boolean} hasClosedCaptions True if the buffer contains CEA closed
498
522
  * captions
523
+ * @param {boolean=} seeked True if we just seeked
499
524
  * @return {!Promise}
500
525
  */
501
- async appendBuffer(contentType, data, startTime, endTime, hasClosedCaptions) {
526
+ async appendBuffer(
527
+ contentType, data, startTime, endTime, hasClosedCaptions, seeked) {
502
528
  const ContentType = shaka.util.ManifestParserUtils.ContentType;
503
529
 
504
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
+ }
505
536
  await this.textEngine_.appendBuffer(data, startTime, endTime);
506
- } else if (this.transmuxers_[contentType]) {
537
+ return;
538
+ }
539
+
540
+ if (this.transmuxers_[contentType]) {
507
541
  const transmuxedData =
508
542
  await this.transmuxers_[contentType].transmux(data);
509
543
  // For HLS CEA-608/708 CLOSED-CAPTIONS, text data is embedded in
510
544
  // the video stream, so textEngine may not have been initialized.
511
545
  if (!this.textEngine_) {
512
- this.reinitText('text/vtt');
546
+ this.reinitText('text/vtt', this.sequenceMode_);
513
547
  }
514
548
 
515
549
  if (transmuxedData.metadata) {
@@ -528,15 +562,10 @@ shaka.media.MediaSourceEngine = class {
528
562
  closedCaptions, startTime, endTime, videoOffset);
529
563
  }
530
564
 
531
- let transmuxedSegment = transmuxedData.data;
532
- transmuxedSegment = this.workAroundBrokenPlatforms_(
533
- transmuxedSegment, startTime, contentType);
534
-
535
- await this.enqueueOperation_(
536
- contentType, () => this.append_(contentType, transmuxedSegment));
565
+ data = transmuxedData.data;
537
566
  } else if (hasClosedCaptions) {
538
567
  if (!this.textEngine_) {
539
- this.reinitText('text/vtt');
568
+ this.reinitText('text/vtt', this.sequenceMode_);
540
569
  }
541
570
  // If it is the init segment for closed captions, initialize the closed
542
571
  // caption parser.
@@ -551,19 +580,78 @@ shaka.media.MediaSourceEngine = class {
551
580
  closedCaptions, startTime, endTime, videoOffset);
552
581
  }
553
582
  }
583
+ }
554
584
 
555
- 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
+ }
556
634
 
557
- await this.enqueueOperation_(
558
- contentType,
559
- () => this.append_(contentType, data));
560
- } else {
561
- data = this.workAroundBrokenPlatforms_(data, startTime, contentType);
635
+ // Now switch to sequence mode and fall through to our normal operations.
636
+ sourceBuffer.mode = SEQUENCE;
637
+ }
562
638
 
563
- await this.enqueueOperation_(
564
- contentType,
565
- () => 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
+ }
566
650
  }
651
+
652
+ await this.enqueueOperation_(
653
+ contentType,
654
+ () => this.append_(contentType, data));
567
655
  }
568
656
 
569
657
  /**
@@ -664,13 +752,18 @@ shaka.media.MediaSourceEngine = class {
664
752
  * @param {number} appendWindowEnd The timestamp to set the append window end
665
753
  * to. For future appends, frames/samples with timestamps greater than this
666
754
  * value will be dropped.
755
+ * @param {boolean} sequenceMode If true, the timestampOffset will not be
756
+ * applied in this step.
667
757
  * @return {!Promise}
668
758
  */
669
759
  async setStreamProperties(
670
- contentType, timestampOffset, appendWindowStart, appendWindowEnd) {
760
+ contentType, timestampOffset, appendWindowStart, appendWindowEnd,
761
+ sequenceMode) {
671
762
  const ContentType = shaka.util.ManifestParserUtils.ContentType;
672
763
  if (contentType == ContentType.TEXT) {
673
- this.textEngine_.setTimestampOffset(timestampOffset);
764
+ if (!sequenceMode) {
765
+ this.textEngine_.setTimestampOffset(timestampOffset);
766
+ }
674
767
  this.textEngine_.setAppendWindow(appendWindowStart, appendWindowEnd);
675
768
  return;
676
769
  }
@@ -687,7 +780,10 @@ shaka.media.MediaSourceEngine = class {
687
780
  this.enqueueOperation_(
688
781
  contentType,
689
782
  () => this.abort_(contentType)),
690
- this.enqueueOperation_(
783
+ // Don't set the timestampOffset here when in sequenceMode, since we
784
+ // use timestampOffset for a different purpose in that mode (e.g. to
785
+ // indicate where the current segment is).
786
+ sequenceMode ? Promise.resolve() : this.enqueueOperation_(
691
787
  contentType,
692
788
  () => this.setTimestampOffset_(contentType, timestampOffset)),
693
789
  this.enqueueOperation_(
@@ -1079,6 +1175,13 @@ shaka.media.MediaSourceEngine = class {
1079
1175
  }
1080
1176
  }
1081
1177
 
1178
+ /**
1179
+ * @param {boolean} segmentRelativeVttTiming
1180
+ */
1181
+ setSegmentRelativeVttTiming(segmentRelativeVttTiming) {
1182
+ this.segmentRelativeVttTiming_ = segmentRelativeVttTiming;
1183
+ }
1184
+
1082
1185
  /**
1083
1186
  * Apply platform-specific transformations to this segment to work around
1084
1187
  * issues in the platform.
@@ -1091,7 +1194,7 @@ shaka.media.MediaSourceEngine = class {
1091
1194
  */
1092
1195
  workAroundBrokenPlatforms_(segment, startTime, contentType) {
1093
1196
  const isInitSegment = startTime == null;
1094
- const encryptionExpected = this.video_.mediaKeys;
1197
+ const encryptionExpected = this.expectedEncryption_[contentType];
1095
1198
 
1096
1199
  // If:
1097
1200
  // 1. this is an init segment,
@@ -1139,3 +1242,26 @@ shaka.media.MediaSourceEngine.createObjectURL = window.URL.createObjectURL;
1139
1242
  * The PublicPromise which is associated with this operation.
1140
1243
  */
1141
1244
  shaka.media.MediaSourceEngine.Operation;
1245
+
1246
+
1247
+ /**
1248
+ * @enum {string}
1249
+ * @private
1250
+ */
1251
+ shaka.media.MediaSourceEngine.SourceBufferMode_ = {
1252
+ SEQUENCE: 'sequence',
1253
+ SEGMENTS: 'segments',
1254
+ };
1255
+
1256
+
1257
+ /**
1258
+ * MIME types of raw formats.
1259
+ *
1260
+ * @const {!Array.<string>}
1261
+ */
1262
+ shaka.media.MediaSourceEngine.RAW_FORMATS = [
1263
+ 'audio/aac',
1264
+ 'audio/ac3',
1265
+ 'audio/ec3',
1266
+ 'audio/mpeg',
1267
+ ];
@@ -160,10 +160,8 @@ shaka.media.MediaSourcePlayhead = class {
160
160
  * @param {function()} onSeek
161
161
  * Called when the user agent seeks to a time within the presentation
162
162
  * timeline.
163
- * @param {function(!Event)} onEvent
164
- * Called when an event is raised to be sent to the application.
165
163
  */
166
- constructor(mediaElement, manifest, config, startTime, onSeek, onEvent) {
164
+ constructor(mediaElement, manifest, config, startTime, onSeek) {
167
165
  /**
168
166
  * The seek range must be at least this number of seconds long. If it is
169
167
  * smaller than this, change it to be this big so we don't repeatedly seek
@@ -199,8 +197,7 @@ shaka.media.MediaSourcePlayhead = class {
199
197
  mediaElement,
200
198
  manifest.presentationTimeline,
201
199
  config,
202
- this.createStallDetector_(mediaElement, config),
203
- onEvent);
200
+ this.createStallDetector_(mediaElement, config));
204
201
 
205
202
  /** @private {shaka.media.VideoWrapper} */
206
203
  this.videoWrapper_ = new shaka.media.VideoWrapper(
@@ -249,8 +249,7 @@ shaka.media.PresentationTimeline = class {
249
249
  * @param {number} startTime
250
250
  * @export
251
251
  */
252
- notifyMinSegmentStartTime(
253
- startTime) {
252
+ notifyMinSegmentStartTime(startTime) {
254
253
  if (this.minSegmentStartTime_ == null) {
255
254
  // No data yet, and Math.min(null, startTime) is always 0. So just store
256
255
  // startTime.
@@ -9,7 +9,6 @@ goog.provide('shaka.media.SegmentIndex');
9
9
  goog.provide('shaka.media.SegmentIterator');
10
10
 
11
11
  goog.require('goog.asserts');
12
- goog.require('shaka.Deprecate');
13
12
  goog.require('shaka.media.SegmentReference');
14
13
  goog.require('shaka.util.IReleasable');
15
14
  goog.require('shaka.util.Timer');
@@ -52,23 +51,6 @@ shaka.media.SegmentIndex = class {
52
51
  }
53
52
 
54
53
 
55
- /**
56
- * SegmentIndex used to be an IDestroyable. Now it is an IReleasable.
57
- * This method is provided for backward compatibility.
58
- *
59
- * @deprecated
60
- * @return {!Promise}
61
- * @export
62
- */
63
- destroy() {
64
- shaka.Deprecate.deprecateFeature(4,
65
- 'shaka.media.SegmentIndex',
66
- 'Please use release() instead of destroy().');
67
- this.release();
68
- return Promise.resolve();
69
- }
70
-
71
-
72
54
  /**
73
55
  * @override
74
56
  * @export
@@ -101,6 +83,17 @@ shaka.media.SegmentIndex = class {
101
83
  }
102
84
 
103
85
 
86
+ /**
87
+ * Iterates over all top-level segment references in this segment index.
88
+ * @param {function(!shaka.media.SegmentReference)} fn
89
+ */
90
+ forEachTopLevelReference(fn) {
91
+ for (const reference of this.references) {
92
+ fn(reference);
93
+ }
94
+ }
95
+
96
+
104
97
  /**
105
98
  * Finds the position of the segment for the given time, in seconds, relative
106
99
  * to the start of the presentation. Returns the position of the segment
@@ -191,9 +184,6 @@ shaka.media.SegmentIndex = class {
191
184
  * @param {!Array.<!shaka.media.SegmentReference>} references The list of
192
185
  * SegmentReferences, which must be sorted first by their start times
193
186
  * (ascending) and second by their end times (ascending).
194
- * @deprecated Not used directly by our own parsers, so will become private in
195
- * v4. Use mergeAndEvict() instead.
196
- * @export
197
187
  */
198
188
  merge(references) {
199
189
  if (goog.DEBUG) {
@@ -348,7 +338,8 @@ shaka.media.SegmentIndex = class {
348
338
  lastReference.appendWindowEnd,
349
339
  lastReference.partialReferences,
350
340
  lastReference.tilesLayout,
351
- lastReference.tileDuration);
341
+ lastReference.tileDuration,
342
+ lastReference.syncTime);
352
343
  }
353
344
 
354
345
 
@@ -513,30 +504,6 @@ shaka.media.SegmentIterator = class {
513
504
  this.currentPartialPosition_ = partialSegmentIndex;
514
505
  }
515
506
 
516
- /**
517
- * Move the iterator to a given timestamp in the underlying SegmentIndex.
518
- *
519
- * @param {number} time
520
- * @return {shaka.media.SegmentReference}
521
- * @deprecated Use SegmentIndex.getIteratorForTime instead
522
- * @export
523
- */
524
- seek(time) {
525
- shaka.Deprecate.deprecateFeature(
526
- 4, 'shaka.media.SegmentIterator',
527
- 'Please use SegmentIndex.getIteratorForTime instead of seek().');
528
-
529
- const iter = this.segmentIndex_.getIteratorForTime(time);
530
- if (iter) {
531
- this.currentPosition_ = iter.currentPosition_;
532
- this.currentPartialPosition_ = iter.currentPartialPosition_;
533
- } else {
534
- this.currentPosition_ = Number.MAX_VALUE;
535
- this.currentPartialPosition_ = 0;
536
- }
537
- return this.next().value;
538
- }
539
-
540
507
  /**
541
508
  * @return {shaka.media.SegmentReference}
542
509
  * @export
@@ -159,11 +159,18 @@ shaka.media.SegmentReference = class {
159
159
  * The explicit duration of an individual tile within the tiles grid.
160
160
  * If not provided, the duration should be automatically calculated based on
161
161
  * the duration of the reference.
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.
165
+ * @param {shaka.media.SegmentReference.Status=} status
166
+ * The segment status is used to indicate that a segment does not exist or is
167
+ * not available.
162
168
  */
163
169
  constructor(
164
170
  startTime, endTime, uris, startByte, endByte, initSegmentReference,
165
171
  timestampOffset, appendWindowStart, appendWindowEnd,
166
- partialReferences = [], tilesLayout = '', tileDuration = null) {
172
+ partialReferences = [], tilesLayout = '', tileDuration = null,
173
+ syncTime = null, status = shaka.media.SegmentReference.Status.AVAILABLE) {
167
174
  // A preload hinted Partial Segment has the same startTime and endTime.
168
175
  goog.asserts.assert(startTime <= endTime,
169
176
  'startTime must be less than or equal to endTime');
@@ -213,6 +220,12 @@ shaka.media.SegmentReference = class {
213
220
 
214
221
  /** @type {?number} */
215
222
  this.tileDuration = tileDuration;
223
+
224
+ /** @type {?number} */
225
+ this.syncTime = syncTime;
226
+
227
+ /** @type {shaka.media.SegmentReference.Status} */
228
+ this.status = status;
216
229
  }
217
230
 
218
231
  /**
@@ -308,6 +321,39 @@ shaka.media.SegmentReference = class {
308
321
  getTileDuration() {
309
322
  return this.tileDuration;
310
323
  }
324
+
325
+ /**
326
+ * Returns the segment's status.
327
+ *
328
+ * @return {shaka.media.SegmentReference.Status}
329
+ * @export
330
+ */
331
+ getStatus() {
332
+ return this.status;
333
+ }
334
+
335
+ /**
336
+ * Mark the reference as unavailable.
337
+ *
338
+ * @export
339
+ */
340
+ markAsUnavailable() {
341
+ this.status = shaka.media.SegmentReference.Status.UNAVAILABLE;
342
+ }
343
+ };
344
+
345
+
346
+ /**
347
+ * Rather than using booleans to communicate what the state of the reference,
348
+ * we have this enum.
349
+ *
350
+ * @enum {number}
351
+ * @export
352
+ */
353
+ shaka.media.SegmentReference.Status = {
354
+ AVAILABLE: 0,
355
+ UNAVAILABLE: 1,
356
+ MISSING: 2,
311
357
  };
312
358
 
313
359
 
@@ -234,7 +234,8 @@ shaka.media.StreamingEngine = class {
234
234
 
235
235
  const mimeType = shaka.util.MimeUtils.getFullType(
236
236
  stream.mimeType, stream.codecs);
237
- this.playerInterface_.mediaSourceEngine.reinitText(mimeType);
237
+ this.playerInterface_.mediaSourceEngine.reinitText(
238
+ mimeType, this.manifest_.sequenceMode);
238
239
 
239
240
  const textDisplayer =
240
241
  this.playerInterface_.mediaSourceEngine.getTextDisplayer();
@@ -428,7 +429,8 @@ shaka.media.StreamingEngine = class {
428
429
  // init segment again.
429
430
  const fullMimeType = shaka.util.MimeUtils.getFullType(
430
431
  stream.mimeType, stream.codecs);
431
- this.playerInterface_.mediaSourceEngine.reinitText(fullMimeType);
432
+ this.playerInterface_.mediaSourceEngine.reinitText(
433
+ fullMimeType, this.manifest_.sequenceMode);
432
434
  }
433
435
 
434
436
  // Releases the segmentIndex of the old stream.
@@ -604,11 +606,10 @@ shaka.media.StreamingEngine = class {
604
606
  */
605
607
  seeked() {
606
608
  const presentationTime = this.playerInterface_.getPresentationTime();
607
- const smallGapLimit = this.config_.smallGapLimit;
608
609
  const ContentType = shaka.util.ManifestParserUtils.ContentType;
609
610
  const newTimeIsBuffered = (type) => {
610
611
  return this.playerInterface_.mediaSourceEngine.isBuffered(
611
- type, presentationTime, smallGapLimit);
612
+ type, presentationTime);
612
613
  };
613
614
 
614
615
  let streamCleared = false;
@@ -645,6 +646,10 @@ shaka.media.StreamingEngine = class {
645
646
  if (type === ContentType.TEXT) {
646
647
  this.playerInterface_.mediaSourceEngine.resetCaptionParser();
647
648
  }
649
+
650
+ // Mark the media state as having seeked, so that the new buffers know
651
+ // that they will need to be at a new position (for sequence mode).
652
+ mediaState.seeked = true;
648
653
  }
649
654
  }
650
655
 
@@ -764,7 +769,9 @@ shaka.media.StreamingEngine = class {
764
769
  const mediaSourceEngine = this.playerInterface_.mediaSourceEngine;
765
770
  const forceTransmuxTS = this.config_.forceTransmuxTS;
766
771
 
767
- await mediaSourceEngine.init(streamsByType, forceTransmuxTS);
772
+
773
+ await mediaSourceEngine.init(streamsByType, forceTransmuxTS,
774
+ this.manifest_.sequenceMode);
768
775
  this.destroyer_.ensureNotDestroyed();
769
776
 
770
777
  this.setDuration_();
@@ -805,6 +812,9 @@ shaka.media.StreamingEngine = class {
805
812
  clearBufferSafeMargin: 0,
806
813
  waitingToFlushBuffer: false,
807
814
  clearingBuffer: false,
815
+ // The playhead might be seeking on startup, if a start time is set, so
816
+ // start "seeked" as true.
817
+ seeked: true,
808
818
  recovering: false,
809
819
  hasError: false,
810
820
  operation: null,
@@ -1272,6 +1282,7 @@ shaka.media.StreamingEngine = class {
1272
1282
  this.scheduleUpdate_(mediaState, 0);
1273
1283
  return;
1274
1284
  }
1285
+
1275
1286
  await this.append_(
1276
1287
  mediaState, presentationTime, stream, reference, result);
1277
1288
  }
@@ -1508,7 +1519,7 @@ shaka.media.StreamingEngine = class {
1508
1519
 
1509
1520
  await this.playerInterface_.mediaSourceEngine.setStreamProperties(
1510
1521
  mediaState.type, timestampOffset, appendWindowStart,
1511
- appendWindowEnd);
1522
+ appendWindowEnd, this.manifest_.sequenceMode);
1512
1523
  } catch (error) {
1513
1524
  mediaState.lastAppendWindowStart = null;
1514
1525
  mediaState.lastAppendWindowEnd = null;
@@ -1565,8 +1576,7 @@ shaka.media.StreamingEngine = class {
1565
1576
  * @return {!Promise}
1566
1577
  * @private
1567
1578
  */
1568
- async append_(mediaState, presentationTime, stream, reference,
1569
- segment) {
1579
+ async append_(mediaState, presentationTime, stream, reference, segment) {
1570
1580
  const logPrefix = shaka.media.StreamingEngine.logPrefix_(mediaState);
1571
1581
 
1572
1582
  const hasClosedCaptions = stream.closedCaptions &&
@@ -1584,14 +1594,18 @@ shaka.media.StreamingEngine = class {
1584
1594
 
1585
1595
  await this.evict_(mediaState, presentationTime);
1586
1596
  this.destroyer_.ensureNotDestroyed();
1587
- shaka.log.v1(logPrefix, 'appending media segment');
1597
+ shaka.log.v1(logPrefix, 'appending media segment at',
1598
+ (reference.syncTime == null ? 'unknown' : reference.syncTime));
1588
1599
 
1600
+ const seeked = mediaState.seeked;
1601
+ mediaState.seeked = false;
1589
1602
  await this.playerInterface_.mediaSourceEngine.appendBuffer(
1590
1603
  mediaState.type,
1591
1604
  segment,
1592
- reference.startTime,
1605
+ reference.syncTime == null ? reference.startTime : reference.syncTime,
1593
1606
  reference.endTime,
1594
- hasClosedCaptions);
1607
+ hasClosedCaptions,
1608
+ seeked);
1595
1609
  this.destroyer_.ensureNotDestroyed();
1596
1610
  shaka.log.v2(logPrefix, 'appended media segment');
1597
1611
  }
@@ -2017,6 +2031,7 @@ shaka.media.StreamingEngine.PlayerInterface;
2017
2031
  * waitingToFlushBuffer: boolean,
2018
2032
  * clearBufferSafeMargin: number,
2019
2033
  * clearingBuffer: boolean,
2034
+ * seeked: boolean,
2020
2035
  * recovering: boolean,
2021
2036
  * hasError: boolean,
2022
2037
  * operation: shaka.net.NetworkingEngine.PendingRequest
@@ -2061,6 +2076,8 @@ shaka.media.StreamingEngine.PlayerInterface;
2061
2076
  * The amount of buffer to retain when clearing the buffer after the update.
2062
2077
  * @property {boolean} clearingBuffer
2063
2078
  * True indicates that the buffer is being cleared.
2079
+ * @property {boolean} seeked
2080
+ * True indicates that the presentation just seeked.
2064
2081
  * @property {boolean} recovering
2065
2082
  * True indicates that the last segment was not appended because it could not
2066
2083
  * fit in the buffer.