shaka-player 4.0.2 → 4.0.5

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 (162) hide show
  1. package/.github/workflows/build-and-test.yaml +67 -15
  2. package/.github/workflows/compute-incremental-coverage.py +251 -0
  3. package/.github/workflows/report-incremental-coverage.yaml +39 -0
  4. package/.github/workflows/selenium-lab-tests.yaml +44 -1
  5. package/AUTHORS +2 -0
  6. package/CHANGELOG.md +43 -0
  7. package/CONTRIBUTORS +4 -0
  8. package/README.md +20 -6
  9. package/demo/common/assets.js +1 -1
  10. package/demo/index.html +1 -3
  11. package/demo/main.js +3 -3
  12. package/dist/controls.css +1 -1
  13. package/dist/demo.compiled.debug.js +3 -3
  14. package/dist/demo.compiled.debug.map +2 -2
  15. package/dist/demo.compiled.js +3 -3
  16. package/dist/demo.compiled.map +2 -2
  17. package/dist/deps.js +3 -3
  18. package/dist/locales.js +29 -29
  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 +12 -1
  24. package/dist/shaka-player.compiled.debug.d.ts +12 -1
  25. package/dist/shaka-player.compiled.debug.externs.js +52 -40
  26. package/dist/shaka-player.compiled.debug.js +269 -256
  27. package/dist/shaka-player.compiled.debug.map +4 -4
  28. package/dist/shaka-player.compiled.externs.js +52 -40
  29. package/dist/shaka-player.compiled.js +739 -730
  30. package/dist/shaka-player.compiled.map +4 -4
  31. package/dist/shaka-player.ui.d.ts +12 -1
  32. package/dist/shaka-player.ui.debug.d.ts +12 -1
  33. package/dist/shaka-player.ui.debug.externs.js +52 -40
  34. package/dist/shaka-player.ui.debug.js +296 -282
  35. package/dist/shaka-player.ui.debug.map +4 -4
  36. package/dist/shaka-player.ui.externs.js +52 -40
  37. package/dist/shaka-player.ui.js +885 -878
  38. package/dist/shaka-player.ui.map +4 -4
  39. package/docs/tutorials/ui.md +5 -5
  40. package/docs/tutorials/upgrade.md +1 -0
  41. package/docs/tutorials/welcome.md +6 -3
  42. package/externs/shaka/player.js +4 -2
  43. package/karma.conf.js +3 -11
  44. package/lib/ads/client_side_ad_manager.js +1 -1
  45. package/lib/dash/dash_parser.js +5 -0
  46. package/lib/hls/hls_parser.js +183 -70
  47. package/lib/media/drm_engine.js +1 -1
  48. package/lib/media/media_source_engine.js +57 -24
  49. package/lib/media/presentation_timeline.js +24 -1
  50. package/lib/media/segment_index.js +26 -1
  51. package/lib/media/segment_reference.js +10 -3
  52. package/lib/media/streaming_engine.js +4 -4
  53. package/lib/media/time_ranges_utils.js +46 -0
  54. package/lib/player.js +1 -6
  55. package/lib/polyfill/media_capabilities.js +9 -0
  56. package/lib/polyfill/videoplaybackquality.js +3 -1
  57. package/lib/text/cue.js +0 -5
  58. package/lib/text/simple_text_displayer.js +21 -9
  59. package/lib/text/ui_text_displayer.js +83 -19
  60. package/lib/util/buffer_utils.js +3 -0
  61. package/lib/util/object_utils.js +1 -0
  62. package/lib/util/platform.js +19 -1
  63. package/lib/util/stream_utils.js +24 -3
  64. package/lib/util/string_utils.js +102 -13
  65. package/maintained-branches.md +15 -0
  66. package/package.json +3 -5
  67. package/test/hls/hls_parser_unit.js +102 -8
  68. package/test/media/drm_engine_integration.js +24 -8
  69. package/test/media/media_source_engine_integration.js +24 -10
  70. package/test/media/media_source_engine_unit.js +53 -38
  71. package/test/media/streaming_engine_unit.js +28 -27
  72. package/test/test/util/fake_media_source_engine.js +10 -9
  73. package/test/text/simple_text_displayer_unit.js +27 -0
  74. package/test/text/ui_text_displayer_unit.js +65 -0
  75. package/test/util/string_utils_unit.js +46 -8
  76. package/ui/text_selection.js +2 -2
  77. package/app-engine/README.md +0 -9
  78. package/app-engine/demo-version-index/README.md +0 -14
  79. package/app-engine/demo-version-index/app.yaml +0 -28
  80. package/app-engine/demo-version-index/generate.py +0 -164
  81. package/app-engine/demo-version-index/main.py +0 -11
  82. package/app-engine/demo-version-index/requirements.txt +0 -5
  83. package/app-engine/demo-version-index/templates/index.html +0 -114
  84. package/app-engine/shaka-player-demo/README.md +0 -18
  85. package/app-engine/shaka-player-demo/app.yaml +0 -61
  86. package/app-engine/shaka-player-demo/main.py +0 -95
  87. package/app-engine/shaka-player-demo/posters/10yo.png +0 -0
  88. package/app-engine/shaka-player-demo/posters/10yo.xcf +0 -0
  89. package/app-engine/shaka-player-demo/posters/1yo.jpg +0 -0
  90. package/app-engine/shaka-player-demo/posters/2yo.png +0 -0
  91. package/app-engine/shaka-player-demo/posters/2yo.xcf +0 -0
  92. package/app-engine/shaka-player-demo/posters/3yo.png +0 -0
  93. package/app-engine/shaka-player-demo/posters/3yo.xcf +0 -0
  94. package/app-engine/shaka-player-demo/posters/4yo.png +0 -0
  95. package/app-engine/shaka-player-demo/posters/4yo.xcf +0 -0
  96. package/app-engine/shaka-player-demo/posters/5yo.png +0 -0
  97. package/app-engine/shaka-player-demo/posters/5yo.xcf +0 -0
  98. package/app-engine/shaka-player-demo/posters/6yo.png +0 -0
  99. package/app-engine/shaka-player-demo/posters/6yo.xcf +0 -0
  100. package/app-engine/shaka-player-demo/posters/7yo.png +0 -0
  101. package/app-engine/shaka-player-demo/posters/7yo.xcf +0 -0
  102. package/app-engine/shaka-player-demo/posters/8yo.png +0 -0
  103. package/app-engine/shaka-player-demo/posters/8yo.xcf +0 -0
  104. package/app-engine/shaka-player-demo/posters/9yo.png +0 -0
  105. package/app-engine/shaka-player-demo/posters/9yo.xcf +0 -0
  106. package/app-engine/shaka-player-demo/posters/audioOnly.gif +0 -0
  107. package/app-engine/shaka-player-demo/posters/audioRickRoll.gif +0 -0
  108. package/app-engine/shaka-player-demo/posters/colorbars.gif +0 -0
  109. package/app-engine/shaka-player-demo/posters/fireworks.jpg +0 -0
  110. package/app-engine/shaka-player-demo/posters/party-hat.png +0 -0
  111. package/app-engine/shaka-player-demo/posters/sources.txt +0 -46
  112. package/app-engine/shaka-player-demo/posters/standard.png +0 -0
  113. package/app-engine/shaka-player-demo/posters/trek.jpg +0 -0
  114. package/app-engine/shaka-player-demo/posters/valentines.png +0 -0
  115. package/app-engine/shaka-player-demo/posters/zombie.jpg +0 -0
  116. package/app-engine/shaka-player-demo/posters.py +0 -110
  117. package/app-engine/shaka-player-demo/requirements.txt +0 -5
  118. package/app-engine/shaka-player-demo/time.txt +0 -1
  119. package/build/README.md +0 -127
  120. package/build/__init__.py +0 -0
  121. package/build/all.py +0 -150
  122. package/build/apps.py +0 -186
  123. package/build/build.py +0 -392
  124. package/build/check.py +0 -354
  125. package/build/checkversion.py +0 -84
  126. package/build/compiler.py +0 -513
  127. package/build/conformance.textproto +0 -335
  128. package/build/docker/Dockerfile +0 -20
  129. package/build/docker/docker-compose.yml +0 -10
  130. package/build/docs.py +0 -54
  131. package/build/eslint-plugin-shaka-rules/arg-comment-spacing.js +0 -72
  132. package/build/eslint-plugin-shaka-rules/index.js +0 -24
  133. package/build/eslint-plugin-shaka-rules/package.json +0 -7
  134. package/build/eslint-plugin-shaka-rules/private.js +0 -109
  135. package/build/gendeps.py +0 -71
  136. package/build/generateExterns.js +0 -867
  137. package/build/generateLocalizations.py +0 -262
  138. package/build/generateTsDefs.py +0 -137
  139. package/build/install-linux-prereqs.sh +0 -136
  140. package/build/license-header +0 -5
  141. package/build/misspellings.txt +0 -39
  142. package/build/pixelsChanged.js +0 -35
  143. package/build/shaka-lab.yaml +0 -161
  144. package/build/shakaBuildHelpers.py +0 -356
  145. package/build/stats.py +0 -816
  146. package/build/subprocessWindowsPatch.py +0 -103
  147. package/build/test.py +0 -541
  148. package/build/types/ads +0 -8
  149. package/build/types/cast +0 -6
  150. package/build/types/cea +0 -13
  151. package/build/types/complete +0 -9
  152. package/build/types/core +0 -108
  153. package/build/types/dash +0 -8
  154. package/build/types/hls +0 -9
  155. package/build/types/manifests +0 -5
  156. package/build/types/networking +0 -6
  157. package/build/types/offline +0 -22
  158. package/build/types/polyfill +0 -18
  159. package/build/types/text +0 -11
  160. package/build/types/ui +0 -39
  161. package/build/updateScreenshots.py +0 -85
  162. package/build/wrapper.template.js +0 -52
