I've written a VideoBuffer class for loading segments of webm video. Each segment works separately when opened as a blob in the video element or directly in a separate browser tab, but I can't seem to get it to work when appending them to a source buffer.
There's no error or exception being thrown, the ready state of the MediaSource gets closed and the SourceBufferList gets emptied. I've based much on my code on this working example, where the main difference is that I am appending each xhr response to the buffer instead of appending the results from a FileReader.
Any ideas of why this isn't working or how I should debug it?
var EventEmitter = require("events").EventEmitter;
function VideoBuffer(options) {
this.video = options.video;
this.source = new MediaSource();
this.video.src = URL.createObjectURL(this.source);
this.url = options.url;
this.segment = 0;
this.time = 0;
this.duration = options.duration;
this.segments = Math.ceil(this.duration/10);
this.preset = options.preset;
this.source.addEventListener("sourceopen", this.onSourceOpen.bind(this));
this.source.addEventListener("sourceended", this.onSourceEnded.bind(this));
this.video.addEventListener("progress", this.onVideoProgress.bind(this));
this.on("error", function(err){
try {
throw err;
} catch (e) {
console.error(e.stack);
}
});
}
VideoBuffer.prototype = Object.create(EventEmitter.prototype);
VideoBuffer.prototype.onSourceOpen = function (e) {
this.debug("Source opened:", this.source);
var buffer = this.source.addSourceBuffer('video/webm; codecs="vorbis,vp8"');
buffer.addEventListener("updatestart", this.onBufferUpdateStart.bind(this));
buffer.addEventListener("update", this.onBufferUpdate.bind(this));
buffer.addEventListener("updateend", this.onBufferUpdateEnd.bind(this));
this.debug("Added source buffer:", buffer);
this.emit("source:open", e);
};
VideoBuffer.prototype.onSourceEnded = function (e) {
this.debug("Source ended:", this.mediaSource);
this.emit("source:ended", e);
};
VideoBuffer.prototype.onBufferUpdateStart = function (e) {
this.emit("buffer:updatestart", e);
};
VideoBuffer.prototype.onBufferUpdate = function (e) {
this.emit("buffer:update", e);
};
VideoBuffer.prototype.onBufferUpdateEnd = function (e) {
this.emit("buffer:updateend", e);
if (this.segment === 0) {
this.debug("Got first segment, starting playback");
this.debug(this);
this.video.play();
}
};
VideoBuffer.prototype.onVideoProgress = function (e) {
this.debug("Video progress:", e);
this.segment++;
this.loadNextSegment();
}
VideoBuffer.prototype.loadNextSegment = function () {
var url = this.url
.replace("%segment", this.segment)
.replace("%time", this.time)
.replace("%preset", this.preset);
this.debug("Loading segment:", this.segment, url);
this.request = new XMLHttpRequest();
this.request.responseType = "arraybuffer";
this.request.open("GET", url, true);
this.request.onload = this.onRequestLoad.bind(this);
this.request.send(null);
}
VideoBuffer.prototype.onRequestLoad = function(e) {
if (this.request.status != 200) {
this.emit("error", new Error("Unexpected status code"));
return false;
}
var ms = this.source;
var sb = ms.sourceBuffers.length ? ms.sourceBuffers[0] : false;
var buffer = new Uint8Array(this.request.response);
this.debug("Got segment:", buffer.length, "bytes");
if (buffer.length && sb) {
try {
this.debug("Appending segment to source buffer");
sb.appendBuffer(buffer);
} catch (e) {
this.emit("error", new Error(e));
return false;
}
if (this.segment === this.segments) {
ms.endOfStream();
return;
}
} else {
this.emit("error", new Error("Empty response or missing source buffer"));
}
}
VideoBuffer.prototype.play = function (time) {
this.time = 0;
this.segment = Math.floor(this.time/10);
this.loadNextSegment();
};
VideoBuffer.prototype.debug = (function () {
return console.log.bind(console);
}());
module.exports = VideoBuffer;
Note: The code above is loaded with browserify, which is why it's a CommonJS module.
Gist for those who prefer and/or wish to experiment.