shaka-player 3.3.5 → 4.0.2

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 (184) hide show
  1. package/.github/config.yml +1 -0
  2. package/.github/workflows/demo-version-index.yaml +8 -0
  3. package/.github/workflows/release-please.yaml +18 -2
  4. package/.github/workflows/sync-labels.yaml +33 -0
  5. package/.github/workflows/update-issues.yaml +29 -0
  6. package/.github/workflows/validate-pr-title.yaml +35 -0
  7. package/AUTHORS +1 -0
  8. package/CHANGELOG.md +215 -41
  9. package/CONTRIBUTORS +1 -0
  10. package/README.md +14 -11
  11. package/app-engine/demo-version-index/README.md +3 -3
  12. package/app-engine/demo-version-index/app.yaml +27 -1
  13. package/app-engine/demo-version-index/generate.py +164 -0
  14. package/app-engine/demo-version-index/main.py +4 -155
  15. package/app-engine/demo-version-index/requirements.txt +0 -1
  16. package/build/checkversion.py +1 -1
  17. package/build/compiler.py +1 -1
  18. package/build/misspellings.txt +1 -0
  19. package/build/shaka-lab.yaml +0 -2
  20. package/build/types/polyfill +0 -3
  21. package/demo/common/asset.js +17 -1
  22. package/demo/common/assets.js +29 -0
  23. package/demo/common/message_ids.js +6 -3
  24. package/demo/config.js +10 -11
  25. package/demo/custom.js +39 -0
  26. package/demo/demo.less +0 -1
  27. package/demo/demo_utils.js +0 -1
  28. package/demo/locales/en.json +6 -3
  29. package/demo/locales/source.json +22 -10
  30. package/demo/main.js +9 -6
  31. package/demo/search.js +2 -1
  32. package/dist/controls.css +3 -3
  33. package/dist/controls.css.map +1 -1
  34. package/dist/demo.compiled.debug.js +71 -64
  35. package/dist/demo.compiled.debug.map +3 -3
  36. package/dist/demo.compiled.js +71 -64
  37. package/dist/demo.compiled.map +3 -3
  38. package/dist/demo.css +1 -1
  39. package/dist/demo.css.map +1 -1
  40. package/dist/deps.js +17 -20
  41. package/dist/locales.js +33 -31
  42. package/dist/receiver.compiled.debug.js +32 -28
  43. package/dist/receiver.compiled.debug.map +3 -3
  44. package/dist/receiver.compiled.js +32 -28
  45. package/dist/receiver.compiled.map +3 -3
  46. package/dist/shaka-player.compiled.d.ts +311 -314
  47. package/dist/shaka-player.compiled.debug.d.ts +311 -314
  48. package/dist/shaka-player.compiled.debug.externs.js +126 -126
  49. package/dist/shaka-player.compiled.debug.js +430 -484
  50. package/dist/shaka-player.compiled.debug.map +4 -4
  51. package/dist/shaka-player.compiled.externs.js +126 -126
  52. package/dist/shaka-player.compiled.js +829 -845
  53. package/dist/shaka-player.compiled.map +4 -4
  54. package/dist/shaka-player.ui.d.ts +357 -358
  55. package/dist/shaka-player.ui.debug.d.ts +357 -358
  56. package/dist/shaka-player.ui.debug.externs.js +134 -126
  57. package/dist/shaka-player.ui.debug.js +489 -541
  58. package/dist/shaka-player.ui.debug.map +4 -4
  59. package/dist/shaka-player.ui.externs.js +134 -126
  60. package/dist/shaka-player.ui.js +988 -1000
  61. package/dist/shaka-player.ui.map +4 -4
  62. package/docs/tutorials/config.md +0 -2
  63. package/docs/tutorials/drm-config.md +5 -0
  64. package/docs/tutorials/fairplay.md +7 -22
  65. package/docs/tutorials/faq.md +0 -11
  66. package/docs/tutorials/index.json +2 -8
  67. package/docs/tutorials/plugins.md +1 -3
  68. package/docs/tutorials/upgrade.md +85 -11
  69. package/externs/polyfill.js +15 -0
  70. package/externs/shaka/manifest.js +5 -1
  71. package/externs/shaka/net.js +12 -0
  72. package/externs/shaka/offline.js +5 -1
  73. package/externs/shaka/player.js +39 -21
  74. package/externs/shaka/text.js +12 -12
  75. package/externs/sourcebuffer.js +15 -0
  76. package/lib/abr/simple_abr_manager.js +2 -1
  77. package/lib/ads/client_side_ad.js +13 -2
  78. package/lib/ads/client_side_ad_manager.js +22 -4
  79. package/lib/cast/cast_utils.js +0 -1
  80. package/lib/dash/content_protection.js +85 -5
  81. package/lib/dash/dash_parser.js +1 -0
  82. package/lib/hls/hls_parser.js +390 -747
  83. package/lib/hls/manifest_text_parser.js +2 -3
  84. package/lib/media/drm_engine.js +105 -37
  85. package/lib/media/gap_jumping_controller.js +10 -45
  86. package/lib/media/media_source_engine.js +150 -30
  87. package/lib/media/playhead.js +2 -5
  88. package/lib/media/presentation_timeline.js +1 -2
  89. package/lib/media/segment_index.js +13 -46
  90. package/lib/media/segment_reference.js +47 -1
  91. package/lib/media/streaming_engine.js +28 -11
  92. package/lib/media/time_ranges_utils.js +4 -9
  93. package/lib/net/networking_engine.js +3 -0
  94. package/lib/offline/indexeddb/v1_storage_cell.js +1 -0
  95. package/lib/offline/indexeddb/v2_storage_cell.js +1 -0
  96. package/lib/offline/manifest_converter.js +1 -0
  97. package/lib/offline/storage.js +8 -41
  98. package/lib/player.js +17 -201
  99. package/lib/polyfill/media_capabilities.js +12 -6
  100. package/lib/polyfill/mediasource.js +0 -28
  101. package/lib/polyfill/patchedmediakeys_nop.js +2 -0
  102. package/lib/polyfill/patchedmediakeys_webkit.js +2 -0
  103. package/lib/text/cue.js +0 -6
  104. package/lib/text/lrc_text_parser.js +8 -0
  105. package/lib/text/mp4_ttml_parser.js +8 -0
  106. package/lib/text/mp4_vtt_parser.js +8 -0
  107. package/lib/text/sbv_text_parser.js +8 -0
  108. package/lib/text/simple_text_displayer.js +1 -7
  109. package/lib/text/srt_text_parser.js +8 -0
  110. package/lib/text/ssa_text_parser.js +8 -0
  111. package/lib/text/text_engine.js +19 -3
  112. package/lib/text/ttml_text_parser.js +8 -0
  113. package/lib/text/ui_text_displayer.js +1 -7
  114. package/lib/text/vtt_text_parser.js +27 -5
  115. package/lib/text/web_vtt_generator.js +1 -7
  116. package/lib/util/error.js +9 -13
  117. package/lib/util/fairplay_utils.js +109 -0
  118. package/lib/util/functional.js +0 -42
  119. package/lib/util/mime_utils.js +16 -5
  120. package/lib/util/platform.js +45 -0
  121. package/lib/util/player_configuration.js +17 -6
  122. package/lib/util/pssh.js +23 -4
  123. package/lib/util/stream_utils.js +92 -22
  124. package/lib/util/uint8array_utils.js +0 -16
  125. package/lib/util/xml_utils.js +1 -1
  126. package/package.json +5 -2
  127. package/roadmap.md +19 -19
  128. package/shaka-player.uncompiled.js +0 -3
  129. package/test/dash/dash_parser_content_protection_unit.js +44 -0
  130. package/test/dash/dash_parser_segment_template_unit.js +3 -4
  131. package/test/demo/demo_unit.js +2 -1
  132. package/test/hls/hls_live_unit.js +155 -230
  133. package/test/hls/hls_parser_unit.js +515 -432
  134. package/test/media/drm_engine_unit.js +86 -120
  135. package/test/media/media_source_engine_integration.js +25 -3
  136. package/test/media/media_source_engine_unit.js +3 -2
  137. package/test/media/playhead_unit.js +27 -191
  138. package/test/media/segment_index_unit.js +15 -25
  139. package/test/media/streaming_engine_integration.js +2 -39
  140. package/test/media/streaming_engine_unit.js +3 -2
  141. package/test/media/time_ranges_utils_unit.js +0 -5
  142. package/test/offline/manifest_convert_unit.js +34 -4
  143. package/test/offline/offline_manifest_parser_unit.js +1 -0
  144. package/test/offline/storage_integration.js +5 -4
  145. package/test/player_external.js +0 -6
  146. package/test/player_integration.js +16 -0
  147. package/test/player_unit.js +26 -0
  148. package/test/test/util/fake_media_source_engine.js +11 -4
  149. package/test/test/util/manifest_generator.js +3 -1
  150. package/test/test/util/manifest_parser_util.js +4 -1
  151. package/test/test/util/offline_utils.js +1 -0
  152. package/test/test/util/streaming_engine_util.js +1 -0
  153. package/test/test/util/test_scheme.js +24 -1
  154. package/test/test/util/util.js +4 -4
  155. package/test/text/cue_integration.js +2 -2
  156. package/test/text/lrc_text_parser_unit.js +6 -7
  157. package/test/text/mp4_ttml_parser_unit.js +6 -4
  158. package/test/text/mp4_vtt_parser_unit.js +6 -5
  159. package/test/text/sbv_text_parser_unit.js +5 -6
  160. package/test/text/srt_text_parser_unit.js +5 -6
  161. package/test/text/ssa_text_parser_unit.js +17 -29
  162. package/test/text/text_engine_unit.js +45 -5
  163. package/test/text/ttml_text_parser_unit.js +77 -77
  164. package/test/text/vtt_text_parser_unit.js +116 -59
  165. package/test/ui/ui_integration.js +0 -16
  166. package/test/ui/ui_unit.js +27 -0
  167. package/test/util/cmcd_manager_unit.js +3 -0
  168. package/test/util/stream_utils_unit.js +23 -7
  169. package/ui/controls.js +65 -40
  170. package/ui/fullscreen_button.js +26 -6
  171. package/ui/less/containers.less +4 -4
  172. package/ui/less/general.less +1 -1
  173. package/ui/less/range_elements.less +1 -0
  174. package/ui/locales/en.json +1 -0
  175. package/ui/locales/source.json +5 -0
  176. package/ui/resolution_selection.js +31 -33
  177. package/ui/ui.js +0 -17
  178. package/.github/pull_request_template.md +0 -10
  179. package/docs/tutorials/upgrade-v2-5.md +0 -441
  180. package/docs/tutorials/upgrade-v3.md +0 -38
  181. package/lib/polyfill/mathround.js +0 -49
  182. package/lib/polyfill/patchedmediakeys_apple.js +0 -744
  183. package/lib/polyfill/patchedmediakeys_ms.js +0 -698
  184. 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,7 +1822,6 @@ 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
