Compare commits

...

81 Commits

Author SHA1 Message Date
ce1fe3ca8d Merge pull request 'Update 'app.js'' (#33) from sergiu-patch-1 into develop
Reviewed-on: #33
Reviewed-by: bmamihai <mihai.bozieru@safemobile.com>
2023-06-07 12:51:12 +00:00
96b770d9c5 Update 'app.js' 2023-06-07 12:50:24 +00:00
4b1f4fab70 Merge pull request 'LH-284: Update duplex documentation workflow diagrams' (#32) from LH-284-update-and-centralize-diagrams-for-video-calls into develop
Reviewed-on: #32
Reviewed-by: Adriana <adriana.epure@safemobile.com>
2023-05-30 11:27:09 +00:00
ba7c2186af LH-284: Update duplex documentation workflow diagrams 2023-05-30 11:57:02 +03:00
636e8a9fab Merge pull request 'added build commit no' (#31) from build-commit-no into develop
Reviewed-on: #31
Reviewed-by: Cristi Ene <cristi.ene@safemobile.com>
2023-04-10 23:36:34 +00:00
5da2eb6927 update build.sh after review 2023-04-05 01:57:58 +03:00
97948a5d8c added build commit no 2023-04-02 01:17:18 +03:00
b94b1bff86 Merge pull request 'docker' (#18) from docker into develop
Reviewed-on: #18
Reviewed-by: Cristi Ene <cristi.ene@safemobile.com>
2023-02-26 22:11:02 +00:00
5c4f2bf13e Merge branch 'develop' into docker 2023-02-26 22:10:34 +00:00
df7f1987f6 Merge pull request 'LH-276: Add close-producer event handler; Update client' (#30) from LH-276-close-producer into develop
Reviewed-on: #30
Reviewed-by: Cristi Ene <cristi.ene@safemobile.com>
2023-02-23 09:46:41 +00:00
7842953faf LH-276: Refactor consumer-resume 2023-02-22 18:57:57 +02:00
4591617b1e LH-276: Fix video consume when initiator is not set yet(at start) 2023-02-22 18:50:37 +02:00
9b3f2f94c8 LH-276: Fix audio when initiator is not set yet(at start) 2023-02-22 18:46:01 +02:00
29a4cd7227 LH-276: Format code 2023-02-22 18:40:39 +02:00
c80265fe25 LH-276: Format code 2023-02-22 18:36:50 +02:00
abb1533c9b LH-276: Format code 2023-02-22 18:32:41 +02:00
6e3ce9fbb3 LH-276: Format code 2023-02-22 18:30:28 +02:00
4d8adf9eac LH-276: Refactor consume(consumeAudio/consumeVideo); Format code 2023-02-22 18:21:53 +02:00
bde02fe250 LH-276: Add close-producer event handler; Update client 2023-02-21 02:41:48 +02:00
0ddb43b4b5 Merge pull request 'LINXD-2342-dispatcher-audio' (#29) from LINXD-2342-dispatcher-audio into develop
Reviewed-on: #29
Reviewed-by: Cristi Ene <cristi.ene@safemobile.com>
2023-02-15 09:14:57 +00:00
f02d6af726 LINXD-2342: Added documentation for kind 2023-02-15 11:14:02 +02:00
7ee93d7963 LINXD-2342: Update video client 2023-02-15 10:06:39 +02:00
98212a78cb Update client to consume audio correctly 2023-02-11 21:32:53 +02:00
33ea44ac02 Update client to consume audio correctly 2023-02-11 21:30:21 +02:00
6b822142d0 Add the kind of producer on new-producer event 2023-02-11 21:13:57 +02:00
50e95b93a4 Update client 2023-02-08 19:37:32 +02:00
6eca0808c9 Merge pull request 'Update web client to work with dev' (#27) from update-web-client into develop
Reviewed-on: #27
Reviewed-by: bmamihai <mihai.bozieru@safemobile.com>
2023-01-23 20:37:27 +00:00
f728b23b7f Update web client to work with dev 2023-01-23 20:45:08 +02:00
33c0234fb0 Merge pull request 'LINXD-2303: Added client-to-client workflow; Added client-mediadoup workflow' (#26) from LINXD-2303-video-documentation-mobile-mobile into develop
Reviewed-on: #26
Reviewed-by: Cristi Ene <cristi.ene@safemobile.com>
2023-01-19 14:44:24 +00:00
f81d5b2dfa LINXD-2303: Update diagrams Mediasoup connect & produce; Update diagram Client-Client workflow 2023-01-19 16:43:46 +02:00
ae87b5bd71 LINXD-2303: Added client-to-client workflow; Added client-mediadoup workflow 2023-01-19 16:31:16 +02:00
09e82fb224 Merge pull request 'Moved 'new-producer' in 'transport-produce' handler' (#25) from new-producer-in-transport-produce into develop
Reviewed-on: #25
Reviewed-by: Cristi Ene <cristi.ene@safemobile.com>
2023-01-19 06:54:53 +00:00
1da0170261 Moved 'new-producer' in 'transport-produce' handler 2023-01-18 11:49:02 +02:00
aca0a6eac0 improved build to get also the git log to know what is on server 2023-01-17 16:00:52 +00:00
23ca56c8c6 rever build.sh 2023-01-17 15:51:55 +00:00
a2d0b6771b Merge pull request 'Add new-producer event; Update client to consume when receives new-producer event' (#24) from LAPI-675-generate-new-producer-in-mediasoup-and-client into develop
Reviewed-on: #24
Reviewed-by: Cristi Ene <cristi.ene@safemobile.com>
2023-01-16 15:40:06 +00:00
9382fd11fb LAPI-675: Remove commented code 2023-01-16 17:39:15 +02:00
fb99fd57f8 LAPI-675: Refactor transport-connect new-producer 2023-01-16 17:32:31 +02:00
0f4342777a LAPI-675: Update bundle 2023-01-13 20:12:59 +02:00
69c167e4e9 Add new-producer event; Update client to consume when receives new-producer event 2023-01-13 20:11:24 +02:00
ad4c1f27e7 Merge pull request 'LAPI-674: Parse transport-recv-connect and consume param' (#23) from LAPI-674-change-events-for-consumer-in-mediasoup-server into develop
Reviewed-on: #23
Reviewed-by: Cristi Ene <cristi.ene@safemobile.com>
2023-01-13 07:52:27 +00:00
b2128c4754 fix build 2023-01-12 16:42:44 +00:00
af53dc1610 fix build 2023-01-12 16:30:39 +00:00
f48fe4f9aa LAPI-674: Parse transport-recv-connect and consume param 2023-01-12 16:23:30 +02:00
6fd6ca9755 Merge pull request 'comsume-undefined' (#22) from comsume-undefined into develop
Reviewed-on: #22
Reviewed-by: Cristi Ene <cristi.ene@safemobile.com>
2023-01-12 13:31:46 +00:00
1b72941a32 Moved callId in consumer-resume 2023-01-12 15:09:21 +02:00
c037240f47 Refactor consumer-resume 2023-01-12 14:32:48 +02:00
9716bddd71 Added log 2023-01-12 12:50:40 +02:00
e6c08a2c1d Refactor consumer-resume v3 2023-01-12 12:46:46 +02:00
1342e812e6 Refactor consumer-resume 2023-01-12 12:45:23 +02:00
d8405eccc7 Refactor consumer-resume 2023-01-12 12:44:11 +02:00
5bf31d452f Check for initiatorConsumerAudio before resume() 2023-01-12 12:38:40 +02:00
c034610471 Rollback 2023-01-12 12:38:01 +02:00
df5b3eab90 Check for initiatorConsumerAudio before resume() 2023-01-12 12:31:52 +02:00
b0293230b6 Merge pull request 'add-diagrams-doc' (#21) from add-diagrams-doc into develop
Reviewed-on: #21
Reviewed-by: Cristi Ene <cristi.ene@safemobile.com>
2023-01-10 17:51:05 +00:00
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
48eee903a5 added command for limiting cpu/memory 2023-01-05 14:31:51 +02:00
d5bc0cd1d3 run under host network 2023-01-05 13:18:13 +02:00
1c353d7c88 Merge branch 'develop' into docker 2023-01-05 01:14:56 +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
3e31ba21bd replace individual copy with copy all 2022-12-15 13:20:22 +02:00
cdf02756d3 fix start 2022-12-14 01:32:17 +02:00
b2f9f5affa remove pm2 and watchify 2022-12-14 00:48:14 +02:00
5b9bfeaa01 improve docker 2022-12-10 03:21:59 +02:00
e3bef9b3e5 New dockerfile 2022-12-10 02:38:09 +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
20 changed files with 1738 additions and 1431 deletions

2
.dockerignore Normal file
View File

@ -0,0 +1,2 @@
node_modules
doc

1
.gitignore vendored
View File

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

View File

@ -1,11 +1,25 @@
FROM ubuntu FROM ubuntu:22.04
WORKDIR /app
RUN apt-get update && \ RUN apt-get update && \
apt-get install -y build-essential pip net-tools iputils-ping iproute2 curl apt-get install -y build-essential pip net-tools iputils-ping iproute2 curl
RUN curl -fsSL https://deb.nodesource.com/setup_16.x | bash - RUN curl -fsSL https://deb.nodesource.com/setup_18.x | bash -
RUN apt-get install -y nodejs RUN apt-get install -y nodejs
RUN npm install -g watchify
EXPOSE 3000 COPY . /app/
EXPOSE 2000-2020
EXPOSE 10000-10100 RUN npm install
EXPOSE 3000/tcp
EXPOSE 2000-2200/udp
CMD node app.js
#docker build -t linx-video .
# docker run -it -d --restart always -p 3000:3000/tcp -p 2000-2200:2000-2200/udp linx-video
#Run under host network
# docker run -it -d --network host --restart always -p 3000:3000/tcp -p 2000-2200:2000-2200/udp linx-video
#https://docs.docker.com/config/containers/resource_constraints/
#docker run -it -d --network host --cpus="0.25" --memory="512m" --restart always -p 3000:3000/tcp -p 2000-2200:2000-2200/udp linx-video

View File

@ -22,18 +22,20 @@
2. Run the `npm start:prod` command to start the server in production mode. 2. Run the `npm start:prod` command to start the server in production mode.
(To connect to the terminal, use `pm2 log video-server`) (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 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 - The web client can be accessed using the /sfu path
ex: http://localhost:3000/sfu/?assetId=1&&accountId=1&producer=true&assetName=Adi&assetType=linx 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 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 accountId = account id of the unit on which you are doing the test
producer = it will always be true because you are the producer 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) (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 assetName = asset name of the unit on which you are doing the test
assetType = asset type 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 ### Demo project
The demo project used initially and then modified for our needs `https://github.com/jamalag/mediasoup2` The demo project used initially and then modified for our needs `https://github.com/jamalag/mediasoup2`

1000
app.js

File diff suppressed because it is too large Load Diff

75
build.sh Executable file
View File

@ -0,0 +1,75 @@
#/!bin/bash
## FUNCTIONS
function getGitVersion(){
version=$(git describe)
count=$(echo ${version%%-*} | grep -o "\." | wc -l)
if (( $count > 1 )); then
version=${version%%-*}
elif (( $count == 0 ));then
echo -e "Error: Git version \"${version%%-*}\" not respecting Safemobile standard.\n Must be like 4.xx or 4.xx.xx"
version="0.0.0"
else
if [[ "$1" == "dev" ]];then
cleanprefix=${version#*-} # remove everything before `-` including `-`
cleansuffix=${cleanprefix%-*} # remove everything after `-` including `-`
version="${version%%-*}.${cleansuffix}"
else
version="${version%%-*}.0" # one `%` remove everything after last `-`, two `%%` remove everything after all `-`
fi
fi
}
function addVersionPm2(){
file_pkg="package.json"
key=" \"version\": \""
if [ -f "$file_pkg" ] && [ ! -z "$version" ]; then
versionApp=" \"version\": \"$version\","
sed -i "s|^.*$key.*|${versionApp//\//\\/}|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
}
## 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
if [ -d "node_modules" ]; then
rm -fr node_modules
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,doc,Dockerfile} dist/
#cp -r ./* dist/
# Generate Git log
dateString=$(date +"%Y%m%d-%H%M%S")
git log --pretty=format:"%ad%x09%an%x09%s" --no-merges -20 > "dist/git-$dateString.log"
# Get Git version control
getGitVersion $1
# Add version control for pm2
cd dist
addVersionPm2
## POST BUILD
cd -

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 419 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 606 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 349 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 412 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 421 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 567 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 660 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 571 KiB

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +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/',
// mediasoupAddress: 'http://localhost:3000/mediasoup',
} }

View File

@ -34,6 +34,9 @@
<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>
@ -43,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>
@ -60,34 +75,11 @@
</td> </td>
<td> <td>
<div id="sharedBtns"> <div id="sharedBtns">
<button id="btnRecvSendTransport">Consume</button> <!-- <button id="btnRecvSendTransport">Consume</button> -->
<button id="remoteSoundControl">Unmute</button>
</div> </div>
</td> </td>
</tr> </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>
</td>
</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"> <div id="closeCallBtn">

View File

@ -10,147 +10,201 @@ const ASSET_NAME = urlParams.get('assetName') || null;
const ASSET_TYPE = urlParams.get('assetType') || 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) {
reconnection: true, produceAudio = true
reconnectionDelay: 1000, console.log('produce audio');
reconnectionDelayMax : 5000, } else {
reconnectionAttempts: Infinity produceAudio = false
})
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()
}
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 = 'Adi' let consumerVideo // local consumer video(consumer not transport)
// let originAssetTypeName = 'linx' 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 = {
console.log('[streamSuccess]'); codecOptions :
localVideo.srcObject = stream {
const track = stream.getVideoTracks()[0] opusStereo : true,
params = { opusDtx : true
track,
...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()
}
if (IS_PRODUCER && urlParams.get('testing') === 'true') { getLocalStream() }
})
socket.on('new-producer', ({ callId, kind }) => {
console.log(`🟢 new-producer | callId: ${callId} | kind: ${kind} | Ready to consume`);
connectRecvTransport();
})
socket.on('close-producer', ({ callId, kind }) => {
console.log(`🔴 close-producer | callId: ${callId} | kind: ${kind}`);
if (kind === 'video') {
consumerVideo.close()
remoteVideo.srcObject = null
}
else if (kind === 'audio') consumerAudio.close()
})
}
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 = () => {
@ -167,7 +221,6 @@ const goCreateTransport = () => {
// server side to send/recive media // server side to send/recive media
const createDevice = async () => { const createDevice = async () => {
try { try {
console.log('[createDevice]');
device = new mediasoupClient.Device() device = new mediasoupClient.Device()
// https://mediasoup.org/documentation/v3/mediasoup-client/api/#device-load // https://mediasoup.org/documentation/v3/mediasoup-client/api/#device-load
@ -178,7 +231,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()
@ -207,18 +261,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
@ -244,10 +300,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', ...)
@ -270,21 +326,45 @@ 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,
@ -294,7 +374,7 @@ const connectSendTransport = async () => {
origin_asset_type_name: ASSET_TYPE, origin_asset_type_name: ASSET_TYPE,
origin_asset_name: ASSET_NAME, 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);
@ -306,11 +386,13 @@ const connectSendTransport = async () => {
// Enable Close call button // Enable Close call button
const closeCallBtn = document.getElementById('btnCloseCall'); const closeCallBtn = document.getElementById('btnCloseCall');
closeCallBtn.removeAttribute('disabled'); closeCallBtn.removeAttribute('disabled');
createRecvTransport();
} }
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
@ -320,15 +402,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 {
@ -345,7 +427,8 @@ const createRecvTransport = async () => {
errback(error) errback(error)
} }
}) })
connectRecvTransport() // We call it in new-rpoducer, we don't need it here anymore
// connectRecvTransport()
}) })
} }
@ -353,7 +436,8 @@ const resetCallSettings = () => {
localVideo.srcObject = null localVideo.srcObject = null
remoteVideo.srcObject = null remoteVideo.srcObject = null
consumer = null consumer = null
producer = null producerVideo = null
producerAudio = null
producerTransport = null producerTransport = null
consumerTransport = null consumerTransport = null
device = undefined device = undefined
@ -361,40 +445,97 @@ const resetCallSettings = () => {
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) => {
console.error(`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
})
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 = () => { const closeCall = () => {
console.log('closeCall'); console.log('closeCall');
@ -416,6 +557,30 @@ const closeCall = () => {
resetCallSettings() resetCallSettings()
} }
// const consume = async (kind) => {
// console.log(`[consume] kind: ${kind}`)
// 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) btnCloseCall.addEventListener('click', closeCall)