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
+
+
+ + + + + +
+

+ UR FACE + + +

+ +

+ Capture Area + + +

+
+
- 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() +}