shaka-player 4.0.1 → 4.0.4

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 (157) hide show
  1. package/AUTHORS +1 -0
  2. package/CHANGELOG.md +37 -0
  3. package/CONTRIBUTORS +3 -0
  4. package/README.md +20 -6
  5. package/demo/common/assets.js +1 -1
  6. package/demo/demo.less +0 -1
  7. package/demo/index.html +0 -2
  8. package/demo/main.js +3 -3
  9. package/dist/controls.css +3 -3
  10. package/dist/controls.css.map +1 -1
  11. package/dist/demo.compiled.debug.js +3 -3
  12. package/dist/demo.compiled.debug.map +2 -2
  13. package/dist/demo.compiled.js +3 -3
  14. package/dist/demo.compiled.map +2 -2
  15. package/dist/demo.css +1 -1
  16. package/dist/demo.css.map +1 -1
  17. package/dist/deps.js +4 -4
  18. package/dist/locales.js +34 -34
  19. package/dist/receiver.compiled.debug.js +1 -1
  20. package/dist/receiver.compiled.debug.map +1 -1
  21. package/dist/receiver.compiled.js +1 -1
  22. package/dist/receiver.compiled.map +1 -1
  23. package/dist/shaka-player.compiled.d.ts +11 -0
  24. package/dist/shaka-player.compiled.debug.d.ts +11 -0
  25. package/dist/shaka-player.compiled.debug.externs.js +51 -39
  26. package/dist/shaka-player.compiled.debug.js +247 -237
  27. package/dist/shaka-player.compiled.debug.map +4 -4
  28. package/dist/shaka-player.compiled.externs.js +51 -39
  29. package/dist/shaka-player.compiled.js +799 -792
  30. package/dist/shaka-player.compiled.map +4 -4
  31. package/dist/shaka-player.ui.d.ts +11 -0
  32. package/dist/shaka-player.ui.debug.d.ts +11 -0
  33. package/dist/shaka-player.ui.debug.externs.js +51 -39
  34. package/dist/shaka-player.ui.debug.js +273 -262
  35. package/dist/shaka-player.ui.debug.map +4 -4
  36. package/dist/shaka-player.ui.externs.js +51 -39
  37. package/dist/shaka-player.ui.js +957 -952
  38. package/dist/shaka-player.ui.map +4 -4
  39. package/docs/tutorials/upgrade.md +1 -0
  40. package/docs/tutorials/welcome.md +6 -3
  41. package/externs/shaka/player.js +4 -2
  42. package/lib/abr/simple_abr_manager.js +2 -1
  43. package/lib/dash/dash_parser.js +5 -0
  44. package/lib/hls/hls_parser.js +197 -73
  45. package/lib/media/drm_engine.js +3 -0
  46. package/lib/media/media_source_engine.js +57 -24
  47. package/lib/media/presentation_timeline.js +24 -1
  48. package/lib/media/segment_index.js +26 -1
  49. package/lib/media/segment_reference.js +10 -3
  50. package/lib/media/streaming_engine.js +3 -4
  51. package/lib/media/time_ranges_utils.js +46 -0
  52. package/lib/player.js +1 -1
  53. package/lib/polyfill/media_capabilities.js +3 -0
  54. package/lib/polyfill/videoplaybackquality.js +3 -1
  55. package/lib/util/mime_utils.js +14 -5
  56. package/lib/util/platform.js +19 -1
  57. package/lib/util/stream_utils.js +1 -0
  58. package/lib/util/string_utils.js +56 -14
  59. package/maintained-branches.md +15 -0
  60. package/package.json +3 -5
  61. package/test/hls/hls_live_unit.js +57 -7
  62. package/test/hls/hls_parser_unit.js +102 -8
  63. package/test/media/drm_engine_integration.js +24 -8
  64. package/test/media/media_source_engine_integration.js +24 -10
  65. package/test/media/media_source_engine_unit.js +53 -38
  66. package/test/media/streaming_engine_unit.js +28 -27
  67. package/test/test/util/fake_media_source_engine.js +10 -9
  68. package/ui/less/containers.less +1 -1
  69. package/ui/less/general.less +1 -1
  70. package/ui/less/range_elements.less +1 -0
  71. package/.github/pull_request_template.md +0 -10
  72. package/app-engine/README.md +0 -9
  73. package/app-engine/demo-version-index/README.md +0 -14
  74. package/app-engine/demo-version-index/app.yaml +0 -28
  75. package/app-engine/demo-version-index/generate.py +0 -164
  76. package/app-engine/demo-version-index/main.py +0 -11
  77. package/app-engine/demo-version-index/requirements.txt +0 -5
  78. package/app-engine/demo-version-index/templates/index.html +0 -114
  79. package/app-engine/shaka-player-demo/README.md +0 -18
  80. package/app-engine/shaka-player-demo/app.yaml +0 -61
  81. package/app-engine/shaka-player-demo/main.py +0 -95
  82. package/app-engine/shaka-player-demo/posters/10yo.png +0 -0
  83. package/app-engine/shaka-player-demo/posters/10yo.xcf +0 -0
  84. package/app-engine/shaka-player-demo/posters/1yo.jpg +0 -0
  85. package/app-engine/shaka-player-demo/posters/2yo.png +0 -0
  86. package/app-engine/shaka-player-demo/posters/2yo.xcf +0 -0
  87. package/app-engine/shaka-player-demo/posters/3yo.png +0 -0
  88. package/app-engine/shaka-player-demo/posters/3yo.xcf +0 -0
  89. package/app-engine/shaka-player-demo/posters/4yo.png +0 -0
  90. package/app-engine/shaka-player-demo/posters/4yo.xcf +0 -0
  91. package/app-engine/shaka-player-demo/posters/5yo.png +0 -0
  92. package/app-engine/shaka-player-demo/posters/5yo.xcf +0 -0
  93. package/app-engine/shaka-player-demo/posters/6yo.png +0 -0
  94. package/app-engine/shaka-player-demo/posters/6yo.xcf +0 -0
  95. package/app-engine/shaka-player-demo/posters/7yo.png +0 -0
  96. package/app-engine/shaka-player-demo/posters/7yo.xcf +0 -0
  97. package/app-engine/shaka-player-demo/posters/8yo.png +0 -0
  98. package/app-engine/shaka-player-demo/posters/8yo.xcf +0 -0
  99. package/app-engine/shaka-player-demo/posters/9yo.png +0 -0
  100. package/app-engine/shaka-player-demo/posters/9yo.xcf +0 -0
  101. package/app-engine/shaka-player-demo/posters/audioOnly.gif +0 -0
  102. package/app-engine/shaka-player-demo/posters/audioRickRoll.gif +0 -0
  103. package/app-engine/shaka-player-demo/posters/colorbars.gif +0 -0
  104. package/app-engine/shaka-player-demo/posters/fireworks.jpg +0 -0
  105. package/app-engine/shaka-player-demo/posters/party-hat.png +0 -0
  106. package/app-engine/shaka-player-demo/posters/sources.txt +0 -46
  107. package/app-engine/shaka-player-demo/posters/standard.png +0 -0
  108. package/app-engine/shaka-player-demo/posters/trek.jpg +0 -0
  109. package/app-engine/shaka-player-demo/posters/valentines.png +0 -0
  110. package/app-engine/shaka-player-demo/posters/zombie.jpg +0 -0
  111. package/app-engine/shaka-player-demo/posters.py +0 -110
  112. package/app-engine/shaka-player-demo/requirements.txt +0 -5
  113. package/app-engine/shaka-player-demo/time.txt +0 -1
  114. package/build/README.md +0 -127
  115. package/build/__init__.py +0 -0
  116. package/build/all.py +0 -150
  117. package/build/apps.py +0 -186
  118. package/build/build.py +0 -392
  119. package/build/check.py +0 -354
  120. package/build/checkversion.py +0 -84
  121. package/build/compiler.py +0 -513
  122. package/build/conformance.textproto +0 -335
  123. package/build/docker/Dockerfile +0 -20
  124. package/build/docker/docker-compose.yml +0 -10
  125. package/build/docs.py +0 -54
  126. package/build/eslint-plugin-shaka-rules/arg-comment-spacing.js +0 -72
  127. package/build/eslint-plugin-shaka-rules/index.js +0 -24
  128. package/build/eslint-plugin-shaka-rules/package.json +0 -7
  129. package/build/eslint-plugin-shaka-rules/private.js +0 -109
  130. package/build/gendeps.py +0 -71
  131. package/build/generateExterns.js +0 -867
  132. package/build/generateLocalizations.py +0 -262
  133. package/build/generateTsDefs.py +0 -137
  134. package/build/install-linux-prereqs.sh +0 -136
  135. package/build/license-header +0 -5
  136. package/build/misspellings.txt +0 -39
  137. package/build/pixelsChanged.js +0 -35
  138. package/build/shaka-lab.yaml +0 -161
  139. package/build/shakaBuildHelpers.py +0 -356
  140. package/build/stats.py +0 -816
  141. package/build/subprocessWindowsPatch.py +0 -103
  142. package/build/test.py +0 -541
  143. package/build/types/ads +0 -8
  144. package/build/types/cast +0 -6
  145. package/build/types/cea +0 -13
  146. package/build/types/complete +0 -9
  147. package/build/types/core +0 -108
  148. package/build/types/dash +0 -8
  149. package/build/types/hls +0 -9
  150. package/build/types/manifests +0 -5
  151. package/build/types/networking +0 -6
  152. package/build/types/offline +0 -22
  153. package/build/types/polyfill +0 -18
  154. package/build/types/text +0 -11
  155. package/build/types/ui +0 -39
  156. package/build/updateScreenshots.py +0 -85
  157. package/build/wrapper.template.js +0 -52
