shaka-player 3.2.9 → 3.2.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 (139) hide show
  1. package/.github/workflows/build-and-test.yaml +67 -15
  2. package/.github/workflows/compute-incremental-coverage.py +251 -0
  3. package/.github/workflows/report-incremental-coverage.yaml +39 -0
  4. package/.github/workflows/selenium-lab-tests.yaml +179 -31
  5. package/CHANGELOG.md +34 -0
  6. package/CONTRIBUTORS +2 -0
  7. package/README.md +20 -6
  8. package/demo/index.html +0 -2
  9. package/dist/controls.css +1 -1
  10. package/dist/controls.css.map +1 -1
  11. package/dist/deps.js +2 -2
  12. package/dist/locales.js +29 -29
  13. package/dist/shaka-player.compiled.d.ts +12 -1
  14. package/dist/shaka-player.compiled.debug.d.ts +12 -1
  15. package/dist/shaka-player.compiled.debug.externs.js +49 -38
  16. package/dist/shaka-player.compiled.debug.js +105 -96
  17. package/dist/shaka-player.compiled.debug.map +4 -4
  18. package/dist/shaka-player.compiled.externs.js +49 -38
  19. package/dist/shaka-player.compiled.js +759 -755
  20. package/dist/shaka-player.compiled.map +4 -4
  21. package/dist/shaka-player.ui.d.ts +12 -1
  22. package/dist/shaka-player.ui.debug.d.ts +12 -1
  23. package/dist/shaka-player.ui.debug.externs.js +49 -38
  24. package/dist/shaka-player.ui.debug.js +119 -110
  25. package/dist/shaka-player.ui.debug.map +4 -4
  26. package/dist/shaka-player.ui.externs.js +49 -38
  27. package/dist/shaka-player.ui.js +866 -863
  28. package/dist/shaka-player.ui.map +4 -4
  29. package/docs/tutorials/welcome.md +6 -3
  30. package/karma.conf.js +3 -11
  31. package/lib/dash/dash_parser.js +5 -0
  32. package/lib/hls/hls_parser.js +6 -2
  33. package/lib/media/drm_engine.js +1 -1
  34. package/lib/media/presentation_timeline.js +24 -1
  35. package/lib/media/streaming_engine.js +1 -0
  36. package/lib/player.js +7 -6
  37. package/lib/polyfill/media_capabilities.js +3 -0
  38. package/lib/polyfill/videoplaybackquality.js +3 -1
  39. package/lib/text/cue.js +0 -5
  40. package/lib/text/simple_text_displayer.js +21 -9
  41. package/lib/text/ui_text_displayer.js +91 -19
  42. package/lib/util/buffer_utils.js +3 -0
  43. package/lib/util/object_utils.js +1 -0
  44. package/lib/util/platform.js +41 -1
  45. package/lib/util/stream_utils.js +23 -3
  46. package/lib/util/string_utils.js +102 -13
  47. package/maintained-branches.md +15 -0
  48. package/package.json +3 -5
  49. package/test/player_src_equals_integration.js +2 -0
  50. package/test/test/assets/small.mp4 +0 -0
  51. package/test/text/simple_text_displayer_unit.js +27 -0
  52. package/test/text/ui_text_displayer_unit.js +65 -0
  53. package/test/util/string_utils_unit.js +46 -8
  54. package/ui/less/ad_controls.less +4 -0
  55. package/app-engine/README.md +0 -9
  56. package/app-engine/demo-version-index/README.md +0 -14
  57. package/app-engine/demo-version-index/app.yaml +0 -2
  58. package/app-engine/demo-version-index/main.py +0 -162
  59. package/app-engine/demo-version-index/requirements.txt +0 -6
  60. package/app-engine/demo-version-index/templates/index.html +0 -114
  61. package/app-engine/shaka-player-demo/README.md +0 -18
  62. package/app-engine/shaka-player-demo/app.yaml +0 -61
  63. package/app-engine/shaka-player-demo/main.py +0 -95
  64. package/app-engine/shaka-player-demo/posters/10yo.png +0 -0
  65. package/app-engine/shaka-player-demo/posters/10yo.xcf +0 -0
  66. package/app-engine/shaka-player-demo/posters/1yo.jpg +0 -0
  67. package/app-engine/shaka-player-demo/posters/2yo.png +0 -0
  68. package/app-engine/shaka-player-demo/posters/2yo.xcf +0 -0
  69. package/app-engine/shaka-player-demo/posters/3yo.png +0 -0
  70. package/app-engine/shaka-player-demo/posters/3yo.xcf +0 -0
  71. package/app-engine/shaka-player-demo/posters/4yo.png +0 -0
  72. package/app-engine/shaka-player-demo/posters/4yo.xcf +0 -0
  73. package/app-engine/shaka-player-demo/posters/5yo.png +0 -0
  74. package/app-engine/shaka-player-demo/posters/5yo.xcf +0 -0
  75. package/app-engine/shaka-player-demo/posters/6yo.png +0 -0
  76. package/app-engine/shaka-player-demo/posters/6yo.xcf +0 -0
  77. package/app-engine/shaka-player-demo/posters/7yo.png +0 -0
  78. package/app-engine/shaka-player-demo/posters/7yo.xcf +0 -0
  79. package/app-engine/shaka-player-demo/posters/8yo.png +0 -0
  80. package/app-engine/shaka-player-demo/posters/8yo.xcf +0 -0
  81. package/app-engine/shaka-player-demo/posters/9yo.png +0 -0
  82. package/app-engine/shaka-player-demo/posters/9yo.xcf +0 -0
  83. package/app-engine/shaka-player-demo/posters/audioOnly.gif +0 -0
  84. package/app-engine/shaka-player-demo/posters/audioRickRoll.gif +0 -0
  85. package/app-engine/shaka-player-demo/posters/colorbars.gif +0 -0
  86. package/app-engine/shaka-player-demo/posters/fireworks.jpg +0 -0
  87. package/app-engine/shaka-player-demo/posters/party-hat.png +0 -0
  88. package/app-engine/shaka-player-demo/posters/sources.txt +0 -46
  89. package/app-engine/shaka-player-demo/posters/standard.png +0 -0
  90. package/app-engine/shaka-player-demo/posters/trek.jpg +0 -0
  91. package/app-engine/shaka-player-demo/posters/valentines.png +0 -0
  92. package/app-engine/shaka-player-demo/posters/zombie.jpg +0 -0
  93. package/app-engine/shaka-player-demo/posters.py +0 -110
  94. package/app-engine/shaka-player-demo/requirements.txt +0 -5
  95. package/app-engine/shaka-player-demo/time.txt +0 -1
  96. package/build/README.md +0 -127
  97. package/build/__init__.py +0 -0
  98. package/build/all.py +0 -150
  99. package/build/apps.py +0 -186
  100. package/build/build.py +0 -392
  101. package/build/check.py +0 -354
  102. package/build/checkversion.py +0 -84
  103. package/build/compiler.py +0 -513
  104. package/build/conformance.textproto +0 -321
  105. package/build/docker/Dockerfile +0 -20
  106. package/build/docker/docker-compose.yml +0 -10
  107. package/build/docs.py +0 -54
  108. package/build/eslint-plugin-shaka-rules/arg-comment-spacing.js +0 -72
  109. package/build/eslint-plugin-shaka-rules/index.js +0 -24
  110. package/build/eslint-plugin-shaka-rules/package.json +0 -7
  111. package/build/eslint-plugin-shaka-rules/private.js +0 -109
  112. package/build/gendeps.py +0 -71
  113. package/build/generateExterns.js +0 -867
  114. package/build/generateLocalizations.py +0 -262
  115. package/build/generateTsDefs.py +0 -137
  116. package/build/install-linux-prereqs.sh +0 -136
  117. package/build/license-header +0 -5
  118. package/build/misspellings.txt +0 -38
  119. package/build/pixelsChanged.js +0 -35
  120. package/build/shaka-lab.yaml +0 -163
  121. package/build/shakaBuildHelpers.py +0 -356
  122. package/build/stats.py +0 -816
  123. package/build/subprocessWindowsPatch.py +0 -103
  124. package/build/test.py +0 -541
  125. package/build/types/ads +0 -8
  126. package/build/types/cast +0 -6
  127. package/build/types/cea +0 -13
  128. package/build/types/complete +0 -9
  129. package/build/types/core +0 -106
  130. package/build/types/dash +0 -8
  131. package/build/types/hls +0 -9
  132. package/build/types/manifests +0 -5
  133. package/build/types/networking +0 -6
  134. package/build/types/offline +0 -21
  135. package/build/types/polyfill +0 -19
  136. package/build/types/text +0 -11
  137. package/build/types/ui +0 -37
  138. package/build/updateScreenshots.py +0 -85
  139. package/build/wrapper.template.js +0 -52
