shaka-player 4.3.12 → 4.3.13

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 (52) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/demo/common/asset.js +5 -0
  3. package/demo/common/assets.js +16 -1
  4. package/demo/common/message_ids.js +1 -0
  5. package/demo/locales/en.json +1 -0
  6. package/demo/locales/source.json +4 -0
  7. package/demo/main.js +1 -1
  8. package/dist/demo.compiled.debug.js +55 -53
  9. package/dist/demo.compiled.debug.map +3 -3
  10. package/dist/demo.compiled.js +55 -53
  11. package/dist/demo.compiled.map +3 -3
  12. package/dist/locales.js +27 -27
  13. package/dist/receiver.compiled.debug.js +24 -22
  14. package/dist/receiver.compiled.debug.map +3 -3
  15. package/dist/receiver.compiled.js +24 -22
  16. package/dist/receiver.compiled.map +3 -3
  17. package/dist/shaka-player.compiled.d.ts +1 -1
  18. package/dist/shaka-player.compiled.debug.d.ts +1 -1
  19. package/dist/shaka-player.compiled.debug.externs.js +6 -4
  20. package/dist/shaka-player.compiled.debug.js +87 -89
  21. package/dist/shaka-player.compiled.debug.map +3 -3
  22. package/dist/shaka-player.compiled.externs.js +6 -4
  23. package/dist/shaka-player.compiled.js +340 -338
  24. package/dist/shaka-player.compiled.map +3 -3
  25. package/dist/shaka-player.ui.d.ts +1 -1
  26. package/dist/shaka-player.ui.debug.d.ts +1 -1
  27. package/dist/shaka-player.ui.debug.externs.js +6 -4
  28. package/dist/shaka-player.ui.debug.js +235 -237
  29. package/dist/shaka-player.ui.debug.map +3 -3
  30. package/dist/shaka-player.ui.externs.js +6 -4
  31. package/dist/shaka-player.ui.js +510 -508
  32. package/dist/shaka-player.ui.map +3 -3
  33. package/docs/tutorials/offline.md +1 -1
  34. package/lib/dash/dash_parser.js +4 -1
  35. package/lib/hls/hls_parser.js +60 -24
  36. package/lib/media/segment_reference.js +11 -5
  37. package/lib/media/streaming_engine.js +15 -25
  38. package/lib/player.js +1 -1
  39. package/lib/text/vtt_text_parser.js +2 -2
  40. package/lib/util/cmcd_manager.js +6 -1
  41. package/lib/util/manifest_parser_utils.js +10 -4
  42. package/lib/util/ts_parser.js +3 -3
  43. package/package.json +1 -1
  44. package/test/dash/dash_parser_manifest_unit.js +5 -0
  45. package/test/dash/dash_parser_segment_base_unit.js +5 -0
  46. package/test/dash/dash_parser_segment_list_unit.js +21 -17
  47. package/test/dash/dash_parser_segment_template_unit.js +5 -0
  48. package/test/hls/hls_live_unit.js +33 -0
  49. package/test/hls/hls_parser_unit.js +2 -57
  50. package/test/test/util/dash_parser_util.js +16 -7
  51. package/test/text/vtt_text_parser_unit.js +2 -17
  52. package/test/util/ts_parser_unit.js +1 -1
@@ -537,7 +537,7 @@ function downloadContent(manifestUri, title) {
537
537
  'downloaded': Date()
538
538
  };
539
539
 
540
- return window.storage.store(manifestUri, metadata);
540
+ return window.storage.store(manifestUri, metadata).promise;
541
541
  }
542
542
 
