Skip to content

Commit 40325c7

Browse files
author
John Crisp
committed
added demo server project from aiortc example
1 parent ae8f6a0 commit 40325c7

File tree

6 files changed

+633
-0
lines changed

6 files changed

+633
-0
lines changed

server/Dockerfile

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
FROM python:3.7-buster
2+
3+
RUN apt update
4+
5+
RUN apt install -y python3-opencv
6+
7+
RUN mkdir /app
8+
9+
WORKDIR /app
10+
11+
ADD requirements.txt /app
12+
13+
RUN pip install --upgrade pip
14+
15+
RUN pip install -r requirements.txt
16+
17+
ADD . /app
18+
19+
ENTRYPOINT ["gunicorn", "main:app", "--bind", ":8080", "--worker-class", "aiohttp.GunicornWebWorker"]

server/client.js

Lines changed: 271 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,271 @@
1+
// get DOM elements
2+
var dataChannelLog = document.getElementById('data-channel'),
3+
iceConnectionLog = document.getElementById('ice-connection-state'),
4+
iceGatheringLog = document.getElementById('ice-gathering-state'),
5+
signalingLog = document.getElementById('signaling-state');
6+
7+
// peer connection
8+
var pc = null;
9+
10+
// data channel
11+
var dc = null, dcInterval = null;
12+
13+
function createPeerConnection() {
14+
var config = {
15+
sdpSemantics: 'unified-plan'
16+
};
17+
18+
if (document.getElementById('use-stun').checked) {
19+
config.iceServers = [{urls: ['stun:stun.l.google.com:19302']}];
20+
}
21+
22+
pc = new RTCPeerConnection(config);
23+
24+
// register some listeners to help debugging
25+
pc.addEventListener('icegatheringstatechange', function() {
26+
iceGatheringLog.textContent += ' -> ' + pc.iceGatheringState;
27+
}, false);
28+
iceGatheringLog.textContent = pc.iceGatheringState;
29+
30+
pc.addEventListener('iceconnectionstatechange', function() {
31+
iceConnectionLog.textContent += ' -> ' + pc.iceConnectionState;
32+
}, false);
33+
iceConnectionLog.textContent = pc.iceConnectionState;
34+
35+
pc.addEventListener('signalingstatechange', function() {
36+
signalingLog.textContent += ' -> ' + pc.signalingState;
37+
}, false);
38+
signalingLog.textContent = pc.signalingState;
39+
40+
// connect audio / video
41+
pc.addEventListener('track', function(evt) {
42+
if (evt.track.kind == 'video')
43+
document.getElementById('video').srcObject = evt.streams[0];
44+
else
45+
document.getElementById('audio').srcObject = evt.streams[0];
46+
});
47+
48+
return pc;
49+
}
50+
51+
function negotiate() {
52+
return pc.createOffer().then(function(offer) {
53+
return pc.setLocalDescription(offer);
54+
}).then(function() {
55+
// wait for ICE gathering to complete
56+
return new Promise(function(resolve) {
57+
if (pc.iceGatheringState === 'complete') {
58+
resolve();
59+
} else {
60+
function checkState() {
61+
if (pc.iceGatheringState === 'complete') {
62+
pc.removeEventListener('icegatheringstatechange', checkState);
63+
resolve();
64+
}
65+
}
66+
pc.addEventListener('icegatheringstatechange', checkState);
67+
}
68+
});
69+
}).then(function() {
70+
var offer = pc.localDescription;
71+
var codec;
72+
73+
codec = document.getElementById('audio-codec').value;
74+
if (codec !== 'default') {
75+
offer.sdp = sdpFilterCodec('audio', codec, offer.sdp);
76+
}
77+
78+
codec = document.getElementById('video-codec').value;
79+
if (codec !== 'default') {
80+
offer.sdp = sdpFilterCodec('video', codec, offer.sdp);
81+
}
82+
83+
document.getElementById('offer-sdp').textContent = offer.sdp;
84+
return fetch('/offer', {
85+
body: JSON.stringify({
86+
sdp: offer.sdp,
87+
type: offer.type,
88+
video_transform: document.getElementById('video-transform').value
89+
}),
90+
headers: {
91+
'Content-Type': 'application/json'
92+
},
93+
method: 'POST'
94+
});
95+
}).then(function(response) {
96+
return response.json();
97+
}).then(function(answer) {
98+
document.getElementById('answer-sdp').textContent = answer.sdp;
99+
return pc.setRemoteDescription(answer);
100+
}).catch(function(e) {
101+
alert(e);
102+
});
103+
}
104+
105+
function start() {
106+
document.getElementById('start').style.display = 'none';
107+
108+
pc = createPeerConnection();
109+
110+
var time_start = null;
111+
112+
function current_stamp() {
113+
if (time_start === null) {
114+
time_start = new Date().getTime();
115+
return 0;
116+
} else {
117+
return new Date().getTime() - time_start;
118+
}
119+
}
120+
121+
if (document.getElementById('use-datachannel').checked) {
122+
var parameters = JSON.parse(document.getElementById('datachannel-parameters').value);
123+
124+
dc = pc.createDataChannel('chat', parameters);
125+
dc.onclose = function() {
126+
clearInterval(dcInterval);
127+
dataChannelLog.textContent += '- close\n';
128+
};
129+
dc.onopen = function() {
130+
dataChannelLog.textContent += '- open\n';
131+
dcInterval = setInterval(function() {
132+
var message = 'ping ' + current_stamp();
133+
dataChannelLog.textContent += '> ' + message + '\n';
134+
dc.send(message);
135+
}, 1000);
136+
};
137+
dc.onmessage = function(evt) {
138+
dataChannelLog.textContent += '< ' + evt.data + '\n';
139+
140+
if (evt.data.substring(0, 4) === 'pong') {
141+
var elapsed_ms = current_stamp() - parseInt(evt.data.substring(5), 10);
142+
dataChannelLog.textContent += ' RTT ' + elapsed_ms + ' ms\n';
143+
}
144+
};
145+
}
146+
147+
var constraints = {
148+
audio: document.getElementById('use-audio').checked,
149+
video: false
150+
};
151+
152+
if (document.getElementById('use-video').checked) {
153+
var resolution = document.getElementById('video-resolution').value;
154+
if (resolution) {
155+
resolution = resolution.split('x');
156+
constraints.video = {
157+
width: parseInt(resolution[0], 0),
158+
height: parseInt(resolution[1], 0)
159+
};
160+
} else {
161+
constraints.video = true;
162+
}
163+
}
164+
165+
if (constraints.audio || constraints.video) {
166+
if (constraints.video) {
167+
document.getElementById('media').style.display = 'block';
168+
}
169+
navigator.mediaDevices.getUserMedia(constraints).then(function(stream) {
170+
stream.getTracks().forEach(function(track) {
171+
pc.addTrack(track, stream);
172+
});
173+
return negotiate();
174+
}, function(err) {
175+
alert('Could not acquire media: ' + err);
176+
});
177+
} else {
178+
negotiate();
179+
}
180+
181+
document.getElementById('stop').style.display = 'inline-block';
182+
}
183+
184+
function stop() {
185+
document.getElementById('stop').style.display = 'none';
186+
187+
// close data channel
188+
if (dc) {
189+
dc.close();
190+
}
191+
192+
// close transceivers
193+
if (pc.getTransceivers) {
194+
pc.getTransceivers().forEach(function(transceiver) {
195+
if (transceiver.stop) {
196+
transceiver.stop();
197+
}
198+
});
199+
}
200+
201+
// close local audio / video
202+
pc.getSenders().forEach(function(sender) {
203+
sender.track.stop();
204+
});
205+
206+
// close peer connection
207+
setTimeout(function() {
208+
pc.close();
209+
}, 500);
210+
}
211+
212+
function sdpFilterCodec(kind, codec, realSdp) {
213+
var allowed = []
214+
var rtxRegex = new RegExp('a=fmtp:(\\d+) apt=(\\d+)\r$');
215+
var codecRegex = new RegExp('a=rtpmap:([0-9]+) ' + escapeRegExp(codec))
216+
var videoRegex = new RegExp('(m=' + kind + ' .*?)( ([0-9]+))*\\s*$')
217+
218+
var lines = realSdp.split('\n');
219+
220+
var isKind = false;
221+
for (var i = 0; i < lines.length; i++) {
222+
if (lines[i].startsWith('m=' + kind + ' ')) {
223+
isKind = true;
224+
} else if (lines[i].startsWith('m=')) {
225+
isKind = false;
226+
}
227+
228+
if (isKind) {
229+
var match = lines[i].match(codecRegex);
230+
if (match) {
231+
allowed.push(parseInt(match[1]));
232+
}
233+
234+
match = lines[i].match(rtxRegex);
235+
if (match && allowed.includes(parseInt(match[2]))) {
236+
allowed.push(parseInt(match[1]));
237+
}
238+
}
239+
}
240+
241+
var skipRegex = 'a=(fmtp|rtcp-fb|rtpmap):([0-9]+)';
242+
var sdp = '';
243+
244+
isKind = false;
245+
for (var i = 0; i < lines.length; i++) {
246+
if (lines[i].startsWith('m=' + kind + ' ')) {
247+
isKind = true;
248+
} else if (lines[i].startsWith('m=')) {
249+
isKind = false;
250+
}
251+
252+
if (isKind) {
253+
var skipMatch = lines[i].match(skipRegex);
254+
if (skipMatch && !allowed.includes(parseInt(skipMatch[2]))) {
255+
continue;
256+
} else if (lines[i].match(videoRegex)) {
257+
sdp += lines[i].replace(videoRegex, '$1 ' + allowed.join(' ')) + '\n';
258+
} else {
259+
sdp += lines[i] + '\n';
260+
}
261+
} else {
262+
sdp += lines[i] + '\n';
263+
}
264+
}
265+
266+
return sdp;
267+
}
268+
269+
function escapeRegExp(string) {
270+
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string
271+
}

server/demo-instruct.wav

1.12 MB
Binary file not shown.

0 commit comments

Comments
 (0)