shaka-player 4.2.2 → 4.2.3
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 +11 -6
- package/.github/workflows/compute-incremental-coverage.py +14 -3
- package/.github/workflows/demo-version-index.yaml +1 -1
- package/.github/workflows/nightly-demo.yaml +1 -1
- package/.github/workflows/release-please.yaml +5 -5
- package/.github/workflows/report-incremental-coverage.yaml +1 -1
- package/.github/workflows/selenium-lab-tests.yaml +8 -6
- package/AUTHORS +1 -0
- package/CHANGELOG.md +17 -0
- package/CONTRIBUTORS +1 -0
- package/dist/deps.js +3 -3
- package/dist/locales.js +32 -32
- package/dist/shaka-player.compiled.d.ts +1 -0
- package/dist/shaka-player.compiled.debug.d.ts +1 -0
- package/dist/shaka-player.compiled.debug.externs.js +1 -0
- package/dist/shaka-player.compiled.debug.js +38 -37
- package/dist/shaka-player.compiled.debug.map +4 -4
- package/dist/shaka-player.compiled.externs.js +1 -0
- package/dist/shaka-player.compiled.js +657 -657
- package/dist/shaka-player.compiled.map +3 -3
- package/dist/shaka-player.ui.d.ts +1 -0
- package/dist/shaka-player.ui.debug.d.ts +1 -0
- package/dist/shaka-player.ui.debug.externs.js +1 -0
- package/dist/shaka-player.ui.debug.js +40 -40
- package/dist/shaka-player.ui.debug.map +3 -3
- package/dist/shaka-player.ui.externs.js +1 -0
- package/dist/shaka-player.ui.js +772 -772
- package/dist/shaka-player.ui.map +3 -3
- package/docs/tutorials/welcome.md +2 -2
- package/lib/ads/client_side_ad_manager.js +5 -2
- package/lib/hls/hls_parser.js +18 -7
- package/lib/media/drm_engine.js +7 -6
- package/lib/media/playhead.js +1 -1
- package/lib/offline/indexeddb/storage_mechanism.js +24 -0
- package/lib/player.js +1 -1
- package/lib/polyfill/media_capabilities.js +7 -6
- package/lib/text/ttml_text_parser.js +69 -93
- package/lib/text/vtt_text_parser.js +1 -1
- package/lib/util/error.js +7 -0
- package/lib/util/periods.js +9 -9
- package/lib/util/platform.js +0 -10
- package/lib/util/xml_utils.js +45 -6
- package/package.json +1 -1
- package/test/offline/storage_integration.js +30 -0
- package/test/text/vtt_text_parser_unit.js +23 -1
- package/test/util/periods_unit.js +149 -11
- package/test/util/xml_utils_unit.js +64 -0
|
@@ -66,8 +66,8 @@ python build/all.py
|
|
|
66
66
|
|
|
67
67
|
Alternatively you can use a docker container:
|
|
68
68
|
```sh
|
|
69
|
-
|
|
70
|
-
docker
|
|
69
|
+
docker build -t shaka-player-build build/docker
|
|
70
|
+
docker run -v $(pwd):/usr/src --user $(id -u):$(id -g) shaka-player-build
|
|
71
71
|
```
|
|
72
72
|
|
|
73
73
|
The output is:
|
|
@@ -15,7 +15,6 @@ goog.require('goog.asserts');
|
|
|
15
15
|
goog.require('shaka.ads.ClientSideAd');
|
|
16
16
|
goog.require('shaka.util.IReleasable');
|
|
17
17
|
|
|
18
|
-
|
|
19
18
|
/**
|
|
20
19
|
* A class responsible for client-side ad interactions.
|
|
21
20
|
* @implements {shaka.util.IReleasable}
|
|
@@ -218,7 +217,11 @@ shaka.ads.ClientSideAdManager = class {
|
|
|
218
217
|
// TODO (ismena): Need a better inderstanding of what this does.
|
|
219
218
|
// The docs say it's called to 'start playing the ads,' but I haven't
|
|
220
219
|
// seen the ads actually play until requestAds() is called.
|
|
221
|
-
|
|
220
|
+
// Note: We listen for a play event to avoid autoplay issues that might
|
|
221
|
+
// crash IMA.
|
|
222
|
+
this.video_.addEventListener('play', () => {
|
|
223
|
+
this.imaAdsManager_.start();
|
|
224
|
+
}, {once: true});
|
|
222
225
|
} catch (adError) {
|
|
223
226
|
// If there was a problem with the VAST response,
|
|
224
227
|
// we we won't be getting an ad. Hide ad UI if we showed it already
|
package/lib/hls/hls_parser.js
CHANGED
|
@@ -2442,7 +2442,12 @@ shaka.hls.HlsParser = class {
|
|
|
2442
2442
|
const extension = parsedUri.getPath().split('.').pop();
|
|
2443
2443
|
const map = HlsParser.EXTENSION_MAP_BY_CONTENT_TYPE_[contentType];
|
|
2444
2444
|
|
|
2445
|
-
|
|
2445
|
+
let mimeType = map[extension];
|
|
2446
|
+
if (mimeType) {
|
|
2447
|
+
return mimeType;
|
|
2448
|
+
}
|
|
2449
|
+
|
|
2450
|
+
mimeType = HlsParser.RAW_FORMATS_TO_MIME_TYPES_[extension];
|
|
2446
2451
|
if (mimeType) {
|
|
2447
2452
|
return mimeType;
|
|
2448
2453
|
}
|
|
@@ -2811,6 +2816,18 @@ shaka.hls.HlsParser.StreamInfo;
|
|
|
2811
2816
|
shaka.hls.HlsParser.StreamInfos;
|
|
2812
2817
|
|
|
2813
2818
|
|
|
2819
|
+
/**
|
|
2820
|
+
* @const {!Object.<string, string>}
|
|
2821
|
+
* @private
|
|
2822
|
+
*/
|
|
2823
|
+
shaka.hls.HlsParser.RAW_FORMATS_TO_MIME_TYPES_ = {
|
|
2824
|
+
'aac': 'audio/aac',
|
|
2825
|
+
'ac3': 'audio/ac3',
|
|
2826
|
+
'ec3': 'audio/ec3',
|
|
2827
|
+
'mp3': 'audio/mpeg',
|
|
2828
|
+
};
|
|
2829
|
+
|
|
2830
|
+
|
|
2814
2831
|
/**
|
|
2815
2832
|
* @const {!Object.<string, string>}
|
|
2816
2833
|
* @private
|
|
@@ -2825,12 +2842,6 @@ shaka.hls.HlsParser.AUDIO_EXTENSIONS_TO_MIME_TYPES_ = {
|
|
|
2825
2842
|
'cmfa': 'audio/mp4',
|
|
2826
2843
|
// MPEG2-TS also uses video/ for audio: https://bit.ly/TsMse
|
|
2827
2844
|
'ts': 'video/mp2t',
|
|
2828
|
-
|
|
2829
|
-
// Raw formats:
|
|
2830
|
-
'aac': 'audio/aac',
|
|
2831
|
-
'ac3': 'audio/ac3',
|
|
2832
|
-
'ec3': 'audio/ec3',
|
|
2833
|
-
'mp3': 'audio/mpeg',
|
|
2834
2845
|
};
|
|
2835
2846
|
|
|
2836
2847
|
|
package/lib/media/drm_engine.js
CHANGED
|
@@ -28,6 +28,7 @@ goog.require('shaka.util.StreamUtils');
|
|
|
28
28
|
goog.require('shaka.util.StringUtils');
|
|
29
29
|
goog.require('shaka.util.Timer');
|
|
30
30
|
goog.require('shaka.util.Uint8ArrayUtils');
|
|
31
|
+
goog.require('shaka.util.XmlUtils');
|
|
31
32
|
|
|
32
33
|
|
|
33
34
|
/** @implements {shaka.util.IDestroyable} */
|
|
@@ -406,6 +407,7 @@ shaka.media.DrmEngine = class {
|
|
|
406
407
|
if (this.mediaKeysAttached_) {
|
|
407
408
|
await this.mediaKeysAttached_;
|
|
408
409
|
|
|
410
|
+
this.destroyer_.ensureNotDestroyed();
|
|
409
411
|
return;
|
|
410
412
|
}
|
|
411
413
|
|
|
@@ -483,14 +485,12 @@ shaka.media.DrmEngine = class {
|
|
|
483
485
|
|
|
484
486
|
/**
|
|
485
487
|
* We can attach media keys before the playback actually begins when:
|
|
486
|
-
* -
|
|
487
|
-
* having webkitneedkey / msneedkey event, which will be translated as
|
|
488
|
-
* an encrypted event by the polyfills
|
|
488
|
+
* - If we are not using FairPlay Modern EME
|
|
489
489
|
* - Some initData already has been generated (through the manifest)
|
|
490
490
|
* - In case of an offline session
|
|
491
491
|
*/
|
|
492
492
|
if (manifestInitData ||
|
|
493
|
-
|
|
493
|
+
this.currentDrmInfo_.keySystem !== 'com.apple.fps' ||
|
|
494
494
|
this.offlineSessionIds_.length) {
|
|
495
495
|
await this.attachMediaKeys_();
|
|
496
496
|
}
|
|
@@ -1506,7 +1506,8 @@ shaka.media.DrmEngine = class {
|
|
|
1506
1506
|
return;
|
|
1507
1507
|
}
|
|
1508
1508
|
shaka.log.debug('Unwrapping PlayReady request.');
|
|
1509
|
-
const dom =
|
|
1509
|
+
const dom = shaka.util.XmlUtils.parseXmlString(xml, 'PlayReadyKeyMessage');
|
|
1510
|
+
goog.asserts.assert(dom, 'Failed to parse PlayReady XML!');
|
|
1510
1511
|
|
|
1511
1512
|
// Set request headers.
|
|
1512
1513
|
const headers = dom.getElementsByTagName('HttpHeader');
|
|
@@ -1590,7 +1591,7 @@ shaka.media.DrmEngine = class {
|
|
|
1590
1591
|
hasExpiredKeys = true;
|
|
1591
1592
|
}
|
|
1592
1593
|
|
|
1593
|
-
const keyIdHex = shaka.util.Uint8ArrayUtils.toHex(keyId);
|
|
1594
|
+
const keyIdHex = shaka.util.Uint8ArrayUtils.toHex(keyId).slice(0, 32);
|
|
1594
1595
|
|
|
1595
1596
|
this.keyStatusByKeyId_.set(keyIdHex, status);
|
|
1596
1597
|
});
|
package/lib/media/playhead.js
CHANGED
|
@@ -294,7 +294,7 @@ shaka.media.MediaSourcePlayhead = class {
|
|
|
294
294
|
|
|
295
295
|
/** @override */
|
|
296
296
|
getStallsDetected() {
|
|
297
|
-
return this.stallDetector_.getStallsDetected();
|
|
297
|
+
return this.stallDetector_ ? this.stallDetector_.getStallsDetected() : 0;
|
|
298
298
|
}
|
|
299
299
|
|
|
300
300
|
/** @override */
|
|
@@ -16,6 +16,7 @@ goog.require('shaka.offline.indexeddb.V5StorageCell');
|
|
|
16
16
|
goog.require('shaka.util.Error');
|
|
17
17
|
goog.require('shaka.util.PublicPromise');
|
|
18
18
|
goog.require('shaka.util.Platform');
|
|
19
|
+
goog.require('shaka.util.Timer');
|
|
19
20
|
|
|
20
21
|
|
|
21
22
|
/**
|
|
@@ -55,8 +56,25 @@ shaka.offline.indexeddb.StorageMechanism = class {
|
|
|
55
56
|
const version = shaka.offline.indexeddb.StorageMechanism.VERSION;
|
|
56
57
|
|
|
57
58
|
const p = new shaka.util.PublicPromise();
|
|
59
|
+
|
|
60
|
+
// Add a timeout mechanism, for the (rare?) case where no callbacks are
|
|
61
|
+
// called at all, so that this method doesn't hang forever.
|
|
62
|
+
let timedOut = false;
|
|
63
|
+
const timeOutTimer = new shaka.util.Timer(() => {
|
|
64
|
+
timedOut = true;
|
|
65
|
+
p.reject(new shaka.util.Error(
|
|
66
|
+
shaka.util.Error.Severity.CRITICAL,
|
|
67
|
+
shaka.util.Error.Category.STORAGE,
|
|
68
|
+
shaka.util.Error.Code.INDEXED_DB_INIT_TIMED_OUT));
|
|
69
|
+
});
|
|
70
|
+
timeOutTimer.tickAfter(2);
|
|
71
|
+
|
|
58
72
|
const open = window.indexedDB.open(name, version);
|
|
59
73
|
open.onsuccess = (event) => {
|
|
74
|
+
if (timedOut) {
|
|
75
|
+
// Too late, we have already given up on opening the storage mechanism.
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
60
78
|
const db = open.result;
|
|
61
79
|
this.db_ = db;
|
|
62
80
|
this.v1_ = shaka.offline.indexeddb.StorageMechanism.createV1_(db);
|
|
@@ -68,6 +86,7 @@ shaka.offline.indexeddb.StorageMechanism = class {
|
|
|
68
86
|
this.v5_ = shaka.offline.indexeddb.StorageMechanism.createV5_(db);
|
|
69
87
|
this.sessions_ =
|
|
70
88
|
shaka.offline.indexeddb.StorageMechanism.createEmeSessionCell_(db);
|
|
89
|
+
timeOutTimer.stop();
|
|
71
90
|
p.resolve();
|
|
72
91
|
};
|
|
73
92
|
open.onupgradeneeded = (event) => {
|
|
@@ -75,11 +94,16 @@ shaka.offline.indexeddb.StorageMechanism = class {
|
|
|
75
94
|
this.createStores_(open.result);
|
|
76
95
|
};
|
|
77
96
|
open.onerror = (event) => {
|
|
97
|
+
if (timedOut) {
|
|
98
|
+
// Too late, we have already given up on opening the storage mechanism.
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
78
101
|
p.reject(new shaka.util.Error(
|
|
79
102
|
shaka.util.Error.Severity.CRITICAL,
|
|
80
103
|
shaka.util.Error.Category.STORAGE,
|
|
81
104
|
shaka.util.Error.Code.INDEXED_DB_ERROR,
|
|
82
105
|
open.error));
|
|
106
|
+
timeOutTimer.stop();
|
|
83
107
|
|
|
84
108
|
// Firefox will raise an error on the main thread unless we stop it here.
|
|
85
109
|
event.preventDefault();
|
package/lib/player.js
CHANGED
|
@@ -6582,7 +6582,7 @@ shaka.Player.TYPICAL_BUFFERING_THRESHOLD_ = 0.5;
|
|
|
6582
6582
|
* @define {string} A version number taken from git at compile time.
|
|
6583
6583
|
* @export
|
|
6584
6584
|
*/
|
|
6585
|
-
shaka.Player.version = 'v4.2.
|
|
6585
|
+
shaka.Player.version = 'v4.2.3-uncompiled';
|
|
6586
6586
|
|
|
6587
6587
|
// Initialize the deprecation system using the version string we just set
|
|
6588
6588
|
// on the player.
|
|
@@ -24,8 +24,9 @@ shaka.polyfill.MediaCapabilities = class {
|
|
|
24
24
|
* @export
|
|
25
25
|
*/
|
|
26
26
|
static install() {
|
|
27
|
-
// Since MediaCapabilities is
|
|
28
|
-
//
|
|
27
|
+
// Since MediaCapabilities implementation is buggy on the Chromecast
|
|
28
|
+
// platform (see https://github.com/shaka-project/shaka-player/issues/4569),
|
|
29
|
+
// we should always install polyfills on all Chromecast models.
|
|
29
30
|
// TODO: re-evaluate MediaCapabilities in the future versions of Chromecast.
|
|
30
31
|
// Since MediaCapabilities implementation is buggy in Apple browsers, we
|
|
31
32
|
// should always install polyfill for Apple browsers.
|
|
@@ -43,18 +44,18 @@ shaka.polyfill.MediaCapabilities = class {
|
|
|
43
44
|
// should always install polyfill for Tizen browsers.
|
|
44
45
|
// Since MediaCapabilities implementation is buggy in WebOS browsers, we
|
|
45
46
|
// should always install polyfill for WebOS browsers.
|
|
47
|
+
// Since MediaCapabilities implementation is buggy in EOS browsers, we
|
|
48
|
+
// should always install polyfill for EOS browsers.
|
|
46
49
|
let canUseNativeMCap = true;
|
|
47
50
|
if (shaka.util.Platform.isApple() ||
|
|
48
51
|
shaka.util.Platform.isPS5() ||
|
|
49
52
|
shaka.util.Platform.isPS4() ||
|
|
50
53
|
shaka.util.Platform.isWebOS() ||
|
|
51
54
|
shaka.util.Platform.isTizen() ||
|
|
52
|
-
shaka.util.Platform.isChromecast()
|
|
55
|
+
shaka.util.Platform.isChromecast() ||
|
|
56
|
+
shaka.util.Platform.isEOS()) {
|
|
53
57
|
canUseNativeMCap = false;
|
|
54
58
|
}
|
|
55
|
-
if (shaka.util.Platform.isAndroidCastDevice()) {
|
|
56
|
-
canUseNativeMCap = true;
|
|
57
|
-
}
|
|
58
59
|
if (canUseNativeMCap && navigator.mediaCapabilities) {
|
|
59
60
|
shaka.log.info(
|
|
60
61
|
'MediaCapabilities: Native mediaCapabilities support found.');
|
|
@@ -49,8 +49,6 @@ shaka.text.TtmlTextParser = class {
|
|
|
49
49
|
const ttsNs = TtmlTextParser.styleNs_;
|
|
50
50
|
const str = shaka.util.StringUtils.fromUTF8(data);
|
|
51
51
|
const cues = [];
|
|
52
|
-
const parser = new DOMParser();
|
|
53
|
-
let xml = null;
|
|
54
52
|
|
|
55
53
|
// dont try to parse empty string as
|
|
56
54
|
// DOMParser will not throw error but return an errored xml
|
|
@@ -58,9 +56,8 @@ shaka.text.TtmlTextParser = class {
|
|
|
58
56
|
return cues;
|
|
59
57
|
}
|
|
60
58
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
} catch (exception) {
|
|
59
|
+
const tt = XmlUtils.parseXmlString(str, 'tt');
|
|
60
|
+
if (!tt) {
|
|
64
61
|
throw new shaka.util.Error(
|
|
65
62
|
shaka.util.Error.Severity.CRITICAL,
|
|
66
63
|
shaka.util.Error.Category.TEXT,
|
|
@@ -68,110 +65,89 @@ shaka.text.TtmlTextParser = class {
|
|
|
68
65
|
'Failed to parse TTML.');
|
|
69
66
|
}
|
|
70
67
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
shaka.util.Error.Severity.CRITICAL,
|
|
76
|
-
shaka.util.Error.Category.TEXT,
|
|
77
|
-
shaka.util.Error.Code.INVALID_XML,
|
|
78
|
-
parserError.textContent);
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
const tt = xml.getElementsByTagName('tt')[0];
|
|
82
|
-
// TTML should always have tt element.
|
|
83
|
-
if (!tt) {
|
|
84
|
-
throw new shaka.util.Error(
|
|
85
|
-
shaka.util.Error.Severity.CRITICAL,
|
|
86
|
-
shaka.util.Error.Category.TEXT,
|
|
87
|
-
shaka.util.Error.Code.INVALID_XML,
|
|
88
|
-
'TTML does not contain <tt> tag.');
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
const body = tt.getElementsByTagName('body')[0];
|
|
92
|
-
if (!body) {
|
|
93
|
-
return [];
|
|
94
|
-
}
|
|
68
|
+
const body = tt.getElementsByTagName('body')[0];
|
|
69
|
+
if (!body) {
|
|
70
|
+
return [];
|
|
71
|
+
}
|
|
95
72
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
73
|
+
// Get the framerate, subFrameRate and frameRateMultiplier if applicable.
|
|
74
|
+
const frameRate = XmlUtils.getAttributeNSList(tt, ttpNs, 'frameRate');
|
|
75
|
+
const subFrameRate = XmlUtils.getAttributeNSList(
|
|
76
|
+
tt, ttpNs, 'subFrameRate');
|
|
77
|
+
const frameRateMultiplier =
|
|
78
|
+
XmlUtils.getAttributeNSList(tt, ttpNs, 'frameRateMultiplier');
|
|
79
|
+
const tickRate = XmlUtils.getAttributeNSList(tt, ttpNs, 'tickRate');
|
|
103
80
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
81
|
+
const cellResolution = XmlUtils.getAttributeNSList(
|
|
82
|
+
tt, ttpNs, 'cellResolution');
|
|
83
|
+
const spaceStyle = tt.getAttribute('xml:space') || 'default';
|
|
84
|
+
const extent = XmlUtils.getAttributeNSList(tt, ttsNs, 'extent');
|
|
108
85
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
86
|
+
if (spaceStyle != 'default' && spaceStyle != 'preserve') {
|
|
87
|
+
throw new shaka.util.Error(
|
|
88
|
+
shaka.util.Error.Severity.CRITICAL,
|
|
89
|
+
shaka.util.Error.Category.TEXT,
|
|
90
|
+
shaka.util.Error.Code.INVALID_XML,
|
|
91
|
+
'Invalid xml:space value: ' + spaceStyle);
|
|
92
|
+
}
|
|
93
|
+
const whitespaceTrim = spaceStyle == 'default';
|
|
117
94
|
|
|
118
|
-
|
|
119
|
-
|
|
95
|
+
const rateInfo = new TtmlTextParser.RateInfo_(
|
|
96
|
+
frameRate, subFrameRate, frameRateMultiplier, tickRate);
|
|
120
97
|
|
|
121
|
-
|
|
122
|
-
|
|
98
|
+
const cellResolutionInfo =
|
|
99
|
+
TtmlTextParser.getCellResolution_(cellResolution);
|
|
123
100
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
101
|
+
const metadata = tt.getElementsByTagName('metadata')[0];
|
|
102
|
+
const metadataElements = metadata ? XmlUtils.getChildren(metadata) : [];
|
|
103
|
+
const styles = Array.from(tt.getElementsByTagName('style'));
|
|
104
|
+
const regionElements = Array.from(tt.getElementsByTagName('region'));
|
|
128
105
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
}
|
|
106
|
+
const cueRegions = [];
|
|
107
|
+
for (const region of regionElements) {
|
|
108
|
+
const cueRegion =
|
|
109
|
+
TtmlTextParser.parseCueRegion_(region, styles, extent);
|
|
110
|
+
if (cueRegion) {
|
|
111
|
+
cueRegions.push(cueRegion);
|
|
136
112
|
}
|
|
113
|
+
}
|
|
137
114
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
115
|
+
// A <body> element should only contain <div> elements, not <p> or <span>
|
|
116
|
+
// elements. We used to allow this, but it is non-compliant, and the
|
|
117
|
+
// loose nature of our previous parser made it difficult to implement TTML
|
|
118
|
+
// nesting more fully.
|
|
119
|
+
if (XmlUtils.findChildren(body, 'p').length) {
|
|
120
|
+
throw new shaka.util.Error(
|
|
121
|
+
shaka.util.Error.Severity.CRITICAL,
|
|
122
|
+
shaka.util.Error.Category.TEXT,
|
|
123
|
+
shaka.util.Error.Code.INVALID_TEXT_CUE,
|
|
124
|
+
'<p> can only be inside <div> in TTML');
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
for (const div of XmlUtils.findChildren(body, 'div')) {
|
|
128
|
+
// A <div> element should only contain <p>, not <span>.
|
|
129
|
+
if (XmlUtils.findChildren(div, 'span').length) {
|
|
143
130
|
throw new shaka.util.Error(
|
|
144
131
|
shaka.util.Error.Severity.CRITICAL,
|
|
145
132
|
shaka.util.Error.Category.TEXT,
|
|
146
133
|
shaka.util.Error.Code.INVALID_TEXT_CUE,
|
|
147
|
-
'<
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
for (const div of XmlUtils.findChildren(body, 'div')) {
|
|
151
|
-
// A <div> element should only contain <p>, not <span>.
|
|
152
|
-
if (XmlUtils.findChildren(div, 'span').length) {
|
|
153
|
-
throw new shaka.util.Error(
|
|
154
|
-
shaka.util.Error.Severity.CRITICAL,
|
|
155
|
-
shaka.util.Error.Category.TEXT,
|
|
156
|
-
shaka.util.Error.Code.INVALID_TEXT_CUE,
|
|
157
|
-
'<span> can only be inside <p> in TTML');
|
|
158
|
-
}
|
|
134
|
+
'<span> can only be inside <p> in TTML');
|
|
159
135
|
}
|
|
136
|
+
}
|
|
160
137
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
}
|
|
173
|
-
cues.push(cue);
|
|
138
|
+
const cue = TtmlTextParser.parseCue_(
|
|
139
|
+
body, time.periodStart, rateInfo, metadataElements, styles,
|
|
140
|
+
regionElements, cueRegions, whitespaceTrim,
|
|
141
|
+
cellResolutionInfo, /* parentCueElement= */ null,
|
|
142
|
+
/* isContent= */ false);
|
|
143
|
+
if (cue) {
|
|
144
|
+
// According to the TTML spec, backgrounds default to transparent.
|
|
145
|
+
// So default the background of the top-level element to transparent.
|
|
146
|
+
// Nested elements may override that background color already.
|
|
147
|
+
if (!cue.backgroundColor) {
|
|
148
|
+
cue.backgroundColor = 'transparent';
|
|
174
149
|
}
|
|
150
|
+
cues.push(cue);
|
|
175
151
|
}
|
|
176
152
|
|
|
177
153
|
return cues;
|
|
@@ -105,7 +105,7 @@ shaka.text.VttTextParser = class {
|
|
|
105
105
|
|
|
106
106
|
const rolloverSeconds =
|
|
107
107
|
shaka.text.VttTextParser.TS_ROLLOVER_ / mpegTimescale;
|
|
108
|
-
let segmentStart = time.segmentStart;
|
|
108
|
+
let segmentStart = time.segmentStart - time.periodStart;
|
|
109
109
|
while (segmentStart >= rolloverSeconds) {
|
|
110
110
|
segmentStart -= rolloverSeconds;
|
|
111
111
|
mpegTime += shaka.text.VttTextParser.TS_ROLLOVER_;
|
package/lib/util/error.js
CHANGED
|
@@ -1013,6 +1013,13 @@ shaka.util.Error.Code = {
|
|
|
1013
1013
|
*/
|
|
1014
1014
|
'MODIFY_OPERATION_NOT_SUPPORTED': 9016,
|
|
1015
1015
|
|
|
1016
|
+
/**
|
|
1017
|
+
* When attempting to open an indexedDB instance, nothing happened for long
|
|
1018
|
+
* enough for us to time out. This keeps the storage mechanism from hanging
|
|
1019
|
+
* indefinitely, if neither the success nor error callbacks are called.
|
|
1020
|
+
*/
|
|
1021
|
+
'INDEXED_DB_INIT_TIMED_OUT': 9017,
|
|
1022
|
+
|
|
1016
1023
|
/**
|
|
1017
1024
|
* CS IMA SDK, required for ad insertion, has not been included on the page.
|
|
1018
1025
|
*/
|
package/lib/util/periods.js
CHANGED
|
@@ -1180,15 +1180,6 @@ shaka.util.PeriodCombiner = class {
|
|
|
1180
1180
|
return false;
|
|
1181
1181
|
}
|
|
1182
1182
|
|
|
1183
|
-
// If the language doesn't match, but the candidate is the "primary"
|
|
1184
|
-
// language, then that should be preferred as a fallback.
|
|
1185
|
-
if (!best.primary && candidate.primary) {
|
|
1186
|
-
return true;
|
|
1187
|
-
}
|
|
1188
|
-
if (best.primary && !candidate.primary) {
|
|
1189
|
-
return false;
|
|
1190
|
-
}
|
|
1191
|
-
|
|
1192
1183
|
// If language-based differences haven't decided this, look at roles. If
|
|
1193
1184
|
// the candidate has more roles in common with the output, upgrade to the
|
|
1194
1185
|
// candidate.
|
|
@@ -1222,6 +1213,15 @@ shaka.util.PeriodCombiner = class {
|
|
|
1222
1213
|
return false;
|
|
1223
1214
|
}
|
|
1224
1215
|
|
|
1216
|
+
// If the language doesn't match, but the candidate is the "primary"
|
|
1217
|
+
// language, then that should be preferred as a fallback.
|
|
1218
|
+
if (!best.primary && candidate.primary) {
|
|
1219
|
+
return true;
|
|
1220
|
+
}
|
|
1221
|
+
if (best.primary && !candidate.primary) {
|
|
1222
|
+
return false;
|
|
1223
|
+
}
|
|
1224
|
+
|
|
1225
1225
|
// If language-based and role-based features are equivalent, take the audio
|
|
1226
1226
|
// with the closes channel count to the output.
|
|
1227
1227
|
const channelsBetterOrWorse =
|
package/lib/util/platform.js
CHANGED
|
@@ -163,16 +163,6 @@ shaka.util.Platform = class {
|
|
|
163
163
|
return shaka.util.Platform.userAgentContains_('CrKey');
|
|
164
164
|
}
|
|
165
165
|
|
|
166
|
-
/**
|
|
167
|
-
* Check if the current platform is a Android-based Cast devices.
|
|
168
|
-
*
|
|
169
|
-
* @return {boolean}
|
|
170
|
-
*/
|
|
171
|
-
static isAndroidCastDevice() {
|
|
172
|
-
return shaka.util.Platform.isChromecast() &&
|
|
173
|
-
shaka.util.Platform.userAgentContains_('Android');
|
|
174
|
-
}
|
|
175
|
-
|
|
176
166
|
/**
|
|
177
167
|
* Returns a major version number for Chrome, or Chromium-based browsers.
|
|
178
168
|
*
|
package/lib/util/xml_utils.js
CHANGED
|
@@ -8,6 +8,7 @@ goog.provide('shaka.util.XmlUtils');
|
|
|
8
8
|
|
|
9
9
|
goog.require('goog.asserts');
|
|
10
10
|
goog.require('shaka.log');
|
|
11
|
+
goog.require('shaka.util.Lazy');
|
|
11
12
|
goog.require('shaka.util.StringUtils');
|
|
12
13
|
|
|
13
14
|
|
|
@@ -348,19 +349,21 @@ shaka.util.XmlUtils = class {
|
|
|
348
349
|
*/
|
|
349
350
|
static parseXmlString(xmlString, expectedRootElemName) {
|
|
350
351
|
const parser = new DOMParser();
|
|
351
|
-
|
|
352
|
+
const unsafeXmlString =
|
|
353
|
+
shaka.util.XmlUtils.trustedHTMLFromString_.value()(xmlString);
|
|
354
|
+
let unsafeXml = null;
|
|
352
355
|
try {
|
|
353
|
-
|
|
356
|
+
unsafeXml = parser.parseFromString(unsafeXmlString, 'text/xml');
|
|
354
357
|
} catch (exception) {
|
|
355
358
|
shaka.log.error('XML parsing exception:', exception);
|
|
356
359
|
return null;
|
|
357
360
|
}
|
|
358
361
|
|
|
359
362
|
// According to MDN, parseFromString never returns null.
|
|
360
|
-
goog.asserts.assert(
|
|
363
|
+
goog.asserts.assert(unsafeXml, 'Parsed XML document cannot be null!');
|
|
361
364
|
|
|
362
365
|
// Check for empty documents.
|
|
363
|
-
const rootElem =
|
|
366
|
+
const rootElem = unsafeXml.documentElement;
|
|
364
367
|
if (!rootElem) {
|
|
365
368
|
shaka.log.error('XML document was empty!');
|
|
366
369
|
return null;
|
|
@@ -374,13 +377,28 @@ shaka.util.XmlUtils = class {
|
|
|
374
377
|
}
|
|
375
378
|
|
|
376
379
|
// The top-level element in the loaded XML should have the name we expect.
|
|
377
|
-
if (
|
|
380
|
+
if (rootElem.tagName != expectedRootElemName) {
|
|
378
381
|
shaka.log.error(
|
|
379
382
|
`XML tag name does not match expected "${expectedRootElemName}":`,
|
|
380
|
-
|
|
383
|
+
rootElem.tagName);
|
|
381
384
|
return null;
|
|
382
385
|
}
|
|
383
386
|
|
|
387
|
+
// SECURITY: Verify that the document does not contain elements from the
|
|
388
|
+
// HTML or SVG namespaces, which could trigger script execution and XSS.
|
|
389
|
+
const iterator = document.createNodeIterator(
|
|
390
|
+
unsafeXml,
|
|
391
|
+
NodeFilter.SHOW_ALL,
|
|
392
|
+
);
|
|
393
|
+
let currentNode;
|
|
394
|
+
while (currentNode = iterator.nextNode()) {
|
|
395
|
+
if (currentNode instanceof HTMLElement ||
|
|
396
|
+
currentNode instanceof SVGElement) {
|
|
397
|
+
shaka.log.error('XML document embeds unsafe content!');
|
|
398
|
+
return null;
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
|
|
384
402
|
return rootElem;
|
|
385
403
|
}
|
|
386
404
|
|
|
@@ -402,3 +420,24 @@ shaka.util.XmlUtils = class {
|
|
|
402
420
|
}
|
|
403
421
|
}
|
|
404
422
|
};
|
|
423
|
+
|
|
424
|
+
/**
|
|
425
|
+
* Promote a string to TrustedHTML. This function is security-sensitive and
|
|
426
|
+
* should only be used with security approval where the string is guaranteed not
|
|
427
|
+
* to cause an XSS vulnerability.
|
|
428
|
+
*
|
|
429
|
+
* @private {!shaka.util.Lazy.<function(!string): (!TrustedHTML|!string)>}
|
|
430
|
+
*/
|
|
431
|
+
shaka.util.XmlUtils.trustedHTMLFromString_ = new shaka.util.Lazy(() => {
|
|
432
|
+
if (typeof trustedTypes !== 'undefined') {
|
|
433
|
+
// Create a Trusted Types policy for promoting the string to TrustedHTML.
|
|
434
|
+
// The Lazy wrapper ensures this policy is only created once.
|
|
435
|
+
const policy = trustedTypes.createPolicy('shaka-player#xml', {
|
|
436
|
+
createHTML: (s) => s,
|
|
437
|
+
});
|
|
438
|
+
return (s) => policy.createHTML(s);
|
|
439
|
+
}
|
|
440
|
+
// Fall back to strings in environments that don't support Trusted Types.
|
|
441
|
+
return (s) => s;
|
|
442
|
+
});
|
|
443
|
+
|
package/package.json
CHANGED
|
@@ -1034,6 +1034,36 @@ filterDescribe('Storage', storageSupport, () => {
|
|
|
1034
1034
|
}
|
|
1035
1035
|
});
|
|
1036
1036
|
|
|
1037
|
+
/**
|
|
1038
|
+
* In some situations, indexedDB.open() can just hang, and call neither the
|
|
1039
|
+
* 'success' nor the 'error' callbacks.
|
|
1040
|
+
* I'm not sure what causes it, but it seems to happen consistently between
|
|
1041
|
+
* reloads when it does so it might be a browser-based issue.
|
|
1042
|
+
* In that case, we should time out with an error, instead of also hanging.
|
|
1043
|
+
*/
|
|
1044
|
+
it('throws an error if indexedDB open times out', async () => {
|
|
1045
|
+
const oldOpen = window.indexedDB.open;
|
|
1046
|
+
window.indexedDB.open = () => {
|
|
1047
|
+
// Just return a dummy object.
|
|
1048
|
+
return /** @type {!IDBOpenDBRequest} */ ({
|
|
1049
|
+
onsuccess: (event) => {},
|
|
1050
|
+
onerror: (error) => {},
|
|
1051
|
+
});
|
|
1052
|
+
};
|
|
1053
|
+
|
|
1054
|
+
/** @type {!shaka.offline.StorageMuxer} */
|
|
1055
|
+
const muxer = new shaka.offline.StorageMuxer();
|
|
1056
|
+
const expectedError = shaka.test.Util.jasmineError(new shaka.util.Error(
|
|
1057
|
+
shaka.util.Error.Severity.CRITICAL,
|
|
1058
|
+
shaka.util.Error.Category.STORAGE,
|
|
1059
|
+
shaka.util.Error.Code.INDEXED_DB_INIT_TIMED_OUT));
|
|
1060
|
+
|
|
1061
|
+
await expectAsync(muxer.init())
|
|
1062
|
+
.toBeRejectedWith(expectedError);
|
|
1063
|
+
|
|
1064
|
+
window.indexedDB.open = oldOpen;
|
|
1065
|
+
});
|
|
1066
|
+
|
|
1037
1067
|
it('throws an error if the content is a live stream', async () => {
|
|
1038
1068
|
const expected = Util.jasmineError(new shaka.util.Error(
|
|
1039
1069
|
shaka.util.Error.Severity.CRITICAL,
|