diff --git a/convulse.css b/convulse.css
new file mode 100644
index 0000000..8b3dd1d
--- /dev/null
+++ b/convulse.css
@@ -0,0 +1,48 @@
+body {
+ background-color: #000;
+ color: #fff;
+ margin: 0;
+ font-family: sans-serif;
+}
+video {
+ height: 100vh;
+ width: 100vw;
+}
+canvas {
+ width: 100vw;
+ filter: grayscale(100%);
+}
+canvas.recording {
+ filter: initial;
+}
+
+.hidden {
+ display: none;
+}
+
+#download {
+ display: none;
+}
+#toasts {
+ position: fixed;
+ z-index: 2;
+ top: 0;
+ background-color: blue;
+ width: 100%;
+ opacity: 0.3;
+ text-align: center;
+}
+#controls {
+ position: fixed;
+ z-index: 2;
+ width: 100%;
+ background-color: ;
+ opacity: 0.8;
+ bottom: 0;
+}
+video {
+ display: none;
+}
+dt {
+ font-weight: bold;
+}
\ No newline at end of file
diff --git a/convulse.html b/convulse.html
index fdcce1a..ca5e89f 100644
--- a/convulse.html
+++ b/convulse.html
@@ -2,130 +2,48 @@
Convulse
-
-
+
+
- Convulse: it's sorta like Twitch!
-
+
+
Convulse: it's sorta like Twitch!
+
+ I need the following permissions:
+
+
+ - Use your microphone
+ - So I can record your velvety-smooth voice
+
+ - Use your camera
+ - So I can record your velvety-smooth face
+
+ - Share your screen
+ - So I can record your velvety-smooth computer
+
+
+
+
+
+
+
+
+
+
+
+
-
diff --git a/convulse.js b/convulse.js
new file mode 100644
index 0000000..abb66d2
--- /dev/null
+++ b/convulse.js
@@ -0,0 +1,139 @@
+// jshint asi:true
+
+function toast(text, timeout=8000) {
+ let toasts = document.querySelector("#toasts")
+ if (! text) {
+ while (toasts.firstChild) {
+ toasts.firstChild.remove()
+ }
+ } else {
+ let p = document.querySelector("#toasts").appendChild(document.createElement("p"))
+ p.textContent = text
+ if (timeout) {
+ setTimeout(() => p.remove(), timeout)
+ }
+ }
+}
+
+class Convulse {
+ constructor() {
+ this.canvas = document.querySelector("canvas")
+ this.ctx = this.canvas.getContext("2d")
+ this.dllink = document.querySelector("#download")
+ this.webcamVideo = document.querySelector("#webcam")
+ this.desktopVideo = document.querySelector("#desktop")
+ document.addEventListener("click", event => this.toggle())
+ this.chunks = []
+
+ document.addEventListener("mouseenter", e => this.showControls(true))
+ document.addEventListener("mouseleave", e => this.showControls(false))
+ this.init()
+ }
+
+ showControls(show) {
+ let controls = document.querySelector("#controls")
+ if (show) {
+ controls.classList.remove("hidden")
+ } else {
+ controls.classList.add("hidden")
+ }
+ }
+
+ download(event) {
+ let recording = window.URL.createObjectURL(new Blob(this.chunks, {type: this.recorder.mimeType}))
+ let now = new Date().toISOString()
+
+ this.dllink.addEventListener('progress', event => console.log(event))
+ this.dllink.href = recording
+ this.dllink.download = "convulse-" + now + ".webm"
+ this.dllink.click()
+ }
+
+ start() {
+ toast(null)
+ this.chunks = []
+ this.recorder.start(10)
+ this.canvas.classList.add("recording")
+ }
+
+ stop() {
+ toast("stopped and downloaded")
+ this.recorder.stop()
+ this.canvas.classList.remove("recording")
+ }
+
+ toggle() {
+ if (this.recorder.state == "recording") {
+ this.stop()
+ this.download()
+ } else {
+ this.start()
+ }
+ }
+
+ frame(timestamp) {
+ if (this.webcamVideo.videoWidth > 0) {
+ let webcamAR = this.webcamVideo.videoWidth / this.webcamVideo.videoHeight
+ let desktopAR = this.desktopVideo.videoWidth / this.desktopVideo.videoHeight
+
+ this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height)
+
+ this.ctx.drawImage(this.desktopVideo, 0, 0, this.canvas.height * desktopAR, this.canvas.height)
+ this.ctx.drawImage(this.webcamVideo, 0, 0, webcamAR * 400, 400)
+ if (timestamp % 2000 < 1000) {
+ this.ctx.beginPath()
+ this.ctx.strokeStyle = "red"
+ this.ctx.lineWidth = "6"
+ this.ctx.rect(20, 20, 150, 100)
+ this.ctx.stroke()
+ }
+ }
+
+ requestAnimationFrame(ts => this.frame(ts))
+ }
+
+ async init() {
+ this.canvas.width = 1920
+ this.canvas.height = 1080
+
+ this.webcamVideo.srcObject = await navigator.mediaDevices.getUserMedia({video: true, audio: true})
+ this.webcamVideo.play()
+
+ this.desktopVideo.srcObject = await navigator.mediaDevices.getDisplayMedia({video: {cursor: "always"}})
+ this.desktopVideo.play()
+
+ document.querySelector("#hello").classList.add("hidden")
+
+ this.mediaStream = new MediaStream()
+ let canvasStream = this.canvas.captureStream(30)
+ for (let vt of canvasStream.getVideoTracks()) {
+ this.mediaStream.addTrack(vt)
+ console.log("Adding video track", vt)
+ }
+ for (let at of this.webcamVideo.srcObject.getAudioTracks()) {
+ this.mediaStream.addTrack(at)
+ console.log("Adding audio track", at)
+ }
+
+ this.recorder = new MediaRecorder(this.canvas.captureStream(30), {mimeType: "video/webm"})
+ this.recorder.addEventListener("dataavailable", event => {
+ if (event.data && event.data.size > 0) {
+ this.chunks.push(event.data)
+ }
+ })
+
+ this.frame()
+
+ toast("Click anywhere to start and stop recording")
+ }
+}
+
+function init() {
+ window.app = new Convulse()
+}
+
+if (document.readyState === "loading") {
+ document.addEventListener("DOMContentLoaded", init)
+} else {
+ init()
+}