UNPKG

shaka-player

Version:
256 lines (224 loc) 6.86 kB
/*! @license * Shaka Player * Copyright 2016 Google LLC * SPDX-License-Identifier: Apache-2.0 */ goog.provide('shaka.media.ClosedCaptionParser'); goog.provide('shaka.media.IClosedCaptionParser'); goog.require('shaka.cea.DummyCaptionDecoder'); goog.require('shaka.cea.DummyCeaParser'); goog.require('shaka.log'); goog.require('shaka.util.BufferUtils'); /** * The IClosedCaptionParser defines the interface to provide all operations for * parsing the closed captions embedded in Dash videos streams. * TODO: Remove this interface and move method definitions * directly to ClosedCaptionParser. * @interface * @export */ shaka.media.IClosedCaptionParser = class { /** * Initialize the caption parser. This should be called whenever new init * segment arrives. * @param {BufferSource} initSegment * @param {boolean=} adaptation True if we just automatically switched active * variant(s). * @param {number=} continuityTimeline the optional continuity timeline */ init(initSegment, adaptation = false, continuityTimeline = -1) {} /** * Parses embedded CEA closed captions and interacts with the underlying * CaptionStream, and calls the callback function when there are closed * captions. * * @param {BufferSource} mediaFragment * @return {!Array<!shaka.extern.ICaptionDecoder.ClosedCaption>} * An array of parsed closed captions. */ parseFrom(mediaFragment) {} /** * Resets the CaptionStream. */ reset() {} /** * Remove items from the decoder cache based on the provided continuity * timelines. Caches relating to provided timelines are kept and the rest * are discarded. * * @param {Array<number>} timelinesToKeep */ remove(timelinesToKeep = []) {} /** * Returns the streams that the CEA decoder found. * @return {!Array<string>} */ getStreams() {} }; /** * Closed Caption Parser provides all operations for parsing the closed captions * embedded in Dash videos streams. * * @implements {shaka.media.IClosedCaptionParser} * @final * @export */ shaka.media.ClosedCaptionParser = class { /** * @param {string} mimeType */ constructor(mimeType) { /** @private {Map<number, shaka.extern.ICaptionDecoder>} */ this.decoderCache_ = new Map(); /** @private {number} */ this.currentContinuityTimeline_ = 0; /** @private {!shaka.extern.ICeaParser} */ this.ceaParser_ = new shaka.cea.DummyCeaParser(); const parserFactory = shaka.media.ClosedCaptionParser.findParser(mimeType.toLowerCase()); if (parserFactory) { this.ceaParser_ = parserFactory(); } /** * Decoder for decoding CEA-X08 data from closed caption packets. * @private {!shaka.extern.ICaptionDecoder} */ this.ceaDecoder_ = new shaka.cea.DummyCaptionDecoder(); const decoderFactory = shaka.media.ClosedCaptionParser.findDecoder(); if (decoderFactory) { this.ceaDecoder_ = decoderFactory(); this.decoderCache_.set(this.currentContinuityTimeline_, this.ceaDecoder_); } } /** * @override */ init(initSegment, adaptation = false, continuityTimeline = -1) { shaka.log.debug('Passing new init segment to CEA parser'); if (continuityTimeline != -1 && this.currentContinuityTimeline_ != continuityTimeline) { // When we get a new init segment associated with a different continuity // timeline, we should switch to a new decoder until we go back to the // current continuity timeline. this.updateDecoder_(continuityTimeline); } else if (!adaptation) { // Reset underlying decoder when new init segment arrives // to clear stored pts values. // This is necessary when a new Period comes in DASH or a discontinuity // in HLS. this.reset(); } this.ceaParser_.init(initSegment); if (continuityTimeline != -1) { this.currentContinuityTimeline_ = continuityTimeline; } } /** * @override */ parseFrom(mediaFragment) { // Parse the fragment. const captionPackets = this.ceaParser_.parse(mediaFragment); // Extract the caption packets for decoding. for (const captionPacket of captionPackets) { const uint8ArrayData = shaka.util.BufferUtils.toUint8(captionPacket.packet); if (uint8ArrayData.length > 0) { this.ceaDecoder_.extract(uint8ArrayData, captionPacket.pts); } } // Decode and return the parsed captions. return this.ceaDecoder_.decode(); } /** * @private */ updateDecoder_(continuityTimeline) { const decoder = this.decoderCache_.get(continuityTimeline); this.decoderCache_.set(this.currentContinuityTimeline_, this.ceaDecoder_); if (decoder) { this.ceaDecoder_ = decoder; } else { const decoderFactory = shaka.media.ClosedCaptionParser.findDecoder(); if (decoderFactory) { this.ceaDecoder_ = decoderFactory(); } this.decoderCache_.set(continuityTimeline, this.ceaDecoder_); } } /** * @override */ reset() { this.ceaDecoder_.clear(); } /** * @override */ remove(timelinesToKeep = []) { const timelines = new Set(timelinesToKeep); for (const key of this.decoderCache_.keys()) { if (!timelines.has(key)) { let decoder = this.decoderCache_.get(key); if (decoder) { decoder.clear(); } this.decoderCache_.delete(key); decoder = null; } } } /** * @override */ getStreams() { return this.ceaDecoder_.getStreams(); } /** * @param {string} mimeType * @param {!shaka.extern.CeaParserPlugin} plugin * @export */ static registerParser(mimeType, plugin) { shaka.media.ClosedCaptionParser.parserMap_.set(mimeType, plugin); } /** * @param {string} mimeType * @export */ static unregisterParser(mimeType) { shaka.media.ClosedCaptionParser.parserMap_.delete(mimeType); } /** * @param {string} mimeType * @return {?shaka.extern.CeaParserPlugin} * @export */ static findParser(mimeType) { return shaka.media.ClosedCaptionParser.parserMap_.get(mimeType); } /** * @param {!shaka.extern.CaptionDecoderPlugin} plugin * @export */ static registerDecoder(plugin) { shaka.media.ClosedCaptionParser.decoderFactory_ = plugin; } /** * @export */ static unregisterDecoder() { shaka.media.ClosedCaptionParser.decoderFactory_ = null; } /** * @return {?shaka.extern.CaptionDecoderPlugin} * @export */ static findDecoder() { return shaka.media.ClosedCaptionParser.decoderFactory_; } }; /** @private {!Map<string, shaka.extern.CeaParserPlugin>} */ shaka.media.ClosedCaptionParser.parserMap_ = new Map(); /** @private {?shaka.extern.CaptionDecoderPlugin} */ shaka.media.ClosedCaptionParser.decoderFactory_ = null;