shaka-player 4.3.12 → 4.3.14

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 (140) 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/AUTHORS +5 -0
  5. package/CHANGELOG.md +108 -0
  6. package/CODE_OF_CONDUCT.md +1 -1
  7. package/CONTRIBUTORS +6 -0
  8. package/README.md +2 -0
  9. package/demo/common/asset.js +5 -1
  10. package/demo/common/assets.js +16 -2
  11. package/demo/common/message_ids.js +1 -0
  12. package/demo/custom.js +2 -0
  13. package/demo/locales/en.json +1 -0
  14. package/demo/locales/source.json +4 -0
  15. package/demo/main.js +1 -1
  16. package/dist/demo.compiled.debug.js +56 -54
  17. package/dist/demo.compiled.debug.map +3 -3
  18. package/dist/demo.compiled.js +56 -54
  19. package/dist/demo.compiled.map +3 -3
  20. package/dist/deps.js +6 -6
  21. package/dist/locales.js +31 -31
  22. package/dist/receiver.compiled.debug.js +24 -22
  23. package/dist/receiver.compiled.debug.map +3 -3
  24. package/dist/receiver.compiled.js +24 -22
  25. package/dist/receiver.compiled.map +3 -3
  26. package/dist/shaka-player.compiled.d.ts +26 -6
  27. package/dist/shaka-player.compiled.debug.d.ts +26 -6
  28. package/dist/shaka-player.compiled.debug.externs.js +79 -49
  29. package/dist/shaka-player.compiled.debug.js +426 -410
  30. package/dist/shaka-player.compiled.debug.map +4 -4
  31. package/dist/shaka-player.compiled.externs.js +79 -49
  32. package/dist/shaka-player.compiled.js +912 -901
  33. package/dist/shaka-player.compiled.map +4 -4
  34. package/dist/shaka-player.ui.d.ts +38 -7
  35. package/dist/shaka-player.ui.debug.d.ts +38 -7
  36. package/dist/shaka-player.ui.debug.externs.js +80 -49
  37. package/dist/shaka-player.ui.debug.js +460 -442
  38. package/dist/shaka-player.ui.debug.map +4 -4
  39. package/dist/shaka-player.ui.externs.js +80 -49
  40. package/dist/shaka-player.ui.js +1060 -1049
  41. package/dist/shaka-player.ui.map +4 -4
  42. package/docs/tutorials/ad_monetization.md +54 -0
  43. package/docs/tutorials/fairplay.md +2 -2
  44. package/docs/tutorials/index.json +1 -0
  45. package/docs/tutorials/offline.md +1 -1
  46. package/docs/tutorials/plugins.md +4 -0
  47. package/docs/tutorials/text-displayer.md +15 -0
  48. package/docs/tutorials/ui-customization.md +29 -0
  49. package/docs/tutorials/ui.md +4 -1
  50. package/externs/lcevc.js +6 -0
  51. package/externs/shaka/manifest_parser.js +8 -0
  52. package/externs/shaka/player.js +1 -1
  53. package/externs/shaka/text.js +3 -1
  54. package/lib/abr/simple_abr_manager.js +1 -1
  55. package/lib/ads/client_side_ad.js +9 -0
  56. package/lib/ads/client_side_ad_manager.js +7 -2
  57. package/lib/ads/server_side_ad_manager.js +4 -0
  58. package/lib/cast/cast_proxy.js +18 -0
  59. package/lib/cea/cea608_memory.js +1 -1
  60. package/lib/cea/cea_decoder.js +11 -1
  61. package/lib/dash/dash_parser.js +79 -42
  62. package/lib/dash/mpd_utils.js +39 -27
  63. package/lib/hls/hls_parser.js +148 -49
  64. package/lib/hls/hls_utils.js +5 -5
  65. package/lib/media/content_workarounds.js +30 -0
  66. package/lib/media/drm_engine.js +16 -12
  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 +16 -7
  72. package/lib/media/streaming_engine.js +64 -54
  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 +79 -18
  79. package/lib/polyfill/media_capabilities.js +23 -3
  80. package/lib/text/mp4_ttml_parser.js +2 -2
  81. package/lib/text/simple_text_displayer.js +7 -6
  82. package/lib/text/srt_text_parser.js +2 -2
  83. package/lib/text/text_engine.js +3 -2
  84. package/lib/text/ttml_text_parser.js +33 -5
  85. package/lib/text/ui_text_displayer.js +81 -15
  86. package/lib/text/vtt_text_parser.js +13 -0
  87. package/lib/util/buffer_utils.js +26 -4
  88. package/lib/util/cmcd_manager.js +16 -1
  89. package/lib/util/fairplay_utils.js +1 -1
  90. package/lib/util/manifest_parser_utils.js +20 -8
  91. package/lib/util/object_utils.js +1 -1
  92. package/lib/util/periods.js +91 -36
  93. package/lib/util/platform.js +39 -2
  94. package/lib/util/player_configuration.js +5 -3
  95. package/lib/util/stream_utils.js +36 -28
  96. package/lib/util/ts_parser.js +3 -3
  97. package/lib/util/xml_utils.js +5 -0
  98. package/package.json +2 -2
  99. package/test/cea/cea_decoder_unit.js +40 -0
  100. package/test/dash/dash_parser_live_unit.js +5 -8
  101. package/test/dash/dash_parser_manifest_unit.js +5 -0
  102. package/test/dash/dash_parser_segment_base_unit.js +5 -0
  103. package/test/dash/dash_parser_segment_list_unit.js +21 -17
  104. package/test/dash/dash_parser_segment_template_unit.js +5 -0
  105. package/test/hls/hls_live_unit.js +33 -0
  106. package/test/hls/hls_parser_unit.js +52 -60
  107. package/test/media/buffering_observer_unit.js +13 -0
  108. package/test/media/content_workarounds_unit.js +1 -1
  109. package/test/media/drm_engine_unit.js +30 -3
  110. package/test/media/media_source_engine_unit.js +1 -1
  111. package/test/media/transmuxer_integration.js +2 -2
  112. package/test/offline/storage_integration.js +3 -0
  113. package/test/player_integration.js +40 -40
  114. package/test/player_unit.js +31 -0
  115. package/test/polyfill/media_capabilities_unit.js +12 -6
  116. package/test/test/util/dash_parser_util.js +16 -7
  117. package/test/test/util/fake_drm_engine.js +7 -0
  118. package/test/test/util/layout_tests.js +2 -1
  119. package/test/test/util/simple_fakes.js +2 -2
  120. package/test/test/util/test_scheme.js +3 -0
  121. package/test/test/util/waiter.js +5 -0
  122. package/test/text/mp4_ttml_parser_unit.js +4 -4
  123. package/test/text/simple_text_displayer_unit.js +1 -1
  124. package/test/text/srt_text_parser_unit.js +1 -1
  125. package/test/text/text_engine_unit.js +6 -0
  126. package/test/text/ttml_text_parser_unit.js +49 -18
  127. package/test/text/ui_text_displayer_unit.js +9 -0
  128. package/test/text/vtt_text_parser_unit.js +2 -17
  129. package/test/util/buffer_utils_unit.js +1 -1
  130. package/test/util/periods_unit.js +113 -0
  131. package/test/util/ts_parser_unit.js +1 -1
  132. package/third_party/closure-uri/uri.js +1 -1
  133. package/ui/externs/ui.js +24 -0
  134. package/ui/language_utils.js +11 -5
  135. package/ui/play_button.js +20 -0
  136. package/ui/playback_rate_selection.js +4 -0
  137. package/ui/presentation_time.js +5 -5
  138. package/ui/small_play_button.js +2 -2
  139. package/ui/volume_bar.js +10 -6
  140. /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.
