shaka-player 4.3.13 → 4.3.15

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 (137) hide show
  1. package/.github/ISSUE_TEMPLATE/bug_report.md +1 -1
  2. package/.github/ISSUE_TEMPLATE/question.md +7 -0
  3. package/.github/workflows/release-please.yaml +2 -27
  4. package/.github/workflows/shaka-bot-commands/lib.sh +37 -12
  5. package/.github/workflows/shaka-bot-commands/main.sh +4 -2
  6. package/.github/workflows/talk-to-shaka-bot.yaml +1 -0
  7. package/AUTHORS +5 -0
  8. package/CHANGELOG.md +96 -0
  9. package/CODE_OF_CONDUCT.md +1 -1
  10. package/CONTRIBUTORS +6 -0
  11. package/README.md +2 -0
  12. package/demo/common/asset.js +0 -1
  13. package/demo/common/assets.js +0 -1
  14. package/demo/custom.js +2 -0
  15. package/dist/controls.css +1 -1
  16. package/dist/controls.css.map +1 -1
  17. package/dist/demo.compiled.debug.js +1 -1
  18. package/dist/demo.compiled.debug.map +1 -1
  19. package/dist/demo.compiled.js +1 -1
  20. package/dist/demo.compiled.map +1 -1
  21. package/dist/deps.js +6 -6
  22. package/dist/locales.js +30 -30
  23. package/dist/receiver.compiled.debug.map +1 -1
  24. package/dist/receiver.compiled.map +1 -1
  25. package/dist/shaka-player.compiled.d.ts +25 -5
  26. package/dist/shaka-player.compiled.debug.d.ts +25 -5
  27. package/dist/shaka-player.compiled.debug.externs.js +73 -45
  28. package/dist/shaka-player.compiled.debug.js +426 -406
  29. package/dist/shaka-player.compiled.debug.map +4 -4
  30. package/dist/shaka-player.compiled.externs.js +73 -45
  31. package/dist/shaka-player.compiled.js +912 -903
  32. package/dist/shaka-player.compiled.map +4 -4
  33. package/dist/shaka-player.ui.d.ts +37 -6
  34. package/dist/shaka-player.ui.debug.d.ts +37 -6
  35. package/dist/shaka-player.ui.debug.externs.js +74 -45
  36. package/dist/shaka-player.ui.debug.js +449 -428
  37. package/dist/shaka-player.ui.debug.map +4 -4
  38. package/dist/shaka-player.ui.externs.js +74 -45
  39. package/dist/shaka-player.ui.js +1060 -1051
  40. package/dist/shaka-player.ui.map +4 -4
  41. package/docs/tutorials/ad_monetization.md +54 -0
  42. package/docs/tutorials/fairplay.md +2 -2
  43. package/docs/tutorials/index.json +1 -0
  44. package/docs/tutorials/plugins.md +4 -0
  45. package/docs/tutorials/text-displayer.md +15 -0
  46. package/docs/tutorials/ui-customization.md +29 -0
  47. package/docs/tutorials/ui.md +4 -1
  48. package/externs/lcevc.js +6 -0
  49. package/externs/shaka/manifest_parser.js +8 -0
  50. package/externs/shaka/player.js +1 -1
  51. package/externs/shaka/text.js +3 -1
  52. package/lib/abr/simple_abr_manager.js +1 -1
  53. package/lib/ads/client_side_ad.js +9 -0
  54. package/lib/ads/client_side_ad_manager.js +7 -2
  55. package/lib/ads/server_side_ad_manager.js +4 -0
  56. package/lib/cast/cast_proxy.js +18 -0
  57. package/lib/cea/cea608_memory.js +1 -1
  58. package/lib/cea/cea_decoder.js +11 -1
  59. package/lib/dash/dash_parser.js +75 -41
  60. package/lib/dash/mpd_utils.js +39 -27
  61. package/lib/deprecate/deprecate.js +1 -1
  62. package/lib/hls/hls_parser.js +106 -28
  63. package/lib/hls/hls_utils.js +5 -5
  64. package/lib/media/content_workarounds.js +30 -0
  65. package/lib/media/drm_engine.js +16 -12
  66. package/lib/media/manifest_parser.js +2 -1
  67. package/lib/media/media_source_engine.js +4 -2
  68. package/lib/media/play_rate_controller.js +1 -0
  69. package/lib/media/presentation_timeline.js +11 -0
  70. package/lib/media/quality_observer.js +1 -1
  71. package/lib/media/segment_reference.js +5 -2
  72. package/lib/media/streaming_engine.js +50 -30
  73. package/lib/media/transmuxer.js +1 -1
  74. package/lib/media/video_wrapper.js +4 -3
  75. package/lib/offline/offline_manifest_parser.js +5 -0
  76. package/lib/offline/session_deleter.js +0 -8
  77. package/lib/offline/storage.js +4 -1
  78. package/lib/player.js +80 -19
  79. package/lib/polyfill/media_capabilities.js +23 -3
  80. package/lib/polyfill/mediasource.js +5 -36
  81. package/lib/text/mp4_ttml_parser.js +2 -2
  82. package/lib/text/simple_text_displayer.js +7 -6
  83. package/lib/text/srt_text_parser.js +2 -2
  84. package/lib/text/text_engine.js +3 -2
  85. package/lib/text/ttml_text_parser.js +33 -5
  86. package/lib/text/ui_text_displayer.js +81 -15
  87. package/lib/text/vtt_text_parser.js +17 -3
  88. package/lib/util/buffer_utils.js +26 -4
  89. package/lib/util/cmcd_manager.js +10 -0
  90. package/lib/util/error.js +1 -0
  91. package/lib/util/fairplay_utils.js +1 -1
  92. package/lib/util/manifest_parser_utils.js +14 -8
  93. package/lib/util/object_utils.js +1 -1
  94. package/lib/util/periods.js +91 -36
  95. package/lib/util/platform.js +39 -2
  96. package/lib/util/player_configuration.js +5 -3
  97. package/lib/util/stream_utils.js +36 -28
  98. package/lib/util/xml_utils.js +5 -0
  99. package/package.json +3 -3
  100. package/test/cea/cea_decoder_unit.js +40 -0
  101. package/test/dash/dash_parser_live_unit.js +5 -8
  102. package/test/hls/hls_parser_unit.js +78 -3
  103. package/test/media/buffering_observer_unit.js +13 -0
  104. package/test/media/content_workarounds_unit.js +1 -1
  105. package/test/media/drm_engine_unit.js +30 -3
  106. package/test/media/media_source_engine_unit.js +1 -1
  107. package/test/media/transmuxer_integration.js +2 -2
  108. package/test/offline/storage_integration.js +3 -0
  109. package/test/player_integration.js +49 -41
  110. package/test/player_load_graph_integration.js +13 -7
  111. package/test/player_src_equals_integration.js +2 -2
  112. package/test/player_unit.js +40 -3
  113. package/test/polyfill/media_capabilities_unit.js +12 -6
  114. package/test/test/util/fake_drm_engine.js +7 -0
  115. package/test/test/util/layout_tests.js +2 -1
  116. package/test/test/util/simple_fakes.js +2 -2
  117. package/test/test/util/test_scheme.js +3 -0
  118. package/test/test/util/waiter.js +5 -0
  119. package/test/text/mp4_ttml_parser_unit.js +4 -4
  120. package/test/text/simple_text_displayer_unit.js +1 -1
  121. package/test/text/srt_text_parser_unit.js +1 -1
  122. package/test/text/text_engine_unit.js +6 -0
  123. package/test/text/ttml_text_parser_unit.js +49 -18
  124. package/test/text/ui_text_displayer_unit.js +9 -0
  125. package/test/text/vtt_text_parser_unit.js +31 -0
  126. package/test/util/buffer_utils_unit.js +1 -1
  127. package/test/util/periods_unit.js +113 -0
  128. package/third_party/closure-uri/uri.js +1 -1
  129. package/ui/externs/ui.js +24 -0
  130. package/ui/language_utils.js +11 -5
  131. package/ui/less/containers.less +1 -0
  132. package/ui/play_button.js +20 -0
  133. package/ui/playback_rate_selection.js +4 -0
  134. package/ui/presentation_time.js +5 -5
  135. package/ui/small_play_button.js +2 -2
  136. package/ui/volume_bar.js +10 -6
  137. /package/.github/{config.yml → ISSUE_TEMPLATE/config.yml} +0 -0
