shaka-player 3.3.4 → 4.0.0

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/workflows/build-and-test.yaml +0 -1
  2. package/.github/workflows/demo-version-index.yaml +8 -0
  3. package/.github/workflows/release-please.yaml +23 -2
  4. package/.github/workflows/selenium-lab-tests.yaml +0 -1
  5. package/.github/workflows/sync-labels.yaml +33 -0
  6. package/.github/workflows/update-issues.yaml +29 -0
  7. package/.github/workflows/validate-pr-title.yaml +35 -0
  8. package/AUTHORS +1 -0
  9. package/CHANGELOG.md +198 -32
  10. package/CONTRIBUTORS +1 -0
  11. package/README.md +10 -7
  12. package/app-engine/demo-version-index/README.md +3 -3
  13. package/app-engine/demo-version-index/app.yaml +27 -1
  14. package/app-engine/demo-version-index/generate.py +164 -0
  15. package/app-engine/demo-version-index/main.py +4 -155
  16. package/app-engine/demo-version-index/requirements.txt +0 -1
  17. package/build/README.md +2 -2
  18. package/build/compiler.py +1 -1
  19. package/build/generateTsDefs.py +0 -2
  20. package/build/shaka-lab.yaml +0 -2
  21. package/build/test.py +7 -1
  22. package/build/types/core +0 -1
  23. package/build/types/polyfill +0 -3
  24. package/demo/common/asset.js +17 -1
  25. package/demo/common/assets.js +29 -0
  26. package/demo/common/message_ids.js +6 -3
  27. package/demo/config.js +10 -11
  28. package/demo/custom.js +39 -0
  29. package/demo/demo_utils.js +0 -1
  30. package/demo/locales/en.json +6 -3
  31. package/demo/locales/source.json +22 -10
  32. package/demo/main.js +9 -6
  33. package/demo/search.js +2 -1
  34. package/dist/controls.css +1 -1
  35. package/dist/demo.compiled.debug.js +71 -64
  36. package/dist/demo.compiled.debug.map +3 -3
  37. package/dist/demo.compiled.js +71 -64
  38. package/dist/demo.compiled.map +3 -3
  39. package/dist/demo.css +1 -1
  40. package/dist/demo.css.map +1 -1
  41. package/dist/deps.js +16 -20
  42. package/dist/locales.js +31 -29
  43. package/dist/receiver.compiled.debug.js +32 -28
  44. package/dist/receiver.compiled.debug.map +3 -3
  45. package/dist/receiver.compiled.js +32 -28
  46. package/dist/receiver.compiled.map +3 -3
  47. package/dist/shaka-player.compiled.d.ts +78 -83
  48. package/dist/shaka-player.compiled.debug.d.ts +78 -83
  49. package/dist/shaka-player.compiled.debug.externs.js +126 -126
  50. package/dist/shaka-player.compiled.debug.js +426 -483
  51. package/dist/shaka-player.compiled.debug.map +4 -4
  52. package/dist/shaka-player.compiled.externs.js +126 -126
  53. package/dist/shaka-player.compiled.js +838 -856
  54. package/dist/shaka-player.compiled.map +4 -4
  55. package/dist/shaka-player.ui.d.ts +80 -83
  56. package/dist/shaka-player.ui.debug.d.ts +80 -83
  57. package/dist/shaka-player.ui.debug.externs.js +134 -126
  58. package/dist/shaka-player.ui.debug.js +488 -543
  59. package/dist/shaka-player.ui.debug.map +4 -4
  60. package/dist/shaka-player.ui.externs.js +134 -126
  61. package/dist/shaka-player.ui.js +980 -994
  62. package/dist/shaka-player.ui.map +4 -4
  63. package/docs/tutorials/config.md +0 -2
  64. package/docs/tutorials/drm-config.md +5 -0
  65. package/docs/tutorials/fairplay.md +7 -22
  66. package/docs/tutorials/faq.md +0 -11
  67. package/docs/tutorials/index.json +2 -8
  68. package/docs/tutorials/plugins.md +1 -3
  69. package/docs/tutorials/upgrade.md +83 -11
  70. package/externs/polyfill.js +15 -0
  71. package/externs/shaka/manifest.js +5 -1
  72. package/externs/shaka/net.js +12 -0
  73. package/externs/shaka/offline.js +5 -1
  74. package/externs/shaka/player.js +39 -32
  75. package/externs/shaka/text.js +12 -12
  76. package/externs/sourcebuffer.js +15 -0
  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 -1
  82. package/lib/hls/hls_parser.js +378 -743
  83. package/lib/hls/manifest_text_parser.js +2 -3
  84. package/lib/media/drm_engine.js +102 -53
  85. package/lib/media/gap_jumping_controller.js +10 -45
  86. package/lib/media/media_source_engine.js +87 -15
  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 +29 -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 +100 -132
  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 +24 -2
  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 +2 -0
  120. package/lib/util/platform.js +45 -0
  121. package/lib/util/player_configuration.js +12 -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 +1 -1
  127. package/roadmap.md +19 -19
  128. package/shaka-player.uncompiled.js +0 -3
  129. package/test/cast/cast_utils_unit.js +1 -0
  130. package/test/dash/dash_parser_content_protection_unit.js +44 -0
  131. package/test/dash/dash_parser_manifest_unit.js +0 -24
  132. package/test/dash/dash_parser_segment_template_unit.js +3 -4
  133. package/test/demo/demo_unit.js +2 -1
  134. package/test/hls/hls_live_unit.js +99 -220
  135. package/test/hls/hls_parser_unit.js +515 -432
  136. package/test/media/drm_engine_unit.js +86 -120
  137. package/test/media/media_source_engine_integration.js +26 -3
  138. package/test/media/media_source_engine_unit.js +7 -6
  139. package/test/media/playhead_unit.js +27 -191
  140. package/test/media/segment_index_unit.js +15 -25
  141. package/test/media/streaming_engine_integration.js +4 -39
  142. package/test/media/streaming_engine_unit.js +3 -2
  143. package/test/media/time_ranges_utils_unit.js +0 -5
  144. package/test/offline/manifest_convert_unit.js +34 -4
  145. package/test/offline/offline_manifest_parser_unit.js +1 -0
  146. package/test/offline/storage_integration.js +5 -4
  147. package/test/player_external.js +0 -6
  148. package/test/player_integration.js +16 -0
  149. package/test/player_unit.js +26 -0
  150. package/test/test/util/fake_media_source_engine.js +11 -4
  151. package/test/test/util/manifest_generator.js +3 -1
  152. package/test/test/util/manifest_parser_util.js +4 -1
  153. package/test/test/util/offline_utils.js +1 -0
  154. package/test/test/util/streaming_engine_util.js +1 -0
  155. package/test/test/util/test_scheme.js +24 -1
  156. package/test/test/util/util.js +4 -4
  157. package/test/text/cue_integration.js +2 -2
  158. package/test/text/lrc_text_parser_unit.js +6 -7
  159. package/test/text/mp4_ttml_parser_unit.js +6 -4
  160. package/test/text/mp4_vtt_parser_unit.js +6 -5
  161. package/test/text/sbv_text_parser_unit.js +5 -6
  162. package/test/text/srt_text_parser_unit.js +5 -6
  163. package/test/text/ssa_text_parser_unit.js +17 -29
  164. package/test/text/text_engine_unit.js +45 -5
  165. package/test/text/ttml_text_parser_unit.js +77 -77
  166. package/test/text/vtt_text_parser_unit.js +55 -57
  167. package/test/ui/ui_integration.js +0 -16
  168. package/test/ui/ui_unit.js +27 -0
  169. package/test/util/cmcd_manager_unit.js +3 -0
  170. package/test/util/stream_utils_unit.js +23 -7
  171. package/ui/controls.js +65 -40
  172. package/ui/fullscreen_button.js +26 -6
  173. package/ui/less/containers.less +3 -3
  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/docs/tutorials/upgrade-v2-5.md +0 -441
  179. package/docs/tutorials/upgrade-v3.md +0 -38
  180. package/lib/polyfill/mathround.js +0 -49
  181. package/lib/polyfill/patchedmediakeys_apple.js +0 -744
  182. package/lib/polyfill/patchedmediakeys_ms.js +0 -698
  183. package/lib/util/mutex.js +0 -52
  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,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,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
  // Create SegmentReferences for the partial segments.
