Skip to content

Commit

Permalink
Added seeking, close phoboslab#9
Browse files Browse the repository at this point in the history
  • Loading branch information
phoboslab committed Jun 2, 2015
1 parent f59de44 commit b2d57a1
Show file tree
Hide file tree
Showing 2 changed files with 71 additions and 0 deletions.
17 changes: 17 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,23 @@ player.play();
player.stop();


// If you pass 'seekable: true' in the options, you can seek to a specific frame
// or time in the video.

var player = new jsmpeg('file.mpeg', {canvas: canvas, seekable: true});

player.seekToFrame(1200); // Seek to intra frame before frame 1200
player.seekToTime(20); // Seek to intra frame before 20sec

// seekToFrame() and seekToTime() only seek to the closest, previous intra frame by
// default. If you want to seek to the exact frame or time, pass 'true' as second
// parameter.
// Depending on the input video, this can be potentially slow, as jsmpeg has
// to decode all frames between the previous intra frame and the seek target

player.seekToFrame(1200, true); // Seek to frame 1200 exactly


// An 'onload' callback can be specified in the 'options' argument
var mpegLoaded = function( player ) {
console.log('Loaded', player);
Expand Down
54 changes: 54 additions & 0 deletions jsmpg.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ var jsmpeg = window.jsmpeg = function( url, opts ) {
this.canvas = opts.canvas || document.createElement('canvas');
this.autoplay = !!opts.autoplay;
this.loop = !!opts.loop;
this.seekable = !!opts.seekable;
this.externalLoadCallback = opts.onload || null;
this.externalDecodeCallback = opts.ondecodeframe || null;
this.externalFinishedCallback = opts.onfinished || null;
Expand Down Expand Up @@ -265,6 +266,8 @@ jsmpeg.prototype.stopRecording = function() {

// ----------------------------------------------------------------------------
// Loading via Ajax

jsmpeg.prototype.intraFrames = [];

jsmpeg.prototype.load = function( url ) {
this.url = url;
Expand Down Expand Up @@ -307,6 +310,11 @@ jsmpeg.prototype.updateLoaderGL = function( ev ) {

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

if( this.seekable ) {
this.collectIntraFrames();
this.buffer.index = 0;
}

this.findStartCode(START_SEQUENCE);
this.firstSequenceHeader = this.buffer.index;
Expand All @@ -324,6 +332,52 @@ jsmpeg.prototype.loadCallback = function(file) {
}
};

jsmpeg.prototype.collectIntraFrames = function() {
// Loop through the whole buffer and collect all intraFrames to build our seek index.
for( var frame = 0; this.findStartCode(START_PICTURE) !== BitReader.NOT_FOUND; frame++ ) {

// Check if the found picture is an intra frame and remember the position
this.buffer.advance(10); // skip temporalReference
if( this.buffer.getBits(3) == PICTURE_TYPE_I ) {
// Remember index 13 bits back, before temporalReference and picture type
this.intraFrames.push({frame: frame, index: this.buffer.index - 13});
}
}
};

jsmpeg.prototype.seekToFrame = function(seekFrame, seekExact) {
var target = null;
for( var i = 1; i < this.intraFrames.length; i++ ) {
if( this.intraFrames[i].frame > seekFrame ) {
target = this.intraFrames[i-1];
break;
}
}

if( !target ) {
return false;
}

this.buffer.index = target.index;

// If we're seeking to the exact frame, we may have to decode some more frames before
// the one we want
if( seekExact ) {
for( var currentFrame = target.frame; currentFrame < seekFrame-1; currentFrame++ ) {
this.decodePicture(DECODE_SKIP_OUTPUT);
this.findStartCode(START_PICTURE);
}
}

// Decode and display the picture we have seeked to
this.decodePicture();
return true;
};

jsmpeg.prototype.seekToTime = function(time, seekExact) {
this.seekToFrame( (time * this.pictureRate)|0, seekExact );
};

jsmpeg.prototype.play = function(file) {
if( this.playing ) { return; }
this.targetTime = this.now();
Expand Down

0 comments on commit b2d57a1

Please sign in to comment.