shaka-player 4.4.0 → 4.4.1

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 (46) hide show
  1. package/.github/workflows/release-please.yaml +21 -13
  2. package/.github/workflows/report-incremental-coverage.yaml +2 -2
  3. package/.github/workflows/shaka-bot-commands/command-help.sh +27 -0
  4. package/.github/workflows/shaka-bot-commands/command-test.sh +56 -0
  5. package/.github/workflows/shaka-bot-commands/lib.sh +83 -0
  6. package/.github/workflows/shaka-bot-commands/main.sh +51 -0
  7. package/.github/workflows/talk-to-shaka-bot.yaml +29 -0
  8. package/.github/workflows/update-issues.yaml +2 -1
  9. package/.github/workflows/validate-pr-title.yaml +3 -11
  10. package/CHANGELOG.md +13 -0
  11. package/dist/deps.js +1 -1
  12. package/dist/locales.js +32 -32
  13. package/dist/shaka-player.compiled.d.ts +297 -297
  14. package/dist/shaka-player.compiled.debug.d.ts +297 -297
  15. package/dist/shaka-player.compiled.debug.externs.js +1 -1
  16. package/dist/shaka-player.compiled.debug.js +221 -222
  17. package/dist/shaka-player.compiled.debug.map +3 -3
  18. package/dist/shaka-player.compiled.externs.js +1 -1
  19. package/dist/shaka-player.compiled.js +947 -946
  20. package/dist/shaka-player.compiled.map +3 -3
  21. package/dist/shaka-player.ui.d.ts +341 -341
  22. package/dist/shaka-player.ui.debug.d.ts +341 -341
  23. package/dist/shaka-player.ui.debug.externs.js +1 -1
  24. package/dist/shaka-player.ui.debug.js +282 -283
  25. package/dist/shaka-player.ui.debug.map +3 -3
  26. package/dist/shaka-player.ui.externs.js +1 -1
  27. package/dist/shaka-player.ui.js +566 -564
  28. package/dist/shaka-player.ui.map +3 -3
  29. package/externs/shaka/namespace.js +2 -2
  30. package/externs/shaka/transmuxer.js +2 -1
  31. package/lib/hls/hls_parser.js +1 -0
  32. package/lib/media/media_source_engine.js +22 -2
  33. package/lib/mss/mss_parser.js +1 -0
  34. package/lib/player.js +1 -1
  35. package/lib/text/simple_text_displayer.js +0 -1
  36. package/lib/transmuxer/aac_transmuxer.js +1 -1
  37. package/lib/transmuxer/ac3_transmuxer.js +1 -0
  38. package/lib/transmuxer/ec3_transmuxer.js +1 -0
  39. package/lib/transmuxer/mp3_transmuxer.js +1 -0
  40. package/lib/transmuxer/ts_transmuxer.js +54 -71
  41. package/lib/util/mp4_generator.js +27 -20
  42. package/lib/util/ts_parser.js +73 -110
  43. package/package.json +1 -1
  44. package/test/hls/hls_parser_unit.js +12 -13
  45. package/test/media/media_source_engine_integration.js +2 -2
  46. package/test/mss/mss_player_integration.js +1 -1
@@ -6,7 +6,6 @@
6
6
 
7
7
  goog.provide('shaka.util.TsParser');
8
8
 
9
- goog.require('goog.asserts');
10
9
  goog.require('shaka.log');
11
10
  goog.require('shaka.util.ExpGolomb');
12
11
  goog.require('shaka.util.Id3Utils');