1760
1874
  const partialSegmentRefs = [];
1761
1875
  if (this.lowLatencyMode_ && hlsSegment.partialSegments.length) {
@@ -1794,7 +1908,7 @@ shaka.hls.HlsParser = class {
1794
1908
  pStartByte,
1795
1909
  pEndByte,
1796
1910
  initSegmentReference,
1797
- timestampOffset,
1911
+ /* timestampOffset= */ 0, // This value is ignored in sequence mode.
1798
1912
  /* appendWindowStart= */ 0,
1799
1913
  /* appendWindowEnd= */ Infinity);
1800
1914
  partialSegmentRefs.push(partial);
@@ -1862,12 +1976,14 @@ shaka.hls.HlsParser = class {
1862
1976
  startByte,
1863
1977
  endByte,
1864
1978
  initSegmentReference,
1865
- timestampOffset,
1979
+ /* timestampOffset= */ 0, // This value is ignored in sequence mode.
1866
1980
  /* appendWindowStart= */ 0,
1867
1981
  /* appendWindowEnd= */ Infinity,
1868
1982
  partialSegmentRefs,
1869
1983
  tilesLayout,
1870
1984
  tileDuration,
1985
+ syncTime,
1986
+ status,
1871
1987
  );
1872
1988
  }
1873
1989
 
