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.
Files changed (114) hide show
  1. package/AUTHORS +2 -0
  2. package/CONTRIBUTORS +2 -0
  3. package/LICENSE +54 -0
  4. package/README.md +22 -1
  5. package/dist/controls.css +1 -1
  6. package/dist/controls.css.map +1 -1
  7. package/dist/deps.js +34 -23
  8. package/dist/locales.js +41 -31
  9. package/dist/shaka-player.compiled.d.ts +196 -45
  10. package/dist/shaka-player.compiled.debug.d.ts +196 -45
  11. package/dist/shaka-player.compiled.debug.externs.js +491 -348
  12. package/dist/shaka-player.compiled.debug.js +969 -847
  13. package/dist/shaka-player.compiled.debug.map +4 -4
  14. package/dist/shaka-player.compiled.externs.js +491 -348
  15. package/dist/shaka-player.compiled.js +1238 -1171
  16. package/dist/shaka-player.compiled.map +4 -4
  17. package/dist/shaka-player.ui.d.ts +259 -48
  18. package/dist/shaka-player.ui.debug.d.ts +259 -48
  19. package/dist/shaka-player.ui.debug.externs.js +598 -377
  20. package/dist/shaka-player.ui.debug.js +1185 -980
  21. package/dist/shaka-player.ui.debug.map +4 -4
  22. package/dist/shaka-player.ui.externs.js +598 -377
  23. package/dist/shaka-player.ui.js +1526 -1408
  24. package/dist/shaka-player.ui.map +4 -4
  25. package/externs/device_sensor_event.js +18 -0
  26. package/externs/hisense.js +16 -0
  27. package/externs/playstation.js +31 -0
  28. package/externs/shaka/abr_manager.js +3 -1
  29. package/externs/shaka/ads.js +5 -0
  30. package/externs/shaka/codecs.js +13 -0
  31. package/externs/shaka/manifest.js +58 -6
  32. package/externs/shaka/net.js +13 -1
  33. package/externs/shaka/player.js +184 -13
  34. package/externs/shaka/text.js +19 -0
  35. package/externs/tizen.js +41 -0
  36. package/externs/webos.js +19 -0
  37. package/externs/xbox.js +43 -0
  38. package/lib/abr/simple_abr_manager.js +9 -1
  39. package/lib/ads/ad_manager.js +19 -2
  40. package/lib/ads/client_side_ad_manager.js +5 -3
  41. package/lib/ads/media_tailor_ad_manager.js +9 -0
  42. package/lib/cast/cast_receiver.js +0 -14
  43. package/lib/cast/cast_utils.js +1 -0
  44. package/lib/cea/mp4_cea_parser.js +6 -1
  45. package/lib/dash/content_protection.js +97 -53
  46. package/lib/dash/dash_parser.js +237 -177
  47. package/lib/dash/mpd_utils.js +72 -65
  48. package/lib/dash/segment_base.js +27 -21
  49. package/lib/dash/segment_list.js +18 -16
  50. package/lib/dash/segment_template.js +45 -22
  51. package/lib/hls/hls_parser.js +358 -164
  52. package/lib/media/buffering_observer.js +25 -1
  53. package/lib/media/drm_engine.js +91 -71
  54. package/lib/media/manifest_filterer.js +220 -0
  55. package/lib/media/media_source_engine.js +34 -5
  56. package/lib/media/preload_manager.js +827 -0
  57. package/lib/media/presentation_timeline.js +11 -0
  58. package/lib/media/segment_index.js +93 -47
  59. package/lib/media/segment_prefetch.js +140 -43
  60. package/lib/media/segment_reference.js +43 -8
  61. package/lib/media/segment_utils.js +208 -25
  62. package/lib/media/streaming_engine.js +355 -66
  63. package/lib/mss/content_protection.js +31 -25
  64. package/lib/mss/mss_parser.js +79 -55
  65. package/lib/net/networking_engine.js +49 -4
  66. package/lib/offline/storage.js +3 -3
  67. package/lib/player.js +882 -672
  68. package/lib/polyfill/media_capabilities.js +16 -1
  69. package/lib/text/simple_text_displayer.js +9 -0
  70. package/lib/text/stub_text_displayer.js +7 -0
  71. package/lib/text/text_engine.js +16 -3
  72. package/lib/text/ttml_text_parser.js +116 -114
  73. package/lib/text/ui_text_displayer.js +28 -11
  74. package/lib/text/vtt_text_parser.js +54 -115
  75. package/lib/transmuxer/opus.js +91 -0
  76. package/lib/transmuxer/ts_transmuxer.js +115 -0
  77. package/lib/util/cmcd_manager.js +153 -3
  78. package/lib/util/dom_utils.js +11 -0
  79. package/lib/util/error.js +5 -0
  80. package/lib/util/fake_event.js +2 -0
  81. package/lib/util/manifest_parser_utils.js +97 -3
  82. package/lib/util/mp4_box_parsers.js +346 -17
  83. package/lib/util/mp4_generator.js +23 -0
  84. package/lib/util/periods.js +19 -0
  85. package/lib/util/platform.js +161 -9
  86. package/lib/util/player_configuration.js +79 -13
  87. package/lib/util/stats.js +16 -0
  88. package/lib/util/stream_utils.js +22 -8
  89. package/lib/util/string_utils.js +36 -0
  90. package/lib/util/tXml.js +814 -0
  91. package/lib/util/ts_parser.js +50 -0
  92. package/lib/util/xml_utils.js +10 -338
  93. package/package.json +2 -2
  94. package/shaka-player.uncompiled.js +8 -0
  95. package/ui/controls.js +187 -5
  96. package/ui/enums.js +2 -0
  97. package/ui/externs/ui.js +30 -3
  98. package/ui/gl_matrix/matrix_4x4.js +568 -0
  99. package/ui/gl_matrix/matrix_quaternion.js +45 -0
  100. package/ui/less/containers.less +11 -0
  101. package/ui/locales/en-GB.json +2 -0
  102. package/ui/locales/en.json +2 -0
  103. package/ui/locales/es-419.json +2 -0
  104. package/ui/locales/es.json +2 -0
  105. package/ui/locales/source.json +8 -0
  106. package/ui/recenter_vr.js +126 -0
  107. package/ui/resolution_selection.js +25 -5
  108. package/ui/statistics_button.js +12 -0
  109. package/ui/text_selection.js +1 -1
  110. package/ui/toggle_stereoscopic.js +127 -0
  111. package/ui/ui.js +74 -38
  112. package/ui/vr_manager.js +557 -0
  113. package/ui/vr_utils.js +232 -0
  114. 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
