2017-11-10 00:36:43 +03:00
|
|
|
const params = new URLSearchParams(location.search.slice(1));
|
2017-10-24 01:18:40 +03:00
|
|
|
var USER_ID = Math.floor(Math.random() * (1000000001));
|
2017-11-14 03:03:38 +03:00
|
|
|
const roomId = params.get("room") != null ? parseInt(params.get("room")) : 42;
|
2017-11-10 00:36:43 +03:00
|
|
|
const mic = !/0|false|off/i.test(params.get("mic"));
|
2017-10-07 03:10:12 +03:00
|
|
|
|
2017-11-02 03:35:49 +03:00
|
|
|
Minijanus.verbose = true;
|
2017-10-27 23:43:53 +03:00
|
|
|
|
2017-10-21 03:44:15 +03:00
|
|
|
const PEER_CONNECTION_CONFIG = {
|
2017-11-02 23:41:27 +03:00
|
|
|
iceServers: [
|
|
|
|
{ urls: "stun:stun.l.google.com:19302" },
|
|
|
|
{ urls: "stun:global.stun.twilio.com:3478?transport=udp" }
|
|
|
|
]
|
2017-10-21 03:44:15 +03:00
|
|
|
};
|
2017-10-07 03:10:12 +03:00
|
|
|
|
2017-10-23 23:45:16 +03:00
|
|
|
// global helper for interactive use
|
|
|
|
var c = {
|
2017-11-02 23:41:27 +03:00
|
|
|
session: null,
|
|
|
|
publisher: null,
|
|
|
|
subscribers: {}
|
2017-10-23 23:45:16 +03:00
|
|
|
};
|
|
|
|
|
2017-11-15 05:53:13 +03:00
|
|
|
const status = document.getElementById("status");
|
|
|
|
function showStatus(message) {
|
|
|
|
status.textContent = message;
|
|
|
|
}
|
|
|
|
|
2017-11-14 03:03:38 +03:00
|
|
|
function isError(signal) {
|
|
|
|
var isPluginError =
|
|
|
|
signal.plugindata &&
|
|
|
|
signal.plugindata.data &&
|
|
|
|
signal.plugindata.data.success === false;
|
|
|
|
return isPluginError || Minijanus.JanusSession.prototype.isError(signal);
|
2017-11-15 06:12:37 +03:00
|
|
|
}
|
2017-11-14 03:03:38 +03:00
|
|
|
|
2017-10-21 03:44:15 +03:00
|
|
|
function init() {
|
2017-11-30 02:20:30 +03:00
|
|
|
const server = params.get("janus") || `ws://localhost:8188`;
|
|
|
|
document.getElementById("janusServer").value = server;
|
2017-11-15 05:53:13 +03:00
|
|
|
showStatus(`Connecting to ${server}...`);
|
|
|
|
var ws = new WebSocket(server, "janus-protocol");
|
2017-11-02 23:41:27 +03:00
|
|
|
ws.addEventListener("open", () => {
|
|
|
|
var session = c.session = new Minijanus.JanusSession(ws.send.bind(ws));
|
2017-11-14 03:03:38 +03:00
|
|
|
session.isError = isError;
|
2017-11-02 23:41:27 +03:00
|
|
|
ws.addEventListener("message", ev => handleMessage(session, ev));
|
|
|
|
session.create().then(() => attachPublisher(session)).then(x => {
|
|
|
|
c.publisher = x;
|
2017-11-14 03:03:38 +03:00
|
|
|
}, err => console.error("Error attaching publisher: ", err));
|
2017-11-02 23:41:27 +03:00
|
|
|
});
|
2017-10-21 03:44:15 +03:00
|
|
|
}
|
2017-10-07 03:10:12 +03:00
|
|
|
|
2017-10-21 03:44:15 +03:00
|
|
|
function handleMessage(session, ev) {
|
2017-11-02 23:41:27 +03:00
|
|
|
var data = JSON.parse(ev.data);
|
|
|
|
session.receive(data);
|
|
|
|
if (data.janus === "event") {
|
|
|
|
if (data.plugindata && data.plugindata.data) {
|
|
|
|
var contents = data.plugindata.data;
|
|
|
|
switch (contents.event) {
|
|
|
|
case "join":
|
2017-11-10 00:36:43 +03:00
|
|
|
if (contents.room_id === roomId) {
|
2017-11-15 05:45:33 +03:00
|
|
|
addUser(session, contents.user_id, data.jsep);
|
2017-11-03 01:16:14 +03:00
|
|
|
}
|
2017-11-02 23:41:27 +03:00
|
|
|
break;
|
|
|
|
case "leave":
|
2017-11-10 00:36:43 +03:00
|
|
|
if (contents.room_id === roomId) {
|
2017-11-03 01:16:14 +03:00
|
|
|
removeUser(session, contents.user_id);
|
|
|
|
}
|
2017-11-02 23:41:27 +03:00
|
|
|
break;
|
|
|
|
case undefined:
|
|
|
|
// a non-plugin event
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
console.error("Unknown event received: ", data.plugindata.data);
|
|
|
|
break;
|
|
|
|
}
|
2017-10-07 03:10:12 +03:00
|
|
|
}
|
2017-11-02 23:41:27 +03:00
|
|
|
}
|
2017-10-07 03:10:12 +03:00
|
|
|
}
|
|
|
|
|
2017-10-21 11:01:42 +03:00
|
|
|
function negotiateIce(conn, handle) {
|
2017-11-02 23:41:27 +03:00
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
conn.addEventListener("icecandidate", ev => {
|
|
|
|
handle.sendTrickle(ev.candidate || null).then(() => {
|
|
|
|
if (!ev.candidate) { // this was the last candidate on our end and now they received it
|
|
|
|
resolve();
|
|
|
|
}
|
2017-11-14 03:03:38 +03:00
|
|
|
}, reject);
|
2017-10-21 11:01:42 +03:00
|
|
|
});
|
2017-11-02 23:41:27 +03:00
|
|
|
});
|
2017-10-21 11:01:42 +03:00
|
|
|
};
|
|
|
|
|
2017-10-23 23:45:16 +03:00
|
|
|
function addUser(session, userId) {
|
2017-11-02 23:41:27 +03:00
|
|
|
console.info("Adding user " + userId + ".");
|
|
|
|
attachSubscriber(session, userId).then(x => {
|
|
|
|
c.subscribers[userId] = x;
|
2017-11-14 03:03:38 +03:00
|
|
|
}, err => console.error("Error attaching subscriber: "));
|
2017-10-23 23:45:16 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
function removeUser(session, userId) {
|
2017-11-02 23:41:27 +03:00
|
|
|
console.info("Removing user " + userId + ".");
|
|
|
|
var subscriber = c.subscribers[userId];
|
|
|
|
if (subscriber != null) {
|
|
|
|
subscriber.handle.detach();
|
|
|
|
subscriber.conn.close();
|
|
|
|
delete c.subscribers[userId];
|
|
|
|
}
|
2017-10-23 23:45:16 +03:00
|
|
|
}
|
|
|
|
|
2017-11-10 00:36:43 +03:00
|
|
|
let messages = [];
|
2017-11-10 23:19:34 +03:00
|
|
|
|
|
|
|
const messageCount = document.getElementById("messageCount");
|
|
|
|
function updateMessageCount() {
|
|
|
|
messageCount.textContent = messages.length;
|
|
|
|
}
|
|
|
|
|
2017-11-10 00:36:43 +03:00
|
|
|
let firstMessageTime;
|
2017-11-10 23:20:35 +03:00
|
|
|
function storeMessage(data, reliable) {
|
2017-11-10 00:36:43 +03:00
|
|
|
if (!firstMessageTime) {
|
|
|
|
firstMessageTime = performance.now();
|
|
|
|
}
|
|
|
|
messages.push({
|
|
|
|
time: performance.now() - firstMessageTime,
|
2017-11-10 23:20:35 +03:00
|
|
|
reliable,
|
|
|
|
message: JSON.parse(data)
|
2017-11-10 00:36:43 +03:00
|
|
|
});
|
2017-11-10 23:19:34 +03:00
|
|
|
updateMessageCount();
|
2017-11-10 00:36:43 +03:00
|
|
|
}
|
|
|
|
|
2017-11-10 23:20:35 +03:00
|
|
|
function storeReliableMessage(ev) {
|
|
|
|
storeMessage(ev.data, true);
|
|
|
|
}
|
|
|
|
|
|
|
|
function storeUnreliableMessage(ev) {
|
|
|
|
storeMessage(ev.data, false);
|
|
|
|
}
|
|
|
|
|
2017-11-10 00:36:43 +03:00
|
|
|
document.getElementById("saveButton").addEventListener("click", function saveToMessagesFile() {
|
|
|
|
const file = new File([JSON.stringify(messages)], "messages.json", {type: "text/json"});
|
|
|
|
saveAs(file);
|
|
|
|
});
|
|
|
|
|
|
|
|
document.getElementById("clearButton").addEventListener("click", function clearMessages() {
|
|
|
|
messages = [];
|
2017-11-10 23:19:34 +03:00
|
|
|
updateMessageCount();
|
2017-11-10 00:36:43 +03:00
|
|
|
});
|
|
|
|
|
2017-10-21 03:44:15 +03:00
|
|
|
function attachPublisher(session) {
|
2017-11-02 23:41:27 +03:00
|
|
|
console.info("Attaching publisher for session: ", session);
|
|
|
|
var conn = new RTCPeerConnection(PEER_CONNECTION_CONFIG);
|
|
|
|
var handle = new Minijanus.JanusPluginHandle(session);
|
|
|
|
return handle.attach("janus.plugin.sfu").then(() => {
|
|
|
|
var iceReady = negotiateIce(conn, handle);
|
2017-11-10 00:36:43 +03:00
|
|
|
|
2017-11-02 23:41:27 +03:00
|
|
|
var channel = conn.createDataChannel("reliable", { ordered: true });
|
2017-11-10 23:20:35 +03:00
|
|
|
channel.addEventListener("message", storeReliableMessage);
|
2017-11-10 00:36:43 +03:00
|
|
|
|
2017-11-10 23:20:35 +03:00
|
|
|
const unreliableChannel = conn.createDataChannel("unreliable", { ordered: false, maxRetransmits: 0 });
|
|
|
|
unreliableChannel.addEventListener("message", storeUnreliableMessage);
|
2017-11-10 00:36:43 +03:00
|
|
|
|
2017-11-10 01:33:58 +03:00
|
|
|
var mediaReady = mic ? navigator.mediaDevices.getUserMedia({ audio: true }) : Promise.reject();
|
2017-11-02 23:41:27 +03:00
|
|
|
var offerReady = mediaReady
|
2017-11-10 01:33:58 +03:00
|
|
|
.then(
|
2017-11-10 20:56:50 +03:00
|
|
|
media => {
|
2017-11-16 01:37:58 +03:00
|
|
|
media.getTracks().forEach(track => conn.addTrack(track, media));
|
2017-11-14 03:03:38 +03:00
|
|
|
return conn.createOffer({ audio: true });
|
2017-11-10 20:56:50 +03:00
|
|
|
},
|
2017-11-10 01:33:58 +03:00
|
|
|
() => conn.createOffer()
|
|
|
|
);
|
2017-11-02 23:41:27 +03:00
|
|
|
var localReady = offerReady.then(conn.setLocalDescription.bind(conn));
|
|
|
|
var remoteReady = offerReady
|
|
|
|
.then(handle.sendJsep.bind(handle))
|
|
|
|
.then(answer => conn.setRemoteDescription(answer.jsep));
|
2017-11-15 05:53:13 +03:00
|
|
|
showStatus(`Connecting WebRTC...`);
|
2017-11-02 23:41:27 +03:00
|
|
|
var connectionReady = Promise.all([iceReady, localReady, remoteReady]);
|
|
|
|
return connectionReady
|
2017-11-15 05:53:13 +03:00
|
|
|
.then(() => {
|
|
|
|
showStatus(`Joining room ${roomId}...`);
|
2017-11-15 06:12:37 +03:00
|
|
|
return handle.sendMessage({ kind: "join", room_id: roomId, user_id: USER_ID, subscribe: { "notifications": true, "data": true }});
|
2017-11-15 05:53:13 +03:00
|
|
|
})
|
2017-11-02 23:41:27 +03:00
|
|
|
.then(reply => {
|
2017-11-15 05:53:13 +03:00
|
|
|
showStatus(`Joined room ${roomId}`);
|
2017-12-02 05:05:56 +03:00
|
|
|
var occupants = reply.plugindata.data.response.users[roomId] || [];
|
2017-11-16 03:25:06 +03:00
|
|
|
for (var i = 0; i < occupants.length; i++) {
|
|
|
|
if (occupants[i] !== USER_ID) {
|
|
|
|
addUser(session, occupants[i]);
|
|
|
|
}
|
2017-11-15 05:45:33 +03:00
|
|
|
}
|
|
|
|
})
|
2017-11-30 02:20:30 +03:00
|
|
|
.then(() => {
|
|
|
|
return { handle, conn, channel, unreliableChannel };
|
|
|
|
});
|
2017-11-02 23:41:27 +03:00
|
|
|
});
|
2017-09-28 13:13:09 +03:00
|
|
|
}
|
2017-10-07 03:10:12 +03:00
|
|
|
|
2017-10-21 03:44:15 +03:00
|
|
|
function attachSubscriber(session, otherId) {
|
2017-11-02 23:41:27 +03:00
|
|
|
console.info("Attaching subscriber to " + otherId + " for session: ", session);
|
|
|
|
var conn = new RTCPeerConnection(PEER_CONNECTION_CONFIG);
|
2017-11-15 05:45:33 +03:00
|
|
|
conn.ontrack = function(ev) {
|
2017-11-02 23:41:27 +03:00
|
|
|
if (ev.track.kind === "audio") {
|
|
|
|
var audioEl = document.createElement("audio");
|
|
|
|
audioEl.controls = true;
|
|
|
|
document.body.appendChild(audioEl);
|
|
|
|
audioEl.srcObject = ev.streams[0];
|
|
|
|
audioEl.play();
|
|
|
|
} else if (ev.track.kind === "video") {
|
|
|
|
var videoEl = document.createElement("video");
|
|
|
|
videoEl.controls = true;
|
|
|
|
document.body.appendChild(videoEl);
|
|
|
|
videoEl.srcObject = ev.streams[0];
|
|
|
|
videoEl.play();
|
|
|
|
}
|
2017-11-15 05:45:33 +03:00
|
|
|
};
|
2017-10-21 03:44:15 +03:00
|
|
|
|
2017-11-02 23:41:27 +03:00
|
|
|
var handle = new Minijanus.JanusPluginHandle(session);
|
|
|
|
return handle.attach("janus.plugin.sfu")
|
|
|
|
.then(() => {
|
|
|
|
var iceReady = negotiateIce(conn, handle);
|
2017-11-15 05:45:33 +03:00
|
|
|
var localReady = handle.sendMessage({ kind: "join", room_id: roomId, user_id: USER_ID, subscribe: { "media": otherId }})
|
|
|
|
.then(resp => {
|
|
|
|
return conn.setRemoteDescription(resp.jsep)
|
|
|
|
.then(() => conn.createAnswer())
|
|
|
|
.then(conn.setLocalDescription.bind(conn));
|
|
|
|
});
|
|
|
|
return Promise.all([iceReady, localReady])
|
|
|
|
.then(() => handle.sendJsep(conn.localDescription))
|
|
|
|
.then(() => {
|
|
|
|
return { handle: handle, conn: conn };
|
|
|
|
});
|
2017-11-02 23:41:27 +03:00
|
|
|
});
|
2017-10-07 03:10:12 +03:00
|
|
|
}
|
2017-10-21 03:44:15 +03:00
|
|
|
|
|
|
|
init();
|