commit 1616320490ba3ac211bab167ff539d398f0408c4
parent c665b7d92648d5b839d40b198610aa05d11cceb8
Author: Joris Vink <joris@coders.se>
Date: Sun, 10 Aug 2014 19:33:43 +0200
Add an html5 video streaming example
Diffstat:
6 files changed, 341 insertions(+), 0 deletions(-)
diff --git a/examples/video_stream/.gitignore b/examples/video_stream/.gitignore
@@ -0,0 +1,5 @@
+*.o
+.objs
+stream.so
+assets.h
+cert
diff --git a/examples/video_stream/README.md b/examples/video_stream/README.md
@@ -0,0 +1,22 @@
+A simple HTML5 video streaming service using Kore.
+
+Building:
+```
+ You must first place a test video inside the videos/ folder. I tested
+ this using Big Buck Bunny (ogg version) on Chrome. But any video that
+ can be played with HTML5 works.
+
+ If you did not save your video as videos/video.ogg make sure you
+ update the assets/video.html file to point to the right video.
+
+ When done, run a kore build.
+```
+
+Run:
+```
+ kore run
+```
+
+Visit the URI and you should see a video stream.
+
+Frontend parts from video.js: http://www.videojs.com/
diff --git a/examples/video_stream/assets/video.html b/examples/video_stream/assets/video.html
@@ -0,0 +1,18 @@
+<!DOCTYPE>
+<html>
+<head>
+ <link href="https:////vjs.zencdn.net/4.7/video-js.css" rel="stylesheet">
+ <script src="https:////vjs.zencdn.net/4.7/video.js"></script>
+ <title>Video stream over Kore</title>
+</head>
+
+<body>
+
+<video class="video-js vjs-default-skin" width="640"
+ height="240" controls preload="auto" data-setup='{"example_option":true}'>
+ <source src="/video.ogg" type="video/ogg">
+Your browser does not support the video tag.
+</video>
+
+</body>
+</html>
diff --git a/examples/video_stream/conf/video_stream.conf b/examples/video_stream/conf/video_stream.conf
@@ -0,0 +1,16 @@
+# Placeholder configuration
+
+bind 127.0.0.1 8888
+load ./video_stream.so init
+
+spdy_idle_time 600
+http_keepalive_time 600
+
+domain 127.0.0.1 {
+ certfile cert/server.crt
+ certkey cert/server.key
+ accesslog access.log
+
+ static / serve_page
+ dynamic ^/[a-z]*.[a-z0-9]{3}$ video_stream
+}
diff --git a/examples/video_stream/src/stream.c b/examples/video_stream/src/stream.c
@@ -0,0 +1,279 @@
+/*
+ * Copyright (c) 2014 Joris Vink <joris@coders.se>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/param.h>
+#include <sys/stat.h>
+#include <sys/mman.h>
+#include <sys/queue.h>
+
+#include <fcntl.h>
+
+#include <kore/kore.h>
+#include <kore/http.h>
+
+#include "assets.h"
+
+struct video {
+ int fd;
+ int ref;
+ off_t size;
+ char *path;
+ u_int8_t *data;
+ void *base;
+
+ TAILQ_ENTRY(video) list;
+};
+
+void init(int);
+int serve_page(struct http_request *);
+int video_stream(struct http_request *);
+
+static void video_unmap(struct video *);
+static int video_stream_finish(struct netbuf *);
+static int video_mmap(struct http_request *, struct video *);
+static int video_open(struct http_request *, struct video **);
+
+TAILQ_HEAD(, video) videos;
+
+void
+init(int state)
+{
+ switch (state) {
+ case KORE_MODULE_LOAD:
+ TAILQ_INIT(&videos);
+ break;
+ case KORE_MODULE_UNLOAD:
+ fatal("cannot reload this module, i should fix this");
+ break;
+ }
+}
+
+int
+serve_page(struct http_request *req)
+{
+ http_response_header(req, "content-type", "text/html");
+ http_response_stream(req, 200, asset_video_html,
+ asset_len_video_html, NULL, NULL);
+
+ return (KORE_RESULT_OK);
+}
+
+int
+video_stream(struct http_request *req)
+{
+ struct video *v;
+ off_t start, end;
+ int n, err, l, status;
+ char *header, *bytes, *range[3], rb[128], *ext, ctype[32];
+
+ if (!video_open(req, &v))
+ return (KORE_RESULT_OK);
+
+ if ((ext = strrchr(req->path, '.')) == NULL) {
+ http_response(req, 400, NULL, 0);
+ return (KORE_RESULT_OK);
+ }
+
+ l = snprintf(ctype, sizeof(ctype), "video/%s", ext + 1);
+ if (l == -1 || (size_t)l >= sizeof(ctype)) {
+ http_response(req, 500, NULL, 0);
+ return (KORE_RESULT_OK);
+ }
+
+ kore_log(LOG_NOTICE, "%p: opened %s (%s) for streaming (%ld ref:%d)",
+ req->owner, v->path, ctype, v->size, v->ref);
+
+ if (http_request_header(req, "range", &header)) {
+ if ((bytes = strchr(header, '=')) == NULL) {
+ http_response(req, 416, NULL, 0);
+ return (KORE_RESULT_OK);
+ }
+
+ bytes++;
+ n = kore_split_string(bytes, "-", range, 2);
+ if (n == 0) {
+ http_response(req, 416, NULL, 0);
+ return (KORE_RESULT_OK);
+ }
+
+ if (n >= 1) {
+ start = kore_strtonum64(range[0], 10, &err);
+ if (err != KORE_RESULT_OK) {
+ http_response(req, 416, NULL, 0);
+ return (KORE_RESULT_OK);
+ }
+ }
+
+ if (n > 1) {
+ end = kore_strtonum64(range[1], 10, &err);
+ if (err != KORE_RESULT_OK) {
+ http_response(req, 416, NULL, 0);
+ return (KORE_RESULT_OK);
+ }
+ } else {
+ end = 0;
+ }
+
+ if (end == 0)
+ end = v->size;
+
+ if (start > end || start > v->size || end > v->size) {
+ http_response(req, 416, NULL, 0);
+ return (KORE_RESULT_OK);
+ }
+
+ status = 206;
+ l = snprintf(rb, sizeof(rb), "bytes %ld-%ld/%ld",
+ start, end - 1, v->size);
+ if (l == -1 || (size_t)l >= sizeof(rb)) {
+ http_response(req, 500, NULL, 0);
+ return (KORE_RESULT_OK);
+ }
+
+ kore_log(LOG_NOTICE, "%p: %s sending: %ld-%ld/%ld",
+ req->owner, v->path, start, end - 1, v->size);
+ http_response_header(req, "content-range", rb);
+ } else {
+ start = 0;
+ status = 200;
+ end = v->size;
+ }
+
+ http_response_header(req, "content-type", ctype);
+ http_response_header(req, "accept-ranges", "bytes");
+ http_response_stream(req, status, v->data + start,
+ end - start, video_stream_finish, v);
+
+ return (KORE_RESULT_OK);
+}
+
+static int
+video_open(struct http_request *req, struct video **out)
+{
+ int l;
+ struct stat st;
+ struct video *v;
+ char fpath[MAXPATHLEN];
+
+ l = snprintf(fpath, sizeof(fpath), "videos%s", req->path);
+ if (l == -1 || (size_t)l >= sizeof(fpath))
+ return (KORE_RESULT_ERROR);
+
+ TAILQ_FOREACH(v, &videos, list) {
+ if (!strcmp(v->path, fpath)) {
+ if (video_mmap(req, v)) {
+ *out = v;
+ return (KORE_RESULT_OK);
+ }
+
+ close(v->fd);
+ TAILQ_REMOVE(&videos, v, list);
+ kore_mem_free(v->path);
+ kore_mem_free(v);
+
+ http_response(req, 500, NULL, 0);
+ return (KORE_RESULT_ERROR);
+ }
+ }
+
+ v = kore_malloc(sizeof(*v));
+ v->ref = 0;
+ v->base = NULL;
+ v->data = NULL;
+ v->path = kore_strdup(fpath);
+
+ if ((v->fd = open(fpath, O_RDONLY)) == -1) {
+ kore_mem_free(v->path);
+ kore_mem_free(v);
+
+ if (errno == ENOENT)
+ http_response(req, 404, NULL, 0);
+ else
+ http_response(req, 500, NULL, 0);
+
+ return (KORE_RESULT_ERROR);
+ }
+
+ if (fstat(v->fd, &st) == -1) {
+ close(v->fd);
+ kore_mem_free(v->path);
+ kore_mem_free(v);
+
+ http_response(req, 500, NULL, 0);
+ return (KORE_RESULT_ERROR);
+ }
+
+ v->size = st.st_size;
+ if (!video_mmap(req, v)) {
+ close(v->fd);
+ kore_mem_free(v->path);
+ kore_mem_free(v);
+
+ http_response(req, 500, NULL, 0);
+ return (KORE_RESULT_ERROR);
+ }
+
+ *out = v;
+ TAILQ_INSERT_TAIL(&videos, v, list);
+
+ return (KORE_RESULT_OK);
+}
+
+static int
+video_mmap(struct http_request *req, struct video *v)
+{
+ if (v->base != NULL && v->data != NULL) {
+ v->ref++;
+ return (KORE_RESULT_OK);
+ }
+
+ v->base = mmap(NULL, v->size, PROT_READ, MAP_SHARED, v->fd, 0);
+ if (v->base == MAP_FAILED)
+ return (KORE_RESULT_ERROR);
+
+ v->ref++;
+ v->data = v->base;
+
+ return (KORE_RESULT_OK);
+}
+
+static int
+video_stream_finish(struct netbuf *nb)
+{
+ struct video *v = nb->extra;
+
+ v->ref--;
+ kore_log(LOG_NOTICE, "%p: video stream %s done (%d/%d ref:%d)",
+ nb->owner, v->path, nb->s_off, nb->b_len, v->ref);
+
+ if (v->ref == 0)
+ video_unmap(v);
+
+ return (KORE_RESULT_OK);
+}
+
+static void
+video_unmap(struct video *v)
+{
+ if (munmap(v->base, v->size) == -1) {
+ kore_log(LOG_ERR, "munmap(%s): %s", v->path, errno_s);
+ } else {
+ v->base = NULL;
+ v->data = NULL;
+ kore_log(LOG_NOTICE,
+ "unmapped %s for streaming, no refs left", v->path);
+ }
+}
diff --git a/examples/video_stream/videos/placeholder b/examples/video_stream/videos/placeholder
@@ -0,0 +1 @@
+Drop your HTML5 videos in here