shaka-player 3.3.16 → 3.3.17

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 (37) hide show
  1. package/.github/workflows/selenium-lab-tests.yaml +22 -0
  2. package/CHANGELOG.md +16 -0
  3. package/dist/locales.js +30 -30
  4. package/dist/shaka-player.compiled.d.ts +5 -0
  5. package/dist/shaka-player.compiled.debug.d.ts +5 -0
  6. package/dist/shaka-player.compiled.debug.externs.js +4 -0
  7. package/dist/shaka-player.compiled.debug.js +80 -75
  8. package/dist/shaka-player.compiled.debug.map +3 -3
  9. package/dist/shaka-player.compiled.externs.js +4 -0
  10. package/dist/shaka-player.compiled.js +255 -252
  11. package/dist/shaka-player.compiled.map +3 -3
  12. package/dist/shaka-player.ui.d.ts +5 -0
  13. package/dist/shaka-player.ui.debug.d.ts +5 -0
  14. package/dist/shaka-player.ui.debug.externs.js +4 -0
  15. package/dist/shaka-player.ui.debug.js +94 -89
  16. package/dist/shaka-player.ui.debug.map +3 -3
  17. package/dist/shaka-player.ui.externs.js +4 -0
  18. package/dist/shaka-player.ui.js +288 -285
  19. package/dist/shaka-player.ui.map +3 -3
  20. package/externs/shaka/text.js +7 -0
  21. package/lib/dash/segment_template.js +17 -5
  22. package/lib/hls/hls_parser.js +1 -1
  23. package/lib/player.js +1 -1
  24. package/lib/polyfill/media_capabilities.js +1 -0
  25. package/lib/text/cue.js +6 -0
  26. package/lib/text/ui_text_displayer.js +1 -5
  27. package/lib/text/vtt_text_parser.js +256 -91
  28. package/lib/util/platform.js +8 -0
  29. package/package.json +1 -1
  30. package/support.html +1 -0
  31. package/test/dash/dash_parser_manifest_unit.js +0 -36
  32. package/test/test/util/layout_tests.js +433 -0
  33. package/test/test/util/util.js +0 -120
  34. package/test/{ui → text}/text_displayer_layout_unit.js +59 -235
  35. package/test/text/ui_text_displayer_unit.js +2 -4
  36. package/test/text/vtt_text_parser_unit.js +127 -6
  37. package/test/text/web_vtt_layout_integration.js +528 -0
