Skip to content

Commit

Permalink
feat(FEC-13664): Add the ability to configure default alignment of ca…
Browse files Browse the repository at this point in the history
…ptions on the player (#772)

Resolves: https://kaltura.atlassian.net/browse/FEC-13664

---------

Co-authored-by: Sergey Marchenko <[email protected]>
  • Loading branch information
semarche-kaltura and semarche authored Apr 8, 2024
1 parent 03d99c3 commit 55c5aca
Show file tree
Hide file tree
Showing 14 changed files with 175 additions and 76 deletions.
5 changes: 5 additions & 0 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -678,6 +678,7 @@ var config = {
> > ```js
> > {
> > fontSize?: '50%' | '75%' | '100%' | '200%' | '300%' | '400%'
> > textAlign?: string, // ['default', 'center', 'left', 'right']
> > fontScale?: -2 | -1 | 0 | 2 | 3 | 4
> > fontFamily?: string, // font family available in browser
> > fontColor?: [number, number, number], // RGB
Expand All @@ -693,6 +694,10 @@ var config = {
> > }
> > ```
> >
> > ##### textAlign
> >
> > An alignment for all lines of text within the cue box, in the dimension of the writing direction
> >
> > ##### fontSize
> >
> > Percentage unit relative to the parent element's font
Expand Down
9 changes: 9 additions & 0 deletions src/engines/html5/html5.ts
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,15 @@ export default class Html5 extends FakeEventTarget implements IEngine {
}
}

/**
* Get the engine mediaSourceAdapter
* @public
* @returns {IMediaSourceAdapter | null}
*/
public get mediaSourceAdapter(): IMediaSourceAdapter | null {
return this._mediaSourceAdapter;
}

/**
* Get the engine's id
* @public
Expand Down
6 changes: 6 additions & 0 deletions src/engines/html5/media-source/base-media-source-adapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import Error from '../../../error/error';
import { CustomEventType, Html5EventType } from '../../../event/event-type';
import getLogger from '../../../utils/logger';
import Track from '../../../track/track';
import TextStyle from '../../../track/text-style';
import VideoTrack from '../../../track/video-track';
import AudioTrack from '../../../track/audio-track';
import PKTextTrack from '../../../track/text-track';
Expand Down Expand Up @@ -177,6 +178,11 @@ export default class BaseMediaSourceAdapter extends FakeEventTarget implements I
return BaseMediaSourceAdapter._throwNotImplementedError('static canPlayType');
}

// eslint-disable-next-line
public applyTextTrackStyles(sheet: CSSStyleSheet, styles: TextStyle, containerId: string, engineClassName?: string): void {
return BaseMediaSourceAdapter._throwNotImplementedError('applyTextTrackStyles');
}

public load(): Promise<{ tracks: Track[] }> {
return BaseMediaSourceAdapter._throwNotImplementedError('load');
}
Expand Down
71 changes: 37 additions & 34 deletions src/player.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ import {EngineProvider} from './engines/engine-provider';
import {ExternalCaptionsHandler} from './track/external-captions-handler';
import {AdBreakType} from './ads/ad-break-type';
import {AdTagType} from './ads/ad-tag-type';
import {ResizeWatcher} from './utils';
import {ResizeWatcher, getSubtitleStyleSheet, resetSubtitleStyleSheet} from './utils';
import {FullscreenController} from './fullscreen/fullscreen-controller';
import {EngineDecorator, EngineDecoratorType} from './engines/engine-decorator';
import {LabelOptions} from './track/label-options';
Expand Down Expand Up @@ -78,13 +78,6 @@ const POSTER_CLASS_NAME: string = 'playkit-poster';
*/
const ENGINE_CLASS_NAME: string = 'playkit-engine';

/**
* The text style class name.
* @type {string}
* @const
*/
const SUBTITLES_STYLE_CLASS_NAME: string = 'playkit-subtitles-style';

/**
* The subtitles class name.
* @type {string}
Expand Down Expand Up @@ -471,7 +464,7 @@ export default class Player extends FakeEventTarget {
public configure(config: any = {}): void {
this._setConfigLogLevel(config);
Utils.Object.mergeDeep(this._config, config);
this._applyTextTrackConfig(config);
this._applyTextTrackConfig();
this._applyABRRestriction(config);
}

Expand Down Expand Up @@ -1473,24 +1466,25 @@ export default class Player extends FakeEventTarget {
* @param {Object} config - new config which configure for checking if it relevant config has changed
* @private
*/
private _applyTextTrackConfig(config: any): void {
if (Utils.Object.hasPropertyPath(config, 'text.textTrackDisplaySetting') || Utils.Object.getPropertyPath(config, 'text.forceCenter')) {
let textDisplaySettings: any = {};
if (Utils.Object.hasPropertyPath(this._config, 'text.textTrackDisplaySetting')) {
textDisplaySettings = Utils.Object.mergeDeep(textDisplaySettings, this._config.text.textTrackDisplaySetting);
}
private _applyTextTrackConfig(): void {
const textTrackDisplaySetting = Utils.Object.getPropertyPath(this._config, 'text.textTrackDisplaySetting');
const textStyle = Utils.Object.getPropertyPath(this._config, 'text.textStyle');
if (textTrackDisplaySetting) {
const textDisplaySettings: any = Utils.Object.mergeDeep({}, textTrackDisplaySetting, {
// align - backward compatibility || new caption alignment API || default value
align: textTrackDisplaySetting?.align || textStyle?.textAlign || 'center'
});
// backward compatibility for `text.forceCenter`
if (Utils.Object.getPropertyPath(this._config, 'text.forceCenter')) {
textDisplaySettings = Utils.Object.mergeDeep(textDisplaySettings, {
position: 'auto',
align: 'center',
size: '100'
});
textDisplaySettings.position = 'auto';
textDisplaySettings.align = 'center';
textDisplaySettings.size = '100';
}
this.setTextDisplaySettings(textDisplaySettings);
}
try {
if (Utils.Object.hasPropertyPath(config, 'text.textStyle')) {
this.textStyle = TextStyle.fromJson(this._config.text.textStyle);
if (textStyle) {
this.textStyle = TextStyle.fromJson(textStyle);
}
} catch (e) {
Player._logger.warn(e);
Expand Down Expand Up @@ -1542,23 +1536,13 @@ export default class Player extends FakeEventTarget {
if (!(style instanceof TextStyle)) {
throw new Error('Style must be instance of TextStyle');
}
let element = Utils.Dom.getElementBySelector(`.${this._playerId}.${SUBTITLES_STYLE_CLASS_NAME}`);
if (!element) {
element = Utils.Dom.createElement('style');
Utils.Dom.addClassName(element, this._playerId);
Utils.Dom.addClassName(element, SUBTITLES_STYLE_CLASS_NAME);
Utils.Dom.appendChild(document.head, element);
}
const sheet = element.sheet;

while (sheet.cssRules.length) {
sheet.deleteRule(0);
}
resetSubtitleStyleSheet(this._playerId);

try {
this._textStyle = style;
if (this._config.text.useNativeTextTrack) {
sheet.insertRule(`#${this._playerId} video.${ENGINE_CLASS_NAME}::cue { ${style.toCSS()} }`, 0);
this._applyCustomSubtitleStyles();
} else if (this._engine) {
this._engine.resetAllCues();
this._externalCaptionsHandler.resetAllCues();
Expand Down Expand Up @@ -1716,6 +1700,22 @@ export default class Player extends FakeEventTarget {
return this._engine.getDrmInfo();
}

private _applyCustomSubtitleStyles(): void {
try {
const containerId = this._el?.parentElement?.id || this._playerId;
if (this._config.text.useNativeTextTrack && !this._config.text.useShakaTextTrackDisplay) {
const sheet = getSubtitleStyleSheet(this._playerId);
ExternalCaptionsHandler.applyNativeTextTrackStyles(sheet, this._textStyle, containerId, ENGINE_CLASS_NAME);
} else if (this._config.text.useShakaTextTrackDisplay) {
resetSubtitleStyleSheet(this._playerId);
const sheet = getSubtitleStyleSheet(this._playerId);
this._engine.mediaSourceAdapter?.applyTextTrackStyles?.(sheet, this._textStyle, containerId);
}
} catch (e) {
Player._logger.error(`Failed to add custom text style: ${e.message}`);
}
}

/**
* Remove the current text track from the player view.
* @returns {void}
Expand Down Expand Up @@ -2564,6 +2564,9 @@ export default class Player extends FakeEventTarget {
* @returns {void}
*/
private _updateTextDisplay(cues: Array<VTTCue>): void {
if (this._config.text.useShakaTextTrackDisplay) {
this._applyCustomSubtitleStyles();
}
if (!this._config.text.useNativeTextTrack && !this._config.text.useShakaTextTrackDisplay) {
processCues(window, cues, this._textDisplayEl, this._textStyle);
}
Expand Down
7 changes: 7 additions & 0 deletions src/track/external-captions-handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import Error from '../error/error';
import * as Utils from '../utils/util';
import {Parser, StringDecoder} from './text-track-display';
import TextTrack, {getActiveCues} from './text-track';
import TextStyle from './text-style';
import Track from './track';
import {CustomEventType, Html5EventType} from '../event/event-type';
import { FakeEvent } from '../event/fake-event';
Expand Down Expand Up @@ -29,6 +30,12 @@ const SRT_POSTFIX: string = 'srt';
const VTT_POSTFIX: string = 'vtt';

class ExternalCaptionsHandler extends FakeEventTarget {

public static applyNativeTextTrackStyles(sheet: CSSStyleSheet, styles: TextStyle, containerId: string, engineClassName: string): void {
sheet.insertRule(`#${containerId} video.${engineClassName}::-webkit-media-text-track-display { text-align: ${styles.textAlign}!important; }`, 0);
sheet.insertRule(`#${containerId} video.${engineClassName}::cue { ${styles.toCSS()} }`, 0);
}

/**
* The external captions handler class logger.
* @type {any}
Expand Down
43 changes: 35 additions & 8 deletions src/track/text-style.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
* font size.
* @type {number}
*/
import {FontScaleOptions, FontSizeOptions, PKTextStyleObject} from '../types';
import { FontScaleOptions, FontSizeOptions, PKTextStyleObject, FontAlignmentOptions } from '../types';

const IMPLICIT_SCALE_PERCENTAGE: number = 0.25;

Expand All @@ -28,7 +28,7 @@ class TextStyle {
* @enum {Object.<string, string>}}
* @export
*/
public static FontFamily: {[font: string]: string} = {
public static FontFamily: { [font: string]: string } = {
ARIAL: 'Arial',
HELVETICA: 'Helvetica',
VERDANA: 'Verdana',
Expand All @@ -41,7 +41,7 @@ class TextStyle {
* @enum {Object.<string, [number, number, number]>}}
* @export
*/
public static StandardColors: {[coloer: string]: [number, number, number]} = {
public static StandardColors: { [coloer: string]: [number, number, number] } = {
WHITE: [255, 255, 255],
BLACK: [0, 0, 0],
RED: [255, 0, 0],
Expand All @@ -57,7 +57,7 @@ class TextStyle {
* @enum {Object.<string, number>}}
* @export
*/
public static StandardOpacities: {[opacityLevel: string]: number} = {
public static StandardOpacities: { [opacityLevel: string]: number } = {
OPAQUE: 1,
SEMI_HIGH: 0.75,
SEMI_LOW: 0.25,
Expand All @@ -74,7 +74,7 @@ class TextStyle {
* @enum {!Array.<!Array.[number, number, number, number, number, number]>}
* @export
*/
public static EdgeStyles: {[edgeStyle:string]: Array<[number, number, number, number, number, number]>} = {
public static EdgeStyles: { [edgeStyle: string]: Array<[number, number, number, number, number, number]> } = {
NONE: [],
RAISED: [
[34, 34, 34, 1, 1, 0],
Expand Down Expand Up @@ -130,6 +130,28 @@ class TextStyle {
}
];

/**
* Possible font alignments are left, center, right
*/
public static FontAlignment: { label: string; value: FontAlignmentOptions }[] = [
{
label: 'Default',
value: 'default'
},
{
label: 'Left',
value: 'left'
},
{
label: 'Center',
value: 'center'
},
{
label: 'Right',
value: 'right'
}
];

/**
* Creates a CSS RGBA sctring for a given color and opacity values
* @param {TextStyle.StandardColors} color - color value in RGB
Expand All @@ -149,6 +171,7 @@ class TextStyle {
const textStyle = new TextStyle();
textStyle.fontEdge = getValue(setting.fontEdge, textStyle.fontEdge);
textStyle.fontSize = getValue(setting.fontSize, textStyle.fontSize);
textStyle.textAlign = getValue(setting.textAlign, textStyle.textAlign);
textStyle.fontScale = getValue(setting.fontScale, textStyle.fontScale);
textStyle.fontColor = getValue(setting.fontColor, textStyle.fontColor);
textStyle.fontOpacity = getValue(setting.fontOpacity, textStyle.fontOpacity);
Expand All @@ -162,6 +185,7 @@ class TextStyle {
return {
fontEdge: text.fontEdge,
fontSize: text.fontSize,
textAlign: text.textAlign,
fontScale: text.fontScale,
fontColor: text.fontColor,
fontOpacity: text.fontOpacity,
Expand All @@ -174,12 +198,14 @@ class TextStyle {
private _fontSizeIndex: number = 2; // 100%

public set fontSize(fontSize: string) {
const index = TextStyle.FontSizes.findIndex(({label}) => label === fontSize);
const index = TextStyle.FontSizes.findIndex(({ label }) => label === fontSize);
if (index !== -1) {
this._fontSizeIndex = index;
}
}

public textAlign: FontAlignmentOptions = TextStyle.FontAlignment[0].value;

/**
* Percentage string matching a FontSizes entry
*/
Expand All @@ -188,7 +214,7 @@ class TextStyle {
}

public set fontScale(fontScale: number) {
const index = TextStyle.FontSizes.findIndex(({value}) => value === fontScale);
const index = TextStyle.FontSizes.findIndex(({ value }) => value === fontScale);
if (index !== -1) {
this._fontSizeIndex = index;
}
Expand Down Expand Up @@ -239,7 +265,7 @@ class TextStyle {
const shadows: Array<string> = [];
for (let i = 0; i < this.fontEdge.length; i++) {
// shaka.asserts.assert(this.fontEdge[i].length == 6);
const color: [number, number, number] = (this.fontEdge[i].slice(0, 3) as any);
const color: [number, number, number] = this.fontEdge[i].slice(0, 3) as any;
const shadow: Array<number> = this.fontEdge[i].slice(3, 6);
shadows.push(TextStyle.toRGBA(color, this.fontOpacity) + ' ' + shadow.join('px ') + 'px');
}
Expand All @@ -254,6 +280,7 @@ class TextStyle {
*/
public toCSS(): string {
const attributes: Array<string> = [];
attributes.push('text-align: ' + this.textAlign);
attributes.push('font-family: ' + this.fontFamily);
attributes.push('color: ' + TextStyle.toRGBA(this.fontColor, this.fontOpacity));
attributes.push('background-color: ' + TextStyle.toRGBA(this.backgroundColor, this.backgroundOpacity));
Expand Down
Loading

0 comments on commit 55c5aca

Please sign in to comment.