shaka-player 4.4.1 → 4.4.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 (84) hide show
  1. package/.github/workflows/deflake.yaml +8 -5
  2. package/.github/workflows/release-please.yaml +47 -7
  3. package/.github/workflows/shaka-bot-commands/command-test.sh +1 -1
  4. package/CHANGELOG.md +43 -0
  5. package/CONTRIBUTORS +1 -0
  6. package/demo/common/asset.js +5 -0
  7. package/demo/common/assets.js +20 -5
  8. package/demo/common/message_ids.js +1 -1
  9. package/demo/custom.js +5 -5
  10. package/demo/locales/en.json +2 -1
  11. package/demo/locales/source.json +5 -1
  12. package/demo/main.js +1 -1
  13. package/demo/search.js +3 -3
  14. package/dist/demo.compiled.debug.js +75 -73
  15. package/dist/demo.compiled.debug.map +3 -3
  16. package/dist/demo.compiled.js +75 -73
  17. package/dist/demo.compiled.map +3 -3
  18. package/dist/deps.js +2 -2
  19. package/dist/locales.js +31 -31
  20. package/dist/receiver.compiled.debug.js +38 -36
  21. package/dist/receiver.compiled.debug.map +3 -3
  22. package/dist/receiver.compiled.js +38 -36
  23. package/dist/receiver.compiled.map +3 -3
  24. package/dist/shaka-player.compiled.d.ts +533 -296
  25. package/dist/shaka-player.compiled.debug.d.ts +533 -296
  26. package/dist/shaka-player.compiled.debug.externs.js +308 -1
  27. package/dist/shaka-player.compiled.debug.js +280 -265
  28. package/dist/shaka-player.compiled.debug.map +3 -3
  29. package/dist/shaka-player.compiled.externs.js +308 -1
  30. package/dist/shaka-player.compiled.js +1037 -1027
  31. package/dist/shaka-player.compiled.map +4 -4
  32. package/dist/shaka-player.ui.d.ts +577 -340
  33. package/dist/shaka-player.ui.debug.d.ts +577 -340
  34. package/dist/shaka-player.ui.debug.externs.js +308 -1
  35. package/dist/shaka-player.ui.debug.js +341 -326
  36. package/dist/shaka-player.ui.debug.map +3 -3
  37. package/dist/shaka-player.ui.externs.js +308 -1
  38. package/dist/shaka-player.ui.js +1149 -1141
  39. package/dist/shaka-player.ui.map +4 -4
  40. package/docs/tutorials/fairplay.md +8 -0
  41. package/docs/tutorials/faq.md +1 -1
  42. package/docs/tutorials/offline.md +1 -1
  43. package/externs/shaka/text.js +7 -0
  44. package/lib/dash/dash_parser.js +4 -1
  45. package/lib/dash/segment_template.js +3 -0
  46. package/lib/hls/hls_parser.js +68 -28
  47. package/lib/media/media_source_engine.js +1 -1
  48. package/lib/media/segment_index.js +4 -4
  49. package/lib/media/segment_reference.js +6 -1
  50. package/lib/media/streaming_engine.js +14 -24
  51. package/lib/player.js +8 -57
  52. package/lib/text/cue.js +47 -47
  53. package/lib/text/lrc_text_parser.js +8 -0
  54. package/lib/text/mp4_ttml_parser.js +8 -0
  55. package/lib/text/mp4_vtt_parser.js +8 -0
  56. package/lib/text/sbv_text_parser.js +8 -0
  57. package/lib/text/srt_text_parser.js +8 -0
  58. package/lib/text/ssa_text_parser.js +8 -0
  59. package/lib/text/text_engine.js +8 -1
  60. package/lib/text/ttml_text_parser.js +10 -1
  61. package/lib/text/ui_text_displayer.js +49 -3
  62. package/lib/text/vtt_text_parser.js +19 -3
  63. package/lib/transmuxer/ts_transmuxer.js +44 -43
  64. package/lib/util/cmcd_manager.js +6 -1
  65. package/lib/util/error.js +16 -10
  66. package/lib/util/manifest_parser_utils.js +10 -4
  67. package/lib/util/platform.js +14 -0
  68. package/lib/util/player_configuration.js +2 -2
  69. package/lib/util/stream_utils.js +13 -11
  70. package/lib/util/ts_parser.js +73 -16
  71. package/package.json +2 -2
  72. package/test/dash/dash_parser_manifest_unit.js +5 -0
  73. package/test/dash/dash_parser_segment_base_unit.js +5 -0
  74. package/test/dash/dash_parser_segment_list_unit.js +21 -17
  75. package/test/dash/dash_parser_segment_template_unit.js +47 -0
  76. package/test/hls/hls_live_unit.js +33 -0
  77. package/test/hls/hls_parser_unit.js +2 -57
  78. package/test/test/util/dash_parser_util.js +16 -7
  79. package/test/text/text_engine_unit.js +4 -2
  80. package/test/text/ttml_text_parser_unit.js +3 -4
  81. package/test/text/web_vtt_layout_integration.js +0 -1
  82. package/test/transmuxer/transmuxer_integration.js +4 -4
  83. package/test/util/ts_parser_unit.js +1 -1
  84. package/ui/language_utils.js +0 -15
