shaka-player 3.3.4 → 4.0.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 (184) hide show
  1. package/.github/workflows/build-and-test.yaml +0 -1
  2. package/.github/workflows/demo-version-index.yaml +8 -0
  3. package/.github/workflows/release-please.yaml +23 -2
  4. package/.github/workflows/selenium-lab-tests.yaml +0 -1
  5. package/.github/workflows/sync-labels.yaml +33 -0
  6. package/.github/workflows/update-issues.yaml +29 -0
  7. package/.github/workflows/validate-pr-title.yaml +35 -0
  8. package/AUTHORS +1 -0
  9. package/CHANGELOG.md +198 -32
  10. package/CONTRIBUTORS +1 -0
  11. package/README.md +10 -7
  12. package/app-engine/demo-version-index/README.md +3 -3
  13. package/app-engine/demo-version-index/app.yaml +27 -1
  14. package/app-engine/demo-version-index/generate.py +164 -0
  15. package/app-engine/demo-version-index/main.py +4 -155
  16. package/app-engine/demo-version-index/requirements.txt +0 -1
  17. package/build/README.md +2 -2
  18. package/build/compiler.py +1 -1
  19. package/build/generateTsDefs.py +0 -2
  20. package/build/shaka-lab.yaml +0 -2
  21. package/build/test.py +7 -1
  22. package/build/types/core +0 -1
  23. package/build/types/polyfill +0 -3
  24. package/demo/common/asset.js +17 -1
  25. package/demo/common/assets.js +29 -0
  26. package/demo/common/message_ids.js +6 -3
  27. package/demo/config.js +10 -11
  28. package/demo/custom.js +39 -0
  29. package/demo/demo_utils.js +0 -1
  30. package/demo/locales/en.json +6 -3
  31. package/demo/locales/source.json +22 -10
  32. package/demo/main.js +9 -6
  33. package/demo/search.js +2 -1
  34. package/dist/controls.css +1 -1
  35. package/dist/demo.compiled.debug.js +71 -64
  36. package/dist/demo.compiled.debug.map +3 -3
  37. package/dist/demo.compiled.js +71 -64
  38. package/dist/demo.compiled.map +3 -3
  39. package/dist/demo.css +1 -1
  40. package/dist/demo.css.map +1 -1
  41. package/dist/deps.js +16 -20
  42. package/dist/locales.js +31 -29
  43. package/dist/receiver.compiled.debug.js +32 -28
  44. package/dist/receiver.compiled.debug.map +3 -3
  45. package/dist/receiver.compiled.js +32 -28
  46. package/dist/receiver.compiled.map +3 -3
  47. package/dist/shaka-player.compiled.d.ts +78 -83
  48. package/dist/shaka-player.compiled.debug.d.ts +78 -83
  49. package/dist/shaka-player.compiled.debug.externs.js +126 -126
  50. package/dist/shaka-player.compiled.debug.js +426 -483
  51. package/dist/shaka-player.compiled.debug.map +4 -4
  52. package/dist/shaka-player.compiled.externs.js +126 -126
  53. package/dist/shaka-player.compiled.js +838 -856
  54. package/dist/shaka-player.compiled.map +4 -4
  55. package/dist/shaka-player.ui.d.ts +80 -83
  56. package/dist/shaka-player.ui.debug.d.ts +80 -83
  57. package/dist/shaka-player.ui.debug.externs.js +134 -126
  58. package/dist/shaka-player.ui.debug.js +488 -543
  59. package/dist/shaka-player.ui.debug.map +4 -4
  60. package/dist/shaka-player.ui.externs.js +134 -126
  61. package/dist/shaka-player.ui.js +980 -994
  62. package/dist/shaka-player.ui.map +4 -4
  63. package/docs/tutorials/config.md +0 -2
  64. package/docs/tutorials/drm-config.md +5 -0
  65. package/docs/tutorials/fairplay.md +7 -22
  66. package/docs/tutorials/faq.md +0 -11
  67. package/docs/tutorials/index.json +2 -8
  68. package/docs/tutorials/plugins.md +1 -3
  69. package/docs/tutorials/upgrade.md +83 -11
  70. package/externs/polyfill.js +15 -0
  71. package/externs/shaka/manifest.js +5 -1
  72. package/externs/shaka/net.js +12 -0
  73. package/externs/shaka/offline.js +5 -1
  74. package/externs/shaka/player.js +39 -32
  75. package/externs/shaka/text.js +12 -12
  76. package/externs/sourcebuffer.js +15 -0
  77. package/lib/ads/client_side_ad.js +13 -2
  78. package/lib/ads/client_side_ad_manager.js +22 -4
  79. package/lib/cast/cast_utils.js +0 -1
  80. package/lib/dash/content_protection.js +85 -5
  81. package/lib/dash/dash_parser.js +1 -1
  82. package/lib/hls/hls_parser.js +378 -743
  83. package/lib/hls/manifest_text_parser.js +2 -3
  84. package/lib/media/drm_engine.js +102 -53
  85. package/lib/media/gap_jumping_controller.js +10 -45
  86. package/lib/media/media_source_engine.js +87 -15
  87. package/lib/media/playhead.js +2 -5
  88. package/lib/media/presentation_timeline.js +1 -2
  89. package/lib/media/segment_index.js +13 -46
  90. package/lib/media/segment_reference.js +47 -1
  91. package/lib/media/streaming_engine.js +29 -11
  92. package/lib/media/time_ranges_utils.js +4 -9
  93. package/lib/net/networking_engine.js +3 -0
  94. package/lib/offline/indexeddb/v1_storage_cell.js +1 -0
  95. package/lib/offline/indexeddb/v2_storage_cell.js +1 -0
  96. package/lib/offline/manifest_converter.js +1 -0
  97. package/lib/offline/storage.js +100 -132
  98. package/lib/player.js +17 -201
  99. package/lib/polyfill/media_capabilities.js +12 -6
  100. package/lib/polyfill/mediasource.js +0 -28
  101. package/lib/polyfill/patchedmediakeys_nop.js +2 -0
  102. package/lib/polyfill/patchedmediakeys_webkit.js +2 -0
  103. package/lib/text/cue.js +0 -6
  104. package/lib/text/lrc_text_parser.js +8 -0
  105. package/lib/text/mp4_ttml_parser.js +8 -0
  106. package/lib/text/mp4_vtt_parser.js +8 -0
  107. package/lib/text/sbv_text_parser.js +8 -0
  108. package/lib/text/simple_text_displayer.js +1 -7
  109. package/lib/text/srt_text_parser.js +8 -0
  110. package/lib/text/ssa_text_parser.js +8 -0
  111. package/lib/text/text_engine.js +19 -3
  112. package/lib/text/ttml_text_parser.js +8 -0
  113. package/lib/text/ui_text_displayer.js +1 -7
  114. package/lib/text/vtt_text_parser.js +24 -2
  115. package/lib/text/web_vtt_generator.js +1 -7
  116. package/lib/util/error.js +9 -13
  117. package/lib/util/fairplay_utils.js +109 -0
  118. package/lib/util/functional.js +0 -42
  119. package/lib/util/mime_utils.js +2 -0
  120. package/lib/util/platform.js +45 -0
  121. package/lib/util/player_configuration.js +12 -6
  122. package/lib/util/pssh.js +23 -4
  123. package/lib/util/stream_utils.js +92 -22
  124. package/lib/util/uint8array_utils.js +0 -16
  125. package/lib/util/xml_utils.js +1 -1
  126. package/package.json +1 -1
  127. package/roadmap.md +19 -19
  128. package/shaka-player.uncompiled.js +0 -3
  129. package/test/cast/cast_utils_unit.js +1 -0
  130. package/test/dash/dash_parser_content_protection_unit.js +44 -0
  131. package/test/dash/dash_parser_manifest_unit.js +0 -24
  132. package/test/dash/dash_parser_segment_template_unit.js +3 -4
  133. package/test/demo/demo_unit.js +2 -1
  134. package/test/hls/hls_live_unit.js +99 -220
  135. package/test/hls/hls_parser_unit.js +515 -432
  136. package/test/media/drm_engine_unit.js +86 -120
  137. package/test/media/media_source_engine_integration.js +26 -3
  138. package/test/media/media_source_engine_unit.js +7 -6
  139. package/test/media/playhead_unit.js +27 -191
  140. package/test/media/segment_index_unit.js +15 -25
  141. package/test/media/streaming_engine_integration.js +4 -39
  142. package/test/media/streaming_engine_unit.js +3 -2
  143. package/test/media/time_ranges_utils_unit.js +0 -5
  144. package/test/offline/manifest_convert_unit.js +34 -4
  145. package/test/offline/offline_manifest_parser_unit.js +1 -0
  146. package/test/offline/storage_integration.js +5 -4
  147. package/test/player_external.js +0 -6
  148. package/test/player_integration.js +16 -0
  149. package/test/player_unit.js +26 -0
  150. package/test/test/util/fake_media_source_engine.js +11 -4
  151. package/test/test/util/manifest_generator.js +3 -1
  152. package/test/test/util/manifest_parser_util.js +4 -1
  153. package/test/test/util/offline_utils.js +1 -0
  154. package/test/test/util/streaming_engine_util.js +1 -0
  155. package/test/test/util/test_scheme.js +24 -1
  156. package/test/test/util/util.js +4 -4
  157. package/test/text/cue_integration.js +2 -2
  158. package/test/text/lrc_text_parser_unit.js +6 -7
  159. package/test/text/mp4_ttml_parser_unit.js +6 -4
  160. package/test/text/mp4_vtt_parser_unit.js +6 -5
  161. package/test/text/sbv_text_parser_unit.js +5 -6
  162. package/test/text/srt_text_parser_unit.js +5 -6
  163. package/test/text/ssa_text_parser_unit.js +17 -29
  164. package/test/text/text_engine_unit.js +45 -5
  165. package/test/text/ttml_text_parser_unit.js +77 -77
  166. package/test/text/vtt_text_parser_unit.js +55 -57
  167. package/test/ui/ui_integration.js +0 -16
  168. package/test/ui/ui_unit.js +27 -0
  169. package/test/util/cmcd_manager_unit.js +3 -0
  170. package/test/util/stream_utils_unit.js +23 -7
  171. package/ui/controls.js +65 -40
  172. package/ui/fullscreen_button.js +26 -6
  173. package/ui/less/containers.less +3 -3
  174. package/ui/locales/en.json +1 -0
  175. package/ui/locales/source.json +5 -0
  176. package/ui/resolution_selection.js +31 -33
  177. package/ui/ui.js +0 -17
  178. package/docs/tutorials/upgrade-v2-5.md +0 -441
  179. package/docs/tutorials/upgrade-v3.md +0 -38
  180. package/lib/polyfill/mathround.js +0 -49
  181. package/lib/polyfill/patchedmediakeys_apple.js +0 -744
  182. package/lib/polyfill/patchedmediakeys_ms.js +0 -698
  183. package/lib/util/mutex.js +0 -52
  184. package/test/util/functional_unit.js +0 -82
