shaka-player 3.1.5 → 3.2.3

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 (134) hide show
  1. package/CHANGELOG.md +129 -6
  2. package/README.md +4 -4
  3. package/build/types/polyfill +1 -0
  4. package/demo/common/asset.js +9 -7
  5. package/demo/common/assets.js +10 -1
  6. package/demo/common/message_ids.js +2 -1
  7. package/demo/config.js +5 -2
  8. package/demo/index.html +1 -1
  9. package/demo/locales/en.json +2 -1
  10. package/demo/locales/source.json +8 -4
  11. package/demo/main.js +2 -1
  12. package/dist/demo.compiled.debug.js +14 -14
  13. package/dist/demo.compiled.debug.map +2 -2
  14. package/dist/demo.compiled.js +14 -14
  15. package/dist/demo.compiled.map +2 -2
  16. package/dist/deps.js +21 -20
  17. package/dist/locales.js +30 -30
  18. package/dist/receiver.compiled.debug.js +8 -8
  19. package/dist/receiver.compiled.debug.map +2 -2
  20. package/dist/receiver.compiled.js +8 -8
  21. package/dist/receiver.compiled.map +2 -2
  22. package/dist/shaka-player.compiled.d.ts +63 -10
  23. package/dist/shaka-player.compiled.debug.d.ts +63 -10
  24. package/dist/shaka-player.compiled.debug.externs.js +786 -716
  25. package/dist/shaka-player.compiled.debug.js +563 -534
  26. package/dist/shaka-player.compiled.debug.map +4 -4
  27. package/dist/shaka-player.compiled.externs.js +786 -716
  28. package/dist/shaka-player.compiled.js +820 -809
  29. package/dist/shaka-player.compiled.map +4 -4
  30. package/dist/shaka-player.ui.d.ts +63 -10
  31. package/dist/shaka-player.ui.debug.d.ts +63 -10
  32. package/dist/shaka-player.ui.debug.externs.js +542 -472
  33. package/dist/shaka-player.ui.debug.js +594 -566
  34. package/dist/shaka-player.ui.debug.map +4 -4
  35. package/dist/shaka-player.ui.externs.js +542 -472
  36. package/dist/shaka-player.ui.js +938 -927
  37. package/dist/shaka-player.ui.map +4 -4
  38. package/docs/tutorials/ui.md +5 -5
  39. package/externs/aria.js +6 -0
  40. package/externs/shaka/ads.js +24 -1
  41. package/externs/shaka/manifest.js +6 -1
  42. package/externs/shaka/offline.js +5 -0
  43. package/externs/shaka/player.js +61 -9
  44. package/externs/shaka/text.js +10 -10
  45. package/externs/texttrack.js +3 -0
  46. package/lib/ads/ad_manager.js +16 -15
  47. package/lib/ads/client_side_ad_manager.js +6 -2
  48. package/lib/ads/server_side_ad_manager.js +19 -2
  49. package/lib/cast/cast_proxy.js +8 -0
  50. package/lib/cast/cast_utils.js +6 -1
  51. package/lib/cea/cea608_data_channel.js +56 -56
  52. package/lib/cea/cea608_memory.js +33 -33
  53. package/lib/cea/cea708_service.js +43 -43
  54. package/lib/cea/cea708_window.js +38 -38
  55. package/lib/cea/cea_decoder.js +13 -14
  56. package/lib/cea/cea_utils.js +23 -23
  57. package/lib/cea/dtvcc_packet_builder.js +7 -7
  58. package/lib/cea/i_caption_decoder.js +2 -3
  59. package/lib/cea/i_cea_parser.js +2 -2
  60. package/lib/cea/mp4_cea_parser.js +6 -6
  61. package/lib/dash/dash_parser.js +5 -0
  62. package/lib/hls/hls_parser.js +159 -12
  63. package/lib/media/closed_caption_parser.js +1 -2
  64. package/lib/media/drm_engine.js +162 -192
  65. package/lib/media/segment_index.js +4 -1
  66. package/lib/media/segment_reference.js +37 -2
  67. package/lib/media/streaming_engine.js +6 -6
  68. package/lib/net/networking_engine.js +1 -0
  69. package/lib/offline/download_manager.js +1 -2
  70. package/lib/offline/indexeddb/v1_storage_cell.js +1 -0
  71. package/lib/offline/indexeddb/v2_storage_cell.js +1 -0
  72. package/lib/offline/manifest_converter.js +3 -1
  73. package/lib/offline/storage.js +12 -14
  74. package/lib/player.js +426 -149
  75. package/lib/polyfill/aria.js +82 -0
  76. package/lib/routing/walker.js +2 -3
  77. package/lib/text/srt_text_parser.js +4 -4
  78. package/lib/text/text_engine.js +2 -2
  79. package/lib/text/vtt_text_parser.js +2 -2
  80. package/lib/text/web_vtt_generator.js +55 -12
  81. package/lib/util/error.js +6 -0
  82. package/lib/util/manifest_parser_utils.js +1 -0
  83. package/lib/util/mp4_box_parsers.js +20 -20
  84. package/lib/util/multi_map.js +8 -0
  85. package/lib/util/player_configuration.js +13 -1
  86. package/lib/util/stream_utils.js +168 -88
  87. package/lib/util/xml_utils.js +31 -12
  88. package/package.json +1 -1
  89. package/roadmap.md +24 -22
  90. package/shaka-player.uncompiled.js +1 -0
  91. package/test/cast/cast_utils_unit.js +1 -0
  92. package/test/cea/cea608_memory_unit.js +1 -1
  93. package/test/cea/cea708_service_unit.js +8 -8
  94. package/test/cea/cea708_window_unit.js +6 -6
  95. package/test/cea/cea_decoder_unit.js +1 -1
  96. package/test/cea/dtvcc_packet_builder_unit.js +2 -2
  97. package/test/dash/dash_parser_manifest_unit.js +43 -1
  98. package/test/hls/hls_parser_unit.js +250 -0
  99. package/test/media/drm_engine_integration.js +86 -12
  100. package/test/media/drm_engine_unit.js +310 -450
  101. package/test/offline/manifest_convert_unit.js +2 -0
  102. package/test/offline/storage_integration.js +9 -19
  103. package/test/player_integration.js +85 -0
  104. package/test/player_src_equals_integration.js +85 -0
  105. package/test/player_unit.js +25 -37
  106. package/test/test/assets/chapters.srt +11 -0
  107. package/test/test/assets/chapters.vtt +10 -0
  108. package/test/test/assets/chapters2.vtt +10 -0
  109. package/test/test/util/cea_utils.js +12 -12
  110. package/test/test/util/fake_ad_manager.js +7 -0
  111. package/test/test/util/manifest_generator.js +2 -0
  112. package/test/test/util/manifest_parser_util.js +3 -1
  113. package/test/text/web_vtt_generator_unit.js +81 -7
  114. package/test/util/stream_utils_unit.js +118 -10
  115. package/ui/airplay_button.js +1 -1
  116. package/ui/audio_language_selection.js +2 -5
  117. package/ui/cast_button.js +4 -6
  118. package/ui/constants.js +0 -3
  119. package/ui/fast_forward_button.js +2 -3
  120. package/ui/fullscreen_button.js +1 -3
  121. package/ui/language_utils.js +1 -1
  122. package/ui/loop_button.js +1 -3
  123. package/ui/mute_button.js +1 -3
  124. package/ui/overflow_menu.js +2 -3
  125. package/ui/pip_button.js +5 -7
  126. package/ui/playback_rate_selection.js +3 -6
  127. package/ui/presentation_time.js +1 -3
  128. package/ui/resolution_selection.js +4 -7
  129. package/ui/rewind_button.js +2 -3
  130. package/ui/seek_bar.js +2 -4
  131. package/ui/spacer.js +1 -1
  132. package/ui/text_selection.js +8 -11
  133. package/ui/ui_utils.js +1 -1
  134. package/ui/volume_bar.js +1 -3
