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.

977 lines
30 KiB
JavaScript

module.exports = (function(){
'use strict';
function assert(condition, message) {
if (!condition) {
error(message);
}
};
/**
* Represents a 2-dimensional size value.
*/
var Size = (function size() {
function constructor(w, h) {
this.w = w;
this.h = h;
}
constructor.prototype = {
toString: function () {
return "(" + this.w + ", " + this.h + ")";
},
getHalfSize: function() {
return new Size(this.w >>> 1, this.h >>> 1);
},
length: function() {
return this.w * this.h;
}
};
return constructor;
})();
var Bytestream = (function BytestreamClosure() {
function constructor(arrayBuffer, start, length) {
this.bytes = new Uint8Array(arrayBuffer);
this.start = start || 0;
this.pos = this.start;
this.end = (start + length) || this.bytes.length;
}
constructor.prototype = {
get length() {
return this.end - this.start;
},
get position() {
return this.pos;
},
get remaining() {
return this.end - this.pos;
},
readU8Array: function (length) {
if (this.pos > this.end - length)
return null;
var res = this.bytes.subarray(this.pos, this.pos + length);
this.pos += length;
return res;
},
readU32Array: function (rows, cols, names) {
cols = cols || 1;
if (this.pos > this.end - (rows * cols) * 4)
return null;
if (cols == 1) {
var array = new Uint32Array(rows);
for (var i = 0; i < rows; i++) {
array[i] = this.readU32();
}
return array;
} else {
var array = new Array(rows);
for (var i = 0; i < rows; i++) {
var row = null;
if (names) {
row = {};
for (var j = 0; j < cols; j++) {
row[names[j]] = this.readU32();
}
} else {
row = new Uint32Array(cols);
for (var j = 0; j < cols; j++) {
row[j] = this.readU32();
}
}
array[i] = row;
}
return array;
}
},
read8: function () {
return this.readU8() << 24 >> 24;
},
readU8: function () {
if (this.pos >= this.end)
return null;
return this.bytes[this.pos++];
},
read16: function () {
return this.readU16() << 16 >> 16;
},
readU16: function () {
if (this.pos >= this.end - 1)
return null;
var res = this.bytes[this.pos + 0] << 8 | this.bytes[this.pos + 1];
this.pos += 2;
return res;
},
read24: function () {
return this.readU24() << 8 >> 8;
},
readU24: function () {
var pos = this.pos;
var bytes = this.bytes;
if (pos > this.end - 3)
return null;
var res = bytes[pos + 0] << 16 | bytes[pos + 1] << 8 | bytes[pos + 2];
this.pos += 3;
return res;
},
peek32: function (advance) {
var pos = this.pos;
var bytes = this.bytes;
if (pos > this.end - 4)
return null;
var res = bytes[pos + 0] << 24 | bytes[pos + 1] << 16 | bytes[pos + 2] << 8 | bytes[pos + 3];
if (advance) {
this.pos += 4;
}
return res;
},
read32: function () {
return this.peek32(true);
},
readU32: function () {
return this.peek32(true) >>> 0;
},
read4CC: function () {
var pos = this.pos;
if (pos > this.end - 4)
return null;
var res = "";
for (var i = 0; i < 4; i++) {
res += String.fromCharCode(this.bytes[pos + i]);
}
this.pos += 4;
return res;
},
readFP16: function () {
return this.read32() / 65536;
},
readFP8: function () {
return this.read16() / 256;
},
readISO639: function () {
var bits = this.readU16();
var res = "";
for (var i = 0; i < 3; i++) {
var c = (bits >>> (2 - i) * 5) & 0x1f;
res += String.fromCharCode(c + 0x60);
}
return res;
},
readUTF8: function (length) {
var res = "";
for (var i = 0; i < length; i++) {
res += String.fromCharCode(this.readU8());
}
return res;
},
readPString: function (max) {
var len = this.readU8();
assert (len <= max);
var res = this.readUTF8(len);
this.reserved(max - len - 1, 0);
return res;
},
skip: function (length) {
this.seek(this.pos + length);
},
reserved: function (length, value) {
for (var i = 0; i < length; i++) {
assert (this.readU8() == value);
}
},
seek: function (index) {
if (index < 0 || index > this.end) {
error("Index out of bounds (bounds: [0, " + this.end + "], index: " + index + ").");
}
this.pos = index;
},
subStream: function (start, length) {
return new Bytestream(this.bytes.buffer, start, length);
}
};
return constructor;
})();
var PARANOID = true; // Heavy-weight assertions.
/**
* Reads an mp4 file and constructs a object graph that corresponds to the box/atom
* structure of the file. Mp4 files are based on the ISO Base Media format, which in
* turn is based on the Apple Quicktime format. The Quicktime spec is available at:
* http://developer.apple.com/library/mac/#documentation/QuickTime/QTFF. An mp4 spec
* also exists, but I cannot find it freely available.
*
* Mp4 files contain a tree of boxes (or atoms in Quicktime). The general structure
* is as follows (in a pseudo regex syntax):
*
* Box / Atom Structure:
*
* [size type [version flags] field* box*]
* <32> <4C> <--8--> <24-> <-?-> <?>
* <------------- box size ------------>
*
* The box size indicates the entire size of the box and its children, we can use it
* to skip over boxes that are of no interest. Each box has a type indicated by a
* four character code (4C), this describes how the box should be parsed and is also
* used as an object key name in the resulting box tree. For example, the expression:
* "moov.trak[0].mdia.minf" can be used to access individual boxes in the tree based
* on their 4C name. If two or more boxes with the same 4C name exist in a box, then
* an array is built with that name.
*
*/
var MP4Reader = (function reader() {
var BOX_HEADER_SIZE = 8;
var FULL_BOX_HEADER_SIZE = BOX_HEADER_SIZE + 4;
function constructor(stream) {
this.stream = stream;
this.tracks = {};
}
constructor.prototype = {
readBoxes: function (stream, parent) {
while (stream.peek32()) {
var child = this.readBox(stream);
if (child.type in parent) {
var old = parent[child.type];
if (!(old instanceof Array)) {
parent[child.type] = [old];
}
parent[child.type].push(child);
} else {
parent[child.type] = child;
}
}
},
readBox: function readBox(stream) {
var box = { offset: stream.position };
function readHeader() {
box.size = stream.readU32();
box.type = stream.read4CC();
}
function readFullHeader() {
box.version = stream.readU8();
box.flags = stream.readU24();
}
function remainingBytes() {
return box.size - (stream.position - box.offset);
}
function skipRemainingBytes () {
stream.skip(remainingBytes());
}
var readRemainingBoxes = function () {
var subStream = stream.subStream(stream.position, remainingBytes());
this.readBoxes(subStream, box);
stream.skip(subStream.length);
}.bind(this);
readHeader();
switch (box.type) {
case 'ftyp':
box.name = "File Type Box";
box.majorBrand = stream.read4CC();
box.minorVersion = stream.readU32();
box.compatibleBrands = new Array((box.size - 16) / 4);
for (var i = 0; i < box.compatibleBrands.length; i++) {
box.compatibleBrands[i] = stream.read4CC();
}
break;
case 'moov':
box.name = "Movie Box";
readRemainingBoxes();
break;
case 'mvhd':
box.name = "Movie Header Box";
readFullHeader();
assert (box.version == 0);
box.creationTime = stream.readU32();
box.modificationTime = stream.readU32();
box.timeScale = stream.readU32();
box.duration = stream.readU32();
box.rate = stream.readFP16();
box.volume = stream.readFP8();
stream.skip(10);
box.matrix = stream.readU32Array(9);
stream.skip(6 * 4);
box.nextTrackId = stream.readU32();
break;
case 'trak':
box.name = "Track Box";
readRemainingBoxes();
this.tracks[box.tkhd.trackId] = new Track(this, box);
break;
case 'tkhd':
box.name = "Track Header Box";
readFullHeader();
assert (box.version == 0);
box.creationTime = stream.readU32();
box.modificationTime = stream.readU32();
box.trackId = stream.readU32();
stream.skip(4);
box.duration = stream.readU32();
stream.skip(8);
box.layer = stream.readU16();
box.alternateGroup = stream.readU16();
box.volume = stream.readFP8();
stream.skip(2);
box.matrix = stream.readU32Array(9);
box.width = stream.readFP16();
box.height = stream.readFP16();
break;
case 'mdia':
box.name = "Media Box";
readRemainingBoxes();
break;
case 'mdhd':
box.name = "Media Header Box";
readFullHeader();
assert (box.version == 0);
box.creationTime = stream.readU32();
box.modificationTime = stream.readU32();
box.timeScale = stream.readU32();
box.duration = stream.readU32();
box.language = stream.readISO639();
stream.skip(2);
break;
case 'hdlr':
box.name = "Handler Reference Box";
readFullHeader();
stream.skip(4);
box.handlerType = stream.read4CC();
stream.skip(4 * 3);
var bytesLeft = box.size - 32;
if (bytesLeft > 0) {
box.name = stream.readUTF8(bytesLeft);
}
break;
case 'minf':
box.name = "Media Information Box";
readRemainingBoxes();
break;
case 'stbl':
box.name = "Sample Table Box";
readRemainingBoxes();
break;
case 'stsd':
box.name = "Sample Description Box";
readFullHeader();
box.sd = [];
var entries = stream.readU32();
readRemainingBoxes();
break;
case 'avc1':
stream.reserved(6, 0);
box.dataReferenceIndex = stream.readU16();
assert (stream.readU16() == 0); // Version
assert (stream.readU16() == 0); // Revision Level
stream.readU32(); // Vendor
stream.readU32(); // Temporal Quality
stream.readU32(); // Spatial Quality
box.width = stream.readU16();
box.height = stream.readU16();
box.horizontalResolution = stream.readFP16();
box.verticalResolution = stream.readFP16();
assert (stream.readU32() == 0); // Reserved
box.frameCount = stream.readU16();
box.compressorName = stream.readPString(32);
box.depth = stream.readU16();
assert (stream.readU16() == 0xFFFF); // Color Table Id
readRemainingBoxes();
break;
case 'mp4a':
stream.reserved(6, 0);
box.dataReferenceIndex = stream.readU16();
box.version = stream.readU16();
stream.skip(2);
stream.skip(4);
box.channelCount = stream.readU16();
box.sampleSize = stream.readU16();
box.compressionId = stream.readU16();
box.packetSize = stream.readU16();
box.sampleRate = stream.readU32() >>> 16;
// TODO: Parse other version levels.
assert (box.version == 0);
readRemainingBoxes();
break;
case 'esds':
box.name = "Elementary Stream Descriptor";
readFullHeader();
// TODO: Do we really need to parse this?
skipRemainingBytes();
break;
case 'avcC':
box.name = "AVC Configuration Box";
box.configurationVersion = stream.readU8();
box.avcProfileIndication = stream.readU8();
box.profileCompatibility = stream.readU8();
box.avcLevelIndication = stream.readU8();
box.lengthSizeMinusOne = stream.readU8() & 3;
assert (box.lengthSizeMinusOne == 3, "TODO");
var count = stream.readU8() & 31;
box.sps = [];
for (var i = 0; i < count; i++) {
box.sps.push(stream.readU8Array(stream.readU16()));
}
var count = stream.readU8() & 31;
box.pps = [];
for (var i = 0; i < count; i++) {
box.pps.push(stream.readU8Array(stream.readU16()));
}
skipRemainingBytes();
break;
case 'btrt':
box.name = "Bit Rate Box";
box.bufferSizeDb = stream.readU32();
box.maxBitrate = stream.readU32();
box.avgBitrate = stream.readU32();
break;
case 'stts':
box.name = "Decoding Time to Sample Box";
readFullHeader();
box.table = stream.readU32Array(stream.readU32(), 2, ["count", "delta"]);
break;
case 'stss':
box.name = "Sync Sample Box";
readFullHeader();
box.samples = stream.readU32Array(stream.readU32());
break;
case 'stsc':
box.name = "Sample to Chunk Box";
readFullHeader();
box.table = stream.readU32Array(stream.readU32(), 3,
["firstChunk", "samplesPerChunk", "sampleDescriptionId"]);
break;
case 'stsz':
box.name = "Sample Size Box";
readFullHeader();
box.sampleSize = stream.readU32();
var count = stream.readU32();
if (box.sampleSize == 0) {
box.table = stream.readU32Array(count);
}
break;
case 'stco':
box.name = "Chunk Offset Box";
readFullHeader();
box.table = stream.readU32Array(stream.readU32());
break;
case 'smhd':
box.name = "Sound Media Header Box";
readFullHeader();
box.balance = stream.readFP8();
stream.reserved(2, 0);
break;
case 'mdat':
box.name = "Media Data Box";
assert (box.size >= 8, "Cannot parse large media data yet.");
box.data = stream.readU8Array(remainingBytes());
break;
default:
skipRemainingBytes();
break;
};
return box;
},
read: function () {
var start = (new Date).getTime();
this.file = {};
this.readBoxes(this.stream, this.file);
console.info("Parsed stream in " + ((new Date).getTime() - start) + " ms");
},
traceSamples: function () {
var video = this.tracks[1];
var audio = this.tracks[2];
console.info("Video Samples: " + video.getSampleCount());
console.info("Audio Samples: " + audio.getSampleCount());
var vi = 0;
var ai = 0;
for (var i = 0; i < 100; i++) {
var vo = video.sampleToOffset(vi);
var ao = audio.sampleToOffset(ai);
var vs = video.sampleToSize(vi, 1);
var as = audio.sampleToSize(ai, 1);
if (vo < ao) {
console.info("V Sample " + vi + " Offset : " + vo + ", Size : " + vs);
vi ++;
} else {
console.info("A Sample " + ai + " Offset : " + ao + ", Size : " + as);
ai ++;
}
}
}
};
return constructor;
})();
var Track = (function track () {
function constructor(file, trak) {
this.file = file;
this.trak = trak;
}
constructor.prototype = {
getSampleSizeTable: function () {
return this.trak.mdia.minf.stbl.stsz.table;
},
getSampleCount: function () {
return this.getSampleSizeTable().length;
},
/**
* Computes the size of a range of samples, returns zero if length is zero.
*/
sampleToSize: function (start, length) {
var table = this.getSampleSizeTable();
var size = 0;
for (var i = start; i < start + length; i++) {
size += table[i];
}
return size;
},
/**
* Computes the chunk that contains the specified sample, as well as the offset of
* the sample in the computed chunk.
*/
sampleToChunk: function (sample) {
/* Samples are grouped in chunks which may contain a variable number of samples.
* The sample-to-chunk table in the stsc box describes how samples are arranged
* in chunks. Each table row corresponds to a set of consecutive chunks with the
* same number of samples and description ids. For example, the following table:
*
* +-------------+-------------------+----------------------+
* | firstChunk | samplesPerChunk | sampleDescriptionId |
* +-------------+-------------------+----------------------+
* | 1 | 3 | 23 |
* | 3 | 1 | 23 |
* | 5 | 1 | 24 |
* +-------------+-------------------+----------------------+
*
* describes 5 chunks with a total of (2 * 3) + (2 * 1) + (1 * 1) = 9 samples,
* each chunk containing samples 3, 3, 1, 1, 1 in chunk order, or
* chunks 1, 1, 1, 2, 2, 2, 3, 4, 5 in sample order.
*
* This function determines the chunk that contains a specified sample by iterating
* over every entry in the table. It also returns the position of the sample in the
* chunk which can be used to compute the sample's exact position in the file.
*
* TODO: Determine if we should memoize this function.
*/
var table = this.trak.mdia.minf.stbl.stsc.table;
if (table.length === 1) {
var row = table[0];
assert (row.firstChunk === 1);
return {
index: Math.floor(sample / row.samplesPerChunk),
offset: sample % row.samplesPerChunk
};
}
var totalChunkCount = 0;
for (var i = 0; i < table.length; i++) {
var row = table[i];
if (i > 0) {
var previousRow = table[i - 1];
var previousChunkCount = row.firstChunk - previousRow.firstChunk;
var previousSampleCount = previousRow.samplesPerChunk * previousChunkCount;
if (sample >= previousSampleCount) {
sample -= previousSampleCount;
if (i == table.length - 1) {
return {
index: totalChunkCount + previousChunkCount + Math.floor(sample / row.samplesPerChunk),
offset: sample % row.samplesPerChunk
};
}
} else {
return {
index: totalChunkCount + Math.floor(sample / previousRow.samplesPerChunk),
offset: sample % previousRow.samplesPerChunk
};
}
totalChunkCount += previousChunkCount;
}
}
assert(false);
},
chunkToOffset: function (chunk) {
var table = this.trak.mdia.minf.stbl.stco.table;
return table[chunk];
},
sampleToOffset: function (sample) {
var res = this.sampleToChunk(sample);
var offset = this.chunkToOffset(res.index);
return offset + this.sampleToSize(sample - res.offset, res.offset);
},
/**
* Computes the sample at the specified time.
*/
timeToSample: function (time) {
/* In the time-to-sample table samples are grouped by their duration. The count field
* indicates the number of consecutive samples that have the same duration. For example,
* the following table:
*
* +-------+-------+
* | count | delta |
* +-------+-------+
* | 4 | 3 |
* | 2 | 1 |
* | 3 | 2 |
* +-------+-------+
*
* describes 9 samples with a total time of (4 * 3) + (2 * 1) + (3 * 2) = 20.
*
* This function determines the sample at the specified time by iterating over every
* entry in the table.
*
* TODO: Determine if we should memoize this function.
*/
var table = this.trak.mdia.minf.stbl.stts.table;
var sample = 0;
for (var i = 0; i < table.length; i++) {
var delta = table[i].count * table[i].delta;
if (time >= delta) {
time -= delta;
sample += table[i].count;
} else {
return sample + Math.floor(time / table[i].delta);
}
}
},
/**
* Gets the total time of the track.
*/
getTotalTime: function () {
if (PARANOID) {
var table = this.trak.mdia.minf.stbl.stts.table;
var duration = 0;
for (var i = 0; i < table.length; i++) {
duration += table[i].count * table[i].delta;
}
assert (this.trak.mdia.mdhd.duration == duration);
}
return this.trak.mdia.mdhd.duration;
},
getTotalTimeInSeconds: function () {
return this.timeToSeconds(this.getTotalTime());
},
getTimeScale: function () {
return this.trak.mdia.mdhd.timeScale;
},
/**
* Converts time units to real time (seconds).
*/
timeToSeconds: function (time) {
return time / this.getTimeScale();
},
/**
* Converts real time (seconds) to time units.
*/
secondsToTime: function (seconds) {
return seconds * this.getTimeScale();
},
foo: function () {
/*
for (var i = 0; i < this.getSampleCount(); i++) {
var res = this.sampleToChunk(i);
console.info("Sample " + i + " -> " + res.index + " % " + res.offset +
" @ " + this.chunkToOffset(res.index) +
" @@ " + this.sampleToOffset(i));
}
console.info("Total Time: " + this.timeToSeconds(this.getTotalTime()));
var total = this.getTotalTimeInSeconds();
for (var i = 50; i < total; i += 0.1) {
// console.info("Time: " + i.toFixed(2) + " " + this.secondsToTime(i));
console.info("Time: " + i.toFixed(2) + " " + this.timeToSample(this.secondsToTime(i)));
}
*/
},
/**
* AVC samples contain one or more NAL units each of which have a length prefix.
* This function returns an array of NAL units without their length prefixes.
*/
getSampleNALUnits: function (sample) {
var bytes = this.file.stream.bytes;
var offset = this.sampleToOffset(sample);
var end = offset + this.sampleToSize(sample, 1);
var nalUnits = [];
while(end - offset > 0) {
var length = (new Bytestream(bytes.buffer, offset)).readU32();
nalUnits.push(bytes.subarray(offset + 4, offset + length + 4));
offset = offset + length + 4;
}
return nalUnits;
}
};
return constructor;
})();
// Only add setZeroTimeout to the window object, and hide everything
// else in a closure. (http://dbaron.org/log/20100309-faster-timeouts)
(function() {
var timeouts = [];
var messageName = "zero-timeout-message";
// Like setTimeout, but only takes a function argument. There's
// no time argument (always zero) and no arguments (you have to
// use a closure).
function setZeroTimeout(fn) {
timeouts.push(fn);
window.postMessage(messageName, "*");
}
function handleMessage(event) {
if (event.source == window && event.data == messageName) {
event.stopPropagation();
if (timeouts.length > 0) {
var fn = timeouts.shift();
fn();
}
}
}
window.addEventListener("message", handleMessage, true);
// Add the one thing we want added to the window object.
window.setZeroTimeout = setZeroTimeout;
})();
var MP4Player = (function reader() {
var defaultConfig = {
filter: "original",
filterHorLuma: "optimized",
filterVerLumaEdge: "optimized",
getBoundaryStrengthsA: "optimized"
};
function constructor(stream, useWorkers, webgl, render) {
this.stream = stream;
this.useWorkers = useWorkers;
this.webgl = webgl;
this.render = render;
this.statistics = {
videoStartTime: 0,
videoPictureCounter: 0,
windowStartTime: 0,
windowPictureCounter: 0,
fps: 0,
fpsMin: 1000,
fpsMax: -1000,
webGLTextureUploadTime: 0
};
this.onStatisticsUpdated = function () {};
this.avc = new Player({
useWorker: useWorkers,
reuseMemory: true,
webgl: webgl,
size: {
width: 640,
height: 368
}
});
this.webgl = this.avc.webgl;
var self = this;
this.avc.onPictureDecoded = function(){
updateStatistics.call(self);
};
this.canvas = this.avc.canvas;
}
function updateStatistics() {
var s = this.statistics;
s.videoPictureCounter += 1;
s.windowPictureCounter += 1;
var now = Date.now();
if (!s.videoStartTime) {
s.videoStartTime = now;
}
var videoElapsedTime = now - s.videoStartTime;
s.elapsed = videoElapsedTime / 1000;
if (videoElapsedTime < 1000) {
return;
}
if (!s.windowStartTime) {
s.windowStartTime = now;
return;
} else if ((now - s.windowStartTime) > 1000) {
var windowElapsedTime = now - s.windowStartTime;
var fps = (s.windowPictureCounter / windowElapsedTime) * 1000;
s.windowStartTime = now;
s.windowPictureCounter = 0;
if (fps < s.fpsMin) s.fpsMin = fps;
if (fps > s.fpsMax) s.fpsMax = fps;
s.fps = fps;
}
var fps = (s.videoPictureCounter / videoElapsedTime) * 1000;
s.fpsSinceStart = fps;
this.onStatisticsUpdated(this.statistics);
return;
}
constructor.prototype = {
readAll: function(callback) {
console.info("MP4Player::readAll()");
this.stream.readAll(null, function (buffer) {
this.reader = new MP4Reader(new Bytestream(buffer));
this.reader.read();
var video = this.reader.tracks[1];
this.size = new Size(video.trak.tkhd.width, video.trak.tkhd.height);
console.info("MP4Player::readAll(), length: " + this.reader.stream.length);
if (callback) callback();
}.bind(this));
},
play: function() {
var reader = this.reader;
if (!reader) {
this.readAll(this.play.bind(this));
return;
};
var video = reader.tracks[1];
var audio = reader.tracks[2];
var avc = reader.tracks[1].trak.mdia.minf.stbl.stsd.avc1.avcC;
var sps = avc.sps[0];
var pps = avc.pps[0];
/* Decode Sequence & Picture Parameter Sets */
this.avc.decode(sps);
this.avc.decode(pps);
/* Decode Pictures */
var pic = 0;
setTimeout(function foo() {
var avc = this.avc;
video.getSampleNALUnits(pic).forEach(function (nal) {
avc.decode(nal);
});
pic ++;
if (pic < 3000) {
setTimeout(foo.bind(this), 1);
};
}.bind(this), 1);
}
};
return constructor;
})();
var Broadway = (function broadway() {
function constructor(div) {
var src = div.attributes.src ? div.attributes.src.value : undefined;
var width = div.attributes.width ? div.attributes.width.value : 640;
var height = div.attributes.height ? div.attributes.height.value : 480;
var controls = document.createElement('div');
controls.setAttribute('style', "z-index: 100; position: absolute; bottom: 0px; background-color: rgba(0,0,0,0.8); height: 30px; width: 100%; text-align: left;");
this.info = document.createElement('div');
this.info.setAttribute('style', "font-size: 14px; font-weight: bold; padding: 6px; color: lime;");
controls.appendChild(this.info);
div.appendChild(controls);
var useWorkers = div.attributes.workers ? div.attributes.workers.value == "true" : false;
var render = div.attributes.render ? div.attributes.render.value == "true" : false;
var webgl = "auto";
if (div.attributes.webgl){
if (div.attributes.webgl.value == "true"){
webgl = true;
};
if (div.attributes.webgl.value == "false"){
webgl = false;
};
};
var infoStrPre = "Click canvas to load and play - ";
var infoStr = "";
if (useWorkers){
infoStr += "worker thread ";
}else{
infoStr += "main thread ";
};
this.player = new MP4Player(new Stream(src), useWorkers, webgl, render);
this.canvas = this.player.canvas;
this.canvas.onclick = function () {
this.play();
}.bind(this);
div.appendChild(this.canvas);
infoStr += " - webgl: " + this.player.webgl;
this.info.innerHTML = infoStrPre + infoStr;
this.score = null;
this.player.onStatisticsUpdated = function (statistics) {
if (statistics.videoPictureCounter % 10 != 0) {
return;
}
var info = "";
if (statistics.fps) {
info += " fps: " + statistics.fps.toFixed(2);
}
if (statistics.fpsSinceStart) {
info += " avg: " + statistics.fpsSinceStart.toFixed(2);
}
var scoreCutoff = 1200;
if (statistics.videoPictureCounter < scoreCutoff) {
this.score = scoreCutoff - statistics.videoPictureCounter;
} else if (statistics.videoPictureCounter == scoreCutoff) {
this.score = statistics.fpsSinceStart.toFixed(2);
}
// info += " score: " + this.score;
this.info.innerHTML = infoStr + info;
}.bind(this);
}
constructor.prototype = {
play: function () {
this.player.play();
}
};
return constructor;
})();
return {
Size,
Track,
MP4Reader,
MP4Player,
Bytestream,
Broadway,
}
})();