├── .gitignore ├── README.md ├── gumPlayground ├── .gitignore ├── changeButtons.js ├── changeVideoSize.js ├── expressServer.js ├── index.html ├── inputOutput.js ├── package.json ├── screenRecorder.js ├── scripts.js ├── shareScreen.js └── styles.css ├── signalingPeerConnection ├── .gitignore ├── index.html ├── package-lock.json ├── package.json ├── scripts.js ├── server.js ├── socketListeners.js ├── styles.css └── taskList.txt ├── starterFiles ├── gumPlayground │ ├── changeButtons.js │ ├── index.html │ └── styles.css ├── signalingPeerConnection │ ├── index.html │ ├── stunServers.js │ └── styles.css └── teleLegalSite │ ├── .htaccess-file │ ├── ActionButtons.js │ ├── CallInfo.js │ ├── HangUpButtons.js │ ├── ProDashboard.css │ ├── ProDashboard.js │ ├── apptSeedData.js │ ├── callStatusReducer.js │ ├── reverse-proxy-file │ ├── stunServers.js │ ├── vhost-file │ └── videoComponents.css └── teleLegalSite ├── .gitignore ├── teleLegal-back-end ├── expressRoutes.js ├── index.js ├── package.json ├── server.js └── socketServer.js └── telelegal-front-end ├── .gitignore ├── README.md ├── package.json ├── public ├── favicon.ico ├── index.html ├── logo192.png ├── logo512.png ├── manifest.json └── robots.txt └── src ├── App.css ├── App.js ├── index.js ├── redux-elements ├── actions │ ├── addStream.js │ └── updateCallStatus.js └── reducers │ ├── callStatusReducer.js │ ├── rootReducer.js │ └── streamsReducer.js ├── siteComponents ├── ProDashboard.css └── ProDashboard.js ├── videoComponents ├── ActionButtonCaretDropDown.js ├── ActionButtons.js ├── AudioButton │ ├── AudioButton.js │ └── startAudioStream.js ├── CallInfo.js ├── ChatWindow.js ├── HangupButton.js ├── MainVideoPage.js ├── ProMainVideoPage.js ├── VideoButton │ ├── VideoButton.js │ ├── getDevices.js │ └── startLocalVideoStream.js └── VideoComponents.css └── webRTCutilities ├── clientSocketListeners.js ├── createPeerConnection.js ├── proSocketListeners.js ├── socketConnection.js └── stunServers.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # webrtcCourse 2 | This is the codebase for [my Udemy webRTC course](https://www.udemy.com/course/mastering-webrtc-part-2-real-time-video-and-screen-share/?couponCode=C8CE681D6BEF9443510E). If the coupon doesn't work, you can message me on Udemy and I'll send you one. 3 | 4 | The tags below coorespond to the git tag for the beginning of the given lecture. For instance, if you are about to start lecture 21 and you need to reset or compare your code to mine, you do: 5 | $ git checkout Add-Ice 6 | 7 | This will put your code base at the same point mine is when I start the video. You must have used git clone for this work (downloading the repo as a directory doesn't include tags), and there may be some mistakes, but I hope it helps a ton! 8 | 9 | * lec-10 - Set-Local-Description 10 | * lec-11 - Add-Local-Description 11 | * lec-12 - Add-Socketio 12 | * lec-14 - Emit-Offer 13 | * lec-15 - Emit-Offer 14 | * lec-16 - Load-Existing-Offers 15 | * lec-17 - Create-Answer 16 | * lec-18 - Signal-Error-Handling 17 | * lec-19 - Emitting-Answer 18 | * lec-20 - On-Answer-Response 19 | * lec-21 - Add-Ice 20 | * lec-22 - Apply-Ice 21 | * lec-23 - Add-Tracks 22 | * lec-28 - Create-JWT 23 | * lec-29 - Add-React-Router 24 | * lec-30 - Join-Video 25 | * lec-31 - Starting-Components 26 | * lec-32 - Wire-Up-Redux 27 | * lec-33 - Add-Action-Buttons 28 | * lec-34 - GUM-Store-Stream 29 | * lec-35 - Create-Peer-Connection 30 | * lec-36 - Thinking-Functions 31 | * lec-37 - Abstract-Video-Audio-Buttons 32 | * lec-38 - Add-Video-Feed 33 | * lec-40 - Enable-Disable-Video 34 | * lec-41 - Display-Local-Video-Inputs 35 | * lec-42 - Set-New-Video-Device 36 | * lec-43 - Replace-Tracks 37 | * lec-44 - Abstract-DropDown 38 | * lec-45 - Setup-Audio-Button 39 | * lec-46 - Switch-Audio-Devices 40 | * lec-47 - Start-Mute-Audio 41 | * lec-48 - Organize-Offers 42 | * lec-49 - Create-Offer-Tele 43 | * lec-50 - Add-Dashboard 44 | * lec-52 - Auth-Pro 45 | * lec-53 - Socket-Refactor 46 | * lec-54 - Reorg-Appt-Data 47 | * lec-55 - Pull-Appt-Data 48 | * lec-56 - Listen-Offers-Tele 49 | * lec-57 - Join-Video-Route 50 | * lec-58 - Call-Info-Pro 51 | * lec-59 - Create-Answer-Tele 52 | * lec-61 - Emit-Answer-Server 53 | * lec-62 - Listen-For-Answer 54 | * lec-63 - Emit-Ice-Candidates-Server 55 | * lec-64 - Ice-To-Clients 56 | * lec-65 - Add-Ice-PC 57 | * lec-66 - Add-Tracks-Victory 58 | * lec-67 - Tele-Bug-Fix-1 59 | * lec-68 - HangUp-Button 60 | * lec-69 - Replace-Tracks-Tele 61 | 62 | 63 | 64 | -------------------------------------------------------------------------------- /gumPlayground/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | package-lock.json -------------------------------------------------------------------------------- /gumPlayground/changeButtons.js: -------------------------------------------------------------------------------- 1 | //green = btn-success 2 | //blue = btn-primary 3 | //grey = btn-secondary 4 | //red = btn-danger 5 | 6 | const buttonsById = [ 7 | 'share', 'show-video', 'stop-video', 'change-size', 'start-record', 8 | 'stop-record','play-record','share-screen' 9 | ] 10 | 11 | //buttonEls will be an array of dom elements in order of buttonsById 12 | const buttonEls = buttonsById.map(buttonId=>document.getElementById(buttonId)); 13 | 14 | const changeButtons = (colorsArray)=>{ 15 | colorsArray.forEach((color,i)=>{ 16 | buttonEls[i].classList.remove('btn-success'); 17 | buttonEls[i].classList.remove('btn-primary'); 18 | buttonEls[i].classList.remove('btn-secondary'); 19 | buttonEls[i].classList.remove('btn-danger'); 20 | if(color === "green"){ 21 | buttonEls[i].classList.add('btn-success'); 22 | }else if(color === "blue"){ 23 | buttonEls[i].classList.add('btn-primary'); 24 | }else if(color === "grey"){ 25 | buttonEls[i].classList.add('btn-secondary'); 26 | }else if(color === "red"){ 27 | buttonEls[i].classList.add('btn-danger'); 28 | } 29 | }) 30 | } -------------------------------------------------------------------------------- /gumPlayground/changeVideoSize.js: -------------------------------------------------------------------------------- 1 | 2 | const supportedConstraints = navigator.mediaDevices.getSupportedConstraints(); 3 | console.log(supportedConstraints); 4 | 5 | const changeVideoSize = ()=>{ 6 | stream.getVideoTracks().forEach(track=>{ 7 | //track is a video track. 8 | //we can get it's capabilities from .getCapabilities() 9 | //or we can apply new constraints with applyConstraints();' 10 | const capabilities = track.getCapabilities() 11 | const height = document.querySelector('#vid-height').value 12 | const width = document.querySelector('#vid-width').value 13 | const vConstraints = { 14 | height: {exact: height < capabilities.height.max ? height : capabilities.height.max}, 15 | width: {exact: width < capabilities.width.max ? width : capabilities.width.max}, 16 | // frameRate: 5, 17 | // aspectRatio: 10, 18 | } 19 | track.applyConstraints(vConstraints) 20 | }) 21 | 22 | // stream.getTracks().forEach(track=>{ 23 | // const capabilities = track.getCapabilities() 24 | // console.log(capabilities); 25 | // }) 26 | } -------------------------------------------------------------------------------- /gumPlayground/expressServer.js: -------------------------------------------------------------------------------- 1 | //we need this to run in a localhost context instead of file 2 | //so that we can run enumerate devices (it must be run in a secure context) 3 | //and localhost counts 4 | const path = require('path'); 5 | const express = require('express'); 6 | const app = express(); 7 | app.use(express.static(path.join(__dirname))) 8 | app.listen(3000) -------------------------------------------------------------------------------- /gumPlayground/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 |
14 | 15 | 16 | 17 |
18 | 19 | 20 | 21 |
22 |
23 | 24 | 25 | 26 |
27 | 28 |
29 | 30 | 31 |
32 |
33 | 34 | 35 |
36 |
37 | 38 | 39 |
40 | 41 | 42 | 43 |
44 |
45 |
46 |