@@ -215,6 +215,9 @@ shaka.hls.HlsParser = class {
215
215
  * {!Map.<string, !shaka.media.InitSegmentReference>} */
216
216
  this.mapTagToInitSegmentRefMap_ = new Map();
217
217
 
218
+ /** @private {Map.<string, !shaka.extern.HlsAes128Key>} */
219
+ this.aesKeyInfoMap_ = new Map();
220
+
218
221
  /** @private {boolean} */
219
222
  this.lowLatencyMode_ = false;
220
223
  }
@@ -277,6 +280,8 @@ shaka.hls.HlsParser = class {
277
280
  this.groupIdToStreamInfosMap_.clear();
278
281
  this.groupIdToCodecsMap_.clear();
279
282
  this.globalVariables_.clear();
283
+ this.mapTagToInitSegmentRefMap_.clear();
284
+ this.aesKeyInfoMap_.clear();
280
285
 
281
286
  return Promise.all(pending);
282
287
  }
@@ -435,6 +440,14 @@ shaka.hls.HlsParser = class {
435
440
  // No-op
436
441
  }
437
442
 
443
+ /**
444
+ * @override
445
+ * @exportInterface
446
+ */
447
+ onInitialVariantChosen(variant) {
448
+ // No-op
449
+ }
450
+
438
451
  /**
439
452
  * Align the streams by sequence number by dropping early segments. Then
440
453
  * offset the streams to begin at presentation time 0.
@@ -833,6 +846,22 @@ shaka.hls.HlsParser = class {
833
846
  this.presentationTimeline_.setSegmentAvailabilityDuration(
834
847
  segmentAvailabilityDuration);
835
848
  }
849
+
850
+ if (!this.presentationTimeline_.isStartTimeLocked()) {
851
+ const streamInfos = Array.from(this.uriToStreamInfosMap_.values());
852
+ const activeStreamInfos =
853
+ streamInfos.filter((s) => s.stream.segmentIndex);
854
+ for (const streamInfo of activeStreamInfos) {
855
+ if (streamInfo.type != 'audio' && streamInfo.type != 'video') {
856
+ continue;
857
+ }
858
+ const firstReference = streamInfo.stream.segmentIndex.get(0);
859
+ if (firstReference && firstReference.syncTime) {
860
+ const syncTime = firstReference.syncTime;
861
+ this.presentationTimeline_.setPresentationStartTime(syncTime);
862
+ }
863
+ }
864
+ }
836
865
  } else {
837
866
  // Use the minimum duration as the presentation duration.
838
867
  this.presentationTimeline_.setDuration(this.getMinDuration_());
@@ -1291,9 +1320,15 @@ shaka.hls.HlsParser = class {
1291
1320
  // the value NONE. If the value is a quoted-string, it MUST match the
1292
1321
  // value of the GROUP-ID attribute of an EXT-X-MEDIA tag elsewhere in the
1293
1322
  // Playlist whose TYPE attribute is CLOSED-CAPTIONS.
1294
- if (type == ContentType.VIDEO && closedCaptionsAttr &&
1295
- closedCaptionsAttr != 'NONE') {
1296
- return this.groupIdToClosedCaptionsMap_.get(closedCaptionsAttr);
1323
+ if (type == ContentType.VIDEO) {
1324
+ if (closedCaptionsAttr && closedCaptionsAttr != 'NONE') {
1325
+ return this.groupIdToClosedCaptionsMap_.get(closedCaptionsAttr);
1326
+ } else if (!closedCaptionsAttr &&
1327
+ this.groupIdToClosedCaptionsMap_.size) {
1328
+ for (const key of this.groupIdToClosedCaptionsMap_.keys()) {
1329
+ return this.groupIdToClosedCaptionsMap_.get(key);
1330
+ }
1331
+ }
1297
1332
  }
1298
1333
  return null;
1299
1334
  }
@@ -2083,8 +2118,15 @@ shaka.hls.HlsParser = class {
2083
2118
  }
2084
2119
  }
2085
2120
 
2086
- const kind = (type == shaka.util.ManifestParserUtils.ContentType.TEXT) ?
2087
- shaka.util.ManifestParserUtils.TextStreamKind.SUBTITLE : undefined;
2121
+ let kind = undefined;
2122
+ if (type == shaka.util.ManifestParserUtils.ContentType.TEXT) {
2123
+ if (roles.includes('public.accessibility.transcribes-spoken-dialog') &&
2124
+ roles.includes('public.accessibility.describes-music-and-sound')) {
2125
+ kind = shaka.util.ManifestParserUtils.TextStreamKind.CLOSED_CAPTION;
2126
+ } else {
2127
+ kind = shaka.util.ManifestParserUtils.TextStreamKind.SUBTITLE;
2128
+ }
2129
+ }
2088
2130
 
2089
2131
  // If there are no roles, and we have defaulted to the subtitle "kind" for
2090
2132
  // this track, add the implied subtitle role.
@@ -2240,30 +2282,36 @@ shaka.hls.HlsParser = class {
2240
2282
  const request = shaka.net.NetworkingEngine.makeRequest(
2241
2283
  [keyUri], this.config_.retryParameters);
2242
2284
 
2243
- const keyInfo = {method: 'AES-128', iv, firstMediaSequenceNumber};
2285
+ const aesKeyInfoKey = `${drmTag.toString()}-${firstMediaSequenceNumber}`;
2286
+ if (!this.aesKeyInfoMap_.has(aesKeyInfoKey)) {
2287
+ const keyInfo = {method: 'AES-128', iv, firstMediaSequenceNumber};
2244
2288
 
2245
- // Don't download the key object until the segment is parsed, to avoid a
2246
- // startup delay for long manifests with lots of keys.
2247
- keyInfo.fetchKey = async () => {
2248
- const keyResponse = await this.makeNetworkRequest_(request, requestType);
2289
+ // Don't download the key object until the segment is parsed, to avoid a
2290
+ // startup delay for long manifests with lots of keys.
2291
+ keyInfo.fetchKey = async () => {
2292
+ const keyResponse = await this.makeNetworkRequest_(
2293
+ request, requestType);
2249
2294
 
2250
- // keyResponse.status is undefined when URI is "data:text/plain;base64,"
2251
- if (!keyResponse.data || keyResponse.data.byteLength != 16) {
2252
- throw new shaka.util.Error(
2253
- shaka.util.Error.Severity.CRITICAL,
2254
- shaka.util.Error.Category.MANIFEST,
2255
- shaka.util.Error.Code.HLS_AES_128_INVALID_KEY_LENGTH);
2256
- }
2295
+ // keyResponse.status is undefined when URI is
2296
+ // "data:text/plain;base64,"
2297
+ if (!keyResponse.data || keyResponse.data.byteLength != 16) {
2298
+ throw new shaka.util.Error(
2299
+ shaka.util.Error.Severity.CRITICAL,
2300
+ shaka.util.Error.Category.MANIFEST,
2301
+ shaka.util.Error.Code.HLS_AES_128_INVALID_KEY_LENGTH);
2302
+ }
2257
2303
 
2258
- const algorithm = {
2259
- name: 'AES-CBC',
2304
+ const algorithm = {
2305
+ name: 'AES-CBC',
2306
+ };
2307
+ keyInfo.cryptoKey = await window.crypto.subtle.importKey(
2308
+ 'raw', keyResponse.data, algorithm, true, ['decrypt']);
2309
+ keyInfo.fetchKey = undefined; // No longer needed.
2260
2310
  };
2261
- keyInfo.cryptoKey = await window.crypto.subtle.importKey(
2262
- 'raw', keyResponse.data, algorithm, true, ['decrypt']);
2263
- keyInfo.fetchKey = undefined; // No longer needed.
2264
- };
2311
+ this.aesKeyInfoMap_.set(aesKeyInfoKey, keyInfo);
2312
+ }
2265
2313
 
2266
- return keyInfo;
2314
+ return this.aesKeyInfoMap_.get(aesKeyInfoKey);
2267
2315
  }
2268
2316
 
2269
2317
 
@@ -2398,16 +2446,19 @@ shaka.hls.HlsParser = class {
2398
2446
  if (!this.mapTagToInitSegmentRefMap_.has(mapTagKey)) {
2399
2447
  /** @type {shaka.extern.HlsAes128Key|undefined} */
