shaka-player 4.0.0 → 4.0.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/config.yml +1 -0
  2. package/.github/workflows/release-please.yaml +6 -11
  3. package/AUTHORS +1 -0
  4. package/CHANGELOG.md +39 -0
  5. package/CONTRIBUTORS +1 -0
  6. package/README.md +4 -4
  7. package/build/misspellings.txt +1 -0
  8. package/build/test.py +1 -1
  9. package/demo/common/assets.js +1 -1
  10. package/demo/demo.less +0 -1
  11. package/demo/main.js +3 -3
  12. package/dist/controls.css +3 -3
  13. package/dist/controls.css.map +1 -1
  14. package/dist/demo.compiled.debug.js +3 -3
  15. package/dist/demo.compiled.debug.map +2 -2
  16. package/dist/demo.compiled.js +3 -3
  17. package/dist/demo.compiled.map +2 -2
  18. package/dist/demo.css +1 -1
  19. package/dist/demo.css.map +1 -1
  20. package/dist/deps.js +1 -1
  21. package/dist/locales.js +33 -33
  22. package/dist/receiver.compiled.debug.js +1 -1
  23. package/dist/receiver.compiled.debug.map +1 -1
  24. package/dist/receiver.compiled.js +1 -1
  25. package/dist/receiver.compiled.map +1 -1
  26. package/dist/shaka-player.compiled.d.ts +2 -1
  27. package/dist/shaka-player.compiled.debug.d.ts +2 -1
  28. package/dist/shaka-player.compiled.debug.externs.js +4 -2
  29. package/dist/shaka-player.compiled.debug.js +278 -267
  30. package/dist/shaka-player.compiled.debug.map +4 -4
  31. package/dist/shaka-player.compiled.externs.js +4 -2
  32. package/dist/shaka-player.compiled.js +815 -806
  33. package/dist/shaka-player.compiled.map +4 -4
  34. package/dist/shaka-player.ui.d.ts +2 -1
  35. package/dist/shaka-player.ui.debug.d.ts +2 -1
  36. package/dist/shaka-player.ui.debug.externs.js +4 -2
  37. package/dist/shaka-player.ui.debug.js +305 -293
  38. package/dist/shaka-player.ui.debug.map +4 -4
  39. package/dist/shaka-player.ui.externs.js +4 -2
  40. package/dist/shaka-player.ui.js +966 -959
  41. package/dist/shaka-player.ui.map +4 -4
  42. package/docs/tutorials/upgrade.md +4 -2
  43. package/externs/shaka/player.js +15 -2
  44. package/karma.conf.js +4 -0
  45. package/lib/abr/simple_abr_manager.js +2 -1
  46. package/lib/dash/dash_parser.js +2 -2
  47. package/lib/hls/hls_parser.js +211 -82
  48. package/lib/media/drm_engine.js +19 -0
  49. package/lib/media/media_source_engine.js +91 -37
  50. package/lib/media/segment_index.js +26 -1
  51. package/lib/media/segment_reference.js +10 -3
  52. package/lib/media/streaming_engine.js +2 -3
  53. package/lib/offline/indexeddb/storage_mechanism.js +4 -2
  54. package/lib/player.js +165 -107
  55. package/lib/text/vtt_text_parser.js +7 -7
  56. package/lib/util/config_utils.js +25 -0
  57. package/lib/util/error.js +7 -0
  58. package/lib/util/manifest_parser_utils.js +1 -1
  59. package/lib/util/mime_utils.js +62 -6
  60. package/lib/util/periods.js +5 -4
  61. package/lib/util/player_configuration.js +14 -14
  62. package/lib/util/stream_utils.js +5 -2
  63. package/package.json +6 -3
  64. package/test/hls/hls_live_unit.js +79 -7
  65. package/test/hls/hls_parser_unit.js +102 -8
  66. package/test/hls/manifest_text_parser_unit.js +19 -0
  67. package/test/offline/indexeddb_storage_unit.js +1 -1
  68. package/test/offline/storage_compatibility_unit.js +1 -1
  69. package/test/player_external.js +7 -1
  70. package/test/player_integration.js +6 -10
  71. package/test/player_src_equals_integration.js +81 -83
  72. package/test/player_unit.js +98 -1
  73. package/test/test/boot.js +9 -0
  74. package/test/test/externs/filters.js +4 -0
  75. package/test/test/util/simple_fakes.js +31 -2
  76. package/test/text/vtt_text_parser_unit.js +64 -5
  77. package/test/util/mime_utils_unit.js +54 -0
  78. package/test/util/periods_unit.js +79 -0
  79. package/ui/less/containers.less +1 -1
  80. package/ui/less/general.less +1 -1
  81. package/ui/less/range_elements.less +1 -0
  82. package/ui/resolution_selection.js +1 -1
  83. package/.github/pull_request_template.md +0 -10
  84. package/docs/tutorials/gsoc-ideas-list.md +0 -3