@@ -25,6 +25,7 @@ application:
25
25
  - TextDecoder/TextEncoder platform support or polyfill required (affects
26
26
  Xbox One, but not evergreen browsers); we suggest the polyfill
27
27
  [https://github.com/anonyco/FastestSmallestTextEncoderDecoder](fastestsmallesttextencoderdecoder/EncoderDecoderTogether.min.js)
28
+ Fallback included by default in v4.2
28
29
 
29
30
  - Support removed:
30
31
  - IE11 support removed
@@ -98,11 +98,14 @@ You can find a full list of available browsers with `--browsers help`, and you
98
98
  can find a complete list of testing options with `--help`.
99
99
 
100
100
 
101
- #### Join the announcement list
101
+ #### Announcements
102
102
 
103
- If you want to receive release or survey announcements, you should join our
103
+ To subscribe to new releases on GitHub, you can follow
104
+ [instructions from this blog](https://www.jessesquires.com/blog/2020/07/30/github-tip-watching-releases/).
105
+
106
+ To receive infrequent announcements and surveys from us, you can join our
104
107
  [mailing list](https://groups.google.com/forum/#!forum/shaka-player-users).
105
- The list is very low volume.
108
+ The list is very low volume, and can only be written to by us.
106
109
 
107
110
 
108
111
  #### Continue the Tutorials
@@ -766,8 +766,10 @@ shaka.extern.DashManifestConfiguration;
766
766
  * <i>Defaults to <code>'avc1.42E01E'</code>.</i>
767
767
  * @property {boolean} ignoreManifestProgramDateTime
768
768
  * If <code>true</code>, the HLS parser will ignore the
769
- * <code>EXT-X-PROGRAM-DATE-TIME</code> tags in the manifest.
770
- * Meant for tags that are incorrect or malformed.
769
+ * <code>EXT-X-PROGRAM-DATE-TIME</code> tags in the manifest and use media
770
+ * sequence numbers instead.
771
+ * Meant for streams where <code>EXT-X-PROGRAM-DATE-TIME</code> is incorrect
772
+ * or malformed.
771
773
  * <i>Defaults to <code>false</code>.</i>
772
774
  * @property {string} mediaPlaylistFullMimeType
773
775
  * A string containing a full mime type, including both the basic mime type
@@ -219,8 +219,9 @@ shaka.abr.SimpleAbrManager = class {
219
219
  * @export
220
220
  */
221
221
  getBandwidthEstimate() {
222
+ const defaultBandwidthEstimate = this.getDefaultBandwidth_();
222
223
  return this.bandwidthEstimator_.getBandwidthEstimate(
223
- this.config_.defaultBandwidthEstimate);
224
+ defaultBandwidthEstimate);
224
225
  }
225
226
 
226
227
 
@@ -513,6 +513,11 @@ shaka.dash.DashParser = class {
513
513
  }
514
514
  presentationTimeline.setClockOffset(offset);
515
515
  }
516
+
517
+ // This is the first point where we have a meaningful presentation start
518
+ // time, and we need to tell PresentationTimeline that so that it can
519
+ // maintain consistency from here on.
520
+ presentationTimeline.lockStartTime();
516
521
  } else {
517
522
  // Just update the variants and text streams, which may change as periods
518
523
  // are added or removed.
@@ -121,13 +121,19 @@ shaka.hls.HlsParser = class {
121
121
  this.updatePlaylistDelay_ = 0;
122
122
 
123
123
  /**
124
- * A time offset to apply to EXT-X-PROGRAM-DATE-TIME values to normalize
125
- * them so that they start at 0. This is necessary because these times will
126
- * be used to set presentation times for segments.
127
- * null means we don't have enough data yet.
124
+ * If true, we have already calculated offsets to synchronize streams.
125
+ * Offsets are computed in syncStreams*_().
126
+ * @private {boolean}
127
+ */
128
+ this.streamsSynced_ = false;
129
+
130
+ /**
131
+ * The minimum sequence number for generated segments, when ignoring
132
+ * EXT-X-PROGRAM-DATE-TIME.
133
+ *
128
134
  * @private {?number}
129
135
  */
130
- this.syncTimeOffset_ = null;
136
+ this.minSequenceNumber_ = null;
131
137
 
132
138
  /**
133
139
  * This timer is used to trigger the start of a manifest update. A manifest
@@ -361,39 +367,155 @@ shaka.hls.HlsParser = class {
361
367
  }
362
368
 
363
369
  /**
364
- * If necessary, makes sure that sync times will be normalized to 0, so that
365
- * a stream does not start buffering at 50 years in because sync times are
366
- * measured in time since 1970.
370
+ * Align all streams by sequence number by dropping early segments. Then
371
+ * offset all streams to begin at presentation time 0.
367
372
  * @private
368
373
  */
369
- calculateSyncTimeOffset_() {
370
- if (this.syncTimeOffset_ != null) {
371
- // The offset was already calculated.
374
+ syncStreamsWithSequenceNumber_() {
375
+ if (this.streamsSynced_) {
372
376
  return;
373
377
  }
374
378
 
375
- const segments = new Set();
376
- let lowestSyncTime = Infinity;
379
+ // Sync using media sequence number. Find the highest starting sequence
380
+ // number among all streams. Later, we will drop any references to
381
+ // earlier segments in other streams, then offset everything back to 0.
382
+ let highestStartingSequenceNumber = -1;
383
+ const firstSequenceNumberMap = new Map();
384
+
377
385
  for (const streamInfo of this.uriToStreamInfosMap_.values()) {
378
386
  const segmentIndex = streamInfo.stream.segmentIndex;
379
387
  if (segmentIndex) {
380
- segmentIndex.forEachTopLevelReference((segment) => {
381
- if (segment.syncTime != null) {
382
- lowestSyncTime = Math.min(lowestSyncTime, segment.syncTime);
383
- segments.add(segment);
388
+ const segment0 = segmentIndex.earliestReference();
389
+ if (segment0) {
390
+ // This looks inefficient, but iteration order is insertion order.
391
+ // So the very first entry should be the one we want.
392
+ // We assert that this holds true so that we are alerted by debug
393
+ // builds and tests if it changes. We still do a loop, though, so
394
+ // that the code functions correctly in production no matter what.
395
+ if (goog.DEBUG) {
396
+ const firstSequenceStartTime =
397
+ streamInfo.mediaSequenceToStartTime.values().next().value;
398
+ goog.asserts.assert(
399
+ firstSequenceStartTime == segment0.startTime,
400
+ 'Sequence number map is not ordered as expected!');
384
401
  }
385
- });
402
+ for (const [sequence, start] of streamInfo.mediaSequenceToStartTime) {
403
+ if (start == segment0.startTime) {
404
+ firstSequenceNumberMap.set(streamInfo, sequence);
405
+
406
+ highestStartingSequenceNumber = Math.max(
407
+ highestStartingSequenceNumber, sequence);
408
+ break;
409
+ }
410
+ }
411
+ }
386
412
  }
387
413
  }
388
- if (segments.size > 0) {
389
- this.syncTimeOffset_ = -lowestSyncTime;
390
- for (const segment of segments) {
391
- segment.syncTime += this.syncTimeOffset_;
392
- for (const partial of segment.partialReferences) {
393
- partial.syncTime += this.syncTimeOffset_;
414
+
415
+ if (highestStartingSequenceNumber < 0) {
416
+ // Nothing to sync.
417
+ return;
418
+ }
419
+
420
+ // From now on, updates will ignore any references before this number.
421
+ this.minSequenceNumber_ = highestStartingSequenceNumber;
422
+
423
+ shaka.log.debug('Syncing HLS streams against base sequence number:',
424
+ this.minSequenceNumber_);
425
+
426
+ for (const streamInfo of this.uriToStreamInfosMap_.values()) {
427
+ const segmentIndex = streamInfo.stream.segmentIndex;
428
+ if (segmentIndex) {
429
+ // Drop any earlier references.
430
+ const numSegmentsToDrop = this.minSequenceNumber_ -
431
+ firstSequenceNumberMap.get(streamInfo);
432
+ segmentIndex.dropFirstReferences(numSegmentsToDrop);
433
+
434
+ // Now adjust timestamps back to begin at 0.
435
+ const segmentN = segmentIndex.earliestReference();
436
+ if (segmentN) {
437
+ this.offsetStream_(streamInfo, -segmentN.startTime);
394
438
  }
395
439
  }
396
440
  }
441
+
442
+ this.streamsSynced_ = true;
443
+ }
444
+
445
+ /**
446
+ * Synchronize streams by the EXT-X-PROGRAM-DATE-TIME tags attached to their
447
+ * segments. Also normalizes segment times so that the earliest segment in
448
+ * any stream is at time 0.
449
+ * @private
450
+ */
451
+ syncStreamsWithProgramDateTime_() {
452
+ if (this.streamsSynced_) {
453
+ return;
454
+ }
455
+
456
+ let lowestSyncTime = Infinity;
457
+
458
+ for (const streamInfo of this.uriToStreamInfosMap_.values()) {
459
+ const segmentIndex = streamInfo.stream.segmentIndex;
460
+ if (segmentIndex) {
461
+ const segment0 = segmentIndex.earliestReference();
462
+ if (segment0 != null && segment0.syncTime != null) {
463
+ lowestSyncTime = Math.min(lowestSyncTime, segment0.syncTime);
464
+ }
465
+ }
466
+ }
467
+
468
+ if (lowestSyncTime == Infinity) {
469
+ // Nothing to sync.
470
+ return;
471
+ }
472
+
473
+ shaka.log.debug('Syncing HLS streams against base time:', lowestSyncTime);
474
+
475
+ for (const streamInfo of this.uriToStreamInfosMap_.values()) {
476
+ const segmentIndex = streamInfo.stream.segmentIndex;
477
+ if (segmentIndex != null) {
478
+ const segment0 = segmentIndex.earliestReference();
479
+ if (segment0.syncTime == null) {
480
+ shaka.log.alwaysError('Missing EXT-X-PROGRAM-DATE-TIME for stream',
481
+ streamInfo.verbatimMediaPlaylistUri,
482
+ 'Expect AV sync issues!');
483
+ } else {
484
+ // The first segment's target startTime should be based entirely on
485
+ // its syncTime. The rest of the stream will be based on that
486
+ // starting point. The earliest segment sync time from any stream
487
+ // will become presentation time 0. If two streams start e.g. 6
488
+ // seconds apart in syncTime, then their first segments will also
489
+ // start 6 seconds apart in presentation time.
490
+ const segment0TargetTime = segment0.syncTime - lowestSyncTime;
491
+ const streamOffset = segment0TargetTime - segment0.startTime;
492
+
493
+ this.offsetStream_(streamInfo, streamOffset);
494
+ }
495
+ }
496
+ }
497
+
498
+ this.streamsSynced_ = true;
499
+ }
500
+
501
+ /**
502
+ * @param {!shaka.hls.HlsParser.StreamInfo} streamInfo
503
+ * @param {number} offset
504
+ * @private
505
+ */
506
+ offsetStream_(streamInfo, offset) {
507
+ streamInfo.stream.segmentIndex.offset(offset);
508
+
509
+ streamInfo.maxTimestamp += offset;
510
+ goog.asserts.assert(streamInfo.maxTimestamp >= 0,
511
+ 'Negative maxTimestamp after adjustment!');
512
+
513
+ for (const [key, value] of streamInfo.mediaSequenceToStartTime) {
514
+ streamInfo.mediaSequenceToStartTime.set(key, value + offset);
515
+ }
516
+
517
+ shaka.log.debug('Offset', offset, 'applied to',
518
+ streamInfo.verbatimMediaPlaylistUri);
397
519
  }
398
520
 
399
521
  /**
@@ -524,7 +646,11 @@ shaka.hls.HlsParser = class {
524
646
 
525
647
  // Now that we have generated all streams, we can determine the offset to
526
648
  // apply to sync times.
527
- this.calculateSyncTimeOffset_();
649
+ if (this.config_.hls.ignoreManifestProgramDateTime) {
650
+ this.syncStreamsWithSequenceNumber_();
651
+ } else {
652
+ this.syncStreamsWithProgramDateTime_();
653
+ }
528
654
 
529
655
  if (this.aesEncrypted_ && variants.length == 0) {
530
656
  // We do not support AES-128 encryption with HLS yet. Variants is null
@@ -539,15 +665,12 @@ shaka.hls.HlsParser = class {
539
665
 
540
666
  // Find the min and max timestamp of the earliest segment in all streams.
541
667
  // Find the minimum duration of all streams as well.
542
- let minFirstTimestamp = Infinity;
543
668
  let minDuration = Infinity;
544
-
545
669
  for (const streamInfo of this.uriToStreamInfosMap_.values()) {
546
- minFirstTimestamp =
547
- Math.min(minFirstTimestamp, streamInfo.minTimestamp);
548
670
  if (streamInfo.stream.type != 'text') {
549
- minDuration = Math.min(minDuration,
550
- streamInfo.maxTimestamp - streamInfo.minTimestamp);
671
+ // Since everything is already offset to 0 (either by sync or by being
672
+ // VOD), only maxTimestamp is necessary to compute the duration.
673
+ minDuration = Math.min(minDuration, streamInfo.maxTimestamp);
551
674
  }
552
675
  }
553
676
 
@@ -591,18 +714,11 @@ shaka.hls.HlsParser = class {
591
714
  segmentAvailabilityDuration);
592
715
  }
593
716
  } else {
594
- // For VOD/EVENT content, offset everything back to 0.
595
- // Use the minimum timestamp as the offset for all streams.
596
717
  // Use the minimum duration as the presentation duration.
597
718
  this.presentationTimeline_.setDuration(minDuration);
598
- // Use a negative offset to adjust towards 0.
599
- this.presentationTimeline_.offset(-minFirstTimestamp);
600
719
 
601
720
  for (const streamInfo of this.uriToStreamInfosMap_.values()) {
602
- // The segments were created with actual media times, rather than
603
- // presentation-aligned times, so offset them all now.
604
- streamInfo.stream.segmentIndex.offset(-minFirstTimestamp);
605
- // Finally, fit the segments to the playlist duration.
721
+ // Fit the segments to the playlist duration.
606
722
  streamInfo.stream.segmentIndex.fit(/* periodStart= */ 0, minDuration);
607
723
  }
608
724
  }
@@ -623,8 +739,14 @@ shaka.hls.HlsParser = class {
623
739
  for (const stream of streamsToNotify) {
624
740
  this.segmentsToNotifyByStream_.push(stream.segmentIndex.references);
625
741
  }
742
+
626
743
  this.notifySegments_();
627
744
 
745
+ // This is the first point where we have a meaningful presentation start
746
+ // time, and we need to tell PresentationTimeline that so that it can
747
+ // maintain consistency from here on.
748
+ this.presentationTimeline_.lockStartTime();
749
+
628
750
  // This asserts that the live edge is being calculated from segment times.
629
751
  // For VOD and event streams, this check should still pass.
630
752
  goog.asserts.assert(
@@ -810,12 +932,7 @@ shaka.hls.HlsParser = class {
810
932
  });
811
933
 
812
934
  // Create stream info for each audio / video media tag.
813
- // Wait for the first stream info created, so that the start time is fetched
814
- // and can be reused.
815
- if (mediaTags.length) {
816
- await this.createStreamInfoFromMediaTag_(mediaTags[0]);
817
- }
818
- const promises = mediaTags.slice(1).map((tag) => {
935
+ const promises = mediaTags.map((tag) => {
819
936
  return this.createStreamInfoFromMediaTag_(tag);
820
937
  });
821
938
  await Promise.all(promises);
@@ -1583,7 +1700,6 @@ shaka.hls.HlsParser = class {
1583
1700
  throw error;
1584
1701
  }
1585
1702
 
1586
- const minTimestamp = segments[0].startTime;
1587
1703
  const lastEndTime = segments[segments.length - 1].endTime;
1588
1704
  /** @type {!shaka.media.SegmentIndex} */
1589
1705
  const segmentIndex = new shaka.media.SegmentIndex(segments);
@@ -1641,7 +1757,6 @@ shaka.hls.HlsParser = class {
1641
1757
  stream,
1642
1758
  verbatimMediaPlaylistUri,
1643
1759
  absoluteMediaPlaylistUri,
1644
- minTimestamp,
1645
1760
  maxTimestamp: lastEndTime,
1646
1761
  mediaSequenceToStartTime,
1647
1762
  canSkipSegments,
@@ -1749,8 +1864,6 @@ shaka.hls.HlsParser = class {
1749
1864
  /* presentationStartTime= */ null, /* delay= */ 0);
1750
1865
  this.presentationTimeline_.setStatic(true);
1751
1866
  }
1752
-
1753
- this.notifySegments_();
1754
1867
  }
1755
1868
 
1756
1869
  /**
@@ -1853,15 +1966,9 @@ shaka.hls.HlsParser = class {
1853
1966
  const dateTimeTag =
1854
1967
  shaka.hls.Utils.getFirstTagWithName(tags, 'EXT-X-PROGRAM-DATE-TIME');
1855
1968
  if (dateTimeTag && dateTimeTag.value) {
1856
- const time = shaka.util.XmlUtils.parseDate(dateTimeTag.value);
1857
- goog.asserts.assert(time != null,
1969
+ syncTime = shaka.util.XmlUtils.parseDate(dateTimeTag.value);
1970
+ goog.asserts.assert(syncTime != null,
1858
1971
  'EXT-X-PROGRAM-DATE-TIME format not valid');
1859
- // Sync time offset is null on the first go-through. This indicates that
1860
- // we have not yet seen every stream, and thus do not yet have enough
1861
- // information to determine how to normalize the sync times.
1862
- // For that first go-through, the sync time will be applied after the
1863
- // references are all created. Until then, just offset by 0.
1864
- syncTime = time + (this.syncTimeOffset_ || 0);
1865
1972
  }
1866
1973
  }
1867
1974
 
@@ -1892,9 +1999,20 @@ shaka.hls.HlsParser = class {
1892
1999
  const pPreviousReference = i == 0 ?
1893
2000
  previousReference : partialSegmentRefs[partialSegmentRefs.length - 1];
1894
2001
  const pStartTime = (i == 0) ? startTime : pPreviousReference.endTime;
1895
- const pDuration = Number(item.getAttributeValue('DURATION'));
1896
- // A preload hinted partial segment doesn't have duration information,
1897
- // so its startTime and endTime are the same.
2002
+
2003
+ // If DURATION is missing from this partial segment, use the target
2004
+ // partial duration from the top of the playlist, which is a required
2005
+ // attribute for content with partial segments.
2006
+ const pDuration = Number(item.getAttributeValue('DURATION')) ||
2007
+ this.partialTargetDuration_;
2008
+
2009
+ // If for some reason we have neither an explicit duration, nor a target
2010
+ // partial duration, we should SKIP this partial segment to avoid
2011
+ // duplicating content in the presentation timeline.
2012
+ if (!pDuration) {
2013
+ continue;
2014
+ }
2015
+
1898
2016
  const pEndTime = pStartTime + pDuration;
1899
2017
 
1900
2018
  let pStartByte = 0;
@@ -2074,16 +2192,13 @@ shaka.hls.HlsParser = class {
2074
2192
  firstStartTime = mediaSequenceToStartTime.get(position);
2075
2193
  }
2076
2194
 
2077
- const firstSegmentUri = hlsSegments[0].absoluteUri;
2078
- shaka.log.debug('First segment', firstSegmentUri.split('/').pop(),
2079
- 'starts at', firstStartTime);
2080
-
2081
2195
  /** @type {!Array.<!shaka.media.SegmentReference>} */
2082
2196
  const references = [];
2083
2197
 
2198
+ let previousReference = null;
2199
+
2084
2200
  for (let i = 0; i < hlsSegments.length; i++) {
2085
2201
  const item = hlsSegments[i];
2086
- const previousReference = references[references.length - 1];
2087
2202
  const startTime =
2088
2203
  (i == 0) ? firstStartTime : previousReference.endTime;
2089
2204
  position = mediaSequenceNumber + skippedSegments + i;
@@ -2113,9 +2228,17 @@ shaka.hls.HlsParser = class {
2113
2228
  variables,
2114
2229
  playlist.absoluteUri,
2115
2230
  type);
2231
+ previousReference = reference;
2116
2232
 
2117
2233
  if (reference) {
2118
- references.push(reference);
2234
+ if (this.config_.hls.ignoreManifestProgramDateTime &&
2235
+ this.minSequenceNumber_ != null &&
2236
+ position < this.minSequenceNumber_) {
2237
+ // This segment is ignored as part of our fallback synchronization
2238
+ // method.
2239
+ } else {
2240
+ references.push(reference);
2241
+ }
2119
2242
  }
2120
2243
  }
2121
2244
 
@@ -2245,12 +2368,16 @@ shaka.hls.HlsParser = class {
2245
2368
  const ContentType = shaka.util.ManifestParserUtils.ContentType;
2246
2369
  const requestType = shaka.net.NetworkingEngine.RequestType.SEGMENT;
2247
2370
 
2371
+ // If you wait long enough, requesting the first segment can fail
2372
+ // because it has fallen off the left edge of DVR, so to be safer,
2373
+ // let's request the middle segment.
2248
2374
  goog.asserts.assert(playlist.segments.length,
2249
2375
  'Playlist should have segments!');
2250
- const firstSegmentUri = this.variableSubstitution_(
2251
- playlist.segments[0].absoluteUri, variables);
2376
+ const middleSegmentIdx = Math.trunc((playlist.segments.length - 1) / 2);
2377
+ const middleSegmentUri = this.variableSubstitution_(
2378
+ playlist.segments[middleSegmentIdx].absoluteUri, variables);
2252
2379
 
2253
- const parsedUri = new goog.Uri(firstSegmentUri);
2380
+ const parsedUri = new goog.Uri(middleSegmentUri);
2254
2381
  const extension = parsedUri.getPath().split('.').pop();
2255
2382
  const map = HlsParser.EXTENSION_MAP_BY_CONTENT_TYPE_[contentType];
2256
2383
 
@@ -2280,7 +2407,7 @@ shaka.hls.HlsParser = class {
2280
2407
  // If unable to guess mime type, request a segment and try getting it
2281
2408
  // from the response.
2282
2409
  const headRequest = shaka.net.NetworkingEngine.makeRequest(
2283
- [firstSegmentUri], this.config_.retryParameters);
2410
+ [middleSegmentUri], this.config_.retryParameters);
2284
2411
  headRequest.method = 'HEAD';
2285
2412
 
2286
2413
  const response = await this.makeNetworkRequest_(
@@ -2567,7 +2694,6 @@ shaka.hls.HlsParser = class {
2567
2694
  * stream: !shaka.extern.Stream,
2568
2695
  * verbatimMediaPlaylistUri: string,
2569
2696
  * absoluteMediaPlaylistUri: string,
2570
- * minTimestamp: number,
2571
2697
  * maxTimestamp: number,
2572
2698
  * mediaSequenceToStartTime: !Map.<number, number>,
2573
2699
  * canSkipSegments: boolean
@@ -2586,8 +2712,6 @@ shaka.hls.HlsParser = class {
2586
2712
  * @property {string} absoluteMediaPlaylistUri
2587
2713
  * The absolute media playlist URI, resolved relative to the master playlist
2588
2714
  * and updated to reflect any redirects.
2589
- * @property {number} minTimestamp
2590
- * The minimum timestamp found in the stream.
2591
2715
  * @property {number} maxTimestamp
2592
2716
  * The maximum timestamp found in the stream.
2593
2717
  * @property {!Map.<number, number>} mediaSequenceToStartTime
@@ -1424,6 +1424,9 @@ shaka.media.DrmEngine = class {
1424
1424
  }
1425
1425
  return;
1426
1426
  }
1427
+ if (this.destroyer_.destroyed()) {
1428
+ return;
1429
+ }
1427
1430
 
1428
1431
  const updateEvent = new shaka.util.FakeEvent('drmsessionupdate');
1429
1432
  this.playerInterface_.onEvent(updateEvent);