summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorhyperion <hyperion@spacekookie.de>2020-10-30 06:18:53 +0100
committerhyperion <hyperion@spacekookie.de>2020-10-30 06:18:53 +0100
commitb0e2d9a77155e78502682276ce38d1278b45f4a7 (patch)
tree99b68df241fe1db088a32b96049125a0fce6c196
Initial commit
-rw-r--r--README.md23
-rw-r--r--brook-metrics/.gitignore1
-rw-r--r--brook-metrics/Cargo.lock767
-rw-r--r--brook-metrics/Cargo.toml16
-rw-r--r--brook-metrics/build3
-rw-r--r--brook-metrics/metrics.csv52
-rw-r--r--brook-metrics/shell.nix6
-rw-r--r--brook-metrics/src/main.rs152
-rw-r--r--index.html97
-rwxr-xr-xstatic/ajax.min.js2
-rw-r--r--static/main.css69
-rw-r--r--static/test-video.webmbin0 -> 4685023 bytes
12 files changed, 1188 insertions, 0 deletions
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..1003850
--- /dev/null
+++ b/README.md
@@ -0,0 +1,23 @@
+<div align="center">
+ <img src="./logo.svg" />
+ <h1>brook</h1>
+</div>
+
+A minimal self-hostable streaming setup.
+
+
+## Setup
+
+`brook` is a modular project, meaning that many different components
+come together to provide a simple and relatively lightweight streaming
+setup. Following is a quick overview of components.
+
+* `ffmpeg` - running an rtmp server, and converting video to `dash`
+ output format
+* `nginx` - serves the `dash` directory
+* `index.html` - static html file with js for a dash player and xmpp
+ web chat
+* `brook-metrics` - the only custom server-side program to track
+ viewer metrics
+* `prosody` - An XMPP server to back the chat room
+
diff --git a/brook-metrics/.gitignore b/brook-metrics/.gitignore
new file mode 100644
index 0000000..ea8c4bf
--- /dev/null
+++ b/brook-metrics/.gitignore
@@ -0,0 +1 @@
+/target
diff --git a/brook-metrics/Cargo.lock b/brook-metrics/Cargo.lock
new file mode 100644
index 0000000..853c1ee
--- /dev/null
+++ b/brook-metrics/Cargo.lock
@@ -0,0 +1,767 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+[[package]]
+name = "arc-swap"
+version = "0.4.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4d25d88fd6b8041580a654f9d0c581a047baee2b3efee13275f2fc392fc75034"
+
+[[package]]
+name = "autocfg"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
+
+[[package]]
+name = "bitflags"
+version = "1.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
+
+[[package]]
+name = "brook-metrics"
+version = "0.1.0"
+dependencies = [
+ "chrono",
+ "hyper",
+ "json",
+ "lazy_static",
+ "ratman-identity",
+ "tokio",
+]
+
+[[package]]
+name = "bytes"
+version = "0.5.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0e4cec68f03f32e44924783795810fa50a7035d8c8ebe78580ad7e6c703fba38"
+
+[[package]]
+name = "cfg-if"
+version = "0.1.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
+
+[[package]]
+name = "chrono"
+version = "0.4.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73"
+dependencies = [
+ "libc",
+ "num-integer",
+ "num-traits",
+ "time",
+ "winapi 0.3.9",
+]
+
+[[package]]
+name = "fnv"
+version = "1.0.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
+
+[[package]]
+name = "fuchsia-zircon"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82"
+dependencies = [
+ "bitflags",
+ "fuchsia-zircon-sys",
+]
+
+[[package]]
+name = "fuchsia-zircon-sys"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7"
+
+[[package]]
+name = "futures-channel"
+version = "0.3.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0448174b01148032eed37ac4aed28963aaaa8cfa93569a08e5b479bbc6c2c151"
+dependencies = [
+ "futures-core",
+]
+
+[[package]]
+name = "futures-core"
+version = "0.3.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "18eaa56102984bed2c88ea39026cff3ce3b4c7f508ca970cedf2450ea10d4e46"
+
+[[package]]
+name = "futures-sink"
+version = "0.3.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0e3ca3f17d6e8804ae5d3df7a7d35b2b3a6fe89dac84b31872720fc3060a0b11"
+
+[[package]]
+name = "futures-task"
+version = "0.3.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "96d502af37186c4fef99453df03e374683f8a1eec9dcc1e66b3b82dc8278ce3c"
+
+[[package]]
+name = "futures-util"
+version = "0.3.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "abcb44342f62e6f3e8ac427b8aa815f724fd705dfad060b18ac7866c15bb8e34"
+dependencies = [
+ "futures-core",
+ "futures-task",
+ "pin-project 1.0.1",
+ "pin-utils",
+]
+
+[[package]]
+name = "getrandom"
+version = "0.1.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fc587bc0ec293155d5bfa6b9891ec18a1e330c234f896ea47fbada4cadbe47e6"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "wasi 0.9.0+wasi-snapshot-preview1",
+]
+
+[[package]]
+name = "h2"
+version = "0.2.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5e4728fd124914ad25e99e3d15a9361a879f6620f63cb56bbb08f95abb97a535"
+dependencies = [
+ "bytes",
+ "fnv",
+ "futures-core",
+ "futures-sink",
+ "futures-util",
+ "http",
+ "indexmap",
+ "slab",
+ "tokio",
+ "tokio-util",
+ "tracing",
+ "tracing-futures",
+]
+
+[[package]]
+name = "hashbrown"
+version = "0.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d7afe4a420e3fe79967a00898cc1f4db7c8a49a9333a29f8a4bd76a253d5cd04"
+
+[[package]]
+name = "hermit-abi"
+version = "0.1.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5aca5565f760fb5b220e499d72710ed156fdb74e631659e99377d9ebfbd13ae8"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "hex"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "644f9158b2f133fd50f5fb3242878846d9eb792e445c893805ff0e3824006e35"
+
+[[package]]
+name = "http"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "28d569972648b2c512421b5f2a405ad6ac9666547189d0c5477a3f200f3e02f9"
+dependencies = [
+ "bytes",
+ "fnv",
+ "itoa",
+]
+
+[[package]]
+name = "http-body"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "13d5ff830006f7646652e057693569bfe0d51760c0085a071769d142a205111b"
+dependencies = [
+ "bytes",
+ "http",
+]
+
+[[package]]
+name = "httparse"
+version = "1.3.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cd179ae861f0c2e53da70d892f5f3029f9594be0c41dc5269cd371691b1dc2f9"
+
+[[package]]
+name = "httpdate"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "494b4d60369511e7dea41cf646832512a94e542f68bb9c49e54518e0f468eb47"
+
+[[package]]
+name = "hyper"
+version = "0.13.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2f3afcfae8af5ad0576a31e768415edb627824129e8e5a29b8bfccb2f234e835"
+dependencies = [
+ "bytes",
+ "futures-channel",
+ "futures-core",
+ "futures-util",
+ "h2",
+ "http",
+ "http-body",
+ "httparse",
+ "httpdate",
+ "itoa",
+ "pin-project 0.4.27",
+ "socket2",
+ "tokio",
+ "tower-service",
+ "tracing",
+ "want",
+]
+
+[[package]]
+name = "indexmap"
+version = "1.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "55e2e4c765aa53a0424761bf9f41aa7a6ac1efa87238f59560640e27fca028f2"
+dependencies = [
+ "autocfg",
+ "hashbrown",
+]
+
+[[package]]
+name = "iovec"
+version = "0.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b2b3ea6ff95e175473f8ffe6a7eb7c00d054240321b84c57051175fe3c1e075e"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "itoa"
+version = "0.4.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dc6f3ad7b9d11a0c00842ff8de1b60ee58661048eb8049ed33c73594f359d7e6"
+
+[[package]]
+name = "json"
+version = "0.12.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "078e285eafdfb6c4b434e0d31e8cfcb5115b651496faca5749b88fafd4f23bfd"
+
+[[package]]
+name = "kernel32-sys"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d"
+dependencies = [
+ "winapi 0.2.8",
+ "winapi-build",
+]
+
+[[package]]
+name = "lazy_static"
+version = "1.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
+
+[[package]]
+name = "libc"
+version = "0.2.80"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4d58d1b70b004888f764dfbf6a26a3b0342a1632d33968e4a179d8011c760614"
+
+[[package]]
+name = "log"
+version = "0.4.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4fabed175da42fed1fa0746b0ea71f412aa9d35e76e95e59b192c64b9dc2bf8b"
+dependencies = [
+ "cfg-if",
+]
+
+[[package]]
+name = "memchr"
+version = "2.3.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0ee1c47aaa256ecabcaea351eae4a9b01ef39ed810004e298d2511ed284b1525"
+
+[[package]]
+name = "mio"
+version = "0.6.22"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fce347092656428bc8eaf6201042cb551b8d67855af7374542a92a0fbfcac430"
+dependencies = [
+ "cfg-if",
+ "fuchsia-zircon",
+ "fuchsia-zircon-sys",
+ "iovec",
+ "kernel32-sys",
+ "libc",
+ "log",
+ "miow 0.2.1",
+ "net2",
+ "slab",
+ "winapi 0.2.8",
+]
+
+[[package]]
+name = "mio-named-pipes"
+version = "0.1.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0840c1c50fd55e521b247f949c241c9997709f23bd7f023b9762cd561e935656"
+dependencies = [
+ "log",
+ "mio",
+ "miow 0.3.5",
+ "winapi 0.3.9",
+]
+
+[[package]]
+name = "mio-uds"
+version = "0.6.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "afcb699eb26d4332647cc848492bbc15eafb26f08d0304550d5aa1f612e066f0"
+dependencies = [
+ "iovec",
+ "libc",
+ "mio",
+]
+
+[[package]]
+name = "miow"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8c1f2f3b1cf331de6896aabf6e9d55dca90356cc9960cca7eaaf408a355ae919"
+dependencies = [
+ "kernel32-sys",
+ "net2",
+ "winapi 0.2.8",
+ "ws2_32-sys",
+]
+
+[[package]]
+name = "miow"
+version = "0.3.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "07b88fb9795d4d36d62a012dfbf49a8f5cf12751f36d31a9dbe66d528e58979e"
+dependencies = [
+ "socket2",
+ "winapi 0.3.9",
+]
+
+[[package]]
+name = "net2"
+version = "0.2.35"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3ebc3ec692ed7c9a255596c67808dee269f64655d8baf7b4f0638e51ba1d6853"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "winapi 0.3.9",
+]
+
+[[package]]
+name = "num-integer"
+version = "0.1.44"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db"
+dependencies = [
+ "autocfg",
+ "num-traits",
+]
+
+[[package]]
+name = "num-traits"
+version = "0.2.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
+name = "num_cpus"
+version = "1.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3"
+dependencies = [
+ "hermit-abi",
+ "libc",
+]
+
+[[package]]
+name = "pin-project"
+version = "0.4.27"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2ffbc8e94b38ea3d2d8ba92aea2983b503cd75d0888d75b86bb37970b5698e15"
+dependencies = [
+ "pin-project-internal 0.4.27",
+]
+
+[[package]]
+name = "pin-project"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ee41d838744f60d959d7074e3afb6b35c7456d0f61cad38a24e35e6553f73841"
+dependencies = [
+ "pin-project-internal 1.0.1",
+]
+
+[[package]]
+name = "pin-project-internal"
+version = "0.4.27"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "65ad2ae56b6abe3a1ee25f15ee605bacadb9a764edaba9c2bf4103800d4a1895"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "pin-project-internal"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "81a4ffa594b66bff340084d4081df649a7dc049ac8d7fc458d8e628bfbbb2f86"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "pin-project-lite"
+version = "0.1.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c917123afa01924fc84bb20c4c03f004d9c38e5127e3c039bbf7f4b9c76a2f6b"
+
+[[package]]
+name = "pin-utils"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
+
+[[package]]
+name = "ppv-lite86"
+version = "0.2.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c36fa947111f5c62a733b652544dd0016a43ce89619538a8ef92724a6f501a20"
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.24"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e0704ee1a7e00d7bb417d0770ea303c1bccbabf0ef1667dae92b5967f5f8a71"
+dependencies = [
+ "unicode-xid",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "aa563d17ecb180e500da1cfd2b028310ac758de548efdd203e18f283af693f37"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "rand"
+version = "0.7.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03"
+dependencies = [
+ "getrandom",
+ "libc",
+ "rand_chacha",
+ "rand_core",
+ "rand_hc",
+]
+
+[[package]]
+name = "rand_chacha"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402"
+dependencies = [
+ "ppv-lite86",
+ "rand_core",
+]
+
+[[package]]
+name = "rand_core"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19"
+dependencies = [
+ "getrandom",
+]
+
+[[package]]
+name = "rand_hc"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c"
+dependencies = [
+ "rand_core",
+]
+
+[[package]]
+name = "ratman-identity"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "463e8a9937dd2525717feb9f85fbf5da115076eb812b894e255d6ce00bdda289"
+dependencies = [
+ "cfg-if",
+ "hex",
+ "rand",
+ "serde",
+]
+
+[[package]]
+name = "redox_syscall"
+version = "0.1.57"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce"
+
+[[package]]
+name = "serde"
+version = "1.0.117"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b88fa983de7720629c9387e9f517353ed404164b1e482c970a90c1a4aaf7dc1a"
+dependencies = [
+ "serde_derive",
+]
+
+[[package]]
+name = "serde_derive"
+version = "1.0.117"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cbd1ae72adb44aab48f325a02444a5fc079349a8d804c1fc922aed3f7454c74e"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "signal-hook-registry"
+version = "1.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a3e12110bc539e657a646068aaf5eb5b63af9d0c1f7b29c97113fad80e15f035"
+dependencies = [
+ "arc-swap",
+ "libc",
+]
+
+[[package]]
+name = "slab"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8"
+
+[[package]]
+name = "socket2"
+version = "0.3.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b1fa70dc5c8104ec096f4fe7ede7a221d35ae13dcd19ba1ad9a81d2cab9a1c44"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "redox_syscall",
+ "winapi 0.3.9",
+]
+
+[[package]]
+name = "syn"
+version = "1.0.48"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cc371affeffc477f42a221a1e4297aedcea33d47d19b61455588bd9d8f6b19ac"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-xid",
+]
+
+[[package]]
+name = "time"
+version = "0.1.44"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255"
+dependencies = [
+ "libc",
+ "wasi 0.10.0+wasi-snapshot-preview1",
+ "winapi 0.3.9",
+]
+
+[[package]]
+name = "tokio"
+version = "0.2.22"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5d34ca54d84bf2b5b4d7d31e901a8464f7b60ac145a284fba25ceb801f2ddccd"
+dependencies = [
+ "bytes",
+ "fnv",
+ "futures-core",
+ "iovec",
+ "lazy_static",
+ "libc",
+ "memchr",
+ "mio",
+ "mio-named-pipes",
+ "mio-uds",
+ "num_cpus",
+ "pin-project-lite",
+ "signal-hook-registry",
+ "slab",
+ "tokio-macros",
+ "winapi 0.3.9",
+]
+
+[[package]]
+name = "tokio-macros"
+version = "0.2.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f0c3acc6aa564495a0f2e1d59fab677cd7f81a19994cfc7f3ad0e64301560389"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "tokio-util"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "be8242891f2b6cbef26a2d7e8605133c2c554cd35b3e4948ea892d6d68436499"
+dependencies = [
+ "bytes",
+ "futures-core",
+ "futures-sink",
+ "log",
+ "pin-project-lite",
+ "tokio",
+]
+
+[[package]]
+name = "tower-service"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e987b6bf443f4b5b3b6f38704195592cca41c5bb7aedd3c3693c7081f8289860"
+
+[[package]]
+name = "tracing"
+version = "0.1.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b0987850db3733619253fe60e17cb59b82d37c7e6c0236bb81e4d6b87c879f27"
+dependencies = [
+ "cfg-if",
+ "log",
+ "pin-project-lite",
+ "tracing-core",
+]
+
+[[package]]
+name = "tracing-core"
+version = "0.1.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f50de3927f93d202783f4513cda820ab47ef17f624b03c096e86ef00c67e6b5f"
+dependencies = [
+ "lazy_static",
+]
+
+[[package]]
+name = "tracing-futures"
+version = "0.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ab7bb6f14721aa00656086e9335d363c5c8747bae02ebe32ea2c7dece5689b4c"
+dependencies = [
+ "pin-project 0.4.27",
+ "tracing",
+]
+
+[[package]]
+name = "try-lock"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642"
+
+[[package]]
+name = "unicode-xid"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564"
+
+[[package]]
+name = "want"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0"
+dependencies = [
+ "log",
+ "try-lock",
+]
+
+[[package]]
+name = "wasi"
+version = "0.9.0+wasi-snapshot-preview1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519"
+
+[[package]]
+name = "wasi"
+version = "0.10.0+wasi-snapshot-preview1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f"
+
+[[package]]
+name = "winapi"
+version = "0.2.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a"
+
+[[package]]
+name = "winapi"
+version = "0.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
+dependencies = [
+ "winapi-i686-pc-windows-gnu",
+ "winapi-x86_64-pc-windows-gnu",
+]
+
+[[package]]
+name = "winapi-build"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc"
+
+[[package]]
+name = "winapi-i686-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
+
+[[package]]
+name = "winapi-x86_64-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
+
+[[package]]
+name = "ws2_32-sys"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d59cefebd0c892fa2dd6de581e937301d8552cb44489cdff035c6187cb63fa5e"
+dependencies = [
+ "winapi 0.2.8",
+ "winapi-build",
+]
diff --git a/brook-metrics/Cargo.toml b/brook-metrics/Cargo.toml
new file mode 100644
index 0000000..a19ccfc
--- /dev/null
+++ b/brook-metrics/Cargo.toml
@@ -0,0 +1,16 @@
+[package]
+name = "brook-metrics"
+description = "A server to handle brook metric reporting"
+version = "0.1.0"
+authors = ["Mx Kookie <kookie@spacekookie.de>"]
+edition = "2018"
+
+[dependencies]
+ratman-identity = { version = "*", features = ["random", "aligned"] }
+chrono = "0.4"
+hyper = "0.13"
+json = "0.12"
+lazy_static = "1.0"
+
+#TODO: remove this when async-h2 comes out
+tokio = { version = "0.2", features = ["full"] } \ No newline at end of file
diff --git a/brook-metrics/build b/brook-metrics/build
new file mode 100644
index 0000000..67e8e8a
--- /dev/null
+++ b/brook-metrics/build
@@ -0,0 +1,3 @@
+#!/bin/sh
+
+nix-shell --run "cargo build" \ No newline at end of file
diff --git a/brook-metrics/metrics.csv b/brook-metrics/metrics.csv
new file mode 100644
index 0000000..cf55087
--- /dev/null
+++ b/brook-metrics/metrics.csv
@@ -0,0 +1,52 @@
+2020-10-30_04:26:57 UTC,0
+2020-10-30_04:27:12 UTC,2
+2020-10-30_04:27:27 UTC,2
+2020-10-30_04:27:42 UTC,2
+2020-10-30_04:27:57 UTC,2
+2020-10-30_04:28:12 UTC,2
+2020-10-30_04:28:27 UTC,1
+2020-10-30_04:39:47 UTC,0
+2020-10-30_04:40:02 UTC,1
+2020-10-30_04:40:17 UTC,1
+2020-10-30_04:40:32 UTC,1
+2020-10-30_04:40:47 UTC,2
+2020-10-30_04:41:02 UTC,2
+2020-10-30_04:41:17 UTC,2
+2020-10-30_04:41:32 UTC,2
+2020-10-30_04:41:47 UTC,2
+2020-10-30_04:42:04 UTC,0
+2020-10-30_04:42:19 UTC,2
+2020-10-30_04:42:34 UTC,2
+2020-10-30_04:42:49 UTC,2
+2020-10-30_04:43:04 UTC,2
+2020-10-30_04:43:19 UTC,2
+2020-10-30_04:43:34 UTC,2
+2020-10-30_04:43:49 UTC,2
+2020-10-30_04:44:04 UTC,2
+2020-10-30_04:44:19 UTC,2
+2020-10-30_04:44:54 UTC,0
+2020-10-30_04:45:09 UTC,2
+2020-10-30_04:46:15 UTC,0
+2020-10-30_04:46:30 UTC,2
+2020-10-30_04:46:45 UTC,2
+2020-10-30_04:46:57 UTC,0
+2020-10-30_04:47:12 UTC,2
+2020-10-30_04:47:27 UTC,3
+2020-10-30_04:55:41 UTC,0
+2020-10-30_04:55:56 UTC,1
+2020-10-30_04:56:11 UTC,1
+2020-10-30_05:00:45 UTC,0
+2020-10-30_05:01:00 UTC,0
+2020-10-30_05:01:15 UTC,0
+2020-10-30_05:01:30 UTC,0
+2020-10-30_05:01:45 UTC,0
+2020-10-30_05:02:00 UTC,0
+2020-10-30_05:02:15 UTC,0
+2020-10-30_05:02:42 UTC,0
+2020-10-30_05:02:57 UTC,1
+2020-10-30_05:03:12 UTC,3
+2020-10-30_05:04:15 UTC,0
+2020-10-30_05:04:30 UTC,1
+2020-10-30_05:04:45 UTC,1
+2020-10-30_05:05:00 UTC,2
+2020-10-30_05:05:15 UTC,2
diff --git a/brook-metrics/shell.nix b/brook-metrics/shell.nix
new file mode 100644
index 0000000..2fa04ad
--- /dev/null
+++ b/brook-metrics/shell.nix
@@ -0,0 +1,6 @@
+with import <nixpkgs> {};
+
+stdenv.mkDerivation {
+ name = "brook-metrics";
+ buildInputs = with pkgs; [ rustup clangStdenv ];
+}
diff --git a/brook-metrics/src/main.rs b/brook-metrics/src/main.rs
new file mode 100644
index 0000000..649ddd9
--- /dev/null
+++ b/brook-metrics/src/main.rs
@@ -0,0 +1,152 @@
+use chrono::Utc;
+use hyper::{
+ service::{make_service_fn, service_fn},
+ Body, Method, Request, Response, Server, StatusCode,
+};
+use lazy_static::lazy_static;
+use ratman_identity::Identity;
+use std::{
+ collections::BTreeMap,
+ convert::Infallible,
+ env,
+ fs::OpenOptions,
+ io::Write,
+ net::SocketAddr,
+ path::PathBuf,
+ sync::{
+ atomic::{AtomicUsize, Ordering},
+ Mutex,
+ },
+ thread,
+ time::Duration,
+};
+
+lazy_static! {
+ static ref STATE: Mutex<BTreeMap<Identity, MetricEntry>> = Mutex::new(BTreeMap::new());
+}
+
+/// An entry of metrics data
+#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
+struct MetricEntry {}
+
+fn dump_metrics() {
+ let p = env::var("BROOK_METRICS_PATH").unwrap_or("metrics.csv".into());
+ let path = PathBuf::new().join(p);
+
+ loop {
+ let time = Utc::now();
+ let mut map = STATE.lock().unwrap();
+ let metrics: Vec<()> = map.iter().map(|_| ()).collect();
+ map.clear();
+ drop(map);
+
+ let line = format!(
+ "{},{}\n",
+ time.format("%Y-%m-%d_%H:%M:%S UTC"),
+ metrics.len(),
+ );
+ let mut file = OpenOptions::new()
+ .write(true)
+ .append(true)
+ .create(true)
+ .open(&path)
+ .unwrap();
+
+ file.write_all(line.as_bytes()).unwrap();
+
+ // Wait for a while before doing it again
+ thread::sleep(Duration::from_secs(15));
+ }
+}
+
+fn get() -> Result<Response<Body>, Infallible> {
+ let mut resp = Response::new(Body::empty());
+ *resp.body_mut() = Body::from(
+ "<!DOCTYPE html>
+<html>
+<head><title>brook-metrics</title></head>
+<body>
+<p>
+Your brook-metrics server is now reachable! \
+Point your brook clients towards $ADDR/report
+</p>
+</body>
+</html>",
+ );
+
+ Ok(resp)
+}
+
+async fn post(req: Request<Body>) -> Result<Response<Body>, Infallible> {
+ let full_body = hyper::body::to_bytes(req.into_body()).await.unwrap();
+ let body_str = String::from_utf8(full_body.to_vec()).unwrap();
+
+ let parsed = json::parse(&body_str).unwrap();
+
+ // Either get the ID or create a new one
+ let id = match parsed {
+ json::JsonValue::Object(mut obj) => obj.remove("id"),
+ _ => unreachable!(),
+ }
+ .map(|jv| match jv {
+ json::JsonValue::String(ref s) => Identity::from_string(s),
+ _ => unreachable!(),
+ })
+ .map(|id| {
+ eprintln!("[DEBUG] Returning client {}", id);
+ id
+ })
+ .unwrap_or_else(|| {
+ eprintln!("[DEBUG] Adding new stream client!");
+ Identity::random()
+ });
+
+ STATE.lock().unwrap().insert(id, MetricEntry {});
+
+ Ok(Response::builder()
+ .header("Content-Type", "text/json")
+ .status(StatusCode::OK)
+ .body(Body::from(format!("{{ \"id\": \"{}\" }}", id.to_string())))
+ .unwrap())
+}
+
+fn get_current() -> Result<Response<Body>, Infallible> {
+ let len = STATE.lock().unwrap().len();
+ Ok(Response::builder()
+ .header("Content-Type", "text/json")
+ .status(StatusCode::OK)
+ .body(Body::from(format!("{{ \"num\": {} }}", len)))
+ .unwrap())
+}
+
+async fn handle_request(req: Request<Body>) -> Result<Response<Body>, Infallible> {
+ match (req.method(), req.uri().path()) {
+ // TODO: make this prefix configurable
+ (&Method::GET, "/metrics") => get(),
+ (&Method::GET, "/metrics/current") => get_current(),
+ (&Method::POST, "/metrics/update") => post(req).await,
+ _ => {
+ let resp = Response::builder()
+ .status(StatusCode::NOT_FOUND)
+ .body(Body::from("Nothing here, I'm afraid!"))
+ .unwrap();
+
+ Ok(resp)
+ }
+ }
+}
+
+#[tokio::main]
+async fn main() {
+ thread::spawn(|| dump_metrics());
+
+ let addr = SocketAddr::from(([0, 0, 0, 0], 7667));
+ let service = make_service_fn(|_c| async { Ok::<_, Infallible>(service_fn(handle_request)) });
+
+ eprintln!("Bindng address: 0.0.0.0:7667");
+ let server = Server::bind(&addr).serve(service);
+
+ if let Err(e) = server.await {
+ eprintln!("[ERROR]: {}", e);
+ }
+}
diff --git a/index.html b/index.html
new file mode 100644
index 0000000..5eb8fac
--- /dev/null
+++ b/index.html
@@ -0,0 +1,97 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="UTF-8">
+
+ <title>stream.spacekookie.de</title>
+ <link href="/static/main.css" rel="stylesheet">
+ <script src="/static/ajax.min.js" charset="utf-8"></script>
+
+ <link rel="stylesheet" type="text/css" media="screen" href="https://cdn.conversejs.org/6.0.0/dist/converse.min.css">
+ <script src="https://cdn.conversejs.org/6.0.0/dist/converse.min.js" charset="utf-8"></script>
+ <script src="https://cdn.dashjs.org/latest/dash.mediaplayer.min.js" charset="utf-8"></script>
+ </head>
+
+ <!-- Main site body -->
+ <body>
+ <header>
+ <h1>stream.spacekookie.de</h1>
+ <p class="stream-status">OFFLINE</p>
+ <!-- <p class="stream-status">LIVE</p> -->
+ </header>
+
+
+ <div class="block">
+ <h2>Welcome to the stream! ✨</h2>
+ <p>
+ The stream chat is backed by an XMPP
+ room: <a href="xmpp:stream@rooms.spacekookie.de?join"><em><code>stream@rooms.spacekookie.de</code></em></a> — Feel free to join
+ it from your favourite XMPP client to say hello!
+ </p>
+ </div>
+
+ <div class="content">
+ <video class="stream" id="stream" controls>
+ <!-- <source src="/static/test-video.webm" type="video/webm"> -->
+ Your browser does not support the video tag!
+ </video>
+
+ <div class="chat">
+ <div id="conversejs"></div>
+ </div>
+ </div>
+
+ <div class="block">
+ <p id="view-count">Viewers: ?</p>
+ </div>
+
+ </body>
+
+ <!-- Initialise converse.js here -->
+ <script>
+ converse.initialize({
+ bosh_service_url: 'https://stream.spacekookie.de/xmpp-bosh',
+ authentication: 'anonymous',
+ auto_login: true,
+ auto_reconnect: true,
+ muc_disable_slash_commands: true,
+ auto_join_rooms: ['stream@rooms.spacekookie.de'],
+ singleton: true,
+ view_mode: 'embedded',
+ theme: 'concord',
+ jid: 'guest-xmpp.spacekookie.de',
+ show_controlbox_by_default: true,
+ });
+
+ var client_id;
+ var url = "https://stream.spacekookie.de/dash/app.mpd";
+ var player = dashjs.MediaPlayer().create();
+ var stream = document.getElementById("stream");
+ player.initialize(stream, url, true);
+ player.on(dashjs.MediaPlayer.events['PLAYBACK_ENDED'], function() {
+ clearInterval(metricPoller);
+ clearInterval(currentPoller);
+ });
+
+ var eventPoller = setInterval(function() {
+ console.log("Letting the backend know we're watching");
+
+ console.log("We are id: " + client_id);
+ Ajax.post('https://stream.spacekookie.de/metrics/update',
+ { id: client_id })
+ .then(function(response) {
+ console.log("We were assigned ID: " + response.id);
+ client_id = response.id;
+ })
+ }, 5000);
+
+ var currentPoller = setInterval(function() {
+ Ajax.get('https://stream.spacekookie.de/metrics/current')
+ .then(function(resp) {
+ document.getElementById("view-count").innerHTML = "Viewers: " + resp.num;
+ });
+
+ }, 10000);
+
+ </script>
+</html>
diff --git a/static/ajax.min.js b/static/ajax.min.js
new file mode 100755
index 0000000..9697988
--- /dev/null
+++ b/static/ajax.min.js
@@ -0,0 +1,2 @@
+/* ainojs-ajax v1.1.2 - October 9th 2015 - (c) Aino - MIT Licensed */
+!function(e){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=e();else if("function"==typeof define&&define.amd)define([],e);else{var t;"undefined"!=typeof window?t=window:"undefined"!=typeof global?t=global:"undefined"!=typeof self&&(t=self),t.Ajax=e()}}(function(){return function e(t,n,r){function i(s,u){if(!n[s]){if(!t[s]){var f="function"==typeof require&&require;if(!u&&f)return f(s,!0);if(o)return o(s,!0);throw new Error("Cannot find module '"+s+"'")}var c=n[s]={exports:{}};t[s][0].call(c.exports,function(e){var n=t[s][1][e];return i(n?n:e)},c,c.exports,e,t,n,r)}return n[s].exports}for(var o="function"==typeof require&&require,s=0;s<r.length;s++)i(r[s]);return i}({1:[function(e,t){var n=e("promise"),r=0,i=function(e){var t;try{t=JSON.parse(e.responseText)}catch(n){t=e.responseText}return t},o=function(e,t,r){return new n(function(n,o){var s=window.XMLHttpRequest||ActiveXObject,u=new s("MSXML2.XMLHTTP.3.0");u.open(e,t,!0),u.setRequestHeader("Content-type","application/json; charset=utf-8"),u.onreadystatechange=function(){4===u.readyState&&(200===u.status?n(i(u)):o(i(u)))},u.send(r)})};t.exports.post=function(e,t){return o("POST",e,JSON.stringify(t))},t.exports.get=function(e){return o("GET",e)},t.exports.jsonp=function(e,t,i,o){return i=i||"callback",o=o||15e3,new n(function(n,s){if("undefined"==typeof window)return s("No document");var u="jsonp"+Date.now()+btoa(navigator.userAgent).replace(/[^\d]+/g,"")+r++,f=document.createElement("script"),c="",a=setTimeout(function(){window[u]=function(){delete window[u]},document.body.removeChild(f),s(o+"ms timeout reached without response.")},o);if("object"==typeof t)for(var l in t)c+="&"+l+"="+t[l];f.src=e+"?"+i+"="+u+c,window[u]=function(e){clearTimeout(a),document.body.removeChild(f),delete window[u],n(e)},document.body.appendChild(f)})}},{promise:2}],2:[function(e,t){"use strict";t.exports=e("./lib")},{"./lib":7}],3:[function(e,t){"use strict";function n(){}function r(e){try{return e.then}catch(t){return d=t,m}}function i(e,t){try{return e(t)}catch(n){return d=n,m}}function o(e,t,n){try{e(t,n)}catch(r){return d=r,m}}function s(e){if("object"!=typeof this)throw new TypeError("Promises must be constructed via new");if("function"!=typeof e)throw new TypeError("not a function");this._37=0,this._12=null,this._59=[],e!==n&&p(e,this)}function u(e,t,r){return new e.constructor(function(i,o){var u=new s(n);u.then(i,o),f(e,new h(t,r,u))})}function f(e,t){for(;3===e._37;)e=e._12;return 0===e._37?(e._59.push(t),void 0):(v(function(){var n=1===e._37?t.onFulfilled:t.onRejected;if(null===n)return 1===e._37?c(t.promise,e._12):a(t.promise,e._12),void 0;var r=i(n,e._12);r===m?a(t.promise,d):c(t.promise,r)}),void 0)}function c(e,t){if(t===e)return a(e,new TypeError("A promise cannot be resolved with itself."));if(t&&("object"==typeof t||"function"==typeof t)){var n=r(t);if(n===m)return a(e,d);if(n===e.then&&t instanceof s)return e._37=3,e._12=t,l(e),void 0;if("function"==typeof n)return p(n.bind(t),e),void 0}e._37=1,e._12=t,l(e)}function a(e,t){e._37=2,e._12=t,l(e)}function l(e){for(var t=0;t<e._59.length;t++)f(e,e._59[t]);e._59=null}function h(e,t,n){this.onFulfilled="function"==typeof e?e:null,this.onRejected="function"==typeof t?t:null,this.promise=n}function p(e,t){var n=!1,r=o(e,function(e){n||(n=!0,c(t,e))},function(e){n||(n=!0,a(t,e))});n||r!==m||(n=!0,a(t,d))}var v=e("asap/raw"),d=null,m={};t.exports=s,s._99=n,s.prototype.then=function(e,t){if(this.constructor!==s)return u(this,e,t);var r=new s(n);return f(this,new h(e,t,r)),r}},{"asap/raw":11}],4:[function(e,t){"use strict";var n=e("./core.js");t.exports=n,n.prototype.done=function(){var e=arguments.length?this.then.apply(this,arguments):this;e.then(null,function(e){setTimeout(function(){throw e},0)})}},{"./core.js":3}],5:[function(e,t){"use strict";function n(e){var t=new r(r._99);return t._37=1,t._12=e,t}var r=e("./core.js");t.exports=r;var i=n(!0),o=n(!1),s=n(null),u=n(void 0),f=n(0),c=n("");r.resolve=function(e){if(e instanceof r)return e;if(null===e)return s;if(void 0===e)return u;if(e===!0)return i;if(e===!1)return o;if(0===e)return f;if(""===e)return c;if("object"==typeof e||"function"==typeof e)try{var t=e.then;if("function"==typeof t)return new r(t.bind(e))}catch(a){return new r(function(e,t){t(a)})}return n(e)},r.all=function(e){var t=Array.prototype.slice.call(e);return new r(function(e,n){function i(s,u){if(u&&("object"==typeof u||"function"==typeof u)){if(u instanceof r&&u.then===r.prototype.then){for(;3===u._37;)u=u._12;return 1===u._37?i(s,u._12):(2===u._37&&n(u._12),u.then(function(e){i(s,e)},n),void 0)}var f=u.then;if("function"==typeof f){var c=new r(f.bind(u));return c.then(function(e){i(s,e)},n),void 0}}t[s]=u,0===--o&&e(t)}if(0===t.length)return e([]);for(var o=t.length,s=0;s<t.length;s++)i(s,t[s])})},r.reject=function(e){return new r(function(t,n){n(e)})},r.race=function(e){return new r(function(t,n){e.forEach(function(e){r.resolve(e).then(t,n)})})},r.prototype["catch"]=function(e){return this.then(null,e)}},{"./core.js":3}],6:[function(e,t){"use strict";var n=e("./core.js");t.exports=n,n.prototype["finally"]=function(e){return this.then(function(t){return n.resolve(e()).then(function(){return t})},function(t){return n.resolve(e()).then(function(){throw t})})}},{"./core.js":3}],7:[function(e,t){"use strict";t.exports=e("./core.js"),e("./done.js"),e("./finally.js"),e("./es6-extensions.js"),e("./node-extensions.js")},{"./core.js":3,"./done.js":4,"./es6-extensions.js":5,"./finally.js":6,"./node-extensions.js":8}],8:[function(e,t){"use strict";var n=e("./core.js"),r=e("asap");t.exports=n,n.denodeify=function(e,t){return t=t||1/0,function(){var r=this,i=Array.prototype.slice.call(arguments,0,t>0?t:0);return new n(function(t,n){i.push(function(e,r){e?n(e):t(r)});var o=e.apply(r,i);!o||"object"!=typeof o&&"function"!=typeof o||"function"!=typeof o.then||t(o)})}},n.nodeify=function(e){return function(){var t=Array.prototype.slice.call(arguments),i="function"==typeof t[t.length-1]?t.pop():null,o=this;try{return e.apply(this,arguments).nodeify(i,o)}catch(s){if(null===i||"undefined"==typeof i)return new n(function(e,t){t(s)});r(function(){i.call(o,s)})}}},n.prototype.nodeify=function(e,t){return"function"!=typeof e?this:(this.then(function(n){r(function(){e.call(t,null,n)})},function(n){r(function(){e.call(t,n)})}),void 0)}},{"./core.js":3,asap:9}],9:[function(e,t){"use strict";function n(){if(u.length)throw u.shift()}function r(e){var t;t=s.length?s.pop():new i,t.task=e,o(t)}function i(){this.task=null}var o=e("./raw"),s=[],u=[],f=o.makeRequestCallFromTimer(n);t.exports=r,i.prototype.call=function(){try{this.task.call()}catch(e){r.onerror?r.onerror(e):(u.push(e),f())}finally{this.task=null,s[s.length]=this}}},{"./raw":10}],10:[function(e,t){(function(e){"use strict";function n(e){u.length||(s(),f=!0),u[u.length]=e}function r(){for(;c<u.length;){var e=c;if(c+=1,u[e].call(),c>a){for(var t=0,n=u.length-c;n>t;t++)u[t]=u[t+c];u.length-=c,c=0}}u.length=0,c=0,f=!1}function i(e){var t=1,n=new l(e),r=document.createTextNode("");return n.observe(r,{characterData:!0}),function(){t=-t,r.data=t}}function o(e){return function(){function t(){clearTimeout(n),clearInterval(r),e()}var n=setTimeout(t,0),r=setInterval(t,50)}}t.exports=n;var s,u=[],f=!1,c=0,a=1024,l=e.MutationObserver||e.WebKitMutationObserver;s="function"==typeof l?i(r):o(r),n.requestFlush=s,n.makeRequestCallFromTimer=o}).call(this,"undefined"!=typeof self?self:"undefined"!=typeof window?window:{})},{}],11:[function(e,t){(function(n){"use strict";function r(e){f.length||(o(),c=!0),f[f.length]=e}function i(){for(;a<f.length;){var e=a;if(a+=1,f[e].call(),a>l){for(var t=0,n=f.length-a;n>t;t++)f[t]=f[t+a];f.length-=a,a=0}}f.length=0,a=0,c=!1}function o(){var t=n.domain;t&&(s||(s=e("domain")),s.active=n.domain=null),c&&u?setImmediate(i):n.nextTick(i),t&&(s.active=n.domain=t)}var s,u="function"==typeof setImmediate;t.exports=r;var f=[],c=!1,a=0,l=1024;r.requestFlush=o}).call(this,e("FWaASH"))},{FWaASH:14,domain:12}],12:[function(e,t){t.exports=function(){var t=e("events"),n={};return n.createDomain=n.create=function(){function e(e){n.emit("error",e)}var n=new t.EventEmitter;return n.add=function(t){t.on("error",e)},n.remove=function(t){t.removeListener("error",e)},n.run=function(e){try{e()}catch(t){this.emit("error",t)}return this},n.dispose=function(){return this.removeAllListeners(),this},n},n}.call(this)},{events:13}],13:[function(e,t){function n(){this._events=this._events||{},this._maxListeners=this._maxListeners||void 0}function r(e){return"function"==typeof e}function i(e){return"number"==typeof e}function o(e){return"object"==typeof e&&null!==e}function s(e){return void 0===e}t.exports=n,n.EventEmitter=n,n.prototype._events=void 0,n.prototype._maxListeners=void 0,n.defaultMaxListeners=10,n.prototype.setMaxListeners=function(e){if(!i(e)||0>e||isNaN(e))throw TypeError("n must be a positive number");return this._maxListeners=e,this},n.prototype.emit=function(e){var t,n,i,u,f,c;if(this._events||(this._events={}),"error"===e&&(!this._events.error||o(this._events.error)&&!this._events.error.length)){if(t=arguments[1],t instanceof Error)throw t;throw TypeError('Uncaught, unspecified "error" event.')}if(n=this._events[e],s(n))return!1;if(r(n))switch(arguments.length){case 1:n.call(this);break;case 2:n.call(this,arguments[1]);break;case 3:n.call(this,arguments[1],arguments[2]);break;default:for(i=arguments.length,u=new Array(i-1),f=1;i>f;f++)u[f-1]=arguments[f];n.apply(this,u)}else if(o(n)){for(i=arguments.length,u=new Array(i-1),f=1;i>f;f++)u[f-1]=arguments[f];for(c=n.slice(),i=c.length,f=0;i>f;f++)c[f].apply(this,u)}return!0},n.prototype.addListener=function(e,t){var i;if(!r(t))throw TypeError("listener must be a function");if(this._events||(this._events={}),this._events.newListener&&this.emit("newListener",e,r(t.listener)?t.listener:t),this._events[e]?o(this._events[e])?this._events[e].push(t):this._events[e]=[this._events[e],t]:this._events[e]=t,o(this._events[e])&&!this._events[e].warned){var i;i=s(this._maxListeners)?n.defaultMaxListeners:this._maxListeners,i&&i>0&&this._events[e].length>i&&(this._events[e].warned=!0,console.error("(node) warning: possible EventEmitter memory leak detected. %d listeners added. Use emitter.setMaxListeners() to increase limit.",this._events[e].length),"function"==typeof console.trace&&console.trace())}return this},n.prototype.on=n.prototype.addListener,n.prototype.once=function(e,t){function n(){this.removeListener(e,n),i||(i=!0,t.apply(this,arguments))}if(!r(t))throw TypeError("listener must be a function");var i=!1;return n.listener=t,this.on(e,n),this},n.prototype.removeListener=function(e,t){var n,i,s,u;if(!r(t))throw TypeError("listener must be a function");if(!this._events||!this._events[e])return this;if(n=this._events[e],s=n.length,i=-1,n===t||r(n.listener)&&n.listener===t)delete this._events[e],this._events.removeListener&&this.emit("removeListener",e,t);else if(o(n)){for(u=s;u-->0;)if(n[u]===t||n[u].listener&&n[u].listener===t){i=u;break}if(0>i)return this;1===n.length?(n.length=0,delete this._events[e]):n.splice(i,1),this._events.removeListener&&this.emit("removeListener",e,t)}return this},n.prototype.removeAllListeners=function(e){var t,n;if(!this._events)return this;if(!this._events.removeListener)return 0===arguments.length?this._events={}:this._events[e]&&delete this._events[e],this;if(0===arguments.length){for(t in this._events)"removeListener"!==t&&this.removeAllListeners(t);return this.removeAllListeners("removeListener"),this._events={},this}if(n=this._events[e],r(n))this.removeListener(e,n);else for(;n.length;)this.removeListener(e,n[n.length-1]);return delete this._events[e],this},n.prototype.listeners=function(e){var t;return t=this._events&&this._events[e]?r(this._events[e])?[this._events[e]]:this._events[e].slice():[]},n.listenerCount=function(e,t){var n;return n=e._events&&e._events[t]?r(e._events[t])?1:e._events[t].length:0}},{}],14:[function(e,t){function n(){}var r=t.exports={};r.nextTick=function(){var e="undefined"!=typeof window&&window.setImmediate,t="undefined"!=typeof window&&window.postMessage&&window.addEventListener;if(e)return function(e){return window.setImmediate(e)};if(t){var n=[];return window.addEventListener("message",function(e){var t=e.source;if((t===window||null===t)&&"process-tick"===e.data&&(e.stopPropagation(),n.length>0)){var r=n.shift();r()}},!0),function(e){n.push(e),window.postMessage("process-tick","*")}}return function(e){setTimeout(e,0)}}(),r.title="browser",r.browser=!0,r.env={},r.argv=[],r.on=n,r.addListener=n,r.once=n,r.off=n,r.removeListener=n,r.removeAllListeners=n,r.emit=n,r.binding=function(){throw new Error("process.binding is not supported")},r.cwd=function(){return"/"},r.chdir=function(){throw new Error("process.chdir is not supported")}},{}]},{},[1])(1)}); \ No newline at end of file
diff --git a/static/main.css b/static/main.css
new file mode 100644
index 0000000..a5305a4
--- /dev/null
+++ b/static/main.css
@@ -0,0 +1,69 @@
+body {
+ margin: 0;
+ font-family: "Roboto", sans-serif;
+ font-weight: 300;
+ display: flex;
+ flex-direction: column;
+ min-height: 100vh;
+ background: #424242;
+ color: #ffffff;
+ font-family: "Roboto", "Open Sans", "Montserrat", sans-serif;
+ font-size: 1.25em;
+
+}
+
+header {
+ display: flex;
+ flex-direction: row;
+ justify-content: space-between;
+ align-items: center;
+ background: #703f96;
+ padding: 0 2rem;
+ color: white;
+}
+
+.stream-status {
+ border-bottom: solid 1px white;
+ animation: flash 0.75s infinite alternate;
+ font-size: 1.5rem;
+}
+
+.content {
+ display: flex;
+ justify-content: center;
+ padding: 2rem 0;
+}
+
+code {
+ font-family: "Iosekva", "Inconsolata", monospace;
+ font-size: 1.25em;
+}
+
+.block {
+ display: flex;
+ flex-direction: column;
+ max-width: 75%;
+ align-self: center;
+}
+
+a {
+ color: #FF88FF;
+ text-decoration: none;
+}
+a:hover {
+ border-bottom: solid 1px #FF88FF;
+}
+
+#conversejs {
+ border: solid 2px black;
+}
+
+.chat {
+ width: 27%;
+}
+
+.stream {
+ border: solid 2px black;
+ align-self: flex-start;
+ width: 70%;
+}
diff --git a/static/test-video.webm b/static/test-video.webm
new file mode 100644
index 0000000..642e562
--- /dev/null
+++ b/static/test-video.webm
Binary files differ