shaka-player 3.1.4 → 3.3.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 (214) hide show
  1. package/.github/ISSUE_TEMPLATE/bug_report.md +1 -1
  2. package/.github/ISSUE_TEMPLATE/feature_request.md +1 -1
  3. package/.github/ISSUE_TEMPLATE/question.md +1 -1
  4. package/AUTHORS +6 -5
  5. package/CHANGELOG.md +363 -0
  6. package/CONTRIBUTORS +4 -2
  7. package/README.md +4 -4
  8. package/build/types/core +3 -0
  9. package/build/types/offline +1 -0
  10. package/build/types/polyfill +2 -0
  11. package/build/types/ui +2 -0
  12. package/demo/common/asset.js +9 -7
  13. package/demo/common/assets.js +28 -1
  14. package/demo/common/message_ids.js +15 -2
  15. package/demo/config.js +35 -2
  16. package/demo/index.html +1 -1
  17. package/demo/locales/en.json +14 -1
  18. package/demo/locales/source.json +56 -4
  19. package/demo/main.js +2 -1
  20. package/dist/controls.css +26 -2
  21. package/dist/controls.css.map +1 -1
  22. package/dist/demo.compiled.debug.js +24 -21
  23. package/dist/demo.compiled.debug.map +3 -3
  24. package/dist/demo.compiled.js +24 -21
  25. package/dist/demo.compiled.map +3 -3
  26. package/dist/deps.js +33 -25
  27. package/dist/locales.js +31 -29
  28. package/dist/receiver.compiled.debug.js +14 -12
  29. package/dist/receiver.compiled.debug.map +3 -3
  30. package/dist/receiver.compiled.js +14 -12
  31. package/dist/receiver.compiled.map +3 -3
  32. package/dist/shaka-player.compiled.d.ts +258 -20
  33. package/dist/shaka-player.compiled.debug.d.ts +258 -20
  34. package/dist/shaka-player.compiled.debug.externs.js +1005 -695
  35. package/dist/shaka-player.compiled.debug.js +720 -640
  36. package/dist/shaka-player.compiled.debug.map +4 -4
  37. package/dist/shaka-player.compiled.externs.js +1005 -695
  38. package/dist/shaka-player.compiled.js +850 -817
  39. package/dist/shaka-player.compiled.map +4 -4
  40. package/dist/shaka-player.ui.d.ts +274 -21
  41. package/dist/shaka-player.ui.debug.d.ts +274 -21
  42. package/dist/shaka-player.ui.debug.externs.js +766 -429
  43. package/dist/shaka-player.ui.debug.js +863 -765
  44. package/dist/shaka-player.ui.debug.map +4 -4
  45. package/dist/shaka-player.ui.externs.js +766 -429
  46. package/dist/shaka-player.ui.js +1042 -996
  47. package/dist/shaka-player.ui.map +4 -4
  48. package/docs/design/bg-fetch-after.gv +29 -0
  49. package/docs/design/bg-fetch-after.gv.png +0 -0
  50. package/docs/design/bg-fetch-before.gv +18 -0
  51. package/docs/design/bg-fetch-before.gv.png +0 -0
  52. package/docs/design/bg-fetch.md +134 -0
  53. package/docs/tutorials/blob-url.md +15 -0
  54. package/docs/tutorials/index.json +1 -0
  55. package/docs/tutorials/ui-customization.md +66 -0
  56. package/docs/tutorials/ui.md +5 -5
  57. package/externs/aria.js +6 -0
  58. package/externs/ima.js +3 -0
  59. package/externs/shaka/ads.js +29 -1
  60. package/externs/shaka/manifest.js +18 -2
  61. package/externs/shaka/manifest_parser.js +10 -1
  62. package/externs/shaka/net.js +20 -4
  63. package/externs/shaka/offline.js +36 -2
  64. package/externs/shaka/player.js +182 -13
  65. package/externs/shaka/text.js +24 -10
  66. package/externs/texttrack.js +3 -0
  67. package/externs/webcrypto.js +18 -0
  68. package/lib/abr/ewma.js +14 -0
  69. package/lib/abr/ewma_bandwidth_estimator.js +16 -2
  70. package/lib/abr/simple_abr_manager.js +6 -0
  71. package/lib/ads/ad_manager.js +16 -15
  72. package/lib/ads/client_side_ad.js +8 -0
  73. package/lib/ads/client_side_ad_manager.js +14 -6
  74. package/lib/ads/server_side_ad.js +7 -0
  75. package/lib/ads/server_side_ad_manager.js +19 -2
  76. package/lib/cast/cast_proxy.js +8 -0
  77. package/lib/cast/cast_utils.js +7 -1
  78. package/lib/cea/cea608_data_channel.js +56 -56
  79. package/lib/cea/cea608_memory.js +33 -33
  80. package/lib/cea/cea708_service.js +43 -43
  81. package/lib/cea/cea708_window.js +38 -38
  82. package/lib/cea/cea_decoder.js +13 -14
  83. package/lib/cea/cea_utils.js +23 -23
  84. package/lib/cea/dtvcc_packet_builder.js +7 -7
  85. package/lib/cea/i_caption_decoder.js +2 -3
  86. package/lib/cea/i_cea_parser.js +2 -2
  87. package/lib/cea/mp4_cea_parser.js +6 -6
  88. package/lib/dash/dash_parser.js +53 -14
  89. package/lib/dash/segment_base.js +25 -1
  90. package/lib/dash/segment_list.js +18 -9
  91. package/lib/dash/segment_template.js +23 -10
  92. package/lib/hls/hls_parser.js +210 -30
  93. package/lib/media/closed_caption_parser.js +1 -2
  94. package/lib/media/drm_engine.js +162 -192
  95. package/lib/media/quality_observer.js +291 -0
  96. package/lib/media/segment_index.js +9 -1
  97. package/lib/media/segment_reference.js +37 -5
  98. package/lib/media/streaming_engine.js +48 -10
  99. package/lib/net/http_fetch_plugin.js +31 -11
  100. package/lib/net/http_plugin_utils.js +1 -0
  101. package/lib/net/http_xhr_plugin.js +40 -14
  102. package/lib/net/networking_engine.js +82 -18
  103. package/lib/offline/download_info.js +68 -0
  104. package/lib/offline/download_manager.js +14 -7
  105. package/lib/offline/indexeddb/base_storage_cell.js +35 -0
  106. package/lib/offline/indexeddb/storage_mechanism.js +1 -1
  107. package/lib/offline/indexeddb/v1_storage_cell.js +1 -0
  108. package/lib/offline/indexeddb/v2_storage_cell.js +1 -0
  109. package/lib/offline/indexeddb/v5_storage_cell.js +5 -0
  110. package/lib/offline/manifest_converter.js +3 -1
  111. package/lib/offline/storage.js +404 -297
  112. package/lib/offline/stored_content_utils.js +2 -0
  113. package/lib/player.js +622 -153
  114. package/lib/polyfill/aria.js +82 -0
  115. package/lib/polyfill/random_uuid.js +52 -0
  116. package/lib/routing/walker.js +2 -3
  117. package/lib/text/cue.js +12 -0
  118. package/lib/text/srt_text_parser.js +4 -4
  119. package/lib/text/text_engine.js +2 -2
  120. package/lib/text/ttml_text_parser.js +25 -2
  121. package/lib/text/ui_text_displayer.js +2 -2
  122. package/lib/text/vtt_text_parser.js +2 -2
  123. package/lib/text/web_vtt_generator.js +55 -12
  124. package/lib/util/cmcd_manager.js +826 -0
  125. package/lib/util/error.js +12 -0
  126. package/lib/util/language_utils.js +12 -0
  127. package/lib/util/manifest_parser_utils.js +1 -0
  128. package/lib/util/mp4_box_parsers.js +20 -20
  129. package/lib/util/multi_map.js +8 -0
  130. package/lib/util/mutex.js +52 -0
  131. package/lib/util/periods.js +98 -64
  132. package/lib/util/player_configuration.js +31 -1
  133. package/lib/util/stream_utils.js +140 -85
  134. package/lib/util/xml_utils.js +31 -12
  135. package/package.json +1 -1
  136. package/roadmap.md +24 -22
  137. package/shaka-player.uncompiled.js +4 -0
  138. package/test/cast/cast_utils_unit.js +1 -0
  139. package/test/cea/cea608_memory_unit.js +1 -1
  140. package/test/cea/cea708_service_unit.js +8 -8
  141. package/test/cea/cea708_window_unit.js +6 -6
  142. package/test/cea/cea_decoder_unit.js +1 -1
  143. package/test/cea/dtvcc_packet_builder_unit.js +2 -2
  144. package/test/dash/dash_parser_content_protection_unit.js +2 -0
  145. package/test/dash/dash_parser_live_unit.js +101 -1
  146. package/test/dash/dash_parser_manifest_unit.js +78 -2
  147. package/test/dash/dash_parser_segment_base_unit.js +2 -0
  148. package/test/dash/dash_parser_segment_list_unit.js +2 -1
  149. package/test/dash/dash_parser_segment_template_unit.js +2 -1
  150. package/test/hls/hls_live_unit.js +2 -0
  151. package/test/hls/hls_parser_unit.js +252 -0
  152. package/test/media/drm_engine_integration.js +86 -12
  153. package/test/media/drm_engine_unit.js +310 -450
  154. package/test/media/quality_observer_unit.js +139 -0
  155. package/test/media/segment_reference_unit.js +16 -1
  156. package/test/media/streaming_engine_integration.js +2 -1
  157. package/test/media/streaming_engine_unit.js +2 -0
  158. package/test/net/http_plugin_unit.js +20 -15
  159. package/test/offline/manifest_convert_unit.js +2 -0
  160. package/test/offline/storage_integration.js +117 -23
  161. package/test/player_integration.js +151 -0
  162. package/test/player_src_equals_integration.js +52 -0
  163. package/test/player_unit.js +11 -33
  164. package/test/test/assets/chapters.srt +11 -0
  165. package/test/test/assets/chapters.vtt +10 -0
  166. package/test/test/util/cea_utils.js +12 -12
  167. package/test/test/util/dash_parser_util.js +4 -0
  168. package/test/test/util/fake_ad.js +7 -0
  169. package/test/test/util/fake_ad_manager.js +7 -0
  170. package/test/test/util/fake_drm_engine.js +9 -2
  171. package/test/test/util/manifest_generator.js +5 -2
  172. package/test/test/util/manifest_parser_util.js +3 -1
  173. package/test/test/util/test_scheme.js +14 -1
  174. package/test/text/ttml_text_parser_unit.js +47 -0
  175. package/test/text/web_vtt_generator_unit.js +81 -7
  176. package/test/ui/ui_unit.js +250 -0
  177. package/test/util/cmcd_manager_unit.js +277 -0
  178. package/test/util/stream_utils_unit.js +118 -10
  179. package/ui/airplay_button.js +2 -1
  180. package/ui/audio_language_selection.js +9 -5
  181. package/ui/cast_button.js +5 -6
  182. package/ui/constants.js +0 -3
  183. package/ui/context_menu.js +109 -0
  184. package/ui/controls.js +41 -7
  185. package/ui/controls.less +1 -0
  186. package/ui/enums.js +4 -0
  187. package/ui/externs/ui.js +28 -1
  188. package/ui/fast_forward_button.js +16 -8
  189. package/ui/fullscreen_button.js +2 -3
  190. package/ui/language_utils.js +1 -1
  191. package/ui/less/buttons.less +0 -4
  192. package/ui/less/containers.less +74 -0
  193. package/ui/less/overflow_menu.less +0 -10
  194. package/ui/less/tooltip.less +97 -0
  195. package/ui/locales/en.json +1 -0
  196. package/ui/locales/source.json +4 -0
  197. package/ui/loop_button.js +6 -3
  198. package/ui/mute_button.js +3 -4
  199. package/ui/overflow_menu.js +7 -28
  200. package/ui/pip_button.js +10 -7
  201. package/ui/play_button.js +2 -2
  202. package/ui/playback_rate_selection.js +11 -15
  203. package/ui/presentation_time.js +1 -3
  204. package/ui/resolution_selection.js +11 -7
  205. package/ui/rewind_button.js +17 -9
  206. package/ui/seek_bar.js +6 -6
  207. package/ui/settings_menu.js +36 -4
  208. package/ui/small_play_button.js +1 -0
  209. package/ui/spacer.js +1 -1
  210. package/ui/statistics_button.js +253 -0
  211. package/ui/text_selection.js +19 -15
  212. package/ui/ui.js +30 -0
  213. package/ui/ui_utils.js +1 -1
  214. package/ui/volume_bar.js +2 -4