@@ -297,6 +297,13 @@ shaka.extern.Cue = class {
297
297
  */
298
298
  this.fontFamily;
299
299
 
300
+ /**
301
+ * Text shadow color as a CSS text-shadow value.
302
+ * @type {string}
303
+ * @exportDoc
304
+ */
305
+ this.textShadow = '';
306
+
300
307
  /**
301
308
  * Text stroke color as a CSS color, e.g. "#FFFFFF" or "white".
302
309
  * @type {string}
@@ -102,11 +102,23 @@ shaka.dash.SegmentTemplate = class {
102
102
  const periodEnd = context.periodInfo.duration ?
103
103
  context.periodInfo.start + context.periodInfo.duration : Infinity;
104
104
 
105
- // Don't fit live content, since it might receive more segments.
106
- // Unless that live content is multi-period; it's safe to fit every period
107
- // but the last one, since only the last period might receive new
108
- // segments.
109
- const shouldFit = periodEnd != Infinity;
105
+ /* When to fit segments. All refactors should honor/update this table:
106
+ *
107
+ * | dynamic | infinite | last | should | notes |
108
+ * | | period | period | fit | |
109
+ * | ------- | -------- | ------ | ------ | ------------------------- |
110
+ * | F | F | X | T | typical VOD |
111
+ * | F | T | X | X | impossible: infinite VOD |
112
+ * | T | F | F | T | typical live, old period |
113
+ * | T | F | T | F | typical IPR |
114
+ * | T | T | F | X | impossible: old, infinite |
115
+ * | T | T | T | F | typical live, new period |
116
+ */
117
+
118
+ // We never fit the final period of dynamic content, which could be
119
+ // infinite live (with no limit to fit to) or IPR (which would expand the
120
+ // most recent segment to the end of the presentation).
121
+ const shouldFit = !(context.dynamic && context.periodInfo.isLastPeriod);
110
122
 
111
123
  if (segmentIndex) {
112
124
  if (shouldFit) {
@@ -3041,7 +3041,7 @@ shaka.hls.HlsParser.TEXT_EXTENSIONS_TO_MIME_TYPES_ = {
3041
3041
  'm4f': 'application/mp4',
3042
3042
  'cmft': 'application/mp4',
3043
3043
  'vtt': 'text/vtt',
3044
- 'webvtt': 'txt/vtt',
3044
+ 'webvtt': 'text/vtt',
3045
3045
  'ttml': 'application/ttml+xml',
3046
3046
  };
3047
3047
 
package/lib/player.js CHANGED
@@ -6663,7 +6663,7 @@ shaka.Player.TYPICAL_BUFFERING_THRESHOLD_ = 0.5;
6663
6663
  * @define {string} A version number taken from git at compile time.
6664
6664
  * @export
6665
6665
  */
6666
- shaka.Player.version = 'v3.3.16-uncompiled';
6666
+ shaka.Player.version = 'v3.3.17-uncompiled';
6667
6667
 
6668
6668
  // Initialize the deprecation system using the version string we just set
6669
6669
  // on the player.
@@ -45,6 +45,7 @@ shaka.polyfill.MediaCapabilities = class {
45
45
  !shaka.util.Platform.isWebOS() &&
46
46
  !shaka.util.Platform.isTizen() &&
47
47
  !shaka.util.Platform.isEOS() &&
48
+ !shaka.util.Platform.isHisense() &&
48
49
  navigator.mediaCapabilities) {
49
50
  shaka.log.info(
50
51
  'MediaCapabilities: Native mediaCapabilities support found.');
package/lib/text/cue.js CHANGED
@@ -139,6 +139,12 @@ shaka.text.Cue = class {
139
139
  */
140
140
  this.border = '';
141
141
 
142
+ /**
143
+ * @override
144
+ * @exportInterface
145
+ */
146
+ this.textShadow = '';
147
+
142
148
  /**
143
149
  * @override
144
150
  * @exportInterface
@@ -529,6 +529,7 @@ shaka.text.UITextDisplayer = class {
529
529
  style.paddingRight =
530
530
  shaka.text.UITextDisplayer.convertLengthValue_(
531
531
  cue.linePadding, cue, this.videoContainer_);
532
+ style.textShadow = cue.textShadow;
532
533
 
533
534
  if (cue.backgroundImage) {
534
535
  style.backgroundImage = 'url(\'' + cue.backgroundImage + '\')';
@@ -622,12 +623,7 @@ shaka.text.UITextDisplayer = class {
622
623
  // TODO: Implement lineAlignment of 'CENTER'.
623
624
  if (cue.line != null) {
624
625
  if (cue.lineInterpretation == Cue.lineInterpretation.PERCENTAGE) {
625
- // When setting absolute positioning, you need to set x/y/width/height
626
- // so the element is positioned correctly. Set these as default and
627
- // other settings will override them.
628
626
  style.position = 'absolute';
629
- style.left = '0';
630
- style.top = '0';
631
627
  if (cue.writingMode == Cue.writingMode.HORIZONTAL_TOP_TO_BOTTOM) {
632
628
  style.width = '100%';
633
629
  if (cue.lineAlign == Cue.lineAlign.START) {
@@ -132,14 +132,14 @@ shaka.text.VttTextParser = class {
132
132
  for (const [key, value] of Object.entries(textColor)) {
133
133
  const cue = new shaka.text.Cue(0, 0, '');
134
134
  cue.color = value;
135
- styles.set(key, cue);
135
+ styles.set('.' + key, cue);
136
136
  }
137
137
 
138
138
  const bgColor = shaka.text.Cue.defaultTextBackgroundColor;
139
139
  for (const [key, value] of Object.entries(bgColor)) {
140
140
  const cue = new shaka.text.Cue(0, 0, '');
141
141
  cue.backgroundColor = value;
142
- styles.set(key, cue);
142
+ styles.set('.' + key, cue);
143
143
  }
144
144
  }
145
145
 
@@ -199,92 +199,121 @@ shaka.text.VttTextParser = class {
199
199
  return;
200
200
  }
201
201
 
202
- if (!text[1].includes('::cue')) {
203
- return;
204
- }
205
- let styleSelector = 'global';
206
- // Look for what is within parentisesis. For example:
207
- // <code>:: cue (b) {</code>, what we are looking for is <code>b</code>
208
- const selector = text[1].match(/\((.*)\)/);
209
- if (selector) {
210
- styleSelector = selector.pop();
202
+ /** @type {!Array.<!Array.<string>>} */
203
+ const styleBlocks = [];
204
+ let lastBlockIndex = -1;
205
+ for (let i = 1; i < text.length; i++) {
206
+ if (text[i].includes('::cue')) {
207
+ styleBlocks.push([]);
208
+ lastBlockIndex = styleBlocks.length - 1;
209
+ }
210
+ if (lastBlockIndex == -1) {
211
+ continue;
212
+ }
213
+ styleBlocks[lastBlockIndex].push(text[i]);
214
+ if (text[i].includes('}')) {
215
+ lastBlockIndex = -1;
216
+ }
211
217
  }
212
218
 
213
- // We start at 2 to avoid '::cue' and end earlier to avoid '}'
214
- let propertyLines = text.slice(2, -1);
215
- if (text[1].includes('}')) {
216
- const payload = /\{(.*?)\}/.exec(text[1]);
217
- if (payload) {
218
- propertyLines = payload[1].split(';');
219
+ for (const styleBlock of styleBlocks) {
220
+ let styleSelector = 'global';
221
+ // Look for what is within parentheses. For example:
222
+ // <code>:: cue (b) {</code>, what we are looking for is <code>b</code>
223
+ const selector = styleBlock[0].match(/\((.*)\)/);
224
+ if (selector) {
225
+ styleSelector = selector.pop();
219
226
  }
220
- }
221
227
 
222
- const cue = new shaka.text.Cue(0, 0, '');
223
- let validStyle = false;
224
- for (let i = 0; i < propertyLines.length; i++) {
225
- // We look for CSS properties. As a general rule they are separated by
226
- // <code>:</code>. Eg: <code>color: red;</code>
227
- const lineParts = /^\s*([^:]+):\s*(.*)/.exec(propertyLines[i]);
228
- if (lineParts) {
229
- const name = lineParts[1].trim();
230
- const value = lineParts[2].trim().replace(';', '');
231
- switch (name) {
232
- case 'background-color':
233
- validStyle = true;
234
- cue.backgroundColor = value;
235
- break;
236
- case 'color':
237
- validStyle = true;
238
- cue.color = value;
239
- break;
240
- case 'font-family':
241
- validStyle = true;
242
- cue.fontFamily = value;
243
- break;
244
- case 'font-size':
245
- validStyle = true;
246
- cue.fontSize = value;
247
- break;
248
- case 'font-weight':
249
- if (parseInt(value, 10) >= 700) {
228
+ // We start at 1 to avoid '::cue' and end earlier to avoid '}'
229
+ let propertyLines = styleBlock.slice(1, -1);
230
+ if (styleBlock[0].includes('}')) {
231
+ const payload = /\{(.*?)\}/.exec(styleBlock[0]);
232
+ if (payload) {
233
+ propertyLines = payload[1].split(';');
234
+ }
235
+ }
236
+
237
+ // Continue styles over multiple selectors if necessary.
238
+ // For example,
239
+ // ::cue(b) { background: white; } ::cue(b) { color: blue; }
240
+ // should set both the background and foreground of bold tags.
241
+ let cue = styles.get(styleSelector);
242
+ if (!cue) {
243
+ cue = new shaka.text.Cue(0, 0, '');
244
+ }
245
+
246
+ let validStyle = false;
247
+ for (let i = 0; i < propertyLines.length; i++) {
248
+ // We look for CSS properties. As a general rule they are separated by
249
+ // <code>:</code>. Eg: <code>color: red;</code>
250
+ const lineParts = /^\s*([^:]+):\s*(.*)/.exec(propertyLines[i]);
251
+ if (lineParts) {
252
+ const name = lineParts[1].trim();
253
+ const value = lineParts[2].trim().replace(';', '');
254
+ switch (name) {
255
+ case 'background-color':
256
+ case 'background':
250
257
  validStyle = true;
251
- cue.fontWeight = shaka.text.Cue.fontWeight.BOLD;
252
- }
253
- break;
254
- case 'font-style':
255
- switch (value) {
256
- case 'normal':
257
- validStyle = true;
258
- cue.fontStyle = shaka.text.Cue.fontStyle.NORMAL;
259
- break;
260
- case 'italic':
261
- validStyle = true;
262
- cue.fontStyle = shaka.text.Cue.fontStyle.ITALIC;
263
- break;
264
- case 'oblique':
258
+ cue.backgroundColor = value;
259
+ break;
260
+ case 'color':
261
+ validStyle = true;
262
+ cue.color = value;
263
+ break;
264
+ case 'font-family':
265
+ validStyle = true;
266
+ cue.fontFamily = value;
267
+ break;
268
+ case 'font-size':
269
+ validStyle = true;
270
+ cue.fontSize = value;
271
+ break;
272
+ case 'font-weight':
273
+ if (parseInt(value, 10) >= 700 || value == 'bold') {
265
274
  validStyle = true;
266
- cue.fontStyle = shaka.text.Cue.fontStyle.OBLIQUE;
267
- break;
268
- }
269
- break;
270
- case 'opacity':
271
- validStyle = true;
272
- cue.opacity = parseFloat(value);
273
- break;
274
- case 'white-space':
275
- validStyle = true;
276
- cue.wrapLine = value != 'noWrap';
277
- break;
278
- default:
279
- shaka.log.warning('VTT parser encountered an unsupported style: ',
280
- lineParts);
281
- break;
275
+ cue.fontWeight = shaka.text.Cue.fontWeight.BOLD;
276
+ }
277
+ break;
278
+ case 'font-style':
279
+ switch (value) {
280
+ case 'normal':
281
+ validStyle = true;
282
+ cue.fontStyle = shaka.text.Cue.fontStyle.NORMAL;
283
+ break;
284
+ case 'italic':
285
+ validStyle = true;
286
+ cue.fontStyle = shaka.text.Cue.fontStyle.ITALIC;
287
+ break;
288
+ case 'oblique':
289
+ validStyle = true;
290
+ cue.fontStyle = shaka.text.Cue.fontStyle.OBLIQUE;
291
+ break;
292
+ }
293
+ break;
294
+ case 'opacity':
295
+ validStyle = true;
296
+ cue.opacity = parseFloat(value);
297
+ break;
298
+ case 'text-shadow':
299
+ validStyle = true;
300
+ cue.textShadow = value;
301
+ break;
302
+ case 'white-space':
303
+ validStyle = true;
304
+ cue.wrapLine = value != 'noWrap';
305
+ break;
306
+ default:
307
+ shaka.log.warning('VTT parser encountered an unsupported style: ',
308
+ lineParts);
309
+ break;
310
+ }
282
311
  }
283
312
  }
284
- }
285
313
 
286
- if (validStyle) {
287
- styles.set(styleSelector, cue);
314
+ if (validStyle) {
315
+ styles.set(styleSelector, cue);
316
+ }
288
317
  }
289
318
  }
290
319
 
@@ -311,8 +340,8 @@ shaka.text.VttTextParser = class {
311
340
  return null;
312
341
  }
313
342
 
314
- // Skip style blocks.
315
- if (text[0] == 'STYLE') {
343
+ // Skip style and region blocks.
344
+ if (text[0] == 'STYLE' || text[0] == 'REGION') {
316
345
  return null;
317
346
  }
318
347
 
@@ -340,7 +369,8 @@ shaka.text.VttTextParser = class {
340
369
  end += timeOffset;
341
370
 
342
371
  // Get the payload.
343
- const payload = text.slice(1).join('\n').trim();
372
+ const payload = VttTextParser.htmlUnescape_(
373
+ text.slice(1).join('\n').trim());
344
374
 
345
375
  let cue = null;
346
376
  if (styles.has('global')) {
@@ -386,6 +416,7 @@ shaka.text.VttTextParser = class {
386
416
  VttTextParser.addDefaultTextColor_(styles);
387
417
  }
388
418
  payload = VttTextParser.replaceColorPayload_(payload);
419
+ payload = VttTextParser.replaceVoiceStylePayload_(payload);
389
420
  const xmlPayload = '<span>' + payload + '</span>';
390
421
  const element = shaka.util.XmlUtils.parseXmlString(xmlPayload, 'span');
391
422
  if (element) {
@@ -411,6 +442,79 @@ shaka.text.VttTextParser = class {
411
442
  }
412
443
  }
413
444
 
445
+ /**
446
+ * Converts voice style tag to be valid for xml parsing
447
+ * For example,
448
+ * input: <v Shaka>Test
449
+ * output: <v.voice-Shaka>Test</v.voice-Shaka>
450
+ *
451
+ * @param {string} payload
452
+ * @return {string} processed payload
453
+ * @private
454
+ */
455
+ static replaceVoiceStylePayload_(payload) {
456
+ const voiceTag = 'v';
457
+ const names = [];
458
+ let nameStart = -1;
459
+ let newPayload = '';
460
+ let hasVoiceEndTag = false;
461
+ for (let i = 0; i < payload.length; i++) {
462
+ // This condition is used to manage tags that have end tags.
463
+ if (payload[i] === '/') {
464
+ const end = payload.indexOf('>', i);
465
+ if (end === -1) {
466
+ return payload;
467
+ }
468
+ const tagEnd = payload.substring(i + 1, end);
469
+ if (!tagEnd || tagEnd != voiceTag) {
470
+ newPayload += payload[i];
471
+ continue;
472
+ }
473
+ hasVoiceEndTag = true;
474
+ let tagStart = null;
475
+ if (names.length) {
476
+ tagStart = names[names.length -1];
477
+ }
478
+ if (!tagStart) {
479
+ newPayload += payload[i];
480
+ } else if (tagStart === tagEnd) {
481
+ newPayload += '/' + tagEnd + '>';
482
+ i += tagEnd.length + 1;
483
+ } else {
484
+ if (!tagStart.startsWith(voiceTag)) {
485
+ newPayload += payload[i];
486
+ continue;
487
+ }
488
+ newPayload += '/' + tagStart + '>';
489
+ i += tagEnd.length + 1;
490
+ }
491
+ } else {
492
+ // Here we only want the tag name, not any other payload.
493
+ if (payload[i] === '<') {
494
+ nameStart = i + 1;
495
+ if (payload[nameStart] != voiceTag) {
496
+ nameStart = -1;
497
+ }
498
+ } else if (payload[i] === '>') {
499
+ if (nameStart > 0) {
500
+ names.push(payload.substr(nameStart, i - nameStart));
501
+ nameStart = -1;
502
+ }
503
+ }
504
+ newPayload += payload[i];
505
+ }
506
+ }
507
+ for (const name of names) {
508
+ const newName = name.replace(' ', '.voice-');
509
+ newPayload = newPayload.replace(`<${name}>`, `<${newName}>`);
510
+ newPayload = newPayload.replace(`</${name}>`, `</${newName}>`);
511
+ if (!hasVoiceEndTag) {
512
+ newPayload += `</${newName}>`;
513
+ }
514
+ }
515
+ return newPayload;
516
+ }
517
+
414
518
  /**
415
519
  * Converts color end tag to be valid for xml parsing
416
520
  * For example,
@@ -429,28 +533,36 @@ shaka.text.VttTextParser = class {
429
533
  let nameStart = -1;
430
534
  let newPayload = '';
431
535
  for (let i = 0; i < payload.length; i++) {
432
- if (payload[i] === '/') {
536
+ if (payload[i] === '/' && i > 0 && payload[i - 1] === '<') {
433
537
  const end = payload.indexOf('>', i);
434
538
  if (end <= i) {
435
539
  return payload;
436
540
  }
437
541
  const tagEnd = payload.substring(i + 1, end);
542
+ if (!tagEnd || tagEnd !== 'c') {
543
+ newPayload += payload[i];
544
+ continue;
545
+ }
438
546
  const tagStart = names.pop();
439
- if (!tagEnd || !tagStart) {
440
- return payload;
547
+ if (!tagStart) {
548
+ newPayload += payload[i];
441
549
  } else if (tagStart === tagEnd) {
442
550
  newPayload += '/' + tagEnd + '>';
443
551
  i += tagEnd.length + 1;
444
552
  } else {
445
- if (!tagStart.startsWith('c.') || tagEnd !== 'c') {
446
- return payload;
553
+ if (!tagStart.startsWith('c.')) {
554
+ newPayload += payload[i];
555
+ continue;
447
556
  }
448
- newPayload += '/' + tagStart + '>';
449
557
  i += tagEnd.length + 1;
558
+ newPayload += '/' + tagStart + '>';
450
559
  }
451
560
  } else {
452
561
  if (payload[i] === '<') {
453
562
  nameStart = i + 1;
563
+ if (payload[nameStart] != 'c') {
564
+ nameStart = -1;
565
+ }
454
566
  } else if (payload[i] === '>') {
455
567
  if (nameStart > 0) {
456
568
  names.push(payload.substr(nameStart, i - nameStart));
@@ -519,12 +631,29 @@ shaka.text.VttTextParser = class {
519
631
  const bold = shaka.text.Cue.fontWeight.BOLD;
520
632
  const italic = shaka.text.Cue.fontStyle.ITALIC;
521
633
  const underline = shaka.text.Cue.textDecoration.UNDERLINE;
522
- const tags = element.nodeName.split(/[ .]+/);
634
+ const tags = element.nodeName.split(/(?=[ .])+/g);
523
635
  for (const tag of tags) {
524
- if (styles.has(tag)) {
525
- VttTextParser.mergeStyle_(nestedCue, styles.get(tag));
636
+ let styleTag = tag;
637
+ // White blanks at start indicate that the style is a voice
638
+ if (styleTag.startsWith('.voice-')) {
639
+ const voice = styleTag.split('-').pop();
640
+ styleTag = `v[voice="${voice}"]`;
641
+ // The specification allows to have quotes and not, so we check to
642
+ // see which one is being used.
643
+ if (!styles.has(styleTag)) {
644
+ styleTag = `v[voice=${voice}]`;
645
+ }
646
+ }
647
+ if (styles.has(styleTag)) {
648
+ VttTextParser.mergeStyle_(nestedCue, styles.get(styleTag));
526
649
  }
527
650
  switch (tag) {
651
+ case 'br': {
652
+ const lineBreakCue = rootCue.clone();
653
+ lineBreakCue.lineBreak = true;
654
+ cues.push(lineBreakCue);
655
+ break;
656
+ }
528
657
  case 'b':
529
658
  nestedCue.fontWeight = bold;
530
659
  break;
@@ -769,6 +898,42 @@ shaka.text.VttTextParser = class {
769
898
 
770
899
  return (milliseconds / 1000) + seconds + (minutes * 60) + (hours * 3600);
771
900
  }
901
+
902
+ /**
903
+ * This method converts the HTML entities &amp;, &lt;, &gt;, &quot;, &#39;,
904
+ * &nbsp;, &lrm; and &rlm; in string to their corresponding characters.
905
+ *
906
+ * @param {!string} input
907
+ * @return {string}
908
+ * @private
909
+ */
910
+ static htmlUnescape_(input) {
911
+ // Used to map HTML entities to characters.
912
+ const htmlUnescapes = {
913
+ '&amp;': '&',
914
+ '&lt;': '<',
915
+ '&gt;': '>',
916
+ '&quot;': '"',
917
+ '&#39;': '\'',
918
+ '&nbsp;': '\u{a0}',
919
+ '&lrm;': '\u{200e}',
920
+ '&rlm;': '\u{200f}',
921
+ };
922
+
923
+ // Used to match HTML entities and HTML characters.
924
+ const reEscapedHtml = /&(?:amp|lt|gt|quot|#(0+)?39|nbsp|lrm|rlm);/g;
925
+ const reHasEscapedHtml = RegExp(reEscapedHtml.source);
926
+ // This check is an optimization, since replace always makes a copy
927
+ if (input && reHasEscapedHtml.test(input)) {
928
+ return input.replace(reEscapedHtml, (entity) => {
929
+ // The only thing that might not match the dictionary above is the
930
+ // single quote, which can be matched by many strings in the regex, but
931
+ // only has a single entry in the dictionary.
932
+ return htmlUnescapes[entity] || '\'';
933
+ });
934
+ }
935
+ return input || '';
936
+ }
772
937
  };
773
938
 
774
939
  /**
@@ -248,6 +248,14 @@ shaka.util.Platform = class {
248
248
  return shaka.util.Platform.userAgentContains_('PlayStation 4');
249
249
  }
250
250
 
251
+ /**
252
+ * Check if the current platform is Hisense.
253
+ */
254
+ static isHisense() {
255
+ return shaka.util.Platform.userAgentContains_('Hisense') ||
256
+ shaka.util.Platform.userAgentContains_('VIDAA');
257
+ }
258
+
251
259
  /**
252
260
  * Check if the current platform is Virgin Media device.
253
261
  */
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "shaka-player",
3
3
  "description": "DASH/EME video player library",
4
- "version": "3.3.16",
4
+ "version": "3.3.17",
5
5
  "homepage": "https://github.com/shaka-project/shaka-player",
6
6
  "author": "Google",
7
7
  "maintainers": [
package/support.html CHANGED
@@ -38,6 +38,7 @@
38
38
  }
39
39
 
40
40
  </style>
41
+ <script defer src="node_modules/mux.js/dist/mux.min.js"></script>
41
42
  <script src="dist/shaka-player.compiled.js"></script>
42
43
  <script>
43
44
  function whenLoaded(fn) {
@@ -194,42 +194,6 @@ describe('DashParser Manifest', () => {
194
194
  }));
195
195
  });
196
196
 
197
- it('rejects periods after one without duration', async () => {
198
- const periodContents = [
199
- ' <AdaptationSet mimeType="video/mp4" lang="en" group="1">',
200
- ' <Representation bandwidth="100">',
201
- ' <SegmentTemplate startNumber="1" media="l-$Number$.mp4">',
202
- ' <SegmentTimeline>',
203
- ' <S t="0" d="10" />',
204
- ' </SegmentTimeline>',
205
- ' </SegmentTemplate>',
206
- ' </Representation>',
207
- ' </AdaptationSet>',
208
- ].join('\n');
209
- const template = [
210
- '<MPD mediaPresentationDuration="PT75S">',
211
- ' <Period id="1">',
212
- '%(periodContents)s',
213
- ' </Period>',
214
- ' <Period id="2">',
215
- '%(periodContents)s',
216
- ' </Period>',
217
- '</MPD>',
218
- ].join('\n');
219
- const source = sprintf(template, {periodContents: periodContents});
220
-
221
- fakeNetEngine.setResponseText('dummy://foo', source);
222
- /** @type {shaka.extern.Manifest} */
223
- const manifest = await parser.start('dummy://foo', playerInterface);
224
- const video = manifest.variants[0].video;
225
- await video.createSegmentIndex();
226
-
227
- // The first period has a segment from 0-10.
228
- // With the second period skipping, we should fail to find a segment at 10.
229
- expect(video.segmentIndex.find(0)).not.toBe(null);
230
- expect(video.segmentIndex.find(10)).toBe(null);
231
- });
232
-
233
197
  it('calculates Period times when missing', async () => {
234
198
  const periodContents = [
235
199
  ' <AdaptationSet mimeType="video/mp4" lang="en" group="1">',