Skip to content

Commit

Permalink
Implement progressive loading via fetch/ReadableByteStream; see phobo…
Browse files Browse the repository at this point in the history
  • Loading branch information
phoboslab committed Feb 10, 2016
1 parent 585da7d commit f1e0a3d
Show file tree
Hide file tree
Showing 2 changed files with 124 additions and 12 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ The `file` argument accepts a URL to a .mpg file or a (yet unconnected) WebSocke
The `options` argument to the `jsmpeg()` supports the following properties:

- `benchmark` whether to log benchmark results to the browser's console
- `progressive` whether to start playback as soon as the first frames have been loaded. Uses the new `fetch` API if available. Default `true`.
- `canvas` the HTML Canvas element to use; jsmpeg will create its own Canvas element if none is provided
- `autoplay` whether playback should start automatically after loading
- `loop` whether playback is looped
Expand Down
135 changes: 123 additions & 12 deletions jsmpg.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,11 @@ var requestAnimFrame = (function(){

var jsmpeg = window.jsmpeg = function( url, opts ) {
opts = opts || {};
this.progressive = (opts.progressive !== false);
this.benchmark = !!opts.benchmark;
this.canvas = opts.canvas || document.createElement('canvas');
this.autoplay = !!opts.autoplay;
this.wantsToPlay = this.autoplay;
this.loop = !!opts.loop;
this.seekable = !!opts.seekable;
this.externalLoadCallback = opts.onload || null;
Expand Down Expand Up @@ -271,25 +273,125 @@ jsmpeg.prototype.currentFrame = -1;
jsmpeg.prototype.currentTime = 0;
jsmpeg.prototype.frameCount = 0;
jsmpeg.prototype.duration = 0;
jsmpeg.prototype.progressiveMinSize = 128 * 1024;


jsmpeg.prototype.fetchReaderPump = function(reader) {
var that = this;
reader.read().then(function (result) {
that.fetchReaderReceive(reader, result);
});
};

jsmpeg.prototype.fetchReaderReceive = function(reader, result) {
if( result.done ) {
if( this.seekable ) {
var currentBufferPos = this.buffer.index;
this.collectIntraFrames();
this.buffer.index = currentBufferPos;
}

this.duration = this.frameCount / this.pictureRate;
this.lastFrameIndex = this.buffer.writePos << 3;
return;
}

this.buffer.bytes.set(result.value, this.buffer.writePos);
this.buffer.writePos += result.value.byteLength;

// Find the last picture start code - we have to be careful not trying
// to decode any frames that aren't fully loaded yet.
this.lastFrameIndex = this.findLastPictureStartCode();

// Initialize the sequence headers and start playback if we have enough data
// (at least 128kb)
if( !this.sequenceStarted && this.buffer.writePos >= this.progressiveMinSize ) {
this.findStartCode(START_SEQUENCE);
this.firstSequenceHeader = this.buffer.index;
this.decodeSequenceHeader();

// Load the first frame
this.nextFrame();

if( this.autoplay ) {
this.play();
}

if( this.externalLoadCallback ) {
this.externalLoadCallback(this);
}
}

// If the player starved previously, restart playback now
else if( this.sequenceStarted && this.wantsToPlay && !this.playing ) {
this.play();
}

// Not enough data to start playback yet - show loading progress
else if( !this.sequenceStarted ) {
var status = {loaded: this.buffer.writePos, total: this.progressiveMinSize};
if( this.gl ) {
this.updateLoaderGL(status);
}
else {
this.updateLoader2D(status);
}
}

this.fetchReaderPump(reader);
};

jsmpeg.prototype.findLastPictureStartCode = function() {
var bufferBytes = this.buffer.bytes;
for( var i = this.buffer.writePos; i > 3; i-- ) {
if(
bufferBytes[i] == START_PICTURE &&
bufferBytes[i-1] == 0x01 &&
bufferBytes[i-2] == 0x00 &&
bufferBytes[i-3] == 0x00
) {
return (i-3) << 3;
}
}
return 0;
};

jsmpeg.prototype.load = function( url ) {
this.url = url;

var request = new XMLHttpRequest();
var that = this;
request.onreadystatechange = function() {
if( request.readyState === request.DONE && request.status === 200 ) {
that.loadCallback(request.response);
}
};
if(
this.progressive &&
window.fetch &&
window.ReadableByteStream
) {
var reqHeaders = new Headers();
reqHeaders.append('Content-Type', 'video/mpeg');
fetch(url, {headers: reqHeaders}).then(function (res) {
var contentLength = res.headers.get('Content-Length');
var reader = res.body.getReader();

request.onprogress = this.gl
? this.updateLoaderGL.bind(this)
: this.updateLoader2D.bind(this);
that.buffer = new BitReader(new ArrayBuffer(contentLength));
that.buffer.writePos = 0;
that.fetchReaderPump(reader);
});
}
else {
var request = new XMLHttpRequest();
request.onreadystatechange = function() {
if( request.readyState === request.DONE && request.status === 200 ) {
that.loadCallback(request.response);
}
};

request.onprogress = this.gl
? this.updateLoaderGL.bind(this)
: this.updateLoader2D.bind(this);

request.open('GET', url);
request.responseType = 'arraybuffer';
request.send();
request.open('GET', url);
request.responseType = 'arraybuffer';
request.send();
}
};

jsmpeg.prototype.updateLoader2D = function( ev ) {
Expand All @@ -311,6 +413,7 @@ jsmpeg.prototype.updateLoaderGL = function( ev ) {
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
};


jsmpeg.prototype.loadCallback = function(file) {
this.buffer = new BitReader(file);

Expand Down Expand Up @@ -392,11 +495,13 @@ jsmpeg.prototype.play = function() {
if( this.playing ) { return; }
this.targetTime = this.now();
this.playing = true;
this.wantsToPlay = true;
this.scheduleNextFrame();
};

jsmpeg.prototype.pause = function() {
this.playing = false;
this.wantsToPlay = false;
};

jsmpeg.prototype.stop = function() {
Expand All @@ -409,6 +514,7 @@ jsmpeg.prototype.stop = function() {
this.client.close();
this.client = null;
}
this.wantsToPlay = false;
};


Expand Down Expand Up @@ -473,6 +579,11 @@ jsmpeg.prototype.nextFrame = function() {
this.decodeSequenceHeader();
}
else if( code === START_PICTURE ) {
if( this.progressive && this.buffer.index >= this.lastFrameIndex ) {
// Starved
this.playing = false;
return;
}
if( this.playing ) {
this.scheduleNextFrame();
}
Expand Down

0 comments on commit f1e0a3d

Please sign in to comment.