1
0
Fork 0
mirror of https://git.rwth-aachen.de/acs/public/villas/node/ synced 2025-03-09 00:00:00 +01:00
VILLASnode/web/webrtc.html

557 lines
21 KiB
HTML
Raw Permalink Normal View History

2022-03-14 15:33:14 -04:00
<!doctype html>
<html>
2022-03-15 09:25:02 -04:00
<!--
-- WebRTC demo
--
-- @author Steffen Vogel <post@steffenvogel.de>
2022-03-15 09:25:02 -04:00
-- @copyright 2014-2022, Institute for Automation of Complex Power Systems, EONERC
2022-07-04 18:20:03 +02:00
-- @license Apache 2.0
2022-03-15 09:25:02 -04:00
-->
2022-03-14 15:33:14 -04:00
<head>
<title>VILLASnode: Simple WebRTC node example</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/prismjs@1.27.0/themes/prism.css">
<style>
pre,
.filename {
font-size: small !important;
}
#log {
display: inline;
}
#received-container {
overflow: auto;
height: 300px;
border: 1px solid darkgray;
}
#log-container {
overflow: auto;
height: 300px;
border: 1px solid darkgray;
}
.log-warn {
color: orange
}
.log-error {
color: red
}
.log-info {
color: darkblue;
}
.log-log {
color: black;
}
.log-warn,
.log-error {
font-weight: bold;
}
.filename {
background-color: #adadad;
font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace;
margin-bottom: -8px;
padding: 5px;
}
h3,
h4,
h5 {
margin-top: 15px;
}
</style>
<script>
(function() {
var btnConnect = null;
var btnDisconnect = null;
var btnSend = null;
var inpMessage = null;
var inpSessionName = null;
var inpServer = null;
var inpUsername = null;
var inpPassword = null;
var received = null;
var receivedContainer = null;
var villasConfig = null;
var first = false;
var polite = false;
var ignoreOffer = false;
var makingOffer = false;
var pc = null; // RTCPeerConnection for our 'local' connection
var dc = null; // RTCDataChannel for the local (sender)
var sc = null; // Signaling Client
var server = 'https://villas.k8s.eonerc.rwth-aachen.de/ws/signaling';
2022-03-14 15:33:14 -04:00
var sessionName = 'my-session-name';
var iceUsername = 'villas';
var icePassword = 'villas';
var iceUrls = [
'stun:stun.0l.de:3478',
'turn:turn.0l.de:3478?transport=udp',
'turn:turn.0l.de:3478?transport=tcp'
2022-03-14 15:33:14 -04:00
];
// Functions
// Set things up, connect event listeners, etc.
function startup() {
log = document.getElementById('log');
logContainer = document.getElementById('log-container');
2022-03-14 15:33:14 -04:00
rewireLoggingToElement();
btnConnect = document.getElementById('connect');
btnDisconnect = document.getElementById('disconnect');
btnSend = document.getElementById('send');
inpMessage = document.getElementById('message');
inpSessionName = document.getElementById('session-name');
inpServer = document.getElementById('server');
inpIceUsername = document.getElementById('ice-username');
inpIcePassword = document.getElementById('ice-password');
inpIceUrls = document.getElementById('ice-urls');
received = document.getElementById('received');
receivedContainer = document.getElementById('received-container');
villasConfig = document.getElementById('villas-config');
btnConnect.addEventListener('click', connectPeers, false);
btnDisconnect.addEventListener('click', disconnectPeers, false);
btnSend.addEventListener('click', sendMessage, false);
let queryParams = new URLSearchParams(window.location.search)
let sessionNameQuery = queryParams.get('session_name');
let autoConnect = queryParams.get('auto_connect');
if (sessionNameQuery !== null) {
sessionName = sessionNameQuery;
}
2022-03-14 15:33:14 -04:00
inpServer.onkeyup = (e) => {
server = e.target.value;
updateConfig();
};
inpSessionName.onkeyup = (e) => {
sessionName = e.target.value;
updateConfig();
};
inpIceUsername.onkeyup = (e) => {
iceUsername = e.target.value;
updateConfig();
};
inpIcePassword.onkeyup = (e) => {
icePassword = e.target.value;
updateConfig();
};
inpIceUrls.onkeyup = (e) => {
iceUrls = e.target.value.split(',');
updateConfig();
};
inpSessionName.value = sessionName;
inpServer.value = server;
inpIcePassword.value = icePassword;
inpIceUsername.value = iceUsername;
inpIceUrls.value = iceUrls;
updateConfig();
if (autoConnect !== null) {
connectPeers();
}
2022-03-14 15:33:14 -04:00
}
function rewireLoggingToElement() {
function produceOutput(name, args) {
let now = new Date();
let el = document.createElement('span');
el.classList.add('log-' + name);
el.innerHTML += '<span>' + now.toLocaleTimeString() + '</span>&nbsp;';
el.innerHTML += '<span>' + (name == 'log' ? 'info' : name) + '</span>';
for (let arg of args) {
let a = document.createElement('code');
a.classList.add('log-' + (typeof arg));
if (typeof arg !== 'string' && (JSON || {}).stringify) {
a.classList.add('lang-json');
a.innerHTML = JSON.stringify(arg);
Prism.highlightElement(a);
} else {
a.innerHTML = arg;
}
el.innerHTML += '&nbsp;';
el.appendChild(a)
}
return el;
}
function fixLoggingFunc(name) {
console['old' + name] = console[name];
console[name] = function(...arguments) {
const output = produceOutput(name, arguments);
const isScrolledToBottom = logContainer.scrollHeight - logContainer.clientHeight <= logContainer.scrollTop + 1;
log.appendChild(output);
log.innerHTML += '<br>';
if (isScrolledToBottom) {
2022-03-14 15:33:14 -04:00
logContainer.scrollTop = logContainer.scrollHeight - logContainer.clientHeight;
}
2022-03-14 15:33:14 -04:00
console['old' + name].apply(undefined, arguments);
};
}
for (logger of['log', 'debug', 'warn', 'error', 'info']) {
2022-03-14 15:33:14 -04:00
fixLoggingFunc(logger);
}
2022-03-14 15:33:14 -04:00
}
function updateConfig() {
let cfg = {
nodes: {
webrtc_1: {
type: 'webrtc',
session: sessionName,
server: server,
ice: {
2022-03-14 19:39:34 -04:00
servers: [
{
urls: iceUrls,
username: iceUsername,
password: icePassword
}
]
2022-03-14 15:33:14 -04:00
}
},
siggen_1: {
type: 'signal',
values: 5,
signal: 'random'
2022-03-14 15:33:14 -04:00
}
},
paths: [{ in: ['siggen_1'],
out: ['webrtc_1']
}]
}
villasConfig.innerHTML = JSON.stringify(cfg, null, 4);
Prism.highlightAll();
}
// Connect the two peers. Normally you look for and connect to a remote
// machine here, but we're just connecting two local objects, so we can
// bypass that step.
function connectPeers() {
// Create the local connection and its event listeners
pc = new RTCPeerConnection({
iceServers: [{
username: iceUsername,
credential: icePassword,
urls: iceUrls
}]
});
sc = new WebSocket(server + '/' + inpSessionName.value);
sc.onmessage = handleSignalingMessage;
pc.onicecandidate = handleIceCandidate;
pc.onnegotiationneeded = handleNegotationNeeded;
pc.ondatachannel = handleNewDataChannel
// Some more logging
sc.onopen = (e) => console.info('Connected to signaling channel', e);
sc.onerror = (e) => console.error('Failed to establish signaling connection', e);
pc.onconnectionstatechange = () => console.info('Connection state changed:', pc.connectionState);
pc.onsignalingstatechange = () => console.info('Signaling state changed:', pc.signalingState);
pc.oniceconnectionstatechange = () => console.info('ICE connection state changed:', pc.iceConnectionState);
pc.onicegatheringstatechange = () => console.info('ICE gathering state changed:', pc.iceGatheringState);
}
async function handleNegotationNeeded() {
console.info('Negotation needed!');
try {
makingOffer = true;
await pc.setLocalDescription();
let msg = {
description: pc.localDescription.toJSON()
};
console.info('Sending signaling message', msg);
sc.send(JSON.stringify(msg));
} catch (err) {
console.error(err);
} finally {
makingOffer = false;
}
}
function handleIceCandidate(event) {
if (event.candidate == null) {
console.info('Candidate gathering completed');
return;
2022-03-14 15:33:14 -04:00
}
console.info('New local ICE Candidate', event.candidate);
let msg = {
candidate: event.candidate.toJSON()
2022-03-14 15:33:14 -04:00
};
console.info('Sending signaling message', msg);
2022-03-14 15:33:14 -04:00
sc.send(JSON.stringify(msg));
}
async function handleSignalingMessage(event) {
let msg = JSON.parse(event.data);
console.info('Received signaling message', msg);
try {
if (msg.control !== undefined) {
first = true;
for (connection of msg.control.connections) {
if (connection.id < msg.control.connection_id)
first = false;
}
polite = first;
console.info('Role', {
polite: polite,
first: first
})
if (!first) {
// Create the data channel and establish its event listeners
ch = pc.createDataChannel('villas');
handleDataChannel(ch);
}
} else if (msg.description !== undefined) {
const offerCollision = (msg.description.type == 'offer') &&
(makingOffer || pc.signalingState != 'stable');
ignoreOffer = !polite && offerCollision;
if (ignoreOffer) {
return;
}
await pc.setRemoteDescription(msg.description);
console.info(msg.description);
if (msg.description.type == 'offer') {
await pc.setLocalDescription();
let msg = {
description: pc.localDescription.toJSON()
}
sc.send(JSON.stringify(msg))
}
} else if (msg.candidate !== undefined) {
try {
console.info('New remote ICE candidate', msg.candidate);
await pc.addIceCandidate(msg.candidate);
} catch (err) {
if (!ignoreOffer) {
throw err;
}
}
}
} catch (err) {
console.error(err);
}
}
// Handles clicks on the 'Send' button by transmitting
// a message to the remote peer.
function sendMessage() {
var msg = inpMessage.value;
console.info('Sending message', msg);
dc.send(msg);
// Clear the input box and re-focus it, so that we're
// ready for the next message.
inpMessage.value = '';
inpMessage.focus();
}
function handleNewDataChannel(e) {
console.info('New datachannel', e.channel)
handleDataChannel(e.channel);
}
function handleDataChannel(ch) {
dc = ch;
dc.onopen = () => console.info('Datachannel opened');
dc.onclose = () => console.info('Datachannel closed');
dc.onmessage = handleDataChannelMessage;
}
// Handle onmessage events for the receiving channel.
// These are the data messages sent by the sending channel.
2022-04-07 13:16:18 +02:00
async function handleDataChannelMessage(event) {
2022-03-14 15:33:14 -04:00
var dec = new TextDecoder();
var raw = event.data;
2022-04-07 13:16:18 +02:00
var msg = dec.decode(await raw.arrayBuffer());
2022-03-14 15:33:14 -04:00
var msgJson = JSON.parse(msg);
console.info('Received message', msgJson);
var el = document.createElement('span');
el.innerHTML = msg;
Prism.highlightAllUnder(el);
const isScrolledToBottom = receivedContainer.scrollHeight - receivedContainer.clientHeight <= receivedContainer.scrollTop + 1;
received.appendChild(el)
received.innerHTML += '<br>';
if (isScrolledToBottom)
receivedContainer.scrollTop = receivedContainer.scrollHeight - receivedContainer.clientHeight;
}
// Close the connection, including data channels if they're open.
// Also update the UI to reflect the disconnected status.
function disconnectPeers() {
sc.close()
dc.close();
pc.close();
dc = null;
pc = null;
sc = null;
}
// Set up an event listener which will run the startup
// function once the page is done loading.
window.addEventListener('load', startup, false);
})();
</script>
</head>
<body>
<nav class="navbar navbar-expand-lg navbar-light bg-light">
<div class="container-fluid">
<a class="navbar-brand" href="#">
<img src="https://git.rwth-aachen.de/acs/public/villas/node/-/raw/master/doc/pictures/villas_node.svg" alt="VILLASnode logo" width="30" height="24" class="d-inline-block align-text-top"> VILLASnode: Simple WebRTC node example
</a>
<div class="btn-group" role="group" aria-label="Basic example">
<button class="btn btn-outline-success" id="connect">Connect</button>
<button class="btn btn-outline-danger" id="disconnect">Disconnect</button>
</div>
</div>
</nav>
<h1></h1>
<div class="container">
<div class="row">
<h3>Configuration</h3>
<div class="col">
<div class="mb-3">
<label class="form-label" for="message">Session name:</label>
<input class="form-control" type="text" id="session-name">
</div>
<div class="mb-3">
<label class="form-label" for="server">Signaling Server:</label>
<input class="form-control" type="text" id="server">
</div>
</div>
<div class="col">
<div class="row">
<div class="col mb-3">
<label class="form-label" for="ice-username">ICE Username:</label>
<input class="form-control" type="text" id="ice-username">
</div>
<div class="col mb-3">
<label class="form-label" for="ice-password">ICE Password:</label>
<input class="form-control" type="text" id="ice-password">
</div>
</div>
<div class="mb-3">
<label class="form-label" for="ice-urls">ICE URLs:</label>
<input class="form-control" type="text" id="ice-urls">
</div>
</div>
</div>
<h3>VILLASnode setup <button class="btn-sm btn-primary" type="button" data-bs-toggle="collapse" data-bs-target="#collapseExample" aria-expanded="false" aria-controls="collapseExample">Show</button></h3>
<div class="collapse" id="collapseExample">
<div class="card card-body">
<h4>Config</h4>
<p>Copy the contents of the following file into a file named <code>webrtc.conf</code>:</p>
<div>
<div class="filename">webrtc.conf</div>
<pre><code class="language-json" id="villas-config"></code></pre>
</div>
<h4>Invocation</h4>
<p> Make sure <code>webrtc.conf</code> is in your current working directory and then run the following command:</p>
<pre><code>
2022-03-14 19:39:34 -04:00
villas signal sine -r 5 | villas pipe webrtc.conf webrtc_1
2022-03-14 15:33:14 -04:00
</code></pre>
<h5>With Docker</h5>
You can also start VILLASnode via <a href="https://docs.docker.com/get-docker/">Docker</a> by running the following command before the commands in the previous section.
<pre><code>
alias villas='docker run -v $(pwd):/mount -w /mount registry.git.rwth-aachen.de/acs/public/villas/node'
</code></pre>
</div>
</div>
<h3>Send message</h3>
<div class="input-group">
<input type="text" class="form-control" id="message" placeholder="Message text">
<button id="send" class="btn-sm btn-primary">Send</button>
</div>
<h3>Log</h3>
<div id="log-container">
<pre id="log">Press "Connect" in the upper right corner to start<br></pre>
</div>
<h3>Received messages</h3>
<div id="received-container">
<pre id="received"></pre>
</div>
<footer class="bg-light text-center text-lg-start fixed-bottom">
<div class="text-center p-3" style="background-color: rgba(var(--bs-light-rgb),var(--bs-bg-opacity))!important;">
© 2022 Copyright:
<a class="text-dark" href="https://www.acs.eonerc.rwth-aachen.de/">Institute for Automation of Complex Power Systems, RWTH Aachen University</a>
</div>
</footer>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.bundle.min.js" integrity="sha384-MrcW6ZMFYlzcLA8Nl+NtUVF0sA7MsXsP1UyJoMp4YLEuNSfAP+JcXn/tWtIaxVXM" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/prismjs@1.27.0/prism.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/prismjs@1.27.0/plugins/autoloader/prism-autoloader.min.js"></script>
</body>
</html>