@@ -179,9 +179,8 @@ shaka.hls.ManifestTextParser = class {
179
179
  segmentTags.push(currentMapTag);
180
180
  }
181
181
  // The URI appears after all of the tags describing the segment.
182
- const segment =
183
- new shaka.hls.Segment(absoluteSegmentUri, segmentTags,
184
- partialSegmentTags);
182
+ const segment = new shaka.hls.Segment(absoluteSegmentUri, segmentTags,
183
+ partialSegmentTags);
185
184
  segments.push(segment);
186
185
  segmentTags = [];
187
186
  partialSegmentTags = [];
@@ -14,7 +14,6 @@ goog.require('shaka.util.BufferUtils');
14
14
  goog.require('shaka.util.Destroyer');
15
15
  goog.require('shaka.util.Error');
16
16
  goog.require('shaka.util.EventManager');
17
- goog.require('shaka.util.FairPlayUtils');
18
17
  goog.require('shaka.util.FakeEvent');
19
18
  goog.require('shaka.util.IDestroyable');
20
19
  goog.require('shaka.util.Iterables');
@@ -128,6 +127,9 @@ shaka.media.DrmEngine = class {
128
127
 
129
128
  /** @private {boolean} */
130
129
  this.srcEquals_ = false;
130
+
131
+ /** @private {Promise} */
132
+ this.mediaKeysAttached_ = null;
131
133
  }
