shaka-player 4.7.13 → 4.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AUTHORS +2 -0
- package/CONTRIBUTORS +2 -0
- package/LICENSE +54 -0
- package/README.md +22 -1
- package/dist/controls.css +1 -1
- package/dist/controls.css.map +1 -1
- package/dist/deps.js +34 -23
- package/dist/locales.js +41 -31
- package/dist/shaka-player.compiled.d.ts +196 -45
- package/dist/shaka-player.compiled.debug.d.ts +196 -45
- package/dist/shaka-player.compiled.debug.externs.js +491 -348
- package/dist/shaka-player.compiled.debug.js +969 -847
- package/dist/shaka-player.compiled.debug.map +4 -4
- package/dist/shaka-player.compiled.externs.js +491 -348
- package/dist/shaka-player.compiled.js +1238 -1171
- package/dist/shaka-player.compiled.map +4 -4
- package/dist/shaka-player.ui.d.ts +259 -48
- package/dist/shaka-player.ui.debug.d.ts +259 -48
- package/dist/shaka-player.ui.debug.externs.js +598 -377
- package/dist/shaka-player.ui.debug.js +1185 -980
- package/dist/shaka-player.ui.debug.map +4 -4
- package/dist/shaka-player.ui.externs.js +598 -377
- package/dist/shaka-player.ui.js +1526 -1408
- package/dist/shaka-player.ui.map +4 -4
- package/externs/device_sensor_event.js +18 -0
- package/externs/hisense.js +16 -0
- package/externs/playstation.js +31 -0
- package/externs/shaka/abr_manager.js +3 -1
- package/externs/shaka/ads.js +5 -0
- package/externs/shaka/codecs.js +13 -0
- package/externs/shaka/manifest.js +58 -6
- package/externs/shaka/net.js +13 -1
- package/externs/shaka/player.js +184 -13
- package/externs/shaka/text.js +19 -0
- package/externs/tizen.js +41 -0
- package/externs/webos.js +19 -0
- package/externs/xbox.js +43 -0
- package/lib/abr/simple_abr_manager.js +9 -1
- package/lib/ads/ad_manager.js +19 -2
- package/lib/ads/client_side_ad_manager.js +5 -3
- package/lib/ads/media_tailor_ad_manager.js +9 -0
- package/lib/cast/cast_receiver.js +0 -14
- package/lib/cast/cast_utils.js +1 -0
- package/lib/cea/mp4_cea_parser.js +6 -1
- package/lib/dash/content_protection.js +97 -53
- package/lib/dash/dash_parser.js +237 -177
- package/lib/dash/mpd_utils.js +72 -65
- package/lib/dash/segment_base.js +27 -21
- package/lib/dash/segment_list.js +18 -16
- package/lib/dash/segment_template.js +45 -22
- package/lib/hls/hls_parser.js +358 -164
- package/lib/media/buffering_observer.js +25 -1
- package/lib/media/drm_engine.js +91 -71
- package/lib/media/manifest_filterer.js +220 -0
- package/lib/media/media_source_engine.js +34 -5
- package/lib/media/preload_manager.js +827 -0
- package/lib/media/presentation_timeline.js +11 -0
- package/lib/media/segment_index.js +93 -47
- package/lib/media/segment_prefetch.js +140 -43
- package/lib/media/segment_reference.js +43 -8
- package/lib/media/segment_utils.js +208 -25
- package/lib/media/streaming_engine.js +355 -66
- package/lib/mss/content_protection.js +31 -25
- package/lib/mss/mss_parser.js +79 -55
- package/lib/net/networking_engine.js +49 -4
- package/lib/offline/storage.js +3 -3
- package/lib/player.js +882 -672
- package/lib/polyfill/media_capabilities.js +16 -1
- package/lib/text/simple_text_displayer.js +9 -0
- package/lib/text/stub_text_displayer.js +7 -0
- package/lib/text/text_engine.js +16 -3
- package/lib/text/ttml_text_parser.js +116 -114
- package/lib/text/ui_text_displayer.js +28 -11
- package/lib/text/vtt_text_parser.js +54 -115
- package/lib/transmuxer/opus.js +91 -0
- package/lib/transmuxer/ts_transmuxer.js +115 -0
- package/lib/util/cmcd_manager.js +153 -3
- package/lib/util/dom_utils.js +11 -0
- package/lib/util/error.js +5 -0
- package/lib/util/fake_event.js +2 -0
- package/lib/util/manifest_parser_utils.js +97 -3
- package/lib/util/mp4_box_parsers.js +346 -17
- package/lib/util/mp4_generator.js +23 -0
- package/lib/util/periods.js +19 -0
- package/lib/util/platform.js +161 -9
- package/lib/util/player_configuration.js +79 -13
- package/lib/util/stats.js +16 -0
- package/lib/util/stream_utils.js +22 -8
- package/lib/util/string_utils.js +36 -0
- package/lib/util/tXml.js +814 -0
- package/lib/util/ts_parser.js +50 -0
- package/lib/util/xml_utils.js +10 -338
- package/package.json +2 -2
- package/shaka-player.uncompiled.js +8 -0
- package/ui/controls.js +187 -5
- package/ui/enums.js +2 -0
- package/ui/externs/ui.js +30 -3
- package/ui/gl_matrix/matrix_4x4.js +568 -0
- package/ui/gl_matrix/matrix_quaternion.js +45 -0
- package/ui/less/containers.less +11 -0
- package/ui/locales/en-GB.json +2 -0
- package/ui/locales/en.json +2 -0
- package/ui/locales/es-419.json +2 -0
- package/ui/locales/es.json +2 -0
- package/ui/locales/source.json +8 -0
- package/ui/recenter_vr.js +126 -0
- package/ui/resolution_selection.js +25 -5
- package/ui/statistics_button.js +12 -0
- package/ui/text_selection.js +1 -1
- package/ui/toggle_stereoscopic.js +127 -0
- package/ui/ui.js +74 -38
- package/ui/vr_manager.js +557 -0
- package/ui/vr_utils.js +232 -0
- package/ui/vr_webgl.js +697 -0
|
@@ -28,6 +28,9 @@ shaka.media.BufferingObserver = class {
|
|
|
28
28
|
this.thresholds_ = new Map()
|
|
29
29
|
.set(State.SATISFIED, thresholdWhenSatisfied)
|
|
30
30
|
.set(State.STARVING, thresholdWhenStarving);
|
|
31
|
+
|
|
32
|
+
/** @private {number} */
|
|
33
|
+
this.lastRebufferTime_ = 0;
|
|
31
34
|
}
|
|
32
35
|
|
|
33
36
|
/**
|
|
@@ -73,7 +76,13 @@ shaka.media.BufferingObserver = class {
|
|
|
73
76
|
this.previousState_ = newState;
|
|
74
77
|
|
|
75
78
|
// Return |true| only when the state has changed.
|
|
76
|
-
|
|
79
|
+
const stateChanged = oldState != newState;
|
|
80
|
+
|
|
81
|
+
if (stateChanged && newState === State.SATISFIED) {
|
|
82
|
+
this.lastRebufferTime_ = Date.now();
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return stateChanged;
|
|
77
86
|
}
|
|
78
87
|
|
|
79
88
|
/**
|
|
@@ -93,6 +102,21 @@ shaka.media.BufferingObserver = class {
|
|
|
93
102
|
getState() {
|
|
94
103
|
return this.previousState_;
|
|
95
104
|
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Return the last time that the state went from |STARVING| to |SATISFIED|.
|
|
108
|
+
* @return {number}
|
|
109
|
+
*/
|
|
110
|
+
getLastRebufferTime() {
|
|
111
|
+
return this.lastRebufferTime_;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Reset the last rebuffer time to zero.
|
|
116
|
+
*/
|
|
117
|
+
resetLastRebufferTime() {
|
|
118
|
+
this.lastRebufferTime_ = 0;
|
|
119
|
+
}
|
|
96
120
|
};
|
|
97
121
|
|
|
98
122
|
/**
|
package/lib/media/drm_engine.js
CHANGED
|
@@ -27,17 +27,16 @@ goog.require('shaka.util.PublicPromise');
|
|
|
27
27
|
goog.require('shaka.util.StreamUtils');
|
|
28
28
|
goog.require('shaka.util.StringUtils');
|
|
29
29
|
goog.require('shaka.util.Timer');
|
|
30
|
+
goog.require('shaka.util.TXml');
|
|
30
31
|
goog.require('shaka.util.Uint8ArrayUtils');
|
|
31
|
-
goog.require('shaka.util.XmlUtils');
|
|
32
32
|
|
|
33
33
|
|
|
34
34
|
/** @implements {shaka.util.IDestroyable} */
|
|
35
35
|
shaka.media.DrmEngine = class {
|
|
36
36
|
/**
|
|
37
37
|
* @param {shaka.media.DrmEngine.PlayerInterface} playerInterface
|
|
38
|
-
* @param {number=} updateExpirationTime
|
|
39
38
|
*/
|
|
40
|
-
constructor(playerInterface
|
|
39
|
+
constructor(playerInterface) {
|
|
41
40
|
/** @private {?shaka.media.DrmEngine.PlayerInterface} */
|
|
42
41
|
this.playerInterface_ = playerInterface;
|
|
43
42
|
|
|
@@ -125,7 +124,7 @@ shaka.media.DrmEngine = class {
|
|
|
125
124
|
/** @private {?shaka.util.Timer} */
|
|
126
125
|
this.expirationTimer_ = new shaka.util.Timer(() => {
|
|
127
126
|
this.pollExpiration_();
|
|
128
|
-
})
|
|
127
|
+
});
|
|
129
128
|
|
|
130
129
|
// Add a catch to the Promise to avoid console logs about uncaught errors.
|
|
131
130
|
const noop = () => {};
|
|
@@ -210,6 +209,10 @@ shaka.media.DrmEngine = class {
|
|
|
210
209
|
*/
|
|
211
210
|
configure(config) {
|
|
212
211
|
this.config_ = config;
|
|
212
|
+
if (this.expirationTimer_) {
|
|
213
|
+
this.expirationTimer_.tickEvery(
|
|
214
|
+
/* seconds= */ this.config_.updateExpirationTime);
|
|
215
|
+
}
|
|
213
216
|
}
|
|
214
217
|
|
|
215
218
|
/**
|
|
@@ -1215,56 +1218,8 @@ shaka.media.DrmEngine = class {
|
|
|
1215
1218
|
return null;
|
|
1216
1219
|
}
|
|
1217
1220
|
|
|
1218
|
-
const
|
|
1219
|
-
|
|
1220
|
-
const keys = [];
|
|
1221
|
-
const keyIds = [];
|
|
1222
|
-
|
|
1223
|
-
clearKeys.forEach((key, keyId) => {
|
|
1224
|
-
let kid = keyId;
|
|
1225
|
-
if (kid.length != 22) {
|
|
1226
|
-
kid = Uint8ArrayUtils.toBase64(
|
|
1227
|
-
Uint8ArrayUtils.fromHex(keyId), false);
|
|
1228
|
-
}
|
|
1229
|
-
let k = key;
|
|
1230
|
-
if (k.length != 22) {
|
|
1231
|
-
k = Uint8ArrayUtils.toBase64(
|
|
1232
|
-
Uint8ArrayUtils.fromHex(key), false);
|
|
1233
|
-
}
|
|
1234
|
-
const keyObj = {
|
|
1235
|
-
kty: 'oct',
|
|
1236
|
-
kid: kid,
|
|
1237
|
-
k: k,
|
|
1238
|
-
};
|
|
1239
|
-
|
|
1240
|
-
keys.push(keyObj);
|
|
1241
|
-
keyIds.push(keyObj.kid);
|
|
1242
|
-
});
|
|
1243
|
-
|
|
1244
|
-
const jwkSet = {keys: keys};
|
|
1245
|
-
const license = JSON.stringify(jwkSet);
|
|
1246
|
-
|
|
1247
|
-
// Use the keyids init data since is suggested by EME.
|
|
1248
|
-
// Suggestion: https://bit.ly/2JYcNTu
|
|
1249
|
-
// Format: https://www.w3.org/TR/eme-initdata-keyids/
|
|
1250
|
-
const initDataStr = JSON.stringify({'kids': keyIds});
|
|
1251
|
-
const initData =
|
|
1252
|
-
shaka.util.BufferUtils.toUint8(StringUtils.toUTF8(initDataStr));
|
|
1253
|
-
const initDatas = [{initData: initData, initDataType: 'keyids'}];
|
|
1254
|
-
|
|
1255
|
-
return {
|
|
1256
|
-
keySystem: 'org.w3.clearkey',
|
|
1257
|
-
licenseServerUri: 'data:application/json;base64,' + window.btoa(license),
|
|
1258
|
-
distinctiveIdentifierRequired: false,
|
|
1259
|
-
persistentStateRequired: false,
|
|
1260
|
-
audioRobustness: '',
|
|
1261
|
-
videoRobustness: '',
|
|
1262
|
-
serverCertificate: null,
|
|
1263
|
-
serverCertificateUri: '',
|
|
1264
|
-
sessionType: '',
|
|
1265
|
-
initData: initDatas,
|
|
1266
|
-
keyIds: new Set(keyIds),
|
|
1267
|
-
};
|
|
1221
|
+
const ManifestParserUtils = shaka.util.ManifestParserUtils;
|
|
1222
|
+
return ManifestParserUtils.createDrmInfoFromClearKeys(clearKeys);
|
|
1268
1223
|
}
|
|
1269
1224
|
|
|
1270
1225
|
/**
|
|
@@ -1551,6 +1506,9 @@ shaka.media.DrmEngine = class {
|
|
|
1551
1506
|
const req = this.playerInterface_.netEngine.request(requestType, request);
|
|
1552
1507
|
response = await req.promise;
|
|
1553
1508
|
} catch (error) {
|
|
1509
|
+
if (this.destroyer_.destroyed()) {
|
|
1510
|
+
return;
|
|
1511
|
+
}
|
|
1554
1512
|
// Request failed!
|
|
1555
1513
|
goog.asserts.assert(error instanceof shaka.util.Error,
|
|
1556
1514
|
'Wrong NetworkingEngine error type!');
|
|
@@ -1559,9 +1517,20 @@ shaka.media.DrmEngine = class {
|
|
|
1559
1517
|
shaka.util.Error.Category.DRM,
|
|
1560
1518
|
shaka.util.Error.Code.LICENSE_REQUEST_FAILED,
|
|
1561
1519
|
error);
|
|
1562
|
-
this.
|
|
1563
|
-
|
|
1564
|
-
metadata.updatePromise
|
|
1520
|
+
if (this.activeSessions_.size == 1) {
|
|
1521
|
+
this.onError_(shakaErr);
|
|
1522
|
+
if (metadata && metadata.updatePromise) {
|
|
1523
|
+
metadata.updatePromise.reject(shakaErr);
|
|
1524
|
+
}
|
|
1525
|
+
} else {
|
|
1526
|
+
if (metadata && metadata.updatePromise) {
|
|
1527
|
+
metadata.updatePromise.reject(shakaErr);
|
|
1528
|
+
}
|
|
1529
|
+
this.activeSessions_.delete(session);
|
|
1530
|
+
if (this.areAllSessionsLoaded_()) {
|
|
1531
|
+
this.allSessionsLoaded_.resolve();
|
|
1532
|
+
this.keyStatusTimer_.tickAfter(/* seconds= */ 0.1);
|
|
1533
|
+
}
|
|
1565
1534
|
}
|
|
1566
1535
|
return;
|
|
1567
1536
|
}
|
|
@@ -1637,6 +1606,7 @@ shaka.media.DrmEngine = class {
|
|
|
1637
1606
|
// </HttpHeaders>
|
|
1638
1607
|
// </LicenseAcquisition>
|
|
1639
1608
|
// </PlayReadyKeyMessage>
|
|
1609
|
+
const TXml = shaka.util.TXml;
|
|
1640
1610
|
|
|
1641
1611
|
const xml = shaka.util.StringUtils.fromUTF16(
|
|
1642
1612
|
request.body, /* littleEndian= */ true, /* noThrow= */ true);
|
|
@@ -1650,24 +1620,28 @@ shaka.media.DrmEngine = class {
|
|
|
1650
1620
|
return;
|
|
1651
1621
|
}
|
|
1652
1622
|
shaka.log.debug('Unwrapping PlayReady request.');
|
|
1653
|
-
const dom =
|
|
1623
|
+
const dom = TXml.parseXmlString(xml, 'PlayReadyKeyMessage');
|
|
1654
1624
|
goog.asserts.assert(dom, 'Failed to parse PlayReady XML!');
|
|
1655
1625
|
|
|
1656
1626
|
// Set request headers.
|
|
1657
|
-
const headers =
|
|
1627
|
+
const headers = TXml.getElementsByTagName(dom, 'HttpHeader');
|
|
1658
1628
|
for (const header of headers) {
|
|
1659
|
-
const name =
|
|
1660
|
-
const value =
|
|
1629
|
+
const name = TXml.getElementsByTagName(header, 'name')[0];
|
|
1630
|
+
const value = TXml.getElementsByTagName(header, 'value')[0];
|
|
1661
1631
|
goog.asserts.assert(name && value, 'Malformed PlayReady headers!');
|
|
1662
|
-
request.headers[
|
|
1632
|
+
request.headers[
|
|
1633
|
+
/** @type {string} */(shaka.util.TXml.getTextContents(name))] =
|
|
1634
|
+
/** @type {string} */(shaka.util.TXml.getTextContents(value));
|
|
1663
1635
|
}
|
|
1664
1636
|
|
|
1665
1637
|
// Unpack the base64-encoded challenge.
|
|
1666
|
-
const challenge =
|
|
1667
|
-
goog.asserts.assert(challenge,
|
|
1668
|
-
|
|
1638
|
+
const challenge = TXml.getElementsByTagName(dom, 'Challenge')[0];
|
|
1639
|
+
goog.asserts.assert(challenge,
|
|
1640
|
+
'Malformed PlayReady challenge!');
|
|
1641
|
+
goog.asserts.assert(challenge.attributes['encoding'] == 'base64encoded',
|
|
1669
1642
|
'Unexpected PlayReady challenge encoding!');
|
|
1670
|
-
request.body = shaka.util.Uint8ArrayUtils.fromBase64(
|
|
1643
|
+
request.body = shaka.util.Uint8ArrayUtils.fromBase64(
|
|
1644
|
+
/** @type{string} */(shaka.util.TXml.getTextContents(challenge)));
|
|
1671
1645
|
}
|
|
1672
1646
|
|
|
1673
1647
|
/**
|
|
@@ -1829,7 +1803,6 @@ shaka.media.DrmEngine = class {
|
|
|
1829
1803
|
'com.microsoft.playready.recommendation',
|
|
1830
1804
|
'com.apple.fps.1_0',
|
|
1831
1805
|
'com.apple.fps',
|
|
1832
|
-
'com.adobe.primetime',
|
|
1833
1806
|
];
|
|
1834
1807
|
|
|
1835
1808
|
const basicVideoCapabilities = [
|
|
@@ -2195,6 +2168,9 @@ shaka.media.DrmEngine = class {
|
|
|
2195
2168
|
* @private
|
|
2196
2169
|
*/
|
|
2197
2170
|
createDrmInfoByInfos_(keySystem, drmInfos) {
|
|
2171
|
+
/** @type {!Array.<string>} */
|
|
2172
|
+
const encryptionSchemes = [];
|
|
2173
|
+
|
|
2198
2174
|
/** @type {!Array.<string>} */
|
|
2199
2175
|
const licenseServers = [];
|
|
2200
2176
|
|
|
@@ -2211,9 +2187,14 @@ shaka.media.DrmEngine = class {
|
|
|
2211
2187
|
const keyIds = new Set();
|
|
2212
2188
|
|
|
2213
2189
|
shaka.media.DrmEngine.processDrmInfos_(
|
|
2214
|
-
drmInfos, licenseServers, serverCerts,
|
|
2190
|
+
drmInfos, encryptionSchemes, licenseServers, serverCerts,
|
|
2215
2191
|
serverCertificateUris, initDatas, keyIds);
|
|
2216
2192
|
|
|
2193
|
+
if (encryptionSchemes.length > 1) {
|
|
2194
|
+
shaka.log.warning('Multiple unique encryption schemes found! ' +
|
|
2195
|
+
'Only the first will be used.');
|
|
2196
|
+
}
|
|
2197
|
+
|
|
2217
2198
|
if (serverCerts.length > 1) {
|
|
2218
2199
|
shaka.log.warning('Multiple unique server certificates found! ' +
|
|
2219
2200
|
'Only the first will be used.');
|
|
@@ -2235,6 +2216,7 @@ shaka.media.DrmEngine = class {
|
|
|
2235
2216
|
/** @type {shaka.extern.DrmInfo} */
|
|
2236
2217
|
const res = {
|
|
2237
2218
|
keySystem,
|
|
2219
|
+
encryptionScheme: encryptionSchemes[0],
|
|
2238
2220
|
licenseServerUri: licenseServers[0],
|
|
2239
2221
|
distinctiveIdentifierRequired: drmInfos[0].distinctiveIdentifierRequired,
|
|
2240
2222
|
persistentStateRequired: drmInfos[0].persistentStateRequired,
|
|
@@ -2271,6 +2253,9 @@ shaka.media.DrmEngine = class {
|
|
|
2271
2253
|
* @private
|
|
2272
2254
|
*/
|
|
2273
2255
|
static createDrmInfoByConfigs_(keySystem, config) {
|
|
2256
|
+
/** @type {!Array.<string>} */
|
|
2257
|
+
const encryptionSchemes = [];
|
|
2258
|
+
|
|
2274
2259
|
/** @type {!Array.<string>} */
|
|
2275
2260
|
const licenseServers = [];
|
|
2276
2261
|
|
|
@@ -2288,9 +2273,14 @@ shaka.media.DrmEngine = class {
|
|
|
2288
2273
|
|
|
2289
2274
|
// TODO: refactor, don't stick drmInfos onto MediaKeySystemConfiguration
|
|
2290
2275
|
shaka.media.DrmEngine.processDrmInfos_(
|
|
2291
|
-
config['drmInfos'], licenseServers, serverCerts,
|
|
2276
|
+
config['drmInfos'], encryptionSchemes, licenseServers, serverCerts,
|
|
2292
2277
|
serverCertificateUris, initDatas, keyIds);
|
|
2293
2278
|
|
|
2279
|
+
if (encryptionSchemes.length > 1) {
|
|
2280
|
+
shaka.log.warning('Multiple unique encryption schemes found! ' +
|
|
2281
|
+
'Only the first will be used.');
|
|
2282
|
+
}
|
|
2283
|
+
|
|
2294
2284
|
if (serverCerts.length > 1) {
|
|
2295
2285
|
shaka.log.warning('Multiple unique server certificates found! ' +
|
|
2296
2286
|
'Only the first will be used.');
|
|
@@ -2315,6 +2305,7 @@ shaka.media.DrmEngine = class {
|
|
|
2315
2305
|
const distinctiveIdentifier = config.distinctiveIdentifier;
|
|
2316
2306
|
return {
|
|
2317
2307
|
keySystem,
|
|
2308
|
+
encryptionScheme: encryptionSchemes[0],
|
|
2318
2309
|
licenseServerUri: licenseServers[0],
|
|
2319
2310
|
distinctiveIdentifierRequired: (distinctiveIdentifier == 'required'),
|
|
2320
2311
|
persistentStateRequired: (config.persistentState == 'required'),
|
|
@@ -2334,6 +2325,7 @@ shaka.media.DrmEngine = class {
|
|
|
2334
2325
|
*
|
|
2335
2326
|
* @param {!Array.<shaka.extern.DrmInfo>} drmInfos
|
|
2336
2327
|
* @param {!Array.<string>} licenseServers
|
|
2328
|
+
* @param {!Array.<string>} encryptionSchemes
|
|
2337
2329
|
* @param {!Array.<!Uint8Array>} serverCerts
|
|
2338
2330
|
* @param {!Array.<string>} serverCertificateUris
|
|
2339
2331
|
* @param {!Array.<!shaka.extern.InitDataOverride>} initDatas
|
|
@@ -2341,7 +2333,7 @@ shaka.media.DrmEngine = class {
|
|
|
2341
2333
|
* @private
|
|
2342
2334
|
*/
|
|
2343
2335
|
static processDrmInfos_(
|
|
2344
|
-
drmInfos, licenseServers, serverCerts,
|
|
2336
|
+
drmInfos, encryptionSchemes, licenseServers, serverCerts,
|
|
2345
2337
|
serverCertificateUris, initDatas, keyIds) {
|
|
2346
2338
|
/** @type {function(shaka.extern.InitDataOverride,
|
|
2347
2339
|
* shaka.extern.InitDataOverride):boolean} */
|
|
@@ -2355,9 +2347,22 @@ shaka.media.DrmEngine = class {
|
|
|
2355
2347
|
shaka.util.BufferUtils.equal(a.initData, b.initData);
|
|
2356
2348
|
};
|
|
2357
2349
|
|
|
2350
|
+
const clearkeyDataStart = 'data:application/json;base64,';
|
|
2351
|
+
const clearKeyLicenseServers = [];
|
|
2352
|
+
|
|
2358
2353
|
for (const drmInfo of drmInfos) {
|
|
2354
|
+
// Build an array of unique encryption schemes.
|
|
2355
|
+
if (!encryptionSchemes.includes(drmInfo.encryptionScheme)) {
|
|
2356
|
+
encryptionSchemes.push(drmInfo.encryptionScheme);
|
|
2357
|
+
}
|
|
2358
|
+
|
|
2359
2359
|
// Build an array of unique license servers.
|
|
2360
|
-
if (
|
|
2360
|
+
if (drmInfo.keySystem == 'org.w3.clearkey' &&
|
|
2361
|
+
drmInfo.licenseServerUri.startsWith(clearkeyDataStart)) {
|
|
2362
|
+
if (!clearKeyLicenseServers.includes(drmInfo.licenseServerUri)) {
|
|
2363
|
+
clearKeyLicenseServers.push(drmInfo.licenseServerUri);
|
|
2364
|
+
}
|
|
2365
|
+
} else if (!licenseServers.includes(drmInfo.licenseServerUri)) {
|
|
2361
2366
|
licenseServers.push(drmInfo.licenseServerUri);
|
|
2362
2367
|
}
|
|
2363
2368
|
|
|
@@ -2394,6 +2399,21 @@ shaka.media.DrmEngine = class {
|
|
|
2394
2399
|
}
|
|
2395
2400
|
}
|
|
2396
2401
|
}
|
|
2402
|
+
|
|
2403
|
+
if (clearKeyLicenseServers.length == 1) {
|
|
2404
|
+
licenseServers.push(clearKeyLicenseServers[0]);
|
|
2405
|
+
} else if (clearKeyLicenseServers.length > 0) {
|
|
2406
|
+
const keys = [];
|
|
2407
|
+
for (const clearKeyLicenseServer of clearKeyLicenseServers) {
|
|
2408
|
+
const license = window.atob(
|
|
2409
|
+
clearKeyLicenseServer.split(clearkeyDataStart).pop());
|
|
2410
|
+
const jwkSet = /** @type {{keys: !Array}} */(JSON.parse(license));
|
|
2411
|
+
keys.push(...jwkSet.keys);
|
|
2412
|
+
}
|
|
2413
|
+
const newJwkSet = {keys: keys};
|
|
2414
|
+
const newLicense = JSON.stringify(newJwkSet);
|
|
2415
|
+
licenseServers.push(clearkeyDataStart + window.btoa(newLicense));
|
|
2416
|
+
}
|
|
2397
2417
|
}
|
|
2398
2418
|
|
|
2399
2419
|
/**
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
/*! @license
|
|
2
|
+
* Shaka Player
|
|
3
|
+
* Copyright 2023 Google LLC
|
|
4
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
goog.provide('shaka.media.ManifestFilterer');
|
|
8
|
+
|
|
9
|
+
goog.require('goog.asserts');
|
|
10
|
+
goog.require('shaka.util.StreamUtils');
|
|
11
|
+
goog.require('shaka.media.DrmEngine');
|
|
12
|
+
goog.require('shaka.util.Error');
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* A class that handles the filtering of manifests.
|
|
16
|
+
* Allows for manifest filtering to be done both by the player and by a
|
|
17
|
+
* preload manager.
|
|
18
|
+
*/
|
|
19
|
+
shaka.media.ManifestFilterer = class {
|
|
20
|
+
/**
|
|
21
|
+
* @param {?shaka.extern.PlayerConfiguration} config
|
|
22
|
+
* @param {{width: number, height: number}} maxHwRes
|
|
23
|
+
* @param {?shaka.media.DrmEngine} drmEngine
|
|
24
|
+
*/
|
|
25
|
+
constructor(config, maxHwRes, drmEngine) {
|
|
26
|
+
goog.asserts.assert(config, 'Must have config');
|
|
27
|
+
|
|
28
|
+
/** @private {!shaka.extern.PlayerConfiguration} */
|
|
29
|
+
this.config_ = config;
|
|
30
|
+
|
|
31
|
+
/** @private {{width: number, height: number}} */
|
|
32
|
+
this.maxHwRes_ = maxHwRes;
|
|
33
|
+
|
|
34
|
+
/** @private {?shaka.media.DrmEngine} drmEngine */
|
|
35
|
+
this.drmEngine_ = drmEngine;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** @param {!shaka.media.DrmEngine} drmEngine */
|
|
39
|
+
setDrmEngine(drmEngine) {
|
|
40
|
+
this.drmEngine_ = drmEngine;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Filters a manifest, removing unplayable streams/variants.
|
|
45
|
+
*
|
|
46
|
+
* @param {?shaka.extern.Manifest} manifest
|
|
47
|
+
* @return {!Promise.<boolean>} tracksChanged
|
|
48
|
+
*/
|
|
49
|
+
async filterManifest(manifest) {
|
|
50
|
+
await this.filterManifestWithStreamUtils_(manifest);
|
|
51
|
+
return this.filterManifestWithRestrictions(manifest);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Filters a manifest, removing unplayable streams/variants.
|
|
56
|
+
*
|
|
57
|
+
* @param {?shaka.extern.Manifest} manifest
|
|
58
|
+
* @private
|
|
59
|
+
*/
|
|
60
|
+
async filterManifestWithStreamUtils_(manifest) {
|
|
61
|
+
goog.asserts.assert(manifest, 'Manifest should exist!');
|
|
62
|
+
await shaka.util.StreamUtils.filterManifest(this.drmEngine_, manifest,
|
|
63
|
+
this.config_.drm.preferredKeySystems);
|
|
64
|
+
this.checkPlayableVariants_(manifest);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* @param {?shaka.extern.Manifest} manifest
|
|
70
|
+
* @return {boolean} tracksChanged
|
|
71
|
+
*/
|
|
72
|
+
applyRestrictions(manifest) {
|
|
73
|
+
return shaka.util.StreamUtils.applyRestrictions(
|
|
74
|
+
manifest.variants, this.config_.restrictions, this.maxHwRes_);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Apply the restrictions configuration to the manifest, and check if there's
|
|
80
|
+
* a variant that meets the restrictions.
|
|
81
|
+
*
|
|
82
|
+
* @param {?shaka.extern.Manifest} manifest
|
|
83
|
+
* @return {boolean} tracksChanged
|
|
84
|
+
*/
|
|
85
|
+
filterManifestWithRestrictions(manifest) {
|
|
86
|
+
const tracksChanged = this.applyRestrictions(manifest);
|
|
87
|
+
|
|
88
|
+
if (manifest) {
|
|
89
|
+
// We may need to create new sessions for any new init data.
|
|
90
|
+
const currentDrmInfo =
|
|
91
|
+
this.drmEngine_ ? this.drmEngine_.getDrmInfo() : null;
|
|
92
|
+
// DrmEngine.newInitData() requires mediaKeys to be available.
|
|
93
|
+
if (currentDrmInfo && this.drmEngine_.getMediaKeys()) {
|
|
94
|
+
for (const variant of manifest.variants) {
|
|
95
|
+
this.processDrmInfos(currentDrmInfo.keySystem, variant.video);
|
|
96
|
+
this.processDrmInfos(currentDrmInfo.keySystem, variant.audio);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
this.checkRestrictedVariants(manifest);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return tracksChanged;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Confirm some variants are playable. Otherwise, throw an exception.
|
|
107
|
+
* @param {!shaka.extern.Manifest} manifest
|
|
108
|
+
* @private
|
|
109
|
+
*/
|
|
110
|
+
checkPlayableVariants_(manifest) {
|
|
111
|
+
const valid = manifest.variants.some(shaka.util.StreamUtils.isPlayable);
|
|
112
|
+
|
|
113
|
+
// If none of the variants are playable, throw
|
|
114
|
+
// CONTENT_UNSUPPORTED_BY_BROWSER.
|
|
115
|
+
if (!valid) {
|
|
116
|
+
throw new shaka.util.Error(
|
|
117
|
+
shaka.util.Error.Severity.CRITICAL,
|
|
118
|
+
shaka.util.Error.Category.MANIFEST,
|
|
119
|
+
shaka.util.Error.Code.CONTENT_UNSUPPORTED_BY_BROWSER);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* @param {string} keySystem
|
|
125
|
+
* @param {?shaka.extern.Stream} stream
|
|
126
|
+
*/
|
|
127
|
+
processDrmInfos(keySystem, stream) {
|
|
128
|
+
if (!stream) {
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
for (const drmInfo of stream.drmInfos) {
|
|
133
|
+
// Ignore any data for different key systems.
|
|
134
|
+
if (drmInfo.keySystem == keySystem) {
|
|
135
|
+
for (const initData of (drmInfo.initData || [])) {
|
|
136
|
+
this.drmEngine_.newInitData(
|
|
137
|
+
initData.initDataType, initData.initData);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Checks if the variants are all restricted, and throw an appropriate
|
|
145
|
+
* exception if so.
|
|
146
|
+
*
|
|
147
|
+
* @param {shaka.extern.Manifest} manifest
|
|
148
|
+
*/
|
|
149
|
+
checkRestrictedVariants(manifest) {
|
|
150
|
+
const restrictedStatuses = shaka.media.ManifestFilterer.restrictedStatuses;
|
|
151
|
+
const keyStatusMap =
|
|
152
|
+
this.drmEngine_ ? this.drmEngine_.getKeyStatuses() : {};
|
|
153
|
+
const keyIds = Object.keys(keyStatusMap);
|
|
154
|
+
const isGlobalStatus = keyIds.length && keyIds[0] == '00';
|
|
155
|
+
|
|
156
|
+
let hasPlayable = false;
|
|
157
|
+
let hasAppRestrictions = false;
|
|
158
|
+
|
|
159
|
+
/** @type {!Set.<string>} */
|
|
160
|
+
const missingKeys = new Set();
|
|
161
|
+
|
|
162
|
+
/** @type {!Set.<string>} */
|
|
163
|
+
const badKeyStatuses = new Set();
|
|
164
|
+
|
|
165
|
+
for (const variant of manifest.variants) {
|
|
166
|
+
// TODO: Combine with onKeyStatus_.
|
|
167
|
+
const streams = [];
|
|
168
|
+
if (variant.audio) {
|
|
169
|
+
streams.push(variant.audio);
|
|
170
|
+
}
|
|
171
|
+
if (variant.video) {
|
|
172
|
+
streams.push(variant.video);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
for (const stream of streams) {
|
|
176
|
+
if (stream.keyIds.size) {
|
|
177
|
+
for (const keyId of stream.keyIds) {
|
|
178
|
+
const keyStatus = keyStatusMap[isGlobalStatus ? '00' : keyId];
|
|
179
|
+
if (!keyStatus) {
|
|
180
|
+
missingKeys.add(keyId);
|
|
181
|
+
} else if (restrictedStatuses.includes(keyStatus)) {
|
|
182
|
+
badKeyStatuses.add(keyStatus);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
} // if (stream.keyIds.size)
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
if (!variant.allowedByApplication) {
|
|
189
|
+
hasAppRestrictions = true;
|
|
190
|
+
} else if (variant.allowedByKeySystem) {
|
|
191
|
+
hasPlayable = true;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
if (!hasPlayable) {
|
|
196
|
+
/** @type {shaka.extern.RestrictionInfo} */
|
|
197
|
+
const data = {
|
|
198
|
+
hasAppRestrictions,
|
|
199
|
+
missingKeys: Array.from(missingKeys),
|
|
200
|
+
restrictedKeyStatuses: Array.from(badKeyStatuses),
|
|
201
|
+
};
|
|
202
|
+
throw new shaka.util.Error(
|
|
203
|
+
shaka.util.Error.Severity.CRITICAL,
|
|
204
|
+
shaka.util.Error.Category.MANIFEST,
|
|
205
|
+
shaka.util.Error.Code.RESTRICTIONS_CANNOT_BE_MET,
|
|
206
|
+
data);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
};
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* These are the EME key statuses that represent restricted playback.
|
|
213
|
+
* 'usable', 'released', 'output-downscaled', 'status-pending' are statuses
|
|
214
|
+
* of the usable keys. 'expired' status is being handled separately in
|
|
215
|
+
* DrmEngine.
|
|
216
|
+
*
|
|
217
|
+
* @const {!Array.<string>}
|
|
218
|
+
*/
|
|
219
|
+
shaka.media.ManifestFilterer.restrictedStatuses =
|
|
220
|
+
['output-restricted', 'internal-error'];
|
|
@@ -557,7 +557,7 @@ shaka.media.MediaSourceEngine = class {
|
|
|
557
557
|
transmuxer.convertCodecs(contentType, mimeTypeWithAllCodecs);
|
|
558
558
|
}
|
|
559
559
|
}
|
|
560
|
-
const type =
|
|
560
|
+
const type = this.addExtraFeaturesToMimeType_(mimeType);
|
|
561
561
|
|
|
562
562
|
this.destroyer_.ensureNotDestroyed();
|
|
563
563
|
|
|
@@ -600,6 +600,9 @@ shaka.media.MediaSourceEngine = class {
|
|
|
600
600
|
*/
|
|
601
601
|
configure(config) {
|
|
602
602
|
this.config_ = config;
|
|
603
|
+
if (this.textEngine_) {
|
|
604
|
+
this.textEngine_.setModifyCueCallback(config.modifyCueCallback);
|
|
605
|
+
}
|
|
603
606
|
}
|
|
604
607
|
|
|
605
608
|
/**
|
|
@@ -621,6 +624,9 @@ shaka.media.MediaSourceEngine = class {
|
|
|
621
624
|
reinitText(mimeType, sequenceMode, external) {
|
|
622
625
|
if (!this.textEngine_) {
|
|
623
626
|
this.textEngine_ = new shaka.text.TextEngine(this.textDisplayer_);
|
|
627
|
+
if (this.textEngine_) {
|
|
628
|
+
this.textEngine_.setModifyCueCallback(this.config_.modifyCueCallback);
|
|
629
|
+
}
|
|
624
630
|
}
|
|
625
631
|
this.textEngine_.initParser(mimeType, sequenceMode,
|
|
626
632
|
external || this.segmentRelativeVttTiming_, this.manifestType_);
|
|
@@ -1735,7 +1741,7 @@ shaka.media.MediaSourceEngine = class {
|
|
|
1735
1741
|
if (transmuxer) {
|
|
1736
1742
|
this.transmuxers_[contentType] = transmuxer;
|
|
1737
1743
|
}
|
|
1738
|
-
const type =
|
|
1744
|
+
const type = this.addExtraFeaturesToMimeType_(mimeType);
|
|
1739
1745
|
this.sourceBuffers_[contentType].changeType(type);
|
|
1740
1746
|
this.sourceBufferTypes_[contentType] = mimeType;
|
|
1741
1747
|
} else {
|
|
@@ -1839,12 +1845,17 @@ shaka.media.MediaSourceEngine = class {
|
|
|
1839
1845
|
|
|
1840
1846
|
const totalOfBuffers = streamsByType.size;
|
|
1841
1847
|
let numberOfSourceBufferAdded = 0;
|
|
1842
|
-
|
|
1848
|
+
const onSourceBufferAdded = () => {
|
|
1843
1849
|
numberOfSourceBufferAdded++;
|
|
1844
1850
|
if (numberOfSourceBufferAdded === totalOfBuffers) {
|
|
1845
1851
|
sourceBufferAdded.resolve();
|
|
1852
|
+
this.eventManager_.unlisten(sourceBuffers, 'addsourcebuffer',
|
|
1853
|
+
onSourceBufferAdded);
|
|
1846
1854
|
}
|
|
1847
|
-
}
|
|
1855
|
+
};
|
|
1856
|
+
|
|
1857
|
+
this.eventManager_.listen(sourceBuffers, 'addsourcebuffer',
|
|
1858
|
+
onSourceBufferAdded);
|
|
1848
1859
|
|
|
1849
1860
|
for (const contentType of streamsByType.keys()) {
|
|
1850
1861
|
const stream = streamsByType.get(contentType);
|
|
@@ -1868,7 +1879,12 @@ shaka.media.MediaSourceEngine = class {
|
|
|
1868
1879
|
this.destroyer_.ensureNotDestroyed();
|
|
1869
1880
|
|
|
1870
1881
|
this.eventManager_.listenOnce(this.video_, 'canplaythrough', () => {
|
|
1871
|
-
|
|
1882
|
+
// Don't use ensureNotDestroyed() from this event listener, because
|
|
1883
|
+
// that results in an uncaught exception. Instead, just check the
|
|
1884
|
+
// flag.
|
|
1885
|
+
if (this.destroyer_.destroyed()) {
|
|
1886
|
+
return;
|
|
1887
|
+
}
|
|
1872
1888
|
|
|
1873
1889
|
this.video_.autoplay = previousAutoPlayState;
|
|
1874
1890
|
if (!previousPausedState) {
|
|
@@ -2054,6 +2070,19 @@ shaka.media.MediaSourceEngine = class {
|
|
|
2054
2070
|
updateLcevcDec(lcevcDec) {
|
|
2055
2071
|
this.lcevcDec_ = lcevcDec;
|
|
2056
2072
|
}
|
|
2073
|
+
|
|
2074
|
+
/**
|
|
2075
|
+
* @param {string} mimeType
|
|
2076
|
+
* @return {string}
|
|
2077
|
+
* @private
|
|
2078
|
+
*/
|
|
2079
|
+
addExtraFeaturesToMimeType_(mimeType) {
|
|
2080
|
+
const extraFeatures = this.config_.addExtraFeaturesToSourceBuffer(mimeType);
|
|
2081
|
+
const extendedType = mimeType + extraFeatures;
|
|
2082
|
+
shaka.log.debug('Using full mime type', extendedType);
|
|
2083
|
+
|
|
2084
|
+
return extendedType;
|
|
2085
|
+
}
|
|
2057
2086
|
};
|
|
2058
2087
|
|
|
2059
2088
|
|