Eagle Animation With Webrtc Camera
Fleetingeagle animation with webrtc camera
Following the idea from aiortc to create a remote web camera with an android phone, to continue the idea of stopmotion android apps.
- peerjs to sync them with a private server,
- the camera registers itself to the name “camera”,
- the stopmotion app registers itself to the name “control”,
- when connected, the camera calls the stopmotion app,
code in the camera
I used the exact same code as the one used in aiortc to create a remote web camera with an android phone.
<html>
<head>
<meta charset="UTF-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<script defer src="https://ipfs.konubinix.eu/p/bafkreic33rowgvvugzgzajagkuwidfnit2dyqyn465iygfs67agsukk24i?orig=https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"></script>
<script src="https://ipfs.konubinix.eu/p/bafybeihp5kzlgqt56dmy5l4z7kpymfc4kn3fnehrrtr7cid7cn7ra36yha?orig=https://cdn.tailwindcss.com/3.4.3"></script>
<script src="https://unpkg.com/peerjs@1.5.2/dist/peerjs.min.js"></script>
<script>
document.addEventListener('alpine:init', () => {
Alpine.data('app', () => ({
pc: null,
message: "",
video: null,
dc: null,
peer: null,
conn: null,
peerId: null,
current_call: null,
wakelock: null,
width: {min: 320, max: 5000, value: 640},
currentwidth: null,
currentFrameRate: null,
configure: true,
bodyClass: "",
async init () {
this.video = document.getElementById("video")
this.peer = new Peer("camera", {host: "home", port: 9999, path: "/peerjs/"});
this.peer.on("error", (err) => {
this.message += ` error: ${err}`
});
this.peer.on('open', (id) => {
this.message += `connected to peerjs with id ${id}`
this.peerId = id
});
if(! 'mediaDevices' in navigator) {
alert( 'Your browser does not support media devices.' );
}
await this.setupMediaStream()
this.peer.on('connection', async (conn) => {
this.message += "connected to the controller"
this.conn = conn
this.message += "calling the controller"
this.current_call = this.peer.call("control", this.mediaStream)
this.conn.on("open", async () => {
conn.send(JSON.stringify({type: "configure", value: this.pc === null ? true : false}))
})
conn.on('data', async (data) => {
this.message += `got ${data}`
const order = JSON.parse(data)
if(order.action === "start") {
await this.start(this.width)
conn.send(JSON.stringify({"type": "answer", "message": "started"}))
}
else if(order.action === "stop") {
await this.stop()
conn.send(JSON.stringify({"type": "answer", "message": "stopped"}))
}
else if(order.action === "setupMediaStream")
{
this.width.value = order.width
await this.setupMediaStream()
conn.send(JSON.stringify({"type": "answer", "message": "mediaStreamUpdate"}))
}
else {
this.message("Unrecognized")
}
});
});
try{
navigator.wakeLock.request("screen").then(
(w) => {
this.wakelock = w;
this.message += "wakelock acquired"
}
).catch((err) => {
this.message += `${err.name}, ${err.message}`;
});
} catch (err) {
this.message += err.toString()
};
},
createPeerConnection() {
this.pc = new RTCPeerConnection();
this.pc.addEventListener('track', (evt) => {
this.video.srcObject = evt.streams[0];
});
},
async setupMediaStream() {
this.mediaStream = await navigator.mediaDevices.getUserMedia(
{
video: {
width: { ideal: this.width.value },
height: { ideal: this.width.value },
frameRate: 30,
facingMode: {exact: "environment"},
},
audio: false,
}
);
// window.ms = this.mediaStream
this.currentwidth = this.mediaStream.getVideoTracks()[0].getSettings().width
this.currentFrameRate = this.mediaStream.getVideoTracks()[0].getSettings().frameRate
this.video.srcObject = this.mediaStream
if(this.conn !== null){
this.current_call.close()
this.current_call = this.peer.call("control", this.mediaStream)
}
},
async start() {
this.configure = false
this.bodyClass = "text-slate-700 bg-black"
this.createPeerConnection()
this.mediaStream.getTracks().forEach((track) => {
this.pc.addTrack(track, this.mediaStream);
});
await this.negociate()
},
async negociate() {
this.message += "\nneg start"
var offer = await this.pc.createOffer();
await this.pc.setLocalDescription(offer);
try{
var resp = await fetch('http://192.168.1.245:9999/offer', {
body: JSON.stringify({
sdp: offer.sdp,
type: offer.type,
}),
headers: {
'Content-Type': "application/json"
},
method: "POST"
});
} catch (err) {
alert(err.toString())
alert(JSON.stringify(err));
throw err
};
this.message += "\nneg sent"
var answer = await resp.json()
await this.pc.setRemoteDescription(answer);
this.message += "\nconnected"
},
async stop() {
// if (this.wakelock != null) {
// this.wakelock.release().then(() => {
// this.wakelock = null;
// // alert("released");
// }).catch((err) => {
// alert(`${err.name}, ${err.message}`);
// });
// }
// this.current_call.close()
this.pc.getTransceivers().forEach(function(transceiver) {
if (transceiver.stop) {
transceiver.stop();
}
});
this.pc.getSenders().forEach(function(sender) {
sender.track.stop();
});
// close peer connection
setTimeout(async () => {
this.pc.close();
this.pc = null
this.message += "\nall closed"
this.conn.send(JSON.stringify({"type": "answer", "message": "allclosed"}))
this.bodyClass = ""
this.configure = true
setTimeout(async () => {
this.message += "connecting back"
await this.setupMediaStream()
this.current_call = this.peer.call("control", this.mediaStream)
}, 500)
}, 500);
},
}))
})
</script>
</head>
<body x-data="app" :class="bodyClass">
<div>
<!-- <button @click="start">Start</button> -->
<!-- <button @click="stop">Stop</button> -->
<div x-text="peerId"></div>
<div x-text="message"></div>
<div x-show="configure">
<span x-text="width.value"></span>
<span x-text="currentwidth"></span>
<span x-text="currentFrameRate"></span>
<div class="justify-center sm:px-20 p-15 h-screen" x-data="{show: true}">
<button class="text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 me-2 mb-2 dark:bg-blue-600 dark:hover:bg-blue-700 focus:outline-none dark:focus:ring-blue-800" @click="show = !show ; $refs.video.scrollIntoView()" x-text="show ? 'Hide' : 'Show'"></button>
<video id="video" @dblclick="$event.target.requestFullscreen()" x-show="show" class="object-scale-down max-h-full" x-ref="video" poster="https://d1tobl1u8ox4qn.cloudfront.net/2018/05/3814a83b-a2d4-4dfd-8945-f1cd003eb16f-1920x1080.jpg" autoplay="true" playsinline="true"></video>
</div>
</div>
</div>
</body>
</html>
code in eagle animations
I forked eagle-animation and added a camera called webrtc, copy of the webcam
one, using the stream got from peerjs instead of one got from
navigator.mediaDevices.getUserMedia
.
See in: https://github.com/Konubinix/eagle-animation
how to run it
- first, run peerjs
- then, run the camera
- then, run the control
If the control cannot connect to the camera, you have to start the camera and reload the control page
This is a snapshot, generated with
set -eu
# npm i --force
npm run build:web
ipfa out/web