543
543
  /*
@@ -522,7 +522,10 @@ shaka.dash.DashParser = class {
522
522
  // Just update the variants and text streams, which may change as periods
523
523
  // are added or removed.
524
524
  this.manifest_.variants = this.periodCombiner_.getVariants();
525
- this.manifest_.textStreams = this.periodCombiner_.getTextStreams();
525
+ const textStreams = this.periodCombiner_.getTextStreams();
526
+ if (textStreams.length > 0) {
527
+ this.manifest_.textStreams = textStreams;
528
+ }
526
529
  this.manifest_.imageStreams = this.periodCombiner_.getImageStreams();
527
530
 
528
531
  // Re-filter the manifest. This will check any configured restrictions on
@@ -774,7 +774,7 @@ shaka.hls.HlsParser = class {
774
774
  // enough info here to determine the presentation type and duration.
775
775
  if (playlist.type == shaka.hls.PlaylistType.MEDIA) {
776
776
  if (this.isLive_()) {
777
- this.changePresentationTimelineToLive_();
777
+ this.changePresentationTimelineToLive_(playlist);
778
778
  const delay = this.updatePlaylistDelay_;
779
779
  this.updatePlaylistTimer_.tickAfter(/* seconds= */ delay);
780
780
  }
@@ -1440,7 +1440,14 @@ shaka.hls.HlsParser = class {
1440
1440
  for (const tag of closedCaptionsTags) {
1441
1441
  goog.asserts.assert(tag.name == 'EXT-X-MEDIA',
1442
1442
  'Should only be called on media tags!');
1443
- const language = this.getLanguage_(tag);
1443
+ const languageValue = tag.getAttributeValue('LANGUAGE');
1444
+ let language = this.getLanguage_(tag);
1445
+ if (!languageValue) {
1446
+ const nameValue = tag.getAttributeValue('NAME');
1447
+ if (nameValue) {
1448
+ language = nameValue;
1449
+ }
1450
+ }
1444
1451
 
1445
1452
  // The GROUP-ID value is a quoted-string that specifies the group to which
1446
1453
  // the Rendition belongs.
@@ -1449,7 +1456,9 @@ shaka.hls.HlsParser = class {
1449
1456
  // The value of INSTREAM-ID is a quoted-string that specifies a Rendition
1450
1457
  // within the segments in the Media Playlist. This attribute is REQUIRED
1451
1458
  // if the TYPE attribute is CLOSED-CAPTIONS.
1452
- const instreamId = tag.getRequiredAttrValue('INSTREAM-ID');
1459
+ // We need replace SERVICE string by our internal svc string.
1460
+ const instreamId = tag.getRequiredAttrValue('INSTREAM-ID')
1461
+ .replace('SERVICE', 'svc');
1453
1462
  if (!this.groupIdToClosedCaptionsMap_.get(groupId)) {
1454
1463
  this.groupIdToClosedCaptionsMap_.set(groupId, new Map());
1455
1464
  }
@@ -1709,7 +1718,7 @@ shaka.hls.HlsParser = class {
1709
1718
  if (this.isLive_() && !wasLive) {
1710
1719
  // Now that we know that the presentation is live, convert the timeline
1711
1720
  // to live.
1712
- this.changePresentationTimelineToLive_();
1721
+ this.changePresentationTimelineToLive_(playlist);
1713
1722
  }
1714
1723
 
1715
1724
  // Copy values from the real stream info to our initial one.
@@ -2323,9 +2332,10 @@ shaka.hls.HlsParser = class {
2323
2332
  }
2324
2333
 
2325
2334
  /**
2335
+ * @param {!shaka.hls.Playlist} playlist
2326
2336
  * @private
2327
2337
  */
2328
- changePresentationTimelineToLive_() {
2338
+ changePresentationTimelineToLive_(playlist) {
2329
2339
  // The live edge will be calculated from segments, so we don't need to
2330
2340
  // set a presentation start time. We will assert later that this is
2331
2341
  // working as expected.
@@ -2346,8 +2356,12 @@ shaka.hls.HlsParser = class {
2346
2356
  } else if (this.lowLatencyPresentationDelay_) {
2347
2357
  presentationDelay = this.lowLatencyPresentationDelay_;
2348
2358
  } else {
2349
- const numberOfSegments = this.config_.hls.liveSegmentsDelay;
2350
- presentationDelay = this.maxTargetDuration_ * numberOfSegments;
2359
+ const playlistSegments = playlist.segments.length;
2360
+ let delaySegments = this.config_.hls.liveSegmentsDelay;
2361
+ if (delaySegments > (playlistSegments - 2)) {
2362
+ delaySegments = Math.max(1, playlistSegments - 2);
2363
+ }
2364
+ presentationDelay = this.maxTargetDuration_ * delaySegments;
2351
2365
  }
2352
2366
 
2353
2367
  this.presentationTimeline_.setPresentationStartTime(0);
@@ -2357,13 +2371,13 @@ shaka.hls.HlsParser = class {
2357
2371
 
2358
2372
  /**
2359
2373
  * 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.
2374
+ * @param {!shaka.hls.Playlist} playlist
2361
2375
  * @param {!Array.<!shaka.hls.Tag>} tags Segment tags
2362
2376
  * @param {!Map.<string, string>} variables
2363
2377
  * @return {shaka.media.InitSegmentReference}
2364
2378
  * @private
2365
2379
  */
2366
- getInitSegmentReference_(playlistUri, tags, variables) {
2380
+ getInitSegmentReference_(playlist, tags, variables) {
2367
2381
  /** @type {?shaka.hls.Tag} */
2368
2382
  const mapTag = shaka.hls.Utils.getFirstTagWithName(tags, 'EXT-X-MAP');
2369
2383
 
@@ -2374,7 +2388,7 @@ shaka.hls.HlsParser = class {
2374
2388
  const verbatimInitSegmentUri = mapTag.getRequiredAttrValue('URI');
2375
2389
  const absoluteInitSegmentUri = this.variableSubstitution_(
2376
2390
  shaka.hls.Utils.constructAbsoluteUri(
2377
- playlistUri, verbatimInitSegmentUri),
2391
+ playlist.absoluteUri, verbatimInitSegmentUri),
2378
2392
  variables);
2379
2393
 
2380
2394
  const mapTagKey = [
@@ -2382,8 +2396,18 @@ shaka.hls.HlsParser = class {
2382
2396
  mapTag.getAttributeValue('BYTERANGE', ''),
2383
2397
  ].join('-');
2384
2398
  if (!this.mapTagToInitSegmentRefMap_.has(mapTagKey)) {
2399
+ /** @type {shaka.extern.HlsAes128Key|undefined} */
2400
+ let aes128Key = undefined;
2401
+ for (const tag of tags) {
2402
+ if (tag.name == 'EXT-X-KEY') {
2403
+ if (tag.getRequiredAttrValue('METHOD') == 'AES-128' &&
2404
+ tag.id < mapTag.id) {
2405
+ aes128Key = this.parseAES128DrmTag_(tag, playlist);
2406
+ }
2407
+ }
2408
+ }
2385
2409
  const initSegmentRef = this.createInitSegmentReference_(
2386
- absoluteInitSegmentUri, mapTag);
2410
+ absoluteInitSegmentUri, mapTag, aes128Key);
2387
2411
  this.mapTagToInitSegmentRefMap_.set(mapTagKey, initSegmentRef);
2388
2412
  }
2389
2413
  return this.mapTagToInitSegmentRefMap_.get(mapTagKey);
@@ -2394,10 +2418,11 @@ shaka.hls.HlsParser = class {
2394
2418
  * playlist.
2395
2419
  * @param {string} absoluteInitSegmentUri
2396
2420
  * @param {!shaka.hls.Tag} mapTag EXT-X-MAP
2421
+ * @param {shaka.extern.HlsAes128Key=} aes128Key
2397
2422
  * @return {!shaka.media.InitSegmentReference}
2398
2423
  * @private
2399
2424
  */
2400
- createInitSegmentReference_(absoluteInitSegmentUri, mapTag) {
2425
+ createInitSegmentReference_(absoluteInitSegmentUri, mapTag, aes128Key) {
2401
2426
  let startByte = 0;
2402
2427
  let endByte = null;
2403
2428
  const byterange = mapTag.getAttributeValue('BYTERANGE');
@@ -2408,12 +2433,25 @@ shaka.hls.HlsParser = class {
2408
2433
  const byteLength = Number(blocks[0]);
2409
2434
  startByte = Number(blocks[1]);
2410
2435
  endByte = startByte + byteLength - 1;
2436
+
2437
+ if (aes128Key) {
2438
+ // MAP segment encrypted with method 'AES-128', when served with
2439
+ // HTTP Range, has the unencrypted size specified in the range.
2440
+ // See: https://tools.ietf.org/html/draft-pantos-hls-rfc8216bis-08#section-6.3.6
2441
+ const length = (endByte + 1) - startByte;
2442
+ if (length % 16) {
2443
+ endByte += (16 - (length % 16));
2444
+ }
2445
+ }
2411
2446
  }
2412
2447
 
2413
2448
  const initSegmentRef = new shaka.media.InitSegmentReference(
2414
2449
  () => [absoluteInitSegmentUri],
2415
2450
  startByte,
2416
- endByte);
2451
+ endByte,
2452
+ /* mediaQuality= */ null,
2453
+ /* timescale= */ null,
2454
+ aes128Key);
2417
2455
  return initSegmentRef;
2418
2456
  }
2419
2457
 
@@ -2427,15 +2465,13 @@ shaka.hls.HlsParser = class {
2427
2465
  * @param {!Map.<string, string>} variables
2428
2466
  * @param {string} absoluteMediaPlaylistUri
2429
2467
  * @param {string} type
2430
- * @param {number} timestampOffset
2431
2468
  * @param {shaka.extern.HlsAes128Key=} hlsAes128Key
2432
2469
  * @return {shaka.media.SegmentReference}
2433
2470
  * @private
2434
2471
  */
2435
2472
  createSegmentReference_(
2436
2473
  initSegmentReference, previousReference, hlsSegment, startTime,
2437
- variables, absoluteMediaPlaylistUri, type, timestampOffset,
2438
- hlsAes128Key) {
2474
+ variables, absoluteMediaPlaylistUri, type, hlsAes128Key) {
2439
2475
  const tags = hlsSegment.tags;
2440
2476
  const absoluteSegmentUri = this.variableSubstitution_(
2441
2477
  hlsSegment.absoluteUri, variables);
@@ -2539,7 +2575,7 @@ shaka.hls.HlsParser = class {
2539
2575
  pStartByte,
2540
2576
  pEndByte,
2541
2577
  initSegmentReference,
2542
- /* timestampOffset= */ 0, // This value is ignored in sequence mode.
2578
+ /* timestampOffset= */ 0,
2543
2579
  /* appendWindowStart= */ 0,
2544
2580
  /* appendWindowEnd= */ Infinity,
2545
2581
  /* partialReferences= */ [],
@@ -2571,8 +2607,11 @@ shaka.hls.HlsParser = class {
2571
2607
  return null;
2572
2608
  }
2573
2609
  endTime = startTime + duration;
2574
- } else {
2610
+ } else if (partialSegmentRefs.length) {
2575
2611
  endTime = partialSegmentRefs[partialSegmentRefs.length - 1].endTime;
2612
+ } else {
2613
+ // Skip segments without duration and without partialsegments
2614
+ return null;
2576
2615
  }
2577
2616
 
2578
2617
  // If the segment has EXT-X-BYTERANGE tag, set the start byte and end byte
@@ -2612,7 +2651,7 @@ shaka.hls.HlsParser = class {
2612
2651
  startByte,
2613
2652
  endByte,
2614
2653
  initSegmentReference,
2615
- timestampOffset, // This value is ignored in sequence mode.
2654
+ /* timestampOffset= */ 0,
2616
2655
  /* appendWindowStart= */ 0,
2617
2656
  /* appendWindowEnd= */ Infinity,
2618
2657
  partialSegmentRefs,
@@ -2697,7 +2736,6 @@ shaka.hls.HlsParser = class {
2697
2736
  const references = [];
2698
2737
 
2699
2738
  let previousReference = null;
2700
- let lastDiscontinuityStartTime = firstStartTime;
2701
2739
 
2702
2740
  for (let i = 0; i < hlsSegments.length; i++) {
2703
2741
  const item = hlsSegments[i];
@@ -2709,7 +2747,6 @@ shaka.hls.HlsParser = class {
2709
2747
  item.tags, 'EXT-X-DISCONTINUITY');
2710
2748
  if (discontinuityTag) {
2711
2749
  discontinuitySequence++;
2712
- lastDiscontinuityStartTime = startTime;
2713
2750
  }
2714
2751
 
2715
2752
  // Apply new AES-128 tags as you see them, keeping a running total.
@@ -2725,8 +2762,8 @@ shaka.hls.HlsParser = class {
2725
2762
 
2726
2763
  mediaSequenceToStartTime.set(position, startTime);
2727
2764
 
2728
- initSegmentRef = this.getInitSegmentReference_(playlist.absoluteUri,
2729
- item.tags, variables);
2765
+ initSegmentRef = this.getInitSegmentReference_(playlist, item.tags,
2766
+ variables);
2730
2767
 
2731
2768
  // If the stream is low latency and the user has not configured the
2732
2769
  // lowLatencyMode, but if it has been configured to activate the
@@ -2748,7 +2785,6 @@ shaka.hls.HlsParser = class {
2748
2785
  variables,
2749
2786
  playlist.absoluteUri,
2750
2787
  type,
2751
- lastDiscontinuityStartTime,
2752
2788
  hlsAes128Key);
2753
2789
 
2754
2790
  if (reference) {
@@ -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
  /**
@@ -1357,16 +1357,11 @@ shaka.media.StreamingEngine = class {
1357
1357
  'ReadableStream is not supported by the browser.');
1358
1358
  }
1359
1359
  const fetchSegment = this.fetch_(mediaState, reference);
1360
- let result = await fetchSegment;
1360
+ const result = await fetchSegment;
1361
1361
  this.destroyer_.ensureNotDestroyed();
1362
1362
  if (this.fatalError_) {
1363
1363
  return;
1364
1364
  }
1365
- if (reference.hlsAes128Key) {
1366
- goog.asserts.assert(iter, 'mediaState.segmentIterator should exist');
1367
- result = await this.aes128Decrypt_(result, reference, iter);
1368
- }
1369
- this.destroyer_.ensureNotDestroyed();
1370
1365
 
1371
1366
  // If the text stream gets switched between fetch_() and append_(), the
1372
1367
  // new text parser is initialized, but the new init segment is not
@@ -1450,13 +1445,13 @@ shaka.media.StreamingEngine = class {
1450
1445
 
1451
1446
  /**
1452
1447
  * @param {!BufferSource} rawResult
1453
- * @param {!shaka.media.SegmentReference} reference
1448
+ * @param {shaka.extern.HlsAes128Key} aes128Key
1454
1449
  * @param {!shaka.media.SegmentIterator} iter
1455
1450
  * @return {!Promise.<!BufferSource>} finalResult
1456
1451
  * @private
1457
1452
  */
1458
- async aes128Decrypt_(rawResult, reference, iter) {
1459
- const key = reference.hlsAes128Key;
1453
+ async aes128Decrypt_(rawResult, aes128Key, iter) {
1454
+ const key = aes128Key;
1460
1455
  if (!key.cryptoKey) {
1461
1456
  goog.asserts.assert(key.fetchKey, 'If AES-128 cryptoKey was not ' +
1462
1457
  'preloaded, fetchKey function should be provided');
@@ -1693,10 +1688,10 @@ shaka.media.StreamingEngine = class {
1693
1688
  }
1694
1689
  }
1695
1690
 
1696
- const lastDiscontinuitySequence =
1697
- mediaState.lastSegmentReference ?
1698
- mediaState.lastSegmentReference.discontinuitySequence : null;
1699
1691
  if (this.manifest_.sequenceMode) {
1692
+ const lastDiscontinuitySequence =
1693
+ mediaState.lastSegmentReference ?
1694
+ mediaState.lastSegmentReference.discontinuitySequence : null;
1700
1695
  // Across discontinuity bounds, we should resync timestamps for
1701
1696
  // sequence mode playbacks. The next segment appended should
1702
1697
  // land at its theoretical timestamp from the segment index.
@@ -1706,18 +1701,6 @@ shaka.media.StreamingEngine = class {
1706
1701
  operations.push(this.playerInterface_.mediaSourceEngine.resync(
1707
1702
  mediaState.type, reference.startTime));
1708
1703
  }
1709
- } else {
1710
- // In segments mode, we need to resync to set the timestampOffset
1711
- // to the start of the current discontinuity sequence. This is
1712
- // because individual discontinuity sequences may have internal
1713
- // timestamps that overlap, so we adjust the timestampOffset to avoid
1714
- // having the SourceBuffer get overwritten.
1715
- if (reference.discontinuitySequence != lastDiscontinuitySequence) {
1716
- operations.push(
1717
- this.playerInterface_.mediaSourceEngine.resync(
1718
- mediaState.type,
1719
- reference.timestampOffset));
1720
- }
1721
1704
  }
1722
1705
 
1723
1706
  await Promise.all(operations);
@@ -2104,7 +2087,14 @@ shaka.media.StreamingEngine = class {
2104
2087
  mediaState.operation = op;
2105
2088
  const response = await op.promise;
2106
2089
  mediaState.operation = null;
2107
- return response.data;
2090
+
2091
+ let result = response.data;
2092
+ if (reference.hlsAes128Key) {
2093
+ const iter = mediaState.segmentIterator;
2094
+ goog.asserts.assert(iter, 'mediaState.segmentIterator should exist');
2095
+ result = await this.aes128Decrypt_(result, reference.hlsAes128Key, iter);
2096
+ }
2097
+ return result;
2108
2098
  }
2109
2099
 
2110
2100
 
package/lib/player.js CHANGED
@@ -6971,7 +6971,7 @@ shaka.Player.TYPICAL_BUFFERING_THRESHOLD_ = 0.5;
6971
6971
  * @define {string} A version number taken from git at compile time.
6972
6972
  * @export
6973
6973
  */
6974
- shaka.Player.version = 'v4.3.12-uncompiled';
6974
+ shaka.Player.version = 'v4.3.13-uncompiled';
6975
6975
 
6976
6976
  // Initialize the deprecation system using the version string we just set
6977
6977
  // on the player.
@@ -403,6 +403,8 @@ shaka.text.VttTextParser = class {
403
403
  cue = new shaka.text.Cue(start, end, '');
404
404
  }
405
405
 
406
+ VttTextParser.parseCueStyles(payload, cue, styles);
407
+
406
408
  // Parse optional settings.
407
409
  parser.skipWhitespace();
408
410
  let word = parser.readWord();
@@ -416,8 +418,6 @@ shaka.text.VttTextParser = class {
416
418
  word = parser.readWord();
417
419
  }
418
420
 
419
- VttTextParser.parseCueStyles(payload, cue, styles);
420
-
421
421
  if (id != null) {
422
422
  cue.id = id;
423
423
  }
@@ -331,12 +331,17 @@ shaka.util.CmcdManager = class {
331
331
  * @private
332
332
  */
333
333
  getObjectTypeFromMimeType_(mimeType) {
334
- switch (mimeType) {
334
+ switch (mimeType.toLowerCase()) {
335
335
  case 'video/webm':
336
336
  case 'video/mp4':
337
+ case 'video/mpeg':
338
+ case 'video/mp2t':
337
339
  return shaka.util.CmcdManager.ObjectType.MUXED;
338
340
 
339
341
  case 'application/x-mpegurl':
342
+ case 'application/vnd.apple.mpegurl':
343
+ case 'application/dash+xml':
344
+ case 'video/vnd.mpeg.dash.mpd':
340
345
  return shaka.util.CmcdManager.ObjectType.MANIFEST;
341
346
 
342
347
  default:
@@ -29,13 +29,19 @@ shaka.util.ManifestParserUtils = class {
29
29
  return baseUris;
30
30
  }
31
31
 
32
+ if (baseUris.length == 1 && relativeUris.length == 1) {
33
+ const baseUri = new goog.Uri(baseUris[0]);
34
+ const relativeUri = new goog.Uri(relativeUris[0]);
35
+ return [baseUri.resolve(relativeUri).toString()];
36
+ }
37
+
32
38
  const relativeAsGoog = relativeUris.map((uri) => new goog.Uri(uri));
33
39
  // Resolve each URI relative to each base URI, creating an Array of Arrays.
34
40
  // Then flatten the Arrays into a single Array.
35
- return baseUris.map((uri) => new goog.Uri(uri))
36
- .map((base) => relativeAsGoog.map((i) => base.resolve(i)))
37
- .reduce(Functional.collapseArrays, [])
38
- .map((uri) => uri.toString());
41
+ return baseUris.map((uri) => {
42
+ const base = new goog.Uri(uri);
43
+ return relativeAsGoog.map((i) => base.resolve(i).toString());
44
+ }).reduce(Functional.collapseArrays, []);
39
45
  }
40
46
 
41
47
 
@@ -122,15 +122,15 @@ shaka.util.TsParser = class {
122
122
  // for example
123
123
  // NOTE this is only the PID of the track as found in TS,
124
124
  // but we are not using this for MP4 track IDs.
125
- if (this.videoPid_ == null) {
125
+ if (parsedPIDs.video != -1) {
126
126
  this.videoPid_ = parsedPIDs.video;
127
127
  this.videoCodec_ = parsedPIDs.videoCodec;
128
128
  }
129
- if (this.audioPid_ == null) {
129
+ if (parsedPIDs.audio != -1) {
130
130
  this.audioPid_ = parsedPIDs.audio;
131
131
  this.audioCodec_ = parsedPIDs.audioCodec;
132
132
  }
133
- if (this.id3Pid_ == null) {
133
+ if (parsedPIDs.id3 != -1) {
134
134
  this.id3Pid_ = parsedPIDs.id3;
135
135
  }
136
136
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "shaka-player",
3
3
  "description": "DASH/EME video player library",
4
- "version": "4.3.12",
4
+ "version": "4.3.13",
5
5
  "homepage": "https://github.com/shaka-project/shaka-player",
6
6
  "author": "Google",
7
7
  "maintainers": [
@@ -57,6 +57,11 @@ describe('DashParser Manifest', () => {
57
57
  };
58
58
  });
59
59
 
60
+ afterEach(() => {
61
+ // Dash parser stop is synchronous.
62
+ parser.stop();
63
+ });
64
+
60
65
  /**
61
66
  * Makes a series of tests for the given manifest type.
62
67
  *
@@ -43,6 +43,11 @@ describe('DashParser SegmentBase', () => {
43
43
  };
44
44
  });
45
45
 
46
+ afterEach(() => {
47
+ // Dash parser stop is synchronous.
48
+ parser.stop();
49
+ });
50
+
46
51
  it('requests init data for WebM', async () => {
47
52
  const source = [
48
53
  '<MPD mediaPresentationDuration="PT75S">',
@@ -351,24 +351,28 @@ describe('DashParser SegmentList', () => {
351
351
  updateDuration: () => {},
352
352
  newDrmInfo: (stream) => {},
353
353
  };
354
- const manifest = await dashParser.start('dummy://foo', playerInterface);
355
- const stream = manifest.variants[0].video;
356
- await stream.createSegmentIndex();
357
- goog.asserts.assert(stream.segmentIndex, 'Expected index to be created');
354
+ try {
355
+ const manifest = await dashParser.start('dummy://foo', playerInterface);
356
+ const stream = manifest.variants[0].video;
357
+ await stream.createSegmentIndex();
358
+ goog.asserts.assert(stream.segmentIndex, 'Expected index to be created');
358
359
 
359
- // Don't use Dash.testSegmentIndex since it uses SegmentIndex.find, which
360
- // doesn't reproduce this issue. We want to use Array.from which uses the
361
- // iterator.
362
- const expected = [
363
- ManifestParser.makeReference('s1.mp4', 0, 10, baseUri),
364
- ManifestParser.makeReference('s2.mp4', 10, 20, baseUri),
365
- ManifestParser.makeReference('s3.mp4', 20, 30, baseUri),
360
+ // Don't use Dash.testSegmentIndex since it uses SegmentIndex.find, which
361
+ // doesn't reproduce this issue. We want to use Array.from which uses
362
+ // the iterator.
363
+ const expected = [
364
+ ManifestParser.makeReference('s1.mp4', 0, 10, baseUri),
365
+ ManifestParser.makeReference('s2.mp4', 10, 20, baseUri),
366
+ ManifestParser.makeReference('s3.mp4', 20, 30, baseUri),
366
367
 
367
- ManifestParser.makeReference('s3.mp4', 30, 40, baseUri),
368
- ManifestParser.makeReference('s4.mp4', 40, 50, baseUri),
369
- ManifestParser.makeReference('s5.mp4', 50, 60, baseUri),
370
- ];
371
- const actual = Array.from(stream.segmentIndex);
372
- expect(actual).toEqual(expected);
368
+ ManifestParser.makeReference('s3.mp4', 30, 40, baseUri),
369
+ ManifestParser.makeReference('s4.mp4', 40, 50, baseUri),
370
+ ManifestParser.makeReference('s5.mp4', 50, 60, baseUri),
371
+ ];
372
+ const actual = Array.from(stream.segmentIndex);
373
+ expect(actual).toEqual(expected);
374
+ } finally {
375
+ dashParser.stop();
376
+ }
373
377
  });
374
378
  });
@@ -52,6 +52,11 @@ describe('DashParser SegmentTemplate', () => {
52
52
  };
53
53
  });
54
54
 
55
+ afterEach(() => {
56
+ // Dash parser stop is synchronous.
57
+ parser.stop();
58
+ });
59
+
55
60
  shaka.test.Dash.makeTimelineTests(
56
61
  'SegmentTemplate', 'media="s$Number$.mp4"', []);
57
62
 
@@ -501,10 +501,43 @@ describe('HlsParser live', () => {
501
501
 
502
502
  it('sets 3 times target duration as presentation delay if not configured',
503
503
  async () => {
504
+ const media = [
505
+ '#EXTM3U\n',
506
+ '#EXT-X-TARGETDURATION:5\n',
507
+ '#EXT-X-MAP:URI="init.mp4",BYTERANGE="616@0"\n',
508
+ '#EXT-X-MEDIA-SEQUENCE:0\n',
509
+ '#EXTINF:2,\n',
510
+ 'main.mp4\n',
511
+ '#EXTINF:2,\n',
512
+ 'main.mp4\n',
513
+ '#EXTINF:2,\n',
514
+ 'main.mp4\n',
515
+ '#EXTINF:2,\n',
516
+ 'main.mp4\n',
517
+ '#EXTINF:2,\n',
518
+ 'main.mp4\n',
519
+ '#EXTINF:2,\n',
520
+ 'main.mp4\n',
521
+ ].join('');
504
522
  const manifest = await testInitialManifest(master, media);
505
523
  expect(manifest.presentationTimeline.getDelay()).toBe(15);
506
524
  });
507
525
 
526
+ it('sets 1 times target duration as presentation delay if there are not enough segments', async () => { // eslint-disable-line max-len
527
+ const media = [
528
+ '#EXTM3U\n',
529
+ '#EXT-X-TARGETDURATION:5\n',
530
+ '#EXT-X-MAP:URI="init.mp4",BYTERANGE="616@0"\n',
531
+ '#EXT-X-MEDIA-SEQUENCE:0\n',
532
+ '#EXTINF:2,\n',
533
+ 'main.mp4\n',
534
+ '#EXTINF:2,\n',
535
+ 'main.mp4\n',
536
+ ].join('');
537
+ const manifest = await testInitialManifest(master, media);
538
+ expect(manifest.presentationTimeline.getDelay()).toBe(5);
539
+ });
540
+
508
541
  it('sets presentation delay for low latency mode', async () => {
509
542
  const mediaWithLowLatency = [
510
543
  '#EXTM3U\n',