shaka-player 3.3.4 → 4.0.1

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 (201) hide show
  1. package/.github/config.yml +1 -0
  2. package/.github/workflows/build-and-test.yaml +0 -1
  3. package/.github/workflows/demo-version-index.yaml +8 -0
  4. package/.github/workflows/release-please.yaml +19 -3
  5. package/.github/workflows/selenium-lab-tests.yaml +0 -1
  6. package/.github/workflows/sync-labels.yaml +33 -0
  7. package/.github/workflows/update-issues.yaml +29 -0
  8. package/.github/workflows/validate-pr-title.yaml +35 -0
  9. package/AUTHORS +1 -0
  10. package/CHANGELOG.md +209 -28
  11. package/CONTRIBUTORS +1 -0
  12. package/README.md +10 -7
  13. package/app-engine/demo-version-index/README.md +3 -3
  14. package/app-engine/demo-version-index/app.yaml +27 -1
  15. package/app-engine/demo-version-index/generate.py +164 -0
  16. package/app-engine/demo-version-index/main.py +4 -155
  17. package/app-engine/demo-version-index/requirements.txt +0 -1
  18. package/build/README.md +2 -2
  19. package/build/compiler.py +1 -1
  20. package/build/generateTsDefs.py +0 -2
  21. package/build/misspellings.txt +1 -0
  22. package/build/shaka-lab.yaml +0 -2
  23. package/build/test.py +7 -1
  24. package/build/types/core +0 -1
  25. package/build/types/polyfill +0 -3
  26. package/demo/common/asset.js +17 -1
  27. package/demo/common/assets.js +29 -0
  28. package/demo/common/message_ids.js +6 -3
  29. package/demo/config.js +10 -11
  30. package/demo/custom.js +39 -0
  31. package/demo/demo_utils.js +0 -1
  32. package/demo/locales/en.json +6 -3
  33. package/demo/locales/source.json +22 -10
  34. package/demo/main.js +9 -6
  35. package/demo/search.js +2 -1
  36. package/dist/controls.css +1 -1
  37. package/dist/demo.compiled.debug.js +71 -64
  38. package/dist/demo.compiled.debug.map +3 -3
  39. package/dist/demo.compiled.js +71 -64
  40. package/dist/demo.compiled.map +3 -3
  41. package/dist/demo.css +1 -1
  42. package/dist/demo.css.map +1 -1
  43. package/dist/deps.js +16 -20
  44. package/dist/locales.js +33 -31
  45. package/dist/receiver.compiled.debug.js +32 -28
  46. package/dist/receiver.compiled.debug.map +3 -3
  47. package/dist/receiver.compiled.js +32 -28
  48. package/dist/receiver.compiled.map +3 -3
  49. package/dist/shaka-player.compiled.d.ts +78 -82
  50. package/dist/shaka-player.compiled.debug.d.ts +78 -82
  51. package/dist/shaka-player.compiled.debug.externs.js +127 -126
  52. package/dist/shaka-player.compiled.debug.js +471 -523
  53. package/dist/shaka-player.compiled.debug.map +4 -4
  54. package/dist/shaka-player.compiled.externs.js +127 -126
  55. package/dist/shaka-player.compiled.js +844 -858
  56. package/dist/shaka-player.compiled.map +4 -4
  57. package/dist/shaka-player.ui.d.ts +80 -82
  58. package/dist/shaka-player.ui.debug.d.ts +80 -82
  59. package/dist/shaka-player.ui.debug.externs.js +135 -126
  60. package/dist/shaka-player.ui.debug.js +531 -581
  61. package/dist/shaka-player.ui.debug.map +4 -4
  62. package/dist/shaka-player.ui.externs.js +135 -126
  63. package/dist/shaka-player.ui.js +991 -1001
  64. package/dist/shaka-player.ui.map +4 -4
  65. package/docs/tutorials/config.md +0 -2
  66. package/docs/tutorials/drm-config.md +5 -0
  67. package/docs/tutorials/fairplay.md +7 -22
  68. package/docs/tutorials/faq.md +0 -11
  69. package/docs/tutorials/index.json +2 -8
  70. package/docs/tutorials/plugins.md +1 -3
  71. package/docs/tutorials/upgrade.md +85 -11
  72. package/externs/polyfill.js +15 -0
  73. package/externs/shaka/manifest.js +5 -1
  74. package/externs/shaka/net.js +12 -0
  75. package/externs/shaka/offline.js +5 -1
  76. package/externs/shaka/player.js +39 -21
  77. package/externs/shaka/text.js +12 -12
  78. package/externs/sourcebuffer.js +15 -0
  79. package/karma.conf.js +4 -0
  80. package/lib/ads/client_side_ad.js +13 -2
  81. package/lib/ads/client_side_ad_manager.js +22 -4
  82. package/lib/cast/cast_utils.js +0 -1
  83. package/lib/dash/content_protection.js +85 -5
  84. package/lib/dash/dash_parser.js +3 -3
  85. package/lib/hls/hls_parser.js +391 -747
  86. package/lib/hls/manifest_text_parser.js +2 -3
  87. package/lib/media/drm_engine.js +102 -37
  88. package/lib/media/gap_jumping_controller.js +10 -45
  89. package/lib/media/media_source_engine.js +157 -31
  90. package/lib/media/playhead.js +2 -5
  91. package/lib/media/presentation_timeline.js +1 -2
  92. package/lib/media/segment_index.js +13 -46
  93. package/lib/media/segment_reference.js +47 -1
  94. package/lib/media/streaming_engine.js +28 -11
  95. package/lib/media/time_ranges_utils.js +4 -9
  96. package/lib/net/networking_engine.js +3 -0
  97. package/lib/offline/indexeddb/storage_mechanism.js +4 -2
  98. package/lib/offline/indexeddb/v1_storage_cell.js +1 -0
  99. package/lib/offline/indexeddb/v2_storage_cell.js +1 -0
  100. package/lib/offline/manifest_converter.js +1 -0
  101. package/lib/offline/storage.js +100 -132
  102. package/lib/player.js +181 -307
  103. package/lib/polyfill/media_capabilities.js +12 -6
  104. package/lib/polyfill/mediasource.js +0 -28
  105. package/lib/polyfill/patchedmediakeys_nop.js +2 -0
  106. package/lib/polyfill/patchedmediakeys_webkit.js +2 -0
  107. package/lib/text/cue.js +0 -6
  108. package/lib/text/lrc_text_parser.js +8 -0
  109. package/lib/text/mp4_ttml_parser.js +8 -0
  110. package/lib/text/mp4_vtt_parser.js +8 -0
  111. package/lib/text/sbv_text_parser.js +8 -0
  112. package/lib/text/simple_text_displayer.js +1 -7
  113. package/lib/text/srt_text_parser.js +8 -0
  114. package/lib/text/ssa_text_parser.js +8 -0
  115. package/lib/text/text_engine.js +19 -3
  116. package/lib/text/ttml_text_parser.js +8 -0
  117. package/lib/text/ui_text_displayer.js +1 -7
  118. package/lib/text/vtt_text_parser.js +27 -5
  119. package/lib/text/web_vtt_generator.js +1 -7
  120. package/lib/util/config_utils.js +25 -0
  121. package/lib/util/error.js +16 -13
  122. package/lib/util/fairplay_utils.js +109 -0
  123. package/lib/util/functional.js +0 -42
  124. package/lib/util/manifest_parser_utils.js +1 -1
  125. package/lib/util/mime_utils.js +50 -1
  126. package/lib/util/periods.js +5 -4
  127. package/lib/util/platform.js +45 -0
  128. package/lib/util/player_configuration.js +26 -20
  129. package/lib/util/pssh.js +23 -4
  130. package/lib/util/stream_utils.js +96 -24
  131. package/lib/util/uint8array_utils.js +0 -16
  132. package/lib/util/xml_utils.js +1 -1
  133. package/package.json +4 -1
  134. package/roadmap.md +19 -19
  135. package/shaka-player.uncompiled.js +0 -3
  136. package/test/cast/cast_utils_unit.js +1 -0
  137. package/test/dash/dash_parser_content_protection_unit.js +44 -0
  138. package/test/dash/dash_parser_manifest_unit.js +0 -24
  139. package/test/dash/dash_parser_segment_template_unit.js +3 -4
  140. package/test/demo/demo_unit.js +2 -1
  141. package/test/hls/hls_live_unit.js +121 -220
  142. package/test/hls/hls_parser_unit.js +515 -432
  143. package/test/hls/manifest_text_parser_unit.js +19 -0
  144. package/test/media/drm_engine_unit.js +86 -120
  145. package/test/media/media_source_engine_integration.js +26 -3
  146. package/test/media/media_source_engine_unit.js +7 -6
  147. package/test/media/playhead_unit.js +27 -191
  148. package/test/media/segment_index_unit.js +15 -25
  149. package/test/media/streaming_engine_integration.js +4 -39
  150. package/test/media/streaming_engine_unit.js +3 -2
  151. package/test/media/time_ranges_utils_unit.js +0 -5
  152. package/test/offline/indexeddb_storage_unit.js +1 -1
  153. package/test/offline/manifest_convert_unit.js +34 -4
  154. package/test/offline/offline_manifest_parser_unit.js +1 -0
  155. package/test/offline/storage_compatibility_unit.js +1 -1
  156. package/test/offline/storage_integration.js +5 -4
  157. package/test/player_external.js +7 -7
  158. package/test/player_integration.js +22 -10
  159. package/test/player_src_equals_integration.js +81 -83
  160. package/test/player_unit.js +124 -1
  161. package/test/test/boot.js +9 -0
  162. package/test/test/externs/filters.js +4 -0
  163. package/test/test/util/fake_media_source_engine.js +11 -4
  164. package/test/test/util/manifest_generator.js +3 -1
  165. package/test/test/util/manifest_parser_util.js +4 -1
  166. package/test/test/util/offline_utils.js +1 -0
  167. package/test/test/util/simple_fakes.js +31 -2
  168. package/test/test/util/streaming_engine_util.js +1 -0
  169. package/test/test/util/test_scheme.js +24 -1
  170. package/test/test/util/util.js +4 -4
  171. package/test/text/cue_integration.js +2 -2
  172. package/test/text/lrc_text_parser_unit.js +6 -7
  173. package/test/text/mp4_ttml_parser_unit.js +6 -4
  174. package/test/text/mp4_vtt_parser_unit.js +6 -5
  175. package/test/text/sbv_text_parser_unit.js +5 -6
  176. package/test/text/srt_text_parser_unit.js +5 -6
  177. package/test/text/ssa_text_parser_unit.js +17 -29
  178. package/test/text/text_engine_unit.js +45 -5
  179. package/test/text/ttml_text_parser_unit.js +77 -77
  180. package/test/text/vtt_text_parser_unit.js +116 -59
  181. package/test/ui/ui_integration.js +0 -16
  182. package/test/ui/ui_unit.js +27 -0
  183. package/test/util/cmcd_manager_unit.js +3 -0
  184. package/test/util/mime_utils_unit.js +54 -0
  185. package/test/util/periods_unit.js +79 -0
  186. package/test/util/stream_utils_unit.js +23 -7
  187. package/ui/controls.js +65 -40
  188. package/ui/fullscreen_button.js +26 -6
  189. package/ui/less/containers.less +3 -3
  190. package/ui/locales/en.json +1 -0
  191. package/ui/locales/source.json +5 -0
  192. package/ui/resolution_selection.js +31 -33
  193. package/ui/ui.js +0 -17
  194. package/docs/tutorials/gsoc-ideas-list.md +0 -3
  195. package/docs/tutorials/upgrade-v2-5.md +0 -441
  196. package/docs/tutorials/upgrade-v3.md +0 -38
  197. package/lib/polyfill/mathround.js +0 -49
  198. package/lib/polyfill/patchedmediakeys_apple.js +0 -744
  199. package/lib/polyfill/patchedmediakeys_ms.js +0 -698
  200. package/lib/util/mutex.js +0 -52
  201. package/test/util/functional_unit.js +0 -82
