shaka-player 4.1.1 → 4.2.0

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 (179) 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 +44 -1
  5. package/AUTHORS +1 -0
  6. package/CHANGELOG.md +36 -8
  7. package/CONTRIBUTORS +3 -0
  8. package/README.md +20 -6
  9. package/demo/common/assets.js +25 -0
  10. package/demo/common/message_ids.js +2 -0
  11. package/demo/config.js +5 -1
  12. package/demo/index.html +1 -3
  13. package/demo/locales/en.json +2 -0
  14. package/demo/locales/source.json +8 -0
  15. package/dist/controls.css +1 -1
  16. package/dist/demo.compiled.debug.js +25 -23
  17. package/dist/demo.compiled.debug.map +3 -3
  18. package/dist/demo.compiled.js +25 -23
  19. package/dist/demo.compiled.map +3 -3
  20. package/dist/deps.js +6 -5
  21. package/dist/locales.js +30 -30
  22. package/dist/receiver.compiled.debug.js +24 -22
  23. package/dist/receiver.compiled.debug.map +3 -3
  24. package/dist/receiver.compiled.js +24 -22
  25. package/dist/receiver.compiled.map +3 -3
  26. package/dist/shaka-player.compiled.d.ts +69 -5
  27. package/dist/shaka-player.compiled.debug.d.ts +69 -5
  28. package/dist/shaka-player.compiled.debug.externs.js +111 -42
  29. package/dist/shaka-player.compiled.debug.js +352 -301
  30. package/dist/shaka-player.compiled.debug.map +4 -4
  31. package/dist/shaka-player.compiled.externs.js +111 -42
  32. package/dist/shaka-player.compiled.js +823 -802
  33. package/dist/shaka-player.compiled.map +4 -4
  34. package/dist/shaka-player.ui.d.ts +69 -5
  35. package/dist/shaka-player.ui.debug.d.ts +69 -5
  36. package/dist/shaka-player.ui.debug.externs.js +111 -42
  37. package/dist/shaka-player.ui.debug.js +360 -310
  38. package/dist/shaka-player.ui.debug.map +4 -4
  39. package/dist/shaka-player.ui.externs.js +111 -42
  40. package/dist/shaka-player.ui.js +980 -957
  41. package/dist/shaka-player.ui.map +4 -4
  42. package/docs/tutorials/fairplay.md +149 -0
  43. package/docs/tutorials/ui.md +6 -6
  44. package/docs/tutorials/upgrade.md +1 -0
  45. package/docs/tutorials/welcome.md +6 -3
  46. package/externs/shaka/abr_manager.js +8 -0
  47. package/externs/shaka/manifest.js +43 -0
  48. package/externs/shaka/player.js +13 -2
  49. package/karma.conf.js +3 -11
  50. package/lib/abr/simple_abr_manager.js +72 -4
  51. package/lib/ads/client_side_ad_manager.js +1 -1
  52. package/lib/dash/dash_parser.js +5 -0
  53. package/lib/hls/hls_parser.js +123 -27
  54. package/lib/media/drm_engine.js +1 -3
  55. package/lib/media/media_source_engine.js +57 -24
  56. package/lib/media/presentation_timeline.js +24 -1
  57. package/lib/media/segment_index.js +8 -0
  58. package/lib/media/segment_reference.js +7 -1
  59. package/lib/media/streaming_engine.js +42 -6
  60. package/lib/media/time_ranges_utils.js +46 -0
  61. package/lib/net/networking_engine.js +1 -0
  62. package/lib/player.js +8 -6
  63. package/lib/polyfill/media_capabilities.js +9 -0
  64. package/lib/polyfill/patchedmediakeys_apple.js +729 -0
  65. package/lib/polyfill/videoplaybackquality.js +3 -1
  66. package/lib/text/cue.js +0 -5
  67. package/lib/text/simple_text_displayer.js +21 -9
  68. package/lib/text/ui_text_displayer.js +83 -19
  69. package/lib/text/vtt_text_parser.js +61 -4
  70. package/lib/util/buffer_utils.js +3 -0
  71. package/lib/util/error.js +21 -1
  72. package/lib/util/fairplay_utils.js +100 -2
  73. package/lib/util/object_utils.js +1 -0
  74. package/lib/util/platform.js +30 -1
  75. package/lib/util/player_configuration.js +3 -10
  76. package/lib/util/stream_utils.js +23 -3
  77. package/lib/util/string_utils.js +102 -13
  78. package/maintained-branches.md +15 -0
  79. package/package.json +3 -5
  80. package/shaka-player.uncompiled.js +1 -0
  81. package/test/hls/hls_parser_unit.js +49 -33
  82. package/test/media/drm_engine_integration.js +24 -8
  83. package/test/media/media_source_engine_integration.js +24 -10
  84. package/test/media/media_source_engine_unit.js +53 -38
  85. package/test/media/streaming_engine_unit.js +107 -32
  86. package/test/test/util/fake_media_source_engine.js +10 -9
  87. package/test/test/util/simple_fakes.js +4 -0
  88. package/test/test/util/streaming_engine_util.js +10 -2
  89. package/test/text/simple_text_displayer_unit.js +27 -0
  90. package/test/text/ui_text_displayer_unit.js +65 -0
  91. package/test/text/vtt_text_parser_unit.js +26 -0
  92. package/test/util/string_utils_unit.js +46 -8
  93. package/ui/text_selection.js +2 -2
  94. package/app-engine/README.md +0 -9
  95. package/app-engine/demo-version-index/README.md +0 -14
  96. package/app-engine/demo-version-index/app.yaml +0 -28
  97. package/app-engine/demo-version-index/generate.py +0 -164
  98. package/app-engine/demo-version-index/main.py +0 -11
  99. package/app-engine/demo-version-index/requirements.txt +0 -5
  100. package/app-engine/demo-version-index/templates/index.html +0 -114
  101. package/app-engine/shaka-player-demo/README.md +0 -18
  102. package/app-engine/shaka-player-demo/app.yaml +0 -61
  103. package/app-engine/shaka-player-demo/main.py +0 -95
  104. package/app-engine/shaka-player-demo/posters/10yo.png +0 -0
  105. package/app-engine/shaka-player-demo/posters/10yo.xcf +0 -0
  106. package/app-engine/shaka-player-demo/posters/1yo.jpg +0 -0
  107. package/app-engine/shaka-player-demo/posters/2yo.png +0 -0
  108. package/app-engine/shaka-player-demo/posters/2yo.xcf +0 -0
  109. package/app-engine/shaka-player-demo/posters/3yo.png +0 -0
  110. package/app-engine/shaka-player-demo/posters/3yo.xcf +0 -0
  111. package/app-engine/shaka-player-demo/posters/4yo.png +0 -0
  112. package/app-engine/shaka-player-demo/posters/4yo.xcf +0 -0
  113. package/app-engine/shaka-player-demo/posters/5yo.png +0 -0
  114. package/app-engine/shaka-player-demo/posters/5yo.xcf +0 -0
  115. package/app-engine/shaka-player-demo/posters/6yo.png +0 -0
  116. package/app-engine/shaka-player-demo/posters/6yo.xcf +0 -0
  117. package/app-engine/shaka-player-demo/posters/7yo.png +0 -0
  118. package/app-engine/shaka-player-demo/posters/7yo.xcf +0 -0
  119. package/app-engine/shaka-player-demo/posters/8yo.png +0 -0
  120. package/app-engine/shaka-player-demo/posters/8yo.xcf +0 -0
  121. package/app-engine/shaka-player-demo/posters/9yo.png +0 -0
  122. package/app-engine/shaka-player-demo/posters/9yo.xcf +0 -0
  123. package/app-engine/shaka-player-demo/posters/audioOnly.gif +0 -0
  124. package/app-engine/shaka-player-demo/posters/audioRickRoll.gif +0 -0
  125. package/app-engine/shaka-player-demo/posters/colorbars.gif +0 -0
  126. package/app-engine/shaka-player-demo/posters/fireworks.jpg +0 -0
  127. package/app-engine/shaka-player-demo/posters/party-hat.png +0 -0
  128. package/app-engine/shaka-player-demo/posters/sources.txt +0 -46
  129. package/app-engine/shaka-player-demo/posters/standard.png +0 -0
  130. package/app-engine/shaka-player-demo/posters/trek.jpg +0 -0
  131. package/app-engine/shaka-player-demo/posters/valentines.png +0 -0
  132. package/app-engine/shaka-player-demo/posters/zombie.jpg +0 -0
  133. package/app-engine/shaka-player-demo/posters.py +0 -110
  134. package/app-engine/shaka-player-demo/requirements.txt +0 -5
  135. package/app-engine/shaka-player-demo/time.txt +0 -1
  136. package/build/README.md +0 -127
  137. package/build/__init__.py +0 -0
  138. package/build/all.py +0 -150
  139. package/build/apps.py +0 -186
  140. package/build/build.py +0 -392
  141. package/build/check.py +0 -354
  142. package/build/checkversion.py +0 -84
  143. package/build/compiler.py +0 -513
  144. package/build/conformance.textproto +0 -335
  145. package/build/docker/Dockerfile +0 -20
  146. package/build/docker/docker-compose.yml +0 -10
  147. package/build/docs.py +0 -54
  148. package/build/eslint-plugin-shaka-rules/arg-comment-spacing.js +0 -72
  149. package/build/eslint-plugin-shaka-rules/index.js +0 -24
  150. package/build/eslint-plugin-shaka-rules/package.json +0 -7
  151. package/build/eslint-plugin-shaka-rules/private.js +0 -109
  152. package/build/gendeps.py +0 -71
  153. package/build/generateExterns.js +0 -867
  154. package/build/generateLocalizations.py +0 -262
  155. package/build/generateTsDefs.py +0 -137
  156. package/build/install-linux-prereqs.sh +0 -136
  157. package/build/license-header +0 -5
  158. package/build/misspellings.txt +0 -39
  159. package/build/pixelsChanged.js +0 -35
  160. package/build/shaka-lab.yaml +0 -161
  161. package/build/shakaBuildHelpers.py +0 -356
  162. package/build/stats.py +0 -816
  163. package/build/subprocessWindowsPatch.py +0 -103
  164. package/build/test.py +0 -541
  165. package/build/types/ads +0 -8
  166. package/build/types/cast +0 -6
  167. package/build/types/cea +0 -13
  168. package/build/types/complete +0 -9
  169. package/build/types/core +0 -108
  170. package/build/types/dash +0 -8
  171. package/build/types/hls +0 -9
  172. package/build/types/manifests +0 -5
  173. package/build/types/networking +0 -6
  174. package/build/types/offline +0 -22
  175. package/build/types/polyfill +0 -18
  176. package/build/types/text +0 -11
  177. package/build/types/ui +0 -39
  178. package/build/updateScreenshots.py +0 -85
  179. package/build/wrapper.template.js +0 -52