@@ -30,7 +30,7 @@ Set up controls with HTML data attributes:
30
30
  The data-shaka-player-cast-receiver-id tag allows you to provide a Cast Application ID that
31
31
  the cast button will cast to; the value provided here is the sample cast receiver. -->
32
32
  <div data-shaka-player-container style="max-width:40em"
33
- data-shaka-player-cast-receiver-id="BBED8D28">
33
+ data-shaka-player-cast-receiver-id="1AAA1145">
34
34
  <!-- The data-shaka-player tag will make the UI library use this video element.
35
35
  If no video is provided, the UI will automatically make one inside the container div. -->
36
36
  <video autoplay data-shaka-player id="video" style="width:100%;height:100%"></video>
@@ -111,7 +111,7 @@ set up a listener for the 'caststatuschanged' events.
111
111
  <!-- Add a data-shaka-player-cast-receiver-id tag to provide a Cast Application ID that
112
112
  the cast button will cast to; the value provided here is the sample cast receiver. -->
113
113
  <div data-shaka-player-container style="max-width:40em"
114
- data-shaka-player-cast-receiver-id="BBED8D28">
114
+ data-shaka-player-cast-receiver-id="1AAA1145">
115
115
  </div>
116
116
  ```
117
117
 
@@ -137,7 +137,7 @@ or a `<source>` tag inside it to enable auto loading of the specified content.
137
137
 
138
138
  ```html
