Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added a websocket livestream demo #10

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
73 changes: 73 additions & 0 deletions demo/libde265_websocket.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
<!DOCTYPE html>
<html>
<head>
<meta charset='utf-8'>
<title>libde265.js</title>
<script type="text/javascript" src="static/libde265.js"></script>
<script>

var player = null;

window.onload = function() {
var URL = (window.URL || window.webkitURL);
var websocket_port = 8080;

// Connect to the specified port
var url_address = "ws://localhost:" + websocket_port + "/websocket";

var video = document.getElementById("video");
var status = document.getElementById("status");

var playback = function(event) {
event.preventDefault();
if (player) {
player.stop();
}

console.log("Playing with libde265", libde265.de265_get_version());
player = new libde265.StreamPlayer(video);
player.set_status_callback(function(msg, fps) {
player.disable_filters(true);
switch (msg) {
case "loading":
status.innerHTML = "Loading movie...";
break;
case "initializing":
status.innerHTML = "Initializing...";
break;
case "playing":
status.innerHTML = "Playing...";
break;
case "stopped":
status.innerHTML = "";
break;
case "fps":
status.innerHTML = Number(fps).toFixed(2) + " fps";
break;
default:
status.innerHTML = msg;
}
});
player.playback(url_address);
};

var button = document.getElementById("play");
if (button.addEventListener) {
button.addEventListener("click", playback, false);
} else if (button.attachEvent){
button.attachEvent('onclick', playback);
}
}
</script>
<body>
<h1>libde265.js</h1>
<a href="https://github.com/strukturag/libde265.js"><img style="position: absolute; top: 0; right: 0; border: 0;" src="https://camo.githubusercontent.com/38ef81f8aca64bb9a64448d0d70f1308ef5341ab/68747470733a2f2f73332e616d617a6f6e6177732e636f6d2f6769746875622f726962626f6e732f666f726b6d655f72696768745f6461726b626c75655f3132313632312e706e67" alt="Fork me on GitHub" data-canonical-src="https://s3.amazonaws.com/github/ribbons/forkme_right_darkblue_121621.png"></a>
<div>
<div>Simple HEVC/H.265 bitstream player in pure JavaScript.</div>
<div><small>Copyright &copy; 2014 by <a href="http://www.struktur.de">struktur AG</a></small></div>
<canvas id="video" width="0" height="0"></canvas>
</div>
<button id="play">Play</button>
<span id="status"></span>
</body>
</html>
60 changes: 60 additions & 0 deletions demo/livevideoserver.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import tornado.ioloop
import tornado.web
import tornado.websocket
import time

def start(ws):
f = open("spreedmovie.hevc", "rb")
while True:
data = f.read(ws.chunk_size)
if not data:
print "Done!"
ws.send_message("flush")
break
ws.send_message(data)
time.sleep(1 / ws.fps)

class VideoStreamWebSocket(tornado.websocket.WebSocketHandler):
def open(self):
print "WebSocket opened"
self.chunk_size = 0
self.fps = 0.0

@tornado.web.asynchronous
def on_message(self, message):
cmd = message.split()
if len(cmd) > 0:
if cmd[0] == "start":
start(self)
if cmd[0] == "chunk_size":
self.chunk_size = int(cmd[1])
if cmd[0] == "fps":
self.fps = float(cmd[1])

@tornado.web.asynchronous
def send_message(self, data):
self.write_message(data, binary=True)

def on_close(self):
print "WebSocket closed"

class MainHandler(tornado.web.RequestHandler):
def get(self):
import os
f = open("libde265_websocket.html")
self.write(f.read())
self.finish()

class Application(tornado.web.Application):
def __init__(self):
handlers = [
(r"/", MainHandler),
(r"/websocket", VideoStreamWebSocket),
(r'/static/(.*)', tornado.web.StaticFileHandler, {'path': '../lib'}),
]
tornado.web.Application.__init__(self, handlers)
print "Waiting for WebSocket..."

application = Application()
application.listen(8080)
tornado.ioloop.IOLoop.instance().start()
212 changes: 212 additions & 0 deletions lib/libde265.js
Original file line number Diff line number Diff line change
Expand Up @@ -11393,10 +11393,222 @@ RawPlayer.prototype.disable_filters = function(disable) {
this.filters = disable;
};

/**
* A simple raw stream bitstream player interface.
*
* @constructor
*/
var StreamPlayer = function(canvas) {
this.canvas = canvas;
this.ctx = canvas.getContext("2d");
this.status_cb = null;
this.error_cb = null;
this.ratio = null;
this.filters = false;
this._reset();
};