@@ -18,6 +18,7 @@ goog.require('shaka.log');
18
18
  goog.require('shaka.media.DrmEngine');
19
19
  goog.require('shaka.media.InitSegmentReference');
20
20
  goog.require('shaka.media.ManifestParser');
21
+ goog.require('shaka.media.MediaSourceEngine');
21
22
  goog.require('shaka.media.PresentationTimeline');
22
23
  goog.require('shaka.media.SegmentIndex');
23
24
  goog.require('shaka.media.SegmentReference');
@@ -26,19 +27,17 @@ goog.require('shaka.net.NetworkingEngine');
26
27
  goog.require('shaka.util.ArrayUtils');
27
28
  goog.require('shaka.util.BufferUtils');
28
29
  goog.require('shaka.util.CmcdManager');
29
- goog.require('shaka.util.DataViewReader');
30
30
  goog.require('shaka.util.Error');
31
31
  goog.require('shaka.util.FakeEvent');
32
32
  goog.require('shaka.util.Functional');
33
33
  goog.require('shaka.util.LanguageUtils');
34
34
  goog.require('shaka.util.ManifestParserUtils');
35
35
  goog.require('shaka.util.MimeUtils');
36
- goog.require('shaka.util.Mp4Parser');
37
- goog.require('shaka.util.Mp4BoxParsers');
38
- goog.require('shaka.util.Networking');
39
36
  goog.require('shaka.util.OperationManager');
40
37
  goog.require('shaka.util.Pssh');
41
38
  goog.require('shaka.util.Timer');
39
+ goog.require('shaka.util.Platform');
40
+ goog.require('shaka.util.XmlUtils');
42
41
  goog.requireType('shaka.hls.Segment');
43
42
 
44
43
 
