shaka-player 3.3.17 → 3.3.18
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 +55 -16
- package/.github/workflows/update-screenshots.yaml +142 -0
- package/CHANGELOG.md +22 -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 +146 -144
- package/dist/demo.compiled.debug.map +3 -3
- package/dist/demo.compiled.js +146 -144
- package/dist/demo.compiled.map +3 -3
- package/dist/deps.js +1 -1
- package/dist/locales.js +31 -31
- package/dist/receiver.compiled.debug.js +15 -15
- package/dist/receiver.compiled.debug.map +2 -2
- package/dist/receiver.compiled.js +15 -15
- 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 +31 -31
- package/dist/shaka-player.compiled.debug.map +2 -2
- package/dist/shaka-player.compiled.js +92 -91
- package/dist/shaka-player.compiled.map +3 -3
- 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 +33 -33
- package/dist/shaka-player.ui.debug.map +2 -2
- package/dist/shaka-player.ui.js +60 -59
- package/dist/shaka-player.ui.map +3 -3
- package/docs/tutorials/errors.md +94 -0
- 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/hls/hls_parser.js +2 -0
- 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 +2 -8
- package/lib/polyfill/mediasource.js +7 -2
- package/lib/text/ui_text_displayer.js +1 -0
- package/lib/text/vtt_text_parser.js +4 -5
- package/lib/util/mime_utils.js +1 -1
- package/lib/util/stream_utils.js +26 -14
- package/package.json +1 -1
- package/test/polyfill/media_capabilities_unit.js +20 -22
- package/test/text/vtt_text_parser_unit.js +26 -2
- package/test/text/web_vtt_layout_integration.js +0 -3
|
@@ -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
|
+
```
|
|
@@ -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
|
@@ -501,6 +501,15 @@ google.ima.dai.api.StreamRequest.prototype.streamActivityMonitorId;
|
|
|
501
501
|
google.ima.dai.api.StreamRequest.prototype.format;
|
|
502
502
|
|
|
503
503
|
|
|
504
|
+
/**
|
|
505
|
+
* @enum {string}
|
|
506
|
+
*/
|
|
507
|
+
google.ima.dai.api.StreamRequest.StreamFormat = {
|
|
508
|
+
DASH: 'dash',
|
|
509
|
+
HLS: 'hls',
|
|
510
|
+
};
|
|
511
|
+
|
|
512
|
+
|
|
504
513
|
/** @const */
|
|
505
514
|
google.ima.dai.api.VODStreamRequest =
|
|
506
515
|
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
|
/**
|
|
@@ -402,6 +402,7 @@ shaka.ads.ClientSideAdManager = class {
|
|
|
402
402
|
if (this.ad_.isLinear()) {
|
|
403
403
|
this.adContainer_.setAttribute('ad-active', 'true');
|
|
404
404
|
this.video_.pause();
|
|
405
|
+
this.ad_.setVolume(this.video_.muted ? 0 : this.video_.volume);
|
|
405
406
|
}
|
|
406
407
|
}
|
|
407
408
|
|
|
@@ -414,7 +415,9 @@ shaka.ads.ClientSideAdManager = class {
|
|
|
414
415
|
(new Map()).set('originalEvent', e)));
|
|
415
416
|
if (this.ad_ && this.ad_.isLinear()) {
|
|
416
417
|
this.adContainer_.removeAttribute('ad-active');
|
|
417
|
-
this.video_.
|
|
418
|
+
if (!this.video_.ended) {
|
|
419
|
+
this.video_.play();
|
|
420
|
+
}
|
|
418
421
|
}
|
|
419
422
|
}
|
|
420
423
|
};
|
package/lib/hls/hls_parser.js
CHANGED
|
@@ -2990,6 +2990,7 @@ shaka.hls.HlsParser.AUDIO_EXTENSIONS_TO_MIME_TYPES_ = {
|
|
|
2990
2990
|
'cmfa': 'audio/mp4',
|
|
2991
2991
|
// MPEG2-TS also uses video/ for audio: https://bit.ly/TsMse
|
|
2992
2992
|
'ts': 'video/mp2t',
|
|
2993
|
+
'tsa': 'video/mp2t',
|
|
2993
2994
|
|
|
2994
2995
|
// Raw formats:
|
|
2995
2996
|
'aac': 'audio/aac',
|
|
@@ -3027,6 +3028,7 @@ shaka.hls.HlsParser.VIDEO_EXTENSIONS_TO_MIME_TYPES_ = {
|
|
|
3027
3028
|
'm4f': 'video/mp4',
|
|
3028
3029
|
'cmfv': 'video/mp4',
|
|
3029
3030
|
'ts': 'video/mp2t',
|
|
3031
|
+
'tsv': 'video/mp2t',
|
|
3030
3032
|
};
|
|
3031
3033
|
|
|
3032
3034
|
|
package/lib/media/transmuxer.js
CHANGED
|
@@ -29,9 +29,7 @@ shaka.media.Transmuxer = class {
|
|
|
29
29
|
this.muxjs_ = shaka.dependencies.muxjs();
|
|
30
30
|
|
|
31
31
|
/** @private {muxjs.mp4.Transmuxer} */
|
|
32
|
-
this.muxTransmuxer_ =
|
|
33
|
-
'keepOriginalTimestamps': true,
|
|
34
|
-
});
|
|
32
|
+
this.muxTransmuxer_ = null;
|
|
35
33
|
|
|
36
34
|
/** @private {shaka.util.PublicPromise} */
|
|
37
35
|
this.transmuxPromise_ = null;
|
|
@@ -48,16 +46,22 @@ shaka.media.Transmuxer = class {
|
|
|
48
46
|
/** @private {boolean} */
|
|
49
47
|
this.isTransmuxing_ = false;
|
|
50
48
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
49
|
+
if (this.muxjs_) {
|
|
50
|
+
this.muxTransmuxer_ = new this.muxjs_.mp4.Transmuxer({
|
|
51
|
+
'keepOriginalTimestamps': true,
|
|
52
|
+
});
|
|
53
|
+
this.muxTransmuxer_.on('data', (segment) => this.onTransmuxed_(segment));
|
|
54
|
+
this.muxTransmuxer_.on('done', () => this.onTransmuxDone_());
|
|
55
|
+
}
|
|
54
56
|
}
|
|
55
57
|
|
|
56
58
|
/**
|
|
57
59
|
* @override
|
|
58
60
|
*/
|
|
59
61
|
destroy() {
|
|
60
|
-
this.muxTransmuxer_
|
|
62
|
+
if (this.muxTransmuxer_) {
|
|
63
|
+
this.muxTransmuxer_.dispose();
|
|
64
|
+
}
|
|
61
65
|
this.muxTransmuxer_ = null;
|
|
62
66
|
return Promise.resolve();
|
|
63
67
|
}
|
|
@@ -156,6 +160,8 @@ shaka.media.Transmuxer = class {
|
|
|
156
160
|
* metadata: !Array.<!Object>}>}
|
|
157
161
|
*/
|
|
158
162
|
transmux(data) {
|
|
163
|
+
goog.asserts.assert(this.muxTransmuxer_,
|
|
164
|
+
'mux.js should be available.');
|
|
159
165
|
goog.asserts.assert(!this.isTransmuxing_,
|
|
160
166
|
'No transmuxing should be in progress.');
|
|
161
167
|
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
|
@@ -2408,6 +2408,8 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
2408
2408
|
// goes wrong.
|
|
2409
2409
|
new shaka.util.Timer(resolve).tickAfter(1);
|
|
2410
2410
|
});
|
|
2411
|
+
} else if (textTracks.length > 0) {
|
|
2412
|
+
this.isTextVisible_ = true;
|
|
2411
2413
|
}
|
|
2412
2414
|
|
|
2413
2415
|
// If we have moved on to another piece of content while waiting for
|
|
@@ -6663,7 +6665,7 @@ shaka.Player.TYPICAL_BUFFERING_THRESHOLD_ = 0.5;
|
|
|
6663
6665
|
* @define {string} A version number taken from git at compile time.
|
|
6664
6666
|
* @export
|
|
6665
6667
|
*/
|
|
6666
|
-
shaka.Player.version = 'v3.3.
|
|
6668
|
+
shaka.Player.version = 'v3.3.18-uncompiled';
|
|
6667
6669
|
|
|
6668
6670
|
// Initialize the deprecation system using the version string we just set
|
|
6669
6671
|
// on the player.
|
|
@@ -8,7 +8,6 @@ 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');
|
|
12
11
|
goog.require('shaka.util.Platform');
|
|
13
12
|
|
|
14
13
|
|
|
@@ -212,13 +211,8 @@ shaka.polyfill.MediaCapabilities = class {
|
|
|
212
211
|
* @private
|
|
213
212
|
*/
|
|
214
213
|
static canCastDisplayType_(videoConfig) {
|
|
215
|
-
if (!(window.cast
|
|
216
|
-
|
|
217
|
-
throw new shaka.util.Error(
|
|
218
|
-
shaka.util.Error.Severity.CRITICAL,
|
|
219
|
-
shaka.util.Error.Category.CAST,
|
|
220
|
-
shaka.util.Error.Code.CAST_API_UNAVAILABLE);
|
|
221
|
-
} else if (!(cast.__platform__ && cast.__platform__.canDisplayType)) {
|
|
214
|
+
if (!(window.cast &&
|
|
215
|
+
cast.__platform__ && cast.__platform__.canDisplayType)) {
|
|
222
216
|
shaka.log.warning('Expected cast APIs to be available! Falling back to ' +
|
|
223
217
|
'MediaSource.isTypeSupported() for type support.');
|
|
224
218
|
return MediaSource.isTypeSupported(videoConfig.contentType);
|
|
@@ -58,14 +58,19 @@ shaka.polyfill.MediaSource = class {
|
|
|
58
58
|
// Offsetting the end of the removal range seems to help.
|
|
59
59
|
// Bug filed: https://bugs.webkit.org/show_bug.cgi?id=177884
|
|
60
60
|
shaka.polyfill.MediaSource.patchRemovalRange_();
|
|
61
|
-
} else {
|
|
62
|
-
shaka.log.info('Patching Safari 13 MSE bugs.');
|
|
61
|
+
} else if (safariVersion <= 15) {
|
|
62
|
+
shaka.log.info('Patching Safari 13 & 14 & 15 MSE bugs.');
|
|
63
63
|
// Safari 13 does not correctly implement abort() on SourceBuffer.
|
|
64
64
|
// Calling abort() before appending a segment causes that segment to be
|
|
65
65
|
// incomplete in the buffer.
|
|
66
66
|
// Bug filed: https://bugs.webkit.org/show_bug.cgi?id=165342
|
|
67
67
|
shaka.polyfill.MediaSource.stubAbort_();
|
|
68
68
|
}
|
|
69
|
+
} else if (shaka.util.Platform.isEdge()) {
|
|
70
|
+
shaka.log.info('Rejecting TS container.');
|
|
71
|
+
// TS content is broken on Edge in general.
|
|
72
|
+
// See https://github.com/shaka-project/shaka-player/issues/4955
|
|
73
|
+
shaka.polyfill.MediaSource.rejectTsContent_();
|
|
69
74
|
} else if (shaka.util.Platform.isTizen2() ||
|
|
70
75
|
shaka.util.Platform.isTizen3() ||
|
|
71
76
|
shaka.util.Platform.isTizen4()) {
|
|
@@ -480,6 +480,7 @@ shaka.text.UITextDisplayer = class {
|
|
|
480
480
|
wrapper = shaka.util.Dom.createHTMLElement('span');
|
|
481
481
|
wrapper.classList.add('shaka-text-wrapper');
|
|
482
482
|
wrapper.style.backgroundColor = cue.backgroundColor;
|
|
483
|
+
wrapper.style.lineHeight = 'normal';
|
|
483
484
|
cueElement.appendChild(wrapper);
|
|
484
485
|
}
|
|
485
486
|
|
|
@@ -369,8 +369,7 @@ shaka.text.VttTextParser = class {
|
|
|
369
369
|
end += timeOffset;
|
|
370
370
|
|
|
371
371
|
// Get the payload.
|
|
372
|
-
const payload =
|
|
373
|
-
text.slice(1).join('\n').trim());
|
|
372
|
+
const payload = text.slice(1).join('\n').trim();
|
|
374
373
|
|
|
375
374
|
let cue = null;
|
|
376
375
|
if (styles.has('global')) {
|
|
@@ -427,7 +426,7 @@ shaka.text.VttTextParser = class {
|
|
|
427
426
|
const childNode = childNodes[0];
|
|
428
427
|
if (childNode.nodeType == Node.TEXT_NODE ||
|
|
429
428
|
childNode.nodeType == Node.CDATA_SECTION_NODE) {
|
|
430
|
-
rootCue.payload = payload;
|
|
429
|
+
rootCue.payload = VttTextParser.htmlUnescape_(payload);
|
|
431
430
|
return;
|
|
432
431
|
}
|
|
433
432
|
}
|
|
@@ -438,7 +437,7 @@ shaka.text.VttTextParser = class {
|
|
|
438
437
|
rootCue.nestedCues = cues;
|
|
439
438
|
} else {
|
|
440
439
|
shaka.log.warning('The cue\'s markup could not be parsed: ', payload);
|
|
441
|
-
rootCue.payload = payload;
|
|
440
|
+
rootCue.payload = VttTextParser.htmlUnescape_(payload);
|
|
442
441
|
}
|
|
443
442
|
}
|
|
444
443
|
|
|
@@ -682,7 +681,7 @@ shaka.text.VttTextParser = class {
|
|
|
682
681
|
}
|
|
683
682
|
if (text.length > 0) {
|
|
684
683
|
const textCue = nestedCue.clone();
|
|
685
|
-
textCue.payload = text;
|
|
684
|
+
textCue.payload = VttTextParser.htmlUnescape_(text);
|
|
686
685
|
cues.push(textCue);
|
|
687
686
|
}
|
|
688
687
|
isFirst = false;
|
package/lib/util/mime_utils.js
CHANGED
|
@@ -50,7 +50,7 @@ shaka.util.MimeUtils = class {
|
|
|
50
50
|
return shaka.media.Transmuxer.convertTsCodecs(
|
|
51
51
|
contentType, fullMimeType);
|
|
52
52
|
}
|
|
53
|
-
} else if (contentType == ContentType.AUDIO) {
|
|
53
|
+
} else if (mimeType != 'video/mp2t' && contentType == ContentType.AUDIO) {
|
|
54
54
|
// video/mp2t is the correct mime type for TS audio, so only replace the
|
|
55
55
|
// word "video" with "audio" for non-TS audio content.
|
|
56
56
|
return fullMimeType.replace('video', 'audio');
|
package/lib/util/stream_utils.js
CHANGED
|
@@ -703,26 +703,38 @@ shaka.util.StreamUtils = class {
|
|
|
703
703
|
}
|
|
704
704
|
|
|
705
705
|
if (audio) {
|
|
706
|
-
|
|
706
|
+
// See: https://github.com/shaka-project/shaka-player/issues/4659
|
|
707
|
+
if (info.audioRobustness != '') {
|
|
708
|
+
if (!keySystemConfig.audio) {
|
|
709
|
+
// KeySystemTrackConfiguration
|
|
710
|
+
keySystemConfig.audio = {
|
|
711
|
+
robustness: info.audioRobustness,
|
|
712
|
+
};
|
|
713
|
+
} else {
|
|
714
|
+
keySystemConfig.audio.robustness =
|
|
715
|
+
keySystemConfig.audio.robustness || info.audioRobustness;
|
|
716
|
+
}
|
|
717
|
+
} else if (!keySystemConfig.audio) {
|
|
707
718
|
// KeySystemTrackConfiguration
|
|
708
|
-
keySystemConfig.audio = {
|
|
709
|
-
robustness: info.audioRobustness,
|
|
710
|
-
};
|
|
711
|
-
} else {
|
|
712
|
-
keySystemConfig.audio.robustness =
|
|
713
|
-
keySystemConfig.audio.robustness || info.audioRobustness;
|
|
719
|
+
keySystemConfig.audio = {};
|
|
714
720
|
}
|
|
715
721
|
}
|
|
716
722
|
|
|
717
723
|
if (video) {
|
|
718
|
-
|
|
724
|
+
// See: https://github.com/shaka-project/shaka-player/issues/4659
|
|
725
|
+
if (info.videoRobustness != '') {
|
|
726
|
+
if (!keySystemConfig.video) {
|
|
727
|
+
// KeySystemTrackConfiguration
|
|
728
|
+
keySystemConfig.video = {
|
|
729
|
+
robustness: info.videoRobustness,
|
|
730
|
+
};
|
|
731
|
+
} else {
|
|
732
|
+
keySystemConfig.video.robustness =
|
|
733
|
+
keySystemConfig.video.robustness || info.videoRobustness;
|
|
734
|
+
}
|
|
735
|
+
} else if (!keySystemConfig.video) {
|
|
719
736
|
// KeySystemTrackConfiguration
|
|
720
|
-
keySystemConfig.video = {
|
|
721
|
-
robustness: info.videoRobustness,
|
|
722
|
-
};
|
|
723
|
-
} else {
|
|
724
|
-
keySystemConfig.video.robustness =
|
|
725
|
-
keySystemConfig.video.robustness || info.videoRobustness;
|
|
737
|
+
keySystemConfig.video = {};
|
|
726
738
|
}
|
|
727
739
|
}
|
|
728
740
|
}
|
package/package.json
CHANGED
|
@@ -5,7 +5,6 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
describe('MediaCapabilities', () => {
|
|
8
|
-
const Util = shaka.test.Util;
|
|
9
8
|
const originalCast = window['cast'];
|
|
10
9
|
const originalVendor = navigator.vendor;
|
|
11
10
|
const originalUserAgent = navigator.userAgent;
|
|
@@ -168,30 +167,29 @@ describe('MediaCapabilities', () => {
|
|
|
168
167
|
expect(result.keySystemAccess).toEqual(mockResult);
|
|
169
168
|
});
|
|
170
169
|
|
|
171
|
-
it('
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
170
|
+
it('falls back to isTypeSupported() when cast namespace is not available',
|
|
171
|
+
async () => {
|
|
172
|
+
// Temporarily remove window.cast to trigger error. It's restored
|
|
173
|
+
// after every test.
|
|
174
|
+
delete window['cast'];
|
|
175
175
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
shaka.util.Error.Code.CAST_API_UNAVAILABLE));
|
|
183
|
-
const isTypeSupportedSpy =
|
|
184
|
-
spyOn(window['MediaSource'], 'isTypeSupported').and.returnValue(true);
|
|
176
|
+
const isChromecastSpy =
|
|
177
|
+
spyOn(shaka['util']['Platform'],
|
|
178
|
+
'isChromecast').and.returnValue(true);
|
|
179
|
+
const isTypeSupportedSpy =
|
|
180
|
+
spyOn(window['MediaSource'], 'isTypeSupported')
|
|
181
|
+
.and.returnValue(true);
|
|
185
182
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
navigator.mediaCapabilities.decodingInfo(mockDecodingConfig))
|
|
189
|
-
.toBeRejectedWith(expected);
|
|
183
|
+
shaka.polyfill.MediaCapabilities.install();
|
|
184
|
+
await navigator.mediaCapabilities.decodingInfo(mockDecodingConfig);
|
|
190
185
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
186
|
+
expect(mockCanDisplayType).not.toHaveBeenCalled();
|
|
187
|
+
// 1 (during install()) + 1 (for video config check).
|
|
188
|
+
expect(isChromecastSpy).toHaveBeenCalledTimes(2);
|
|
189
|
+
// 1 (fallback in canCastDisplayType()) +
|
|
190
|
+
// 1 (mockDecodingConfig.audio).
|
|
191
|
+
expect(isTypeSupportedSpy).toHaveBeenCalledTimes(2);
|
|
192
|
+
});
|
|
195
193
|
|
|
196
194
|
it('falls back to isTypeSupported() when canDisplayType() missing',
|
|
197
195
|
async () => {
|
|
@@ -776,11 +776,35 @@ describe('VttTextParser', () => {
|
|
|
776
776
|
it('support escaped html payload', () => {
|
|
777
777
|
verifyHelper(
|
|
778
778
|
[
|
|
779
|
-
{
|
|
779
|
+
{
|
|
780
|
+
startTime: 20.1,
|
|
781
|
+
endTime: 40.505,
|
|
782
|
+
payload: '"Test & 1"\u{a0}',
|
|
783
|
+
},
|
|
784
|
+
{
|
|
785
|
+
startTime: 41,
|
|
786
|
+
endTime: 42,
|
|
787
|
+
payload: '',
|
|
788
|
+
nestedCues: [
|
|
789
|
+
{
|
|
790
|
+
startTime: 41,
|
|
791
|
+
endTime: 42,
|
|
792
|
+
payload: 'Test',
|
|
793
|
+
fontStyle: Cue.fontStyle.ITALIC,
|
|
794
|
+
},
|
|
795
|
+
{
|
|
796
|
+
startTime: 41,
|
|
797
|
+
endTime: 42,
|
|
798
|
+
payload: '&',
|
|
799
|
+
},
|
|
800
|
+
],
|
|
801
|
+
},
|
|
780
802
|
],
|
|
781
803
|
'WEBVTT\n\n' +
|
|
782
804
|
'00:00:20.100 --> 00:00:40.505\n' +
|
|
783
|
-
'"Test & 1" 
|
|
805
|
+
'"Test & 1" \n\n' +
|
|
806
|
+
'00:00:41.000 --> 00:00:42.000\n' +
|
|
807
|
+
'<i>Test</i>&',
|
|
784
808
|
{periodStart: 0, segmentStart: 0, segmentEnd: 0, vttOffset: 0});
|
|
785
809
|
});
|
|
786
810
|
|
|
@@ -265,7 +265,6 @@ filterDescribe('WebVTT layout', shaka.test.TextLayoutTests.supported, () => {
|
|
|
265
265
|
await helper.checkScreenshot('line-85-position-50-size-63');
|
|
266
266
|
});
|
|
267
267
|
|
|
268
|
-
// FIXME: UI version is slightly wrong: gaps between lines when bold
|
|
269
268
|
it('bold long', async () => {
|
|
270
269
|
parseAndDisplay([
|
|
271
270
|
'WEBVTT\n',
|
|
@@ -278,7 +277,6 @@ filterDescribe('WebVTT layout', shaka.test.TextLayoutTests.supported, () => {
|
|
|
278
277
|
await helper.checkScreenshot('bold-long');
|
|
279
278
|
});
|
|
280
279
|
|
|
281
|
-
// FIXME: UI version is slightly wrong: gaps between lines when italic
|
|
282
280
|
it('italic long', async () => {
|
|
283
281
|
parseAndDisplay([
|
|
284
282
|
'WEBVTT\n',
|
|
@@ -291,7 +289,6 @@ filterDescribe('WebVTT layout', shaka.test.TextLayoutTests.supported, () => {
|
|
|
291
289
|
await helper.checkScreenshot('italic-long');
|
|
292
290
|
});
|
|
293
291
|
|
|
294
|
-
// FIXME: UI version is slightly wrong: gaps between lines when italic
|
|
295
292
|
it('underline long', async () => {
|
|
296
293
|
parseAndDisplay([
|
|
297
294
|
'WEBVTT\n',
|