mirror of
https://github.com/shaka-project/shaka-player.git
synced 2026-06-15 16:06:41 +03:00
38ce45dce5
Cleanup imported from an internal Google migration process, courtesy of Laura Harker. Change-Id: I11de518eafe6008938589e5250bdcaf8151267e9
273 lines
7.2 KiB
JavaScript
273 lines
7.2 KiB
JavaScript
/*! @license
|
|
* Shaka Player
|
|
* Copyright 2016 Google LLC
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
goog.provide('shaka.cea.CeaUtils');
|
|
goog.provide('shaka.cea.CeaUtils.StyledChar');
|
|
|
|
goog.require('shaka.cea.ICaptionDecoder');
|
|
goog.require('shaka.text.Cue');
|
|
|
|
|
|
shaka.cea.CeaUtils = class {
|
|
/**
|
|
* Emits a closed caption based on the state of the buffer.
|
|
* @param {!shaka.text.Cue} topLevelCue
|
|
* @param {string} stream
|
|
* @param {!Array<!Array<?shaka.cea.CeaUtils.StyledChar>>} memory
|
|
* @param {number} startTime Start time of the cue.
|
|
* @param {number} endTime End time of the cue.
|
|
* @return {?shaka.cea.ICaptionDecoder.ClosedCaption}
|
|
*/
|
|
static getParsedCaption(topLevelCue, stream, memory, startTime, endTime) {
|
|
if (startTime >= endTime) {
|
|
return null;
|
|
}
|
|
|
|
// Find the first and last row that contains characters.
|
|
let firstNonEmptyRow = -1;
|
|
let lastNonEmptyRow = -1;
|
|
|
|
for (let i = 0; i < memory.length; i++) {
|
|
if (memory[i].some((e) => e != null && e.getChar().trim() != '')) {
|
|
firstNonEmptyRow = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
for (let i = memory.length - 1; i >= 0; i--) {
|
|
if (memory[i].some((e) => e != null && e.getChar().trim() != '')) {
|
|
lastNonEmptyRow = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Exit early if no non-empty row was found.
|
|
if (firstNonEmptyRow === -1 || lastNonEmptyRow === -1) {
|
|
return null;
|
|
}
|
|
|
|
// Keeps track of the current styles for a cue being emitted.
|
|
let currentUnderline = false;
|
|
let currentItalics = false;
|
|
let currentTextColor = shaka.cea.CeaUtils.DEFAULT_TXT_COLOR;
|
|
let currentBackgroundColor = shaka.cea.CeaUtils.DEFAULT_BG_COLOR;
|
|
|
|
// Create first cue that will be nested in top level cue. Default styles.
|
|
let currentCue = shaka.cea.CeaUtils.createStyledCue(
|
|
startTime, endTime, currentUnderline, currentItalics,
|
|
currentTextColor, currentBackgroundColor);
|
|
|
|
// Logic: Reduce rows into a single top level cue containing nested cues.
|
|
// Each nested cue corresponds either a style change or a line break.
|
|
|
|
for (let i = firstNonEmptyRow; i <= lastNonEmptyRow; i++) {
|
|
// Find the first and last non-empty characters in this row. We do this so
|
|
// no styles creep in before/after the first and last non-empty chars.
|
|
const row = memory[i];
|
|
let firstNonEmptyCol = -1;
|
|
let lastNonEmptyCol = -1;
|
|
|
|
for (let j = 0; j < row.length; j++) {
|
|
if (row[j] != null && row[j].getChar().trim() !== '') {
|
|
firstNonEmptyCol = j;
|
|
break;
|
|
}
|
|
}
|
|
|
|
for (let j = row.length - 1; j >= 0; j--) {
|
|
if (row[j] != null && row[j].getChar().trim() !== '') {
|
|
lastNonEmptyCol = j;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// If no non-empty char. was found in this row, it must be a linebreak.
|
|
if (firstNonEmptyCol === -1 || lastNonEmptyCol === -1) {
|
|
const linebreakCue = shaka.cea.CeaUtils
|
|
.createLineBreakCue(startTime, endTime);
|
|
topLevelCue.nestedCues.push(linebreakCue);
|
|
continue;
|
|
}
|
|
|
|
for (let j = firstNonEmptyCol; j <= lastNonEmptyCol; j++) {
|
|
const styledChar = row[j];
|
|
|
|
// A null between non-empty cells in a row is handled as a space.
|
|
if (!styledChar) {
|
|
currentCue.payload += ' ';
|
|
continue;
|
|
}
|
|
const underline = styledChar.isUnderlined();
|
|
const italics = styledChar.isItalicized();
|
|
const textColor = styledChar.getTextColor();
|
|
const backgroundColor = styledChar.getBackgroundColor();
|
|
|
|
// If any style properties have changed, we need to open a new cue.
|
|
if (underline != currentUnderline || italics != currentItalics ||
|
|
textColor != currentTextColor ||
|
|
backgroundColor != currentBackgroundColor) {
|
|
// Push the currently built cue and start a new cue, with new styles.
|
|
if (currentCue.payload) {
|
|
topLevelCue.nestedCues.push(currentCue);
|
|
}
|
|
currentCue = shaka.cea.CeaUtils.createStyledCue(
|
|
startTime, endTime, underline,
|
|
italics, textColor, backgroundColor);
|
|
|
|
currentUnderline = underline;
|
|
currentItalics = italics;
|
|
currentTextColor = textColor;
|
|
currentBackgroundColor = backgroundColor;
|
|
}
|
|
|
|
currentCue.payload += styledChar.getChar();
|
|
}
|
|
if (currentCue.payload) {
|
|
topLevelCue.nestedCues.push(currentCue);
|
|
}
|
|
|
|
// Add a linebreak since the row just ended.
|
|
if (i !== lastNonEmptyRow) {
|
|
const linebreakCue = shaka.cea.CeaUtils
|
|
.createLineBreakCue(startTime, endTime);
|
|
topLevelCue.nestedCues.push(linebreakCue);
|
|
}
|
|
|
|
// Create a new cue.
|
|
currentCue = shaka.cea.CeaUtils.createStyledCue(
|
|
startTime, endTime, currentUnderline, currentItalics,
|
|
currentTextColor, currentBackgroundColor);
|
|
}
|
|
|
|
if (topLevelCue.nestedCues.length) {
|
|
return {
|
|
cue: topLevelCue,
|
|
stream,
|
|
};
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* @param {number} startTime
|
|
* @param {number} endTime
|
|
* @param {boolean} underline
|
|
* @param {boolean} italics
|
|
* @param {string} txtColor
|
|
* @param {string} bgColor
|
|
* @return {!shaka.text.Cue}
|
|
*/
|
|
static createStyledCue(startTime, endTime, underline,
|
|
italics, txtColor, bgColor) {
|
|
const cue = new shaka.text.Cue(startTime, endTime, /* payload= */ '');
|
|
if (underline) {
|
|
cue.textDecoration.push(shaka.text.Cue.textDecoration.UNDERLINE);
|
|
}
|
|
if (italics) {
|
|
cue.fontStyle = shaka.text.Cue.fontStyle.ITALIC;
|
|
}
|
|
cue.color = txtColor;
|
|
cue.backgroundColor = bgColor;
|
|
return cue;
|
|
}
|
|
|
|
/**
|
|
* @param {number} startTime
|
|
* @param {number} endTime
|
|
* @return {!shaka.text.Cue}
|
|
*/
|
|
static createLineBreakCue(startTime, endTime) {
|
|
const linebreakCue = new shaka.text.Cue(
|
|
startTime, endTime, /* payload= */ '');
|
|
linebreakCue.lineBreak = true;
|
|
return linebreakCue;
|
|
}
|
|
};
|
|
|
|
shaka.cea.CeaUtils.StyledChar = class {
|
|
/**
|
|
* @param {string} character
|
|
* @param {boolean} underline
|
|
* @param {boolean} italics
|
|
* @param {string} backgroundColor
|
|
* @param {string} textColor
|
|
*/
|
|
constructor(character, underline, italics, backgroundColor, textColor) {
|
|
/**
|
|
* @private {string}
|
|
*/
|
|
this.character_ = character;
|
|
|
|
/**
|
|
* @private {boolean}
|
|
*/
|
|
this.underline_ = underline;
|
|
|
|
/**
|
|
* @private {boolean}
|
|
*/
|
|
this.italics_ = italics;
|
|
|
|
/**
|
|
* @private {string}
|
|
*/
|
|
this.backgroundColor_ = backgroundColor;
|
|
|
|
/**
|
|
* @private {string}
|
|
*/
|
|
this.textColor_ = textColor;
|
|
}
|
|
|
|
/**
|
|
* @return {string}
|
|
*/
|
|
getChar() {
|
|
return this.character_;
|
|
}
|
|
|
|
/**
|
|
* @return {boolean}
|
|
*/
|
|
isUnderlined() {
|
|
return this.underline_;
|
|
}
|
|
|
|
/**
|
|
* @return {boolean}
|
|
*/
|
|
isItalicized() {
|
|
return this.italics_;
|
|
}
|
|
|
|
/**
|
|
* @return {string}
|
|
*/
|
|
getBackgroundColor() {
|
|
return this.backgroundColor_;
|
|
}
|
|
|
|
/**
|
|
* @return {string}
|
|
*/
|
|
getTextColor() {
|
|
return this.textColor_;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Default background color for text.
|
|
* @const {string}
|
|
*/
|
|
shaka.cea.CeaUtils.DEFAULT_BG_COLOR = 'black';
|
|
|
|
/**
|
|
* Default text color.
|
|
* @const {string}
|
|
*/
|
|
shaka.cea.CeaUtils.DEFAULT_TXT_COLOR = 'white';
|