Source: lib/text/cue.js

  1. /*! @license
  2. * Shaka Player
  3. * Copyright 2016 Google LLC
  4. * SPDX-License-Identifier: Apache-2.0
  5. */
  6. goog.provide('shaka.text.Cue');
  7. goog.require('shaka.text.CueRegion');
  8. goog.require('shaka.util.ArrayUtils');
  9. /**
  10. * @export
  11. */
  12. shaka.text.Cue = class {
  13. /**
  14. * @param {number} startTime
  15. * @param {number} endTime
  16. * @param {string} payload
  17. */
  18. constructor(startTime, endTime, payload) {
  19. const Cue = shaka.text.Cue;
  20. /**
  21. * The start time of the cue in seconds, relative to the start of the
  22. * presentation.
  23. * @type {number}
  24. * @export
  25. */
  26. this.startTime = startTime;
  27. /**
  28. * The end time of the cue in seconds, relative to the start of the
  29. * presentation.
  30. * @type {number}
  31. * @export
  32. */
  33. this.endTime = endTime;
  34. /**
  35. * The text payload of the cue. If nestedCues is non-empty, this should be
  36. * empty. Top-level block containers should have no payload of their own.
  37. * @type {string}
  38. * @export
  39. */
  40. this.payload = payload;
  41. /**
  42. * The region to render the cue into. Only supported on top-level cues,
  43. * because nested cues are inline elements.
  44. * @type {shaka.text.CueRegion}
  45. * @export
  46. */
  47. this.region = new shaka.text.CueRegion();
  48. /**
  49. * The indent (in percent) of the cue box in the direction defined by the
  50. * writing direction.
  51. * @type {?number}
  52. * @export
  53. */
  54. this.position = null;
  55. /**
  56. * Position alignment of the cue.
  57. * @type {shaka.text.Cue.positionAlign}
  58. * @export
  59. */
  60. this.positionAlign = Cue.positionAlign.AUTO;
  61. /**
  62. * Size of the cue box (in percents), where 0 means "auto".
  63. * @type {number}
  64. * @export
  65. */
  66. this.size = 0;
  67. /**
  68. * Alignment of the text inside the cue box.
  69. * @type {shaka.text.Cue.textAlign}
  70. * @export
  71. */
  72. this.textAlign = Cue.textAlign.CENTER;
  73. /**
  74. * Text direction of the cue.
  75. * @type {shaka.text.Cue.direction}
  76. * @export
  77. */
  78. this.direction = Cue.direction.HORIZONTAL_LEFT_TO_RIGHT;
  79. /**
  80. * Text writing mode of the cue.
  81. * @type {shaka.text.Cue.writingMode}
  82. * @export
  83. */
  84. this.writingMode = Cue.writingMode.HORIZONTAL_TOP_TO_BOTTOM;
  85. /**
  86. * The way to interpret line field. (Either as an integer line number or
  87. * percentage from the display box).
  88. * @type {shaka.text.Cue.lineInterpretation}
  89. * @export
  90. */
  91. this.lineInterpretation = Cue.lineInterpretation.LINE_NUMBER;
  92. /**
  93. * The offset from the display box in either number of lines or
  94. * percentage depending on the value of lineInterpretation.
  95. * @type {?number}
  96. * @export
  97. */
  98. this.line = null;
  99. /**
  100. * Separation between line areas inside the cue box in px or em
  101. * (e.g. '100px'/'100em'). If not specified, this should be no less than
  102. * the largest font size applied to the text in the cue.
  103. * @type {string}.
  104. * @export
  105. */
  106. this.lineHeight = '';
  107. /**
  108. * Line alignment of the cue box.
  109. * Start alignment means the cue box’s top side (for horizontal cues), left
  110. * side (for vertical growing right), or right side (for vertical growing
  111. * left) is aligned at the line.
  112. * Center alignment means the cue box is centered at the line.
  113. * End alignment The cue box’s bottom side (for horizontal cues), right side
  114. * (for vertical growing right), or left side (for vertical growing left) is
  115. * aligned at the line.
  116. * @type {shaka.text.Cue.lineAlign}
  117. * @export
  118. */
  119. this.lineAlign = Cue.lineAlign.START;
  120. /**
  121. * Vertical alignments of the cues within their extents.
  122. * 'BEFORE' means displaying the captions at the top of the text display
  123. * container box, 'CENTER' means in the middle, 'AFTER' means at the bottom.
  124. * @type {shaka.text.Cue.displayAlign}
  125. * @export
  126. */
  127. this.displayAlign = Cue.displayAlign.AFTER;
  128. /**
  129. * Text color as a CSS color, e.g. "#FFFFFF" or "white".
  130. * @type {string}
  131. * @export
  132. */
  133. this.color = '';
  134. /**
  135. * Text background color as a CSS color, e.g. "#FFFFFF" or "white".
  136. * @type {string}
  137. * @export
  138. */
  139. this.backgroundColor = '';
  140. /**
  141. * The URL of the background image, e.g. "data:[mime type];base64,[data]".
  142. * @type {string}
  143. * @export
  144. */
  145. this.backgroundImage = '';
  146. /**
  147. * The border around this cue as a CSS border.
  148. * @type {string}
  149. * @export
  150. */
  151. this.border = '';
  152. /**
  153. * Text font size in px or em (e.g. '100px'/'100em').
  154. * @type {string}
  155. * @export
  156. */
  157. this.fontSize = '';
  158. /**
  159. * Text font weight. Either normal or bold.
  160. * @type {shaka.text.Cue.fontWeight}
  161. * @export
  162. */
  163. this.fontWeight = Cue.fontWeight.NORMAL;
  164. /**
  165. * Text font style. Normal, italic or oblique.
  166. * @type {shaka.text.Cue.fontStyle}
  167. * @export
  168. */
  169. this.fontStyle = Cue.fontStyle.NORMAL;
  170. /**
  171. * Text font family.
  172. * @type {string}
  173. * @export
  174. */
  175. this.fontFamily = '';
  176. /**
  177. * Text letter spacing as a CSS letter-spacing value.
  178. * @type {string}
  179. * @export
  180. */
  181. this.letterSpacing = '';
  182. /**
  183. * Text line padding as a CSS line-padding value.
  184. * @type {string}
  185. * @export
  186. */
  187. this.linePadding = '';
  188. /**
  189. * Opacity of the cue element, from 0-1.
  190. * @type {number}
  191. * @export
  192. */
  193. this.opacity = 1;
  194. /**
  195. * Text combine upright as a CSS text-combine-upright value.
  196. * @type {string}
  197. * @export
  198. */
  199. this.textCombineUpright = '';
  200. /**
  201. * Text decoration. A combination of underline, overline
  202. * and line through. Empty array means no decoration.
  203. * @type {!Array.<!shaka.text.Cue.textDecoration>}
  204. * @export
  205. */
  206. this.textDecoration = [];
  207. /**
  208. * Text shadow color as a CSS text-shadow value.
  209. * @type {string}
  210. * @export
  211. */
  212. this.textShadow = '';
  213. /**
  214. * Text stroke color as a CSS color, e.g. "#FFFFFF" or "white".
  215. * @type {string}
  216. * @export
  217. */
  218. this.textStrokeColor = '';
  219. /**
  220. * Text stroke width as a CSS stroke-width value.
  221. * @type {string}
  222. * @export
  223. */
  224. this.textStrokeWidth = '';
  225. /**
  226. * Whether or not line wrapping should be applied to the cue.
  227. * @type {boolean}
  228. * @export
  229. */
  230. this.wrapLine = true;
  231. /**
  232. * Id of the cue.
  233. * @type {string}
  234. * @export
  235. */
  236. this.id = '';
  237. /**
  238. * Nested cues, which should be laid out horizontally in one block.
  239. * Top-level cues are blocks, and nested cues are inline elements.
  240. * Cues can be nested arbitrarily deeply.
  241. * @type {!Array.<!shaka.text.Cue>}
  242. * @export
  243. */
  244. this.nestedCues = [];
  245. /**
  246. * If true, this represents a container element that is "above" the main
  247. * cues. For example, the <body> and <div> tags that contain the <p> tags
  248. * in a TTML file. This controls the flow of the final cues; any nested cues
  249. * within an "isContainer" cue will be laid out as separate lines.
  250. * @type {boolean}
  251. * @export
  252. */
  253. this.isContainer = false;
  254. /**
  255. * Whether or not the cue only acts as a line break between two nested cues.
  256. * Should only appear in nested cues.
  257. * @type {boolean}
  258. * @export
  259. */
  260. this.lineBreak = false;
  261. /**
  262. * Used to indicate the type of ruby tag that should be used when rendering
  263. * the cue. Valid values: ruby, rp, rt.
  264. * @type {?string}
  265. * @export
  266. */
  267. this.rubyTag = null;
  268. /**
  269. * The number of horizontal and vertical cells into which the Root Container
  270. * Region area is divided.
  271. *
  272. * @type {{ columns: number, rows: number }}
  273. * @export
  274. */
  275. this.cellResolution = {
  276. columns: 32,
  277. rows: 15,
  278. };
  279. }
  280. /**
  281. * @param {number} start
  282. * @param {number} end
  283. * @return {!shaka.text.Cue}
  284. */
  285. static lineBreak(start, end) {
  286. const cue = new shaka.text.Cue(start, end, '');
  287. cue.lineBreak = true;
  288. return cue;
  289. }
  290. /**
  291. * Create a copy of the cue with the same properties.
  292. * @return {!shaka.text.Cue}
  293. * @suppress {checkTypes} since we must use [] and "in" with a struct type.
  294. * @export
  295. */
  296. clone() {
  297. const clone = new shaka.text.Cue(0, 0, '');
  298. for (const k in this) {
  299. clone[k] = this[k];
  300. // Make copies of array fields, but only one level deep. That way, if we
  301. // change, for instance, textDecoration on the clone, we don't affect the
  302. // original.
  303. if (clone[k] && clone[k].constructor == Array) {
  304. clone[k] = /** @type {!Array} */(clone[k]).slice();
  305. }
  306. }
  307. return clone;
  308. }
  309. /**
  310. * Check if two Cues have all the same values in all properties.
  311. * @param {!shaka.text.Cue} cue1
  312. * @param {!shaka.text.Cue} cue2
  313. * @return {boolean}
  314. * @suppress {checkTypes} since we must use [] and "in" with a struct type.
  315. * @export
  316. */
  317. static equal(cue1, cue2) {
  318. // Compare the start time, end time and payload of the cues first for
  319. // performance optimization. We can avoid the more expensive recursive
  320. // checks if the top-level properties don't match.
  321. // See: https://github.com/shaka-project/shaka-player/issues/3018
  322. if (cue1.startTime != cue2.startTime || cue1.endTime != cue2.endTime ||
  323. cue1.payload != cue2.payload) {
  324. return false;
  325. }
  326. for (const k in cue1) {
  327. if (k == 'startTime' || k == 'endTime' || k == 'payload') {
  328. // Already compared.
  329. } else if (k == 'nestedCues') {
  330. // This uses shaka.text.Cue.equal rather than just this.equal, since
  331. // otherwise recursing here will unbox the method and cause "this" to be
  332. // undefined in deeper recursion.
  333. if (!shaka.util.ArrayUtils.equal(
  334. cue1.nestedCues, cue2.nestedCues, shaka.text.Cue.equal)) {
  335. return false;
  336. }
  337. } else if (k == 'region' || k == 'cellResolution') {
  338. for (const k2 in cue1[k]) {
  339. if (cue1[k][k2] != cue2[k][k2]) {
  340. return false;
  341. }
  342. }
  343. } else if (Array.isArray(cue1[k])) {
  344. if (!shaka.util.ArrayUtils.equal(cue1[k], cue2[k])) {
  345. return false;
  346. }
  347. } else {
  348. if (cue1[k] != cue2[k]) {
  349. return false;
  350. }
  351. }
  352. }
  353. return true;
  354. }
  355. };
  356. /**
  357. * @enum {string}
  358. * @export
  359. */
  360. shaka.text.Cue.positionAlign = {
  361. 'LEFT': 'line-left',
  362. 'RIGHT': 'line-right',
  363. 'CENTER': 'center',
  364. 'AUTO': 'auto',
  365. };
  366. /**
  367. * @enum {string}
  368. * @export
  369. */
  370. shaka.text.Cue.textAlign = {
  371. 'LEFT': 'left',
  372. 'RIGHT': 'right',
  373. 'CENTER': 'center',
  374. 'START': 'start',
  375. 'END': 'end',
  376. };
  377. /**
  378. * Vertical alignments of the cues within their extents.
  379. * 'BEFORE' means displaying at the top of the captions container box, 'CENTER'
  380. * means in the middle, 'AFTER' means at the bottom.
  381. * @enum {string}
  382. * @export
  383. */
  384. shaka.text.Cue.displayAlign = {
  385. 'BEFORE': 'before',
  386. 'CENTER': 'center',
  387. 'AFTER': 'after',
  388. };
  389. /**
  390. * @enum {string}
  391. * @export
  392. */
  393. shaka.text.Cue.direction = {
  394. 'HORIZONTAL_LEFT_TO_RIGHT': 'ltr',
  395. 'HORIZONTAL_RIGHT_TO_LEFT': 'rtl',
  396. };
  397. /**
  398. * @enum {string}
  399. * @export
  400. */
  401. shaka.text.Cue.writingMode = {
  402. 'HORIZONTAL_TOP_TO_BOTTOM': 'horizontal-tb',
  403. 'VERTICAL_LEFT_TO_RIGHT': 'vertical-lr',
  404. 'VERTICAL_RIGHT_TO_LEFT': 'vertical-rl',
  405. };
  406. /**
  407. * @enum {number}
  408. * @export
  409. */
  410. shaka.text.Cue.lineInterpretation = {
  411. 'LINE_NUMBER': 0,
  412. 'PERCENTAGE': 1,
  413. };
  414. /**
  415. * @enum {string}
  416. * @export
  417. */
  418. shaka.text.Cue.lineAlign = {
  419. 'CENTER': 'center',
  420. 'START': 'start',
  421. 'END': 'end',
  422. };
  423. /**
  424. * Default text color according to
  425. * https://w3c.github.io/webvtt/#default-text-color
  426. * @enum {string}
  427. * @export
  428. */
  429. shaka.text.Cue.defaultTextColor = {
  430. 'white': 'white',
  431. 'lime': 'lime',
  432. 'cyan': 'cyan',
  433. 'red': 'red',
  434. 'yellow': 'yellow',
  435. 'magenta': 'magenta',
  436. 'blue': 'blue',
  437. 'black': 'black',
  438. };
  439. /**
  440. * Default text background color according to
  441. * https://w3c.github.io/webvtt/#default-text-background
  442. * @enum {string}
  443. * @export
  444. */
  445. shaka.text.Cue.defaultTextBackgroundColor = {
  446. 'bg_white': 'white',
  447. 'bg_lime': 'lime',
  448. 'bg_cyan': 'cyan',
  449. 'bg_red': 'red',
  450. 'bg_yellow': 'yellow',
  451. 'bg_magenta': 'magenta',
  452. 'bg_blue': 'blue',
  453. 'bg_black': 'black',
  454. };
  455. /**
  456. * In CSS font weight can be a number, where 400 is normal and 700 is bold.
  457. * Use these values for the enum for consistency.
  458. * @enum {number}
  459. * @export
  460. */
  461. shaka.text.Cue.fontWeight = {
  462. 'NORMAL': 400,
  463. 'BOLD': 700,
  464. };
  465. /**
  466. * @enum {string}
  467. * @export
  468. */
  469. shaka.text.Cue.fontStyle = {
  470. 'NORMAL': 'normal',
  471. 'ITALIC': 'italic',
  472. 'OBLIQUE': 'oblique',
  473. };
  474. /**
  475. * @enum {string}
  476. * @export
  477. */
  478. shaka.text.Cue.textDecoration = {
  479. 'UNDERLINE': 'underline',
  480. 'LINE_THROUGH': 'lineThrough',
  481. 'OVERLINE': 'overline',
  482. };