Browse Source

Simplified streamer example client

Paul-Louis Ageneau 3 years ago
parent
commit
335e9bd22a
3 changed files with 114 additions and 129 deletions
  1. 104 110
      examples/streamer/client.js
  2. 6 14
      examples/streamer/index.html
  3. 4 5
      examples/streamer/main.cpp

+ 104 - 110
examples/streamer/client.js

@@ -1,32 +1,31 @@
-/** @type {RTCPeerConnection} */
-let rtc;
 const iceConnectionLog = document.getElementById('ice-connection-state'),
     iceGatheringLog = document.getElementById('ice-gathering-state'),
     signalingLog = document.getElementById('signaling-state'),
     dataChannelLog = document.getElementById('data-channel');
 
-function randomString(len) {
-    const charSet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
-    let randomString = '';
-    for (let i = 0; i < len; i++) {
-        const randomPoz = Math.floor(Math.random() * charSet.length);
-        randomString += charSet.substring(randomPoz, randomPoz + 1);
-    }
-    return randomString;
-}
+const clientId = randomId(10);
+const websocket = new WebSocket('ws://127.0.0.1:8000/' + clientId);
 
-const receiveID = randomString(10);
-const websocket = new WebSocket('ws://127.0.0.1:8000/' + receiveID);
-websocket.onopen = function () {
+websocket.onopen = () => {
     document.getElementById('start').disabled = false;
 }
 
-// data channel
-let dc = null, dcTimeout = null;
+websocket.onmessage = async (evt) => {
+    if (typeof evt.data !== 'string') {
+        return;
+    }
+    const message = JSON.parse(evt.data);
+    if (message.type == "offer") {
+        document.getElementById('offer-sdp').textContent = message.sdp;
+        await handleOffer(message)
+    }
+}
+
+let pc = null;
+let dc = null;
 
 function createPeerConnection() {
     const config = {
-        sdpSemantics: 'unified-plan',
         bundlePolicy: "max-bundle",
     };
 
@@ -36,64 +35,59 @@ function createPeerConnection() {
 
     let pc = new RTCPeerConnection(config);
 
-    // register some listeners to help debugging
-    pc.addEventListener('icegatheringstatechange', function () {
-        iceGatheringLog.textContent += ' -> ' + pc.iceGatheringState;
-    }, false);
-    iceGatheringLog.textContent = pc.iceGatheringState;
-
-    pc.addEventListener('iceconnectionstatechange', function () {
-        iceConnectionLog.textContent += ' -> ' + pc.iceConnectionState;
-    }, false);
+    // Register some listeners to help debugging
+    pc.addEventListener('iceconnectionstatechange', () =>
+        iceConnectionLog.textContent += ' -> ' + pc.iceConnectionState);
     iceConnectionLog.textContent = pc.iceConnectionState;
 
-    pc.addEventListener('signalingstatechange', function () {
-        signalingLog.textContent += ' -> ' + pc.signalingState;
-    }, false);
+    pc.addEventListener('icegatheringstatechange', () =>
+        iceGatheringLog.textContent += ' -> ' + pc.iceGatheringState);
+    iceGatheringLog.textContent = pc.iceGatheringState;
+
+    pc.addEventListener('signalingstatechange', () =>
+        signalingLog.textContent += ' -> ' + pc.signalingState);
     signalingLog.textContent = pc.signalingState;
 
-    // connect audio / video
-    pc.addEventListener('track', function (evt) {
+    // Receive audio/video track
+    pc.ontrack = (evt) => {
         document.getElementById('media').style.display = 'block';
-        const videoTag = document.getElementById('video');
-        videoTag.srcObject = evt.streams[0];
-        videoTag.play();
-    });
-
-    let time_start = null;
-
-    function current_stamp() {
-        if (time_start === null) {
-            time_start = new Date().getTime();
-            return 0;
-        } else {
-            return new Date().getTime() - time_start;
+        const video = document.getElementById('video');
+        if (!video.srcObject) {
+          video.srcObject = evt.streams[0]; // The stream groups audio and video tracks
+          video.play();
         }
-    }
+    };
+
+    // Receive data channel
+    pc.ondatachannel = (evt) => {
+        dc = evt.channel;
 
-    pc.ondatachannel = function (event) {
-        dc = event.channel;
-        dc.onopen = function () {
+        dc.onopen = () => {
             dataChannelLog.textContent += '- open\n';
             dataChannelLog.scrollTop = dataChannelLog.scrollHeight;
         };
-        dc.onmessage = function (evt) {
+
+        let dcTimeout = null;
+        dc.onmessage = (evt) => {
+            if (typeof evt.data !== 'string') {
+                return;
+            }
 
             dataChannelLog.textContent += '< ' + evt.data + '\n';
             dataChannelLog.scrollTop = dataChannelLog.scrollHeight;
 
-            dcTimeout = setTimeout(function () {
-                if (dc == null && dcTimeout != null) {
-                    dcTimeout = null;
-                    return
+            dcTimeout = setTimeout(() => {
+                if (!dc) {
+                    return;
                 }
-                const message = 'Pong ' + current_stamp();
+                const message = `Pong ${currentTimestamp()}`;
                 dataChannelLog.textContent += '> ' + message + '\n';
                 dataChannelLog.scrollTop = dataChannelLog.scrollHeight;
                 dc.send(message);
             }, 1000);
         }
-        dc.onclose = function () {
+
+        dc.onclose = () => {
             clearTimeout(dcTimeout);
             dcTimeout = null;
             dataChannelLog.textContent += '- close\n';
@@ -104,61 +98,52 @@ function createPeerConnection() {
     return pc;
 }
 
-function sendAnswer(pc) {
-    return pc.createAnswer()
-        .then((answer) => rtc.setLocalDescription(answer))
-        .then(function () {
-            // wait for ICE gathering to complete
-            return new Promise(function (resolve) {
+async function waitGatheringComplete() {
+    return new Promise((resolve) => {
+        if (pc.iceGatheringState === 'complete') {
+            resolve();
+        } else {
+            pc.addEventListener('icegatheringstatechange', () => {
                 if (pc.iceGatheringState === 'complete') {
                     resolve();
-                } else {
-                    function checkState() {
-                        if (pc.iceGatheringState === 'complete') {
-                            pc.removeEventListener('icegatheringstatechange', checkState);
-                            resolve();
-                        }
-                    }
-
-                    pc.addEventListener('icegatheringstatechange', checkState);
                 }
             });
-        }).then(function () {
-            const answer = pc.localDescription;
-
-            document.getElementById('answer-sdp').textContent = answer.sdp;
-
-            return websocket.send(JSON.stringify(
-                {
-                    id: "server",
-                    type: answer.type,
-                    sdp: answer.sdp,
-                }));
-        }).catch(function (e) {
-            alert(e);
-        });
+        }
+    });
 }
 
-function handleOffer(offer) {
-    rtc = createPeerConnection();
-    return rtc.setRemoteDescription(offer)
-        .then(() => sendAnswer(rtc));
+async function sendAnswer(pc) {
+    await pc.setLocalDescription(await pc.createAnswer());
+    await waitGatheringComplete();
+
+    const answer = pc.localDescription;
+    document.getElementById('answer-sdp').textContent = answer.sdp;
+
+    websocket.send(JSON.stringify({
+        id: "server",
+        type: answer.type,
+        sdp: answer.sdp,
+    }));
 }
 
-function sendStreamRequest() {
-    websocket.send(JSON.stringify(
-        {
-            id: "server",
-            type: "streamRequest",
-            receiver: receiveID,
-        }));
+async function handleOffer(offer) {
+    pc = createPeerConnection();
+    await pc.setRemoteDescription(offer);
+    await sendAnswer(pc);
 }
 
-async function start() {
+function sendRequest() {
+    websocket.send(JSON.stringify({
+        id: "server",
+        type: "request",
+    }));
+}
+
+function start() {
     document.getElementById('start').style.display = 'none';
     document.getElementById('stop').style.display = 'inline-block';
     document.getElementById('media').style.display = 'block';
-    sendStreamRequest();
+    sendRequest();
 }
 
 function stop() {
@@ -173,16 +158,16 @@ function stop() {
     }
 
     // close transceivers
-    if (rtc.getTransceivers) {
-        rtc.getTransceivers().forEach(function (transceiver) {
+    if (pc.getTransceivers) {
+        pc.getTransceivers().forEach((transceiver) => {
             if (transceiver.stop) {
                 transceiver.stop();
             }
         });
     }
 
-    // close local audio / video
-    rtc.getSenders().forEach(function (sender) {
+    // close local audio/video
+    pc.getSenders().forEach((sender) => {
         const track = sender.track;
         if (track !== null) {
             sender.track.stop();
@@ -190,18 +175,27 @@ function stop() {
     });
 
     // close peer connection
-    setTimeout(function () {
-        rtc.close();
-        rtc = null;
+    setTimeout(() => {
+        pc.close();
+        pc = null;
     }, 500);
 }
 
+// Helper function to generate a random ID
+function randomId(length) {
+  const characters = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
+  const pickRandom = () => characters.charAt(Math.floor(Math.random() * characters.length));
+  return [...Array(length) ].map(pickRandom).join('');
+}
 
-websocket.onmessage = async function (evt) {
-    const received_msg = evt.data;
-    const object = JSON.parse(received_msg);
-    if (object.type == "offer") {
-        document.getElementById('offer-sdp').textContent = object.sdp;
-        await handleOffer(object)
+// Helper function to generate a timestamp
+let startTime = null;
+function currentTimestamp() {
+    if (startTime === null) {
+        startTime = Date.now();
+        return 0;
+    } else {
+        return Date.now() - startTime;
     }
 }
+

+ 6 - 14
examples/streamer/index.html

@@ -25,37 +25,30 @@
             max-width: 1280px;
         }
     </style>
+	<script src="https://webrtc.github.io/adapter/adapter-latest.js"></script>
 </head>
 <body>
-<script src="https://webrtc.github.io/adapter/adapter-latest.js"></script>
+<h1>libdatachannel streamer example client</h1>
 
 <h2>Options</h2>
-
 <div class="option">
     <input id="use-stun" type="checkbox"/>
     <label for="use-stun">Use STUN server</label>
 </div>
-
 <button id="start" onclick="start()" disabled>Start</button>
 <button id="stop" style="display: none" onclick="stop()">Stop</button>
 
 <h2>State</h2>
-<p>
-    ICE gathering state: <span id="ice-gathering-state"></span>
-</p>
-<p>
-    ICE connection state: <span id="ice-connection-state"></span>
-</p>
-<p>
-    Signaling state: <span id="signaling-state"></span>
-</p>
+<p>ICE Connection state: <span id="ice-connection-state"></span></p>
+<p>ICE Gathering state: <span id="ice-gathering-state"></span></p>
+<p>Signaling state: <span id="signaling-state"></span></p>
 
 <div id="media" style="display: none">
     <h2>Media</h2>
     <video id="video" autoplay playsinline></video>
 </div>
 
-<h2>Data channel</h2>
+<h2>Data Channel</h2>
 <pre id="data-channel" style="height: 200px;"></pre>
 
 <h2>SDP</h2>
@@ -67,6 +60,5 @@
 <pre id="answer-sdp"></pre>
 
 <script src="client.js"></script>
-
 </body>
 </html>

+ 4 - 5
examples/streamer/main.cpp

@@ -89,18 +89,17 @@ void wsOnMessage(json message, Configuration config, shared_ptr<WebSocket> ws) {
     if (it == message.end())
         return;
     string id = it->get<string>();
+
     it = message.find("type");
     if (it == message.end())
         return;
     string type = it->get<string>();
 
-    if (type == "streamRequest") {
-        shared_ptr<Client> c = createPeerConnection(config, make_weak_ptr(ws), id);
-        clients.emplace(id, c);
+    if (type == "request") {
+        clients.emplace(id, createPeerConnection(config, make_weak_ptr(ws), id));
     } else if (type == "answer") {
-        shared_ptr<Client> c;
         if (auto jt = clients.find(id); jt != clients.end()) {
-            auto pc = clients.at(id)->peerConnection;
+            auto pc = jt->second->peerConnection;
             auto sdp = message["sdp"].get<string>();
             auto description = Description(sdp, type);
             pc->setRemoteDescription(description);