@@ -1921,15 +2037,12 @@ shaka.hls.HlsParser = class {
1921
2037
  * @param {string} mimeType
1922
2038
  * @param {!Map.<number, number>} mediaSequenceToStartTime
1923
2039
  * @param {!Map.<string, string>} variables
1924
- * @param {!Map.<number, number>} discontinuityToMediaSequence
1925
2040
  * @param {string} codecs
1926
- * @param {(number|undefined)} bandwidth
1927
- * @return {!Promise<!Array.<!shaka.media.SegmentReference>>}
2041
+ * @return {!Array.<!shaka.media.SegmentReference>}
1928
2042
  * @private
1929
2043
  */
1930
- async createSegments_(verbatimMediaPlaylistUri, playlist, type, mimeType,
1931
- mediaSequenceToStartTime, variables, discontinuityToMediaSequence,
1932
- codecs, bandwidth) {
2044
+ createSegments_(verbatimMediaPlaylistUri, playlist, type, mimeType,
2045
+ mediaSequenceToStartTime, variables, codecs) {
1933
2046
  /** @type {Array.<!shaka.hls.Segment>} */
1934
2047
  const hlsSegments = playlist.segments;
1935
2048
  goog.asserts.assert(hlsSegments.length, 'Playlist should have segments!');
@@ -1946,69 +2059,26 @@ shaka.hls.HlsParser = class {
1946
2059
  const skippedSegments =
1947
2060
  skipTag ? Number(skipTag.getAttributeValue('SKIPPED-SEGMENTS')) : 0;
1948
2061
  let position = mediaSequenceNumber + skippedSegments;
1949
- let firstStartTime;
2062
+ let firstStartTime = 0;
2063
+
1950
2064
  // For live stream, use the cached value in the mediaSequenceToStartTime
1951
2065
  // 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
2066
  if (this.isLive_() && mediaSequenceToStartTime.has(position)) {
1957
2067
  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
2068
  }
1979
2069
 
1980
2070
  const firstSegmentUri = hlsSegments[0].absoluteUri;
1981
2071
  shaka.log.debug('First segment', firstSegmentUri.split('/').pop(),
1982
2072
  'starts at', firstStartTime);
1983
2073
 
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
2074
  /** @type {!Array.<!shaka.media.SegmentReference>} */
2005
2075
  const references = [];
2006
2076
 
2007
2077
  for (let i = 0; i < hlsSegments.length; i++) {
2008
2078
  const item = hlsSegments[i];
2009
2079
  const previousReference = references[references.length - 1];
2010
- const startTime = (i == 0) ? firstStartTime :
2011
- previousReference.endTime;
2080
+ const startTime =
2081
+ (i == 0) ? firstStartTime : previousReference.endTime;
2012
2082
  position = mediaSequenceNumber + skippedSegments + i;
2013
2083
 
2014
2084
  mediaSequenceToStartTime.set(position, startTime);
@@ -2016,19 +2086,6 @@ shaka.hls.HlsParser = class {
2016
2086
  initSegmentRef = this.getInitSegmentReference_(playlist.absoluteUri,
2017
2087
  item.tags, variables);
2018
2088
 
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
2089
  // If the stream is low latency and the user has not configured the
2033
2090
  // lowLatencyMode, but if it has been configured to activate the
2034
2091
  // lowLatencyMode if a stream of this type is detected, we automatically
@@ -2041,514 +2098,94 @@ shaka.hls.HlsParser = class {
2041
2098
  }
2042
2099
  }
2043
2100
 
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);
2056
-
2057
- 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
- }
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
- }
2336
-
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;
2101
+ const reference = this.createSegmentReference_(
2102
+ initSegmentRef,
2103
+ previousReference,
2104
+ item,
2105
+ startTime,
2106
+ variables,
2107
+ playlist.absoluteUri,
2108
+ type);
2109
+
2110
+ references.push(reference);
2111
+ }
2112
+
2113
+ // If some segments have sync times, but not all, extrapolate the sync
2114
+ // times of the ones with none.
2115
+ const someSyncTime = references.some((ref) => ref.syncTime != null);
2116
+ if (someSyncTime) {
2117
+ for (let i = 0; i < references.length; i++) {
2118
+ const reference = references[i];
2119
+ if (reference.syncTime != null) {
2120
+ // No need to extrapolate.
2476
2121
  continue;
2477
2122
  }
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();
2123
+ // Find the nearest segment with syncTime, in either direction.
2124
+ // This looks forward and backward simultaneously, keeping track of what
2125
+ // to offset the syncTime it finds by as it goes.
2126
+ let forwardAdd = 0;
2127
+ let forwardI = i;
2128
+ /**
2129
+ * Look forwards one reference at a time, summing all durations as we
2130
+ * go, until we find a reference with a syncTime to use as a basis.
2131
+ * This DOES count the original reference, but DOESN'T count the first
2132
+ * reference with a syncTime (as we approach it from behind).
2133
+ * @return {?number}
2134
+ */
2135
+ const lookForward = () => {
2136
+ const other = references[forwardI];
2137
+ if (other) {
2138
+ if (other.syncTime != null) {
2139
+ return other.syncTime + forwardAdd;
2140
+ }
2141
+ forwardAdd -= other.endTime - other.startTime;
2142
+ forwardI += 1;
2143
+ }
2144
+ return null;
2145
+ };
2146
+ let backwardAdd = 0;
2147
+ let backwardI = i;
2148
+ /**
2149
+ * Look backwards 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 DOESN'T count the original reference, but DOES count the first
2152
+ * reference with a syncTime (as we approach it from ahead).
2153
+ * @return {?number}
2154
+ */
2155
+ const lookBackward = () => {
2156
+ const other = references[backwardI];
2157
+ if (other) {
2158
+ if (other != reference) {
2159
+ backwardAdd += other.endTime - other.startTime;
2160
+ }
2161
+ if (other.syncTime != null) {
2162
+ return other.syncTime + backwardAdd;
2163
+ }
2164
+ backwardI -= 1;
2165
+ }
2166
+ return null;
2167
+ };
2168
+ while (reference.syncTime == null) {
2169
+ reference.syncTime = lookBackward();
2170
+ if (reference.syncTime == null) {
2171
+ reference.syncTime = lookForward();
2172
+ }
2173
+ }
2533
2174
  }
2175
+ }
2534
2176
 
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?');
2177
+ // Split the sync times properly among partial segments.
2178
+ if (someSyncTime) {
2179
+ for (const reference of references) {
2180
+ let syncTime = reference.syncTime;
2181
+ for (const partial of reference.partialReferences) {
2182
+ partial.syncTime = syncTime;
2183
+ syncTime += partial.endTime - partial.startTime;
2184
+ }
2539
2185
  }
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
2186
  }
2187
+
2188
+ return references;
2552
2189
  }
