mirror of https://github.com/nealey/convulse.git
Make it more like an app
This commit is contained in:
parent
69bf1c0de6
commit
ab799618f1
|
@ -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;
|
||||||
|
}
|
162
convulse.html
162
convulse.html
|
@ -2,130 +2,48 @@
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<title>Convulse</title>
|
<title>Convulse</title>
|
||||||
<style>
|
<link rel="stylesheet" href="convulse.css">
|
||||||
body {
|
<script src="convulse.js"></script>
|
||||||
background-color: #000;
|
|
||||||
color: #fff;
|
|
||||||
margin: 0;
|
|
||||||
font-family: sans-serif;
|
|
||||||
}
|
|
||||||
video {
|
|
||||||
height: 100vh;
|
|
||||||
width: 100vw;
|
|
||||||
}
|
|
||||||
#download {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
#toasts {
|
|
||||||
position: fixed;
|
|
||||||
z-index: 2;
|
|
||||||
top: 0;
|
|
||||||
background-color: blue;
|
|
||||||
width: 100%;
|
|
||||||
opacity: 0.3;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
video {
|
|
||||||
filter: grayscale(100%);
|
|
||||||
}
|
|
||||||
video.recording {
|
|
||||||
filter: initial;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
<script>
|
|
||||||
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.video = document.querySelector("video")
|
|
||||||
this.dllink = document.querySelector("#download")
|
|
||||||
this.init()
|
|
||||||
this.chunks = []
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
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.video.classList.add("recording")
|
|
||||||
}
|
|
||||||
|
|
||||||
stop() {
|
|
||||||
toast("stopped and downloaded")
|
|
||||||
this.recorder.stop()
|
|
||||||
this.video.classList.remove("recording")
|
|
||||||
}
|
|
||||||
|
|
||||||
toggle() {
|
|
||||||
if (this.recorder.state == "recording") {
|
|
||||||
this.stop()
|
|
||||||
this.download()
|
|
||||||
} else {
|
|
||||||
this.start()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async init() {
|
|
||||||
document.querySelector("h1").remove()
|
|
||||||
|
|
||||||
this.webcam = await navigator.mediaDevices.getUserMedia({video: true})
|
|
||||||
this.video.srcObject = this.webcam
|
|
||||||
this.video.play()
|
|
||||||
this.video.addEventListener("click", event => this.toggle())
|
|
||||||
|
|
||||||
this.desktop = await navigator.mediaDevices.getDisplayMedia({video: {cursor: "always"}})
|
|
||||||
this.recorder = new MediaRecorder(this.desktop, {mimeType: "video/webm"})
|
|
||||||
this.recorder.addEventListener("dataavailable", event => {
|
|
||||||
if (event.data && event.data.size > 0) {
|
|
||||||
this.chunks.push(event.data)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
toast("Click anywhere to start and stop recording")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function init() {
|
|
||||||
window.app = new Convulse()
|
|
||||||
}
|
|
||||||
|
|
||||||
if (document.readyState === "loading") {
|
|
||||||
document.addEventListener("DOMContentLoaded", init)
|
|
||||||
} else {
|
|
||||||
init()
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<h1>Convulse: it's sorta like Twitch!</h1>
|
<div id="hello">
|
||||||
<video>
|
<h1>Convulse: it's sorta like Twitch!</h1>
|
||||||
It's too bad about your browser.
|
<p>
|
||||||
</video>
|
I need the following permissions:
|
||||||
|
</p>
|
||||||
|
<dl>
|
||||||
|
<dt>Use your microphone</dt>
|
||||||
|
<dd>So I can record your velvety-smooth voice</dd>
|
||||||
|
|
||||||
|
<dt>Use your camera</dt>
|
||||||
|
<dd>So I can record your velvety-smooth face</dd>
|
||||||
|
|
||||||
|
<dt>Share your screen</dt>
|
||||||
|
<dd>So I can record your velvety-smooth computer</dd>
|
||||||
|
</dl>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<canvas></canvas>
|
||||||
|
|
||||||
|
<div id="videos" class="hidden">
|
||||||
|
<video id="webcam"></video>
|
||||||
|
<video id="desktop"></video>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="controls">
|
||||||
|
<p>
|
||||||
|
UR FACE
|
||||||
|
<input id="webcam-size" type="range" min="0.05" max="1.0" step="0.05" value="0.2">
|
||||||
|
<button id="webcam-pos">Move</button>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Capture Area
|
||||||
|
<input id="capture-size" type="range" min="0.04" max="1.0" step="0.05" value="0.2">
|
||||||
|
<button id="range-pos">Move</button>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div id="toasts"></div>
|
<div id="toasts"></div>
|
||||||
<a id="download"></a>
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -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()
|
||||||
|
}
|
Loading…
Reference in New Issue