blob: f8b27b930ddc97227548553600bdfffd15012f28 [file] [log] [blame]
<!DOCTYPE html>
<html>
<head lang="en">
<meta charset="UTF-8">
<title>WebRTC Data Channel Example</title>
<style>
@keyframes new-fade {
0% {
background-color: #fffb85;
}
100% {
background-color: transparent;
}
}
html, body {
margin: 0;
padding: 0;
height: 100%;
}
body {
height: 100vh;
display: flex;
}
main {
width: 100%;
display: flex;
flex-grow: 1;
flex-wrap: nowrap;
}
section, aside {
padding: 5px;
flex-grow: 1;
flex-basis: 0;
}
aside {
overflow-y: auto;
}
section textarea {
width: 100%;
}
#log {
margin: 0;
padding: 0;
flex-grow: 1;
flex-basis: 0;
}
#log > * {
margin: 2px 0;
padding: 0 6px;
animation: new-fade 1.5s ease-out 1;
}
#log .debug, #log .log {
border-left: 2px solid lightgrey;
}
#log .error {
border-left: 2px solid red;
}
#log .info {
border-left: 2px solid cornflowerblue;
}
#log .warn {
border-left: 2px solid orange;
}
</style>
</head>
<body>
<main>
<section>
<form autocomplete="off">
<!-- Role -->
<label for="role">Role: Offering</label>
<input type="checkbox" id="role" checked>
<br>
<!-- Message size to be used -->
<label for="message-size">Message size (bytes):</label>
<input id="message-size" type="number" name="message-size" value="16384" step="16384">
<br>
<!-- WebSocket URL & Start button -->
<input type="text" id="ws-url" placeholder="Optional Server URL">
<button type="button" id="start" disabled>Start</button>
<br><hr>
<!-- Copy local description from this textarea -->
Copy local description:<br>
<textarea rows="15" id="local-description" disabled readonly></textarea>
<!-- Paste remote description into this textarea -->
Paste remote description:<br>
<textarea rows="15" id="remote-description" disabled></textarea>
<br>
</form>
</section>
<aside>
<pre id="log"></pre>
</aside>
</main>
<!-- Import dependencies -->
<script src="https://webrtc.github.io/adapter/adapter-6.1.1.js"></script>
<script src="signaling.js"></script>
<script src="peerconnection.js"></script>
<!-- UI code -->
<script>
'use strict';
// Get elements
const roleCheckbox = document.getElementById('role');
const messageSizeInput = document.getElementById('message-size');
const localDescriptionTextarea = document.getElementById('local-description');
const remoteDescriptionTextarea = document.getElementById('remote-description');
const wsUrlInput = document.getElementById('ws-url');
const startButton = document.getElementById('start');
const logPre = document.getElementById('log');
// Load & store persistent values
if (typeof(Storage) !== 'undefined') {
const persistentElements = [
['role', 'checked', (value) => value === 'true'],
['message-size', 'value'],
['ws-url', 'value']
];
for (const [id, property, transform] of persistentElements) {
let value = localStorage.getItem(id);
const element = document.getElementById(id);
if (transform !== undefined) {
value = transform(value);
}
if (value !== null) {
element[property] = value;
}
element.addEventListener('change', () => {
localStorage.setItem(id, element[property]);
});
}
}
// Display console logs in the browser as well
for (const name of ['debug', 'error', 'info', 'log', 'warn']) {
const method = window.console[name];
window.console[name] = function() {
method.apply(null, arguments);
const entry = document.createElement('div');
entry.classList.add(name);
for (let i = 0; i < arguments.length; ++i) {
let item = arguments[i];
if (typeof arguments[i] === 'object') {
entry.innerHTML += JSON.stringify(item, null, 2) + ' ';
} else {
entry.innerHTML += item + ' ';
}
}
logPre.prepend(entry);
};
}
// Auto-select all text when clicking local description
localDescriptionTextarea.addEventListener('click', function() {
this.select();
});
// Bind start button & enable
startButton.addEventListener('click', () => {
roleCheckbox.disabled = true;
messageSizeInput.disabled = true;
wsUrlInput.disabled = true;
startButton.disabled = true;
localDescriptionTextarea.disabled = false;
start(roleCheckbox.checked);
});
startButton.disabled = false;
const start = (offering) => {
console.info('Starting with role:', offering ? 'Offering' : 'Answering');
// Create signaling instance
const wsUrl = wsUrlInput.value;
let signaling;
if (wsUrl === '') {
signaling = new CopyPasteSignaling();
} else {
signaling = new WebSocketSignaling(wsUrl + (offering ? '/1' : '/0'));
}
signaling.onLocalDescriptionUpdate = (description) => {
localDescriptionTextarea.value = JSON.stringify(description);
// Enable remote description once local description has been set
remoteDescriptionTextarea.disabled = false;
};
signaling.onRemoteDescriptionUpdate = (description) => {
remoteDescriptionTextarea.value = JSON.stringify(description);
};
// Create peer connection instance
const pc = new WebRTCPeerConnection(signaling, offering);
window.pc = pc;
// Apply remote description when pasting
const onRemoteDescriptionTextareaChange = () => {
// Remove event listener
remoteDescriptionTextarea.oninput = null;
remoteDescriptionTextarea.onchange = null;
// Apply remote description once (needs to include candidates)
const description = JSON.parse(remoteDescriptionTextarea.value);
signaling.handleRemoteDescription(description, true)
.catch((error) => console.error(error));
// Make read-only
remoteDescriptionTextarea.readOnly = true;
};
remoteDescriptionTextarea.oninput = onRemoteDescriptionTextareaChange;
remoteDescriptionTextarea.onchange = onRemoteDescriptionTextareaChange;
// Enable remote description early (if not offering)
if (!offering) {
remoteDescriptionTextarea.disabled = false;
}
// Get message size
const messageSize = parseInt(messageSizeInput.value);
if (isNaN(messageSize)) {
throw 'Invalid message size value';
}
// Create data channels
const createDataChannelWithName = (
name, options = null, createOnOpenWithName = null, createOnOpenOptions = null
) => {
const dc = pc.createDataChannel(name, options);
const defaultOnOpenHandler = dc.onopen;
dc.onopen = (event) => {
defaultOnOpenHandler(event);
if (createOnOpenWithName !== null) {
window.setTimeout(() => {
createDataChannelWithName(createOnOpenWithName, createOnOpenOptions);
}, 1000);
}
if (messageSize > pc.pc.sctp.maxMessageSize) {
console.warn(dc._name, 'message size (' + messageSize + ') > maximum message size' +
' (' + pc.pc.sctp.maxMessageSize + ')');
}
let data = new Uint8Array(messageSize);
console.log(dc._name, 'outgoing message (' + data.byteLength + ' bytes)');
try {
dc.send(data);
} catch (error) {
if (error.name === 'TypeError') {
console.error(dc._name, 'message too large to send');
} else {
console.error(dc._name, 'Unknown error:', error.name);
}
}
};
};
createDataChannelWithName('cat-noises', {
negotiated: true,
id: 0,
}, 'dinosaur-noises');
};
// Introduction
console.info("Hello! Press 'Start' when you're ready.");
</script>
</body>
</html>