From 49f12cdd9e311d0a8fa7a3bb887628499259257b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wojciech=20Tyczy=C5=84ski?= Date: Thu, 27 Apr 2023 10:36:21 +0200 Subject: [PATCH 1/2] Convert CEA parsers to plugins --- AUTHORS | 1 + CONTRIBUTORS | 1 + build/types/cea | 3 - build/types/complete | 2 +- build/types/core | 5 +- externs/shaka/cea.js | 110 +++++++++++++++++++++++ lib/cea/cea608_data_channel.js | 13 ++- lib/cea/cea608_memory.js | 3 +- lib/cea/cea708_service.js | 17 ++-- lib/cea/cea708_window.js | 3 +- lib/cea/cea_decoder.js | 21 +++-- lib/cea/cea_utils.js | 26 +++++- lib/cea/dummy_caption_decoder.js | 21 +++++ lib/cea/dummy_cea_parser.js | 4 +- lib/cea/i_caption_decoder.js | 51 ----------- lib/cea/i_cea_parser.js | 68 -------------- lib/cea/mp4_cea_parser.js | 23 +++-- lib/cea/ts_cea_parser.js | 15 ++-- lib/media/closed_caption_parser.js | 98 ++++++++++++++++---- lib/media/media_source_engine.js | 11 ++- lib/text/text_engine.js | 7 +- shaka-player.uncompiled.js | 3 + test/media/closed_caption_parser_unit.js | 6 +- test/text/text_engine_unit.js | 25 ++++-- 24 files changed, 332 insertions(+), 205 deletions(-) create mode 100644 externs/shaka/cea.js create mode 100644 lib/cea/dummy_caption_decoder.js delete mode 100644 lib/cea/i_caption_decoder.js delete mode 100644 lib/cea/i_cea_parser.js diff --git a/AUTHORS b/AUTHORS index 882b236e0f..14d2db17c2 100644 --- a/AUTHORS +++ b/AUTHORS @@ -92,6 +92,7 @@ Vincent Valot V-Nova Limited <*@v-nova.com> Wayne Morgan Wen Ren +Wojciech Tyczyński Raymond Cheng Blue Billywig <*@bluebillywig.com> João Nabais diff --git a/CONTRIBUTORS b/CONTRIBUTORS index 58af9023a2..e61c775f84 100644 --- a/CONTRIBUTORS +++ b/CONTRIBUTORS @@ -132,6 +132,7 @@ Vinod Balakrishnan Wayne Morgan Wen Ren Will Harris +Wojciech Tyczyński Yohann Connell Raymond Cheng Janroel Koppen diff --git a/build/types/cea b/build/types/cea index 0f5c3cc88d..2663942d4a 100644 --- a/build/types/cea +++ b/build/types/cea @@ -7,9 +7,6 @@ +../../lib/cea/cea708_service.js +../../lib/cea/cea708_window.js +../../lib/cea/dtvcc_packet_builder.js -+../../lib/cea/dummy_cea_parser.js -+../../lib/cea/i_caption_decoder.js -+../../lib/cea/i_cea_parser.js +../../lib/cea/mp4_cea_parser.js +../../lib/cea/sei_processor.js +../../lib/cea/ts_cea_parser.js diff --git a/build/types/complete b/build/types/complete index 99092a3eb3..5df0124d64 100644 --- a/build/types/complete +++ b/build/types/complete @@ -2,6 +2,7 @@ +@ads +@cast ++@cea +@fairplay +@networking +@manifests @@ -9,4 +10,3 @@ +@text +@transmuxer +@ui -+@lcevc diff --git a/build/types/core b/build/types/core index 3e9874db1a..f73e71f276 100644 --- a/build/types/core +++ b/build/types/core @@ -54,6 +54,9 @@ +../../lib/routing/payload.js +../../lib/routing/walker.js ++../../lib/cea/dummy_cea_parser.js ++../../lib/cea/dummy_caption_decoder.js + +../../lib/text/cue.js +../../lib/text/simple_text_displayer.js +../../lib/text/text_engine.js @@ -116,4 +119,4 @@ +../../third_party/closure-uri/uri.js +../../third_party/closure-uri/utils.js -+@cea ++@lcevc diff --git a/externs/shaka/cea.js b/externs/shaka/cea.js new file mode 100644 index 0000000000..f2c8037470 --- /dev/null +++ b/externs/shaka/cea.js @@ -0,0 +1,110 @@ +/*! @license + * Shaka Player + * Copyright 2016 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + + +/** + * @externs + */ + +/** + * Interface for parsing inband closed caption data from MP4 streams. + * @interface + * @exportDoc + */ +shaka.extern.ICeaParser = class { + /** + * Initializes the parser with init segment data. + * @param {!BufferSource} initSegment init segment to parse. + * @exportDoc + */ + init(initSegment) {} + + /** + * Parses the stream and extracts closed captions packets. + * @param {!BufferSource} mediaSegment media segment to parse. + * @return {!Array} + * @exportDoc + */ + parse(mediaSegment) {} +}; + +/** + * @typedef {{ + * packet: !Uint8Array, + * pts: number + * }} + * + * @description Parsed Caption Packet. + * @property {!Uint8Array} packet + * Caption packet. More specifically, it contains a "User data + * registered by Recommendation ITU-T T.35 SEI message", from section D.1.6 + * and section D.2.6 of Rec. ITU-T H.264 (06/2019). + * @property {number} pts + * The presentation timestamp (pts) at which the ITU-T T.35 data shows up. + * in seconds. + * @exportDoc + */ +shaka.extern.ICeaParser.CaptionPacket; + + +/** + * Interface for decoding inband closed captions from packets. + * @interface + * @exportDoc + */ +shaka.extern.ICaptionDecoder = class { + /** + * Extracts packets and prepares them for decoding. In a given media fragment, + * all the caption packets found in its SEI messages should be extracted by + * successive calls to extract(), followed by a single call to decode(). + * + * @param {!Uint8Array} userDataSeiMessage + * This is a User Data registered by Rec.ITU-T T.35 SEI message. + * It is described in sections D.1.6 and D.2.6 of Rec. ITU-T H.264 (06/2019). + * @param {number} pts PTS when this packet was received, in seconds. + * @exportDoc + */ + extract(userDataSeiMessage, pts) {} + + /** + * Decodes all currently extracted packets and then clears them. + * This should be called once for a set of extracts (see comment on extract). + * @return {!Array.} + * @exportDoc + */ + decode() {} + + /** + * Clears the decoder state completely. + * Should be used when an action renders the decoder state invalid, + * e.g. unbuffered seeks. + * @exportDoc + */ + clear() {} +}; + +/** + * Parsed Cue. + * @typedef {{ + * cue: !shaka.text.Cue, + * stream: string + * }} + * + * @exportDoc + */ +shaka.extern.ICaptionDecoder.ClosedCaption; + +/** + * @typedef {function():!shaka.extern.ICeaParser} + * @exportDoc + */ +shaka.extern.CeaParserPlugin; + +/** + * @typedef {function():!shaka.extern.ICaptionDecoder} + * @exportDoc + */ +shaka.extern.CaptionDecoderPlugin; diff --git a/lib/cea/cea608_data_channel.js b/lib/cea/cea608_data_channel.js index 36263a8b18..9f75d3a1bb 100644 --- a/lib/cea/cea608_data_channel.js +++ b/lib/cea/cea608_data_channel.js @@ -9,7 +9,6 @@ goog.provide('shaka.cea.Cea608DataChannel'); goog.require('shaka.cea.Cea608Memory'); goog.require('shaka.cea.CeaUtils'); goog.require('shaka.log'); -goog.requireType('shaka.cea.ICaptionDecoder'); /** @@ -225,7 +224,7 @@ shaka.cea.Cea608DataChannel = class { /** * The Cea608DataChannel control methods implement all CC control operations. * @param {!shaka.cea.Cea608DataChannel.Cea608Packet} ccPacket - * @return {?shaka.cea.ICaptionDecoder.ClosedCaption} + * @return {?shaka.extern.ICaptionDecoder.ClosedCaption} * @private */ controlMiscellaneous_(ccPacket) { @@ -291,7 +290,7 @@ shaka.cea.Cea608DataChannel = class { * Any currently buffered line needs to be emitted, along * with a window scroll action. * @param {number} pts in seconds. - * @return {?shaka.cea.ICaptionDecoder.ClosedCaption} + * @return {?shaka.extern.ICaptionDecoder.ClosedCaption} * @private */ controlCr_(pts) { @@ -324,7 +323,7 @@ shaka.cea.Cea608DataChannel = class { * This means must force emit entire display buffer. * @param {number} scrollSize New scroll window size. * @param {number} pts - * @return {?shaka.cea.ICaptionDecoder.ClosedCaption} + * @return {?shaka.extern.ICaptionDecoder.ClosedCaption} * @private */ controlRu_(scrollSize, pts) { @@ -368,7 +367,7 @@ shaka.cea.Cea608DataChannel = class { * Mode check: * EDM affects all captioning modes (but not Text mode); * @param {number} pts - * @return {?shaka.cea.ICaptionDecoder.ClosedCaption} + * @return {?shaka.extern.ICaptionDecoder.ClosedCaption} * @private */ controlEdm_(pts) { @@ -415,7 +414,7 @@ shaka.cea.Cea608DataChannel = class { * This forces Pop-On mode, and swaps the displayed and nondisplayed memories. * @private * @param {number} pts - * @return {?shaka.cea.ICaptionDecoder.ClosedCaption} + * @return {?shaka.extern.ICaptionDecoder.ClosedCaption} */ controlEoc_(pts) { let parsedClosedCaption = null; @@ -513,7 +512,7 @@ shaka.cea.Cea608DataChannel = class { * Three types of control codes: * Preamble Address Codes, Mid-Row Codes, and Miscellaneous Control Codes. * @param {!shaka.cea.Cea608DataChannel.Cea608Packet} ccPacket - * @return {?shaka.cea.ICaptionDecoder.ClosedCaption} + * @return {?shaka.extern.ICaptionDecoder.ClosedCaption} */ handleControlCode(ccPacket) { const b1 = ccPacket.ccData1; diff --git a/lib/cea/cea608_memory.js b/lib/cea/cea608_memory.js index 751141a39f..9a20ba1767 100644 --- a/lib/cea/cea608_memory.js +++ b/lib/cea/cea608_memory.js @@ -8,7 +8,6 @@ goog.provide('shaka.cea.Cea608Memory'); goog.require('shaka.cea.CeaUtils'); goog.require('shaka.text.Cue'); -goog.requireType('shaka.cea.ICaptionDecoder'); /** @@ -77,7 +76,7 @@ shaka.cea.Cea608Memory = class { * Emits a closed caption based on the state of the buffer. * @param {number} startTime Start time of the cue. * @param {number} endTime End time of the cue. - * @return {?shaka.cea.ICaptionDecoder.ClosedCaption} + * @return {?shaka.extern.ICaptionDecoder.ClosedCaption} */ forceEmit(startTime, endTime) { const stream = `CC${(this.fieldNum_<< 1) | this.channelNum_ +1}`; diff --git a/lib/cea/cea708_service.js b/lib/cea/cea708_service.js index 63652c6f48..6e141f6811 100644 --- a/lib/cea/cea708_service.js +++ b/lib/cea/cea708_service.js @@ -8,7 +8,6 @@ goog.provide('shaka.cea.Cea708Service'); goog.require('shaka.cea.Cea708Window'); goog.require('shaka.cea.DtvccPacket'); -goog.require('shaka.cea.ICaptionDecoder'); /** @@ -44,7 +43,7 @@ shaka.cea.Cea708Service = class { /** * Processes a CEA-708 control code. * @param {!shaka.cea.DtvccPacket} dtvccPacket - * @return {?shaka.cea.ICaptionDecoder.ClosedCaption} + * @return {?shaka.extern.ICaptionDecoder.ClosedCaption} * @throws {!shaka.util.Error} */ handleCea708ControlCode(dtvccPacket) { @@ -156,7 +155,7 @@ shaka.cea.Cea708Service = class { * Handles C0 group data. * @param {number} controlCode * @param {number} pts - * @return {?shaka.cea.ICaptionDecoder.ClosedCaption} + * @return {?shaka.extern.ICaptionDecoder.ClosedCaption} * @private */ handleC0_(controlCode, pts) { @@ -207,7 +206,7 @@ shaka.cea.Cea708Service = class { * @param {!shaka.cea.DtvccPacket} dtvccPacket * @param {number} captionCommand * @param {number} pts in seconds - * @return {?shaka.cea.ICaptionDecoder.ClosedCaption} + * @return {?shaka.extern.ICaptionDecoder.ClosedCaption} * @throws {!shaka.util.Error} a possible out-of-range buffer read. * @private */ @@ -318,7 +317,7 @@ shaka.cea.Cea708Service = class { /** * @param {number} windowsBitmap * @param {number} pts - * @return {?shaka.cea.ICaptionDecoder.ClosedCaption} + * @return {?shaka.extern.ICaptionDecoder.ClosedCaption} * @private */ clearWindows_(windowsBitmap, pts) { @@ -356,7 +355,7 @@ shaka.cea.Cea708Service = class { /** * @param {number} windowsBitmap * @param {number} pts - * @return {?shaka.cea.ICaptionDecoder.ClosedCaption} + * @return {?shaka.extern.ICaptionDecoder.ClosedCaption} * @private */ hideWindows_(windowsBitmap, pts) { @@ -377,7 +376,7 @@ shaka.cea.Cea708Service = class { /** * @param {number} windowsBitmap * @param {number} pts - * @return {?shaka.cea.ICaptionDecoder.ClosedCaption} + * @return {?shaka.extern.ICaptionDecoder.ClosedCaption} * @private */ toggleWindows_(windowsBitmap, pts) { @@ -402,7 +401,7 @@ shaka.cea.Cea708Service = class { /** * @param {number} windowsBitmap * @param {number} pts - * @return {?shaka.cea.ICaptionDecoder.ClosedCaption} + * @return {?shaka.extern.ICaptionDecoder.ClosedCaption} * @private */ deleteWindows_(windowsBitmap, pts) { @@ -424,7 +423,7 @@ shaka.cea.Cea708Service = class { * Emits anything currently present in any of the windows, and then * deletes all windows, cancels all delays, reinitializes the service. * @param {number} pts - * @return {?shaka.cea.ICaptionDecoder.ClosedCaption} + * @return {?shaka.extern.ICaptionDecoder.ClosedCaption} * @private */ reset_(pts) { diff --git a/lib/cea/cea708_window.js b/lib/cea/cea708_window.js index 7fed3b99a4..71e40b6728 100644 --- a/lib/cea/cea708_window.js +++ b/lib/cea/cea708_window.js @@ -8,7 +8,6 @@ goog.provide('shaka.cea.Cea708Window'); goog.require('shaka.cea.CeaUtils'); goog.require('shaka.cea.CeaUtils.StyledChar'); -goog.require('shaka.cea.ICaptionDecoder'); goog.require('shaka.text.Cue'); goog.require('shaka.util.Functional'); @@ -288,7 +287,7 @@ shaka.cea.Cea708Window = class { /** * @param {number} endTime * @param {number} serviceNumber Number of the service emitting this caption. - * @return {?shaka.cea.ICaptionDecoder.ClosedCaption} + * @return {?shaka.extern.ICaptionDecoder.ClosedCaption} */ forceEmit(endTime, serviceNumber) { const stream = `svc${serviceNumber}`; diff --git a/lib/cea/cea_decoder.js b/lib/cea/cea_decoder.js index 1075a13c1b..172ffead51 100644 --- a/lib/cea/cea_decoder.js +++ b/lib/cea/cea_decoder.js @@ -9,16 +9,18 @@ goog.provide('shaka.cea.CeaDecoder'); goog.require('shaka.cea.Cea608DataChannel'); goog.require('shaka.cea.Cea708Service'); goog.require('shaka.cea.DtvccPacketBuilder'); -goog.require('shaka.cea.ICaptionDecoder'); goog.require('shaka.log'); +goog.require('shaka.media.ClosedCaptionParser'); goog.require('shaka.util.DataViewReader'); goog.require('shaka.util.Error'); +goog.require('shaka.util.MimeUtils'); goog.requireType('shaka.cea.DtvccPacket'); /** - * CEA-X08 captions decoder. Currently only CEA-608 supported. - * @implements {shaka.cea.ICaptionDecoder} + * CEA-X08 captions decoder. + * @implements {shaka.extern.ICaptionDecoder} + * @export */ shaka.cea.CeaDecoder = class { /** */ @@ -208,7 +210,7 @@ shaka.cea.CeaDecoder = class { * @override */ decode() { - /** @type {!Array.} */ + /** @type {!Array.} */ const parsedClosedCaptions = []; // In some versions of Chrome, and other browsers, the default sorting @@ -250,7 +252,7 @@ shaka.cea.CeaDecoder = class { /** * Decodes a CEA-608 closed caption packet based on ANSI/CEA-608. * @param {shaka.cea.Cea608DataChannel.Cea608Packet} ccPacket - * @return {?shaka.cea.ICaptionDecoder.ClosedCaption} + * @return {?shaka.extern.ICaptionDecoder.ClosedCaption} * @private */ decodeCea608_(ccPacket) { @@ -315,7 +317,7 @@ shaka.cea.CeaDecoder = class { /** * Decodes a CEA-708 DTVCC packet based on ANSI/CTA-708-E. * @param {shaka.cea.DtvccPacket} dtvccPacket - * @return {!Array} + * @return {!Array} * @private */ decodeCea708_(dtvccPacket) { @@ -415,3 +417,10 @@ shaka.cea.CeaDecoder.NTSC_CC_FIELD_2 = 1; * @private @const {number} */ shaka.cea.CeaDecoder.USA_COUNTRY_CODE = 0xb5; + +shaka.media.ClosedCaptionParser.registerDecoder( + shaka.util.MimeUtils.CEA608_CLOSED_CAPTION_MIMETYPE, + () => new shaka.cea.CeaDecoder()); +shaka.media.ClosedCaptionParser.registerDecoder( + shaka.util.MimeUtils.CEA708_CLOSED_CAPTION_MIMETYPE, + () => new shaka.cea.CeaDecoder()); diff --git a/lib/cea/cea_utils.js b/lib/cea/cea_utils.js index f9075699dc..537938e6e0 100644 --- a/lib/cea/cea_utils.js +++ b/lib/cea/cea_utils.js @@ -7,7 +7,6 @@ goog.provide('shaka.cea.CeaUtils'); goog.provide('shaka.cea.CeaUtils.StyledChar'); -goog.require('shaka.cea.ICaptionDecoder'); goog.require('shaka.text.Cue'); @@ -19,7 +18,7 @@ shaka.cea.CeaUtils = class { * @param {!Array>} memory * @param {number} startTime Start time of the cue. * @param {number} endTime End time of the cue. - * @return {?shaka.cea.ICaptionDecoder.ClosedCaption} + * @return {?shaka.extern.ICaptionDecoder.ClosedCaption} */ static getParsedCaption(topLevelCue, stream, memory, startTime, endTime) { if (startTime >= endTime) { @@ -270,3 +269,26 @@ shaka.cea.CeaUtils.DEFAULT_BG_COLOR = 'black'; * @const {string} */ shaka.cea.CeaUtils.DEFAULT_TXT_COLOR = 'white'; + +/** + * NALU type for Supplemental Enhancement Information (SEI) for H.264. + * @const {number} + */ +shaka.cea.CeaUtils.H264_NALU_TYPE_SEI = 0x06; + +/** + * NALU type for Supplemental Enhancement Information (SEI) for H.265. + * @const {number} + */ +shaka.cea.CeaUtils.H265_PREFIX_NALU_TYPE_SEI = 0x27; + +/** + * NALU type for Supplemental Enhancement Information (SEI) for H.265. + * @const {number} + */ +shaka.cea.CeaUtils.H265_SUFFIX_NALU_TYPE_SEI = 0x28; + +/** + * Default timescale value for a track. + */ +shaka.cea.CeaUtils.DEFAULT_TIMESCALE_VALUE = 90000; diff --git a/lib/cea/dummy_caption_decoder.js b/lib/cea/dummy_caption_decoder.js new file mode 100644 index 0000000000..ff8e5c8a45 --- /dev/null +++ b/lib/cea/dummy_caption_decoder.js @@ -0,0 +1,21 @@ +/*! @license + * Shaka Player + * Copyright 2016 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +goog.provide('shaka.cea.DummyCaptionDecoder'); + +/** @implements {shaka.extern.ICaptionDecoder} */ +shaka.cea.DummyCaptionDecoder = class { + /** @override */ + extract(userDataSeiMessage, pts) {} + + /** @override */ + decode() { + return []; + } + + /** @override */ + clear() {} +}; diff --git a/lib/cea/dummy_cea_parser.js b/lib/cea/dummy_cea_parser.js index dbb6e6a26f..0b9b7a48a7 100644 --- a/lib/cea/dummy_cea_parser.js +++ b/lib/cea/dummy_cea_parser.js @@ -6,11 +6,9 @@ goog.provide('shaka.cea.DummyCeaParser'); -goog.require('shaka.cea.ICeaParser'); - /** * Dummy CEA parser. - * @implements {shaka.cea.ICeaParser} + * @implements {shaka.extern.ICeaParser} */ shaka.cea.DummyCeaParser = class { /** diff --git a/lib/cea/i_caption_decoder.js b/lib/cea/i_caption_decoder.js deleted file mode 100644 index 8b194bfd00..0000000000 --- a/lib/cea/i_caption_decoder.js +++ /dev/null @@ -1,51 +0,0 @@ -/*! @license - * Shaka Player - * Copyright 2016 Google LLC - * SPDX-License-Identifier: Apache-2.0 - */ - -goog.provide('shaka.cea.ICaptionDecoder'); - -goog.require('shaka.text.Cue'); - - -/** - * Interface for decoding inband closed captions from packets. - * @interface - */ -shaka.cea.ICaptionDecoder = class { - /** - * Extracts packets and prepares them for decoding. In a given media fragment, - * all the caption packets found in its SEI messages should be extracted by - * successive calls to extract(), followed by a single call to decode(). - * - * @param {!Uint8Array} userDataSeiMessage - * This is a User Data registered by Rec.ITU-T T.35 SEI message. - * It is described in sections D.1.6 and D.2.6 of Rec. ITU-T H.264 (06/2019). - * @param {number} pts PTS when this packet was received, in seconds. - */ - extract(userDataSeiMessage, pts) {} - - /** - * Decodes all currently extracted packets and then clears them. - * This should be called once for a set of extracts (see comment on extract). - * @return {!Array.} - */ - decode() {} - - /** - * Clears the decoder state completely. - * Should be used when an action renders the decoder state invalid, - * e.g. unbuffered seeks. - */ - clear() {} -}; - -/** - * Parsed Cue. - * @typedef {{ - * cue: !shaka.text.Cue, - * stream: string - * }} - */ -shaka.cea.ICaptionDecoder.ClosedCaption; diff --git a/lib/cea/i_cea_parser.js b/lib/cea/i_cea_parser.js deleted file mode 100644 index ec4b7b2c85..0000000000 --- a/lib/cea/i_cea_parser.js +++ /dev/null @@ -1,68 +0,0 @@ -/*! @license - * Shaka Player - * Copyright 2016 Google LLC - * SPDX-License-Identifier: Apache-2.0 - */ - -goog.provide('shaka.cea.ICeaParser'); - -/** - * Interface for parsing inband closed caption data from MP4 streams. - * @interface - */ -shaka.cea.ICeaParser = class { - /** - * Initializes the parser with init segment data. - * @param {!BufferSource} initSegment init segment to parse. - */ - init(initSegment) {} - - /** - * Parses the stream and extracts closed captions packets. - * @param {!BufferSource} mediaSegment media segment to parse. - * @return {!Array} - */ - parse(mediaSegment) {} -}; - -/** - * NALU type for Supplemental Enhancement Information (SEI) for H.264. - * @const {number} - */ -shaka.cea.ICeaParser.H264_NALU_TYPE_SEI = 0x06; - -/** - * NALU type for Supplemental Enhancement Information (SEI) for H.265. - * @const {number} - */ -shaka.cea.ICeaParser.H265_PREFIX_NALU_TYPE_SEI = 0x27; - -/** - * NALU type for Supplemental Enhancement Information (SEI) for H.265. - * @const {number} - */ -shaka.cea.ICeaParser.H265_SUFFIX_NALU_TYPE_SEI = 0x28; - -/** - * Default timescale value for a track. - */ -shaka.cea.ICeaParser.DEFAULT_TIMESCALE_VALUE = 90000; - -/** - * @typedef {{ - * packet: !Uint8Array, - * pts: number - * }} - * - * @description Parsed Caption Packet. - * @property {!Uint8Array} packet - * Caption packet. More specifically, it contains a "User data - * registered by Recommendation ITU-T T.35 SEI message", from section D.1.6 - * and section D.2.6 of Rec. ITU-T H.264 (06/2019). - * @property {number} pts - * The presentation timestamp (pts) at which the ITU-T T.35 data shows up. - * in seconds. - * @exportDoc - */ -shaka.cea.ICeaParser.CaptionPacket; - diff --git a/lib/cea/mp4_cea_parser.js b/lib/cea/mp4_cea_parser.js index 553407813a..b1f41693d3 100644 --- a/lib/cea/mp4_cea_parser.js +++ b/lib/cea/mp4_cea_parser.js @@ -7,9 +7,10 @@ goog.provide('shaka.cea.Mp4CeaParser'); goog.require('goog.asserts'); -goog.require('shaka.cea.ICeaParser'); +goog.require('shaka.cea.CeaUtils'); goog.require('shaka.cea.SeiProcessor'); goog.require('shaka.log'); +goog.require('shaka.media.ClosedCaptionParser'); goog.require('shaka.util.DataViewReader'); goog.require('shaka.util.Error'); goog.require('shaka.util.Mp4Parser'); @@ -17,7 +18,8 @@ goog.require('shaka.util.Mp4BoxParsers'); /** * MPEG4 stream parser used for extracting 708 closed captions data. - * @implements {shaka.cea.ICeaParser} + * @implements {shaka.extern.ICeaParser} + * @export */ shaka.cea.Mp4CeaParser = class { /** */ @@ -157,7 +159,7 @@ shaka.cea.Mp4CeaParser = class { return []; } - /** @type {!Array} **/ + /** @type {!Array} **/ const captionPackets = []; // Fields that are found in MOOF boxes @@ -167,7 +169,7 @@ shaka.cea.Mp4CeaParser = class { let moofOffset = null; let trunOffset = null; let baseMediaDecodeTime = null; - let timescale = shaka.cea.ICeaParser.DEFAULT_TIMESCALE_VALUE; + let timescale = shaka.cea.CeaUtils.DEFAULT_TIMESCALE_VALUE; new Mp4Parser() .box('moof', (box) => { @@ -249,13 +251,13 @@ shaka.cea.Mp4CeaParser = class { * @param {number} defaultSampleSize * @param {number} offset * @param {!Array} sampleData - * @param {!Array} captionPackets + * @param {!Array} captionPackets * @private */ parseMdat_(reader, time, timescale, defaultSampleDuration, defaultSampleSize, offset, sampleData, captionPackets) { const BitstreamFormat = shaka.cea.Mp4CeaParser.BitstreamFormat; - const ICeaParser = shaka.cea.ICeaParser; + const CeaUtils = shaka.cea.CeaUtils; let sampleIndex = 0; // The fields in each ParsedTRUNSample contained in the sampleData @@ -281,14 +283,14 @@ shaka.cea.Mp4CeaParser = class { switch (this.bitstreamFormat_) { case BitstreamFormat.H264: naluType = naluHeader & 0x1f; - isSeiMessage = naluType == ICeaParser.H264_NALU_TYPE_SEI; + isSeiMessage = naluType == CeaUtils.H264_NALU_TYPE_SEI; break; case BitstreamFormat.H265: naluType = (naluHeader >> 1) & 0x3f; isSeiMessage = - naluType == ICeaParser.H265_PREFIX_NALU_TYPE_SEI || - naluType == ICeaParser.H265_SUFFIX_NALU_TYPE_SEI; + naluType == CeaUtils.H265_PREFIX_NALU_TYPE_SEI || + naluType == CeaUtils.H265_SUFFIX_NALU_TYPE_SEI; break; default: @@ -367,3 +369,6 @@ shaka.cea.Mp4CeaParser.CodecBitstreamMap_ = { 'dvh1': shaka.cea.Mp4CeaParser.BitstreamFormat.H265, 'dvhe': shaka.cea.Mp4CeaParser.BitstreamFormat.H265, }; + +shaka.media.ClosedCaptionParser.registerParser('video/mp4', + () => new shaka.cea.Mp4CeaParser()); diff --git a/lib/cea/ts_cea_parser.js b/lib/cea/ts_cea_parser.js index eeab6d3ab8..e1b0798058 100644 --- a/lib/cea/ts_cea_parser.js +++ b/lib/cea/ts_cea_parser.js @@ -6,14 +6,16 @@ goog.provide('shaka.cea.TsCeaParser'); -goog.require('shaka.cea.ICeaParser'); +goog.require('shaka.cea.CeaUtils'); goog.require('shaka.cea.SeiProcessor'); +goog.require('shaka.media.ClosedCaptionParser'); goog.require('shaka.util.BufferUtils'); goog.require('shaka.util.TsParser'); /** * MPEG TS CEA parser. - * @implements {shaka.cea.ICeaParser} + * @implements {shaka.extern.ICeaParser} + * @export */ shaka.cea.TsCeaParser = class { /** */ @@ -37,9 +39,9 @@ shaka.cea.TsCeaParser = class { * @override */ parse(mediaSegment) { - const ICeaParser = shaka.cea.ICeaParser; + const CeaUtils = shaka.cea.CeaUtils; - /** @type {!Array} **/ + /** @type {!Array} **/ const captionPackets = []; const uint8ArrayData = shaka.util.BufferUtils.toUint8(mediaSegment); @@ -49,7 +51,7 @@ shaka.cea.TsCeaParser = class { const tsParser = new shaka.util.TsParser().parse(uint8ArrayData); const videoNalus = tsParser.getVideoNalus(); for (const nalu of videoNalus) { - if (nalu.type == ICeaParser.H264_NALU_TYPE_SEI && + if (nalu.type == CeaUtils.H264_NALU_TYPE_SEI && nalu.time != null) { for (const packet of this.seiProcessor_.process(nalu.data)) { captionPackets.push({ @@ -62,3 +64,6 @@ shaka.cea.TsCeaParser = class { return captionPackets; } }; + +shaka.media.ClosedCaptionParser.registerParser('video/mp2t', + () => new shaka.cea.TsCeaParser()); diff --git a/lib/media/closed_caption_parser.js b/lib/media/closed_caption_parser.js index a657bb2d92..0dc307df2f 100644 --- a/lib/media/closed_caption_parser.js +++ b/lib/media/closed_caption_parser.js @@ -7,13 +7,9 @@ goog.provide('shaka.media.ClosedCaptionParser'); goog.provide('shaka.media.IClosedCaptionParser'); -goog.require('shaka.cea.CeaDecoder'); +goog.require('shaka.cea.DummyCaptionDecoder'); goog.require('shaka.cea.DummyCeaParser'); -goog.require('shaka.cea.Mp4CeaParser'); -goog.require('shaka.cea.TsCeaParser'); goog.require('shaka.util.BufferUtils'); -goog.requireType('shaka.cea.ICaptionDecoder'); -goog.requireType('shaka.cea.ICeaParser'); /** @@ -22,6 +18,7 @@ goog.requireType('shaka.cea.ICeaParser'); * TODO: Remove this interface and move method definitions * directly to ClosedCaptonParser. * @interface + * @export */ shaka.media.IClosedCaptionParser = class { /** @@ -36,7 +33,7 @@ shaka.media.IClosedCaptionParser = class { * captions. * * @param {BufferSource} mediaFragment - * @return {!Array} + * @return {!Array} * An array of parsed closed captions. */ parseFrom(mediaFragment) {} @@ -53,27 +50,34 @@ shaka.media.IClosedCaptionParser = class { * * @implements {shaka.media.IClosedCaptionParser} * @final + * @export */ shaka.media.ClosedCaptionParser = class { - /** */ - constructor(mimeType) { - /** @private {!shaka.cea.ICeaParser} */ + /** + * @param {string} streamMimeType + * @param {string} ceaMimeType + */ + constructor(streamMimeType, ceaMimeType) { + /** @private {!shaka.extern.ICeaParser} */ this.ceaParser_ = new shaka.cea.DummyCeaParser(); - if (mimeType.toLowerCase().includes('video/mp4')) { - // MP4 Parser to extract closed caption packets from H.264/H.265 video. - this.ceaParser_ = new shaka.cea.Mp4CeaParser(); - } - if (mimeType.toLowerCase().includes('video/mp2t')) { - // TS Parser to extract closed caption packets from H.264 video. - this.ceaParser_ = new shaka.cea.TsCeaParser(); + const parserFactory = + shaka.media.ClosedCaptionParser.findParser(streamMimeType); + if (parserFactory) { + this.ceaParser_ = parserFactory(); } /** * Decoder for decoding CEA-X08 data from closed caption packets. - * @private {!shaka.cea.ICaptionDecoder} + * @private {!shaka.extern.ICaptionDecoder} */ - this.ceaDecoder_ = new shaka.cea.CeaDecoder(); + this.ceaDecoder_ = new shaka.cea.DummyCaptionDecoder(); + + const decoderFactory = + shaka.media.ClosedCaptionParser.findDecoder(ceaMimeType); + if (decoderFactory) { + this.ceaDecoder_ = decoderFactory(); + } } /** @@ -109,4 +113,62 @@ shaka.media.ClosedCaptionParser = class { reset() { this.ceaDecoder_.clear(); } + + /** + * @param {string} mimeType + * @param {!shaka.extern.CeaParserPlugin} plugin + * @export + */ + static registerParser(mimeType, plugin) { + shaka.media.ClosedCaptionParser.parserMap_[mimeType] = plugin; + } + + /** + * @param {string} mimeType + * @export + */ + static unregisterParser(mimeType) { + delete shaka.media.ClosedCaptionParser.parserMap_[mimeType]; + } + + /** + * @param {string} mimeType + * @return {?shaka.extern.CeaParserPlugin} + * @export + */ + static findParser(mimeType) { + return shaka.media.ClosedCaptionParser.parserMap_[mimeType]; + } + + /** + * @param {string} mimeType + * @param {!shaka.extern.CaptionDecoderPlugin} plugin + * @export + */ + static registerDecoder(mimeType, plugin) { + shaka.media.ClosedCaptionParser.decoderMap_[mimeType] = plugin; + } + + /** + * @param {string} mimeType + * @export + */ + static unregisterDecoder(mimeType) { + delete shaka.media.ClosedCaptionParser.decoderMap_[mimeType]; + } + + /** + * @param {string} mimeType + * @return {?shaka.extern.CaptionDecoderPlugin} + * @export + */ + static findDecoder(mimeType) { + return shaka.media.ClosedCaptionParser.decoderMap_[mimeType]; + } }; + +/** @private {!Object} */ +shaka.media.ClosedCaptionParser.parserMap_ = {}; + +/** @private {!Object} */ +shaka.media.ClosedCaptionParser.decoderMap_ = {}; diff --git a/lib/media/media_source_engine.js b/lib/media/media_source_engine.js index 307c83cb1f..f67f79345c 100644 --- a/lib/media/media_source_engine.js +++ b/lib/media/media_source_engine.js @@ -585,11 +585,12 @@ shaka.media.MediaSourceEngine = class { * Create a new closed caption parser. This will ONLY be replaced by tests as * a way to inject fake closed caption parser instances. * - * @param {string} mimeType + * @param {string} streamMimeType + * @param {string} ceaMimeType * @return {!shaka.media.IClosedCaptionParser} */ - getCaptionParser(mimeType) { - return new shaka.media.ClosedCaptionParser(mimeType); + getCaptionParser(streamMimeType, ceaMimeType) { + return new shaka.media.ClosedCaptionParser(streamMimeType, ceaMimeType); } /** @@ -743,7 +744,9 @@ shaka.media.MediaSourceEngine = class { this.sequenceMode_); } if (!this.captionParser_) { - this.captionParser_ = this.getCaptionParser(mimeType); + const basicType = mimeType.split(';', 1)[0]; + this.captionParser_ = this.getCaptionParser(basicType, + shaka.util.MimeUtils.CEA608_CLOSED_CAPTION_MIMETYPE); } // If it is the init segment for closed captions, initialize the closed // caption parser. diff --git a/lib/text/text_engine.js b/lib/text/text_engine.js index 789d4b1471..ad9ccdf5b2 100644 --- a/lib/text/text_engine.js +++ b/lib/text/text_engine.js @@ -8,11 +8,11 @@ goog.provide('shaka.text.TextEngine'); goog.require('goog.asserts'); goog.require('shaka.log'); +goog.require('shaka.media.ClosedCaptionParser'); goog.require('shaka.text.Cue'); goog.require('shaka.util.BufferUtils'); goog.require('shaka.util.IDestroyable'); goog.require('shaka.util.MimeUtils'); -goog.requireType('shaka.cea.ICaptionDecoder'); // TODO: revisit this when Closure Compiler supports partially-exported classes. @@ -100,8 +100,7 @@ shaka.text.TextEngine = class { } if (mimeType == shaka.util.MimeUtils.CEA608_CLOSED_CAPTION_MIMETYPE || mimeType == shaka.util.MimeUtils.CEA708_CLOSED_CAPTION_MIMETYPE ) { - // Closed captions. - return true; + return !!shaka.media.ClosedCaptionParser.findDecoder(mimeType); } return false; } @@ -362,7 +361,7 @@ shaka.text.TextEngine = class { * Store the closed captions in the text engine, and append the cues to the * text displayer. This is a side-channel used for embedded text only. * - * @param {!Array.} closedCaptions + * @param {!Array} closedCaptions * @param {?number} startTime relative to the start of the presentation * @param {?number} endTime relative to the start of the presentation * @param {number} videoTimestampOffset the timestamp offset of the video diff --git a/shaka-player.uncompiled.js b/shaka-player.uncompiled.js index 78c00fea64..578ce4eb90 100644 --- a/shaka-player.uncompiled.js +++ b/shaka-player.uncompiled.js @@ -62,6 +62,9 @@ goog.require('shaka.text.SsaTextParser'); goog.require('shaka.text.TtmlTextParser'); goog.require('shaka.text.VttTextParser'); goog.require('shaka.text.WebVttGenerator'); +goog.require('shaka.cea.CeaDecoder'); +goog.require('shaka.cea.Mp4CeaParser'); +goog.require('shaka.cea.TsCeaParser'); goog.require('shaka.transmuxer.TransmuxerEngine'); goog.require('shaka.transmuxer.MssTransmuxer'); goog.require('shaka.transmuxer.MuxjsTransmuxer'); diff --git a/test/media/closed_caption_parser_unit.js b/test/media/closed_caption_parser_unit.js index bdbd4b1003..db992976f4 100644 --- a/test/media/closed_caption_parser_unit.js +++ b/test/media/closed_caption_parser_unit.js @@ -10,8 +10,10 @@ describe('ClosedCaptionParser', () => { 'base/test/test/assets/empty_caption_video_init.mp4'); const videoSegment = await shaka.test.Util.fetch( 'base/test/test/assets/empty_caption_video_segment.mp4'); - const mimeType = 'video/mp4'; - const parser = new shaka.media.ClosedCaptionParser(mimeType); + const streamMimeType = 'video/mp4'; + const captionMimeType = shaka.util.MimeUtils.CEA608_CLOSED_CAPTION_MIMETYPE; + const parser = + new shaka.media.ClosedCaptionParser(streamMimeType, captionMimeType); parser.init(initSegment); parser.parseFrom(videoSegment); }); diff --git a/test/text/text_engine_unit.js b/test/text/text_engine_unit.js index c057e6f565..2a05ec742b 100644 --- a/test/text/text_engine_unit.js +++ b/test/text/text_engine_unit.js @@ -66,14 +66,23 @@ describe('TextEngine', () => { expect(TextEngine.isTypeSupported(dummyMimeType)).toBe(false); }); - it('reports support when it\'s closed captions', - () => { - // Both CEA-608 and CEA-708 is supported by our closed caption parser. - expect(TextEngine.isTypeSupported( - shaka.util.MimeUtils.CEA608_CLOSED_CAPTION_MIMETYPE)).toBe(true); - expect(TextEngine.isTypeSupported( - shaka.util.MimeUtils.CEA708_CLOSED_CAPTION_MIMETYPE)).toBe(true); - }); + it('reports support for closed captions if decoder is installed', () => { + const originalFind = shaka.media.ClosedCaptionParser.findDecoder; + const mockFind = jasmine.createSpy('findDecoder').and.returnValue(null); + shaka.media.ClosedCaptionParser.findDecoder = + shaka.test.Util.spyFunc(mockFind); + // Both CEA-608 and CEA-708 is supported by our closed caption parser. + expect(TextEngine.isTypeSupported( + shaka.util.MimeUtils.CEA608_CLOSED_CAPTION_MIMETYPE)).toBe(false); + expect(TextEngine.isTypeSupported( + shaka.util.MimeUtils.CEA708_CLOSED_CAPTION_MIMETYPE)).toBe(false); + + shaka.media.ClosedCaptionParser.findDecoder = originalFind; + expect(TextEngine.isTypeSupported( + shaka.util.MimeUtils.CEA608_CLOSED_CAPTION_MIMETYPE)).toBe(true); + expect(TextEngine.isTypeSupported( + shaka.util.MimeUtils.CEA708_CLOSED_CAPTION_MIMETYPE)).toBe(true); + }); }); describe('appendBuffer', () => { From 9828aa3639a4fefe2942f5be0d6b4c5fe0ae1aa9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wojciech=20Tyczy=C5=84ski?= Date: Thu, 27 Apr 2023 14:59:41 +0200 Subject: [PATCH 2/2] Do not use MIME type to register CEA decoder --- lib/cea/cea_decoder.js | 5 ---- lib/cea/cea_utils.js | 1 + lib/media/closed_caption_parser.js | 29 ++++++++++-------------- lib/media/media_source_engine.js | 10 ++++---- lib/text/text_engine.js | 2 +- test/media/closed_caption_parser_unit.js | 6 ++--- 6 files changed, 20 insertions(+), 33 deletions(-) diff --git a/lib/cea/cea_decoder.js b/lib/cea/cea_decoder.js index 172ffead51..820cc4c52e 100644 --- a/lib/cea/cea_decoder.js +++ b/lib/cea/cea_decoder.js @@ -13,7 +13,6 @@ goog.require('shaka.log'); goog.require('shaka.media.ClosedCaptionParser'); goog.require('shaka.util.DataViewReader'); goog.require('shaka.util.Error'); -goog.require('shaka.util.MimeUtils'); goog.requireType('shaka.cea.DtvccPacket'); @@ -419,8 +418,4 @@ shaka.cea.CeaDecoder.NTSC_CC_FIELD_2 = 1; shaka.cea.CeaDecoder.USA_COUNTRY_CODE = 0xb5; shaka.media.ClosedCaptionParser.registerDecoder( - shaka.util.MimeUtils.CEA608_CLOSED_CAPTION_MIMETYPE, - () => new shaka.cea.CeaDecoder()); -shaka.media.ClosedCaptionParser.registerDecoder( - shaka.util.MimeUtils.CEA708_CLOSED_CAPTION_MIMETYPE, () => new shaka.cea.CeaDecoder()); diff --git a/lib/cea/cea_utils.js b/lib/cea/cea_utils.js index 537938e6e0..dbc523bd59 100644 --- a/lib/cea/cea_utils.js +++ b/lib/cea/cea_utils.js @@ -290,5 +290,6 @@ shaka.cea.CeaUtils.H265_SUFFIX_NALU_TYPE_SEI = 0x28; /** * Default timescale value for a track. + * @const {number} */ shaka.cea.CeaUtils.DEFAULT_TIMESCALE_VALUE = 90000; diff --git a/lib/media/closed_caption_parser.js b/lib/media/closed_caption_parser.js index 0dc307df2f..c526456c09 100644 --- a/lib/media/closed_caption_parser.js +++ b/lib/media/closed_caption_parser.js @@ -54,15 +54,14 @@ shaka.media.IClosedCaptionParser = class { */ shaka.media.ClosedCaptionParser = class { /** - * @param {string} streamMimeType - * @param {string} ceaMimeType + * @param {string} mimeType */ - constructor(streamMimeType, ceaMimeType) { + constructor(mimeType) { /** @private {!shaka.extern.ICeaParser} */ this.ceaParser_ = new shaka.cea.DummyCeaParser(); const parserFactory = - shaka.media.ClosedCaptionParser.findParser(streamMimeType); + shaka.media.ClosedCaptionParser.findParser(mimeType.toLowerCase()); if (parserFactory) { this.ceaParser_ = parserFactory(); } @@ -73,8 +72,7 @@ shaka.media.ClosedCaptionParser = class { */ this.ceaDecoder_ = new shaka.cea.DummyCaptionDecoder(); - const decoderFactory = - shaka.media.ClosedCaptionParser.findDecoder(ceaMimeType); + const decoderFactory = shaka.media.ClosedCaptionParser.findDecoder(); if (decoderFactory) { this.ceaDecoder_ = decoderFactory(); } @@ -141,34 +139,31 @@ shaka.media.ClosedCaptionParser = class { } /** - * @param {string} mimeType * @param {!shaka.extern.CaptionDecoderPlugin} plugin * @export */ - static registerDecoder(mimeType, plugin) { - shaka.media.ClosedCaptionParser.decoderMap_[mimeType] = plugin; + static registerDecoder(plugin) { + shaka.media.ClosedCaptionParser.decoderFactory_ = plugin; } /** - * @param {string} mimeType * @export */ - static unregisterDecoder(mimeType) { - delete shaka.media.ClosedCaptionParser.decoderMap_[mimeType]; + static unregisterDecoder() { + shaka.media.ClosedCaptionParser.decoderFactory_ = null; } /** - * @param {string} mimeType * @return {?shaka.extern.CaptionDecoderPlugin} * @export */ - static findDecoder(mimeType) { - return shaka.media.ClosedCaptionParser.decoderMap_[mimeType]; + static findDecoder() { + return shaka.media.ClosedCaptionParser.decoderFactory_; } }; /** @private {!Object} */ shaka.media.ClosedCaptionParser.parserMap_ = {}; -/** @private {!Object} */ -shaka.media.ClosedCaptionParser.decoderMap_ = {}; +/** @private {?shaka.extern.CaptionDecoderPlugin} */ +shaka.media.ClosedCaptionParser.decoderFactory_ = null; diff --git a/lib/media/media_source_engine.js b/lib/media/media_source_engine.js index f67f79345c..093ce79d24 100644 --- a/lib/media/media_source_engine.js +++ b/lib/media/media_source_engine.js @@ -585,12 +585,11 @@ shaka.media.MediaSourceEngine = class { * Create a new closed caption parser. This will ONLY be replaced by tests as * a way to inject fake closed caption parser instances. * - * @param {string} streamMimeType - * @param {string} ceaMimeType + * @param {string} mimeType * @return {!shaka.media.IClosedCaptionParser} */ - getCaptionParser(streamMimeType, ceaMimeType) { - return new shaka.media.ClosedCaptionParser(streamMimeType, ceaMimeType); + getCaptionParser(mimeType) { + return new shaka.media.ClosedCaptionParser(mimeType); } /** @@ -745,8 +744,7 @@ shaka.media.MediaSourceEngine = class { } if (!this.captionParser_) { const basicType = mimeType.split(';', 1)[0]; - this.captionParser_ = this.getCaptionParser(basicType, - shaka.util.MimeUtils.CEA608_CLOSED_CAPTION_MIMETYPE); + this.captionParser_ = this.getCaptionParser(basicType); } // If it is the init segment for closed captions, initialize the closed // caption parser. diff --git a/lib/text/text_engine.js b/lib/text/text_engine.js index ad9ccdf5b2..fe6ab54889 100644 --- a/lib/text/text_engine.js +++ b/lib/text/text_engine.js @@ -100,7 +100,7 @@ shaka.text.TextEngine = class { } if (mimeType == shaka.util.MimeUtils.CEA608_CLOSED_CAPTION_MIMETYPE || mimeType == shaka.util.MimeUtils.CEA708_CLOSED_CAPTION_MIMETYPE ) { - return !!shaka.media.ClosedCaptionParser.findDecoder(mimeType); + return !!shaka.media.ClosedCaptionParser.findDecoder(); } return false; } diff --git a/test/media/closed_caption_parser_unit.js b/test/media/closed_caption_parser_unit.js index db992976f4..bdbd4b1003 100644 --- a/test/media/closed_caption_parser_unit.js +++ b/test/media/closed_caption_parser_unit.js @@ -10,10 +10,8 @@ describe('ClosedCaptionParser', () => { 'base/test/test/assets/empty_caption_video_init.mp4'); const videoSegment = await shaka.test.Util.fetch( 'base/test/test/assets/empty_caption_video_segment.mp4'); - const streamMimeType = 'video/mp4'; - const captionMimeType = shaka.util.MimeUtils.CEA608_CLOSED_CAPTION_MIMETYPE; - const parser = - new shaka.media.ClosedCaptionParser(streamMimeType, captionMimeType); + const mimeType = 'video/mp4'; + const parser = new shaka.media.ClosedCaptionParser(mimeType); parser.init(initSegment); parser.parseFrom(videoSegment); });