shaka-player 4.0.2 → 4.1.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 (86) hide show
  1. package/AUTHORS +1 -0
  2. package/CHANGELOG.md +36 -16
  3. package/CONTRIBUTORS +2 -0
  4. package/build/test.py +1 -1
  5. package/demo/common/assets.js +1 -1
  6. package/demo/common/message_ids.js +1 -0
  7. package/demo/config.js +3 -1
  8. package/demo/locales/en.json +1 -0
  9. package/demo/locales/source.json +4 -0
  10. package/demo/main.js +3 -3
  11. package/dist/demo.compiled.debug.js +7 -7
  12. package/dist/demo.compiled.debug.map +2 -2
  13. package/dist/demo.compiled.js +7 -7
  14. package/dist/demo.compiled.map +2 -2
  15. package/dist/deps.js +2 -2
  16. package/dist/locales.js +30 -30
  17. package/dist/receiver.compiled.debug.js +3 -3
  18. package/dist/receiver.compiled.debug.map +2 -2
  19. package/dist/receiver.compiled.js +3 -3
  20. package/dist/receiver.compiled.map +2 -2
  21. package/dist/shaka-player.compiled.d.ts +14 -8
  22. package/dist/shaka-player.compiled.debug.d.ts +14 -8
  23. package/dist/shaka-player.compiled.debug.externs.js +13 -4
  24. package/dist/shaka-player.compiled.debug.js +296 -282
  25. package/dist/shaka-player.compiled.debug.map +4 -4
  26. package/dist/shaka-player.compiled.externs.js +13 -4
  27. package/dist/shaka-player.compiled.js +781 -773
  28. package/dist/shaka-player.compiled.map +4 -4
  29. package/dist/shaka-player.ui.d.ts +15 -9
  30. package/dist/shaka-player.ui.debug.d.ts +15 -9
  31. package/dist/shaka-player.ui.debug.externs.js +13 -4
  32. package/dist/shaka-player.ui.debug.js +345 -326
  33. package/dist/shaka-player.ui.debug.map +4 -4
  34. package/dist/shaka-player.ui.externs.js +13 -4
  35. package/dist/shaka-player.ui.js +955 -949
  36. package/dist/shaka-player.ui.map +4 -4
  37. package/docs/design/chromecast.md +3 -1
  38. package/docs/tutorials/drm-config.md +16 -0
  39. package/docs/tutorials/ui.md +26 -0
  40. package/externs/chromecast.js +9 -2
  41. package/externs/shaka/manifest.js +7 -0
  42. package/externs/shaka/player.js +25 -4
  43. package/externs/shaka/text.js +7 -0
  44. package/lib/cast/cast_proxy.js +19 -8
  45. package/lib/cast/cast_receiver.js +3 -3
  46. package/lib/cast/cast_sender.js +12 -2
  47. package/lib/hls/hls_parser.js +189 -69
  48. package/lib/media/drm_engine.js +17 -8
  49. package/lib/media/gap_jumping_controller.js +23 -1
  50. package/lib/media/playhead.js +47 -4
  51. package/lib/media/segment_index.js +26 -1
  52. package/lib/media/segment_reference.js +10 -3
  53. package/lib/media/stall_detector.js +19 -2
  54. package/lib/media/streaming_engine.js +9 -2
  55. package/lib/offline/manifest_converter.js +1 -0
  56. package/lib/player.js +175 -69
  57. package/lib/text/cue.js +6 -0
  58. package/lib/text/ui_text_displayer.js +1 -0
  59. package/lib/text/vtt_text_parser.js +4 -0
  60. package/lib/util/error.js +6 -0
  61. package/lib/util/fake_event.js +39 -0
  62. package/lib/util/player_configuration.js +2 -0
  63. package/lib/util/stats.js +28 -1
  64. package/lib/util/stream_utils.js +8 -0
  65. package/package.json +2 -2
  66. package/test/cast/cast_proxy_unit.js +3 -1
  67. package/test/cast/cast_sender_unit.js +21 -6
  68. package/test/demo/demo_unit.js +2 -1
  69. package/test/hls/hls_parser_unit.js +102 -8
  70. package/test/media/adaptation_set_unit.js +1 -0
  71. package/test/media/playhead_unit.js +114 -25
  72. package/test/media/stall_detector_unit.js +20 -2
  73. package/test/media/streaming_engine_integration.js +2 -1
  74. package/test/media/streaming_engine_unit.js +1 -0
  75. package/test/player_integration.js +3 -0
  76. package/test/player_unit.js +142 -1
  77. package/test/test/util/manifest_generator.js +2 -0
  78. package/test/test/util/simple_fakes.js +14 -0
  79. package/test/test/util/streaming_engine_util.js +1 -0
  80. package/test/text/vtt_text_parser_unit.js +4 -0
  81. package/test/ui/ui_unit.js +18 -5
  82. package/test/util/stream_utils_unit.js +108 -0
  83. package/ui/controls.js +12 -8
  84. package/ui/externs/ui.js +10 -1
  85. package/ui/statistics_button.js +10 -0
  86. package/ui/ui.js +12 -1