139
139
  <div data-shaka-player-container style="max-width:40em"
140
- data-shaka-player-cast-receiver-id="BBED8D28">
140
+ data-shaka-player-cast-receiver-id="1AAA1145">
141
141
  <!-- The manifest url in the src attribute will be automatically loaded -->
142
142
  <video autoplay data-shaka-player id="video" style="width:100%;height:100%"
143
143
  src="https://storage.googleapis.com/shaka-demo-assets/angel-one/dash.mpd"></video>
@@ -148,7 +148,7 @@ or
148
148
 
149
149
  ```html
150
150
  <div data-shaka-player-container style="max-width:40em"
151
- data-shaka-player-cast-receiver-id="BBED8D28">
151
+ data-shaka-player-cast-receiver-id="1AAA1145">
152
152
  <video autoplay data-shaka-player id="video" style="width:100%;height:100%">
153
153
  <!-- The manifest url in the src attribute will be auto loaded -->
154
154
  <source src="https://storage.googleapis.com/shaka-demo-assets/angel-one/dash.mpd"/>
@@ -161,7 +161,7 @@ call to the first one fails.
161
161
 
162
162
  ```html
163
163
  <div data-shaka-player-container style="max-width:40em"
164
- data-shaka-player-cast-receiver-id="BBED8D28">
164
+ data-shaka-player-cast-receiver-id="1AAA1145">
165
165
  <video autoplay data-shaka-player id="video" style="width:100%;height:100%">