@@ -15,10 +15,10 @@ shaka.cea.CeaUtils = class {
15
15
  /**
16
16
  * Emits a closed caption based on the state of the buffer.
17
17
  * @param {!shaka.text.Cue} topLevelCue
18
- * @param {!string} stream
18
+ * @param {string} stream
19
19
  * @param {!Array<!Array<?shaka.cea.CeaUtils.StyledChar>>} memory
20
- * @param {!number} startTime Start time of the cue.
21
- * @param {!number} endTime End time of the cue.
20
+ * @param {number} startTime Start time of the cue.
21
+ * @param {number} endTime End time of the cue.
22
22
  * @return {?shaka.cea.ICaptionDecoder.ClosedCaption}
23
23
  */
24
24
  static getParsedCaption(topLevelCue, stream, memory, startTime, endTime) {
@@ -153,12 +153,12 @@ shaka.cea.CeaUtils = class {
153
153
  }
154
154
 
155
155
  /**
156
- * @param {!number} startTime
157
- * @param {!number} endTime
158
- * @param {!boolean} underline
159
- * @param {!boolean} italics
160
- * @param {!string} txtColor
161
- * @param {!string} bgColor
156
+ * @param {number} startTime
157
+ * @param {number} endTime
158
+ * @param {boolean} underline
159
+ * @param {boolean} italics
160
+ * @param {string} txtColor
161
+ * @param {string} bgColor
162
162
  * @return {!shaka.text.Cue}
163
163
  */
164
164
  static createStyledCue(startTime, endTime, underline,
@@ -176,8 +176,8 @@ shaka.cea.CeaUtils = class {
176
176
  }
177
177
 
178
178
  /**
179
- * @param {!number} startTime
180
- * @param {!number} endTime
179
+ * @param {number} startTime
180
+ * @param {number} endTime
181
181
  * @return {!shaka.text.Cue}
182
182
  */
183
183
  static createLineBreakCue(startTime, endTime) {
@@ -198,61 +198,61 @@ shaka.cea.CeaUtils.StyledChar = class {
198
198
  */
199
199
  constructor(character, underline, italics, backgroundColor, textColor) {
200
200
  /**
201
- * @private {!string}
201
+ * @private {string}
202
202
  */
203
203
  this.character_ = character;
204
204
 
205
205
  /**
206
- * @private {!boolean}
206
+ * @private {boolean}
207
207
  */
208
208
  this.underline_ = underline;
209
209
 
210
210
  /**
211
- * @private {!boolean}
211
+ * @private {boolean}
212
212
  */
213
213
  this.italics_ = italics;
214
214
 
215
215
  /**
216
- * @private {!string}
216
+ * @private {string}
217
217
  */
218
218
  this.backgroundColor_ = backgroundColor;
219
219
 
220
220
  /**
221
- * @private {!string}
221
+ * @private {string}
222
222
  */
223
223
  this.textColor_ = textColor;
224
224
  }
225
225
 
226
226
  /**
227
- * @return {!string}
227
+ * @return {string}
228
228
  */
229
229
  getChar() {
230
230
  return this.character_;
231
231
  }
232
232
 
233
233
  /**
234
- * @return {!boolean}
234
+ * @return {boolean}
235
235
  */
236
236
  isUnderlined() {
237
237
  return this.underline_;
238
238
  }
239
239
 
240
240
  /**
241
- * @return {!boolean}
241
+ * @return {boolean}
242
242
  */
243
243
  isItalicized() {
244
244
  return this.italics_;
245
245
  }
246
246
 
247
247
  /**
248
- * @return {!string}
248
+ * @return {string}
249
249
  */
250
250
  getBackgroundColor() {
251
251
  return this.backgroundColor_;
252
252
  }
253
253
 
254
254
  /**
255
- * @return {!string}
255
+ * @return {string}
256
256
  */
257
257
  getTextColor() {
258
258
  return this.textColor_;
@@ -261,12 +261,12 @@ shaka.cea.CeaUtils.StyledChar = class {
261
261
 
262
262
  /**
263
263
  * Default background color for text.
264
- * @const {!string}
264
+ * @const {string}
265
265
  */
266
266
  shaka.cea.CeaUtils.DEFAULT_BG_COLOR = 'black';
267
267
 
268
268
  /**
269
269
  * Default text color.
270
- * @const {!string}
270
+ * @const {string}
271
271
  */
272
272
  shaka.cea.CeaUtils.DEFAULT_TXT_COLOR = 'white';
@@ -35,7 +35,7 @@ shaka.cea.DtvccPacketBuilder = class {
35
35
 
36
36
  /**
37
37
  * Keeps track of the number of bytes left to add in the current packet.
38
- * @private {!number}
38
+ * @private {number}
39
39
  */
40
40
  this.bytesLeftToAddInCurrentPacket_ = 0;
41
41
  }
@@ -107,7 +107,7 @@ shaka.cea.DtvccPacket = class {
107
107
  constructor(packetData) {
108
108
  /**
109
109
  * Keeps track of the position to read the next byte from in the packet.
110
- * @private {!number}
110
+ * @private {number}
111
111
  */
112
112
  this.pos_ = 0;
113
113
 
@@ -119,14 +119,14 @@ shaka.cea.DtvccPacket = class {
119
119
  }
120
120
 
121
121
  /**
122
- * @return {!boolean}
122
+ * @return {boolean}
123
123
  */
124
124
  hasMoreData() {
125
125
  return this.pos_ < this.packetData_.length;
126
126
  }
127
127
 
128
128
  /**
129
- * @return {!number}
129
+ * @return {number}
130
130
  */
131
131
  getPosition() {
132
132
  return this.pos_;
@@ -149,7 +149,7 @@ shaka.cea.DtvccPacket = class {
149
149
 
150
150
  /**
151
151
  * Skips the provided number of blocks in the buffer.
152
- * @param {!number} numBlocks
152
+ * @param {number} numBlocks
153
153
  * @throws {!shaka.util.Error}
154
154
  */
155
155
  skip(numBlocks) {
@@ -164,11 +164,11 @@ shaka.cea.DtvccPacket = class {
164
164
  };
165
165
 
166
166
  /**
167
- * @const {!number}
167
+ * @const {number}
168
168
  */
169
169
  shaka.cea.DtvccPacketBuilder.DTVCC_PACKET_DATA = 2;
170
170
 
171
171
  /**
172
- * @const {!number}
172
+ * @const {number}
173
173
  */
174
174
  shaka.cea.DtvccPacketBuilder.DTVCC_PACKET_START = 3;
@@ -5,7 +5,6 @@
5
5
  */
6
6
 
7
7
  goog.provide('shaka.cea.ICaptionDecoder');
8
- goog.provide('shaka.cea.ICaptionDecoder.ClosedCaption');
9
8
 
10
9
  goog.require('shaka.text.Cue');
11
10
 
@@ -23,7 +22,7 @@ shaka.cea.ICaptionDecoder = class {
23
22
  * @param {!Uint8Array} userDataSeiMessage
24
23
  * This is a User Data registered by Rec.ITU-T T.35 SEI message.
25
24
  * It is described in sections D.1.6 and D.2.6 of Rec. ITU-T H.264 (06/2019).
26
- * @param {!number} pts PTS when this packet was received, in seconds.
25
+ * @param {number} pts PTS when this packet was received, in seconds.
27
26
  */
28
27
  extract(userDataSeiMessage, pts) {}
29
28
 
@@ -46,7 +45,7 @@ shaka.cea.ICaptionDecoder = class {
46
45
  * Parsed Cue.
47
46
  * @typedef {{
48
47
  * cue: !shaka.text.Cue,
49
- * stream: !string
48
+ * stream: string
50
49
  * }}
51
50
  */
52
51
  shaka.cea.ICaptionDecoder.ClosedCaption;
@@ -39,7 +39,7 @@ shaka.cea.ICeaParser.DEFAULT_TIMESCALE_VALUE = 90000;
39
39
  /**
40
40
  * @typedef {{
41
41
  * packet: !Uint8Array,
42
- * pts: !number
42
+ * pts: number
43
43
  * }}
44
44
  *
45
45
  * @description Parsed Caption Packet.
@@ -47,7 +47,7 @@ shaka.cea.ICeaParser.DEFAULT_TIMESCALE_VALUE = 90000;
47
47
  * Caption packet. More specifically, it contains a "User data
48
48
  * registered by Recommendation ITU-T T.35 SEI message", from section D.1.6
49
49
  * and section D.2.6 of Rec. ITU-T H.264 (06/2019).
50
- * @property {!number} pts
50
+ * @property {number} pts
51
51
  * The presentation timestamp (pts) at which the ITU-T T.35 data shows up.
52
52
  * in seconds.
53
53
  * @exportDoc
@@ -36,13 +36,13 @@ shaka.cea.Mp4CeaParser = class {
36
36
 
37
37
  /**
38
38
  * Default sample duration, as specified by the TREX box.
39
- * @private {!number}
39
+ * @private {number}
40
40
  */
41
41
  this.defaultSampleDuration_ = 0;
42
42
 
43
43
  /**
44
44
  * Default sample size, as specified by the TREX box.
45
- * @private {!number}
45
+ * @private {number}
46
46
  */
47
47
  this.defaultSampleSize_ = 0;
48
48
  }
@@ -195,10 +195,10 @@ shaka.cea.Mp4CeaParser = class {
195
195
  /**
196
196
  * Parse MDAT box.
197
197
  * @param {!shaka.util.DataViewReader} reader
198
- * @param {!number} time
199
- * @param {!number} timescale
200
- * @param {!number} defaultSampleDuration
201
- * @param {!number} defaultSampleSize
198
+ * @param {number} time
199
+ * @param {number} timescale
200
+ * @param {number} defaultSampleDuration
201
+ * @param {number} defaultSampleSize
202
202
  * @param {!Array<shaka.util.ParsedTRUNSample>} sampleData
203
203
  * @param {!Array<!shaka.cea.ICeaParser.CaptionPacket>} captionPackets
204
204
  * @private
@@ -301,6 +301,11 @@ shaka.dash.DashParser = class {
301
301
  const Functional = shaka.util.Functional;
302
302
  const XmlUtils = shaka.util.XmlUtils;
303
303
 
304
+ const manifestPreprocessor = this.config_.dash.manifestPreprocessor;
305
+ if (manifestPreprocessor) {
306
+ manifestPreprocessor(mpd);
307
+ }
308
+
304
309
  // Get any Location elements. This will update the manifest location and
305
310
  // the base URI.
306
311
  /** @type {!Array.<string>} */
@@ -92,7 +92,8 @@ shaka.hls.HlsParser = class {
92
92
  * timestamps, offsets, and to handle TS rollover.
93
93
  *
94
94
  * During parsing, used to avoid duplicates in the async methods
95
- * createStreamInfoFromMediaTag_ and createStreamInfoFromVariantTag_.
95
+ * createStreamInfoFromMediaTag_, createStreamInfoFromImageTag_ and
96
+ * createStreamInfoFromVariantTag_.
96
97
  *
97
98
  * During parsing of updates, used by getStartTime_ to determine the start
98
99
  * time of the first segment from existing segment references.
@@ -403,6 +404,9 @@ shaka.hls.HlsParser = class {
403
404
  /** @type {!Array.<!shaka.hls.Tag>} */
404
405
  const variantTags = Utils.filterTagsByName(
405
406
  playlist.tags, 'EXT-X-STREAM-INF');
407
+ /** @type {!Array.<!shaka.hls.Tag>} */
408
+ const imageTags = Utils.filterTagsByName(
409
+ playlist.tags, 'EXT-X-IMAGE-STREAM-INF');
406
410
 
407
411
  this.parseCodecs_(variantTags);
408
412
 
@@ -437,6 +441,7 @@ shaka.hls.HlsParser = class {
437
441
  this.parseClosedCaptions_(mediaTags);
438
442
  const variants = await this.createVariantsForTags_(variantTags);
439
443
  const textStreams = await this.parseTexts_(mediaTags);
444
+ const imageStreams = await this.parseImages_(imageTags);
440
445
 
441
446
  // Make sure that the parser has not been destroyed.
442
447
  if (!this.playerInterface_) {
@@ -555,7 +560,7 @@ shaka.hls.HlsParser = class {
555
560
  presentationTimeline: this.presentationTimeline_,
556
561
  variants,
557
562
  textStreams,
558
- imageStreams: [],
563
+ imageStreams,
559
564
  offlineSessionIds: [],
560
565
  minBufferTime: 0,
561
566
  };
@@ -688,6 +693,34 @@ shaka.hls.HlsParser = class {
688
693
  return textStreams.filter((s) => s);
689
694
  }
690
695
 
696
+ /**
697
+ * @param {!Array.<!shaka.hls.Tag>} imageTags from the playlist.
698
+ * @return {!Promise.<!Array.<!shaka.extern.Stream>>}
699
+ * @private
700
+ */
701
+ async parseImages_(imageTags) {
702
+ // Create image stream for each image tag.
703
+ const imageStreamPromises = imageTags.map(async (tag) => {
704
+ const disableThumbnails = this.config_.disableThumbnails;
705
+ if (disableThumbnails) {
706
+ return null;
707
+ }
708
+ try {
709
+ const streamInfo = await this.createStreamInfoFromImageTag_(tag);
710
+ goog.asserts.assert(
711
+ streamInfo, 'Should always have a streamInfo for image');
712
+ return streamInfo.stream;
713
+ } catch (e) {
714
+ if (this.config_.hls.ignoreImageStreamFailures) {
715
+ return null;
716
+ }
717
+ throw e;
718
+ }
719
+ });
720
+ const imageStreams = await Promise.all(imageStreamPromises);
721
+ return imageStreams.filter((s) => s);
722
+ }
723
+
691
724
  /**
692
725
  * @param {!Array.<!shaka.hls.Tag>} mediaTags Media tags from the playlist.
693
726
  * @private
@@ -1206,6 +1239,75 @@ shaka.hls.HlsParser = class {
1206
1239
  return streamInfo;
1207
1240
  }
1208
1241
 
1242
+ /**
1243
+ * Parse EXT-X-MEDIA media tag into a Stream object.
1244
+ *
1245
+ * @param {shaka.hls.Tag} tag
1246
+ * @return {!Promise.<?shaka.hls.HlsParser.StreamInfo>}
1247
+ * @private
1248
+ */
1249
+ async createStreamInfoFromImageTag_(tag) {
1250
+ goog.asserts.assert(tag.name == 'EXT-X-IMAGE-STREAM-INF',
1251
+ 'Should only be called on image tags!');
1252
+ /** @type {string} */
1253
+ const type = shaka.util.ManifestParserUtils.ContentType.IMAGE;
1254
+
1255
+ const verbatimImagePlaylistUri = this.variableSubstitution_(
1256
+ tag.getRequiredAttrValue('URI'), this.globalVariables_);
1257
+ const codecs = tag.getAttributeValue('CODECS', 'jpeg') || '';
1258
+
1259
+ // Check if the stream has already been created as part of another Variant
1260
+ // and return it if it has.
1261
+ if (this.uriToStreamInfosMap_.has(verbatimImagePlaylistUri)) {
1262
+ return this.uriToStreamInfosMap_.get(verbatimImagePlaylistUri);
1263
+ }
1264
+
1265
+ const language = this.getLanguage_(tag);
1266
+ const name = tag.getAttributeValue('NAME');
1267
+
1268
+ const characteristics = tag.getAttributeValue('CHARACTERISTICS');
1269
+
1270
+ const streamInfo = await this.createStreamInfo_(
1271
+ verbatimImagePlaylistUri, codecs, type, language, /* primary= */ false,
1272
+ name, /* channelsCount= */ null, /* closedCaptions= */ null,
1273
+ characteristics, /* forced= */ false, /* spatialAudio= */ false);
1274
+ if (streamInfo == null) {
1275
+ return null;
1276
+ }
1277
+
1278
+ // TODO: This check is necessary because of the possibility of multiple
1279
+ // calls to createStreamInfoFromImageTag_ before either has resolved.
1280
+ if (this.uriToStreamInfosMap_.has(verbatimImagePlaylistUri)) {
1281
+ return this.uriToStreamInfosMap_.get(verbatimImagePlaylistUri);
1282
+ }
1283
+
1284
+ // Parse misc attributes.
1285
+ const resolution = tag.getAttributeValue('RESOLUTION');
1286
+ if (resolution) {
1287
+ // The RESOLUTION tag represents the resolution of a single thumbnail, not
1288
+ // of the entire sheet at once (like we expect in the output).
1289
+ // So multiply by the layout size.
1290
+
1291
+ const reference = streamInfo.stream.segmentIndex.get(0);
1292
+ const layout = reference.getTilesLayout();
1293
+ if (layout) {
1294
+ streamInfo.stream.width =
1295
+ Number(resolution.split('x')[0]) * Number(layout.split('x')[0]);
1296
+ streamInfo.stream.height =
1297
+ Number(resolution.split('x')[1]) * Number(layout.split('x')[1]);
1298
+ // TODO: What happens if there are multiple grids, with different
1299
+ // layout sizes, inside this image stream?
1300
+ }
1301
+ }
1302
+ const bandwidth = tag.getAttributeValue('BANDWIDTH');
1303
+ if (bandwidth) {
1304
+ streamInfo.stream.bandwidth = Number(bandwidth);
1305
+ }
1306
+
1307
+ this.uriToStreamInfosMap_.set(verbatimImagePlaylistUri, streamInfo);
1308
+ return streamInfo;
1309
+ }
1310
+
1209
1311
  /**
1210
1312
  * Parse an EXT-X-STREAM-INF media tag into a Stream object.
1211
1313
  *
@@ -1278,7 +1380,8 @@ shaka.hls.HlsParser = class {
1278
1380
  response.data, absoluteMediaPlaylistUri);
1279
1381
 
1280
1382
  if (playlist.type != shaka.hls.PlaylistType.MEDIA) {
1281
- // EXT-X-MEDIA tags should point to media playlists.
1383
+ // EXT-X-MEDIA and EXT-X-IMAGE-STREAM-INF tags should point to media
1384
+ // playlists.
1282
1385
  throw new shaka.util.Error(
1283
1386
  shaka.util.Error.Severity.CRITICAL,
1284
1387
  shaka.util.Error.Category.MANIFEST,
@@ -1635,11 +1738,12 @@ shaka.hls.HlsParser = class {
1635
1738
  * @param {!Map.<string, string>} variables
1636
1739
  * @param {string} absoluteMediaPlaylistUri
1637
1740
  * @return {!shaka.media.SegmentReference}
1741
+ * @param {string} type
1638
1742
  * @private
1639
1743
  */
1640
1744
  createSegmentReference_(
1641
1745
  initSegmentReference, previousReference, hlsSegment, startTime,
1642
- timestampOffset, variables, absoluteMediaPlaylistUri) {
1746
+ timestampOffset, variables, absoluteMediaPlaylistUri, type) {
1643
1747
  const tags = hlsSegment.tags;
1644
1748
  const absoluteSegmentUri = this.variableSubstitution_(
1645
1749
  hlsSegment.absoluteUri, variables);
@@ -1730,6 +1834,22 @@ shaka.hls.HlsParser = class {
1730
1834
  endByte = partialSegmentRefs[partialSegmentRefs.length - 1].endByte;
1731
1835
  }
1732
1836
 
1837
+ let tilesLayout = '';
1838
+ let tileDuration = null;
1839
+ if (type == shaka.util.ManifestParserUtils.ContentType.IMAGE) {
1840
+ // By default in HLS the tilesLayout is 1x1
1841
+ tilesLayout = '1x1';
1842
+ const tilesTag =
1843
+ shaka.hls.Utils.getFirstTagWithName(tags, 'EXT-X-TILES');
1844
+ if (tilesTag) {
1845
+ tilesLayout = tilesTag.getRequiredAttrValue('LAYOUT');
1846
+ const duration = tilesTag.getAttributeValue('DURATION');
1847
+ if (duration) {
1848
+ tileDuration = Number(duration);
1849
+ }
1850
+ }
1851
+ }
1852
+
1733
1853
  return new shaka.media.SegmentReference(
1734
1854
  startTime,
1735
1855
  endTime,
@@ -1741,6 +1861,8 @@ shaka.hls.HlsParser = class {
1741
1861
  /* appendWindowStart= */ 0,
1742
1862
  /* appendWindowEnd= */ Infinity,
1743
1863
  partialSegmentRefs,
1864
+ tilesLayout,
1865
+ tileDuration,
1744
1866
  );
1745
1867
  }
1746
1868
 
@@ -1836,12 +1958,13 @@ shaka.hls.HlsParser = class {
1836
1958
  initSegmentRef = this.getInitSegmentReference_(
1837
1959
  playlist.absoluteUri, hlsSegments[0].tags, variables);
1838
1960
  goog.asserts.assert(
1839
- type != shaka.util.ManifestParserUtils.ContentType.TEXT,
1961
+ type != shaka.util.ManifestParserUtils.ContentType.TEXT &&
1962
+ type != shaka.util.ManifestParserUtils.ContentType.IMAGE,
1840
1963
  'Should only get start time from audio or video streams');
1841
1964
  this.playlistStartTime_ = await this.getStartTime_(
1842
1965
  verbatimMediaPlaylistUri, initSegmentRef, mimeType,
1843
1966
  position, /* isDiscontinuity= */ false,
1844
- hlsSegments[0], variables);
1967
+ hlsSegments[0], variables, type);
1845
1968
  }
1846
1969
  firstStartTime = this.playlistStartTime_;
1847
1970
  }
@@ -1894,7 +2017,7 @@ shaka.hls.HlsParser = class {
1894
2017
  // eslint-disable-next-line no-await-in-loop
1895
2018
  timestampOffset = await this.getTimestampOffset_(
1896
2019
  discontintuitySequenceNum, verbatimMediaPlaylistUri, initSegmentRef,
1897
- mimeType, position, item, variables, startTime);
2020
+ mimeType, position, item, variables, startTime, type);
1898
2021
  }
1899
2022
 
1900
2023
  // If the stream is low latency and the user has not configured the
@@ -1919,7 +2042,8 @@ shaka.hls.HlsParser = class {
1919
2042
  startTime,
1920
2043
  timestampOffset,
1921
2044
  variables,
1922
- playlist.absoluteUri);
2045
+ playlist.absoluteUri,
2046
+ type);
1923
2047
 
1924
2048
  references.push(reference);
1925
2049
  } else if (!this.lowLatencyMode_) {
@@ -1946,13 +2070,14 @@ shaka.hls.HlsParser = class {
1946
2070
  * @param {!shaka.hls.Segment} segment
1947
2071
  * @param {!Map.<string, string>} variables
1948
2072
  * @param {number} startTime
2073
+ * @param {string} type
1949
2074
  * @return {!Promise.<number>}
1950
2075
  * @throws {shaka.util.Error}
1951
2076
  * @private
1952
2077
  */
1953
2078
  async getTimestampOffset_(discontintuitySequenceNum,
1954
2079
  verbatimMediaPlaylistUri, initSegmentRef,
1955
- mimeType, mediaSequenceNumber, segment, variables, startTime) {
2080
+ mimeType, mediaSequenceNumber, segment, variables, startTime, type) {
1956
2081
  let timestampOffset = 0;
1957
2082
  if (this.discontinuityToTso_.has(discontintuitySequenceNum)) {
1958
2083
  timestampOffset =
@@ -1961,7 +2086,7 @@ shaka.hls.HlsParser = class {
1961
2086
  const mediaStartTime = await this.getStartTime_(
1962
2087
  verbatimMediaPlaylistUri, initSegmentRef, mimeType,
1963
2088
  mediaSequenceNumber, /* isDiscontinuity= */ true, segment,
1964
- variables);
2089
+ variables, type);
1965
2090
  timestampOffset = startTime - mediaStartTime;
1966
2091
  shaka.log.v1('Segment timestampOffset =', timestampOffset);
1967
2092
  this.discontinuityToTso_.set(
@@ -2048,12 +2173,13 @@ shaka.hls.HlsParser = class {
2048
2173
  * @param {boolean} isDiscontinuity
2049
2174
  * @param {!shaka.hls.Segment} segment
2050
2175
  * @param {!Map.<string, string>} variables
2176
+ * @param {string} type
2051
2177
  * @return {!Promise.<number>}
2052
2178
  * @private
2053
2179
  */
2054
2180
  async getStartTime_(
2055
2181
  verbatimMediaPlaylistUri, initSegmentRef, mimeType, mediaSequenceNumber,
2056
- isDiscontinuity, segment, variables) {
2182
+ isDiscontinuity, segment, variables, type) {
2057
2183
  const segmentRef = this.createSegmentReference_(
2058
2184
  initSegmentRef,
2059
2185
  /* previousReference= */ null,
@@ -2061,7 +2187,8 @@ shaka.hls.HlsParser = class {
2061
2187
  /* startTime= */ 0,
2062
2188
  /* timestampOffset= */ 0,
2063
2189
  variables,
2064
- /* absoluteMediaPlaylistUri= */ '');
2190
+ /* absoluteMediaPlaylistUri= */ '',
2191
+ type);
2065
2192
  // If we are updating the manifest, we can usually skip fetching the segment
2066
2193
  // by examining the references we already have. This won't be possible if
2067
2194
  // there was some kind of lag or delay updating the manifest on the server,
@@ -2437,6 +2564,12 @@ shaka.hls.HlsParser = class {
2437
2564
  }
2438
2565
  }
2439
2566
 
2567
+ if (contentType == ContentType.IMAGE) {
2568
+ if (!codecs || codecs == 'jpeg') {
2569
+ return 'image/jpeg';
2570
+ }
2571
+ }
2572
+
2440
2573
  // If unable to guess mime type, request a segment and try getting it
2441
2574
  // from the response.
2442
2575
  const headRequest = shaka.net.NetworkingEngine.makeRequest(
@@ -2807,6 +2940,19 @@ shaka.hls.HlsParser.TEXT_EXTENSIONS_TO_MIME_TYPES_ = {
2807
2940
  };
2808
2941
 
2809
2942
 
2943
+ /**
2944
+ * @const {!Object.<string, string>}
2945
+ * @private
2946
+ */
2947
+ shaka.hls.HlsParser.IMAGE_EXTENSIONS_TO_MIME_TYPES_ = {
2948
+ 'jpg': 'image/jpeg',
2949
+ 'png': 'image/png',
2950
+ 'svg': 'image/svg+xml',
2951
+ 'webp': 'image/webp',
2952
+ 'avif': 'image/avif',
2953
+ };
2954
+
2955
+
2810
2956
  /**
2811
2957
  * @const {!Object.<string, !Object.<string, string>>}
2812
2958
  * @private
@@ -2815,6 +2961,7 @@ shaka.hls.HlsParser.EXTENSION_MAP_BY_CONTENT_TYPE_ = {
2815
2961
  'audio': shaka.hls.HlsParser.AUDIO_EXTENSIONS_TO_MIME_TYPES_,
2816
2962
  'video': shaka.hls.HlsParser.VIDEO_EXTENSIONS_TO_MIME_TYPES_,
2817
2963
  'text': shaka.hls.HlsParser.TEXT_EXTENSIONS_TO_MIME_TYPES_,
2964
+ 'image': shaka.hls.HlsParser.IMAGE_EXTENSIONS_TO_MIME_TYPES_,
2818
2965
  };
2819
2966
 
2820
2967
 
@@ -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