├── README.md
├── package.json
├── public
├── index.html
└── client.js
├── LICENSE
├── .gitignore
└── server.js
/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/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | WebRTC Audio Demo
4 |
17 |
18 |
19 | WebRTC Audio Demo
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
Toggle Audio
31 |
Remote audio events
32 |
33 |
34 |
35 |
36 |
37 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/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 | });
--------------------------------------------------------------------------------
/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 | }
--------------------------------------------------------------------------------