@@ -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
 
@@ -121,16 +121,28 @@ shaka.text.SimpleTextDisplayer = class {
121
121
  // We don't want to modify the array or objects passed in, since we don't
122
122
  // technically own them. So we build a new array and replace certain items
123
123
  // in it if they need to be flattened.
124
- const flattenedCues = cues.map((cue) => {
125
- if (cue.nestedCues.length) {
126
- const flatCue = cue.clone();
127
- flatCue.nestedCues = [];
128
- flatCue.payload = flattenPayload(cue);
129
- return flatCue;
130
- } else {
131
- return cue;
124
+ // We also don't want to flatten the text payloads starting at a container
125
+ // element; otherwise, for containers encapsulating multiple caption lines,
126
+ // the lines would merge into a single cue. This is undesirable when a
127
+ // subset of the captions are outside of the append time window. To fix
128
+ // this, we only call flattenPayload() starting at elements marked as
129
+ // isContainer = false.
130
+ const getCuesToFlatten = (cues, result) => {
131
+ for (const cue of cues) {
132
+ if (cue.isContainer) {
133
+ // Recurse to find the actual text payload cues.
134
+ getCuesToFlatten(cue.nestedCues, result);
135
+ } else {
136
+ // Flatten the payload.
137
+ const flatCue = cue.clone();
138
+ flatCue.nestedCues = [];
139
+ flatCue.payload = flattenPayload(cue);
140
+ result.push(flatCue);
141
+ }
132
142
  }
133
- });
143
+ return result;
144
+ };
145
+ const flattenedCues = getCuesToFlatten(cues, []);
134
146
 
135
147
  // Convert cues.
136
148
  const textTrackCues = [];
@@ -77,6 +77,7 @@ shaka.text.UITextDisplayer = class {
77
77
  * the cue (e.g. the HTML element to put nested cues inside).
78
78
  * @private {Map.<!shaka.extern.Cue, !{
79
79
  * cueElement: !HTMLElement,
80
+ * regionElement: HTMLElement,
80
81
  * wrapper: !HTMLElement
81
82
  * }>}
82
83
  */
@@ -97,6 +98,9 @@ shaka.text.UITextDisplayer = class {
97
98
  });
98
99
  this.resizeObserver_.observe(this.textContainer_);
99
100
  }
101
+
102
+ /** @private {Map.<string, !HTMLElement>} */
103
+ this.regionElements_ = new Map();
100
104
  }
