Compare commits

...

161 Commits

Author SHA1 Message Date
391e8eb6f0 Added diagram for close call before accept/reject 2023-01-10 13:17:57 +02:00
3139a625a2 Added workflow diagrams 2023-01-10 12:05:29 +02:00
9d43b7ec0c Merge pull request 'LINXD-2270: Allow server and web client to have full duplex' (#20) from LINXD-2270-full-duplex into develop
Reviewed-on: #20
Reviewed-by: Cristi Ene <cristi.ene@safemobile.com>
2022-12-27 11:12:38 +00:00
342d09c3e6 LINXD-2270: Removed console logs; Update commented code 2022-12-27 13:11:06 +02:00
b3409de3ba LINXD-2270: Allow server and web client to have full duplex 2022-12-19 19:31:33 +02:00
8a9c370f02 Merge pull request 'LH-265-audio' (#17) from LH-265-audio into develop
Reviewed-on: #17
Reviewed-by: Cristi Ene <cristi.ene@safemobile.com>
2022-12-06 12:45:08 +00:00
652019b07d LH-265: Update doc; Update bundle 2022-11-29 15:35:28 +02:00
09c4a4b90e LH-265: Added audio on client and server 2022-11-29 14:19:02 +02:00
75d0e3aee7 exe right for build.sh 2022-10-31 22:26:08 +00:00
30ac997634 Merge pull request 'added build.sh' (#14) from temp-build into develop
Reviewed-on: #14
2022-10-31 22:22:20 +00:00
5aea138f6a added build.sh 2022-10-31 12:17:07 +02:00
5b01ddc2a8 Merge pull request 'LH-259-mediasoup-always-return-a-callback-response-to-clients' (#13) from LH-259-mediasoup-always-return-a-callback-response-to-clients into develop
Reviewed-on: #13
2022-10-25 16:18:54 +00:00
084ff36ebe LH-259: Refactor createRoom 2022-10-24 22:38:06 +03:00
f4ebf92783 LH-259: Added callback from transport-produce 2022-10-24 22:35:22 +03:00
b59a157b18 LH-259: Comment callback from transport-produce 2022-10-24 22:19:37 +03:00
9f8347bec5 LH-259: Update createRoom callback 2022-10-24 22:16:47 +03:00
24390c98e5 LH-259: Added callbacks 2022-10-24 22:11:14 +03:00
1a7371fe18 Parse RTC_MIN_PORT and RTC_MAX_PORT 2022-10-18 18:27:02 +03:00
be5f97762a Merge pull request 'LH-253: Added callId for transportclose and producerclose events' (#12) from LH-253-mediasoup-handle-callid-undefined into master
Reviewed-on: #12
2022-10-18 07:53:33 +00:00
03a11126c4 LH-253: Check if we have callId in closeCall 2022-10-18 10:51:20 +03:00
fafbee6e4c LH-253: Added callId for transportclose and producerclose events 2022-10-18 02:05:22 +03:00
bbf23c33d4 Merge pull request 'LH-252: Update .env variables' (#11) from LH-252-mediasoup-add-a-config-file-with-keys-and-ports into master
Reviewed-on: #11
2022-10-09 06:50:45 +00:00
5c2808e75a LH-252: Update .env variables 2022-10-06 15:21:54 +03:00
2aea7497cc Merge pull request 'added log for dtls transport-connect' (#10) from LH-249-debug-for-i-os-dtls-problems into master
Reviewed-on: #10
2022-10-06 06:41:07 +00:00
56835d6660 added log for dtls transport-connect 2022-10-05 15:44:46 +03:00
fc42c79210 Fix missing callId 2022-09-27 13:13:29 +03:00
d81bc8582d Merge branch 'master' of https://git.safemobile.org/Safemobile/mediasoup 2022-09-27 13:05:15 +03:00
a4d16998cd Fix call check before call close() 2022-09-27 13:03:32 +03:00
de1458bbde Merge pull request 'LINXD-2197: Added comments; Catch errors; Fix package.json start:run script' (#8) from LINXD-2197-refactor-improving-mediasoup-web-socket-component into master
Reviewed-on: #8
2022-09-27 10:00:25 +00:00
b0fad5f1db LINXD-2197: On peer disconnect delete the call; Added log when call is already deleted; Added log when user send multiple createWebRtcTransport 2022-09-27 12:43:07 +03:00
eb5aa12d65 LINXD-2197: Added the initial demo project used; Check before set producerTransport and consumerTransport if it was set before 2022-09-27 07:55:25 +03:00
52b4794a86 LINXD-2197: Added workflow diagram 2022-09-25 20:29:32 +03:00
5f8f2ab44c LINXD-2197: Added comments; Catch errors; Fix package.json start:run script 2022-09-25 20:03:17 +03:00
55455be8e7 Merge pull request 'LINXD-2222-debugging-for-i-os' (#7) from LINXD-2222-debugging-for-i-os into master
Reviewed-on: #7
2022-09-20 23:16:16 +00:00
62a82dc3a5 LINXD-2222: Removed socketio-wildcard 2022-09-20 14:17:16 +03:00
ac078e72ff LINXD-2222: Removed requestCert and rejectedUnauthorized from server options 2022-09-20 14:15:54 +03:00
be396e1047 LINXD-2222: Set namespate to '/'; Removed httpolyglot; Removed unused code 2022-09-20 14:02:22 +03:00
149876fc70 LINXD-2222: use https instead of httpolyglot; Added logs 2022-09-20 09:36:31 +03:00
adbeb2071b Update to start with defeult port 3000 2022-09-19 23:37:20 +03:00
a6681ffe40 LINXD-2222: Update 2022-09-19 23:32:15 +03:00
efc9bfd114 LINXD-2222: Update 2022-09-19 23:31:36 +03:00
a8afa8a532 LINXD-2222: Update 2022-09-19 23:30:18 +03:00
507c131058 LINXD-2222: Update 2022-09-19 23:28:39 +03:00
043f66eb0c LINXD-2222: Update 2022-09-19 23:24:32 +03:00
cb5716dd5c LINXD-2222: Update 2022-09-19 23:12:24 +03:00
ae39a45f6d LINXD-2222: Update 2022-09-19 23:09:55 +03:00
0ec5769ee0 LINXD-2222: Update 2022-09-19 18:12:37 +03:00
72ee3e43ab LINXD-2222: Update 2022-09-19 18:10:34 +03:00
f20c7fada8 LINXD-2222: Update 2022-09-19 18:06:39 +03:00
53a654c50f LINXD-2222: Update 2022-09-19 18:02:30 +03:00
d54403299f LINXD-2222: Update 2022-09-19 17:55:21 +03:00
177d54ec67 LINXD-2222: Update 2022-09-19 17:45:42 +03:00
649c7a3767 LINXD-2222: Update 2022-09-19 17:45:18 +03:00
08d6ccbb21 LINXD-2222: Update 2022-09-19 17:44:45 +03:00
fd005351b5 LINXD-2222: Update 2022-09-19 17:43:39 +03:00
fc111540d8 LINXD-2222: Update 2022-09-19 17:42:42 +03:00
c4f4be0aa8 LINXD-2222: Update 2022-09-19 17:42:16 +03:00
40c03592df LINXD-2222: Update 2022-09-19 17:40:57 +03:00
a59cbcf8cc LINXD-2222: Update 2022-09-19 17:13:48 +03:00
7cc3a95b38 LINXD-2222: Update 2022-09-19 17:12:22 +03:00
05e3d997f1 LINXD-2222: Update 2022-09-19 17:04:56 +03:00
9c731f4085 LINXD-2222: Update 2022-09-19 17:03:58 +03:00
f6d862966e LINXD-2222: Update 2022-09-19 17:02:13 +03:00
05ccd5cfd4 LINXD-2222: Update 2022-09-19 17:00:43 +03:00
43eee11c7e LINXD-2222: Update 2022-09-19 16:53:32 +03:00
0033cd528d LINXD-2222: Update 2022-09-19 16:53:09 +03:00
5022d88b1d LINXD-2222: Update 2022-09-19 16:51:15 +03:00
52b922825f LINXD-2222: Update 2022-09-19 16:48:46 +03:00
07be8af9ae LINXD-2222: Update 2022-09-19 16:46:43 +03:00
29737fe5d8 LINXD-2222: Fix middleware typo 2022-09-19 16:44:29 +03:00
1f5755b72d LINXD-2222: Added wildcard; Replace httpolyglot with https; Set CORS to * 2022-09-19 16:21:50 +03:00
a2c878f91c Merge pull request 'Delete the whole call(with id) when we call closeCall' (#5) from delete-whole-call-id into master
Reviewed-on: #5
2022-09-17 08:43:57 +00:00
7b6f78725b LINXD-2209: Call closeCall from producerclose and transportclose on consumer handlers; Update README.md 2022-09-16 18:49:56 +03:00
41c6ad281d Delete the whole call(with id) when we call closeCall 2022-09-16 11:08:02 +03:00
f5406f163f Merge pull request 'Allow io3 on server creation' (#4) from Allow-io3 into master
Reviewed-on: #4
2022-09-15 14:54:41 +00:00
28497fda91 Merge with master 2022-09-15 17:53:37 +03:00
4a98a79630 Merge pull request 'LINXD-2209-black-screen-when-2-video-calls-are-answered-simultaneously' (#3) from LINXD-2209-black-screen-when-2-video-calls-are-answered-simultaneously into master
Reviewed-on: #3
2022-09-15 14:49:55 +00:00
22e8b4d364 LINXD-2209: Refactor how we close the call; Check for callId in createRoom event 2022-09-15 17:07:47 +03:00
575dbd69b0 Allow io3 on server creation 2022-09-15 14:49:10 +03:00
a51a757d17 LINXD-2209: Correctly close the call 2022-09-15 09:57:57 +03:00
c059dd5afc LINXD-2209: Correctly close the call 2022-09-15 09:56:32 +03:00
19808da24e LINXD-2209: Get callId from soekct dictionary in consumer-resume case 2022-09-15 09:43:59 +03:00
2f6c25c171 LINXD-2209: Correctly set router to videoCalls 2022-09-15 09:41:24 +03:00
ead0069aa8 LINXD-2209: Correctly set router to videoCalls 2022-09-15 09:39:52 +03:00
434c8f744c LINXD-2209: Correctly set router to videoCalls 2022-09-15 09:35:35 +03:00
7198dc91b1 LINXD-2209: Check for router in videoCalls 2022-09-15 09:33:30 +03:00
f629012712 LINXD-2209: Check for router in videoCalls 2022-09-15 09:32:41 +03:00
41b50d2a11 LINXD-2209: Identify the callId from dictionary 2022-09-15 09:19:04 +03:00
b85ba68c9c LINXD-2209: Added comments 2022-09-13 22:24:10 +03:00
6acd276324 LINXD-2209: Refactor how we save router, consumer, producer, producerTransport and consumerTransport 2022-09-13 22:16:51 +03:00
294dbdf38d LINXD-2209: Added logs 2022-09-13 21:43:16 +03:00
c3d50fdc4e LINXD-2209: Add 4000ms delay between room creation 2022-09-13 21:38:06 +03:00
c12ececf47 LINXD-2209: Add 2000ms delay between room creation 2022-09-13 21:35:26 +03:00
47eb302f5f LINXD-2209: Added logs on consume 2022-09-13 21:33:04 +03:00
accf960aa7 LINXD-2209: Added logs on consume 2022-09-13 21:15:51 +03:00
ab685270f1 LINXD-2209: Add 1000ms delay between room creation 2022-09-13 21:08:14 +03:00
6938e751fe LINXD-2209: Add 100ms delay between room creation 2022-09-13 21:08:03 +03:00
031a7bc4c5 LINXD-2209: Remove console.logs 2022-09-13 21:05:24 +03:00
d7486d0fd6 LINXD-2209: Add 10ms delay between room creation 2022-09-13 21:04:42 +03:00
38931f0654 LINXD-2209: Add 50ms delay between room creation 2022-09-13 21:02:29 +03:00
bb684ca4db LINXD-2209: Add 300ms delay 2022-09-13 20:59:55 +03:00
25a76c343b LINXD-2209: Move getRtpCapa outside of room creation 2022-09-13 20:59:12 +03:00
817a49204d LINXD-2209: Added logs 2022-09-13 20:58:06 +03:00
91b4db1982 LINXD-2209: Added logs 2022-09-13 20:54:35 +03:00
8562f6c58c LINXD-2209: Added logs 2022-09-13 20:49:40 +03:00
5abc309502 LINXD-2209: Added logs 2022-09-13 20:28:07 +03:00
ecb5a88a2c LINXD-2209: Update port 2022-09-13 20:10:34 +03:00
782b749ea3 LINXD-2209: Check queue length 2022-09-13 20:09:25 +03:00
b834016dcb LINXD-2209: Create rooms in sequence 2022-09-13 19:56:06 +03:00
523945271e Remove callback with producer id from transport-produce 2022-08-31 17:06:54 +03:00
0af7ddd786 Remove callback with producer id from transport-produce 2022-08-31 16:53:08 +03:00
ba5add489d Remove callback with producer id from transport-produce 2022-08-31 16:49:23 +03:00
d5cb144799 Remove callback with producer id from transport-produce 2022-08-31 16:47:54 +03:00
4e92f6cdd3 Remove callback with producer id from transport-produce 2022-08-31 16:43:59 +03:00
aaa1c5cea4 Remove callback with producer id from transport-produce 2022-08-31 16:40:38 +03:00
4f302570a2 Remove callback with producer id from transport-produce 2022-08-31 16:24:21 +03:00
cd887142f7 Add log on consume 2022-08-18 09:34:22 +03:00
afb328ea3b Add log on consume 2022-08-18 09:08:55 +03:00
9b36a78be2 Add log on consume 2022-08-16 14:36:23 +03:00
d67debc70b Add log on createRecvTransport 2022-08-16 14:21:02 +03:00
b64c49c1cd Refactor transportclose 21 2022-08-12 15:08:30 +03:00
383e34bf22 Refactor transportclose 20 2022-08-12 15:07:13 +03:00
004c0f66a7 Refactor transportclose 19 2022-08-12 15:02:42 +03:00
89ee1f301a Refactor transportclose 18 2022-08-12 14:56:20 +03:00
7a564d4a61 Refactor transportclose 17 2022-08-11 22:24:33 +03:00
3473c10608 Refactor transportclose 16 2022-08-11 22:11:49 +03:00
f6e3203198 Refactor transportclose 15 2022-08-11 15:03:00 +03:00
12516b59b8 Refactor transportclose 14.5 2022-08-11 15:00:28 +03:00
d9a53ecf20 Refactor transportclose 14 2022-08-11 14:59:50 +03:00
01fec638c9 Refactor transportclose 13 2022-08-11 14:54:46 +03:00
6d8f22939a Refactor transportclose 12.5 2022-08-11 14:45:25 +03:00
cd8153e3e7 Refactor transportclose 12 2022-08-11 14:43:58 +03:00
0f1c268fa5 Refactor transportclose 11 2022-08-11 14:40:40 +03:00
6147d3bede Refactor transportclose 10 2022-08-11 14:22:28 +03:00
1df588bac7 Refactor transportclose 9 2022-08-11 14:17:05 +03:00
3f3048e54f Refactor transportclose 8 2022-08-11 13:17:44 +03:00
ac5d5d7fd0 Refactor transportclose 7 2022-08-11 13:15:23 +03:00
65b5b563ff Refactor transportclose 6 2022-08-11 12:52:50 +03:00
8f7b60c78a Refactor transportclose 5 2022-08-11 12:50:18 +03:00
9c46ed4944 Refactor transportclose 4 2022-08-11 12:46:24 +03:00
7bb45dca19 Refactor transportclose 3 2022-08-11 12:18:01 +03:00
e5fbb1e5a7 Refactor transportclose 2 2022-08-11 12:14:39 +03:00
05f4985075 Refactor transportclose 2022-08-11 12:10:29 +03:00
c49da6696c Added room it when close transport; Added ts config file 2022-08-11 11:55:11 +03:00
112ab0b229 Added room it when close transport; Added ts config file 2022-08-11 11:45:48 +03:00
625fb28e65 Added room it when close transport; Added ts config file 2022-08-11 11:33:16 +03:00
c8a774a903 Update package.json(build/dev start commands); Added README 2022-08-09 18:58:17 +03:00
9cc00c05fd Added my assetName and assetType in URL 2022-08-07 16:54:39 +03:00
6d80943907 Remove callId from transportClose(from producer) 2022-08-06 10:15:51 +03:00
a2223529da Close transport, producer/consumer and reset room when we close the video call 2022-08-06 10:05:43 +03:00
3cc5b0dea8 Added logs 2022-08-05 11:51:07 +03:00
c31922f93a Added logs 2022-08-05 11:49:15 +03:00
e07ffb3181 Added logs 2022-08-05 11:43:37 +03:00
d07df95652 Added logs 2022-08-05 11:32:40 +03:00
222ff46655 Close producer when dispatcher close the window 2022-08-02 19:52:06 +03:00
b947318142 Close producer when dispatcher close the window 2022-08-02 19:50:04 +03:00
1ab77c1f17 Close producer when dispatcher close the window 2022-08-02 19:43:08 +03:00
14d96fa7c5 Close producer when dispatcher close the window 2022-08-02 19:33:39 +03:00
ea8d4268ec Close producer when dispatcher close the window 2022-08-02 19:10:52 +03:00
809e343d67 Update .env 2022-08-02 08:13:19 +00:00
28d9321129 Added logs for callId 2022-08-02 07:09:05 +00:00
22 changed files with 30134 additions and 21178 deletions

6
.env
View File

@ -1,3 +1,7 @@
PORT=3000 PORT=3000
IP=0.0.0.0 # Listening IPv4 or IPv6. IP=0.0.0.0 # Listening IPv4 or IPv6.
ANNOUNCED_IP=185.8.154.190 # Announced IPv4 or IPv6 (useful when running mediasoup behind NAT with private IP). ANNOUNCED_IP=185.8.154.190 # Announced IPv4 or IPv6 (useful when running mediasoup behind NAT with private IP).
RTC_MIN_PORT=2000
RTC_MAX_PORT=2020
SERVER_CERT="./server/ssl/cert.pem"
SERVER_KEY="./server/ssl/key.pem"

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
/node_modules
/dist

View File

@ -1,2 +1,41 @@
Generate keys command: # Video server
openssl req -newkey rsa:2048 -new -nodes -x509 -days 3650 -keyout key.pem -out cert.pem
### Generating certificates
##### To generate SSL certificates you must:
1. Go to `/server/ssl`
2. Execute `openssl req -newkey rsa:2048 -new -nodes -x509 -days 3650 -keyout key.pem -out cert.pem`
### Development
##### To start in development mode you must:
1. Install the dependencies `npm install`.
2. Run the `npm start:dev` command to start the server in dev mode.
(Any change will trigger a refresh of the server)
### Production
##### To start in production mode you must:
1. Install the dependencies `npm install`.
2. Run the `npm start:prod` command to start the server in production mode.
(To connect to the terminal, use `pm2 log video-server`)
### Web client
- The server will start by default on port 3000, and the ssl certificates will have to be configured
- The web client can be accessed using the /sfu path
ex: https://HOST/sfu/?assetId=1&&accountId=1&producer=true&dest_asset_id=75&assetName=Adi
assetId = asset id of the unit on which you are doing the test
accountId = account id of the unit on which you are doing the test
producer = it will always be true because you are the producer
(it's possible to put false, but then you have to have another client with producer true)
assetName = asset name of the unit on which you are doing the test
dest_asset_id= the addressee with whom the call is made
- To make a call using this client, you need a microphone and permission to use it
- For any changes related to the client, the command `npm run watch' will have to be used to generate the bundle.js used by the web client
### Demo project
The demo project used initially and then modified for our needs `https://github.com/jamalag/mediasoup2`

770
app.js
View File

@ -1,230 +1,595 @@
import 'dotenv/config' require('dotenv').config()
const express = require('express');
const app = express();
const Server = require('socket.io');
const path = require('node:path');
const fs = require('node:fs');
let https;
try {
https = require('node:https');
} catch (err) {
console.log('https support is disabled!');
}
const mediasoup = require('mediasoup');
let worker;
/** /**
* integrating mediasoup server with a node.js application *
*/ * videoCalls - Dictionary of Object(s)
* '<callId>': {
/* Please follow mediasoup installation requirements */ * router: Router,
/* https://mediasoup.org/documentation/v3/mediasoup/installation/ */ * initiatorAudioProducer: Producer,
import express from 'express' * initiatorVideoProducer: Producer,
const app = express() * receiverVideoProducer: Producer,
* receiverAudioProducer: Producer,
import https from 'httpolyglot' * initiatorProducerTransport: Producer Transport,
import fs from 'fs' * receiverProducerTransport: Producer Transport,
import path from 'path' * initiatorConsumerVideo: Consumer,
const __dirname = path.resolve() * initiatorConsumerAudio: Consumer,
* initiatorConsumerTransport: Consumer Transport
import Server from 'socket.io' * initiatorSockerId
import mediasoup, { getSupportedRtpCapabilities } from 'mediasoup' * receiverSocketId
* }
let worker *
let router = {} **/
let producerTransport let videoCalls = {};
let consumerTransport let socketDetails = {};
let producer
let consumer
app.get('/', (_req, res) => { app.get('/', (_req, res) => {
res.send('Hello from mediasoup app!') res.send('Hello from mediasoup app!')
}) });
app.use('/sfu', express.static(path.join(__dirname, 'public'))) app.use('/sfu', express.static(path.join(__dirname, 'public')));
// SSL cert for HTTPS access // SSL cert for HTTPS access
const options = { const options = {
key: fs.readFileSync('./server/ssl/key.pem', 'utf-8'), key: fs.readFileSync(process.env.SERVER_KEY, 'utf-8'),
cert: fs.readFileSync('./server/ssl/cert.pem', 'utf-8') cert: fs.readFileSync(process.env.SERVER_CERT, 'utf-8'),
} };
const httpsServer = https.createServer(options, app) const httpsServer = https.createServer(options, app);
const io = new Server(httpsServer, {
allowEIO3: true,
origins: ["*:*"]
});
httpsServer.listen(process.env.PORT, () => { httpsServer.listen(process.env.PORT, () => {
console.log('Listening on port:', process.env.PORT) console.log('Video server listening on port:', process.env.PORT);
}) });
const io = new Server(httpsServer) const peers = io.of('/');
// socket.io namespace (could represent a room?)
const peers = io.of('/mediasoup')
/**
* Worker
* |-> Router(s)
* |-> Producer Transport(s)
* |-> Producer
* |-> Consumer Transport(s)
* |-> Consumer
**/
const createWorker = async () => { const createWorker = async () => {
worker = await mediasoup.createWorker({ try {
rtcMinPort: 2000, worker = await mediasoup.createWorker({
rtcMaxPort: 2020, rtcMinPort: parseInt(process.env.RTC_MIN_PORT),
}) rtcMaxPort: parseInt(process.env.RTC_MAX_PORT),
console.log(`[createWorker] worker pid ${worker.pid}`) })
console.log(`[createWorker] worker pid ${worker.pid}`);
worker.on('died', error => {
// This implies something serious happened, so kill the application worker.on('died', error => {
console.error('mediasoup worker has died', error) // This implies something serious happened, so kill the application
setTimeout(() => process.exit(1), 2000) // exit in 2 seconds console.error('mediasoup worker has died', error);
}) setTimeout(() => process.exit(1), 2000); // exit in 2 seconds
})
return worker return worker;
} catch (error) {
console.log(`ERROR | createWorker | ${error.message}`);
}
} }
// We create a Worker as soon as our application starts // We create a Worker as soon as our application starts
worker = createWorker() worker = createWorker();
// This is an Array of RtpCapabilities // This is an Array of RtpCapabilities
// https://mediasoup.org/documentation/v3/mediasoup/rtp-parameters-and-capabilities/#RtpCodecCapability // https://mediasoup.org/documentation/v3/mediasoup/rtp-parameters-and-capabilities/#RtpCodecCapability
// list of media codecs supported by mediasoup ... // list of media codecs supported by mediasoup ...
// https://github.com/versatica/mediasoup/blob/v3/src/supportedRtpCapabilities.ts // https://github.com/versatica/mediasoup/blob/v3/src/supportedRtpCapabilities.ts
const mediaCodecs = [ const mediaCodecs = [
{ {
kind: 'audio', kind : 'audio',
mimeType: 'audio/opus', mimeType : 'audio/opus',
clockRate: 48000, clockRate : 48000,
channels: 2, channels : 2
}, },
{ {
kind: 'video', kind : 'video',
mimeType: 'video/VP8', mimeType : 'video/VP8',
clockRate: 90000, clockRate : 90000,
parameters: { parameters :
'x-google-start-bitrate': 1000, {
'x-google-start-bitrate' : 1000
}, },
channels : 2
}, },
] {
kind : 'video',
peers.on('connection', async socket => { mimeType : 'video/VP9',
console.log('[connection] socketId:', socket.id) clockRate : 90000,
socket.emit('connection-success', { parameters :
socketId: socket.id, {
existsProducer: producer ? true : false, 'profile-id' : 2,
}) 'x-google-start-bitrate' : 1000
}
socket.on('disconnect', () => { },
// do some cleanup {
console.log('peer disconnected') kind : 'video',
}) mimeType : 'video/h264',
clockRate : 90000,
socket.on('createRoom', async ({ callId }, callback) => { parameters :
if (router[callId] === undefined) { {
// worker.createRouter(options) 'packetization-mode' : 1,
// options = { mediaCodecs, appData } 'profile-level-id' : '4d0032',
// mediaCodecs -> defined above 'level-asymmetry-allowed' : 1,
// appData -> custom application data - we are not supplying any 'x-google-start-bitrate' : 1000
// none of the two are required }
router[callId] = await worker.createRouter({ mediaCodecs }) },
console.log(`[createRoom] Router ID: ${router[callId].id}`) {
kind : 'video',
mimeType : 'video/h264',
clockRate : 90000,
parameters :
{
'packetization-mode' : 1,
'profile-level-id' : '42e01f',
'level-asymmetry-allowed' : 1,
'x-google-start-bitrate' : 1000
} }
getRtpCapabilities(callId, callback)
})
const getRtpCapabilities = (callId, callback) => {
const rtpCapabilities = router[callId].rtpCapabilities
callback({ rtpCapabilities })
} }
];
// Client emits a request to create server side Transport const closeCall = (callId) => {
// We need to differentiate between the producer and consumer transports try {
socket.on('createWebRtcTransport', async ({ sender, callId }, callback) => { if (callId && videoCalls[callId]) {
console.log(`[createWebRtcTransport] Is this a sender request? ${sender} | callId ${callId}`) videoCalls[callId].receiverVideoProducer?.close();
// The client indicates if it is a producer or a consumer videoCalls[callId].receiverAudioProducer?.close();
// if sender is true, indicates a producer else a consumer videoCalls[callId].initiatorConsumerVideo?.close();
if (sender) videoCalls[callId].initiatorConsumerAudio?.close();
producerTransport = await createWebRtcTransportLayer(callId, callback)
else
consumerTransport = await createWebRtcTransportLayer(callId, callback)
})
// see client's socket.emit('transport-connect', ...) videoCalls[callId]?.initiatorConsumerTransport?.close();
socket.on('transport-connect', async ({ dtlsParameters }) => { videoCalls[callId]?.receiverProducerTransport?.close();
console.log('[transport-connect] DTLS PARAMS... ', { dtlsParameters }) videoCalls[callId]?.router?.close();
await producerTransport.connect({ dtlsParameters }) delete videoCalls[callId];
}) } else {
console.log(`The call with id ${callId} has already been deleted`);
}
} catch (error) {
console.log(`ERROR | closeCall | callid ${callId} | ${error.message}`);
}
}
// see client's socket.emit('transport-produce', ...) /*
socket.on('transport-produce', async ({ kind, rtpParameters, appData }, callback) => { - Handlers for WS events
// call produce based on the prameters from the client - These are created only when we have a connection with a peer
producer = await producerTransport.produce({ */
kind, peers.on('connection', async socket => {
rtpParameters, console.log('[connection] socketId:', socket.id);
})
console.log(`[transport-produce] Producer ID: ${producer.id} | kind: ${producer.kind}`) // After making the connection successfully, we send the client a 'connection-success' event
socket.emit('connection-success', {
socketId: socket.id
});
producer.on('transportclose', () => { // It is triggered when the peer is disconnected
console.log('transport for this producer closed ') socket.on('disconnect', () => {
producer.close() const callId = socketDetails[socket.id];
}) console.log(`disconnect | socket ${socket.id} | callId ${callId}`);
delete socketDetails[socket.id];
closeCall(callId);
});
// Send back to the client the Producer's id /*
callback({ - This event creates a room with the roomId and the callId sent
id: producer.id - It will return the rtpCapabilities of that room
}) - If the room already exists, it will not create it, but will only return rtpCapabilities
}) */
socket.on('createRoom', async ({ callId }, callback) => {
// see client's socket.emit('transport-recv-connect', ...) let callbackResponse = null;
socket.on('transport-recv-connect', async ({ dtlsParameters }) => {
console.log(`[transport-recv-connect] DTLS PARAMS: ${dtlsParameters}`)
await consumerTransport.connect({ dtlsParameters })
})
socket.on('consume', async ({ rtpCapabilities, callId }, callback) => {
try { try {
// check if the router can consume the specified producer // We can continue with the room creation process only if we have a callId
if (router[callId].canConsume({ if (callId) {
producerId: producer.id, console.log(`[createRoom] socket.id ${socket.id} callId ${callId}`);
rtpCapabilities if (!videoCalls[callId]) {
})) { console.log('[createRoom] callId', callId);
// transport can now consume and return a consumer videoCalls[callId] = { router: await worker.createRouter({ mediaCodecs }) }
consumer = await consumerTransport.consume({ console.log(`[createRoom] Router ID: ${videoCalls[callId].router.id}`);
producerId: producer.id, videoCalls[callId].receiverSocketId = socket.id
rtpCapabilities, } else {
paused: true, videoCalls[callId].initiatorSockerId = socket.id
})
consumer.on('transportclose', () => {
console.log('transport close from consumer')
})
consumer.on('producerclose', () => {
console.log('producer of consumer closed')
})
// from the consumer extract the following params
// to send back to the Client
const params = {
id: consumer.id,
producerId: producer.id,
kind: consumer.kind,
rtpParameters: consumer.rtpParameters,
} }
socketDetails[socket.id] = callId;
// send the parameters to the client // rtpCapabilities is set for callback
callback({ params }) console.log('[getRtpCapabilities] callId', callId);
callbackResponse = {
rtpCapabilities :videoCalls[callId].router.rtpCapabilities
};
} else {
console.log(`[createRoom] missing callId ${callId}`);
} }
} catch (error) { } catch (error) {
console.log(error.message) console.log(`ERROR | createRoom | callId ${callId} | ${error.message}`);
callback({ } finally {
params: { callback(callbackResponse);
error: error }
});
/*
- Client emits a request to create server side Transport
- Depending on the sender, a producer or consumer is created is created on that router
- It will return parameters, these are required for the client to create the RecvTransport
from the client.
- If the client is producer(sender: true) then it will use parameters for device.createSendTransport(params)
- If the client is a consumer(sender: false) then it will use parameters for device.createRecvTransport(params)
*/
socket.on('createWebRtcTransport', async ({ sender }, callback) => {
try {
const callId = socketDetails[socket.id];
console.log(`[createWebRtcTransport] socket ${socket.id} | sender ${sender} | callId ${callId}`);
if (sender) {
if(!videoCalls[callId].receiverProducerTransport && !isInitiator(callId, socket.id)) {
videoCalls[callId].receiverProducerTransport = await createWebRtcTransportLayer(callId, callback);
} else if(!videoCalls[callId].initiatorProducerTransport && isInitiator(callId, socket.id)) {
videoCalls[callId].initiatorProducerTransport = await createWebRtcTransportLayer(callId, callback);
} else {
console.log(`producerTransport has already been defined | callId ${callId}`);
callback(null);
} }
}) } else if (!sender) {
if(!videoCalls[callId].receiverConsumerTransport && !isInitiator(callId, socket.id)) {
videoCalls[callId].receiverConsumerTransport = await createWebRtcTransportLayer(callId, callback);
} else if(!videoCalls[callId].initiatorConsumerTransport && isInitiator(callId, socket.id)) {
videoCalls[callId].initiatorConsumerTransport = await createWebRtcTransportLayer(callId, callback);
}
}
} catch (error) {
console.log(`ERROR | createWebRtcTransport | callId ${socketDetails[socket.id]} | sender ${sender} | ${error.message}`);
callback(error);
}
});
/*
- The client sends this event after successfully creating a createSendTransport(AS PRODUCER)
- The connection is made to the created transport
*/
socket.on('transport-connect', async ({ dtlsParameters }) => {
try {
const callId = socketDetails[socket.id];
if (typeof dtlsParameters === 'string') dtlsParameters = JSON.parse(dtlsParameters);
console.log(`[transport-connect] socket ${socket.id} | callId ${callId}`);
if (!isInitiator(callId, socket.id)) {
await videoCalls[callId].receiverProducerTransport.connect({ dtlsParameters });
} else {
await videoCalls[callId].initiatorProducerTransport.connect({ dtlsParameters });
}
} catch (error) {
console.log(`ERROR | transport-connect | callId ${socketDetails[socket.id]} | ${error.message}`);
}
});
/*
- The event sent by the client (PRODUCER) after successfully connecting to receiverProducerTransport/initiatorProducerTransport
- For the router with the id callId, we make produce on receiverProducerTransport/initiatorProducerTransport
- Create the handler on producer at the 'transportclose' event
*/
socket.on('transport-produce', async ({ kind, rtpParameters, appData }, callback) => {
try {
const callId = socketDetails[socket.id];
if (typeof rtpParameters === 'string') rtpParameters = JSON.parse(rtpParameters);
console.log(`[transport-produce] kind: ${kind} | socket: ${socket.id} | callId: ${callId}`);
if (kind === 'video') {
if (!isInitiator(callId, socket.id)) {
videoCalls[callId].receiverVideoProducer = await videoCalls[callId].receiverProducerTransport.produce({
kind,
rtpParameters,
});
console.log(`[transport-produce] receiverVideoProducer Producer ID: ${videoCalls[callId].receiverVideoProducer.id} | kind: ${videoCalls[callId].receiverVideoProducer.kind}`);
videoCalls[callId].receiverVideoProducer.on('transportclose', () => {
const callId = socketDetails[socket.id];
console.log('transport for this producer closed', callId)
closeCall(callId);
});
// Send back to the client the Producer's id
callback && callback({
id: videoCalls[callId].receiverVideoProducer.id
});
} else {
videoCalls[callId].initiatorVideoProducer = await videoCalls[callId].initiatorProducerTransport.produce({
kind,
rtpParameters,
});
console.log(`[transport-produce] initiatorVideoProducer Producer ID: ${videoCalls[callId].initiatorVideoProducer.id} | kind: ${videoCalls[callId].initiatorVideoProducer.kind}`);
videoCalls[callId].initiatorVideoProducer.on('transportclose', () => {
const callId = socketDetails[socket.id];
console.log('transport for this producer closed', callId)
closeCall(callId);
});
callback && callback({
id: videoCalls[callId].initiatorVideoProducer.id
});
}
} else if (kind === 'audio') {
if (!isInitiator(callId, socket.id)) {
videoCalls[callId].receiverAudioProducer = await videoCalls[callId].receiverProducerTransport.produce({
kind,
rtpParameters,
});
console.log(`[transport-produce] receiverAudioProducer Producer ID: ${videoCalls[callId].receiverAudioProducer.id} | kind: ${videoCalls[callId].receiverAudioProducer.kind}`);
videoCalls[callId].receiverAudioProducer.on('transportclose', () => {
const callId = socketDetails[socket.id];
console.log('transport for this producer closed', callId)
closeCall(callId);
});
// Send back to the client the Producer's id
callback && callback({
id: videoCalls[callId].receiverAudioProducer.id
});
} else {
videoCalls[callId].initiatorAudioProducer = await videoCalls[callId].initiatorProducerTransport.produce({
kind,
rtpParameters,
});
console.log(`[transport-produce] initiatorAudioProducer Producer ID: ${videoCalls[callId].initiatorAudioProducer.id} | kind: ${videoCalls[callId].initiatorAudioProducer.kind}`);
videoCalls[callId].initiatorAudioProducer.on('transportclose', () => {
const callId = socketDetails[socket.id];
console.log('transport for this producer closed', callId)
closeCall(callId);
});
// Send back to the client the Producer's id
callback && callback({
id: videoCalls[callId].initiatorAudioProducer.id
});
}
}
} catch (error) {
console.log(`ERROR | transport-produce | callId ${socketDetails[socket.id]} | ${error.message}`);
}
});
/*
- The client sends this event after successfully creating a createRecvTransport(AS CONSUMER)
- The connection is made to the created consumerTransport
*/
socket.on('transport-recv-connect', async ({ dtlsParameters }) => {
try {
const callId = socketDetails[socket.id];
console.log(`[transport-recv-connect] socket ${socket.id} | callId ${callId}`);
// await videoCalls[callId].consumerTransport.connect({ dtlsParameters });
if(!isInitiator(callId, socket.id)) {
await videoCalls[callId].receiverConsumerTransport.connect({ dtlsParameters });
} else if(isInitiator(callId, socket.id)) {
await videoCalls[callId].initiatorConsumerTransport.connect({ dtlsParameters });
}
} catch (error) {
console.log(`ERROR | transport-recv-connect | callId ${socketDetails[socket.id]} | ${error.message}`);
} }
}) })
socket.on('consumer-resume', async () => { /*
console.log(`[consumer-resume]`) - The customer consumes after successfully connecting to consumerTransport
await consumer.resume() - The previous step was 'transport-recv-connect', and before that 'createWebRtcTransport'
}) - This event is only sent by the consumer
}) - The parameters that the consumer consumes are returned
- The consumer does consumerTransport.consume(params)
*/
socket.on('consume', async ({ rtpCapabilities }, callback) => {
try {
const callId = socketDetails[socket.id];
console.log(`[consume] socket ${socket.id} | callId ${callId} | rtpCapabilities: ${JSON.stringify(rtpCapabilities)}`);
console.log('[consume] callId', callId);
let canConsumeVideo, canConsumeAudio;
if (isInitiator(callId, socket.id)) {
canConsumeVideo = !!videoCalls[callId].receiverVideoProducer && !!videoCalls[callId].router.canConsume({
producerId: videoCalls[callId].receiverVideoProducer.id,
rtpCapabilities
});
canConsumeAudio = !!videoCalls[callId].receiverAudioProducer && !!videoCalls[callId].router.canConsume({
producerId: videoCalls[callId].receiverAudioProducer.id,
rtpCapabilities
});
} else {
canConsumeVideo = !!videoCalls[callId].initiatorVideoProducer && !!videoCalls[callId].router.canConsume({
producerId: videoCalls[callId].initiatorVideoProducer.id,
rtpCapabilities
});
canConsumeAudio = !!videoCalls[callId].initiatorAudioProducer && !!videoCalls[callId].router.canConsume({
producerId: videoCalls[callId].initiatorAudioProducer.id,
rtpCapabilities
});
}
console.log('[consume] canConsumeVideo', canConsumeVideo);
console.log('[consume] canConsumeAudio', canConsumeAudio);
if (canConsumeVideo && !canConsumeAudio) {
const videoParams = await consumeVideo(callId, socket.id, rtpCapabilities)
callback({ videoParams, audioParams: null });
} else if (canConsumeVideo && canConsumeAudio) {
const videoParams = await consumeVideo(callId, socket.id, rtpCapabilities)
const audioParams = await consumeAudio(callId, socket.id, rtpCapabilities)
callback({ videoParams, audioParams });
} else if (!canConsumeVideo && canConsumeAudio) {
const audioParams = await consumeAudio(callId, socket.id, rtpCapabilities)
const data = { videoParams: null, audioParams };
callback(data);
} else {
console.log(`[consume] Can't consume | callId ${callId}`);
callback(null);
}
} catch (error) {
console.log(`ERROR | consume | callId ${socketDetails[socket.id]} | ${error.message}`)
callback({ params: { error } });
}
});
/*
- Event sent by the consumer after consuming to resume the pause
- When consuming on consumerTransport, it is initially done with paused: true, here we will resume
*/
socket.on('consumer-resume', async () => {
try {
const callId = socketDetails[socket.id];
console.log(`[consumer-resume] callId ${callId}`)
if (isInitiator(callId, socket.id)) {
console.log(`[consumer-resume] isInitiator true`);
await videoCalls[callId].initiatorConsumerVideo.resume();
await videoCalls[callId].initiatorConsumerAudio.resume();
} else {
console.log(`[consumer-resume] isInitiator false`);
(videoCalls[callId].receiverConsumerVideo) && await videoCalls[callId].receiverConsumerVideo.resume();
(videoCalls[callId].receiverConsumerVideo) && await videoCalls[callId].receiverConsumerAudio.resume();
}
} catch (error) {
console.log(`ERROR | consumer-resume | callId ${socketDetails[socket.id]} | ${error.message}`);
}
});
});
const consumeVideo = async (callId, socketId, rtpCapabilities) => {
if(isInitiator(callId, socketId)) {
videoCalls[callId].initiatorConsumerVideo = await videoCalls[callId].initiatorConsumerTransport.consume({
producerId: videoCalls[callId].receiverVideoProducer.id,
rtpCapabilities,
paused: true,
});
// https://mediasoup.org/documentation/v3/mediasoup/api/#consumer-on-transportclose
videoCalls[callId].initiatorConsumerVideo.on('transportclose', () => {
const callId = socketDetails[socket.id];
console.log('transport close from consumer', callId);
closeCall(callId);
});
// https://mediasoup.org/documentation/v3/mediasoup/api/#consumer-on-producerclose
videoCalls[callId].initiatorConsumerVideo.on('producerclose', () => {
const callId = socketDetails[socket.id];
console.log('producer of consumer closed', callId);
closeCall(callId);
});
return {
id: videoCalls[callId].initiatorConsumerVideo.id,
producerId: videoCalls[callId].receiverVideoProducer.id,
kind: 'video',
rtpParameters: videoCalls[callId].initiatorConsumerVideo.rtpParameters,
}
} else {
videoCalls[callId].receiverConsumerVideo = await videoCalls[callId].receiverConsumerTransport.consume({
producerId: videoCalls[callId].initiatorVideoProducer.id,
rtpCapabilities,
paused: true,
});
videoCalls[callId].receiverConsumerVideo.on('transportclose', () => {
const callId = socketDetails[socket.id];
console.log('transport close from consumer', callId);
closeCall(callId);
});
videoCalls[callId].receiverConsumerVideo.on('producerclose', () => {
const callId = socketDetails[socket.id];
console.log('producer of consumer closed', callId);
closeCall(callId);
});
return {
id: videoCalls[callId].receiverConsumerVideo.id,
producerId: videoCalls[callId].initiatorVideoProducer.id,
kind: 'video',
rtpParameters: videoCalls[callId].receiverConsumerVideo.rtpParameters,
}
}
}
const consumeAudio = async (callId, socketId, rtpCapabilities) => {
if(isInitiator(callId, socketId)) {
videoCalls[callId].initiatorConsumerAudio = await videoCalls[callId].initiatorConsumerTransport.consume({
producerId: videoCalls[callId].receiverAudioProducer.id,
rtpCapabilities,
paused: true,
});
// https://mediasoup.org/documentation/v3/mediasoup/api/#consumer-on-transportclose
videoCalls[callId].initiatorConsumerAudio.on('transportclose', () => {
const callId = socketDetails[socket.id];
console.log('transport close from consumer', callId);
closeCall(callId);
});
// https://mediasoup.org/documentation/v3/mediasoup/api/#consumer-on-producerclose
videoCalls[callId].initiatorConsumerAudio.on('producerclose', () => {
const callId = socketDetails[socket.id];
console.log('producer of consumer closed', callId);
closeCall(callId);
});
return {
id: videoCalls[callId].initiatorConsumerAudio.id,
producerId: videoCalls[callId].receiverAudioProducer.id,
kind: 'audio',
rtpParameters: videoCalls[callId].initiatorConsumerAudio.rtpParameters,
}
} else {
videoCalls[callId].receiverConsumerAudio = await videoCalls[callId].receiverConsumerTransport.consume({
producerId: videoCalls[callId].initiatorAudioProducer.id,
rtpCapabilities,
paused: true,
});
videoCalls[callId].receiverConsumerAudio.on('transportclose', () => {
const callId = socketDetails[socket.id];
console.log('transport close from consumer', callId);
closeCall(callId);
});
videoCalls[callId].receiverConsumerAudio.on('producerclose', () => {
const callId = socketDetails[socket.id];
console.log('producer of consumer closed', callId);
closeCall(callId);
});
return {
id: videoCalls[callId].receiverConsumerAudio.id,
producerId: videoCalls[callId].initiatorAudioProducer.id,
kind: 'audio',
rtpParameters: videoCalls[callId].receiverConsumerAudio.rtpParameters,
}
}
}
const isInitiator = (callId, socketId) => {
return (videoCalls[callId].initiatorSockerId === socketId);
}
/*
- Called from at event 'createWebRtcTransport' and assigned to the consumer or producer transport
- It will return parameters, these are required for the client to create the RecvTransport
from the client.
- If the client is producer(sender: true) then it will use parameters for device.createSendTransport(params)
- If the client is a consumer(sender: false) then it will use parameters for device.createRecvTransport(params)
*/
const createWebRtcTransportLayer = async (callId, callback) => { const createWebRtcTransportLayer = async (callId, callback) => {
try { try {
console.log('[createWebRtcTransportLayer] callId', callId);
// https://mediasoup.org/documentation/v3/mediasoup/api/#WebRtcTransportOptions // https://mediasoup.org/documentation/v3/mediasoup/api/#WebRtcTransportOptions
const webRtcTransport_options = { const webRtcTransport_options = {
listenIps: [ listenIps: [
@ -236,40 +601,41 @@ const createWebRtcTransportLayer = async (callId, callback) => {
enableUdp: true, enableUdp: true,
enableTcp: true, enableTcp: true,
preferUdp: true, preferUdp: true,
} };
// https://mediasoup.org/documentation/v3/mediasoup/api/#router-createWebRtcTransport // https://mediasoup.org/documentation/v3/mediasoup/api/#router-createWebRtcTransport
let transport = await router[callId].createWebRtcTransport(webRtcTransport_options) let transport = await videoCalls[callId].router.createWebRtcTransport(webRtcTransport_options)
console.log(`callId: ${callId} | transport id: ${transport.id}`) console.log(`callId: ${callId} | transport id: ${transport.id}`)
// Handler for when DTLS(Datagram Transport Layer Security) changes
transport.on('dtlsstatechange', dtlsState => { transport.on('dtlsstatechange', dtlsState => {
console.log(`transport | dtlsstatechange | calldId ${callId} | dtlsState ${dtlsState}`);
if (dtlsState === 'closed') { if (dtlsState === 'closed') {
transport.close() transport.close();
} }
}) });
// Handler if the transport layer has closed (for various reasons)
transport.on('close', () => { transport.on('close', () => {
console.log('transport closed') console.log(`transport | closed | calldId ${callId}`);
}) });
// send back to the client the following prameters const params = {
callback({ id: transport.id,
// https://mediasoup.org/documentation/v3/mediasoup-client/api/#TransportOptions iceParameters: transport.iceParameters,
params: { iceCandidates: transport.iceCandidates,
id: transport.id, dtlsParameters: transport.dtlsParameters,
iceParameters: transport.iceParameters, };
iceCandidates: transport.iceCandidates,
dtlsParameters: transport.dtlsParameters,
}
})
return transport console.log('[createWebRtcTransportLayer] callback params', params);
// Send back to the client the params
callback({ params });
// Set transport to producerTransport or consumerTransport
return transport;
} catch (error) { } catch (error) {
callback({ console.log(`ERROR | createWebRtcTransportLayer | callId ${socketDetails[socket.id]} | ${error.message}`);
params: { callback({ params: { error } });
error: error
}
})
} }
} }

46
build.sh Executable file
View File

@ -0,0 +1,46 @@
#/!bin/bash
## PREBUILD PROCESS
# check dist dir to be present and empty
if [ ! -d "dist" ]; then
## MAKE DIR
mkdir "dist"
echo "Directory dist created."
else
## CLEANUP
rm -fr dist/*
fi
# Install dependencies
#npm install
## PROJECT NEEDS
echo "Building app... from $(git rev-parse --abbrev-ref HEAD)"
#npm run-script build
cp -r {.env,app.js,package.json,server,public} dist/
#Add version control for pm2
cd dist
#Add version control for pm2
version=$(git describe)
file_pkg="package.json"
key=" \"version\": \""
count=$(echo ${version%%-*} | grep -o "\." | wc -l)
if (( $count > 1 )); then
version=${version%%-*}
else
version="${version%%-*}.0"
fi
if [ -f "$file_pkg" ] && [ ! -z "$version" ]; then
version=" \"version\": \"$version\","
sed -i "s|^.*$key.*|${version//\//\\/}|g" $file_pkg
text=$(cat $file_pkg | grep -c "$version")
if [ $text -eq 0 ]; then
echo "Version couldn't be set"
else
echo "Version $version successfully applied to App"
fi
fi
## POST BUILD
cd -

Binary file not shown.

After

Width:  |  Height:  |  Size: 614 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 354 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 462 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 252 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 346 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 407 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 439 KiB

BIN
doc/[video] Workflow.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 571 KiB

8556
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -5,26 +5,28 @@
"main": "app.js", "main": "app.js",
"scripts": { "scripts": {
"test": "echo \"Error: no test specified\" && exit 1", "test": "echo \"Error: no test specified\" && exit 1",
"start": "nodemon app.js", "start:dev": "nodemon app.js",
"start:prod": "pm2 start ./app.js -n video-server",
"watch": "watchify public/index.js -o public/bundle.js -v" "watch": "watchify public/index.js -o public/bundle.js -v"
}, },
"keywords": [], "keywords": [],
"author": "", "author": "",
"license": "ISC", "license": "ISC",
"type": "module",
"dependencies": { "dependencies": {
"@types/express": "^4.17.13",
"dotenv": "^16.0.1", "dotenv": "^16.0.1",
"express": "^4.18.1", "express": "^4.18.1",
"httpolyglot": "^0.1.2",
"mediasoup": "^3.10.4", "mediasoup": "^3.10.4",
"mediasoup-client": "^3.6.54", "mediasoup-client": "^3.6.54",
"parcel": "^2.7.0",
"socket.io": "^2.0.3", "socket.io": "^2.0.3",
"socket.io-client": "^2.0.3", "socket.io-client": "^2.0.3"
"nodemon": "^2.0.19",
"watchify": "^4.0.0"
}, },
"devDependencies": { "devDependencies": {
"nodemon": "^2.0.19", "nodemon": "^2.0.19",
"pm2": "^5.2.0",
"ts-node": "^10.9.1",
"ts-node-dev": "^2.0.0",
"watchify": "^4.0.0" "watchify": "^4.0.0"
} }
} }

File diff suppressed because one or more lines are too long

View File

@ -1,4 +1,4 @@
module.exports = { module.exports = {
hubAddress: 'https://hub.dev.linx.safemobile.com/', hubAddress: 'https://hub.dev.linx.safemobile.com/',
mediasoupAddress: 'https://video.safemobile.org/mediasoup', mediasoupAddress: 'https://testing.video.safemobile.org',
} }

View File

@ -21,11 +21,22 @@
display: flex; display: flex;
justify-content: center; justify-content: center;
} }
#closeCallBtn {
padding: 5;
background-color: papayawhip;
display: flex;
justify-content: center;
width: 736px;
}
</style> </style>
</head> </head>
<body> <body>
<body> <body>
<div id="video"> <div id="video">
<legend>Client options:</legend>
<input type="checkbox" id="produceAudio" name="produceAudio">
<label for="produceAudio">Produce audio</label><br>
<table> <table>
<thead> <thead>
<th>Local Video</th> <th>Local Video</th>
@ -35,12 +46,24 @@
<tr> <tr>
<td> <td>
<div id="sharedBtns"> <div id="sharedBtns">
<video id="localVideo" autoplay class="video" ></video> <video
id="localVideo"
class="video"
autoplay
muted
playsinline
></video>
</div> </div>
</td> </td>
<td> <td>
<div id="sharedBtns"> <div id="sharedBtns">
<video id="remoteVideo" autoplay class="video" ></video> <video
id="remoteVideo"
class="video"
autoplay
muted
playsinline
></video>
</div> </div>
</td> </td>
</tr> </tr>
@ -53,38 +76,15 @@
<td> <td>
<div id="sharedBtns"> <div id="sharedBtns">
<button id="btnRecvSendTransport">Consume</button> <button id="btnRecvSendTransport">Consume</button>
</div> <button id="remoteSoundControl">Unmute</button>
</td>
<td class="remoteColumn">
<div id="videoContainer"></div>
</td>
</tr>
<!-- <tr>
<td colspan="2">
<div id="sharedBtns">
<button id="btnRtpCapabilities">2. Get Rtp Capabilities</button>
<br />
<button id="btnDevice">3. Create Device</button>
</div> </div>
</td> </td>
</tr> </tr>
<tr>
<td>
<div id="sharedBtns">
<button id="btnCreateSendTransport">4. Create Send Transport</button>
<br />
<button id="btnConnectSendTransport">5. Connect Send Transport & Produce</button></td>
</div>
<td>
<div id="sharedBtns">
<button id="btnRecvSendTransport">6. Create Recv Transport</button>
<br />
<button id="btnConnectRecvTransport">7. Connect Recv Transport & Consume</button>
</div>
</td>
</tr> -->
</tbody> </tbody>
</table> </table>
<div id="closeCallBtn">
<button id="btnCloseCall" disabled>Close Call</button>
</div>
</div> </div>
</body> </body>
</body> </body>

View File

@ -6,145 +6,201 @@ console.log('[CONFIG]', config);
const ASSET_ID = parseInt(urlParams.get('assetId')) || null; const ASSET_ID = parseInt(urlParams.get('assetId')) || null;
const ACCOUNT_ID = parseInt(urlParams.get('accountId')) || null; const ACCOUNT_ID = parseInt(urlParams.get('accountId')) || null;
const ASSET_NAME = urlParams.get('assetName') || null;
const ASSET_TYPE = urlParams.get('assetType') || null;
let callId = parseInt(urlParams.get('callId')) || null; let callId = parseInt(urlParams.get('callId')) || null;
const IS_PRODUCER = urlParams.get('producer') === 'true' ? true : false const IS_PRODUCER = urlParams.get('producer') === 'true' ? true : false
let remoteVideo = document.getElementById('remoteVideo')
remoteVideo.defaultMuted = true
let produceAudio = false
console.log('[URL] ASSET_ID', ASSET_ID, '| ACCOUNT_ID', ACCOUNT_ID, '| callId', callId, ' | IS_PRODUCER', IS_PRODUCER) console.log('[URL] ASSET_ID', ASSET_ID, '| ACCOUNT_ID', ACCOUNT_ID, '| callId', callId, ' | IS_PRODUCER', IS_PRODUCER)
let socket console.log('🟩 config', config)
hub = io(config.hubAddress)
const connectToMediasoup = () => { produceAudioSelector = document.getElementById('produceAudio');
produceAudioSelector.addEventListener('change', e => {
socket = io(config.mediasoupAddress) if(e.target.checked) {
produceAudio = true
socket.on('connection-success', ({ _socketId, existsProducer }) => { console.log('produce audio');
console.log(`[MEDIA] ${config.mediasoupAddress} | connected: ${socket.connected} | existsProducer: ${existsProducer}`) } else {
if (!IS_PRODUCER && existsProducer && consumer === undefined) { produceAudio = false
goConnect()
// document.getElementById('btnRecvSendTransport').click();
} }
if (IS_PRODUCER && urlParams.get('testing') === 'true') { getLocalStream() } });
})
}
if (IS_PRODUCER === true) {
hub.on('connect', async () => {
console.log(`[HUB] ${config.hubAddress} | connected: ${hub.connected}`)
connectToMediasoup()
hub.emit(
'ars',
JSON.stringify({
ars: true,
asset_id: ASSET_ID,
account_id: ACCOUNT_ID,
})
);
hub.on('video', (data) => {
const parsedData = JSON.parse(data);
console.log('video', parsedData)
originAssetId = parsedData.origin_asset_id;
originAssetName = parsedData.origin_asset_name;
originAssetTypeName = parsedData.origin_asset_type_name;
callId = parsedData.video_call_id;
console.log('IS_PRODUCER', IS_PRODUCER, 'callId', callId);
if (parsedData.type === 'notify-request' && IS_PRODUCER) {
getLocalStream()
}
})
})
hub.on('connect_error', (error) => {
console.log('connect_error', error);
});
hub.on('connection', () => {
console.log('connection')
})
hub.on('disconnect', () => {
console.log('disconnect')
})
} else {
connectToMediasoup()
}
let socket, hub
let device let device
let rtpCapabilities let rtpCapabilities
let producerTransport let producerTransport
let consumerTransport let consumerTransport
let producer let producerVideo
let producerAudio
let consumer let consumer
let originAssetId let originAssetId
let originAssetName let consumerVideo // local consumer video(consumer not transport)
let originAssetTypeName let consumerAudio // local consumer audio(consumer not transport)
const remoteSoundControl = document.getElementById('remoteSoundControl');
remoteSoundControl.addEventListener('click', function handleClick() {
console.log('remoteSoundControl.textContent', remoteSoundControl.textContent);
if (remoteSoundControl.textContent === 'Unmute') {
remoteVideo.muted = false
remoteSoundControl.textContent = 'Mute';
} else {
remoteVideo.muted = true
remoteSoundControl.textContent = 'Unmute';
}
});
// https://mediasoup.org/documentation/v3/mediasoup-client/api/#ProducerOptions // https://mediasoup.org/documentation/v3/mediasoup-client/api/#ProducerOptions
// https://mediasoup.org/documentation/v3/mediasoup-client/api/#transport-produce // https://mediasoup.org/documentation/v3/mediasoup-client/api/#transport-produce
let params = { let videoParams = {
// mediasoup params
encodings: [ encodings: [
{ { scaleResolutionDownBy: 4, maxBitrate: 500000 },
rid: 'r0', { scaleResolutionDownBy: 2, maxBitrate: 1000000 },
maxBitrate: 100000, { scaleResolutionDownBy: 1, maxBitrate: 5000000 },
scalabilityMode: 'S1T3', { scalabilityMode: 'S3T3_KEY' }
},
{
rid: 'r1',
maxBitrate: 300000,
scalabilityMode: 'S1T3',
},
{
rid: 'r2',
maxBitrate: 900000,
scalabilityMode: 'S1T3',
},
], ],
// https://mediasoup.org/documentation/v3/mediasoup-client/api/#ProducerCodecOptions
codecOptions: { codecOptions: {
videoGoogleStartBitrate: 1000 videoGoogleStartBitrate: 1000
} }
} }
const streamSuccess = (stream) => { let audioParams = {
localVideo.srcObject = stream codecOptions :
const track = stream.getVideoTracks()[0] {
params = { opusStereo : true,
track, opusDtx : true
...params
} }
}
setTimeout(() => {
hub = io(config.hubAddress)
const connectToMediasoup = () => {
socket = io(config.mediasoupAddress, {
reconnection: true,
reconnectionDelay: 1000,
reconnectionDelayMax : 5000,
reconnectionAttempts: Infinity
})
socket.on('connection-success', ({ _socketId, existsProducer }) => {
console.log(`[MEDIA] ${config.mediasoupAddress} | connected: ${socket.connected} | existsProducer: ${existsProducer}`)
if (!IS_PRODUCER && existsProducer && consumer === undefined) {
goConnect()
// document.getElementById('btnRecvSendTransport').click();
}
if (IS_PRODUCER && urlParams.get('testing') === 'true') { getLocalStream() }
})
}
if (IS_PRODUCER === true) {
hub.on('connect', async () => {
console.log(`[HUB]! ${config.hubAddress} | connected: ${hub.connected}`)
connectToMediasoup()
hub.emit(
'ars',
JSON.stringify({
ars: true,
asset_id: ASSET_ID,
account_id: ACCOUNT_ID,
})
)
hub.on('video', (data) => {
const parsedData = JSON.parse(data);
if (parsedData.type === 'notify-request') {
console.log('video', parsedData)
originAssetId = parsedData.origin_asset_id;
// originAssetName = parsedData.origin_asset_name;
// originAssetTypeName = parsedData.origin_asset_type_name;
callId = parsedData.video_call_id;
console.log('[VIDEO] notify-request | IS_PRODUCER', IS_PRODUCER, 'callId', callId);
getLocalStream()
}
if (parsedData.type === 'notify-end') {
console.log('[VIDEO] notify-end | IS_PRODUCER', IS_PRODUCER, 'callId', callId);
resetCallSettings()
}
})
})
hub.on('connect_error', (error) => {
console.log('connect_error', error);
});
hub.on('connection', () => {
console.log('connection')
})
hub.on('disconnect', () => {
console.log('disconnect')
})
} else {
connectToMediasoup()
}
}, 1600);
const streamSuccess = (stream) => {
console.log('[streamSuccess] device', device);
localVideo.srcObject = stream
console.log('stream', stream);
const videoTrack = stream.getVideoTracks()[0]
const audioTrack = stream.getAudioTracks()[0]
videoParams = {
track: videoTrack,
...videoParams
}
audioParams = {
track: audioTrack,
...audioParams
}
console.log('[streamSuccess] videoParams', videoParams, ' | audioParams', audioParams);
goConnect() goConnect()
} }
const getLocalStream = () => { const getLocalStream = () => {
console.log('[getLocalStream]'); console.log('[getLocalStream]');
navigator.mediaDevices.getUserMedia({ navigator.mediaDevices.getUserMedia({
audio: false, audio: produceAudio ? true : false,
video: { video: {
width: { qvga : { width: { ideal: 320 }, height: { ideal: 240 } },
min: 640, vga : { width: { ideal: 640 }, height: { ideal: 480 } },
max: 1920, hd : { width: { ideal: 1280 }, height: { ideal: 720 } }
},
height: {
min: 400,
max: 1080,
}
} }
}) })
.then(streamSuccess) .then(streamSuccess)
.catch(error => { .catch(error => {
console.log(error.message) console.log(error.message)
}) })
navigator.permissions.query(
{ name: 'microphone' }
).then((permissionStatus) =>{
console.log('🟨 [PERMISSION] permissionStatus', permissionStatus); // granted, denied, prompt
// It will block the code from execution and display "Permission denied" if we don't have microphone permissions
})
} }
const goConnect = () => { const goConnect = () => {
console.log('[goConnect] device:', device);
device === undefined ? getRtpCapabilities() : goCreateTransport() device === undefined ? getRtpCapabilities() : goCreateTransport()
} }
const goCreateTransport = () => { const goCreateTransport = () => {
console.log('[goCreateTransport] IS_PRODUCER:', IS_PRODUCER);
IS_PRODUCER ? createSendTransport() : createRecvTransport() IS_PRODUCER ? createSendTransport() : createRecvTransport()
} }
@ -162,7 +218,8 @@ const createDevice = async () => {
}) })
console.log('Device RTP Capabilities', device.rtpCapabilities) console.log('Device RTP Capabilities', device.rtpCapabilities)
console.log('[createDevice] device', device);
// once the device loads, create transport // once the device loads, create transport
goCreateTransport() goCreateTransport()
@ -174,6 +231,7 @@ const createDevice = async () => {
} }
const getRtpCapabilities = () => { const getRtpCapabilities = () => {
console.log('[getRtpCapabilities]');
// make a request to the server for Router RTP Capabilities // make a request to the server for Router RTP Capabilities
// see server's socket.on('getRtpCapabilities', ...) // see server's socket.on('getRtpCapabilities', ...)
// the server sends back data object which contains rtpCapabilities // the server sends back data object which contains rtpCapabilities
@ -190,18 +248,20 @@ const getRtpCapabilities = () => {
} }
const createSendTransport = () => { const createSendTransport = () => {
console.log('[createSendTransport');
// see server's socket.on('createWebRtcTransport', sender?, ...) // see server's socket.on('createWebRtcTransport', sender?, ...)
// this is a call from Producer, so sender = true // this is a call from Producer, so sender = true
socket.emit('createWebRtcTransport', { sender: true, callId }, ({ params }) => { socket.emit('createWebRtcTransport', { sender: true }, (value) => {
console.log(`[createWebRtcTransport] value: ${JSON.stringify(value)}`);
const params = value.params;
// The server sends back params needed // The server sends back params needed
// to create Send Transport on the client side // to create Send Transport on the client side
if (params.error) { if (params.error) {
console.log(params.error) console.log(params.error)
return return
} }
console.log(params)
// creates a new WebRTC Transport to send media // creates a new WebRTC Transport to send media
// based on the server's producer transport params // based on the server's producer transport params
// https://mediasoup.org/documentation/v3/mediasoup-client/api/#TransportOptions // https://mediasoup.org/documentation/v3/mediasoup-client/api/#TransportOptions
@ -227,10 +287,10 @@ const createSendTransport = () => {
}) })
producerTransport.on('produce', async (parameters, callback, errback) => { producerTransport.on('produce', async (parameters, callback, errback) => {
console.log(parameters) console.log('[produce] parameters', parameters)
try { try {
// tell the server to create a Producer // Tell the server to create a Producer
// with the following parameters and produce // with the following parameters and produce
// and expect back a server side producer id // and expect back a server side producer id
// see server's socket.on('transport-produce', ...) // see server's socket.on('transport-produce', ...)
@ -253,32 +313,55 @@ const createSendTransport = () => {
} }
const connectSendTransport = async () => { const connectSendTransport = async () => {
// we now call produce() to instruct the producer transport
console.log('[connectSendTransport] producerTransport');
// We now call produce() to instruct the producer transport
// to send media to the Router // to send media to the Router
// https://mediasoup.org/documentation/v3/mediasoup-client/api/#transport-produce // https://mediasoup.org/documentation/v3/mediasoup-client/api/#transport-produce
// this action will trigger the 'connect' and 'produce' events above // this action will trigger the 'connect' and 'produce' events above
producer = await producerTransport.produce(params)
// Produce video
let producerVideoHandler = await producerTransport.produce(videoParams)
console.log('videoParams', videoParams);
console.log('producerVideo', producerVideo);
producer.on('trackended', () => { producerVideoHandler.on('trackended', () => {
console.log('track ended') console.log('track ended')
// close video track // close video track
}) })
producer.on('transportclose', () => { producerVideoHandler.on('transportclose', () => {
console.log('transport ended') console.log('transport ended')
// close video track // close video track
}) })
// Produce audio
if (produceAudio) {
let producerAudioHandler = await producerTransport.produce(audioParams)
console.log('audioParams', audioParams);
console.log('producerAudio', producerAudio);
producerAudioHandler.on('trackended', () => {
console.log('track ended')
// close audio track
})
producerAudioHandler.on('transportclose', () => {
console.log('transport ended')
// close audio track
})
}
const answer = { const answer = {
origin_asset_id: ASSET_ID, origin_asset_id: ASSET_ID,
dest_asset_id: originAssetId || parseInt(urlParams.get('dest_asset_id')), dest_asset_id: originAssetId || parseInt(urlParams.get('dest_asset_id')),
type: 'notify-answer', type: 'notify-answer',
origin_asset_priority: 1, origin_asset_priority: 1,
origin_asset_type_name: originAssetTypeName, origin_asset_type_name: ASSET_TYPE,
origin_asset_name: originAssetName, origin_asset_name: ASSET_NAME,
video_call_id: callId, video_call_id: callId,
answer: 'accepted', // answer: 'rejected' answer: 'accepted', // answer: accepted/rejected
}; };
console.log('SEND answer', answer); console.log('SEND answer', answer);
@ -286,11 +369,15 @@ const connectSendTransport = async () => {
'video', 'video',
JSON.stringify(answer) JSON.stringify(answer)
); );
// Enable Close call button
const closeCallBtn = document.getElementById('btnCloseCall');
closeCallBtn.removeAttribute('disabled');
} }
const createRecvTransport = async () => { const createRecvTransport = async () => {
console.log('createRecvTransport'); console.log('createRecvTransport');
// see server's socket.on('consume', sender?, ...) // See server's socket.on('consume', sender?, ...)
// this is a call from Consumer, so sender = false // this is a call from Consumer, so sender = false
await socket.emit('createWebRtcTransport', { sender: false, callId }, ({ params }) => { await socket.emit('createWebRtcTransport', { sender: false, callId }, ({ params }) => {
// The server sends back params needed // The server sends back params needed
@ -300,15 +387,15 @@ const createRecvTransport = async () => {
return return
} }
console.log(params) console.log('[createRecvTransport] params', params)
// creates a new WebRTC Transport to receive media // Creates a new WebRTC Transport to receive media
// based on server's consumer transport params // based on server's consumer transport params
// https://mediasoup.org/documentation/v3/mediasoup-client/api/#device-createRecvTransport // https://mediasoup.org/documentation/v3/mediasoup-client/api/#device-createRecvTransport
consumerTransport = device.createRecvTransport(params) consumerTransport = device.createRecvTransport(params)
// https://mediasoup.org/documentation/v3/communication-between-client-and-server/#producing-media // https://mediasoup.org/documentation/v3/communication-between-client-and-server/#producing-media
// this event is raised when a first call to transport.produce() is made // This event is raised when a first call to transport.produce() is made
// see connectRecvTransport() below // see connectRecvTransport() below
consumerTransport.on('connect', async ({ dtlsParameters }, callback, errback) => { consumerTransport.on('connect', async ({ dtlsParameters }, callback, errback) => {
try { try {
@ -329,41 +416,159 @@ const createRecvTransport = async () => {
}) })
} }
const resetCallSettings = () => {
localVideo.srcObject = null
remoteVideo.srcObject = null
consumer = null
producerVideo = null
producerAudio = null
producerTransport = null
consumerTransport = null
device = undefined
}
const connectRecvTransport = async () => { const connectRecvTransport = async () => {
console.log('connectRecvTransport'); console.log('connectRecvTransport');
// for consumer, we need to tell the server first // For consumer, we need to tell the server first
// to create a consumer based on the rtpCapabilities and consume // to create a consumer based on the rtpCapabilities and consume
// if the router can consume, it will send back a set of params as below // if the router can consume, it will send back a set of params as below
await socket.emit('consume', { await socket.emit('consume', {
rtpCapabilities: device.rtpCapabilities, rtpCapabilities: device.rtpCapabilities,
callId callId
}, async ({ params }) => { }, async ({videoParams, audioParams}) => {
if (params.error) { console.log(`[consume] 🟩 videoParams`, videoParams)
console.log('Cannot Consume') console.log(`[consume] 🟩 audioParams`, audioParams)
return console.log('[consume] 🟩 consumerTransport', consumerTransport)
}
// then consume with the local consumer transport
// which creates a consumer
consumer = await consumerTransport.consume({
id: params.id,
producerId: params.producerId,
kind: params.kind,
rtpParameters: params.rtpParameters
})
// destructure and retrieve the video track from the producer
const { track } = consumer
let stream = new MediaStream() let stream = new MediaStream()
stream.addTrack(track)
// stream.removeTrack(track)
remoteVideo.srcObject = stream
socket.emit('consumer-resume')
console.log('consumer', consumer);
// Maybe the unit does not produce video or audio, so we must only consume what is produced
if (videoParams) {
console.log('❗ Have VIDEO stream to consume');
stream.addTrack(await getVideoTrask(videoParams))
} else {
console.log('❗ Don\'t have VIDEO stream to consume');
}
if (audioParams) {
console.log('❗ Have AUDIO stream to consume');
let audioTrack = await getAudioTrask(audioParams)
stream.addTrack(audioTrack)
} else {
console.log('❗ Don\'t have AUDIO stream to consume');
}
socket.emit('consumer-resume')
remoteVideo.srcObject = stream
remoteVideo.setAttribute('autoplay', true)
remoteVideo.play()
.then(() => {
console.log('remoteVideo PLAY')
})
.catch((error) => {
displayError(`remoteVideo PLAY ERROR | ${error.message}`)
})
})
}
const getVideoTrask = async (videoParams) => {
consumerVideo = await consumerTransport.consume({
id: videoParams.id,
producerId: videoParams.producerId,
kind: videoParams.kind,
rtpParameters: videoParams.rtpParameters
})
consumerVideo.on('transportclose', () => {
console.log('transport closed so consumer closed')
})
return consumerVideo.track
}
const getAudioTrask = async (audioParams) => {
consumerAudio = await consumerTransport.consume({
id: audioParams.id,
producerId: audioParams.producerId,
kind: audioParams.kind,
rtpParameters: audioParams.rtpParameters
})
consumerAudio.on('transportclose', () => {
console.log('transport closed so consumer closed')
})
const audioTrack = consumerAudio.track
audioTrack.applyConstraints({
audio: {
advanced: [
{
echoCancellation: {exact: true}
},
{
autoGainControl: {exact: true}
},
{
noiseSuppression: {exact: true}
},
{
highpassFilter: {exact: true}
}
]
}
})
return audioTrack
}
const closeCall = () => {
console.log('closeCall');
// Emit 'notify-end' to Hub so the consumer will know to close the video
const notifyEnd = {
origin_asset_id: ASSET_ID,
dest_asset_id: originAssetId || parseInt(urlParams.get('dest_asset_id')),
type: 'notify-end',
video_call_id: callId
}
console.log('notifyEnd', notifyEnd)
hub.emit('video', JSON.stringify(notifyEnd))
// Disable Close call button
const closeCallBtn = document.getElementById('btnCloseCall')
closeCallBtn.setAttribute('disabled', '')
// Reset settings
resetCallSettings()
}
const consume = async () => {
console.log('[consume]')
console.log('createRecvTransport Consumer')
await socket.emit('createWebRtcTransport', { sender: false, callId, dispatcher: true }, ({ params }) => {
if (params.error) {
console.log('createRecvTransport | createWebRtcTransport | Error', params.error)
return
}
consumerTransport = device.createRecvTransport(params)
consumerTransport.on('connect', async ({ dtlsParameters }, callback, errback) => {
try {
await socket.emit('transport-recv-connect', {
dtlsParameters,
})
callback()
} catch (error) {
errback(error)
}
})
connectRecvTransport()
}) })
} }
btnLocalVideo.addEventListener('click', getLocalStream) btnLocalVideo.addEventListener('click', getLocalStream)
btnRecvSendTransport.addEventListener('click', goConnect) btnRecvSendTransport.addEventListener('click', consume)
btnCloseCall.addEventListener('click', closeCall)

View File

@ -1,24 +1,25 @@
-----BEGIN CERTIFICATE----- -----BEGIN CERTIFICATE-----
MIIEDzCCAvegAwIBAgIUaoTnmT3ltr9S5ysxQIXoLOHM6IYwDQYJKoZIhvcNAQEL MIIEJTCCAw2gAwIBAgIURHg2am+RarQxIVY1f3CicUQgRowwDQYJKoZIhvcNAQEL
BQAwgZYxCzAJBgNVBAYTAlJPMQ0wCwYDVQQIDARBTEJBMRMwEQYDVQQHDApBTEJB BQAwgaExCzAJBgNVBAYTAlJPMRIwEAYDVQQIDAlCdWNoYXJlc3QxEjAQBgNVBAcM
IElVTElBMRMwEQYDVQQKDApTQUZFTU9CSUxFMRIwEAYDVQQLDAlBSVJXSVpBUkQx CUJ1Y2hhcmVzdDETMBEGA1UECgwKU2FmZW1vYmlsZTETMBEGA1UECwwKU2FmZW1v
DzANBgNVBAMMBlNFUkdJVTEpMCcGCSqGSIb3DQEJARYac2VyZ2l1LnRvbWFAc2Fm YmlsZTETMBEGA1UEAwwKU2FmZW1vYmlsZTErMCkGCSqGSIb3DQEJARYcbWloYWku
ZW1vYmlsZS5jb20wHhcNMjIwNzIwMTgwNzMzWhcNMzIwNzE3MTgwNzMzWjCBljEL Ym96aWVydUBzYWZlbW9iaWxlLmNvbTAeFw0yMjA4MDEyMjA0MjFaFw0zMjA3Mjky
MAkGA1UEBhMCUk8xDTALBgNVBAgMBEFMQkExEzARBgNVBAcMCkFMQkEgSVVMSUEx MjA0MjFaMIGhMQswCQYDVQQGEwJSTzESMBAGA1UECAwJQnVjaGFyZXN0MRIwEAYD
EzARBgNVBAoMClNBRkVNT0JJTEUxEjAQBgNVBAsMCUFJUldJWkFSRDEPMA0GA1UE VQQHDAlCdWNoYXJlc3QxEzARBgNVBAoMClNhZmVtb2JpbGUxEzARBgNVBAsMClNh
AwwGU0VSR0lVMSkwJwYJKoZIhvcNAQkBFhpzZXJnaXUudG9tYUBzYWZlbW9iaWxl ZmVtb2JpbGUxEzARBgNVBAMMClNhZmVtb2JpbGUxKzApBgkqhkiG9w0BCQEWHG1p
LmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAL+KCJzVjXipimVc aGFpLmJvemllcnVAc2FmZW1vYmlsZS5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IB
wYOwemwSQRsF/4qkWliZJBdgs6foCd8lFG2JUqMrHPruhOJA3Ue/5Xw3HXi8s34X DwAwggEKAoIBAQCSEk80aBAbmWtPBLcTjFLbvVmxuzDgzrjH7h2Hg/ly8lE/o2nZ
EDVxRqZEOC9KSZg15Z/9dtF83PrShR0TslqS+JIfcNHBDryYihzuMoQLLucqBVzF 1T2ESSuaQFsxw54ukqbj1ooQXF1DoIxSp+CiNzf/FTB6BaMkaG0ayE2Wnm2wkjKp
dj+RKRTtor8WesNILa4ivGnr55Tq26qAVAv6CV6tyGRpHSfAOCdjr9ViLHQsNvFd POnAzZgTabJoB/qeUlr9i4xiAyBhiQDk5KjdWYHxeZnSznqfIOPzAdw7ZJVYvqvT
+NBcCIbABAWyXfzGLl4iEdcu7zZxTE2Q+MRURTPJNIcDLrr2QL7VF/7vbp7iuKUE GciHnoina5TzPUbpnLcR2LvHcLxuSuWQ6dTz/sfdZRx8lkbR3qltUazmJX+yxJJr
sPi/iXyZtyMTkjgyQA/OSY1JXWVSWtIYTHumbx1xQ/jR3CCwTPxXL5aMuT6fq4eC kagq2V3cfpfLM8DOzPPEzuKHM6sK6ZgTqbc4ti+ul7Q1V+e0v2xNDtuYHkbaOuyd
7GZhodECAwEAAaNTMFEwHQYDVR0OBBYEFBWHLoyJtgWVDFIEguh3ZVcmgbGbMB8G ucmaZ3R++0ryoWWan5OFWZIKjttKy/yq8MUrAgMBAAGjUzBRMB0GA1UdDgQWBBSM
A1UdIwQYMBaAFBWHLoyJtgWVDFIEguh3ZVcmgbGbMA8GA1UdEwEB/wQFMAMBAf8w nlDraef71C/filHpA7dDpwmB7zAfBgNVHSMEGDAWgBSMnlDraef71C/filHpA7dD
DQYJKoZIhvcNAQELBQADggEBAAOzDIR4K2BKKoD/retoVNyFtHSKXs2FU1UIR2QP pwmB7zAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQBNySms9mXG
NJvS9GbkQW/jMYxehHJ9jxB7AVoq+ice8kyhipFYYOPRm9WEouF+BvFyQD1bhrD1 PVOmFAm9YjMjRY+cUpa0Gm6saxp9VOyrAg2KzdwG6LNGgauNsIra1ytM40NASspN
b5kJMKOTcTuqqlRWgECrIZT/oumgujkAT6xulPu+jSbV3XyEp4rJq4E6lks1y8RG r+L49gUCmASUGOqeZCpJjkKAsGspQ4WQKKI6YW8h5dsSuud2qyQtm+w1RKDq+wih
4hdllhfLtmE06hqBgtSVxHg4y0PgKkn0rU+2V7ZaPDc5IViGKbFo/lZI1iW3nD9Y A+B82xWXcFFd52gp6nerib4Pf9ATooOmBMCHFZwC+74sKCv7fXDlzLGdCII8lmI4
UmATM2co6zImrpt/bfBByfwpdZevpEKzSilbQ06JcGEtUm90U1Qzi0YLSv40aUYD uq5eFrSS1NeT3iQCwGb9SHfyFkCliaEdpskqmWhonckN0tJVV118SvknV/h9oIsw
jSYmcpuMoI4vLSIFpPYRAIRbvEwmdwiwCd0CcPWAY98CzhQ= uEMIib6YOBlrU+FInnpqpc8VuR4vv0Yro9XrvmurzLuN8k/lVVkr6NMzyNY9mbkF
9p/Sxd5yIeam
-----END CERTIFICATE----- -----END CERTIFICATE-----

View File

@ -1,28 +1,28 @@
-----BEGIN PRIVATE KEY----- -----BEGIN PRIVATE KEY-----
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC/igic1Y14qYpl MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCSEk80aBAbmWtP
XMGDsHpsEkEbBf+KpFpYmSQXYLOn6AnfJRRtiVKjKxz67oTiQN1Hv+V8Nx14vLN+ BLcTjFLbvVmxuzDgzrjH7h2Hg/ly8lE/o2nZ1T2ESSuaQFsxw54ukqbj1ooQXF1D
FxA1cUamRDgvSkmYNeWf/XbRfNz60oUdE7JakviSH3DRwQ68mIoc7jKECy7nKgVc oIxSp+CiNzf/FTB6BaMkaG0ayE2Wnm2wkjKpPOnAzZgTabJoB/qeUlr9i4xiAyBh
xXY/kSkU7aK/FnrDSC2uIrxp6+eU6tuqgFQL+glerchkaR0nwDgnY6/VYix0LDbx iQDk5KjdWYHxeZnSznqfIOPzAdw7ZJVYvqvTGciHnoina5TzPUbpnLcR2LvHcLxu
XfjQXAiGwAQFsl38xi5eIhHXLu82cUxNkPjEVEUzyTSHAy669kC+1Rf+726e4ril SuWQ6dTz/sfdZRx8lkbR3qltUazmJX+yxJJrkagq2V3cfpfLM8DOzPPEzuKHM6sK
BLD4v4l8mbcjE5I4MkAPzkmNSV1lUlrSGEx7pm8dcUP40dwgsEz8Vy+WjLk+n6uH 6ZgTqbc4ti+ul7Q1V+e0v2xNDtuYHkbaOuyducmaZ3R++0ryoWWan5OFWZIKjttK
guxmYaHRAgMBAAECggEAC3GIRTJ/EsvyUE8D4sXK8qT+jcpEc7iRC0UPAnSNF0WG y/yq8MUrAgMBAAECggEAMRH1iaVrw9nGMsViuy5op2j0uMApq1vGt2NGiD/NjM/a
PXY+K7MuJECBqT5R4yCj99Lvt4WldZ60jJ59IexEH8/omW6zSexCbVYb4LU4kKW+ e4ZqCMOZ5tatzyPPfug4O20Io4Fu4BAnRJCqkxnSXKwwI4D6yAMcyx5JiLXBWtfe
PTf9imrIfhZxy55groTgj4ztqLTwVvBMRLgpce/OyAjQleEWcY9g50v8/MkS23M4 AXMbkb7kx+BJNjxLsqb7ijQgXQyEHGjwd9OOeVZXZAStonE3O5ohl1N1QC1fzpN5
HU+KdRBBB/StI+9uSHQf5XP6nok8dRKGPE0IvAlCM5H0iran6Er0+jMlIq7TwYzX qBFPaAiNhZgaxrB+pp/uRruZXzNGCwLdhpd2HuryJfxkaAD53mqpHwHJbM7wRQl4
940h4DVf9AaUiI8UTUkvp8odtRDYNrICPRnMsm2McmpEvlhHE34/L0gLiwdaz/PZ NJCbFR/lf4sqPO7zWJGyfU8fFVLuNspSW8AdLcsapOUSMhXTEU+vKbdWM+MYRNuk
xK/LvexscmfYrWjDbdoXndKsuoWmi8w3/mjXlAKSFwKBgQDKBQtp/uCVKzFn9jaA ltJVWG1nPkbyyGQoUNEh4rSFOX+3aiN435qkPw7wAQKBgQDEPFQJe+2DpS+M0zvq
3v6ElUKVW0UPAS9pvH9NSZ1Fw5ZxgOieWraZ5zUVhP7DOz7uSWdleB31cN1sLLD+ sZVVkEDxwGZfHnO0h57C4dsGPyLSX7A1r+EM8ooZhCgrXZFru3EDzFuO5isCIeml
WqhCD0T6XYtwWxYNTtLiB8Pvuufp432gYula9FctC6GF+q2o5OSa6VfIaT94TfXs bBET5q2qGEozdb+wUfcHOBZZKR4imY0SYi3lyJdxBeNIOPhUkEpOg3uo2RRklpi6
eHB7V5txx3rFu7Y7G+rpSvgGCwKBgQDyuBSGXukAEgeh3qYPMv2iE4bdT9qwLVM3 Fk4LYXReJ+t36yZyocTn3PfmxwKBgQC+juTHoJGZjqWtVMygUC8kP5G5GXxY9Yk1
Iq0b2LvNmBlAniSe2QXJrLGY/E/71gViRd0up16sgthkq4y/Nc7EVa3ELVXrAenV 7j4Iv8ok1c5xWM6N4GBNG9rKKOD7WQX4dD9IOs35pZqGDaNE44q7na9UabRFR92M
Zv656ArPVr/hirUSPpe7p12whk3riKziy+swyffueIw2WlA6JssM2ghdZDZlknzr I+VAsi1Q2gQPyihW84ESXw6uH85pO5FfGO3fF/ppLXBCVYN85VT+1HFxG+Je2GXE
hlAPmsPtEwKBgGIoL1KSH8b61Kehzvuw3dPHvjFZxmUy+zmR9/yyjvMNxHRj4SLI 50/3e4Q6fQKBgBl/zVu+IsrseBVQjYSdts37hLTlT2gkyNw4k0S3nIJfSeMUVA1l
Cr8ewwEwvKRZjgszqUR5J31RGJTt5hXAMM7gInDPsOenqkBPE1H12fMKSpKvESQz 4VSRX6iZJ68a5X6eSL05nNwgxI3uYjIArOdtHjvwFBRDxLjgrbzeaOkFEslkMpSk
yxSBcjYV16aElYphpN3MEPX0hX1Ly/jY0DZt675N4U5gFXwE5Y/y+C3fAoGBAJYG 9VnaivNA1JvZ60rxxPYW18bFDoVTnFzx8QpBi6GAhnR6tfBHXRLT/9KZAoGAStHI
3ID2F3V4r4kJc4F/ljDhewI/AsDVM1OoNnI49FSviCRd2+rn4GD5Qsl6ZAIZFzjY OiltgaFko73b6kYRfGYJTWgYTsV5bldwu/ax4+ye9hosX8Btj1kUerO6QnYdxgO+
UFAvGiDtu832D8h8/PJ0tzTEhQ//HcdPo3HMESp5x58kuqmtUOKoIAwl5/IHiD2j pRmRrie7mE7agD3nRusO4FHwmhMxhcjCRriu2kP/vENfu2Q4lYIFPZD3dpIQ7gnX
qBt/K6YWkyxhhKd9JRMrjfIUKF9+PYLjgYcztZlLAoGAE4KgqwduWPiOOqI5BdId u/SqOYnBvgndariQus2nDQYpx5unubwoxb8Vl/ECgYAV7nkyMjkakwbFyiAsUMz6
Rq/x6JOoIVlDMKxTS4MLCrdt7RZaEOJZOZ6KDTMcTyh6uqIYQiu9eENMtZjWfA0Q QvSxWC+x5OBv79Nm02bgecdwJny/PULA/R/KHNI/WXHSkM2DdeoMv4XZPdI8TNRo
l0p3yZjLMHh5lTVhbJTsMfp1Etut9yVZPG+7P9+XevTac/t2CNAs+xrXES/b5M8Y bBD217yfRfOMIX2jIhZeTtTAIiOafBdIG0fUtM9nMPkgQGTvgM0FZPdfAtNY/nFu
Eq7JQiVhWZZ4CGNRcQb+y34= xvrhZIQLy0ujoDPPBE8+3Q==
-----END PRIVATE KEY----- -----END PRIVATE KEY-----

103
tsconfig.json Normal file
View File

@ -0,0 +1,103 @@
{
"compilerOptions": {
/* Visit https://aka.ms/tsconfig to read more about this file */
/* Projects */
// "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */
// "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */
// "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */
// "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */
// "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */
// "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
/* Language and Environment */
"target": "es2016", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
// "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
// "jsx": "preserve", /* Specify what JSX code is generated. */
// "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */
// "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
// "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */
// "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
// "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */
// "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */
// "noLib": true, /* Disable including any library files, including the default lib.d.ts. */
// "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */
// "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */
/* Modules */
"module": "commonjs", /* Specify what module code is generated. */
// "rootDir": "./", /* Specify the root folder within your source files. */
// "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */
// "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
// "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
// "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
// "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */
// "types": [], /* Specify type package names to be included without being referenced in a source file. */
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
// "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */
// "resolveJsonModule": true, /* Enable importing .json files. */
// "noResolve": true, /* Disallow 'import's, 'require's or '<reference>'s from expanding the number of files TypeScript should add to a project. */
/* JavaScript Support */
// "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */
// "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */
// "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */
/* Emit */
// "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
// "declarationMap": true, /* Create sourcemaps for d.ts files. */
// "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
// "sourceMap": true, /* Create source map files for emitted JavaScript files. */
// "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */
// "outDir": "./", /* Specify an output folder for all emitted files. */
// "removeComments": true, /* Disable emitting comments. */
// "noEmit": true, /* Disable emitting files from a compilation. */
// "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
// "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */
// "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
// "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
// "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
// "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */
// "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
// "newLine": "crlf", /* Set the newline character for emitting files. */
// "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */
// "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */
// "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */
// "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */
// "declarationDir": "./", /* Specify the output directory for generated declaration files. */
// "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */
/* Interop Constraints */
// "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */
// "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
"esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */
// "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
"forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */
/* Type Checking */
"strict": true, /* Enable all strict type-checking options. */
// "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */
// "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */
// "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
// "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */
// "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */
// "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */
// "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */
// "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
// "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */
// "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */
// "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
// "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
// "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */
// "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */
// "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */
// "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */
// "allowUnusedLabels": true, /* Disable error reporting for unused labels. */
// "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */
/* Completeness */
// "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
"skipLibCheck": true /* Skip type checking all .d.ts files. */
}
}