132
134
 
133
135
  /** @override */
@@ -184,6 +186,7 @@ shaka.media.DrmEngine = class {
184
186
  this.onError_ = () => {};
185
187
  this.playerInterface_ = null;
186
188
  this.srcEquals_ = false;
189
+ this.mediaKeysAttached_ = null;
187
190
  }
188
191
 
189
192
  /**
@@ -387,7 +390,59 @@ shaka.media.DrmEngine = class {
387
390
  }
388
391
 
389
392
  /**
390
- * Attach MediaKeys to the video element and start processing events.
393
+ * Attach MediaKeys to the video element
394
+ * @return {Promise}
395
+ * @private
396
+ */
397
+ async attachMediaKeys_() {
398
+ if (this.video_.mediaKeys) {
399
+ return;
400
+ }
401
+
402
+ // An attach process has already started, let's wait it out
403
+ if (this.mediaKeysAttached_) {
404
+ await this.mediaKeysAttached_;
405
+
406
+ return;
407
+ }
408
+
409
+ try {
410
+ this.mediaKeysAttached_ = this.video_.setMediaKeys(this.mediaKeys_);
411
+
412
+ await this.mediaKeysAttached_;
413
+ } catch (exception) {
414
+ goog.asserts.assert(exception instanceof Error, 'Wrong error type!');
415
+
416
+ this.onError_(new shaka.util.Error(
417
+ shaka.util.Error.Severity.CRITICAL,
418
+ shaka.util.Error.Category.DRM,
419
+ shaka.util.Error.Code.FAILED_TO_ATTACH_TO_VIDEO,
420
+ exception.message));
421
+ }
422
+
423
+ this.destroyer_.ensureNotDestroyed();
424
+ }
425
+
426
+ /**
427
+ * Processes encrypted event and start licence challenging
428
+ * @return {!Promise}
429
+ * @private
430
+ */
431
+ async onEncryptedEvent_(event) {
432
+ /**
433
+ * MediaKeys should be added when receiving an encrypted event. Setting
434
+ * mediaKeys before could result into encrypted event not being fired on
435
+ * some browsers
436
+ */
437
+ await this.attachMediaKeys_();
438
+
439
+ this.newInitData(
440
+ event.initDataType,
441
+ shaka.util.BufferUtils.toUint8(event.initData));
442
+ }
443
+
444
+ /**
445
+ * Start processing events.
391
446
  * @param {HTMLMediaElement} video
392
447
  * @return {!Promise}
393
448
  */