1827
  * @param {string} type
@@ -1745,7 +1830,7 @@ shaka.hls.HlsParser = class {
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,6 +1841,35 @@ 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
+
1759
1873
  if (!extinfTag) {
1760
1874
  if (hlsSegment.partialSegments.length == 0) {
1761
1875
  // EXTINF tag must be available if the segment has no partial segments.
@@ -1778,9 +1892,20 @@ shaka.hls.HlsParser = class {
1778
1892
  const pPreviousReference = i == 0 ?
1779
1893
  previousReference : partialSegmentRefs[partialSegmentRefs.length - 1];
1780
1894
  const pStartTime = (i == 0) ? startTime : pPreviousReference.endTime;
1781
- const pDuration = Number(item.getAttributeValue('DURATION'));
1782
- // A preload hinted partial segment doesn't have duration information,
1783
- // so its startTime and endTime are the same.
1895
+
1896
+ // If DURATION is missing from this partial segment, use the target
1897
+ // partial duration from the top of the playlist, which is a required
1898
+ // attribute for content with partial segments.
1899
+ const pDuration = Number(item.getAttributeValue('DURATION')) ||
1900
+ this.partialTargetDuration_;
1901
+
1902
+ // If for some reason we have neither an explicit duration, nor a target
1903
+ // partial duration, we should SKIP this partial segment to avoid
1904
+ // duplicating content in the presentation timeline.
1905
+ if (!pDuration) {
1906
+ continue;
1907
+ }
1908
+
1784
1909
  const pEndTime = pStartTime + pDuration;
1785
1910
 
1786
1911
  let pStartByte = 0;
@@ -1808,7 +1933,7 @@ shaka.hls.HlsParser = class {
1808
1933
  pStartByte,
1809
1934
  pEndByte,
1810
1935
  initSegmentReference,
1811
- timestampOffset,
1936
+ /* timestampOffset= */ 0, // This value is ignored in sequence mode.
1812
1937
  /* appendWindowStart= */ 0,
1813
1938
  /* appendWindowEnd= */ Infinity);
1814
1939
  partialSegmentRefs.push(partial);
@@ -1869,12 +1994,14 @@ shaka.hls.HlsParser = class {
1869
1994
  startByte,
1870
1995
  endByte,
1871
1996
  initSegmentReference,
1872
- timestampOffset,
1997
+ /* timestampOffset= */ 0, // This value is ignored in sequence mode.
1873
1998
  /* appendWindowStart= */ 0,
1874
1999
  /* appendWindowEnd= */ Infinity,
1875
2000
  partialSegmentRefs,
1876
2001
  tilesLayout,
1877
2002
  tileDuration,
2003
+ syncTime,
2004
+ status,
1878
2005
  );
1879
2006
  }
1880
2007
 
@@ -1928,15 +2055,12 @@ shaka.hls.HlsParser = class {
1928
2055
  * @param {string} mimeType
1929
2056
  * @param {!Map.<number, number>} mediaSequenceToStartTime
1930
2057
  * @param {!Map.<string, string>} variables
1931
- * @param {!Map.<number, number>} discontinuityToMediaSequence
1932
2058
  * @param {string} codecs
1933
- * @param {(number|undefined)} bandwidth
1934
- * @return {!Promise<!Array.<!shaka.media.SegmentReference>>}
2059
+ * @return {!Array.<!shaka.media.SegmentReference>}
1935
2060
  * @private
1936
2061
  */
1937
- async createSegments_(verbatimMediaPlaylistUri, playlist, type, mimeType,
1938
- mediaSequenceToStartTime, variables, discontinuityToMediaSequence,
1939
- codecs, bandwidth) {
2062
+ createSegments_(verbatimMediaPlaylistUri, playlist, type, mimeType,
2063
+ mediaSequenceToStartTime, variables, codecs) {
1940
2064
  /** @type {Array.<!shaka.hls.Segment>} */
1941
2065
  const hlsSegments = playlist.segments;
1942
2066
  goog.asserts.assert(hlsSegments.length, 'Playlist should have segments!');
@@ -1953,69 +2077,26 @@ shaka.hls.HlsParser = class {
1953
2077
  const skippedSegments =
1954
2078
  skipTag ? Number(skipTag.getAttributeValue('SKIPPED-SEGMENTS')) : 0;
1955
2079
  let position = mediaSequenceNumber + skippedSegments;
1956
- let firstStartTime;
2080
+ let firstStartTime = 0;
2081
+
1957
2082
  // For live stream, use the cached value in the mediaSequenceToStartTime
1958
2083
  // map if available.
1959
- // Since createSegments_() is asynchronous and we are updating the streams
1960
- // in parallel, the global playlistStartTime_ may get updated by other
1961
- // playlist updates rather than the current one.
1962
-
1963
2084
  if (this.isLive_() && mediaSequenceToStartTime.has(position)) {
1964
2085
  firstStartTime = mediaSequenceToStartTime.get(position);
1965
- } else {
1966
- if (this.playlistStartTime_ == null) {
1967
- // For VOD and EVENT playlists, all variants must start at the same
1968
- // time, so we can fetch the start time once and reuse for the others.
1969
- // This is not guaranteed when updating a LIVE stream. We assume the
1970
- // first segment in each live playlist is no more than one segment out
1971
- // of sync with the other playlists, so we can fetch the start time for
1972
- // once.
1973
- initSegmentRef = this.getInitSegmentReference_(
1974
- playlist.absoluteUri, hlsSegments[0].tags, variables);
1975
- goog.asserts.assert(
1976
- type != shaka.util.ManifestParserUtils.ContentType.TEXT &&
1977
- type != shaka.util.ManifestParserUtils.ContentType.IMAGE,
1978
- 'Should only get start time from audio or video streams');
1979
- this.playlistStartTime_ = await this.getStartTime_(
1980
- verbatimMediaPlaylistUri, initSegmentRef, mimeType,
1981
- position, /* isDiscontinuity= */ false,
1982
- hlsSegments[0], variables, type, codecs, bandwidth);
1983
- }
1984
- firstStartTime = this.playlistStartTime_;
1985
2086
  }
1986
2087
 
1987
2088
  const firstSegmentUri = hlsSegments[0].absoluteUri;
1988
2089
  shaka.log.debug('First segment', firstSegmentUri.split('/').pop(),
1989
2090
  'starts at', firstStartTime);
1990
2091
 
1991
- let discontintuitySequenceNum = shaka.hls.Utils.getFirstTagWithNameAsNumber(
1992
- playlist.tags, 'EXT-X-DISCONTINUITY-SEQUENCE');
1993
- if (this.lowLatencyMode_) {
1994
- if (!discontinuityToMediaSequence.has(discontintuitySequenceNum)) {
1995
- discontinuityToMediaSequence.set(discontintuitySequenceNum, position);
1996
- }
1997
- if (skippedSegments) {
1998
- // With delta updates, the DISCONTINUITY may be skipped. Check if
1999
- // the discontintuity Sequence Number based on the media sequence
2000
- // number.
2001
- const disconMap = discontinuityToMediaSequence;
2002
- while (disconMap.has(discontintuitySequenceNum + 1) &&
2003
- disconMap.get(discontintuitySequenceNum + 1) < position) {
2004
- discontintuitySequenceNum++;
2005
- }
2006
- }
2007
- }
2008
- let timestampOffset =
2009
- this.discontinuityToTso_.get(discontintuitySequenceNum) || 0;
2010
-
2011
2092
  /** @type {!Array.<!shaka.media.SegmentReference>} */
2012
2093
  const references = [];
2013
2094
 
2014
2095
  for (let i = 0; i < hlsSegments.length; i++) {
2015
2096
  const item = hlsSegments[i];
2016
2097
  const previousReference = references[references.length - 1];
2017
- const startTime = (i == 0) ? firstStartTime :
2018
- previousReference.endTime;
2098
+ const startTime =
2099
+ (i == 0) ? firstStartTime : previousReference.endTime;
2019
2100
  position = mediaSequenceNumber + skippedSegments + i;
2020
2101
 
2021
2102
  mediaSequenceToStartTime.set(position, startTime);
@@ -2023,19 +2104,6 @@ shaka.hls.HlsParser = class {
2023
2104
  initSegmentRef = this.getInitSegmentReference_(playlist.absoluteUri,
2024
2105
  item.tags, variables);
2025
2106
 
2026
- const discontintuityTag = shaka.hls.Utils.getFirstTagWithName(item.tags,
2027
- 'EXT-X-DISCONTINUITY');
2028
- if (discontintuityTag) {
2029
- discontintuitySequenceNum++;
2030
- discontinuityToMediaSequence.set(discontintuitySequenceNum, position);
2031
-
2032
- // eslint-disable-next-line no-await-in-loop
2033
- timestampOffset = await this.getTimestampOffset_(
2034
- discontintuitySequenceNum, verbatimMediaPlaylistUri, initSegmentRef,
2035
- mimeType, position, item, variables, startTime, type, codecs,
2036
- bandwidth);
2037
- }
2038
-
2039
2107
  // If the stream is low latency and the user has not configured the
2040
2108
  // lowLatencyMode, but if it has been configured to activate the
2041
2109
  // lowLatencyMode if a stream of this type is detected, we automatically
@@ -2048,519 +2116,96 @@ shaka.hls.HlsParser = class {
2048
2116
  }
2049
2117
  }
2050
2118
 
2051
- const extinfTag =
2052
- shaka.hls.Utils.getFirstTagWithName(item.tags, 'EXTINF');
2053
- if (this.lowLatencyMode_ || extinfTag) {
2054
- const reference = this.createSegmentReference_(
2055
- initSegmentRef,
2056
- previousReference,
2057
- item,
2058
- startTime,
2059
- timestampOffset,
2060
- variables,
2061
- playlist.absoluteUri,
2062
- type);
2063
-
2064
- if (reference) {
2065
- references.push(reference);
2066
- }
2067
- } else if (!this.lowLatencyMode_) {
2068
- // If a segment has no extinfTag, it must contain partial segments.
2069
- shaka.log.alwaysWarn('Low-latency HLS live stream detected, but ' +
2070
- 'low-latency streaming mode is not enabled in Shaka Player. ' +
2071
- 'Set streaming.lowLatencyMode configuration to true, and see ' +
2072
- 'https://bit.ly/3clctcj for details.');
2073
- }
2074
- }
2075
-
2076
- return references;
2077
- }
2078
-
2079
- /**
2080
- * Gets the start time of the first segment of the playlist from existing
2081
- * value (if possible) or by downloading it and parsing it otherwise.
2082
- *
2083
- * @param {number} discontintuitySequenceNum
2084
- * @param {string} verbatimMediaPlaylistUri
2085
- * @param {shaka.media.InitSegmentReference} initSegmentRef
2086
- * @param {string} mimeType
2087
- * @param {number} mediaSequenceNumber
2088
- * @param {!shaka.hls.Segment} segment
2089
- * @param {!Map.<string, string>} variables
2090
- * @param {number} startTime
2091
- * @param {string} type
2092
- * @param {string} codecs
2093
- * @param {number|undefined} bandwidth
2094
- * @return {!Promise.<number>}
2095
- * @throws {shaka.util.Error}
2096
- * @private
2097
- */
2098
- async getTimestampOffset_(discontintuitySequenceNum,
2099
- verbatimMediaPlaylistUri, initSegmentRef,
2100
- mimeType, mediaSequenceNumber, segment, variables, startTime, type,
2101
- codecs, bandwidth) {
2102
- let timestampOffset = 0;
2103
- if (this.discontinuityToTso_.has(discontintuitySequenceNum)) {
2104
- timestampOffset =
2105
- this.discontinuityToTso_.get(discontintuitySequenceNum);
2106
- } else {
2107
- const mediaStartTime = await this.getStartTime_(
2108
- verbatimMediaPlaylistUri, initSegmentRef, mimeType,
2109
- mediaSequenceNumber, /* isDiscontinuity= */ true, segment,
2110
- variables, type, codecs, bandwidth);
2111
- timestampOffset = startTime - mediaStartTime;
2112
- shaka.log.v1('Segment timestampOffset =', timestampOffset);
2113
- this.discontinuityToTso_.set(
2114
- discontintuitySequenceNum, timestampOffset);
2115
- }
2116
- return timestampOffset;
2117
- }
2118
-
2119
- /**
2120
- * Try to fetch the starting part of a segment, and fall back to a full
2121
- * segment if we have to.
2122
- *
2123
- * @param {!shaka.media.AnySegmentReference} reference
2124
- * @param {string} type
2125
- * @param {string} mimeType
2126
- * @param {string} codecs
2127
- * @param {number|undefined} bandwidth
2128
- * @return {!Promise.<shaka.extern.Response>}
2129
- * @private
2130
- */
2131
- async fetchStartOfSegment_(reference, type, mimeType, codecs, bandwidth) {
2132
- const requestType = shaka.net.NetworkingEngine.RequestType.SEGMENT;
2133
-
2134
- // Create two requests:
2135
- // 1. A partial request meant to fetch the smallest part of the segment
2136
- // required to get the time stamp.
2137
- // 2. A full request meant as a fallback for when the server does not
2138
- // support partial requests.
2139
- const fullRequest = shaka.util.Networking.createSegmentRequest(
2140
- reference.getUris(),
2141
- reference.startByte,
2142
- reference.endByte,
2143
- this.config_.retryParameters);
2144
-
2145
- // We can only add partial CMCD data here because the stream
2146
- // and manifest objects are still being created
2147
- this.playerInterface_.modifySegmentRequest(
2148
- fullRequest,
2149
- {
2150
- type: type,
2151
- init: reference instanceof shaka.media.InitSegmentReference,
2152
- duration: reference.endTime - reference.startTime,
2153
- mimeType: mimeType,
2154
- codecs: codecs,
2155
- bandwidth: bandwidth,
2156
- },
2157
- );
2158
-
2159
- if (this.config_.hls.useFullSegmentsForStartTime) {
2160
- return this.makeNetworkRequest_(fullRequest, requestType);
2161
- }
2162
-
2163
- const partialRequest = shaka.util.Networking.createSegmentRequest(
2164
- reference.getUris(),
2165
- reference.startByte,
2166
- reference.startByte + shaka.hls.HlsParser.START_OF_SEGMENT_SIZE_ - 1,
2167
- this.config_.retryParameters);
2168
-
2169
- this.playerInterface_.modifySegmentRequest(
2170
- partialRequest,
2171
- {
2172
- type: type,
2173
- init: reference instanceof shaka.media.InitSegmentReference,
2174
- duration: reference.endTime - reference.startTime,
2175
- mimeType: mimeType,
2176
- codecs: codecs,
2177
- bandwidth: bandwidth,
2178
- },
2179
- );
2180
-
2181
- // TODO(vaage): The need to do fall back requests is not likely to be unique
2182
- // to here. It would be nice if the fallback(s) could be included into
2183
- // the same abortable operation as the original request.
2184
- //
2185
- // What would need to change with networking engine to support requests
2186
- // with fallback(s)?
2187
- try {
2188
- const response = await this.makeNetworkRequest_(
2189
- partialRequest, requestType);
2190
-
2191
- return response;
2192
- } catch (e) {
2193
- // If the networking operation was aborted, we don't want to treat it as
2194
- // a request failure. We surface the error so that the OPERATION_ABORTED
2195
- // error will be handled correctly.
2196
- if (e.code == shaka.util.Error.Code.OPERATION_ABORTED) {
2197
- throw e;
2198
- }
2199
-
2200
- // The partial request may fail for a number of reasons.
2201
- // Some servers do not support Range requests, and others do not support
2202
- // the OPTIONS request which must be made before any cross-origin Range
2203
- // request. Since this fallback is expensive, warn the app developer.
2204
- shaka.log.alwaysWarn('Unable to fetch the starting part of HLS ' +
2205
- 'segment! Falling back to a full segment request, ' +
2206
- 'which is expensive! Your server should ' +
2207
- 'support Range requests and CORS preflights.',
2208
- partialRequest.uris[0]);
2209
-
2210
- const response = await this.makeNetworkRequest_(fullRequest, requestType);
2211
-
2212
- return response;
2213
- }
2214
- }
2215
-
2216
- /**
2217
- * Gets the start time of a segment from the existing manifest (if possible)
2218
- * or by downloading it and parsing it otherwise.
2219
- *
2220
- * @param {string} verbatimMediaPlaylistUri
2221
- * @param {shaka.media.InitSegmentReference} initSegmentRef
2222
- * @param {string} mimeType
2223
- * @param {number} mediaSequenceNumber
2224
- * @param {boolean} isDiscontinuity
2225
- * @param {!shaka.hls.Segment} segment
2226
- * @param {!Map.<string, string>} variables
2227
- * @param {string} type
2228
- * @param {string} codecs
2229
- * @param {number|undefined} bandwidth
2230
- * @return {!Promise.<number>}
2231
- * @private
2232
- */
2233
- async getStartTime_(
2234
- verbatimMediaPlaylistUri, initSegmentRef, mimeType, mediaSequenceNumber,
2235
- isDiscontinuity, segment, variables, type, codecs, bandwidth) {
2236
- const segmentRef = this.createSegmentReference_(
2237
- initSegmentRef,
2238
- /* previousReference= */ null,
2239
- segment,
2240
- /* startTime= */ 0,
2241
- /* timestampOffset= */ 0,
2242
- variables,
2243
- /* absoluteMediaPlaylistUri= */ '',
2244
- type);
2245
- goog.asserts.assert(
2246
- segmentRef != null, 'Segment reference should not be null!');
2247
-
2248
- // If we are updating the manifest, we can usually skip fetching the segment
2249
- // by examining the references we already have. This won't be possible if
2250
- // there was some kind of lag or delay updating the manifest on the server,
2251
- // in which extreme case we would fall back to fetching a segment. This
2252
- // allows us to both avoid fetching segments when possible, and recover from
2253
- // certain server-side issues gracefully.
2254
- // Do not use cached start time for the segments with discontinuity tags.
2255
- if (this.manifest_ && !isDiscontinuity) {
2256
- const streamInfo =
2257
- this.uriToStreamInfosMap_.get(verbatimMediaPlaylistUri);
2258
- const startTime = streamInfo.mediaSequenceToStartTime.get(
2259
- mediaSequenceNumber);
2260
- if (startTime != undefined) {
2261
- // We found it! Avoid fetching and parsing the segment.
2262
- shaka.log.v1('Found segment start time in previous manifest',
2263
- startTime);
2264
- return startTime;
2265
- }
2266
-
2267
- shaka.log.debug(
2268
- 'Unable to find segment start time in previous manifest!');
2269
- }
2270
-
2271
- // TODO: Introduce a new tag to extend HLS and provide the first segment's
2272
- // start time. This will avoid the need for these fetches in content
2273
- // packaged with Shaka Packager. This web-friendly extension to HLS can
2274
- // then be proposed to Apple for inclusion in a future version of HLS.
2275
- // See https://github.com/google/shaka-packager/issues/294
2276
-
2277
- shaka.log.v1('Fetching segment to find start time');
2278
- mimeType = mimeType.toLowerCase();
2279
-
2280
- if (shaka.hls.HlsParser.RAW_FORMATS_.includes(mimeType)) {
2281
- // Raw formats contain no timestamps. Even if there is an ID3 tag with a
2282
- // timestamp, that's not going to be honored by MediaSource, which will
2283
- // use sequence mode for these segments. We don't yet support sequence
2284
- // mode, so we must reject these streams.
2285
- // TODO(#2337): Support sequence mode and align raw format timestamps to
2286
- // other streams.
2287
- shaka.log.alwaysWarn(
2288
- 'Raw formats are not yet supported. Skipping ' + mimeType);
2289
- throw new shaka.util.Error(
2290
- shaka.util.Error.Severity.RECOVERABLE,
2291
- shaka.util.Error.Category.MANIFEST,
2292
- shaka.util.Error.Code.HLS_INTERNAL_SKIP_STREAM);
2293
- }
2294
-
2295
- if (mimeType == 'video/webm') {
2296
- shaka.log.alwaysWarn('WebM in HLS is not yet supported. Skipping.');
2297
- throw new shaka.util.Error(
2298
- shaka.util.Error.Severity.RECOVERABLE,
2299
- shaka.util.Error.Category.MANIFEST,
2300
- shaka.util.Error.Code.HLS_INTERNAL_SKIP_STREAM);
2301
- }
2302
-
2303
- if (mimeType == 'video/mp4' || mimeType == 'audio/mp4') {
2304
- // We also need the init segment to get the correct timescale. But if the
2305
- // stream is self-initializing, use the same response for both.
2306
- const fetches = [this.fetchStartOfSegment_(
2307
- segmentRef,
2308
- type,
2309
- mimeType,
2310
- codecs,
2311
- bandwidth,
2312
- )];
2313
-
2314
- if (initSegmentRef) {
2315
- fetches.push(this.fetchStartOfSegment_(
2316
- initSegmentRef,
2317
- type,
2318
- mimeType,
2319
- codecs,
2320
- bandwidth,
2321
- ));
2119
+ const reference = this.createSegmentReference_(
2120
+ initSegmentRef,
2121
+ previousReference,
2122
+ item,
2123
+ startTime,
2124
+ variables,
2125
+ playlist.absoluteUri,
2126
+ type);
2127
+
2128
+ if (reference) {
2129
+ references.push(reference);
2322
2130
  }
2323
-
2324
- const responses = await Promise.all(fetches);
2325
-
2326
- // If the stream is self-initializing, use the main segment in-place of
2327
- // the init segment.
2328
- const segmentResponse = responses[0];
2329
- const initSegmentResponse = responses[1] || responses[0];
2330
-
2331
- return this.getStartTimeFromMp4Segment_(
2332
- verbatimMediaPlaylistUri, segmentResponse.uri,
2333
- segmentResponse.data, initSegmentResponse.data);
2334
- }
2335
-
2336
- if (mimeType == 'video/mp2t') {
2337
- const response = await this.fetchStartOfSegment_(
2338
- segmentRef,
2339
- type,
2340
- mimeType,
2341
- codecs,
2342
- bandwidth,
2343
- );
2344
- goog.asserts.assert(response.data, 'Should have a response body!');
2345
- return this.getStartTimeFromTsSegment_(
2346
- verbatimMediaPlaylistUri, response.uri, response.data);
2347
- }
2348
-
2349
- throw new shaka.util.Error(
2350
- shaka.util.Error.Severity.CRITICAL,
2351
- shaka.util.Error.Category.MANIFEST,
2352
- shaka.util.Error.Code.HLS_COULD_NOT_PARSE_SEGMENT_START_TIME,
2353
- verbatimMediaPlaylistUri);
2354
- }
2355
-
2356
- /**
2357
- * Parses an mp4 segment to get its start time.
2358
- *
2359
- * @param {string} playlistUri
2360
- * @param {string} segmentUri
2361
- * @param {BufferSource} mediaData
2362
- * @param {BufferSource} initData
2363
- * @return {number}
2364
- * @private
2365
- */
2366
- getStartTimeFromMp4Segment_(playlistUri, segmentUri, mediaData, initData) {
2367
- const Mp4Parser = shaka.util.Mp4Parser;
2368
-
2369
- let timescale = 0;
2370
- new Mp4Parser()
2371
- .box('moov', Mp4Parser.children)
2372
- .box('trak', Mp4Parser.children)
2373
- .box('mdia', Mp4Parser.children)
2374
- .fullBox('mdhd', (box) => {
2375
- goog.asserts.assert(
2376
- box.version == 0 || box.version == 1,
2377
- 'MDHD version can only be 0 or 1');
2378
-
2379
- const parsedMDHDBox = shaka.util.Mp4BoxParsers.parseMDHD(
2380
- box.reader, box.version);
2381
-
2382
- timescale = parsedMDHDBox.timescale;
2383
- box.parser.stop();
2384
- }).parse(initData, /* partialOkay= */ true);
2385
-
2386
- if (!timescale) {
2387
- shaka.log.error('Unable to find timescale in init segment!');
2388
- throw new shaka.util.Error(
2389
- shaka.util.Error.Severity.CRITICAL,
2390
- shaka.util.Error.Category.MANIFEST,
2391
- shaka.util.Error.Code.HLS_COULD_NOT_PARSE_SEGMENT_START_TIME,
2392
- playlistUri, segmentUri);
2393
- }
2394
-
2395
- let startTime = 0;
2396
- let parsedMedia = false;
2397
- new Mp4Parser()
2398
- .box('moof', Mp4Parser.children)
2399
- .box('traf', Mp4Parser.children)
2400
- .fullBox('tfdt', (box) => {
2401
- goog.asserts.assert(
2402
- box.version == 0 || box.version == 1,
2403
- 'TFDT version can only be 0 or 1');
2404
-
2405
- const parsedTFDTBox = shaka.util.Mp4BoxParsers.parseTFDT(
2406
- box.reader, box.version);
2407
- const baseTime = parsedTFDTBox.baseMediaDecodeTime;
2408
- startTime = baseTime / timescale;
2409
- parsedMedia = true;
2410
- box.parser.stop();
2411
- }).parse(mediaData, /* partialOkay= */ true);
2412
-
2413
- if (!parsedMedia) {
2414
- throw new shaka.util.Error(
2415
- shaka.util.Error.Severity.CRITICAL,
2416
- shaka.util.Error.Category.MANIFEST,
2417
- shaka.util.Error.Code.HLS_COULD_NOT_PARSE_SEGMENT_START_TIME,
2418
- playlistUri, segmentUri);
2419
2131
  }
2420
- return startTime;
2421
- }
2422
2132
 
2423
- /**
2424
- * Parses a TS segment to get its start time.
2425
- *
2426
- * @param {string} playlistUri
2427
- * @param {string} segmentUri
2428
- * @param {BufferSource} data
2429
- * @return {number}
2430
- * @private
2431
- */
2432
- getStartTimeFromTsSegment_(playlistUri, segmentUri, data) {
2433
- const reader = new shaka.util.DataViewReader(
2434
- data, shaka.util.DataViewReader.Endianness.BIG_ENDIAN);
2435
-
2436
- const fail = () => {
2437
- throw new shaka.util.Error(
2438
- shaka.util.Error.Severity.CRITICAL,
2439
- shaka.util.Error.Category.MANIFEST,
2440
- shaka.util.Error.Code.HLS_COULD_NOT_PARSE_SEGMENT_START_TIME,
2441
- playlistUri, segmentUri);
2442
- };
2443
-
2444
- let packetStart = 0;
2445
- let syncByte = 0;
2446
-
2447
- const skipPacket = () => {
2448
- // 188-byte packets are standard, so assume that.
2449
- reader.seek(packetStart + 188);
2450
- syncByte = reader.readUint8();
2451
- if (syncByte != 0x47) {
2452
- // We haven't found the sync byte, so try it as a 192-byte packet.
2453
- reader.seek(packetStart + 192);
2454
- syncByte = reader.readUint8();
2455
- }
2456
- if (syncByte != 0x47) {
2457
- // We still haven't found the sync byte, so try as a 204-byte packet.
2458
- reader.seek(packetStart + 204);
2459
- syncByte = reader.readUint8();
2460
- }
2461
- if (syncByte != 0x47) {
2462
- // We still haven't found the sync byte, so the packet was of a
2463
- // non-standard size.
2464
- fail();
2465
- }
2466
- // Put the sync byte back so we can read it in the next loop.
2467
- reader.rewind(1);
2468
- };
2469
-
2470
- // We will look a few packet-lengths forward to find the first sync byte.
2471
- // Note that we are using this method on what is already a subset of the
2472
- // file (the first |shaka.hls.HlsParser.START_OF_SEGMENT_SIZE_| bytes), so
2473
- // we can't look too far ahead to begin with.
2474
- let syncByteScanLength = Math.min(reader.getLength() - 188, 5 * 188);
2475
-
2476
- // TODO: refactor this while loop for better readability.
2477
- // eslint-disable-next-line no-constant-condition
2478
- while (true) {
2479
- // Format reference: https://bit.ly/TsPacket
2480
- packetStart = reader.getPosition();
2481
-
2482
- syncByte = reader.readUint8();
2483
- if (syncByte != 0x47) {
2484
- if (syncByteScanLength > 0) {
2485
- // This file could have started with a cut-off TS packet. Scan forward
2486
- // until we find a sync byte.
2487
- syncByteScanLength -= 1;
2133
+ // If some segments have sync times, but not all, extrapolate the sync
2134
+ // times of the ones with none.
2135
+ const someSyncTime = references.some((ref) => ref.syncTime != null);
2136
+ if (someSyncTime) {
2137
+ for (let i = 0; i < references.length; i++) {
2138
+ const reference = references[i];
2139
+ if (reference.syncTime != null) {
2140
+ // No need to extrapolate.
2488
2141
  continue;
2489
2142
  }
2490
- fail();
2491
- }
2492
- // If we've found a sync byte, stop scanning forward for future packets.
2493
- syncByteScanLength = 0;
2494
-
2495
- const flagsAndPacketId = reader.readUint16();
2496
- const packetId = flagsAndPacketId & 0x1fff;
2497
- if (packetId == 0x1fff) {
2498
- // A "null" TS packet. Skip this TS packet and try again.
2499
- skipPacket();
2500
- continue;
2501
- }
2502
-
2503
- const hasPesPacket = flagsAndPacketId & 0x4000;
2504
- if (!hasPesPacket) {
2505
- // Not a PES packet yet. Skip this TS packet and try again.
2506
- skipPacket();
2507
- continue;
2508
- }
2509
-
2510
- const flags = reader.readUint8();
2511
- const adaptationFieldControl = (flags & 0x30) >> 4;
2512
- if (adaptationFieldControl == 0 /* reserved */ ||
2513
- adaptationFieldControl == 2 /* adaptation field, no payload */) {
2514
- fail();
2515
- }
2516
-
2517
- if (adaptationFieldControl == 3) {
2518
- // Skip over adaptation field.
2519
- const length = reader.readUint8();
2520
- reader.skip(length);
2521
- }
2522
-
2523
- // Now we come to the PES header (hopefully).
2524
- // Format reference: https://bit.ly/TsPES
2525
- const startCode = reader.readUint32();
2526
- const startCodePrefix = startCode >> 8;
2527
- if (startCodePrefix != 1) {
2528
- // Not a PES packet yet. Skip this TS packet and try again.
2529
- skipPacket();
2530
- continue;
2531
- }
2532
-
2533
- // Skip the 16-bit PES length and the first 8 bits of the optional header.
2534
- reader.skip(3);
2535
- // The next 8 bits contain flags about DTS & PTS.
2536
- const ptsDtsIndicator = reader.readUint8() >> 6;
2537
- if (ptsDtsIndicator == 0 /* no timestamp */ ||
2538
- ptsDtsIndicator == 1 /* forbidden */) {
2539
- fail();
2540
- }
2541
-
2542
- const pesHeaderLengthRemaining = reader.readUint8();
2543
- if (pesHeaderLengthRemaining == 0) {
2544
- fail();
2143
+ // Find the nearest segment with syncTime, in either direction.
2144
+ // This looks forward and backward simultaneously, keeping track of what
2145
+ // to offset the syncTime it finds by as it goes.
2146
+ let forwardAdd = 0;
2147
+ let forwardI = i;
2148
+ /**
2149
+ * Look forwards one reference at a time, summing all durations as we
2150
+ * go, until we find a reference with a syncTime to use as a basis.
2151
+ * This DOES count the original reference, but DOESN'T count the first
2152
+ * reference with a syncTime (as we approach it from behind).
2153
+ * @return {?number}
2154
+ */
2155
+ const lookForward = () => {
2156
+ const other = references[forwardI];
2157
+ if (other) {
2158
+ if (other.syncTime != null) {
2159
+ return other.syncTime + forwardAdd;
2160
+ }
2161
+ forwardAdd -= other.endTime - other.startTime;
2162
+ forwardI += 1;
2163
+ }
2164
+ return null;
2165
+ };
2166
+ let backwardAdd = 0;
2167
+ let backwardI = i;
2168
+ /**
2169
+ * Look backwards one reference at a time, summing all durations as we
2170
+ * go, until we find a reference with a syncTime to use as a basis.
2171
+ * This DOESN'T count the original reference, but DOES count the first
2172
+ * reference with a syncTime (as we approach it from ahead).
2173
+ * @return {?number}
2174
+ */
2175
+ const lookBackward = () => {
2176
+ const other = references[backwardI];
2177
+ if (other) {
2178
+ if (other != reference) {
2179
+ backwardAdd += other.endTime - other.startTime;
2180
+ }
2181
+ if (other.syncTime != null) {
2182
+ return other.syncTime + backwardAdd;
2183
+ }
2184
+ backwardI -= 1;
2185
+ }
2186
+ return null;
2187
+ };
2188
+ while (reference.syncTime == null) {
2189
+ reference.syncTime = lookBackward();
2190
+ if (reference.syncTime == null) {
2191
+ reference.syncTime = lookForward();
2192
+ }
2193
+ }
2545
2194
  }
2195
+ }
2546
2196
 
2547
- if (ptsDtsIndicator == 2 /* PTS only */) {
2548
- goog.asserts.assert(pesHeaderLengthRemaining == 5, 'Bad PES header?');
2549
- } else if (ptsDtsIndicator == 3 /* PTS and DTS */) {
2550
- goog.asserts.assert(pesHeaderLengthRemaining == 10, 'Bad PES header?');
2197
+ // Split the sync times properly among partial segments.
2198
+ if (someSyncTime) {
2199
+ for (const reference of references) {
2200
+ let syncTime = reference.syncTime;
2201
+ for (const partial of reference.partialReferences) {
2202
+ partial.syncTime = syncTime;
2203
+ syncTime += partial.endTime - partial.startTime;
2204
+ }
2551
2205
  }
2552
-
2553
- const pts0 = reader.readUint8();
2554
- const pts1 = reader.readUint16();
2555
- const pts2 = reader.readUint16();
2556
- // Reconstruct 33-bit PTS from the 5-byte, padded structure.
2557
- const ptsHigh3 = (pts0 & 0x0e) >> 1;
2558
- const ptsLow30 = ((pts1 & 0xfffe) << 14) | ((pts2 & 0xfffe) >> 1);
2559
- // Reconstruct the PTS as a float. Avoid bitwise operations to combine
2560
- // because bitwise ops treat the values as 32-bit ints.
2561
- const pts = ptsHigh3 * (1 << 30) + ptsLow30;
2562
- return pts / shaka.hls.HlsParser.TS_TIMESCALE_;
2563
2206
  }
2207
+
2208
+ return references;
2564
2209
  }
2565
2210
 
2566
2211
  /**
@@ -2627,11 +2272,10 @@ shaka.hls.HlsParser = class {
2627
2272
 
2628
2273
  if (contentType == ContentType.TEXT) {
2629
2274
  // The extension map didn't work.
2630
- if (!codecs || codecs == 'vtt' || codecs == 'wvtt') {
2275
+ if (codecs == 'vtt' || codecs == 'wvtt') {
2631
2276
  // If codecs is 'vtt', it's WebVTT.
2632
- // If there was no codecs string, assume HLS text streams are WebVTT.
2633
2277
  return 'text/vtt';
2634
- } else {
2278
+ } else if (codecs && codecs !== '') {
2635
2279
  // Otherwise, assume MP4-embedded text, since text-based formats tend
2636
2280
  // not to have a codecs string at all.
2637
2281
  return 'application/mp4';
@@ -2656,6 +2300,11 @@ shaka.hls.HlsParser = class {
2656
2300
  const contentMimeType = response.headers['content-type'];
2657
2301
 
2658
2302
  if (!contentMimeType) {
2303
+ if (contentType == ContentType.TEXT) {
2304
+ // If there was no codecs string and no content-type, assume HLS text
2305
+ // streams are WebVTT.
2306
+ return 'text/vtt';
2307
+ }
2659
2308
  // If the HLS content is lacking in both MIME type metadata and
2660
2309
  // segment file extensions, we fall back to assuming it's MP4.
2661
2310
  const fallbackMimeType = map['mp4'];
@@ -2821,6 +2470,33 @@ shaka.hls.HlsParser = class {
2821
2470
  return op.promise;
2822
2471
  }
2823
2472
 
2473
+ /**
2474
+ * @param {!shaka.hls.Tag} drmTag
2475
+ * @param {string} mimeType
2476
+ * @return {?shaka.extern.DrmInfo}
2477
+ * @private
2478
+ */
2479
+ static fairplayDrmParser_(drmTag, mimeType) {
2480
+ if (mimeType == 'video/mp2t') {
2481
+ throw new shaka.util.Error(
2482
+ shaka.util.Error.Severity.CRITICAL,
2483
+ shaka.util.Error.Category.MANIFEST,
2484
+ shaka.util.Error.Code.HLS_MSE_ENCRYPTED_MP2T_NOT_SUPPORTED);
2485
+ }
2486
+
2487
+ /*
2488
+ * Even if we're not able to construct initData through the HLS tag, adding
2489
+ * a DRMInfo will allow DRM Engine to request a media key system access
2490
+ * with the correct keySystem and initDataType
2491
+ */
2492
+ const drmInfo = shaka.util.ManifestParserUtils.createDrmInfo(
2493
+ 'com.apple.fps', [
2494
+ {initDataType: 'sinf', initData: new Uint8Array(0)},
2495
+ ]);
2496
+
2497
+ return drmInfo;
2498
+ }
2499
+
2824
2500
  /**
2825
2501
  * @param {!shaka.hls.Tag} drmTag
2826
2502
  * @return {?shaka.extern.DrmInfo}
@@ -2883,7 +2559,10 @@ shaka.hls.HlsParser = class {
2883
2559
  0x9a, 0x04, 0xf0, 0x79, 0x98, 0x40, 0x42, 0x86,
2884
2560
  0xab, 0x92, 0xe6, 0x5b, 0xe0, 0x88, 0x5f, 0x95,
2885
2561
  ]);
2886
- const pssh = shaka.util.Pssh.createPssh(data, systemId);
2562
+ const keyIds = new Set();
2563
+ const psshVersion = 0;
2564
+ const pssh =
2565
+ shaka.util.Pssh.createPssh(data, systemId, keyIds, psshVersion);
2887
2566
  const drmInfo = shaka.util.ManifestParserUtils.createDrmInfo(
2888
2567
  'com.microsoft.playready', [
2889
2568
  {initDataType: 'cenc', initData: pssh},
@@ -2902,7 +2581,6 @@ shaka.hls.HlsParser = class {
2902
2581
  * minTimestamp: number,
2903
2582
  * maxTimestamp: number,
2904
2583
  * mediaSequenceToStartTime: !Map.<number, number>,
2905
- * discontinuityToMediaSequence: !Map.<number, number>,
2906
2584
  * canSkipSegments: boolean
2907
2585
  * }}
2908
2586
  *
@@ -2925,9 +2603,6 @@ shaka.hls.HlsParser = class {
2925
2603
  * The maximum timestamp found in the stream.
2926
2604
  * @property {!Map.<number, number>} mediaSequenceToStartTime
2927
2605
  * A map of media sequence numbers to media start times.
2928
- * @property {!Map.<number, number>} discontinuityToMediaSequence
2929
- * A map of discontinuity sequence numbers to the media sequence number of the
2930
- * segment starting with that discontinuity sequence number.
2931
2606
  * @property {boolean} canSkipSegments
2932
2607
  * True if the server supports delta playlist updates, and we can send a
2933
2608
  * request for a playlist that can skip older media segments.
@@ -2971,21 +2646,6 @@ shaka.hls.HlsParser.AUDIO_EXTENSIONS_TO_MIME_TYPES_ = {
2971
2646
  };
2972
2647
 
2973
2648
 
2974
- /**
2975
- * MIME types of raw formats.
2976
- * TODO(#2337): Support raw formats and share this list among parsers.
2977
- *
2978
- * @const {!Array.<string>}
2979
- * @private
2980
- */
2981
- shaka.hls.HlsParser.RAW_FORMATS_ = [
2982
- 'audio/aac',
2983
- 'audio/ac3',
2984
- 'audio/ec3',
2985
- 'audio/mpeg',
2986
- ];
2987
-
2988
-
2989
2649
  /**
2990
2650
  * @const {!Object.<string, string>}
2991
2651
  * @private
@@ -3043,7 +2703,7 @@ shaka.hls.HlsParser.EXTENSION_MAP_BY_CONTENT_TYPE_ = {
3043
2703
 
3044
2704
 
3045
2705
  /**
3046
- * @typedef {function(!shaka.hls.Tag):?shaka.extern.DrmInfo}
2706
+ * @typedef {function(!shaka.hls.Tag, string):?shaka.extern.DrmInfo}
3047
2707
  * @private
3048
2708
  */
3049
2709
  shaka.hls.HlsParser.DrmParser_;
@@ -3054,10 +2714,8 @@ shaka.hls.HlsParser.DrmParser_;
3054
2714
  * @private
3055
2715
  */
3056
2716
  shaka.hls.HlsParser.KEYFORMATS_TO_DRM_PARSERS_ = {
3057
- /* TODO: https://github.com/google/shaka-player/issues/382
3058
2717
  'com.apple.streamingkeydelivery':
3059
2718
  shaka.hls.HlsParser.fairplayDrmParser_,
3060
- */
3061
2719
  'urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed':
3062
2720
  shaka.hls.HlsParser.widevineDrmParser_,
3063
2721
  'com.microsoft.playready':
@@ -3075,28 +2733,13 @@ shaka.hls.HlsParser.PresentationType_ = {
3075
2733
  LIVE: 'LIVE',
3076
2734
  };
3077
2735
 
3078
-
3079
- /**
3080
- * @const {number}
3081
- * @private
3082
- */
3083
- shaka.hls.HlsParser.TS_TIMESCALE_ = 90000;
3084
-
3085
-
3086
- /**
3087
- * The amount of data from the start of a segment we will try to fetch when we
3088
- * need to know the segment start time. This allows us to avoid fetching the
3089
- * entire segment in many cases.
3090
- *
3091
- * @const {number}
3092
- * @private
3093
- */
3094
- shaka.hls.HlsParser.START_OF_SEGMENT_SIZE_ = 2048;
3095
-
3096
-
3097
- shaka.media.ManifestParser.registerParserByExtension(
3098
- 'm3u8', () => new shaka.hls.HlsParser());
3099
- shaka.media.ManifestParser.registerParserByMime(
3100
- 'application/x-mpegurl', () => new shaka.hls.HlsParser());
3101
- shaka.media.ManifestParser.registerParserByMime(
3102
- 'application/vnd.apple.mpegurl', () => new shaka.hls.HlsParser());
2736
+ if (!shaka.util.Platform.isTizen3() &&
2737
+ !shaka.util.Platform.isTizen2() &&
2738
+ !shaka.util.Platform.isWebOS3()) {
2739
+ shaka.media.ManifestParser.registerParserByExtension(
2740
+ 'm3u8', () => new shaka.hls.HlsParser());
2741
+ shaka.media.ManifestParser.registerParserByMime(
2742
+ 'application/x-mpegurl', () => new shaka.hls.HlsParser());
2743
+ shaka.media.ManifestParser.registerParserByMime(
2744
+ 'application/vnd.apple.mpegurl', () => new shaka.hls.HlsParser());
2745
+ }