@@ -98,11 +98,14 @@ You can find a full list of available browsers with `--browsers help`, and you
98
98
  can find a complete list of testing options with `--help`.
99
99
 
100
100
 
101
- #### Join the announcement list
101
+ #### Announcements
102
102
 
103
- If you want to receive release or survey announcements, you should join our
103
+ To subscribe to new releases on GitHub, you can follow
104
+ [instructions from this blog](https://www.jessesquires.com/blog/2020/07/30/github-tip-watching-releases/).
105
+
106
+ To receive infrequent announcements and surveys from us, you can join our
104
107
  [mailing list](https://groups.google.com/forum/#!forum/shaka-player-users).
105
- The list is very low volume.
108
+ The list is very low volume, and can only be written to by us.
106
109
 
107
110
 
108
111
  #### Continue the Tutorials
package/karma.conf.js CHANGED
@@ -160,10 +160,6 @@ module.exports = (config) => {
160
160
  'node_modules/es6-promise-polyfill/promise.js',
161
161
  // Babel polyfill, required for async/await
162
162
  'node_modules/@babel/polyfill/dist/polyfill.js',
163
- // TextDecoder polyfill, required for TextDecoder/TextEncoder on IE and
164
- // legacy Edge
165
- // eslint-disable-next-line max-len
166
- 'node_modules/fastestsmallesttextencoderdecoder/EncoderDecoderTogether.min.js',
167
163
 
168
164
  // muxjs module next
169
165
  'node_modules/mux.js/dist/mux.min.js',
@@ -306,13 +302,6 @@ module.exports = (config) => {
306
302
  // Force failure when running empty test-suites.
307
303
  failOnEmptyTestSuite: true,
308
304
 
309
- coverageReporter: {
310
- includeAllSources: true,
311
- reporters: [
312
- {type: 'text'},
313
- ],
314
- },
315
-
316
305
  specReporter: {
317
306
  suppressSkipped: true,
318
307
  showBrowser: true,
@@ -405,9 +394,12 @@ module.exports = (config) => {
405
394
 
406
395
  config.set({
407
396
  coverageReporter: {
397
+ includeAllSources: true,
408
398
  reporters: [
409
399
  {type: 'html', dir: 'coverage'},
410
400
  {type: 'cobertura', dir: 'coverage', file: 'coverage.xml'},
401
+ {type: 'json-summary', dir: 'coverage', file: 'coverage.json'},
402
+ {type: 'json', dir: 'coverage', file: 'coverage-details.json'},
411
403
  ],
412
404
  },
413
405
  });
@@ -503,6 +503,11 @@ shaka.dash.DashParser = class {
503
503
  }
504
504
  presentationTimeline.setClockOffset(offset);
505
505
  }
506
+
507
+ // This is the first point where we have a meaningful presentation start
508
+ // time, and we need to tell PresentationTimeline that so that it can
509
+ // maintain consistency from here on.
510
+ presentationTimeline.lockStartTime();
506
511
  } else {
507
512
  // Just update the variants and text streams, which may change as periods
508
513
  // are added or removed.
@@ -547,8 +547,14 @@ shaka.hls.HlsParser = class {
547
547
  for (const stream of streamsToNotify) {
548
548
  this.segmentsToNotifyByStream_.push(stream.segmentIndex.references);
549
549
  }
550
+
550
551
  this.notifySegments_();
551
552
 
553
+ // This is the first point where we have a meaningful presentation start
554
+ // time, and we need to tell PresentationTimeline that so that it can
555
+ // maintain consistency from here on.
556
+ this.presentationTimeline_.lockStartTime();
557
+
552
558
  // This asserts that the live edge is being calculated from segment times.
553
559
  // For VOD and event streams, this check should still pass.
554
560
  goog.asserts.assert(
@@ -1660,8 +1666,6 @@ shaka.hls.HlsParser = class {
1660
1666
  /* presentationStartTime= */ null, /* delay= */ 0);
1661
1667
  this.presentationTimeline_.setStatic(true);
1662
1668
  }
1663
-
1664
- this.notifySegments_();
1665
1669
  }
1666
1670
 
1667
1671
  /**
@@ -1495,7 +1495,7 @@ shaka.media.DrmEngine = class {
1495
1495
  if (shaka.media.DrmEngine.isPlayReadyKeySystem(
1496
1496
  this.currentDrmInfo_.keySystem) &&
1497
1497
  keyId.byteLength == 16 &&
1498
- shaka.util.Platform.isEdge()) {
1498
+ (shaka.util.Platform.isEdge() || shaka.util.Platform.isPS4())) {
1499
1499
  // Read out some fields in little-endian:
1500
1500
  const dataView = shaka.util.BufferUtils.toDataView(keyId);
1501
1501
  const part0 = dataView.getUint32(0, /* LE= */ true);
@@ -93,6 +93,9 @@ shaka.media.PresentationTimeline = class {
93
93
  * @private {number}
94
94
  */
95
95
  this.availabilityTimeOffset_ = 0;
96
+
97
+ /** @private {boolean} */
98
+ this.startTimeLocked_ = false;
96
99
  }
97
100
 
98
101
 
@@ -229,7 +232,8 @@ shaka.media.PresentationTimeline = class {
229
232
  this.maxSegmentEndTime_ =
230
233
  Math.max(this.maxSegmentEndTime_, lastReferenceEndTime);
231
234
 
232
- if (this.presentationStartTime_ != null && this.autoCorrectDrift_) {
235
+ if (this.presentationStartTime_ != null && this.autoCorrectDrift_ &&
236
+ !this.startTimeLocked_) {
233
237
  // Since we have explicit segment end times, calculate a presentation
234
238
  // start based on them. This start time accounts for drift.
235
239
  // Date.now() is in milliseconds, from which we compute "now" in seconds.
@@ -243,6 +247,25 @@ shaka.media.PresentationTimeline = class {
243
247
  }
244
248
 
245
249
 
250
+ /**
251
+ * Lock the presentation timeline's start time. After this is called, no
252
+ * further adjustments to presentationStartTime_ will be permitted.
253
+ *
254
+ * This should be called after all Periods have been parsed, and all calls to
255
+ * notifySegments() from the initial manifest parse have been made.
256
+ *
257
+ * Without this, we can get assertion failures in SegmentIndex for certain
258
+ * DAI content. If DAI adds ad segments to the manifest faster than
259
+ * real-time, adjustments to presentationStartTime_ can cause availability
260
+ * windows to jump around on updates.
261
+ *
262
+ * @export
263
+ */
264
+ lockStartTime() {
265
+ this.startTimeLocked_ = true;
266
+ }
267
+
268
+
246
269
  /**
247
270
  * Gives PresentationTimeline a Stream's minimum segment start time.
248
271
  *
@@ -1754,6 +1754,7 @@ shaka.media.StreamingEngine = class {
1754
1754
  *
1755
1755
  * @return {!Promise.<BufferSource>}
1756
1756
  * @private
1757
+ * @suppress {strictMissingProperties}
1757
1758
  */
1758
1759
  async fetch_(mediaState, reference, streamDataCallback) {
1759
1760
  const requestType = shaka.net.NetworkingEngine.RequestType.SEGMENT;
package/lib/player.js CHANGED
@@ -792,14 +792,9 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
792
792
  if (!window.Promise) {
793
793
  shaka.log.alwaysWarn('A Promise implementation or polyfill is required');
794
794
  }
795
- if (!window.TextDecoder || !window.TextEncoder) {
796
- shaka.log.alwaysWarn(
797
- 'A TextDecoder/TextEncoder implementation or polyfill is required');
798
- }
799
795
 
800
796
  // Basic features needed for the library to be usable.
801
797
  const basicSupport = !!window.Promise && !!window.Uint8Array &&
802
- !!window.TextDecoder && !!window.TextEncoder &&
803
798
  // eslint-disable-next-line no-restricted-syntax
804
799
  !!Array.prototype.forEach;
805
800
  if (!basicSupport) {
@@ -4274,6 +4269,12 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
4274
4269
  this.stats_.setBandwidthEstimate(estimate);
4275
4270
  }
4276
4271
 
4272
+ if (this.loadMode_ == shaka.Player.LoadMode.SRC_EQUALS) {
4273
+ this.stats_.setResolution(
4274
+ /* width= */ element.videoWidth || NaN,
4275
+ /* height= */ element.videoHeight || NaN);
4276
+ }
4277
+
4277
4278
  return this.stats_.getBlob();
4278
4279
  }
4279
4280
 
@@ -6430,7 +6431,7 @@ shaka.Player.TYPICAL_BUFFERING_THRESHOLD_ = 0.5;
6430
6431
  * @define {string} A version number taken from git at compile time.
6431
6432
  * @export
6432
6433
  */
6433
- shaka.Player.version = 'v3.2.9-uncompiled';
6434
+ shaka.Player.version = 'v3.2.12-uncompiled';
6434
6435
 
6435
6436
  // Initialize the deprecation system using the version string we just set
6436
6437
  // on the player.
@@ -39,7 +39,10 @@ shaka.polyfill.MediaCapabilities = class {
39
39
  // Browsers.
40
40
  if (!shaka.util.Platform.isChromecast() &&
41
41
  !shaka.util.Platform.isApple() &&
42
+ !shaka.util.Platform.isPS4() &&
42
43
  !shaka.util.Platform.isPS5() &&
44
+ !shaka.util.Platform.isWebOS() &&
45
+ !shaka.util.Platform.isTizen() &&
43
46
  navigator.mediaCapabilities) {
44
47
  shaka.log.info(
45
48
  'MediaCapabilities: Native mediaCapabilities support found.');
@@ -7,6 +7,7 @@
7
7
  goog.provide('shaka.polyfill.VideoPlaybackQuality');
8
8
 
9
9
  goog.require('shaka.polyfill');
10
+ goog.require('shaka.util.Platform');
10
11
 
11
12
 
12
13
  /**
@@ -33,7 +34,8 @@ shaka.polyfill.VideoPlaybackQuality = class {
33
34
  return;
34
35
  }
35
36
 
36
- if ('webkitDroppedFrameCount' in proto) {
37
+ if ('webkitDroppedFrameCount' in proto ||
38
+ shaka.util.Platform.isWebOS3()) {
37
39
  proto.getVideoPlaybackQuality =
38
40
  shaka.polyfill.VideoPlaybackQuality.webkit_;
39
41
  }
package/lib/text/cue.js CHANGED
@@ -41,14 +41,9 @@ shaka.text.Cue = class {
41
41
  */
42
42
  this.endTime = endTime;
43
43
 
44
- // The explicit type here is to work around an apparent compiler bug where
45
- // the compiler seems to get confused about the type. Since we are
46
- // overriding the interface type, it shouldn't be necessary to do this.
47
- // Compiler version 20200517.
48
44
  /**
49
45
  * @override
50
46
  * @exportInterface
51
- * @type {string}
52
47
  */
53
48
  this.payload = payload;
54
49
 
@@ -127,16 +127,28 @@ shaka.text.SimpleTextDisplayer = class {
127
127
  // We don't want to modify the array or objects passed in, since we don't
128
128
  // technically own them. So we build a new array and replace certain items
129
129
  // in it if they need to be flattened.
130
- const flattenedCues = cues.map((cue) => {
131
- if (cue.nestedCues.length) {
132
- const flatCue = cue.clone();
133
- flatCue.nestedCues = [];
134
- flatCue.payload = flattenPayload(cue);
135
- return flatCue;
136
- } else {
137
- return cue;
130
+ // We also don't want to flatten the text payloads starting at a container
131
+ // element; otherwise, for containers encapsulating multiple caption lines,
132
+ // the lines would merge into a single cue. This is undesirable when a
133
+ // subset of the captions are outside of the append time window. To fix
134
+ // this, we only call flattenPayload() starting at elements marked as
135
+ // isContainer = false.
136
+ const getCuesToFlatten = (cues, result) => {
137
+ for (const cue of cues) {
138
+ if (cue.isContainer) {
139
+ // Recurse to find the actual text payload cues.
140
+ getCuesToFlatten(cue.nestedCues, result);
141
+ } else {
142
+ // Flatten the payload.
143
+ const flatCue = cue.clone();
144
+ flatCue.nestedCues = [];
145
+ flatCue.payload = flattenPayload(cue);
146
+ result.push(flatCue);
147
+ }
138
148
  }
139
- });
149
+ return result;
150
+ };
151
+ const flattenedCues = getCuesToFlatten(cues, []);
140
152
 
141
153
  // Convert cues.
142
154
  const textTrackCues = [];
@@ -78,6 +78,7 @@ shaka.text.UITextDisplayer = class {
78
78
  * the cue (e.g. the HTML element to put nested cues inside).
79
79
  * @private {Map.<!shaka.extern.Cue, !{
80
80
  * cueElement: !HTMLElement,
81
+ * regionElement: HTMLElement,
81
82
  * wrapper: !HTMLElement
82
83
  * }>}
83
84
  */
@@ -98,6 +99,9 @@ shaka.text.UITextDisplayer = class {
98
99
  });
99
100
  this.resizeObserver_.observe(this.textContainer_);
100
101
  }
102
+
103
+ /** @private {Map.<string, !HTMLElement>} */
104
+ this.regionElements_ = new Map();
101
105
  }
102
106
 
103
107
 
@@ -235,6 +239,11 @@ shaka.text.UITextDisplayer = class {
235
239
  // even ones which are going to be planted again.
236
240
  toUproot.push(cueRegistry.cueElement);
237
241
 
242
+ // Also uproot all displayed region elements.
243
+ if (cueRegistry.regionElement) {
244
+ toUproot.push(cueRegistry.regionElement);
245
+ }
246
+
238
247
  // If the cue should not be displayed, remove it entirely.
239
248
  if (!shouldBeDisplayed) {
240
249
  // Since something has to be removed, we will need to update the DOM.
@@ -269,8 +278,12 @@ shaka.text.UITextDisplayer = class {
269
278
  goog.asserts.assert(topCue == cue, 'Parent cues should be kept in order');
270
279
  }
271
280
  if (updateDOM) {
272
- for (const cueElement of toUproot) {
273
- container.removeChild(cueElement);
281
+ for (const element of toUproot) {
282
+ // NOTE: Because we uproot shared region elements, too, we might hit an
283
+ // element here that has no parent because we've already processed it.
284
+ if (element.parentElement) {
285
+ element.parentElement.removeChild(element);
286
+ }
274
287
  }
275
288
  toPlant.sort((a, b) => {
276
289
  if (a.startTime != b.startTime) {
@@ -282,7 +295,12 @@ shaka.text.UITextDisplayer = class {
282
295
  for (const cue of toPlant) {
283
296
  const cueRegistry = this.currentCuesMap_.get(cue);
284
297
  goog.asserts.assert(cueRegistry, 'cueRegistry should exist.');
285
- container.appendChild(cueRegistry.cueElement);
298
+ if (cueRegistry.regionElement) {
299
+ container.appendChild(cueRegistry.regionElement);
300
+ cueRegistry.regionElement.appendChild(cueRegistry.cueElement);
301
+ } else {
302
+ container.appendChild(cueRegistry.cueElement);
303
+ }
286
304
  }
287
305
  }
288
306
  }
@@ -299,11 +317,15 @@ shaka.text.UITextDisplayer = class {
299
317
 
300
318
  const currentTime = this.video_.currentTime;
301
319
  if (!this.isTextVisible_ || forceUpdate) {
302
- if (this.currentCuesMap_.size > 0) {
303
- // Clear away any existing cues.
304
- shaka.util.Dom.removeAllChildren(this.textContainer_);
305
- this.currentCuesMap_.clear();
320
+ // Remove child elements from all regions.
321
+ for (const regionElement of this.regionElements_.values()) {
322
+ shaka.util.Dom.removeAllChildren(regionElement);
306
323
  }
324
+ // Remove all top-level elements in the text container.
325
+ shaka.util.Dom.removeAllChildren(this.textContainer_);
326
+ // Clear the element maps.
327
+ this.currentCuesMap_.clear();
328
+ this.regionElements_.clear();
307
329
  }
308
330
  if (this.isTextVisible_) {
309
331
  // Log currently attached cue elements for verification, later.
@@ -333,6 +355,54 @@ shaka.text.UITextDisplayer = class {
333
355
  }
334
356
  }
335
357
 
358
+ /**
359
+ * Get or create a region element corresponding to the cue region. These are
360
+ * cached by ID.
361
+ *
362
+ * @param {!shaka.extern.Cue} cue
363
+ * @return {!HTMLElement}
364
+ * @private
365
+ */
366
+ getRegionElement_(cue) {
367
+ const region = cue.region;
368
+
369
+ if (this.regionElements_.has(region.id)) {
370
+ return this.regionElements_.get(region.id);
371
+ }
372
+
373
+ const regionElement = shaka.util.Dom.createHTMLElement('span');
374
+
375
+ const percentageUnit = shaka.text.CueRegion.units.PERCENTAGE;
376
+ const heightUnit = region.heightUnits == percentageUnit ? '%' : 'px';
377
+ const widthUnit = region.widthUnits == percentageUnit ? '%' : 'px';
378
+ const viewportAnchorUnit =
379
+ region.viewportAnchorUnits == percentageUnit ? '%' : 'px';
380
+
381
+ regionElement.id = 'shaka-text-region---' + region.id;
382
+ regionElement.classList.add('shaka-text-region');
383
+
384
+ regionElement.style.height = region.height + heightUnit;
385
+ regionElement.style.width = region.width + widthUnit;
386
+ regionElement.style.position = 'absolute';
387
+ regionElement.style.top = region.viewportAnchorY + viewportAnchorUnit;
388
+ regionElement.style.left = region.viewportAnchorX + viewportAnchorUnit;
389
+
390
+ regionElement.style.display = 'flex';
391
+ regionElement.style.flexDirection = 'column';
392
+ regionElement.style.alignItems = 'center';
393
+
394
+ if (cue.displayAlign == shaka.text.Cue.displayAlign.BEFORE) {
395
+ regionElement.style.justifyContent = 'flex-start';
396
+ } else if (cue.displayAlign == shaka.text.Cue.displayAlign.CENTER) {
397
+ regionElement.style.justifyContent = 'center';
398
+ } else {
399
+ regionElement.style.justifyContent = 'flex-end';
400
+ }
401
+
402
+ this.regionElements_.set(region.id, regionElement);
403
+ return regionElement;
404
+ }
405
+
336
406
  /**
337
407
  * Creates the object for a cue.
338
408
  *
@@ -360,6 +430,11 @@ shaka.text.UITextDisplayer = class {
360
430
  this.setCaptionStyles_(cueElement, cue, parents, needWrapper);
361
431
  }
362
432
 
433
+ let regionElement = null;
434
+ if (cue.region && cue.region.id) {
435
+ regionElement = this.getRegionElement_(cue);
436
+ }
437
+
363
438
  let wrapper = cueElement;
364
439
  if (needWrapper) {
365
440
  // Create a wrapper element which will serve to contain all children into
@@ -371,7 +446,7 @@ shaka.text.UITextDisplayer = class {
371
446
  cueElement.appendChild(wrapper);
372
447
  }
373
448
 
374
- this.currentCuesMap_.set(cue, {cueElement, wrapper});
449
+ this.currentCuesMap_.set(cue, {cueElement, wrapper, regionElement});
375
450
  }
376
451
 
377
452
  /**
@@ -423,6 +498,14 @@ shaka.text.UITextDisplayer = class {
423
498
  style.backgroundRepeat = 'no-repeat';
424
499
  style.backgroundSize = 'contain';
425
500
  style.backgroundPosition = 'center';
501
+
502
+ // Quoting https://www.w3.org/TR/ttml-imsc1.2/:
503
+ // "The width and height (in pixels) of the image resource referenced by
504
+ // smpte:backgroundImage SHALL be equal to the width and height expressed
505
+ // by the tts:extent attribute of the region in which the div element is
506
+ // presented".
507
+ style.width = '100%';
508
+ style.height = '100%';
426
509
  } else {
427
510
  // If we have both text and nested cues, then style everything; otherwise
428
511
  // place the text in its own <span> so the background doesn't fill the
@@ -531,17 +614,6 @@ shaka.text.UITextDisplayer = class {
531
614
  }
532
615
  }
533
616
  }
534
- } else if (cue.region && cue.region.id) {
535
- const percentageUnit = shaka.text.CueRegion.units.PERCENTAGE;
536
- const heightUnit = cue.region.heightUnits == percentageUnit ? '%' : 'px';
537
- const widthUnit = cue.region.widthUnits == percentageUnit ? '%' : 'px';
538
- const viewportAnchorUnit =
539
- cue.region.viewportAnchorUnits == percentageUnit ? '%' : 'px';
540
- style.height = cue.region.height + heightUnit;
541
- style.width = cue.region.width + widthUnit;
542
- style.position = 'absolute';
543
- style.top = cue.region.viewportAnchorY + viewportAnchorUnit;
544
- style.left = cue.region.viewportAnchorX + viewportAnchorUnit;
545
617
  }
546
618
 
547
619
  style.lineHeight = cue.lineHeight;
@@ -20,6 +20,7 @@ shaka.util.BufferUtils = class {
20
20
  * @param {?BufferSource} arr2
21
21
  * @return {boolean}
22
22
  * @export
23
+ * @suppress {strictMissingProperties}
23
24
  */
24
25
  static equal(arr1, arr2) {
25
26
  const BufferUtils = shaka.util.BufferUtils;
@@ -135,8 +136,10 @@ shaka.util.BufferUtils = class {
135
136
  static view_(data, offset, length, Type) {
136
137
  const buffer = shaka.util.BufferUtils.unsafeGetArrayBuffer_(data);
137
138
  // Absolute end of the |data| view within |buffer|.
139
+ /** @suppress {strictMissingProperties} */
138
140
  const dataEnd = (data.byteOffset || 0) + data.byteLength;
139
141
  // Absolute start of the result within |buffer|.
142
+ /** @suppress {strictMissingProperties} */
140
143
  const rawStart = (data.byteOffset || 0) + offset;
141
144
  const start = Math.max(0, Math.min(rawStart, dataEnd));
142
145
  // Absolute end of the result within |buffer|.
@@ -22,6 +22,7 @@ shaka.util.ObjectUtils = class {
22
22
  const seenObjects = new Set();
23
23
  // This recursively clones the value |val|, using the captured variable
24
24
  // |seenObjects| to track the objects we have already cloned.
25
+ /** @suppress {strictMissingProperties} */
25
26
  const clone = (val) => {
26
27
  switch (typeof val) {
27
28
  case 'undefined':
@@ -142,6 +142,18 @@ shaka.util.Platform = class {
142
142
  return shaka.util.Platform.userAgentContains_('Web0S');
143
143
  }
144
144
 
145
+ /**
146
+ * Check if the current platform is a WebOS 3.
147
+ *
148
+ * @return {boolean}
149
+ */
150
+ static isWebOS3() {
151
+ // See: http://webostv.developer.lge.com/discover/specifications/web-engine/
152
+ return shaka.util.Platform.userAgentContains_('Web0S') &&
153
+ shaka.util.Platform.userAgentContains_(
154
+ 'Chrome/38.0.2125.122 Safari/537.36');
155
+ }
156
+
145
157
  /**
146
158
  * Check if the current platform is a Google Chromecast.
147
159
  *
@@ -175,7 +187,9 @@ shaka.util.Platform = class {
175
187
  */
176
188
  static isApple() {
177
189
  return !!navigator.vendor && navigator.vendor.includes('Apple') &&
178
- !shaka.util.Platform.isTizen();
190
+ !shaka.util.Platform.isTizen() &&
191
+ !shaka.util.Platform.isEOS() &&
192
+ !shaka.util.Platform.isPS4();
179
193
  }
180
194
 
181
195
  /**
@@ -191,6 +205,13 @@ shaka.util.Platform = class {
191
205
  return shaka.util.Platform.userAgentContains_('PlayStation 5');
192
206
  }
193
207
 
208
+ /**
209
+ * Check if the current platform is Playstation 4.
210
+ */
211
+ static isPS4() {
212
+ return shaka.util.Platform.userAgentContains_('PlayStation 4');
213
+ }
214
+
194
215
  /**
195
216
  * Returns a major version number for Safari, or Safari-based iOS browsers.
196
217
  *
@@ -228,6 +249,25 @@ shaka.util.Platform = class {
228
249
  return null;
229
250
  }
230
251
 
252
+ /**
253
+ * Check if the current platform is Apple Safari
254
+ * or Safari-based iOS browsers.
255
+ *
256
+ * @return {boolean}
257
+ */
258
+ static isSafari() {
259
+ return !!shaka.util.Platform.safariVersion();
260
+ }
261
+
262
+ /**
263
+ * Check if the current platform is an EOS set-top box.
264
+ *
265
+ * @return {boolean}
266
+ */
267
+ static isEOS() {
268
+ return shaka.util.Platform.userAgentContains_('PC=EOS');
269
+ }
270
+
231
271
  /**
232
272
  * Guesses if the platform is a mobile one (iOS or Android).
233
273
  *
@@ -578,8 +578,16 @@ shaka.util.StreamUtils = class {
578
578
  // VideoConfiguration
579
579
  mediaDecodingConfig.video = {
580
580
  contentType: fullType,
581
- width: video.width || 1,
582
- height: video.height || 1,
581
+
582
+ // NOTE: Some decoders strictly check the width and height fields and
583
+ // won't decode smaller than 64x64. So if we don't have this info (as
584
+ // is the case in some of our simpler tests), assume a 64x64 resolution
585
+ // to fill in this required field for MediaCapabilities.
586
+ //
587
+ // This became an issue specifically on Firefox on M1 Macs.
588
+ width: video.width || 64,
589
+ height: video.height || 64,
590
+
583
591
  bitrate: video.bandwidth || variant.bandwidth || 1,
584
592
  // framerate must be greater than 0, otherwise the config is invalid.
585
593
  framerate: video.frameRate || 1,
@@ -742,7 +750,19 @@ shaka.util.StreamUtils = class {
742
750
  */
743
751
  static patchVp9(codec) {
744
752
  if (codec == 'vp9') {
745
- return 'vp09.00.10.08';
753
+ // This means profile 0, level 4.1, 8-bit color. This supports 1080p @
754
+ // 60Hz. See https://en.wikipedia.org/wiki/VP9#Levels
755
+ //
756
+ // If we don't have more detailed codec info, assume this profile and
757
+ // level because it's high enough to likely accommodate the parameters we
758
+ // do have, such as width and height. If an implementation is checking
759
+ // the profile and level very strictly, we want older VP9 content to
760
+ // still work to some degree. But we don't want to set a level so high
761
+ // that it is rejected by a hardware decoder that can't handle the
762
+ // maximum requirements of the level.
763
+ //
764
+ // This became an issue specifically on Firefox on M1 Macs.
765
+ return 'vp09.00.41.08';
746
766
  }
747
767
  return codec;
748
768
  }