├── .gitignore ├── LICENSE ├── README.md ├── package.json ├── public ├── client.js └── index.html └── server.js /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (http://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # Typescript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # Yarn Integrity file 55 | .yarn-integrity 56 | 57 | # dotenv environment variables file 58 | .env 59 | 60 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 AgilityFeat 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # webrtc-audio-demo 2 | A simple demo app for webrtc audio 3 | 4 | # how to install/run/test 5 | 6 | ~~~~ 7 | cd webrtc-audio-demo 8 | npm install 9 | npm start 10 | ~~~~ 11 | 12 | Hit the URL at http://localhost:3000 13 | 14 | *change the port as you may want to avoid conflicts with other things 15 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "webrtc-audio-demo", 3 | "version": "1.0.0", 4 | "description": "A simple demo app for webrtc audio", 5 | "main": "server.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "start": "node server.js" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "git+https://github.com/agilityfeat/webrtc-audio-demo.git" 13 | }, 14 | "keywords": [], 15 | "author": "", 16 | "license": "ISC", 17 | "bugs": { 18 | "url": "https://github.com/agilityfeat/webrtc-audio-demo/issues" 19 | }, 20 | "homepage": "https://github.com/agilityfeat/webrtc-audio-demo#readme", 21 | "dependencies": { 22 | "express": "^4.16.2", 23 | "socket.io": "^2.0.4" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /public/client.js: -------------------------------------------------------------------------------- 1 | // getting dom elements 2 | var divSelectRoom = document.getElementById("selectRoom"); 3 | var divConferenceRoom = document.getElementById("conferenceRoom"); 4 | var btnGoBoth = document.getElementById("goBoth"); 5 | var btnGoVideoOnly = document.getElementById("goVideoOnly"); 6 | var localVideo = document.getElementById("localVideo"); 7 | var remoteVideo = document.getElementById("remoteVideo"); 8 | var btnMute = document.getElementById("mute"); 9 | var listAudioEvents = document.getElementById("audioEvents"); 10 | 11 | // variables 12 | var roomNumber = 'webrtc-audio-demo'; 13 | var localStream; 14 | var remoteStream; 15 | var rtcPeerConnection; 16 | var iceServers = { 17 | 'iceServers': [{ 18 | 'url': 'stun:stun.services.mozilla.com' 19 | }, 20 | { 21 | 'url': 'stun:stun.l.google.com:19302' 22 | } 23 | ] 24 | } 25 | var streamConstraints; 26 | var isCaller; 27 | 28 | // Let's do this 29 | var socket = io(); 30 | 31 | btnGoBoth.onclick = () => initiateCall(true); 32 | btnGoVideoOnly.onclick = () => initiateCall(false); 33 | btnMute.onclick = toggleAudio; 34 | 35 | function initiateCall(audio) { 36 | streamConstraints = { 37 | video: true, 38 | audio: audio 39 | } 40 | socket.emit('create or join', roomNumber); 41 | divSelectRoom.style = "display: none;"; 42 | divConferenceRoom.style = "display: block;"; 43 | } 44 | 45 | // message handlers 46 | socket.on('created', function (room) { 47 | navigator.mediaDevices.getUserMedia(streamConstraints).then(function (stream) { 48 | addLocalStream(stream); 49 | isCaller = true; 50 | }).catch(function (err) { 51 | console.log('An error ocurred when accessing media devices'); 52 | }); 53 | }); 54 | 55 | socket.on('joined', function (room) { 56 | navigator.mediaDevices.getUserMedia(streamConstraints).then(function (stream) { 57 | addLocalStream(stream); 58 | socket.emit('ready', roomNumber); 59 | }).catch(function (err) { 60 | console.log('An error ocurred when accessing media devices'); 61 | }); 62 | }); 63 | 64 | socket.on('candidate', function (event) { 65 | var candidate = new RTCIceCandidate({ 66 | sdpMLineIndex: event.label, 67 | candidate: event.candidate 68 | }); 69 | rtcPeerConnection.addIceCandidate(candidate); 70 | }); 71 | 72 | socket.on('ready', function () { 73 | if (isCaller) { 74 | createPeerConnection(); 75 | let offerOptions = { 76 | offerToReceiveAudio: 1 77 | } 78 | rtcPeerConnection.createOffer(offerOptions) 79 | .then(desc => setLocalAndOffer(desc)) 80 | .catch(e => console.log(e)); 81 | } 82 | }); 83 | 84 | socket.on('offer', function (event) { 85 | if (!isCaller) { 86 | createPeerConnection(); 87 | rtcPeerConnection.setRemoteDescription(new RTCSessionDescription(event)); 88 | rtcPeerConnection.createAnswer() 89 | .then(desc => setLocalAndAnswer(desc)) 90 | .catch(e => console.log(e)); 91 | } 92 | }); 93 | 94 | socket.on('answer', function (event) { 95 | rtcPeerConnection.setRemoteDescription(new RTCSessionDescription(event)); 96 | }) 97 | 98 | socket.on('toggleAudio', function (event) { 99 | addAudioEvent(event); 100 | }); 101 | 102 | // handler functions 103 | function onIceCandidate(event) { 104 | if (event.candidate) { 105 | console.log('sending ice candidate'); 106 | socket.emit('candidate', { 107 | type: 'candidate', 108 | label: event.candidate.sdpMLineIndex, 109 | id: event.candidate.sdpMid, 110 | candidate: event.candidate.candidate, 111 | room: roomNumber 112 | }) 113 | } 114 | } 115 | 116 | function onAddStream(event) { 117 | remoteVideo.srcObject = event.stream; 118 | remoteStream = event.stream; 119 | if (remoteStream.getAudioTracks().length > 0) { 120 | addAudioEvent('Remote user is sending Audio'); 121 | } else { 122 | addAudioEvent('Remote user is not sending Audio'); 123 | } 124 | } 125 | 126 | function setLocalAndOffer(sessionDescription) { 127 | rtcPeerConnection.setLocalDescription(sessionDescription); 128 | socket.emit('offer', { 129 | type: 'offer', 130 | sdp: sessionDescription, 131 | room: roomNumber 132 | }); 133 | } 134 | 135 | function setLocalAndAnswer(sessionDescription) { 136 | rtcPeerConnection.setLocalDescription(sessionDescription); 137 | socket.emit('answer', { 138 | type: 'answer', 139 | sdp: sessionDescription, 140 | room: roomNumber 141 | }); 142 | } 143 | 144 | //utility functions 145 | function addLocalStream(stream) { 146 | localStream = stream; 147 | localVideo.srcObject = stream 148 | 149 | if (stream.getAudioTracks().length > 0) { 150 | btnMute.style = "display: block"; 151 | } 152 | } 153 | 154 | function createPeerConnection() { 155 | rtcPeerConnection = new RTCPeerConnection(iceServers); 156 | rtcPeerConnection.onicecandidate = onIceCandidate; 157 | rtcPeerConnection.onaddstream = onAddStream; 158 | rtcPeerConnection.addStream(localStream); 159 | } 160 | 161 | function toggleAudio() { 162 | localStream.getAudioTracks()[0].enabled = !localStream.getAudioTracks()[0].enabled 163 | socket.emit('toggleAudio', { 164 | type: 'toggleAudio', 165 | room: roomNumber, 166 | message: localStream.getAudioTracks()[0].enabled ? "Remote user's audio is unmuted" : "Remote user's audio is muted" 167 | }); 168 | } 169 | 170 | function addAudioEvent(event) { 171 | var p = document.createElement("p"); 172 | p.appendChild(document.createTextNode(event)); 173 | listAudioEvents.appendChild(p); 174 | } -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | WebRTC Audio Demo 4 | 17 | 18 | 19 |

WebRTC Audio Demo

20 | 21 |
22 | 23 | 24 |
25 | 26 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | //requires 2 | const express = require('express'); 3 | const app = express(); 4 | var http = require('http').Server(app); 5 | var io = require('socket.io')(http); 6 | 7 | // express routing 8 | app.use(express.static('public')); 9 | 10 | 11 | // signaling 12 | io.on('connection', function (socket) { 13 | console.log('a user connected'); 14 | 15 | socket.on('create or join', function (room) { 16 | console.log('create or join to room ', room); 17 | 18 | var myRoom = io.sockets.adapter.rooms[room] || { length: 0 }; 19 | var numClients = myRoom.length; 20 | 21 | console.log(room, ' has ', numClients, ' clients'); 22 | 23 | if (numClients == 0) { 24 | socket.join(room); 25 | socket.emit('created', room); 26 | } else if (numClients == 1) { 27 | socket.join(room); 28 | socket.emit('joined', room); 29 | } else { 30 | socket.emit('full', room); 31 | } 32 | }); 33 | 34 | socket.on('ready', function (room){ 35 | socket.broadcast.to(room).emit('ready'); 36 | }); 37 | 38 | socket.on('candidate', function (event){ 39 | socket.broadcast.to(event.room).emit('candidate', event); 40 | }); 41 | 42 | socket.on('offer', function(event){ 43 | socket.broadcast.to(event.room).emit('offer',event.sdp); 44 | }); 45 | 46 | socket.on('answer', function(event){ 47 | socket.broadcast.to(event.room).emit('answer',event.sdp); 48 | }); 49 | 50 | socket.on('toggleAudio', function(event){ 51 | socket.broadcast.to(event.room).emit('toggleAudio', event.message); 52 | }); 53 | 54 | }); 55 | 56 | // listener 57 | http.listen(3000, function () { 58 | console.log('listening on *:3000'); 59 | }); --------------------------------------------------------------------------------