diff --git a/demo/libde265_websocket.html b/demo/libde265_websocket.html
new file mode 100644
index 0000000..0778145
--- /dev/null
+++ b/demo/libde265_websocket.html
@@ -0,0 +1,73 @@
+
+
+
+
+libde265.js
+
+
+
+ libde265.js
+
+
+
Simple HEVC/H.265 bitstream player in pure JavaScript.
+
+
+
+
+
+
+
diff --git a/demo/livevideoserver.py b/demo/livevideoserver.py
new file mode 100644
index 0000000..438bd26
--- /dev/null
+++ b/demo/livevideoserver.py
@@ -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()
diff --git a/lib/libde265.js b/lib/libde265.js
index 481758c..61f3dbc 100644
--- a/lib/libde265.js
+++ b/lib/libde265.js
@@ -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 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