Skip to content

feat: text displayer config overrides for text color/background color #8265

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 5 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion demo/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -355,7 +355,9 @@ shakaDemo.Config = class {
/* canBeDecimal= */ true)
.addNumberInput_('Font scale factor',
'textDisplayer.fontScaleFactor',
/* canBeDecimal= */ true);
/* canBeDecimal= */ true)
.addTextInput_('Text Color', 'textDisplayer.textColor')
.addTextInput_('Background Color', 'textDisplayer.backgroundColor');
}

/** @private */
Expand Down
13 changes: 12 additions & 1 deletion externs/shaka/player.js
Original file line number Diff line number Diff line change
Expand Up @@ -2432,7 +2432,9 @@ shaka.extern.OfflineConfiguration;
/**
* @typedef {{
* captionsUpdatePeriod: number,
* fontScaleFactor: number
* fontScaleFactor: number,
* textColor: string,
* backgroundColor: string
* }}
*
* @description
Expand All @@ -2446,6 +2448,15 @@ shaka.extern.OfflineConfiguration;
* The font scale factor used to increase or decrease the font size.
* <br>
* Defaults to <code>1</code>.
* @property {string} textColor
* The override of text color (& opacity).
* <br>
* Defaults to <code>''</code> i.e. fallback to the stream's prescribed style.
* @property {string} backgroundColor
* The override of background color (& opacity).
* <br>
* Defaults to <code>''</code> i.e. fallback to the stream's prescribed style.
*
* @exportDoc
*/
shaka.extern.TextDisplayerConfiguration;
Expand Down
54 changes: 46 additions & 8 deletions lib/text/ui_text_displayer.js
Original file line number Diff line number Diff line change
Expand Up @@ -670,6 +670,48 @@ shaka.text.UITextDisplayer = class {
return Cue.positionAlign.CENTER;
}

/**
* Allows configuration to override a property from the cue or its ancestors.
* @param {string} cueColor prescribed color from the cue
* @return {string} the determined color (& alpha)
* @private
*/
readabilityTextColor_(cueColor) {
let returnColor = '';

if (this.config_ && this.config_.textColor != '') {
returnColor = this.config_.textColor;
} else if (cueColor && cueColor != '') {
returnColor = cueColor;
}

return returnColor;
}

/**
* Allows configuration to override a property from the cue or its ancestors.
* @param {string} text the text to be displayed (if any)
* @param {string} cueBGColor the background color from the cue
* @return {string} the determined background color (& alpha)
* @private
*/
readabilityBackgroundColor_(text, cueBGColor) {
let returnColor = '';

if (text) {
if (this.config_ && this.config_.backgroundColor != '') {
returnColor = this.config_.backgroundColor;
} else if (cueBGColor && cueBGColor != '') {
returnColor = cueBGColor;
} else {
// If there is no background, default to a semi-transparent black.
returnColor = 'rgba(0, 0, 0, 0.8)';
}
}

return returnColor;
}

/**
* @param {!HTMLElement} cueElement
* @param {!shaka.text.Cue} cue
Expand Down Expand Up @@ -705,7 +747,7 @@ shaka.text.UITextDisplayer = class {

style.webkitTextStrokeColor = cue.textStrokeColor;
style.webkitTextStrokeWidth = cue.textStrokeWidth;
style.color = cue.color;
style.color = this.readabilityTextColor_(cue.color);
Copy link
Contributor

@matvp91 matvp91 Mar 13, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion to drop a separate function for each style property. You can add a default styles map to player_configuration.js. This would make it a one liner:

style.color = cue.color || this.config_.textColor

We can maybe introduce a helper function later on to resolve the correct value by key name (as the same logic would apply for all style properties). Especially if we're going to distinguish default vs. forced.

style.direction = cue.direction;
style.opacity = cue.opacity;
style.paddingLeft = shaka.text.UITextDisplayer.convertLengthValue_(
Expand Down Expand Up @@ -750,13 +792,9 @@ shaka.text.UITextDisplayer = class {
}
if (!hasWrapper) {
const bgColor = inherit((c) => c.backgroundColor);
if (bgColor) {
elem.style.backgroundColor = bgColor;
} else if (text) {
// If there is no background, default to a semi-transparent black.
// Only do this for the text itself.
elem.style.backgroundColor = 'rgba(0, 0, 0, 0.8)';
}

elem.style.backgroundColor =
this.readabilityBackgroundColor_(text, bgColor);
}
if (text) {
elem.textContent = text;
Expand Down
2 changes: 2 additions & 0 deletions lib/util/player_configuration.js
Original file line number Diff line number Diff line change
Expand Up @@ -447,6 +447,8 @@ shaka.util.PlayerConfiguration = class {
const textDisplayer = {
captionsUpdatePeriod: 0.25,
fontScaleFactor: 1,
textColor: '',
backgroundColor: '',
};

const AutoShowText = shaka.config.AutoShowText;
Expand Down
44 changes: 44 additions & 0 deletions test/text/ui_text_displayer_unit.js
Original file line number Diff line number Diff line change
Expand Up @@ -613,6 +613,50 @@ describe('UITextDisplayer', () => {
expect(childrenOfTwo.length).toBe(3);
});

it('correctly displays styles for cues when configured overrides', () => {
textDisplayer.configure({
fontScaleFactor: 1.75,
textColor: 'red',
captionsUpdatePeriod: 0.25,
backgroundColor: 'rgba(255, 255, 255, 0.5)',
});

/** @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;
cue.fontSize = '10px';
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': 'red',
'direction': 'ltr',
'font-size': '17.5px',
'font-style': 'normal',
'font-weight': 400,
'text-align': 'center',
};

expect(cssObj).toEqual(jasmine.objectContaining(expectCssObj));
expect(parseCssText(textContainer.querySelector('span').style.cssText))
.toEqual(jasmine.objectContaining(
{'background-color': 'rgba(255,255,255,0.5)'}));
});

it('textDisplayer does not crash if destroy is called more than once', () => {
expect(videoContainer.childNodes.length).toBe(1);

Expand Down
Loading