- return oldState != newState;
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
  /**
@@ -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, updateExpirationTime = 1) {
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
- }).tickEvery(/* seconds= */ updateExpirationTime);
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 StringUtils = shaka.util.StringUtils;
1219
- const Uint8ArrayUtils = shaka.util.Uint8ArrayUtils;
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.onError_(shakaErr);
1563
- if (metadata && metadata.updatePromise) {
1564
- metadata.updatePromise.reject(shakaErr);
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 = shaka.util.XmlUtils.parseXmlString(xml, 'PlayReadyKeyMessage');
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 = dom.getElementsByTagName('HttpHeader');
1627
+ const headers = TXml.getElementsByTagName(dom, 'HttpHeader');
1658
1628
  for (const header of headers) {
1659
- const name = header.getElementsByTagName('name')[0];
1660
- const value = header.getElementsByTagName('value')[0];
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[name.textContent] = value.textContent;
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 = dom.getElementsByTagName('Challenge')[0];
1667
- goog.asserts.assert(challenge, 'Malformed PlayReady challenge!');
1668
- goog.asserts.assert(challenge.getAttribute('encoding') == 'base64encoded',
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(challenge.textContent);
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 (!licenseServers.includes(drmInfo.licenseServerUri)) {
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 = mimeType + this.config_.sourceBufferExtraFeatures;
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 = mimeType + this.config_.sourceBufferExtraFeatures;
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
- this.eventManager_.listen(sourceBuffers, 'addsourcebuffer', (event) => {
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
- this.destroyer_.ensureNotDestroyed();
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