Merge branch 'master' of https://git.xpub.nl/XPUB/SI21
@ -0,0 +1,17 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<script type="text/javascript" src="https://p5livemedia.itp.io/simplepeer.min.js"></script>
|
||||
<script type="text/javascript" src="https://p5livemedia.itp.io/socket.io.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.1.9/p5.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.1.9/addons/p5.sound.min.js"></script>
|
||||
<script type="text/javascript" src="https://p5livemedia.itp.io/p5livemedia.js"></script>
|
||||
|
||||
<link rel="stylesheet" type="text/css" href="style.css">
|
||||
<meta charset="utf-8" />
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<script src="sketch.js"></script>
|
||||
</body>
|
||||
</html>
|
@ -0,0 +1,380 @@
|
||||
/**
|
||||
*
|
||||
* @class p5LiveMedia
|
||||
* @constructor
|
||||
* @param {p5.sketch} [something] blah blah blah.
|
||||
* @param {p5LiveMedia.MEDIA TYPE}
|
||||
* @param {WebRTC stream}
|
||||
* @example
|
||||
*
|
||||
function setup() {
|
||||
// Stream Audio/Video
|
||||
createCanvas(400, 300);
|
||||
// For A/V streams, we need to use the createCapture callback method to get the "stream" object
|
||||
video = createCapture(VIDEO, function(stream) {
|
||||
let p5lm = new p5LiveMedia(this,"CAPTURE",stream)
|
||||
p5lm.on('stream', gotStream);
|
||||
p5lm.on('data', gotData);
|
||||
p5lm.on('disconnect', gotDisconnect);
|
||||
});
|
||||
video.muted = true;
|
||||
video.hide();
|
||||
|
||||
// OR //
|
||||
|
||||
// Stream Canvas as Video
|
||||
let c = createCanvas(400, 300);
|
||||
video = createCapture(VIDEO);
|
||||
video.muted = true;
|
||||
video.hide();
|
||||
let p5lm = new p5LiveMedia(this,"CANVAS",c);
|
||||
p5lm.on('stream', gotStream);
|
||||
p5lm.on('data', gotData);
|
||||
p5lm.on('disconnect', gotDisconnect);
|
||||
|
||||
|
||||
// OR //
|
||||
|
||||
// Just Data
|
||||
createCanvas(400, 300);
|
||||
let p5lm = new p5LiveMedia(this,"DATA");
|
||||
p5lm.on('data', gotData);
|
||||
p5lm.on('disconnect', gotDisconnect);
|
||||
}
|
||||
|
||||
function draw() {
|
||||
image(video,0,0,width/2,height);
|
||||
ellipse(mouseX,mouseY,100,100);
|
||||
if (ovideo != null) {
|
||||
rect(10,10,10,10);
|
||||
image(ovideo,width/2,0,width/2,height);
|
||||
}
|
||||
}
|
||||
|
||||
// We got a new stream!
|
||||
function gotStream(stream, id) {
|
||||
print("New Stream from " + id);
|
||||
// This is just like a video/stream from createCapture(VIDEO)
|
||||
ovideo = stream;
|
||||
//ovideo.hide();
|
||||
}
|
||||
|
||||
function gotData(data, id) {
|
||||
print("New Data from " + id);
|
||||
// Got some data from a peer
|
||||
print(data);
|
||||
}
|
||||
|
||||
function gotDisconnect(id) {
|
||||
print(id + " disconnected");
|
||||
}
|
||||
*/
|
||||
class p5LiveMedia {
|
||||
|
||||
constructor(sketch, type, elem, room, host) {
|
||||
|
||||
this.sketch = sketch;
|
||||
//sketch.disableFriendlyErrors = true;
|
||||
|
||||
this.simplepeers = [];
|
||||
this.mystream;
|
||||
this.onStreamCallback;
|
||||
this.onDataCallback;
|
||||
this.onDisconnectCallback;
|
||||
|
||||
if (!host) {
|
||||
this.socket = io.connect("https://p5livemedia.itp.io/");
|
||||
} else {
|
||||
this.socket = io.connect(host);
|
||||
}
|
||||
|
||||
//console.log(elem.elt);
|
||||
|
||||
if (type == "CANVAS") {
|
||||
this.mystream = elem.elt.captureStream(30);
|
||||
} else if (type == "CAPTURE") {
|
||||
this.mystream = elem;
|
||||
} else {
|
||||
// Assume it is just "DATA"
|
||||
|
||||
}
|
||||
|
||||
this.socket.on('connect', () => {
|
||||
//console.log("Socket Connected");
|
||||
//console.log("My socket id: ", this.socket.id);
|
||||
|
||||
//console.log("***"+window.location.href);
|
||||
|
||||
// Sends back a list of users in the room
|
||||
if (!room) {
|
||||
this.socket.emit("room_connect", window.location.href);
|
||||
} else {
|
||||
this.socket.emit("room_connect", room);
|
||||
}
|
||||
});
|
||||
|
||||
this.socket.on('disconnect', (data) => {
|
||||
// console.log("Socket disconnected");
|
||||
});
|
||||
|
||||
this.socket.on('peer_disconnect', (data) => {
|
||||
//console.log("simplepeer has disconnected " + data);
|
||||
for (let i = 0; i < this.simplepeers.length; i++) {
|
||||
if (this.simplepeers[i].socket_id == data) {
|
||||
//console.log("Removed the DOM Element if it exits");
|
||||
this.removeDomElement(this.simplepeers[i]);
|
||||
//console.log("Removing simplepeer: " + i);
|
||||
this.simplepeers.splice(i,1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
this.callOnDisconnectCallback(data);
|
||||
});
|
||||
|
||||
// Receive listresults from server
|
||||
this.socket.on('listresults', (data) => {
|
||||
//console.log(data);
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
// Make sure it's not us
|
||||
if (data[i] != this.socket.id) {
|
||||
|
||||
// create a new simplepeer and we'll be the "initiator"
|
||||
let simplepeer = new SimplePeerWrapper(this,
|
||||
true, data[i], this.socket, this.mystream
|
||||
);
|
||||
|
||||
// Push into our array
|
||||
this.simplepeers.push(simplepeer);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
this.socket.on('signal', (to, from, data) => {
|
||||
|
||||
//console.log("Got a signal from the server: ", to, from, data);
|
||||
|
||||
// // to should be us
|
||||
// if (to != this.socket.id) {
|
||||
// console.log("Socket IDs don't match");
|
||||
// }
|
||||
|
||||
// Look for the right simplepeer in our array
|
||||
let found = false;
|
||||
for (let i = 0; i < this.simplepeers.length; i++)
|
||||
{
|
||||
|
||||
if (this.simplepeers[i].socket_id == from) {
|
||||
//console.log("Found right object");
|
||||
// Give that simplepeer the signal
|
||||
this.simplepeers[i].inputsignal(data);
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
if (!found) {
|
||||
//console.log("Never found right simplepeer object");
|
||||
// Let's create it then, we won't be the "initiator"
|
||||
let simplepeer = new SimplePeerWrapper(this,
|
||||
false, from, this.socket, this.mystream
|
||||
);
|
||||
|
||||
// Push into our array
|
||||
this.simplepeers.push(simplepeer);
|
||||
|
||||
// Tell the new simplepeer that signal
|
||||
simplepeer.inputsignal(data);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// // use this to add a track to a stream - assuming this is a stream, it will have to extract the track out
|
||||
// addtrack(stream, type) {
|
||||
// if (type == "CANVAS") {
|
||||
// this.mystream = elem.elt.captureStream(30);
|
||||
// } else if (type == "CAPTURE") {
|
||||
// this.mystream = elem;
|
||||
// }
|
||||
// }
|
||||
|
||||
send(data) {
|
||||
for (let i = 0; i < this.simplepeers.length; i++) {
|
||||
if (this.simplepeers[i] != null) {
|
||||
this.simplepeers[i].send(data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
on(event, callback) {
|
||||
if (event == 'stream') {
|
||||
this.onStream(callback);
|
||||
} else if (event == 'data') {
|
||||
this.onData(callback);
|
||||
} else if (event == "disconnect") {
|
||||
this.onDisconnect(callback);
|
||||
}
|
||||
}
|
||||
|
||||
onDisconnect(callback) {
|
||||
this.onDisconnectCallback = callback;
|
||||
}
|
||||
|
||||
onStream(callback) {
|
||||
this.onStreamCallback = callback;
|
||||
}
|
||||
|
||||
onData(callback) {
|
||||
this.onDataCallback = callback;
|
||||
}
|
||||
|
||||
callOnDisconnectCallback(id) {
|
||||
if (this.onDisconnectCallback) {
|
||||
this.onDisconnectCallback(id);
|
||||
}
|
||||
}
|
||||
|
||||
callOnDataCallback(data, id) {
|
||||
if (this.onDataCallback) {
|
||||
this.onDataCallback(data, id);
|
||||
}
|
||||
}
|
||||
|
||||
removeDomElement(ssp) {
|
||||
if (ssp.domElement) {
|
||||
document.body.removeChild(ssp.domElement);
|
||||
}
|
||||
}
|
||||
|
||||
callOnStreamCallback(domElement, id) {
|
||||
if (this.onStreamCallback) {
|
||||
|
||||
//////////////////////
|
||||
// Copied from createCapture and addElement in p5.js source 10/12/2020
|
||||
//const videoEl = addElement(domElement, this.sketch, true);
|
||||
document.body.appendChild(domElement);
|
||||
let videoEl = new p5.MediaElement(domElement, this.sketch);
|
||||
this.sketch._elements.push(videoEl);
|
||||
|
||||
videoEl.loadedmetadata = false;
|
||||
// set width and height onload metadata
|
||||
domElement.addEventListener('loadedmetadata', function() {
|
||||
domElement.play();
|
||||
if (domElement.width) {
|
||||
videoEl.width = domElement.width;
|
||||
videoEl.height = domElement.height;
|
||||
} else {
|
||||
videoEl.width = videoEl.elt.width = domElement.videoWidth;
|
||||
videoEl.height = videoEl.elt.height = domElement.videoHeight;
|
||||
}
|
||||
videoEl.loadedmetadata = true;
|
||||
});
|
||||
/////////////////////////////
|
||||
|
||||
this.onStreamCallback(videoEl, id);
|
||||
}
|
||||
else {
|
||||
//console.log("no onStreamCallback set");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// A wrapper for simplepeer as we need a bit more than it provides
|
||||
class SimplePeerWrapper {
|
||||
|
||||
constructor(p5lm, initiator, socket_id, socket, stream) {
|
||||
this.simplepeer = new SimplePeer({
|
||||
initiator: initiator,
|
||||
trickle: false
|
||||
});
|
||||
|
||||
this.p5livemedia = p5lm;
|
||||
|
||||
// Their socket id, our unique id for them
|
||||
this.socket_id = socket_id;
|
||||
|
||||
// Socket.io Socket
|
||||
this.socket = socket;
|
||||
|
||||
// Are we connected?
|
||||
this.connected = false;
|
||||
|
||||
// Our video stream
|
||||
this.stream = stream;
|
||||
|
||||
// Dom Element
|
||||
this.domElement = null;
|
||||
|
||||
// simplepeer generates signals which need to be sent across socket
|
||||
this.simplepeer.on('signal', data => {
|
||||
this.socket.emit('signal', this.socket_id, this.socket.id, data);
|
||||
});
|
||||
|
||||
// When we have a connection, send our stream
|
||||
this.simplepeer.on('connect', () => {
|
||||
//console.log('simplepeer connection')
|
||||
//console.log(this.simplepeer);
|
||||
//p.send('whatever' + Math.random())
|
||||
|
||||
// We are connected
|
||||
this.connected = true;
|
||||
|
||||
// Let's give them our stream, if we have a stream that is
|
||||
if (stream != null) {
|
||||
this.simplepeer.addStream(stream);
|
||||
//console.log("Send our stream");
|
||||
}
|
||||
});
|
||||
|
||||
// Stream coming in to us
|
||||
this.simplepeer.on('stream', stream => {
|
||||
//console.log('Incoming Stream');
|
||||
|
||||
// This should really be a callback
|
||||
|
||||
// Create a video object
|
||||
this.domElement = document.createElement("VIDEO");
|
||||
this.domElement.id = this.socket_id;
|
||||
this.domElement.srcObject = stream;
|
||||
this.domElement.muted = false;
|
||||
this.domElement.onloadedmetadata = function(e) {
|
||||
e.target.play();
|
||||
};
|
||||
//document.body.appendChild(ovideo);
|
||||
//console.log(this.domElement);
|
||||
|
||||
this.p5livemedia.callOnStreamCallback(this.domElement, this.socket_id);
|
||||
});
|
||||
|
||||
this.simplepeer.on('data', data => {
|
||||
let stringData = String(data);
|
||||
|
||||
this.p5livemedia.callOnDataCallback(stringData, this.socket_id);
|
||||
});
|
||||
|
||||
this.simplepeer.on('error', (err) => {
|
||||
// ERR_WEBRTC_SUPPORT
|
||||
// ERR_CREATE_OFFER
|
||||
// ERR_CREATE_ANSWER
|
||||
// ERR_SET_LOCAL_DESCRIPTION
|
||||
// ERR_SET_REMOTE_DESCRIPTION
|
||||
// ERR_ADD_ICE_CANDIDATE
|
||||
// ERR_ICE_CONNECTION_FAILURE
|
||||
// ERR_SIGNALING
|
||||
// ERR_DATA_CHANNEL
|
||||
// ERR_CONNECTION_FAILURE
|
||||
console.log(err);
|
||||
});
|
||||
}
|
||||
|
||||
send(data) {
|
||||
if (this.connected) {
|
||||
this.simplepeer.send(data);
|
||||
} else {
|
||||
//console.log("Can't send, not connected");
|
||||
}
|
||||
}
|
||||
|
||||
inputsignal(sig) {
|
||||
this.simplepeer.signal(sig);
|
||||
}
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
|
||||
let myVideo;
|
||||
let otherVideo;
|
||||
|
||||
function setup() {
|
||||
createCanvas(windowHeight, windowWidth);
|
||||
|
||||
let constraints = {audio: true, video: true};
|
||||
myVideo = createCapture(constraints,
|
||||
function(stream) {
|
||||
let p5l = new p5LiveMedia(this, "CAPTURE", stream, "NYCAMST")
|
||||
p5l.on('stream', gotStream);
|
||||
}
|
||||
);
|
||||
myVideo.elt.muted = true;
|
||||
myVideo.hide();
|
||||
}
|
||||
|
||||
function draw() {
|
||||
|
||||
background(220);
|
||||
stroke(255);
|
||||
image(myVideo,0,0,width,height);
|
||||
if (otherVideo) {
|
||||
blend(otherVideo, 0, 0, otherVideo.width, otherVideo.height, 0, 0, width, height, MULTIPLY);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// We got a new stream!
|
||||
function gotStream(stream, id) {
|
||||
// This is just like a video/stream from createCapture(VIDEO)
|
||||
otherVideo = stream;
|
||||
//otherVideo.id and id are the same and unique identifiers
|
||||
otherVideo.hide();
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
html, body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
canvas {
|
||||
display: block;
|
||||
}
|
@ -0,0 +1,2 @@
|
||||
GESTURE_APPLICATION_ROOT=/breadcube/gesture
|
||||
GESTURE_PORTNUMBER=5002
|
@ -0,0 +1,10 @@
|
||||
include .env
|
||||
export
|
||||
|
||||
default: local
|
||||
|
||||
local:
|
||||
@flask run --debug
|
||||
|
||||
breadcube:
|
||||
@SCRIPT_NAME=${GESTURE_APPLICATION_ROOT} gunicorn -b localhost:${GESTURE_PORTNUMBER} --reload app:app
|
@ -0,0 +1,10 @@
|
||||
import os
|
||||
from dotenv import main
|
||||
|
||||
# Load environment variables from the .env file
|
||||
main.load_dotenv()
|
||||
|
||||
# Bind them to Python variables
|
||||
APPLICATION_ROOT = os.environ.get('GESTURE_APPLICATION_ROOT', '/')
|
||||
PORTNUMBER = int(os.environ.get('GESTURE_PORTNUMBER', 5001))
|
||||
|
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 2.1 KiB |
Before Width: | Height: | Size: 2.4 KiB After Width: | Height: | Size: 2.4 KiB |
Before Width: | Height: | Size: 2.4 KiB After Width: | Height: | Size: 2.4 KiB |
Before Width: | Height: | Size: 3.2 MiB After Width: | Height: | Size: 3.2 MiB |