2553
2190
 
2554
2191
  /**
@@ -2615,11 +2252,10 @@ shaka.hls.HlsParser = class {
2615
2252
 
2616
2253
  if (contentType == ContentType.TEXT) {
2617
2254
  // The extension map didn't work.
2618
- if (!codecs || codecs == 'vtt' || codecs == 'wvtt') {
2255
+ if (codecs == 'vtt' || codecs == 'wvtt') {
2619
2256
  // If codecs is 'vtt', it's WebVTT.
2620
- // If there was no codecs string, assume HLS text streams are WebVTT.
2621
2257
  return 'text/vtt';
2622
- } else {
2258
+ } else if (codecs && codecs !== '') {
2623
2259
  // Otherwise, assume MP4-embedded text, since text-based formats tend
2624
2260
  // not to have a codecs string at all.
2625
2261
  return 'application/mp4';
@@ -2644,6 +2280,11 @@ shaka.hls.HlsParser = class {
2644
2280
  const contentMimeType = response.headers['content-type'];
2645
2281
 
2646
2282
  if (!contentMimeType) {
2283
+ if (contentType == ContentType.TEXT) {
2284
+ // If there was no codecs string and no content-type, assume HLS text
2285
+ // streams are WebVTT.
2286
+ return 'text/vtt';
2287
+ }
2647
2288
  // If the HLS content is lacking in both MIME type metadata and
2648
2289
  // segment file extensions, we fall back to assuming it's MP4.
2649
2290
  const fallbackMimeType = map['mp4'];
@@ -2809,6 +2450,33 @@ shaka.hls.HlsParser = class {
2809
2450
  return op.promise;
2810
2451
  }
2811
2452
 
2453
+ /**
2454
+ * @param {!shaka.hls.Tag} drmTag
2455
+ * @param {string} mimeType
2456
+ * @return {?shaka.extern.DrmInfo}
2457
+ * @private
2458
+ */
2459
+ static fairplayDrmParser_(drmTag, mimeType) {
2460
+ if (mimeType == 'video/mp2t') {
2461
+ throw new shaka.util.Error(
2462
+ shaka.util.Error.Severity.CRITICAL,
2463
+ shaka.util.Error.Category.MANIFEST,
2464
+ shaka.util.Error.Code.HLS_MSE_ENCRYPTED_MP2T_NOT_SUPPORTED);
2465
+ }
2466
+
2467
+ /*
2468
+ * Even if we're not able to construct initData through the HLS tag, adding
2469
+ * a DRMInfo will allow DRM Engine to request a media key system access
2470
+ * with the correct keySystem and initDataType
2471
+ */
2472
+ const drmInfo = shaka.util.ManifestParserUtils.createDrmInfo(
2473
+ 'com.apple.fps', [
2474
+ {initDataType: 'sinf', initData: new Uint8Array(0)},
2475
+ ]);
2476
+
2477
+ return drmInfo;
2478
+ }
2479
+
2812
2480
  /**
2813
2481
  * @param {!shaka.hls.Tag} drmTag
2814
2482
  * @return {?shaka.extern.DrmInfo}
@@ -2871,7 +2539,10 @@ shaka.hls.HlsParser = class {
2871
2539
  0x9a, 0x04, 0xf0, 0x79, 0x98, 0x40, 0x42, 0x86,
2872
2540
  0xab, 0x92, 0xe6, 0x5b, 0xe0, 0x88, 0x5f, 0x95,
2873
2541
  ]);
2874
- const pssh = shaka.util.Pssh.createPssh(data, systemId);
2542
+ const keyIds = new Set();
2543
+ const psshVersion = 0;
2544
+ const pssh =
2545
+ shaka.util.Pssh.createPssh(data, systemId, keyIds, psshVersion);
2875
2546
  const drmInfo = shaka.util.ManifestParserUtils.createDrmInfo(
2876
2547
  'com.microsoft.playready', [
2877
2548
  {initDataType: 'cenc', initData: pssh},
@@ -2890,7 +2561,6 @@ shaka.hls.HlsParser = class {
2890
2561
  * minTimestamp: number,
2891
2562
  * maxTimestamp: number,
2892
2563
  * mediaSequenceToStartTime: !Map.<number, number>,
2893
- * discontinuityToMediaSequence: !Map.<number, number>,
2894
2564
  * canSkipSegments: boolean
2895
2565
  * }}
2896
2566
  *
@@ -2913,9 +2583,6 @@ shaka.hls.HlsParser = class {
2913
2583
  * The maximum timestamp found in the stream.
2914
2584
  * @property {!Map.<number, number>} mediaSequenceToStartTime
2915
2585
  * 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
2586
  * @property {boolean} canSkipSegments
2920
2587
  * True if the server supports delta playlist updates, and we can send a
2921
2588
  * request for a playlist that can skip older media segments.
@@ -2959,21 +2626,6 @@ shaka.hls.HlsParser.AUDIO_EXTENSIONS_TO_MIME_TYPES_ = {
2959
2626
  };
2960
2627
 
2961
2628
 
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
2629
  /**
2978
2630
  * @const {!Object.<string, string>}
2979
2631
  * @private
@@ -3031,7 +2683,7 @@ shaka.hls.HlsParser.EXTENSION_MAP_BY_CONTENT_TYPE_ = {
3031
2683
 
3032
2684
 
3033
2685
  /**
3034
- * @typedef {function(!shaka.hls.Tag):?shaka.extern.DrmInfo}
2686
+ * @typedef {function(!shaka.hls.Tag, string):?shaka.extern.DrmInfo}
3035
2687
  * @private
3036
2688
  */