@@ -420,27 +475,30 @@ shaka.media.DrmEngine = class {
420
475
  () => this.closeOpenSessions_());
421
476
  }
422
477
 
423
- let setMediaKeys = this.video_.setMediaKeys(this.mediaKeys_);
424
- setMediaKeys = setMediaKeys.catch((exception) => {
425
- goog.asserts.assert(exception instanceof Error, 'Wrong error type!');
426
- return Promise.reject(new shaka.util.Error(
427
- shaka.util.Error.Severity.CRITICAL,
428
- shaka.util.Error.Category.DRM,
429
- shaka.util.Error.Code.FAILED_TO_ATTACH_TO_VIDEO,
430
- exception.message));
431
- });
478
+ const manifestInitData = this.currentDrmInfo_.initData.find(
479
+ (initDataOverride) => initDataOverride.initData.length > 0);
432
480
 
433
- await setMediaKeys;
434
- this.destroyer_.ensureNotDestroyed();
481
+ /**
482
+ * We can attach media keys before the playback actually begins when:
483
+ * - Using legacy implementations requires MediaKeys to be set before
484
+ * having webkitneedkey / msneedkey event, which will be translated as
485
+ * an encrypted event by the polyfills
486
+ * - Some initData already has been generated (through the manifest)
487
+ * - In case of an offline session
488
+ */
489
+ if (manifestInitData ||
490
+ shaka.util.Platform.isMediaKeysPolyfilled() ||
491
+ this.offlineSessionIds_.length) {
492
+ await this.attachMediaKeys_();
493
+ }
435
494
 
436
495
  this.createOrLoad();
437
- if (!this.currentDrmInfo_.initData.length &&
438
- !this.offlineSessionIds_.length) {
439
- // Explicit init data for any one stream or an offline session is
440
- // sufficient to suppress 'encrypted' events for all streams.
441
- const cb = (e) => this.newInitData(
442
- e.initDataType, shaka.util.BufferUtils.toUint8(e.initData));
443
- this.eventManager_.listen(this.video_, 'encrypted', cb);
496
+
497
+ // Explicit init data for any one stream or an offline session is
498
+ // sufficient to suppress 'encrypted' events for all streams.
499
+ if (!manifestInitData && !this.offlineSessionIds_.length) {
500
+ this.eventManager_.listen(
501
+ this.video_, 'encrypted', (e) => this.onEncryptedEvent_(e));
444
502
  }
445
503
  }
446
504
 