@@ -774,7 +787,7 @@ shaka.hls.HlsParser = class {
774
787
  // enough info here to determine the presentation type and duration.
775
788
  if (playlist.type == shaka.hls.PlaylistType.MEDIA) {
776
789
  if (this.isLive_()) {
777
- this.changePresentationTimelineToLive_();
790
+ this.changePresentationTimelineToLive_(playlist);
778
791
  const delay = this.updatePlaylistDelay_;
779
792
  this.updatePlaylistTimer_.tickAfter(/* seconds= */ delay);
780
793
  }
@@ -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
  }
@@ -1440,7 +1475,14 @@ shaka.hls.HlsParser = class {
1440
1475
  for (const tag of closedCaptionsTags) {
1441
1476
  goog.asserts.assert(tag.name == 'EXT-X-MEDIA',
1442
1477
  'Should only be called on media tags!');
1443
- const language = this.getLanguage_(tag);
1478
+ const languageValue = tag.getAttributeValue('LANGUAGE');
1479
+ let language = this.getLanguage_(tag);
1480
+ if (!languageValue) {
1481
+ const nameValue = tag.getAttributeValue('NAME');
1482
+ if (nameValue) {
1483
+ language = nameValue;
1484
+ }
1485
+ }
1444
1486
 
1445
1487
  // The GROUP-ID value is a quoted-string that specifies the group to which
1446
1488
  // the Rendition belongs.
@@ -1449,7 +1491,9 @@ shaka.hls.HlsParser = class {
1449
1491
  // The value of INSTREAM-ID is a quoted-string that specifies a Rendition
1450
1492
  // within the segments in the Media Playlist. This attribute is REQUIRED
1451
1493
  // if the TYPE attribute is CLOSED-CAPTIONS.
1452
- const instreamId = tag.getRequiredAttrValue('INSTREAM-ID');
1494
+ // We need replace SERVICE string by our internal svc string.
1495
+ const instreamId = tag.getRequiredAttrValue('INSTREAM-ID')
1496
+ .replace('SERVICE', 'svc');
1453
1497
  if (!this.groupIdToClosedCaptionsMap_.get(groupId)) {
1454
1498
  this.groupIdToClosedCaptionsMap_.set(groupId, new Map());
1455
1499
  }
@@ -1709,7 +1753,7 @@ shaka.hls.HlsParser = class {
1709
1753
  if (this.isLive_() && !wasLive) {
1710
1754
  // Now that we know that the presentation is live, convert the timeline
1711
1755
  // to live.
1712
- this.changePresentationTimelineToLive_();
1756
+ this.changePresentationTimelineToLive_(playlist);
1713
1757
  }
1714
1758
 
1715
1759
  // Copy values from the real stream info to our initial one.
@@ -2074,8 +2118,15 @@ shaka.hls.HlsParser = class {
2074
2118
  }
2075
2119
  }
2076
2120
 
2077
- const kind = (type == shaka.util.ManifestParserUtils.ContentType.TEXT) ?
2078
- 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
+ }
2079
2130
 
2080
2131
  // If there are no roles, and we have defaulted to the subtitle "kind" for
2081
2132
  // this track, add the implied subtitle role.
@@ -2231,30 +2282,36 @@ shaka.hls.HlsParser = class {
2231
2282
  const request = shaka.net.NetworkingEngine.makeRequest(
2232
2283
  [keyUri], this.config_.retryParameters);
2233
2284
 
2234
- 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};
2235
2288
 
2236
- // Don't download the key object until the segment is parsed, to avoid a
2237
- // startup delay for long manifests with lots of keys.
2238
- keyInfo.fetchKey = async () => {
2239
- 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);
2240
2294
 
2241
- // keyResponse.status is undefined when URI is "data:text/plain;base64,"
2242
- if (!keyResponse.data || keyResponse.data.byteLength != 16) {
2243
- throw new shaka.util.Error(
2244
- shaka.util.Error.Severity.CRITICAL,
2245
- shaka.util.Error.Category.MANIFEST,
2246
- shaka.util.Error.Code.HLS_AES_128_INVALID_KEY_LENGTH);
2247
- }
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
+ }
2248
2303
 
2249
- const algorithm = {
2250
- 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.
2251
2310
  };
2252
- keyInfo.cryptoKey = await window.crypto.subtle.importKey(
2253
- 'raw', keyResponse.data, algorithm, true, ['decrypt']);
2254
- keyInfo.fetchKey = undefined; // No longer needed.
2255
- };
2311
+ this.aesKeyInfoMap_.set(aesKeyInfoKey, keyInfo);
2312
+ }
2256
2313
 
