UNPKG

shaka-player

Version:
247 lines (233 loc) • 7.03 kB
/*! @license * Shaka Player * Copyright 2016 Google LLC * SPDX-License-Identifier: Apache-2.0 */ goog.provide('shaka.util.Scte35'); goog.require('shaka.util.StringUtils'); goog.require('shaka.util.TXml'); goog.require('shaka.util.Uint8ArrayUtils'); /** * Recognizes SCTE-35 messages in the transports that can carry them, and * normalizes them onto the presentation timeline. The payload itself is * passed through untouched for the application to decode. */ shaka.util.Scte35 = class { /** * @param {string} scheme * @return {boolean} */ static isScheme(scheme) { return shaka.util.Scte35.SCHEMES_.includes(scheme.trim()); } /** * @param {shaka.extern.TimelineRegionInfo} region * @return {?shaka.extern.Scte35Event} */ static fromRegion(region) { if (!shaka.util.Scte35.isScheme(region.schemeIdUri)) { return null; } const event = { schemeIdUri: region.schemeIdUri, startTime: region.startTime, endTime: region.endTime, id: region.id, source: 'dash', kind: '', data: null, node: null, }; if (region.eventNode) { // xml+bin wraps the section in <Binary>; plain xml has no binary form. event.data = shaka.util.Scte35.binaryPayload_(region.eventNode); if (!event.data) { event.node = region.eventNode; } } return event; } /** * @param {shaka.extern.EmsgInfo} emsg * @return {?shaka.extern.Scte35Event} */ static fromEmsg(emsg) { if (!shaka.util.Scte35.isScheme(emsg.schemeIdUri)) { return null; } const event = { schemeIdUri: emsg.schemeIdUri, startTime: emsg.startTime, // 0xffffffff means the duration is unknown. endTime: emsg.eventDuration == 0xffffffff ? emsg.startTime : emsg.endTime, id: String(emsg.id), source: 'emsg', kind: '', data: null, node: null, }; if (emsg.messageData && emsg.messageData.length) { if (emsg.schemeIdUri.trim().endsWith(':bin')) { event.data = emsg.messageData; } else { // A message we cannot even decode as text must not fail playback. let node = null; try { node = shaka.util.Scte35.parseXml_( shaka.util.StringUtils.fromBytesAutoDetect(emsg.messageData)); } catch (error) {} if (node) { event.data = shaka.util.Scte35.binaryPayload_(node); if (!event.data) { event.node = node; } } } } return event; } /** * Reads one record of an MSF event timeline track carrying SCTE-35 * (draft-wilaw-moq-scte35-event-timeline), once the caller has placed it on * the presentation timeline. * * @param {string} eventType The track's catalog `eventType`. * @param {string} payload The record's `scte35_payload`: base64 for binary * carriage, the XML itself for XML carriage. * @param {number} startTime * @param {string} id * @return {?shaka.extern.Scte35Event} Null when the event type is not an * SCTE-35 one, or the payload cannot be decoded. */ static fromMsf(eventType, payload, startTime, id) { const format = shaka.util.Scte35.MSF_EVENT_TYPES_.get(eventType.trim()); if (!format) { return null; } const event = { schemeIdUri: eventType, startTime, // The envelope carries no duration; it is only inside the payload. endTime: startTime, id, source: 'msf', kind: '', data: null, node: null, }; if (format == 'bin') { event.data = shaka.util.Scte35.fromBase64_(payload); return event.data ? event : null; } const node = shaka.util.Scte35.parseXml_(payload); if (!node) { return null; } event.data = shaka.util.Scte35.binaryPayload_(node); if (!event.data) { event.node = node; } return event; } /** * Whether an MSF event timeline `eventType` is one fromMsf() reads. * * @param {string} eventType * @return {boolean} */ static isMsfEventType(eventType) { return shaka.util.Scte35.MSF_EVENT_TYPES_.has(eventType.trim()); } /** * The first element of an XML message, or null when there is none. * * TXml.parseXmlString() answers the first node it finds, which for text * that is not XML at all is the text itself rather than an element, and for * a document with an XML declaration is the declaration. * * @param {string} text * @return {?shaka.extern.xml.Node} * @private */ static parseXml_(text) { // A message we cannot even parse must not fail playback. try { for (const node of shaka.util.TXml.parse(text, false) || []) { if (typeof node != 'string' && !node.tagName.startsWith('?')) { return node; } } } catch (error) {} return null; } /** * Finds the base64 <Binary> section, ignoring the producer's namespace * prefix and how deeply it nested the element. * @param {!shaka.extern.xml.Node} node * @return {?Uint8Array} * @private */ static binaryPayload_(node) { if (node.tagName.split(':').pop() == 'Binary') { return shaka.util.Scte35.fromBase64_( shaka.util.TXml.getTextContents(node)); } for (const child of node.children) { if (typeof child != 'string') { const data = shaka.util.Scte35.binaryPayload_(child); if (data) { return data; } } } return null; } /** * @param {?string} text * @return {?Uint8Array} * @private */ static fromBase64_(text) { text = (text || '').replace(/\s/g, ''); if (!text || text.length % 4 || !/^[A-Za-z0-9+/]+={0,2}$/.test(text)) { return null; } return shaka.util.Uint8ArrayUtils.fromBase64(text); } /** * @param {?string} text * @return {?Uint8Array} */ static fromHex(text) { if (!text || !/^0[xX](?:[0-9a-fA-F]{2})+$/.test(text)) { return null; } return shaka.util.Uint8ArrayUtils.fromHex(text.substring(2)); } }; /** @private @const {!Array<string>} */ shaka.util.Scte35.SCHEMES_ = [ 'urn:scte:scte35:2013:xml', 'urn:scte:scte35:2014:xml+bin', // Compatibility alias used by some producers. 'urn:scte:scte35:2013:xml+bin', 'urn:scte:scte35:2013:bin', ]; /** * The MSF event timeline types carrying SCTE-35, and whether their payload is * base64 binary or XML. draft-wilaw-moq-scte35-event-timeline defines the * 2022 names; the MSF draft's own catalog example uses the 2013 one. These * are kept apart from SCHEMES_ so that recognizing them in MSF does not change * what DASH and emsg recognize. * * @private @const {!Map<string, string>} */ shaka.util.Scte35.MSF_EVENT_TYPES_ = new Map([ ['urn:scte:scte35:2022:bin', 'bin'], ['urn:scte:scte35:2022:xml', 'xml'], ['urn:scte:scte35:2013:bin', 'bin'], ['urn:scte:scte35:2013:xml', 'xml'], ['urn:scte:scte35:2014:xml+bin', 'xml'], ['urn:scte:scte35:2013:xml+bin', 'xml'], ]);