package/lib/text/cue.js CHANGED
@@ -144,6 +144,12 @@ shaka.text.Cue = class {
144
144
  */
145
145
  this.border = '';
146
146
 
147
+ /**
148
+ * @override
149
+ * @exportInterface
150
+ */
151
+ this.textShadow = '';
152
+
147
153
  /**
148
154
  * @override
149
155
  * @exportInterface
@@ -411,6 +411,7 @@ shaka.text.UITextDisplayer = class {
411
411
  style.paddingRight =
412
412
  shaka.text.UITextDisplayer.convertLengthValue_(
413
413
  cue.linePadding, cue, this.videoContainer_);
414
+ style.textShadow = cue.textShadow;
414
415
 
415
416
  if (cue.backgroundImage) {
416
417
  style.backgroundImage = 'url(\'' + cue.backgroundImage + '\')';
@@ -293,6 +293,10 @@ shaka.text.VttTextParser = class {
293
293
  validStyle = true;
294
294
  cue.opacity = parseFloat(value);
295
295
  break;
296
+ case 'text-shadow':
297
+ validStyle = true;
298
+ cue.textShadow = value;
299
+ break;
296
300
  case 'white-space':
297
301
  validStyle = true;
298
302
  cue.wrapLine = value != 'noWrap';
package/lib/util/error.js CHANGED
@@ -267,6 +267,12 @@ shaka.util.Error.Code = {
267
267
  */
268
268
  'ATTEMPTS_EXHAUSTED': 1010,
269
269
 
270
+ /**
271
+ * The segment is missing.
272
+ * <br> error.data[0] is the URI.
273
+ */
274
+ 'SEGMENT_MISSING': 1011,
275
+
270
276
 
271
277
  /** The text parser failed to parse a text stream due to an invalid header. */
272
278
  'INVALID_TEXT_HEADER': 2000,
@@ -144,3 +144,42 @@ shaka.util.FakeEvent = class {
144
144
  */
145
145
  stopPropagation() {}
146
146
  };
147
+
148
+
149
+ /**
150
+ * An internal enum that contains the string values of all of the player events.
151
+ * This exists primarily to act as an implicit list of events, for tests.
152
+ *
153
+ * @enum {string}
154
+ */
155
+ shaka.util.FakeEvent.EventName = {
156
+ AbrStatusChanged: 'abrstatuschanged',
157
+ Adaptation: 'adaptation',
158
+ Buffering: 'buffering',
159
+ DownloadFailed: 'downloadfailed',
160
+ DownloadHeadersReceived: 'downloadheadersreceived',
161
+ DrmSessionUpdate: 'drmsessionupdate',
162
+ Emsg: 'emsg',
163
+ Error: 'error',
164
+ ExpirationUpdated: 'expirationupdated',
165
+ GapJumped: 'gapjumped',
166
+ Loaded: 'loaded',
167
+ Loading: 'loading',
168
+ ManifestParsed: 'manifestparsed',
169
+ MediaQualityChanged: 'mediaqualitychanged',
170
+ Metadata: 'metadata',
171
+ OnStateChange: 'onstatechange',
172
+ OnStateIdle: 'onstateidle',
173
+ RateChange: 'ratechange',
174
+ SessionDataEvent: 'sessiondata',
175
+ StallDetected: 'stalldetected',
176
+ Streaming: 'streaming',
177
+ TextChanged: 'textchanged',
178
+ TextTrackVisibility: 'texttrackvisibility',
179
+ TimelineRegionAdded: 'timelineregionadded',
180
+ TimelineRegionEnter: 'timelineregionenter',
181
+ TimelineRegionExit: 'timelineregionexit',
182
+ TracksChanged: 'trackschanged',
183
+ Unloading: 'unloading',
184
+ VariantChanged: 'variantchanged',
185
+ };
@@ -74,6 +74,7 @@ shaka.util.PlayerConfiguration = class {
74
74
  logLicenseExchange: false,
75
75
  updateExpirationTime: 1,
76
76
  preferredKeySystems: [],
77
+ keySystemsMapping: {},
77
78
  };
78
79
 
79
80
  const manifest = {
@@ -169,6 +170,7 @@ shaka.util.PlayerConfiguration = class {
169
170
  updateIntervalSeconds: 1,
170
171
  dispatchAllEmsgBoxes: false,
171
172
  observeQualityChanges: false,
173
+ maxDisabledTime: 30,
172
174
  };
173
175
 
174
176
  // Some browsers will stop earlier than others before a gap (e.g., Edge
package/lib/util/stats.js CHANGED
@@ -31,6 +31,11 @@ shaka.util.Stats = class {
31
31
  /** @private {number} */
32
32
  this.totalCorruptedFrames_ = NaN;
33
33
 
34
+ /** @private {number} */
35
+ this.totalStallsDetected_ = NaN;
36
+ /** @private {number} */
37
+ this.totalGapsJumped_ = NaN;
38
+
34
39
  /** @private {number} */
35
40
  this.completionPercent_ = NaN;
36
41
 
@@ -76,7 +81,6 @@ shaka.util.Stats = class {
76
81
  this.totalDecodedFrames_ = decoded;
77
82
  }
78
83
 
79
-
80
84
  /**
81
85
  * Update corrupted frames. This will replace the previous values.
82
86
  *
@@ -86,6 +90,25 @@ shaka.util.Stats = class {
86
90
  this.totalCorruptedFrames_ = corrupted;
87
91
  }
88
92
 
93
+ /**
94
+ * Update number of stalls detected. This will replace the previous value.
95
+ *
96
+ * @param {number} stallsDetected
97
+ */
98
+ setStallsDetected(stallsDetected) {
99
+ this.totalStallsDetected_ = stallsDetected;
100
+ }
101
+
102
+ /**
103
+ * Update number of playback gaps jumped over. This will replace the previous
104
+ * value.
105
+ *
106
+ * @param {number} gapsJumped
107
+ */
108
+ setGapsJumped(gapsJumped) {
109
+ this.totalGapsJumped_ = gapsJumped;
110
+ }
111
+
89
112
  /**
90
113
  * Set the width and height of the video we are currently playing.
91
114
  *
@@ -208,6 +231,8 @@ shaka.util.Stats = class {
208
231
  decodedFrames: this.totalDecodedFrames_,
209
232
  droppedFrames: this.totalDroppedFrames_,
210
233
  corruptedFrames: this.totalCorruptedFrames_,
234
+ stallsDetected: this.totalStallsDetected_,
235
+ gapsJumped: this.totalGapsJumped_,
211
236
  estimatedBandwidth: this.bandwidthEstimate_,
212
237
  completionPercent: this.completionPercent_,
213
238
  loadLatency: this.loadLatencySeconds_,
@@ -238,6 +263,8 @@ shaka.util.Stats = class {
238
263
  decodedFrames: NaN,
239
264
  droppedFrames: NaN,
240
265
  corruptedFrames: NaN,
266
+ stallsDetected: NaN,
267
+ gapsJumped: NaN,
241
268
  estimatedBandwidth: NaN,
242
269
  completionPercent: NaN,
243
270
  loadLatency: NaN,
@@ -329,6 +329,13 @@ shaka.util.StreamUtils = class {
329
329
 
330
330
  const video = variant.video;
331
331
 
332
+ if (variant.disabledUntilTime != 0) {
333
+ if (variant.disabledUntilTime > Date.now() / 1000) {
334
+ return false;
335
+ }
336
+ variant.disabledUntilTime = 0;
337
+ }
338
+
332
339
  // |video.width| and |video.height| can be undefined, which breaks
333
340
  // the math, so make sure they are there first.
334
341
  if (video && video.width && video.height) {
@@ -1008,6 +1015,7 @@ shaka.util.StreamUtils = class {
1008
1015
  track.channelsCount = audio.channelsCount;
1009
1016
  track.audioSamplingRate = audio.audioSamplingRate;
1010
1017
  track.audioBandwidth = audio.bandwidth || null;
1018
+ track.spatialAudio = audio.spatialAudio;
1011
1019
  track.label = audio.label;
1012
1020
  track.audioRoles = audio.roles;
1013
1021
  }
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.2",
4
+ "version": "4.1.1",
5
5
  "homepage": "https://github.com/shaka-project/shaka-player",
6
6
  "author": "Google",
7
7
  "maintainers": [
@@ -96,7 +96,7 @@
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
100
  },
101
101
  "engines": {
102
102
  "node": ">=14"
@@ -11,6 +11,7 @@ describe('CastProxy', () => {
11
11
 
12
12
  const originalCastSender = shaka.cast.CastSender;
13
13
  const fakeAppId = 'fake app ID';
14
+ const fakeAndroidReceiverCompatible = false;
14
15
 
15
16
  let mockPlayer;
16
17
  let mockSender;
@@ -31,7 +32,8 @@ describe('CastProxy', () => {
31
32
  mockPlayer = createMockPlayer();
32
33
  mockSender = null;
33
34
 
34
- proxy = new CastProxy(mockVideo, mockPlayer, fakeAppId);
35
+ proxy = new CastProxy(mockVideo, mockPlayer, fakeAppId,
36
+ fakeAndroidReceiverCompatible);
35
37
  });
36
38
 
37
39
  afterEach(async () => {
@@ -13,6 +13,7 @@ describe('CastSender', () => {
13
13
  const originalStatusDelay = shaka.cast.CastSender.STATUS_DELAY;
14
14
 
15
15
  const fakeAppId = 'asdf';
16
+ const fakeAndroidReceiverCompatible = false;
16
17
  const fakeInitState = {
17
18
  manifest: null,
18
19
  player: null,
@@ -52,7 +53,8 @@ describe('CastSender', () => {
52
53
  sender = new CastSender(
53
54
  fakeAppId, Util.spyFunc(onStatusChanged),
54
55
  Util.spyFunc(onFirstCastStateUpdate), Util.spyFunc(onRemoteEvent),
55
- Util.spyFunc(onResumeLocal), Util.spyFunc(onInitStateRequired));
56
+ Util.spyFunc(onResumeLocal), Util.spyFunc(onInitStateRequired),
57
+ fakeAndroidReceiverCompatible);
56
58
  });
57
59
 
58
60
  afterEach(async () => {
@@ -87,7 +89,12 @@ describe('CastSender', () => {
87
89
  expect(sender.apiReady()).toBe(true);
88
90
  expect(sender.hasReceivers()).toBe(false);
89
91
  expect(onStatusChanged).toHaveBeenCalled();
90
- expect(mockCastApi.SessionRequest).toHaveBeenCalledWith(fakeAppId);
92
+ expect(mockCastApi.SessionRequest).toHaveBeenCalledWith(
93
+ fakeAppId,
94
+ /* capabilities= */ [],
95
+ /* timeout= */ null,
96
+ fakeAndroidReceiverCompatible,
97
+ /* credentialsData= */ null);
91
98
  expect(mockCastApi.initialize).toHaveBeenCalled();
92
99
  });