166
166
  <!-- Try this first -->
167
167
  <source src="https://storage.googleapis.com/shaka-demo-assets/angel-one/dash.mpd"/>
@@ -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
package/karma.conf.js CHANGED
@@ -160,10 +160,6 @@ module.exports = (config) => {
160
160
  'node_modules/es6-promise-polyfill/promise.js',
161
161
  // Babel polyfill, required for async/await
162
162
  'node_modules/@babel/polyfill/dist/polyfill.js',
163
- // TextDecoder polyfill, required for TextDecoder/TextEncoder on IE and
164
- // legacy Edge
165
- // eslint-disable-next-line max-len
166
- 'node_modules/fastestsmallesttextencoderdecoder/EncoderDecoderTogether.min.js',
167
163
 
168
164
  // muxjs module next
169
165
  'node_modules/mux.js/dist/mux.min.js',
@@ -306,13 +302,6 @@ module.exports = (config) => {
306
302
  // Force failure when running empty test-suites.
307
303
  failOnEmptyTestSuite: true,
308
304
 
309
- coverageReporter: {
310
- includeAllSources: true,
311
- reporters: [
312
- {type: 'text'},
313
- ],
314
- },
315
-
316
305
  specReporter: {
317
306
  suppressSkipped: true,
318
307
  showBrowser: true,
@@ -405,9 +394,12 @@ module.exports = (config) => {
405
394
 
406
395
  config.set({
407
396
  coverageReporter: {
397
+ includeAllSources: true,
408
398
  reporters: [
409
399
  {type: 'html', dir: 'coverage'},
410
400
  {type: 'cobertura', dir: 'coverage', file: 'coverage.xml'},
401
+ {type: 'json-summary', dir: 'coverage', file: 'coverage.json'},
402
+ {type: 'json', dir: 'coverage', file: 'coverage-details.json'},
411
403
  ],
412
404
  },
413
405
  });
@@ -427,7 +427,7 @@ shaka.ads.ClientSideAdManager = class {
427
427
  onAdComplete_(e) {
428
428
  this.onEvent_(new shaka.util.FakeEvent(shaka.ads.AdManager.AD_STOPPED,
429
429
  (new Map()).set('originalEvent', e)));
430
- if (this.ad_.isLinear()) {
430
+ if (this.ad_ && this.ad_.isLinear()) {
431
431
  this.adContainer_.removeAttribute('ad-active');
432
432
  this.video_.play();
433
433
  }
@@ -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!');
401
+ }
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
+ }
384
410
  }
385
- });
411
+ }
412
+ }
413
+ }
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);
438
+ }
439
+ }
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
+ }
386
465
  }
387
466
  }
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_;
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);
394
494
  }
395
495
  }
396
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
 
@@ -2085,16 +2192,13 @@ shaka.hls.HlsParser = class {
2085
2192
  firstStartTime = mediaSequenceToStartTime.get(position);
2086
2193
  }
2087
2194
 
2088
- const firstSegmentUri = hlsSegments[0].absoluteUri;
2089
- shaka.log.debug('First segment', firstSegmentUri.split('/').pop(),
2090
- 'starts at', firstStartTime);
2091
-
2092
2195
  /** @type {!Array.<!shaka.media.SegmentReference>} */
