You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

381 lines
11 KiB
JavaScript

/**
*
* @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);
}
}