shaka-player 4.0.0 → 4.1.0

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 (114) hide show
  1. package/.github/config.yml +1 -0
  2. package/.github/workflows/release-please.yaml +6 -11
  3. package/CHANGELOG.md +33 -0
  4. package/CONTRIBUTORS +1 -0
  5. package/README.md +4 -4
  6. package/build/misspellings.txt +1 -0
  7. package/demo/common/message_ids.js +1 -0
  8. package/demo/config.js +3 -1
  9. package/demo/demo.less +0 -1
  10. package/demo/locales/en.json +1 -0
  11. package/demo/locales/source.json +4 -0
  12. package/dist/controls.css +3 -3
  13. package/dist/controls.css.map +1 -1
  14. package/dist/demo.compiled.debug.js +4 -4
  15. package/dist/demo.compiled.debug.map +2 -2
  16. package/dist/demo.compiled.js +4 -4
  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 +3 -3
  21. package/dist/locales.js +32 -32
  22. package/dist/receiver.compiled.debug.js +2 -2
  23. package/dist/receiver.compiled.debug.map +2 -2
  24. package/dist/receiver.compiled.js +2 -2
  25. package/dist/receiver.compiled.map +2 -2
  26. package/dist/shaka-player.compiled.d.ts +15 -8
  27. package/dist/shaka-player.compiled.debug.d.ts +15 -8
  28. package/dist/shaka-player.compiled.debug.externs.js +11 -2
  29. package/dist/shaka-player.compiled.debug.js +266 -253
  30. package/dist/shaka-player.compiled.debug.map +3 -3
  31. package/dist/shaka-player.compiled.externs.js +11 -2
  32. package/dist/shaka-player.compiled.js +792 -784
  33. package/dist/shaka-player.compiled.map +3 -3
  34. package/dist/shaka-player.ui.d.ts +16 -9
  35. package/dist/shaka-player.ui.debug.d.ts +16 -9
  36. package/dist/shaka-player.ui.debug.externs.js +11 -2
  37. package/dist/shaka-player.ui.debug.js +299 -282
  38. package/dist/shaka-player.ui.debug.map +3 -3
  39. package/dist/shaka-player.ui.externs.js +11 -2
  40. package/dist/shaka-player.ui.js +948 -941
  41. package/dist/shaka-player.ui.map +3 -3
  42. package/docs/design/chromecast.md +3 -1
  43. package/docs/tutorials/drm-config.md +16 -0
  44. package/docs/tutorials/ui.md +26 -0
  45. package/docs/tutorials/upgrade.md +4 -2
  46. package/externs/chromecast.js +9 -2
  47. package/externs/shaka/manifest.js +7 -0
  48. package/externs/shaka/player.js +32 -2
  49. package/externs/shaka/text.js +7 -0
  50. package/karma.conf.js +4 -0
  51. package/lib/abr/simple_abr_manager.js +2 -1
  52. package/lib/cast/cast_proxy.js +19 -8
  53. package/lib/cast/cast_receiver.js +3 -3
  54. package/lib/cast/cast_sender.js +12 -2
  55. package/lib/dash/dash_parser.js +2 -2
  56. package/lib/hls/hls_parser.js +46 -15
  57. package/lib/media/drm_engine.js +36 -8
  58. package/lib/media/gap_jumping_controller.js +23 -1
  59. package/lib/media/media_source_engine.js +91 -37
  60. package/lib/media/playhead.js +47 -4
  61. package/lib/media/stall_detector.js +19 -2
  62. package/lib/media/streaming_engine.js +9 -3
  63. package/lib/offline/indexeddb/storage_mechanism.js +4 -2
  64. package/lib/offline/manifest_converter.js +1 -0
  65. package/lib/player.js +339 -175
  66. package/lib/text/cue.js +6 -0
  67. package/lib/text/ui_text_displayer.js +1 -0
  68. package/lib/text/vtt_text_parser.js +11 -7
  69. package/lib/util/config_utils.js +25 -0
  70. package/lib/util/error.js +13 -0
  71. package/lib/util/fake_event.js +39 -0
  72. package/lib/util/manifest_parser_utils.js +1 -1
  73. package/lib/util/mime_utils.js +62 -6
  74. package/lib/util/periods.js +5 -4
  75. package/lib/util/player_configuration.js +16 -14
  76. package/lib/util/stats.js +28 -1
  77. package/lib/util/stream_utils.js +11 -2
  78. package/package.json +5 -2
  79. package/test/cast/cast_proxy_unit.js +3 -1
  80. package/test/cast/cast_sender_unit.js +21 -6
  81. package/test/demo/demo_unit.js +2 -1
  82. package/test/hls/hls_live_unit.js +79 -7
  83. package/test/hls/manifest_text_parser_unit.js +19 -0
  84. package/test/media/adaptation_set_unit.js +1 -0
  85. package/test/media/playhead_unit.js +114 -25
  86. package/test/media/stall_detector_unit.js +20 -2
  87. package/test/media/streaming_engine_integration.js +2 -1
  88. package/test/media/streaming_engine_unit.js +1 -0
  89. package/test/offline/indexeddb_storage_unit.js +1 -1
  90. package/test/offline/storage_compatibility_unit.js +1 -1
  91. package/test/player_external.js +7 -1
  92. package/test/player_integration.js +9 -10
  93. package/test/player_src_equals_integration.js +81 -83
  94. package/test/player_unit.js +240 -2
  95. package/test/test/boot.js +9 -0
  96. package/test/test/externs/filters.js +4 -0
  97. package/test/test/util/manifest_generator.js +2 -0
  98. package/test/test/util/simple_fakes.js +45 -2
  99. package/test/test/util/streaming_engine_util.js +1 -0
  100. package/test/text/vtt_text_parser_unit.js +68 -5
  101. package/test/ui/ui_unit.js +18 -5
  102. package/test/util/mime_utils_unit.js +54 -0
  103. package/test/util/periods_unit.js +79 -0
  104. package/test/util/stream_utils_unit.js +108 -0
  105. package/ui/controls.js +12 -8
  106. package/ui/externs/ui.js +10 -1
  107. package/ui/less/containers.less +1 -1
  108. package/ui/less/general.less +1 -1
  109. package/ui/less/range_elements.less +1 -0
  110. package/ui/resolution_selection.js +1 -1
  111. package/ui/statistics_button.js +10 -0
  112. package/ui/ui.js +12 -1
  113. package/.github/pull_request_template.md +0 -10
  114. package/docs/tutorials/gsoc-ideas-list.md +0 -3