2093
2196
  const references = [];
2094
2197
 
2198
+ let previousReference = null;
2199
+
2095
2200
  for (let i = 0; i < hlsSegments.length; i++) {
2096
2201
  const item = hlsSegments[i];
2097
- const previousReference = references[references.length - 1];
2098
2202
  const startTime =
2099
2203
  (i == 0) ? firstStartTime : previousReference.endTime;
2100
2204
  position = mediaSequenceNumber + skippedSegments + i;
@@ -2124,9 +2228,17 @@ shaka.hls.HlsParser = class {
2124
2228
  variables,
2125
2229
  playlist.absoluteUri,
2126
2230
  type);
2231
+ previousReference = reference;
2127
2232
 
2128
2233
  if (reference) {
2129
- 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
+ }
2130
2242
  }
2131
2243
  }
2132
2244
 
@@ -2256,12 +2368,16 @@ shaka.hls.HlsParser = class {
2256
2368
  const ContentType = shaka.util.ManifestParserUtils.ContentType;
2257
2369
  const requestType = shaka.net.NetworkingEngine.RequestType.SEGMENT;
2258
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.
2259
2374
  goog.asserts.assert(playlist.segments.length,
2260
2375
  'Playlist should have segments!');
2261
- const firstSegmentUri = this.variableSubstitution_(
2262
- 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);
2263
2379
 
2264
- const parsedUri = new goog.Uri(firstSegmentUri);
2380
+ const parsedUri = new goog.Uri(middleSegmentUri);
2265
2381
  const extension = parsedUri.getPath().split('.').pop();
2266
2382
  const map = HlsParser.EXTENSION_MAP_BY_CONTENT_TYPE_[contentType];
2267
2383
 
@@ -2291,7 +2407,7 @@ shaka.hls.HlsParser = class {
2291
2407
  // If unable to guess mime type, request a segment and try getting it
2292
2408
  // from the response.
2293
2409
  const headRequest = shaka.net.NetworkingEngine.makeRequest(
2294
- [firstSegmentUri], this.config_.retryParameters);
2410
+ [middleSegmentUri], this.config_.retryParameters);
2295
2411
  headRequest.method = 'HEAD';
2296
2412
 
2297
2413
  const response = await this.makeNetworkRequest_(
@@ -2578,7 +2694,6 @@ shaka.hls.HlsParser = class {
2578
2694
  * stream: !shaka.extern.Stream,
2579
2695
  * verbatimMediaPlaylistUri: string,
2580
2696
  * absoluteMediaPlaylistUri: string,
2581
- * minTimestamp: number,
2582
2697
  * maxTimestamp: number,
2583
2698
  * mediaSequenceToStartTime: !Map.<number, number>,
2584
2699
  * canSkipSegments: boolean
@@ -2597,8 +2712,6 @@ shaka.hls.HlsParser = class {
2597
2712
  * @property {string} absoluteMediaPlaylistUri
2598
2713
  * The absolute media playlist URI, resolved relative to the master playlist
2599
2714
  * and updated to reflect any redirects.
2600
- * @property {number} minTimestamp
2601
- * The minimum timestamp found in the stream.
2602
2715
  * @property {number} maxTimestamp
2603
2716
  * The maximum timestamp found in the stream.
2604
2717
  * @property {!Map.<number, number>} mediaSequenceToStartTime
@@ -1547,7 +1547,7 @@ shaka.media.DrmEngine = class {
1547
1547
  if (shaka.media.DrmEngine.isPlayReadyKeySystem(
1548
1548
  this.currentDrmInfo_.keySystem) &&
1549
1549
  keyId.byteLength == 16 &&
1550
- shaka.util.Platform.isEdge()) {
1550
+ (shaka.util.Platform.isEdge() || shaka.util.Platform.isPS4())) {
1551
1551
  // Read out some fields in little-endian:
1552
1552
  const dataView = shaka.util.BufferUtils.toDataView(keyId);
1553
1553
  const part0 = dataView.getUint32(0, /* LE= */ true);