@@ -95,9 +94,6 @@ shaka.hls.HlsParser = class {
95
94
  * createStreamInfoFromMediaTag_, createStreamInfoFromImageTag_ and
96
95
  * createStreamInfoFromVariantTag_.
97
96
  *
98
- * During parsing of updates, used by getStartTime_ to determine the start
99
- * time of the first segment from existing segment references.
100
- *
101
97
  * @private {!Map.<string, shaka.hls.HlsParser.StreamInfo>}
102
98
  */
103
99
  this.uriToStreamInfosMap_ = new Map();
@@ -124,6 +120,15 @@ shaka.hls.HlsParser = class {
124
120
  */
125
121
  this.updatePlaylistDelay_ = 0;
126
122
 
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.
128
+ * @private {?number}
129
+ */
130
+ this.syncTimeOffset_ = null;
131
+
127
132
  /**
128
133
  * This timer is used to trigger the start of a manifest update. A manifest
129
134
  * update is async. Once the update is finished, the timer will be restarted
@@ -175,9 +180,6 @@ shaka.hls.HlsParser = class {
175
180
  /** @private {Map.<string, string>} */
176
181
  this.groupIdToCodecsMap_ = new Map();
177
182
 
178
- /** @private {?number} */
179
- this.playlistStartTime_ = null;
180
-
181
183
  /** A cache mapping EXT-X-MAP tag info to the InitSegmentReference created
182
184
  * from the tag.
183
185
  * The key is a string combining the EXT-X-MAP tag's absolute uri, and
@@ -185,15 +187,6 @@ shaka.hls.HlsParser = class {
185
187
  * {!Map.<string, !shaka.media.InitSegmentReference>} */
186
188
  this.mapTagToInitSegmentRefMap_ = new Map();
187
189
 
188
- /**
189
- * A cache mapping a discontinuity sequence number of a segment with
190
- * EXT-X-DISCONTINUITY tag into its timestamp offset.
191
- * Key: the discontinuity sequence number of a segment
192
- * Value: the segment reference's timestamp offset.
193
- * {!Map.<number, number>}
194
- */
195
- this.discontinuityToTso_ = new Map();
196
-
197
190
  /** @private {boolean} */
198
191
  this.lowLatencyMode_ = false;
199
192
  }
@@ -222,7 +215,7 @@ shaka.hls.HlsParser = class {
222
215
  this.masterPlaylistUri_ = response.uri;
223
216
 
224
217
  goog.asserts.assert(response.data, 'Response data should be non-null!');
225
- await this.parseManifest_(response.data);
218
+ await this.parseManifest_(response.data, uri);
226
219
 
227
220
  // Start the update timer if we want updates.
228
221
  const delay = this.updatePlaylistDelay_;
@@ -277,8 +270,6 @@ shaka.hls.HlsParser = class {
277
270
 
278
271
  /** @type {!Array.<!Promise>} */
279
272
  const updates = [];
280
- // Reset the start time for the new media playlist.
281
- this.playlistStartTime_ = null;
282
273
  const streamInfos = Array.from(this.uriToStreamInfosMap_.values());
283
274
  // Wait for the first stream info created, so that the start time is fetched
284
275
  // and can be reused.
@@ -329,11 +320,10 @@ shaka.hls.HlsParser = class {
329
320
 
330
321
  const stream = streamInfo.stream;
331
322
 
332
- const segments = await this.createSegments_(
323
+ const segments = this.createSegments_(
333
324
  streamInfo.verbatimMediaPlaylistUri, playlist, stream.type,
334
325
  stream.mimeType, streamInfo.mediaSequenceToStartTime, mediaVariables,
335
- streamInfo.discontinuityToMediaSequence, stream.codecs,
336
- stream.bandwidth);
326
+ stream.codecs);
337
327
 
338
328
  stream.segmentIndex.mergeAndEvict(
339
329
  segments, this.presentationTimeline_.getSegmentAvailabilityStart());
@@ -370,14 +360,51 @@ shaka.hls.HlsParser = class {
370
360
  // No-op
371
361
  }
372
362
 
363
+ /**
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.
367
+ * @private
368
+ */
369
+ calculateSyncTimeOffset_() {
370
+ if (this.syncTimeOffset_ != null) {
371
+ // The offset was already calculated.
372
+ return;
373
+ }
374
+
375
+ const segments = new Set();
376
+ let lowestSyncTime = Infinity;
377
+ for (const streamInfo of this.uriToStreamInfosMap_.values()) {
378
+ const segmentIndex = streamInfo.stream.segmentIndex;
379
+ if (segmentIndex) {
380
+ segmentIndex.forEachTopLevelReference((segment) => {
381
+ if (segment.syncTime != null) {
382
+ lowestSyncTime = Math.min(lowestSyncTime, segment.syncTime);
383
+ segments.add(segment);
384
+ }
385
+ });
386
+ }
387
+ }
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_;
394
+ }
395
+ }
396
+ }
397
+ }
398
+
373
399
  /**
374
400
  * Parses the manifest.
375
401
  *
376
402
  * @param {BufferSource} data
403
+ * @param {string} uri
377
404
  * @return {!Promise}
378
405
  * @private
379
406
  */
380
- async parseManifest_(data) {
407
+ async parseManifest_(data, uri) {
381
408
  const Utils = shaka.hls.Utils;
382
409
 
383
410
  goog.asserts.assert(this.masterPlaylistUri_,
@@ -386,63 +413,106 @@ shaka.hls.HlsParser = class {
386
413
  const playlist = this.manifestTextParser_.parsePlaylist(
387
414
  data, this.masterPlaylistUri_);
388
415
 
389
- // We don't support directly providing a Media Playlist.
390
- // See the error code for details.
391
- if (playlist.type != shaka.hls.PlaylistType.MASTER) {
392
- throw new shaka.util.Error(
393
- shaka.util.Error.Severity.CRITICAL,
394
- shaka.util.Error.Category.MANIFEST,
395
- shaka.util.Error.Code.HLS_MASTER_PLAYLIST_NOT_PROVIDED);
396
- }
397
-
398
416
  /** @type {!Array.<!shaka.hls.Tag>} */
399
417
  const variablesTags = Utils.filterTagsByName(playlist.tags, 'EXT-X-DEFINE');
400
418
 
401
419
  this.parseMasterVariables_(variablesTags);
402
420
 
403
- /** @type {!Array.<!shaka.hls.Tag>} */
404
- const mediaTags = Utils.filterTagsByName(playlist.tags, 'EXT-X-MEDIA');
405
- /** @type {!Array.<!shaka.hls.Tag>} */
406
- const variantTags = Utils.filterTagsByName(
407
- playlist.tags, 'EXT-X-STREAM-INF');
408
- /** @type {!Array.<!shaka.hls.Tag>} */
409
- const imageTags = Utils.filterTagsByName(
410
- playlist.tags, 'EXT-X-IMAGE-STREAM-INF');
411
-
412
- this.parseCodecs_(variantTags);
413
-
414
- /** @type {!Array.<!shaka.hls.Tag>} */
415
- const sesionDataTags =
416
- Utils.filterTagsByName(playlist.tags, 'EXT-X-SESSION-DATA');
417
- for (const tag of sesionDataTags) {
418
- const id = tag.getAttributeValue('DATA-ID');
419
- const uri = tag.getAttributeValue('URI');
420
- const language = tag.getAttributeValue('LANGUAGE');
421
- const value = tag.getAttributeValue('VALUE');
422
- const data = (new Map()).set('id', id);
423
- if (uri) {
424
- data.set('uri',
425
- shaka.hls.Utils.constructAbsoluteUri(this.masterPlaylistUri_, uri));
426
- }
427
- if (language) {
428
- data.set('language', language);
429
- }
430
- if (value) {
431
- data.set('value', value);
432
- }
433
- const event = new shaka.util.FakeEvent('sessiondata', data);
434
- if (this.playerInterface_) {
435
- this.playerInterface_.onEvent(event);
421
+ /** @type {!Array.<!shaka.extern.Variant>} */
422
+ let variants = [];
423
+ /** @type {!Array.<!shaka.extern.Stream>} */
424
+ let textStreams = [];
425
+ /** @type {!Array.<!shaka.extern.Stream>} */
426
+ let imageStreams = [];
427
+
428
+ // Parsing a media playlist results in a single-variant stream.
429
+ if (playlist.type == shaka.hls.PlaylistType.MEDIA) {
430
+ // Get necessary info for this stream, from the config. These are things
431
+ // we would normally find from the master playlist (e.g. from values on
432
+ // EXT-X-MEDIA tags).
433
+ const fullMimeType = this.config_.hls.mediaPlaylistFullMimeType;
434
+ const mimeType = shaka.util.MimeUtils.getBasicType(fullMimeType);
435
+ const type = mimeType.split('/')[0];
436
+ const codecs = shaka.util.MimeUtils.getCodecs(fullMimeType);
437
+
438
+ // Some values we cannot figure out, and aren't important enough to ask
439
+ // the user to provide through config values. A lot of these are only
440
+ // relevant to ABR, which isn't necessary if there's only one variant.
441
+ // So these unknowns should be set to false or null, largely.
442
+ const language = '';
443
+ const channelsCount = null;
444
+ const spatialAudio = false;
445
+ const characteristics = null;
446
+ const closedCaptions = new Map();
447
+ const forced = false; // Only relevant for text.
448
+ const primary = true; // This is the only stream!
449
+ const name = 'Media Playlist';
450
+
451
+ // Make the stream info, with those values.
452
+ const streamInfo = await this.convertParsedPlaylistIntoStreamInfo_(
453
+ playlist, uri, uri, codecs, type, language, primary, name,
454
+ channelsCount, closedCaptions, characteristics, forced, spatialAudio,
455
+ mimeType);
456
+ goog.asserts.assert(streamInfo != null, 'StreamInfo is null!');
457
+ this.uriToStreamInfosMap_.set(uri, streamInfo);
458
+
459
+ // Wrap the stream from that stream info with a variant.
460
+ variants.push({
461
+ id: 0,
462
+ language: 'und',
463
+ primary: true,
464
+ audio: type == 'audio' ? streamInfo.stream : null,
465
+ video: type == 'video' ? streamInfo.stream : null,
466
+ bandwidth: 0,
467
+ allowedByApplication: true,
468
+ allowedByKeySystem: true,
469
+ decodingInfos: [],
470
+ });
471
+ } else {
472
+ /** @type {!Array.<!shaka.hls.Tag>} */
473
+ const mediaTags = Utils.filterTagsByName(playlist.tags, 'EXT-X-MEDIA');
474
+ /** @type {!Array.<!shaka.hls.Tag>} */
475
+ const variantTags = Utils.filterTagsByName(
476
+ playlist.tags, 'EXT-X-STREAM-INF');
477
+ /** @type {!Array.<!shaka.hls.Tag>} */
478
+ const imageTags = Utils.filterTagsByName(
479
+ playlist.tags, 'EXT-X-IMAGE-STREAM-INF');
480
+
481
+ this.parseCodecs_(variantTags);
482
+
483
+ /** @type {!Array.<!shaka.hls.Tag>} */
484
+ const sesionDataTags =
485
+ Utils.filterTagsByName(playlist.tags, 'EXT-X-SESSION-DATA');
486
+ for (const tag of sesionDataTags) {
487
+ const id = tag.getAttributeValue('DATA-ID');
488
+ const uri = tag.getAttributeValue('URI');
489
+ const language = tag.getAttributeValue('LANGUAGE');
490
+ const value = tag.getAttributeValue('VALUE');
491
+ const data = (new Map()).set('id', id);
492
+ if (uri) {
493
+ data.set('uri', shaka.hls.Utils.constructAbsoluteUri(
494
+ this.masterPlaylistUri_, uri));
495
+ }
496
+ if (language) {
497
+ data.set('language', language);
498
+ }
499
+ if (value) {
500
+ data.set('value', value);
501
+ }
502
+ const event = new shaka.util.FakeEvent('sessiondata', data);
503
+ if (this.playerInterface_) {
504
+ this.playerInterface_.onEvent(event);
505
+ }
436
506
  }
437
- }
438
507
 
439
- // Parse audio and video media tags first, so that we can extract segment
440
- // start time from audio/video streams and reuse for text streams.
441
- await this.createStreamInfosFromMediaTags_(mediaTags);
442
- this.parseClosedCaptions_(mediaTags);
443
- const variants = await this.createVariantsForTags_(variantTags);
444
- const textStreams = await this.parseTexts_(mediaTags);
445
- const imageStreams = await this.parseImages_(imageTags);
508
+ // Parse audio and video media tags first, so that we can extract segment
509
+ // start time from audio/video streams and reuse for text streams.
510
+ await this.createStreamInfosFromMediaTags_(mediaTags);
511
+ this.parseClosedCaptions_(mediaTags);
512
+ variants = await this.createVariantsForTags_(variantTags);
513
+ textStreams = await this.parseTexts_(mediaTags);
514
+ imageStreams = await this.parseImages_(imageTags);
515
+ }
446
516
 
447
517
  // Make sure that the parser has not been destroyed.
448
518
  if (!this.playerInterface_) {
@@ -452,6 +522,10 @@ shaka.hls.HlsParser = class {
452
522
  shaka.util.Error.Code.OPERATION_ABORTED);
453
523
  }
454
524
 
525
+ // Now that we have generated all streams, we can determine the offset to
526
+ // apply to sync times.
527
+ this.calculateSyncTimeOffset_();
528
+
455
529
  if (this.aesEncrypted_ && variants.length == 0) {
456
530
  // We do not support AES-128 encryption with HLS yet. Variants is null
457
531
  // when the playlist is encrypted with AES-128.
@@ -564,6 +638,7 @@ shaka.hls.HlsParser = class {
564
638
  imageStreams,
565
639
  offlineSessionIds: [],
566
640
  minBufferTime: 0,
641
+ sequenceMode: true,
567
642
  };
568
643
  this.playerInterface_.makeTextStreamsForClosedCaptions(this.manifest_);
569
644
  }
@@ -764,7 +839,7 @@ shaka.hls.HlsParser = class {
764
839
  const videoRange = tag.getAttributeValue('VIDEO-RANGE');
765
840
 
766
841
  const streamInfos = await this.createStreamInfosForVariantTag_(tag,
767
- resolution, frameRate, bandwidth);
842
+ resolution, frameRate);
768
843
 
769
844
  if (streamInfos) {
770
845
  goog.asserts.assert(streamInfos.audio.length ||
@@ -798,11 +873,10 @@ shaka.hls.HlsParser = class {
798
873
  * @param {!shaka.hls.Tag} tag
799
874
  * @param {?string} resolution
800
875
  * @param {?string} frameRate
801
- * @param {number} bandwidth
802
876
  * @return {!Promise.<?shaka.hls.HlsParser.StreamInfos>}
803
877
  * @private
804
878
  */
805
- async createStreamInfosForVariantTag_(tag, resolution, frameRate, bandwidth) {
879
+ async createStreamInfosForVariantTag_(tag, resolution, frameRate) {
806
880
  const ContentType = shaka.util.ManifestParserUtils.ContentType;
807
881
  /** @type {!Array.<string>} */
808
882
  let allCodecs = this.getCodecsForVariantTag_(tag);
@@ -872,8 +946,7 @@ shaka.hls.HlsParser = class {
872
946
  let streamInfo;
873
947
  if (!ignoreStream) {
874
948
  streamInfo =
875
- await this.createStreamInfoFromVariantTag_(tag, allCodecs, type,
876
- bandwidth);
949
+ await this.createStreamInfoFromVariantTag_(tag, allCodecs, type);
877
950
  }
878
951
  if (streamInfo) {
879
952
  res[streamInfo.stream.type] = [streamInfo];
@@ -1314,11 +1387,10 @@ shaka.hls.HlsParser = class {
1314
1387
  * @param {!shaka.hls.Tag} tag
1315
1388
  * @param {!Array.<string>} allCodecs
1316
1389
  * @param {string} type
1317
- * @param {number} bandwidth
1318
1390
  * @return {!Promise.<?shaka.hls.HlsParser.StreamInfo>}
1319
1391
  * @private
1320
1392
  */
1321
- async createStreamInfoFromVariantTag_(tag, allCodecs, type, bandwidth) {
1393
+ async createStreamInfoFromVariantTag_(tag, allCodecs, type) {
1322
1394
  goog.asserts.assert(tag.name == 'EXT-X-STREAM-INF',
1323
1395
  'Should only be called on variant tags!');
1324
1396
  const verbatimMediaPlaylistUri = this.variableSubstitution_(
@@ -1334,7 +1406,7 @@ shaka.hls.HlsParser = class {
1334
1406
  codecs, type, /* language= */ 'und', /* primary= */ false,
1335
1407
  /* name= */ null, /* channelcount= */ null, closedCaptions,
1336
1408
  /* characteristics= */ null, /* forced= */ false,
1337
- /* spatialAudio= */ false, bandwidth);
1409
+ /* spatialAudio= */ false);
1338
1410
  if (streamInfo == null) {
1339
1411
  return null;
1340
1412
  }
@@ -1361,13 +1433,12 @@ shaka.hls.HlsParser = class {
1361
1433
  * @param {?string} characteristics
1362
1434
  * @param {boolean} forced
1363
1435
  * @param {boolean} spatialAudio
1364
- * @param {(number|undefined)} bandwidth
1365
1436
  * @return {!Promise.<?shaka.hls.HlsParser.StreamInfo>}
1366
1437
  * @private
1367
1438
  */
1368
1439
  async createStreamInfo_(verbatimMediaPlaylistUri, codecs, type, language,
1369
1440
  primary, name, channelsCount, closedCaptions, characteristics, forced,
1370
- spatialAudio, bandwidth = undefined) {
1441
+ spatialAudio) {
1371
1442
  // TODO: Refactor, too many parameters
1372
1443
  let absoluteMediaPlaylistUri = shaka.hls.Utils.constructAbsoluteUri(
1373
1444
  this.masterPlaylistUri_, verbatimMediaPlaylistUri);
@@ -1381,6 +1452,34 @@ shaka.hls.HlsParser = class {
1381
1452
  const playlist = this.manifestTextParser_.parsePlaylist(
1382
1453
  response.data, absoluteMediaPlaylistUri);
1383
1454
 
1455
+ return this.convertParsedPlaylistIntoStreamInfo_(playlist,
1456
+ verbatimMediaPlaylistUri, absoluteMediaPlaylistUri, codecs, type,
1457
+ language, primary, name, channelsCount, closedCaptions, characteristics,
1458
+ forced, spatialAudio);
1459
+ }
1460
+
1461
+ /**
1462
+ * @param {!shaka.hls.Playlist} playlist
1463
+ * @param {string} verbatimMediaPlaylistUri
1464
+ * @param {string} absoluteMediaPlaylistUri
1465
+ * @param {string} codecs
1466
+ * @param {string} type
1467
+ * @param {string} language
1468
+ * @param {boolean} primary
1469
+ * @param {?string} name
1470
+ * @param {?number} channelsCount
1471
+ * @param {Map.<string, string>} closedCaptions
1472
+ * @param {?string} characteristics
1473
+ * @param {boolean} forced
1474
+ * @param {boolean} spatialAudio
1475
+ * @param {(string|undefined)} mimeType
1476
+ * @return {!Promise.<?shaka.hls.HlsParser.StreamInfo>}
1477
+ * @private
1478
+ */
1479
+ async convertParsedPlaylistIntoStreamInfo_(playlist, verbatimMediaPlaylistUri,
1480
+ absoluteMediaPlaylistUri, codecs, type, language, primary, name,
1481
+ channelsCount, closedCaptions, characteristics, forced, spatialAudio,
1482
+ mimeType = undefined) {
1384
1483
  if (playlist.type != shaka.hls.PlaylistType.MEDIA) {
1385
1484
  // EXT-X-MEDIA and EXT-X-IMAGE-STREAM-INF tags should point to media
1386
1485
  // playlists.
@@ -1390,6 +1489,22 @@ shaka.hls.HlsParser = class {
1390
1489
  shaka.util.Error.Code.HLS_INVALID_PLAYLIST_HIERARCHY);
1391
1490
  }
1392
1491
 
1492
+ /** @type {!Array.<!shaka.hls.Tag>} */
1493
+ const variablesTags = shaka.hls.Utils.filterTagsByName(playlist.tags,
1494
+ 'EXT-X-DEFINE');
1495
+
1496
+ const mediaVariables = this.parseMediaVariables_(variablesTags);
1497
+
1498
+ goog.asserts.assert(playlist.segments != null,
1499
+ 'Media playlist should have segments!');
1500
+
1501
+ this.determinePresentationType_(playlist);
1502
+
1503
+ if (!mimeType) {
1504
+ mimeType = await this.guessMimeType_(type, codecs, playlist,
1505
+ mediaVariables);
1506
+ }
1507
+
1393
1508
  /** @type {!Array.<!shaka.hls.Tag>} */
1394
1509
  const drmTags = [];
1395
1510
  if (playlist.segments) {
@@ -1424,7 +1539,7 @@ shaka.hls.HlsParser = class {
1424
1539
  const drmParser =
1425
1540
  shaka.hls.HlsParser.KEYFORMATS_TO_DRM_PARSERS_[keyFormat];
1426
1541
 
1427
- const drmInfo = drmParser ? drmParser(drmTag) : null;
1542
+ const drmInfo = drmParser ? drmParser(drmTag, mimeType) : null;
1428
1543
  if (drmInfo) {
1429
1544
  if (drmInfo.keyIds) {
1430
1545
  for (const keyId of drmInfo.keyIds) {
@@ -1445,47 +1560,19 @@ shaka.hls.HlsParser = class {
1445
1560
  shaka.util.Error.Code.HLS_KEYFORMATS_NOT_SUPPORTED);
1446
1561
  }
1447
1562
 
1448
- /** @type {!Array.<!shaka.hls.Tag>} */
1449
- const variablesTags = shaka.hls.Utils.filterTagsByName(playlist.tags,
1450
- 'EXT-X-DEFINE');
1451
-
1452
- const mediaVariables = this.parseMediaVariables_(variablesTags);
1453
-
1454
- goog.asserts.assert(playlist.segments != null,
1455
- 'Media playlist should have segments!');
1456
-
1457
- this.determinePresentationType_(playlist);
1458
-
1459
- /** @type {string} */
1460
- const mimeType = await this.guessMimeType_(type, codecs, playlist,
1461
- mediaVariables);
1462
-
1463
1563
  // MediaSource expects no codec strings combined with raw formats.
1464
- // TODO(#2337): Instead, create a Stream flag indicating a raw format.
1465
- if (shaka.hls.HlsParser.RAW_FORMATS_.includes(mimeType)) {
1564
+ if (shaka.media.MediaSourceEngine.RAW_FORMATS.includes(mimeType)) {
1466
1565
  codecs = '';
1467
1566
  }
1468
1567
 
1469
1568
  /** @type {!Map.<number, number>} */
1470
1569
  const mediaSequenceToStartTime = new Map();
1471
1570
 
1472
- /**
1473
- * A map of a discontinuity sequence number, to the first segment's media
1474
- * sequence number with the discontinuity sequence number.
1475
- * Key: the discontinuity sequence number of a few segments
1476
- * Value: the first segment's media sequence number of the segments with
1477
- * this discontinuity sequence number.
1478
- * Used to get the discontinuity sequence number with playlist delta
1479
- * updates with lowLatencyMode enabled.
1480
- * {!Map.<number, number>}
1481
- */
1482
- const discontinuityToMediaSequence = new Map();
1483
-
1484
1571
  let segments;
1485
1572
  try {
1486
- segments = await this.createSegments_(verbatimMediaPlaylistUri,
1573
+ segments = this.createSegments_(verbatimMediaPlaylistUri,
1487
1574
  playlist, type, mimeType, mediaSequenceToStartTime, mediaVariables,
1488
- discontinuityToMediaSequence, codecs, bandwidth);
1575
+ codecs);
1489
1576
  } catch (error) {
1490
1577
  if (error.code == shaka.util.Error.Code.HLS_INTERNAL_SKIP_STREAM) {
1491
1578
  shaka.log.alwaysWarn('Skipping unsupported HLS stream',
@@ -1557,7 +1644,6 @@ shaka.hls.HlsParser = class {
1557
1644
  minTimestamp,
1558
1645
  maxTimestamp: lastEndTime,
1559
1646
  mediaSequenceToStartTime,
1560
- discontinuityToMediaSequence,
1561
1647
  canSkipSegments,
1562
1648
  };
1563
1649
  }
@@ -1736,16 +1822,15 @@ shaka.hls.HlsParser = class {
1736
1822
  * @param {shaka.media.SegmentReference} previousReference
1737
1823
  * @param {!shaka.hls.Segment} hlsSegment
1738
1824
  * @param {number} startTime
1739
- * @param {number} timestampOffset
1740
1825
  * @param {!Map.<string, string>} variables
1741
1826
  * @param {string} absoluteMediaPlaylistUri
1742
- * @return {!shaka.media.SegmentReference}
1743
1827
  * @param {string} type
1828
+ * @return {shaka.media.SegmentReference}
1744
1829
  * @private
1745
1830
  */
1746
1831
  createSegmentReference_(
1747
1832
  initSegmentReference, previousReference, hlsSegment, startTime,
1748
- timestampOffset, variables, absoluteMediaPlaylistUri, type) {
1833
+ variables, absoluteMediaPlaylistUri, type) {
1749
1834
  const tags = hlsSegment.tags;
1750
1835
  const absoluteSegmentUri = this.variableSubstitution_(
1751
1836
  hlsSegment.absoluteUri, variables);
@@ -1756,9 +1841,52 @@ shaka.hls.HlsParser = class {
1756
1841
  let startByte = 0;
1757
1842
  let endByte = null;
1758
1843
 
1844
+ if (hlsSegment.partialSegments.length && !this.lowLatencyMode_) {
1845
+ shaka.log.alwaysWarn('Low-latency HLS live stream detected, but ' +
1846
+ 'low-latency streaming mode is not enabled in Shaka ' +
1847
+ 'Player. Set streaming.lowLatencyMode configuration to ' +
1848
+ 'true, and see https://bit.ly/3clctcj for details.');
1849
+ }
1850
+
1851
+ let syncTime = null;
1852
+ if (!this.config_.hls.ignoreManifestProgramDateTime) {
1853
+ const dateTimeTag =
1854
+ shaka.hls.Utils.getFirstTagWithName(tags, 'EXT-X-PROGRAM-DATE-TIME');
1855
+ if (dateTimeTag && dateTimeTag.value) {
1856
+ const time = shaka.util.XmlUtils.parseDate(dateTimeTag.value);
1857
+ goog.asserts.assert(time != null,
1858
+ '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
+ }
1866
+ }
1867
+
1868
+ let status = shaka.media.SegmentReference.Status.AVAILABLE;
1869
+ if (shaka.hls.Utils.getFirstTagWithName(tags, 'EXT-X-GAP')) {
1870
+ status = shaka.media.SegmentReference.Status.MISSING;
1871
+ }
1872
+
1873
+ if (!extinfTag) {
1874
+ if (hlsSegment.partialSegments.length == 0) {
1875
+ // EXTINF tag must be available if the segment has no partial segments.
1876
+ throw new shaka.util.Error(
1877
+ shaka.util.Error.Severity.CRITICAL,
1878
+ shaka.util.Error.Category.MANIFEST,
1879
+ shaka.util.Error.Code.HLS_REQUIRED_TAG_MISSING, 'EXTINF');
1880
+ } else if (!this.lowLatencyMode_) {
1881
+ // Without EXTINF and without low-latency mode, partial segments get
1882
+ // ignored.
1883
+ return null;
1884
+ }
1885
+ }
1886
+
1759
1887
  // Create SegmentReferences for the partial segments.
1760
1888
  const partialSegmentRefs = [];
1761
- if (this.lowLatencyMode_ && hlsSegment.partialSegments.length) {
1889
+ if (this.lowLatencyMode_) {
1762
1890
  for (let i = 0; i < hlsSegment.partialSegments.length; i++) {
1763
1891
  const item = hlsSegment.partialSegments[i];
1764
1892
  const pPreviousReference = i == 0 ?
@@ -1794,19 +1922,11 @@ shaka.hls.HlsParser = class {
1794
1922
  pStartByte,
1795
1923
  pEndByte,
1796
1924
  initSegmentReference,
1797
- timestampOffset,
1925
+ /* timestampOffset= */ 0, // This value is ignored in sequence mode.
1798
1926
  /* appendWindowStart= */ 0,
1799
1927
  /* appendWindowEnd= */ Infinity);
1800
1928
  partialSegmentRefs.push(partial);
1801
1929
  } // for-loop of hlsSegment.partialSegments
1802
- } else {
1803
- // EXTINF tag must be available if the segment has no partial segments.
1804
- if (!extinfTag) {
1805
- throw new shaka.util.Error(
1806
- shaka.util.Error.Severity.CRITICAL,
1807
- shaka.util.Error.Category.MANIFEST,
1808
- shaka.util.Error.Code.HLS_REQUIRED_TAG_MISSING, 'EXTINF');
1809
- }
1810
1930
  }
1811
1931
 
1812
1932
  // If the segment has EXTINF tag, set the segment's end time, start byte
@@ -1825,6 +1945,7 @@ shaka.hls.HlsParser = class {
1825
1945
  } else {
1826
1946
  endTime = partialSegmentRefs[partialSegmentRefs.length - 1].endTime;
1827
1947
  }
1948
+
1828
1949
  // If the segment has EXT-X-BYTERANGE tag, set the start byte and end byte
1829
1950
  // base on the byterange information. If segment has no EXT-X-BYTERANGE tag
1830
1951
  // and has partial segments, set the start byte and end byte base on the
@@ -1862,12 +1983,14 @@ shaka.hls.HlsParser = class {
1862
1983
  startByte,
1863
1984
  endByte,
1864
1985
  initSegmentReference,
1865
- timestampOffset,
1986
+ /* timestampOffset= */ 0, // This value is ignored in sequence mode.
1866
1987
  /* appendWindowStart= */ 0,
1867
1988
  /* appendWindowEnd= */ Infinity,
1868
1989
  partialSegmentRefs,
1869
1990
  tilesLayout,
1870
1991
  tileDuration,
1992
+ syncTime,
1993
+ status,
1871
1994
  );
1872
1995
  }
1873
1996
 
@@ -1921,15 +2044,12 @@ shaka.hls.HlsParser = class {
1921
2044
  * @param {string} mimeType
1922
2045
  * @param {!Map.<number, number>} mediaSequenceToStartTime
1923
2046
  * @param {!Map.<string, string>} variables
1924
- * @param {!Map.<number, number>} discontinuityToMediaSequence
1925
2047
  * @param {string} codecs
1926
- * @param {(number|undefined)} bandwidth
1927
- * @return {!Promise<!Array.<!shaka.media.SegmentReference>>}
2048
+ * @return {!Array.<!shaka.media.SegmentReference>}
1928
2049
  * @private
1929
2050
  */
1930
- async createSegments_(verbatimMediaPlaylistUri, playlist, type, mimeType,
1931
- mediaSequenceToStartTime, variables, discontinuityToMediaSequence,
1932
- codecs, bandwidth) {
2051
+ createSegments_(verbatimMediaPlaylistUri, playlist, type, mimeType,
2052
+ mediaSequenceToStartTime, variables, codecs) {
1933
2053
  /** @type {Array.<!shaka.hls.Segment>} */
1934
2054
  const hlsSegments = playlist.segments;
1935
2055
  goog.asserts.assert(hlsSegments.length, 'Playlist should have segments!');
@@ -1946,69 +2066,26 @@ shaka.hls.HlsParser = class {
1946
2066
  const skippedSegments =
1947
2067
  skipTag ? Number(skipTag.getAttributeValue('SKIPPED-SEGMENTS')) : 0;
1948
2068
  let position = mediaSequenceNumber + skippedSegments;
1949
- let firstStartTime;
2069
+ let firstStartTime = 0;
2070
+
1950
2071
  // For live stream, use the cached value in the mediaSequenceToStartTime
1951
2072
  // map if available.
1952
- // Since createSegments_() is asynchronous and we are updating the streams
1953
- // in parallel, the global playlistStartTime_ may get updated by other
1954
- // playlist updates rather than the current one.
1955
-
1956
2073
  if (this.isLive_() && mediaSequenceToStartTime.has(position)) {
1957
2074
  firstStartTime = mediaSequenceToStartTime.get(position);
1958
- } else {
1959
- if (this.playlistStartTime_ == null) {
1960
- // For VOD and EVENT playlists, all variants must start at the same
1961
- // time, so we can fetch the start time once and reuse for the others.
1962
- // This is not guaranteed when updating a LIVE stream. We assume the
1963
- // first segment in each live playlist is no more than one segment out
1964
- // of sync with the other playlists, so we can fetch the start time for
1965
- // once.
1966
- initSegmentRef = this.getInitSegmentReference_(
1967
- playlist.absoluteUri, hlsSegments[0].tags, variables);
1968
- goog.asserts.assert(
1969
- type != shaka.util.ManifestParserUtils.ContentType.TEXT &&
1970
- type != shaka.util.ManifestParserUtils.ContentType.IMAGE,
1971
- 'Should only get start time from audio or video streams');
1972
- this.playlistStartTime_ = await this.getStartTime_(
1973
- verbatimMediaPlaylistUri, initSegmentRef, mimeType,
1974
- position, /* isDiscontinuity= */ false,
1975
- hlsSegments[0], variables, type, codecs, bandwidth);
1976
- }
1977
- firstStartTime = this.playlistStartTime_;
1978
2075
  }
1979
2076
 
1980
2077
  const firstSegmentUri = hlsSegments[0].absoluteUri;
1981
2078
  shaka.log.debug('First segment', firstSegmentUri.split('/').pop(),
1982
2079
  'starts at', firstStartTime);
1983
2080
 
1984
- let discontintuitySequenceNum = shaka.hls.Utils.getFirstTagWithNameAsNumber(
1985
- playlist.tags, 'EXT-X-DISCONTINUITY-SEQUENCE');
1986
- if (this.lowLatencyMode_) {
1987
- if (!discontinuityToMediaSequence.has(discontintuitySequenceNum)) {
1988
- discontinuityToMediaSequence.set(discontintuitySequenceNum, position);
1989
- }
1990
- if (skippedSegments) {
1991
- // With delta updates, the DISCONTINUITY may be skipped. Check if
1992
- // the discontintuity Sequence Number based on the media sequence
1993
- // number.
1994
- const disconMap = discontinuityToMediaSequence;
1995
- while (disconMap.has(discontintuitySequenceNum + 1) &&
1996
- disconMap.get(discontintuitySequenceNum + 1) < position) {
1997
- discontintuitySequenceNum++;
1998
- }
1999
- }
2000
- }
2001
- let timestampOffset =
2002
- this.discontinuityToTso_.get(discontintuitySequenceNum) || 0;
2003
-
2004
2081
  /** @type {!Array.<!shaka.media.SegmentReference>} */
2005
2082
  const references = [];
2006
2083
 
2007
2084
  for (let i = 0; i < hlsSegments.length; i++) {
2008
2085
  const item = hlsSegments[i];
2009
2086
  const previousReference = references[references.length - 1];
2010
- const startTime = (i == 0) ? firstStartTime :
2011
- previousReference.endTime;
2087
+ const startTime =
2088
+ (i == 0) ? firstStartTime : previousReference.endTime;
2012
2089
  position = mediaSequenceNumber + skippedSegments + i;
2013
2090
 
2014
2091
  mediaSequenceToStartTime.set(position, startTime);
@@ -2016,19 +2093,6 @@ shaka.hls.HlsParser = class {
2016
2093
  initSegmentRef = this.getInitSegmentReference_(playlist.absoluteUri,
2017
2094
  item.tags, variables);
2018
2095
 
2019
- const discontintuityTag = shaka.hls.Utils.getFirstTagWithName(item.tags,
2020
- 'EXT-X-DISCONTINUITY');
2021
- if (discontintuityTag) {
2022
- discontintuitySequenceNum++;
2023
- discontinuityToMediaSequence.set(discontintuitySequenceNum, position);
2024
-
2025
- // eslint-disable-next-line no-await-in-loop
2026
- timestampOffset = await this.getTimestampOffset_(
2027
- discontintuitySequenceNum, verbatimMediaPlaylistUri, initSegmentRef,
2028
- mimeType, position, item, variables, startTime, type, codecs,
2029
- bandwidth);
2030
- }
2031
-
2032
2096
  // If the stream is low latency and the user has not configured the
2033
2097
  // lowLatencyMode, but if it has been configured to activate the
2034
2098
  // lowLatencyMode if a stream of this type is detected, we automatically
@@ -2041,514 +2105,96 @@ shaka.hls.HlsParser = class {
2041
2105
  }
2042
2106
  }
2043
2107
 
2044
- const extinfTag =
2045
- shaka.hls.Utils.getFirstTagWithName(item.tags, 'EXTINF');
2046
- if (this.lowLatencyMode_ || extinfTag) {
2047
- const reference = this.createSegmentReference_(
2048
- initSegmentRef,
2049
- previousReference,
2050
- item,
2051
- startTime,
2052
- timestampOffset,
2053
- variables,
2054
- playlist.absoluteUri,
2055
- type);
2108
+ const reference = this.createSegmentReference_(
2109
+ initSegmentRef,
2110
+ previousReference,
2111
+ item,
2112
+ startTime,
2113
+ variables,
2114
+ playlist.absoluteUri,
2115
+ type);
2056
2116
 
2117
+ if (reference) {
2057
2118
  references.push(reference);
2058
- } else if (!this.lowLatencyMode_) {
2059
- // If a segment has no extinfTag, it must contain partial segments.
2060
- shaka.log.alwaysWarn('Low-latency HLS live stream detected, but ' +
2061
- 'low-latency streaming mode is not enabled in Shaka Player. ' +
2062
- 'Set streaming.lowLatencyMode configuration to true, and see ' +
2063
- 'https://bit.ly/3clctcj for details.');
2064
- }
2065
- }
2066
-
2067
- return references;
2068
- }
2069
-
2070
- /**
2071
- * Gets the start time of the first segment of the playlist from existing
2072
- * value (if possible) or by downloading it and parsing it otherwise.
2073
- *
2074
- * @param {number} discontintuitySequenceNum
2075
- * @param {string} verbatimMediaPlaylistUri
2076
- * @param {shaka.media.InitSegmentReference} initSegmentRef
2077
- * @param {string} mimeType
2078
- * @param {number} mediaSequenceNumber
2079
- * @param {!shaka.hls.Segment} segment
2080
- * @param {!Map.<string, string>} variables
2081
- * @param {number} startTime
2082
- * @param {string} type
2083
- * @param {string} codecs
2084
- * @param {number|undefined} bandwidth
2085
- * @return {!Promise.<number>}
2086
- * @throws {shaka.util.Error}
2087
- * @private
2088
- */
2089
- async getTimestampOffset_(discontintuitySequenceNum,
2090
- verbatimMediaPlaylistUri, initSegmentRef,
2091
- mimeType, mediaSequenceNumber, segment, variables, startTime, type,
2092
- codecs, bandwidth) {
2093
- let timestampOffset = 0;
2094
- if (this.discontinuityToTso_.has(discontintuitySequenceNum)) {
2095
- timestampOffset =
2096
- this.discontinuityToTso_.get(discontintuitySequenceNum);
2097
- } else {
2098
- const mediaStartTime = await this.getStartTime_(
2099
- verbatimMediaPlaylistUri, initSegmentRef, mimeType,
2100
- mediaSequenceNumber, /* isDiscontinuity= */ true, segment,
2101
- variables, type, codecs, bandwidth);
2102
- timestampOffset = startTime - mediaStartTime;
2103
- shaka.log.v1('Segment timestampOffset =', timestampOffset);
2104
- this.discontinuityToTso_.set(
2105
- discontintuitySequenceNum, timestampOffset);
2106
- }
2107
- return timestampOffset;
2108
- }
2109
-
2110
- /**
2111
- * Try to fetch the starting part of a segment, and fall back to a full
2112
- * segment if we have to.
2113
- *
2114
- * @param {!shaka.media.AnySegmentReference} reference
2115
- * @param {string} type
2116
- * @param {string} mimeType
2117
- * @param {string} codecs
2118
- * @param {number|undefined} bandwidth
2119
- * @return {!Promise.<shaka.extern.Response>}
2120
- * @private
2121
- */
2122
- async fetchStartOfSegment_(reference, type, mimeType, codecs, bandwidth) {
2123
- const requestType = shaka.net.NetworkingEngine.RequestType.SEGMENT;
2124
-
2125
- // Create two requests:
2126
- // 1. A partial request meant to fetch the smallest part of the segment
2127
- // required to get the time stamp.
2128
- // 2. A full request meant as a fallback for when the server does not
2129
- // support partial requests.
2130
- const fullRequest = shaka.util.Networking.createSegmentRequest(
2131
- reference.getUris(),
2132
- reference.startByte,
2133
- reference.endByte,
2134
- this.config_.retryParameters);
2135
-
2136
- // We can only add partial CMCD data here because the stream
2137
- // and manifest objects are still being created
2138
- this.playerInterface_.modifySegmentRequest(
2139
- fullRequest,
2140
- {
2141
- type: type,
2142
- init: reference instanceof shaka.media.InitSegmentReference,
2143
- duration: reference.endTime - reference.startTime,
2144
- mimeType: mimeType,
2145
- codecs: codecs,
2146
- bandwidth: bandwidth,
2147
- },
2148
- );
2149
-
2150
- if (this.config_.hls.useFullSegmentsForStartTime) {
2151
- return this.makeNetworkRequest_(fullRequest, requestType);
2152
- }
2153
-
2154
- const partialRequest = shaka.util.Networking.createSegmentRequest(
2155
- reference.getUris(),
2156
- reference.startByte,
2157
- reference.startByte + shaka.hls.HlsParser.START_OF_SEGMENT_SIZE_ - 1,
2158
- this.config_.retryParameters);
2159
-
2160
- this.playerInterface_.modifySegmentRequest(
2161
- partialRequest,
2162
- {
2163
- type: type,
2164
- init: reference instanceof shaka.media.InitSegmentReference,
2165
- duration: reference.endTime - reference.startTime,
2166
- mimeType: mimeType,
2167
- codecs: codecs,
2168
- bandwidth: bandwidth,
2169
- },
2170
- );
2171
-
2172
- // TODO(vaage): The need to do fall back requests is not likely to be unique
2173
- // to here. It would be nice if the fallback(s) could be included into
2174
- // the same abortable operation as the original request.
2175
- //
2176
- // What would need to change with networking engine to support requests
2177
- // with fallback(s)?
2178
- try {
2179
- const response = await this.makeNetworkRequest_(
2180
- partialRequest, requestType);
2181
-
2182
- return response;
2183
- } catch (e) {
2184
- // If the networking operation was aborted, we don't want to treat it as
2185
- // a request failure. We surface the error so that the OPERATION_ABORTED
2186
- // error will be handled correctly.
2187
- if (e.code == shaka.util.Error.Code.OPERATION_ABORTED) {
2188
- throw e;
2189
- }
2190
-
2191
- // The partial request may fail for a number of reasons.
2192
- // Some servers do not support Range requests, and others do not support
2193
- // the OPTIONS request which must be made before any cross-origin Range
2194
- // request. Since this fallback is expensive, warn the app developer.
2195
- shaka.log.alwaysWarn('Unable to fetch the starting part of HLS ' +
2196
- 'segment! Falling back to a full segment request, ' +
2197
- 'which is expensive! Your server should ' +
2198
- 'support Range requests and CORS preflights.',
2199
- partialRequest.uris[0]);
2200
-
2201
- const response = await this.makeNetworkRequest_(fullRequest, requestType);
2202
-
2203
- return response;
2204
- }
2205
- }
2206
-
2207
- /**
2208
- * Gets the start time of a segment from the existing manifest (if possible)
2209
- * or by downloading it and parsing it otherwise.
2210
- *
2211
- * @param {string} verbatimMediaPlaylistUri
2212
- * @param {shaka.media.InitSegmentReference} initSegmentRef
2213
- * @param {string} mimeType
2214
- * @param {number} mediaSequenceNumber
2215
- * @param {boolean} isDiscontinuity
2216
- * @param {!shaka.hls.Segment} segment
2217
- * @param {!Map.<string, string>} variables
2218
- * @param {string} type
2219
- * @param {string} codecs
2220
- * @param {number|undefined} bandwidth
2221
- * @return {!Promise.<number>}
2222
- * @private
2223
- */
2224
- async getStartTime_(
2225
- verbatimMediaPlaylistUri, initSegmentRef, mimeType, mediaSequenceNumber,
2226
- isDiscontinuity, segment, variables, type, codecs, bandwidth) {
2227
- const segmentRef = this.createSegmentReference_(
2228
- initSegmentRef,
2229
- /* previousReference= */ null,
2230
- segment,
2231
- /* startTime= */ 0,
2232
- /* timestampOffset= */ 0,
2233
- variables,
2234
- /* absoluteMediaPlaylistUri= */ '',
2235
- type);
2236
- // If we are updating the manifest, we can usually skip fetching the segment
2237
- // by examining the references we already have. This won't be possible if
2238
- // there was some kind of lag or delay updating the manifest on the server,
2239
- // in which extreme case we would fall back to fetching a segment. This
2240
- // allows us to both avoid fetching segments when possible, and recover from
2241
- // certain server-side issues gracefully.
2242
- // Do not use cached start time for the segments with discontinuity tags.
2243
- if (this.manifest_ && !isDiscontinuity) {
2244
- const streamInfo =
2245
- this.uriToStreamInfosMap_.get(verbatimMediaPlaylistUri);
2246
- const startTime = streamInfo.mediaSequenceToStartTime.get(
2247
- mediaSequenceNumber);
2248
- if (startTime != undefined) {
2249
- // We found it! Avoid fetching and parsing the segment.
2250
- shaka.log.v1('Found segment start time in previous manifest',
2251
- startTime);
2252
- return startTime;
2253
- }
2254
-
2255
- shaka.log.debug(
2256
- 'Unable to find segment start time in previous manifest!');
2257
- }
2258
-
2259
- // TODO: Introduce a new tag to extend HLS and provide the first segment's
2260
- // start time. This will avoid the need for these fetches in content
2261
- // packaged with Shaka Packager. This web-friendly extension to HLS can
2262
- // then be proposed to Apple for inclusion in a future version of HLS.
2263
- // See https://github.com/google/shaka-packager/issues/294
2264
-
2265
- shaka.log.v1('Fetching segment to find start time');
2266
- mimeType = mimeType.toLowerCase();
2267
-
2268
- if (shaka.hls.HlsParser.RAW_FORMATS_.includes(mimeType)) {
2269
- // Raw formats contain no timestamps. Even if there is an ID3 tag with a
2270
- // timestamp, that's not going to be honored by MediaSource, which will
2271
- // use sequence mode for these segments. We don't yet support sequence
2272
- // mode, so we must reject these streams.
2273
- // TODO(#2337): Support sequence mode and align raw format timestamps to
2274
- // other streams.
2275
- shaka.log.alwaysWarn(
2276
- 'Raw formats are not yet supported. Skipping ' + mimeType);
2277
- throw new shaka.util.Error(
2278
- shaka.util.Error.Severity.RECOVERABLE,
2279
- shaka.util.Error.Category.MANIFEST,
2280
- shaka.util.Error.Code.HLS_INTERNAL_SKIP_STREAM);
2281
- }
2282
-
2283
- if (mimeType == 'video/webm') {
2284
- shaka.log.alwaysWarn('WebM in HLS is not yet supported. Skipping.');
2285
- throw new shaka.util.Error(
2286
- shaka.util.Error.Severity.RECOVERABLE,
2287
- shaka.util.Error.Category.MANIFEST,
2288
- shaka.util.Error.Code.HLS_INTERNAL_SKIP_STREAM);
2289
- }
2290
-
2291
- if (mimeType == 'video/mp4' || mimeType == 'audio/mp4') {
2292
- // We also need the init segment to get the correct timescale. But if the
2293
- // stream is self-initializing, use the same response for both.
2294
- const fetches = [this.fetchStartOfSegment_(
2295
- segmentRef,
2296
- type,
2297
- mimeType,
2298
- codecs,
2299
- bandwidth,
2300
- )];
2301
-
2302
- if (initSegmentRef) {
2303
- fetches.push(this.fetchStartOfSegment_(
2304
- initSegmentRef,
2305
- type,
2306
- mimeType,
2307
- codecs,
2308
- bandwidth,
2309
- ));
2310
2119
  }
2311
-
2312
- const responses = await Promise.all(fetches);
2313
-
2314
- // If the stream is self-initializing, use the main segment in-place of
2315
- // the init segment.
2316
- const segmentResponse = responses[0];
2317
- const initSegmentResponse = responses[1] || responses[0];
2318
-
2319
- return this.getStartTimeFromMp4Segment_(
2320
- verbatimMediaPlaylistUri, segmentResponse.uri,
2321
- segmentResponse.data, initSegmentResponse.data);
2322
- }
2323
-
2324
- if (mimeType == 'video/mp2t') {
2325
- const response = await this.fetchStartOfSegment_(
2326
- segmentRef,
2327
- type,
2328
- mimeType,
2329
- codecs,
2330
- bandwidth,
2331
- );
2332
- goog.asserts.assert(response.data, 'Should have a response body!');
2333
- return this.getStartTimeFromTsSegment_(
2334
- verbatimMediaPlaylistUri, response.uri, response.data);
2335
2120
  }
2336
2121
 
2337
- throw new shaka.util.Error(
2338
- shaka.util.Error.Severity.CRITICAL,
2339
- shaka.util.Error.Category.MANIFEST,
2340
- shaka.util.Error.Code.HLS_COULD_NOT_PARSE_SEGMENT_START_TIME,
2341
- verbatimMediaPlaylistUri);
2342
- }
2343
-
2344
- /**
2345
- * Parses an mp4 segment to get its start time.
2346
- *
2347
- * @param {string} playlistUri
2348
- * @param {string} segmentUri
2349
- * @param {BufferSource} mediaData
2350
- * @param {BufferSource} initData
2351
- * @return {number}
2352
- * @private
2353
- */
2354
- getStartTimeFromMp4Segment_(playlistUri, segmentUri, mediaData, initData) {
2355
- const Mp4Parser = shaka.util.Mp4Parser;
2356
-
2357
- let timescale = 0;
2358
- new Mp4Parser()
2359
- .box('moov', Mp4Parser.children)
2360
- .box('trak', Mp4Parser.children)
2361
- .box('mdia', Mp4Parser.children)
2362
- .fullBox('mdhd', (box) => {
2363
- goog.asserts.assert(
2364
- box.version == 0 || box.version == 1,
2365
- 'MDHD version can only be 0 or 1');
2366
-
2367
- const parsedMDHDBox = shaka.util.Mp4BoxParsers.parseMDHD(
2368
- box.reader, box.version);
2369
-
2370
- timescale = parsedMDHDBox.timescale;
2371
- box.parser.stop();
2372
- }).parse(initData, /* partialOkay= */ true);
2373
-
2374
- if (!timescale) {
2375
- shaka.log.error('Unable to find timescale in init segment!');
2376
- throw new shaka.util.Error(
2377
- shaka.util.Error.Severity.CRITICAL,
2378
- shaka.util.Error.Category.MANIFEST,
2379
- shaka.util.Error.Code.HLS_COULD_NOT_PARSE_SEGMENT_START_TIME,
2380
- playlistUri, segmentUri);
2381
- }
2382
-
2383
- let startTime = 0;
2384
- let parsedMedia = false;
2385
- new Mp4Parser()
2386
- .box('moof', Mp4Parser.children)
2387
- .box('traf', Mp4Parser.children)
2388
- .fullBox('tfdt', (box) => {
2389
- goog.asserts.assert(
2390
- box.version == 0 || box.version == 1,
2391
- 'TFDT version can only be 0 or 1');
2392
-
2393
- const parsedTFDTBox = shaka.util.Mp4BoxParsers.parseTFDT(
2394
- box.reader, box.version);
2395
- const baseTime = parsedTFDTBox.baseMediaDecodeTime;
2396
- startTime = baseTime / timescale;
2397
- parsedMedia = true;
2398
- box.parser.stop();
2399
- }).parse(mediaData, /* partialOkay= */ true);
2400
-
2401
- if (!parsedMedia) {
2402
- throw new shaka.util.Error(
2403
- shaka.util.Error.Severity.CRITICAL,
2404
- shaka.util.Error.Category.MANIFEST,
2405
- shaka.util.Error.Code.HLS_COULD_NOT_PARSE_SEGMENT_START_TIME,
2406
- playlistUri, segmentUri);
2407
- }
2408
- return startTime;
2409
- }
2410
-
2411
- /**
2412
- * Parses a TS segment to get its start time.
2413
- *
2414
- * @param {string} playlistUri
2415
- * @param {string} segmentUri
2416
- * @param {BufferSource} data
2417
- * @return {number}
2418
- * @private
2419
- */
2420
- getStartTimeFromTsSegment_(playlistUri, segmentUri, data) {
2421
- const reader = new shaka.util.DataViewReader(
2422
- data, shaka.util.DataViewReader.Endianness.BIG_ENDIAN);
2423
-
2424
- const fail = () => {
2425
- throw new shaka.util.Error(
2426
- shaka.util.Error.Severity.CRITICAL,
2427
- shaka.util.Error.Category.MANIFEST,
2428
- shaka.util.Error.Code.HLS_COULD_NOT_PARSE_SEGMENT_START_TIME,
2429
- playlistUri, segmentUri);
2430
- };
2431
-
2432
- let packetStart = 0;
2433
- let syncByte = 0;
2434
-
2435
- const skipPacket = () => {
2436
- // 188-byte packets are standard, so assume that.
2437
- reader.seek(packetStart + 188);
2438
- syncByte = reader.readUint8();
2439
- if (syncByte != 0x47) {
2440
- // We haven't found the sync byte, so try it as a 192-byte packet.
2441
- reader.seek(packetStart + 192);
2442
- syncByte = reader.readUint8();
2443
- }
2444
- if (syncByte != 0x47) {
2445
- // We still haven't found the sync byte, so try as a 204-byte packet.
2446
- reader.seek(packetStart + 204);
2447
- syncByte = reader.readUint8();
2448
- }
2449
- if (syncByte != 0x47) {
2450
- // We still haven't found the sync byte, so the packet was of a
2451
- // non-standard size.
2452
- fail();
2453
- }
2454
- // Put the sync byte back so we can read it in the next loop.
2455
- reader.rewind(1);
2456
- };
2457
-
2458
- // We will look a few packet-lengths forward to find the first sync byte.
2459
- // Note that we are using this method on what is already a subset of the
2460
- // file (the first |shaka.hls.HlsParser.START_OF_SEGMENT_SIZE_| bytes), so
2461
- // we can't look too far ahead to begin with.
2462
- let syncByteScanLength = Math.min(reader.getLength() - 188, 5 * 188);
2463
-
2464
- // TODO: refactor this while loop for better readability.
2465
- // eslint-disable-next-line no-constant-condition
2466
- while (true) {
2467
- // Format reference: https://bit.ly/TsPacket
2468
- packetStart = reader.getPosition();
2469
-
2470
- syncByte = reader.readUint8();
2471
- if (syncByte != 0x47) {
2472
- if (syncByteScanLength > 0) {
2473
- // This file could have started with a cut-off TS packet. Scan forward
2474
- // until we find a sync byte.
2475
- syncByteScanLength -= 1;
2122
+ // If some segments have sync times, but not all, extrapolate the sync
2123
+ // times of the ones with none.
2124
+ const someSyncTime = references.some((ref) => ref.syncTime != null);
2125
+ if (someSyncTime) {
2126
+ for (let i = 0; i < references.length; i++) {
2127
+ const reference = references[i];
2128
+ if (reference.syncTime != null) {
2129
+ // No need to extrapolate.
2476
2130
  continue;
2477
2131
  }
2478
- fail();
2479
- }
2480
- // If we've found a sync byte, stop scanning forward for future packets.
2481
- syncByteScanLength = 0;
2482
-
2483
- const flagsAndPacketId = reader.readUint16();
2484
- const packetId = flagsAndPacketId & 0x1fff;
2485
- if (packetId == 0x1fff) {
2486
- // A "null" TS packet. Skip this TS packet and try again.
2487
- skipPacket();
2488
- continue;
2489
- }
2490
-
2491
- const hasPesPacket = flagsAndPacketId & 0x4000;
2492
- if (!hasPesPacket) {
2493
- // Not a PES packet yet. Skip this TS packet and try again.
2494
- skipPacket();
2495
- continue;
2496
- }
2497
-
2498
- const flags = reader.readUint8();
2499
- const adaptationFieldControl = (flags & 0x30) >> 4;
2500
- if (adaptationFieldControl == 0 /* reserved */ ||
2501
- adaptationFieldControl == 2 /* adaptation field, no payload */) {
2502
- fail();
2503
- }
2504
-
2505
- if (adaptationFieldControl == 3) {
2506
- // Skip over adaptation field.
2507
- const length = reader.readUint8();
2508
- reader.skip(length);
2509
- }
2510
-
2511
- // Now we come to the PES header (hopefully).
2512
- // Format reference: https://bit.ly/TsPES
2513
- const startCode = reader.readUint32();
2514
- const startCodePrefix = startCode >> 8;
2515
- if (startCodePrefix != 1) {
2516
- // Not a PES packet yet. Skip this TS packet and try again.
2517
- skipPacket();
2518
- continue;
2519
- }
2520
-
2521
- // Skip the 16-bit PES length and the first 8 bits of the optional header.
2522
- reader.skip(3);
2523
- // The next 8 bits contain flags about DTS & PTS.
2524
- const ptsDtsIndicator = reader.readUint8() >> 6;
2525
- if (ptsDtsIndicator == 0 /* no timestamp */ ||
2526
- ptsDtsIndicator == 1 /* forbidden */) {
2527
- fail();
2528
- }
2529
-
2530
- const pesHeaderLengthRemaining = reader.readUint8();
2531
- if (pesHeaderLengthRemaining == 0) {
2532
- fail();
2132
+ // Find the nearest segment with syncTime, in either direction.
2133
+ // This looks forward and backward simultaneously, keeping track of what
2134
+ // to offset the syncTime it finds by as it goes.
2135
+ let forwardAdd = 0;
2136
+ let forwardI = i;
2137
+ /**
2138
+ * Look forwards one reference at a time, summing all durations as we
2139
+ * go, until we find a reference with a syncTime to use as a basis.
2140
+ * This DOES count the original reference, but DOESN'T count the first
2141
+ * reference with a syncTime (as we approach it from behind).
2142
+ * @return {?number}
2143
+ */
2144
+ const lookForward = () => {
2145
+ const other = references[forwardI];
2146
+ if (other) {
2147
+ if (other.syncTime != null) {
2148
+ return other.syncTime + forwardAdd;
2149
+ }
2150
+ forwardAdd -= other.endTime - other.startTime;
2151
+ forwardI += 1;
2152
+ }
2153
+ return null;
2154
+ };
2155
+ let backwardAdd = 0;
2156
+ let backwardI = i;
2157
+ /**
2158
+ * Look backwards one reference at a time, summing all durations as we
2159
+ * go, until we find a reference with a syncTime to use as a basis.
2160
+ * This DOESN'T count the original reference, but DOES count the first
2161
+ * reference with a syncTime (as we approach it from ahead).
2162
+ * @return {?number}
2163
+ */
2164
+ const lookBackward = () => {
2165
+ const other = references[backwardI];
2166
+ if (other) {
2167
+ if (other != reference) {
2168
+ backwardAdd += other.endTime - other.startTime;
2169
+ }
2170
+ if (other.syncTime != null) {
2171
+ return other.syncTime + backwardAdd;
2172
+ }
2173
+ backwardI -= 1;
2174
+ }
2175
+ return null;
2176
+ };
2177
+ while (reference.syncTime == null) {
2178
+ reference.syncTime = lookBackward();
2179
+ if (reference.syncTime == null) {
2180
+ reference.syncTime = lookForward();
2181
+ }
2182
+ }
2533
2183
  }
2184
+ }
2534
2185
 
2535
- if (ptsDtsIndicator == 2 /* PTS only */) {
2536
- goog.asserts.assert(pesHeaderLengthRemaining == 5, 'Bad PES header?');
2537
- } else if (ptsDtsIndicator == 3 /* PTS and DTS */) {
2538
- goog.asserts.assert(pesHeaderLengthRemaining == 10, 'Bad PES header?');
2186
+ // Split the sync times properly among partial segments.
2187
+ if (someSyncTime) {
2188
+ for (const reference of references) {
2189
+ let syncTime = reference.syncTime;
2190
+ for (const partial of reference.partialReferences) {
2191
+ partial.syncTime = syncTime;
2192
+ syncTime += partial.endTime - partial.startTime;
2193
+ }
2539
2194
  }
2540
-
2541
- const pts0 = reader.readUint8();
2542
- const pts1 = reader.readUint16();
2543
- const pts2 = reader.readUint16();
2544
- // Reconstruct 33-bit PTS from the 5-byte, padded structure.
2545
- const ptsHigh3 = (pts0 & 0x0e) >> 1;
2546
- const ptsLow30 = ((pts1 & 0xfffe) << 14) | ((pts2 & 0xfffe) >> 1);
2547
- // Reconstruct the PTS as a float. Avoid bitwise operations to combine
2548
- // because bitwise ops treat the values as 32-bit ints.
2549
- const pts = ptsHigh3 * (1 << 30) + ptsLow30;
2550
- return pts / shaka.hls.HlsParser.TS_TIMESCALE_;
2551
2195
  }
2196
+
2197
+ return references;
2552
2198
  }
2553
2199
 
2554
2200
  /**
@@ -2615,11 +2261,10 @@ shaka.hls.HlsParser = class {
2615
2261
 
2616
2262
  if (contentType == ContentType.TEXT) {
2617
2263
  // The extension map didn't work.
2618
- if (!codecs || codecs == 'vtt' || codecs == 'wvtt') {
2264
+ if (codecs == 'vtt' || codecs == 'wvtt') {
2619
2265
  // If codecs is 'vtt', it's WebVTT.
2620
- // If there was no codecs string, assume HLS text streams are WebVTT.
2621
2266
  return 'text/vtt';
2622
- } else {
2267
+ } else if (codecs && codecs !== '') {
2623
2268
  // Otherwise, assume MP4-embedded text, since text-based formats tend
2624
2269
  // not to have a codecs string at all.
2625
2270
  return 'application/mp4';
@@ -2644,6 +2289,11 @@ shaka.hls.HlsParser = class {
2644
2289
  const contentMimeType = response.headers['content-type'];
2645
2290
 
2646
2291
  if (!contentMimeType) {
2292
+ if (contentType == ContentType.TEXT) {
2293
+ // If there was no codecs string and no content-type, assume HLS text
2294
+ // streams are WebVTT.
2295
+ return 'text/vtt';
2296
+ }
2647
2297
  // If the HLS content is lacking in both MIME type metadata and
2648
2298
  // segment file extensions, we fall back to assuming it's MP4.
2649
2299
  const fallbackMimeType = map['mp4'];
@@ -2809,6 +2459,33 @@ shaka.hls.HlsParser = class {
2809
2459
  return op.promise;
2810
2460
  }
2811
2461
 
2462
+ /**
2463
+ * @param {!shaka.hls.Tag} drmTag
2464
+ * @param {string} mimeType
2465
+ * @return {?shaka.extern.DrmInfo}
2466
+ * @private
2467
+ */
2468
+ static fairplayDrmParser_(drmTag, mimeType) {
2469
+ if (mimeType == 'video/mp2t') {
2470
+ throw new shaka.util.Error(
2471
+ shaka.util.Error.Severity.CRITICAL,
2472
+ shaka.util.Error.Category.MANIFEST,
2473
+ shaka.util.Error.Code.HLS_MSE_ENCRYPTED_MP2T_NOT_SUPPORTED);
2474
+ }
2475
+
2476
+ /*
2477
+ * Even if we're not able to construct initData through the HLS tag, adding
2478
+ * a DRMInfo will allow DRM Engine to request a media key system access
2479
+ * with the correct keySystem and initDataType
2480
+ */
2481
+ const drmInfo = shaka.util.ManifestParserUtils.createDrmInfo(
2482
+ 'com.apple.fps', [
2483
+ {initDataType: 'sinf', initData: new Uint8Array(0)},
2484
+ ]);
2485
+
2486
+ return drmInfo;
2487
+ }
2488
+
2812
2489
  /**
2813
2490
  * @param {!shaka.hls.Tag} drmTag
2814
2491
  * @return {?shaka.extern.DrmInfo}
@@ -2871,7 +2548,10 @@ shaka.hls.HlsParser = class {
2871
2548
  0x9a, 0x04, 0xf0, 0x79, 0x98, 0x40, 0x42, 0x86,
2872
2549
  0xab, 0x92, 0xe6, 0x5b, 0xe0, 0x88, 0x5f, 0x95,
2873
2550
  ]);
2874
- const pssh = shaka.util.Pssh.createPssh(data, systemId);
2551
+ const keyIds = new Set();
2552
+ const psshVersion = 0;
2553
+ const pssh =
2554
+ shaka.util.Pssh.createPssh(data, systemId, keyIds, psshVersion);
2875
2555
  const drmInfo = shaka.util.ManifestParserUtils.createDrmInfo(
2876
2556
  'com.microsoft.playready', [
2877
2557
  {initDataType: 'cenc', initData: pssh},
@@ -2890,7 +2570,6 @@ shaka.hls.HlsParser = class {
2890
2570
  * minTimestamp: number,
2891
2571
  * maxTimestamp: number,
2892
2572
  * mediaSequenceToStartTime: !Map.<number, number>,
2893
- * discontinuityToMediaSequence: !Map.<number, number>,
2894
2573
  * canSkipSegments: boolean
2895
2574
  * }}
2896
2575
  *
@@ -2913,9 +2592,6 @@ shaka.hls.HlsParser = class {
2913
2592
  * The maximum timestamp found in the stream.
2914
2593
  * @property {!Map.<number, number>} mediaSequenceToStartTime
2915
2594
  * A map of media sequence numbers to media start times.
2916
- * @property {!Map.<number, number>} discontinuityToMediaSequence
2917
- * A map of discontinuity sequence numbers to the media sequence number of the
2918
- * segment starting with that discontinuity sequence number.
2919
2595
  * @property {boolean} canSkipSegments
2920
2596
  * True if the server supports delta playlist updates, and we can send a
2921
2597
  * request for a playlist that can skip older media segments.
@@ -2959,21 +2635,6 @@ shaka.hls.HlsParser.AUDIO_EXTENSIONS_TO_MIME_TYPES_ = {
2959
2635
  };
2960
2636
 
2961
2637
 
2962
- /**
2963
- * MIME types of raw formats.
2964
- * TODO(#2337): Support raw formats and share this list among parsers.
2965
- *
2966
- * @const {!Array.<string>}
2967
- * @private
2968
- */
2969
- shaka.hls.HlsParser.RAW_FORMATS_ = [
2970
- 'audio/aac',
2971
- 'audio/ac3',
2972
- 'audio/ec3',
2973
- 'audio/mpeg',
2974
- ];
2975
-
2976
-
2977
2638
  /**
2978
2639
  * @const {!Object.<string, string>}
2979
2640
  * @private
@@ -3031,7 +2692,7 @@ shaka.hls.HlsParser.EXTENSION_MAP_BY_CONTENT_TYPE_ = {
3031
2692
 
3032
2693
 
3033
2694
  /**
3034
- * @typedef {function(!shaka.hls.Tag):?shaka.extern.DrmInfo}
2695
+ * @typedef {function(!shaka.hls.Tag, string):?shaka.extern.DrmInfo}
3035
2696
  * @private
3036
2697
  */
3037
2698
  shaka.hls.HlsParser.DrmParser_;
@@ -3042,10 +2703,8 @@ shaka.hls.HlsParser.DrmParser_;
3042
2703
  * @private
3043
2704
  */
3044
2705
  shaka.hls.HlsParser.KEYFORMATS_TO_DRM_PARSERS_ = {
3045
- /* TODO: https://github.com/google/shaka-player/issues/382
3046
2706
  'com.apple.streamingkeydelivery':
3047
2707
  shaka.hls.HlsParser.fairplayDrmParser_,
3048
- */
3049
2708
  'urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed':
3050
2709
  shaka.hls.HlsParser.widevineDrmParser_,
3051
2710
  'com.microsoft.playready':
@@ -3063,28 +2722,13 @@ shaka.hls.HlsParser.PresentationType_ = {
3063
2722
  LIVE: 'LIVE',
3064
2723
  };
3065
2724
 
3066
-
3067
- /**
3068
- * @const {number}
3069
- * @private
3070
- */
3071
- shaka.hls.HlsParser.TS_TIMESCALE_ = 90000;
3072
-
3073
-
3074
- /**
3075
- * The amount of data from the start of a segment we will try to fetch when we
3076
- * need to know the segment start time. This allows us to avoid fetching the
3077
- * entire segment in many cases.
3078
- *
3079
- * @const {number}
3080
- * @private
3081
- */
3082
- shaka.hls.HlsParser.START_OF_SEGMENT_SIZE_ = 2048;
3083
-
3084
-
3085
- shaka.media.ManifestParser.registerParserByExtension(
3086
- 'm3u8', () => new shaka.hls.HlsParser());
3087
- shaka.media.ManifestParser.registerParserByMime(
3088
- 'application/x-mpegurl', () => new shaka.hls.HlsParser());
3089
- shaka.media.ManifestParser.registerParserByMime(
3090
- 'application/vnd.apple.mpegurl', () => new shaka.hls.HlsParser());
2725
+ if (!shaka.util.Platform.isTizen3() &&
2726
+ !shaka.util.Platform.isTizen2() &&
2727
+ !shaka.util.Platform.isWebOS3()) {
2728
+ shaka.media.ManifestParser.registerParserByExtension(
2729
+ 'm3u8', () => new shaka.hls.HlsParser());
2730
+ shaka.media.ManifestParser.registerParserByMime(
2731
+ 'application/x-mpegurl', () => new shaka.hls.HlsParser());
2732
+ shaka.media.ManifestParser.registerParserByMime(
2733
+ 'application/vnd.apple.mpegurl', () => new shaka.hls.HlsParser());
2734
+ }