shaka-player 3.3.8 → 3.3.9
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/build-and-test.yaml +67 -15
- package/.github/workflows/compute-incremental-coverage.py +251 -0
- package/.github/workflows/report-incremental-coverage.yaml +39 -0
- package/.github/workflows/selenium-lab-tests.yaml +44 -1
- package/AUTHORS +1 -0
- package/CHANGELOG.md +15 -0
- package/CONTRIBUTORS +1 -0
- package/dist/controls.css +1 -1
- package/dist/locales.js +32 -32
- package/dist/shaka-player.compiled.d.ts +1 -1
- package/dist/shaka-player.compiled.debug.d.ts +1 -1
- package/dist/shaka-player.compiled.debug.externs.js +1 -1
- package/dist/shaka-player.compiled.debug.js +85 -79
- package/dist/shaka-player.compiled.debug.map +3 -3
- package/dist/shaka-player.compiled.externs.js +1 -1
- package/dist/shaka-player.compiled.js +317 -313
- package/dist/shaka-player.compiled.map +3 -3
- package/dist/shaka-player.ui.d.ts +1 -1
- package/dist/shaka-player.ui.debug.d.ts +1 -1
- package/dist/shaka-player.ui.debug.externs.js +1 -1
- package/dist/shaka-player.ui.debug.js +100 -94
- package/dist/shaka-player.ui.debug.map +3 -3
- package/dist/shaka-player.ui.externs.js +1 -1
- package/dist/shaka-player.ui.js +492 -489
- package/dist/shaka-player.ui.map +3 -3
- package/karma.conf.js +3 -11
- package/lib/ads/client_side_ad_manager.js +1 -1
- package/lib/media/drm_engine.js +1 -1
- package/lib/media/streaming_engine.js +1 -0
- package/lib/player.js +1 -6
- package/lib/polyfill/media_capabilities.js +2 -0
- package/lib/text/cue.js +0 -5
- package/lib/text/simple_text_displayer.js +21 -9
- package/lib/text/ui_text_displayer.js +83 -19
- package/lib/util/buffer_utils.js +3 -0
- package/lib/util/object_utils.js +1 -0
- package/lib/util/stream_utils.js +23 -3
- package/lib/util/string_utils.js +63 -16
- package/package.json +5 -2
- package/test/text/simple_text_displayer_unit.js +27 -0
- package/test/text/ui_text_displayer_unit.js +65 -0
- package/test/util/string_utils_unit.js +46 -8
- package/ui/text_selection.js +2 -2
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
|
});
|
|
@@ -409,7 +409,7 @@ shaka.ads.ClientSideAdManager = class {
|
|
|
409
409
|
onAdComplete_(e) {
|
|
410
410
|
this.onEvent_(new shaka.util.FakeEvent(shaka.ads.AdManager.AD_STOPPED,
|
|
411
411
|
(new Map()).set('originalEvent', e)));
|
|
412
|
-
if (this.ad_.isLinear()) {
|
|
412
|
+
if (this.ad_ && this.ad_.isLinear()) {
|
|
413
413
|
this.adContainer_.removeAttribute('ad-active');
|
|
414
414
|
this.video_.play();
|
|
415
415
|
}
|
package/lib/media/drm_engine.js
CHANGED
|
@@ -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);
|
|
@@ -1770,6 +1770,7 @@ shaka.media.StreamingEngine = class {
|
|
|
1770
1770
|
*
|
|
1771
1771
|
* @return {!Promise.<BufferSource>}
|
|
1772
1772
|
* @private
|
|
1773
|
+
* @suppress {strictMissingProperties}
|
|
1773
1774
|
*/
|
|
1774
1775
|
async fetch_(mediaState, reference, streamDataCallback) {
|
|
1775
1776
|
const requestType = shaka.net.NetworkingEngine.RequestType.SEGMENT;
|
package/lib/player.js
CHANGED
|
@@ -851,14 +851,9 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
851
851
|
if (!window.Promise) {
|
|
852
852
|
shaka.log.alwaysWarn('A Promise implementation or polyfill is required');
|
|
853
853
|
}
|
|
854
|
-
if (!window.TextDecoder || !window.TextEncoder) {
|
|
855
|
-
shaka.log.alwaysWarn(
|
|
856
|
-
'A TextDecoder/TextEncoder implementation or polyfill is required');
|
|
857
|
-
}
|
|
858
854
|
|
|
859
855
|
// Basic features needed for the library to be usable.
|
|
860
856
|
const basicSupport = !!window.Promise && !!window.Uint8Array &&
|
|
861
|
-
!!window.TextDecoder && !!window.TextEncoder &&
|
|
862
857
|
// eslint-disable-next-line no-restricted-syntax
|
|
863
858
|
!!Array.prototype.forEach;
|
|
864
859
|
if (!basicSupport) {
|
|
@@ -6634,7 +6629,7 @@ shaka.Player.TYPICAL_BUFFERING_THRESHOLD_ = 0.5;
|
|
|
6634
6629
|
* @define {string} A version number taken from git at compile time.
|
|
6635
6630
|
* @export
|
|
6636
6631
|
*/
|
|
6637
|
-
shaka.Player.version = 'v3.3.
|
|
6632
|
+
shaka.Player.version = 'v3.3.9-uncompiled';
|
|
6638
6633
|
|
|
6639
6634
|
// Initialize the deprecation system using the version string we just set
|
|
6640
6635
|
// on the player.
|
|
@@ -41,6 +41,8 @@ shaka.polyfill.MediaCapabilities = class {
|
|
|
41
41
|
!shaka.util.Platform.isApple() &&
|
|
42
42
|
!shaka.util.Platform.isPS4() &&
|
|
43
43
|
!shaka.util.Platform.isPS5() &&
|
|
44
|
+
!shaka.util.Platform.isWebOS() &&
|
|
45
|
+
!shaka.util.Platform.isTizen() &&
|
|
44
46
|
navigator.mediaCapabilities) {
|
|
45
47
|
shaka.log.info(
|
|
46
48
|
'MediaCapabilities: Native mediaCapabilities support found.');
|
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
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
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
|
|
273
|
-
|
|
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
|
-
|
|
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
|
-
|
|
303
|
-
|
|
304
|
-
shaka.util.Dom.removeAllChildren(
|
|
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
|
/**
|
|
@@ -531,17 +606,6 @@ shaka.text.UITextDisplayer = class {
|
|
|
531
606
|
}
|
|
532
607
|
}
|
|
533
608
|
}
|
|
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
609
|
}
|
|
546
610
|
|
|
547
611
|
style.lineHeight = cue.lineHeight;
|
package/lib/util/buffer_utils.js
CHANGED
|
@@ -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/object_utils.js
CHANGED
|
@@ -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':
|
package/lib/util/stream_utils.js
CHANGED
|
@@ -578,8 +578,16 @@ shaka.util.StreamUtils = class {
|
|
|
578
578
|
// VideoConfiguration
|
|
579
579
|
mediaDecodingConfig.video = {
|
|
580
580
|
contentType: fullType,
|
|
581
|
-
|
|
582
|
-
|
|
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
|
-
|
|
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
|
}
|
package/lib/util/string_utils.js
CHANGED
|
@@ -37,6 +37,7 @@ shaka.util.StringUtils = class {
|
|
|
37
37
|
if (uint8[0] == 0xef && uint8[1] == 0xbb && uint8[2] == 0xbf) {
|
|
38
38
|
uint8 = uint8.subarray(3);
|
|
39
39
|
}
|
|
40
|
+
|
|
40
41
|
if (window.TextDecoder && !shaka.util.Platform.isPS4()) {
|
|
41
42
|
// Use the TextDecoder interface to decode the text. This has the
|
|
42
43
|
// advantage compared to the previously-standard decodeUriComponent that
|
|
@@ -51,23 +52,69 @@ shaka.util.StringUtils = class {
|
|
|
51
52
|
}
|
|
52
53
|
return decoded;
|
|
53
54
|
} else {
|
|
54
|
-
//
|
|
55
|
-
|
|
56
|
-
//
|
|
57
|
-
//
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
55
|
+
// Homebrewed UTF-8 decoder based on
|
|
56
|
+
// https://en.wikipedia.org/wiki/UTF-8#Encoding
|
|
57
|
+
// Unlike decodeURIComponent, won't throw on bad encoding.
|
|
58
|
+
// In this way, it is similar to TextDecoder.
|
|
59
|
+
|
|
60
|
+
let decoded = '';
|
|
61
|
+
for (let i = 0; i < uint8.length; ++i) {
|
|
62
|
+
// By default, the "replacement character" codepoint.
|
|
63
|
+
let codePoint = 0xFFFD;
|
|
64
|
+
|
|
65
|
+
// Top bit is 0, 1-byte encoding.
|
|
66
|
+
if ((uint8[i] & 0x80) == 0) {
|
|
67
|
+
codePoint = uint8[i];
|
|
68
|
+
|
|
69
|
+
// Top 3 bits of byte 0 are 110, top 2 bits of byte 1 are 10,
|
|
70
|
+
// 2-byte encoding.
|
|
71
|
+
} else if (uint8.length >= i + 2 &&
|
|
72
|
+
(uint8[i] & 0xe0) == 0xc0 &&
|
|
73
|
+
(uint8[i + 1] & 0xc0) == 0x80) {
|
|
74
|
+
codePoint = ((uint8[i] & 0x1f) << 6) |
|
|
75
|
+
((uint8[i + 1] & 0x3f));
|
|
76
|
+
i += 1; // Consume one extra byte.
|
|
77
|
+
|
|
78
|
+
// Top 4 bits of byte 0 are 1110, top 2 bits of byte 1 and 2 are 10,
|
|
79
|
+
// 3-byte encoding.
|
|
80
|
+
} else if (uint8.length >= i + 3 &&
|
|
81
|
+
(uint8[i] & 0xf0) == 0xe0 &&
|
|
82
|
+
(uint8[i + 1] & 0xc0) == 0x80 &&
|
|
83
|
+
(uint8[i + 2] & 0xc0) == 0x80) {
|
|
84
|
+
codePoint = ((uint8[i] & 0x0f) << 12) |
|
|
85
|
+
((uint8[i + 1] & 0x3f) << 6) |
|
|
86
|
+
((uint8[i + 2] & 0x3f));
|
|
87
|
+
i += 2; // Consume two extra bytes.
|
|
88
|
+
|
|
89
|
+
// Top 5 bits of byte 0 are 11110, top 2 bits of byte 1, 2 and 3 are 10,
|
|
90
|
+
// 4-byte encoding.
|
|
91
|
+
} else if (uint8.length >= i + 4 &&
|
|
92
|
+
(uint8[i] & 0xf1) == 0xf0 &&
|
|
93
|
+
(uint8[i + 1] & 0xc0) == 0x80 &&
|
|
94
|
+
(uint8[i + 2] & 0xc0) == 0x80 &&
|
|
95
|
+
(uint8[i + 3] & 0xc0) == 0x80) {
|
|
96
|
+
codePoint = ((uint8[i] & 0x07) << 18) |
|
|
97
|
+
((uint8[i + 1] & 0x3f) << 12) |
|
|
98
|
+
((uint8[i + 2] & 0x3f) << 6) |
|
|
99
|
+
((uint8[i + 3] & 0x3f));
|
|
100
|
+
i += 3; // Consume three extra bytes.
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// JavaScript strings are a series of UTF-16 characters.
|
|
104
|
+
if (codePoint <= 0xffff) {
|
|
105
|
+
decoded += String.fromCharCode(codePoint);
|
|
106
|
+
} else {
|
|
107
|
+
// UTF-16 surrogate-pair encoding, based on
|
|
108
|
+
// https://en.wikipedia.org/wiki/UTF-16#Description
|
|
109
|
+
const baseCodePoint = codePoint - 0x10000;
|
|
110
|
+
const highPart = baseCodePoint >> 10;
|
|
111
|
+
const lowPart = baseCodePoint & 0x3ff;
|
|
112
|
+
decoded += String.fromCharCode(0xd800 + highPart);
|
|
113
|
+
decoded += String.fromCharCode(0xdc00 + lowPart);
|
|
114
|
+
}
|
|
70
115
|
}
|
|
116
|
+
|
|
117
|
+
return decoded;
|
|
71
118
|
}
|
|
72
119
|
}
|
|
73
120
|
|
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.
|
|
4
|
+
"version": "3.3.9",
|
|
5
5
|
"homepage": "https://github.com/shaka-project/shaka-player",
|
|
6
6
|
"author": "Google",
|
|
7
7
|
"maintainers": [
|
|
@@ -94,6 +94,9 @@
|
|
|
94
94
|
"prepublishOnly": "python build/checkversion.py && python build/all.py --force"
|
|
95
95
|
},
|
|
96
96
|
"dependencies": {
|
|
97
|
-
"eme-encryption-scheme-polyfill": "^2.
|
|
97
|
+
"eme-encryption-scheme-polyfill": "^2.1.1"
|
|
98
|
+
},
|
|
99
|
+
"engines": {
|
|
100
|
+
"node": ">=14"
|
|
98
101
|
}
|
|
99
102
|
}
|
|
@@ -127,6 +127,33 @@ describe('SimpleTextDisplayer', () => {
|
|
|
127
127
|
[shakaCue]);
|
|
128
128
|
});
|
|
129
129
|
|
|
130
|
+
it('flattens nested cue payloads correctly', () => {
|
|
131
|
+
const level0ContainerCue = new shaka.text.Cue(10, 30, '');
|
|
132
|
+
level0ContainerCue.isContainer = true;
|
|
133
|
+
|
|
134
|
+
const level1NonContainerCueA = new shaka.text.Cue(10, 20, '');
|
|
135
|
+
const level1NonContainerCueB = new shaka.text.Cue(20, 30, '');
|
|
136
|
+
|
|
137
|
+
// Add a trailing whitespace character to get a space-delimited expected
|
|
138
|
+
// result.
|
|
139
|
+
const cueANestedCue0 = new shaka.text.Cue(10, 20, 'Cue A Test0 ');
|
|
140
|
+
const cueANestedCue1 = new shaka.text.Cue(10, 20, 'Cue A Test1');
|
|
141
|
+
const cueBNestedCue0 = new shaka.text.Cue(20, 30, 'Cue B Test0 ');
|
|
142
|
+
const cueBNestedCue1 = new shaka.text.Cue(20, 30, 'Cue B Test1');
|
|
143
|
+
|
|
144
|
+
level1NonContainerCueA.nestedCues = [cueANestedCue0, cueANestedCue1];
|
|
145
|
+
level1NonContainerCueB.nestedCues = [cueBNestedCue0, cueBNestedCue1];
|
|
146
|
+
level0ContainerCue.nestedCues =
|
|
147
|
+
[level1NonContainerCueA, level1NonContainerCueB];
|
|
148
|
+
|
|
149
|
+
verifyHelper(
|
|
150
|
+
[
|
|
151
|
+
{startTime: 10, endTime: 20, text: 'Cue A Test0 Cue A Test1'},
|
|
152
|
+
{startTime: 20, endTime: 30, text: 'Cue B Test0 Cue B Test1'},
|
|
153
|
+
],
|
|
154
|
+
[level0ContainerCue]);
|
|
155
|
+
});
|
|
156
|
+
|
|
130
157
|
// Regression test for b/159050711
|
|
131
158
|
it('maintains the styles of the parent cue', () => {
|
|
132
159
|
const shakaCue = new shaka.text.Cue(10, 20, '');
|
|
@@ -50,6 +50,7 @@ describe('UITextDisplayer', () => {
|
|
|
50
50
|
});
|
|
51
51
|
|
|
52
52
|
beforeEach(() => {
|
|
53
|
+
video.currentTime = 0;
|
|
53
54
|
textDisplayer = new shaka.text.UITextDisplayer(video, videoContainer);
|
|
54
55
|
});
|
|
55
56
|
|
|
@@ -396,4 +397,68 @@ describe('UITextDisplayer', () => {
|
|
|
396
397
|
expect(parentCueElements.length).toBe(1);
|
|
397
398
|
expect(parentCueElements[0].textContent).toBe('');
|
|
398
399
|
});
|
|
400
|
+
|
|
401
|
+
it('creates separate elements for cue regions', () => {
|
|
402
|
+
const cueRegion = new shaka.text.CueRegion();
|
|
403
|
+
cueRegion.id = 'regionId';
|
|
404
|
+
cueRegion.height = 80;
|
|
405
|
+
cueRegion.heightUnits = shaka.text.CueRegion.units.PERCENTAGE;
|
|
406
|
+
cueRegion.width = 80;
|
|
407
|
+
cueRegion.widthUnits = shaka.text.CueRegion.units.PERCENTAGE;
|
|
408
|
+
cueRegion.viewportAnchorX = 10;
|
|
409
|
+
cueRegion.viewportAnchorY = 10;
|
|
410
|
+
cueRegion.viewportAnchorUnits = shaka.text.CueRegion.units.PERCENTAGE;
|
|
411
|
+
|
|
412
|
+
// These all attach to the same region, but only one region element should
|
|
413
|
+
// be created.
|
|
414
|
+
const cues = [
|
|
415
|
+
new shaka.text.Cue(0, 100, ''),
|
|
416
|
+
new shaka.text.Cue(0, 100, ''),
|
|
417
|
+
new shaka.text.Cue(0, 100, ''),
|
|
418
|
+
];
|
|
419
|
+
for (const cue of cues) {
|
|
420
|
+
cue.displayAlign = shaka.text.Cue.displayAlign.CENTER;
|
|
421
|
+
cue.region = cueRegion;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
textDisplayer.setTextVisibility(true);
|
|
425
|
+
textDisplayer.append(cues);
|
|
426
|
+
updateCaptions();
|
|
427
|
+
|
|
428
|
+
const textContainer = videoContainer.querySelector('.shaka-text-container');
|
|
429
|
+
const allRegionElements = textContainer.querySelectorAll(
|
|
430
|
+
'.shaka-text-region');
|
|
431
|
+
|
|
432
|
+
// Verify that the nested cues are all attached to a single region element.
|
|
433
|
+
expect(allRegionElements.length).toBe(1);
|
|
434
|
+
const regionElement = allRegionElements[0];
|
|
435
|
+
const children = Array.from(regionElement.childNodes).filter(
|
|
436
|
+
(e) => e.nodeType == Node.ELEMENT_NODE);
|
|
437
|
+
expect(children.length).toBe(3);
|
|
438
|
+
|
|
439
|
+
// Verify styles applied to the region element.
|
|
440
|
+
const regionCssObj = parseCssText(regionElement.style.cssText);
|
|
441
|
+
const expectRegionCssObj = {
|
|
442
|
+
'position': 'absolute',
|
|
443
|
+
'height': '80%',
|
|
444
|
+
'width': '80%',
|
|
445
|
+
'top': '10%',
|
|
446
|
+
'left': '10%',
|
|
447
|
+
'display': 'flex',
|
|
448
|
+
'flex-direction': 'column',
|
|
449
|
+
'align-items': 'center',
|
|
450
|
+
'justify-content': 'center',
|
|
451
|
+
};
|
|
452
|
+
expect(regionCssObj).toEqual(jasmine.objectContaining(expectRegionCssObj));
|
|
453
|
+
|
|
454
|
+
for (const caption of children) {
|
|
455
|
+
// Verify that styles applied to the nested cues _DO NOT_ include region
|
|
456
|
+
// placement.
|
|
457
|
+
const cueCssObj = parseCssText(caption.style.cssText);
|
|
458
|
+
expect(Object.keys(cueCssObj)).not.toContain('height');
|
|
459
|
+
expect(Object.keys(cueCssObj)).not.toContain('width');
|
|
460
|
+
expect(Object.keys(cueCssObj)).not.toContain('top');
|
|
461
|
+
expect(Object.keys(cueCssObj)).not.toContain('left');
|
|
462
|
+
}
|
|
463
|
+
});
|
|
399
464
|
});
|