Skip to content

Commit

Permalink
Fix: shaka-project#1667 Javascript's number overflow for timescale 10…
Browse files Browse the repository at this point in the history
…000000
  • Loading branch information
Artem Gelun committed Nov 15, 2018
1 parent 1831ce9 commit f37578b
Show file tree
Hide file tree
Showing 6 changed files with 135 additions and 34 deletions.
1 change: 1 addition & 0 deletions demo/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
<script defer src="//www.gstatic.com/cv/js/sender/v1/cast_sender.js"></script>
<!-- transmuxing support is enabled by including this: -->
<script defer src="../node_modules/mux.js/dist/mux.js"></script>
<script defer src="../node_modules/big-integer/BigInteger.js"></script>

<script>
COMPILED_JS = [
Expand Down
77 changes: 77 additions & 0 deletions externs/bigInt.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
/**
* @license
* Copyright 2016 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.
*/

/**
* @fileoverview Externs for big-integer.js library.
* @externs
*/

/**
* @constructor
* @struct
* @param {Object=} options
*/
var BigInteger = function(options) {};

/**
* @param {number|BigInteger|string} v
* @return {BigInteger}
*/
BigInteger.prototype.minus = function(v) {};

/**
* @param {number|BigInteger|string} v
* @return {BigInteger}
*/
BigInteger.prototype.plus = function(v) {};

/**
* @param {number|BigInteger|string} v
* @return {BigInteger}
*/
BigInteger.prototype.divide = function(v) {};

/**
* @param {number|BigInteger|string} v
* @return {BigInteger}
*/
BigInteger.prototype.multiply = function(v) {};

/**
* @param {number|BigInteger|string} v
* @return {boolean}
*/
BigInteger.prototype.greater = function(v) {};

/**
* @param {number|BigInteger|string} v
* @return {boolean}
*/
BigInteger.prototype.neq = function(v) {};

/**
* @return {number}
*/
BigInteger.prototype.toJSNumber = function() {};

/**
* @param {number|string|BigInteger} options
* @return {BigInteger}
*/
var bigInt = function(options) {};


61 changes: 34 additions & 27 deletions lib/dash/mpd_utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ goog.require('shaka.util.XmlUtils');
/**
* @typedef {{
* start: number,
* unscaledStart: number,
* unscaledStart: BigInteger,
* end: number
* }}
*
Expand All @@ -46,7 +46,7 @@ goog.require('shaka.util.XmlUtils');
*
* @property {number} start
* The start time of the range.
* @property {number} unscaledStart
* @property {BigInteger} unscaledStart
* The start time of the range in representation timescale units.
* @property {number} end
* The end time (exclusive) of the range.
Expand All @@ -58,9 +58,10 @@ shaka.dash.MpdUtils.TimeRange;
* @typedef {{
* timescale: number,
* segmentDuration: ?number,
* unscaledSegmentDuration: BigInteger,
* startNumber: number,
* scaledPresentationTimeOffset: number,
* unscaledPresentationTimeOffset: number,
* unscaledPresentationTimeOffset: BigInteger,
* timeline: Array.<shaka.dash.MpdUtils.TimeRange>
* }}
*
Expand All @@ -71,11 +72,13 @@ shaka.dash.MpdUtils.TimeRange;
* The time-scale of the representation.
* @property {?number} segmentDuration
* The duration of the segments in seconds, if given.
* @property {?BigInteger} unscaledSegmentDuration
* The duration of the segments in timescale units, if given.
* @property {number} startNumber
* The start number of the segments; 1 or greater.
* @property {number} scaledPresentationTimeOffset
* The presentation time offset of the representation, in seconds.
* @property {number} unscaledPresentationTimeOffset
* @property {BigInteger} unscaledPresentationTimeOffset
* The presentation time offset of the representation, in timescale units.
* @property {Array.<shaka.dash.MpdUtils.TimeRange>} timeline
* The timeline of the representation, if given. Times in seconds.
Expand All @@ -98,7 +101,7 @@ shaka.dash.MpdUtils.XlinkNamespaceUri_ = 'http://www.w3.org/1999/xlink';
* @param {?string} representationId
* @param {?number} number
* @param {?number} bandwidth
* @param {?number} time
* @param {?BigInteger} time
* @return {string} A URI string.
* @see ISO/IEC 23009-1:2014 section 5.3.9.4.4
*/
Expand Down Expand Up @@ -138,13 +141,13 @@ shaka.dash.MpdUtils.fillUriTemplate = function(
uriTemplate);
widthString = undefined;
}

