shaka-player 4.3.7 → 4.3.9

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 (86) hide show
  1. package/.github/ISSUE_TEMPLATE/bug_report.md +11 -0
  2. package/.github/workflows/appspot.yaml +39 -0
  3. package/.github/workflows/build-and-test.yaml +8 -0
  4. package/.github/workflows/selenium-lab-tests.yaml +3 -2
  5. package/.github/workflows/update-issues.yaml +1 -0
  6. package/CHANGELOG.md +44 -0
  7. package/CONTRIBUTORS +1 -0
  8. package/demo/custom.js +1 -1
  9. package/dist/demo.compiled.debug.js +2 -2
  10. package/dist/demo.compiled.debug.map +2 -2
  11. package/dist/demo.compiled.js +2 -2
  12. package/dist/demo.compiled.map +2 -2
  13. package/dist/deps.js +2 -2
  14. package/dist/locales.js +31 -31
  15. package/dist/shaka-player.compiled.d.ts +24 -2
  16. package/dist/shaka-player.compiled.debug.d.ts +24 -2
  17. package/dist/shaka-player.compiled.debug.js +251 -247
  18. package/dist/shaka-player.compiled.debug.map +3 -3
  19. package/dist/shaka-player.compiled.js +796 -793
  20. package/dist/shaka-player.compiled.map +4 -4
  21. package/dist/shaka-player.ui.d.ts +24 -2
  22. package/dist/shaka-player.ui.debug.d.ts +24 -2
  23. package/dist/shaka-player.ui.debug.js +289 -286
  24. package/dist/shaka-player.ui.debug.map +3 -3
  25. package/dist/shaka-player.ui.js +831 -827
  26. package/dist/shaka-player.ui.map +4 -4
  27. package/docs/tutorials/drm-config.md +9 -6
  28. package/docs/tutorials/selenium-grid-config.md +60 -8
  29. package/externs/ima.js +34 -0
  30. package/externs/shaka/manifest.js +5 -1
  31. package/externs/shaka/offline.js +5 -1
  32. package/karma.conf.js +50 -7
  33. package/lib/abr/simple_abr_manager.js +6 -6
  34. package/lib/cea/mp4_cea_parser.js +24 -18
  35. package/lib/dash/dash_parser.js +19 -3
  36. package/lib/hls/hls_parser.js +24 -4
  37. package/lib/media/drm_engine.js +14 -8
  38. package/lib/media/gap_jumping_controller.js +24 -2
  39. package/lib/media/media_source_engine.js +11 -8
  40. package/lib/media/playhead.js +11 -0
  41. package/lib/media/region_observer.js +19 -1
  42. package/lib/media/streaming_engine.js +41 -21
  43. package/lib/media/video_wrapper.js +6 -1
  44. package/lib/offline/indexeddb/v1_storage_cell.js +1 -0
  45. package/lib/offline/indexeddb/v2_storage_cell.js +1 -0
  46. package/lib/offline/manifest_converter.js +1 -0
  47. package/lib/offline/storage.js +1 -0
  48. package/lib/player.js +20 -9
  49. package/lib/polyfill/abort_controller.js +7 -0
  50. package/lib/polyfill/orientation.js +34 -7
  51. package/lib/text/vtt_text_parser.js +3 -1
  52. package/lib/util/manifest_parser_utils.js +5 -3
  53. package/lib/util/periods.js +2 -0
  54. package/lib/util/platform.js +14 -0
  55. package/lib/util/stream_utils.js +70 -26
  56. package/package.json +2 -2
  57. package/test/cast/cast_utils_unit.js +4 -1
  58. package/test/cea/mp4_cea_parser_unit.js +28 -5
  59. package/test/dash/dash_parser_manifest_unit.js +120 -0
  60. package/test/hls/hls_parser_unit.js +216 -0
  61. package/test/media/adaptation_set_unit.js +1 -0
  62. package/test/media/drm_engine_integration.js +2 -2
  63. package/test/media/media_source_engine_unit.js +1 -1
  64. package/test/media/region_observer_unit.js +2 -1
  65. package/test/media/segment_index_unit.js +3 -3
  66. package/test/media/streaming_engine_integration.js +14 -11
  67. package/test/media/streaming_engine_unit.js +26 -2
  68. package/test/offline/manifest_convert_unit.js +5 -0
  69. package/test/offline/offline_integration.js +6 -3
  70. package/test/player_external.js +19 -16
  71. package/test/player_integration.js +35 -48
  72. package/test/player_src_equals_integration.js +9 -5
  73. package/test/test/assets/multiple-trun-init.mp4 +0 -0
  74. package/test/test/assets/multiple-trun-segment.mp4 +0 -0
  75. package/test/test/util/fake_demo_main.js +7 -3
  76. package/test/test/util/layout_tests.js +2 -2
  77. package/test/test/util/manifest_generator.js +2 -0
  78. package/test/test/util/offline_utils.js +1 -0
  79. package/test/test/util/streaming_engine_util.js +3 -0
  80. package/test/test/util/ui_utils.js +9 -2
  81. package/test/test/util/util.js +6 -27
  82. package/test/test/util/waiter.js +73 -3
  83. package/test/ui/ad_ui_unit.js +9 -9
  84. package/test/util/stream_utils_unit.js +46 -0
  85. package/ui/range_element.js +5 -0
  86. package/ui/resolution_selection.js +7 -1
