shaka-player 3.2.17 → 3.2.19
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.
- package/.github/workflows/selenium-lab-tests.yaml +22 -0
- package/CHANGELOG.md +27 -0
- package/dist/locales.js +28 -28
- package/dist/shaka-player.compiled.d.ts +5 -0
- package/dist/shaka-player.compiled.debug.d.ts +5 -0
- package/dist/shaka-player.compiled.debug.externs.js +4 -0
- package/dist/shaka-player.compiled.debug.js +93 -87
- package/dist/shaka-player.compiled.debug.map +3 -3
- package/dist/shaka-player.compiled.externs.js +4 -0
- package/dist/shaka-player.compiled.js +258 -255
- package/dist/shaka-player.compiled.map +3 -3
- package/dist/shaka-player.ui.d.ts +5 -0
- package/dist/shaka-player.ui.debug.d.ts +5 -0
- package/dist/shaka-player.ui.debug.externs.js +4 -0
- package/dist/shaka-player.ui.debug.js +107 -101
- package/dist/shaka-player.ui.debug.map +3 -3
- package/dist/shaka-player.ui.externs.js +4 -0
- package/dist/shaka-player.ui.js +271 -267
- package/dist/shaka-player.ui.map +3 -3
- package/externs/shaka/text.js +7 -0
- package/lib/dash/segment_template.js +17 -5
- package/lib/hls/hls_parser.js +1 -1
- package/lib/player.js +7 -1
- package/lib/polyfill/media_capabilities.js +1 -0
- package/lib/text/cue.js +6 -0
- package/lib/text/ui_text_displayer.js +25 -9
- package/lib/text/vtt_text_parser.js +256 -91
- package/lib/text/web_vtt_generator.js +21 -9
- package/lib/util/dom_utils.js +3 -1
- package/lib/util/platform.js +8 -0
- package/lib/util/stream_utils.js +6 -0
- package/package.json +1 -1
- package/support.html +1 -0
- package/test/dash/dash_parser_manifest_unit.js +0 -36
- package/test/player_unit.js +4 -2
- package/test/test/util/layout_tests.js +433 -0
- package/test/test/util/util.js +0 -120
- package/test/{ui → text}/text_displayer_layout_unit.js +59 -235
- package/test/text/ui_text_displayer_unit.js +72 -4
- package/test/text/vtt_text_parser_unit.js +127 -6
- package/test/text/web_vtt_layout_integration.js +528 -0
package/externs/shaka/text.js
CHANGED
|
@@ -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 letter spacing as a CSS letter-spacing value.
|
|
302
309
|
* @type {string}
|
|
@@ -98,11 +98,23 @@ shaka.dash.SegmentTemplate = class {
|
|
|
98
98
|
const periodEnd = context.periodInfo.duration ?
|
|
99
99
|
context.periodInfo.start + context.periodInfo.duration : Infinity;
|
|
100
100
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
101
|
+
/* When to fit segments. All refactors should honor/update this table:
|
|
102
|
+
*
|
|
103
|
+
* | dynamic | infinite | last | should | notes |
|
|
104
|
+
* | | period | period | fit | |
|
|
105
|
+
* | ------- | -------- | ------ | ------ | ------------------------- |
|
|
106
|
+
* | F | F | X | T | typical VOD |
|
|
107
|
+
* | F | T | X | X | impossible: infinite VOD |
|
|
108
|
+
* | T | F | F | T | typical live, old period |
|
|
109
|
+
* | T | F | T | F | typical IPR |
|
|
110
|
+
* | T | T | F | X | impossible: old, infinite |
|
|
111
|
+
* | T | T | T | F | typical live, new period |
|
|
112
|
+
*/
|
|
113
|
+
|
|
114
|
+
// We never fit the final period of dynamic content, which could be
|
|
115
|
+
// infinite live (with no limit to fit to) or IPR (which would expand the
|
|
116
|
+
// most recent segment to the end of the presentation).
|
|
117
|
+
const shouldFit = !(context.dynamic && context.periodInfo.isLastPeriod);
|
|
106
118
|
|
|
107
119
|
if (segmentIndex) {
|
|
108
120
|
if (shouldFit) {
|
package/lib/hls/hls_parser.js
CHANGED
|
@@ -2976,7 +2976,7 @@ shaka.hls.HlsParser.TEXT_EXTENSIONS_TO_MIME_TYPES_ = {
|
|
|
2976
2976
|
'm4f': 'application/mp4',
|
|
2977
2977
|
'cmft': 'application/mp4',
|
|
2978
2978
|
'vtt': 'text/vtt',
|
|
2979
|
-
'webvtt': '
|
|
2979
|
+
'webvtt': 'text/vtt',
|
|
2980
2980
|
'ttml': 'application/ttml+xml',
|
|
2981
2981
|
};
|
|
2982
2982
|
|
package/lib/player.js
CHANGED
|
@@ -1342,6 +1342,12 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
1342
1342
|
has.startTime = null;
|
|
1343
1343
|
has.uri = null;
|
|
1344
1344
|
|
|
1345
|
+
// Release the region timeline, which is created when parsing the manifest.
|
|
1346
|
+
if (this.regionTimeline_) {
|
|
1347
|
+
this.regionTimeline_.release();
|
|
1348
|
+
this.regionTimeline_ = null;
|
|
1349
|
+
}
|
|
1350
|
+
|
|
1345
1351
|
// In most cases we should have a media element. The one exception would
|
|
1346
1352
|
// be if there was an error and we, by chance, did not have a media element.
|
|
1347
1353
|
if (has.mediaElement) {
|
|
@@ -6453,7 +6459,7 @@ shaka.Player.TYPICAL_BUFFERING_THRESHOLD_ = 0.5;
|
|
|
6453
6459
|
* @define {string} A version number taken from git at compile time.
|
|
6454
6460
|
* @export
|
|
6455
6461
|
*/
|
|
6456
|
-
shaka.Player.version = 'v3.2.
|
|
6462
|
+
shaka.Player.version = 'v3.2.19-uncompiled';
|
|
6457
6463
|
|
|
6458
6464
|
// Initialize the deprecation system using the version string we just set
|
|
6459
6465
|
// 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
|
@@ -372,6 +372,25 @@ shaka.text.UITextDisplayer = class {
|
|
|
372
372
|
}
|
|
373
373
|
}
|
|
374
374
|
|
|
375
|
+
/**
|
|
376
|
+
* Compute a unique internal id:
|
|
377
|
+
* Regions can reuse the id but have different dimensions, we need to
|
|
378
|
+
* consider those differences
|
|
379
|
+
* @param {shaka.extern.CueRegion} region
|
|
380
|
+
* @private
|
|
381
|
+
*/
|
|
382
|
+
generateRegionId_(region) {
|
|
383
|
+
const percentageUnit = shaka.text.CueRegion.units.PERCENTAGE;
|
|
384
|
+
const heightUnit = region.heightUnits == percentageUnit ? '%' : 'px';
|
|
385
|
+
const viewportAnchorUnit =
|
|
386
|
+
region.viewportAnchorUnits == percentageUnit ? '%' : 'px';
|
|
387
|
+
const uniqueRegionId = `${region.id}_${
|
|
388
|
+
region.width}x${region.height}${heightUnit}-${
|
|
389
|
+
region.viewportAnchorX}x${region.viewportAnchorY}${viewportAnchorUnit}`;
|
|
390
|
+
|
|
391
|
+
return uniqueRegionId;
|
|
392
|
+
}
|
|
393
|
+
|
|
375
394
|
/**
|
|
376
395
|
* Get or create a region element corresponding to the cue region. These are
|
|
377
396
|
* cached by ID.
|
|
@@ -383,8 +402,9 @@ shaka.text.UITextDisplayer = class {
|
|
|
383
402
|
getRegionElement_(cue) {
|
|
384
403
|
const region = cue.region;
|
|
385
404
|
|
|
386
|
-
|
|
387
|
-
|
|
405
|
+
const regionId = this.generateRegionId_(region);
|
|
406
|
+
if (this.regionElements_.has(regionId)) {
|
|
407
|
+
return this.regionElements_.get(regionId);
|
|
388
408
|
}
|
|
389
409
|
|
|
390
410
|
const regionElement = shaka.util.Dom.createHTMLElement('span');
|
|
@@ -395,7 +415,7 @@ shaka.text.UITextDisplayer = class {
|
|
|
395
415
|
const viewportAnchorUnit =
|
|
396
416
|
region.viewportAnchorUnits == percentageUnit ? '%' : 'px';
|
|
397
417
|
|
|
398
|
-
regionElement.id = 'shaka-text-region---' +
|
|
418
|
+
regionElement.id = 'shaka-text-region---' + regionId;
|
|
399
419
|
regionElement.classList.add('shaka-text-region');
|
|
400
420
|
|
|
401
421
|
regionElement.style.height = region.height + heightUnit;
|
|
@@ -416,7 +436,7 @@ shaka.text.UITextDisplayer = class {
|
|
|
416
436
|
regionElement.style.justifyContent = 'flex-end';
|
|
417
437
|
}
|
|
418
438
|
|
|
419
|
-
this.regionElements_.set(
|
|
439
|
+
this.regionElements_.set(regionId, regionElement);
|
|
420
440
|
return regionElement;
|
|
421
441
|
}
|
|
422
442
|
|
|
@@ -509,6 +529,7 @@ shaka.text.UITextDisplayer = class {
|
|
|
509
529
|
style.paddingRight =
|
|
510
530
|
shaka.text.UITextDisplayer.convertLengthValue_(
|
|
511
531
|
cue.linePadding, cue, this.videoContainer_);
|
|
532
|
+
style.textShadow = cue.textShadow;
|
|
512
533
|
|
|
513
534
|
if (cue.backgroundImage) {
|
|
514
535
|
style.backgroundImage = 'url(\'' + cue.backgroundImage + '\')';
|
|
@@ -602,12 +623,7 @@ shaka.text.UITextDisplayer = class {
|
|
|
602
623
|
// TODO: Implement lineAlignment of 'CENTER'.
|
|
603
624
|
if (cue.line != null) {
|
|
604
625
|
if (cue.lineInterpretation == Cue.lineInterpretation.PERCENTAGE) {
|
|
605
|
-
// When setting absolute positioning, you need to set x/y/width/height
|
|
606
|
-
// so the element is positioned correctly. Set these as default and
|
|
607
|
-
// other settings will override them.
|
|
608
626
|
style.position = 'absolute';
|
|
609
|
-
style.left = '0';
|
|
610
|
-
style.top = '0';
|
|
611
627
|
if (cue.writingMode == Cue.writingMode.HORIZONTAL_TOP_TO_BOTTOM) {
|
|
612
628
|
style.width = '100%';
|
|
613
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
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
let
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
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
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
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
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
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.
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
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.
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
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
|
-
|
|
287
|
-
|
|
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 =
|
|
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 (!
|
|
440
|
-
|
|
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.')
|
|
446
|
-
|
|
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
|
-
|
|
525
|
-
|
|
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 &, <, >, ", ',
|
|
904
|
+
* , ‎ and ‏ 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
|
+
'&': '&',
|
|
914
|
+
'<': '<',
|
|
915
|
+
'>': '>',
|
|
916
|
+
'"': '"',
|
|
917
|
+
''': '\'',
|
|
918
|
+
' ': '\u{a0}',
|
|
919
|
+
'‎': '\u{200e}',
|
|
920
|
+
'‏': '\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
|
/**
|
|
@@ -87,16 +87,28 @@ shaka.text.WebVttGenerator = class {
|
|
|
87
87
|
// We don't want to modify the array or objects passed in, since we don't
|
|
88
88
|
// technically own them. So we build a new array and replace certain items
|
|
89
89
|
// in it if they need to be flattened.
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
90
|
+
// We also don't want to flatten the text payloads starting at a container
|
|
91
|
+
// element; otherwise, for containers encapsulating multiple caption lines,
|
|
92
|
+
// the lines would merge into a single cue. This is undesirable when a
|
|
93
|
+
// subset of the captions are outside of the append time window. To fix
|
|
94
|
+
// this, we only call flattenPayload() starting at elements marked as
|
|
95
|
+
// isContainer = false.
|
|
96
|
+
const getCuesToFlatten = (cues, result) => {
|
|
97
|
+
for (const cue of cues) {
|
|
98
|
+
if (cue.isContainer) {
|
|
99
|
+
// Recurse to find the actual text payload cues.
|
|
100
|
+
getCuesToFlatten(cue.nestedCues, result);
|
|
101
|
+
} else {
|
|
102
|
+
// Flatten the payload.
|
|
103
|
+
const flatCue = cue.clone();
|
|
104
|
+
flatCue.nestedCues = [];
|
|
105
|
+
flatCue.payload = flattenPayload(cue);
|
|
106
|
+
result.push(flatCue);
|
|
107
|
+
}
|
|
98
108
|
}
|
|
99
|
-
|
|
109
|
+
return result;
|
|
110
|
+
};
|
|
111
|
+
const flattenedCues = getCuesToFlatten(cues, []);
|
|
100
112
|
|
|
101
113
|
let webvttString = 'WEBVTT\n\n';
|
|
102
114
|
for (const cue of flattenedCues) {
|
package/lib/util/dom_utils.js
CHANGED
|
@@ -36,7 +36,9 @@ shaka.util.Dom = class {
|
|
|
36
36
|
* @return {!HTMLButtonElement}
|
|
37
37
|
*/
|
|
38
38
|
static createButton() {
|
|
39
|
-
|
|
39
|
+
const button = document.createElement('button');
|
|
40
|
+
button.setAttribute('type', 'button');
|
|
41
|
+
return /** @type {!HTMLButtonElement} */ (button);
|
|
40
42
|
}
|
|
41
43
|
|
|
42
44
|
|