shaka-player 3.3.12 → 3.3.14
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/AUTHORS +1 -0
- package/CHANGELOG.md +33 -0
- package/CONTRIBUTORS +1 -0
- package/dist/deps.js +3 -3
- package/dist/locales.js +31 -31
- package/dist/shaka-player.compiled.d.ts +1 -1
- package/dist/shaka-player.compiled.debug.d.ts +1 -1
- package/dist/shaka-player.compiled.debug.externs.js +2 -1
- package/dist/shaka-player.compiled.debug.js +135 -126
- package/dist/shaka-player.compiled.debug.map +3 -3
- package/dist/shaka-player.compiled.externs.js +2 -1
- package/dist/shaka-player.compiled.js +744 -739
- package/dist/shaka-player.compiled.map +3 -3
- package/dist/shaka-player.ui.d.ts +1 -1
- package/dist/shaka-player.ui.debug.d.ts +1 -1
- package/dist/shaka-player.ui.debug.externs.js +2 -1
- package/dist/shaka-player.ui.debug.js +228 -219
- package/dist/shaka-player.ui.debug.map +3 -3
- package/dist/shaka-player.ui.externs.js +2 -1
- package/dist/shaka-player.ui.js +849 -845
- package/dist/shaka-player.ui.map +3 -3
- package/docs/tutorials/welcome.md +2 -2
- package/externs/shaka/player.js +2 -1
- package/lib/cast/cast_proxy.js +2 -1
- package/lib/cast/cast_receiver.js +19 -5
- package/lib/cast/cast_sender.js +2 -1
- package/lib/cast/cast_utils.js +16 -6
- package/lib/cea/cea708_window.js +2 -2
- package/lib/dash/segment_template.js +1 -1
- package/lib/hls/hls_parser.js +17 -3
- package/lib/media/content_workarounds.js +12 -0
- package/lib/media/playhead.js +23 -1
- package/lib/media/streaming_engine.js +3 -12
- package/lib/player.js +24 -11
- package/lib/polyfill/media_capabilities.js +59 -9
- package/lib/text/ttml_text_parser.js +32 -7
- package/lib/text/ui_text_displayer.js +22 -5
- package/lib/util/manifest_parser_utils.js +4 -0
- package/lib/util/platform.js +21 -2
- package/lib/util/stream_utils.js +30 -10
- package/package.json +1 -1
- package/test/cast/cast_receiver_integration.js +97 -16
- package/test/cast/cast_receiver_unit.js +17 -12
- package/test/cast/cast_utils_unit.js +1 -0
- package/test/dash/dash_parser_manifest_unit.js +1 -0
- package/test/hls/hls_parser_unit.js +1 -0
- package/test/media/playhead_unit.js +41 -0
- package/test/media/streaming_engine_integration.js +13 -0
- package/test/media/streaming_engine_unit.js +23 -8
- package/test/player_unit.js +87 -0
- package/test/polyfill/media_capabilities_unit.js +261 -0
- package/test/test/util/simple_fakes.js +3 -0
- package/test/text/mp4_ttml_parser_unit.js +4 -4
- package/test/text/ttml_text_parser_unit.js +119 -86
- package/test/text/ui_text_displayer_unit.js +76 -0
- package/test/ui/text_displayer_layout_unit.js +14 -0
- package/test/util/stream_utils_unit.js +18 -0
- package/test/util/xml_utils_unit.js +6 -1
- package/ui/controls.js +5 -2
- package/ui/settings_menu.js +11 -2
|
@@ -29,8 +29,8 @@ You can build Shaka on Linux, Windows, or Mac.
|
|
|
29
29
|
To get the sources and compile the library, you will need:
|
|
30
30
|
* {@link https://git-scm.com/downloads Git v1.9+}
|
|
31
31
|
* {@link https://www.python.org/downloads/ Python v2.7 or v3.5+}
|
|
32
|
-
* {@link https://java.com/en/download/ Java Runtime Environment
|
|
33
|
-
* {@link https://nodejs.org/en/download/ NodeJS
|
|
32
|
+
* {@link https://java.com/en/download/ Java Runtime Environment v14+}
|
|
33
|
+
* {@link https://nodejs.org/en/download/ NodeJS v14+}
|
|
34
34
|
* A local web server, such as {@link https://httpd.apache.org/ Apache}
|
|
35
35
|
* _NOTE: A local web server is required because browsers place restrictions
|
|
36
36
|
on applications from file:/// URLs._
|
package/externs/shaka/player.js
CHANGED
|
@@ -123,7 +123,8 @@ shaka.extern.StateChange;
|
|
|
123
123
|
* @property {number} manifestTimeSeconds
|
|
124
124
|
* The amount of time it took to download and parse the manifest.
|
|
125
125
|
* @property {number} drmTimeSeconds
|
|
126
|
-
* The amount of time it took to download the first drm key
|
|
126
|
+
* The amount of time it took to download the first drm key, and load that key
|
|
127
|
+
* into the drm system.
|
|
127
128
|
* @property {number} playTime
|
|
128
129
|
* The total time spent in a playing state in seconds.
|
|
129
130
|
* @property {number} pauseTime
|
package/lib/cast/cast_proxy.js
CHANGED
|
@@ -694,7 +694,8 @@ shaka.cast.CastProxy = class extends shaka.util.FakeEventTarget {
|
|
|
694
694
|
// If we are casting, but the first update has not come in yet, use local
|
|
695
695
|
// getters, but not local methods.
|
|
696
696
|
if (this.sender_.isCasting() && !this.sender_.hasRemoteProperties()) {
|
|
697
|
-
if (shaka.cast.CastUtils.PlayerGetterMethods[name]
|
|
697
|
+
if (shaka.cast.CastUtils.PlayerGetterMethods[name] ||
|
|
698
|
+
shaka.cast.CastUtils.LargePlayerGetterMethods[name]) {
|
|
698
699
|
const value = /** @type {Object} */(this.localPlayer_)[name];
|
|
699
700
|
goog.asserts.assert(typeof value == 'function',
|
|
700
701
|
'only methods on Player');
|
|
@@ -518,6 +518,25 @@ shaka.cast.CastReceiver = class extends shaka.util.FakeEventTarget {
|
|
|
518
518
|
update['video']['muted'] = systemVolume.muted;
|
|
519
519
|
}
|
|
520
520
|
|
|
521
|
+
this.sendMessage_({
|
|
522
|
+
'type': 'update',
|
|
523
|
+
'update': update,
|
|
524
|
+
}, this.shakaBus_);
|
|
525
|
+
|
|
526
|
+
// Getters with large outputs each get sent in their own update message.
|
|
527
|
+
for (const name in shaka.cast.CastUtils.LargePlayerGetterMethods) {
|
|
528
|
+
const frequency = shaka.cast.CastUtils.LargePlayerGetterMethods[name];
|
|
529
|
+
if (this.updateNumber_ % frequency == 0) {
|
|
530
|
+
const update = {'player': {}};
|
|
531
|
+
update['player'][name] = /** @type {Object} */ (this.player_)[name]();
|
|
532
|
+
|
|
533
|
+
this.sendMessage_({
|
|
534
|
+
'type': 'update',
|
|
535
|
+
'update': update,
|
|
536
|
+
}, this.shakaBus_);
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
|
|
521
540
|
// Only start progressing the update number once data is loaded,
|
|
522
541
|
// just in case any of the "rarely changing" properties with less frequent
|
|
523
542
|
// update messages changes significantly during the loading process.
|
|
@@ -525,11 +544,6 @@ shaka.cast.CastReceiver = class extends shaka.util.FakeEventTarget {
|
|
|
525
544
|
this.updateNumber_ += 1;
|
|
526
545
|
}
|
|
527
546
|
|
|
528
|
-
this.sendMessage_({
|
|
529
|
-
'type': 'update',
|
|
530
|
-
'update': update,
|
|
531
|
-
}, this.shakaBus_);
|
|
532
|
-
|
|
533
547
|
this.maybeSendMediaInfoMessage_();
|
|
534
548
|
}
|
|
535
549
|
|
package/lib/cast/cast_sender.js
CHANGED
|
@@ -360,7 +360,8 @@ shaka.cast.CastSender = class {
|
|
|
360
360
|
return (...args) =>
|
|
361
361
|
this.remoteAsyncCall_(targetName, property, ...args);
|
|
362
362
|
}
|
|
363
|
-
if (CastUtils.PlayerGetterMethods[property]
|
|
363
|
+
if (CastUtils.PlayerGetterMethods[property] ||
|
|
364
|
+
CastUtils.LargePlayerGetterMethods[property]) {
|
|
364
365
|
return () => this.propertyGetter_(targetName, property);
|
|
365
366
|
}
|
|
366
367
|
}
|
package/lib/cast/cast_utils.js
CHANGED
|
@@ -307,9 +307,6 @@ shaka.cast.CastUtils.PlayerGetterMethods = {
|
|
|
307
307
|
'getAudioLanguagesAndRoles': 4,
|
|
308
308
|
'getBufferFullness': 1,
|
|
309
309
|
'getBufferedInfo': 2,
|
|
310
|
-
// NOTE: The 'getSharedConfiguration' property is not proxied as it would
|
|
311
|
-
// not be possible to share a reference.
|
|
312
|
-
'getConfiguration': 4,
|
|
313
310
|
'getExpiration': 2,
|
|
314
311
|
'getKeyStatuses': 2,
|
|
315
312
|
// NOTE: The 'getManifest' property is not proxied, as it is very large.
|
|
@@ -318,9 +315,6 @@ shaka.cast.CastUtils.PlayerGetterMethods = {
|
|
|
318
315
|
'getPlaybackRate': 2,
|
|
319
316
|
'getTextLanguages': 4,
|
|
320
317
|
'getTextLanguagesAndRoles': 4,
|
|
321
|
-
'getTextTracks': 2,
|
|
322
|
-
'getStats': 5,
|
|
323
|
-
'getVariantTracks': 2,
|
|
324
318
|
'getImageTracks': 2,
|
|
325
319
|
'getThumbnails': 2,
|
|
326
320
|
'isAudioOnly': 10,
|
|
@@ -334,6 +328,22 @@ shaka.cast.CastUtils.PlayerGetterMethods = {
|
|
|
334
328
|
};
|
|
335
329
|
|
|
336
330
|
|
|
331
|
+
/**
|
|
332
|
+
* Player getter methods with data large enough to be sent in their own update
|
|
333
|
+
* messages, to reduce the size of each message. The format of this is
|
|
334
|
+
* identical to PlayerGetterMethods.
|
|
335
|
+
* @const {!Object.<string, number>}
|
|
336
|
+
*/
|
|
337
|
+
shaka.cast.CastUtils.LargePlayerGetterMethods = {
|
|
338
|
+
// NOTE: The 'getSharedConfiguration' property is not proxied as it would
|
|
339
|
+
// not be possible to share a reference.
|
|
340
|
+
'getConfiguration': 4,
|
|
341
|
+
'getStats': 5,
|
|
342
|
+
'getTextTracks': 2,
|
|
343
|
+
'getVariantTracks': 2,
|
|
344
|
+
};
|
|
345
|
+
|
|
346
|
+
|
|
337
347
|
/**
|
|
338
348
|
* Player getter methods that are proxied while casting, but only when casting
|
|
339
349
|
* a livestream.
|
package/lib/cea/cea708_window.js
CHANGED
|
@@ -420,7 +420,7 @@ shaka.cea.Cea708Window.TextJustification = {
|
|
|
420
420
|
shaka.cea.Cea708Window.MAX_COLS = 42;
|
|
421
421
|
|
|
422
422
|
/**
|
|
423
|
-
* Maximum of
|
|
423
|
+
* Maximum of 16 rows that can be indexed from 0 to 15.
|
|
424
424
|
* @private @const {number}
|
|
425
425
|
*/
|
|
426
|
-
shaka.cea.Cea708Window.MAX_ROWS =
|
|
426
|
+
shaka.cea.Cea708Window.MAX_ROWS = 16;
|
|
@@ -67,7 +67,7 @@ shaka.dash.SegmentTemplate = class {
|
|
|
67
67
|
},
|
|
68
68
|
};
|
|
69
69
|
} else if (info.segmentDuration) {
|
|
70
|
-
if (!isUpdate) {
|
|
70
|
+
if (!isUpdate && context.adaptationSet.contentType !== 'image') {
|
|
71
71
|
context.presentationTimeline.notifyMaxSegmentDuration(
|
|
72
72
|
info.segmentDuration);
|
|
73
73
|
context.presentationTimeline.notifyMinSegmentStartTime(
|
package/lib/hls/hls_parser.js
CHANGED
|
@@ -639,13 +639,19 @@ shaka.hls.HlsParser = class {
|
|
|
639
639
|
shaka.util.ArrayUtils.remove(allCodecs, textCodecs);
|
|
640
640
|
}
|
|
641
641
|
if (audioGroupId) {
|
|
642
|
-
|
|
642
|
+
let codecs = shaka.util.ManifestParserUtils.guessCodecsSafe(
|
|
643
643
|
ContentType.AUDIO, allCodecs);
|
|
644
|
+
if (!codecs) {
|
|
645
|
+
codecs = this.config_.hls.defaultAudioCodec;
|
|
646
|
+
}
|
|
644
647
|
this.groupIdToCodecsMap_.set(audioGroupId, codecs);
|
|
645
648
|
}
|
|
646
649
|
if (videoGroupId) {
|
|
647
|
-
|
|
650
|
+
let codecs = shaka.util.ManifestParserUtils.guessCodecsSafe(
|
|
648
651
|
ContentType.VIDEO, allCodecs);
|
|
652
|
+
if (!codecs) {
|
|
653
|
+
codecs = this.config_.hls.defaultVideoCodec;
|
|
654
|
+
}
|
|
649
655
|
this.groupIdToCodecsMap_.set(videoGroupId, codecs);
|
|
650
656
|
}
|
|
651
657
|
}
|
|
@@ -866,7 +872,7 @@ shaka.hls.HlsParser = class {
|
|
|
866
872
|
shaka.log.debug('Guessing audio-only.');
|
|
867
873
|
type = ContentType.AUDIO;
|
|
868
874
|
ignoreStream = true;
|
|
869
|
-
} else if (res.video.length) {
|
|
875
|
+
} else if (res.video.length && !res.audio.length) {
|
|
870
876
|
// There are associated video streams. Assume this is audio.
|
|
871
877
|
shaka.log.debug('Guessing audio-only.');
|
|
872
878
|
type = ContentType.AUDIO;
|
|
@@ -1487,6 +1493,7 @@ shaka.hls.HlsParser = class {
|
|
|
1487
1493
|
*/
|
|
1488
1494
|
const discontinuityToMediaSequence = new Map();
|
|
1489
1495
|
|
|
1496
|
+
const prevLowLatencyMode = this.lowLatencyMode_;
|
|
1490
1497
|
let segments;
|
|
1491
1498
|
try {
|
|
1492
1499
|
segments = await this.createSegments_(verbatimMediaPlaylistUri,
|
|
@@ -1502,6 +1509,12 @@ shaka.hls.HlsParser = class {
|
|
|
1502
1509
|
throw error;
|
|
1503
1510
|
}
|
|
1504
1511
|
|
|
1512
|
+
// This happens when autoLowLatencyMode is true, so we need set the
|
|
1513
|
+
// correct lowLatencyPresentationDelay_
|
|
1514
|
+
if (prevLowLatencyMode != this.lowLatencyMode_) {
|
|
1515
|
+
this.determinePresentationType_(playlist);
|
|
1516
|
+
}
|
|
1517
|
+
|
|
1505
1518
|
const minTimestamp = segments[0].startTime;
|
|
1506
1519
|
const lastEndTime = segments[segments.length - 1].endTime;
|
|
1507
1520
|
/** @type {!shaka.media.SegmentIndex} */
|
|
@@ -3028,6 +3041,7 @@ shaka.hls.HlsParser.TEXT_EXTENSIONS_TO_MIME_TYPES_ = {
|
|
|
3028
3041
|
'm4f': 'application/mp4',
|
|
3029
3042
|
'cmft': 'application/mp4',
|
|
3030
3043
|
'vtt': 'text/vtt',
|
|
3044
|
+
'webvtt': 'txt/vtt',
|
|
3031
3045
|
'ttml': 'application/ttml+xml',
|
|
3032
3046
|
};
|
|
3033
3047
|
|
|
@@ -69,6 +69,18 @@ shaka.media.ContentWorkarounds = class {
|
|
|
69
69
|
})
|
|
70
70
|
.fullBox('encv', onEncryptionMetadataBox)
|
|
71
71
|
.fullBox('enca', onEncryptionMetadataBox)
|
|
72
|
+
.fullBox('hev1', (box) => {
|
|
73
|
+
boxesToModify.push({
|
|
74
|
+
box,
|
|
75
|
+
newType: ContentWorkarounds.BOX_TYPE_ENCV_,
|
|
76
|
+
});
|
|
77
|
+
})
|
|
78
|
+
.fullBox('hvc1', (box) => {
|
|
79
|
+
boxesToModify.push({
|
|
80
|
+
box,
|
|
81
|
+
newType: ContentWorkarounds.BOX_TYPE_ENCV_,
|
|
82
|
+
});
|
|
83
|
+
})
|
|
72
84
|
.fullBox('avc1', (box) => {
|
|
73
85
|
boxesToModify.push({
|
|
74
86
|
box,
|
package/lib/media/playhead.js
CHANGED
|
@@ -33,6 +33,14 @@ goog.requireType('shaka.media.PresentationTimeline');
|
|
|
33
33
|
* @interface
|
|
34
34
|
*/
|
|
35
35
|
shaka.media.Playhead = class {
|
|
36
|
+
/**
|
|
37
|
+
* Called when the Player is ready to begin playback. Anything that depends
|
|
38
|
+
* on setStartTime() should be done here, not in the constructor.
|
|
39
|
+
*
|
|
40
|
+
* @see https://github.com/shaka-project/shaka-player/issues/4244
|
|
41
|
+
*/
|
|
42
|
+
ready() {}
|
|
43
|
+
|
|
36
44
|
/**
|
|
37
45
|
* Set the start time. If the content has already started playback, this will
|
|
38
46
|
* be ignored.
|
|
@@ -76,6 +84,14 @@ shaka.media.SrcEqualsPlayhead = class {
|
|
|
76
84
|
|
|
77
85
|
/** @private {shaka.util.EventManager} */
|
|
78
86
|
this.eventManager_ = new shaka.util.EventManager();
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** @override */
|
|
90
|
+
ready() {
|
|
91
|
+
goog.asserts.assert(
|
|
92
|
+
this.mediaElement_ != null,
|
|
93
|
+
'Playhead should not be released before calling ready()',
|
|
94
|
+
);
|
|
79
95
|
|
|
80
96
|
// We listen for the loaded-data-event so that we know when we can
|
|
81
97
|
// interact with |currentTime|.
|
|
@@ -88,6 +104,7 @@ shaka.media.SrcEqualsPlayhead = class {
|
|
|
88
104
|
this.eventManager_.listenOnce(this.mediaElement_, 'seeking', () => {
|
|
89
105
|
this.started_ = true;
|
|
90
106
|
});
|
|
107
|
+
|
|
91
108
|
const currentTime = this.mediaElement_.currentTime;
|
|
92
109
|
// Using the currentTime allows using a negative number in Live HLS
|
|
93
110
|
const newTime = Math.max(0, currentTime + this.startTime_);
|
|
@@ -211,7 +228,12 @@ shaka.media.MediaSourcePlayhead = class {
|
|
|
211
228
|
/** @type {shaka.util.Timer} */
|
|
212
229
|
this.checkWindowTimer_ = new shaka.util.Timer(() => {
|
|
213
230
|
this.onPollWindow_();
|
|
214
|
-
})
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/** @override */
|
|
235
|
+
ready() {
|
|
236
|
+
this.checkWindowTimer_.tickEvery(/* seconds= */ 0.25);
|
|
215
237
|
}
|
|
216
238
|
|
|
217
239
|
/** @override */
|
|
@@ -1366,16 +1366,6 @@ shaka.media.StreamingEngine = class {
|
|
|
1366
1366
|
this.mediaStates_.delete(ContentType.TEXT);
|
|
1367
1367
|
} else if (error.code == shaka.util.Error.Code.QUOTA_EXCEEDED_ERROR) {
|
|
1368
1368
|
this.handleQuotaExceeded_(mediaState, error);
|
|
1369
|
-
} else if (error.code == shaka.util.Error.Code.BAD_HTTP_STATUS &&
|
|
1370
|
-
error.data && error.data[1] == 404) {
|
|
1371
|
-
// The segment could not be found, does not exist, or is not available.
|
|
1372
|
-
// In any case just try again.
|
|
1373
|
-
// The current segment is not available. Schedule another update to
|
|
1374
|
-
// fetch the segment again.
|
|
1375
|
-
shaka.log.v2(logPrefix, 'segment not available.');
|
|
1376
|
-
mediaState.performingUpdate = false;
|
|
1377
|
-
mediaState.updateTimer = null;
|
|
1378
|
-
this.scheduleUpdate_(mediaState, 1);
|
|
1379
1369
|
} else {
|
|
1380
1370
|
shaka.log.error(logPrefix, 'failed fetch and append: code=' +
|
|
1381
1371
|
error.code);
|
|
@@ -1390,9 +1380,10 @@ shaka.media.StreamingEngine = class {
|
|
|
1390
1380
|
|
|
1391
1381
|
/**
|
|
1392
1382
|
* Clear per-stream error states and retry any failed streams.
|
|
1383
|
+
* @param {number} delaySeconds
|
|
1393
1384
|
* @return {boolean} False if unable to retry.
|
|
1394
1385
|
*/
|
|
1395
|
-
retry() {
|
|
1386
|
+
retry(delaySeconds) {
|
|
1396
1387
|
if (this.destroyer_.destroyed()) {
|
|
1397
1388
|
shaka.log.error('Unable to retry after StreamingEngine is destroyed!');
|
|
1398
1389
|
return false;
|
|
@@ -1409,7 +1400,7 @@ shaka.media.StreamingEngine = class {
|
|
|
1409
1400
|
if (mediaState.hasError) {
|
|
1410
1401
|
shaka.log.info(logPrefix, 'Retrying after failure...');
|
|
1411
1402
|
mediaState.hasError = false;
|
|
1412
|
-
this.scheduleUpdate_(mediaState,
|
|
1403
|
+
this.scheduleUpdate_(mediaState, delaySeconds);
|
|
1413
1404
|
}
|
|
1414
1405
|
}
|
|
1415
1406
|
|
package/lib/player.js
CHANGED
|
@@ -2040,6 +2040,8 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
2040
2040
|
shaka.util.StreamUtils.variantToTrack(initialVariant));
|
|
2041
2041
|
}
|
|
2042
2042
|
|
|
2043
|
+
this.playhead_.ready();
|
|
2044
|
+
|
|
2043
2045
|
// Decide if text should be shown automatically.
|
|
2044
2046
|
// similar to video/audio track, we would skip switch initial text track
|
|
2045
2047
|
// if user already pick text track (via selectTextTrack api)
|
|
@@ -2374,6 +2376,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
2374
2376
|
HTMLMediaElement.HAVE_METADATA,
|
|
2375
2377
|
this.loadEventManager_,
|
|
2376
2378
|
() => {
|
|
2379
|
+
this.playhead_.ready();
|
|
2377
2380
|
fullyLoaded.resolve();
|
|
2378
2381
|
});
|
|
2379
2382
|
|
|
@@ -4958,12 +4961,13 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
4958
4961
|
* If the player has loaded content, and streaming seen an error, but the
|
|
4959
4962
|
* could not resume streaming, this will return <code>false</code>.
|
|
4960
4963
|
*
|
|
4964
|
+
* @param {number=} retryDelaySeconds
|
|
4961
4965
|
* @return {boolean}
|
|
4962
4966
|
* @export
|
|
4963
4967
|
*/
|
|
4964
|
-
retryStreaming() {
|
|
4968
|
+
retryStreaming(retryDelaySeconds = 0.1) {
|
|
4965
4969
|
return this.loadMode_ == shaka.Player.LoadMode.MEDIA_SOURCE ?
|
|
4966
|
-
this.streamingEngine_.retry() :
|
|
4970
|
+
this.streamingEngine_.retry(retryDelaySeconds) :
|
|
4967
4971
|
false;
|
|
4968
4972
|
}
|
|
4969
4973
|
|
|
@@ -5059,17 +5063,26 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
5059
5063
|
* @private
|
|
5060
5064
|
*/
|
|
5061
5065
|
defaultStreamingFailureCallback_(error) {
|
|
5062
|
-
|
|
5063
|
-
|
|
5064
|
-
|
|
5065
|
-
|
|
5066
|
-
|
|
5066
|
+
// For live streams, we retry streaming automatically for certain errors.
|
|
5067
|
+
// For VOD streams, all streaming failures are fatal.
|
|
5068
|
+
if (!this.isLive()) {
|
|
5069
|
+
return;
|
|
5070
|
+
}
|
|
5067
5071
|
|
|
5068
|
-
|
|
5069
|
-
|
|
5072
|
+
let retryDelaySeconds = null;
|
|
5073
|
+
if (error.code == shaka.util.Error.Code.BAD_HTTP_STATUS ||
|
|
5074
|
+
error.code == shaka.util.Error.Code.HTTP_ERROR) {
|
|
5075
|
+
// These errors can be near-instant, so delay a bit before retrying.
|
|
5076
|
+
retryDelaySeconds = 1;
|
|
5077
|
+
} else if (error.code == shaka.util.Error.Code.TIMEOUT) {
|
|
5078
|
+
// We already waited for a timeout, so retry quickly.
|
|
5079
|
+
retryDelaySeconds = 0.1;
|
|
5080
|
+
}
|
|
5070
5081
|
|
|
5082
|
+
if (retryDelaySeconds != null) {
|
|
5083
|
+
error.severity = shaka.util.Error.Severity.RECOVERABLE;
|
|
5071
5084
|
shaka.log.warning('Live streaming error. Retrying automatically...');
|
|
5072
|
-
this.retryStreaming();
|
|
5085
|
+
this.retryStreaming(retryDelaySeconds);
|
|
5073
5086
|
}
|
|
5074
5087
|
}
|
|
5075
5088
|
|
|
@@ -6635,7 +6648,7 @@ shaka.Player.TYPICAL_BUFFERING_THRESHOLD_ = 0.5;
|
|
|
6635
6648
|
* @define {string} A version number taken from git at compile time.
|
|
6636
6649
|
* @export
|
|
6637
6650
|
*/
|
|
6638
|
-
shaka.Player.version = 'v3.3.
|
|
6651
|
+
shaka.Player.version = 'v3.3.14-uncompiled';
|
|
6639
6652
|
|
|
6640
6653
|
// Initialize the deprecation system using the version string we just set
|
|
6641
6654
|
// on the player.
|
|
@@ -8,6 +8,7 @@ goog.provide('shaka.polyfill.MediaCapabilities');
|
|
|
8
8
|
|
|
9
9
|
goog.require('shaka.log');
|
|
10
10
|
goog.require('shaka.polyfill');
|
|
11
|
+
goog.require('shaka.util.Error');
|
|
11
12
|
goog.require('shaka.util.Platform');
|
|
12
13
|
|
|
13
14
|
|
|
@@ -84,37 +85,48 @@ shaka.polyfill.MediaCapabilities = class {
|
|
|
84
85
|
return res;
|
|
85
86
|
}
|
|
86
87
|
|
|
88
|
+
const videoConfig = mediaDecodingConfig['video'];
|
|
89
|
+
const audioConfig = mediaDecodingConfig['audio'];
|
|
90
|
+
|
|
87
91
|
if (mediaDecodingConfig.type == 'media-source') {
|
|
88
92
|
if (!shaka.util.Platform.supportsMediaSource()) {
|
|
89
93
|
return res;
|
|
90
94
|
}
|
|
91
95
|
// Use 'MediaSource.isTypeSupported' to check if the stream is supported.
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
96
|
+
// Cast platforms will additionally check canDisplayType(), which
|
|
97
|
+
// accepts extended MIME type parameters.
|
|
98
|
+
// See: https://github.com/shaka-project/shaka-player/issues/4726
|
|
99
|
+
if (videoConfig) {
|
|
100
|
+
let isSupported;
|
|
101
|
+
if (shaka.util.Platform.isChromecast()) {
|
|
102
|
+
isSupported =
|
|
103
|
+
shaka.polyfill.MediaCapabilities.canCastDisplayType_(videoConfig);
|
|
104
|
+
} else {
|
|
105
|
+
isSupported = MediaSource.isTypeSupported(videoConfig.contentType);
|
|
106
|
+
}
|
|
95
107
|
if (!isSupported) {
|
|
96
108
|
return res;
|
|
97
109
|
}
|
|
98
110
|
}
|
|
99
111
|
|
|
100
|
-
if (
|
|
101
|
-
const contentType =
|
|
112
|
+
if (audioConfig) {
|
|
113
|
+
const contentType = audioConfig.contentType;
|
|
102
114
|
const isSupported = MediaSource.isTypeSupported(contentType);
|
|
103
115
|
if (!isSupported) {
|
|
104
116
|
return res;
|
|
105
117
|
}
|
|
106
118
|
}
|
|
107
119
|
} else if (mediaDecodingConfig.type == 'file') {
|
|
108
|
-
if (
|
|
109
|
-
const contentType =
|
|
120
|
+
if (videoConfig) {
|
|
121
|
+
const contentType = videoConfig.contentType;
|
|
110
122
|
const isSupported = shaka.util.Platform.supportsMediaType(contentType);
|
|
111
123
|
if (!isSupported) {
|
|
112
124
|
return res;
|
|
113
125
|
}
|
|
114
126
|
}
|
|
115
127
|
|
|
116
|
-
if (
|
|
117
|
-
const contentType =
|
|
128
|
+
if (audioConfig) {
|
|
129
|
+
const contentType = audioConfig.contentType;
|
|
118
130
|
const isSupported = shaka.util.Platform.supportsMediaType(contentType);
|
|
119
131
|
if (!isSupported) {
|
|
120
132
|
return res;
|
|
@@ -189,6 +201,44 @@ shaka.polyfill.MediaCapabilities = class {
|
|
|
189
201
|
|
|
190
202
|
return res;
|
|
191
203
|
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Checks if the given media parameters of the video or audio streams are
|
|
207
|
+
* supported by the Cast platform.
|
|
208
|
+
* @param {!VideoConfiguration} videoConfig The 'video' field of the
|
|
209
|
+
* MediaDecodingConfiguration.
|
|
210
|
+
* @return {boolean} `true` when the stream can be displayed on a Cast device.
|
|
211
|
+
* @private
|
|
212
|
+
*/
|
|
213
|
+
static canCastDisplayType_(videoConfig) {
|
|
214
|
+
if (!(window.cast)) {
|
|
215
|
+
shaka.log.error('Expected cast namespace to be available!');
|
|
216
|
+
throw new shaka.util.Error(
|
|
217
|
+
shaka.util.Error.Severity.CRITICAL,
|
|
218
|
+
shaka.util.Error.Category.CAST,
|
|
219
|
+
shaka.util.Error.Code.CAST_API_UNAVAILABLE);
|
|
220
|
+
} else if (!(cast.__platform__ && cast.__platform__.canDisplayType)) {
|
|
221
|
+
shaka.log.warning('Expected cast APIs to be available! Falling back to ' +
|
|
222
|
+
'MediaSource.isTypeSupported() for type support.');
|
|
223
|
+
return MediaSource.isTypeSupported(videoConfig.contentType);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
let displayType = videoConfig.contentType;
|
|
227
|
+
if (videoConfig.width && videoConfig.height) {
|
|
228
|
+
displayType +=
|
|
229
|
+
`; width=${videoConfig.width}; height=${videoConfig.height}`;
|
|
230
|
+
}
|
|
231
|
+
if (videoConfig.framerate) {
|
|
232
|
+
displayType += `; framerate=${videoConfig.framerate}`;
|
|
233
|
+
}
|
|
234
|
+
if (videoConfig.transferFunction === 'pq') {
|
|
235
|
+
// A "PQ" transfer function indicates this is an HDR-capable stream;
|
|
236
|
+
// "smpte2084" is the published standard. We need to inform the platform
|
|
237
|
+
// this query is specifically for HDR.
|
|
238
|
+
displayType += '; eotf=smpte2084';
|
|
239
|
+
}
|
|
240
|
+
return cast.__platform__.canDisplayType(displayType);
|
|
241
|
+
}
|
|
192
242
|
};
|
|
193
243
|
|
|
194
244
|
/**
|
|
@@ -128,7 +128,7 @@ shaka.text.TtmlTextParser = class {
|
|
|
128
128
|
}
|
|
129
129
|
|
|
130
130
|
const cue = TtmlTextParser.parseCue_(
|
|
131
|
-
body, time
|
|
131
|
+
body, time, rateInfo, metadataElements, styles,
|
|
132
132
|
regionElements, cueRegions, whitespaceTrim,
|
|
133
133
|
cellResolutionInfo, /* parentCueElement= */ null,
|
|
134
134
|
/* isContent= */ false);
|
|
@@ -149,7 +149,7 @@ shaka.text.TtmlTextParser = class {
|
|
|
149
149
|
* Parses a TTML node into a Cue.
|
|
150
150
|
*
|
|
151
151
|
* @param {!Node} cueNode
|
|
152
|
-
* @param {
|
|
152
|
+
* @param {shaka.extern.TextParser.TimeContext} timeContext
|
|
153
153
|
* @param {!shaka.text.TtmlTextParser.RateInfo_} rateInfo
|
|
154
154
|
* @param {!Array.<!Element>} metadataElements
|
|
155
155
|
* @param {!Array.<!Element>} styles
|
|
@@ -163,7 +163,7 @@ shaka.text.TtmlTextParser = class {
|
|
|
163
163
|
* @private
|
|
164
164
|
*/
|
|
165
165
|
static parseCue_(
|
|
166
|
-
cueNode,
|
|
166
|
+
cueNode, timeContext, rateInfo, metadataElements, styles, regionElements,
|
|
167
167
|
cueRegions, whitespaceTrim, cellResolution, parentCueElement, isContent) {
|
|
168
168
|
/** @type {Element} */
|
|
169
169
|
let cueElement;
|
|
@@ -228,7 +228,7 @@ shaka.text.TtmlTextParser = class {
|
|
|
228
228
|
for (const childNode of cueElement.childNodes) {
|
|
229
229
|
const nestedCue = shaka.text.TtmlTextParser.parseCue_(
|
|
230
230
|
childNode,
|
|
231
|
-
|
|
231
|
+
timeContext,
|
|
232
232
|
rateInfo,
|
|
233
233
|
metadataElements,
|
|
234
234
|
styles,
|
|
@@ -285,15 +285,20 @@ shaka.text.TtmlTextParser = class {
|
|
|
285
285
|
if (start == null) {
|
|
286
286
|
start = 0;
|
|
287
287
|
}
|
|
288
|
-
start +=
|
|
288
|
+
start += timeContext.periodStart;
|
|
289
289
|
|
|
290
290
|
// If end is null, that means the duration is effectively infinite.
|
|
291
291
|
if (end == null) {
|
|
292
292
|
end = Infinity;
|
|
293
293
|
} else {
|
|
294
|
-
end +=
|
|
294
|
+
end += timeContext.periodStart;
|
|
295
295
|
}
|
|
296
296
|
|
|
297
|
+
// Clip times to segment boundaries.
|
|
298
|
+
// https://github.com/shaka-project/shaka-player/issues/4631
|
|
299
|
+
start = Math.max(start, timeContext.segmentStart);
|
|
300
|
+
end = Math.min(end, timeContext.segmentEnd);
|
|
301
|
+
|
|
297
302
|
if (!hasTimeAttributes && nestedCues.length > 0) {
|
|
298
303
|
// If no time is defined for this cue, base the timing information on
|
|
299
304
|
// the time of the nested cues. In the case of multiple nested cues with
|
|
@@ -561,8 +566,28 @@ shaka.text.TtmlTextParser = class {
|
|
|
561
566
|
|
|
562
567
|
const fontFamily = TtmlTextParser.getStyleAttribute_(
|
|
563
568
|
cueElement, region, styles, 'fontFamily', shouldInheritRegionStyles);
|
|
569
|
+
// See https://github.com/sandflow/imscJS/blob/1.1.3/src/main/js/html.js#L1384
|
|
564
570
|
if (fontFamily) {
|
|
565
|
-
|
|
571
|
+
switch (fontFamily) {
|
|
572
|
+
case 'monospaceSerif':
|
|
573
|
+
cue.fontFamily = 'Courier New,Liberation Mono,Courier,monospace';
|
|
574
|
+
break;
|
|
575
|
+
case 'proportionalSansSerif':
|
|
576
|
+
cue.fontFamily = 'Arial,Helvetica,Liberation Sans,sans-serif';
|
|
577
|
+
break;
|
|
578
|
+
case 'sansSerif':
|
|
579
|
+
cue.fontFamily = 'sans-serif';
|
|
580
|
+
break;
|
|
581
|
+
case 'monospaceSansSerif':
|
|
582
|
+
cue.fontFamily = 'Consolas,monospace';
|
|
583
|
+
break;
|
|
584
|
+
case 'proportionalSerif':
|
|
585
|
+
cue.fontFamily = 'serif';
|
|
586
|
+
break;
|
|
587
|
+
default:
|
|
588
|
+
cue.fontFamily = fontFamily;
|
|
589
|
+
break;
|
|
590
|
+
}
|
|
566
591
|
}
|
|
567
592
|
|
|
568
593
|
const fontWeight = TtmlTextParser.getStyleAttribute_(
|
|
@@ -200,6 +200,19 @@ shaka.text.UITextDisplayer = class {
|
|
|
200
200
|
this.isTextVisible_ = on;
|
|
201
201
|
}
|
|
202
202
|
|
|
203
|
+
/**
|
|
204
|
+
* @private
|
|
205
|
+
*/
|
|
206
|
+
isElementUnderTextContainer_(elemToCheck) {
|
|
207
|
+
while (elemToCheck != null) {
|
|
208
|
+
if (elemToCheck == this.textContainer_) {
|
|
209
|
+
return true;
|
|
210
|
+
}
|
|
211
|
+
elemToCheck = elemToCheck.parentElement;
|
|
212
|
+
}
|
|
213
|
+
return false;
|
|
214
|
+
}
|
|
215
|
+
|
|
203
216
|
/**
|
|
204
217
|
* @param {!Array.<!shaka.extern.Cue>} cues
|
|
205
218
|
* @param {!HTMLElement} container
|
|
@@ -261,6 +274,9 @@ shaka.text.UITextDisplayer = class {
|
|
|
261
274
|
cueRegistry = this.currentCuesMap_.get(cue);
|
|
262
275
|
wrapper = cueRegistry.wrapper;
|
|
263
276
|
updateDOM = true;
|
|
277
|
+
} else if (!this.isElementUnderTextContainer_(wrapper)) {
|
|
278
|
+
// We found that the wrapper needs to be in the DOM
|
|
279
|
+
updateDOM = true;
|
|
264
280
|
}
|
|
265
281
|
}
|
|
266
282
|
|
|
@@ -277,6 +293,7 @@ shaka.text.UITextDisplayer = class {
|
|
|
277
293
|
const topCue = parents.pop();
|
|
278
294
|
goog.asserts.assert(topCue == cue, 'Parent cues should be kept in order');
|
|
279
295
|
}
|
|
296
|
+
|
|
280
297
|
if (updateDOM) {
|
|
281
298
|
for (const element of toUproot) {
|
|
282
299
|
// NOTE: Because we uproot shared region elements, too, we might hit an
|
|
@@ -583,7 +600,7 @@ shaka.text.UITextDisplayer = class {
|
|
|
583
600
|
// (for vertical growing left) is aligned at the line.
|
|
584
601
|
// TODO: Implement line alignment with line number.
|
|
585
602
|
// TODO: Implement lineAlignment of 'CENTER'.
|
|
586
|
-
if (cue.line) {
|
|
603
|
+
if (cue.line != null) {
|
|
587
604
|
if (cue.lineInterpretation == Cue.lineInterpretation.PERCENTAGE) {
|
|
588
605
|
// When setting absolute positioning, you need to set x/y/width/height
|
|
589
606
|
// so the element is positioned correctly. Set these as default and
|
|
@@ -596,21 +613,21 @@ shaka.text.UITextDisplayer = class {
|
|
|
596
613
|
if (cue.lineAlign == Cue.lineAlign.START) {
|
|
597
614
|
style.top = cue.line + '%';
|
|
598
615
|
} else if (cue.lineAlign == Cue.lineAlign.END) {
|
|
599
|
-
style.bottom = cue.line + '%';
|
|
616
|
+
style.bottom = (100 - cue.line) + '%';
|
|
600
617
|
}
|
|
601
618
|
} else if (cue.writingMode == Cue.writingMode.VERTICAL_LEFT_TO_RIGHT) {
|
|
602
619
|
style.height = '100%';
|
|
603
620
|
if (cue.lineAlign == Cue.lineAlign.START) {
|
|
604
621
|
style.left = cue.line + '%';
|
|
605
622
|
} else if (cue.lineAlign == Cue.lineAlign.END) {
|
|
606
|
-
style.right = cue.line + '%';
|
|
623
|
+
style.right = (100 - cue.line) + '%';
|
|
607
624
|
}
|
|
608
625
|
} else {
|
|
609
626
|
style.height = '100%';
|
|
610
627
|
if (cue.lineAlign == Cue.lineAlign.START) {
|
|
611
628
|
style.right = cue.line + '%';
|
|
612
629
|
} else if (cue.lineAlign == Cue.lineAlign.END) {
|
|
613
|
-
style.left = cue.line + '%';
|
|
630
|
+
style.left = (100 - cue.line) + '%';
|
|
614
631
|
}
|
|
615
632
|
}
|
|
616
633
|
}
|
|
@@ -620,7 +637,7 @@ shaka.text.UITextDisplayer = class {
|
|
|
620
637
|
|
|
621
638
|
// The position defines the indent of the text container in the
|
|
622
639
|
// direction defined by the writing direction.
|
|
623
|
-
if (cue.position) {
|
|
640
|
+
if (cue.position != null) {
|
|
624
641
|
if (cue.writingMode == Cue.writingMode.HORIZONTAL_TOP_TO_BOTTOM) {
|
|
625
642
|
style.paddingLeft = cue.position;
|
|
626
643
|
} else {
|