/*
if (name == 'Time') {
goog.asserts.assert(Math.abs(value - Math.round(value)) < 0.2,
'Calculated $Time$ values must be close to integers');
value = Math.round(value);
}

*/
/** @type {string} */
let valueString;
switch (format) {
Expand Down Expand Up @@ -187,7 +190,7 @@ shaka.dash.MpdUtils.fillUriTemplate = function(
*
* @param {!Element} segmentTimeline
* @param {number} timescale
* @param {number} unscaledPresentationTimeOffset
* @param {BigInteger} unscaledPresentationTimeOffset
* @param {number} periodDuration The Period's duration in seconds.
* Infinity indicates that the Period continues indefinitely.
* @return {!Array.<shaka.dash.MpdUtils.TimeRange>}
Expand All @@ -208,17 +211,17 @@ shaka.dash.MpdUtils.createTimeline =

/** @type {!Array.<shaka.dash.MpdUtils.TimeRange>} */
let timeline = [];
let lastEndTime = 0;
let lastEndTime = bigInt(0);

for (let i = 0; i < timePoints.length; ++i) {
let timePoint = timePoints[i];
let t = XmlUtils.parseAttr(timePoint, 't', XmlUtils.parseNonNegativeInt);
let d = XmlUtils.parseAttr(timePoint, 'd', XmlUtils.parseNonNegativeInt);
let t = XmlUtils.parseAttr(timePoint, 't', XmlUtils.parseBigInt);
let d = XmlUtils.parseAttr(timePoint, 'd', XmlUtils.parseBigInt);
let r = XmlUtils.parseAttr(timePoint, 'r', XmlUtils.parseInt);

// Adjust the start time to account for the presentation time offset.
if (t != null) {
t -= unscaledPresentationTimeOffset;
t = t.minus(unscaledPresentationTimeOffset);
}

if (!d) {
Expand All @@ -236,23 +239,23 @@ shaka.dash.MpdUtils.createTimeline =
if (i + 1 < timePoints.length) {
let nextTimePoint = timePoints[i + 1];
let nextStartTime = XmlUtils.parseAttr(
nextTimePoint, 't', XmlUtils.parseNonNegativeInt);
nextTimePoint, 't', XmlUtils.parseBigInt);
if (nextStartTime == null) {
shaka.log.warning(
'An "S" element cannot have a negative repeat',
'if the next "S" element does not have a valid start time:',
'ignoring the remaining "S" elements.',
timePoint);
return timeline;
} else if (startTime >= nextStartTime) {
} else if (startTime.greater(nextStartTime)) {
shaka.log.warning(
'An "S" element cannot have a negative repeat',
'if its start time exceeds the next "S" element\'s start time:',
'ignoring the remaining "S" elements.',
timePoint);
return timeline;
}
repeat = Math.ceil((nextStartTime - startTime) / d) - 1;
repeat = nextStartTime.minus(startTime).divide(d).minus(1).toJSNumber();
} else {
if (periodDuration == Infinity) {
// The DASH spec. actually allows the last "S" element to have a
Expand All @@ -272,7 +275,8 @@ shaka.dash.MpdUtils.createTimeline =
timePoint);
return timeline;
}
repeat = Math.ceil((periodDuration * timescale - startTime) / d) - 1;
repeat = bigInt(periodDuration).multiply(timescale)
.minus(startTime).divide(d).minus(1).toJSNumber();
}
}

Expand All @@ -284,25 +288,26 @@ shaka.dash.MpdUtils.createTimeline =
// Note: it is possible to move the start of the current segment to the
// end of the last segment, but this would complicate the computation of
// the $Time$ placeholder later on.
if ((timeline.length > 0) && (startTime != lastEndTime)) {
let delta = startTime - lastEndTime;
if ((timeline.length > 0) && startTime.neq(lastEndTime)) {
let delta = startTime.minus(lastEndTime);

if (Math.abs(delta / timescale) >=
if (Math.abs(delta.divide(timescale).toJSNumber()) >=
shaka.util.ManifestParserUtils.GAP_OVERLAP_TOLERANCE_SECONDS) {
shaka.log.warning(
'SegmentTimeline contains a large gap/overlap:',
'the content may have errors in it.',
timePoint);
}

timeline[timeline.length - 1].end = startTime / timescale;
timeline[timeline.length - 1].end
= startTime.divide(timescale).toJSNumber();
}

for (let j = 0; j <= repeat; ++j) {
let endTime = startTime + d;
let endTime = startTime.plus(d);
let item = {
start: startTime / timescale,
end: endTime / timescale,
start: startTime.divide(timescale).toJSNumber(),
end: endTime.divide(timescale).toJSNumber(),
unscaledStart: startTime,
};
timeline.push(item);
Expand Down Expand Up @@ -338,16 +343,17 @@ shaka.dash.MpdUtils.parseSegmentInfo = function(context, callback) {
}

let durationStr = MpdUtils.inheritAttribute(context, callback, 'duration');
let segmentDuration = XmlUtils.parsePositiveInt(durationStr || '');
let unscaledSegmentDuration = XmlUtils.parseBigInt(durationStr || '');
let segmentDuration = null;
if (segmentDuration) {
segmentDuration /= timescale;
segmentDuration = unscaledSegmentDuration.divide(timescale).toJSNumber();
}

let startNumberStr =
MpdUtils.inheritAttribute(context, callback, 'startNumber');
let unscaledPresentationTimeOffset =
Number(MpdUtils.inheritAttribute(context, callback,
'presentationTimeOffset')) || 0;
bigInt(MpdUtils.inheritAttribute(context, callback,
'presentationTimeOffset')) || bigInt(0);
let startNumber = XmlUtils.parseNonNegativeInt(startNumberStr || '');
if (startNumberStr == null || startNumber == null) {
startNumber = 1;
Expand All @@ -368,6 +374,7 @@ shaka.dash.MpdUtils.parseSegmentInfo = function(context, callback) {
return {
timescale: timescale,
segmentDuration: segmentDuration,
unscaledSegmentDuration: unscaledSegmentDuration,
startNumber: startNumber,
scaledPresentationTimeOffset: scaledPresentationTimeOffset,
unscaledPresentationTimeOffset: unscaledPresentationTimeOffset,
Expand Down
15 changes: 9 additions & 6 deletions lib/dash/segment_template.js
Original file line number Diff line number Diff line change
Expand Up @@ -130,9 +130,10 @@ shaka.dash.SegmentTemplate.createStream = function(
* @typedef {{
* timescale: number,
* segmentDuration: ?number,
* unscaledSegmentDuration: ?BigInteger,
* startNumber: number,
* scaledPresentationTimeOffset: number,
* unscaledPresentationTimeOffset: number,
* unscaledPresentationTimeOffset: BigInteger,
* timeline: Array.<shaka.dash.MpdUtils.TimeRange>,
* mediaTemplate: ?string,
* indexTemplate: ?string
Expand All @@ -150,7 +151,7 @@ shaka.dash.SegmentTemplate.createStream = function(
* The start number of the segments; 1 or greater.
* @property {number} scaledPresentationTimeOffset
* The presentation time offset of the representation, in seconds.
* @property {number} unscaledPresentationTimeOffset
* @property {BigInteger} unscaledPresentationTimeOffset
* The presentation time offset of the representation, in timescale units.
* @property {Array.<shaka.dash.MpdUtils.TimeRange>} timeline
* The timeline of the representation, if given. Times in seconds.
Expand Down Expand Up @@ -192,6 +193,7 @@ shaka.dash.SegmentTemplate.parseSegmentTemplateInfo_ = function(context) {

return {
segmentDuration: segmentInfo.segmentDuration,
unscaledSegmentDuration: segmentInfo.unscaledSegmentDuration,
timescale: segmentInfo.timescale,
startNumber: segmentInfo.startNumber,
scaledPresentationTimeOffset: segmentInfo.scaledPresentationTimeOffset,
Expand Down Expand Up @@ -327,8 +329,8 @@ shaka.dash.SegmentTemplate.createFromDuration_ = function(context, info) {

let periodDuration = context.periodInfo.duration;
let segmentDuration = info.segmentDuration;
let unscaledSegmentDuration = info.unscaledSegmentDuration;
let startNumber = info.startNumber;
let timescale = info.timescale;

let template = info.mediaTemplate;
let bandwidth = context.bandwidth || null;
Expand All @@ -346,6 +348,7 @@ shaka.dash.SegmentTemplate.createFromDuration_ = function(context, info) {
};
let get = function(position) {
let segmentStart = position * segmentDuration;
let unscaledSegmentStart = unscaledSegmentDuration.multiply(position);
// Cap the segment end at the period end, to avoid period transition issues
// in StreamingEngine.
let segmentEnd = segmentStart + segmentDuration;
Expand All @@ -361,7 +364,7 @@ shaka.dash.SegmentTemplate.createFromDuration_ = function(context, info) {
let getUris = function() {
let mediaUri = MpdUtils.fillUriTemplate(
template, id, position + startNumber, bandwidth,
segmentStart * timescale);
unscaledSegmentStart);
return ManifestParserUtils.resolveUris(baseUris, [mediaUri]);
};

Expand Down Expand Up @@ -403,8 +406,8 @@ shaka.dash.SegmentTemplate.createFromTimeline_ = function(context, info) {
let segmentReplacement = i + info.startNumber;

// Consider the presentation time offset in segment uri computation
let timeReplacement = unscaledStart +
info.unscaledPresentationTimeOffset;
let timeReplacement = unscaledStart
.plus(info.unscaledPresentationTimeOffset);
let createUris = (function(
template, repId, bandwidth, baseUris, segmentId, time) {
let mediaUri = MpdUtils.fillUriTemplate(
Expand Down
12 changes: 12 additions & 0 deletions lib/util/xml_utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,18 @@ shaka.util.XmlUtils.parseInt = function(intString) {
return (n % 1 === 0) ? n : null;
};

/**
* Parses an bitInt value.
* @param {string} intString The integer string.
* @return {?BigInteger} The parsed integer on success; otherwise, return null.
*/
shaka.util.XmlUtils.parseBigInt = function(intString) {
if (intString == '') {
return null;
}
let n = bigInt(intString);
return (n % 1 === 0) ? n : null;
};

/**
* Parses a positive integer.
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,8 @@
"sprintf-js": "~1.0.3",
"useragent": "~2.1.13",
"wd": "^1.8.0 <1.11.0",
"which": "~1.3.0"
"which": "~1.3.0",
"big-integer": "~1.6.36"
},
"main": "dist/shaka-player.compiled.js",
"repository": {
Expand Down

0 comments on commit f37578b

Please sign in to comment.