From 7ad8283f7f9718addee1486ecd9a4b2dd5897e51 Mon Sep 17 00:00:00 2001 From: "Eric Shepherd (:sheppy)" Date: Mon, 27 Aug 2018 06:34:28 -0400 Subject: [PATCH] Update the sample for WebRTC signaling & video calling to the latest code from the Glitch version now being referenced from the documentation. --- s/webrtc-from-chat/chat.css | 179 ----------- s/webrtc-from-chat/chatserver.js | 286 ++++++++---------- s/webrtc-from-chat/index.html | 75 ----- .../{chatclient.js => public/client.js} | 179 +++++------ s/webrtc-from-chat/public/style.css | 159 ++++++++++ s/webrtc-from-chat/startup.sh | 2 +- s/webrtc-from-chat/views/index.html | 57 ++++ 7 files changed, 441 insertions(+), 496 deletions(-) delete mode 100644 s/webrtc-from-chat/chat.css delete mode 100644 s/webrtc-from-chat/index.html rename s/webrtc-from-chat/{chatclient.js => public/client.js} (83%) create mode 100644 s/webrtc-from-chat/public/style.css create mode 100644 s/webrtc-from-chat/views/index.html diff --git a/s/webrtc-from-chat/chat.css b/s/webrtc-from-chat/chat.css deleted file mode 100644 index fe602d0..0000000 --- a/s/webrtc-from-chat/chat.css +++ /dev/null @@ -1,179 +0,0 @@ -/* - WebSocket chat client - - WebSocket and WebRTC based multi-user chat sample with two-way video - calling, including use of TURN if applicable or necessary. - - This file describes the styling for the contents of the site as - presented to users. - - To read about how this sample works: http://bit.ly/webrtc-from-chat - - Any copyright is dedicated to the Public Domain. - http: creativecommons.org/publicdomain/zero/1.0/ -*/ - -/* The list of users on the left side */ - -#userlistbox { - border: 1px solid black; - width:100%; - height:740px; - margin-top:0px; - margin-right:10px; - padding:1px; - list-style:none; - display:block; - line-height:1.1; - overflow-y:auto; - overflow-x:hidden; -} - -#userlistbox li { - cursor: pointer; - padding: 1px; -} - -/* The - - - -
-
- - - -
-
- - -
-
-
- Chat: - -
-
- - - diff --git a/s/webrtc-from-chat/chatclient.js b/s/webrtc-from-chat/public/client.js similarity index 83% rename from s/webrtc-from-chat/chatclient.js rename to s/webrtc-from-chat/public/client.js index 1adcacb..634b717 100644 --- a/s/webrtc-from-chat/chatclient.js +++ b/s/webrtc-from-chat/public/client.js @@ -11,10 +11,11 @@ "use strict"; -// Get our hostname +// Get the hostname of the server; this will be used to open a +// connection to the WebSocket server later. var myHostname = window.location.hostname; -console.log("Hostname: " + myHostname); +console.log("Server hostname: " + myHostname); // WebSocket chat/signaling channel variables. @@ -43,11 +44,6 @@ var myUsername = null; var targetUsername = null; // To store username of other peer var myPeerConnection = null; // RTCPeerConnection -// To work both with and without addTrack() we need to note -// if it's available - -var hasAddTrack = false; - // Output logging information to console. function log(text) { @@ -96,22 +92,35 @@ function connect() { var scheme = "ws"; // If this is an HTTPS connection, we have to use a secure WebSocket - // connection too, so add another "s" to the scheme. - + // connection too, so add another "s" to the scheme. + if (document.location.protocol === "https:") { scheme += "s"; } - serverUrl = scheme + "://" + myHostname + ":6503"; + + // Build the URL of the WebSocket server; in this case, it's the same + // as the web server. Be sure to add ":" if the WebSocket + // service is on a different port. + + serverUrl = scheme + "://" + myHostname; + + // Connect to the WebSocket server, using the "json" protocol. connection = new WebSocket(serverUrl, "json"); + // Handle the WebSocket "open" message; this occurs when the WebSocket + // connection is successfully opened. + connection.onopen = function(evt) { document.getElementById("text").disabled = false; document.getElementById("send").disabled = false; }; + // Handle the "message" WebSocket event. This occurs when the + // chat/signaling server sends a JSON message to the client. + connection.onmessage = function(evt) { - var chatFrameDocument = document.getElementById("chatbox").contentDocument; + var chatBox = document.querySelector(".chatbox"); var text = ""; var msg = JSON.parse(evt.data); log("Message received: "); @@ -119,29 +128,48 @@ function connect() { var time = new Date(msg.date); var timeStr = time.toLocaleTimeString(); + // Handle the received JSON messages. + switch(msg.type) { + + // The "id" message is sent immediately after connecting, to tell + // the client what it's unique ID number is. We respond by sending + // our proposed username to the server. + case "id": clientID = msg.id; setUsername(); break; + + // The "username" message is sent to indicate that a user has + // logged in. case "username": text = "User " + msg.name + " signed in at " + timeStr + "
"; break; - case "message": - text = "(" + timeStr + ") " + msg.name + ": " + msg.text + "
"; - break; - + // The "rejectusername" message is sent if the user name we + // propose in response to the "id" message is already taken. + case "rejectusername": myUsername = msg.name; text = "Your username has been set to " + myUsername + " because the name you chose is in use.
"; break; + // The "userlist" message is sent to update the list of users + // displayed in our sidebar. + case "userlist": // Received an updated user list handleUserlistMsg(msg); break; + + // The "message" message contains a message posted by one of the + // connected users (including potentially ourselves). + + case "message": + text = "(" + timeStr + ") " + msg.name + ": " + msg.text + "
"; + break; // Signaling messages: these messages are used to trade WebRTC // signaling information during negotiations leading up to a video @@ -174,14 +202,15 @@ function connect() { // scroll the chat panel so that the new text is visible. if (text.length) { - chatFrameDocument.write(text); - document.getElementById("chatbox").contentWindow.scrollByPages(1); + chatBox.innerHTML += text; + chatBox.scrollTop = chatBox.scrollHeight - chatBox.clientHeight; } }; } // Handles a click on the Send button (or pressing return/enter) by // building a "message" object and sending it to the server. + function handleSendButton() { var msg = { text: document.getElementById("text").value, @@ -196,6 +225,7 @@ function handleSendButton() { // Handler for keyboard events. This is used to intercept the return and // enter keys so that we can call send() to transmit the entered text // to the server. + function handleKey(evt) { if (evt.keyCode === 13 || evt.keyCode === 14) { if (!document.getElementById("send").disabled) { @@ -219,34 +249,20 @@ function createPeerConnection() { myPeerConnection = new RTCPeerConnection({ iceServers: [ // Information about ICE servers - Use your own! { - urls: "turn:" + myHostname, // A TURN server - username: "webrtc", - credential: "turnserver" + urls: "stun:stun.stunprotocol.org" } ] }); - // Do we have addTrack()? If not, we will use streams instead. - - hasAddTrack = (myPeerConnection.addTrack !== undefined); - // Set up event handlers for the ICE negotiation process. myPeerConnection.onicecandidate = handleICECandidateEvent; - myPeerConnection.onnremovestream = handleRemoveStreamEvent; + myPeerConnection.ontrack = handleTrackEvent; + myPeerConnection.onremovetrack = handleRemoveTrackEvent; myPeerConnection.oniceconnectionstatechange = handleICEConnectionStateChangeEvent; myPeerConnection.onicegatheringstatechange = handleICEGatheringStateChangeEvent; myPeerConnection.onsignalingstatechange = handleSignalingStateChangeEvent; myPeerConnection.onnegotiationneeded = handleNegotiationNeededEvent; - - // Because the deprecation of addStream() and the addstream event is recent, - // we need to use those if addTrack() and track aren't available. - - if (hasAddTrack) { - myPeerConnection.ontrack = handleTrackEvent; - } else { - myPeerConnection.onaddstream = handleAddStreamEvent; - } } // Called by the WebRTC layer to let us know when it's time to @@ -276,9 +292,7 @@ function handleNegotiationNeededEvent() { .catch(reportError); } -// Called by the WebRTC layer when events occur on the media tracks -// on our WebRTC call. This includes when streams are added to and -// removed from the call. +// Called by the WebRTC layer when tracks are added to the connection. // // track events include the following fields: // @@ -293,27 +307,25 @@ function handleTrackEvent(event) { document.getElementById("hangup-button").disabled = false; } -// Called by the WebRTC layer when a stream starts arriving from the -// remote peer. We use this to update our user interface, in this -// example. - -function handleAddStreamEvent(event) { - log("*** Stream added"); - document.getElementById("received_video").srcObject = event.stream; - document.getElementById("hangup-button").disabled = false; -} - -// An event handler which is called when the remote end of the connection -// removes its stream. We consider this the same as hanging up the call. -// It could just as well be treated as a "mute". +// Handler for the |removetrack| event; this event is is received +// by the RTCPeerConnection whenever a track is removed from the +// connection's media stream. // -// Note that currently, the spec is hazy on exactly when this and other -// "connection failure" scenarios should occur, so sometimes they simply -// don't happen. - -function handleRemoveStreamEvent(event) { - log("*** Stream removed"); - closeVideoCall(); +// We test to determine if the track removed was the only track left, +// and if so, we close the connection. + +function handleRemoveTrackEvent(event) { + var stream = document.getElementById("received_video").srcObject; + var trackList = stream.getTracks(); + + var track = event.track; + var kind = track.kind.charAt(0).toUpperCase() + track.kind.slice(1); + log(track.kind + " track removed from incoming stream: " + track.label); + + if (trackList.length == 0) { + log("*** All tracks removed; closing connection"); + closeVideoCall(); + } } // Handles |icecandidate| events by forwarding the specified @@ -385,8 +397,7 @@ function handleICEGatheringStateChangeEvent(event) { function handleUserlistMsg(msg) { var i; - - var listElem = document.getElementById("userlistbox"); + var listElem = document.querySelector(".userlistbox"); // Remove all current list members. We could do this smarter, // by adding and updating users instead of rebuilding from @@ -396,15 +407,15 @@ function handleUserlistMsg(msg) { listElem.removeChild(listElem.firstChild); } - // Add member names from the received list + // Add member names from the received list. - for (i=0; i < msg.users.length; i++) { + msg.users.forEach(function(username) { var item = document.createElement("li"); - item.appendChild(document.createTextNode(msg.users[i])); + item.appendChild(document.createTextNode(username)); item.addEventListener("click", invite, false); listElem.appendChild(item); - } + }); } // Close the RTCPeerConnection and reset variables so that the user can @@ -426,16 +437,16 @@ function closeVideoCall() { // Disconnect all our event listeners; we don't want stray events // to interfere with the hangup while it's ongoing. - myPeerConnection.onaddstream = null; // For older implementations - myPeerConnection.ontrack = null; // For newer ones - myPeerConnection.onremovestream = null; + myPeerConnection.ontrack = null; + myPeerConnection.onremovetrack = null; myPeerConnection.onnicecandidate = null; myPeerConnection.oniceconnectionstatechange = null; myPeerConnection.onsignalingstatechange = null; myPeerConnection.onicegatheringstatechange = null; myPeerConnection.onnotificationneeded = null; - // Stop the videos + // Stop the videos by iterating over their tracks, stopping each + // one by one. if (remoteVideo.srcObject) { remoteVideo.srcObject.getTracks().forEach(track => track.stop()); @@ -445,14 +456,18 @@ function closeVideoCall() { localVideo.srcObject.getTracks().forEach(track => track.stop()); } - remoteVideo.src = null; - localVideo.src = null; - // Close the peer connection myPeerConnection.close(); myPeerConnection = null; } + + // Detach the streams from the video elements. + + remoteVideo.removeAttribute("src"); + remoteVideo.removeAttribute("srcObject"); + localVideo.removeAttribute("src"); + localVideo.removeAttribute("srcObject"); // Disable the hangup button @@ -487,7 +502,7 @@ function hangUpCall() { // Handle a click on an item in the user list by inviting the clicked // user to video chat. Note that we don't actually send a message to -// the callee here -- calling RTCPeerConnection.addStream() issues +// the callee here -- adding tracks to the connection triggers a // a |notificationneeded| event, so we'll let our handler for that // make the offer. @@ -524,16 +539,10 @@ function invite(evt) { navigator.mediaDevices.getUserMedia(mediaConstraints) .then(function(localStream) { log("-- Local video stream obtained"); - document.getElementById("local_video").src = window.URL.createObjectURL(localStream); document.getElementById("local_video").srcObject = localStream; - if (hasAddTrack) { - log("-- Adding tracks to the RTCPeerConnection"); - localStream.getTracks().forEach(track => myPeerConnection.addTrack(track, localStream)); - } else { - log("-- Adding stream to the RTCPeerConnection"); - myPeerConnection.addStream(localStream); - } + log("-- Adding incoming tracks to the RTCPeerConnection"); + localStream.getTracks().forEach(track => myPeerConnection.addTrack(track, localStream)); }) .catch(handleGetUserMediaError); } @@ -565,18 +574,10 @@ function handleVideoOfferMsg(msg) { .then(function(stream) { log("-- Local video stream obtained"); localStream = stream; - document.getElementById("local_video").src = window.URL.createObjectURL(localStream); document.getElementById("local_video").srcObject = localStream; - if (hasAddTrack) { - log("-- Adding tracks to the RTCPeerConnection"); - localStream.getTracks().forEach(track => - myPeerConnection.addTrack(track, localStream) - ); - } else { - log("-- Adding stream to the RTCPeerConnection"); - myPeerConnection.addStream(localStream); - } + log("-- Adding outgoing tracks to the RTCPeerConnection"); + localStream.getTracks().forEach(track => myPeerConnection.addTrack(track, localStream)); }) .then(function() { log("------> Creating answer"); diff --git a/s/webrtc-from-chat/public/style.css b/s/webrtc-from-chat/public/style.css new file mode 100644 index 0000000..51f6006 --- /dev/null +++ b/s/webrtc-from-chat/public/style.css @@ -0,0 +1,159 @@ +/* + WebSocket chat client + + WebSocket and WebRTC based multi-user chat sample with two-way video + calling, including use of TURN if applicable or necessary. + + This file describes the styling for the contents of the site as + presented to users. + + To read about how this sample works: http://bit.ly/webrtc-from-chat + + Any copyright is dedicated to the Public Domain. + http: creativecommons.org/publicdomain/zero/1.0/ +*/ + +html, body { + height: 100%; + font-family:'Open Sans', 'Lucida Grande', Arial, Helvetica, sans-serif; +} + +.mdn-disclaimer { + font-size:18px; + background-color: #ddd; + color: black; + margin-left: 80px; + margin-right: 80px; + max-width: 620px; + padding: 12px; + border-radius: 5px; + border: 1px solid black; + box-shadow: 1px 1px 2px black; +} + +.mdn-footer { + font-weight:normal; + font-size:12px; + text-align:right; + border-top:2px solid #000000; +} + +/* The grid that contains it all */ + +.container { + display: grid; + width: 100%; + height: 100%; + grid-template-areas: "infobox infobox infobox" + "userlistbox chatbox video-container" + "empty-container chat-controls video-container"; + grid-template-columns: 16em 1fr 500px; + grid-template-rows: 16em 1fr 5em; + grid-gap: 1rem; +} + +.infobox { + grid-area: infobox; + overflow: auto; +} + +.userlistbox { + grid-area: userlistbox; + border: 1px solid black; + width:100%; + height:100%; + margin:0; + padding:1px; + list-style:none; + line-height:1.1; + overflow-y:auto; + overflow-x:hidden; +} + +.userlistbox li { + cursor: pointer; + padding: 1px; +} + +.chatbox { + grid-area: chatbox; + border: 1px solid black; + width:100%; + height:100%; + margin: 0; + overflow-y: scroll; + padding: 1px; + padding: 0.1rem 0.5rem; +} + +.video-container { + grid-area: video-container; +} + +.camerabox { + width: 100%; + height: 100%; + vertical-align:top; + display: block; + position:relative; + overflow:auto; +} + +#received_video { + width: 480px; + height: 360px; + border: 1px solid black; + box-shadow: 2px 2px 3px black; + right:10px; + position:absolute; +} + +/* The small "preview" view of your camera */ +#local_video { + width: 120px; + height: 90px; + position: absolute; + top: 1rem; + left: 1.5rem; + border: 1px solid rgba(255, 255, 255, 0.75); + box-shadow: 0 0 4px black; +} + +/* The "Hang up" button */ +#hangup-button { + display:block; + width:80px; + height:24px; + border-radius: 8px; + position:relative; + margin:auto; + top:324px; + background-color: rgba(150, 0, 0, 0.5); + border: 1px solid rgba(255, 255, 255, 0.7); + box-shadow: 0px 0px 1px 2px rgba(0, 0, 0, 0.2); + font-size: 14px; + font-family: "Lucida Grande", "Arial", sans-serif; + color: rgba(255, 255, 255, 1.0); + cursor: pointer; +} + +#hangup-button:hover { + filter: brightness(150%); + -webkit-filter: brightness(150%); +} + +#hangup-button:disabled { + filter: grayscale(50%); + -webkit-filter: grayscale(50%); + cursor: default; +} + +.empty-container { + grid-area: empty-container; +} + +.chat-controls { + grid-area: chat-controls; + width: 100%; + height: 100%; +} diff --git a/s/webrtc-from-chat/startup.sh b/s/webrtc-from-chat/startup.sh index e011bf6..cbc1112 100755 --- a/s/webrtc-from-chat/startup.sh +++ b/s/webrtc-from-chat/startup.sh @@ -15,6 +15,6 @@ npm install websocket npm install webrtc-adapter -cp node_modules/webrtc-adapter/out/adapter.js . +cp node_modules/webrtc-adapter/out/adapter.js public/adapter.js node chatserver.js diff --git a/s/webrtc-from-chat/views/index.html b/s/webrtc-from-chat/views/index.html new file mode 100644 index 0000000..9765cea --- /dev/null +++ b/s/webrtc-from-chat/views/index.html @@ -0,0 +1,57 @@ + + + + + + + WebSocket Chat Demo with WebRTC Calling + + + + + + +
+

This is a simple chat system implemented using WebSockets. It works by sending packets of JSON back and forth with the server. + + Check out the source on Github.

+

This text and audio/video chat example is offered as-is for demonstration purposes only, and should not be used for any other purpose. +

+

Click a username in the user list to ask them to enter a one-on-one video chat with you.

+

Enter a username: +

+
+ +
+
+
+ + + +
+
+
+
+ Chat:
+ + +
+ + +