shaka-player 3.3.10 → 3.3.12

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 (59) hide show
  1. package/.github/workflows/build-and-test.yaml +42 -22
  2. package/.github/workflows/compute-incremental-coverage.py +14 -3
  3. package/.github/workflows/custom-actions/set-commit-status/action.yaml +1 -5
  4. package/.github/workflows/deflake.yaml +28 -0
  5. package/.github/workflows/demo-version-index.yaml +9 -1
  6. package/.github/workflows/nightly-demo.yaml +1 -1
  7. package/.github/workflows/release-please.yaml +4 -4
  8. package/.github/workflows/report-incremental-coverage.yaml +1 -1
  9. package/.github/workflows/selenium-lab-tests.yaml +21 -10
  10. package/AUTHORS +3 -0
  11. package/CHANGELOG.md +25 -0
  12. package/CONTRIBUTORS +2 -0
  13. package/dist/deps.js +3 -3
  14. package/dist/locales.js +30 -30
  15. package/dist/shaka-player.compiled.d.ts +1 -0
  16. package/dist/shaka-player.compiled.debug.d.ts +1 -0
  17. package/dist/shaka-player.compiled.debug.externs.js +1 -0
  18. package/dist/shaka-player.compiled.debug.js +133 -131
  19. package/dist/shaka-player.compiled.debug.map +4 -4
  20. package/dist/shaka-player.compiled.externs.js +1 -0
  21. package/dist/shaka-player.compiled.js +712 -711
  22. package/dist/shaka-player.compiled.map +4 -4
  23. package/dist/shaka-player.ui.d.ts +1 -0
  24. package/dist/shaka-player.ui.debug.d.ts +1 -0
  25. package/dist/shaka-player.ui.debug.externs.js +1 -0
  26. package/dist/shaka-player.ui.debug.js +197 -195
  27. package/dist/shaka-player.ui.debug.map +4 -4
  28. package/dist/shaka-player.ui.externs.js +1 -0
  29. package/dist/shaka-player.ui.js +830 -829
  30. package/dist/shaka-player.ui.map +4 -4
  31. package/docs/jsdoc-template/publish.js +33 -0
  32. package/docs/jsdoc-template/tmpl/container.tmpl +4 -1
  33. package/docs/jsdoc.conf.json +1 -0
  34. package/docs/tutorials/welcome.md +2 -2
  35. package/lib/ads/client_side_ad_manager.js +5 -2
  36. package/lib/cast/cast_sender.js +39 -14
  37. package/lib/cea/cea608_memory.js +4 -0
  38. package/lib/media/drm_engine.js +4 -2
  39. package/lib/media/streaming_engine.js +41 -4
  40. package/lib/offline/indexeddb/storage_mechanism.js +24 -0
  41. package/lib/player.js +1 -1
  42. package/lib/polyfill/media_capabilities.js +1 -0
  43. package/lib/text/ttml_text_parser.js +69 -87
  44. package/lib/util/error.js +7 -0
  45. package/lib/util/periods.js +9 -9
  46. package/lib/util/platform.js +34 -0
  47. package/lib/util/stream_utils.js +2 -1
  48. package/lib/util/xml_utils.js +45 -6
  49. package/maintained-branches.md +2 -2
  50. package/package.json +1 -1
  51. package/test/cea/cea608_memory_unit.js +88 -0
  52. package/test/media/streaming_engine_integration.js +8 -7
  53. package/test/offline/offline_integration.js +3 -3
  54. package/test/offline/storage_integration.js +30 -0
  55. package/test/test/util/ttml_utils.js +0 -2
  56. package/test/text/ttml_text_parser_unit.js +46 -0
  57. package/test/ui/text_displayer_layout_unit.js +17 -2
  58. package/test/util/periods_unit.js +149 -11
  59. package/test/util/xml_utils_unit.js +64 -0
