mirror of
https://github.com/shaka-project/shaka-player.git
synced 2026-06-24 17:35:10 +03:00
4cc4e96dbd
* Updates all Copyright years to 2015. * Adds licenses annotations to all JS. * Makes all licenses identical to avoid repeated appearance in the compiled output. * Drops fileoverview annotations, which do not affect docs output. * The linter still requires fileoverview on externs. This patch required a newer closure compiler, since the previous version we used had a bug regarding license annotations that caused the license comment block to appear in the output once per file regardless of uniqueness. Change-Id: I2e9272db680cba7ecc4613d97f1d3a94ac2244cc
343 lines
9.6 KiB
JavaScript
343 lines
9.6 KiB
JavaScript
/**
|
|
* @license
|
|
* Copyright 2015 Google Inc.
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
goog.provide('shaka.media.WebmSegmentIndexParser');
|
|
|
|
goog.require('goog.Uri');
|
|
goog.require('shaka.asserts');
|
|
goog.require('shaka.log');
|
|
goog.require('shaka.media.SegmentReference');
|
|
goog.require('shaka.util.EbmlElement');
|
|
goog.require('shaka.util.EbmlParser');
|
|
goog.require('shaka.util.FailoverUri');
|
|
|
|
|
|
|
|
/**
|
|
* Creates a WebM Cues element parser.
|
|
*
|
|
* @constructor
|
|
* @struct
|
|
*/
|
|
shaka.media.WebmSegmentIndexParser = function() {};
|
|
|
|
|
|
/**
|
|
* Parses SegmentReferences from a WebM containeir.
|
|
*
|
|
* @param {!DataView} cuesData The WebM container's "Cueing Data" section.
|
|
* @param {!DataView} initData The WebM container's headers.
|
|
* @param {!Array.<!goog.Uri>} url The location of each SegmentReference.
|
|
* @param {shaka.util.FailoverUri.NetworkCallback} networkCallback
|
|
* @return {Array.<!shaka.media.SegmentReference>} SegmentReferences on success;
|
|
* otherwise, return null.
|
|
*/
|
|
shaka.media.WebmSegmentIndexParser.prototype.parse = function(
|
|
cuesData, initData, url, networkCallback) {
|
|
var references = null;
|
|
|
|
try {
|
|
references = this.parseInternal_(cuesData, initData, url, networkCallback);
|
|
} catch (exception) {
|
|
if (!(exception instanceof RangeError)) {
|
|
throw exception;
|
|
}
|
|
}
|
|
|
|
return references;
|
|
};
|
|
|
|
|
|
/** @const {number} */
|
|
shaka.media.WebmSegmentIndexParser.EBML_ID = 0x1a45dfa3;
|
|
|
|
|
|
/** @const {number} */
|
|
shaka.media.WebmSegmentIndexParser.SEGMENT_ID = 0x18538067;
|
|
|
|
|
|
/** @const {number} */
|
|
shaka.media.WebmSegmentIndexParser.INFO_ID = 0x1549a966;
|
|
|
|
|
|
/** @const {number} */
|
|
shaka.media.WebmSegmentIndexParser.TIMECODE_SCALE_ID = 0x2ad7b1;
|
|
|
|
|
|
/** @const {number} */
|
|
shaka.media.WebmSegmentIndexParser.CUES_ID = 0x1c53bb6b;
|
|
|
|
|
|
/** @const {number} */
|
|
shaka.media.WebmSegmentIndexParser.CUE_POINT_ID = 0xbb;
|
|
|
|
|
|
/** @const {number} */
|
|
shaka.media.WebmSegmentIndexParser.CUE_TIME_ID = 0xb3;
|
|
|
|
|
|
/** @const {number} */
|
|
shaka.media.WebmSegmentIndexParser.CUE_TRACK_POSITIONS_ID = 0xb7;
|
|
|
|
|
|
/** @const {number} */
|
|
shaka.media.WebmSegmentIndexParser.CUE_CLUSTER_POSITION = 0xf1;
|
|
|
|
|
|
/**
|
|
* Parses SegmentReferences from a WebM container.
|
|
* @param {!DataView} cuesData
|
|
* @param {!DataView} initData
|
|
* @return {Array.<!shaka.media.SegmentReference>} SegmentReferences on success;
|
|
* otherwise, return null.
|
|
* @param {!Array.<!goog.Uri>} url The location of each SegmentReference.
|
|
* @param {shaka.util.FailoverUri.NetworkCallback} networkCallback
|
|
* @throws {RangeError}
|
|
* @see http://www.matroska.org/technical/specs/index.html
|
|
* @see http://www.webmproject.org/docs/container/
|
|
* @private
|
|
*/
|
|
shaka.media.WebmSegmentIndexParser.prototype.parseInternal_ = function(
|
|
cuesData, initData, url, networkCallback) {
|
|
var tuple = this.parseWebmContainer_(initData);
|
|
if (!tuple) {
|
|
return null;
|
|
}
|
|
|
|
var parser = new shaka.util.EbmlParser(cuesData);
|
|
var cuesElement = parser.parseElement();
|
|
if (cuesElement.id != shaka.media.WebmSegmentIndexParser.CUES_ID) {
|
|
shaka.log.error('CuesElement does not exist.');
|
|
return null;
|
|
}
|
|
|
|
return this.parseCues_(
|
|
cuesElement, tuple.segmentOffset, tuple.timecodeScale, url,
|
|
networkCallback);
|
|
};
|
|
|
|
|
|
/**
|
|
* Parses a WebM container to get the segment's offset and timecode scale.
|
|
* @param {!DataView} initData
|
|
* @return {?{segmentOffset: number, timecodeScale: number}} The segment's
|
|
* offset in bytes, and the segment's timecode scale in seconds.
|
|
* @private
|
|
*/
|
|
shaka.media.WebmSegmentIndexParser.prototype.parseWebmContainer_ = function(
|
|
initData) {
|
|
var parser = new shaka.util.EbmlParser(initData);
|
|
|
|
// Check that the WebM container data starts with the EBML header, but
|
|
// skip its contents.
|
|
var ebmlElement = parser.parseElement();
|
|
if (ebmlElement.id != shaka.media.WebmSegmentIndexParser.EBML_ID) {
|
|
shaka.log.error('EBML element does not exist.');
|
|
return null;
|
|
}
|
|
|
|
var segmentElement = parser.parseElement();
|
|
if (segmentElement.id != shaka.media.WebmSegmentIndexParser.SEGMENT_ID) {
|
|
shaka.log.error('Segment element does not exist.');
|
|
return null;
|
|
}
|
|
|
|
// This value is used as the initial offset to the first referenced segment.
|
|
var segmentOffset = segmentElement.getOffset();
|
|
|
|
// Parse the Segment element to get the segment's timecode scale.
|
|
var timecodeScale = this.parseSegment_(segmentElement);
|
|
if (!timecodeScale) {
|
|
return null;
|
|
}
|
|
|
|
return { segmentOffset: segmentOffset, timecodeScale: timecodeScale };
|
|
};
|
|
|
|
|
|
/**
|
|
* Parses a WebM Info element to get the segment's timecode scale.
|
|
* @param {!shaka.util.EbmlElement} segmentElement
|
|
* @return {?number} The segment's timecode scale in seconds, or null if an
|
|
* error occurred.
|
|
* @private
|
|
*/
|
|
shaka.media.WebmSegmentIndexParser.prototype.parseSegment_ = function(
|
|
segmentElement) {
|
|
var parser = segmentElement.createParser();
|
|
|
|
// Find the Info element.
|
|
var infoElement = null;
|
|
while (parser.hasMoreData()) {
|
|
var elem = parser.parseElement();
|
|
if (elem.id != shaka.media.WebmSegmentIndexParser.INFO_ID) {
|
|
continue;
|
|
}
|
|
|
|
infoElement = elem;
|
|
|
|
break;
|
|
}
|
|
|
|
if (!infoElement) {
|
|
shaka.log.error('Info element does not exist.');
|
|
return null;
|
|
}
|
|
|
|
return this.parseInfo_(infoElement);
|
|
};
|
|
|
|
|
|
/**
|
|
* Parses a WebM Info element to get the segment's timecode scale.
|
|
* @param {!shaka.util.EbmlElement} infoElement
|
|
* @return {number} The segment's timecode scale in seconds.
|
|
* @private
|
|
*/
|
|
shaka.media.WebmSegmentIndexParser.prototype.parseInfo_ = function(
|
|
infoElement) {
|
|
var parser = infoElement.createParser();
|
|
|
|
// The timecode scale factor in units of [nanoseconds / T], where [T] are the
|
|
// units used to express all other time values in the WebM container. By
|
|
// default it's assumed that [T] == [milliseconds].
|
|
var timecodeScaleNanoseconds = 1000000;
|
|
|
|
while (parser.hasMoreData()) {
|
|
var elem = parser.parseElement();
|
|
if (elem.id != shaka.media.WebmSegmentIndexParser.TIMECODE_SCALE_ID) {
|
|
continue;
|
|
}
|
|
|
|
timecodeScaleNanoseconds = elem.getUint();
|
|
|
|
break;
|
|
}
|
|
|
|
// The timecode scale factor in units of [seconds / T].
|
|
var timecodeScale = timecodeScaleNanoseconds / 1000000000;
|
|
|
|
return timecodeScale;
|
|
};
|
|
|
|
|
|
/**
|
|
* Parses a WebM CuesElement.
|
|
* @param {!shaka.util.EbmlElement} cuesElement
|
|
* @param {number} segmentOffset
|
|
* @param {number} timecodeScale
|
|
* @param {!Array.<!goog.Uri>} url
|
|
* @param {shaka.util.FailoverUri.NetworkCallback} networkCallback
|
|
* @return {!Array.<!shaka.media.SegmentReference>}
|
|
* @private
|
|
*/
|
|
shaka.media.WebmSegmentIndexParser.prototype.parseCues_ = function(
|
|
cuesElement, segmentOffset, timecodeScale, url, networkCallback) {
|
|
var references = [];
|
|
|
|
var parser = cuesElement.createParser();
|
|
|
|
var lastTime = -1;
|
|
var lastOffset = -1;
|
|
|
|
while (parser.hasMoreData()) {
|
|
var elem = parser.parseElement();
|
|
if (elem.id != shaka.media.WebmSegmentIndexParser.CUE_POINT_ID) {
|
|
continue;
|
|
}
|
|
|
|
var tuple = this.parseCuePoint_(elem);
|
|
if (!tuple) {
|
|
continue;
|
|
}
|
|
|
|
var currentTime = timecodeScale * tuple.unscaledTime;
|
|
var currentOffset = segmentOffset + tuple.relativeOffset;
|
|
|
|
if (lastTime >= 0) {
|
|
shaka.asserts.assert(lastOffset >= 0);
|
|
|
|
var failover = new shaka.util.FailoverUri(
|
|
networkCallback, url, lastOffset, currentOffset - 1);
|
|
references.push(
|
|
new shaka.media.SegmentReference(lastTime, currentTime, failover));
|
|
}
|
|
|
|
lastTime = currentTime;
|
|
lastOffset = currentOffset;
|
|
}
|
|
|
|
if (lastTime >= 0) {
|
|
shaka.asserts.assert(lastOffset >= 0);
|
|
|
|
var failover = new shaka.util.FailoverUri(networkCallback, url,
|
|
lastOffset, null);
|
|
references.push(
|
|
new shaka.media.SegmentReference(lastTime, null, failover));
|
|
}
|
|
|
|
return references;
|
|
};
|
|
|
|
|
|
/**
|
|
* Parses a WebM CuePointElement to get an "unadjusted" segment reference.
|
|
* @param {shaka.util.EbmlElement} cuePointElement
|
|
* @return {?{unscaledTime: number, relativeOffset: number}} The referenced
|
|
* segment's start time in units of [T] (see parseInfo_()), and the
|
|
* referenced segment's offset in bytes, relative to a WebM Segment
|
|
* element.
|
|
* @private
|
|
*/
|
|
shaka.media.WebmSegmentIndexParser.prototype.parseCuePoint_ = function(
|
|
cuePointElement) {
|
|
var parser = cuePointElement.createParser();
|
|
|
|
// Parse CueTime element.
|
|
var cueTimeElement = parser.parseElement();
|
|
if (cueTimeElement.id != shaka.media.WebmSegmentIndexParser.CUE_TIME_ID) {
|
|
shaka.log.warning('CueTime element does not exist.');
|
|
return null;
|
|
}
|
|
var unscaledTime = cueTimeElement.getUint();
|
|
|
|
// Parse CueTrackPositions element.
|
|
var cueTrackPositionsElement = parser.parseElement();
|
|
if (cueTrackPositionsElement.id !=
|
|
shaka.media.WebmSegmentIndexParser.CUE_TRACK_POSITIONS_ID) {
|
|
shaka.log.warning('CueTrackPositions element does not exist.');
|
|
return null;
|
|
}
|
|
|
|
var cueTrackParser = cueTrackPositionsElement.createParser();
|
|
var relativeOffset = 0;
|
|
|
|
while (cueTrackParser.hasMoreData()) {
|
|
var elem = cueTrackParser.parseElement();
|
|
if (elem.id != shaka.media.WebmSegmentIndexParser.CUE_CLUSTER_POSITION) {
|
|
continue;
|
|
}
|
|
|
|
relativeOffset = elem.getUint();
|
|
|
|
break;
|
|
}
|
|
|
|
return { unscaledTime: unscaledTime, relativeOffset: relativeOffset };
|
|
};
|
|
|