|
- <!DOCTYPE html>
- <html>
- <head>
- <meta charset="utf-8"/>
- <title>Simple WebRTC</title>
- <style>
- body.justVideo {
- margin: 0;
- overflow: hidden;
- }
- #selfView {
- position: fixed;
- right: 0;
- bottom: 0;
- border: 5px solid blue;
- }
- #unmute, #start {
- position: fixed;
- top: 0;
- width: 100%;
- font-size: 4em;
- }
- form {
- background: lightgray;
- border: solid gold 5px;
- display: inline-block;
- display: table;
- }
- form label {
- display: table-row;
- }
- #status {
- clear: both;
- }
- </style>
- </head>
- <body>
- <a id="qrcodelink" style="display:none;"><img id="qrcode" /></a>
- <form name="settings">
- <label>
- Recieve remote video:
- <select name="client-video">
- <option value="none">none</option>
- <option value="environment" selected>rear camera</option>
- <option value="user">front camera</option>
- <option value="true" selected>any camera</option>
- <option value="screen">screen share</option>
- </select>
- </label>
- <label>
- Recieve remote audio:
- <input name="client-audio" type="checkbox" value="true" />
- </label>
- <label>
- Transmit video:
- <select name="host-video">
- <option value="none" selected>none</option>
- <option value="true">any camera</option>
- <option value="screen">screen share</option>
- </select>
- </label>
- <label>
- Transmit audio:
- <input name="host-audio" type="checkbox" value="true" />
- </label>
- </form>
- <div id="status"></div>
- <div id="videos">
- <button id="unmute" style="display:none;">Unmute</button>
- <button id="start" style="display:none;">Start Streaming</button>
- <video id="remoteView" width="100%" autoplay muted style="display:none;"></video>
- <video id="selfView" width="200" height="150" autoplay muted style="display:none;"></video>
- </div>
- <script >
- const create = (container, type) => container.appendChild(document.createElement(type));
-
- const body = document.querySelector("body");
- const out = document.getElementById("status");
- const qrcode = document.getElementById("qrcode");
- const qrcodelink = document.getElementById("qrcodelink");
- const remoteView = document.getElementById("remoteView");
- const selfView = document.getElementById("selfView");
- const start = document.getElementById("start");
- const unmute = document.getElementById("unmute");
- const form = document.forms["settings"];
-
- out.innerText += "Loading...\n";
-
- unmute.addEventListener("click", _ => {
- remoteView.muted = false;
- unmute.style.display = 'none';
- });
-
- var settings = undefined;
- function readSettingsForm() {
- const obj = {};
- for (const el of form.elements) {
- obj[el.name] = el.type == 'checkbox' ? el.checked : el.value;
- el.disabled = true;
- }
- return obj;
- }
-
- function getRoomName() {
- JSON.parse(document.getElementById('room-name').textContent);
- }
-
- let roomName = window.location.hash;
- let isHost = roomName === undefined || !roomName;
- if (isHost) {
- // From https://stackoverflow.com/a/1349426
- function makeid(length) {
- var result = '';
- var characters = 'abcdefghijklmnopqrstuvwxyz';
- var charactersLength = characters.length;
- for (var i = 0; i < length; i++) {
- result += characters.charAt(Math.floor(Math.random() * charactersLength));
- }
- return result;
- }
-
- roomName = makeid(8);
- qrcodelink.href = window.location.href.split('#')[0] + '#' + roomName;
- qrcode.src = window.location.href.split('#')[0] + roomName + '/qr';
- qrcodelink.style.display = '';
- } else {
- roomName = roomName.substring(1);
- qrcodelink.style.display = 'none';
- form.style.display = 'none';
- }
-
- out.innerText += "Room: " + roomName + "\n";
-
- var webSocket = undefined;
-
- function sendJson(data) {
- const toSend = JSON.stringify(data);
- out.innerText += "Sending message...\n";
- create(out, 'pre').innerText = toSend.split('\\r\\n').join('\r\n');
- create(out, 'br');
- webSocket.send(toSend);
- }
-
- var pc = undefined;
- function createRTCPeerConnection() {
- const pc = new RTCPeerConnection();
- out.innerText += "Created RTCPeerConnection.\n";
-
- pc.onicecandidate = ({candidate}) => sendJson({candidate});
-
- // let the "negotiationneeded" event trigger offer generation
- pc.onnegotiationneeded = async function () {
- out.innerText += "In pc.onnegotiationneeded...\n";
- await pc.setLocalDescription(await pc.createOffer());
- sendJson({
- description: pc.localDescription
- });
- }
-
- pc.ontrack = ({streams: [stream]}) => {
- out.innerText += "In pc.ontrack...\n";
-
- remoteView.srcObject = stream;
- remoteView.style.display = '';
- remoteView.play();
- out.innerText += "Set srcObject\n";
- out.style.display = 'none';
- form.style.display = 'none';
- videos.style.display = '';
- body.classList.add('justVideo');
- };
-
- return pc;
- }
-
- // get a local stream, show it in a self-view and add it to be sent
- async function startStreaming(fromButton) {
- const otherAudioSettings = isHost
- ? settings['client-audio']
- : settings['host-audio'];
- if (otherAudioSettings) {
- unmute.style.display = '';
- }
-
- const videoSettings = isHost
- ? settings['host-video']
- : settings['client-video'];
- out.innerText += "videoSettings=" + videoSettings + "\n";
- const audioSettings = isHost
- ? settings['host-audio']
- : settings['client-audio'];
- out.innerText += "audioSettings=" + audioSettings + "\n";
-
- if (videoSettings == 'screen' && !fromButton) {
- start.style.display = '';
- return;
- }
- start.style.display = 'none';
-
- if (isHost) {
- sendJson({
- settings: settings
- });
- }
-
- if (pc !== undefined) return;
- pc = createRTCPeerConnection();
-
- const videoConstraints = videoSettings == 'none'
- ? false
- : videoSettings == 'true'
- ? true
- : { advanced: [{facingMode: videoSettings}] };
- out.innerText += "Created videoConstraints.\n";
- if (!videoConstraints && !audioSettings) return;
-
- const stream = videoSettings == 'screen'
- ? await navigator.mediaDevices.getDisplayMedia({
- audio: audioSettings,
- video: true
- })
- : await navigator.mediaDevices.getUserMedia({
- audio: audioSettings,
- video: videoConstraints
- });
- out.innerText += "Created stream.\n";
- if (videoConstraints) {
- selfView.srcObject = stream;
- selfView.style.display = '';
- }
- for (const track of stream.getTracks()) {
- out.innerText += "Added track.\n";
- pc.addTrack(track, stream);
- }
- }
- function startStartingWithErorrHandling(fromButton) {
- startStreaming(fromButton)
- .then(() => {
- out.innerText += "startStreaming() finished.\n";
- })
- .catch(e => {
- out.innerText += "startStreaming() errored: " + e.message + "\n";
- });
- }
-
- start.addEventListener("click", _ => {
- startStartingWithErorrHandling(true)
- });
-
- async function receiveMessage(e) {
- qrcode.style.display = 'none';
- out.innerText += "In webSocket.onmessage...\n";
- create(out, 'pre').innerText = e.data.split('\\r\\n').join('\r\n');
- create(out, 'br');
- const data = JSON.parse(e.data);
- if (data.requestSettings) {
- settings = readSettingsForm();
- startStartingWithErorrHandling(false);
- } else if (data.settings) {
- settings = data.settings;
- startStartingWithErorrHandling(false);
- } else if (data.description) {
- await pc.setRemoteDescription(data.description);
- if (data.description.type == "offer") {
- out.innerText += "Got an offer...\n";
- await pc.setLocalDescription(await pc.createAnswer());
- sendJson({
- description: pc.localDescription
- });
- }
- } else if (data.candidate) {
- out.innerText += "Adding ice candidate...\n";
- await pc.addIceCandidate(data.candidate);
- }
- };
-
- function createWebSocket() {
- const webSocket = new WebSocket(
- 'ws' + (window.location.protocol == 'https:' ? 's' : '') + '://'
- + window.location.host
- + '/camera/ws/' + (isHost ? 'host' : 'client') + '/'
- + roomName
- + '/'
- );
- out.innerText += "Created WebSocket.\n";
-
- webSocket.onclose = function(e) {
- out.innerText += 'WebSocket closed unexpectedly: ' + e + '\n';
- };
- webSocket.onerror = function(e) {
- out.innerText += 'WebSocket error: ' + e + '\n';
- };
-
- webSocket.onmessage = receiveMessage;
-
- return webSocket;
- }
-
- webSocket = createWebSocket();
-
- if (!isHost) {
- webSocket.onopen = _ => sendJson({requestSettings: true});
- }
-
- out.innerText += "Finished <script> block.\n";
- </script>
- </body>
- </html>
|