@@ -440,6 +440,7 @@ function buildNav(members) {
440
440
  [members.externals, 'Externals', seen, linktoExternal],
441
441
  [members.namespaces, 'Namespaces', seen, linkto],
442
442
  [members.classes, 'Classes', seen, linkto],
443
+ [members.enums, 'Enums', seen, linkto],
443
444
  [members.interfaces, 'Interfaces', seen, linkto],
444
445
  [members.events, 'Events', seen, linkto],
445
446
  [members.mixins, 'Mixins', seen, linkto],
@@ -484,6 +485,7 @@ function fixUpType(type) {
484
485
  exports.publish = (taffyData, opts, tutorials) => {
485
486
  let classes;
486
487
  let conf;
488
+ let enums;
487
489
  let externals;
488
490
  let files;
489
491
  let fromDir;
@@ -733,6 +735,31 @@ exports.publish = (taffyData, opts, tutorials) => {
733
735
  });
734
736
 
735
737
  members = helper.getMembers(data);
738
+ members.enums = [];
739
+
740
+ data().each(doclet => {
741
+ if (doclet.isEnum) {
742
+ const hasParent = !!docletMap[doclet.memberof];
743
+
744
+ // Enums without parents (not attached to a class, but directly to a
745
+ // namespace) aren't recognized by jsdoc by default and need to be
746
+ // massaged into the docs separately.
747
+ if (!hasParent) {
748
+ // Temporarily call this a "class", so that we can fix the URL.
749
+ doclet.kind = 'class';
750
+ // Regenerate the URL, so that this enum gets its own page
751
+ // instead of a URL that links to an ID within a non-existent
752
+ // parent page.
753
+ delete helper.longnameToUrl[doclet.longname];
754
+ helper.createLink(doclet);
755
+ // Mark this as an enum (custom type), so that it doesn't get
756
+ // slotted with the classes.
757
+ doclet.kind = 'enum';
758
+ members.enums.push(doclet);
759
+ }
760
+ }
761
+ });
762
+
736
763
  members.tutorials = tutorials.children;
737
764
  if (conf.default.sortTutorialsByConfigOrder) {
738
765
  members.tutorials.sort((a, b) => {
@@ -778,6 +805,7 @@ exports.publish = (taffyData, opts, tutorials) => {
778
805
 
779
806
  // set up the lists that we'll use to generate pages
780
807
  classes = taffy(members.classes);
808
+ enums = taffy(members.enums);
781
809
  modules = taffy(members.modules);
782
810
  namespaces = taffy(members.namespaces);
783
811
  mixins = taffy(members.mixins);
@@ -786,6 +814,7 @@ exports.publish = (taffyData, opts, tutorials) => {
786
814
 
787
815
  Object.keys(helper.longnameToUrl).forEach(longname => {
788
816
  const myClasses = helper.find(classes, {longname: longname});
817
+ const myEnums = helper.find(enums, {longname: longname});
789
818
  const myExternals = helper.find(externals, {longname: longname});
790
819
  const myInterfaces = helper.find(interfaces, {longname: longname});
791
820
  const myMixins = helper.find(mixins, {longname: longname});
@@ -800,6 +829,10 @@ exports.publish = (taffyData, opts, tutorials) => {
800
829
  generate(`Class: ${myClasses[0].longname}`, myClasses, helper.longnameToUrl[longname]);
801
830
  }
802
831
 
832
+ if (myEnums.length) {
833
+ generate(`Enum: ${myEnums[0].longname}`, myEnums, helper.longnameToUrl[longname]);
834
+ }
835
+
803
836
  if (myNamespaces.length) {
804
837
  generate(`Namespace: ${myNamespaces[0].longname}`, myNamespaces, helper.longnameToUrl[longname]);
805
838
  }
@@ -144,7 +144,10 @@
144
144
  return m.longname && m.longname.indexOf('module:') !== 0;
145
145
  });
146
146
  }
147
- if (members && members.length && members.forEach) {
147
+
148
+ // Skip enums, since they already have their values listed under
149
+ // "Properties".
150
+ if (members && members.length && members.forEach && doc.kind != 'enum') {
148
151
  ?>
149
152
  <h3 class="subsection-title">Members</h3>
150
153
 
@@ -16,6 +16,7 @@
16
16
  "navOrder": [
17
17
  "Tutorials",
18
18
  "Classes",
19
+ "Enums",
19
20
  "Interfaces",
20
21
  "Events"
21
22
  ]
@@ -66,8 +66,8 @@ python build/all.py
66
66
 
67
67
  Alternatively you can use a docker container:
68
68
  ```sh
69
- cd build/docker
70
- docker-compose run --rm shaka-compiler
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}
@@ -211,7 +210,11 @@ shaka.ads.ClientSideAdManager = class {
211
210
  // TODO (ismena): Need a better inderstanding of what this does.
212
211
  // The docs say it's called to 'start playing the ads,' but I haven't
213
212
  // seen the ads actually play until requestAds() is called.
214
- this.imaAdsManager_.start();
213
+ // Note: We listen for a play event to avoid autoplay issues that might
214
+ // crash IMA.
215
+ this.video_.addEventListener('play', () => {
216
+ this.imaAdsManager_.start();
217
+ }, {once: true});
215
218
  } catch (adError) {
216
219
  // If there was a problem with the VAST response,
217
220
  // we we won't be getting an ad. Hide ad UI if we showed it already
@@ -186,21 +186,14 @@ shaka.cast.CastSender = class {
186
186
  // If the API becomes available before this instance dies, init will be
187
187
  // called again.
188
188
 
189
- // A note about this value: In our testing environment, we load both
190
- // uncompiled and compiled code. This global callback in uncompiled mode
191
- // can be overwritten by the same in compiled mode. The two versions will
192
- // each have their own instances_ map. Therefore the callback must have a
193
- // name, as opposed to being anonymous. This way, the CastSender tests
194
- // can invoke the named static method instead of using a global that could
195
- // be overwritten.
196
- if (!window.__onGCastApiAvailable) {
197
- window.__onGCastApiAvailable = shaka.cast.CastSender.onSdkLoaded_;
198
- }
199
- if (window.__onGCastApiAvailable != shaka.cast.CastSender.onSdkLoaded_) {
200
- shaka.log.alwaysWarn('A global Cast SDK hook is already installed! ' +
201
- 'Shaka Player will be unable to receive a notification when the ' +
202
- 'Cast SDK is ready.');
189
+ // Check if our callback is already installed.
190
+ if (window.__onGCastApiAvailable !== CastSender.onGCastApiAvailable_) {
191
+ // Save pre-existing __onGCastApiAvailable in order to restore later.
192
+ CastSender.__onGCastApiAvailable_ =
193
+ window.__onGCastApiAvailable || null;
194
+ window.__onGCastApiAvailable = CastSender.onGCastApiAvailable_;
203
195
  }
196
+
204
197
  return;
205
198
  }
206
199
 
@@ -747,6 +740,9 @@ shaka.cast.CastSender.hasReceivers_ = false;
747
740
  /** @private {chrome.cast.Session} */
748
741
  shaka.cast.CastSender.session_ = null;
749
742
 
743
+ /** @private {?function(boolean)} */
744
+ shaka.cast.CastSender.__onGCastApiAvailable_ = null;
745
+
750
746
  /**
751
747
  * A set of all living CastSender instances. The constructor and destroy
752
748
  * methods will add and remove instances from this set.
@@ -774,3 +770,32 @@ shaka.cast.CastSender.onSdkLoaded_ = (loaded) => {
774
770
  }
775
771
  }
776
772
  };
773
+
774
+ /**
775
+ * @param {boolean} available
776
+ * @private
777
+ */
778
+ shaka.cast.CastSender.onGCastApiAvailable_ = (available) => {
779
+ // Restore callback from saved.
780
+ if (shaka.cast.CastSender.__onGCastApiAvailable_) {
781
+ window.__onGCastApiAvailable =
782
+ shaka.cast.CastSender.__onGCastApiAvailable_;
783
+ } else {
784
+ delete window.__onGCastApiAvailable;
785
+ }
786
+ shaka.cast.CastSender.__onGCastApiAvailable_ = null;
787
+
788
+ // A note about this value: In our testing environment, we load both
789
+ // uncompiled and compiled code. This global callback in uncompiled mode
790
+ // can be overwritten by the same in compiled mode. The two versions will
791
+ // each have their own instances_ map. Therefore the callback must have a
792
+ // name, as opposed to being anonymous. This way, the CastSender tests
793
+ // can invoke the named static method instead of using a global that could
794
+ // be overwritten.
795
+ shaka.cast.CastSender.onSdkLoaded_(available);
796
+
797
+ // call restored callback (if any)
798
+ if (typeof window.__onGCastApiAvailable === 'function') {
799
+ window.__onGCastApiAvailable(available);
800
+ }
801
+ };
@@ -184,6 +184,10 @@ shaka.cea.Cea608Memory = class {
184
184
  * @param {number} count Count of rows to move.
185
185
  */
186
186
  moveRows(dst, src, count) {
187
+ if (src < 0 || dst < 0) {
188
+ return;
189
+ }
190
+
187
191
  if (dst >= src) {
188
192
  for (let i = count-1; i >= 0; i--) {
189
193
  this.rows_[dst + i] = this.rows_[src + i].map((e) => e);
@@ -27,6 +27,7 @@ goog.require('shaka.util.StreamUtils');
27
27
  goog.require('shaka.util.StringUtils');
28
28
  goog.require('shaka.util.Timer');
29
29
  goog.require('shaka.util.Uint8ArrayUtils');
30
+ goog.require('shaka.util.XmlUtils');
30
31
 
31
32
 
32
33
  /** @implements {shaka.util.IDestroyable} */
@@ -1439,7 +1440,8 @@ shaka.media.DrmEngine = class {
1439
1440
  return;
1440
1441
  }
1441
1442
  shaka.log.debug('Unwrapping PlayReady request.');
1442
- const dom = new DOMParser().parseFromString(xml, 'application/xml');
1443
+ const dom = shaka.util.XmlUtils.parseXmlString(xml, 'PlayReadyKeyMessage');
1444
+ goog.asserts.assert(dom, 'Failed to parse PlayReady XML!');
1443
1445
 
1444
1446
  // Set request headers.
1445
1447
  const headers = dom.getElementsByTagName('HttpHeader');
@@ -1523,7 +1525,7 @@ shaka.media.DrmEngine = class {
1523
1525
  hasExpiredKeys = true;
1524
1526
  }
1525
1527
 
1526
- const keyIdHex = shaka.util.Uint8ArrayUtils.toHex(keyId);
1528
+ const keyIdHex = shaka.util.Uint8ArrayUtils.toHex(keyId).slice(0, 32);
1527
1529
 
1528
1530
  this.keyStatusByKeyId_.set(keyIdHex, status);
1529
1531
  });
@@ -991,7 +991,11 @@ shaka.media.StreamingEngine = class {
991
991
  const timeUntilEnd =
992
992
  this.manifest_.presentationTimeline.getDuration() - timeNeeded;
993
993
  const oneMicrosecond = 1e-6;
994
- if (timeUntilEnd < oneMicrosecond) {
994
+
995
+ const bufferEnd =
996
+ this.playerInterface_.mediaSourceEngine.bufferEnd(mediaState.type);
997
+
998
+ if (timeUntilEnd < oneMicrosecond && !!bufferEnd) {
995
999
  // We shouldn't rebuffer if the playhead is close to the end of the
996
1000
  // presentation.
997
1001
  shaka.log.debug(logPrefix, 'buffered to end of presentation');
@@ -1021,8 +1025,6 @@ shaka.media.StreamingEngine = class {
1021
1025
  return this.config_.updateIntervalSeconds / 2;
1022
1026
  }
1023
1027
 
1024
- const bufferEnd =
1025
- this.playerInterface_.mediaSourceEngine.bufferEnd(mediaState.type);
1026
1028
  const reference = this.getSegmentReferenceNeeded_(
1027
1029
  mediaState, presentationTime, bufferEnd);
1028
1030
  if (!reference) {
@@ -1218,7 +1220,16 @@ shaka.media.StreamingEngine = class {
1218
1220
  // Enable MP4 low latency streaming with ReadableStream chunked data.
1219
1221
  if (this.config_.lowLatencyMode && isReadableStreamSupported && isMP4) {
1220
1222
  let remaining = new Uint8Array(0);
1223
+ let processingResult = false;
1224
+ let callbackCalled = false;
1221
1225
  const streamDataCallback = async (data) => {
1226
+ if (processingResult) {
1227
+ // If the fallback result processing was triggered, don't also
1228
+ // append the buffer here. In theory this should never happen,
1229
+ // but it does on some older TVs.
1230
+ return;
1231
+ }
1232
+ callbackCalled = true;
1222
1233
  this.destroyer_.ensureNotDestroyed();
1223
1234
  if (this.fatalError_) {
1224
1235
  return;
@@ -1249,7 +1260,33 @@ shaka.media.StreamingEngine = class {
1249
1260
  }
1250
1261
  };
1251
1262
 
1252
- await this.fetch_(mediaState, reference, streamDataCallback);
1263
+ const result =
1264
+ await this.fetch_(mediaState, reference, streamDataCallback);
1265
+ if (!callbackCalled) {
1266
+ // In some environments, we might be forced to use network plugins
1267
+ // that don't support streamDataCallback. In those cases, as a
1268
+ // fallback, append the buffer here.
1269
+ processingResult = true;
1270
+ this.destroyer_.ensureNotDestroyed();
1271
+ if (this.fatalError_) {
1272
+ return;
1273
+ }
1274
+
1275
+ // If the text stream gets switched between fetch_() and append_(),
1276
+ // the new text parser is initialized, but the new init segment is
1277
+ // not fetched yet. That would cause an error in
1278
+ // TextParser.parseMedia().
1279
+ // See http://b/168253400
1280
+ if (mediaState.waitingToClearBuffer) {
1281
+ shaka.log.info(logPrefix, 'waitingToClearBuffer, skip append');
1282
+ mediaState.performingUpdate = false;
1283
+ this.scheduleUpdate_(mediaState, 0);
1284
+ return;
1285
+ }
1286
+
1287
+ await this.append_(
1288
+ mediaState, presentationTime, stream, reference, result);
1289
+ }
1253
1290
  } else {
1254
1291
  if (this.config_.lowLatencyMode && !isReadableStreamSupported) {
1255
1292
  shaka.log.warning('Low latency streaming mode is enabled, but ' +
@@ -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
@@ -6635,7 +6635,7 @@ shaka.Player.TYPICAL_BUFFERING_THRESHOLD_ = 0.5;
6635
6635
  * @define {string} A version number taken from git at compile time.
6636
6636
  * @export
6637
6637
  */
6638
- shaka.Player.version = 'v3.3.10-uncompiled';
6638
+ shaka.Player.version = 'v3.3.12-uncompiled';
6639
6639
 
6640
6640
  // Initialize the deprecation system using the version string we just set
6641
6641
  // on the player.
@@ -43,6 +43,7 @@ shaka.polyfill.MediaCapabilities = class {
43
43
  !shaka.util.Platform.isPS5() &&
44
44
  !shaka.util.Platform.isWebOS() &&
45
45
  !shaka.util.Platform.isTizen() &&
46
+ !shaka.util.Platform.isEOS() &&
46
47
  navigator.mediaCapabilities) {
47
48
  shaka.log.info(
48
49
  'MediaCapabilities: Native mediaCapabilities support found.');
@@ -41,8 +41,6 @@ shaka.text.TtmlTextParser = class {
41
41
  const ttsNs = TtmlTextParser.styleNs_;
42
42
  const str = shaka.util.StringUtils.fromUTF8(data);
43
43
  const cues = [];
44
- const parser = new DOMParser();
45
- let xml = null;
46
44
 
47
45
  // dont try to parse empty string as
48
46
  // DOMParser will not throw error but return an errored xml
@@ -50,9 +48,8 @@ shaka.text.TtmlTextParser = class {
50
48
  return cues;
51
49
  }
52
50
 
53
- try {
54
- xml = parser.parseFromString(str, 'text/xml');
55
- } catch (exception) {
51
+ const tt = XmlUtils.parseXmlString(str, 'tt');
52
+ if (!tt) {
56
53
  throw new shaka.util.Error(
57
54
  shaka.util.Error.Severity.CRITICAL,
58
55
  shaka.util.Error.Category.TEXT,
@@ -60,104 +57,89 @@ shaka.text.TtmlTextParser = class {
60
57
  'Failed to parse TTML.');
61
58
  }
62
59
 
63
- if (xml) {
64
- const parserError = xml.getElementsByTagName('parsererror')[0];
65
- if (parserError) {
66
- throw new shaka.util.Error(
67
- shaka.util.Error.Severity.CRITICAL,
68
- shaka.util.Error.Category.TEXT,
69
- shaka.util.Error.Code.INVALID_XML,
70
- parserError.textContent);
71
- }
72
-
73
- const tt = xml.getElementsByTagName('tt')[0];
74
- // TTML should always have tt element.
75
- if (!tt) {
76
- throw new shaka.util.Error(
77
- shaka.util.Error.Severity.CRITICAL,
78
- shaka.util.Error.Category.TEXT,
79
- shaka.util.Error.Code.INVALID_XML,
80
- 'TTML does not contain <tt> tag.');
81
- }
82
-
83
- const body = tt.getElementsByTagName('body')[0];
84
- if (!body) {
85
- return [];
86
- }
60
+ const body = tt.getElementsByTagName('body')[0];
61
+ if (!body) {
62
+ return [];
63
+ }
87
64
 
88
- // Get the framerate, subFrameRate and frameRateMultiplier if applicable.
89
- const frameRate = XmlUtils.getAttributeNSList(tt, ttpNs, 'frameRate');
90
- const subFrameRate = XmlUtils.getAttributeNSList(
91
- tt, ttpNs, 'subFrameRate');
92
- const frameRateMultiplier =
93
- XmlUtils.getAttributeNSList(tt, ttpNs, 'frameRateMultiplier');
94
- const tickRate = XmlUtils.getAttributeNSList(tt, ttpNs, 'tickRate');
65
+ // Get the framerate, subFrameRate and frameRateMultiplier if applicable.
66
+ const frameRate = XmlUtils.getAttributeNSList(tt, ttpNs, 'frameRate');
67
+ const subFrameRate = XmlUtils.getAttributeNSList(
68
+ tt, ttpNs, 'subFrameRate');
69
+ const frameRateMultiplier =
70
+ XmlUtils.getAttributeNSList(tt, ttpNs, 'frameRateMultiplier');
71
+ const tickRate = XmlUtils.getAttributeNSList(tt, ttpNs, 'tickRate');
95
72
 
96
- const cellResolution = XmlUtils.getAttributeNSList(
97
- tt, ttpNs, 'cellResolution');
98
- const spaceStyle = tt.getAttribute('xml:space') || 'default';
99
- const extent = XmlUtils.getAttributeNSList(tt, ttsNs, 'extent');
73
+ const cellResolution = XmlUtils.getAttributeNSList(
74
+ tt, ttpNs, 'cellResolution');
75
+ const spaceStyle = tt.getAttribute('xml:space') || 'default';
76
+ const extent = XmlUtils.getAttributeNSList(tt, ttsNs, 'extent');
100
77
 
101
- if (spaceStyle != 'default' && spaceStyle != 'preserve') {
102
- throw new shaka.util.Error(
103
- shaka.util.Error.Severity.CRITICAL,
104
- shaka.util.Error.Category.TEXT,
105
- shaka.util.Error.Code.INVALID_XML,
106
- 'Invalid xml:space value: ' + spaceStyle);
107
- }
108
- const whitespaceTrim = spaceStyle == 'default';
78
+ if (spaceStyle != 'default' && spaceStyle != 'preserve') {
79
+ throw new shaka.util.Error(
80
+ shaka.util.Error.Severity.CRITICAL,
81
+ shaka.util.Error.Category.TEXT,
82
+ shaka.util.Error.Code.INVALID_XML,
83
+ 'Invalid xml:space value: ' + spaceStyle);
84
+ }
85
+ const whitespaceTrim = spaceStyle == 'default';
109
86
 
110
- const rateInfo = new TtmlTextParser.RateInfo_(
111
- frameRate, subFrameRate, frameRateMultiplier, tickRate);
87
+ const rateInfo = new TtmlTextParser.RateInfo_(
88
+ frameRate, subFrameRate, frameRateMultiplier, tickRate);
112
89
 
113
- const cellResolutionInfo =
114
- TtmlTextParser.getCellResolution_(cellResolution);
90
+ const cellResolutionInfo =
91
+ TtmlTextParser.getCellResolution_(cellResolution);
115
92
 
116
- const metadata = tt.getElementsByTagName('metadata')[0];
117
- const metadataElements = metadata ? XmlUtils.getChildren(metadata) : [];
118
- const styles = Array.from(tt.getElementsByTagName('style'));
119
- const regionElements = Array.from(tt.getElementsByTagName('region'));
93
+ const metadata = tt.getElementsByTagName('metadata')[0];
94
+ const metadataElements = metadata ? XmlUtils.getChildren(metadata) : [];
95
+ const styles = Array.from(tt.getElementsByTagName('style'));
96
+ const regionElements = Array.from(tt.getElementsByTagName('region'));
120
97
 
121
- const cueRegions = [];
122
- for (const region of regionElements) {
123
- const cueRegion =
124
- TtmlTextParser.parseCueRegion_(region, styles, extent);
125
- if (cueRegion) {
126
- cueRegions.push(cueRegion);
127
- }
98
+ const cueRegions = [];
99
+ for (const region of regionElements) {
100
+ const cueRegion =
101
+ TtmlTextParser.parseCueRegion_(region, styles, extent);
102
+ if (cueRegion) {
103
+ cueRegions.push(cueRegion);
128
104
  }
105
+ }
106
+
107
+ // A <body> element should only contain <div> elements, not <p> or <span>
108
+ // elements. We used to allow this, but it is non-compliant, and the
109
+ // loose nature of our previous parser made it difficult to implement TTML
110
+ // nesting more fully.
111
+ if (XmlUtils.findChildren(body, 'p').length) {
112
+ throw new shaka.util.Error(
113
+ shaka.util.Error.Severity.CRITICAL,
114
+ shaka.util.Error.Category.TEXT,
115
+ shaka.util.Error.Code.INVALID_TEXT_CUE,
116
+ '<p> can only be inside <div> in TTML');
117
+ }
129
118
 
130
- // A <body> element should only contain <div> elements, not <p> or <span>
131
- // elements. We used to allow this, but it is non-compliant, and the
132
- // loose nature of our previous parser made it difficult to implement TTML
133
- // nesting more fully.
134
- if (XmlUtils.findChildren(body, 'p').length) {
119
+ for (const div of XmlUtils.findChildren(body, 'div')) {
120
+ // A <div> element should only contain <p>, not <span>.
121
+ if (XmlUtils.findChildren(div, 'span').length) {
135
122
  throw new shaka.util.Error(
136
123
  shaka.util.Error.Severity.CRITICAL,
137
124
  shaka.util.Error.Category.TEXT,
138
125
  shaka.util.Error.Code.INVALID_TEXT_CUE,
139
- '<p> can only be inside <div> in TTML');
140
- }
141
-
142
- for (const div of XmlUtils.findChildren(body, 'div')) {
143
- // A <div> element should only contain <p>, not <span>.
144
- if (XmlUtils.findChildren(div, 'span').length) {
145
- throw new shaka.util.Error(
146
- shaka.util.Error.Severity.CRITICAL,
147
- shaka.util.Error.Category.TEXT,
148
- shaka.util.Error.Code.INVALID_TEXT_CUE,
149
- '<span> can only be inside <p> in TTML');
150
- }
126
+ '<span> can only be inside <p> in TTML');
151
127
  }
128
+ }
152
129
 
153
- const cue = TtmlTextParser.parseCue_(
154
- body, time.periodStart, rateInfo, metadataElements, styles,
155
- regionElements, cueRegions, whitespaceTrim,
156
- cellResolutionInfo, /* parentCueElement= */ null,
157
- /* isContent= */ false);
158
- if (cue) {
159
- cues.push(cue);
130
+ const cue = TtmlTextParser.parseCue_(
131
+ body, time.periodStart, rateInfo, metadataElements, styles,
132
+ regionElements, cueRegions, whitespaceTrim,
133
+ cellResolutionInfo, /* parentCueElement= */ null,
134
+ /* isContent= */ false);
135
+ if (cue) {
136
+ // According to the TTML spec, backgrounds default to transparent.
137
+ // So default the background of the top-level element to transparent.
138
+ // Nested elements may override that background color already.
139
+ if (!cue.backgroundColor) {
140
+ cue.backgroundColor = 'transparent';
160
141
  }
142
+ cues.push(cue);
161
143
  }
162
144
 
163
145
  return cues;
package/lib/util/error.js CHANGED
@@ -999,6 +999,13 @@ shaka.util.Error.Code = {
999
999
  */
1000
1000
  'MODIFY_OPERATION_NOT_SUPPORTED': 9016,
1001
1001
 
1002
+ /**
1003
+ * When attempting to open an indexedDB instance, nothing happened for long
1004
+ * enough for us to time out. This keeps the storage mechanism from hanging
1005
+ * indefinitely, if neither the success nor error callbacks are called.
1006
+ */
1007
+ 'INDEXED_DB_INIT_TIMED_OUT': 9017,
1008
+
1002
1009
  /**
1003
1010
  * CS IMA SDK, required for ad insertion, has not been included on the page.
1004
1011
  */
@@ -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 =