Compare commits

..

1 Commits

Author SHA1 Message Date
801652170e LINXD-2180: Added recordings 2022-08-26 10:01:08 +03:00
12 changed files with 1972 additions and 1521 deletions

4
.env
View File

@ -1,7 +1,3 @@
PORT=3000
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).
RTC_MIN_PORT=2000
RTC_MAX_PORT=2020
SERVER_CERT="./server/ssl/cert.pem"
SERVER_KEY="./server/ssl/key.pem"

1
.gitignore vendored
View File

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

View File

@ -1,11 +1,5 @@
# Video server
### 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
@ -22,20 +16,14 @@
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
ex: http://localhost:3000/sfu/?assetId=1&&accountId=1&producer=true&assetName=Adi&assetType=linx
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`
assetType = asset type of the unit on which you are doing the test

916
app.js

File diff suppressed because it is too large Load Diff

View File

@ -1,46 +0,0 @@
#/!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.

Before

Width:  |  Height:  |  Size: 571 KiB

805
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -5,17 +5,20 @@
"main": "app.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"start:dev": "nodemon app.js",
"start:dev": "nodemon app.ts",
"start:prod": "pm2 start ./app.js -n video-server",
"watch": "watchify public/index.js -o public/bundle.js -v"
},
"keywords": [],
"author": "",
"license": "ISC",
"type": "module",
"dependencies": {
"@types/express": "^4.17.13",
"dotenv": "^16.0.1",
"express": "^4.18.1",
"ffmpeg-static": "^5.0.2",
"httpolyglot": "^0.1.2",
"mediasoup": "^3.10.4",
"mediasoup-client": "^3.6.54",
"parcel": "^2.7.0",

File diff suppressed because it is too large Load Diff

View File

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

View File

@ -43,7 +43,7 @@
<tr>
<td>
<div id="sharedBtns">
<video id="localVideo" autoplay class="video" muted></video>
<video id="localVideo" autoplay class="video" ></video>
</div>
</td>
<td>

View File

@ -12,44 +12,10 @@ let callId = parseInt(urlParams.get('callId')) || null;
const IS_PRODUCER = urlParams.get('producer') === 'true' ? true : false
console.log('[URL] ASSET_ID', ASSET_ID, '| ACCOUNT_ID', ACCOUNT_ID, '| callId', callId, ' | IS_PRODUCER', IS_PRODUCER)
console.log('🟩 config', config)
let socket
hub = io(config.hubAddress)
let socket, hub
let device
let rtpCapabilities
let producerTransport
let consumerTransport
let producerVideo
let producerAudio
let consumer
let originAssetId
// https://mediasoup.org/documentation/v3/mediasoup-client/api/#ProducerOptions
// https://mediasoup.org/documentation/v3/mediasoup-client/api/#transport-produce
let videoParams = {
encodings: [
{ scaleResolutionDownBy: 4, maxBitrate: 500000 },
{ scaleResolutionDownBy: 2, maxBitrate: 1000000 },
{ scaleResolutionDownBy: 1, maxBitrate: 5000000 },
{ scalabilityMode: 'S3T3_KEY' }
],
codecOptions: {
videoGoogleStartBitrate: 1000
}
}
let audioParams = {
codecOptions :
{
opusStereo : true,
opusDtx : true
}
}
setTimeout(() => {
hub = io(config.hubAddress)
const connectToMediasoup = () => {
const connectToMediasoup = () => {
socket = io(config.mediasoupAddress, {
reconnection: true,
@ -66,11 +32,11 @@ setTimeout(() => {
}
if (IS_PRODUCER && urlParams.get('testing') === 'true') { getLocalStream() }
})
}
}
if (IS_PRODUCER === true) {
if (IS_PRODUCER === true) {
hub.on('connect', async () => {
console.log(`[HUB]! ${config.hubAddress} | connected: ${hub.connected}`)
console.log(`[HUB] ${config.hubAddress} | connected: ${hub.connected}`)
connectToMediasoup()
hub.emit(
@ -114,55 +80,77 @@ setTimeout(() => {
hub.on('disconnect', () => {
console.log('disconnect')
})
} else {
} else {
connectToMediasoup()
}
}
}, 1600);
let device
let rtpCapabilities
let producerTransport
let consumerTransport
let producer
let consumer
let originAssetId
// let originAssetName = 'Adi'
// let originAssetTypeName = 'linx'
// https://mediasoup.org/documentation/v3/mediasoup-client/api/#ProducerOptions
// https://mediasoup.org/documentation/v3/mediasoup-client/api/#transport-produce
let params = {
// mediasoup params
encodings: [
{
rid: 'r0',
maxBitrate: 100000,
scalabilityMode: 'S1T3',
},
{
rid: 'r1',
maxBitrate: 300000,
scalabilityMode: 'S1T3',
},
{
rid: 'r2',
maxBitrate: 900000,
scalabilityMode: 'S1T3',
},
],
// https://mediasoup.org/documentation/v3/mediasoup-client/api/#ProducerCodecOptions
codecOptions: {
videoGoogleStartBitrate: 1000
}
}
const streamSuccess = (stream) => {
console.log('[streamSuccess] device', device);
console.log('[streamSuccess]');
localVideo.srcObject = stream
console.log('stream', stream);
const videoTrack = stream.getVideoTracks()[0]
const audioTrack = stream.getAudioTracks()[0]
videoParams = {
track: videoTrack,
...videoParams
const track = stream.getVideoTracks()[0]
params = {
track,
...params
}
audioParams = {
track: audioTrack,
...audioParams
}
console.log('[streamSuccess] videoParams', videoParams, ' | audioParams', audioParams);
goConnect()
}
const getLocalStream = () => {
console.log('[getLocalStream]');
navigator.mediaDevices.getUserMedia({
audio: true,
audio: false,
video: {
qvga : { width: { ideal: 320 }, height: { ideal: 240 } },
vga : { width: { ideal: 640 }, height: { ideal: 480 } },
hd : { width: { ideal: 1280 }, height: { ideal: 720 } }
width: {
min: 640,
max: 1920,
},
height: {
min: 400,
max: 1080,
}
}
})
.then(streamSuccess)
.catch(error => {
console.log(error.message)
console.log('getLocalStream', error)
})
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 = () => {
@ -179,6 +167,7 @@ const goCreateTransport = () => {
// server side to send/recive media
const createDevice = async () => {
try {
console.log('[createDevice]');
device = new mediasoupClient.Device()
// https://mediasoup.org/documentation/v3/mediasoup-client/api/#device-load
@ -189,7 +178,6 @@ const createDevice = async () => {
})
console.log('Device RTP Capabilities', device.rtpCapabilities)
console.log('[createDevice] device', device);
// once the device loads, create transport
goCreateTransport()
@ -219,20 +207,18 @@ const getRtpCapabilities = () => {
}
const createSendTransport = () => {
console.log('[createSendTransport');
// see server's socket.on('createWebRtcTransport', sender?, ...)
// this is a call from Producer, so sender = true
socket.emit('createWebRtcTransport', { sender: true, callId }, (value) => {
console.log(`[createWebRtcTransport] value: ${JSON.stringify(value)}`);
const params = value.params;
socket.emit('createWebRtcTransport', { sender: true, callId }, ({ params }) => {
// The server sends back params needed
// to create Send Transport on the client side
if (params.error) {
console.log(params.error)
return
}
console.log(params)
// creates a new WebRTC Transport to send media
// based on the server's producer transport params
// https://mediasoup.org/documentation/v3/mediasoup-client/api/#TransportOptions
@ -258,17 +244,17 @@ const createSendTransport = () => {
})
producerTransport.on('produce', async (parameters, callback, errback) => {
console.log('[produce] parameters', parameters)
console.log('produce', parameters)
try {
// Tell the server to create a Producer
// tell the server to create a Producer
// with the following parameters and produce
// and expect back a server side producer id
// see server's socket.on('transport-produce', ...)
await socket.emit('transport-produce', {
kind: parameters.kind,
rtpParameters: parameters.rtpParameters,
appData: parameters.appData,
callId: callId
}, ({ id }) => {
// Tell the transport that parameters were transmitted and provide it with the
// server side producer's id.
@ -284,44 +270,22 @@ const createSendTransport = () => {
}
const connectSendTransport = async () => {
console.log('[connectSendTransport] producerTransport');
// We now call produce() to instruct the producer transport
// we now call produce() to instruct the producer transport
// to send media to the Router
// https://mediasoup.org/documentation/v3/mediasoup-client/api/#transport-produce
// this action will trigger the 'connect' and 'produce' events above
producer = await producerTransport.produce(params)
// Produce video
producerVideo = await producerTransport.produce(videoParams)
console.log('videoParams', videoParams);
console.log('producerVideo', producerVideo);
producerVideo.on('trackended', () => {
producer.on('trackended', () => {
console.log('track ended')
// close video track
})
producerVideo.on('transportclose', () => {
producer.on('transportclose', () => {
console.log('transport ended')
// close video track
})
// Produce audio
producerAudio = await producerTransport.produce(audioParams)
console.log('audioParams', audioParams);
console.log('producerAudio', producerAudio);
producerAudio.on('trackended', () => {
console.log('track ended')
// close audio track
})
producerAudio.on('transportclose', () => {
console.log('transport ended')
// close audio track
})
const answer = {
origin_asset_id: ASSET_ID,
dest_asset_id: originAssetId || parseInt(urlParams.get('dest_asset_id')),
@ -330,7 +294,7 @@ const connectSendTransport = async () => {
origin_asset_type_name: ASSET_TYPE,
origin_asset_name: ASSET_NAME,
video_call_id: callId,
answer: 'accepted', // answer: accepted/rejected
answer: 'accepted', // answer: 'rejected'
};
console.log('SEND answer', answer);
@ -346,7 +310,7 @@ const connectSendTransport = async () => {
const createRecvTransport = async () => {
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
await socket.emit('createWebRtcTransport', { sender: false, callId }, ({ params }) => {
// The server sends back params needed
@ -356,15 +320,15 @@ const createRecvTransport = async () => {
return
}
console.log('[createRecvTransport] params', params)
console.log(params)
// Creates a new WebRTC Transport to receive media
// creates a new WebRTC Transport to receive media
// based on server's consumer transport params
// https://mediasoup.org/documentation/v3/mediasoup-client/api/#device-createRecvTransport
consumerTransport = device.createRecvTransport(params)
// 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
consumerTransport.on('connect', async ({ dtlsParameters }, callback, errback) => {
try {
@ -389,8 +353,7 @@ const resetCallSettings = () => {
localVideo.srcObject = null
remoteVideo.srcObject = null
consumer = null
producerVideo = null
producerAudio = null
producer = null
producerTransport = null
consumerTransport = null
device = undefined
@ -398,7 +361,7 @@ const resetCallSettings = () => {
const connectRecvTransport = async () => {
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
// if the router can consume, it will send back a set of params as below
await socket.emit('consume', {
@ -410,7 +373,7 @@ const connectRecvTransport = async () => {
return
}
// Then consume with the local consumer transport
// then consume with the local consumer transport
// which creates a consumer
consumer = await consumerTransport.consume({
id: params.id,
@ -453,7 +416,6 @@ const closeCall = () => {
resetCallSettings()
}
btnLocalVideo.addEventListener('click', getLocalStream)
btnRecvSendTransport.addEventListener('click', goConnect)
btnCloseCall.addEventListener('click', closeCall)