48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 | SDP Semantics:
59 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
--------------------------------------------------------------------------------
/webrtc-web-client/basic_peerconnection/index.js:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nhancv/nc-flutter-webrtc-ex/ea298b16a81ba05da7f81a5292e74ebe75aea5e2/webrtc-web-client/basic_peerconnection/index.js
--------------------------------------------------------------------------------
/webrtc-web-client/basic_peerconnection/js/webrtc.js:
--------------------------------------------------------------------------------
1 | /*
2 | * MIT License
3 | *
4 | * Copyright (c) 2020 Nhan Cao
5 | *
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy
7 | * of this software and associated documentation files (the "Software"), to deal
8 | * in the Software without restriction, including without limitation the rights
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | * copies of the Software, and to permit persons to whom the Software is
11 | * furnished to do so, subject to the following conditions:
12 | *
13 | * The above copyright notice and this permission notice shall be included in all
14 | * copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | * SOFTWARE.
23 | *
24 | */
25 |
26 | 'use strict';
27 | // https://github.com/webrtc/samples/tree/gh-pages/src/content/peerconnection/pc1
28 | const startButton = document.getElementById('startButton');
29 | const callButton = document.getElementById('callButton');
30 | const hangupButton = document.getElementById('hangupButton');
31 | callButton.disabled = true;
32 | hangupButton.disabled = true;
33 | startButton.addEventListener('click', start);
34 | callButton.addEventListener('click', call);
35 | hangupButton.addEventListener('click', hangup);
36 |
37 | let startTime;
38 | const localVideo = document.getElementById('localVideo');
39 | const remoteVideo = document.getElementById('remoteVideo');
40 |
41 | localVideo.addEventListener('loadedmetadata', function() {
42 | console.log(`Local video videoWidth: ${this.videoWidth}px, videoHeight: ${this.videoHeight}px`);
43 | });
44 |
45 | remoteVideo.addEventListener('loadedmetadata', function() {
46 | console.log(`Remote video videoWidth: ${this.videoWidth}px, videoHeight: ${this.videoHeight}px`);
47 | });
48 |
49 | remoteVideo.addEventListener('resize', () => {
50 | console.log(`Remote video size changed to ${remoteVideo.videoWidth}x${remoteVideo.videoHeight}`);
51 | // We'll use the first onsize callback as an indication that video has started
52 | // playing out.
53 | if (startTime) {
54 | const elapsedTime = window.performance.now() - startTime;
55 | console.log('Setup time: ' + elapsedTime.toFixed(3) + 'ms');
56 | startTime = null;
57 | }
58 | });
59 |
60 | let localStream;
61 | let pc1;
62 | let pc2;
63 | const offerOptions = {
64 | offerToReceiveAudio: 1,
65 | offerToReceiveVideo: 1
66 | };
67 |
68 | function getName(pc) {
69 | return (pc === pc1) ? 'pc1' : 'pc2';
70 | }
71 |
72 | function getOtherPc(pc) {
73 | return (pc === pc1) ? pc2 : pc1;
74 | }
75 |
76 | async function start() {
77 | console.log('Requesting local stream');
78 | startButton.disabled = true;
79 | try {
80 | const stream = await navigator.mediaDevices.getUserMedia({audio: true, video: true});
81 | console.log('Received local stream');
82 | localVideo.srcObject = stream;
83 | localStream = stream;
84 | callButton.disabled = false;
85 | } catch (e) {
86 | alert(`getUserMedia() error: ${e.name}`);
87 | }
88 | }
89 |
90 | function getSelectedSdpSemantics() {
91 | const sdpSemanticsSelect = document.querySelector('#sdpSemantics');
92 | const option = sdpSemanticsSelect.options[sdpSemanticsSelect.selectedIndex];
93 | return option.value === '' ? {} : {sdpSemantics: option.value};
94 | }
95 |
96 | async function call() {
97 | callButton.disabled = true;
98 | hangupButton.disabled = false;
99 | console.log('Starting call');
100 | startTime = window.performance.now();
101 | const videoTracks = localStream.getVideoTracks();
102 | const audioTracks = localStream.getAudioTracks();
103 | if (videoTracks.length > 0) {
104 | console.log(`Using video device: ${videoTracks[0].label}`);
105 | }
106 | if (audioTracks.length > 0) {
107 | console.log(`Using audio device: ${audioTracks[0].label}`);
108 | }
109 | const configuration = getSelectedSdpSemantics();
110 | console.log('RTCPeerConnection configuration:', configuration);
111 | pc1 = new RTCPeerConnection(configuration);
112 | console.log('Created local peer connection object pc1');
113 | pc1.addEventListener('icecandidate', e => onIceCandidate(pc1, e));
114 | pc2 = new RTCPeerConnection(configuration);
115 | console.log('Created remote peer connection object pc2');
116 | pc2.addEventListener('icecandidate', e => onIceCandidate(pc2, e));
117 | pc1.addEventListener('iceconnectionstatechange', e => onIceStateChange(pc1, e));
118 | pc2.addEventListener('iceconnectionstatechange', e => onIceStateChange(pc2, e));
119 | pc2.addEventListener('track', gotRemoteStream);
120 |
121 | localStream.getTracks().forEach(track => pc1.addTrack(track, localStream));
122 | console.log('Added local stream to pc1');
123 |
124 | try {
125 | console.log('pc1 createOffer start');
126 | const offer = await pc1.createOffer(offerOptions);
127 | await onCreateOfferSuccess(offer);
128 | } catch (e) {
129 | onCreateSessionDescriptionError(e);
130 | }
131 | }
132 |
133 | function onCreateSessionDescriptionError(error) {
134 | console.log(`Failed to create session description: ${error.toString()}`);
135 | }
136 |
137 | async function onCreateOfferSuccess(desc) {
138 | console.log(`Offer from pc1\n${desc.sdp}`);
139 | console.log('pc1 setLocalDescription start');
140 | try {
141 | await pc1.setLocalDescription(desc);
142 | onSetLocalSuccess(pc1);
143 | } catch (e) {
144 | onSetSessionDescriptionError();
145 | }
146 |
147 | console.log('pc2 setRemoteDescription start');
148 | try {
149 | await pc2.setRemoteDescription(desc);
150 | onSetRemoteSuccess(pc2);
151 | } catch (e) {
152 | onSetSessionDescriptionError();
153 | }
154 |
155 | console.log('pc2 createAnswer start');
156 | // Since the 'remote' side has no media stream we need
157 | // to pass in the right constraints in order for it to
158 | // accept the incoming offer of audio and video.
159 | try {
160 | const answer = await pc2.createAnswer();
161 | await onCreateAnswerSuccess(answer);
162 | } catch (e) {
163 | onCreateSessionDescriptionError(e);
164 | }
165 | }
166 |
167 | function onSetLocalSuccess(pc) {
168 | console.log(`${getName(pc)} setLocalDescription complete`);
169 | }
170 |
171 | function onSetRemoteSuccess(pc) {
172 | console.log(`${getName(pc)} setRemoteDescription complete`);
173 | }
174 |
175 | function onSetSessionDescriptionError(error) {
176 | console.log(`Failed to set session description: ${error.toString()}`);
177 | }
178 |
179 | function gotRemoteStream(e) {
180 | const remoteStream = e.streams[0];
181 | if (remoteVideo.srcObject !== remoteStream) {
182 | remoteVideo.srcObject = remoteStream;
183 | remoteStream.getTracks().forEach(track => pc2.addTrack(track, remoteStream));
184 | console.log('pc2 received remote stream');
185 | }
186 | }
187 |
188 | async function onCreateAnswerSuccess(desc) {
189 | console.log(`Answer from pc2:\n${desc.sdp}`);
190 | console.log('pc2 setLocalDescription start');
191 | try {
192 | await pc2.setLocalDescription(desc);
193 | onSetLocalSuccess(pc2);
194 | } catch (e) {
195 | onSetSessionDescriptionError(e);
196 | }
197 | console.log('pc1 setRemoteDescription start');
198 | try {
199 | await pc1.setRemoteDescription(desc);
200 | onSetRemoteSuccess(pc1);
201 | } catch (e) {
202 | onSetSessionDescriptionError(e);
203 | }
204 | }
205 |
206 | async function onIceCandidate(pc, event) {
207 | try {
208 | await (getOtherPc(pc).addIceCandidate(event.candidate));
209 | onAddIceCandidateSuccess(pc);
210 | } catch (e) {
211 | onAddIceCandidateError(pc, e);
212 | }
213 | console.log(`${getName(pc)} ICE candidate:\n${event.candidate ? event.candidate.candidate : '(null)'}`);
214 | }
215 |
216 | function onAddIceCandidateSuccess(pc) {
217 | console.log(`${getName(pc)} addIceCandidate success`);
218 | }
219 |
220 | function onAddIceCandidateError(pc, error) {
221 | console.log(`${getName(pc)} failed to add ICE Candidate: ${error.toString()}`);
222 | }
223 |
224 | function onIceStateChange(pc, event) {
225 | if (pc) {
226 | console.log(`${getName(pc)} ICE state: ${pc.iceConnectionState}`);
227 | console.log('ICE state change event: ', event);
228 | }
229 | }
230 |
231 | function hangup() {
232 | console.log('Ending call');
233 | pc1.close();
234 | pc2.close();
235 | pc1 = null;
236 | pc2 = null;
237 | hangupButton.disabled = true;
238 | callButton.disabled = false;
239 | }
240 |
--------------------------------------------------------------------------------
/webrtc-web-client/basic_peerconnection/logo.svg:
--------------------------------------------------------------------------------
1 |
2 |