Source: lib/cea/cea708_service.js

  1. /*! @license
  2. * Shaka Player
  3. * Copyright 2016 Google LLC
  4. * SPDX-License-Identifier: Apache-2.0
  5. */
  6. goog.provide('shaka.cea.Cea708Service');
  7. goog.require('shaka.cea.Cea708Window');
  8. goog.require('shaka.cea.DtvccPacket');
  9. goog.require('shaka.cea.ICaptionDecoder');
  10. /**
  11. * CEA-708 closed captions service as defined by CEA-708-E. A decoder can own up
  12. * to 63 services. Each service owns eight windows.
  13. */
  14. shaka.cea.Cea708Service = class {
  15. /**
  16. * @param {number} serviceNumber
  17. */
  18. constructor(serviceNumber) {
  19. /**
  20. * Number for this specific service (1 - 63).
  21. * @private {number}
  22. */
  23. this.serviceNumber_ = serviceNumber;
  24. /**
  25. * Eight Cea708 Windows, as defined by the spec.
  26. * @private {!Array<?shaka.cea.Cea708Window>}
  27. */
  28. this.windows_ = [
  29. null, null, null, null, null, null, null, null,
  30. ];
  31. /**
  32. * The current window for which window command operate on.
  33. * @private {?shaka.cea.Cea708Window}
  34. */
  35. this.currentWindow_ = null;
  36. }
  37. /**
  38. * Processes a CEA-708 control code.
  39. * @param {!shaka.cea.DtvccPacket} dtvccPacket
  40. * @return {?shaka.cea.ICaptionDecoder.ClosedCaption}
  41. * @throws {!shaka.util.Error}
  42. */
  43. handleCea708ControlCode(dtvccPacket) {
  44. const blockData = dtvccPacket.readByte();
  45. let controlCode = blockData.value;
  46. const pts = blockData.pts;
  47. // Read extended control code if needed.
  48. if (controlCode === shaka.cea.Cea708Service.EXT_CEA708_CTRL_CODE_BYTE1) {
  49. const extendedControlCodeBlock = dtvccPacket.readByte();
  50. controlCode = (controlCode << 16) | extendedControlCodeBlock.value;
  51. }
  52. // Control codes are in 1 of 4 logical groups:
  53. // CL (C0, C2), CR (C1, C3), GL (G0, G2), GR (G1, G2).
  54. if (controlCode >= 0x00 && controlCode <= 0x1f) {
  55. return this.handleC0_(controlCode, pts);
  56. } else if (controlCode >= 0x80 && controlCode <= 0x9f) {
  57. return this.handleC1_(dtvccPacket, controlCode, pts);
  58. } else if (controlCode >= 0x1000 && controlCode <= 0x101f) {
  59. this.handleC2_(dtvccPacket, controlCode & 0xff);
  60. } else if (controlCode >= 0x1080 && controlCode <= 0x109f) {
  61. this.handleC3_(dtvccPacket, controlCode & 0xff);
  62. } else if (controlCode >= 0x20 && controlCode <= 0x7f) {
  63. this.handleG0_(controlCode);
  64. } else if (controlCode >= 0xa0 && controlCode <= 0xff) {
  65. this.handleG1_(controlCode);
  66. } else if (controlCode >= 0x1020 && controlCode <= 0x107f) {
  67. this.handleG2_(controlCode & 0xff);
  68. } else if (controlCode >= 0x10a0 && controlCode <= 0x10ff) {
  69. this.handleG3_(controlCode & 0xff);
  70. }
  71. return null;
  72. }
  73. /**
  74. * Handles G0 group data.
  75. * @param {number} controlCode
  76. * @private
  77. */
  78. handleG0_(controlCode) {
  79. if (!this.currentWindow_) {
  80. return;
  81. }
  82. // G0 contains ASCII from 0x20 to 0x7f, with the exception that 0x7f
  83. // is replaced by a musical note.
  84. if (controlCode === 0x7f) {
  85. this.currentWindow_.setCharacter('♪');
  86. return;
  87. }
  88. this.currentWindow_.setCharacter(String.fromCharCode(controlCode));
  89. }
  90. /**
  91. * Handles G1 group data.
  92. * @param {number} controlCode
  93. * @private
  94. */
  95. handleG1_(controlCode) {
  96. if (!this.currentWindow_) {
  97. return;
  98. }
  99. // G1 is the Latin-1 Character Set from 0xa0 to 0xff.
  100. this.currentWindow_.setCharacter(String.fromCharCode(controlCode));
  101. }
  102. /**
  103. * Handles G2 group data.
  104. * @param {number} controlCode
  105. * @private
  106. */
  107. handleG2_(controlCode) {
  108. if (!this.currentWindow_) {
  109. return;
  110. }
  111. if (!shaka.cea.Cea708Service.G2Charset.has(controlCode)) {
  112. // If the character is unsupported, the spec says to put an underline.
  113. this.currentWindow_.setCharacter('_');
  114. return;
  115. }
  116. const char = shaka.cea.Cea708Service.G2Charset.get(controlCode);
  117. this.currentWindow_.setCharacter(char);
  118. }
  119. /**
  120. * Handles G3 group data.
  121. * @param {number} controlCode
  122. * @private
  123. */
  124. handleG3_(controlCode) {
  125. if (!this.currentWindow_) {
  126. return;
  127. }
  128. // As of CEA-708-E, the G3 group only contains 1 character. It's a
  129. // [CC] character which has no unicode value on 0xa0.
  130. if (controlCode != 0xa0) {
  131. // Similar to G2, the spec decrees an underline if char is unsupported.
  132. this.currentWindow_.setCharacter('_');
  133. return;
  134. }
  135. this.currentWindow_.setCharacter('[CC]');
  136. }
  137. /**
  138. * Handles C0 group data.
  139. * @param {number} controlCode
  140. * @param {number} pts
  141. * @return {?shaka.cea.ICaptionDecoder.ClosedCaption}
  142. * @private
  143. */
  144. handleC0_(controlCode, pts) {
  145. // All these commands pertain to the current window, so ensure it exists.
  146. if (!this.currentWindow_) {
  147. return null;
  148. }
  149. const window = this.currentWindow_;
  150. let parsedClosedCaption = null;
  151. // Note: This decoder ignores the "ETX" (end of text) control code. Since
  152. // this is JavaScript, a '\0' is not needed to terminate a string.
  153. switch (controlCode) {
  154. case shaka.cea.Cea708Service.ASCII_BACKSPACE:
  155. window.backspace();
  156. break;
  157. case shaka.cea.Cea708Service.ASCII_CARRIAGE_RETURN:
  158. // Force out the buffer, since the top row could be lost.
  159. if (window.isVisible()) {
  160. parsedClosedCaption = window.forceEmit(pts, this.serviceNumber_);
  161. }
  162. window.carriageReturn();
  163. break;
  164. case shaka.cea.Cea708Service.ASCII_HOR_CARRIAGE_RETURN:
  165. // Force out the buffer, a row will be erased.
  166. if (window.isVisible()) {
  167. parsedClosedCaption = window.forceEmit(pts, this.serviceNumber_);
  168. }
  169. window.horizontalCarriageReturn();
  170. break;
  171. case shaka.cea.Cea708Service.ASCII_FORM_FEED:
  172. // Clear window and move pen to (0,0).
  173. // Force emit if the window is visible.
  174. if (window.isVisible()) {
  175. parsedClosedCaption = window.forceEmit(pts, this.serviceNumber_);
  176. }
  177. window.resetMemory();
  178. window.setPenLocation(0, 0);
  179. break;
  180. }
  181. return parsedClosedCaption;
  182. }
  183. /**
  184. * Processes C1 group data.
  185. * These are caption commands.
  186. * @param {!shaka.cea.DtvccPacket} dtvccPacket
  187. * @param {number} captionCommand
  188. * @param {number} pts in seconds
  189. * @return {?shaka.cea.ICaptionDecoder.ClosedCaption}
  190. * @throws {!shaka.util.Error} a possible out-of-range buffer read.
  191. * @private
  192. */
  193. handleC1_(dtvccPacket, captionCommand, pts) {
  194. // Note: This decoder ignores delay and delayCancel control codes in the C1.
  195. // group. These control codes delay processing of data for a set amount of
  196. // time, however this decoder processes that data immediately.
  197. if (captionCommand >= 0x80 && captionCommand <= 0x87) {
  198. const windowNum = captionCommand & 0x07;
  199. this.setCurrentWindow_(windowNum);
  200. } else if (captionCommand === 0x88) {
  201. const bitmap = dtvccPacket.readByte().value;
  202. return this.clearWindows_(bitmap, pts);
  203. } else if (captionCommand === 0x89) {
  204. const bitmap = dtvccPacket.readByte().value;
  205. this.displayWindows_(bitmap, pts);
  206. } else if (captionCommand === 0x8a) {
  207. const bitmap = dtvccPacket.readByte().value;
  208. return this.hideWindows_(bitmap, pts);
  209. } else if (captionCommand === 0x8b) {
  210. const bitmap = dtvccPacket.readByte().value;
  211. return this.toggleWindows_(bitmap, pts);
  212. } else if (captionCommand === 0x8c) {
  213. const bitmap = dtvccPacket.readByte().value;
  214. return this.deleteWindows_(bitmap, pts);
  215. } else if (captionCommand === 0x8f) {
  216. return this.reset_(pts);
  217. } else if (captionCommand === 0x90) {
  218. this.setPenAttributes_(dtvccPacket);
  219. } else if (captionCommand === 0x91) {
  220. this.setPenColor_(dtvccPacket);
  221. } else if (captionCommand === 0x92) {
  222. this.setPenLocation_(dtvccPacket);
  223. } else if (captionCommand === 0x97) {
  224. this.setWindowAttributes_(dtvccPacket);
  225. } else if (captionCommand >= 0x98 && captionCommand <= 0x9f) {
  226. const windowNum = (captionCommand & 0x0f) - 8;
  227. this.defineWindow_(dtvccPacket, windowNum, pts);
  228. }
  229. return null;
  230. }
  231. /**
  232. * Handles C2 group data.
  233. * @param {!shaka.cea.DtvccPacket} dtvccPacket
  234. * @param {number} controlCode
  235. * @private
  236. */
  237. handleC2_(dtvccPacket, controlCode) {
  238. // As of the CEA-708-E spec there are no commands on the C2 table, but if
  239. // seen, then the appropriate number of bytes must be skipped as per spec.
  240. if (controlCode >= 0x08 && controlCode <= 0x0f) {
  241. dtvccPacket.skip(1);
  242. } else if (controlCode >= 0x10 && controlCode <= 0x17) {
  243. dtvccPacket.skip(2);
  244. } else if (controlCode >= 0x18 && controlCode <= 0x1f) {
  245. dtvccPacket.skip(3);
  246. }
  247. }
  248. /**
  249. * Handles C3 group data.
  250. * @param {!shaka.cea.DtvccPacket} dtvccPacket
  251. * @param {number} controlCode
  252. * @private
  253. */
  254. handleC3_(dtvccPacket, controlCode) {
  255. // As of the CEA-708-E spec there are no commands on the C3 table, but if
  256. // seen, then the appropriate number of bytes must be skipped as per spec.
  257. if (controlCode >= 0x80 && controlCode <= 0x87) {
  258. dtvccPacket.skip(4);
  259. } else if (controlCode >= 0x88 && controlCode <= 0x8f) {
  260. dtvccPacket.skip(5);
  261. }
  262. }
  263. /**
  264. * @param {number} windowNum
  265. * @private
  266. */
  267. setCurrentWindow_(windowNum) {
  268. // If the window isn't created, ignore the command.
  269. if (!this.windows_[windowNum]) {
  270. return;
  271. }
  272. this.currentWindow_ = this.windows_[windowNum];
  273. }
  274. /**
  275. * Yields each non-null window specified in the 8-bit bitmap.
  276. * @param {number} bitmap 8 bits corresponding to each of the 8 windows.
  277. * @return {!Array.<number>}
  278. * @private
  279. */
  280. getSpecifiedWindowIds_(bitmap) {
  281. const ids = [];
  282. for (let i = 0; i < 8; i++) {
  283. const windowSpecified = (bitmap & 0x01) === 0x01;
  284. if (windowSpecified && this.windows_[i]) {
  285. ids.push(i);
  286. }
  287. bitmap >>= 1;
  288. }
  289. return ids;
  290. }
  291. /**
  292. * @param {number} windowsBitmap
  293. * @param {number} pts
  294. * @return {?shaka.cea.ICaptionDecoder.ClosedCaption}
  295. * @private
  296. */
  297. clearWindows_(windowsBitmap, pts) {
  298. let parsedClosedCaption = null;
  299. // Clears windows from the 8 bit bitmap.
  300. for (const windowId of this.getSpecifiedWindowIds_(windowsBitmap)) {
  301. // If window visible and being cleared, emit buffer and reset start time!
  302. const window = this.windows_[windowId];
  303. if (window.isVisible()) {
  304. parsedClosedCaption = window.forceEmit(pts, this.serviceNumber_);
  305. }
  306. window.resetMemory();
  307. }
  308. return parsedClosedCaption;
  309. }
  310. /**
  311. * @param {number} windowsBitmap
  312. * @param {number} pts
  313. * @private
  314. */
  315. displayWindows_(windowsBitmap, pts) {
  316. // Displays windows from the 8 bit bitmap.
  317. for (const windowId of this.getSpecifiedWindowIds_(windowsBitmap)) {
  318. const window = this.windows_[windowId];
  319. if (!window.isVisible()) {
  320. // We are turning on the visibility, set the start time.
  321. window.setStartTime(pts);
  322. }
  323. window.display();
  324. }
  325. }
  326. /**
  327. * @param {number} windowsBitmap
  328. * @param {number} pts
  329. * @return {?shaka.cea.ICaptionDecoder.ClosedCaption}
  330. * @private
  331. */
  332. hideWindows_(windowsBitmap, pts) {
  333. let parsedClosedCaption = null;
  334. // Hides windows from the 8 bit bitmap.
  335. for (const windowId of this.getSpecifiedWindowIds_(windowsBitmap)) {
  336. const window = this.windows_[windowId];
  337. if (window.isVisible()) {
  338. // We are turning off the visibility, emit!
  339. parsedClosedCaption = window.forceEmit(pts, this.serviceNumber_);
  340. }
  341. window.hide();
  342. }
  343. return parsedClosedCaption;
  344. }
  345. /**
  346. * @param {number} windowsBitmap
  347. * @param {number} pts
  348. * @return {?shaka.cea.ICaptionDecoder.ClosedCaption}
  349. * @private
  350. */
  351. toggleWindows_(windowsBitmap, pts) {
  352. let parsedClosedCaption = null;
  353. // Toggles windows from the 8 bit bitmap.
  354. for (const windowId of this.getSpecifiedWindowIds_(windowsBitmap)) {
  355. const window = this.windows_[windowId];
  356. if (window.isVisible()) {
  357. // We are turning off the visibility, emit!
  358. parsedClosedCaption = window.forceEmit(pts, this.serviceNumber_);
  359. } else {
  360. // We are turning on visibility, set the start time.
  361. window.setStartTime(pts);
  362. }
  363. window.toggle();
  364. }
  365. return parsedClosedCaption;
  366. }
  367. /**
  368. * @param {number} windowsBitmap
  369. * @param {number} pts
  370. * @return {?shaka.cea.ICaptionDecoder.ClosedCaption}
  371. * @private
  372. */
  373. deleteWindows_(windowsBitmap, pts) {
  374. let parsedClosedCaption = null;
  375. // Deletes windows from the 8 bit bitmap.
  376. for (const windowId of this.getSpecifiedWindowIds_(windowsBitmap)) {
  377. const window = this.windows_[windowId];
  378. if (window.isVisible()) {
  379. // We are turning off the visibility, emit!
  380. parsedClosedCaption = window.forceEmit(pts, this.serviceNumber_);
  381. }
  382. // Delete the window from the list of windows
  383. this.windows_[windowId] = null;
  384. }
  385. return parsedClosedCaption;
  386. }
  387. /**
  388. * Emits anything currently present in any of the windows, and then
  389. * deletes all windows, cancels all delays, reinitializes the service.
  390. * @param {number} pts
  391. * @return {?shaka.cea.ICaptionDecoder.ClosedCaption}
  392. * @private
  393. */
  394. reset_(pts) {
  395. const allWindowsBitmap = 0xff; // All windows should be deleted.
  396. const caption = this.deleteWindows_(allWindowsBitmap, pts);
  397. this.clear();
  398. return caption;
  399. }
  400. /**
  401. * Clears the state of the service completely.
  402. */
  403. clear() {
  404. this.currentWindow_ = null;
  405. this.windows_ = [null, null, null, null, null, null, null, null];
  406. }
  407. /**
  408. * @param {!shaka.cea.DtvccPacket} dtvccPacket
  409. * @throws {!shaka.util.Error}
  410. * @private
  411. */
  412. setPenAttributes_(dtvccPacket) {
  413. // Two bytes follow. For the purpose of this decoder, we are only concerned
  414. // with byte 2, which is of the form |I|U|EDTYP|FNTAG|.
  415. // I (1 bit): Italics toggle.
  416. // U (1 bit): Underline toggle.
  417. // EDTYP (3 bits): Edge type (unused in this decoder).
  418. // FNTAG (3 bits): Font tag (unused in this decoder).
  419. // More info at https://en.wikipedia.org/wiki/CEA-708#SetPenAttributes_(0x90_+_2_bytes)
  420. dtvccPacket.skip(1); // Skip first byte
  421. const attrByte2 = dtvccPacket.readByte().value;
  422. if (!this.currentWindow_) {
  423. return;
  424. }
  425. const italics = (attrByte2 & 0x80) > 0;
  426. const underline = (attrByte2 & 0x40) > 0;
  427. this.currentWindow_.setPenItalics(italics);
  428. this.currentWindow_.setPenUnderline(underline);
  429. }
  430. /**
  431. * @param {!shaka.cea.DtvccPacket} dtvccPacket
  432. * @throws {!shaka.util.Error}
  433. * @private
  434. */
  435. setPenColor_(dtvccPacket) {
  436. // Read foreground and background properties.
  437. const foregroundByte = dtvccPacket.readByte().value;
  438. const backgroundByte = dtvccPacket.readByte().value;
  439. dtvccPacket.skip(1); // Edge color not supported, skip it.
  440. if (!this.currentWindow_) {
  441. return;
  442. }
  443. // Byte semantics are described at the following link:
  444. // https://en.wikipedia.org/wiki/CEA-708#SetPenColor_(0x91_+_3_bytes)
  445. // Foreground color properties: |FOP|F_R|F_G|F_B|.
  446. const foregroundBlue = foregroundByte & 0x03;
  447. const foregroundGreen = (foregroundByte & 0x0c) >> 2;
  448. const foregroundRed = (foregroundByte & 0x30) >> 4;
  449. // Background color properties: |BOP|B_R|B_G|B_B|.
  450. const backgroundBlue = backgroundByte & 0x03;
  451. const backgroundGreen = (backgroundByte & 0x0c) >> 2;
  452. const backgroundRed = (backgroundByte & 0x30) >> 4;
  453. const foregroundColor = this.rgbColorToHex_(
  454. foregroundRed, foregroundGreen, foregroundBlue);
  455. const backgroundColor = this.rgbColorToHex_(
  456. backgroundRed, backgroundGreen, backgroundBlue);
  457. this.currentWindow_.setPenTextColor(foregroundColor);
  458. this.currentWindow_.setPenBackgroundColor(backgroundColor);
  459. }
  460. /**
  461. * @param {!shaka.cea.DtvccPacket} dtvccPacket
  462. * @throws {!shaka.util.Error}
  463. * @private
  464. */
  465. setPenLocation_(dtvccPacket) {
  466. // Following 2 bytes take the following form:
  467. // b1 = |0|0|0|0|ROW| and b2 = |0|0|COLUMN|
  468. const locationByte1 = dtvccPacket.readByte().value;
  469. const locationByte2 = dtvccPacket.readByte().value;
  470. if (!this.currentWindow_) {
  471. return;
  472. }
  473. const row = locationByte1 & 0x0f;
  474. const col = locationByte2 & 0x3f;
  475. this.currentWindow_.setPenLocation(row, col);
  476. }
  477. /**
  478. * @param {!shaka.cea.DtvccPacket} dtvccPacket
  479. * @throws {!shaka.util.Error}
  480. * @private
  481. */
  482. setWindowAttributes_(dtvccPacket) {
  483. // 4 bytes follow, with the following form:
  484. // Byte 1 contains fill-color information. Unused in this decoder.
  485. // Byte 2 contains border color information. Unused in this decoder.
  486. // Byte 3 contains justification information. In this decoder, we only use
  487. // the last 2 bits, which specifies text justification on the screen.
  488. // Byte 4 is special effects. Unused in this decoder.
  489. // More info at https://en.wikipedia.org/wiki/CEA-708#SetWindowAttributes_(0x97_+_4_bytes)
  490. dtvccPacket.skip(1); // Fill color not supported, skip.
  491. dtvccPacket.skip(1); // Border colors not supported, skip.
  492. const b3 = dtvccPacket.readByte().value;
  493. dtvccPacket.skip(1); // Effects not supported, skip.
  494. if (!this.currentWindow_) {
  495. return;
  496. }
  497. // Word wrap is outdated as of CEA-708-E, so we ignore those bits.
  498. // Extract the text justification and set it on the window.
  499. const justification =
  500. /** @type {!shaka.cea.Cea708Window.TextJustification} */ (b3 & 0x03);
  501. this.currentWindow_.setJustification(justification);
  502. }
  503. /**
  504. * @param {!shaka.cea.DtvccPacket} dtvccPacket
  505. * @param {number} windowNum
  506. * @param {number} pts
  507. * @throws {!shaka.util.Error}
  508. * @private
  509. */
  510. defineWindow_(dtvccPacket, windowNum, pts) {
  511. // Create the window if it doesn't exist.
  512. const windowAlreadyExists = this.windows_[windowNum] !== null;
  513. if (!windowAlreadyExists) {
  514. const window = new shaka.cea.Cea708Window(windowNum);
  515. window.setStartTime(pts);
  516. this.windows_[windowNum] = window;
  517. }
  518. // 6 Bytes follow, with the following form:
  519. // b1 = |0|0|V|R|C|PRIOR| , b2 = |P|VERT_ANCHOR| , b3 = |HOR_ANCHOR|
  520. // b4 = |ANC_ID|ROW_CNT| , b5 = |0|0|COL_COUNT| , b6 = |0|0|WNSTY|PNSTY|
  521. // Semantics of these bytes at https://en.wikipedia.org/wiki/CEA-708#DefineWindow07_(0x98-0x9F,_+_6_bytes)
  522. const b1 = dtvccPacket.readByte().value;
  523. const b2 = dtvccPacket.readByte().value;
  524. const b3 = dtvccPacket.readByte().value;
  525. const b4 = dtvccPacket.readByte().value;
  526. const b5 = dtvccPacket.readByte().value;
  527. const b6 = dtvccPacket.readByte().value;
  528. // As per 8.4.7 of CEA-708-E, row locks and column locks are to be ignored.
  529. // So this decoder will ignore these values.
  530. const visible = (b1 & 0x20) > 0;
  531. const verticalAnchor = b2 & 0x7f;
  532. const relativeToggle = (b2 & 0x80) > 0;
  533. const horAnchor = b3;
  534. const rowCount = (b4 & 0x0f) + 1; // Spec says to add 1.
  535. const anchorId = (b4 & 0xf0) >> 4;
  536. const colCount = (b5 & 0x3f) + 1; // Spec says to add 1.
  537. // If pen style = 0 AND window previously existed, keep its pen style.
  538. // Otherwise, change the pen style (For now, just reset to the default pen).
  539. // TODO add support for predefined pen styles and fonts.
  540. const penStyle = b6 & 0x07;
  541. if (!windowAlreadyExists || penStyle !== 0) {
  542. this.windows_[windowNum].resetPen();
  543. }
  544. this.windows_[windowNum].defineWindow(visible, verticalAnchor,
  545. horAnchor, anchorId, relativeToggle, rowCount, colCount);
  546. // Set the current window to the newly defined window.
  547. this.currentWindow_ = this.windows_[windowNum];
  548. }
  549. /**
  550. * Maps 64 possible CEA-708 colors to 8 CSS colors.
  551. * @param {number} red value from 0-3
  552. * @param {number} green value from 0-3
  553. * @param {number} blue value from 0-3
  554. * @return {string}
  555. * @private
  556. */
  557. rgbColorToHex_(red, green, blue) {
  558. // Rather than supporting 64 colors, this decoder supports 8 colors and
  559. // gets the closest color, as per 9.19 of CEA-708-E. This is because some
  560. // colors on television such as white, are often sent with lower intensity
  561. // and often appear dull/greyish on the browser, making them hard to read.
  562. // As per CEA-708-E 9.19, these mappings will map 64 colors to 8 colors.
  563. const colorMapping = {0: 0, 1: 0, 2: 1, 3: 1};
  564. red = colorMapping[red];
  565. green = colorMapping[green];
  566. blue = colorMapping[blue];
  567. const colorCode = (red << 2) | (green << 1) | blue;
  568. return shaka.cea.Cea708Service.Colors[colorCode];
  569. }
  570. };
  571. /**
  572. * @private @const {number}
  573. */
  574. shaka.cea.Cea708Service.ASCII_BACKSPACE = 0x08;
  575. /**
  576. * @private @const {number}
  577. */
  578. shaka.cea.Cea708Service.ASCII_FORM_FEED = 0x0c;
  579. /**
  580. * @private @const {number}
  581. */
  582. shaka.cea.Cea708Service.ASCII_CARRIAGE_RETURN = 0x0d;
  583. /**
  584. * @private @const {number}
  585. */
  586. shaka.cea.Cea708Service.ASCII_HOR_CARRIAGE_RETURN = 0x0e;
  587. /**
  588. * For extended control codes in block_data on CEA-708, byte 1 is 0x10.
  589. * @private @const {number}
  590. */
  591. shaka.cea.Cea708Service.EXT_CEA708_CTRL_CODE_BYTE1 = 0x10;
  592. /**
  593. * Holds characters mapping for bytes that are G2 control codes.
  594. * @private @const {!Map<number, string>}
  595. */
  596. shaka.cea.Cea708Service.G2Charset = new Map([
  597. [0x20, ' '], [0x21, '\xa0'], [0x25, '…'], [0x2a, 'Š'], [0x2c, 'Œ'],
  598. [0x30, '█'], [0x31, '‘'], [0x32, '’'], [0x33, '“'], [0x34, '”'],
  599. [0x35, '•'], [0x39, '™'], [0x3a, 'š'], [0x3c, 'œ'], [0x3d, '℠'],
  600. [0x3f, 'Ÿ'], [0x76, '⅛'], [0x77, '⅜'], [0x78, '⅝'], [0x79, '⅞'],
  601. [0x7a, '│'], [0x7b, '┐'], [0x7c, '└'], [0x7d, '─'], [0x7e, '┘'], [0x7f, '┌'],
  602. ]);
  603. /**
  604. * An array of 8 colors that 64 colors can be quantized to. Order here matters.
  605. * @private @const {!Array<string>}
  606. */
  607. shaka.cea.Cea708Service.Colors = [
  608. 'black', 'blue', 'green', 'cyan',
  609. 'red', 'magenta', 'yellow', 'white',
  610. ];
  611. /**
  612. * CEA-708 closed captions byte.
  613. * @typedef {{
  614. * pts: number,
  615. * type: number,
  616. * value: number,
  617. * order: number
  618. * }}
  619. *
  620. * @property {number} pts
  621. * Presentation timestamp (in second) at which this packet was received.
  622. * @property {number} type
  623. * Type of the byte. Either 2 or 3, DTVCC Packet Data or a DTVCC Packet Start.
  624. * @property {number} value The byte containing data relevant to the packet.
  625. * @property {number} order
  626. * A number indicating the order this packet was received in a sequence
  627. * of packets. Used to break ties in a stable sorting algorithm
  628. */
  629. shaka.cea.Cea708Service.Cea708Byte;