@@ -11,6 +11,7 @@ goog.require('shaka.media.PresentationTimeline');
11
11
  goog.require('shaka.media.StallDetector');
12
12
  goog.require('shaka.media.TimeRangesUtils');
13
13
  goog.require('shaka.util.EventManager');
14
+ goog.require('shaka.util.FakeEvent');
14
15
  goog.require('shaka.util.IReleasable');
15
16
  goog.require('shaka.util.Timer');
16
17
 
@@ -32,8 +33,13 @@ shaka.media.GapJumpingController = class {
32
33
  * playable region. The gap jumping controller takes ownership over the
33
34
  * stall detector.
34
35
  * If no stall detection logic is desired, |null| may be provided.
36
+ * @param {function(!Event)} onEvent
37
+ * Called when an event is raised to be sent to the application.
35
38
  */
36
- constructor(video, timeline, config, stallDetector) {
39
+ constructor(video, timeline, config, stallDetector, onEvent) {
40
+ /** @private {?function(!Event)} */
41
+ this.onEvent_ = onEvent;
42
+
37
43
  /** @private {HTMLMediaElement} */
38
44
  this.video_ = video;
39
45
 
@@ -52,6 +58,9 @@ shaka.media.GapJumpingController = class {
52
58
  /** @private {number} */
53
59
  this.prevReadyState_ = video.readyState;
54
60
 
61
+ /** @private {number} */
62
+ this.gapsJumped_ = 0;
63
+
55
64
  /**
56
65
  * The stall detector tries to keep the playhead moving forward. It is
57
66
  * managed by the gap-jumping controller to avoid conflicts. On some
@@ -98,6 +107,7 @@ shaka.media.GapJumpingController = class {
98
107
  this.stallDetector_ = null;
99
108
  }
100
109
 
110
+ this.onEvent_ = null;
101
111
  this.timeline_ = null;
102
112
  this.video_ = null;
103
113
  }
@@ -121,6 +131,15 @@ shaka.media.GapJumpingController = class {
121
131
  }
122
132
 
123
133
 
134
+ /**
135
+ * Returns the total number of playback gaps jumped.
136
+ * @return {number}
137
+ */
138
+ getGapsJumped() {
139
+ return this.gapsJumped_;
140
+ }
141
+
142
+
124
143
  /**
125
144
  * Called on a recurring timer to check for gaps in the media. This is also
126
145
  * called in a 'waiting' event.
@@ -209,6 +228,9 @@ shaka.media.GapJumpingController = class {
209
228
  }
210
229
 
211
230
  this.video_.currentTime = jumpTo;
231
+ this.gapsJumped_++;
232
+ this.onEvent_(
233
+ new shaka.util.FakeEvent(shaka.util.FakeEvent.EventName.GapJumped));
212
234
  }
213
235
  };
214
236
 
@@ -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
  /**
@@ -41,6 +41,20 @@ shaka.media.Playhead = class {
41
41
  */
42
42
  setStartTime(startTime) {}
43
43
 
44
+ /**
45
+ * Get the number of playback stalls detected by the StallDetector.
46
+ *
47
+ * @return {number}
48
+ */
49
+ getStallsDetected() {}
50
+
51
+ /**
52
+ * Get the number of playback gaps jumped by the GapJumpingController.
53
+ *
54
+ * @return {number}
55
+ */
56
+ getGapsJumped() {}
57
+
44
58
  /**
45
59
  * Get the current playhead position. The position will be restricted to valid
46
60
  * time ranges.
@@ -133,6 +147,16 @@ shaka.media.SrcEqualsPlayhead = class {
133
147
  return time || 0;
134
148
  }
135
149
 
150
+ /** @override */
151
+ getStallsDetected() {
152
+ return 0;
153
+ }
154
+
155
+ /** @override */
156
+ getGapsJumped() {
157
+ return 0;
158
+ }
159
+
136
160
  /** @override */
137
161
  notifyOfBufferingChange() {}
138
162
  };
@@ -160,8 +184,10 @@ shaka.media.MediaSourcePlayhead = class {
160
184
  * @param {function()} onSeek
161
185
  * Called when the user agent seeks to a time within the presentation
162
186
  * timeline.
187
+ * @param {function(!Event)} onEvent
188
+ * Called when an event is raised to be sent to the application.
163
189
  */
164
- constructor(mediaElement, manifest, config, startTime, onSeek) {
190
+ constructor(mediaElement, manifest, config, startTime, onSeek, onEvent) {
165
191
  /**
166
192
  * The seek range must be at least this number of seconds long. If it is
167
193
  * smaller than this, change it to be this big so we don't repeatedly seek
@@ -192,12 +218,17 @@ shaka.media.MediaSourcePlayhead = class {
192
218
  /** @private {?number} */
193
219
  this.lastCorrectiveSeek_ = null;
194
220
 
221
+ /** @private {shaka.media.StallDetector} */
222
+ this.stallDetector_ =
223
+ this.createStallDetector_(mediaElement, config, onEvent);
224
+
195
225
  /** @private {shaka.media.GapJumpingController} */
196
226
  this.gapController_ = new shaka.media.GapJumpingController(
197
227
  mediaElement,
198
228
  manifest.presentationTimeline,
199
229
  config,
200
- this.createStallDetector_(mediaElement, config));
230
+ this.stallDetector_,
231
+ onEvent);
201
232
 
202
233
  /** @private {shaka.media.VideoWrapper} */
203
234
  this.videoWrapper_ = new shaka.media.VideoWrapper(
@@ -261,6 +292,16 @@ shaka.media.MediaSourcePlayhead = class {
261
292
  return time;
262
293
  }
263
294
 
295
+ /** @override */
296
+ getStallsDetected() {
297
+ return this.stallDetector_.getStallsDetected();
298
+ }
299
+
300
+ /** @override */
301
+ getGapsJumped() {
302
+ return this.gapController_.getGapsJumped();
303
+ }
304
+
264
305
  /**
265
306
  * Gets the playhead's initial position in seconds.
266
307
  *
@@ -475,10 +516,12 @@ shaka.media.MediaSourcePlayhead = class {
475
516
  *
476
517
  * @param {!HTMLMediaElement} mediaElement
477
518
  * @param {shaka.extern.StreamingConfiguration} config
519
+ * @param {function(!Event)} onEvent
520
+ * Called when an event is raised to be sent to the application.
478
521
  * @return {shaka.media.StallDetector}
479
522
  * @private
480
523
  */
481
- createStallDetector_(mediaElement, config) {
524
+ createStallDetector_(mediaElement, config, onEvent) {
482
525
  if (!config.stallEnabled) {
483
526
  return null;
484
527
  }
@@ -492,7 +535,7 @@ shaka.media.MediaSourcePlayhead = class {
492
535
  // playhead forward.
493
536
  const detector = new shaka.media.StallDetector(
494
537
  new shaka.media.StallDetector.MediaElementImplementation(mediaElement),
495
- threshold);
538
+ threshold, onEvent);
496
539
 
497
540
  detector.onStall((at, duration) => {
498
541
  shaka.log.debug(`Stall detected at ${at} for ${duration} seconds.`);
@@ -9,6 +9,7 @@ goog.provide('shaka.media.StallDetector.Implementation');
9
9
  goog.provide('shaka.media.StallDetector.MediaElementImplementation');
10
10
 
11
11
  goog.require('shaka.media.TimeRangesUtils');
12
+ goog.require('shaka.util.FakeEvent');
12
13
  goog.require('shaka.util.IReleasable');
13
14
 
14
15
  /**
@@ -23,11 +24,14 @@ shaka.media.StallDetector = class {
23
24
  /**
24
25
  * @param {shaka.media.StallDetector.Implementation} implementation
25
26
  * @param {number} stallThresholdSeconds
27
+ * @param {function(!Event)} onEvent
28
+ * Called when an event is raised to be sent to the application.
26
29
  */
27
- constructor(implementation, stallThresholdSeconds) {
30
+ constructor(implementation, stallThresholdSeconds, onEvent) {
31
+ /** @private {?function(!Event)} */
32
+ this.onEvent_ = onEvent;
28
33
  /** @private {shaka.media.StallDetector.Implementation} */
29
34
  this.implementation_ = implementation;
30
-
31
35
  /** @private {boolean} */
32
36
  this.wasMakingProgress_ = implementation.shouldBeMakingProgress();
33
37
  /** @private {number} */
@@ -36,6 +40,8 @@ shaka.media.StallDetector = class {
36
40
  this.lastUpdateSeconds_ = implementation.getWallSeconds();
37
41
  /** @private {boolean} */
38
42
  this.didJump_ = false;
43
+ /** @private {number} */
44
+ this.stallsDetected_ = 0;
39
45
 
40
46
  /**
41
47
  * The amount of time in seconds that we must have the same value of
@@ -53,6 +59,7 @@ shaka.media.StallDetector = class {
53
59
  release() {
54
60
  // Drop external references to make things easier on the GC.
55
61
  this.implementation_ = null;
62
+ this.onEvent_ = null;
56
63
  this.onStall_ = () => {};
57
64
  }
58
65
 
@@ -66,6 +73,13 @@ shaka.media.StallDetector = class {
66
73
  this.onStall_ = doThis;
67
74
  }
68
75
 
76
+ /**
77
+ * Returns the number of playback stalls detected.
78
+ */
79
+ getStallsDetected() {
80
+ return this.stallsDetected_;
81
+ }
82
+
69
83
  /**
70
84
  * Have the detector update itself and fire the "on stall" callback if a stall
71
85
  * was detected.
@@ -100,6 +114,9 @@ shaka.media.StallDetector = class {
100
114
  // If the onStall_ method updated the current time, update our stored
101
115
  // value so we don't think that was an update.
102
116
  this.value_ = impl.getPresentationSeconds();
117
+ this.stallsDetected_++;
118
+ this.onEvent_(new shaka.util.FakeEvent(
119
+ shaka.util.FakeEvent.EventName.StallDetected));
103
120
  }
104
121
 
105
122
  return triggerCallback;
@@ -1215,6 +1215,13 @@ shaka.media.StreamingEngine = class {
1215
1215
  mediaState.performingUpdate = true;
1216
1216
 
1217
1217
  try {
1218
+ if (reference.getStatus() ==
1219
+ shaka.media.SegmentReference.Status.MISSING) {
1220
+ throw new shaka.util.Error(
1221
+ shaka.util.Error.Severity.RECOVERABLE,
1222
+ shaka.util.Error.Category.NETWORK,
1223
+ shaka.util.Error.Code.SEGMENT_MISSING);
1224
+ }
1218
1225
  await this.initSourceBuffer_(mediaState, reference);
1219
1226
  this.destroyer_.ensureNotDestroyed();
1220
1227
  if (this.fatalError_) {
@@ -1605,8 +1612,7 @@ shaka.media.StreamingEngine = class {
1605
1612
  reference.syncTime == null ? reference.startTime : reference.syncTime,
1606
1613
  reference.endTime,
1607
1614
  hasClosedCaptions,
1608
- seeked,
1609
- this.manifest_.sequenceMode);
1615
+ seeked);
1610
1616
  this.destroyer_.ensureNotDestroyed();
1611
1617
  shaka.log.v2(logPrefix, 'appended media segment');
1612
1618
  }
@@ -1695,7 +1701,7 @@ shaka.media.StreamingEngine = class {
1695
1701
  };
1696
1702
 
1697
1703
  // Dispatch an event to notify the application about the emsg box.
1698
- const eventName = shaka.Player.EventName.Emsg;
1704
+ const eventName = shaka.util.FakeEvent.EventName.Emsg;
1699
1705
  const data = (new Map()).set('detail', emsg);
1700
1706
  const event = new shaka.util.FakeEvent(eventName, data);
1701
1707
  this.playerInterface_.onEvent(event);
@@ -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.
@@ -302,6 +302,7 @@ shaka.offline.ManifestConverter = class {
302
302
  return {
303
303
  id: id,
304
304
  language: '',
305
+ disabledUntilTime: 0,
305
306
  primary: false,
306
307
  audio: null,
307
308
  video: null,