2400
2448
  let aes128Key = undefined;
2449
+ let byteRangeTag = null;
2401
2450
  for (const tag of tags) {
2402
2451
  if (tag.name == 'EXT-X-KEY') {
2403
2452
  if (tag.getRequiredAttrValue('METHOD') == 'AES-128' &&
2404
2453
  tag.id < mapTag.id) {
2405
2454
  aes128Key = this.parseAES128DrmTag_(tag, playlist);
2406
2455
  }
2456
+ } else if (tag.name == 'EXT-X-BYTERANGE' && tag.id < mapTag.id) {
2457
+ byteRangeTag = tag;
2407
2458
  }
2408
2459
  }
2409
2460
  const initSegmentRef = this.createInitSegmentReference_(
2410
- absoluteInitSegmentUri, mapTag, aes128Key);
2461
+ absoluteInitSegmentUri, mapTag, byteRangeTag, aes128Key);
2411
2462
  this.mapTagToInitSegmentRefMap_.set(mapTagKey, initSegmentRef);
2412
2463
  }
2413
2464
  return this.mapTagToInitSegmentRefMap_.get(mapTagKey);
@@ -2418,14 +2469,19 @@ shaka.hls.HlsParser = class {
2418
2469
  * playlist.
2419
2470
  * @param {string} absoluteInitSegmentUri
2420
2471
  * @param {!shaka.hls.Tag} mapTag EXT-X-MAP
2472
+ * @param {shaka.hls.Tag=} byteRangeTag EXT-X-BYTERANGE
2421
2473
  * @param {shaka.extern.HlsAes128Key=} aes128Key
2422
2474
  * @return {!shaka.media.InitSegmentReference}
2423
2475
  * @private
2424
2476
  */
2425
- createInitSegmentReference_(absoluteInitSegmentUri, mapTag, aes128Key) {
2477
+ createInitSegmentReference_(absoluteInitSegmentUri, mapTag, byteRangeTag,
2478
+ aes128Key) {
2426
2479
  let startByte = 0;
2427
2480
  let endByte = null;
2428
- const byterange = mapTag.getAttributeValue('BYTERANGE');
2481
+ let byterange = mapTag.getAttributeValue('BYTERANGE');
2482
+ if (!byterange && byteRangeTag) {
2483
+ byterange = byteRangeTag.value;
2484
+ }
2429
2485
  // If a BYTERANGE attribute is not specified, the segment consists
2430
2486
  // of the entire resource.
2431
2487
  if (byterange) {
@@ -2731,6 +2787,13 @@ shaka.hls.HlsParser = class {
2731
2787
  if (this.isLive_() && mediaSequenceToStartTime.has(position)) {
2732
2788
  firstStartTime = mediaSequenceToStartTime.get(position);
2733
2789
  }
2790
+ // This is for recovering from disconnects.
2791
+ if (firstStartTime === 0 &&
2792
+ this.presentationType_ == shaka.hls.HlsParser.PresentationType_.LIVE &&
2793
+ mediaSequenceToStartTime.size > 0 &&
2794
+ !mediaSequenceToStartTime.has(position)) {
2795
+ firstStartTime = this.presentationTimeline_.getSegmentAvailabilityStart();
2796
+ }
2734
2797
 
2735
2798
  /** @type {!Array.<!shaka.media.SegmentReference>} */
2736
2799
  const references = [];
@@ -3319,7 +3382,22 @@ shaka.hls.HlsParser = class {
3319
3382
  * @return {?shaka.extern.DrmInfo}
3320
3383
  * @private
3321
3384
  */
3322
- static identityDrmParser_(drmTag) {
3385
+ static identityDrmParser_(drmTag, mimeType) {
3386
+ if (mimeType == 'video/mp2t') {
3387
+ throw new shaka.util.Error(
3388
+ shaka.util.Error.Severity.CRITICAL,
3389
+ shaka.util.Error.Category.MANIFEST,
3390
+ shaka.util.Error.Code.HLS_MSE_ENCRYPTED_MP2T_NOT_SUPPORTED);
3391
+ }
3392
+
3393
+ if (shaka.util.Platform.isMediaKeysPolyfilled()) {
3394
+ throw new shaka.util.Error(
3395
+ shaka.util.Error.Severity.CRITICAL,
3396
+ shaka.util.Error.Category.MANIFEST,
3397
+ shaka.util.Error.Code
3398
+ .HLS_MSE_ENCRYPTED_LEGACY_APPLE_MEDIA_KEYS_NOT_SUPPORTED);
3399
+ }
3400
+
3323
3401
  const method = drmTag.getRequiredAttrValue('METHOD');
3324
3402
  const VALID_METHODS = ['SAMPLE-AES', 'SAMPLE-AES-CTR'];
3325
3403
  if (!VALID_METHODS.includes(method)) {
@@ -45,12 +45,12 @@ shaka.hls.Utils = class {
45
45
  * @return {?shaka.hls.Tag}
46
46
  */
47
47
  static getFirstTagWithName(tags, name) {
48
- const tagsWithName = shaka.hls.Utils.filterTagsByName(tags, name);
49
- if (!tagsWithName.length) {
50
- return null;
48
+ for (const tag of tags) {
49
+ if (tag.name === name) {
50
+ return tag;
51
+ }
51
52
  }
52
-
53
- return tagsWithName[0];
53
+ return null;
54
54
  }
55
55
 
56
56
  /**
@@ -69,6 +69,30 @@ shaka.media.ContentWorkarounds = class {
69
69
  })
70
70
  .fullBox('encv', onEncryptionMetadataBox)
71
71
  .fullBox('enca', onEncryptionMetadataBox)
72
+ .fullBox('dvav', (box) => {
73
+ boxesToModify.push({
74
+ box,
75
+ newType: ContentWorkarounds.BOX_TYPE_ENCV_,
76
+ });
77
+ })
78
+ .fullBox('dva1', (box) => {
79
+ boxesToModify.push({
80
+ box,
81
+ newType: ContentWorkarounds.BOX_TYPE_ENCV_,
82
+ });
83
+ })
84
+ .fullBox('dvh1', (box) => {
85
+ boxesToModify.push({
86
+ box,
87
+ newType: ContentWorkarounds.BOX_TYPE_ENCV_,
88
+ });
89
+ })
90
+ .fullBox('dvhe', (box) => {
91
+ boxesToModify.push({
92
+ box,
93
+ newType: ContentWorkarounds.BOX_TYPE_ENCV_,
94
+ });
95
+ })
72
96
  .fullBox('hev1', (box) => {
73
97
  boxesToModify.push({
74
98
  box,
@@ -105,6 +129,12 @@ shaka.media.ContentWorkarounds = class {
105
129
  newType: ContentWorkarounds.BOX_TYPE_ENCA_,
106
130
  });
107
131
  })
132
+ .fullBox('ac-4', (box) => {
133
+ boxesToModify.push({
134
+ box,
135
+ newType: ContentWorkarounds.BOX_TYPE_ENCA_,
136
+ });
137
+ })
108
138
  .fullBox('mp4a', (box) => {
109
139
  boxesToModify.push({
110
140
  box,
@@ -520,6 +520,15 @@ shaka.media.DrmEngine = class {
520
520
  }
521
521
  }
522
522
 
523
+ /**
524
+ * Returns true if the manifest has init data.
525
+ *
526
+ * @return {boolean}
527
+ */
528
+ hasManifestInitData() {
529
+ return !!this.manifestInitData_;
530
+ }
531
+
523
532
  /**
524
533
  * Sets the server certificate based on the current DrmInfo.
525
534
  *
@@ -1920,19 +1929,14 @@ shaka.media.DrmEngine = class {
1920
1929
 
1921
1930
  for (const drm1 of drms1) {
1922
1931
  for (const drm2 of drms2) {
1923
- // This method is only called to compare drmInfos of a video and an
1924
- // audio adaptations, so we shouldn't have to worry about checking
1925
- // robustness.
1926
1932
  if (drm1.keySystem == drm2.keySystem) {
1927
- /** @type {Array<shaka.extern.InitDataOverride>} */
1928
- let initData = [];
1929
- initData = initData.concat(drm1.initData || []);
1930
- initData = initData.concat(drm2.initData || []);
1931
- initData = initData.filter((d, i) => {
1932
- return d.keyId === undefined || i === initData.findIndex((d2) => {
1933
- return d2.keyId === d.keyId;
1934
- });
1935
- });
1933
+ const initDataMap = new Map();
1934
+ const bothInitDatas = (drm1.initData || [])
1935
+ .concat(drm2.initData || []);
1936
+ for (const d of bothInitDatas) {
1937
+ initDataMap.set(d.keyId, d);
1938
+ }
1939
+ const initData = Array.from(initDataMap.values());
1936
1940
 
1937
1941
  const keyIds = drm1.keyIds && drm2.keyIds ?
1938
1942
  new Set([...drm1.keyIds, ...drm2.keyIds]) :
@@ -176,7 +176,8 @@ shaka.media.ManifestParser = class {
176
176
  shaka.util.Error.Severity.CRITICAL,
177
177
  shaka.util.Error.Category.MANIFEST,
178
178
  shaka.util.Error.Code.UNABLE_TO_GUESS_MANIFEST_TYPE,
179
- uri);
179
+ uri,
180
+ mimeType);
180
181
  }
181
182
 
182
183
 
@@ -202,6 +202,7 @@ shaka.media.MediaSourceEngine = class {
202
202
  'audio/mp4; codecs="mp4a.40.2"',
203
203
  'audio/mp4; codecs="ac-3"',
204
204
  'audio/mp4; codecs="ec-3"',
205
+ 'audio/mp4; codecs="ac-4"',
205
206
  'audio/mp4; codecs="opus"',
206
207
  'audio/mp4; codecs="flac"',
207
208
  'audio/mp4; codecs="dtsc"', // DTS Digital Surround
@@ -601,14 +602,15 @@ shaka.media.MediaSourceEngine = class {
601
602
  await this.textEngine_.appendBuffer(
602
603
  data,
603
604
  reference ? reference.startTime : null,
604
- reference ? reference.endTime : null);
605
+ reference ? reference.endTime : null,
606
+ reference ? reference.getUris()[0] : null);
605
607
  return;
606
608
  }
607
609
 
608
610
  const uint8ArrayData = shaka.util.BufferUtils.toUint8(data);
609
611
  let mimeType = this.sourceBufferTypes_[contentType];
610
612
  if (this.transmuxers_[contentType]) {
611
- mimeType = this.transmuxers_[contentType].getOrginalMimeType();
613
+ mimeType = this.transmuxers_[contentType].getOriginalMimeType();
612
614
  }
613
615
  if (shaka.util.TsParser.probe(uint8ArrayData)) {
614
616
  const tsParser = new shaka.util.TsParser().parse(uint8ArrayData);
@@ -47,6 +47,7 @@ shaka.media.PlayRateController = class {
47
47
 
48
48
  /** @override */
49
49
  release() {
50
+ this.set(this.getDefaultRate());
50
51
  if (this.timer_) {
51
52
  this.timer_.stop();
52
53
  this.timer_ = null;
@@ -288,6 +288,17 @@ shaka.media.PresentationTimeline = class {
288
288
  }
289
289
 
290
290
 
291
+ /**
292
+ * Returns if the presentation timeline's start time is locked.
293
+ *
294
+ * @return {boolean}
295
+ * @export
296
+ */
297
+ isStartTimeLocked() {
298
+ return this.startTimeLocked_;
299
+ }
300
+
301
+
291
302
  /**
292
303
  * Gives PresentationTimeline a Stream's minimum segment start time.
293
304
  *
@@ -261,7 +261,7 @@ shaka.media.QualityObserver.QualityChangePosition;
261
261
  *
262
262
  * @description
263
263
  * Contains media quality information for a specific content type
264
- * e.g video or audio.
264
+ * e.g. video or audio.
265
265
  *
266
266
  * @property {!Array.<shaka.media.QualityObserver.QualityChangePosition>}
267
267
  * qualityChangePositions
@@ -107,12 +107,15 @@ shaka.media.InitSegmentReference = class {
107
107
  */
108
108
  static equal(reference1, reference2) {
109
109
  const ArrayUtils = shaka.util.ArrayUtils;
110
- if (!reference1 || !reference2) {
110
+ if (reference1 === reference2) {
111
+ return true;
112
+ } else if (!reference1 || !reference2) {
111
113
  return reference1 == reference2;
112
114
  } else {
113
115
  return reference1.getStartByte() == reference2.getStartByte() &&
114
116
  reference1.getEndByte() == reference2.getEndByte() &&
115
- ArrayUtils.equal(reference1.getUris(), reference2.getUris());
117
+ ArrayUtils.equal(
118
+ reference1.getUris().sort(), reference2.getUris().sort());
116
119
  }
117
120
  }
118
121
  };
@@ -6,7 +6,6 @@
6
6
 
7
7
  /**
8
8
  * @fileoverview
9
- * @suppress {missingRequire} TODO(b/152540451): this shouldn't be needed
10
9
  */
11
10
 
12
11
  goog.provide('shaka.media.StreamingEngine');
@@ -19,6 +18,8 @@ goog.require('shaka.media.SegmentIterator');
19
18
  goog.require('shaka.media.SegmentReference');
20
19
  goog.require('shaka.net.Backoff');
21
20
  goog.require('shaka.net.NetworkingEngine');
21
+ goog.require('shaka.util.BufferUtils');
22
+ goog.require('shaka.util.CmcdManager');
22
23
  goog.require('shaka.util.DelayedTick');
23
24
  goog.require('shaka.util.Destroyer');
24
25
  goog.require('shaka.util.Error');
@@ -27,6 +28,7 @@ goog.require('shaka.util.IDestroyable');
27
28
  goog.require('shaka.util.Id3Utils');
28
29
  goog.require('shaka.util.ManifestParserUtils');
29
30
  goog.require('shaka.util.MimeUtils');
31
+ goog.require('shaka.util.Mp4BoxParsers');
30
32
  goog.require('shaka.util.Mp4Parser');
31
33
  goog.require('shaka.util.Networking');
32
34
 
@@ -1285,6 +1287,7 @@ shaka.media.StreamingEngine = class {
1285
1287
  let remaining = new Uint8Array(0);
1286
1288
  let processingResult = false;
1287
1289
  let callbackCalled = false;
1290
+ let streamDataCallbackError;
1288
1291
  const streamDataCallback = async (data) => {
1289
1292
  if (processingResult) {
1290
1293
  // If the fallback result processing was triggered, don't also
@@ -1297,35 +1300,44 @@ shaka.media.StreamingEngine = class {
1297
1300
  if (this.fatalError_) {
1298
1301
  return;
1299
1302
  }
1300
- // Append the data with complete boxes.
1301
- // Every time streamDataCallback gets called, append the new data to
1302
- // the remaining data.
1303
- // Find the last fully completed Mdat box, and slice the data into two
1304
- // parts: the first part with completed Mdat boxes, and the second
1305
- // part with an incomplete box.
1306
- // Append the first part, and save the second part as remaining data,
1307
- // and handle it with the next streamDataCallback call.
1308
- remaining = this.concatArray_(remaining, data);
1309
- let sawMDAT = false;
1310
- let offset = 0;
1311
- new shaka.util.Mp4Parser()
1312
- .box('mdat', (box) => {
1313
- offset = box.size + box.start;
1314
- sawMDAT = true;
1315
- })
1316
- .parse(remaining, /* partialOkay= */ false,
1303
+ try {
1304
+ // Append the data with complete boxes.
1305
+ // Every time streamDataCallback gets called, append the new data
1306
+ // to the remaining data.
1307
+ // Find the last fully completed Mdat box, and slice the data into
1308
+ // two parts: the first part with completed Mdat boxes, and the
1309
+ // second part with an incomplete box.
1310
+ // Append the first part, and save the second part as remaining
1311
+ // data, and handle it with the next streamDataCallback call.
1312
+ remaining = this.concatArray_(remaining, data);
1313
+ let sawMDAT = false;
1314
+ let offset = 0;
1315
+ new shaka.util.Mp4Parser()
1316
+ .box('mdat', (box) => {
1317
+ offset = box.size + box.start;
1318
+ sawMDAT = true;
1319
+ })
1320
+ .parse(remaining, /* partialOkay= */ false,
1321
+ /* isChunkedData= */ true);
1322
+ if (sawMDAT) {
1323
+ const dataToAppend = remaining.subarray(0, offset);
1324
+ remaining = remaining.subarray(offset);
1325
+ await this.append_(
1326
+ mediaState, presentationTime, stream, reference, dataToAppend,
1317
1327
  /* isChunkedData= */ true);
1318
- if (sawMDAT) {
1319
- const dataToAppend = remaining.subarray(0, offset);
1320
- remaining = remaining.subarray(offset);
1321
- await this.append_(
1322
- mediaState, presentationTime, stream, reference, dataToAppend,
1323
- /* isChunkedData= */ true);
1328
+ }
1329
+ } catch (error) {
1330
+ streamDataCallbackError = error;
1324
1331
  }
1325
1332
  };
1326
1333
 
1327
1334
  const result =
1328
1335
  await this.fetch_(mediaState, reference, streamDataCallback);
1336
+
1337
+ if (streamDataCallbackError) {
1338
+ throw streamDataCallbackError;
1339
+ }
1340
+
1329
1341
  if (!callbackCalled) {
1330
1342
  // In some environments, we might be forced to use network plugins
1331
1343
  // that don't support streamDataCallback. In those cases, as a
@@ -2004,8 +2016,8 @@ shaka.media.StreamingEngine = class {
2004
2016
  const bufferedBehind = presentationTime - startTime;
2005
2017
 
2006
2018
  const overflow = bufferedBehind - bufferBehind;
2007
- // See: https://github.com/shaka-project/shaka-player/issues/2982
2008
- if (overflow <= 0.01) {
2019
+ // See: https://github.com/shaka-project/shaka-player/issues/6240
2020
+ if (overflow <= 1) {
2009
2021
  shaka.log.v2(logPrefix,
2010
2022
  'buffer behind okay:',
2011
2023
  'presentationTime=' + presentationTime,
@@ -2056,7 +2068,6 @@ shaka.media.StreamingEngine = class {
2056
2068
  *
2057
2069
  * @return {!Promise.<BufferSource>}
2058
2070
  * @private
2059
- * @suppress {strictMissingProperties}
2060
2071
  */
2061
2072
  async fetch_(mediaState, reference, streamDataCallback) {
2062
2073
  const requestType = shaka.net.NetworkingEngine.RequestType.SEGMENT;
@@ -2071,12 +2082,17 @@ shaka.media.StreamingEngine = class {
2071
2082
  shaka.log.v2('fetching: reference=', reference);
2072
2083
 
2073
2084
  const stream = mediaState.stream;
2085
+ const isInit = reference instanceof shaka.media.InitSegmentReference;
2086
+ const mediaReference =
2087
+ /** @type {!shaka.media.SegmentReference} */(reference);
2088
+ const duration = isInit ? 0 :
2089
+ mediaReference.endTime - mediaReference.startTime;
2074
2090
  this.playerInterface_.modifySegmentRequest(
2075
2091
  request,
2076
2092
  {
2077
2093
  type: stream.type,
2078
- init: reference instanceof shaka.media.InitSegmentReference,
2079
- duration: reference.endTime - reference.startTime,
2094
+ init: isInit,
2095
+ duration,
2080
2096
  mimeType: stream.mimeType,
2081
2097
  codecs: stream.codecs,
2082
2098
  bandwidth: stream.bandwidth,
@@ -2090,7 +2106,11 @@ shaka.media.StreamingEngine = class {
2090
2106
 
2091
2107
  let result = response.data;
2092
2108
  if (reference.hlsAes128Key) {
2093
- const iter = mediaState.segmentIterator;
2109
+ let iter = mediaState.segmentIterator;
2110
+ const time = isInit ? null : mediaReference.startTime;
2111
+ if (!iter && time != null) {
2112
+ iter = mediaState.stream.segmentIndex.getIteratorForTime(time);
2113
+ }
2094
2114
  goog.asserts.assert(iter, 'mediaState.segmentIterator should exist');
2095
2115
  result = await this.aes128Decrypt_(result, reference.hlsAes128Key, iter);
2096
2116
  }
@@ -208,7 +208,7 @@ shaka.media.Transmuxer = class {
208
208
  * Returns the original mimetype of the transmuxer.
209
209
  * @return {string}
210
210
  */
211
- getOrginalMimeType() {
211
+ getOriginalMimeType() {
212
212
  return this.originalMimeType_;
213
213
  }
214
214
 
@@ -175,8 +175,8 @@ shaka.media.VideoWrapper = class {
175
175
 
176
176
  /**
177
177
  * A class used to move the playhead away from its current time. Sometimes,
178
- * Edge ignores re-seeks. After changing the current time, check every 100ms,
179
- * retrying if the change was not accepted.
178
+ * legacy Edge ignores re-seeks. After changing the current time, check every
179
+ * 100ms, retrying if the change was not accepted.
180
180
  *
181
181
  * Delay stats over 100 runs of a re-seeking integration test:
182
182
  * Edge - 0ms - 2%
@@ -186,7 +186,8 @@ shaka.media.VideoWrapper = class {
186
186
  * Edge - 400ms - 2%
187
187
  * Chrome - 0ms - 100%
188
188
  *
189
- * TODO: File a bug on Edge about this.
189
+ * Unfortunately, legacy Edge is not receiving updates anymore, but it still
190
+ * must be supported as it is used for web browsers on XBox.
190
191
  *
191
192
  * @implements {shaka.util.IReleasable}
192
193
  * @final
@@ -112,6 +112,11 @@ shaka.offline.OfflineManifestParser = class {
112
112
  await muxer.destroy();
113
113
  }
114
114
  }
115
+
116
+ /** @override */
117
+ onInitialVariantChosen(variant) {
118
+ // No-op
119
+ }
115
120
  };
116
121
 
117
122
 
@@ -71,14 +71,6 @@ shaka.offline.SessionDeleter = class {
71
71
  return [];
72
72
  }
73
73
 
74
- try {
75
- await drmEngine.setServerCertificate();
76
- } catch (e) {
77
- shaka.log.warning('Error setting server certificate', e);
78
- await drmEngine.destroy();
79
- return [];
80
- }
81
-
82
74
  /** @type {!Array.<string>} */
83
75
  const sessionIds = [];
84
76
  await Promise.all(bucket.sessionIds.map(async (sessionId) => {
@@ -396,6 +396,10 @@ shaka.offline.Storage = class {
396
396
  downloader, config, activeHandle.cell, manifest, drmEngine);
397
397
  this.ensureNotDestroyed_();
398
398
 
399
+ this.setManifestDrmFields_(manifest, manifestDB, drmEngine, config);
400
+ await activeHandle.cell.updateManifest(manifestId, manifestDB);
401
+ this.ensureNotDestroyed_();
402
+
399
403
  const offlineUri = shaka.offline.OfflineUri.manifest(
400
404
  activeHandle.path.mechanism, activeHandle.path.cell, manifestId);
401
405
 
@@ -1231,7 +1235,6 @@ shaka.offline.Storage = class {
1231
1235
  drmEngine.configure(config.drm);
1232
1236
  await drmEngine.initForStorage(
1233
1237
  manifest.variants, config.offline.usePersistentLicense);
1234
- await drmEngine.setServerCertificate();
1235
1238
  await drmEngine.createOrLoad();
1236
1239
 
1237
1240
  return drmEngine;