@@ -8,6 +8,7 @@ goog.provide('shaka.util.MimeUtils');
8
8
 
9
9
  goog.require('shaka.dependencies');
10
10
  goog.require('shaka.media.Transmuxer');
11
+ goog.require('shaka.util.ManifestParserUtils');
11
12
 
12
13
  /**
13
14
  * @summary A set of utility functions for dealing with MIME types.
@@ -34,6 +35,8 @@ shaka.util.MimeUtils = class {
34
35
  /**
35
36
  * Takes a MIME type and a codecs string and produces the full MIME
36
37
  * type. If it's a transport stream, convert its codecs to MP4 codecs.
38
+ * Otherwise for multiplexed content, convert the video MIME types to
39
+ * their audio equivalents if the content type is audio.
37
40
  *
38
41
  * @param {string} mimeType
39
42
  * @param {string} codecs
@@ -42,13 +45,19 @@ shaka.util.MimeUtils = class {
42
45
  */
43
46
  static getFullOrConvertedType(mimeType, codecs, contentType) {
44
47
  const fullMimeType = shaka.util.MimeUtils.getFullType(mimeType, codecs);
48
+ const ContentType = shaka.util.ManifestParserUtils.ContentType;
45
49
 
46
- if (!shaka.dependencies.muxjs() ||
47
- !shaka.media.Transmuxer.isTsContainer(fullMimeType)) {
48
- return fullMimeType;
50
+ if (shaka.media.Transmuxer.isTsContainer(fullMimeType)) {
51
+ if (shaka.dependencies.muxjs()) {
52
+ return shaka.media.Transmuxer.convertTsCodecs(
53
+ contentType, fullMimeType);
54
+ }
55
+ } else if (contentType == ContentType.AUDIO) {
56
+ // video/mp2t is the correct mime type for TS audio, so only replace the
57
+ // word "video" with "audio" for non-TS audio content.
58
+ return fullMimeType.replace('video', 'audio');
49
59
  }
50
-
51
- return shaka.media.Transmuxer.convertTsCodecs(contentType, fullMimeType);
60
+ return fullMimeType;
52
61
  }
53
62
 
54
63
 
@@ -96,6 +105,53 @@ shaka.util.MimeUtils = class {
96
105
  return codecs.split(',');
97
106
  }
98
107
 
108
+ /**
109
+ * Get the normalized codec from a codec string,
110
+ * independently of their container.
111
+ *
112
+ * @param {string} codecString
113
+ * @return {string}
114
+ */
115
+ static getNormalizedCodec(codecString) {
116
+ const parts =
117
+ shaka.util.MimeUtils.getCodecParts_(codecString);
118
+ const base = parts[0];
119
+ const profile = parts[1].toLowerCase();
120
+ switch (true) {
121
+ case base === 'mp4a' && profile === '69':
122
+ case base === 'mp4a' && profile === '6b':
123
+ return 'mp3';
124
+ case base === 'mp4a' && profile === '66':
125
+ case base === 'mp4a' && profile === '67':
126
+ case base === 'mp4a' && profile === '68':
127
+ case base === 'mp4a' && profile === '40.2':
128
+ case base === 'mp4a' && profile === '40.02':
129
+ case base === 'mp4a' && profile === '40.5':
130
+ case base === 'mp4a' && profile === '40.05':
131
+ case base === 'mp4a' && profile === '40.29':
132
+ case base === 'mp4a' && profile === '40.42': // Extended HE-AAC
133
+ return 'aac';
134
+ case base === 'mp4a' && profile === 'a5':
135
+ return 'ac-3'; // Dolby Digital
136
+ case base === 'mp4a' && profile === 'a6':
137
+ return 'ec-3'; // Dolby Digital Plus
138
+ case base === 'mp4a' && profile === 'b2':
139
+ return 'dtsx'; // DTS:X
140
+ case base === 'mp4a' && profile === 'a9':
141
+ return 'dtsc'; // DTS Digital Surround
142
+ case base === 'avc1':
143
+ case base === 'avc3':
144
+ return 'avc'; // H264
145
+ case base === 'hvc1':
146
+ case base === 'hev1':
147
+ return 'hevc'; // H265
148
+ case base === 'dvh1':
149
+ case base === 'dvhe':
150
+ return 'dovi'; // Dolby Vision
151
+ }
152
+ return base;
153
+ }
154
+
99
155
  /**
100
156
  * Get the base codec from a codec string.
101
157
  *
@@ -152,7 +208,7 @@ shaka.util.MimeUtils = class {
152
208
 
153
209
  const base = parts[0];
154
210
 
155
- parts.pop();
211
+ parts.shift();
156
212
  const profile = parts.join('.');
157
213
 
158
214
  // Make sure that we always return a "base" and "profile".
@@ -573,8 +573,8 @@ shaka.util.PeriodCombiner = class {
573
573
  // TODO(#1528): Consider changing this when we support codec switching.
574
574
  const hasCodec = outputStreams.some((s) => {
575
575
  return s.mimeType == stream.mimeType &&
576
- shaka.util.MimeUtils.getCodecBase(s.codecs) ==
577
- shaka.util.MimeUtils.getCodecBase(stream.codecs);
576
+ shaka.util.MimeUtils.getNormalizedCodec(s.codecs) ==
577
+ shaka.util.MimeUtils.getNormalizedCodec(stream.codecs);
578
578
  });
579
579
  if (!hasCodec) {
580
580
  continue;
@@ -1050,10 +1050,11 @@ shaka.util.PeriodCombiner = class {
1050
1050
  * @private
1051
1051
  */
1052
1052
  static areAVStreamsCompatible_(outputStream, candidate) {
1053
- const getCodecBase = (codecs) => shaka.util.MimeUtils.getCodecBase(codecs);
1053
+ const getCodec = (codecs) =>
1054
+ shaka.util.MimeUtils.getNormalizedCodec(codecs);
1054
1055
  // Check MIME type and codecs, which should always be the same.
1055
1056
  if (candidate.mimeType != outputStream.mimeType ||
1056
- getCodecBase(candidate.codecs) != getCodecBase(outputStream.codecs)) {
1057
+ getCodec(candidate.codecs) != getCodec(outputStream.codecs)) {
1057
1058
  return false;
1058
1059
  }
1059
1060
 
@@ -16,14 +16,6 @@ goog.require('shaka.util.ManifestParserUtils');
16
16
  goog.require('shaka.util.Platform');
17
17
 
18
18
 
19
- // TODO(vaage): Many times in our configs, we need to create an empty
20
- // implementation of a method, but to avoid closure from removing unused
21
- // parameters (and breaking our merge config code) we need to use each
22
- // parameter. Is there a better solution to this problem than what we are
23
- // doing now?
24
- //
25
- // NOTE: Chrome App Content Security Policy prohibits usage of new Function()
26
-
27
19
  /**
28
20
  * @final
29
21
  * @export
@@ -74,6 +66,11 @@ shaka.util.PlayerConfiguration = class {
74
66
  clearKeys: {}, // key is arbitrary key system ID, value must be string
75
67
  advanced: {}, // key is arbitrary key system ID, value is a record type
76
68
  delayLicenseRequestUntilPlayed: false,
69
+ initDataTransform: (initData, initDataType, drmInfo) => {
70
+ return shaka.util.ConfigUtils.referenceParametersAndReturn(
71
+ [initData, initDataType, drmInfo],
72
+ initData);
73
+ },
77
74
  logLicenseExchange: false,
78
75
  updateExpirationTime: 1,
79
76
  preferredKeySystems: [],
@@ -113,11 +110,10 @@ shaka.util.PlayerConfiguration = class {
113
110
  'urn:uuid:f239e769-efa3-4850-9c16-a903c6932efb':
114
111
  'com.adobe.primetime',
115
112
  },
116
- // Need some operation in the callback or else closure may remove calls
117
- // to the function as it would be a no-op. The operation can't just be
118
- // a log message, because those are stripped in the compiled build.
119
113
  manifestPreprocessor: (element) => {
120
- return element;
114
+ return shaka.util.ConfigUtils.referenceParametersAndReturn(
115
+ [element],
116
+ element);
121
117
  },
122
118
  },
123
119
  hls: {
@@ -138,7 +134,9 @@ shaka.util.PlayerConfiguration = class {
138
134
  // log message, because those are stripped in the compiled build.
139
135
  failureCallback: (error) => {
140
136
  shaka.log.error('Unhandled streaming error', error);
141
- return [error];
137
+ return shaka.util.ConfigUtils.referenceParametersAndReturn(
138
+ [error],
139
+ undefined);
142
140
  },
143
141
  // When low latency streaming is enabled, rebufferingGoal will default to
144
142
  // 0.01 if not specified.
@@ -214,7 +212,9 @@ shaka.util.PlayerConfiguration = class {
214
212
  // to the function as it would be a no-op. The operation can't just be a
215
213
  // log message, because those are stripped in the compiled build.
216
214
  progressCallback: (content, progress) => {
217
- return [content, progress];
215
+ return shaka.util.ConfigUtils.referenceParametersAndReturn(
216
+ [content, progress],
217
+ undefined);
218
218
  },
219
219
 
220
220
  // By default we use persistent licenses as forces errors to surface if
@@ -283,12 +283,14 @@ shaka.util.StreamUtils = class {
283
283
  // both be considered the same codec: avc1.42c01e, avc1.4d401f
284
284
  let baseVideoCodec = '';
285
285
  if (variant.video) {
286
- baseVideoCodec = shaka.util.MimeUtils.getCodecBase(variant.video.codecs);
286
+ baseVideoCodec =
287
+ shaka.util.MimeUtils.getNormalizedCodec(variant.video.codecs);
287
288
  }
288
289
 
289
290
  let baseAudioCodec = '';
290
291
  if (variant.audio) {
291
- baseAudioCodec = shaka.util.MimeUtils.getCodecBase(variant.audio.codecs);
292
+ baseAudioCodec =
293
+ shaka.util.MimeUtils.getNormalizedCodec(variant.audio.codecs);
292
294
  }
293
295
 
294
296
  return baseVideoCodec + '-' + baseAudioCodec;
@@ -1006,6 +1008,7 @@ shaka.util.StreamUtils = class {
1006
1008
  track.channelsCount = audio.channelsCount;
1007
1009
  track.audioSamplingRate = audio.audioSamplingRate;
1008
1010
  track.audioBandwidth = audio.bandwidth || null;
1011
+ track.spatialAudio = audio.spatialAudio;
1009
1012
  track.label = audio.label;
1010
1013
  track.audioRoles = audio.roles;
1011
1014
  }
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.0.0",
4
+ "version": "4.0.3",
5
5
  "homepage": "https://github.com/shaka-project/shaka-player",
6
6
  "author": "Google",
7
7
  "maintainers": [
@@ -50,7 +50,7 @@
50
50
  "less": "https://gitpkg.now.sh/joeyparrish/less.js/packages/less?28c63a43",
51
51
  "less-plugin-clean-css": "github:austingardner/less-plugin-clean-css#4e9e77bf",
52
52
  "material-design-lite": "^1.3.0",
53
- "mux.js": "^5.14.1",
53
+ "mux.js": "^6.1.0",
54
54
  "open-sans-fonts": "^1.6.2",
55
55
  "postcss-less": "^6.0.0",
56
56
  "pwacompat": "^2.0.17",
@@ -96,6 +96,9 @@
96
96
  "prepublishOnly": "python build/checkversion.py && python build/all.py --force"
97
97
  },
98
98
  "dependencies": {
99
- "eme-encryption-scheme-polyfill": "^2.0.3"
99
+ "eme-encryption-scheme-polyfill": "^2.0.5"
100
+ },
101
+ "engines": {
102
+ "node": ">=14"
100
103
  }
101
104
  }
@@ -539,11 +539,13 @@ describe('HlsParser live', () => {
539
539
  ManifestParser.verifySegmentIndex(video, [ref1, ref2]);
540
540
  });
541
541
 
542
+ // Test for https://github.com/shaka-project/shaka-player/issues/4223
542
543
  it('parses streams with partial and preload hinted segments', async () => {
543
544
  playerInterface.isLowLatencyMode = () => true;
544
545
  const mediaWithPartialSegments = [
545
546
  '#EXTM3U\n',
546
547
  '#EXT-X-TARGETDURATION:5\n',
548
+ '#EXT-X-PART-INF:PART-TARGET=1.5\n',
547
549
  '#EXT-X-MAP:URI="init.mp4",BYTERANGE="616@0"\n',
548
550
  '#EXT-X-MEDIA-SEQUENCE:0\n',
549
551
  // ref includes partialRef, partialRef2
@@ -576,26 +578,73 @@ describe('HlsParser live', () => {
576
578
  'test:/partial2.mp4', 2, 4, /* syncTime= */ null,
577
579
  /* baseUri= */ '', /* startByte= */ 200, /* endByte= */ 429);
578
580
 
581
+ const ref = makeReference(
582
+ 'test:/main.mp4', 0, 4, /* syncTime= */ null,
583
+ /* baseUri= */ '', /* startByte= */ 0, /* endByte= */ 429,
584
+ /* timestampOffset= */ 0, [partialRef, partialRef2]);
585
+
579
586
  const partialRef3 = makeReference(
580
587
  'test:/partial.mp4', 4, 6, /* syncTime= */ null,
581
588
  /* baseUri= */ '', /* startByte= */ 0, /* endByte= */ 209);
582
589
 
583
- // A preload hinted partial segment doesn't have duration information,
584
- // so its startTime and endTime are the same.
585
590
  const preloadRef = makeReference(
586
- 'test:/partial.mp4', 6, 6, /* syncTime= */ null,
591
+ 'test:/partial.mp4', 6, 7.5, /* syncTime= */ null,
587
592
  /* baseUri= */ '', /* startByte= */ 210, /* endByte= */ null);
588
593
 
594
+ // ref2 is not fully published yet, so it doesn't have a segment uri.
595
+ const ref2 = makeReference(
596
+ '', 4, 7.5, /* syncTime= */ null,
597
+ /* baseUri= */ '', /* startByte= */ 0, /* endByte= */ null,
598
+ /* timestampOffset= */ 0, [partialRef3, preloadRef]);
599
+
600
+ const manifest = await parser.start('test:/master', playerInterface);
601
+ const video = manifest.variants[0].video;
602
+ await video.createSegmentIndex();
603
+ ManifestParser.verifySegmentIndex(video, [ref, ref2]);
604
+ });
605
+
606
+ // Test for https://github.com/shaka-project/shaka-player/issues/4223
607
+ it('ignores preload hinted segments without target duration', async () => {
608
+ playerInterface.isLowLatencyMode = () => true;
609
+
610
+ // Missing PART-TARGET, so preload hints are skipped.
611
+ const mediaWithPartialSegments = [
612
+ '#EXTM3U\n',
613
+ '#EXT-X-TARGETDURATION:5\n',
614
+ '#EXT-X-MAP:URI="init.mp4",BYTERANGE="616@0"\n',
615
+ '#EXT-X-MEDIA-SEQUENCE:0\n',
616
+ '#EXTINF:4,\n',
617
+ // ref1
618
+ 'main.mp4\n',
619
+ // ref2 includes partialRef, but not preloadRef
620
+ // partialRef
621
+ '#EXT-X-PART:DURATION=2,URI="partial.mp4",BYTERANGE=210@0\n',
622
+ // preloadRef
623
+ '#EXT-X-PRELOAD-HINT:TYPE=PART,URI="partial.mp4",BYTERANGE-START=210\n',
624
+ ].join('');
625
+
626
+ fakeNetEngine
627
+ .setResponseText('test:/master', master)
628
+ .setResponseText('test:/video', mediaWithPartialSegments)
629
+ .setResponseValue('test:/init.mp4', initSegmentData)
630
+ .setResponseValue('test:/main.mp4', segmentData)
631
+ .setResponseValue('test:/partial.mp4', segmentData)
632
+ .setResponseValue('test:/partial2.mp4', segmentData);
633
+
589
634
  const ref = makeReference(
590
635
  'test:/main.mp4', 0, 4, /* syncTime= */ null,
591
- /* baseUri= */ '', /* startByte= */ 0, /* endByte= */ 429,
592
- /* timestampOffset= */ 0, [partialRef, partialRef2]);
636
+ /* baseUri= */ '', /* startByte= */ 0, /* endByte= */ null,
637
+ /* timestampOffset= */ 0, []);
638
+
639
+ const partialRef = makeReference(
640
+ 'test:/partial.mp4', 4, 6, /* syncTime= */ null,
641
+ /* baseUri= */ '', /* startByte= */ 0, /* endByte= */ 209);
593
642
 
594
643
  // ref2 is not fully published yet, so it doesn't have a segment uri.
595
644
  const ref2 = makeReference(
596
645
  '', 4, 6, /* syncTime= */ null,
597
- /* baseUri= */ '', /* startByte= */ 0, /* endByte= */ null,
598
- /* timestampOffset= */ 0, [partialRef3, preloadRef]);
646
+ /* baseUri= */ '', /* startByte= */ 0, /* endByte= */ 209,
647
+ /* timestampOffset= */ 0, [partialRef]);
599
648
 