StreamPlayer.prototype._reset = function() {
this.start = null;
this.frames = 0;
this.image_data = null;
this.running = false;
this.pending_image_data = null;
};

/** @expose */
StreamPlayer.prototype.set_status_callback = function(callback) {
this.status_cb = callback;
};

StreamPlayer.prototype._set_status = function() {
if (this.status_cb) {
this.status_cb.apply(this.status_cb, arguments);
}
};

/** @expose */
StreamPlayer.prototype.set_error_callback = function(callback) {
this.error_cb = callback;
};

StreamPlayer.prototype._set_error = function(error, message) {
if (this.error_cb) {
this.error_cb(error, message);
}
};

StreamPlayer.prototype._display_image = function(image) {
if (!this.start) {
this.start = new Date();
this._set_status("playing");
} else {
this.frames += 1;
var duration = (new Date()) - this.start;
if (duration > 1000) {
this._set_status("fps", this.frames / (duration * 0.001));
}
}

var w = image.get_width();
var h = image.get_height();
if (w != this.canvas.width || h != this.canvas.height || !this.image_data) {
this.canvas.width = w;
this.canvas.height = h;
this.image_data = this.ctx.createImageData(w, h);
var image_data = this.image_data.data;
for (var i=0; i<w*h; i++) {
image_data[i*4+3] = 255;
}
}

var that = this;
image.display(this.image_data, function(display_image_data) {
if (window.requestAnimationFrame) {
that.pending_image_data = display_image_data;
window.requestAnimationFrame(function() {
if (that.pending_image_data) {
that.ctx.putImageData(that.pending_image_data, 0, 0);
that.pending_image_data = null;
}
});
} else {
that.ctx.putImageData(display_image_data, 0, 0);
}
});
};

StreamPlayer.prototype._handle_onload = function(ws) {
var that = this;
this._set_status("initializing");

var decoder = new Decoder();
decoder.set_image_callback(function(image) {
that._display_image(image);
image.free();
});
var ratio = null;
var filters = false;
var chunk_size = 4096;
var fps = 30.0;

var decode = function(evt) {
if (evt == null)
{
return;
}

if (!that.running) {
return;
}

var err;
if (evt.data == null) {
err = decoder.flush();
} else {
try
{
var tmp = new Uint8Array(evt.data, 0, chunk_size);
err = decoder.push_data(tmp);
}
catch(err)
{
console.log(err);
err = decoder.flush();
return;
}
}
if (!libde265.de265_isOK(err)) {
that._set_error(err, libde265.de265_get_error_text(err));
return;
}

if (that.ratio !== ratio) {
decoder.set_framerate_ratio(that.ratio);
ratio = that.ratio;
}

if (that.filters !== filters) {
decoder.disable_filters(that.filters);
filters = that.filters;
}

decoder.decode(function(err) {
switch(err) {
case libde265.DE265_ERROR_WAITING_FOR_INPUT_DATA:
console.log("DE265_ERROR_WAITING_FOR_INPUT_DATA");
setTimeout(decode(null), 0);
return;

default:
if (!libde265.de265_isOK(err)) {
that._set_error(err, libde265.de265_get_error_text(err));
return;
}
}

if (decoder.has_more()) {
console.log("has more");
setTimeout(decode(null), 0);
return;
}

decoder.free();
that.stop();
});
};

ws.onmessage = decode;

// Start the transaction
ws.send("chunk_size " + chunk_size);
ws.send("fps " + fps);
ws.send("start");
};

/** @expose */
StreamPlayer.prototype.playback = function(url_address) {
this._reset();

var that = this;

var ws = new WebSocket(url_address);
ws.binaryType = "arraybuffer";

ws.onopen = function() {
console.log("Stream open");
that._handle_onload(ws);
};

ws.onclose = function() {
console.log("Connection closed.");
}

this._set_status("loading");
this.running = true;
};

/** @expose */
StreamPlayer.prototype.stop = function() {
this._set_status("stopped");
this._reset();
};

/** @expose */
StreamPlayer.prototype.set_framerate_ratio = function(ratio) {
this.ratio = ratio;
};

/** @expose */
StreamPlayer.prototype.disable_filters = function(disable) {
this.filters = disable;
};

/**
* @expose
*/
libde265.RawPlayer = RawPlayer;
libde265.StreamPlayer = StreamPlayer;

var root = this;

Expand Down