@@ -25,6 +25,7 @@ goog.require('shaka.net.DataUriPlugin');
25
25
  goog.require('shaka.net.NetworkingEngine');
26
26
  goog.require('shaka.util.ArrayUtils');
27
27
  goog.require('shaka.util.BufferUtils');
28
+ goog.require('shaka.util.CmcdManager');
28
29
  goog.require('shaka.util.DataViewReader');
29
30
  goog.require('shaka.util.Error');
30
31
  goog.require('shaka.util.FakeEvent');
@@ -92,7 +93,8 @@ shaka.hls.HlsParser = class {
92
93
  * timestamps, offsets, and to handle TS rollover.
93
94
  *
94
95
  * During parsing, used to avoid duplicates in the async methods
95
- * createStreamInfoFromMediaTag_ and createStreamInfoFromVariantTag_.
96
+ * createStreamInfoFromMediaTag_, createStreamInfoFromImageTag_ and
97
+ * createStreamInfoFromVariantTag_.
96
98
  *
97
99
  * During parsing of updates, used by getStartTime_ to determine the start
98
100
  * time of the first segment from existing segment references.
@@ -331,7 +333,8 @@ shaka.hls.HlsParser = class {
331
333
  const segments = await this.createSegments_(
332
334
  streamInfo.verbatimMediaPlaylistUri, playlist, stream.type,
333
335
  stream.mimeType, streamInfo.mediaSequenceToStartTime, mediaVariables,
334
- streamInfo.discontinuityToMediaSequence);
336
+ streamInfo.discontinuityToMediaSequence, stream.codecs,
337
+ stream.bandwidth);
335
338
 
336
339
  stream.segmentIndex.mergeAndEvict(
337
340
  segments, this.presentationTimeline_.getSegmentAvailabilityStart());
@@ -403,6 +406,9 @@ shaka.hls.HlsParser = class {
403
406
  /** @type {!Array.<!shaka.hls.Tag>} */
404
407
  const variantTags = Utils.filterTagsByName(
405
408
  playlist.tags, 'EXT-X-STREAM-INF');
409
+ /** @type {!Array.<!shaka.hls.Tag>} */
410
+ const imageTags = Utils.filterTagsByName(
411
+ playlist.tags, 'EXT-X-IMAGE-STREAM-INF');
406
412
 
407
413
  this.parseCodecs_(variantTags);
408
414
 
@@ -437,6 +443,7 @@ shaka.hls.HlsParser = class {
437
443
  this.parseClosedCaptions_(mediaTags);
438
444
  const variants = await this.createVariantsForTags_(variantTags);
439
445
  const textStreams = await this.parseTexts_(mediaTags);
446
+ const imageStreams = await this.parseImages_(imageTags);
440
447
 
441
448
  // Make sure that the parser has not been destroyed.
442
449
  if (!this.playerInterface_) {
@@ -555,7 +562,7 @@ shaka.hls.HlsParser = class {
555
562
  presentationTimeline: this.presentationTimeline_,
556
563
  variants,
557
564
  textStreams,
558
- imageStreams: [],
565
+ imageStreams,
559
566
  offlineSessionIds: [],
560
567
  minBufferTime: 0,
561
568
  };
@@ -688,6 +695,34 @@ shaka.hls.HlsParser = class {
688
695
  return textStreams.filter((s) => s);
689
696
  }
690
697
 
698
+ /**
699
+ * @param {!Array.<!shaka.hls.Tag>} imageTags from the playlist.
700
+ * @return {!Promise.<!Array.<!shaka.extern.Stream>>}
701
+ * @private
702
+ */
703
+ async parseImages_(imageTags) {
704
+ // Create image stream for each image tag.
705
+ const imageStreamPromises = imageTags.map(async (tag) => {
706
+ const disableThumbnails = this.config_.disableThumbnails;
707
+ if (disableThumbnails) {
708
+ return null;
709
+ }
710
+ try {
711
+ const streamInfo = await this.createStreamInfoFromImageTag_(tag);
712
+ goog.asserts.assert(
713
+ streamInfo, 'Should always have a streamInfo for image');
714
+ return streamInfo.stream;
715
+ } catch (e) {
716
+ if (this.config_.hls.ignoreImageStreamFailures) {
717
+ return null;
718
+ }
719
+ throw e;
720
+ }
721
+ });
722
+ const imageStreams = await Promise.all(imageStreamPromises);
723
+ return imageStreams.filter((s) => s);
724
+ }
725
+
691
726
  /**
692
727
  * @param {!Array.<!shaka.hls.Tag>} mediaTags Media tags from the playlist.
693
728
  * @private
@@ -730,7 +765,7 @@ shaka.hls.HlsParser = class {
730
765
  const videoRange = tag.getAttributeValue('VIDEO-RANGE');
731
766
 
732
767
  const streamInfos = await this.createStreamInfosForVariantTag_(tag,
733
- resolution, frameRate);
768
+ resolution, frameRate, bandwidth);
734
769
 
735
770
  if (streamInfos) {
736
771
  goog.asserts.assert(streamInfos.audio.length ||
@@ -764,10 +799,11 @@ shaka.hls.HlsParser = class {
764
799
  * @param {!shaka.hls.Tag} tag
765
800
  * @param {?string} resolution
766
801
  * @param {?string} frameRate
802
+ * @param {number} bandwidth
767
803
  * @return {!Promise.<?shaka.hls.HlsParser.StreamInfos>}
768
804
  * @private
769
805
  */
770
- async createStreamInfosForVariantTag_(tag, resolution, frameRate) {
806
+ async createStreamInfosForVariantTag_(tag, resolution, frameRate, bandwidth) {
771
807
  const ContentType = shaka.util.ManifestParserUtils.ContentType;
772
808
  /** @type {!Array.<string>} */
773
809
  let allCodecs = this.getCodecsForVariantTag_(tag);
@@ -837,7 +873,8 @@ shaka.hls.HlsParser = class {
837
873
  let streamInfo;
838
874
  if (!ignoreStream) {
839
875
  streamInfo =
840
- await this.createStreamInfoFromVariantTag_(tag, allCodecs, type);
876
+ await this.createStreamInfoFromVariantTag_(tag, allCodecs, type,
877
+ bandwidth);
841
878
  }
842
879
  if (streamInfo) {
843
880
  res[streamInfo.stream.type] = [streamInfo];
@@ -860,15 +897,12 @@ shaka.hls.HlsParser = class {
860
897
  */
861
898
  getCodecsForVariantTag_(tag) {
862
899
  // These are the default codecs to assume if none are specified.
863
- // The video codec is H.264, with baseline profile and level 3.0.
864
- // http://blog.pearce.org.nz/2013/11/what-does-h264avc1-codecs-parameters.html
865
- // The audio codec is "low-complexity" AAC.
866
900
  const defaultCodecsArray = [];
867
901
  if (!this.config_.disableVideo) {
868
- defaultCodecsArray.push('avc1.42E01E');
902
+ defaultCodecsArray.push(this.config_.hls.defaultVideoCodec);
869
903
  }
870
904
  if (!this.config_.disableAudio) {
871
- defaultCodecsArray.push('mp4a.40.2');
905
+ defaultCodecsArray.push(this.config_.hls.defaultAudioCodec);
872
906
  }
873
907
  const defaultCodecs = defaultCodecsArray.join(',');
874
908
 
@@ -1206,16 +1240,62 @@ shaka.hls.HlsParser = class {
1206
1240
  return streamInfo;
1207
1241
  }
1208
1242
 
1243
+ /**
1244
+ * Parse EXT-X-MEDIA media tag into a Stream object.
1245
+ *
1246
+ * @param {shaka.hls.Tag} tag
1247
+ * @return {!Promise.<?shaka.hls.HlsParser.StreamInfo>}
1248
+ * @private
1249
+ */
1250
+ async createStreamInfoFromImageTag_(tag) {
1251
+ goog.asserts.assert(tag.name == 'EXT-X-IMAGE-STREAM-INF',
1252
+ 'Should only be called on image tags!');
1253
+ /** @type {string} */
1254
+ const type = shaka.util.ManifestParserUtils.ContentType.IMAGE;
1255
+
1256
+ const verbatimImagePlaylistUri = this.variableSubstitution_(
1257
+ tag.getRequiredAttrValue('URI'), this.globalVariables_);
1258
+ const codecs = tag.getAttributeValue('CODECS', 'jpeg') || '';
1259
+
1260
+ // Check if the stream has already been created as part of another Variant
1261
+ // and return it if it has.
1262
+ if (this.uriToStreamInfosMap_.has(verbatimImagePlaylistUri)) {
1263
+ return this.uriToStreamInfosMap_.get(verbatimImagePlaylistUri);
1264
+ }
1265
+
1266
+ const language = this.getLanguage_(tag);
1267
+ const name = tag.getAttributeValue('NAME');
1268
+
1269
+ const characteristics = tag.getAttributeValue('CHARACTERISTICS');
1270
+
1271
+ const streamInfo = await this.createStreamInfo_(
1272
+ verbatimImagePlaylistUri, codecs, type, language, /* primary= */ false,
1273
+ name, /* channelsCount= */ null, /* closedCaptions= */ null,
1274
+ characteristics, /* forced= */ false, /* spatialAudio= */ false);
1275
+ if (streamInfo == null) {
1276
+ return null;
1277
+ }
1278
+
1279
+ // TODO: This check is necessary because of the possibility of multiple
1280
+ // calls to createStreamInfoFromImageTag_ before either has resolved.
1281
+ if (this.uriToStreamInfosMap_.has(verbatimImagePlaylistUri)) {
1282
+ return this.uriToStreamInfosMap_.get(verbatimImagePlaylistUri);
1283
+ }
1284
+ this.uriToStreamInfosMap_.set(verbatimImagePlaylistUri, streamInfo);
1285
+ return streamInfo;
1286
+ }
1287
+
1209
1288
  /**
1210
1289
  * Parse an EXT-X-STREAM-INF media tag into a Stream object.
1211
1290
  *
1212
1291
  * @param {!shaka.hls.Tag} tag
1213
1292
  * @param {!Array.<string>} allCodecs
1214
1293
  * @param {string} type
1294
+ * @param {number} bandwidth
1215
1295
  * @return {!Promise.<?shaka.hls.HlsParser.StreamInfo>}
1216
1296
  * @private
1217
1297
  */
1218
- async createStreamInfoFromVariantTag_(tag, allCodecs, type) {
1298
+ async createStreamInfoFromVariantTag_(tag, allCodecs, type, bandwidth) {
1219
1299
  goog.asserts.assert(tag.name == 'EXT-X-STREAM-INF',
1220
1300
  'Should only be called on variant tags!');
1221
1301
  const verbatimMediaPlaylistUri = this.variableSubstitution_(
@@ -1231,7 +1311,7 @@ shaka.hls.HlsParser = class {
1231
1311
  codecs, type, /* language= */ 'und', /* primary= */ false,
1232
1312
  /* name= */ null, /* channelcount= */ null, closedCaptions,
1233
1313
  /* characteristics= */ null, /* forced= */ false,
1234
- /* spatialAudio= */ false);
1314
+ /* spatialAudio= */ false, bandwidth);
1235
1315
  if (streamInfo == null) {
1236
1316
  return null;
1237
1317
  }
@@ -1258,12 +1338,13 @@ shaka.hls.HlsParser = class {
1258
1338
  * @param {?string} characteristics
1259
1339
  * @param {boolean} forced
1260
1340
  * @param {boolean} spatialAudio
1341
+ * @param {(number|undefined)} bandwidth
1261
1342
  * @return {!Promise.<?shaka.hls.HlsParser.StreamInfo>}
1262
1343
  * @private
1263
1344
  */
1264
1345
  async createStreamInfo_(verbatimMediaPlaylistUri, codecs, type, language,
1265
1346
  primary, name, channelsCount, closedCaptions, characteristics, forced,
1266
- spatialAudio) {
1347
+ spatialAudio, bandwidth = undefined) {
1267
1348
  // TODO: Refactor, too many parameters
1268
1349
  let absoluteMediaPlaylistUri = shaka.hls.Utils.constructAbsoluteUri(
1269
1350
  this.masterPlaylistUri_, verbatimMediaPlaylistUri);
@@ -1278,7 +1359,8 @@ shaka.hls.HlsParser = class {
1278
1359
  response.data, absoluteMediaPlaylistUri);
1279
1360
 
1280
1361
  if (playlist.type != shaka.hls.PlaylistType.MEDIA) {
1281
- // EXT-X-MEDIA tags should point to media playlists.
1362
+ // EXT-X-MEDIA and EXT-X-IMAGE-STREAM-INF tags should point to media
1363
+ // playlists.
1282
1364
  throw new shaka.util.Error(
1283
1365
  shaka.util.Error.Severity.CRITICAL,
1284
1366
  shaka.util.Error.Category.MANIFEST,
@@ -1380,7 +1462,7 @@ shaka.hls.HlsParser = class {
1380
1462
  try {
1381
1463
  segments = await this.createSegments_(verbatimMediaPlaylistUri,
1382
1464
  playlist, type, mimeType, mediaSequenceToStartTime, mediaVariables,
1383
- discontinuityToMediaSequence);
1465
+ discontinuityToMediaSequence, codecs, bandwidth);
1384
1466
  } catch (error) {
1385
1467
  if (error.code == shaka.util.Error.Code.HLS_INTERNAL_SKIP_STREAM) {
1386
1468
  shaka.log.alwaysWarn('Skipping unsupported HLS stream',
@@ -1635,11 +1717,12 @@ shaka.hls.HlsParser = class {
1635
1717
  * @param {!Map.<string, string>} variables
1636
1718
  * @param {string} absoluteMediaPlaylistUri
1637
1719
  * @return {!shaka.media.SegmentReference}
1720
+ * @param {string} type
1638
1721
  * @private
1639
1722
  */
1640
1723
  createSegmentReference_(
1641
1724
  initSegmentReference, previousReference, hlsSegment, startTime,
1642
- timestampOffset, variables, absoluteMediaPlaylistUri) {
1725
+ timestampOffset, variables, absoluteMediaPlaylistUri, type) {
1643
1726
  const tags = hlsSegment.tags;
1644
1727
  const absoluteSegmentUri = this.variableSubstitution_(
1645
1728
  hlsSegment.absoluteUri, variables);
@@ -1730,6 +1813,17 @@ shaka.hls.HlsParser = class {
1730
1813
  endByte = partialSegmentRefs[partialSegmentRefs.length - 1].endByte;
1731
1814
  }
1732
1815
 
1816
+ let tilesLayout = '';
1817
+ if (type == shaka.util.ManifestParserUtils.ContentType.IMAGE) {
1818
+ // By default in HLS the tilesLayout is 1x1
1819
+ tilesLayout = '1x1';
1820
+ const tilesTag =
1821
+ shaka.hls.Utils.getFirstTagWithName(tags, 'EXT-X-TILES');
1822
+ if (tilesTag) {
1823
+ tilesLayout = tilesTag.getRequiredAttrValue('LAYOUT');
1824
+ }
1825
+ }
1826
+
1733
1827
  return new shaka.media.SegmentReference(
1734
1828
  startTime,
1735
1829
  endTime,
@@ -1741,6 +1835,7 @@ shaka.hls.HlsParser = class {
1741
1835
  /* appendWindowStart= */ 0,
1742
1836
  /* appendWindowEnd= */ Infinity,
1743
1837
  partialSegmentRefs,
1838
+ tilesLayout,
1744
1839
  );
1745
1840
  }
1746
1841
 
@@ -1795,11 +1890,14 @@ shaka.hls.HlsParser = class {
1795
1890
  * @param {!Map.<number, number>} mediaSequenceToStartTime
1796
1891
  * @param {!Map.<string, string>} variables
1797
1892
  * @param {!Map.<number, number>} discontinuityToMediaSequence
1893
+ * @param {string} codecs
1894
+ * @param {(number|undefined)} bandwidth
1798
1895
  * @return {!Promise<!Array.<!shaka.media.SegmentReference>>}
1799
1896
  * @private
1800
1897
  */
1801
1898
  async createSegments_(verbatimMediaPlaylistUri, playlist, type, mimeType,
1802
- mediaSequenceToStartTime, variables, discontinuityToMediaSequence) {
1899
+ mediaSequenceToStartTime, variables, discontinuityToMediaSequence,
1900
+ codecs, bandwidth) {
1803
1901
  /** @type {Array.<!shaka.hls.Segment>} */
1804
1902
  const hlsSegments = playlist.segments;
1805
1903
  goog.asserts.assert(hlsSegments.length, 'Playlist should have segments!');
@@ -1836,12 +1934,13 @@ shaka.hls.HlsParser = class {
1836
1934
  initSegmentRef = this.getInitSegmentReference_(
1837
1935
  playlist.absoluteUri, hlsSegments[0].tags, variables);
1838
1936
  goog.asserts.assert(
1839
- type != shaka.util.ManifestParserUtils.ContentType.TEXT,
1937
+ type != shaka.util.ManifestParserUtils.ContentType.TEXT &&
1938
+ type != shaka.util.ManifestParserUtils.ContentType.IMAGE,
1840
1939
  'Should only get start time from audio or video streams');
1841
1940
  this.playlistStartTime_ = await this.getStartTime_(
1842
1941
  verbatimMediaPlaylistUri, initSegmentRef, mimeType,
1843
1942
  position, /* isDiscontinuity= */ false,
1844
- hlsSegments[0], variables);
1943
+ hlsSegments[0], variables, type, codecs, bandwidth);
1845
1944
  }
1846
1945
  firstStartTime = this.playlistStartTime_;
1847
1946
  }
@@ -1894,7 +1993,8 @@ shaka.hls.HlsParser = class {
1894
1993
  // eslint-disable-next-line no-await-in-loop
1895
1994
  timestampOffset = await this.getTimestampOffset_(
1896
1995
  discontintuitySequenceNum, verbatimMediaPlaylistUri, initSegmentRef,
1897
- mimeType, position, item, variables, startTime);
1996
+ mimeType, position, item, variables, startTime, type, codecs,
1997
+ bandwidth);
1898
1998
  }
1899
1999
 
1900
2000
  // If the stream is low latency and the user has not configured the
@@ -1919,7 +2019,8 @@ shaka.hls.HlsParser = class {
1919
2019
  startTime,
1920
2020
  timestampOffset,
1921
2021
  variables,
1922
- playlist.absoluteUri);
2022
+ playlist.absoluteUri,
2023
+ type);
1923
2024
 
1924
2025
  references.push(reference);
1925
2026
  } else if (!this.lowLatencyMode_) {
@@ -1946,13 +2047,17 @@ shaka.hls.HlsParser = class {
1946
2047
  * @param {!shaka.hls.Segment} segment
1947
2048
  * @param {!Map.<string, string>} variables
1948
2049
  * @param {number} startTime
2050
+ * @param {string} type
2051
+ * @param {string} codecs
2052
+ * @param {number|undefined} bandwidth
1949
2053
  * @return {!Promise.<number>}
1950
2054
  * @throws {shaka.util.Error}
1951
2055
  * @private
1952
2056
  */
1953
2057
  async getTimestampOffset_(discontintuitySequenceNum,
1954
2058
  verbatimMediaPlaylistUri, initSegmentRef,
1955
- mimeType, mediaSequenceNumber, segment, variables, startTime) {
2059
+ mimeType, mediaSequenceNumber, segment, variables, startTime, type,
2060
+ codecs, bandwidth) {
1956
2061
  let timestampOffset = 0;
1957
2062
  if (this.discontinuityToTso_.has(discontintuitySequenceNum)) {
1958
2063
  timestampOffset =
@@ -1961,7 +2066,7 @@ shaka.hls.HlsParser = class {
1961
2066
  const mediaStartTime = await this.getStartTime_(
1962
2067
  verbatimMediaPlaylistUri, initSegmentRef, mimeType,
1963
2068
  mediaSequenceNumber, /* isDiscontinuity= */ true, segment,
1964
- variables);
2069
+ variables, type, codecs, bandwidth);
1965
2070
  timestampOffset = startTime - mediaStartTime;
1966
2071
  shaka.log.v1('Segment timestampOffset =', timestampOffset);
1967
2072
  this.discontinuityToTso_.set(
@@ -1975,10 +2080,14 @@ shaka.hls.HlsParser = class {
1975
2080
  * segment if we have to.
1976
2081
  *
1977
2082
  * @param {!shaka.media.AnySegmentReference} reference
2083
+ * @param {string} type
2084
+ * @param {string} mimeType
2085
+ * @param {string} codecs
2086
+ * @param {number|undefined} bandwidth
1978
2087
  * @return {!Promise.<shaka.extern.Response>}
1979
2088
  * @private
1980
2089
  */
1981
- async fetchStartOfSegment_(reference) {
2090
+ async fetchStartOfSegment_(reference, type, mimeType, codecs, bandwidth) {
1982
2091
  const requestType = shaka.net.NetworkingEngine.RequestType.SEGMENT;
1983
2092
 
1984
2093
  // Create two requests:
@@ -1992,6 +2101,20 @@ shaka.hls.HlsParser = class {
1992
2101
  reference.endByte,
1993
2102
  this.config_.retryParameters);
1994
2103
 
2104
+ // We can only add partial CMCD data here because the stream
2105
+ // and manifest objects are still being created
2106
+ this.playerInterface_.modifySegmentRequest(
2107
+ fullRequest,
2108
+ {
2109
+ type: type,
2110
+ init: reference instanceof shaka.media.InitSegmentReference,
2111
+ duration: reference.endTime - reference.startTime,
2112
+ mimeType: mimeType,
2113
+ codecs: codecs,
2114
+ bandwidth: bandwidth,
2115
+ },
2116
+ );
2117
+
1995
2118
  if (this.config_.hls.useFullSegmentsForStartTime) {
1996
2119
  return this.makeNetworkRequest_(fullRequest, requestType);
1997
2120
  }
@@ -2002,6 +2125,18 @@ shaka.hls.HlsParser = class {
2002
2125
  reference.startByte + shaka.hls.HlsParser.START_OF_SEGMENT_SIZE_ - 1,
2003
2126
  this.config_.retryParameters);
2004
2127
 
2128
+ this.playerInterface_.modifySegmentRequest(
2129
+ partialRequest,
2130
+ {
2131
+ type: type,
2132
+ init: reference instanceof shaka.media.InitSegmentReference,
2133
+ duration: reference.endTime - reference.startTime,
2134
+ mimeType: mimeType,
2135
+ codecs: codecs,
2136
+ bandwidth: bandwidth,
2137
+ },
2138
+ );
2139
+
2005
2140
  // TODO(vaage): The need to do fall back requests is not likely to be unique
2006
2141
  // to here. It would be nice if the fallback(s) could be included into
2007
2142
  // the same abortable operation as the original request.
@@ -2048,12 +2183,15 @@ shaka.hls.HlsParser = class {
2048
2183
  * @param {boolean} isDiscontinuity
2049
2184
  * @param {!shaka.hls.Segment} segment
2050
2185
  * @param {!Map.<string, string>} variables
2186
+ * @param {string} type
2187
+ * @param {string} codecs
2188
+ * @param {number|undefined} bandwidth
2051
2189
  * @return {!Promise.<number>}
2052
2190
  * @private
2053
2191
  */
2054
2192
  async getStartTime_(
2055
2193
  verbatimMediaPlaylistUri, initSegmentRef, mimeType, mediaSequenceNumber,
2056
- isDiscontinuity, segment, variables) {
2194
+ isDiscontinuity, segment, variables, type, codecs, bandwidth) {
2057
2195
  const segmentRef = this.createSegmentReference_(
2058
2196
  initSegmentRef,
2059
2197
  /* previousReference= */ null,
@@ -2061,7 +2199,8 @@ shaka.hls.HlsParser = class {
2061
2199
  /* startTime= */ 0,
2062
2200
  /* timestampOffset= */ 0,
2063
2201
  variables,
2064
- /* absoluteMediaPlaylistUri= */ '');
2202
+ /* absoluteMediaPlaylistUri= */ '',
2203
+ type);
2065
2204
  // If we are updating the manifest, we can usually skip fetching the segment
2066
2205
  // by examining the references we already have. This won't be possible if
2067
2206
  // there was some kind of lag or delay updating the manifest on the server,
@@ -2120,10 +2259,22 @@ shaka.hls.HlsParser = class {
2120
2259
  if (mimeType == 'video/mp4' || mimeType == 'audio/mp4') {
2121
2260
  // We also need the init segment to get the correct timescale. But if the
2122
2261
  // stream is self-initializing, use the same response for both.
2123
- const fetches = [this.fetchStartOfSegment_(segmentRef)];
2262
+ const fetches = [this.fetchStartOfSegment_(
2263
+ segmentRef,
2264
+ type,
2265
+ mimeType,
2266
+ codecs,
2267
+ bandwidth,
2268
+ )];
2124
2269
 
2125
2270
  if (initSegmentRef) {
2126
- fetches.push(this.fetchStartOfSegment_(initSegmentRef));
2271
+ fetches.push(this.fetchStartOfSegment_(
2272
+ initSegmentRef,
2273
+ type,
2274
+ mimeType,
2275
+ codecs,
2276
+ bandwidth,
2277
+ ));
2127
2278
  }
2128
2279
 
2129
2280
  const responses = await Promise.all(fetches);
@@ -2139,7 +2290,13 @@ shaka.hls.HlsParser = class {
2139
2290
  }
2140
2291
 
2141
2292
  if (mimeType == 'video/mp2t') {
2142
- const response = await this.fetchStartOfSegment_(segmentRef);
2293
+ const response = await this.fetchStartOfSegment_(
2294
+ segmentRef,
2295
+ type,
2296
+ mimeType,
2297
+ codecs,
2298
+ bandwidth,
2299
+ );
2143
2300
  goog.asserts.assert(response.data, 'Should have a response body!');
2144
2301
  return this.getStartTimeFromTsSegment_(
2145
2302
  verbatimMediaPlaylistUri, response.uri, response.data);
@@ -2437,6 +2594,12 @@ shaka.hls.HlsParser = class {
2437
2594
  }
2438
2595
  }
2439
2596
 
2597
+ if (contentType == ContentType.IMAGE) {
2598
+ if (!codecs || codecs == 'jpeg') {
2599
+ return 'image/jpeg';
2600
+ }
2601
+ }
2602
+
2440
2603
  // If unable to guess mime type, request a segment and try getting it
2441
2604
  // from the response.
2442
2605
  const headRequest = shaka.net.NetworkingEngine.makeRequest(
@@ -2511,6 +2674,9 @@ shaka.hls.HlsParser = class {
2511
2674
  const request = shaka.net.NetworkingEngine.makeRequest(
2512
2675
  [absoluteUri], this.config_.retryParameters);
2513
2676
 
2677
+ const format = shaka.util.CmcdManager.StreamingFormat.HLS;
2678
+ this.playerInterface_.modifyManifestRequest(request, {format: format});
2679
+
2514
2680
  return this.makeNetworkRequest_(request, requestType);
2515
2681
  }
2516
2682
 
@@ -2807,6 +2973,19 @@ shaka.hls.HlsParser.TEXT_EXTENSIONS_TO_MIME_TYPES_ = {
2807
2973
  };
2808
2974
 
2809
2975
 
2976
+ /**
2977
+ * @const {!Object.<string, string>}
2978
+ * @private
2979
+ */
2980
+ shaka.hls.HlsParser.IMAGE_EXTENSIONS_TO_MIME_TYPES_ = {
2981
+ 'jpg': 'image/jpeg',
2982
+ 'png': 'image/png',
2983
+ 'svg': 'image/svg+xml',
2984
+ 'webp': 'image/webp',
2985
+ 'avif': 'image/avif',
2986
+ };
2987
+
2988
+
2810
2989
  /**
2811
2990
  * @const {!Object.<string, !Object.<string, string>>}
2812
2991
  * @private
@@ -2815,6 +2994,7 @@ shaka.hls.HlsParser.EXTENSION_MAP_BY_CONTENT_TYPE_ = {
2815
2994
  'audio': shaka.hls.HlsParser.AUDIO_EXTENSIONS_TO_MIME_TYPES_,
2816
2995
  'video': shaka.hls.HlsParser.VIDEO_EXTENSIONS_TO_MIME_TYPES_,
2817
2996
  'text': shaka.hls.HlsParser.TEXT_EXTENSIONS_TO_MIME_TYPES_,
2997
+ 'image': shaka.hls.HlsParser.IMAGE_EXTENSIONS_TO_MIME_TYPES_,
2818
2998
  };
2819
2999
 
2820
3000
 
@@ -4,14 +4,13 @@
4
4
  * SPDX-License-Identifier: Apache-2.0
5
5
  */
6
6
 
7
- goog.provide('shaka.media.IClosedCaptionParser');
8
7
  goog.provide('shaka.media.ClosedCaptionParser');
8
+ goog.provide('shaka.media.IClosedCaptionParser');
9
9
 
10
10
  goog.require('shaka.cea.CeaDecoder');
11
11
  goog.require('shaka.cea.Mp4CeaParser');
12
12
  goog.require('shaka.util.BufferUtils');
13
13
  goog.requireType('shaka.cea.ICaptionDecoder');
14
- goog.requireType('shaka.cea.ICaptionDecoder.ClosedCaption');
15
14
  goog.requireType('shaka.cea.ICeaParser');
16
15
 
17
16