@@ -437,48 +437,62 @@ shaka.util.StreamUtils = class {
437
437
  manifest.variants = manifest.variants.filter((variant) => {
438
438
  // See: https://github.com/shaka-project/shaka-player/issues/3860
439
439
  const video = variant.video;
440
+
440
441
  const ContentType = shaka.util.ManifestParserUtils.ContentType;
441
442
  const Capabilities = shaka.media.Capabilities;
443
+ const ManifestParserUtils = shaka.util.ManifestParserUtils;
444
+ const MimeUtils = shaka.util.MimeUtils;
445
+ const StreamUtils = shaka.util.StreamUtils;
446
+
442
447
  if (video) {
443
- let videoCodecs =
444
- shaka.util.StreamUtils.getCorrectVideoCodecs_(video.codecs);
448
+ let videoCodecs = StreamUtils.getCorrectVideoCodecs_(video.codecs);
449
+
445
450
  // For multiplexed streams. Here we must check the audio of the
446
451
  // stream to see if it is compatible.
447
452
  if (video.codecs.includes(',')) {
448
453
  const allCodecs = video.codecs.split(',');
449
- videoCodecs = shaka.util.ManifestParserUtils.guessCodecs(
454
+
455
+ videoCodecs = ManifestParserUtils.guessCodecs(
450
456
  ContentType.VIDEO, allCodecs);
451
- videoCodecs =
452
- shaka.util.StreamUtils.getCorrectVideoCodecs_(videoCodecs);
453
- let audioCodecs = shaka.util.ManifestParserUtils.guessCodecs(
457
+ videoCodecs = StreamUtils.getCorrectVideoCodecs_(videoCodecs);
458
+
459
+ let audioCodecs = ManifestParserUtils.guessCodecs(
454
460
  ContentType.AUDIO, allCodecs);
455
- audioCodecs =
456
- shaka.util.StreamUtils.getCorrectAudioCodecs_(audioCodecs);
457
- const audioFullType = shaka.util.MimeUtils.getFullOrConvertedType(
461
+ audioCodecs = StreamUtils.getCorrectAudioCodecs_(audioCodecs);
462
+
463
+ const audioFullType = MimeUtils.getFullOrConvertedType(
458
464
  video.mimeType, audioCodecs, ContentType.AUDIO);
465
+
459
466
  if (!Capabilities.isTypeSupported(audioFullType)) {
460
467
  return false;
461
468
  }
469
+
462
470
  // Update the codec string with the (possibly) converted codecs.
463
471
  videoCodecs = [videoCodecs, audioCodecs].join(',');
464
472
  }
465
- const fullType = shaka.util.MimeUtils.getFullOrConvertedType(
473
+
474
+ const fullType = MimeUtils.getFullOrConvertedType(
466
475
  video.mimeType, videoCodecs, ContentType.VIDEO);
476
+
467
477
  if (!Capabilities.isTypeSupported(fullType)) {
468
478
  return false;
469
479
  }
480
+
470
481
  // Update the codec string with the (possibly) converted codecs.
471
482
  video.codecs = videoCodecs;
472
483
  }
484
+
473
485
  const audio = variant.audio;
486
+
474
487
  if (audio) {
475
- const codecs =
476
- shaka.util.StreamUtils.getCorrectAudioCodecs_(audio.codecs);
477
- const fullType = shaka.util.MimeUtils.getFullOrConvertedType(
488
+ const codecs = StreamUtils.getCorrectAudioCodecs_(audio.codecs);
489
+ const fullType = MimeUtils.getFullOrConvertedType(
478
490
  audio.mimeType, codecs, ContentType.AUDIO);
491
+
479
492
  if (!Capabilities.isTypeSupported(fullType)) {
480
493
  return false;
481
494
  }
495
+
482
496
  // Update the codec string with the (possibly) converted codecs.
483
497
  audio.codecs = codecs;
484
498
  }
@@ -490,7 +504,7 @@ shaka.util.StreamUtils = class {
490
504
  (video.codecs.includes('avc1.') ||
491
505
  video.codecs.includes('avc3.'))) {
492
506
  shaka.log.debug('Dropping variant - not compatible with platform',
493
- shaka.util.StreamUtils.getVariantSummaryString_(variant));
507
+ StreamUtils.getVariantSummaryString_(variant));
494
508
  return false;
495
509
  }
496
510
 
@@ -500,7 +514,7 @@ shaka.util.StreamUtils = class {
500
514
  // Filter out all unsupported variants.
501
515
  if (!supported) {
502
516
  shaka.log.debug('Dropping variant - not compatible with platform',
503
- shaka.util.StreamUtils.getVariantSummaryString_(variant));
517
+ StreamUtils.getVariantSummaryString_(variant));
504
518
  }
505
519
  return supported;
506
520
  });
@@ -564,7 +578,11 @@ shaka.util.StreamUtils = class {
564
578
  static getDecodingConfigs_(variant, usePersistentLicenses, srcEquals) {
565
579
  const audio = variant.audio;
566
580
  const video = variant.video;
581
+
567
582
  const ContentType = shaka.util.ManifestParserUtils.ContentType;
583
+ const ManifestParserUtils = shaka.util.ManifestParserUtils;
584
+ const MimeUtils = shaka.util.MimeUtils;
585
+ const StreamUtils = shaka.util.StreamUtils;
568
586
 
569
587
  /** @type {!MediaDecodingConfiguration} */
570
588
  const mediaDecodingConfig = {
@@ -573,19 +591,23 @@ shaka.util.StreamUtils = class {
573
591
 
574
592
  if (video) {
575
593
  let videoCodecs = video.codecs;
594
+
576
595
  // For multiplexed streams with audio+video codecs, the config should have
577
596
  // AudioConfiguration and VideoConfiguration.
578
597
  if (video.codecs.includes(',')) {
579
598
  const allCodecs = video.codecs.split(',');
580
- videoCodecs = shaka.util.ManifestParserUtils.guessCodecs(
599
+
600
+ videoCodecs = ManifestParserUtils.guessCodecs(
581
601
  ContentType.VIDEO, allCodecs);
582
- videoCodecs =
583
- shaka.util.StreamUtils.getCorrectVideoCodecs_(videoCodecs);
584
- const audioCodecs = shaka.util.ManifestParserUtils.guessCodecs(
602
+ videoCodecs = StreamUtils.getCorrectVideoCodecs_(videoCodecs);
603
+
604
+ let audioCodecs = ManifestParserUtils.guessCodecs(
585
605
  ContentType.AUDIO, allCodecs);
606
+ audioCodecs = StreamUtils.getCorrectAudioCodecs_(audioCodecs);
586
607
 
587
- const audioFullType = shaka.util.MimeUtils.getFullOrConvertedType(
608
+ const audioFullType = MimeUtils.getFullOrConvertedType(
588
609
  video.mimeType, audioCodecs, ContentType.AUDIO);
610
+
589
611
  mediaDecodingConfig.audio = {
590
612
  contentType: audioFullType,
591
613
  channels: 2,
@@ -594,9 +616,11 @@ shaka.util.StreamUtils = class {
594
616
  spatialRendering: false,
595
617
  };
596
618
  }
597
- videoCodecs = shaka.util.StreamUtils.getCorrectVideoCodecs_(videoCodecs);
598
- const fullType = shaka.util.MimeUtils.getFullOrConvertedType(
619
+
620
+ videoCodecs = StreamUtils.getCorrectVideoCodecs_(videoCodecs);
621
+ const fullType = MimeUtils.getFullOrConvertedType(
599
622
  video.mimeType, videoCodecs, ContentType.VIDEO);
623
+
600
624
  // VideoConfiguration
601
625
  mediaDecodingConfig.video = {
602
626
  contentType: fullType,
@@ -629,9 +653,8 @@ shaka.util.StreamUtils = class {
629
653
  }
630
654
  }
631
655
  if (audio) {
632
- const codecs =
633
- shaka.util.StreamUtils.getCorrectAudioCodecs_(audio.codecs);
634
- const fullType = shaka.util.MimeUtils.getFullOrConvertedType(
656
+ const codecs = StreamUtils.getCorrectAudioCodecs_(audio.codecs);
657
+ const fullType = MimeUtils.getFullOrConvertedType(
635
658
  audio.mimeType, codecs, ContentType.AUDIO);
636
659
 
637
660
  // AudioConfiguration
@@ -762,6 +785,20 @@ shaka.util.StreamUtils = class {
762
785
  * @private
763
786
  */
764
787
  static getCorrectAudioCodecs_(codecs) {
788
+ // According to RFC 6381 section 3.3, 'fLaC' is actually the correct
789
+ // codec string. We still need to map it to 'flac', as some browsers
790
+ // currently don't support 'fLaC', while 'flac' is supported by most
791
+ // major browsers.
792
+ // See https://bugs.chromium.org/p/chromium/issues/detail?id=1422728
793
+ if (codecs === 'fLaC') {
794
+ return 'flac';
795
+ }
796
+
797
+ // The same is true for 'Opus'.
798
+ if (codecs === 'Opus') {
799
+ return 'opus';
800
+ }
801
+
765
802
  // Some Tizen devices seem to misreport AC-3 support, but correctly
766
803
  // report EC-3 support. So query EC-3 as a fallback for AC-3.
767
804
  // See https://github.com/shaka-project/shaka-player/issues/2989 for
@@ -1068,6 +1105,7 @@ shaka.util.StreamUtils = class {
1068
1105
  track.frameRate = video.frameRate || null;
1069
1106
  track.pixelAspectRatio = video.pixelAspectRatio || null;
1070
1107
  track.videoBandwidth = video.bandwidth || null;
1108
+ track.hdr = video.hdr || null;
1071
1109
  }
1072
1110
 
1073
1111
  if (audio) {
@@ -1239,6 +1277,9 @@ shaka.util.StreamUtils = class {
1239
1277
  if (textTrack.kind == 'captions') {
1240
1278
  track.mimeType = CLOSED_CAPTION_MIMETYPE;
1241
1279
  }
1280
+ if (textTrack.kind == 'subtitles') {
1281
+ track.mimeType = 'text/vtt';
1282
+ }
1242
1283
  if (textTrack.kind) {
1243
1284
  track.roles = [textTrack.kind];
1244
1285
  }
@@ -1286,13 +1327,15 @@ shaka.util.StreamUtils = class {
1286
1327
  * @private
1287
1328
  */
1288
1329
  static html5TrackToGenericShakaTrack_(html5Track) {
1330
+ const language = html5Track.language;
1331
+
1289
1332
  /** @type {shaka.extern.Track} */
1290
1333
  const track = {
1291
1334
  id: shaka.util.StreamUtils.html5TrackId(html5Track),
1292
1335
  active: false,
1293
1336
  type: '',
1294
1337
  bandwidth: 0,
1295
- language: shaka.util.LanguageUtils.normalize(html5Track.language),
1338
+ language: shaka.util.LanguageUtils.normalize(language || 'und'),
1296
1339
  label: html5Track.label,
1297
1340
  kind: html5Track.kind,
1298
1341
  width: null,
@@ -1322,6 +1365,7 @@ shaka.util.StreamUtils = class {
1322
1365
  originalAudioId: null,
1323
1366
  originalTextId: null,
1324
1367
  originalImageId: null,
1368
+ originalLanguage: language,
1325
1369
  };
1326
1370
 
1327
1371
  return track;
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.3.7",
4
+ "version": "4.3.9",
5
5
  "homepage": "https://github.com/shaka-project/shaka-player",
6
6
  "author": "Google",
7
7
  "maintainers": [
@@ -42,7 +42,7 @@
42
42
  "karma-coverage": "^2.2.0",
43
43
  "karma-jasmine": "^4.0.1",
44
44
  "karma-jasmine-ajax": "^0.1.13",
45
- "karma-local-wd-launcher": "^1.6.1",
45
+ "karma-local-wd-launcher": "^1.6.4",
46
46
  "karma-opera-launcher": "^1.0.0",
47
47
  "karma-sourcemap-loader": "^0.3.8",
48
48
  "karma-spec-reporter": "^0.0.34",
@@ -171,7 +171,10 @@ describe('CastUtils', () => {
171
171
  }
172
172
  });
173
173
 
174
- describe('TimeRanges', () => {
174
+ // Disable because these tests are flakey on ChromeLinux, and this whole
175
+ // module will be removed in
176
+ // https://github.com/shaka-project/shaka-player/issues/4214
177
+ xdescribe('TimeRanges', () => {
175
178
  /** @type {!HTMLVideoElement} */
176
179
  let video;
177
180
  /** @type {!shaka.util.EventManager} */
@@ -9,6 +9,10 @@ describe('Mp4CeaParser', () => {
9
9
  const ceaSegmentUri = '/base/test/test/assets/cea-segment.mp4';
10
10
  const h265ceaInitSegmentUri = '/base/test/test/assets/h265-cea-init.mp4';
11
11
  const h265ceaSegmentUri = '/base/test/test/assets/h265-cea-segment.mp4';
12
+ const multipleTrunInitSegmentUri =
13
+ '/base/test/test/assets/multiple-trun-init.mp4';
14
+ const multipleTrunSegmentUri =
15
+ '/base/test/test/assets/multiple-trun-segment.mp4';
12
16
  const Util = shaka.test.Util;
13
17
 
14
18
  /** @type {!ArrayBuffer} */
@@ -19,18 +23,27 @@ describe('Mp4CeaParser', () => {
19
23
  let h265ceaInitSegment;
20
24
  /** @type {!ArrayBuffer} */
21
25
  let h265ceaSegment;
26
+ /** @type {!ArrayBuffer} */
27
+ let multipleTrunInitSegment;
28
+ /** @type {!ArrayBuffer} */
29
+ let multipleTrunSegment;
22
30
 
23
31
  beforeAll(async () => {
24
- const responses = await Promise.all([
32
+ [
33
+ ceaInitSegment,
34
+ ceaSegment,
35
+ h265ceaInitSegment,
36
+ h265ceaSegment,
37
+ multipleTrunInitSegment,
38
+ multipleTrunSegment,
39
+ ] = await Promise.all([
25
40
  shaka.test.Util.fetch(ceaInitSegmentUri),
26
41
  shaka.test.Util.fetch(ceaSegmentUri),
27
42
  shaka.test.Util.fetch(h265ceaInitSegmentUri),
28
43
  shaka.test.Util.fetch(h265ceaSegmentUri),
44
+ shaka.test.Util.fetch(multipleTrunInitSegmentUri),
45
+ shaka.test.Util.fetch(multipleTrunSegmentUri),
29
46
  ]);
30
- ceaInitSegment = responses[0];
31
- ceaSegment = responses[1];
32
- h265ceaInitSegment = responses[2];
33
- h265ceaSegment = responses[3];
34
47
  });
35
48
 
36
49
  /**
@@ -89,6 +102,16 @@ describe('Mp4CeaParser', () => {
89
102
  expect(ceaPackets.length).toBe(60);
90
103
  });
91
104
 
105
+ it('parses cea data from a segment with multiple trun boxes', () => {
106
+ const ceaParser = new shaka.cea.Mp4CeaParser();
107
+
108
+ ceaParser.init(multipleTrunInitSegment);
109
+ const ceaPackets = ceaParser.parse(multipleTrunSegment);
110
+ // The first trun box references samples with 48 CEA packets.
111
+ // The second trun box references samples with 132 more, for a total of 180.
112
+ expect(ceaPackets.length).toBe(180);
113
+ });
114
+
92
115
  it('parses an invalid init segment', () => {
93
116
  const cea708Parser = new shaka.cea.Mp4CeaParser();
94
117
 
@@ -2023,6 +2023,66 @@ describe('DashParser Manifest', () => {
2023
2023
  expect(stream.hdr).toBe('PQ');
2024
2024
  });
2025
2025
 
2026
+ it('supports SupplementalProperty MatrixCoefficients', async () => {
2027
+ // (DASH-IF IOP v4.3 6.2.5.1.)
2028
+ const scheme = cicpScheme('MatrixCoefficients');
2029
+ const manifestText = [
2030
+ '<MPD minBufferTime="PT75S">',
2031
+ ' <Period id="1" duration="PT30S">',
2032
+ ' <AdaptationSet id="2" mimeType="video/mp4">',
2033
+ ` <SupplementalProperty schemeIdUri="${scheme}" value="9" />`,
2034
+ ' <Representation codecs="hvc1.2.4.L153.B0">',
2035
+ ' <BaseURL>v-sd.mp4</BaseURL>',
2036
+ ' <SegmentBase indexRange="100-200" />',
2037
+ ' </Representation>',
2038
+ ' </AdaptationSet>',
2039
+ ' <AdaptationSet id="3" mimeType="audio/mp4">',
2040
+ ' <Representation id="audio-en">',
2041
+ ' <BaseURL>a-en.mp4</BaseURL>',
2042
+ ' <SegmentBase indexRange="100-200" />',
2043
+ ' </Representation>',
2044
+ ' </AdaptationSet>',
2045
+ ' </Period>',
2046
+ '</MPD>',
2047
+ ].join('\n');
2048
+
2049
+ fakeNetEngine.setResponseText('dummy://foo', manifestText);
2050
+
2051
+ /** @type {shaka.extern.Manifest} */
2052
+ const manifest = await parser.start('dummy://foo', playerInterface);
2053
+ expect(manifest.variants.length).toBe(1);
2054
+ });
2055
+
2056
+ it('supports SupplementalProperty ColourPrimaries', async () => {
2057
+ // (DASH-IF IOP v4.3 6.2.5.1.)
2058
+ const scheme = cicpScheme('ColourPrimaries');
2059
+ const manifestText = [
2060
+ '<MPD minBufferTime="PT75S">',
2061
+ ' <Period id="1" duration="PT30S">',
2062
+ ' <AdaptationSet id="2" mimeType="video/mp4">',
2063
+ ` <SupplementalProperty schemeIdUri="${scheme}" value="9" />`,
2064
+ ' <Representation codecs="hvc1.2.4.L153.B0">',
2065
+ ' <BaseURL>v-sd.mp4</BaseURL>',
2066
+ ' <SegmentBase indexRange="100-200" />',
2067
+ ' </Representation>',
2068
+ ' </AdaptationSet>',
2069
+ ' <AdaptationSet id="3" mimeType="audio/mp4">',
2070
+ ' <Representation id="audio-en">',
2071
+ ' <BaseURL>a-en.mp4</BaseURL>',
2072
+ ' <SegmentBase indexRange="100-200" />',
2073
+ ' </Representation>',
2074
+ ' </AdaptationSet>',
2075
+ ' </Period>',
2076
+ '</MPD>',
2077
+ ].join('\n');
2078
+
2079
+ fakeNetEngine.setResponseText('dummy://foo', manifestText);
2080
+
2081
+ /** @type {shaka.extern.Manifest} */
2082
+ const manifest = await parser.start('dummy://foo', playerInterface);
2083
+ expect(manifest.variants.length).toBe(1);
2084
+ });
2085
+
2026
2086
  it('supports HDR signaling via EssentialProperty', async () => {
2027
2087
  // (DASH-IF IOP v4.3 6.2.5.1.)
2028
2088
  const hdrScheme = cicpScheme('TransferCharacteristics');
@@ -2056,6 +2116,66 @@ describe('DashParser Manifest', () => {
2056
2116
  expect(stream.hdr).toBe('HLG');
2057
2117
  });
2058
2118
 
2119
+ it('supports EssentialProperty MatrixCoefficients', async () => {
2120
+ // (DASH-IF IOP v4.3 6.2.5.1.)
2121
+ const hdrScheme = cicpScheme('MatrixCoefficients');
2122
+ const manifestText = [
2123
+ '<MPD minBufferTime="PT75S">',
2124
+ ' <Period id="1" duration="PT30S">',
2125
+ ' <AdaptationSet id="2" mimeType="video/mp4">',
2126
+ ` <EssentialProperty schemeIdUri="${hdrScheme}" value="9" />`,
2127
+ ' <Representation codecs="hvc1.2.4.L153.B0">',
2128
+ ' <BaseURL>v-sd.mp4</BaseURL>',
2129
+ ' <SegmentBase indexRange="100-200" />',
2130
+ ' </Representation>',
2131
+ ' </AdaptationSet>',
2132
+ ' <AdaptationSet id="3" mimeType="audio/mp4">',
2133
+ ' <Representation id="audio-en">',
2134
+ ' <BaseURL>a-en.mp4</BaseURL>',
2135
+ ' <SegmentBase indexRange="100-200" />',
2136
+ ' </Representation>',
2137
+ ' </AdaptationSet>',
2138
+ ' </Period>',
2139
+ '</MPD>',
2140
+ ].join('\n');
2141
+
2142
+ fakeNetEngine.setResponseText('dummy://foo', manifestText);
2143
+
2144
+ /** @type {shaka.extern.Manifest} */
2145
+ const manifest = await parser.start('dummy://foo', playerInterface);
2146
+ expect(manifest.variants.length).toBe(1);
2147
+ });
2148
+
2149
+ it('supports EssentialProperty ColourPrimaries', async () => {
2150
+ // (DASH-IF IOP v4.3 6.2.5.1.)
2151
+ const hdrScheme = cicpScheme('ColourPrimaries');
2152
+ const manifestText = [
2153
+ '<MPD minBufferTime="PT75S">',
2154
+ ' <Period id="1" duration="PT30S">',
2155
+ ' <AdaptationSet id="2" mimeType="video/mp4">',
2156
+ ` <EssentialProperty schemeIdUri="${hdrScheme}" value="9" />`,
2157
+ ' <Representation codecs="hvc1.2.4.L153.B0">',
2158
+ ' <BaseURL>v-sd.mp4</BaseURL>',
2159
+ ' <SegmentBase indexRange="100-200" />',
2160
+ ' </Representation>',
2161
+ ' </AdaptationSet>',
2162
+ ' <AdaptationSet id="3" mimeType="audio/mp4">',
2163
+ ' <Representation id="audio-en">',
2164
+ ' <BaseURL>a-en.mp4</BaseURL>',
2165
+ ' <SegmentBase indexRange="100-200" />',
2166
+ ' </Representation>',
2167
+ ' </AdaptationSet>',
2168
+ ' </Period>',
2169
+ '</MPD>',
2170
+ ].join('\n');
2171
+
2172
+ fakeNetEngine.setResponseText('dummy://foo', manifestText);
2173
+
2174
+ /** @type {shaka.extern.Manifest} */
2175
+ const manifest = await parser.start('dummy://foo', playerInterface);
2176
+ expect(manifest.variants.length).toBe(1);
2177
+ });
2178
+
2059
2179
  it('supports SDR signalling via EssentialProperty', async () => {
2060
2180
  // (DASH-IF IOP v4.3 6.2.5.1.)
2061
2181
  const scheme = cicpScheme('TransferCharacteristics');
@@ -628,6 +628,78 @@ describe('HlsParser', () => {
628
628
  await testHlsParser(master, media, manifest);
629
629
  });
630
630
 
631
+ it('accepts fLaC codec as audio/mp4', async () => {
632
+ const master = [
633
+ '#EXTM3U\n',
634
+ '#EXT-X-STREAM-INF:BANDWIDTH=1000000,CODECS="fLaC"\n',
635
+ 'audio\n',
636
+ '#EXT-X-STREAM-INF:BANDWIDTH=1000000,CODECS="flac"\n',
637
+ 'audio2\n',
638
+ ].join('');
639
+
640
+ const media = [
641
+ '#EXTM3U\n',
642
+ '#EXT-X-PLAYLIST-TYPE:VOD\n',
643
+ '#EXT-X-MAP:URI="init.mp4",BYTERANGE="616@0"\n',
644
+ '#EXTINF:5,\n',
645
+ '#EXT-X-BYTERANGE:121090@616\n',
646
+ 'main.mp4',
647
+ ].join('');
648
+
649
+ const manifest = shaka.test.ManifestGenerator.generate((manifest) => {
650
+ manifest.anyTimeline();
651
+ manifest.addPartialVariant((variant) => {
652
+ variant.addPartialStream(ContentType.AUDIO, (stream) => {
653
+ stream.mime('audio/mp4', 'fLaC');
654
+ });
655
+ });
656
+ manifest.addPartialVariant((variant) => {
657
+ variant.addPartialStream(ContentType.AUDIO, (stream) => {
658
+ stream.mime('audio/mp4', 'flac');
659
+ });
660
+ });
661
+ manifest.sequenceMode = true;
662
+ });
663
+
664
+ await testHlsParser(master, media, manifest);
665
+ });
666
+
667
+ it('accepts Opus codec as audio/mp4', async () => {
668
+ const master = [
669
+ '#EXTM3U\n',
670
+ '#EXT-X-STREAM-INF:BANDWIDTH=128000,CODECS="Opus"\n',
671
+ 'audio\n',
672
+ '#EXT-X-STREAM-INF:BANDWIDTH=128000,CODECS="opus"\n',
673
+ 'audio2\n',
674
+ ].join('');
675
+
676
+ const media = [
677
+ '#EXTM3U\n',
678
+ '#EXT-X-PLAYLIST-TYPE:VOD\n',
679
+ '#EXT-X-MAP:URI="init.mp4",BYTERANGE="616@0"\n',
680
+ '#EXTINF:5,\n',
681
+ '#EXT-X-BYTERANGE:121090@616\n',
682
+ 'main.mp4',
683
+ ].join('');
684
+
685
+ const manifest = shaka.test.ManifestGenerator.generate((manifest) => {
686
+ manifest.anyTimeline();
687
+ manifest.addPartialVariant((variant) => {
688
+ variant.addPartialStream(ContentType.AUDIO, (stream) => {
689
+ stream.mime('audio/mp4', 'Opus');
690
+ });
691
+ });
692
+ manifest.addPartialVariant((variant) => {
693
+ variant.addPartialStream(ContentType.AUDIO, (stream) => {
694
+ stream.mime('audio/mp4', 'opus');
695
+ });
696
+ });
697
+ manifest.sequenceMode = true;
698
+ });
699
+
700
+ await testHlsParser(master, media, manifest);
701
+ });
702
+
631
703
  it('parses audio+video variant with closed captions', async () => {
632
704
  const master = [
633
705
  '#EXTM3U\n',
@@ -1150,6 +1222,74 @@ describe('HlsParser', () => {
1150
1222
  await testHlsParser(master, media, manifest);
1151
1223
  });
1152
1224
 
1225
+ it('adds subtitle role when characteristics are empty', async () => {
1226
+ const master = [
1227
+ '#EXTM3U\n',
1228
+ '#EXT-X-STREAM-INF:BANDWIDTH=200,CODECS="avc1",',
1229
+ 'RESOLUTION=960x540,FRAME-RATE=60,AUDIO="aud1",SUBTITLES="sub1"\n',
1230
+ 'video\n',
1231
+
1232
+ '#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="aud1",LANGUAGE="en",',
1233
+ 'NAME="English",URI="audio"\n',
1234
+
1235
+ '#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="aud1",LANGUAGE="en",',
1236
+ 'CHARACTERISTICS="public.accessibility.describes-video,',
1237
+ 'public.accessibility.describes-music-and-sound",',
1238
+ 'NAME="English (describes-video)",URI="audio2"\n',
1239
+
1240
+ '#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID="sub1",LANGUAGE="en",',
1241
+ 'NAME="English (subtitle)",DEFAULT=YES,AUTOSELECT=YES,',
1242
+ 'URI="text"\n',
1243
+
1244
+ '#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID="sub1",LANGUAGE="en",',
1245
+ 'NAME="English (caption)",DEFAULT=YES,AUTOSELECT=YES,',
1246
+ 'CHARACTERISTICS="public.accessibility.describes-music-and-sound",',
1247
+ 'URI="text2"\n',
1248
+ ].join('');
1249
+ const manifest = shaka.test.ManifestGenerator.generate((manifest) => {
1250
+ manifest.anyTimeline();
1251
+ manifest.addPartialVariant((variant) => {
1252
+ variant.language = 'en';
1253
+ variant.addPartialStream(ContentType.VIDEO);
1254
+ variant.addPartialStream(ContentType.AUDIO, (stream) => {
1255
+ stream.language = 'en';
1256
+ stream.roles = [];
1257
+ });
1258
+ });
1259
+ manifest.addPartialVariant((variant) => {
1260
+ variant.language = 'en';
1261
+ variant.addPartialStream(ContentType.VIDEO);
1262
+ variant.addPartialStream(ContentType.AUDIO, (stream) => {
1263
+ stream.language = 'en';
1264
+ stream.roles = [
1265
+ 'public.accessibility.describes-video',
1266
+ 'public.accessibility.describes-music-and-sound',
1267
+ ];
1268
+ });
1269
+ });
1270
+ manifest.addPartialTextStream((stream) => {
1271
+ stream.language = 'en';
1272
+ stream.kind = TextStreamKind.SUBTITLE;
1273
+ stream.roles = [
1274
+ 'subtitle',
1275
+ ];
1276
+ });
1277
+ manifest.addPartialTextStream((stream) => {
1278
+ stream.language = 'en';
1279
+ stream.kind = TextStreamKind.SUBTITLE;
1280
+ stream.roles = [
1281
+ 'public.accessibility.describes-music-and-sound',
1282
+ ];
1283
+ });
1284
+ manifest.sequenceMode = true;
1285
+ });
1286
+
1287
+ fakeNetEngine.setResponseText('test:/master', master);
1288
+
1289
+ const actual = await parser.start('test:/master', playerInterface);
1290
+ expect(actual).toEqual(manifest);
1291
+ });
1292
+
1153
1293
  it('parses characteristics from text tags', async () => {
1154
1294
  const master = [
1155
1295
  '#EXTM3U\n',
@@ -1898,6 +2038,82 @@ describe('HlsParser', () => {
1898
2038
  }
1899
2039
  });
1900
2040
 
2041
+ it('ignore segments with #EXTINF:0', async () => {
2042
+ const master = [
2043
+ '#EXTM3U\n',
2044
+ '#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="aud1",LANGUAGE="eng",',
2045
+ 'CHANNELS="2",URI="audio"\n',
2046
+ '#EXT-X-STREAM-INF:BANDWIDTH=200,CODECS="avc1,mp4a",',
2047
+ 'RESOLUTION=960x540,FRAME-RATE=60,AUDIO="aud1"\n',
2048
+ 'video\n',
2049
+ ].join('');
2050
+
2051
+ const video = [
2052
+ '#EXTM3U\n',
2053
+ '#EXT-X-PLAYLIST-TYPE:VOD\n',
2054
+ '#EXT-X-MAP:URI="init.mp4",BYTERANGE="616@0"\n',
2055
+ '#EXTINF:5,\n',
2056
+ 'main.mp4\n',
2057
+ '#EXTINF:0,\n',
2058
+ 'main.mp4\n',
2059
+ '#EXTINF:5,\n',
2060
+ 'main.mp4\n',
2061
+ ].join('');
2062
+
2063
+ const audio = [
2064
+ '#EXTM3U\n',
2065
+ '#EXT-X-PLAYLIST-TYPE:VOD\n',
2066
+ '#EXT-X-MAP:URI="init.mp4",BYTERANGE="616@0"\n',
2067
+ '#EXTINF:5,\n',
2068
+ 'main.mp4\n',
2069
+ '#EXTINF:0,\n',
2070
+ 'main.mp4\n',
2071
+ '#EXTINF:5,\n',
2072
+ 'main.mp4\n',
2073
+ ].join('');
2074
+
2075
+ fakeNetEngine
2076
+ .setResponseText('test:/master', master)
2077
+ .setResponseText('test:/audio', audio)
2078
+ .setResponseText('test:/video', video)
2079
+ .setResponseValue('test:/init.mp4', initSegmentData)
2080
+ .setResponseValue('test:/main.mp4', segmentData);
2081
+
2082
+ const actual = await parser.start('test:/master', playerInterface);
2083
+ await loadAllStreamsFor(actual);
2084
+
2085
+ expect(actual.variants.length).toBe(1);
2086
+
2087
+ const variant = actual.variants[0];
2088
+ expect(variant.video).toBeTruthy();
2089
+ expect(variant.audio).toBeTruthy();
2090
+
2091
+ await variant.video.createSegmentIndex();
2092
+ goog.asserts.assert(variant.video.segmentIndex != null,
2093
+ 'Null segmentIndex!');
2094
+
2095
+ const firstVideoReference = variant.video.segmentIndex.get(0);
2096
+ const secondVideoReference = variant.video.segmentIndex.get(1);
2097
+ const thirdVideoReference = variant.video.segmentIndex.get(2);
2098
+
2099
+ expect(firstVideoReference).not.toBe(null);
2100
+ expect(secondVideoReference).not.toBe(null);
2101
+ expect(thirdVideoReference).toBe(null);
2102
+
2103
+
2104
+ await variant.audio.createSegmentIndex();
2105
+ goog.asserts.assert(variant.audio.segmentIndex != null,
2106
+ 'Null segmentIndex!');
2107
+
2108
+ const firstAudioReference = variant.audio.segmentIndex.get(0);
2109
+ const secondAudioReference = variant.audio.segmentIndex.get(1);
2110
+ const thirdAudioReference = variant.audio.segmentIndex.get(2);
2111
+
2112
+ expect(firstAudioReference).not.toBe(null);
2113
+ expect(secondAudioReference).not.toBe(null);
2114
+ expect(thirdAudioReference).toBe(null);
2115
+ });
2116
+
1901
2117
  it('Disable audio does not create audio streams', async () => {
1902
2118
  const master = [
1903
2119
  '#EXTM3U\n',