shaka-player 4.3.5 → 4.3.6
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.
- package/.github/workflows/selenium-lab-tests.yaml +23 -1
- package/AUTHORS +2 -1
- package/CHANGELOG.md +32 -0
- package/CONTRIBUTORS +4 -1
- package/dist/controls.css +2 -2
- package/dist/controls.css.map +1 -1
- package/dist/demo.compiled.debug.js +167 -296
- package/dist/demo.compiled.debug.map +4 -4
- package/dist/demo.compiled.js +167 -296
- package/dist/demo.compiled.map +4 -4
- package/dist/deps.js +1 -1
- package/dist/locales.js +32 -32
- package/dist/receiver.compiled.debug.js +34 -63
- package/dist/receiver.compiled.debug.map +4 -4
- package/dist/receiver.compiled.js +34 -63
- package/dist/receiver.compiled.map +4 -4
- package/dist/shaka-player.compiled.d.ts +12 -3
- package/dist/shaka-player.compiled.debug.d.ts +12 -3
- package/dist/shaka-player.compiled.debug.externs.js +20 -14
- package/dist/shaka-player.compiled.debug.js +261 -255
- package/dist/shaka-player.compiled.debug.map +4 -4
- package/dist/shaka-player.compiled.externs.js +20 -14
- package/dist/shaka-player.compiled.js +862 -858
- package/dist/shaka-player.compiled.map +3 -3
- package/dist/shaka-player.ui.d.ts +12 -3
- package/dist/shaka-player.ui.debug.d.ts +12 -3
- package/dist/shaka-player.ui.debug.externs.js +8 -2
- package/dist/shaka-player.ui.debug.js +271 -264
- package/dist/shaka-player.ui.debug.map +3 -3
- package/dist/shaka-player.ui.externs.js +8 -2
- package/dist/shaka-player.ui.js +1005 -1002
- package/dist/shaka-player.ui.map +3 -3
- package/docs/tutorials/a11y.md +0 -4
- package/docs/tutorials/ad_monetization.md +0 -4
- package/docs/tutorials/basic-usage.md +0 -5
- package/docs/tutorials/config.md +0 -5
- package/docs/tutorials/debugging.md +0 -5
- package/docs/tutorials/drm-config.md +0 -5
- package/docs/tutorials/fairplay.md +2 -2
- package/docs/tutorials/license-server-auth.md +0 -5
- package/docs/tutorials/license-wrapping.md +0 -5
- package/docs/tutorials/network-and-buffering-config.md +0 -5
- package/docs/tutorials/ui-customization.md +0 -5
- package/docs/tutorials/ui.md +0 -7
- package/docs/tutorials/welcome.md +0 -5
- package/externs/network_information.js +7 -2
- package/externs/shaka/abr_manager.js +6 -0
- package/externs/shaka/net.js +1 -0
- package/externs/shaka/player.js +13 -7
- package/lib/abr/simple_abr_manager.js +27 -3
- package/lib/ads/client_side_ad_manager.js +6 -3
- package/lib/dash/dash_parser.js +49 -48
- package/lib/debug/log.js +9 -13
- package/lib/hls/hls_parser.js +17 -5
- package/lib/media/media_source_engine.js +27 -9
- package/lib/media/presentation_timeline.js +9 -2
- package/lib/media/segment_index.js +2 -0
- package/lib/media/segment_reference.js +3 -2
- package/lib/media/streaming_engine.js +31 -7
- package/lib/net/http_fetch_plugin.js +79 -57
- package/lib/net/networking_engine.js +1 -0
- package/lib/player.js +22 -3
- package/lib/polyfill/media_capabilities.js +13 -0
- package/lib/text/mp4_vtt_parser.js +4 -0
- package/lib/text/text_engine.js +1 -0
- package/lib/util/manifest_parser_utils.js +1 -2
- package/lib/util/mime_utils.js +11 -0
- package/lib/util/periods.js +5 -5
- package/lib/util/stream_utils.js +2 -2
- package/maintained-branches.md +2 -3
- package/package.json +1 -1
- package/test/dash/dash_parser_live_unit.js +3 -2
- package/test/hls/hls_parser_unit.js +156 -0
- package/test/media/content_workarounds_unit.js +43 -0
- package/test/media/drm_engine_integration.js +4 -17
- package/test/media/streaming_engine_unit.js +6 -1
- package/test/net/networking_engine_unit.js +6 -1
- package/test/player_integration.js +39 -0
- package/test/player_unit.js +57 -0
- package/test/polyfill/media_capabilities_unit.js +1 -0
- package/test/test/assets/text-clip-alt.vtt +63 -0
- package/test/test/util/fake_drm_engine.js +3 -0
- package/test/test/util/fake_media_source_engine.js +4 -0
- package/test/test/util/simple_fakes.js +6 -0
- package/test/test/util/test_scheme.js +9 -14
- package/test/text/mp4_vtt_parser_unit.js +8 -0
- package/test/util/mime_utils_unit.js +10 -0
- package/ui/less/containers.less +4 -0
|
@@ -78,6 +78,9 @@ shaka.media.StreamingEngine = class {
|
|
|
78
78
|
/** @private {?shaka.extern.Stream} */
|
|
79
79
|
this.currentTextStream_ = null;
|
|
80
80
|
|
|
81
|
+
/** @private {number} */
|
|
82
|
+
this.textStreamSequenceId_ = 0;
|
|
83
|
+
|
|
81
84
|
/** @private {boolean} */
|
|
82
85
|
this.parsedPrftEventRaised_ = false;
|
|
83
86
|
|
|
@@ -224,6 +227,8 @@ shaka.media.StreamingEngine = class {
|
|
|
224
227
|
const ContentType = shaka.util.ManifestParserUtils.ContentType;
|
|
225
228
|
goog.asserts.assert(!this.mediaStates_.has(ContentType.TEXT),
|
|
226
229
|
'Should not call loadNewTextStream_ while streaming text!');
|
|
230
|
+
this.textStreamSequenceId_++;
|
|
231
|
+
const currentSequenceId = this.textStreamSequenceId_;
|
|
227
232
|
|
|
228
233
|
try {
|
|
229
234
|
// Clear MediaSource's buffered text, so that the new text stream will
|
|
@@ -246,7 +251,7 @@ shaka.media.StreamingEngine = class {
|
|
|
246
251
|
const streamText =
|
|
247
252
|
textDisplayer.isTextVisible() || this.config_.alwaysStreamText;
|
|
248
253
|
|
|
249
|
-
if (streamText) {
|
|
254
|
+
if (streamText && (this.textStreamSequenceId_ == currentSequenceId)) {
|
|
250
255
|
const state = this.createMediaState_(stream);
|
|
251
256
|
this.mediaStates_.set(ContentType.TEXT, state);
|
|
252
257
|
this.scheduleUpdate_(state, 0);
|
|
@@ -357,7 +362,7 @@ shaka.media.StreamingEngine = class {
|
|
|
357
362
|
/**
|
|
358
363
|
* @param {shaka.extern.Stream} textStream
|
|
359
364
|
*/
|
|
360
|
-
switchTextStream(textStream) {
|
|
365
|
+
async switchTextStream(textStream) {
|
|
361
366
|
this.currentTextStream_ = textStream;
|
|
362
367
|
|
|
363
368
|
if (!this.startupComplete_) {
|
|
@@ -369,6 +374,13 @@ shaka.media.StreamingEngine = class {
|
|
|
369
374
|
goog.asserts.assert(textStream && textStream.type == ContentType.TEXT,
|
|
370
375
|
'Wrong stream type passed to switchTextStream!');
|
|
371
376
|
|
|
377
|
+
// In HLS it is possible that the mimetype changes when the media
|
|
378
|
+
// playlist is downloaded, so it is necessary to have the updated data
|
|
379
|
+
// here.
|
|
380
|
+
if (!textStream.segmentIndex) {
|
|
381
|
+
await textStream.createSegmentIndex();
|
|
382
|
+
}
|
|
383
|
+
|
|
372
384
|
this.switchInternal_(
|
|
373
385
|
textStream, /* clearBuffer= */ true,
|
|
374
386
|
/* safeMargin= */ 0, /* force= */ false);
|
|
@@ -1564,10 +1576,10 @@ shaka.media.StreamingEngine = class {
|
|
|
1564
1576
|
'waiting for another stream to recover...');
|
|
1565
1577
|
}
|
|
1566
1578
|
|
|
1567
|
-
// QuotaExceededError gets thrown if
|
|
1579
|
+
// QuotaExceededError gets thrown if eviction didn't help to make room
|
|
1568
1580
|
// for a segment. We want to wait for a while (4 seconds is just an
|
|
1569
1581
|
// arbitrary number) before updating to give the playhead a chance to
|
|
1570
|
-
// advance, so we don't
|
|
1582
|
+
// advance, so we don't immediately throw again.
|
|
1571
1583
|
this.scheduleUpdate_(mediaState, 4);
|
|
1572
1584
|
}
|
|
1573
1585
|
|
|
@@ -1675,19 +1687,31 @@ shaka.media.StreamingEngine = class {
|
|
|
1675
1687
|
}
|
|
1676
1688
|
}
|
|
1677
1689
|
|
|
1690
|
+
const lastDiscontinuitySequence =
|
|
1691
|
+
mediaState.lastSegmentReference ?
|
|
1692
|
+
mediaState.lastSegmentReference.discontinuitySequence : null;
|
|
1678
1693
|
if (this.manifest_.sequenceMode) {
|
|
1679
1694
|
// Across discontinuity bounds, we should resync timestamps for
|
|
1680
1695
|
// sequence mode playbacks. The next segment appended should
|
|
1681
1696
|
// land at its theoretical timestamp from the segment index.
|
|
1682
|
-
const lastDiscontinuitySequence =
|
|
1683
|
-
mediaState.lastSegmentReference ?
|
|
1684
|
-
mediaState.lastSegmentReference.discontinuitySequence : null;
|
|
1685
1697
|
if (reference.discontinuitySequence != lastDiscontinuitySequence ||
|
|
1686
1698
|
mediaState.needsResync) {
|
|
1687
1699
|
mediaState.needsResync = false;
|
|
1688
1700
|
operations.push(this.playerInterface_.mediaSourceEngine.resync(
|
|
1689
1701
|
mediaState.type, reference.startTime));
|
|
1690
1702
|
}
|
|
1703
|
+
} else {
|
|
1704
|
+
// In segments mode, we need to resync to set the timestampOffset
|
|
1705
|
+
// to the start of the current discontinuity sequence. This is
|
|
1706
|
+
// because individual discontinuity sequences may have internal
|
|
1707
|
+
// timestamps that overlap, so we adjust the timestampOffset to avoid
|
|
1708
|
+
// having the SourceBuffer get overwritten.
|
|
1709
|
+
if (reference.discontinuitySequence != lastDiscontinuitySequence) {
|
|
1710
|
+
operations.push(
|
|
1711
|
+
this.playerInterface_.mediaSourceEngine.resync(
|
|
1712
|
+
mediaState.type,
|
|
1713
|
+
reference.timestampOffset));
|
|
1714
|
+
}
|
|
1691
1715
|
}
|
|
1692
1716
|
|
|
1693
1717
|
await Promise.all(operations);
|
|
@@ -120,65 +120,77 @@ shaka.net.HttpFetchPlugin = class {
|
|
|
120
120
|
// not the body yet.
|
|
121
121
|
headersReceived(shaka.net.HttpFetchPlugin.headersToGenericObject_(
|
|
122
122
|
response.headers));
|
|
123
|
-
|
|
124
|
-
//
|
|
125
|
-
//
|
|
126
|
-
//
|
|
127
|
-
//
|
|
128
|
-
//
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
123
|
+
|
|
124
|
+
// In new versions of Chromium, HEAD requests now have a response body
|
|
125
|
+
// that is null.
|
|
126
|
+
// So just don't try to download the body at all, if it's a HEAD request,
|
|
127
|
+
// to avoid null reference errors.
|
|
128
|
+
// See: https://crbug.com/1297060
|
|
129
|
+
if (init.method != 'HEAD') {
|
|
130
|
+
goog.asserts.assert(response.body,
|
|
131
|
+
'non-HEAD responses should have a body');
|
|
132
|
+
|
|
133
|
+
// Getting the reader in this way allows us to observe the process of
|
|
134
|
+
// downloading the body, instead of just waiting for an opaque promise
|
|
135
|
+
// to resolve.
|
|
136
|
+
// We first clone the response because calling getReader locks the body
|
|
137
|
+
// stream; if we didn't clone it here, we would be unable to get the
|
|
138
|
+
// response's arrayBuffer later.
|
|
139
|
+
const reader = response.clone().body.getReader();
|
|
140
|
+
|
|
141
|
+
const contentLengthRaw = response.headers.get('Content-Length');
|
|
142
|
+
const contentLength =
|
|
143
|
+
contentLengthRaw ? parseInt(contentLengthRaw, 10) : 0;
|
|
144
|
+
|
|
145
|
+
const start = (controller) => {
|
|
146
|
+
const push = async () => {
|
|
147
|
+
let readObj;
|
|
148
|
+
try {
|
|
149
|
+
readObj = await reader.read();
|
|
150
|
+
} catch (e) {
|
|
151
|
+
// If we abort the request, we'll get an error here. Just ignore
|
|
152
|
+
// it since real errors will be reported when we read the buffer
|
|
153
|
+
// below.
|
|
154
|
+
shaka.log.v1('error reading from stream', e.message);
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if (!readObj.done) {
|
|
159
|
+
loaded += readObj.value.byteLength;
|
|
160
|
+
if (streamDataCallback) {
|
|
161
|
+
await streamDataCallback(readObj.value);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const currentTime = Date.now();
|
|
166
|
+
// If the time between last time and this time we got progress event
|
|
167
|
+
// is long enough, or if a whole segment is downloaded, call
|
|
168
|
+
// progressUpdated().
|
|
169
|
+
if (currentTime - lastTime > 100 || readObj.done) {
|
|
170
|
+
progressUpdated(currentTime - lastTime, loaded - lastLoaded,
|
|
171
|
+
contentLength - loaded);
|
|
172
|
+
lastLoaded = loaded;
|
|
173
|
+
lastTime = currentTime;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
if (readObj.done) {
|
|
177
|
+
goog.asserts.assert(!readObj.value,
|
|
178
|
+
'readObj should be unset when "done" is true.');
|
|
179
|
+
controller.close();
|
|
180
|
+
} else {
|
|
181
|
+
controller.enqueue(readObj.value);
|
|
182
|
+
push();
|
|
151
183
|
}
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
const currentTime = Date.now();
|
|
155
|
-
// If the time between last time and this time we got progress event
|
|
156
|
-
// is long enough, or if a whole segment is downloaded, call
|
|
157
|
-
// progressUpdated().
|
|
158
|
-
if (currentTime - lastTime > 100 || readObj.done) {
|
|
159
|
-
progressUpdated(currentTime - lastTime, loaded - lastLoaded,
|
|
160
|
-
contentLength - loaded);
|
|
161
|
-
lastLoaded = loaded;
|
|
162
|
-
lastTime = currentTime;
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
if (readObj.done) {
|
|
166
|
-
goog.asserts.assert(!readObj.value,
|
|
167
|
-
'readObj should be unset when "done" is true.');
|
|
168
|
-
controller.close();
|
|
169
|
-
} else {
|
|
170
|
-
controller.enqueue(readObj.value);
|
|
171
|
-
push();
|
|
172
|
-
}
|
|
184
|
+
};
|
|
185
|
+
push();
|
|
173
186
|
};
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
arrayBuffer = await response.arrayBuffer();
|
|
187
|
+
// Create a ReadableStream to use the reader. We don't need to use the
|
|
188
|
+
// actual stream for anything, though, as we are using the response's
|
|
189
|
+
// arrayBuffer method to get the body, so we don't store the
|
|
190
|
+
// ReadableStream.
|
|
191
|
+
new ReadableStream({start}); // eslint-disable-line no-new
|
|
192
|
+
arrayBuffer = await response.arrayBuffer();
|
|
193
|
+
}
|
|
182
194
|
} catch (error) {
|
|
183
195
|
if (abortStatus.canceled) {
|
|
184
196
|
throw new shaka.util.Error(
|
|
@@ -243,6 +255,16 @@ shaka.net.HttpFetchPlugin = class {
|
|
|
243
255
|
} else {
|
|
244
256
|
return false;
|
|
245
257
|
}
|
|
258
|
+
// Old fetch implementations hasn't body and ReadableStream implementation
|
|
259
|
+
// See: https://github.com/shaka-project/shaka-player/issues/5088
|
|
260
|
+
if (window.Response) {
|
|
261
|
+
const response = new Response('');
|
|
262
|
+
if (!response.body) {
|
|
263
|
+
return false;
|
|
264
|
+
}
|
|
265
|
+
} else {
|
|
266
|
+
return false;
|
|
267
|
+
}
|
|
246
268
|
return !!(window.fetch && window.AbortController);
|
|
247
269
|
}
|
|
248
270
|
};
|
|
@@ -312,6 +312,7 @@ shaka.net.NetworkingEngine = class extends shaka.util.FakeEventTarget {
|
|
|
312
312
|
if (!responseAndGotProgress.gotProgress &&
|
|
313
313
|
this.onProgressUpdated_ &&
|
|
314
314
|
!response.fromCache &&
|
|
315
|
+
request.method != 'HEAD' &&
|
|
315
316
|
type == shaka.net.NetworkingEngine.RequestType.SEGMENT) {
|
|
316
317
|
this.onProgressUpdated_(response.timeMs, response.data.byteLength);
|
|
317
318
|
}
|
package/lib/player.js
CHANGED
|
@@ -897,7 +897,6 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
897
897
|
}
|
|
898
898
|
|
|
899
899
|
this.abrManagerFactory_ = null;
|
|
900
|
-
this.abrManager_ = null;
|
|
901
900
|
this.config_ = null;
|
|
902
901
|
this.stats_ = null;
|
|
903
902
|
this.videoContainer_ = null;
|
|
@@ -908,6 +907,11 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
908
907
|
this.networkingEngine_ = null;
|
|
909
908
|
}
|
|
910
909
|
|
|
910
|
+
if (this.abrManager_) {
|
|
911
|
+
this.abrManager_.release();
|
|
912
|
+
this.abrManager_ = null;
|
|
913
|
+
}
|
|
914
|
+
|
|
911
915
|
// FakeEventTarget implements IReleasable
|
|
912
916
|
super.release();
|
|
913
917
|
}
|
|
@@ -1342,6 +1346,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
1342
1346
|
*/
|
|
1343
1347
|
shouldUseSrcEquals_(payload) {
|
|
1344
1348
|
const Platform = shaka.util.Platform;
|
|
1349
|
+
const MimeUtils = shaka.util.MimeUtils;
|
|
1345
1350
|
|
|
1346
1351
|
// If we are using a platform that does not support media source, we will
|
|
1347
1352
|
// fall back to src= to handle all playback.
|
|
@@ -1396,7 +1401,8 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
1396
1401
|
// version there.
|
|
1397
1402
|
|
|
1398
1403
|
// Native HLS can be preferred on any platform via this flag:
|
|
1399
|
-
if (
|
|
1404
|
+
if (MimeUtils.isHlsType(mimeType) &&
|
|
1405
|
+
this.config_.streaming.preferNativeHls) {
|
|
1400
1406
|
return true;
|
|
1401
1407
|
}
|
|
1402
1408
|
|
|
@@ -4276,6 +4282,19 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
4276
4282
|
firstVariantWithLabel.language, '', 0, label);
|
|
4277
4283
|
|
|
4278
4284
|
this.chooseVariantAndSwitch_();
|
|
4285
|
+
} else if (this.video_ && this.video_.audioTracks) {
|
|
4286
|
+
const audioTracks = Array.from(this.video_.audioTracks);
|
|
4287
|
+
|
|
4288
|
+
let trackMatch = null;
|
|
4289
|
+
|
|
4290
|
+
for (const audioTrack of audioTracks) {
|
|
4291
|
+
if (audioTrack.label == label) {
|
|
4292
|
+
trackMatch = audioTrack;
|
|
4293
|
+
}
|
|
4294
|
+
}
|
|
4295
|
+
if (trackMatch) {
|
|
4296
|
+
this.switchHtml5Track_(trackMatch);
|
|
4297
|
+
}
|
|
4279
4298
|
}
|
|
4280
4299
|
}
|
|
4281
4300
|
|
|
@@ -6986,7 +7005,7 @@ shaka.Player.TYPICAL_BUFFERING_THRESHOLD_ = 0.5;
|
|
|
6986
7005
|
* @define {string} A version number taken from git at compile time.
|
|
6987
7006
|
* @export
|
|
6988
7007
|
*/
|
|
6989
|
-
shaka.Player.version = 'v4.3.
|
|
7008
|
+
shaka.Player.version = 'v4.3.6-uncompiled';
|
|
6990
7009
|
|
|
6991
7010
|
// Initialize the deprecation system using the version string we just set
|
|
6992
7011
|
// on the player.
|
|
@@ -119,6 +119,19 @@ shaka.polyfill.MediaCapabilities = class {
|
|
|
119
119
|
if (shaka.util.Platform.isChromecast()) {
|
|
120
120
|
isSupported =
|
|
121
121
|
shaka.polyfill.MediaCapabilities.canCastDisplayType_(videoConfig);
|
|
122
|
+
} else if (shaka.util.Platform.isTizen()) {
|
|
123
|
+
let extendedType = videoConfig.contentType;
|
|
124
|
+
if (videoConfig.width && videoConfig.height) {
|
|
125
|
+
extendedType += `; width=${videoConfig.width}`;
|
|
126
|
+
extendedType += `; height=${videoConfig.height}`;
|
|
127
|
+
}
|
|
128
|
+
if (videoConfig.framerate) {
|
|
129
|
+
extendedType += `; framerate=${videoConfig.framerate}`;
|
|
130
|
+
}
|
|
131
|
+
if (videoConfig.bitrate) {
|
|
132
|
+
extendedType += `; bitrate=${videoConfig.bitrate}`;
|
|
133
|
+
}
|
|
134
|
+
isSupported = Capabilities.isTypeSupported(extendedType);
|
|
122
135
|
} else {
|
|
123
136
|
isSupported = Capabilities.isTypeSupported(videoConfig.contentType);
|
|
124
137
|
}
|
package/lib/text/text_engine.js
CHANGED
|
@@ -139,6 +139,7 @@ shaka.text.TextEngine = class {
|
|
|
139
139
|
// into TextEngine and TextDisplayer.
|
|
140
140
|
if (mimeType == shaka.util.MimeUtils.CEA608_CLOSED_CAPTION_MIMETYPE ||
|
|
141
141
|
mimeType == shaka.util.MimeUtils.CEA708_CLOSED_CAPTION_MIMETYPE) {
|
|
142
|
+
this.parser_ = null;
|
|
142
143
|
return;
|
|
143
144
|
}
|
|
144
145
|
|
|
@@ -185,8 +185,7 @@ shaka.util.ManifestParserUtils.AUDIO_CODEC_REGEXPS_ = [
|
|
|
185
185
|
/^mp4a/,
|
|
186
186
|
/^[ae]c-3$/,
|
|
187
187
|
/^ac-4$/,
|
|
188
|
-
/^dts[
|
|
189
|
-
/^[du]dts$/, // DTS Surround Sound
|
|
188
|
+
/^dts[cex]$/, // DTS Digital Surround (dtsc), DTS Express (dtse), DTS:X (dtsx)
|
|
190
189
|
];
|
|
191
190
|
|
|
192
191
|
|
package/lib/util/mime_utils.js
CHANGED
|
@@ -193,6 +193,17 @@ shaka.util.MimeUtils = class {
|
|
|
193
193
|
return value;
|
|
194
194
|
}
|
|
195
195
|
|
|
196
|
+
/**
|
|
197
|
+
* Checks if the given MIME type is HLS MIME type.
|
|
198
|
+
*
|
|
199
|
+
* @param {string} mimeType
|
|
200
|
+
* @return {boolean}
|
|
201
|
+
*/
|
|
202
|
+
static isHlsType(mimeType) {
|
|
203
|
+
return mimeType === 'application/x-mpegurl' ||
|
|
204
|
+
mimeType === 'application/vnd.apple.mpegurl';
|
|
205
|
+
}
|
|
206
|
+
|
|
196
207
|
/**
|
|
197
208
|
* Get the base and profile of a codec string. Where [0] will be the codec
|
|
198
209
|
* base and [1] will be the profile.
|
package/lib/util/periods.js
CHANGED
|
@@ -671,11 +671,11 @@ shaka.util.PeriodCombiner = class {
|
|
|
671
671
|
// Also checks if the segmentIndex is still valid after the async
|
|
672
672
|
// operations, to make sure we stop if the active stream has changed.
|
|
673
673
|
if (outputStream.segmentIndex instanceof shaka.media.MetaSegmentIndex) {
|
|
674
|
-
for (let i =
|
|
674
|
+
for (let i = firstNewPeriodIndex; i < streams.length; i++) {
|
|
675
675
|
const match = streams[i];
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
676
|
+
goog.asserts.assert(match.segmentIndex,
|
|
677
|
+
'stream should have a segmentIndex.');
|
|
678
|
+
if (match.segmentIndex) {
|
|
679
679
|
outputStream.segmentIndex.appendSegmentIndex(match.segmentIndex);
|
|
680
680
|
}
|
|
681
681
|
}
|
|
@@ -728,7 +728,7 @@ shaka.util.PeriodCombiner = class {
|
|
|
728
728
|
// in advance. concat() will add them to the output's MetaSegmentIndex.
|
|
729
729
|
}
|
|
730
730
|
|
|
731
|
-
if (!outputStream.matchedStreams) {
|
|
731
|
+
if (!outputStream.matchedStreams || !outputStream.matchedStreams.length) {
|
|
732
732
|
// This is not a stream we can build output from, but it may become part
|
|
733
733
|
// of another output based on another period's stream.
|
|
734
734
|
return null;
|
package/lib/util/stream_utils.js
CHANGED
|
@@ -830,7 +830,7 @@ shaka.util.StreamUtils = class {
|
|
|
830
830
|
const video = variant.video;
|
|
831
831
|
if (audio && currentVariant && currentVariant.audio) {
|
|
832
832
|
if (!StreamUtils.areStreamsCompatible_(audio, currentVariant.audio)) {
|
|
833
|
-
shaka.log.debug('
|
|
833
|
+
shaka.log.debug('Dropping variant - not compatible with active audio',
|
|
834
834
|
'active audio',
|
|
835
835
|
StreamUtils.getStreamSummaryString_(currentVariant.audio),
|
|
836
836
|
'variant.audio',
|
|
@@ -841,7 +841,7 @@ shaka.util.StreamUtils = class {
|
|
|
841
841
|
|
|
842
842
|
if (video && currentVariant && currentVariant.video) {
|
|
843
843
|
if (!StreamUtils.areStreamsCompatible_(video, currentVariant.video)) {
|
|
844
|
-
shaka.log.debug('
|
|
844
|
+
shaka.log.debug('Dropping variant - not compatible with active video',
|
|
845
845
|
'active video',
|
|
846
846
|
StreamUtils.getStreamSummaryString_(currentVariant.video),
|
|
847
847
|
'variant.video',
|
package/maintained-branches.md
CHANGED
|
@@ -2,10 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
We are currently maintaining the following branches of Shaka Player:
|
|
4
4
|
|
|
5
|
-
- v4.3 (latest)
|
|
5
|
+
- v4.3 (latest, also in use by the Cast Application Framework)
|
|
6
6
|
- v4.2 (previous)
|
|
7
|
-
-
|
|
8
|
-
- v3.2 (in use by the Cast Application Framework)
|
|
7
|
+
- No active LTS branches at this time
|
|
9
8
|
|
|
10
9
|
Other branches are no longer receiving bug fixes, and we recommend you upgrade
|
|
11
10
|
to a maintained branch. For details on upgrading, please see our
|
package/package.json
CHANGED
|
@@ -194,7 +194,8 @@ describe('DashParser Live', () => {
|
|
|
194
194
|
template, {updateTime: updateTime, contents: basicLines.join('\n')});
|
|
195
195
|
|
|
196
196
|
fakeNetEngine.setResponseText('dummy://foo', text);
|
|
197
|
-
|
|
197
|
+
const baseTime = new Date(2015, 11, 30);
|
|
198
|
+
Date.now = () => baseTime.getTime();
|
|
198
199
|
const manifest = await parser.start('dummy://foo', playerInterface);
|
|
199
200
|
|
|
200
201
|
expect(manifest).toBeTruthy();
|
|
@@ -211,7 +212,7 @@ describe('DashParser Live', () => {
|
|
|
211
212
|
// seconds long in all of these cases. So 11 seconds after the
|
|
212
213
|
// manifest was parsed, the first segment should have fallen out of
|
|
213
214
|
// the availability window.
|
|
214
|
-
Date.now = () => 11 * 1000;
|
|
215
|
+
Date.now = () => baseTime.getTime() + (11 * 1000);
|
|
215
216
|
await updateManifest();
|
|
216
217
|
// The first reference should have been evicted.
|
|
217
218
|
expect(stream.segmentIndex.find(0)).toBe(firstPosition + 1);
|
|
@@ -987,6 +987,118 @@ describe('HlsParser', () => {
|
|
|
987
987
|
await testHlsParser(master, media, manifest);
|
|
988
988
|
});
|
|
989
989
|
|
|
990
|
+
it('parses discontinuity tags', async () => {
|
|
991
|
+
const master = [
|
|
992
|
+
'#EXTM3U\n',
|
|
993
|
+
'#EXT-X-STREAM-INF:BANDWIDTH=2000000,CODECS="avc1"\n',
|
|
994
|
+
'video\n',
|
|
995
|
+
].join('');
|
|
996
|
+
|
|
997
|
+
const media = [
|
|
998
|
+
'#EXTM3U\n',
|
|
999
|
+
'#EXT-X-VERSION:3\n',
|
|
1000
|
+
'#EXT-X-TARGETDURATION:5\n',
|
|
1001
|
+
'#EXT-X-MEDIA-SEQUENCE:0\n',
|
|
1002
|
+
'#EXTINF:3,\n',
|
|
1003
|
+
'clip0-video-0.ts\n',
|
|
1004
|
+
'#EXTINF:1,\n',
|
|
1005
|
+
'clip0-video-1.ts\n',
|
|
1006
|
+
'#EXT-X-DISCONTINUITY\n',
|
|
1007
|
+
'#EXTINF:2,\n',
|
|
1008
|
+
'clip1-video-1.ts\n',
|
|
1009
|
+
'#EXTINF:3,\n',
|
|
1010
|
+
'clip1-video-2.ts\n',
|
|
1011
|
+
'#EXT-X-DISCONTINUITY\n',
|
|
1012
|
+
'#EXTINF:1,\n',
|
|
1013
|
+
'media-clip2-video-0.ts\n',
|
|
1014
|
+
'#EXTINF:1,\n',
|
|
1015
|
+
'media-clip2-video-1.ts\n',
|
|
1016
|
+
'#EXT-X-DISCONTINUITY\n',
|
|
1017
|
+
'#EXTINF:4,\n',
|
|
1018
|
+
'media-clip3-video-1.ts\n',
|
|
1019
|
+
'#EXT-X-ENDLIST\n',
|
|
1020
|
+
].join('');
|
|
1021
|
+
|
|
1022
|
+
fakeNetEngine
|
|
1023
|
+
.setResponseText('test:/master', master)
|
|
1024
|
+
.setResponseText('test:/video', media);
|
|
1025
|
+
|
|
1026
|
+
const manifest = await parser.start('test:/master', playerInterface);
|
|
1027
|
+
await manifest.variants[0].video.createSegmentIndex();
|
|
1028
|
+
|
|
1029
|
+
const segmentIndex = manifest.variants[0].video.segmentIndex;
|
|
1030
|
+
const references = [];
|
|
1031
|
+
|
|
1032
|
+
for (let i = 0; i < 7; i++) {
|
|
1033
|
+
references.push(segmentIndex.get(i));
|
|
1034
|
+
}
|
|
1035
|
+
|
|
1036
|
+
expect(references[0].discontinuitySequence).toBe(0);
|
|
1037
|
+
expect(references[1].discontinuitySequence).toBe(0);
|
|
1038
|
+
expect(references[2].discontinuitySequence).toBe(1);
|
|
1039
|
+
expect(references[3].discontinuitySequence).toBe(1);
|
|
1040
|
+
expect(references[4].discontinuitySequence).toBe(2);
|
|
1041
|
+
expect(references[5].discontinuitySequence).toBe(2);
|
|
1042
|
+
expect(references[6].discontinuitySequence).toBe(3);
|
|
1043
|
+
});
|
|
1044
|
+
|
|
1045
|
+
it('sets reference timetampOffset based on discontinuity start time',
|
|
1046
|
+
async () => {
|
|
1047
|
+
const master = [
|
|
1048
|
+
'#EXTM3U\n',
|
|
1049
|
+
'#EXT-X-STREAM-INF:BANDWIDTH=2000000,CODECS="avc1"\n',
|
|
1050
|
+
'video\n',
|
|
1051
|
+
].join('');
|
|
1052
|
+
|
|
1053
|
+
const media = [
|
|
1054
|
+
'#EXTM3U\n',
|
|
1055
|
+
'#EXT-X-VERSION:3\n',
|
|
1056
|
+
'#EXT-X-TARGETDURATION:5\n',
|
|
1057
|
+
'#EXT-X-MEDIA-SEQUENCE:0\n',
|
|
1058
|
+
'#EXTINF:3,\n',
|
|
1059
|
+
'clip0-video-0.ts\n',
|
|
1060
|
+
'#EXTINF:1,\n',
|
|
1061
|
+
'clip0-video-1.ts\n',
|
|
1062
|
+
'#EXT-X-DISCONTINUITY\n',
|
|
1063
|
+
'#EXTINF:2,\n',
|
|
1064
|
+
'clip1-video-1.ts\n',
|
|
1065
|
+
'#EXTINF:3,\n',
|
|
1066
|
+
'clip1-video-2.ts\n',
|
|
1067
|
+
'#EXT-X-DISCONTINUITY\n',
|
|
1068
|
+
'#EXTINF:1,\n',
|
|
1069
|
+
'media-clip2-video-0.ts\n',
|
|
1070
|
+
'#EXTINF:1,\n',
|
|
1071
|
+
'media-clip2-video-1.ts\n',
|
|
1072
|
+
'#EXT-X-DISCONTINUITY\n',
|
|
1073
|
+
'#EXTINF:4,\n',
|
|
1074
|
+
'media-clip3-video-1.ts\n',
|
|
1075
|
+
'#EXT-X-ENDLIST\n',
|
|
1076
|
+
].join('');
|
|
1077
|
+
|
|
1078
|
+
fakeNetEngine
|
|
1079
|
+
.setResponseText('test:/master', master)
|
|
1080
|
+
.setResponseText('test:/video', media);
|
|
1081
|
+
|
|
1082
|
+
const manifest = await parser.start('test:/master', playerInterface);
|
|
1083
|
+
await manifest.variants[0].video.createSegmentIndex();
|
|
1084
|
+
|
|
1085
|
+
const segmentIndex = manifest.variants[0].video.segmentIndex;
|
|
1086
|
+
const references = [];
|
|
1087
|
+
|
|
1088
|
+
for (let i = 0; i < 7; i++) {
|
|
1089
|
+
references.push(segmentIndex.get(i));
|
|
1090
|
+
}
|
|
1091
|
+
|
|
1092
|
+
expect(references[0].timestampOffset).toBe(0);
|
|
1093
|
+
expect(references[1].timestampOffset).toBe(0);
|
|
1094
|
+
expect(references[2].timestampOffset).toBe(4);
|
|
1095
|
+
expect(references[3].timestampOffset).toBe(4);
|
|
1096
|
+
expect(references[4].timestampOffset).toBe(9);
|
|
1097
|
+
expect(references[5].timestampOffset).toBe(9);
|
|
1098
|
+
expect(references[6].timestampOffset).toBe(11);
|
|
1099
|
+
},
|
|
1100
|
+
);
|
|
1101
|
+
|
|
990
1102
|
it('parses characteristics from audio tags', async () => {
|
|
991
1103
|
const master = [
|
|
992
1104
|
'#EXTM3U\n',
|
|
@@ -2132,6 +2244,50 @@ describe('HlsParser', () => {
|
|
|
2132
2244
|
expect(actual).toEqual(manifest);
|
|
2133
2245
|
});
|
|
2134
2246
|
|
|
2247
|
+
it('parses manifest with MP4+WEBVTT streams', async () => {
|
|
2248
|
+
const master = [
|
|
2249
|
+
'#EXTM3U\n',
|
|
2250
|
+
'#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID="sub1",LANGUAGE="eng",',
|
|
2251
|
+
'CODECS="wvtt",URI="text"\n',
|
|
2252
|
+
'#EXT-X-STREAM-INF:BANDWIDTH=200,CODECS="avc1",',
|
|
2253
|
+
'RESOLUTION=960x540,FRAME-RATE=60,SUBTITLES="sub1"\n',
|
|
2254
|
+
'video\n',
|
|
2255
|
+
].join('');
|
|
2256
|
+
|
|
2257
|
+
const media = [
|
|
2258
|
+
'#EXTM3U\n',
|
|
2259
|
+
'#EXT-X-PLAYLIST-TYPE:VOD\n',
|
|
2260
|
+
'#EXT-X-MAP:URI="init.mp4",BYTERANGE="616@0"\n',
|
|
2261
|
+
'#EXTINF:5,\n',
|
|
2262
|
+
'#EXT-X-BYTERANGE:121090@616\n',
|
|
2263
|
+
'main.mp4',
|
|
2264
|
+
].join('');
|
|
2265
|
+
|
|
2266
|
+
const manifest = shaka.test.ManifestGenerator.generate((manifest) => {
|
|
2267
|
+
manifest.anyTimeline();
|
|
2268
|
+
manifest.addPartialVariant((variant) => {
|
|
2269
|
+
variant.addPartialStream(ContentType.VIDEO);
|
|
2270
|
+
});
|
|
2271
|
+
manifest.addPartialTextStream((stream) => {
|
|
2272
|
+
stream.language = 'en';
|
|
2273
|
+
stream.mime('application/mp4', 'wvtt');
|
|
2274
|
+
});
|
|
2275
|
+
manifest.sequenceMode = true;
|
|
2276
|
+
});
|
|
2277
|
+
|
|
2278
|
+
fakeNetEngine
|
|
2279
|
+
.setResponseText('test:/master', master)
|
|
2280
|
+
.setResponseText('test:/audio', media)
|
|
2281
|
+
.setResponseText('test:/video', media)
|
|
2282
|
+
.setResponseText('test:/text', media)
|
|
2283
|
+
.setResponseValue('test:/init.mp4', initSegmentData)
|
|
2284
|
+
.setResponseValue('test:/main.mp4', segmentData);
|
|
2285
|
+
|
|
2286
|
+
const actual = await parser.start('test:/master', playerInterface);
|
|
2287
|
+
await loadAllStreamsFor(actual);
|
|
2288
|
+
expect(actual).toEqual(manifest);
|
|
2289
|
+
});
|
|
2290
|
+
|
|
2135
2291
|
it('detects VTT streams by codec', async () => {
|
|
2136
2292
|
const master = [
|
|
2137
2293
|
'#EXTM3U\n',
|