2257
- return keyInfo;
2314
+ return this.aesKeyInfoMap_.get(aesKeyInfoKey);
2258
2315
  }
2259
2316
 
2260
2317
 
@@ -2323,9 +2380,10 @@ shaka.hls.HlsParser = class {
2323
2380
  }
2324
2381
 
2325
2382
  /**
2383
+ * @param {!shaka.hls.Playlist} playlist
2326
2384
  * @private
2327
2385
  */
2328
- changePresentationTimelineToLive_() {
2386
+ changePresentationTimelineToLive_(playlist) {
2329
2387
  // The live edge will be calculated from segments, so we don't need to
2330
2388
  // set a presentation start time. We will assert later that this is
2331
2389
  // working as expected.
@@ -2346,8 +2404,12 @@ shaka.hls.HlsParser = class {
2346
2404
  } else if (this.lowLatencyPresentationDelay_) {
2347
2405
  presentationDelay = this.lowLatencyPresentationDelay_;
2348
2406
  } else {
2349
- const numberOfSegments = this.config_.hls.liveSegmentsDelay;
2350
- presentationDelay = this.maxTargetDuration_ * numberOfSegments;
2407
+ const playlistSegments = playlist.segments.length;
2408
+ let delaySegments = this.config_.hls.liveSegmentsDelay;
2409
+ if (delaySegments > (playlistSegments - 2)) {
2410
+ delaySegments = Math.max(1, playlistSegments - 2);
2411
+ }
2412
+ presentationDelay = this.maxTargetDuration_ * delaySegments;
2351
2413
  }
2352
2414
 
2353
2415
  this.presentationTimeline_.setPresentationStartTime(0);
@@ -2357,13 +2419,13 @@ shaka.hls.HlsParser = class {
2357
2419
 
2358
2420
  /**
2359
2421
  * Get the InitSegmentReference for a segment if it has a EXT-X-MAP tag.
2360
- * @param {string} playlistUri The absolute uri of the media playlist.
2422
+ * @param {!shaka.hls.Playlist} playlist
2361
2423
  * @param {!Array.<!shaka.hls.Tag>} tags Segment tags
2362
2424
  * @param {!Map.<string, string>} variables
2363
2425
  * @return {shaka.media.InitSegmentReference}
2364
2426
  * @private
2365
2427
  */
2366
- getInitSegmentReference_(playlistUri, tags, variables) {
2428
+ getInitSegmentReference_(playlist, tags, variables) {
2367
2429
  /** @type {?shaka.hls.Tag} */
2368
2430
  const mapTag = shaka.hls.Utils.getFirstTagWithName(tags, 'EXT-X-MAP');
2369
2431
 
@@ -2374,7 +2436,7 @@ shaka.hls.HlsParser = class {
2374
2436
  const verbatimInitSegmentUri = mapTag.getRequiredAttrValue('URI');
2375
2437
  const absoluteInitSegmentUri = this.variableSubstitution_(
2376
2438
  shaka.hls.Utils.constructAbsoluteUri(
2377
- playlistUri, verbatimInitSegmentUri),
2439
+ playlist.absoluteUri, verbatimInitSegmentUri),
2378
2440
  variables);
2379
2441
 
2380
2442
  const mapTagKey = [
@@ -2382,8 +2444,21 @@ shaka.hls.HlsParser = class {
2382
2444
  mapTag.getAttributeValue('BYTERANGE', ''),
2383
2445
  ].join('-');
2384
2446
  if (!this.mapTagToInitSegmentRefMap_.has(mapTagKey)) {
2447
+ /** @type {shaka.extern.HlsAes128Key|undefined} */
2448
+ let aes128Key = undefined;
2449
+ let byteRangeTag = null;
2450
+ for (const tag of tags) {
2451
+ if (tag.name == 'EXT-X-KEY') {
2452
+ if (tag.getRequiredAttrValue('METHOD') == 'AES-128' &&
2453
+ tag.id < mapTag.id) {
2454
+ aes128Key = this.parseAES128DrmTag_(tag, playlist);
2455
+ }
2456
+ } else if (tag.name == 'EXT-X-BYTERANGE' && tag.id < mapTag.id) {
2457
+ byteRangeTag = tag;
2458
+ }
2459
+ }
2385
2460
  const initSegmentRef = this.createInitSegmentReference_(
2386
- absoluteInitSegmentUri, mapTag);
2461
+ absoluteInitSegmentUri, mapTag, byteRangeTag, aes128Key);
2387
2462
  this.mapTagToInitSegmentRefMap_.set(mapTagKey, initSegmentRef);
2388
2463
  }
2389
2464
  return this.mapTagToInitSegmentRefMap_.get(mapTagKey);
@@ -2394,13 +2469,19 @@ shaka.hls.HlsParser = class {
2394
2469
  * playlist.
2395
2470
  * @param {string} absoluteInitSegmentUri
2396
2471
  * @param {!shaka.hls.Tag} mapTag EXT-X-MAP
2472
+ * @param {shaka.hls.Tag=} byteRangeTag EXT-X-BYTERANGE
2473
+ * @param {shaka.extern.HlsAes128Key=} aes128Key
2397
2474
  * @return {!shaka.media.InitSegmentReference}
2398
2475
  * @private
2399
2476
  */
2400
- createInitSegmentReference_(absoluteInitSegmentUri, mapTag) {
2477
+ createInitSegmentReference_(absoluteInitSegmentUri, mapTag, byteRangeTag,
2478
+ aes128Key) {
2401
2479
  let startByte = 0;
2402
2480
  let endByte = null;
2403
- const byterange = mapTag.getAttributeValue('BYTERANGE');
2481
+ let byterange = mapTag.getAttributeValue('BYTERANGE');
2482
+ if (!byterange && byteRangeTag) {
2483
+ byterange = byteRangeTag.value;
2484
+ }
2404
2485
  // If a BYTERANGE attribute is not specified, the segment consists
2405
2486
  // of the entire resource.
2406
2487
  if (byterange) {
@@ -2408,12 +2489,25 @@ shaka.hls.HlsParser = class {
2408
2489
  const byteLength = Number(blocks[0]);
2409
2490
  startByte = Number(blocks[1]);
2410
2491
  endByte = startByte + byteLength - 1;
2492
+
2493
+ if (aes128Key) {
2494
+ // MAP segment encrypted with method 'AES-128', when served with
2495
+ // HTTP Range, has the unencrypted size specified in the range.
2496
+ // See: https://tools.ietf.org/html/draft-pantos-hls-rfc8216bis-08#section-6.3.6
2497
+ const length = (endByte + 1) - startByte;
2498
+ if (length % 16) {
2499
+ endByte += (16 - (length % 16));
2500
+ }
2501
+ }
2411
2502
  }
2412
2503
 
2413
2504
  const initSegmentRef = new shaka.media.InitSegmentReference(
2414
2505
  () => [absoluteInitSegmentUri],
2415
2506
  startByte,
2416
- endByte);
2507
+ endByte,
2508
+ /* mediaQuality= */ null,
2509
+ /* timescale= */ null,
2510
+ aes128Key);
2417
2511
  return initSegmentRef;
2418
2512
  }
2419
2513
 
@@ -2427,15 +2521,13 @@ shaka.hls.HlsParser = class {
2427
2521
  * @param {!Map.<string, string>} variables
2428
2522
  * @param {string} absoluteMediaPlaylistUri
2429
2523
  * @param {string} type
2430
- * @param {number} timestampOffset
2431
2524
  * @param {shaka.extern.HlsAes128Key=} hlsAes128Key
2432
2525
  * @return {shaka.media.SegmentReference}
2433
2526
  * @private
2434
2527
  */
2435
2528
  createSegmentReference_(
2436
2529
  initSegmentReference, previousReference, hlsSegment, startTime,
2437
- variables, absoluteMediaPlaylistUri, type, timestampOffset,
2438
- hlsAes128Key) {
2530
+ variables, absoluteMediaPlaylistUri, type, hlsAes128Key) {
2439
2531
  const tags = hlsSegment.tags;
2440
2532
  const absoluteSegmentUri = this.variableSubstitution_(
2441
2533
  hlsSegment.absoluteUri, variables);
@@ -2539,7 +2631,7 @@ shaka.hls.HlsParser = class {
2539
2631
  pStartByte,
2540
2632
  pEndByte,
2541
2633
  initSegmentReference,
2542
- /* timestampOffset= */ 0, // This value is ignored in sequence mode.
2634
+ /* timestampOffset= */ 0,
2543
2635
  /* appendWindowStart= */ 0,
2544
2636
  /* appendWindowEnd= */ Infinity,
2545
2637
  /* partialReferences= */ [],
@@ -2571,8 +2663,11 @@ shaka.hls.HlsParser = class {
2571
2663
  return null;
2572
2664
  }
2573
2665
  endTime = startTime + duration;
2574
- } else {
2666
+ } else if (partialSegmentRefs.length) {
2575
2667
  endTime = partialSegmentRefs[partialSegmentRefs.length - 1].endTime;
2668
+ } else {
2669
+ // Skip segments without duration and without partialsegments
2670
+ return null;
2576
2671
  }
2577
2672
 
2578
2673
  // If the segment has EXT-X-BYTERANGE tag, set the start byte and end byte
@@ -2612,7 +2707,7 @@ shaka.hls.HlsParser = class {
2612
2707
  startByte,
2613
2708
  endByte,
2614
2709
  initSegmentReference,
2615
- timestampOffset, // This value is ignored in sequence mode.
2710
+ /* timestampOffset= */ 0,
2616
2711
  /* appendWindowStart= */ 0,
2617
2712
  /* appendWindowEnd= */ Infinity,
2618
2713
  partialSegmentRefs,
@@ -2692,12 +2787,18 @@ shaka.hls.HlsParser = class {
2692
2787
  if (this.isLive_() && mediaSequenceToStartTime.has(position)) {
2693
2788
  firstStartTime = mediaSequenceToStartTime.get(position);
2694
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
+ }
2695
2797
 
2696
2798
  /** @type {!Array.<!shaka.media.SegmentReference>} */
2697
2799
  const references = [];
2698
2800
 
2699
2801
  let previousReference = null;
2700
- let lastDiscontinuityStartTime = firstStartTime;
2701
2802
 
2702
2803
  for (let i = 0; i < hlsSegments.length; i++) {
2703
2804
  const item = hlsSegments[i];
@@ -2709,7 +2810,6 @@ shaka.hls.HlsParser = class {
2709
2810
  item.tags, 'EXT-X-DISCONTINUITY');
2710
2811
  if (discontinuityTag) {
2711
2812
  discontinuitySequence++;
2712
- lastDiscontinuityStartTime = startTime;
2713
2813
  }
2714
2814
 
2715
2815
  // Apply new AES-128 tags as you see them, keeping a running total.
@@ -2725,8 +2825,8 @@ shaka.hls.HlsParser = class {
2725
2825
 
2726
2826
  mediaSequenceToStartTime.set(position, startTime);
2727
2827
 
2728
- initSegmentRef = this.getInitSegmentReference_(playlist.absoluteUri,
2729
- item.tags, variables);
2828
+ initSegmentRef = this.getInitSegmentReference_(playlist, item.tags,
2829
+ variables);
2730
2830
 
2731
2831
  // If the stream is low latency and the user has not configured the
2732
2832
  // lowLatencyMode, but if it has been configured to activate the
@@ -2748,7 +2848,6 @@ shaka.hls.HlsParser = class {
2748
2848
  variables,
2749
2849
  playlist.absoluteUri,
2750
2850
  type,
2751
- lastDiscontinuityStartTime,
2752
2851
  hlsAes128Key);
2753
2852
 
2754
2853
  if (reference) {
@@ -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]) :
@@ -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
@@ -27,11 +27,14 @@ shaka.media.InitSegmentReference = class {
27
27
  * @param {?number} endByte The offset from the start of the resource
28
28
  * to the end of the segment, inclusive. A value of null indicates that the
29
29
  * segment extends to the end of the resource.
30
- * @param {null|shaka.extern.MediaQualityInfo=} mediaQuality Information about
31
- * the quality of the media associated with this init segment.
32
- * @param {number=} timescale
30
+ * @param {(null|shaka.extern.MediaQualityInfo)=} mediaQuality Information
31
+ * about the quality of the media associated with this init segment.
32
+ * @param {(null|number)=} timescale
33
+ * @param {?shaka.extern.HlsAes128Key=} aes128Key
34
+ * The segment's AES-128-CBC full segment encryption key and iv.
33
35
  */
34
- constructor(uris, startByte, endByte, mediaQuality = null, timescale) {
36
+ constructor(uris, startByte, endByte, mediaQuality = null, timescale = null,
37
+ aes128Key = null) {
35
38
  /** @type {function():!Array.<string>} */
36
39
  this.getUris = uris;
37
40
 
@@ -44,8 +47,11 @@ shaka.media.InitSegmentReference = class {
44
47
  /** @const {shaka.extern.MediaQualityInfo|null} */
45
48
  this.mediaQuality = mediaQuality;
46
49
 
47
- /** @type {number|undefined} */
50
+ /** @type {number|null} */
48
51
  this.timescale = timescale;
52
+
53
+ /** @type {?shaka.extern.HlsAes128Key} */
54
+ this.hlsAes128Key = aes128Key;
49
55
  }
50
56
 
51
57
  /**
@@ -101,12 +107,15 @@ shaka.media.InitSegmentReference = class {
101
107
  */
102
108
  static equal(reference1, reference2) {
103
109
  const ArrayUtils = shaka.util.ArrayUtils;
104
- if (!reference1 || !reference2) {
110
+ if (reference1 === reference2) {
111
+ return true;
112
+ } else if (!reference1 || !reference2) {
105
113
  return reference1 == reference2;
106
114
  } else {
107
115
  return reference1.getStartByte() == reference2.getStartByte() &&
108
116
  reference1.getEndByte() == reference2.getEndByte() &&
109
- ArrayUtils.equal(reference1.getUris(), reference2.getUris());
117
+ ArrayUtils.equal(
118
+ reference1.getUris().sort(), reference2.getUris().sort());
110
119
  }
111
120
  }
112
121
  };