93
100
 
@@ -97,7 +104,12 @@ describe('CastSender', () => {
97
104
  expect(sender.apiReady()).toBe(true);
98
105
  expect(sender.hasReceivers()).toBe(false);
99
106
  expect(onStatusChanged).toHaveBeenCalled();
100
- expect(mockCastApi.SessionRequest).toHaveBeenCalledWith(fakeAppId);
107
+ expect(mockCastApi.SessionRequest).toHaveBeenCalledWith(
108
+ fakeAppId,
109
+ /* capabilities= */ [],
110
+ /* timeout= */ null,
111
+ fakeAndroidReceiverCompatible,
112
+ /* credentialsData= */ null);
101
113
  expect(mockCastApi.initialize).toHaveBeenCalled();
102
114
  });
103
115
  });
@@ -122,7 +134,8 @@ describe('CastSender', () => {
122
134
  sender = new CastSender(
123
135
  fakeAppId, Util.spyFunc(onStatusChanged),
124
136
  Util.spyFunc(onFirstCastStateUpdate), Util.spyFunc(onRemoteEvent),
125
- Util.spyFunc(onResumeLocal), Util.spyFunc(onInitStateRequired));
137
+ Util.spyFunc(onResumeLocal), Util.spyFunc(onInitStateRequired),
138
+ /* androidReceiverCompatible= */ false);
126
139
  sender.init();
127
140
  // You get an initial call to onStatusChanged when it initializes.
128
141
  expect(onStatusChanged).toHaveBeenCalledTimes(3);
@@ -256,7 +269,8 @@ describe('CastSender', () => {
256
269
  sender = new CastSender(
257
270
  fakeAppId, Util.spyFunc(onStatusChanged),
258
271
  Util.spyFunc(onFirstCastStateUpdate), Util.spyFunc(onRemoteEvent),
259
- Util.spyFunc(onResumeLocal), Util.spyFunc(onInitStateRequired));
272
+ Util.spyFunc(onResumeLocal), Util.spyFunc(onInitStateRequired),
273
+ /* androidReceiverCompatible= */ false);
260
274
  sender.init();
261
275
 
262
276
  // The sender should automatically rejoin the session, without needing
@@ -288,7 +302,8 @@ describe('CastSender', () => {
288
302
  sender = new CastSender(
289
303
  fakeAppId, Util.spyFunc(onStatusChanged),
290
304
  Util.spyFunc(onFirstCastStateUpdate), Util.spyFunc(onRemoteEvent),
291
- Util.spyFunc(onResumeLocal), Util.spyFunc(onInitStateRequired));
305
+ Util.spyFunc(onResumeLocal), Util.spyFunc(onInitStateRequired),
306
+ /* androidReceiverCompatible= */ false);
292
307
  sender.init();
293
308
 
294
309
  expect(sender.isCasting()).toBe(false);
@@ -94,7 +94,8 @@ describe('Demo', () => {
94
94
  .add('playRangeStart')
95
95
  .add('playRangeEnd')
96
96
  .add('manifest.dash.keySystemsByURI')
97
- .add('manifest.hls.mediaPlaylistFullMimeType');
97
+ .add('manifest.hls.mediaPlaylistFullMimeType')
98
+ .add('drm.keySystemsMapping');
98
99
 
99
100
  /**
100
101
  * @param {!Object} section
@@ -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
  });
@@ -179,6 +179,7 @@ describe('AdaptationSet', () => {
179
179
  audio: audio,
180
180
  bandwidth: 1024,
181
181
  id: id,
182
+ disabledUntilTime: 0,
182
183
  language: '',
183
184
  primary: false,
184
185
  video: video,