3037
2689
  shaka.hls.HlsParser.DrmParser_;
@@ -3042,10 +2694,8 @@ shaka.hls.HlsParser.DrmParser_;
3042
2694
  * @private
3043
2695
  */
3044
2696
  shaka.hls.HlsParser.KEYFORMATS_TO_DRM_PARSERS_ = {
3045
- /* TODO: https://github.com/google/shaka-player/issues/382
3046
2697
  'com.apple.streamingkeydelivery':
3047
2698
  shaka.hls.HlsParser.fairplayDrmParser_,
3048
- */
3049
2699
  'urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed':
3050
2700
  shaka.hls.HlsParser.widevineDrmParser_,
3051
2701
  'com.microsoft.playready':
@@ -3063,28 +2713,13 @@ shaka.hls.HlsParser.PresentationType_ = {
3063
2713
  LIVE: 'LIVE',
3064
2714
  };
3065
2715
 
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());
2716
+ if (!shaka.util.Platform.isTizen3() &&
2717
+ !shaka.util.Platform.isTizen2() &&
2718
+ !shaka.util.Platform.isWebOS3()) {
2719
+ shaka.media.ManifestParser.registerParserByExtension(
2720
+ 'm3u8', () => new shaka.hls.HlsParser());
2721
+ shaka.media.ManifestParser.registerParserByMime(
2722
+ 'application/x-mpegurl', () => new shaka.hls.HlsParser());
2723
+ shaka.media.ManifestParser.registerParserByMime(
2724
+ 'application/vnd.apple.mpegurl', () => new shaka.hls.HlsParser());
2725
+ }