@@ -170,17 +170,7 @@ shaka.transmuxer.TsTransmuxer = class {
170
170
 
171
171
  const uint8ArrayData = shaka.util.BufferUtils.toUint8(data);
172
172
 
173
- let timestamp = reference.endTime * 1000;
174
-
175
173
  const tsParser = this.tsParser_.parse(uint8ArrayData);
176
- const startTime = tsParser.getStartTime();
177
-
178
- if (startTime.audio != null) {
179
- timestamp = startTime.audio;
180
- }
181
- if (startTime.video != null) {
182
- timestamp = startTime.video;
183
- }
184
174
  const streamInfos = [];
185
175
  const codecs = tsParser.getCodecs();
186
176
  try {
@@ -189,8 +179,7 @@ shaka.transmuxer.TsTransmuxer = class {
189
179
  switch (codecs.video) {
190
180
  case 'avc':
191
181
  streamInfo =
192
- this.getAvcStreamInfo_(
193
- tsParser, timestamp, stream, duration);
182
+ this.getAvcStreamInfo_(tsParser, stream, duration);
194
183
  break;
195
184
  }
196
185
  if (streamInfo) {
@@ -202,23 +191,19 @@ shaka.transmuxer.TsTransmuxer = class {
202
191
  switch (codecs.audio) {
203
192
  case 'aac':
204
193
  streamInfo =
205
- this.getAacStreamInfo_(
206
- tsParser, timestamp, stream, duration);
194
+ this.getAacStreamInfo_(tsParser, stream, duration);
207
195
  break;
208
196
  case 'ac3':
209
197
  streamInfo =
210
- this.getAc3StreamInfo_(
211
- tsParser, timestamp, stream, duration);
198
+ this.getAc3StreamInfo_(tsParser, stream, duration);
212
199
  break;
213
200
  case 'ec3':
214
201
  streamInfo =
215
- this.getEc3StreamInfo_(
216
- tsParser, timestamp, stream, duration);
202
+ this.getEc3StreamInfo_(tsParser, stream, duration);
217
203
  break;
218
204
  case 'mp3':
219
205
  streamInfo =
220
- this.getMp3StreamInfo_(
221
- tsParser, timestamp, stream, duration);
206
+ this.getMp3StreamInfo_(tsParser, stream, duration);
222
207
  break;
223
208
  }
224
209
  if (streamInfo) {
@@ -255,20 +240,22 @@ shaka.transmuxer.TsTransmuxer = class {
255
240
 
256
241
  /**
257
242
  * @param {shaka.util.TsParser} tsParser
258
- * @param {number} timestamp
259
243
  * @param {shaka.extern.Stream} stream
260
244
  * @param {number} duration
261
245
  * @return {shaka.util.Mp4Generator.StreamInfo}
262
246
  * @private
263
247
  */
264
- getAacStreamInfo_(tsParser, timestamp, stream, duration) {
248
+ getAacStreamInfo_(tsParser, stream, duration) {
265
249
  const ADTS = shaka.transmuxer.ADTS;
250
+ const timescale = shaka.util.TsParser.Timescale;
266
251
 
267
252
  /** @type {!Array.<shaka.util.Mp4Generator.Mp4Sample>} */
268
253
  const samples = [];
269
254
 
270
255
  let info;
271
256
 
257
+ let firstPts = null;
258
+
272
259
  for (const audioData of tsParser.getAudioData()) {
273
260
  const data = audioData.data;
274
261
  if (!data) {
@@ -284,7 +271,9 @@ shaka.transmuxer.TsTransmuxer = class {
284
271
  }
285
272
  stream.audioSamplingRate = info.sampleRate;
286
273
  stream.channelsCount = info.channelCount;
287
-
274
+ if (firstPts == null && audioData.pts !== null) {
275
+ firstPts = audioData.pts;
276
+ }
288
277
  while (offset < data.length) {
289
278
  const header = ADTS.parseHeader(data, offset);
290
279
  if (!header) {
@@ -316,7 +305,7 @@ shaka.transmuxer.TsTransmuxer = class {
316
305
  }
317
306
  }
318
307
 
319
- if (!info) {
308
+ if (!info || firstPts == null) {
320
309
  throw new shaka.util.Error(
321
310
  shaka.util.Error.Severity.CRITICAL,
322
311
  shaka.util.Error.Category.MEDIA,
@@ -326,7 +315,7 @@ shaka.transmuxer.TsTransmuxer = class {
326
315
  /** @type {number} */
327
316
  const sampleRate = info.sampleRate;
328
317
  /** @type {number} */
329
- const baseMediaDecodeTime = Math.floor(timestamp * sampleRate / 1000);
318
+ const baseMediaDecodeTime = firstPts / timescale * sampleRate;
330
319
 
331
320
  return {
332
321
  id: stream.id,
@@ -350,14 +339,14 @@ shaka.transmuxer.TsTransmuxer = class {
350
339
 
351
340
  /**
352
341
  * @param {shaka.util.TsParser} tsParser
353
- * @param {number} timestamp
354
342
  * @param {shaka.extern.Stream} stream
355
343
  * @param {number} duration
356
344
  * @return {shaka.util.Mp4Generator.StreamInfo}
357
345
  * @private
358
346
  */
359
- getAc3StreamInfo_(tsParser, timestamp, stream, duration) {
347
+ getAc3StreamInfo_(tsParser, stream, duration) {
360
348
  const Ac3 = shaka.transmuxer.Ac3;
349
+ const timescale = shaka.util.TsParser.Timescale;
361
350
 
362
351
  /** @type {!Array.<shaka.util.Mp4Generator.Mp4Sample>} */
363
352
  const samples = [];
@@ -368,8 +357,13 @@ shaka.transmuxer.TsTransmuxer = class {
368
357
  /** @type {!Uint8Array} */
369
358
  let audioConfig = new Uint8Array([]);
370
359
 
360
+ let firstPts = null;
361
+
371
362
  for (const audioData of tsParser.getAudioData()) {
372
363
  const data = audioData.data;
364
+ if (firstPts == null && audioData.pts !== null) {
365
+ firstPts = audioData.pts;
366
+ }
373
367
  let offset = 0;
374
368
  while (offset < data.length) {
375
369
  const frame = Ac3.parseFrame(data, offset);
@@ -403,7 +397,7 @@ shaka.transmuxer.TsTransmuxer = class {
403
397
  }
404
398
  }
405
399
 
406
- if (sampleRate == 0 || audioConfig.byteLength == 0) {
400
+ if (sampleRate == 0 || audioConfig.byteLength == 0 || firstPts == null) {
407
401
  throw new shaka.util.Error(
408
402
  shaka.util.Error.Severity.CRITICAL,
409
403
  shaka.util.Error.Category.MEDIA,
@@ -412,7 +406,7 @@ shaka.transmuxer.TsTransmuxer = class {
412
406
 
413
407
 
414
408
  /** @type {number} */
415
- const baseMediaDecodeTime = Math.floor(timestamp * sampleRate / 1000);
409
+ const baseMediaDecodeTime = firstPts / timescale * sampleRate;
416
410
 
417
411
  return {
418
412
  id: stream.id,
@@ -436,14 +430,14 @@ shaka.transmuxer.TsTransmuxer = class {
436
430
 
437
431
  /**
438
432
  * @param {shaka.util.TsParser} tsParser
439
- * @param {number} timestamp
440
433
  * @param {shaka.extern.Stream} stream
441
434
  * @param {number} duration
442
435
  * @return {shaka.util.Mp4Generator.StreamInfo}
443
436
  * @private
444
437
  */
445
- getEc3StreamInfo_(tsParser, timestamp, stream, duration) {
438
+ getEc3StreamInfo_(tsParser, stream, duration) {
446
439
  const Ec3 = shaka.transmuxer.Ec3;
440
+ const timescale = shaka.util.TsParser.Timescale;
447
441
 
448
442
  /** @type {!Array.<shaka.util.Mp4Generator.Mp4Sample>} */
449
443
  const samples = [];
@@ -454,8 +448,13 @@ shaka.transmuxer.TsTransmuxer = class {
454
448
  /** @type {!Uint8Array} */
455
449
  let audioConfig = new Uint8Array([]);
456
450
 
451
+ let firstPts = null;
452
+
457
453
  for (const audioData of tsParser.getAudioData()) {
458
454
  const data = audioData.data;
455
+ if (firstPts == null && audioData.pts !== null) {
456
+ firstPts = audioData.pts;
457
+ }
459
458
  let offset = 0;
460
459
  while (offset < data.length) {
461
460
  const frame = Ec3.parseFrame(data, offset);
@@ -489,7 +488,7 @@ shaka.transmuxer.TsTransmuxer = class {
489
488
  }
490
489
  }
491
490
 
492
- if (sampleRate == 0 || audioConfig.byteLength == 0) {
491
+ if (sampleRate == 0 || audioConfig.byteLength == 0 || firstPts == null) {
493
492
  throw new shaka.util.Error(
494
493
  shaka.util.Error.Severity.CRITICAL,
495
494
  shaka.util.Error.Category.MEDIA,
@@ -498,7 +497,7 @@ shaka.transmuxer.TsTransmuxer = class {
498
497
 
499
498
 
500
499
  /** @type {number} */
501
- const baseMediaDecodeTime = Math.floor(timestamp * sampleRate / 1000);
500
+ const baseMediaDecodeTime = firstPts / timescale * sampleRate;
502
501
 
503
502
  return {
504
503
  id: stream.id,
@@ -522,25 +521,30 @@ shaka.transmuxer.TsTransmuxer = class {
522
521
 
523
522
  /**
524
523
  * @param {shaka.util.TsParser} tsParser
525
- * @param {number} timestamp
526
524
  * @param {shaka.extern.Stream} stream
527
525
  * @param {number} duration
528
526
  * @return {shaka.util.Mp4Generator.StreamInfo}
529
527
  * @private
530
528
  */
531
- getMp3StreamInfo_(tsParser, timestamp, stream, duration) {
529
+ getMp3StreamInfo_(tsParser, stream, duration) {
532
530
  const MpegAudio = shaka.transmuxer.MpegAudio;
531
+ const timescale = shaka.util.TsParser.Timescale;
533
532
 
534
533
  /** @type {!Array.<shaka.util.Mp4Generator.Mp4Sample>} */
535
534
  const samples = [];
536
535
 
537
536
  let firstHeader;
538
537
 
538
+ let firstPts = null;
539
+
539
540
  for (const audioData of tsParser.getAudioData()) {
540
541
  const data = audioData.data;
541
542
  if (!data) {
542
543
  continue;
543
544
  }
545
+ if (firstPts == null && audioData.pts !== null) {
546
+ firstPts = audioData.pts;
547
+ }
544
548
  let offset = 0;
545
549
  while (offset < data.length) {
546
550
  const header = MpegAudio.parseHeader(data, offset);
@@ -570,7 +574,7 @@ shaka.transmuxer.TsTransmuxer = class {
570
574
  offset += header.frameLength;
571
575
  }
572
576
  }
573
- if (!firstHeader) {
577
+ if (!firstHeader || firstPts == null) {
574
578
  throw new shaka.util.Error(
575
579
  shaka.util.Error.Severity.CRITICAL,
576
580
  shaka.util.Error.Category.MEDIA,
@@ -579,7 +583,7 @@ shaka.transmuxer.TsTransmuxer = class {
579
583
  /** @type {number} */
580
584
  const sampleRate = firstHeader.sampleRate;
581
585
  /** @type {number} */
582
- const baseMediaDecodeTime = Math.floor(timestamp * sampleRate / 1000);
586
+ const baseMediaDecodeTime = firstPts / timescale * sampleRate;
583
587
 
584
588
  return {
585
589
  id: stream.id,
@@ -603,13 +607,12 @@ shaka.transmuxer.TsTransmuxer = class {
603
607
 
604
608
  /**
605
609
  * @param {shaka.util.TsParser} tsParser
606
- * @param {number} timestamp
607
610
  * @param {shaka.extern.Stream} stream
608
611
  * @param {number} duration
609
612
  * @return {shaka.util.Mp4Generator.StreamInfo}
610
613
  * @private
611
614
  */
612
- getAvcStreamInfo_(tsParser, timestamp, stream, duration) {
615
+ getAvcStreamInfo_(tsParser, stream, duration) {
613
616
  const H264 = shaka.transmuxer.H264;
614
617
  const timescale = shaka.util.TsParser.Timescale;
615
618
 
@@ -620,9 +623,7 @@ shaka.transmuxer.TsTransmuxer = class {
620
623
  let baseMediaDecodeTime = null;
621
624
 
622
625
  let nalus = [];
623
- const videoData = tsParser.getVideoData().filter((pes) => {
624
- return pes.pts != null && pes.dts != null;
625
- });
626
+ const videoData = tsParser.getVideoData();
626
627
  for (let i = 0; i < videoData.length; i++) {
627
628
  const pes = videoData[i];
628
629
  let nextPes;
@@ -635,7 +636,7 @@ shaka.transmuxer.TsTransmuxer = class {
635
636
  if (!frame) {
636
637
  continue;
637
638
  }
638
- if (baseMediaDecodeTime == null && pes.dts != null) {
639
+ if (baseMediaDecodeTime == null) {
639
640
  baseMediaDecodeTime = pes.dts;
640
641
  }
641
642
  let duration;
@@ -406,12 +406,17 @@ shaka.util.CmcdManager = class {
406
406
  * @private
407
407
  */
408
408
  getObjectTypeFromMimeType_(mimeType) {
409
- switch (mimeType) {
409
+ switch (mimeType.toLowerCase()) {
410
410
  case 'video/webm':
411
411
  case 'video/mp4':
412
+ case 'video/mpeg':
413
+ case 'video/mp2t':
412
414
  return shaka.util.CmcdManager.ObjectType.MUXED;
413
415
 
414
416
  case 'application/x-mpegurl':
417
+ case 'application/vnd.apple.mpegurl':
418
+ case 'application/dash+xml':
419
+ case 'video/vnd.mpeg.dash.mpd':
415
420
  return shaka.util.CmcdManager.ObjectType.MANIFEST;
416
421
 
417
422
  default:
package/lib/util/error.js CHANGED
@@ -63,8 +63,11 @@ shaka.util.Error = class {
63
63
  */
64
64
  this.handled = false;
65
65
 
66
- // This improves the formatting of Errors in failure messages in the tests.
66
+ // A basic message for compiled mode.
67
+ let formattedMessage = 'Shaka Error ' + this.code;
67
68
  if (goog.DEBUG) {
69
+ // This improves the formatting of Errors in failure messages in the
70
+ // tests in debug mode.
68
71
  let categoryName = 'UNKNOWN';
69
72
  let codeName = 'UNKNOWN';
70
73
 
@@ -79,17 +82,20 @@ shaka.util.Error = class {
79
82
  }
80
83
  }
81
84
 
82
- /**
83
- * A human-readable version of the category and code.
84
- * <i>(Only available in uncompiled mode.)</i>
85
- *
86
- * @const {string}
87
- * @exportDoc
88
- */
89
- this.message = 'Shaka Error ' + categoryName + '.' + codeName +
90
- ' (' + this.data.toString() + ')';
85
+ formattedMessage = 'Shaka Error ' + categoryName + '.' + codeName +
86
+ ' (' + this.data.toString() + ')';
91
87
  }
92
88
 
89
+ /**
90
+ * In compiled mode, contains a basic message string with the error code.
91
+ * In uncompiled and debug modes, contains a human-readable version of the
92
+ * category and code as enums.
93
+ *
94
+ * @const {string}
95
+ * @exportDoc
96
+ */
97
+ this.message = formattedMessage;
98
+
93
99
  if (shaka.util.Error.createStack) {
94
100
  try {
95
101
  throw new Error(this.message || 'Shaka Error');
@@ -32,13 +32,19 @@ shaka.util.ManifestParserUtils = class {
32
32
  return baseUris;
33
33
  }
34
34
 
35
+ if (baseUris.length == 1 && relativeUris.length == 1) {
36
+ const baseUri = new goog.Uri(baseUris[0]);
37
+ const relativeUri = new goog.Uri(relativeUris[0]);
38
+ return [baseUri.resolve(relativeUri).toString()];
39
+ }
40
+
35
41
  const relativeAsGoog = relativeUris.map((uri) => new goog.Uri(uri));
36
42
  // Resolve each URI relative to each base URI, creating an Array of Arrays.
37
43
  // Then flatten the Arrays into a single Array.
38
- return baseUris.map((uri) => new goog.Uri(uri))
39
- .map((base) => relativeAsGoog.map((i) => base.resolve(i)))
40
- .reduce(Functional.collapseArrays, [])
41
- .map((uri) => uri.toString());
44
+ return baseUris.map((uri) => {
45
+ const base = new goog.Uri(uri);
46
+ return relativeAsGoog.map((i) => base.resolve(i).toString());
47
+ }).reduce(Functional.collapseArrays, []);
42
48
  }
43
49
 
44
50
 
@@ -368,6 +368,20 @@ shaka.util.Platform = class {
368
368
  return navigator.platform.toLowerCase().includes('mac');
369
369
  }
370
370
 
371
+ /**
372
+ * Return true if the platform is a Windows, regardless of the browser.
373
+ *
374
+ * @return {boolean}
375
+ */
376
+ static isWindows() {
377
+ // Try the newer standard first.
378
+ if (navigator.userAgentData && navigator.userAgentData.platform) {
379
+ return navigator.userAgentData.platform.toLowerCase() == 'windows';
380
+ }
381
+ // Fall back to the old API, with less strict matching.
382
+ return navigator.platform.toLowerCase().includes('windows');
383
+ }
384
+
371
385
  /**
372
386
  * Check if the user agent contains a key. This is the best way we know of
373
387
  * right now to detect platforms. If there is a better way, please send a
@@ -71,8 +71,8 @@ shaka.util.PlayerConfiguration = class {
71
71
  persistentSessionOnlinePlayback: false,
72
72
  persistentSessionsMetadata: [],
73
73
  initDataTransform: (initData, initDataType, drmInfo) => {
74
- const keySystem = drmInfo.keySystem;
75
- if (keySystem == 'com.apple.fps.1_0' && initDataType == 'skd') {
74
+ if (shaka.util.Platform.isMediaKeysPolyfilled() &&
75
+ initDataType == 'skd') {
76
76
  const cert = drmInfo.serverCertificate;
77
77
  const contentId =
78
78
  shaka.util.FairPlayUtils.defaultGetContentId(initData);
@@ -1543,22 +1543,23 @@ shaka.util.StreamUtils = class {
1543
1543
 
1544
1544
  /**
1545
1545
  * Chooses streams according to the given config.
1546
+ * Works both for Stream and Track types due to their similarities.
1546
1547
  *
1547
- * @param {!Array.<shaka.extern.Stream>} streams
1548
+ * @param {!Array<!shaka.extern.Stream>|!Array<!shaka.extern.Track>} streams
1548
1549
  * @param {string} preferredLanguage
1549
1550
  * @param {string} preferredRole
1550
1551
  * @param {boolean} preferredForced
1551
- * @return {!Array.<!shaka.extern.Stream>}
1552
+ * @return {!Array<!shaka.extern.Stream>|!Array<!shaka.extern.Track>}
1552
1553
  */
1553
1554
  static filterStreamsByLanguageAndRole(
1554
1555
  streams, preferredLanguage, preferredRole, preferredForced) {
1555
1556
  const LanguageUtils = shaka.util.LanguageUtils;
1556
1557
 
1557
- /** @type {!Array.<!shaka.extern.Stream>} */
1558
+ /** @type {!Array<!shaka.extern.Stream>|!Array<!shaka.extern.Track>} */
1558
1559
  let chosen = streams;
1559
1560
 
1560
1561
  // Start with the set of primary streams.
1561
- /** @type {!Array.<!shaka.extern.Stream>} */
1562
+ /** @type {!Array<!shaka.extern.Stream>|!Array<!shaka.extern.Track>} */
1562
1563
  const primary = streams.filter((stream) => {
1563
1564
  return stream.primary;
1564
1565
  });
@@ -1598,7 +1599,7 @@ shaka.util.StreamUtils = class {
1598
1599
 
1599
1600
  // Now refine the choice based on role preference.
1600
1601
  if (preferredRole) {
1601
- const roleMatches = shaka.util.StreamUtils.filterTextStreamsByRole_(
1602
+ const roleMatches = shaka.util.StreamUtils.filterStreamsByRole_(
1602
1603
  chosen, preferredRole);
1603
1604
  if (roleMatches.length) {
1604
1605
  return roleMatches;
@@ -1626,20 +1627,21 @@ shaka.util.StreamUtils = class {
1626
1627
  if (!allRoles.length) {
1627
1628
  return chosen;
1628
1629
  }
1629
- return shaka.util.StreamUtils.filterTextStreamsByRole_(chosen, allRoles[0]);
1630
+ return shaka.util.StreamUtils.filterStreamsByRole_(chosen, allRoles[0]);
1630
1631
  }
1631
1632
 
1632
1633
 
1633
1634
  /**
1634
- * Filter text Streams by role.
1635
+ * Filter Streams by role.
1636
+ * Works both for Stream and Track types due to their similarities.
1635
1637
  *
1636
- * @param {!Array.<shaka.extern.Stream>} textStreams
1638
+ * @param {!Array<!shaka.extern.Stream>|!Array<!shaka.extern.Track>} streams
1637
1639
  * @param {string} preferredRole
1638
- * @return {!Array.<shaka.extern.Stream>}
1640
+ * @return {!Array<!shaka.extern.Stream>|!Array<!shaka.extern.Track>}
1639
1641
  * @private
1640
1642
  */
1641
- static filterTextStreamsByRole_(textStreams, preferredRole) {
1642
- return textStreams.filter((stream) => {
1643
+ static filterStreamsByRole_(streams, preferredRole) {
1644
+ return streams.filter((stream) => {
1643
1645
  return stream.roles.includes(preferredRole);
1644
1646
  });
1645
1647
  }
@@ -6,6 +6,7 @@
6
6
 
7
7
  goog.provide('shaka.util.TsParser');
8
8
 
9
+ goog.require('shaka.Deprecate');
9
10
  goog.require('shaka.log');
10
11
  goog.require('shaka.util.ExpGolomb');
11
12
  goog.require('shaka.util.Id3Utils');
@@ -128,15 +129,15 @@ shaka.util.TsParser = class {
128
129
  // for example
129
130
  // NOTE this is only the PID of the track as found in TS,
130
131
  // but we are not using this for MP4 track IDs.
131
- if (this.videoPid_ == null) {
132
+ if (parsedPIDs.video != -1) {
132
133
  this.videoPid_ = parsedPIDs.video;
133
134
  this.videoCodec_ = parsedPIDs.videoCodec;
134
135
  }
135
- if (this.audioPid_ == null) {
136
+ if (parsedPIDs.audio != -1) {
136
137
  this.audioPid_ = parsedPIDs.audio;
137
138
  this.audioCodec_ = parsedPIDs.audioCodec;
138
139
  }
139
- if (this.id3Pid_ == null) {
140
+ if (parsedPIDs.id3 != -1) {
140
141
  this.id3Pid_ = parsedPIDs.id3;
141
142
  }
142
143
 
@@ -361,6 +362,12 @@ shaka.util.TsParser = class {
361
362
  dts: null,
362
363
  };
363
364
 
365
+ // if PES parsed length is not zero and greater than total received length,
366
+ // stop parsing. PES might be truncated. minus 6 : PES header size
367
+ if (pes.packetLength && pes.packetLength > data.byteLength - 6) {
368
+ return null;
369
+ }
370
+
364
371
  // PES packets may be annotated with a PTS value, or a PTS value
365
372
  // and a DTS value. Determine what combination of values is
366
373
  // available to work with.
@@ -395,10 +402,15 @@ shaka.util.TsParser = class {
395
402
  (data[18] & 0xfe) / 2;
396
403
  }
397
404
  }
398
- // the data section starts immediately after the PES header.
399
- // pes_header_data_length specifies the number of header bytes
400
- // that follow the last byte of the field.
401
- pes.data = data.subarray(9 + data[8]);
405
+
406
+ const pesHdrLen = data[8];
407
+ // 9 bytes : 6 bytes for PES header + 3 bytes for PES extension
408
+ const payloadStartOffset = pesHdrLen + 9;
409
+ if (data.byteLength <= payloadStartOffset) {
410
+ return null;
411
+ }
412
+
413
+ pes.data = data.subarray(payloadStartOffset);
402
414
 
403
415
  return pes;
404
416
  }
@@ -450,6 +462,10 @@ shaka.util.TsParser = class {
450
462
  // previous Nalu and don't scan for more nalus.
451
463
  const startCodeSize = numZeros > 3 ? 3 : numZeros;
452
464
  const lastByteToKeep = i - startCodeSize;
465
+ // Optimization
466
+ if (lastByteToKeep == 0) {
467
+ break;
468
+ }
453
469
  infoOfLastNalu.data = shaka.util.Uint8ArrayUtils.concat(
454
470
  infoOfLastNalu.data, data.subarray(0, lastByteToKeep));
455
471
  infoOfLastNalu.fullData = shaka.util.Uint8ArrayUtils.concat(
@@ -575,11 +591,21 @@ shaka.util.TsParser = class {
575
591
  * @export
576
592
  */
577
593
  getVideoData() {
594
+ const Uint8ArrayUtils = shaka.util.Uint8ArrayUtils;
578
595
  const video = [];
579
596
  for (const videoData of this.videoData_) {
580
597
  const pes = this.parsePES_(videoData);
581
- if (pes) {
598
+ if (pes && pes.pts != null && pes.dts != null) {
582
599
  video.push(pes);
600
+ } else if (video.length) {
601
+ const data = pes ? pes.data : videoData;
602
+ if (!data) {
603
+ continue;
604
+ }
605
+ const previousPes = video.pop();
606
+ previousPes.data =
607
+ Uint8ArrayUtils.concat(previousPes.data, data);
608
+ video.push(previousPes);
583
609
  }
584
610
  }
585
611
  return video;
@@ -660,29 +686,47 @@ shaka.util.TsParser = class {
660
686
  * @export
661
687
  */
662
688
  getVideoResolution() {
689
+ shaka.Deprecate.deprecateFeature(5,
690
+ 'TsParser',
691
+ 'Please use getVideoInfo function instead.');
692
+ const videoInfo = this.getVideoInfo();
693
+ return {
694
+ height: videoInfo.height,
695
+ width: videoInfo.width,
696
+ };
697
+ }
698
+
699
+ /**
700
+ * Return the video information
701
+ *
702
+ * @return {{height: ?string, width: ?string, codec: ?string}}
703
+ * @export
704
+ */
705
+ getVideoInfo() {
663
706
  const TsParser = shaka.util.TsParser;
664
- const resolution = {
707
+ const videoInfo = {
665
708
  height: null,
666
709
  width: null,
710
+ codec: null,
667
711
  };
668
712
  const videoNalus = this.getVideoNalus();
669
713
  if (!videoNalus.length) {
670
- return resolution;
714
+ return videoInfo;
671
715
  }
672
716
  const spsNalu = videoNalus.find((nalu) => {
673
717
  return nalu.type == TsParser.H264_NALU_TYPE_SPS_;
674
718
  });
675
719
  if (!spsNalu) {
676
- return resolution;
720
+ return videoInfo;
677
721
  }
678
722
 
679
723
  const expGolombDecoder = new shaka.util.ExpGolomb(spsNalu.data);
680
724
  // profile_idc
681
725
  const profileIdc = expGolombDecoder.readUnsignedByte();
682
726
  // constraint_set[0-5]_flag
683
- expGolombDecoder.readUnsignedByte();
727
+ const profileCompatibility = expGolombDecoder.readUnsignedByte();
684
728
  // level_idc u(8)
685
- expGolombDecoder.readUnsignedByte();
729
+ const levelIdc = expGolombDecoder.readUnsignedByte();
686
730
  // seq_parameter_set_id
687
731
  expGolombDecoder.skipExpGolomb();
688
732
 
@@ -768,13 +812,26 @@ shaka.util.TsParser = class {
768
812
  frameCropBottomOffset = expGolombDecoder.readUnsignedExpGolomb();
769
813
  }
770
814
 
771
- resolution.height = String(((2 - frameMbsOnlyFlag) *
815
+ videoInfo.height = String(((2 - frameMbsOnlyFlag) *
772
816
  (picHeightInMapUnitsMinus1 + 1) * 16) - (frameCropTopOffset * 2) -
773
817
  (frameCropBottomOffset * 2));
774
- resolution.width = String(((picWidthInMbsMinus1 + 1) * 16) -
818
+ videoInfo.width = String(((picWidthInMbsMinus1 + 1) * 16) -
775
819
  frameCropLeftOffset * 2 - frameCropRightOffset * 2);
820
+ videoInfo.codec = 'avc1.' + this.byteToHex_(profileIdc) +
821
+ this.byteToHex_(profileCompatibility) + this.byteToHex_(levelIdc);
776
822
 
777
- return resolution;
823
+ return videoInfo;
824
+ }
825
+
826
+ /**
827
+ * Convert a byte to 2 digits of hex. (Only handles values 0-255.)
828
+ *
829
+ * @param {number} x
830
+ * @return {string}
831
+ * @private
832
+ */
833
+ byteToHex_(x) {
834
+ return ('0' + x.toString(16).toUpperCase()).slice(-2);
778
835
  }
779
836
 
780
837
  /**
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "shaka-player",
3
3
  "description": "DASH/EME video player library",
4
- "version": "4.4.1",
4
+ "version": "4.4.3",
5
5
  "homepage": "https://github.com/shaka-project/shaka-player",
6
6
  "author": "Google",
7
7
  "maintainers": [
@@ -43,7 +43,7 @@
43
43
  "karma-coverage": "^2.2.0",
44
44
  "karma-jasmine": "^4.0.1",
45
45
  "karma-jasmine-ajax": "^0.1.13",
46
- "karma-local-wd-launcher": "^1.6.4",
46
+ "karma-local-wd-launcher": "^1.6.6",
47
47
  "karma-opera-launcher": "^1.0.0",
48
48
  "karma-sourcemap-loader": "^0.3.8",
49
49
  "karma-spec-reporter": "^0.0.34",
@@ -57,6 +57,11 @@ describe('DashParser Manifest', () => {
57
57
  };
58
58
  });
59
59
 
60
+ afterEach(() => {
61
+ // Dash parser stop is synchronous.
62
+ parser.stop();
63
+ });
64
+
60
65
  /**
61
66
  * Makes a series of tests for the given manifest type.
62
67
  *