600
649
  const manifest = await parser.start('test:/master', playerInterface);
601
650
  const video = manifest.variants[0].video;
@@ -603,6 +652,29 @@ describe('HlsParser live', () => {
603
652
  ManifestParser.verifySegmentIndex(video, [ref, ref2]);
604
653
  });
605
654
 
655
+ // Test for https://github.com/shaka-project/shaka-player/issues/4185
656
+ it('does not fail on preload hints with LL mode off', async () => {
657
+ // LL mode must be off for this test!
658
+ playerInterface.isLowLatencyMode = () => false;
659
+
660
+ const mediaWithPartialSegments = [
661
+ '#EXTM3U\n',
662
+ '#EXT-X-TARGETDURATION:5\n',
663
+ '#EXT-X-PART-INF:PART-TARGET=1.5\n',
664
+ '#EXTINF:4,\n',
665
+ 'main.mp4\n',
666
+ '#EXT-X-PART:DURATION=2,URI="partial.mp4",BYTERANGE=210@0\n',
667
+ '#EXT-X-PRELOAD-HINT:TYPE=PART,URI="partial.mp4",BYTERANGE-START=210\n',
668
+ ].join('');
669
+
670
+ fakeNetEngine
671
+ .setResponseText('test:/master', master)
672
+ .setResponseText('test:/video', mediaWithPartialSegments);
673
+
674
+ // If this throws, the test fails. Otherwise, it passes.
675
+ await parser.start('test:/master', playerInterface);
676
+ });
677
+
606
678
  describe('update', () => {
607
679
  it('adds new segments when they appear', async () => {
608
680
  const ref1 = makeReference(
@@ -2021,6 +2021,10 @@ describe('HlsParser', () => {
2021
2021
  });
2022
2022
 
2023
2023
  describe('produces syncTime', () => {
2024
+ // Corresponds to "2000-01-01T00:00:00.00Z".
2025
+ // All the PROGRAM-DATE-TIME values in the tests below are at or after this.
2026
+ const syncTimeBase = 946684800;
2027
+
2024
2028
  /**
2025
2029
  * @param {number} startTime
2026
2030
  * @param {number} endTime
@@ -2039,9 +2043,10 @@ describe('HlsParser', () => {
2039
2043
  /**
2040
2044
  * @param {string} media
2041
2045
  * @param {!Array.<number>} startTimes
2046
+ * @param {number} syncTimeOffset
2042
2047
  * @param {(function(!shaka.media.SegmentReference))=} modifyFn
2043
2048
  */
2044
- async function test(media, startTimes, modifyFn) {
2049
+ async function test(media, startTimes, syncTimeOffset, modifyFn) {
2045
2050
  const master = [
2046
2051
  '#EXTM3U\n',
2047
2052
  '#EXT-X-STREAM-INF:BANDWIDTH=200,CODECS="avc1,vtt",',
@@ -2053,7 +2058,8 @@ describe('HlsParser', () => {
2053
2058
  for (let i = 0; i < startTimes.length - 1; i++) {
2054
2059
  const startTime = startTimes[i];
2055
2060
  const endTime = startTimes[i + 1];
2056
- const reference = makeReference(startTime, endTime, startTime);
2061
+ const reference = makeReference(
2062
+ startTime, endTime, syncTimeOffset + startTime);
2057
2063
  if (modifyFn) {
2058
2064
  modifyFn(reference);
2059
2065
  }
@@ -2100,7 +2106,7 @@ describe('HlsParser', () => {
2100
2106
  '#EXT-X-PROGRAM-DATE-TIME:2000-01-01T00:00:25.00Z\n',
2101
2107
  '#EXTINF:5,\n',
2102
2108
  'main.mp4',
2103
- ].join(''), [0, 5, 10, 15, 20, 25]);
2109
+ ].join(''), [0, 5, 10, 15, 20, 25], syncTimeBase + 5);
2104
2110
  });
2105
2111
 
2106
2112
  it('when some EXT-X-PROGRAM-DATE-TIME values are missing', async () => {
@@ -2122,7 +2128,7 @@ describe('HlsParser', () => {
2122
2128
  'main.mp4\n',
2123
2129
  '#EXTINF:4,\n',
2124
2130
  'main.mp4',
2125
- ].join(''), [0, 2, 7, 12, 17, 19, 23]);
2131
+ ].join(''), [0, 2, 7, 12, 17, 19, 23], syncTimeBase + 8);
2126
2132
  });
2127
2133
 
2128
2134
  it('except when ignoreManifestProgramDateTime is set', async () => {
@@ -2148,7 +2154,7 @@ describe('HlsParser', () => {
2148
2154
  '#EXT-X-PROGRAM-DATE-TIME:2000-01-01T00:00:25.00Z\n',
2149
2155
  '#EXTINF:5,\n',
2150
2156
  'main.mp4',
2151
- ].join(''), [0, 5, 10, 15, 20, 25], (reference) => {
2157
+ ].join(''), [0, 5, 10, 15, 20, 25], syncTimeBase + 5, (reference) => {
2152
2158
  reference.syncTime = null;
2153
2159
  });
2154
2160
  });
@@ -2176,11 +2182,11 @@ describe('HlsParser', () => {
2176
2182
  '#EXT-X-PROGRAM-DATE-TIME:2000-01-01T00:00:25.00Z\n',
2177
2183
  '#EXTINF:5,\n',
2178
2184
  'main.mp4',
2179
- ].join(''), [0, 5, 10, 15, 20, 25], (reference) => {
2185
+ ].join(''), [0, 5, 10, 15, 20, 25], syncTimeBase + 5, (reference) => {
2180
2186
  if (reference.startTime == 10) {
2181
2187
  reference.partialReferences = [
2182
- makeReference(10, 12.5, 10),
2183
- makeReference(12.5, 15, 12.5),
2188
+ makeReference(10, 12.5, syncTimeBase + 15),
2189
+ makeReference(12.5, 15, syncTimeBase + 17.5),
2184
2190
  ];
2185
2191
  }
2186
2192
  });
@@ -3555,4 +3561,92 @@ describe('HlsParser', () => {
3555
3561
 
3556
3562
  await testHlsParser(media, '', manifest);
3557
3563
  });
3564
+
3565
+ it('syncs on sequence with ignoreManifestProgramDateTime', async () => {
3566
+ const config = shaka.util.PlayerConfiguration.createDefault().manifest;
3567
+ config.hls.ignoreManifestProgramDateTime = true;
3568
+ parser.configure(config);
3569
+
3570
+ const master = [
3571
+ '#EXTM3U\n',
3572
+ '#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="aud1",LANGUAGE="eng",URI="audio"\n',
3573
+ '#EXT-X-STREAM-INF:BANDWIDTH=200,CODECS="avc1,mp4a",',
3574
+ 'RESOLUTION=960x540,FRAME-RATE=60,AUDIO="aud1"\n',
3575
+ 'video\n',
3576
+ ].join('');
3577
+
3578
+ const video = [
3579
+ '#EXTM3U\n',
3580
+ '#EXT-X-TARGETDURATION:5\n',
3581
+ '#EXT-X-MAP:URI="init.mp4",BYTERANGE="616@0"\n',
3582
+ '#EXT-X-MEDIA-SEQUENCE:1\n',
3583
+ '#EXTINF:5,\n',
3584
+ 'video1.mp4\n',
3585
+ '#EXTINF:5,\n',
3586
+ 'video2.mp4\n',
3587
+ '#EXTINF:5,\n',
3588
+ 'video3.mp4\n',
3589
+ ].join('');
3590
+
3591
+ const audio = [
3592
+ '#EXTM3U\n',
3593
+ '#EXT-X-TARGETDURATION:5\n',
3594
+ '#EXT-X-MAP:URI="init.mp4",BYTERANGE="616@0"\n',
3595
+ '#EXT-X-MEDIA-SEQUENCE:3\n',
3596
+ '#EXTINF:5,\n',
3597
+ 'audio3.mp4\n',
3598
+ '#EXTINF:5,\n',
3599
+ 'audio4.mp4\n',
3600
+ ].join('');
3601
+
3602
+ const manifest = shaka.test.ManifestGenerator.generate((manifest) => {
3603
+ manifest.sequenceMode = true;
3604
+ manifest.anyTimeline();
3605
+ manifest.addPartialVariant((variant) => {
3606
+ variant.language = 'en';
3607
+ variant.bandwidth = 200;
3608
+ variant.addPartialStream(ContentType.VIDEO, (stream) => {
3609
+ stream.mime('video/mp4', 'avc1');
3610
+ stream.size(960, 540);
3611
+ stream.frameRate = 60;
3612
+ });
3613
+ variant.addPartialStream(ContentType.AUDIO, (stream) => {
3614
+ stream.language = 'en';
3615
+ stream.mime('audio/mp4', 'mp4a');
3616
+ });
3617
+ });
3618
+ });
3619
+
3620
+ fakeNetEngine
3621
+ .setResponseText('test:/master', master)
3622
+ .setResponseText('test:/audio', audio)
3623
+ .setResponseText('test:/video', video)
3624
+ .setResponseValue('test:/init.mp4', initSegmentData);
3625
+
3626
+ const actualManifest = await parser.start('test:/master', playerInterface);
3627
+ expect(actualManifest).toEqual(manifest);
3628
+
3629
+ const actualVideo = actualManifest.variants[0].video;
3630
+ await actualVideo.createSegmentIndex();
3631
+ goog.asserts.assert(actualVideo.segmentIndex != null, 'Null segmentIndex!');
3632
+
3633
+ const actualAudio = actualManifest.variants[0].audio;
3634
+ await actualAudio.createSegmentIndex();
3635
+ goog.asserts.assert(actualAudio.segmentIndex != null, 'Null segmentIndex!');
3636
+
3637
+ // Verify that the references are aligned on sequence number.
3638
+ const videoSegments = Array.from(actualVideo.segmentIndex);
3639
+ const audioSegments = Array.from(actualAudio.segmentIndex);
3640
+
3641
+ // The first two were dropped to align with the audio.
3642
+ expect(videoSegments.map((ref) => ref.getUris()[0])).toEqual([
3643
+ 'test:/video3.mp4',
3644
+ ]);
3645
+ // Audio has a 4th segment that video doesn't, but that doesn't get clipped
3646
+ // or used as the base. Alignment is truly based on media sequence number.
3647
+ expect(audioSegments.map((ref) => ref.getUris()[0])).toEqual([
3648
+ 'test:/audio3.mp4',
3649
+ 'test:/audio4.mp4',
3650
+ ]);
3651
+ });
3558
3652
  });
@@ -238,6 +238,25 @@ describe('ManifestTextParser', () => {
238
238
  '#EXTM3U\n' +
239
239
  '#EXT-X-MEDIA:CODECS="avc1.64002a,mp4a.40.2",AUDIO="a1,a2"',
240
240
 
241
+ // manifest URI:
242
+ 'https://test/manifest.m3u8');
243
+
244
+ verifyPlaylist(
245
+ {
246
+ type: shaka.hls.PlaylistType.MASTER,
247
+ tags: [
248
+ new shaka.hls.Tag(/* id= */ 3, 'EXT-X-MEDIA',
249
+ [
250
+ new shaka.hls.Attribute('CODECS',
251
+ 'av01.0.08M.08,mp4a.40.2'),
252
+ ]),
253
+ ],
254
+ },
255
+
256
+ // playlist text:
257
+ '#EXTM3U\n' +
258
+ '#EXT-X-MEDIA:CODECS="av01.0.08M.08,mp4a.40.2"',
259
+
241
260
  // manifest URI:
242
261
  'https://test/manifest.m3u8');
243
262
  });
@@ -4,7 +4,7 @@
4
4
  * SPDX-License-Identifier: Apache-2.0
5
5
  */
6
6
 
7
- filterDescribe('IndexeddbStorageCell', () => window.indexedDB, () => {
7
+ filterDescribe('IndexeddbStorageCell', offlineSupported, () => {
8
8
  const IndexedDBUtils = shaka.test.IndexedDBUtils;
9
9
  const OfflineUtils = shaka.test.OfflineUtils;
10
10
  const Util = shaka.test.Util;
@@ -91,7 +91,7 @@ const compatibilityTestsMetadata = [
91
91
  },
92
92
  ];
93
93
 
94
- filterDescribe('Storage Compatibility', () => window.indexedDB, () => {
94
+ filterDescribe('Storage Compatibility', offlineSupported, () => {
95
95
  for (const metadata of compatibilityTestsMetadata) {
96
96
  describe(metadata.name, () => {
97
97
  makeTests(metadata);
@@ -114,7 +114,13 @@ describe('Player', () => {
114
114
  const networkingEngine = player.getNetworkingEngine();
115
115
  asset.applyFilters(networkingEngine);
116
116
 
117
- await player.load(asset.manifestUri);
117
+ // Rather than awaiting the load() method, catch any load() errors and
118
+ // wait on the 'canplay' event. This has the advantage that we will
119
+ // get better logging of the media state on a timeout, since that
120
+ // capabilitiy is built into the waiter for media element events.
121
+ player.load(asset.manifestUri).catch(fail);
122
+ await waiter.timeoutAfter(60).waitForEvent(video, 'canplay');
123
+
118
124
  if (asset.features) {
119
125
  const isLive = asset.features.includes(Feature.LIVE);
120
126
  expect(player.isLive()).toBe(isLive);
@@ -1069,16 +1069,16 @@ describe('Player', () => {
1069
1069
  });
1070
1070
  }); // describe('unloading')
1071
1071
 
1072
- describe('chapters', () => {
1073
- it('add external chapters in vtt format', async () => {
1072
+ describe('addChaptersTrack', () => {
1073
+ it('adds external chapters in vtt format', async () => {
1074
1074
  await player.load('test:sintel_no_text_compiled');
1075
1075
  const locationUri = new goog.Uri(location.href);
1076
1076
  const partialUri1 = new goog.Uri('/base/test/test/assets/chapters.vtt');
1077
1077
  const absoluteUri1 = locationUri.resolve(partialUri1);
1078
1078
  await player.addChaptersTrack(absoluteUri1.toString(), 'en');
1079
1079
 
1080
- await shaka.test.Util.delay(1.5);
1081
-
1080
+ // Data should be available as soon as addChaptersTrack resolves.
1081
+ // See https://github.com/shaka-project/shaka-player/issues/4186
1082
1082
  const chapters = player.getChapters('en');
1083
1083
  expect(chapters.length).toBe(3);
1084
1084
  const chapter1 = chapters[0];
@@ -1098,8 +1098,6 @@ describe('Player', () => {
1098
1098
  const absoluteUri2 = locationUri.resolve(partialUri2);
1099
1099
  await player.addChaptersTrack(absoluteUri2.toString(), 'en');
1100
1100
 
1101
- await shaka.test.Util.delay(1.5);
1102
-
1103
1101
  const chaptersUpdated = player.getChapters('en');
1104
1102
  expect(chaptersUpdated.length).toBe(6);
1105
1103
  const chapterUpdated1 = chaptersUpdated[0];
@@ -1128,15 +1126,13 @@ describe('Player', () => {
1128
1126
  expect(chapterUpdated6.endTime).toBe(61.349);
1129
1127
  });
1130
1128
 
1131
- it('add external chapters in srt format', async () => {
1129
+ it('adds external chapters in srt format', async () => {
1132
1130
  await player.load('test:sintel_no_text_compiled');
1133
1131
  const locationUri = new goog.Uri(location.href);
1134
1132
  const partialUri = new goog.Uri('/base/test/test/assets/chapters.srt');
1135
1133
  const absoluteUri = locationUri.resolve(partialUri);
1136
1134
  await player.addChaptersTrack(absoluteUri.toString(), 'es');
1137
1135
 
1138
- await shaka.test.Util.delay(1.5);
1139
-
1140
1136
  const chapters = player.getChapters('es');
1141
1137
  expect(chapters.length).toBe(3);
1142
1138
  const chapter1 = chapters[0];
@@ -1152,5 +1148,5 @@ describe('Player', () => {
1152
1148
  expect(chapter3.startTime).toBe(30);
1153
1149
  expect(chapter3.endTime).toBe(61.349);
1154
1150
  });
1155
- }); // describe('chapters')
1151
+ }); // describe('addChaptersTrack')
1156
1152
  });