101
105
 
102
106
 
@@ -234,6 +238,11 @@ shaka.text.UITextDisplayer = class {
234
238
  // even ones which are going to be planted again.
235
239
  toUproot.push(cueRegistry.cueElement);
236
240
 
241
+ // Also uproot all displayed region elements.
242
+ if (cueRegistry.regionElement) {
243
+ toUproot.push(cueRegistry.regionElement);
244
+ }
245
+
237
246
  // If the cue should not be displayed, remove it entirely.
238
247
  if (!shouldBeDisplayed) {
239
248
  // Since something has to be removed, we will need to update the DOM.
@@ -268,8 +277,12 @@ shaka.text.UITextDisplayer = class {
268
277
  goog.asserts.assert(topCue == cue, 'Parent cues should be kept in order');
269
278
  }
270
279
  if (updateDOM) {
271
- for (const cueElement of toUproot) {
272
- container.removeChild(cueElement);
280
+ for (const element of toUproot) {
281
+ // NOTE: Because we uproot shared region elements, too, we might hit an
282
+ // element here that has no parent because we've already processed it.
283
+ if (element.parentElement) {
284
+ element.parentElement.removeChild(element);
285
+ }
273
286
  }
274
287
  toPlant.sort((a, b) => {
275
288
  if (a.startTime != b.startTime) {
@@ -281,7 +294,12 @@ shaka.text.UITextDisplayer = class {
281
294
  for (const cue of toPlant) {
282
295
  const cueRegistry = this.currentCuesMap_.get(cue);
283
296
  goog.asserts.assert(cueRegistry, 'cueRegistry should exist.');
284
- container.appendChild(cueRegistry.cueElement);
297
+ if (cueRegistry.regionElement) {
298
+ container.appendChild(cueRegistry.regionElement);
299
+ cueRegistry.regionElement.appendChild(cueRegistry.cueElement);
300
+ } else {
301
+ container.appendChild(cueRegistry.cueElement);
302
+ }
285
303
  }
286
304
  }
287
305
  }
@@ -298,11 +316,15 @@ shaka.text.UITextDisplayer = class {
298
316
 
299
317
  const currentTime = this.video_.currentTime;
300
318
  if (!this.isTextVisible_ || forceUpdate) {
301
- if (this.currentCuesMap_.size > 0) {
302
- // Clear away any existing cues.
303
- shaka.util.Dom.removeAllChildren(this.textContainer_);
304
- this.currentCuesMap_.clear();
319
+ // Remove child elements from all regions.
320
+ for (const regionElement of this.regionElements_.values()) {
321
+ shaka.util.Dom.removeAllChildren(regionElement);
305
322
  }
323
+ // Remove all top-level elements in the text container.
324
+ shaka.util.Dom.removeAllChildren(this.textContainer_);
325
+ // Clear the element maps.
326
+ this.currentCuesMap_.clear();
327
+ this.regionElements_.clear();
306
328
  }
307
329
  if (this.isTextVisible_) {
308
330
  // Log currently attached cue elements for verification, later.
@@ -332,6 +354,54 @@ shaka.text.UITextDisplayer = class {
332
354
  }
333
355
  }
334
356
 
357
+ /**
358
+ * Get or create a region element corresponding to the cue region. These are
359
+ * cached by ID.
360
+ *
361
+ * @param {!shaka.extern.Cue} cue
362
+ * @return {!HTMLElement}
363
+ * @private
364
+ */
365
+ getRegionElement_(cue) {
366
+ const region = cue.region;
367
+
368
+ if (this.regionElements_.has(region.id)) {
369
+ return this.regionElements_.get(region.id);
370
+ }
371
+
372
+ const regionElement = shaka.util.Dom.createHTMLElement('span');
373
+
374
+ const percentageUnit = shaka.text.CueRegion.units.PERCENTAGE;
375
+ const heightUnit = region.heightUnits == percentageUnit ? '%' : 'px';
376
+ const widthUnit = region.widthUnits == percentageUnit ? '%' : 'px';
377
+ const viewportAnchorUnit =
378
+ region.viewportAnchorUnits == percentageUnit ? '%' : 'px';
379
+
380
+ regionElement.id = 'shaka-text-region---' + region.id;
381
+ regionElement.classList.add('shaka-text-region');
382
+
383
+ regionElement.style.height = region.height + heightUnit;
384
+ regionElement.style.width = region.width + widthUnit;
385
+ regionElement.style.position = 'absolute';
386
+ regionElement.style.top = region.viewportAnchorY + viewportAnchorUnit;
387
+ regionElement.style.left = region.viewportAnchorX + viewportAnchorUnit;
388
+
389
+ regionElement.style.display = 'flex';
390
+ regionElement.style.flexDirection = 'column';
391
+ regionElement.style.alignItems = 'center';
392
+
393
+ if (cue.displayAlign == shaka.text.Cue.displayAlign.BEFORE) {
394
+ regionElement.style.justifyContent = 'flex-start';
395
+ } else if (cue.displayAlign == shaka.text.Cue.displayAlign.CENTER) {
396
+ regionElement.style.justifyContent = 'center';
397
+ } else {
398
+ regionElement.style.justifyContent = 'flex-end';
399
+ }
400
+
401
+ this.regionElements_.set(region.id, regionElement);
402
+ return regionElement;
403
+ }
404
+
335
405
  /**
336
406
  * Creates the object for a cue.
337
407
  *
@@ -354,6 +424,11 @@ shaka.text.UITextDisplayer = class {
354
424
  this.setCaptionStyles_(cueElement, cue, parents, needWrapper);
355
425
  }
356
426
 
427
+ let regionElement = null;
428
+ if (cue.region && cue.region.id) {
429
+ regionElement = this.getRegionElement_(cue);
430
+ }
431
+
357
432
  let wrapper = cueElement;
358
433
  if (needWrapper) {
359
434
  // Create a wrapper element which will serve to contain all children into
@@ -365,7 +440,7 @@ shaka.text.UITextDisplayer = class {
365
440
  cueElement.appendChild(wrapper);
366
441
  }
367
442
 
368
- this.currentCuesMap_.set(cue, {cueElement, wrapper});
443
+ this.currentCuesMap_.set(cue, {cueElement, wrapper, regionElement});
369
444
  }
370
445
 
371
446
  /**
@@ -526,17 +601,6 @@ shaka.text.UITextDisplayer = class {
526
601
  }
527
602
  }
528
603
  }
529
- } else if (cue.region && cue.region.id) {
530
- const percentageUnit = shaka.text.CueRegion.units.PERCENTAGE;
531
- const heightUnit = cue.region.heightUnits == percentageUnit ? '%' : 'px';
532
- const widthUnit = cue.region.widthUnits == percentageUnit ? '%' : 'px';
533
- const viewportAnchorUnit =
534
- cue.region.viewportAnchorUnits == percentageUnit ? '%' : 'px';
535
- style.height = cue.region.height + heightUnit;
536
- style.width = cue.region.width + widthUnit;
537
- style.position = 'absolute';
538
- style.top = cue.region.viewportAnchorY + viewportAnchorUnit;
539
- style.left = cue.region.viewportAnchorX + viewportAnchorUnit;
540
604
  }
541
605
 
542
606
  style.lineHeight = cue.lineHeight;
@@ -412,6 +412,7 @@ shaka.text.VttTextParser = class {
412
412
  VttTextParser.addDefaultTextColor_(styles);
413
413
  }
414
414
  payload = VttTextParser.replaceColorPayload_(payload);
415
+ payload = VttTextParser.replaceKaraokeStylePayload_(payload);
415
416
  const xmlPayload = '<span>' + payload + '</span>';
416
417
  const element = shaka.util.XmlUtils.parseXmlString(xmlPayload, 'span');
417
418
  if (element) {
@@ -437,6 +438,44 @@ shaka.text.VttTextParser = class {
437
438
  }
438
439
  }
439
440
 
441
+ /**
442
+ * Converts karaoke style tag to be valid for xml parsing
443
+ * For example,
444
+ * input: Text <00:00:00.450> time <00:00:01.450> 1
445
+ * output: Text <div time="00:00:00.450"> time
446
+ * <div time="00:00:01.450"> 1</div></div>
447
+ *
448
+ * @param {string} payload
449
+ * @return {string} processed payload
450
+ * @private
451
+ */
452
+ static replaceKaraokeStylePayload_(payload) {
453
+ const names = [];
454
+ let nameStart = -1;
455
+ for (let i = 0; i < payload.length; i++) {
456
+ if (payload[i] === '<') {
457
+ nameStart = i + 1;
458
+ } else if (payload[i] === '>') {
459
+ if (nameStart > 0) {
460
+ const name = payload.substr(nameStart, i - nameStart);
461
+ if (name.match(shaka.text.VttTextParser.timeFormat_)) {
462
+ names.push(name);
463
+ }
464
+ nameStart = -1;
465
+ }
466
+ }
467
+ }
468
+ let newPayload = payload;
469
+ for (const name of names) {
470
+ const replaceTag = '<' + name + '>';
471
+ const startTag = '<div time="' + name + '">';
472
+ const endTag = '</div>';
473
+ newPayload = newPayload.replace(replaceTag, startTag);
474
+ newPayload += endTag;
475
+ }
476
+ return newPayload;
477
+ }
478
+
440
479
  /**
441
480
  * Converts color end tag to be valid for xml parsing
442
481
  * For example,
@@ -560,6 +599,18 @@ shaka.text.VttTextParser = class {
560
599
  case 'u':
561
600
  nestedCue.textDecoration.push(underline);
562
601
  break;
602
+ case 'div': {
603
+ const time = /** @type {!Element} */(element).getAttribute('time');
604
+ if (!time) {
605
+ break;
606
+ }
607
+ const parser = new shaka.util.TextParser(time);
608
+ const cueTime = shaka.text.VttTextParser.parseTime_(parser);
609
+ if (cueTime) {
610
+ nestedCue.startTime = cueTime;
611
+ }
612
+ break;
613
+ }
563
614
  default:
564
615
  break;
565
616
  }
@@ -776,10 +827,7 @@ shaka.text.VttTextParser = class {
776
827
  * @private
777
828
  */
778
829
  static parseTime_(parser) {
779
- // 00:00.000 or 00:00:00.000 or 0:00:00.000 or
780
- // 00:00.00 or 00:00:00.00 or 0:00:00.00
781
- const regexExpresion = /(?:(\d{1,}):)?(\d{2}):(\d{2})\.(\d{2,3})/g;
782
- const results = parser.readRegex(regexExpresion);
830
+ const results = parser.readRegex(shaka.text.VttTextParser.timeFormat_);
783
831
  if (results == null) {
784
832
  return null;
785
833
  }
@@ -810,6 +858,15 @@ shaka.text.VttTextParser.MPEG_TIMESCALE_ = 90000;
810
858
  */
811
859
  shaka.text.VttTextParser.TS_ROLLOVER_ = 0x200000000;
812
860
 
861
+ /**
862
+ * @const
863
+ * @private {!RegExp}
864
+ * @example 00:00.000 or 00:00:00.000 or 0:00:00.000 or
865
+ * 00:00.00 or 00:00:00.00 or 0:00:00.00
866
+ */
867
+ shaka.text.VttTextParser.timeFormat_ =
868
+ /(?:(\d{1,}):)?(\d{2}):(\d{2})\.(\d{2,3})/g;
869
+
813
870
  shaka.text.TextEngine.registerParser(
814
871
  'text/vtt', () => new shaka.text.VttTextParser());
815
872
 
@@ -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|.
package/lib/util/error.js CHANGED
@@ -693,7 +693,27 @@ shaka.util.Error.Code = {
693
693
  */
694
694
  'HLS_MSE_ENCRYPTED_MP2T_NOT_SUPPORTED': 4040,
695
695
 
696
- // RETIRED: 'HLS_MSE_ENCRYPTED_LEGACY_APPLE_MEDIA_KEYS_NOT_SUPPORTED': 4041,
696
+ /**
697
+ * We do not support playing encrypted content (different than mp2t) with MSE
698
+ * and legacy Apple MediaKeys API.
699
+ */
700
+ 'HLS_MSE_ENCRYPTED_LEGACY_APPLE_MEDIA_KEYS_NOT_SUPPORTED': 4041,
701
+
702
+ /**
703
+ * Web Crypto API is not available (to decrypt AES-128 streams). Web Crypto
704
+ * only exists in secure origins like https.
705
+ */
706
+ 'NO_WEB_CRYPTO_API': 4042,
707
+
708
+ /**
709
+ * AES-128 iv length should be 16 bytes.
710
+ */
711
+ 'HLS_AES_128_INVALID_IV_LENGTH': 4043,
712
+
713
+ /**
714
+ * AES-128 encryption key length should be 16 bytes.
715
+ */
716
+ 'HLS_AES_128_INVALID_KEY_LENGTH': 4044,
697
717
 
698
718
 
699
719
  // RETIRED: 'INCONSISTENT_BUFFER_STATE': 5000,
@@ -133,13 +133,83 @@ shaka.util.FairPlayUtils = class {
133
133
  }
134
134
 
135
135
  /**
136
- * SPC FairPlay request.
136
+ * Verimatrix initDataTransform configuration.
137
+ *
138
+ * @param {!Uint8Array} initData
139
+ * @param {string} initDataType
140
+ * @param {?shaka.extern.DrmInfo} drmInfo
141
+ * @export
142
+ */
143
+ static verimatrixInitDataTransform(initData, initDataType, drmInfo) {
144
+ if (initDataType !== 'skd') {
145
+ return initData;
146
+ }
147
+ const StringUtils = shaka.util.StringUtils;
148
+ const FairPlayUtils = shaka.util.FairPlayUtils;
149
+ const cert = drmInfo.serverCertificate;
150
+ const initDataAsString = StringUtils.fromBytesAutoDetect(initData);
151
+ const contentId = initDataAsString.split('skd://').pop();
152
+ return FairPlayUtils.initDataTransform(initData, contentId, cert);
153
+ }
154
+
155
+ /**
156
+ * EZDRM initDataTransform configuration.
157
+ *
158
+ * @param {!Uint8Array} initData
159
+ * @param {string} initDataType
160
+ * @param {?shaka.extern.DrmInfo} drmInfo
161
+ * @export
162
+ */
163
+ static ezdrmInitDataTransform(initData, initDataType, drmInfo) {
164
+ if (initDataType !== 'skd') {
165
+ return initData;
166
+ }
167
+ const StringUtils = shaka.util.StringUtils;
168
+ const FairPlayUtils = shaka.util.FairPlayUtils;
169
+ const cert = drmInfo.serverCertificate;
170
+ const initDataAsString = StringUtils.fromBytesAutoDetect(initData);
171
+ const contentId = initDataAsString.split(';').pop();
172
+ return FairPlayUtils.initDataTransform(initData, contentId, cert);
173
+ }
174
+
175
+ /**
176
+ * Conax initDataTransform configuration.
177
+ *
178
+ * @param {!Uint8Array} initData
179
+ * @param {string} initDataType
180
+ * @param {?shaka.extern.DrmInfo} drmInfo
181
+ * @export
182
+ */
183
+ static conaxInitDataTransform(initData, initDataType, drmInfo) {
184
+ if (initDataType !== 'skd') {
185
+ return initData;
186
+ }
187
+ const StringUtils = shaka.util.StringUtils;
188
+ const FairPlayUtils = shaka.util.FairPlayUtils;
189
+ const cert = drmInfo.serverCertificate;
190
+ const initDataAsString = StringUtils.fromBytesAutoDetect(initData);
191
+ const skdValue = initDataAsString.split('skd://').pop().split('?').shift();
192
+ const stringToArray = (string) => {
193
+ // 2 bytes for each char
194
+ const buffer = new ArrayBuffer(string.length * 2);
195
+ const array = new Uint16Array(buffer);
196
+ for (let i = 0, strLen = string.length; i < strLen; i++) {
197
+ array[i] = string.charCodeAt(i);
198
+ }
199
+ return array;
200
+ };
201
+ const contentId = stringToArray(window.atob(skdValue));
202
+ return FairPlayUtils.initDataTransform(initData, contentId, cert);
203
+ }
204
+
205
+ /**
206
+ * Verimatrix FairPlay request.
137
207
  *
138
208
  * @param {shaka.net.NetworkingEngine.RequestType} type
139
209
  * @param {shaka.extern.Request} request
140
210
  * @export
141
211
  */
142
- static spcFairPlayRequest(type, request) {
212
+ static verimatrixFairPlayRequest(type, request) {
143
213
  if (type !== shaka.net.NetworkingEngine.RequestType.LICENSE) {
144
214
  return;
145
215
  }
@@ -150,6 +220,34 @@ shaka.util.FairPlayUtils = class {
150
220
  request.body = shaka.util.StringUtils.toUTF8('spc=' + base64Payload);
151
221
  }
152
222
 
223
+ /**
224
+ * EZDRM FairPlay request.
225
+ *
226
+ * @param {shaka.net.NetworkingEngine.RequestType} type
227
+ * @param {shaka.extern.Request} request
228
+ * @export
229
+ */
230
+ static ezdrmFairPlayRequest(type, request) {
231
+ if (type !== shaka.net.NetworkingEngine.RequestType.LICENSE) {
232
+ return;
233
+ }
234
+ request.headers['Content-Type'] = 'application/octet-stream';
235
+ }
236
+
237
+ /**
238
+ * Conax FairPlay request.
239
+ *
240
+ * @param {shaka.net.NetworkingEngine.RequestType} type
241
+ * @param {shaka.extern.Request} request
242
+ * @export
243
+ */
244
+ static conaxFairPlayRequest(type, request) {
245
+ if (type !== shaka.net.NetworkingEngine.RequestType.LICENSE) {
246
+ return;
247
+ }
248
+ request.headers['Content-Type'] = 'application/octet-stream';
249
+ }
250
+
153
251
  /**
154
252
  * Common FairPlay response transform for some DRMs providers.
155
253
  *
@@ -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':
@@ -197,7 +197,10 @@ shaka.util.Platform = class {
197
197
  */
198
198
  static isApple() {
199
199
  return !!navigator.vendor && navigator.vendor.includes('Apple') &&
200
- !shaka.util.Platform.isTizen();
200
+ !shaka.util.Platform.isTizen() &&
201
+ !shaka.util.Platform.isEOS() &&
202
+ !shaka.util.Platform.isPS4() &&
203
+ !shaka.util.Platform.isAmazonFireTV();
201
204
  }
202
205
 
203
206
  /**
@@ -213,6 +216,23 @@ shaka.util.Platform = class {
213
216
  return shaka.util.Platform.userAgentContains_('PlayStation 5');
214
217
  }
215
218
 
219
+ /**
220
+ * Check if the current platform is Playstation 4.
221
+ */
222
+ static isPS4() {
223
+ return shaka.util.Platform.userAgentContains_('PlayStation 4');
224
+ }
225
+
226
+ /**
227
+ * Check if the current platform is Amazon Fire TV.
228
+ * https://developer.amazon.com/docs/fire-tv/identify-amazon-fire-tv-devices.html
229
+ *
230
+ * @return {boolean}
231
+ */
232
+ static isAmazonFireTV() {
233
+ return shaka.util.Platform.userAgentContains_('AFT');
234
+ }
235
+
216
236
  /**
217
237
  * Returns a major version number for Safari, or Safari-based iOS browsers.
218
238
  *
@@ -260,6 +280,15 @@ shaka.util.Platform = class {
260
280
  return !!shaka.util.Platform.safariVersion();
261
281
  }
262
282
 
283
+ /**
284
+ * Check if the current platform is an EOS set-top box.
285
+ *
286
+ * @return {boolean}
287
+ */
288
+ static isEOS() {
289
+ return shaka.util.Platform.userAgentContains_('PC=EOS');
290
+ }
291
+
263
292
  /**
264
293
  * Guesses if the platform is a mobile one (iOS or Android).
265
294
  *
@@ -147,7 +147,7 @@ shaka.util.PlayerConfiguration = class {
147
147
  ignoreTextStreamFailures: false,
148
148
  alwaysStreamText: false,
149
149
  startAtSegmentBoundary: false,
150
- gapDetectionThreshold: 0.1,
150
+ gapDetectionThreshold: 0.5,
151
151
  durationBackoff: 1,
152
152
  forceTransmuxTS: false,
153
153
  // Offset by 5 seconds since Chromecast takes a few seconds to start
@@ -173,15 +173,6 @@ shaka.util.PlayerConfiguration = class {
173
173
  maxDisabledTime: 30,
174
174
  };
175
175
 
176
- // Some browsers will stop earlier than others before a gap (e.g., Edge
177
- // stops 0.5 seconds before a gap). So for some browsers we need to use a
178
- // larger threshold. See: https://bit.ly/2K5xmJO
179
- if (shaka.util.Platform.isLegacyEdge() ||
180
- shaka.util.Platform.isTizen() ||
181
- shaka.util.Platform.isChromecast()) {
182
- streaming.gapDetectionThreshold = 0.5;
183
- }
184
-
185
176
  // WebOS, Tizen, and Chromecast have long hardware pipelines that respond
186
177
  // slowly to seeking. Therefore we should not seek when we detect a stall
187
178
  // on one of these platforms. Instead, default stallSkip to 0 to force the
@@ -253,6 +244,8 @@ shaka.util.PlayerConfiguration = class {
253
244
  fastHalfLife: 2,
254
245
  slowHalfLife: 5,
255
246
  },
247
+ restrictToElementSize: false,
248
+ ignoreDevicePixelRatio: false,
256
249
  };
257
250
 
258
251
  const cmcd = {
@@ -585,8 +585,16 @@ shaka.util.StreamUtils = class {
585
585
  // VideoConfiguration
586
586
  mediaDecodingConfig.video = {
587
587
  contentType: fullType,
588
- width: video.width || 1,
589
- height: video.height || 1,
588
+
589
+ // NOTE: Some decoders strictly check the width and height fields and
590
+ // won't decode smaller than 64x64. So if we don't have this info (as
591
+ // is the case in some of our simpler tests), assume a 64x64 resolution
592
+ // to fill in this required field for MediaCapabilities.
593
+ //
594
+ // This became an issue specifically on Firefox on M1 Macs.
595
+ width: video.width || 64,
596
+ height: video.height || 64,
597
+
590
598
  bitrate: video.bandwidth || variant.bandwidth || 1,
591
599
  // framerate must be greater than 0, otherwise the config is invalid.
592
600
  framerate: video.frameRate || 1,
@@ -749,7 +757,19 @@ shaka.util.StreamUtils = class {
749
757
  */
750
758
  static patchVp9(codec) {
751
759
  if (codec == 'vp9') {
752
- return 'vp09.00.10.08';
760
+ // This means profile 0, level 4.1, 8-bit color. This supports 1080p @
761
+ // 60Hz. See https://en.wikipedia.org/wiki/VP9#Levels
762
+ //
763
+ // If we don't have more detailed codec info, assume this profile and
764
+ // level because it's high enough to likely accommodate the parameters we
765
+ // do have, such as width and height. If an implementation is checking
766
+ // the profile and level very strictly, we want older VP9 content to
767
+ // still work to some degree. But we don't want to set a level so high
768
+ // that it is rejected by a hardware decoder that can't handle the
769
+ // maximum requirements of the level.
770
+ //
771
+ // This became an issue specifically on Firefox on M1 Macs.
772
+ return 'vp09.00.41.08';
753
773
  }
754
774
  return codec;
755
775
  }