My feed

47 | 48 |
49 |
50 |

Their feed

51 | 52 |
53 |
54 |
55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | -------------------------------------------------------------------------------- /gumPlayground/inputOutput.js: -------------------------------------------------------------------------------- 1 | const audioInputEl = document.querySelector('#audio-input') 2 | const audioOutputEl = document.querySelector('#audio-output') 3 | const videoInputEl = document.querySelector('#video-input') 4 | 5 | const getDevices = async()=>{ 6 | try{ 7 | const devices = await navigator.mediaDevices.enumerateDevices(); 8 | console.log(devices) 9 | devices.forEach(d=>{ 10 | const option = document.createElement('option') //create the option tag 11 | option.value = d.deviceId 12 | option.text = d.label 13 | //add the option tag we just created to the right select 14 | if(d.kind === "audioinput"){ 15 | audioInputEl.appendChild(option) 16 | }else if(d.kind === "audiooutput"){ 17 | audioOutputEl.appendChild(option) 18 | }else if(d.kind === "videoinput"){ 19 | videoInputEl.appendChild(option) 20 | } 21 | }) 22 | }catch(err){ 23 | console.log(err); 24 | } 25 | } 26 | 27 | const changeAudioInput = async(e)=>{ 28 | //changed audio input!!! 29 | const deviceId = e.target.value; 30 | const newConstraints = { 31 | audio: {deviceId: {exact: deviceId}}, 32 | video: true, 33 | } 34 | try{ 35 | stream = await navigator.mediaDevices.getUserMedia(newConstraints); 36 | console.log(stream); 37 | const tracks = stream.getAudioTracks(); 38 | console.log(tracks); 39 | }catch(err){ 40 | console.log(err) 41 | } 42 | } 43 | 44 | const changeAudioOutput = async(e)=>{ 45 | await videoEl.setSinkId(e.target.value) 46 | console.log("Changed audio device!") 47 | } 48 | 49 | const changeVideo = async(e)=>{ 50 | //changed video input!!! 51 | const deviceId = e.target.value; 52 | const newConstraints = { 53 | audio: true, 54 | video: {deviceId: {exact: deviceId}}, 55 | } 56 | try{ 57 | stream = await navigator.mediaDevices.getUserMedia(newConstraints); 58 | console.log(stream); 59 | const tracks = stream.getVideoTracks(); 60 | console.log(tracks); 61 | }catch(err){ 62 | console.log(err) 63 | } 64 | } 65 | 66 | getDevices(); 67 | -------------------------------------------------------------------------------- /gumPlayground/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "gumplayground", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "changeButtons.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "keywords": [], 10 | "author": "", 11 | "license": "ISC", 12 | "dependencies": { 13 | "express": "^4.18.2" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /gumPlayground/screenRecorder.js: -------------------------------------------------------------------------------- 1 | let mediaRecorder; 2 | let recordedBlobs; 3 | 4 | const startRecording = ()=>{ 5 | if(!stream){ //you could use mediaStream! 6 | alert("No current feed"); 7 | return 8 | } 9 | console.log("Start recording") 10 | recordedBlobs = []; // an array to hold the blobs for playback 11 | //you could use mediaStream to record! 12 | mediaRecorder = new MediaRecorder(stream) //make a mediaRecorder from the constructor 13 | mediaRecorder.ondataavailable = e=>{ 14 | //ondataavailable will run when the stream ends, or stopped, or we specifically ask for it 15 | console.log("Data is available for the media recorder!") 16 | recordedBlobs.push(e.data) 17 | } 18 | mediaRecorder.start(); 19 | changeButtons([ 20 | 'green','green','blue','blue','green','blue','grey','blue' 21 | ]) 22 | } 23 | 24 | 25 | const stopRecording = ()=>{ 26 | if(!mediaRecorder){ 27 | alert("Please record before stopping!") 28 | return 29 | } 30 | console.log("stop recording") 31 | mediaRecorder.stop() 32 | changeButtons([ 33 | 'green','green','blue','blue','green','green','blue','blue' 34 | ]) 35 | 36 | } 37 | 38 | const playRecording = ()=>{ 39 | console.log("play recording") 40 | if(!recordedBlobs){ 41 | alert("No Recording saved") 42 | return 43 | } 44 | const superBuffer = new Blob(recordedBlobs) // superBuffer is a super buffer of our array of blobs 45 | const recordedVideoEl = document.querySelector('#other-video'); 46 | recordedVideoEl.src = window.URL.createObjectURL(superBuffer); 47 | recordedVideoEl.controls = true; 48 | recordedVideoEl.play(); 49 | changeButtons([ 50 | 'green','green','blue','blue','green','green','green','blue' 51 | ]) 52 | } 53 | -------------------------------------------------------------------------------- /gumPlayground/scripts.js: -------------------------------------------------------------------------------- 1 | const videoEl = document.querySelector('#my-video'); 2 | let stream = null // Init stream var so we can use anywhere 3 | let mediaStream = null //Init mediaStream var for screenShare 4 | const constraints = { 5 | audio: true, //use your headphones, or be prepared for feedback! 6 | video: true, 7 | } 8 | 9 | 10 | const getMicAndCamera = async(e)=>{ 11 | try{ 12 | stream = await navigator.mediaDevices.getUserMedia(constraints); 13 | console.log(stream) 14 | changeButtons([ 15 | 'green','blue','blue','grey','grey','grey','grey','grey' 16 | ]) 17 | }catch(err){ 18 | //user denied access to constraints 19 | console.log("user denied access to constraints") 20 | console.log(err) 21 | } 22 | }; 23 | 24 | const showMyFeed = e=>{ 25 | console.log("showMyFeed is working") 26 | if(!stream){ 27 | alert("Stream still loading...") 28 | return; 29 | } 30 | videoEl.srcObject = stream; // this will set our MediaStream (stream) to our