shaka-player 4.3.3 → 4.3.5
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/build-and-test.yaml +4 -4
- package/.github/workflows/demo-version-index.yaml +6 -0
- package/.github/workflows/selenium-lab-tests.yaml +72 -11
- package/.github/workflows/update-screenshots.yaml +142 -0
- package/CHANGELOG.md +41 -0
- package/demo/common/asset.js +15 -0
- package/demo/common/assets.js +4 -2
- package/demo/common/message_ids.js +1 -0
- package/demo/custom.js +19 -0
- package/demo/locales/en.json +1 -0
- package/demo/locales/source.json +4 -0
- package/demo/main.js +14 -0
- package/dist/demo.compiled.debug.js +30 -28
- package/dist/demo.compiled.debug.map +3 -3
- package/dist/demo.compiled.js +30 -28
- package/dist/demo.compiled.map +3 -3
- package/dist/deps.js +4 -3
- package/dist/locales.js +30 -30
- package/dist/receiver.compiled.debug.js +22 -22
- package/dist/receiver.compiled.debug.map +2 -2
- package/dist/receiver.compiled.js +22 -22
- package/dist/receiver.compiled.map +2 -2
- package/dist/shaka-player.compiled.d.ts +7 -0
- package/dist/shaka-player.compiled.debug.d.ts +7 -0
- package/dist/shaka-player.compiled.debug.js +116 -109
- package/dist/shaka-player.compiled.debug.map +4 -4
- package/dist/shaka-player.compiled.js +825 -821
- package/dist/shaka-player.compiled.map +4 -4
- package/dist/shaka-player.ui.d.ts +7 -0
- package/dist/shaka-player.ui.debug.d.ts +7 -0
- package/dist/shaka-player.ui.debug.js +128 -121
- package/dist/shaka-player.ui.debug.map +4 -4
- package/dist/shaka-player.ui.js +937 -932
- package/dist/shaka-player.ui.map +4 -4
- package/docs/tutorials/errors.md +94 -0
- package/docs/tutorials/fairplay.md +2 -2
- package/docs/tutorials/index.json +2 -0
- package/docs/tutorials/widevine-service-certs.md +45 -0
- package/externs/ima.js +9 -0
- package/lib/ads/client_side_ad_manager.js +6 -3
- package/lib/dash/segment_template.js +17 -5
- package/lib/hls/hls_parser.js +12 -2
- package/lib/media/drm_engine.js +4 -2
- package/lib/media/transmuxer.js +13 -7
- package/lib/media/video_wrapper.js +3 -3
- package/lib/offline/indexeddb/storage_mechanism.js +1 -1
- package/lib/player.js +3 -1
- package/lib/polyfill/media_capabilities.js +6 -9
- package/lib/polyfill/mediasource.js +24 -2
- package/lib/text/simple_text_displayer.js +2 -64
- package/lib/text/text_utils.js +88 -0
- package/lib/text/ui_text_displayer.js +1 -5
- package/lib/text/vtt_text_parser.js +228 -102
- package/lib/text/web_vtt_generator.js +2 -64
- package/lib/util/mime_utils.js +1 -1
- package/lib/util/platform.js +8 -0
- package/lib/util/stream_utils.js +26 -14
- package/package.json +1 -1
- package/support.html +1 -0
- package/test/dash/dash_parser_manifest_unit.js +0 -36
- package/test/polyfill/media_capabilities_unit.js +21 -22
- package/test/test/util/layout_tests.js +433 -0
- package/test/test/util/util.js +0 -120
- package/test/{ui → text}/text_displayer_layout_unit.js +59 -235
- package/test/text/ui_text_displayer_unit.js +2 -4
- package/test/text/vtt_text_parser_unit.js +138 -8
- package/test/text/web_vtt_layout_integration.js +525 -0
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# Error Handling
|
|
2
|
+
|
|
3
|
+
The basics for handling native Error and {@link shaka.util.Error} thrown by Shaka.
|
|
4
|
+
|
|
5
|
+
## Listening To and Handling Errors
|
|
6
|
+
|
|
7
|
+
```javascript
|
|
8
|
+
const handleError = (error) => {
|
|
9
|
+
if (error instanceof Error) {
|
|
10
|
+
// shaka crashed with an unhandled native error
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
if (error.severity === shaka.util.Error.Severity.CRITICAL) {
|
|
14
|
+
// handle fatal error, playback can not continue
|
|
15
|
+
} else {
|
|
16
|
+
// handle non-fatal error, playback can continue
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
const player = new shaka.Player(video);
|
|
21
|
+
|
|
22
|
+
// handle errors that occur after load
|
|
23
|
+
player.addEventListener('error', handleError);
|
|
24
|
+
|
|
25
|
+
// there are two options for catching errors that occur during load
|
|
26
|
+
|
|
27
|
+
// it's possible to listen directly on the promise
|
|
28
|
+
player.load(url).catch(handleError);
|
|
29
|
+
|
|
30
|
+
// or to use async/await with a try/catch
|
|
31
|
+
try {
|
|
32
|
+
await player.load(url);
|
|
33
|
+
} catch (e) {
|
|
34
|
+
handleError(e);
|
|
35
|
+
}
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Custom Handling of Streaming Errors
|
|
39
|
+
|
|
40
|
+
The `streaming.failureCallback` property of {@link shaka.extern.PlayerConfiguration} can be used to add custom handling or error conversion of errors that occur during streaming.
|
|
41
|
+
|
|
42
|
+
```javascript
|
|
43
|
+
player.configure('streaming.failureCallback', (error) => {
|
|
44
|
+
if (error.severity === shaka.util.Error.Severity.CRITICAL) {
|
|
45
|
+
// custom handling of critical error
|
|
46
|
+
// e.g. player.retryStreaming();
|
|
47
|
+
} else {
|
|
48
|
+
// custom handling of recoverable error
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Custom Handling of Retries
|
|
54
|
+
|
|
55
|
+
When configuring retry parameters in Shaka there may be known error codes that should not be retried, or the need to break out of an infinite retry loop in a live context. Retries for failed network requests are not reported as `RECOVERABLE` errors.
|
|
56
|
+
|
|
57
|
+
For example, if a VOD manifest is missing, unlike when a LIVE manifest is missing, it can be expected to not show up and there is no need to retry.
|
|
58
|
+
|
|
59
|
+
This is how to convert a retry into a critical error, see {@link shaka.net.NetworkingEngine.RetryEvent} and {@link shaka.util.Error} to decipher how the event can be parsed:
|
|
60
|
+
|
|
61
|
+
```javascript
|
|
62
|
+
const nwEngine = player.getNetworkingEngine();
|
|
63
|
+
|
|
64
|
+
const vodManifestNotFoundHandler = (event /* shaka.net.NetworkingEngine.RetryEvent */) => {
|
|
65
|
+
const code = event.error.code;
|
|
66
|
+
const data = event.error.data;
|
|
67
|
+
|
|
68
|
+
if (code === shaka.util.Error.Code.BAD_HTTP_STATUS) {
|
|
69
|
+
if (
|
|
70
|
+
// each type of error has its own data structure (or none at all), tread with care
|
|
71
|
+
Array.isArray(data) &&
|
|
72
|
+
data[1] === 404 &&
|
|
73
|
+
data[4] === shaka.net.NetworkingEngine.RequestType.MANIFEST
|
|
74
|
+
) {
|
|
75
|
+
// Throwing inside a retry callback will immediately stop retries
|
|
76
|
+
throw error;
|
|
77
|
+
|
|
78
|
+
// A proprietary error code can also be thrown
|
|
79
|
+
// throw new shaka.util.Error(
|
|
80
|
+
// shaka.util.Error.Severity.CRITICAL,
|
|
81
|
+
// shaka.util.Error.Category.NETWORK,
|
|
82
|
+
// 'RECOGNIZABLE_ERROR_MESSAGE'
|
|
83
|
+
// );
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
nwEngine.addEventListener('retry', vodManifestNotFoundHandler);
|
|
89
|
+
|
|
90
|
+
player.addEventListener('load'() => {
|
|
91
|
+
nwEngine.removeEventListener('retry', vodManifestNotFoundHandler);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
```
|
|
@@ -53,12 +53,12 @@ you.
|
|
|
53
53
|
const req = await fetch('https://example.com/cert.der');
|
|
54
54
|
const cert = await req.arrayBuffer();
|
|
55
55
|
|
|
56
|
-
player.configure('drm.advanced.com\\.apple\\.fps
|
|
56
|
+
player.configure('drm.advanced.com\\.apple\\.fps.serverCertificate',
|
|
57
57
|
new Uint8Array(cert));
|
|
58
58
|
```
|
|
59
59
|
|
|
60
60
|
```js
|
|
61
|
-
player.configure('drm.advanced.com\\.apple\\.fps
|
|
61
|
+
player.configure('drm.advanced.com\\.apple\\.fps.serverCertificateUri',
|
|
62
62
|
'https://example.com/cert.der');
|
|
63
63
|
```
|
|
64
64
|
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
{ "license-wrapping": { "title": "License Wrapping" } },
|
|
10
10
|
{ "ui": { "title": "UI Library" } },
|
|
11
11
|
{ "ui-customization": { "title": "Configuring the UI" } },
|
|
12
|
+
{ "errors": { "title": "Error Handling" } },
|
|
12
13
|
{ "a11y": { "title": "Creating accessible buttons" } },
|
|
13
14
|
{ "ad_monetization": { "title": "Serving ads with our IMA SDK integration" } },
|
|
14
15
|
{ "plugins": { "title": "Plugins and Customizing the Build" } },
|
|
@@ -16,6 +17,7 @@
|
|
|
16
17
|
{ "architecture": { "title": "Architecture Diagrams" } },
|
|
17
18
|
{ "service-worker": { "title": "Service Worker Caching" } },
|
|
18
19
|
{ "offline": { "title": "Offline Storage and Playback" } },
|
|
20
|
+
{ "widevine-service-certs": { "title": "Widevine Service Certificates" } },
|
|
19
21
|
{ "fairplay": { "title": "FairPlay support" } },
|
|
20
22
|
{ "application-level-redirects": { "title": "Application-Level Redirects" } },
|
|
21
23
|
{ "blob-url": { "title": "Blob URL" } },
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# Widevine Service Certificates
|
|
2
|
+
|
|
3
|
+
With Widevine, you can eliminate one round-trip to your license server or proxy
|
|
4
|
+
per session by preloading a Widevine service certificate. You can either provide
|
|
5
|
+
it directly or set a URI and let Shaka fetch it for you. Do this before calling
|
|
6
|
+
`player.load()`. The values will persist across multiple calls to `load()` on
|
|
7
|
+
the same `shaka.Player` instance.
|
|
8
|
+
|
|
9
|
+
```js
|
|
10
|
+
// This is an example of loading the certificate from your site at runtime.
|
|
11
|
+
// You could also choose to bundle it into your JavaScript as a Uint8Array.
|
|
12
|
+
const req = await fetch('https://example.com/service.cert');
|
|
13
|
+
const cert = new Uint8Array(await req.arrayBuffer());
|
|
14
|
+
|
|
15
|
+
// This is the short form for configuration of a certificate:
|
|
16
|
+
player.configure('drm.advanced.com\\.widevine\\.alpha.serverCertificate',
|
|
17
|
+
cert);
|
|
18
|
+
|
|
19
|
+
// This is the long form:
|
|
20
|
+
player.configure({
|
|
21
|
+
drm: {
|
|
22
|
+
advanced: {
|
|
23
|
+
'com.widevine.alpha': {
|
|
24
|
+
'serverCertificate': cert,
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
// This is the short form for configuration of a certificate URI:
|
|
32
|
+
player.configure('drm.advanced.com\\.widevine\\.alpha.serverCertificateUri',
|
|
33
|
+
'https://example.com/service.cert');
|
|
34
|
+
|
|
35
|
+
// This is the long form:
|
|
36
|
+
player.configure({
|
|
37
|
+
drm: {
|
|
38
|
+
advanced: {
|
|
39
|
+
'com.widevine.alpha': {
|
|
40
|
+
'serverCertificateUri': 'https://example.com/service.cert',
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
});
|
|
45
|
+
```
|
package/externs/ima.js
CHANGED
|
@@ -513,6 +513,15 @@ google.ima.dai.api.StreamRequest.prototype.streamActivityMonitorId;
|
|
|
513
513
|
google.ima.dai.api.StreamRequest.prototype.format;
|
|
514
514
|
|
|
515
515
|
|
|
516
|
+
/**
|
|
517
|
+
* @enum {string}
|
|
518
|
+
*/
|
|
519
|
+
google.ima.dai.api.StreamRequest.StreamFormat = {
|
|
520
|
+
DASH: 'dash',
|
|
521
|
+
HLS: 'hls',
|
|
522
|
+
};
|
|
523
|
+
|
|
524
|
+
|
|
516
525
|
/** @const */
|
|
517
526
|
google.ima.dai.api.VODStreamRequest =
|
|
518
527
|
class extends google.ima.dai.api.StreamRequest {};
|
|
@@ -79,9 +79,9 @@ shaka.ads.ClientSideAdManager = class {
|
|
|
79
79
|
});
|
|
80
80
|
|
|
81
81
|
// Notify the SDK when the video has ended, so it can play post-roll ads.
|
|
82
|
-
this.video_
|
|
82
|
+
this.eventManager_.listen(this.video_, 'ended', () => {
|
|
83
83
|
this.adsLoader_.contentComplete();
|
|
84
|
-
};
|
|
84
|
+
});
|
|
85
85
|
}
|
|
86
86
|
|
|
87
87
|
/**
|
|
@@ -440,6 +440,7 @@ shaka.ads.ClientSideAdManager = class {
|
|
|
440
440
|
if (this.ad_.isLinear()) {
|
|
441
441
|
this.adContainer_.setAttribute('ad-active', 'true');
|
|
442
442
|
this.video_.pause();
|
|
443
|
+
this.ad_.setVolume(this.video_.muted ? 0 : this.video_.volume);
|
|
443
444
|
}
|
|
444
445
|
}
|
|
445
446
|
|
|
@@ -452,7 +453,9 @@ shaka.ads.ClientSideAdManager = class {
|
|
|
452
453
|
(new Map()).set('originalEvent', e)));
|
|
453
454
|
if (this.ad_ && this.ad_.isLinear()) {
|
|
454
455
|
this.adContainer_.removeAttribute('ad-active');
|
|
455
|
-
this.video_.
|
|
456
|
+
if (!this.video_.ended) {
|
|
457
|
+
this.video_.play();
|
|
458
|
+
}
|
|
456
459
|
}
|
|
457
460
|
}
|
|
458
461
|
};
|
|
@@ -102,11 +102,23 @@ shaka.dash.SegmentTemplate = class {
|
|
|
102
102
|
const periodEnd = context.periodInfo.duration ?
|
|
103
103
|
context.periodInfo.start + context.periodInfo.duration : Infinity;
|
|
104
104
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
105
|
+
/* When to fit segments. All refactors should honor/update this table:
|
|
106
|
+
*
|
|
107
|
+
* | dynamic | infinite | last | should | notes |
|
|
108
|
+
* | | period | period | fit | |
|
|
109
|
+
* | ------- | -------- | ------ | ------ | ------------------------- |
|
|
110
|
+
* | F | F | X | T | typical VOD |
|
|
111
|
+
* | F | T | X | X | impossible: infinite VOD |
|
|
112
|
+
* | T | F | F | T | typical live, old period |
|
|
113
|
+
* | T | F | T | F | typical IPR |
|
|
114
|
+
* | T | T | F | X | impossible: old, infinite |
|
|
115
|
+
* | T | T | T | F | typical live, new period |
|
|
116
|
+
*/
|
|
117
|
+
|
|
118
|
+
// We never fit the final period of dynamic content, which could be
|
|
119
|
+
// infinite live (with no limit to fit to) or IPR (which would expand the
|
|
120
|
+
// most recent segment to the end of the presentation).
|
|
121
|
+
const shouldFit = !(context.dynamic && context.periodInfo.isLastPeriod);
|
|
110
122
|
|
|
111
123
|
if (segmentIndex) {
|
|
112
124
|
if (shouldFit) {
|
package/lib/hls/hls_parser.js
CHANGED
|
@@ -311,7 +311,10 @@ shaka.hls.HlsParser = class {
|
|
|
311
311
|
const PresentationType = shaka.hls.HlsParser.PresentationType_;
|
|
312
312
|
this.setPresentationType_(PresentationType.VOD);
|
|
313
313
|
|
|
314
|
-
|
|
314
|
+
// The duration is the minimum of the end times of all active streams.
|
|
315
|
+
// Non-active streams are not guaranteed to have useful maxTimestamp
|
|
316
|
+
// values, due to the lazy-loading system, so they are ignored.
|
|
317
|
+
const maxTimestamps = activeStreamInfos.map((s) => s.maxTimestamp);
|
|
315
318
|
// The duration is the minimum of the end times of all streams.
|
|
316
319
|
this.presentationTimeline_.setDuration(Math.min(...maxTimestamps));
|
|
317
320
|
this.playerInterface_.updateDuration();
|
|
@@ -945,6 +948,8 @@ shaka.hls.HlsParser = class {
|
|
|
945
948
|
}
|
|
946
949
|
});
|
|
947
950
|
|
|
951
|
+
const type = shaka.util.ManifestParserUtils.ContentType.TEXT;
|
|
952
|
+
|
|
948
953
|
// Set the codecs for text streams.
|
|
949
954
|
for (const tag of subtitleTags) {
|
|
950
955
|
const groupId = tag.getRequiredAttrValue('GROUP-ID');
|
|
@@ -954,6 +959,9 @@ shaka.hls.HlsParser = class {
|
|
|
954
959
|
if (textStreamInfos) {
|
|
955
960
|
for (const textStreamInfo of textStreamInfos) {
|
|
956
961
|
textStreamInfo.stream.codecs = codecs;
|
|
962
|
+
textStreamInfo.stream.mimeType =
|
|
963
|
+
this.guessMimeTypeBeforeLoading_(type, codecs) ||
|
|
964
|
+
this.guessMimeTypeFallback_(type);
|
|
957
965
|
}
|
|
958
966
|
}
|
|
959
967
|
}
|
|
@@ -3312,6 +3320,7 @@ shaka.hls.HlsParser.AUDIO_EXTENSIONS_TO_MIME_TYPES_ = {
|
|
|
3312
3320
|
'cmfa': 'audio/mp4',
|
|
3313
3321
|
// MPEG2-TS also uses video/ for audio: https://bit.ly/TsMse
|
|
3314
3322
|
'ts': 'video/mp2t',
|
|
3323
|
+
'tsa': 'video/mp2t',
|
|
3315
3324
|
};
|
|
3316
3325
|
|
|
3317
3326
|
|
|
@@ -3328,6 +3337,7 @@ shaka.hls.HlsParser.VIDEO_EXTENSIONS_TO_MIME_TYPES_ = {
|
|
|
3328
3337
|
'm4f': 'video/mp4',
|
|
3329
3338
|
'cmfv': 'video/mp4',
|
|
3330
3339
|
'ts': 'video/mp2t',
|
|
3340
|
+
'tsv': 'video/mp2t',
|
|
3331
3341
|
};
|
|
3332
3342
|
|
|
3333
3343
|
|
|
@@ -3342,7 +3352,7 @@ shaka.hls.HlsParser.TEXT_EXTENSIONS_TO_MIME_TYPES_ = {
|
|
|
3342
3352
|
'm4f': 'application/mp4',
|
|
3343
3353
|
'cmft': 'application/mp4',
|
|
3344
3354
|
'vtt': 'text/vtt',
|
|
3345
|
-
'webvtt': '
|
|
3355
|
+
'webvtt': 'text/vtt',
|
|
3346
3356
|
'ttml': 'application/ttml+xml',
|
|
3347
3357
|
};
|
|
3348
3358
|
|
package/lib/media/drm_engine.js
CHANGED
|
@@ -480,8 +480,10 @@ shaka.media.DrmEngine = class {
|
|
|
480
480
|
() => this.closeOpenSessions_());
|
|
481
481
|
}
|
|
482
482
|
|
|
483
|
-
const manifestInitData = this.currentDrmInfo_
|
|
484
|
-
|
|
483
|
+
const manifestInitData = this.currentDrmInfo_ ?
|
|
484
|
+
this.currentDrmInfo_.initData.find(
|
|
485
|
+
(initDataOverride) => initDataOverride.initData.length > 0,
|
|
486
|
+
) : null;
|
|
485
487
|
|
|
486
488
|
/**
|
|
487
489
|
* We can attach media keys before the playback actually begins when:
|
package/lib/media/transmuxer.js
CHANGED
|
@@ -35,9 +35,7 @@ shaka.media.Transmuxer = class {
|
|
|
35
35
|
this.muxjs_ = shaka.dependencies.muxjs();
|
|
36
36
|
|
|
37
37
|
/** @private {muxjs.mp4.Transmuxer} */
|
|
38
|
-
this.muxTransmuxer_ =
|
|
39
|
-
'keepOriginalTimestamps': true,
|
|
40
|
-
});
|
|
38
|
+
this.muxTransmuxer_ = null;
|
|
41
39
|
|
|
42
40
|
/** @private {shaka.util.PublicPromise} */
|
|
43
41
|
this.transmuxPromise_ = null;
|
|
@@ -51,16 +49,22 @@ shaka.media.Transmuxer = class {
|
|
|
51
49
|
/** @private {boolean} */
|
|
52
50
|
this.isTransmuxing_ = false;
|
|
53
51
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
52
|
+
if (this.muxjs_) {
|
|
53
|
+
this.muxTransmuxer_ = new this.muxjs_.mp4.Transmuxer({
|
|
54
|
+
'keepOriginalTimestamps': true,
|
|
55
|
+
});
|
|
56
|
+
this.muxTransmuxer_.on('data', (segment) => this.onTransmuxed_(segment));
|
|
57
|
+
this.muxTransmuxer_.on('done', () => this.onTransmuxDone_());
|
|
58
|
+
}
|
|
57
59
|
}
|
|
58
60
|
|
|
59
61
|
/**
|
|
60
62
|
* @override
|
|
61
63
|
*/
|
|
62
64
|
destroy() {
|
|
63
|
-
this.muxTransmuxer_
|
|
65
|
+
if (this.muxTransmuxer_) {
|
|
66
|
+
this.muxTransmuxer_.dispose();
|
|
67
|
+
}
|
|
64
68
|
this.muxTransmuxer_ = null;
|
|
65
69
|
return Promise.resolve();
|
|
66
70
|
}
|
|
@@ -216,6 +220,8 @@ shaka.media.Transmuxer = class {
|
|
|
216
220
|
* captions: !Array.<!muxjs.mp4.ClosedCaption>}>}
|
|
217
221
|
*/
|
|
218
222
|
transmux(data) {
|
|
223
|
+
goog.asserts.assert(this.muxTransmuxer_,
|
|
224
|
+
'mux.js should be available.');
|
|
219
225
|
goog.asserts.assert(!this.isTransmuxing_,
|
|
220
226
|
'No transmuxing should be in progress.');
|
|
221
227
|
this.isTransmuxing_ = true;
|
|
@@ -135,15 +135,15 @@ shaka.media.VideoWrapper = class {
|
|
|
135
135
|
// calling |Player.load|, meaning that |currentTime| is more meaningful than
|
|
136
136
|
// |startTime|.
|
|
137
137
|
//
|
|
138
|
-
// Seeking to the current time is a work around for Issue 1298
|
|
139
|
-
// do this, the video may get stuck and not play.
|
|
138
|
+
// Seeking to the current time is a work around for Issue 1298 and 4888.
|
|
139
|
+
// If we don't do this, the video may get stuck and not play.
|
|
140
140
|
//
|
|
141
141
|
// TODO: Need further investigation why it happens. Before and after
|
|
142
142
|
// setting the current time, video.readyState is 1, video.paused is true,
|
|
143
143
|
// and video.buffered's TimeRanges length is 0.
|
|
144
144
|
// See: https://github.com/shaka-project/shaka-player/issues/1298
|
|
145
145
|
this.mover_.moveTo(
|
|
146
|
-
this.video_.currentTime == 0 ?
|
|
146
|
+
(!this.video_.currentTime || this.video_.currentTime == 0) ?
|
|
147
147
|
startTime :
|
|
148
148
|
this.video_.currentTime);
|
|
149
149
|
}
|
|
@@ -67,7 +67,7 @@ shaka.offline.indexeddb.StorageMechanism = class {
|
|
|
67
67
|
shaka.util.Error.Category.STORAGE,
|
|
68
68
|
shaka.util.Error.Code.INDEXED_DB_INIT_TIMED_OUT));
|
|
69
69
|
});
|
|
70
|
-
timeOutTimer.tickAfter(
|
|
70
|
+
timeOutTimer.tickAfter(5);
|
|
71
71
|
|
|
72
72
|
const open = window.indexedDB.open(name, version);
|
|
73
73
|
open.onsuccess = (event) => {
|
package/lib/player.js
CHANGED
|
@@ -2584,6 +2584,8 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
2584
2584
|
// goes wrong.
|
|
2585
2585
|
new shaka.util.Timer(resolve).tickAfter(1);
|
|
2586
2586
|
});
|
|
2587
|
+
} else if (textTracks.length > 0) {
|
|
2588
|
+
this.isTextVisible_ = true;
|
|
2587
2589
|
}
|
|
2588
2590
|
|
|
2589
2591
|
// If we have moved on to another piece of content while waiting for
|
|
@@ -6984,7 +6986,7 @@ shaka.Player.TYPICAL_BUFFERING_THRESHOLD_ = 0.5;
|
|
|
6984
6986
|
* @define {string} A version number taken from git at compile time.
|
|
6985
6987
|
* @export
|
|
6986
6988
|
*/
|
|
6987
|
-
shaka.Player.version = 'v4.3.
|
|
6989
|
+
shaka.Player.version = 'v4.3.5-uncompiled';
|
|
6988
6990
|
|
|
6989
6991
|
// Initialize the deprecation system using the version string we just set
|
|
6990
6992
|
// on the player.
|
|
@@ -9,7 +9,6 @@ goog.provide('shaka.polyfill.MediaCapabilities');
|
|
|
9
9
|
goog.require('shaka.log');
|
|
10
10
|
goog.require('shaka.media.Capabilities');
|
|
11
11
|
goog.require('shaka.polyfill');
|
|
12
|
-
goog.require('shaka.util.Error');
|
|
13
12
|
goog.require('shaka.util.Platform');
|
|
14
13
|
|
|
15
14
|
|
|
@@ -48,6 +47,8 @@ shaka.polyfill.MediaCapabilities = class {
|
|
|
48
47
|
// should always install polyfill for WebOS browsers.
|
|
49
48
|
// Since MediaCapabilities implementation is buggy in EOS browsers, we
|
|
50
49
|
// should always install polyfill for EOS browsers.
|
|
50
|
+
// Since MediaCapabilities implementation is buggy in Hisense browsers, we
|
|
51
|
+
// should always install polyfill for Hisense browsers.
|
|
51
52
|
let canUseNativeMCap = true;
|
|
52
53
|
if (shaka.util.Platform.isApple() ||
|
|
53
54
|
shaka.util.Platform.isPS5() ||
|
|
@@ -55,7 +56,8 @@ shaka.polyfill.MediaCapabilities = class {
|
|
|
55
56
|
shaka.util.Platform.isWebOS() ||
|
|
56
57
|
shaka.util.Platform.isTizen() ||
|
|
57
58
|
shaka.util.Platform.isChromecast() ||
|
|
58
|
-
shaka.util.Platform.isEOS()
|
|
59
|
+
shaka.util.Platform.isEOS() ||
|
|
60
|
+
shaka.util.Platform.isHisense()) {
|
|
59
61
|
canUseNativeMCap = false;
|
|
60
62
|
}
|
|
61
63
|
if (canUseNativeMCap && navigator.mediaCapabilities) {
|
|
@@ -227,13 +229,8 @@ shaka.polyfill.MediaCapabilities = class {
|
|
|
227
229
|
* @private
|
|
228
230
|
*/
|
|
229
231
|
static canCastDisplayType_(videoConfig) {
|
|
230
|
-
if (!(window.cast
|
|
231
|
-
|
|
232
|
-
throw new shaka.util.Error(
|
|
233
|
-
shaka.util.Error.Severity.CRITICAL,
|
|
234
|
-
shaka.util.Error.Category.CAST,
|
|
235
|
-
shaka.util.Error.Code.CAST_API_UNAVAILABLE);
|
|
236
|
-
} else if (!(cast.__platform__ && cast.__platform__.canDisplayType)) {
|
|
232
|
+
if (!(window.cast &&
|
|
233
|
+
cast.__platform__ && cast.__platform__.canDisplayType)) {
|
|
237
234
|
shaka.log.warning('Expected cast APIs to be available! Falling back to ' +
|
|
238
235
|
'shaka.media.Capabilities.isTypeSupported() for type support.');
|
|
239
236
|
return shaka.media.Capabilities.isTypeSupported(videoConfig.contentType);
|
|
@@ -53,14 +53,19 @@ shaka.polyfill.MediaSource = class {
|
|
|
53
53
|
// Offsetting the end of the removal range seems to help.
|
|
54
54
|
// Bug filed: https://bugs.webkit.org/show_bug.cgi?id=177884
|
|
55
55
|
shaka.polyfill.MediaSource.patchRemovalRange_();
|
|
56
|
-
} else {
|
|
57
|
-
shaka.log.info('Patching Safari 13 MSE bugs.');
|
|
56
|
+
} else if (safariVersion <= 15) {
|
|
57
|
+
shaka.log.info('Patching Safari 13 & 14 & 15 MSE bugs.');
|
|
58
58
|
// Safari 13 does not correctly implement abort() on SourceBuffer.
|
|
59
59
|
// Calling abort() before appending a segment causes that segment to be
|
|
60
60
|
// incomplete in the buffer.
|
|
61
61
|
// Bug filed: https://bugs.webkit.org/show_bug.cgi?id=165342
|
|
62
62
|
shaka.polyfill.MediaSource.stubAbort_();
|
|
63
63
|
}
|
|
64
|
+
} else if (shaka.util.Platform.isEdge()) {
|
|
65
|
+
shaka.log.info('Rejecting TS container.');
|
|
66
|
+
// TS content is broken on Edge in general.
|
|
67
|
+
// See https://github.com/shaka-project/shaka-player/issues/4955
|
|
68
|
+
shaka.polyfill.MediaSource.rejectContainer_('mp2t');
|
|
64
69
|
} else if (shaka.util.Platform.isTizen2() ||
|
|
65
70
|
shaka.util.Platform.isTizen3() ||
|
|
66
71
|
shaka.util.Platform.isTizen4()) {
|
|
@@ -122,6 +127,23 @@ shaka.polyfill.MediaSource = class {
|
|
|
122
127
|
};
|
|
123
128
|
}
|
|
124
129
|
|
|
130
|
+
/**
|
|
131
|
+
* Patch |MediaSource.isTypeSupported| to always reject |container|. This is
|
|
132
|
+
* used when we know that we are on a platform that does not work well with
|
|
133
|
+
* a given container.
|
|
134
|
+
*
|
|
135
|
+
* @param {string} container
|
|
136
|
+
* @private
|
|
137
|
+
*/
|
|
138
|
+
static rejectContainer_(container) {
|
|
139
|
+
const isTypeSupported = MediaSource.isTypeSupported;
|
|
140
|
+
|
|
141
|
+
MediaSource.isTypeSupported = (mimeType) => {
|
|
142
|
+
const actualContainer = shaka.util.MimeUtils.getContainerType(mimeType);
|
|
143
|
+
return actualContainer != container && isTypeSupported(mimeType);
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
|
|
125
147
|
/**
|
|
126
148
|
* Patch |MediaSource.isTypeSupported| to always reject |codec|. This is used
|
|
127
149
|
* when we know that we are on a platform that does not work well with a given
|
|
@@ -14,6 +14,7 @@ goog.provide('shaka.text.SimpleTextDisplayer');
|
|
|
14
14
|
goog.require('goog.asserts');
|
|
15
15
|
goog.require('shaka.log');
|
|
16
16
|
goog.require('shaka.text.Cue');
|
|
17
|
+
goog.require('shaka.text.Utils');
|
|
17
18
|
|
|
18
19
|
|
|
19
20
|
/**
|
|
@@ -79,70 +80,7 @@ shaka.text.SimpleTextDisplayer = class {
|
|
|
79
80
|
* @export
|
|
80
81
|
*/
|
|
81
82
|
append(cues) {
|
|
82
|
-
|
|
83
|
-
// their contents should be combined and replace the payload of the parent.
|
|
84
|
-
const flattenPayload = (cue) => {
|
|
85
|
-
// Handle styles (currently bold/italics/underline).
|
|
86
|
-
// TODO add support for color rendering.
|
|
87
|
-
const openStyleTags = [];
|
|
88
|
-
const bold = cue.fontWeight >= shaka.text.Cue.fontWeight.BOLD;
|
|
89
|
-
const italics = cue.fontStyle == shaka.text.Cue.fontStyle.ITALIC;
|
|
90
|
-
const underline = cue.textDecoration.includes(
|
|
91
|
-
shaka.text.Cue.textDecoration.UNDERLINE);
|
|
92
|
-
if (bold) {
|
|
93
|
-
openStyleTags.push('b');
|
|
94
|
-
}
|
|
95
|
-
if (italics) {
|
|
96
|
-
openStyleTags.push('i');
|
|
97
|
-
}
|
|
98
|
-
if (underline) {
|
|
99
|
-
openStyleTags.push('u');
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
// Prefix opens tags, suffix closes tags in reverse order of opening.
|
|
103
|
-
const prefixStyleTags = openStyleTags.reduce((acc, tag) => {
|
|
104
|
-
return `${acc}<${tag}>`;
|
|
105
|
-
}, '');
|
|
106
|
-
const suffixStyleTags = openStyleTags.reduceRight((acc, tag) => {
|
|
107
|
-
return `${acc}</${tag}>`;
|
|
108
|
-
}, '');
|
|
109
|
-
|
|
110
|
-
if (cue.lineBreak) {
|
|
111
|
-
// This is a vertical lineBreak, so insert a newline.
|
|
112
|
-
return '\n';
|
|
113
|
-
} else if (cue.nestedCues.length) {
|
|
114
|
-
return cue.nestedCues.map(flattenPayload).join('');
|
|
115
|
-
} else {
|
|
116
|
-
// This is a real cue.
|
|
117
|
-
return prefixStyleTags + cue.payload + suffixStyleTags;
|
|
118
|
-
}
|
|
119
|
-
};
|
|
120
|
-
|
|
121
|
-
// We don't want to modify the array or objects passed in, since we don't
|
|
122
|
-
// technically own them. So we build a new array and replace certain items
|
|
123
|
-
// in it if they need to be flattened.
|
|
124
|
-
// We also don't want to flatten the text payloads starting at a container
|
|
125
|
-
// element; otherwise, for containers encapsulating multiple caption lines,
|
|
126
|
-
// the lines would merge into a single cue. This is undesirable when a
|
|
127
|
-
// subset of the captions are outside of the append time window. To fix
|
|
128
|
-
// this, we only call flattenPayload() starting at elements marked as
|
|
129
|
-
// isContainer = false.
|
|
130
|
-
const getCuesToFlatten = (cues, result) => {
|
|
131
|
-
for (const cue of cues) {
|
|
132
|
-
if (cue.isContainer) {
|
|
133
|
-
// Recurse to find the actual text payload cues.
|
|
134
|
-
getCuesToFlatten(cue.nestedCues, result);
|
|
135
|
-
} else {
|
|
136
|
-
// Flatten the payload.
|
|
137
|
-
const flatCue = cue.clone();
|
|
138
|
-
flatCue.nestedCues = [];
|
|
139
|
-
flatCue.payload = flattenPayload(cue);
|
|
140
|
-
result.push(flatCue);
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
return result;
|
|
144
|
-
};
|
|
145
|
-
const flattenedCues = getCuesToFlatten(cues, []);
|
|
83
|
+
const flattenedCues = shaka.text.Utils.getCuesToFlatten(cues, []);
|
|
146
84
|
|
|
147
85
|
// Convert cues.
|
|
148
86
|
const textTrackCues = [];
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/*! @license
|
|
2
|
+
* Shaka Player
|
|
3
|
+
* Copyright 2016 Google LLC
|
|
4
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
goog.provide('shaka.text.Utils');
|
|
8
|
+
|
|
9
|
+
goog.require('shaka.text.Cue');
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
shaka.text.Utils = class {
|
|
13
|
+
/**
|
|
14
|
+
* Flatten nested cue payloads recursively. If a cue has nested cues,
|
|
15
|
+
* their contents should be combined and replace the payload of the parent.
|
|
16
|
+
*
|
|
17
|
+
* @param {!shaka.text.Cue} cue
|
|
18
|
+
* @return {string}
|
|
19
|
+
* @private
|
|
20
|
+
*/
|
|
21
|
+
static flattenPayload_(cue) {
|
|
22
|
+
// Handle styles (currently bold/italics/underline).
|
|
23
|
+
// TODO: add support for color rendering.
|
|
24
|
+
const openStyleTags = [];
|
|
25
|
+
const bold = cue.fontWeight >= shaka.text.Cue.fontWeight.BOLD;
|
|
26
|
+
const italics = cue.fontStyle == shaka.text.Cue.fontStyle.ITALIC;
|
|
27
|
+
const underline = cue.textDecoration.includes(
|
|
28
|
+
shaka.text.Cue.textDecoration.UNDERLINE);
|
|
29
|
+
if (bold) {
|
|
30
|
+
openStyleTags.push('b');
|
|
31
|
+
}
|
|
32
|
+
if (italics) {
|
|
33
|
+
openStyleTags.push('i');
|
|
34
|
+
}
|
|
35
|
+
if (underline) {
|
|
36
|
+
openStyleTags.push('u');
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Prefix opens tags, suffix closes tags in reverse order of opening.
|
|
40
|
+
const prefixStyleTags = openStyleTags.reduce((acc, tag) => {
|
|
41
|
+
return `${acc}<${tag}>`;
|
|
42
|
+
}, '');
|
|
43
|
+
const suffixStyleTags = openStyleTags.reduceRight((acc, tag) => {
|
|
44
|
+
return `${acc}</${tag}>`;
|
|
45
|
+
}, '');
|
|
46
|
+
|
|
47
|
+
if (cue.lineBreak) {
|
|
48
|
+
// This is a vertical lineBreak, so insert a newline.
|
|
49
|
+
return '\n';
|
|
50
|
+
} else if (cue.nestedCues.length) {
|
|
51
|
+
return cue.nestedCues.map(shaka.text.Utils.flattenPayload_).join('');
|
|
52
|
+
} else {
|
|
53
|
+
// This is a real cue.
|
|
54
|
+
return prefixStyleTags + cue.payload + suffixStyleTags;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* We don't want to modify the array or objects passed in, since we don't
|
|
60
|
+
* technically own them. So we build a new array and replace certain items
|
|
61
|
+
* in it if they need to be flattened.
|
|
62
|
+
* We also don't want to flatten the text payloads starting at a container
|
|
63
|
+
* element; otherwise, for containers encapsulating multiple caption lines,
|
|
64
|
+
* the lines would merge into a single cue. This is undesirable when a
|
|
65
|
+
* subset of the captions are outside of the append time window. To fix
|
|
66
|
+
* this, we only call flattenPayload() starting at elements marked as
|
|
67
|
+
* isContainer = false.
|
|
68
|
+
*
|
|
69
|
+
* @param {!Array.<!shaka.text.Cue>} cues
|
|
70
|
+
* @param {!Array.<!shaka.text.Cue>} result
|
|
71
|
+
* @return {!Array.<!shaka.text.Cue>}
|
|
72
|
+
*/
|
|
73
|
+
static getCuesToFlatten(cues, result) {
|
|
74
|
+
for (const cue of cues) {
|
|
75
|
+
if (cue.isContainer) {
|
|
76
|
+
// Recurse to find the actual text payload cues.
|
|
77
|
+
shaka.text.Utils.getCuesToFlatten(cue.nestedCues, result);
|
|
78
|
+
} else {
|
|
79
|
+
// Flatten the payload.
|
|
80
|
+
const flatCue = cue.clone();
|
|
81
|
+
flatCue.nestedCues = [];
|
|
82
|
+
flatCue.payload = shaka.text.Utils.flattenPayload_(cue);
|
|
83
|
+
result.push(flatCue);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return result;
|
|
87
|
+
}
|
|
88
|
+
};
|