shaka-player 4.2.2 → 4.2.4
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 +2 -0
- package/CHANGELOG.md +32 -0
- package/CONTRIBUTORS +2 -0
- package/dist/deps.js +5 -5
- package/dist/locales.js +33 -33
- package/dist/shaka-player.compiled.d.ts +6 -1
- package/dist/shaka-player.compiled.debug.d.ts +6 -1
- package/dist/shaka-player.compiled.debug.externs.js +3 -1
- package/dist/shaka-player.compiled.debug.js +119 -115
- package/dist/shaka-player.compiled.debug.map +4 -4
- package/dist/shaka-player.compiled.externs.js +3 -1
- package/dist/shaka-player.compiled.js +719 -716
- package/dist/shaka-player.compiled.map +4 -4
- package/dist/shaka-player.ui.d.ts +6 -1
- package/dist/shaka-player.ui.debug.d.ts +6 -1
- package/dist/shaka-player.ui.debug.externs.js +3 -1
- package/dist/shaka-player.ui.debug.js +135 -132
- package/dist/shaka-player.ui.debug.map +4 -4
- package/dist/shaka-player.ui.externs.js +3 -1
- package/dist/shaka-player.ui.js +846 -843
- package/dist/shaka-player.ui.map +4 -4
- package/docs/tutorials/welcome.md +2 -2
- package/externs/mux.js +3 -0
- package/lib/ads/client_side_ad_manager.js +5 -2
- 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/hls/hls_parser.js +27 -9
- package/lib/media/drm_engine.js +7 -6
- package/lib/media/media_source_engine.js +6 -0
- package/lib/media/playhead.js +24 -2
- package/lib/media/streaming_engine.js +3 -12
- package/lib/media/transmuxer.js +6 -0
- package/lib/offline/indexeddb/storage_mechanism.js +24 -0
- package/lib/player.js +24 -11
- 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/manifest_parser_utils.js +4 -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/roadmap.md +24 -6
- 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/media/media_source_engine_integration.js +31 -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 +36 -23
- package/test/offline/storage_integration.js +30 -0
- package/test/player_unit.js +87 -0
- package/test/test/util/simple_fakes.js +3 -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
- package/ui/controls.js +4 -1
- package/ui/settings_menu.js +11 -2
|
@@ -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
|
*/
|
|
@@ -168,6 +168,7 @@ shaka.util.ManifestParserUtils.VIDEO_CODEC_REGEXPS_ = [
|
|
|
168
168
|
/^hvc/,
|
|
169
169
|
/^vp0?[89]/,
|
|
170
170
|
/^av01/,
|
|
171
|
+
/^dvhe/,
|
|
171
172
|
];
|
|
172
173
|
|
|
173
174
|
|
|
@@ -183,6 +184,9 @@ shaka.util.ManifestParserUtils.AUDIO_CODEC_REGEXPS_ = [
|
|
|
183
184
|
/^flac$/,
|
|
184
185
|
/^mp4a/,
|
|
185
186
|
/^[ae]c-3$/,
|
|
187
|
+
/^ac-4$/,
|
|
188
|
+
/^dts[cx]$/, // DTS Surround Sound
|
|
189
|
+
/^[du]dts$/, // DTS Surround Sound
|
|
186
190
|
];
|
|
187
191
|
|
|
188
192
|
|
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
package/roadmap.md
CHANGED
|
@@ -9,28 +9,46 @@ The goals of future milestones are fluid until we begin that development cycle,
|
|
|
9
9
|
so the exact milestone for future features is not pre-determined.
|
|
10
10
|
|
|
11
11
|
Priority features up next:
|
|
12
|
-
-
|
|
13
|
-
https://github.com/shaka-project/shaka-player/
|
|
12
|
+
- HLS improvements
|
|
13
|
+
https://github.com/shaka-project/shaka-player/projects/6
|
|
14
14
|
|
|
15
15
|
Candidate features for future release cycles:
|
|
16
|
+
- Preload API
|
|
17
|
+
https://github.com/shaka-project/shaka-player/issues/880
|
|
16
18
|
- Official TypeScript defs, generated from source
|
|
17
19
|
https://github.com/shaka-project/shaka-player/issues/1030
|
|
18
20
|
- Codec-switching
|
|
19
21
|
https://github.com/shaka-project/shaka-player/issues/1528
|
|
20
22
|
- Background fetch for offline storage
|
|
21
23
|
https://github.com/shaka-project/shaka-player/issues/879
|
|
24
|
+
- Smaller, more modular binary
|
|
25
|
+
|
|
26
|
+
v4.3 - 2022 Q4 / 2023 Q1
|
|
22
27
|
- Support ID3 tags in mp4
|
|
23
28
|
https://github.com/shaka-project/shaka-player/issues/3351
|
|
24
|
-
-
|
|
29
|
+
- Lazy-load HLS playlists on adaptation
|
|
30
|
+
https://github.com/shaka-project/shaka-player/issues/1936
|
|
25
31
|
|
|
26
|
-
|
|
32
|
+
=====
|
|
33
|
+
|
|
34
|
+
v4.2 - 2022 Q3
|
|
35
|
+
- Automatic ABR quality restrictions based on size
|
|
36
|
+
https://github.com/shaka-project/shaka-player/issues/2333
|
|
37
|
+
- HLS support for AES-128 encryption
|
|
38
|
+
https://github.com/shaka-project/shaka-player/issues/850
|
|
39
|
+
|
|
40
|
+
v4.1 - 2022 Q2
|
|
41
|
+
- HLS support for EXT-X-GAP
|
|
42
|
+
https://github.com/shaka-project/shaka-player/issues/1308
|
|
43
|
+
- Temporarily disable the active variant after `HTTP_ERROR`
|
|
44
|
+
https://github.com/shaka-project/shaka-player/issues/1542
|
|
45
|
+
|
|
46
|
+
v4.0 - 2022 Q2
|
|
27
47
|
- MSE support for FairPlay
|
|
28
48
|
https://github.com/shaka-project/shaka-player/issues/3346
|
|
29
49
|
- Support containerless formats
|
|
30
50
|
https://github.com/shaka-project/shaka-player/issues/2337
|
|
31
51
|
|
|
32
|
-
=====
|
|
33
|
-
|
|
34
52
|
v3.3 - 2022 Q1
|
|
35
53
|
- Common Media Client Data (CMCD) logging support
|
|
36
54
|
https://github.com/shaka-project/shaka-player/issues/3619
|
|
@@ -34,6 +34,8 @@ filterDescribe('CastReceiver', castReceiverIntegrationSupport, () => {
|
|
|
34
34
|
|
|
35
35
|
/** @type {shaka.util.PublicPromise} */
|
|
36
36
|
let messageWaitPromise;
|
|
37
|
+
/** @type {Array.<string>} */
|
|
38
|
+
let pendingMessages = null;
|
|
37
39
|
|
|
38
40
|
/** @type {!Array.<function()>} */
|
|
39
41
|
let toRestore;
|
|
@@ -91,6 +93,9 @@ filterDescribe('CastReceiver', castReceiverIntegrationSupport, () => {
|
|
|
91
93
|
toRestore = [];
|
|
92
94
|
pendingWaitWrapperCalls = 0;
|
|
93
95
|
|
|
96
|
+
messageWaitPromise = null;
|
|
97
|
+
pendingMessages = null;
|
|
98
|
+
|
|
94
99
|
fakeInitState = {
|
|
95
100
|
player: {
|
|
96
101
|
configure: {},
|
|
@@ -118,6 +123,12 @@ filterDescribe('CastReceiver', castReceiverIntegrationSupport, () => {
|
|
|
118
123
|
player = null;
|
|
119
124
|
video = null;
|
|
120
125
|
receiver = null;
|
|
126
|
+
|
|
127
|
+
if (messageWaitPromise) {
|
|
128
|
+
messageWaitPromise.resolve([]);
|
|
129
|
+
}
|
|
130
|
+
messageWaitPromise = null;
|
|
131
|
+
pendingMessages = null;
|
|
121
132
|
});
|
|
122
133
|
|
|
123
134
|
afterAll(() => {
|
|
@@ -128,6 +139,66 @@ filterDescribe('CastReceiver', castReceiverIntegrationSupport, () => {
|
|
|
128
139
|
}
|
|
129
140
|
});
|
|
130
141
|
|
|
142
|
+
describe('without drm', () => {
|
|
143
|
+
it('sends reasonably-sized updates', async () => {
|
|
144
|
+
// Use an unencrypted asset.
|
|
145
|
+
fakeInitState.manifest = 'test:sintel';
|
|
146
|
+
|
|
147
|
+
const p = waitForLoadedData();
|
|
148
|
+
|
|
149
|
+
// Start the process of loading by sending a fake init message.
|
|
150
|
+
fakeConnectedSenders(1);
|
|
151
|
+
fakeIncomingMessage({
|
|
152
|
+
type: 'init',
|
|
153
|
+
initState: fakeInitState,
|
|
154
|
+
appData: {},
|
|
155
|
+
}, mockShakaMessageBus);
|
|
156
|
+
|
|
157
|
+
await p;
|
|
158
|
+
// Wait for an update message.
|
|
159
|
+
const messages = await waitForUpdateMessages();
|
|
160
|
+
for (const message of messages) {
|
|
161
|
+
// Check that the update message is of a reasonable size. From previous
|
|
162
|
+
// testing we found that the socket would silently reject data that got
|
|
163
|
+
// too big. 6KB is safely below the limit.
|
|
164
|
+
expect(message.length).toBeLessThan(6000);
|
|
165
|
+
}
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
it('has reasonable average message size', async () => {
|
|
169
|
+
// Use an unencrypted asset.
|
|
170
|
+
fakeInitState.manifest = 'test:sintel';
|
|
171
|
+
|
|
172
|
+
const p = waitForLoadedData();
|
|
173
|
+
|
|
174
|
+
// Start the process of loading by sending a fake init message.
|
|
175
|
+
fakeConnectedSenders(1);
|
|
176
|
+
fakeIncomingMessage({
|
|
177
|
+
type: 'init',
|
|
178
|
+
initState: fakeInitState,
|
|
179
|
+
appData: {},
|
|
180
|
+
}, mockShakaMessageBus);
|
|
181
|
+
|
|
182
|
+
await p;
|
|
183
|
+
|
|
184
|
+
// Collect messages over 50 update cycles, and average their length.
|
|
185
|
+
// Not all properties are passed along on every update message, so
|
|
186
|
+
// the average length is expected to be lower than the length of the first
|
|
187
|
+
// update message.
|
|
188
|
+
let totalLength = 0;
|
|
189
|
+
let totalMessages = 0;
|
|
190
|
+
for (let i = 0; i < 50; i++) {
|
|
191
|
+
// eslint-disable-next-line no-await-in-loop
|
|
192
|
+
const messages = await waitForUpdateMessages();
|
|
193
|
+
for (const message of messages) {
|
|
194
|
+
totalLength += message.length;
|
|
195
|
+
totalMessages += 1;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
expect(totalLength / totalMessages).toBeLessThan(2000);
|
|
199
|
+
});
|
|
200
|
+
});
|
|
201
|
+
|
|
131
202
|
filterDescribe('with drm', () => support['com.widevine.alpha'], () => {
|
|
132
203
|
drmIt('sends reasonably-sized updates', async () => {
|
|
133
204
|
// Use an encrypted asset, to make sure DRM info doesn't balloon the size.
|
|
@@ -150,11 +221,13 @@ filterDescribe('CastReceiver', castReceiverIntegrationSupport, () => {
|
|
|
150
221
|
|
|
151
222
|
await p;
|
|
152
223
|
// Wait for an update message.
|
|
153
|
-
const
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
224
|
+
const messages = await waitForUpdateMessages();
|
|
225
|
+
for (const message of messages) {
|
|
226
|
+
// Check that the update message is of a reasonable size. From previous
|
|
227
|
+
// testing we found that the socket would silently reject data that got
|
|
228
|
+
// too big. 6KB is safely below the limit.
|
|
229
|
+
expect(message.length).toBeLessThan(6000);
|
|
230
|
+
}
|
|
158
231
|
});
|
|
159
232
|
|
|
160
233
|
drmIt('has reasonable average message size', async () => {
|
|
@@ -177,17 +250,22 @@ filterDescribe('CastReceiver', castReceiverIntegrationSupport, () => {
|
|
|
177
250
|
}, mockShakaMessageBus);
|
|
178
251
|
|
|
179
252
|
await p;
|
|
180
|
-
|
|
253
|
+
|
|
254
|
+
// Collect messages over 50 update cycles, and average their length.
|
|
181
255
|
// Not all properties are passed along on every update message, so
|
|
182
256
|
// the average length is expected to be lower than the length of the first
|
|
183
257
|
// update message.
|
|
184
258
|
let totalLength = 0;
|
|
259
|
+
let totalMessages = 0;
|
|
185
260
|
for (let i = 0; i < 50; i++) {
|
|
186
261
|
// eslint-disable-next-line no-await-in-loop
|
|
187
|
-
const
|
|
188
|
-
|
|
262
|
+
const messages = await waitForUpdateMessages();
|
|
263
|
+
for (const message of messages) {
|
|
264
|
+
totalLength += message.length;
|
|
265
|
+
totalMessages += 1;
|
|
266
|
+
}
|
|
189
267
|
}
|
|
190
|
-
expect(totalLength /
|
|
268
|
+
expect(totalLength / totalMessages).toBeLessThan(2000);
|
|
191
269
|
});
|
|
192
270
|
});
|
|
193
271
|
|
|
@@ -227,12 +305,12 @@ filterDescribe('CastReceiver', castReceiverIntegrationSupport, () => {
|
|
|
227
305
|
expect(pendingWaitWrapperCalls).toBe(0);
|
|
228
306
|
|
|
229
307
|
// Wait for a final update message before proceeding.
|
|
230
|
-
await
|
|
308
|
+
await waitForUpdateMessages();
|
|
231
309
|
});
|
|
232
310
|
|
|
233
311
|
/**
|
|
234
312
|
* Creates a wrapper around a method on a given prototype, which makes it
|
|
235
|
-
* wait on
|
|
313
|
+
* wait on waitForUpdateMessages before returning, and registers that wrapper
|
|
236
314
|
* to be uninstalled afterwards.
|
|
237
315
|
* The replaced method is expected to be a method that returns a promise.
|
|
238
316
|
* @param {!Object} prototype
|
|
@@ -249,7 +327,7 @@ filterDescribe('CastReceiver', castReceiverIntegrationSupport, () => {
|
|
|
249
327
|
'Waiting for update message before calling ' +
|
|
250
328
|
name + '.' + methodName + '...');
|
|
251
329
|
const originalArguments = Array.from(arguments);
|
|
252
|
-
await
|
|
330
|
+
await waitForUpdateMessages();
|
|
253
331
|
// eslint-disable-next-line no-restricted-syntax
|
|
254
332
|
return original.apply(this, originalArguments);
|
|
255
333
|
};
|
|
@@ -265,7 +343,8 @@ filterDescribe('CastReceiver', castReceiverIntegrationSupport, () => {
|
|
|
265
343
|
});
|
|
266
344
|
}
|
|
267
345
|
|
|
268
|
-
function
|
|
346
|
+
function waitForUpdateMessages() {
|
|
347
|
+
pendingMessages = [];
|
|
269
348
|
messageWaitPromise = new shaka.util.PublicPromise();
|
|
270
349
|
return messageWaitPromise;
|
|
271
350
|
}
|
|
@@ -316,10 +395,12 @@ filterDescribe('CastReceiver', castReceiverIntegrationSupport, () => {
|
|
|
316
395
|
bus.messages.push(CastUtils.deserialize(message));
|
|
317
396
|
// Check to see if it's an update message.
|
|
318
397
|
const parsed = CastUtils.deserialize(message);
|
|
319
|
-
if (parsed.type == 'update' &&
|
|
398
|
+
if (parsed.type == 'update' && pendingMessages) {
|
|
320
399
|
shaka.log.debug('Received update message. Proceeding...');
|
|
321
|
-
|
|
322
|
-
|
|
400
|
+
// The code waiting on this Promise will get an array of all of the
|
|
401
|
+
// messages processed within this tick.
|
|
402
|
+
pendingMessages.push(message);
|
|
403
|
+
messageWaitPromise.resolve(pendingMessages);
|
|
323
404
|
}
|
|
324
405
|
});
|
|
325
406
|
const channel = {
|