mirror of
https://github.com/shaka-project/shaka-player.git
synced 2026-06-13 15:46:46 +03:00
781 lines
27 KiB
JavaScript
781 lines
27 KiB
JavaScript
/*! @license
|
|
* Shaka Player
|
|
* Copyright 2016 Google LLC
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
describe('UITextDisplayer', () => {
|
|
/** @type {!HTMLElement} */
|
|
let videoContainer;
|
|
/** @type {!HTMLVideoElement} */
|
|
let video;
|
|
/** @type {shaka.text.UITextDisplayer} */
|
|
let textDisplayer;
|
|
/** @type {number} */
|
|
const videoContainerHeight = 450;
|
|
/** @type {Object} **/
|
|
let player;
|
|
|
|
/**
|
|
* Transform a cssText to an object.
|
|
* Example:
|
|
* cssText: 'background-color: black; color: green; font-size: 10px;'
|
|
* cssObject: {
|
|
* background-color: 'black',
|
|
* color: 'green',
|
|
* font-size: '10px',
|
|
* }
|
|
* @param {string} cssStr
|
|
* @return {!Object<string, string | number>}
|
|
*/
|
|
function parseCssText(cssStr) {
|
|
// Remove the white spaces in the string.
|
|
// Split with ';' and ignore the last one.
|
|
const css = cssStr.replace(/\s/g, '').substring(0, cssStr.length - 1)
|
|
.split(';');
|
|
const cssObj = {};
|
|
for (const cssStyle of css) {
|
|
const propertyAndValue = cssStyle.split(':');
|
|
let value = propertyAndValue[1];
|
|
value = isNaN(value) ? value : Number(value);
|
|
cssObj[propertyAndValue[0]] = value;
|
|
}
|
|
return cssObj;
|
|
}
|
|
|
|
beforeAll(() => {
|
|
videoContainer =
|
|
/** @type {!HTMLElement} */ (document.createElement('div'));
|
|
videoContainer.style.height = `${videoContainerHeight}px`;
|
|
document.body.appendChild(videoContainer);
|
|
video = new shaka.test.FakeVideo();
|
|
player = {
|
|
getMediaElement: () => video,
|
|
getVideoContainer: () => videoContainer,
|
|
};
|
|
});
|
|
|
|
beforeEach(() => {
|
|
video.currentTime = 0;
|
|
/** @suppress {checkTypes} */
|
|
textDisplayer = new shaka.text.UITextDisplayer(player);
|
|
});
|
|
|
|
afterEach(async () => {
|
|
await textDisplayer.destroy();
|
|
});
|
|
|
|
afterAll(() => {
|
|
document.body.removeChild(videoContainer);
|
|
});
|
|
|
|
/**
|
|
* @suppress {visibility}
|
|
* "suppress visibility" has function scope, so this is a mini-function that
|
|
* exists solely to suppress visibility rules for these actions.
|
|
*/
|
|
function updateCaptions() {
|
|
// Rather than wait for a timer, which can be unreliable on Safari when the
|
|
// device is heavily loaded, trigger the update explicitly.
|
|
textDisplayer.updateCaptions_();
|
|
}
|
|
|
|
it('correctly displays styles for cues', () => {
|
|
/** @type {!shaka.text.Cue} */
|
|
const cue = new shaka.text.Cue(0, 100, 'Captain\'s log.');
|
|
cue.color = 'green';
|
|
cue.backgroundColor = 'black';
|
|
cue.direction = shaka.text.Cue.direction.HORIZONTAL_LEFT_TO_RIGHT;
|
|
const cueSampleFontSize = '10px';
|
|
cue.fontSize = cueSampleFontSize;
|
|
cue.fontWeight = shaka.text.Cue.fontWeight.NORMAL;
|
|
cue.fontStyle = shaka.text.Cue.fontStyle.NORMAL;
|
|
cue.lineHeight = '2';
|
|
cue.nestedCues = [];
|
|
cue.textAlign = shaka.text.Cue.textAlign.CENTER;
|
|
cue.writingMode = shaka.text.Cue.writingMode.HORIZONTAL_TOP_TO_BOTTOM;
|
|
|
|
textDisplayer.setTextVisibility(true);
|
|
textDisplayer.append([cue]);
|
|
updateCaptions();
|
|
|
|
const textContainer = videoContainer.querySelector('.shaka-text-container');
|
|
const captions = textContainer.querySelector('div');
|
|
const cssObj = parseCssText(captions.style.cssText);
|
|
|
|
const expectCssObj = {
|
|
'color': 'green',
|
|
'direction': 'ltr',
|
|
'font-size': cueSampleFontSize,
|
|
'font-style': 'normal',
|
|
'font-weight': 400,
|
|
'text-align': 'center',
|
|
};
|
|
|
|
// Either the prefixed or unprefixed version may be present. We will accept
|
|
// either. Detecting which property the platform has may not work, because
|
|
// Tizen 3, for example, has a writingMode property, but it is
|
|
// non-functional. Instead of checking for which properties are on the
|
|
// platform's style interface, check which properties are in the cssObj.
|
|
// We expect one or the other to work on all supported platforms.
|
|
if ('writing-mode' in cssObj) {
|
|
expectCssObj['writing-mode'] = 'horizontal-tb';
|
|
} else {
|
|
expectCssObj['-webkit-writing-mode'] = 'horizontal-tb';
|
|
}
|
|
|
|
expect(cssObj).toEqual(jasmine.objectContaining(expectCssObj));
|
|
expect(parseCssText(textContainer.querySelector('span').style.cssText))
|
|
.toEqual(jasmine.objectContaining({'background-color': 'black'}));
|
|
});
|
|
|
|
it('correctly displays styles for nested cues', () => {
|
|
/** @type {!shaka.text.Cue} */
|
|
const cue = new shaka.text.Cue(0, 100, '');
|
|
const nestedCue = new shaka.text.Cue(0, 100, 'Captain\'s log.');
|
|
cue.nestedCues = [nestedCue];
|
|
nestedCue.textAlign = shaka.text.Cue.textAlign.CENTER;
|
|
nestedCue.writingMode = shaka.text.Cue.writingMode.HORIZONTAL_TOP_TO_BOTTOM;
|
|
nestedCue.color = 'green';
|
|
nestedCue.backgroundColor = 'black';
|
|
const cueSampleFontSize = '10px';
|
|
nestedCue.fontSize = cueSampleFontSize;
|
|
nestedCue.fontWeight = shaka.text.Cue.fontWeight.NORMAL;
|
|
nestedCue.fontStyle = shaka.text.Cue.fontStyle.NORMAL;
|
|
nestedCue.lineHeight = '2';
|
|
nestedCue.nestedCues = [];
|
|
|
|
textDisplayer.setTextVisibility(true);
|
|
textDisplayer.append([cue]);
|
|
updateCaptions();
|
|
|
|
// Verify styles applied to the nested cues.
|
|
const textContainer = videoContainer.querySelector('.shaka-text-container');
|
|
const captions =
|
|
textContainer.querySelector('span:not(.shaka-text-wrapper)');
|
|
const cssObj = parseCssText(captions.style.cssText);
|
|
|
|
const expectCssObj = {
|
|
'color': 'green',
|
|
'font-size': cueSampleFontSize,
|
|
'font-style': 'normal',
|
|
'font-weight': 400,
|
|
'text-align': 'center',
|
|
};
|
|
|
|
// Either the prefixed or unprefixed version may be present. We will accept
|
|
// either. Detecting which property the platform has may not work, because
|
|
// Tizen 3, for example, has a writingMode property, but it is
|
|
// non-functional. Instead of checking for which properties are on the
|
|
// platform's style interface, check which properties are in the cssObj.
|
|
// We expect one or the other to work on all supported platforms.
|
|
if ('writing-mode' in cssObj) {
|
|
expectCssObj['writing-mode'] = 'horizontal-tb';
|
|
} else {
|
|
expectCssObj['-webkit-writing-mode'] = 'horizontal-tb';
|
|
}
|
|
|
|
expect(cssObj).toEqual(jasmine.objectContaining(expectCssObj));
|
|
expect(parseCssText(captions.querySelector('span').style.cssText))
|
|
.toEqual(jasmine.objectContaining({'background-color': 'black'}));
|
|
});
|
|
|
|
it('correctly displays styles for cellResolution units', () => {
|
|
/** @type {!shaka.text.Cue} */
|
|
const cue = new shaka.text.Cue(0, 100, 'Captain\'s log.');
|
|
|
|
const fontSizeAsCellResolution = 0.80;
|
|
cue.fontSize = `${fontSizeAsCellResolution}c`;
|
|
cue.linePadding = '0.50c';
|
|
cue.cellResolution = {
|
|
columns: 60,
|
|
rows: 20,
|
|
};
|
|
|
|
textDisplayer.setTextVisibility(true);
|
|
textDisplayer.append([cue]);
|
|
updateCaptions();
|
|
|
|
// Expected value is calculated based on ttp:cellResolution="60 20"
|
|
// videoContainerHeight=450px and tts:fontSize="0.80c" on the default style.
|
|
const calculatedFontSize = (450/20) * fontSizeAsCellResolution;
|
|
const expectedFontSize = `${calculatedFontSize}px`;
|
|
|
|
// Expected value is calculated based on ttp:cellResolution="60 20"
|
|
// videoContainerHeight=450px and ebutts:linePadding="0.5c" on the default
|
|
// style.
|
|
const expectedLinePadding = '11.25px';
|
|
|
|
const textContainer = videoContainer.querySelector('.shaka-text-container');
|
|
const captions = textContainer.querySelector('div');
|
|
const cssObj = parseCssText(captions.style.cssText);
|
|
expect(cssObj).toEqual(
|
|
jasmine.objectContaining({
|
|
'font-size': expectedFontSize,
|
|
'padding-left': expectedLinePadding,
|
|
'padding-right': expectedLinePadding,
|
|
}));
|
|
});
|
|
|
|
it('correctly displays styles for percentages units', () => {
|
|
/** @type {!shaka.text.Cue} */
|
|
const cue = new shaka.text.Cue(0, 100, 'Captain\'s log.');
|
|
const cueSampleFontSize = 90;
|
|
cue.fontSize = `${cueSampleFontSize}%`;
|
|
cue.cellResolution = {
|
|
columns: 32,
|
|
rows: 15,
|
|
};
|
|
|
|
textDisplayer.setTextVisibility(true);
|
|
textDisplayer.append([cue]);
|
|
updateCaptions();
|
|
|
|
// Expected value is calculated based on ttp:cellResolution="32 15"
|
|
// videoContainerHeight=450px and tts:fontSize="90%" on the default style.
|
|
const calculatedFontSize = (450/15) * (cueSampleFontSize/100);
|
|
const expectedFontSize = `${calculatedFontSize}px`;
|
|
|
|
const textContainer = videoContainer.querySelector('.shaka-text-container');
|
|
const captions = textContainer.querySelector('div');
|
|
const cssObj = parseCssText(captions.style.cssText);
|
|
expect(cssObj).toEqual(
|
|
jasmine.objectContaining({'font-size': expectedFontSize}));
|
|
});
|
|
|
|
it('does not display duplicate cues', () => {
|
|
// These are identical.
|
|
const cue1 = new shaka.text.Cue(0, 100, 'Captain\'s log.');
|
|
const cue2 = new shaka.text.Cue(0, 100, 'Captain\'s log.');
|
|
|
|
textDisplayer.setTextVisibility(true);
|
|
textDisplayer.append([cue1]);
|
|
updateCaptions();
|
|
|
|
/** @type {Element} */
|
|
const textContainer = videoContainer.querySelector('.shaka-text-container');
|
|
let captions = textContainer.querySelectorAll('div');
|
|
// Expect textContainer to display this cue.
|
|
expect(captions.length).toBe(1);
|
|
|
|
textDisplayer.append([cue2]);
|
|
updateCaptions();
|
|
|
|
captions = textContainer.querySelectorAll('div');
|
|
// Expect textContainer to display one cue without duplication.
|
|
expect(captions.length).toBe(1);
|
|
});
|
|
|
|
it('does not mistake cues with nested cues as duplicates', () => {
|
|
// These are not identical, but might look like it at the top level.
|
|
const cue1 = new shaka.text.Cue(0, 100, '');
|
|
cue1.nestedCues = [
|
|
new shaka.text.Cue(0, 100, 'Nested cue 1.'),
|
|
];
|
|
const cue2 = new shaka.text.Cue(0, 100, '');
|
|
cue2.nestedCues = [
|
|
new shaka.text.Cue(0, 100, 'Nested cue 2.'),
|
|
];
|
|
const cue3 = new shaka.text.Cue(0, 100, '');
|
|
cue3.nestedCues = [
|
|
new shaka.text.Cue(0, 100, 'Nested cue 3.'),
|
|
];
|
|
|
|
textDisplayer.setTextVisibility(true);
|
|
textDisplayer.append([cue1]);
|
|
updateCaptions();
|
|
|
|
/** @type {Element} */
|
|
const textContainer = videoContainer.querySelector('.shaka-text-container');
|
|
let captions = textContainer.querySelectorAll('div');
|
|
// Expect textContainer to display this cue.
|
|
expect(captions.length).toBe(1);
|
|
|
|
textDisplayer.append([cue2, cue3]);
|
|
updateCaptions();
|
|
|
|
captions = textContainer.querySelectorAll('div');
|
|
// Expect textContainer to display all three cues, since they are not truly
|
|
// duplicates.
|
|
expect(captions.length).toBe(3);
|
|
});
|
|
|
|
it('does not mistake cues with different styles duplicates', () => {
|
|
// These all have the same text and timing, but different styles.
|
|
const cue1 = new shaka.text.Cue(0, 100, 'Hello!');
|
|
cue1.color = 'green';
|
|
|
|
const cue2 = new shaka.text.Cue(0, 100, 'Hello!');
|
|
cue2.color = 'green';
|
|
cue2.fontStyle = shaka.text.Cue.fontStyle.ITALIC;
|
|
|
|
const cue3 = new shaka.text.Cue(0, 100, 'Hello!');
|
|
cue3.color = 'blue';
|
|
|
|
textDisplayer.setTextVisibility(true);
|
|
textDisplayer.append([cue1]);
|
|
updateCaptions();
|
|
|
|
/** @type {Element} */
|
|
const textContainer = videoContainer.querySelector('.shaka-text-container');
|
|
let captions = textContainer.querySelectorAll('div');
|
|
// Expect textContainer to display this cue.
|
|
expect(captions.length).toBe(1);
|
|
|
|
textDisplayer.append([cue2, cue3]);
|
|
updateCaptions();
|
|
|
|
captions = textContainer.querySelectorAll('div');
|
|
// Expect textContainer to display all three cues, since they are not truly
|
|
// duplicates.
|
|
expect(captions.length).toBe(3);
|
|
});
|
|
|
|
it('hides currently displayed cue when removed', () => {
|
|
const cue = new shaka.text.Cue(0, 50, 'One');
|
|
textDisplayer.setTextVisibility(true);
|
|
textDisplayer.append([cue]);
|
|
video.currentTime = 10;
|
|
updateCaptions();
|
|
const textContainer = videoContainer.querySelector('.shaka-text-container');
|
|
|
|
let cueElements = textContainer.querySelectorAll('div');
|
|
expect(cueElements.length).toBe(1);
|
|
expect(cueElements[0].textContent).toBe('One');
|
|
|
|
textDisplayer.remove(0, 100);
|
|
|
|
cueElements = textContainer.querySelectorAll('div');
|
|
expect(cueElements.length).toBe(0);
|
|
});
|
|
|
|
it('hides and shows nested cues at appropriate times', () => {
|
|
const parentCue1 = new shaka.text.Cue(0, 100, '');
|
|
const cue1 = new shaka.text.Cue(0, 50, 'One');
|
|
const cue2 = new shaka.text.Cue(25, 75, 'Two');
|
|
const cue3 = new shaka.text.Cue(50, 100, 'Three');
|
|
parentCue1.nestedCues = [cue1, cue2, cue3];
|
|
|
|
const parentCue2 = new shaka.text.Cue(90, 190, '');
|
|
const cue4 = new shaka.text.Cue(90, 130, 'Four');
|
|
parentCue2.nestedCues = [cue4];
|
|
|
|
textDisplayer.setTextVisibility(true);
|
|
textDisplayer.append([parentCue1, parentCue2]);
|
|
|
|
video.currentTime = 10;
|
|
updateCaptions();
|
|
/** @type {Element} */
|
|
const textContainer = videoContainer.querySelector('.shaka-text-container');
|
|
let parentCueElements = textContainer.querySelectorAll('div');
|
|
|
|
expect(parentCueElements.length).toBe(1);
|
|
expect(parentCueElements[0].textContent).toBe('One');
|
|
|
|
video.currentTime = 35;
|
|
updateCaptions();
|
|
parentCueElements = textContainer.querySelectorAll('div');
|
|
expect(parentCueElements.length).toBe(1);
|
|
expect(parentCueElements[0].textContent).toBe('OneTwo');
|
|
|
|
video.currentTime = 60;
|
|
updateCaptions();
|
|
parentCueElements = textContainer.querySelectorAll('div');
|
|
expect(parentCueElements.length).toBe(1);
|
|
expect(parentCueElements[0].textContent).toBe('TwoThree');
|
|
|
|
video.currentTime = 85;
|
|
updateCaptions();
|
|
parentCueElements = textContainer.querySelectorAll('div');
|
|
expect(parentCueElements.length).toBe(1);
|
|
expect(parentCueElements[0].textContent).toBe('Three');
|
|
|
|
video.currentTime = 95;
|
|
updateCaptions();
|
|
parentCueElements = textContainer.querySelectorAll('div');
|
|
expect(parentCueElements.length).toBe(2);
|
|
expect(parentCueElements[0].textContent).toBe('Three');
|
|
expect(parentCueElements[1].textContent).toBe('Four');
|
|
|
|
video.currentTime = 105;
|
|
updateCaptions();
|
|
parentCueElements = textContainer.querySelectorAll('div');
|
|
expect(parentCueElements.length).toBe(1);
|
|
expect(parentCueElements[0].textContent).toBe('Four');
|
|
|
|
video.currentTime = 150;
|
|
updateCaptions();
|
|
parentCueElements = textContainer.querySelectorAll('div');
|
|
expect(parentCueElements.length).toBe(1);
|
|
expect(parentCueElements[0].textContent).toBe('');
|
|
});
|
|
|
|
it('creates separate elements for cue regions', () => {
|
|
const cueRegion = new shaka.text.CueRegion();
|
|
cueRegion.id = 'regionId';
|
|
cueRegion.height = 80;
|
|
cueRegion.heightUnits = shaka.text.CueRegion.units.PERCENTAGE;
|
|
cueRegion.width = 80;
|
|
cueRegion.widthUnits = shaka.text.CueRegion.units.PERCENTAGE;
|
|
cueRegion.viewportAnchorX = 10;
|
|
cueRegion.viewportAnchorY = 10;
|
|
cueRegion.viewportAnchorUnits = shaka.text.CueRegion.units.PERCENTAGE;
|
|
|
|
// These all attach to the same region, but only one region element should
|
|
// be created.
|
|
const cues = [
|
|
new shaka.text.Cue(0, 100, ''),
|
|
new shaka.text.Cue(0, 200, ''),
|
|
new shaka.text.Cue(0, 300, ''),
|
|
];
|
|
for (const cue of cues) {
|
|
cue.displayAlign = shaka.text.Cue.displayAlign.CENTER;
|
|
cue.region = cueRegion;
|
|
}
|
|
|
|
textDisplayer.setTextVisibility(true);
|
|
textDisplayer.append(cues);
|
|
updateCaptions();
|
|
|
|
const textContainer = videoContainer.querySelector('.shaka-text-container');
|
|
const allRegionElements = textContainer.querySelectorAll(
|
|
'.shaka-text-region');
|
|
|
|
// Verify that the nested cues are all attached to a single region element.
|
|
expect(allRegionElements.length).toBe(1);
|
|
const regionElement = allRegionElements[0];
|
|
const children = Array.from(regionElement.childNodes).filter(
|
|
(e) => e.nodeType == Node.ELEMENT_NODE);
|
|
expect(children.length).toBe(3);
|
|
|
|
// Verify styles applied to the region element.
|
|
const regionCssObj = parseCssText(regionElement.style.cssText);
|
|
const expectRegionCssObj = {
|
|
'position': 'absolute',
|
|
'height': '80%',
|
|
'width': '80%',
|
|
'top': '10%',
|
|
'left': '10%',
|
|
'display': 'flex',
|
|
'flex-direction': 'column',
|
|
'align-items': 'center',
|
|
'justify-content': 'center',
|
|
};
|
|
expect(regionCssObj).toEqual(jasmine.objectContaining(expectRegionCssObj));
|
|
|
|
for (const caption of children) {
|
|
// Verify that styles applied to the nested cues _DO NOT_ include region
|
|
// placement.
|
|
const cueCssObj = parseCssText(caption.style.cssText);
|
|
expect(Object.keys(cueCssObj)).not.toContain('height');
|
|
expect(Object.keys(cueCssObj)).not.toContain('width');
|
|
expect(Object.keys(cueCssObj)).not.toContain('top');
|
|
expect(Object.keys(cueCssObj)).not.toContain('left');
|
|
}
|
|
});
|
|
|
|
it('does not lose second item in a region', () => {
|
|
const cueRegion = new shaka.text.CueRegion();
|
|
cueRegion.id = 'regionId';
|
|
cueRegion.height = 80;
|
|
cueRegion.heightUnits = shaka.text.CueRegion.units.PERCENTAGE;
|
|
cueRegion.width = 80;
|
|
cueRegion.widthUnits = shaka.text.CueRegion.units.PERCENTAGE;
|
|
cueRegion.viewportAnchorX = 10;
|
|
cueRegion.viewportAnchorY = 10;
|
|
cueRegion.viewportAnchorUnits = shaka.text.CueRegion.units.PERCENTAGE;
|
|
|
|
// These have identical nested.
|
|
const cue1 = new shaka.text.Cue(168, 181.84, '');
|
|
cue1.nestedCues = [
|
|
new shaka.text.Cue(168, 181.84, ''),
|
|
];
|
|
cue1.region = cueRegion;
|
|
|
|
const nested1 = new shaka.text.Cue(168, 170.92, '');
|
|
nested1.nestedCues = [new shaka.text.Cue(0, 170.92,
|
|
'Emo look. I mean listen.')];
|
|
|
|
const nested2 = new shaka.text.Cue(172, 174.84, '');
|
|
nested2.nestedCues = [new shaka.text.Cue(172, 174.84,
|
|
'You have to learn to listen.')];
|
|
|
|
const nested3 = new shaka.text.Cue(175.84, 177.64, '');
|
|
nested3.nestedCues = [new shaka.text.Cue(175.84, 177.64,
|
|
'This is not some game.')];
|
|
|
|
const nested4 = new shaka.text.Cue(177.68, 181.84, '');
|
|
nested4.nestedCues = [new shaka.text.Cue(177.68, 181.84,
|
|
'You - I mean we - we could easily die out here.')];
|
|
|
|
cue1.nestedCues[0].nestedCues = [nested1, nested2, nested3, nested4];
|
|
|
|
video.currentTime = 170;
|
|
textDisplayer.setTextVisibility(true);
|
|
textDisplayer.append([cue1]);
|
|
updateCaptions();
|
|
|
|
/** @type {Element} */
|
|
const textContainer = videoContainer.querySelector('.shaka-text-container');
|
|
let captions = textContainer.querySelectorAll('div');
|
|
expect(captions.length).toBe(1);
|
|
let allRegionElements = textContainer.querySelectorAll(
|
|
'.shaka-text-region');
|
|
// Verify that the nested cues are all attached to a single region element.
|
|
expect(allRegionElements.length).toBe(1);
|
|
|
|
// Advance time to where there is none to show
|
|
video.currentTime = 171;
|
|
updateCaptions();
|
|
|
|
allRegionElements = textContainer.querySelectorAll(
|
|
'.shaka-text-region');
|
|
expect(allRegionElements.length).toBe(1);
|
|
|
|
// Advance time to where there is something to show
|
|
video.currentTime = 173;
|
|
updateCaptions();
|
|
|
|
allRegionElements = textContainer.querySelectorAll(
|
|
'.shaka-text-region');
|
|
expect(allRegionElements.length).toBe(1);
|
|
|
|
captions = textContainer.querySelectorAll('div');
|
|
|
|
expect(captions.length).toBe(1);
|
|
expect(captions[0].textContent).toBe('You have to learn to listen.');
|
|
|
|
allRegionElements = textContainer.querySelectorAll(
|
|
'.shaka-text-region');
|
|
expect(allRegionElements.length).toBe(1);
|
|
});
|
|
|
|
it('creates separate regions when dimensions differ but id same', () => {
|
|
const identicalRegionId = 'regionId';
|
|
|
|
const cueRegion1 = new shaka.text.CueRegion();
|
|
const cueRegion2 = new shaka.text.CueRegion();
|
|
cueRegion1.id = identicalRegionId;
|
|
cueRegion2.id = identicalRegionId;
|
|
|
|
cueRegion1.height = 80;
|
|
cueRegion1.heightUnits = shaka.text.CueRegion.units.PERCENTAGE;
|
|
cueRegion1.width = 80;
|
|
cueRegion1.widthUnits = shaka.text.CueRegion.units.PERCENTAGE;
|
|
|
|
cueRegion2.height = 160; // the only difference!
|
|
cueRegion2.heightUnits = shaka.text.CueRegion.units.PERCENTAGE;
|
|
cueRegion2.width = 80;
|
|
cueRegion2.widthUnits = shaka.text.CueRegion.units.PERCENTAGE;
|
|
|
|
cueRegion1.viewportAnchorX = 10;
|
|
cueRegion1.viewportAnchorY = 10;
|
|
cueRegion1.viewportAnchorUnits = shaka.text.CueRegion.units.PERCENTAGE;
|
|
|
|
cueRegion2.viewportAnchorX = 10;
|
|
cueRegion2.viewportAnchorY = 10;
|
|
cueRegion2.viewportAnchorUnits = shaka.text.CueRegion.units.PERCENTAGE;
|
|
|
|
// These all attach to the same region, but only one region element should
|
|
// be created.
|
|
const firstBatchOfCues = [
|
|
new shaka.text.Cue(0, 100, ''),
|
|
new shaka.text.Cue(0, 200, ''),
|
|
new shaka.text.Cue(0, 300, ''),
|
|
];
|
|
for (const cue of firstBatchOfCues) {
|
|
cue.displayAlign = shaka.text.Cue.displayAlign.CENTER;
|
|
cue.region = cueRegion1;
|
|
}
|
|
|
|
// Another batch for the other region
|
|
const secondBatchOfCues = [
|
|
new shaka.text.Cue(0, 100, ''),
|
|
new shaka.text.Cue(0, 200, ''),
|
|
new shaka.text.Cue(0, 300, ''),
|
|
];
|
|
for (const cue of secondBatchOfCues) {
|
|
cue.displayAlign = shaka.text.Cue.displayAlign.CENTER;
|
|
cue.region = cueRegion2;
|
|
}
|
|
|
|
textDisplayer.setTextVisibility(true);
|
|
textDisplayer.append(firstBatchOfCues);
|
|
textDisplayer.append(secondBatchOfCues);
|
|
updateCaptions();
|
|
|
|
const textContainer = videoContainer.querySelector('.shaka-text-container');
|
|
const allRegionElements = textContainer.querySelectorAll(
|
|
'.shaka-text-region');
|
|
|
|
// Verify that the nested cues are attached to respective region element.
|
|
expect(allRegionElements.length).toBe(2);
|
|
|
|
const childrenOfOne = Array.from(allRegionElements[0].childNodes).filter(
|
|
(e) => e.nodeType == Node.ELEMENT_NODE);
|
|
expect(childrenOfOne.length).toBe(3);
|
|
|
|
const childrenOfTwo = Array.from(allRegionElements[1].childNodes).filter(
|
|
(e) => e.nodeType == Node.ELEMENT_NODE);
|
|
expect(childrenOfTwo.length).toBe(3);
|
|
});
|
|
|
|
it('textDisplayer does not crash if destroy is called more than once', () => {
|
|
textDisplayer.setTextVisibility(true);
|
|
|
|
expect(videoContainer.childNodes.length).toBe(1);
|
|
|
|
textDisplayer.destroy();
|
|
textDisplayer.destroy();
|
|
|
|
expect(videoContainer.childNodes.length).toBe(0);
|
|
});
|
|
|
|
it('previews text styles through the normal renderer', () => {
|
|
const cue = new shaka.text.Cue(0, 100, 'Previewed cue');
|
|
const config =
|
|
shaka.util.PlayerConfiguration.createDefault().textDisplayer;
|
|
|
|
textDisplayer.configure(config);
|
|
textDisplayer.setTextVisibility(true);
|
|
textDisplayer.append([cue]);
|
|
updateCaptions();
|
|
|
|
const textContainer = videoContainer.querySelector('.shaka-text-container');
|
|
let cueElement = textContainer.querySelector('div');
|
|
expect(cueElement.textContent).toBe('Previewed cue');
|
|
expect(cueElement.style.fontSize).toBe('');
|
|
|
|
const previewConfig =
|
|
/** @type {!shaka.extern.TextDisplayerConfiguration} */(
|
|
Object.assign({}, config, {'fontScaleFactor': 2}));
|
|
textDisplayer.setTextStylePreview(
|
|
previewConfig, 'Subtitles example');
|
|
|
|
cueElement = textContainer.querySelector('div');
|
|
expect(cueElement.textContent).toBe('Previewed cue');
|
|
expect(cueElement.style.fontSize).toBe('2em');
|
|
|
|
textDisplayer.clearTextStylePreview();
|
|
|
|
cueElement = textContainer.querySelector('div');
|
|
expect(cueElement.textContent).toBe('Previewed cue');
|
|
expect(cueElement.style.fontSize).toBe('');
|
|
});
|
|
|
|
it('shows example text only when no cue is active during preview', () => {
|
|
const config =
|
|
shaka.util.PlayerConfiguration.createDefault().textDisplayer;
|
|
|
|
textDisplayer.configure(config);
|
|
textDisplayer.setTextVisibility(true);
|
|
|
|
textDisplayer.setTextStylePreview(config, 'Subtitles example');
|
|
|
|
const textContainer = videoContainer.querySelector('.shaka-text-container');
|
|
expect(textContainer.textContent).toBe('Subtitles example');
|
|
|
|
textDisplayer.clearTextStylePreview();
|
|
expect(textContainer.textContent).toBe('');
|
|
|
|
textDisplayer.setTextStylePreview(config, 'Subtitles example');
|
|
expect(textContainer.textContent).toBe('Subtitles example');
|
|
|
|
textDisplayer.append([new shaka.text.Cue(0, 100, 'Real subtitle')]);
|
|
updateCaptions();
|
|
|
|
expect(textContainer.textContent).toBe('Real subtitle');
|
|
});
|
|
|
|
it('shows example text while normal text visibility is off', () => {
|
|
const config =
|
|
shaka.util.PlayerConfiguration.createDefault().textDisplayer;
|
|
|
|
textDisplayer.configure(config);
|
|
|
|
textDisplayer.setTextStylePreview(config, 'Subtitles example');
|
|
|
|
const textContainer = videoContainer.querySelector('.shaka-text-container');
|
|
expect(textContainer.textContent).toBe('Subtitles example');
|
|
|
|
textDisplayer.clearTextStylePreview();
|
|
|
|
expect(videoContainer.querySelector('.shaka-text-container')).toBe(null);
|
|
});
|
|
|
|
it('replaces example text during repeated preview updates', () => {
|
|
const config =
|
|
shaka.util.PlayerConfiguration.createDefault().textDisplayer;
|
|
|
|
textDisplayer.configure(config);
|
|
textDisplayer.setTextVisibility(true);
|
|
|
|
textDisplayer.setTextStylePreview(config, 'First example');
|
|
|
|
const textContainer = videoContainer.querySelector('.shaka-text-container');
|
|
let cueElements = textContainer.querySelectorAll('div');
|
|
expect(cueElements.length).toBe(1);
|
|
expect(textContainer.textContent).toBe('First example');
|
|
|
|
textDisplayer.setTextStylePreview(config, 'Second example');
|
|
|
|
cueElements = textContainer.querySelectorAll('div');
|
|
expect(cueElements.length).toBe(1);
|
|
expect(textContainer.textContent).toBe('Second example');
|
|
});
|
|
|
|
it('positions cue at top-left when positionArea=TOP_LEFT', () => {
|
|
/** @type {!shaka.text.Cue} */
|
|
const cue = new shaka.text.Cue(0, 100, 'Top-Left');
|
|
|
|
textDisplayer.setTextVisibility(true);
|
|
const config =
|
|
shaka.util.PlayerConfiguration.createDefault().textDisplayer;
|
|
config.positionArea = shaka.config.PositionArea.TOP_LEFT;
|
|
textDisplayer.configure(config);
|
|
|
|
textDisplayer.append([cue]);
|
|
updateCaptions();
|
|
|
|
/** @type {Element} */
|
|
const textContainer = videoContainer.querySelector('.shaka-text-container');
|
|
|
|
// Top-level cue should be a DIV
|
|
const cueElement = textContainer.querySelector('div');
|
|
|
|
// The custom region (CustomRegion) should be created and wrap the cue
|
|
const regionElement = textContainer.querySelector('.shaka-text-region');
|
|
|
|
// --- Region validations (CustomRegion: 90% x 90% at top/left 5%) ---
|
|
const regionCss = parseCssText(regionElement.style.cssText);
|
|
expect(regionCss).toEqual(jasmine.objectContaining({
|
|
'position': 'absolute',
|
|
'height': '90%',
|
|
'width': '90%',
|
|
'top': '5%',
|
|
'left': '5%',
|
|
'display': 'flex',
|
|
'flex-direction': 'column',
|
|
'align-items': 'center',
|
|
// displayAlign BEFORE => justifyContent 'flex-start'
|
|
'justify-content': 'flex-start',
|
|
}));
|
|
|
|
// --- Cue validations (LEFT + BEFORE) ---
|
|
const cueCss = parseCssText(cueElement.style.cssText);
|
|
expect(cueCss).toEqual(jasmine.objectContaining({
|
|
'display': 'flex',
|
|
'flex-direction': 'column',
|
|
// textAlign LEFT => alignItems 'flex-start' and width 100%
|
|
'align-items': 'flex-start',
|
|
'width': '100%',
|
|
// displayAlign BEFORE => justifyContent 'flex-start'
|
|
'justify-content': 'flex-start',
|
|
'text-align': 'left',
|
|
}));
|
|
|
|
// Text content should be present
|
|
expect(cueElement.textContent).toBe('Top-Left');
|
|
});
|
|
});
|