@@ -589,6 +647,10 @@ shaka.media.DrmEngine = class {
589
647
  * @param {!Uint8Array} initData
590
648
  */
591
649
  newInitData(initDataType, initData) {
650
+ if (!initData.length) {
651
+ return;
652
+ }
653
+
592
654
  // Suppress duplicate init data.
593
655
  // Note that some init data are extremely large and can't portably be used
594
656
  // as keys in a dictionary.
@@ -1106,6 +1168,7 @@ shaka.media.DrmEngine = class {
1106
1168
 
1107
1169
  const metadata = {
1108
1170
  initData: null,
1171
+ initDataType: null,
1109
1172
  loaded: false,
1110
1173
  oldExpiration: Infinity,
1111
1174
  updatePromise: null,
@@ -1182,6 +1245,7 @@ shaka.media.DrmEngine = class {
1182
1245
 
1183
1246
  const metadata = {
1184
1247
  initData: initData,
1248
+ initDataType: initDataType,
1185
1249
  loaded: false,
1186
1250
  oldExpiration: Infinity,
1187
1251
  updatePromise: null,
@@ -1189,22 +1253,6 @@ shaka.media.DrmEngine = class {
1189
1253
  };
1190
1254
  this.activeSessions_.set(session, metadata);
1191
1255
 
1192
- try {
1193
- initData = this.config_.initDataTransform(
1194
- initData, initDataType, this.currentDrmInfo_);
1195
- } catch (error) {
1196
- let shakaError = error;
1197
- if (!(error instanceof shaka.util.Error)) {
1198
- shakaError = new shaka.util.Error(
1199
- shaka.util.Error.Severity.CRITICAL,
1200
- shaka.util.Error.Category.DRM,
1201
- shaka.util.Error.Code.INIT_DATA_TRANSFORM_ERROR,
1202
- error);
1203
- }
1204
- this.onError_(shakaError);
1205
- return;
1206
- }
1207
-
1208
1256
  if (this.config_.logLicenseExchange) {
1209
1257
  const str = shaka.util.Uint8ArrayUtils.toBase64(initData);
1210
1258
  shaka.log.info('EME init data: type=', initDataType, 'data=', str);
@@ -1239,23 +1287,6 @@ shaka.media.DrmEngine = class {
1239
1287
  });
1240
1288
  }
1241
1289
 
1242
- /**
1243
- * @param {!Uint8Array} initData
1244
- * @param {string} initDataType
1245
- * @param {?shaka.extern.DrmInfo} drmInfo
1246
- * @return {!Uint8Array}
1247
- */
1248
- static defaultInitDataTransform(initData, initDataType, drmInfo) {
1249
- if (initDataType == 'skd') {
1250
- const cert = drmInfo.serverCertificate;
1251
- const contentId =
1252
- shaka.util.FairPlayUtils.defaultGetContentId(initData);
1253
- initData = shaka.util.FairPlayUtils.initDataTransform(
1254
- initData, contentId, cert);
1255
- }
1256
- return initData;
1257
- }
1258
-
1259
1290
  /**
1260
1291
  * @param {!MediaKeyMessageEvent} event
1261
1292
  * @private
@@ -1316,6 +1347,11 @@ shaka.media.DrmEngine = class {
1316
1347
  request.method = 'POST';
1317
1348
  request.licenseRequestType = event.messageType;
1318
1349
  request.sessionId = session.sessionId;
1350
+ request.drmInfo = this.currentDrmInfo_;
1351
+ if (metadata) {
1352
+ request.initData = metadata.initData;
1353
+ request.initDataType = metadata.initDataType;
1354
+ }
1319
1355
  // NOTE: allowCrossSiteCredentials can be set in a request filter.
1320
1356
 
1321
1357
  if (shaka.media.DrmEngine.isPlayReadyKeySystem(
@@ -1642,6 +1678,16 @@ shaka.media.DrmEngine = class {
1642
1678
 
1643
1679
  const testSystem = async (keySystem) => {
1644
1680
  try {
1681
+ // Our Polyfill will reject anything apart com.apple.fps key systems.
1682
+ // It seems the Safari modern EME API will allow to request a
1683
+ // MediaKeySystemAccess for the ClearKey CDM, create and update a key
1684
+ // session but playback will never start
1685
+ // Safari bug: https://bugs.webkit.org/show_bug.cgi?id=231006
1686
+ if (keySystem === 'org.w3.clearkey' &&
1687
+ shaka.util.Platform.isSafari()) {
1688
+ throw new Error('Unsupported keySystem');
1689
+ }
1690
+
1645
1691
  const access = await navigator.requestMediaKeySystemAccess(
1646
1692
  keySystem, configs);
1647
1693
 
@@ -2262,6 +2308,7 @@ shaka.media.DrmEngine = class {
2262
2308
  * @typedef {{
2263
2309
  * loaded: boolean,
2264
2310
  * initData: Uint8Array,
2311
+ * initDataType: ?string,
2265
2312
  * oldExpiration: number,
2266
2313
  * type: string,
2267
2314
  * updatePromise: shaka.util.PublicPromise
@@ -2273,6 +2320,8 @@ shaka.media.DrmEngine = class {
2273
2320
  * does not mean the session is 'usable'.
2274
2321
  * @property {Uint8Array} initData
2275
2322
  * The init data used to create the session.
2323
+ * @property {?string} initDataType
2324
+ * The init data type used to create the session.
2276
2325
  * @property {!MediaKeySession} session
2277
2326
  * The session object.
2278
2327
  * @property {number} oldExpiration
@@ -11,7 +11,6 @@ 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');
15
14
  goog.require('shaka.util.IReleasable');
16
15
  goog.require('shaka.util.Timer');
17
16
 
@@ -33,10 +32,8 @@ shaka.media.GapJumpingController = class {
33
32
  * playable region. The gap jumping controller takes ownership over the
34
33
  * stall detector.
35
34
  * If no stall detection logic is desired, |null| may be provided.
36
- * @param {function(!Event)} onEvent Called when an event is raised to be sent
37
- * to the application.
38
35
  */
39
- constructor(video, timeline, config, stallDetector, onEvent) {
36
+ constructor(video, timeline, config, stallDetector) {
40
37
  /** @private {HTMLMediaElement} */
41
38
  this.video_ = video;
42
39
 
@@ -46,9 +43,6 @@ shaka.media.GapJumpingController = class {
46
43
  /** @private {?shaka.extern.StreamingConfiguration} */
47
44
  this.config_ = config;
48
45
 
49
- /** @private {?function(!Event)} */
50
- this.onEvent_ = onEvent;
51
-
52
46
  /** @private {shaka.util.EventManager} */
53
47
  this.eventManager_ = new shaka.util.EventManager();
54
48
 
@@ -58,9 +52,6 @@ shaka.media.GapJumpingController = class {
58
52
  /** @private {number} */
59
53
  this.prevReadyState_ = video.readyState;
60
54
 
61
- /** @private {boolean} */
62
- this.didFireLargeGap_ = false;
63
-
64
55
  /**
65
56
  * The stall detector tries to keep the playhead moving forward. It is
66
57
  * managed by the gap-jumping controller to avoid conflicts. On some
@@ -107,7 +98,6 @@ shaka.media.GapJumpingController = class {
107
98
  this.stallDetector_ = null;
108
99
  }
109
100
 
110
- this.onEvent_ = null;
111
101
  this.timeline_ = null;
112
102
  this.video_ = null;
113
103
  }
@@ -128,7 +118,6 @@ shaka.media.GapJumpingController = class {
128
118
  onSeeking() {
129
119
  this.seekingEventReceived_ = true;
130
120
  this.hadSegmentAppended_ = false;
131
- this.didFireLargeGap_ = false;
132
121
  }
133
122
 
134
123
 
@@ -165,7 +154,6 @@ shaka.media.GapJumpingController = class {
165
154
  // When the ready state changes, we have moved on, so we should fire the
166
155
  // large gap event if we see one.
167
156
  if (this.video_.readyState != this.prevReadyState_) {
168
- this.didFireLargeGap_ = false;
169
157
  this.prevReadyState_ = this.video_.readyState;
170
158
  }
171
159
 
@@ -175,7 +163,6 @@ shaka.media.GapJumpingController = class {
175
163
  }
176
164
 
177
165
 
178
- const smallGapLimit = this.config_.smallGapLimit;
179
166
  const currentTime = this.video_.currentTime;
180
167
  const buffered = this.video_.buffered;
181
168
  const gapDetectionThreshold = this.config_.gapDetectionThreshold;
@@ -203,8 +190,6 @@ shaka.media.GapJumpingController = class {
203
190
  }
204
191
 
205
192
  const jumpSize = jumpTo - currentTime;
206
- const isGapSmall = jumpSize <= smallGapLimit;
207
- let jumpLargeGap = false;
208
193
 
209
194
  // If we jump to exactly the gap start, we may detect a small gap due to
210
195
  // rounding errors or browser bugs. We can ignore these extremely small
@@ -213,37 +198,17 @@ shaka.media.GapJumpingController = class {
213
198
  return;
214
199
  }
215
200
 
216
- if (!isGapSmall && !this.didFireLargeGap_) {
217
- this.didFireLargeGap_ = true;
218
-
219
- // Event firing is synchronous.
220
- const data = new Map()
221
- .set('currentTime', currentTime)
222
- .set('gapSize', jumpSize);
223
- const event = new shaka.util.FakeEvent('largegap', data);
224
- event.cancelable = true;
225
- this.onEvent_(event);
226
-
227
- if (this.config_.jumpLargeGaps && !event.defaultPrevented) {
228
- jumpLargeGap = true;
229
- } else {
230
- shaka.log.info('Ignoring large gap at', currentTime, 'size', jumpSize);
231
- }
201
+ if (gapIndex == 0) {
202
+ shaka.log.info(
203
+ 'Jumping forward', jumpSize,
204
+ 'seconds because of gap before start time of', jumpTo);
205
+ } else {
206
+ shaka.log.info(
207
+ 'Jumping forward', jumpSize, 'seconds because of gap starting at',
208
+ buffered.end(gapIndex - 1), 'and ending at', jumpTo);
232
209
  }
233
210
 
234
- if (isGapSmall || jumpLargeGap) {
235
- if (gapIndex == 0) {
236
- shaka.log.info(
237
- 'Jumping forward', jumpSize,
238
- 'seconds because of gap before start time of', jumpTo);
239
- } else {
240
- shaka.log.info(
241
- 'Jumping forward', jumpSize, 'seconds because of gap starting at',
242
- buffered.end(gapIndex - 1), 'and ending at', jumpTo);
243
- }
244
-
245
- this.video_.currentTime = jumpTo;
246
- }
211
+ this.video_.currentTime = jumpTo;
247
212
  }
248
213
  };
249
214
 
@@ -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>,
@@ -196,6 +204,8 @@ shaka.media.MediaSourceEngine = class {
196
204
  // TTML types
197
205
  'application/ttml+xml',
198
206
  'application/mp4; codecs="stpp"',
207
+ // Containerless types
208
+ ...shaka.media.MediaSourceEngine.RAW_FORMATS,
199
209
  ];
200
210
 
201
211
  const support = {};
@@ -310,10 +320,13 @@ shaka.media.MediaSourceEngine = class {
310
320
  * according to MediaSourceEngine.isStreamSupported.
311
321
  * @param {boolean} forceTransmuxTS
312
322
  * If true, this will transmux TS content even if it is natively supported.
323
+ * @param {boolean=} sequenceMode
324
+ * If true, the media segments are appended to the SourceBuffer in strict
325
+ * sequence.
313
326
  *
314
327
  * @return {!Promise}
315
328
  */
316
- async init(streamsByType, forceTransmuxTS) {
329
+ async init(streamsByType, forceTransmuxTS, sequenceMode=false) {
317
330
  const ContentType = shaka.util.ManifestParserUtils.ContentType;
318
331
 
319
332
  await this.mediaSourceOpen_;
@@ -327,7 +340,7 @@ shaka.media.MediaSourceEngine = class {
327
340
  let mimeType = shaka.util.MimeUtils.getFullType(
328
341
  stream.mimeType, stream.codecs);
329
342
  if (contentType == ContentType.TEXT) {
330
- this.reinitText(mimeType);
343
+ this.reinitText(mimeType, sequenceMode);
331
344
  } else {
332
345
  if ((forceTransmuxTS || !MediaSource.isTypeSupported(mimeType)) &&
333
346
  shaka.media.Transmuxer.isSupported(mimeType, contentType)) {
@@ -336,6 +349,10 @@ shaka.media.MediaSourceEngine = class {
336
349
  shaka.media.Transmuxer.convertTsCodecs(contentType, mimeType);
337
350
  }
338
351
  const sourceBuffer = this.mediaSource_.addSourceBuffer(mimeType);
352
+ if (sequenceMode) {
353
+ sourceBuffer.mode =
354
+ shaka.media.MediaSourceEngine.SourceBufferMode_.SEQUENCE;
355
+ }
339
356
  this.eventManager_.listen(
340
357
  sourceBuffer, 'error',
341
358
  () => this.onError_(contentType));
@@ -345,6 +362,7 @@ shaka.media.MediaSourceEngine = class {
345
362
  this.sourceBuffers_[contentType] = sourceBuffer;
346
363
  this.sourceBufferTypes_[contentType] = mimeType;
347
364
  this.queues_[contentType] = [];
365
+ this.expectedEncryption_[contentType] = !!stream.drmInfos.length;
348
366
  }
349
367
  }
350
368
  }
@@ -352,12 +370,14 @@ shaka.media.MediaSourceEngine = class {
352
370
  /**
353
371
  * Reinitialize the TextEngine for a new text type.
354
372
  * @param {string} mimeType
373
+ * @param {boolean} sequenceMode
355
374
  */
356
- reinitText(mimeType) {
375
+ reinitText(mimeType, sequenceMode) {
357
376
  if (!this.textEngine_) {
358
377
  this.textEngine_ = new shaka.text.TextEngine(this.textDisplayer_);
359
378
  }
360
- this.textEngine_.initParser(mimeType);
379
+ this.textEngine_.initParser(mimeType, sequenceMode,
380
+ this.segmentRelativeVttTiming_);
361
381
  }
362
382
 
363
383
  /**
@@ -404,17 +424,15 @@ shaka.media.MediaSourceEngine = class {
404
424
  *
405
425
  * @param {shaka.util.ManifestParserUtils.ContentType} contentType
406
426
  * @param {number} time Playhead time
407
- * @param {number=} smallGapLimit
408
427
  * @return {boolean}
409
428
  */
410
- isBuffered(contentType, time, smallGapLimit) {
429
+ isBuffered(contentType, time) {
411
430
  const ContentType = shaka.util.ManifestParserUtils.ContentType;
412
431
  if (contentType == ContentType.TEXT) {
413
432
  return this.textEngine_.isBuffered(time);
414
433
  } else {
415
434
  const buffered = this.getBuffered_(contentType);
416
- return shaka.media.TimeRangesUtils.isBuffered(
417
- buffered, time, smallGapLimit);
435
+ return shaka.media.TimeRangesUtils.isBuffered(buffered, time);
418
436
  }
419
437
  }
420
438
 
@@ -496,11 +514,27 @@ shaka.media.MediaSourceEngine = class {
496
514
  * @param {?number} endTime relative to the start of the presentation
497
515
  * @param {?boolean} hasClosedCaptions True if the buffer contains CEA closed
498
516
  * captions
517
+ * @param {boolean=} seeked True if we just seeked
518
+ * @param {boolean=} sequenceMode True if sequence mode
499
519
  * @return {!Promise}
500
520
  */
501
- async appendBuffer(contentType, data, startTime, endTime, hasClosedCaptions) {
521
+ async appendBuffer(contentType, data, startTime, endTime, hasClosedCaptions,
522
+ seeked, sequenceMode) {
502
523
  const ContentType = shaka.util.ManifestParserUtils.ContentType;
503
524
 
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));
535
+ }
536
+ }
537
+
504
538
  if (contentType == ContentType.TEXT) {
505
539
  await this.textEngine_.appendBuffer(data, startTime, endTime);
506
540
  } else if (this.transmuxers_[contentType]) {
@@ -509,7 +543,7 @@ shaka.media.MediaSourceEngine = class {
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', sequenceMode || false);
513
547
  }
514
548
 
515
549
  if (transmuxedData.metadata) {
@@ -536,7 +570,7 @@ shaka.media.MediaSourceEngine = class {
536
570
  contentType, () => this.append_(contentType, transmuxedSegment));
537
571
  } else if (hasClosedCaptions) {
538
572
  if (!this.textEngine_) {
539
- this.reinitText('text/vtt');
573
+ this.reinitText('text/vtt', sequenceMode || false);
540
574
  }
541
575
  // If it is the init segment for closed captions, initialize the closed
542
576
  // caption parser.
@@ -664,13 +698,18 @@ shaka.media.MediaSourceEngine = class {
664
698
  * @param {number} appendWindowEnd The timestamp to set the append window end
665
699
  * to. For future appends, frames/samples with timestamps greater than this
666
700
  * value will be dropped.
701
+ * @param {boolean} sequenceMode If true, the timestampOffset will not be
702
+ * applied in this step.
667
703
  * @return {!Promise}
668
704
  */
669
705
  async setStreamProperties(
670
- contentType, timestampOffset, appendWindowStart, appendWindowEnd) {
706
+ contentType, timestampOffset, appendWindowStart, appendWindowEnd,
707
+ sequenceMode) {
671
708
  const ContentType = shaka.util.ManifestParserUtils.ContentType;
672
709
  if (contentType == ContentType.TEXT) {
673
- this.textEngine_.setTimestampOffset(timestampOffset);
710
+ if (!sequenceMode) {
711
+ this.textEngine_.setTimestampOffset(timestampOffset);
712
+ }
674
713
  this.textEngine_.setAppendWindow(appendWindowStart, appendWindowEnd);
675
714
  return;
676
715
  }
@@ -687,7 +726,10 @@ shaka.media.MediaSourceEngine = class {
687
726
  this.enqueueOperation_(
688
727
  contentType,
689
728
  () => this.abort_(contentType)),
690
- this.enqueueOperation_(
729
+ // Don't set the timestampOffset here when in sequenceMode, since we
730
+ // use timestampOffset for a different purpose in that mode (e.g. to
731
+ // indicate where the current segment is).
732
+ sequenceMode ? Promise.resolve() : this.enqueueOperation_(
691
733
  contentType,
692
734
  () => this.setTimestampOffset_(contentType, timestampOffset)),
693
735
  this.enqueueOperation_(
@@ -1079,6 +1121,13 @@ shaka.media.MediaSourceEngine = class {
1079
1121
  }
1080
1122
  }
1081
1123
 
1124
+ /**
1125
+ * @param {boolean} segmentRelativeVttTiming
1126
+ */
1127
+ setSegmentRelativeVttTiming(segmentRelativeVttTiming) {
1128
+ this.segmentRelativeVttTiming_ = segmentRelativeVttTiming;
1129
+ }
1130
+
1082
1131
  /**
1083
1132
  * Apply platform-specific transformations to this segment to work around
1084
1133
  * issues in the platform.
@@ -1091,7 +1140,7 @@ shaka.media.MediaSourceEngine = class {
1091
1140
  */
1092
1141
  workAroundBrokenPlatforms_(segment, startTime, contentType) {
1093
1142
  const isInitSegment = startTime == null;
1094
- const encryptionExpected = this.video_.mediaKeys;
1143
+ const encryptionExpected = this.expectedEncryption_[contentType];
1095
1144
 
1096
1145
  // If:
1097
1146
  // 1. this is an init segment,
@@ -1139,3 +1188,26 @@ shaka.media.MediaSourceEngine.createObjectURL = window.URL.createObjectURL;
1139
1188
  * The PublicPromise which is associated with this operation.
1140
1189
  */
1141
1190
  shaka.media.MediaSourceEngine.Operation;
1191
+
1192
+
1193
+ /**
1194
+ * @enum {string}
1195
+ * @private
1196
+ */
1197
+ shaka.media.MediaSourceEngine.SourceBufferMode_ = {
1198
+ SEQUENCE: 'sequence',
1199
+ SEGMENTS: 'segments',
1200
+ };
1201
+
1202
+
1203
+ /**
1204
+ * MIME types of raw formats.
1205
+ *
1206
+ * @const {!Array.<string>}
1207
+ */
1208
+ shaka.media.MediaSourceEngine.RAW_FORMATS = [
1209
+ 'audio/aac',
1210
+ 'audio/ac3',
1211
+ 'audio/ec3',
1212
+ 'audio/mpeg',
1213
+ ];
@@ -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.