@@ -26,9 +25,6 @@ shaka.util.TsParser = class {
26
25
  /** @private {boolean} */
27
26
  this.pmtParsed_ = false;
28
27
 
29
- /** @private {?number} */
30
- this.videoStartTime_ = null;
31
-
32
28
  /** @private {?number} */
33
29
  this.videoPid_ = null;
34
30
 
@@ -38,9 +34,6 @@ shaka.util.TsParser = class {
38
34
  /** @private {!Array.<Uint8Array>} */
39
35
  this.videoData_ = [];
40
36
 
41
- /** @private {?number} */
42
- this.audioStartTime_ = null;
43
-
44
37
  /** @private {?number} */
45
38
  this.audioPid_ = null;
46
39
 
@@ -63,9 +56,7 @@ shaka.util.TsParser = class {
63
56
  * @export
64
57
  */
65
58
  clearData() {
66
- this.videoStartTime_ = null;
67
59
  this.videoData_ = [];
68
- this.audioStartTime_ = null;
69
60
  this.audioData_ = [];
70
61
  this.id3Data_ = [];
71
62
  }
@@ -78,8 +69,8 @@ shaka.util.TsParser = class {
78
69
  * @export
79
70
  */
80
71
  parse(data) {
81
- const timescale = shaka.util.TsParser.Timescale;
82
72
  const packetLength = shaka.util.TsParser.PacketLength_;
73
+ const Uint8ArrayUtils = shaka.util.Uint8ArrayUtils;
83
74
 
84
75
  // A TS fragment should contain at least 3 TS packets, a PAT, a PMT, and
85
76
  // one PID.
@@ -161,33 +152,43 @@ shaka.util.TsParser = class {
161
152
  }
162
153
  case this.videoPid_: {
163
154
  const videoData = data.subarray(offset, start + packetLength);
164
- const pes = this.parsePES_(videoData, /* isVideo= */ true);
165
- if (pes && pes.pts != null) {
166
- const startTime = Math.min(pes.dts, pes.pts) / timescale;
167
- if (this.videoStartTime_ == null ||
168
- this.videoStartTime_ > startTime) {
169
- this.videoStartTime_ = startTime;
155
+ if (payloadUnitStartIndicator) {
156
+ this.videoData_.push(videoData);
157
+ } else if (this.videoData_.length) {
158
+ const prevVideoData = this.videoData_[this.videoData_.length - 1];
159
+ if (prevVideoData) {
160
+ this.videoData_[this.videoData_.length - 1] =
161
+ Uint8ArrayUtils.concat(prevVideoData, videoData);
170
162
  }
171
163
  }
172
- this.videoData_.push(videoData);
173
164
  break;
174
165
  }
175
166
  case this.audioPid_: {
176
167
  const audioData = data.subarray(offset, start + packetLength);
177
- const pes = this.parsePES_(audioData);
178
- if (pes && pes.pts != null) {
179
- const startTime = Math.min(pes.dts, pes.pts) / timescale;
180
- if (this.audioStartTime_ == null ||
181
- this.audioStartTime_ > startTime) {
182
- this.audioStartTime_ = startTime;
168
+ if (payloadUnitStartIndicator) {
169
+ this.audioData_.push(audioData);
170
+ } else if (this.audioData_.length) {
171
+ const prevAudioData = this.audioData_[this.audioData_.length - 1];
172
+ if (prevAudioData) {
173
+ this.audioData_[this.audioData_.length - 1] =
174
+ Uint8ArrayUtils.concat(prevAudioData, audioData);
183
175
  }
184
176
  }
185
- this.audioData_.push(audioData);
186
177
  break;
187
178
  }
188
- case this.id3Pid_:
189
- this.id3Data_.push(data.subarray(offset, start + packetLength));
179
+ case this.id3Pid_: {
180
+ const id3Data = data.subarray(offset, start + packetLength);
181
+ if (payloadUnitStartIndicator) {
182
+ this.id3Data_.push(id3Data);
183
+ } else if (this.id3Data_.length) {
184
+ const prevId3Data = this.id3Data_[this.id3Data_.length - 1];
185
+ if (prevId3Data) {
186
+ this.id3Data_[this.id3Data_.length - 1] =
187
+ Uint8ArrayUtils.concat(prevId3Data, id3Data);
188
+ }
189
+ }
190
190
  break;
191
+ }
191
192
  default:
192
193
  unknownPIDs = true;
193
194
  break;
@@ -340,11 +341,10 @@ shaka.util.TsParser = class {
340
341
  * Parse PES
341
342
  *
342
343
  * @param {Uint8Array} data
343
- * @param {boolean=} isVideo
344
344
  * @return {?shaka.extern.MPEG_PES}
345
345
  * @private
346
346
  */
347
- parsePES_(data, isVideo = false) {
347
+ parsePES_(data) {
348
348
  const startPrefix = (data[0] << 16) | (data[1] << 8) | data[2];
349
349
  // In certain live streams, the start of a TS fragment has ts packets
350
350
  // that are frame data that is continuing from the previous fragment. This
@@ -361,12 +361,6 @@ shaka.util.TsParser = class {
361
361
  dts: null,
362
362
  };
363
363
 
364
- // If it is video, we expect the packet length to be 0. When the PES packet
365
- // length is set to zero, the PES packet can be any length.
366
- if (isVideo && pes.packetLength != 0) {
367
- return null;
368
- }
369
-
370
364
  // PES packets may be annotated with a PTS value, or a PTS value
371
365
  // and a DTS value. Determine what combination of values is
372
366
  // available to work with.
@@ -454,13 +448,12 @@ shaka.util.TsParser = class {
454
448
  } else if (numZeros >= 2 && value == 1 && tryToFinishLastNalu) {
455
449
  // If we are scanning the next PES, we need append the data to the
456
450
  // previous Nalu and don't scan for more nalus.
457
- if (i <= 2) {
458
- break;
459
- }
451
+ const startCodeSize = numZeros > 3 ? 3 : numZeros;
452
+ const lastByteToKeep = i - startCodeSize;
460
453
  infoOfLastNalu.data = shaka.util.Uint8ArrayUtils.concat(
461
- infoOfLastNalu.data, data.subarray(0, i - 2));
454
+ infoOfLastNalu.data, data.subarray(0, lastByteToKeep));
462
455
  infoOfLastNalu.fullData = shaka.util.Uint8ArrayUtils.concat(
463
- infoOfLastNalu.fullData, data.subarray(0, i - 2));
456
+ infoOfLastNalu.fullData, data.subarray(0, lastByteToKeep));
464
457
  break;
465
458
  } else if (numZeros >= 2 && value == 1) {
466
459
  // We just read a start code. Consume the NALU we passed, if any.
@@ -519,7 +512,7 @@ shaka.util.TsParser = class {
519
512
  type: lastNaluType,
520
513
  time: time,
521
514
  };
522
- if (nextPes) {
515
+ if (nextPes && pes.packetLength == 0) {
523
516
  data = nextPes.data;
524
517
  len = data.byteLength;
525
518
  i = -1;
@@ -542,30 +535,17 @@ shaka.util.TsParser = class {
542
535
  */
543
536
  getMetadata() {
544
537
  const timescale = shaka.util.TsParser.Timescale;
545
- const Uint8ArrayUtils = shaka.util.Uint8ArrayUtils;
546
538
  const metadata = [];
547
- let prevId3Data = new Uint8Array(0);
548
- // parsePES_() only works if the data begins on a PES boundary.
549
- // Try the last data blob first, and if it doesn't begin on a
550
- // PES boundary, prepend the previous blob and try again.
551
- // This way, a successful parse will always begin and end on
552
- // the correct boundary, and no data will be skipped.
553
- for (let i = this.id3Data_.length - 1; i >= 0; i--) {
554
- const data = this.id3Data_[i];
555
- goog.asserts.assert(data, 'We should have a data');
556
- const id3Data = Uint8ArrayUtils.concat(data, prevId3Data);
539
+ for (const id3Data of this.id3Data_) {
557
540
  const pes = this.parsePES_(id3Data);
558
541
  if (pes) {
559
- metadata.unshift({
542
+ metadata.push({
560
543
  cueTime: pes.pts ? pes.pts / timescale : null,
561
544
  data: pes.data,
562
545
  frames: shaka.util.Id3Utils.getID3Frames(pes.data),
563
546
  dts: pes.dts,
564
547
  pts: pes.pts,
565
548
  });
566
- prevId3Data = new Uint8Array(0);
567
- } else {
568
- prevId3Data = id3Data;
569
549
  }
570
550
  }
571
551
  return metadata;
@@ -578,24 +558,11 @@ shaka.util.TsParser = class {
578
558
  * @export
579
559
  */
580
560
  getAudioData() {
581
- const Uint8ArrayUtils = shaka.util.Uint8ArrayUtils;
582
561
  const audio = [];
583
- let prevAudioData = new Uint8Array(0);
584
- // parsePES_() only works if the data begins on a PES boundary.
585
- // Try the last data blob first, and if it doesn't begin on a
586
- // PES boundary, prepend the previous blob and try again.
587
- // This way, a successful parse will always begin and end on
588
- // the correct boundary, and no data will be skipped.
589
- for (let i = this.audioData_.length - 1; i >= 0; i--) {
590
- const data = this.audioData_[i];
591
- goog.asserts.assert(data, 'We should have a data');
592
- const audioData = Uint8ArrayUtils.concat(data, prevAudioData);
562
+ for (const audioData of this.audioData_) {
593
563
  const pes = this.parsePES_(audioData);
594
564
  if (pes) {
595
- audio.unshift(pes);
596
- prevAudioData = new Uint8Array(0);
597
- } else {
598
- prevAudioData = audioData;
565
+ audio.push(pes);
599
566
  }
600
567
  }
601
568
  return audio;
@@ -608,24 +575,11 @@ shaka.util.TsParser = class {
608
575
  * @export
609
576
  */
610
577
  getVideoData() {
611
- const Uint8ArrayUtils = shaka.util.Uint8ArrayUtils;
612
578
  const video = [];
613
- let prevVideoData = new Uint8Array(0);
614
- // parsePES_() only works if the data begins on a PES boundary.
615
- // Try the last data blob first, and if it doesn't begin on a
616
- // PES boundary, prepend the previous blob and try again.
617
- // This way, a successful parse will always begin and end on
618
- // the correct boundary, and no data will be skipped.
619
- for (let i = this.videoData_.length - 1; i >= 0; i--) {
620
- const data = this.videoData_[i];
621
- goog.asserts.assert(data, 'We should have a data');
622
- const videoData = Uint8ArrayUtils.concat(data, prevVideoData);
623
- const pes = this.parsePES_(videoData, /* isVideo= */ true);
579
+ for (const videoData of this.videoData_) {
580
+ const pes = this.parsePES_(videoData);
624
581
  if (pes) {
625
- video.unshift(pes);
626
- prevVideoData = new Uint8Array(0);
627
- } else {
628
- prevVideoData = videoData;
582
+ video.push(pes);
629
583
  }
630
584
  }
631
585
  return video;
@@ -638,9 +592,28 @@ shaka.util.TsParser = class {
638
592
  * @export
639
593
  */
640
594
  getStartTime() {
595
+ const timescale = shaka.util.TsParser.Timescale;
596
+ let audioStartTime = null;
597
+ for (const pes of this.getAudioData()) {
598
+ if (pes && pes.pts != null) {
599
+ const startTime = Math.min(pes.dts, pes.pts) / timescale;
600
+ if (audioStartTime == null || audioStartTime > startTime) {
601
+ audioStartTime = startTime;
602
+ }
603
+ }
604
+ }
605
+ let videoStartTime = null;
606
+ for (const pes of this.getVideoData()) {
607
+ if (pes && pes.pts != null) {
608
+ const startTime = Math.min(pes.dts, pes.pts) / timescale;
609
+ if (videoStartTime == null || videoStartTime > startTime) {
610
+ videoStartTime = startTime;
611
+ }
612
+ }
613
+ }
641
614
  return {
642
- audio: this.audioStartTime_,
643
- video: this.videoStartTime_,
615
+ audio: audioStartTime,
616
+ video: videoStartTime,
644
617
  };
645
618
  }
646
619
 
@@ -664,30 +637,20 @@ shaka.util.TsParser = class {
664
637
  * @export
665
638
  */
666
639
  getVideoNalus() {
667
- const Uint8ArrayUtils = shaka.util.Uint8ArrayUtils;
668
- let nalus = [];
669
- let prevVideoData = new Uint8Array(0);
670
- // parsePES_() only works if the data begins on a PES boundary.
671
- // Try the last data blob first, and if it doesn't begin on a
672
- // PES boundary, prepend the previous blob and try again.
673
- // This way, a successful parse will always begin and end on
674
- // the correct boundary, and no data will be skipped.
675
- for (let i = this.videoData_.length - 1; i >= 0; i--) {
676
- const data = this.videoData_[i];
677
- goog.asserts.assert(data, 'We should have a data');
678
- const videoData = Uint8ArrayUtils.concat(data, prevVideoData);
679
- const pes = this.parsePES_(videoData);
680
- if (pes) {
681
- if (this.videoCodec_ == 'avc') {
682
- nalus = nalus.concat(this.parseAvcNalus(pes));
683
- }
684
- prevVideoData = new Uint8Array(0);
685
- } else {
686
- prevVideoData = videoData;
640
+ const nalus = [];
641
+ if (this.videoCodec_ != 'avc') {
642
+ return nalus;
643
+ }
644
+ const videoData = this.getVideoData();
645
+ for (let i = 0; i < videoData.length; i++) {
646
+ const pes = videoData[i];
647
+ let nextPes;
648
+ if (i + 1 < videoData.length) {
649
+ nextPes = videoData[i + 1];
687
650
  }
651
+ nalus.push(...this.parseAvcNalus(pes, nextPes));
688
652
  }
689
- // We need to invert the array to return it in the correct order.
690
- return nalus.reverse();
653
+ return nalus;
691
654
  }
692
655
 
693
656
  /**
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.0",
4
+ "version": "4.4.1",
5
5
  "homepage": "https://github.com/shaka-project/shaka-player",
6
6
  "author": "Google",
7
7
  "maintainers": [
@@ -4180,19 +4180,11 @@ describe('HlsParser', () => {
4180
4180
  expect(videoSegments2[2].endTime).toBe(10);
4181
4181
  });
4182
4182
 
4183
- // Issue #1875
4184
- it('ignores audio groups on audio-only content', async () => {
4185
- // NOTE: To reproduce the original issue accurately, the two audio playlist
4186
- // URIs must differ. When the issue occurred, the audio-only variant would
4187
- // be detected as a video stream and combined with the audio group, leading
4188
- // the player to buffer "video" that was really audio, resulting in
4189
- // audio-only playback to the exclusion of any other streams. Since the
4190
- // root cause of that was the mis-detection, this repro case does not need
4191
- // to include any audio+video variants.
4183
+ it('allow audio groups on audio-only content', async () => {
4192
4184
  const master = [
4193
4185
  '#EXTM3U\n',
4194
- '#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="aud",LANG="en",URI="audio1"\n',
4195
- '#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="aud",LANG="eo",URI="audio2"\n',
4186
+ '#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="aud",LANGUAGE="en",URI="audio1"\n',
4187
+ '#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="aud",LANGUAGE="eo",URI="audio2"\n',
4196
4188
  '#EXT-X-STREAM-INF:BANDWIDTH=200,CODECS="mp4a",AUDIO="aud"\n',
4197
4189
  'audio3\n',
4198
4190
  ].join('');
@@ -4210,7 +4202,14 @@ describe('HlsParser', () => {
4210
4202
  manifest.anyTimeline();
4211
4203
  manifest.addPartialVariant((variant) => {
4212
4204
  variant.bandwidth = 200;
4213
- variant.language = 'und';
4205
+ variant.language = 'en';
4206
+ variant.addPartialStream(ContentType.AUDIO, (stream) => {
4207
+ stream.mime('audio/mp4', 'mp4a');
4208
+ });
4209
+ });
4210
+ manifest.addPartialVariant((variant) => {
4211
+ variant.bandwidth = 200;
4212
+ variant.language = 'eo';
4214
4213
  variant.addPartialStream(ContentType.AUDIO, (stream) => {
4215
4214
  stream.mime('audio/mp4', 'mp4a');
4216
4215
  });
@@ -4229,7 +4228,7 @@ describe('HlsParser', () => {
4229
4228
 
4230
4229
  const actual = await parser.start('test:/master', playerInterface);
4231
4230
  await loadAllStreamsFor(actual);
4232
- expect(actual.variants.length).toBe(1);
4231
+ expect(actual.variants.length).toBe(2);
4233
4232
  expect(actual).toEqual(manifest);
4234
4233
  });
4235
4234
 
@@ -656,7 +656,7 @@ describe('MediaSourceEngine', () => {
656
656
  it('extracts ID3 metadata from AAC', async () => {
657
657
  if (!MediaSource.isTypeSupported('audio/aac') ||
658
658
  !shaka.util.Platform.supportsSequenceMode()) {
659
- return;
659
+ pending('Raw AAC codec is not supported by the platform.');
660
660
  }
661
661
  metadata = shaka.test.TestScheme.DATA['id3-metadata_aac'];
662
662
  generators = shaka.test.TestScheme.GENERATORS['id3-metadata_aac'];
@@ -672,7 +672,7 @@ describe('MediaSourceEngine', () => {
672
672
 
673
673
  it('extracts ID3 metadata from AAC when transmuxing', async () => {
674
674
  if (!MediaSource.isTypeSupported('audio/aac')) {
675
- return;
675
+ pending('Raw AAC codec is not supported by the platform.');
676
676
  }
677
677
  metadata = shaka.test.TestScheme.DATA['id3-metadata_aac'];
678
678
  generators = shaka.test.TestScheme.GENERATORS['id3-metadata_aac'];
@@ -94,7 +94,7 @@ describe('MSS Player', () => {
94
94
  it('MSS VoD PlayReady', async () => {
95
95
  const support = await shaka.media.DrmEngine.probeSupport();
96
96
  if (!support['com.microsoft.playready']) {
97
- return;
97
+ pending('PlayReady is not supported by the platform.');
98
98
  }
99
99
 
100
100
  player.configure({