You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
441 lines
15 KiB
JavaScript
441 lines
15 KiB
JavaScript
10 years ago
|
var MediaFragments = (function(window) {
|
||
|
|
||
|
// "use strict";
|
||
|
|
||
|
if (!Array.prototype.forEach) {
|
||
|
Array.prototype.forEach = function(fun /*, thisp */) {
|
||
|
"use strict";
|
||
|
if (this === void 0 || this === null) {
|
||
|
throw new TypeError();
|
||
|
}
|
||
|
var t = Object(this);
|
||
|
var len = t.length >>> 0;
|
||
|
if (typeof fun !== "function") {
|
||
|
throw new TypeError();
|
||
|
}
|
||
|
var thisp = arguments[1];
|
||
|
for (var i = 0; i < len; i++) {
|
||
|
if (i in t) {
|
||
|
fun.call(thisp, t[i], i, t);
|
||
|
}
|
||
|
}
|
||
|
};
|
||
|
}
|
||
|
|
||
|
// '&' is the only primary separator for key-value pairs
|
||
|
var SEPARATOR = '&';
|
||
|
|
||
|
// report errors?
|
||
|
var VERBOSE = true;
|
||
|
|
||
|
var logWarning = function(message) {
|
||
|
if (VERBOSE) {
|
||
|
console.log('Media Fragments URI Parsing Warning: ' + message);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// the currently supported media fragments dimensions are: t, xywh, track, id
|
||
|
// allows for O(1) checks for existence of valid keys
|
||
|
var dimensions = {
|
||
|
t: function(value) {
|
||
|
var components = value.split(',');
|
||
|
if (components.length > 2) {
|
||
|
return false;
|
||
|
}
|
||
|
var start = components[0]? components[0] : '';
|
||
|
var end = components[1]? components[1] : '';
|
||
|
if ((start === '' && end === '') ||
|
||
|
(start && !end && value.indexOf(',') !== -1)) {
|
||
|
return false;
|
||
|
}
|
||
|
// hours:minutes:seconds.milliseconds
|
||
|
var npt = /^((npt\:)?((\d+\:(\d\d)\:(\d\d))|((\d\d)\:(\d\d))|(\d+))(\.\d*)?)?$/;
|
||
|
if ((npt.test(start)) &&
|
||
|
(npt.test(end))) {
|
||
|
start = start.replace(/^npt\:/, '');
|
||
|
// replace a sole trailing dot, which is legal:
|
||
|
// npt-sec = 1*DIGIT [ "." *DIGIT ]
|
||
|
start = start.replace(/\.$/, '');
|
||
|
end = end.replace(/\.$/, '');
|
||
|
var convertToSeconds = function(time) {
|
||
|
if (time === '') {
|
||
|
return false;
|
||
|
}
|
||
|
// possible cases:
|
||
|
// 12:34:56.789
|
||
|
// 34:56.789
|
||
|
// 56.789
|
||
|
// 56
|
||
|
var hours;
|
||
|
var minutes;
|
||
|
var seconds;
|
||
|
time = time.split(':');
|
||
|
var length = time.length;
|
||
|
if (length === 3) {
|
||
|
hours = parseInt(time[0], 10);
|
||
|
minutes = parseInt(time[1], 10);
|
||
|
seconds = parseFloat(time[2]);
|
||
|
} else if (length === 2) {
|
||
|
var hours = 0;
|
||
|
var minutes = parseInt(time[0], 10);
|
||
|
var seconds = parseFloat(time[1]);
|
||
|
} else if (length === 1) {
|
||
|
var hours = 0;
|
||
|
var minutes = 0;
|
||
|
var seconds = parseFloat(time[0]);
|
||
|
} else {
|
||
|
return false;
|
||
|
}
|
||
|
if (hours > 23) {
|
||
|
logWarning('Please ensure that hours <= 23.');
|
||
|
return false;
|
||
|
}
|
||
|
if (minutes > 59) {
|
||
|
logWarning('Please ensure that minutes <= 59.');
|
||
|
return false;
|
||
|
}
|
||
|
if (seconds >= 60) {
|
||
|
logWarning('Please ensure that seconds < 60.');
|
||
|
return false;
|
||
|
}
|
||
|
return hours * 3600 + minutes * 60 + seconds;
|
||
|
};
|
||
|
var startNormalized = convertToSeconds(start);
|
||
|
var endNormalized = convertToSeconds(end);
|
||
|
if (start && end) {
|
||
|
if (startNormalized < endNormalized) {
|
||
|
return {
|
||
|
value: value,
|
||
|
unit: 'npt',
|
||
|
start: start,
|
||
|
end: end,
|
||
|
startNormalized: startNormalized,
|
||
|
endNormalized: endNormalized
|
||
|
};
|
||
|
} else {
|
||
|
logWarning('Please ensure that start < end.');
|
||
|
return false;
|
||
|
}
|
||
|
} else {
|
||
|
if ((convertToSeconds(start) !== false) ||
|
||
|
(convertToSeconds(end) !== false)) {
|
||
|
return {
|
||
|
value: value,
|
||
|
unit: 'npt',
|
||
|
start: start,
|
||
|
end: end,
|
||
|
startNormalized: startNormalized === false ? '' : startNormalized,
|
||
|
endNormalized: endNormalized === false ? '' : endNormalized,
|
||
|
};
|
||
|
} else {
|
||
|
logWarning('Please ensure that start or end are legal.');
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
// hours:minutes:seconds:frames.further-subdivison-of-frames
|
||
|
var smpte = /^(\d+\:\d\d\:\d\d(\:\d\d(\.\d\d)?)?)?$/;
|
||
|
var prefix = start.replace(/^(smpte(-25|-30|-30-drop)?).*/, '$1');
|
||
|
start = start.replace(/^smpte(-25|-30|-30-drop)?\:/, '');
|
||
|
if ((smpte.test(start)) && (smpte.test(end))) {
|
||
|
// we interpret frames as milliseconds, and further-subdivison-of-frames
|
||
|
// as microseconds. this allows for relatively easy comparison.
|
||
|
var convertToSeconds = function(time) {
|
||
|
if (time === '') {
|
||
|
return false;
|
||
|
}
|
||
|
// possible cases:
|
||
|
// 12:34:56
|
||
|
// 12:34:56:78
|
||
|
// 12:34:56:78.90
|
||
|
var hours;
|
||
|
var minutes;
|
||
|
var seconds;
|
||
|
var frames;
|
||
|
var subframes;
|
||
|
time = time.split(':');
|
||
|
var length = time.length;
|
||
|
if (length === 3) {
|
||
|
hours = parseInt(time[0], 10);
|
||
|
minutes = parseInt(time[1], 10);
|
||
|
seconds = parseInt(time[2], 10);
|
||
|
frames = 0;
|
||
|
subframes = 0;
|
||
|
} else if (length === 4) {
|
||
|
hours = parseInt(time[0], 10);
|
||
|
minutes = parseInt(time[1], 10);
|
||
|
seconds = parseInt(time[2], 10);
|
||
|
if (time[3].indexOf('.') === -1) {
|
||
|
frames = parseInt(time[3], 10);
|
||
|
subframes = 0;
|
||
|
} else {
|
||
|
var frameSubFrame = time[3].split('.');
|
||
|
frames = parseInt(frameSubFrame[0], 10);
|
||
|
subframes = parseInt(frameSubFrame[1], 10);
|
||
|
}
|
||
|
} else {
|
||
|
return false;
|
||
|
}
|
||
|
if (hours > 23) {
|
||
|
logWarning('Please ensure that hours <= 23.');
|
||
|
return false;
|
||
|
}
|
||
|
if (minutes > 59) {
|
||
|
logWarning('Please ensure that minutes <= 59.');
|
||
|
return false;
|
||
|
}
|
||
|
if (seconds > 59) {
|
||
|
logWarning('Please ensure that seconds <= 59.');
|
||
|
return false;
|
||
|
}
|
||
|
return hours * 3600 + minutes * 60 + seconds +
|
||
|
frames * 0.001 + subframes * 0.000001;
|
||
|
};
|
||
|
if (start && end) {
|
||
|
if (convertToSeconds(start) < convertToSeconds(end)) {
|
||
|
return {
|
||
|
value: value,
|
||
|
unit: prefix,
|
||
|
start: start,
|
||
|
end: end
|
||
|
};
|
||
|
} else {
|
||
|
logWarning('Please ensure that start < end.');
|
||
|
return false;
|
||
|
}
|
||
|
} else {
|
||
|
if ((convertToSeconds(start) !== false) ||
|
||
|
(convertToSeconds(end) !== false)) {
|
||
|
return {
|
||
|
value: value,
|
||
|
unit: prefix,
|
||
|
start: start,
|
||
|
end: end
|
||
|
};
|
||
|
} else {
|
||
|
logWarning('Please ensure that start or end are legal.');
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
// regexp adapted from http://delete.me.uk/2005/03/iso8601.html
|
||
|
var wallClock = /^((\d{4})(-(\d{2})(-(\d{2})(T(\d{2})\:(\d{2})(\:(\d{2})(\.(\d+))?)?(Z|(([-\+])(\d{2})\:(\d{2})))?)?)?)?)?$/;
|
||
|
start = start.replace('clock:', '');
|
||
|
if ((wallClock.test(start)) && (wallClock.test(end))) {
|
||
|
// the last condition is to ensure ISO 8601 date conformance.
|
||
|
// not all browsers parse ISO 8601, so we can only use date parsing
|
||
|
// when it's there.
|
||
|
if (start && end && !isNaN(Date.parse('2009-07-26T11:19:01Z'))) {
|
||
|
// if both start and end are given, then the start must be before
|
||
|
// the end
|
||
|
if (Date.parse(start) <= Date.parse(end)) {
|
||
|
return {
|
||
|
value: value,
|
||
|
unit: 'clock',
|
||
|
start: start,
|
||
|
end: end
|
||
|
};
|
||
|
} else {
|
||
|
logWarning('Please ensure that start < end.');
|
||
|
return false;
|
||
|
}
|
||
|
} else {
|
||
|
return {
|
||
|
value: value,
|
||
|
unit: 'clock',
|
||
|
start: start,
|
||
|
end: end
|
||
|
};
|
||
|
}
|
||
|
}
|
||
|
logWarning('Invalid time dimension.');
|
||
|
return false;
|
||
|
},
|
||
|
xywh: function(value) {
|
||
|
// "pixel:" is optional
|
||
|
var pixelCoordinates = /^(pixel\:)?\d+,\d+,\d+,\d+$/;
|
||
|
// "percent:" is obligatory
|
||
|
var percentSelection = /^percent\:\d+,\d+,\d+,\d+$/;
|
||
|
|
||
|
var values = value.replace(/(pixel|percent)\:/, '').split(',');
|
||
|
var x = values[0];
|
||
|
var y = values[1];
|
||
|
var w = values[2];
|
||
|
var h = values[3];
|
||
|
if (pixelCoordinates.test(value)) {
|
||
|
if (w > 0 && h > 0) {
|
||
|
return {
|
||
|
value: value,
|
||
|
unit: 'pixel',
|
||
|
x: x,
|
||
|
y: y,
|
||
|
w: w,
|
||
|
h: h
|
||
|
};
|
||
|
} else {
|
||
|
logWarning('Please ensure that w > 0 and h > 0');
|
||
|
return false;
|
||
|
}
|
||
|
} else if (percentSelection.test(value)) {
|
||
|
/**
|
||
|
* checks for valid percent selections
|
||
|
*/
|
||
|
var checkPercentSelection = (function checkPercentSelection(
|
||
|
x, y, w, h) {
|
||
|
if (!((0 <= x) && (x <= 100))) {
|
||
|
logWarning('Please ensure that 0 <= x <= 100.');
|
||
|
return false;
|
||
|
}
|
||
|
if (!((0 <= y) && (y <= 100))) {
|
||
|
logWarning('Please ensure that 0 <= y <= 100.');
|
||
|
return false;
|
||
|
}
|
||
|
if (!((0 <= w) && (w <= 100))) {
|
||
|
logWarning('Please ensure that 0 <= w <= 100.');
|
||
|
return false;
|
||
|
}
|
||
|
if (!((0 <= h) && (h <= 100))) {
|
||
|
logWarning('Please ensure that 0 <= h <= 100.');
|
||
|
return false;
|
||
|
}
|
||
|
return true;
|
||
|
});
|
||
|
if (checkPercentSelection(x, y, w, h)) {
|
||
|
return {
|
||
|
value: value,
|
||
|
unit: 'percent',
|
||
|
x: x,
|
||
|
y: y,
|
||
|
w: w,
|
||
|
h: h
|
||
|
};
|
||
|
}
|
||
|
logWarning('Invalid percent selection.');
|
||
|
return false;
|
||
|
} else {
|
||
|
logWarning('Invalid spatial dimension.');
|
||
|
return false;
|
||
|
}
|
||
|
},
|
||
|
track: function(value) {
|
||
|
return {
|
||
|
value: value,
|
||
|
name: value
|
||
|
};
|
||
|
},
|
||
|
id: function(value) {
|
||
|
return {
|
||
|
value: value,
|
||
|
name: value
|
||
|
};
|
||
|
},
|
||
|
chapter: function(value) {
|
||
|
return {
|
||
|
value: value,
|
||
|
chapter: value
|
||
|
};
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* splits an octet string into allowed key-value pairs
|
||
|
*/
|
||
|
var splitKeyValuePairs = function(octetString) {
|
||
|
var keyValues = {};
|
||
|
var keyValuePairs = octetString.split(SEPARATOR);
|
||
|
keyValuePairs.forEach(function(keyValuePair) {
|
||
|
// the key part is up to the first(!) occurrence of '=', further '='-s
|
||
|
// form part of the value
|
||
|
var position = keyValuePair.indexOf('=');
|
||
|
if (position < 1) {
|
||
|
return;
|
||
|
}
|
||
|
var components = [
|
||
|
keyValuePair.substring(0, position),
|
||
|
keyValuePair.substring(position + 1)];
|
||
|
// we require a value for each key
|
||
|
if (!components[1]) {
|
||
|
return;
|
||
|
}
|
||
|
// the key name needs to be decoded
|
||
|
var key = decodeURIComponent(components[0]);
|
||
|
// only allow keys that are currently supported media fragments dimensions
|
||
|
var dimensionChecker = dimensions[key];
|
||
|
// the value needs to be decoded
|
||
|
var value = decodeURIComponent(components[1]);
|
||
|
if (dimensionChecker) {
|
||
|
value = dimensionChecker(value);
|
||
|
} else {
|
||
|
// we had a key that is not part of media fragments
|
||
|
return;
|
||
|
}
|
||
|
if (!value) {
|
||
|
return;
|
||
|
}
|
||
|
// keys may appear more than once, thus store all values in an array,
|
||
|
// the exception being &t
|
||
|
if (!keyValues[key]) {
|
||
|
keyValues[key] = [];
|
||
|
}
|
||
|
if (key !== 't') {
|
||
|
keyValues[key].push(value);
|
||
|
} else {
|
||
|
keyValues[key][0] = value;
|
||
|
}
|
||
|
});
|
||
|
return keyValues;
|
||
|
}
|
||
|
|
||
|
return {
|
||
|
parse: function(opt_uri) {
|
||
|
return MediaFragments.parseMediaFragmentsUri(opt_uri);
|
||
|
},
|
||
|
parseMediaFragmentsUri: function(opt_uri) {
|
||
|
var uri = opt_uri? opt_uri : window.location.href;
|
||
|
// retrieve the query part of the URI
|
||
|
var indexOfHash = uri.indexOf('#');
|
||
|
var indexOfQuestionMark = uri.indexOf('?');
|
||
|
var end = (indexOfHash !== -1? indexOfHash : uri.length);
|
||
|
var query = indexOfQuestionMark !== -1?
|
||
|
uri.substring(indexOfQuestionMark + 1, end) : '';
|
||
|
// retrieve the hash part of the URI
|
||
|
var hash = indexOfHash !== -1? uri.substring(indexOfHash + 1) : '';
|
||
|
var queryValues = splitKeyValuePairs(query);
|
||
|
var hashValues = splitKeyValuePairs(hash);
|
||
|
return {
|
||
|
query: queryValues,
|
||
|
hash: hashValues,
|
||
|
toString: function() {
|
||
|
var buildString = function(name, thing) {
|
||
|
var s = '\n[' + name + ']:\n';
|
||
|
if(!Object.keys) Object.keys = function(o){
|
||
|
if (o !== Object(o)) {
|
||
|
throw new TypeError('Object.keys called on non-object');
|
||
|
}
|
||
|
var ret = [], p;
|
||
|
for (p in o) {
|
||
|
if (Object.prototype.hasOwnProperty.call(o,p)) ret.push(p);
|
||
|
}
|
||
|
return ret;
|
||
|
}
|
||
|
Object.keys(thing).forEach(function(key) {
|
||
|
s += ' * ' + key + ':\n';
|
||
|
thing[key].forEach(function(value) {
|
||
|
s += ' [\n';
|
||
|
Object.keys(value).forEach(function(valueKey) {
|
||
|
s += ' - ' + valueKey + ': ' + value[valueKey] + '\n';
|
||
|
});
|
||
|
s += ' ]\n';
|
||
|
});
|
||
|
});
|
||
|
return s;
|
||
|
}
|
||
|
var string =
|
||
|
buildString('Query', queryValues) +
|
||
|
buildString('Hash', hashValues);
|
||
|
return string;
|
||
|
}
|
||
|
};
|
||
|
}
|
||
|
}
|
||
|
})(window);
|