Compare commits

..

1 Commits

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

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

775
app.js
View File

@ -1,38 +1,31 @@
require('dotenv').config()
import '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 = require('https');
try {
https = require('node:https');
} catch (err) {
console.log('https support is disabled!');
}
const mediasoup = require('mediasoup');
/**
* integrating mediasoup server with a node.js application
*/
/* Please follow mediasoup installation requirements */
/* https://mediasoup.org/documentation/v3/mediasoup/installation/ */
import express from 'express'
const app = express()
import https from 'httpolyglot'
import fs from 'fs'
import path from 'path'
const __dirname = path.resolve()
// const FFmpegStatic = require("ffmpeg-static")
import FFmpegStatic from 'ffmpeg-static'
import Server from 'socket.io'
import mediasoup, { getSupportedRtpCapabilities } from 'mediasoup'
import Process from 'child_process'
let worker
/**
* videoCalls
* |-> Router
* |-> Producer
* |-> Consumer
* |-> Producer Transport
* |-> Consumer Transport
*
* '<callId>': {
* router: Router,
* producer: Producer,
* producerTransport: Producer Transport,
* consumer: Consumer,
* consumerTransport: Consumer Transport
* }
*
**/
let videoCalls = {}
let socketDetails = {}
let router = {}
let producerTransport
let consumerTransport
let producer
let consumer
app.get('/', (_req, res) => {
res.send('Hello from mediasoup app!')
@ -43,44 +36,294 @@ app.use('/sfu', express.static(path.join(__dirname, 'public')))
// SSL cert for HTTPS access
const options = {
key: fs.readFileSync('./server/ssl/key.pem', 'utf-8'),
cert: fs.readFileSync('./server/ssl/cert.pem', 'utf-8'),
cert: fs.readFileSync('./server/ssl/cert.pem', 'utf-8')
}
const httpsServer = https.createServer(options, app);
const io = new Server(httpsServer, {
allowEIO3: true,
origins: ["*:*"],
// allowRequest: (req, next) => {
// console.log('req', req);
// next(null, true)
// }
});
// const io = new Server(server, { origins: '*:*', allowEIO3: true });
const httpsServer = https.createServer(options, app)
httpsServer.listen(process.env.PORT, () => {
console.log('Video server listening on port:', process.env.PORT)
console.log('Listening on port:', process.env.PORT)
})
const peers = io.of('/')
const startRecordingFfmpeg = () => {
// Return a Promise that can be awaited
let recResolve;
const promise = new Promise((res, _rej) => {
recResolve = res;
});
// const useAudio = audioEnabled();
// const useVideo = videoEnabled();
// const useH264 = h264Enabled();
// const cmdProgram = "ffmpeg"; // Found through $PATH
const cmdProgram = FFmpegStatic; // From package "ffmpeg-static"
let cmdInputPath = `${__dirname}/recording/input-vp8.sdp`;
let cmdOutputPath = `${__dirname}/recording/output-ffmpeg-vp8.webm`;
let cmdCodec = "";
let cmdFormat = "-f webm -flags +global_header";
// Ensure correct FFmpeg version is installed
const ffmpegOut = Process.execSync(cmdProgram + " -version", {
encoding: "utf8",
});
const ffmpegVerMatch = /ffmpeg version (\d+)\.(\d+)\.(\d+)/.exec(ffmpegOut);
let ffmpegOk = false;
if (ffmpegOut.startsWith("ffmpeg version git")) {
// Accept any Git build (it's up to the developer to ensure that a recent
// enough version of the FFmpeg source code has been built)
ffmpegOk = true;
} else if (ffmpegVerMatch) {
const ffmpegVerMajor = parseInt(ffmpegVerMatch[1], 10);
if (ffmpegVerMajor >= 4) {
ffmpegOk = true;
}
}
if (!ffmpegOk) {
console.error("FFmpeg >= 4.0.0 not found in $PATH; please install it");
process.exit(1);
}
// if (useAudio) {
// cmdCodec += " -map 0:a:0 -c:a copy";
// }
// if (useVideo) {
cmdCodec += " -map 0:v:0 -c:v copy";
// if (useH264) {
cmdInputPath = `${__dirname}/recording/input-h264.sdp`;
cmdOutputPath = `${__dirname}/recording/output-ffmpeg-h264.mp4`;
// "-strict experimental" is required to allow storing
// OPUS audio into MP4 container
cmdFormat = "-f mp4 -strict experimental";
// }
// }
// Run process
const cmdArgStr = [
"-nostdin",
"-protocol_whitelist file,rtp,udp",
"-loglevel debug",
"-analyzeduration 5M",
"-probesize 5M",
"-fflags +genpts",
`-i ${cmdInputPath}`,
cmdCodec,
cmdFormat,
`-y ${cmdOutputPath}`,
]
.join(" ")
.trim();
console.log('💗', cmdCodec);
console.log(`Run command: ${cmdProgram} ${cmdArgStr}`);
let recProcess = Process.spawn(cmdProgram, cmdArgStr.split(/\s+/));
global.recProcess = recProcess;
recProcess.on("error", (err) => {
console.error("Recording process error:", err);
});
recProcess.on("exit", (code, signal) => {
console.log("Recording process exit, code: %d, signal: %s", code, signal);
global.recProcess = null;
stopMediasoupRtp();
if (!signal || signal === "SIGINT") {
console.log("Recording stopped");
} else {
console.warn(
"Recording process didn't exit cleanly, output file might be corrupt"
);
}
});
// FFmpeg writes its logs to stderr
recProcess.stderr.on("data", (chunk) => {
chunk
.toString()
.split(/\r?\n/g)
.filter(Boolean) // Filter out empty strings
.forEach((line) => {
console.log(line);
if (line.startsWith("ffmpeg version")) {
setTimeout(() => {
recResolve();
}, 1000);
}
});
});
return promise;
}
const startRecordingGstreamer = () => {
// Return a Promise that can be awaited
let recResolve;
const promise = new Promise((res, _rej) => {
recResolve = res;
});
// const useAudio = audioEnabled();
// const useVideo = videoEnabled();
// const useH264 = h264Enabled();
let cmdInputPath = `${__dirname}/recording/input-vp8.sdp`;
let cmdOutputPath = `${__dirname}/recording/output-gstreamer-vp8.webm`;
let cmdMux = "webmmux";
let cmdAudioBranch = "";
let cmdVideoBranch = "";
// if (useAudio) {
// // prettier-ignore
// cmdAudioBranch =
// "demux. ! queue \
// ! rtpopusdepay \
// ! opusparse \
// ! mux.";
// }
// if (useVideo) {
// if (useH264) {
cmdInputPath = `${__dirname}/recording/input-h264.sdp`;
cmdOutputPath = `${__dirname}/recording/output-gstreamer-h264.mp4`;
cmdMux = `mp4mux faststart=true faststart-file=${cmdOutputPath}.tmp`;
// prettier-ignore
cmdVideoBranch =
"demux. ! queue \
! rtph264depay \
! h264parse \
! mux.";
// } else {
// // prettier-ignore
// cmdVideoBranch =
// "demux. ! queue \
// ! rtpvp8depay \
// ! mux.";
// }
// }
// Run process
const cmdProgram = "gst-launch-1.0"; // Found through $PATH
const cmdArgStr = [
"--eos-on-shutdown",
`filesrc location=${cmdInputPath}`,
"! sdpdemux timeout=0 name=demux",
`${cmdMux} name=mux`,
`! filesink location=${cmdOutputPath}`,
cmdAudioBranch,
cmdVideoBranch,
]
.join(" ")
.trim();
console.log(
`Run command: ${cmdProgram} ${cmdArgStr}`
);
let recProcess = Process.spawn(cmdProgram, cmdArgStr.split(/\s+/));
global.recProcess = recProcess;
recProcess.on("error", (err) => {
console.error("Recording process error:", err);
});
recProcess.on("exit", (code, signal) => {
console.log("Recording process exit, code: %d, signal: %s", code, signal);
global.recProcess = null;
stopMediasoupRtp();
if (!signal || signal === "SIGINT") {
console.log("Recording stopped");
} else {
console.warn(
"Recording process didn't exit cleanly, output file might be corrupt"
);
}
});
// GStreamer writes some initial logs to stdout
recProcess.stdout.on("data", (chunk) => {
chunk
.toString()
.split(/\r?\n/g)
.filter(Boolean) // Filter out empty strings
.forEach((line) => {
console.log(line);
if (line.startsWith("Setting pipeline to PLAYING")) {
setTimeout(() => {
recResolve();
}, 1000);
}
});
});
// GStreamer writes its progress logs to stderr
recProcess.stderr.on("data", (chunk) => {
chunk
.toString()
.split(/\r?\n/g)
.filter(Boolean) // Filter out empty strings
.forEach((line) => {
console.log(line);
});
});
return promise;
}
function stopMediasoupRtp() {
console.log("Stop mediasoup RTP transport and consumer");
// const useAudio = audioEnabled();
// const useVideo = videoEnabled();
// if (useAudio) {
// global.mediasoup.rtp.audioConsumer.close();
// global.mediasoup.rtp.audioTransport.close();
// }
// if (useVideo) {
// global.mediasoup.rtp.videoConsumer.close();
// global.mediasoup.rtp.videoTransport.close();
// }
}
const io = new Server(httpsServer)
// 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 () => {
try {
worker = await mediasoup.createWorker({
rtcMinPort: 2000,
rtcMaxPort: 2020,
rtcMinPort: 32256,
rtcMaxPort: 65535,
})
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
console.error('mediasoup worker has died', error);
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;
} catch (error) {
console.log(`ERROR | createWorker | ${error.message}`);
}
return worker
}
// We create a Worker as soon as our application starts
@ -92,243 +335,222 @@ worker = createWorker()
// https://github.com/versatica/mediasoup/blob/v3/src/supportedRtpCapabilities.ts
const mediaCodecs = [
{
kind: 'audio',
mimeType: 'audio/opus',
kind: "audio",
mimeType: "audio/opus",
preferredPayloadType: 111,
clockRate: 48000,
channels: 2,
parameters: {
minptime: 10,
useinbandfec: 1,
},
},
{
kind: 'video',
mimeType: 'video/VP8',
kind: "video",
mimeType: "video/VP8",
preferredPayloadType: 96,
clockRate: 90000,
},
{
kind: "video",
mimeType: "video/H264",
preferredPayloadType: 125,
clockRate: 90000,
parameters: {
'x-google-start-bitrate': 1000,
"level-asymmetry-allowed": 1,
"packetization-mode": 1,
"profile-level-id": "42e01f",
},
},
]
const closeCall = (callId) => {
try {
if (videoCalls[callId]) {
videoCalls[callId].producer?.close();
videoCalls[callId].consumer?.close();
videoCalls[callId]?.consumerTransport.close();
videoCalls[callId]?.producerTransport.close();
videoCalls[callId].router.close();
delete videoCalls[callId];
}
} catch (error) {
console.log(`ERROR | closeCall | callid ${callId} | ${error.message}`);
}
}
const getRtpCapabilities = (callId, callback) => {
try {
console.log('[getRtpCapabilities] callId', callId);
const rtpCapabilities = videoCalls[callId].router.rtpCapabilities;
callback({ rtpCapabilities });
} catch (error) {
console.log(`ERROR | getRtpCapabilities | callId ${callId} | ${error.message}`);
}
}
/*
- Handlers for WS events
- These are created only when we have a connection with a peer
*/
peers.on('connection', async socket => {
console.log('[connection] socketId:', socket.id);
// After making the connection successfully, we send the client a 'connection-success' event
console.log('[connection] socketId:', socket.id)
socket.emit('connection-success', {
socketId: socket.id
});
socketId: socket.id,
existsProducer: producer ? true : false,
})
// It is triggered when the peer is disconnected
socket.on('disconnect', () => {
console.log('peer disconnected | socket.id', socket.id);
delete socketDetails[socket.id];
});
// do some cleanup
console.log('peer disconnected')
})
/*
- This event creates a room with the roomId and the callId sent
- 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) => {
try {
if (callId) {
console.log(`[createRoom] socket.id ${socket.id} callId ${callId}`);
if (!videoCalls[callId]) {
console.log('[createRoom] callId', callId);
videoCalls[callId] = { router: await worker.createRouter({ mediaCodecs }) }
console.log(`[createRoom] Router ID: ${videoCalls[callId].router.id}`);
console.log('Router length:', Object.keys(router).length);
if (router[callId] === undefined) {
// worker.createRouter(options)
// options = { mediaCodecs, appData }
// mediaCodecs -> defined above
// appData -> custom application data - we are not supplying any
// none of the two are required
router[callId] = await worker.createRouter({ mediaCodecs })
console.log(`[createRoom] Router ID: ${router[callId].id}`)
}
socketDetails[socket.id] = callId;
getRtpCapabilities(callId, callback);
} else {
console.log(`[createRoom] missing callId ${callId}`);
}
} catch (error) {
console.log(`ERROR | createRoom | callId ${callId} | ${error.message}`);
}
});
/*
- Client emits a request to create server side Transport
- Depending on the sender, producerTransport or consumerTransport 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] sender ${sender} | callId ${callId}`);
if (sender) {
videoCalls[callId].producerTransport = await createWebRtcTransportLayer(callId, callback);
} else {
videoCalls[callId].consumerTransport = await createWebRtcTransportLayer(callId, callback);
}
} catch (error) {
console.log(`ERROR | createWebRtcTransport | callId ${callId} | sender ${sender} | ${error.message}`);
}
});
getRtpCapabilities(callId, callback)
})
/*
- The client sends this event after successfully creating a createSendTransport(AS PRODUCER)
- The connection is made to the created transport
*/
const getRtpCapabilities = (callId, callback) => {
const rtpCapabilities = router[callId].rtpCapabilities
callback({ rtpCapabilities })
}
// Client emits a request to create server side Transport
// We need to differentiate between the producer and consumer transports
socket.on('createWebRtcTransport', async ({ sender, callId }, callback) => {
console.log(`[createWebRtcTransport] Is this a sender request? ${sender} | callId ${callId}`)
// The client indicates if it is a producer or a consumer
// if sender is true, indicates a producer else a consumer
if (sender)
producerTransport = await createWebRtcTransportLayer(callId, callback)
else
consumerTransport = await createWebRtcTransportLayer(callId, callback)
})
// see client's socket.emit('transport-connect', ...)
socket.on('transport-connect', async ({ dtlsParameters }) => {
try {
const callId = socketDetails[socket.id];
console.log(`[transport-connect] socket.id ${socket.id} | callId ${callId}`)
await videoCalls[callId].producerTransport.connect({ dtlsParameters });
} catch (error) {
console.log(`ERROR | transport-connect | callId ${socketDetails[socket.id]} | ${error.message}`);
}
});
console.log('[transport-connect] DTLS PARAMS... ', { dtlsParameters })
await producerTransport.connect({ dtlsParameters })
})
/*
- The event sent by the client (PRODUCER) after successfully connecting to producerTransport
- For the router with the id callId, we make produce on producerTransport
- Create the handler on producer at the 'transportclose' event
*/
socket.on('transport-produce', async ({ kind, rtpParameters, appData }) => {
try {
const callId = socketDetails[socket.id];
console.log('[transport-produce] | socket.id', socket.id, '| callId', callId);
videoCalls[callId].producer = await videoCalls[callId].producerTransport.produce({
// see client's socket.emit('transport-produce', ...)
socket.on('transport-produce', async ({ kind, rtpParameters, callId }, callback) => {
// call produce based on the prameters from the client
producer = await producerTransport.produce({
kind,
rtpParameters,
});
console.log(`[transport-produce] Producer ID: ${videoCalls[callId].producer.id} | kind: ${videoCalls[callId].producer.kind}`);
})
videoCalls[callId].producer.on('transportclose', () => {
const callId = socketDetails[socket.id];
console.log(`[transport-produce] Producer ID: ${producer.id} | kind: ${producer.kind}`)
producer.on('transportclose', () => {
console.log('transport for this producer closed', callId)
closeCall(callId);
// https://mediasoup.org/documentation/v3/mediasoup/api/#producer-close
producer.close()
// https://mediasoup.org/documentation/v3/mediasoup/api/#router-close
router[callId].close()
delete router[callId]
})
// Send back to the client the Producer's id
callback({
id: producer.id
})
console.log('🔴', callId);
const rtpTransport = await router[callId].createPlainTransport({
comedia: false,
rtcpMux: false,
listenIp: { ip: "127.0.0.1", announcedIp: null }
});
} catch (error) {
console.log(`ERROR | transport-produce | callId ${socketDetails[socket.id]} | ${error.message}`);
}
await rtpTransport.connect({
ip: "127.0.0.1",
port: 5006,
rtcpPort: 5007,
});
/*
- The client sends this event after successfully creating a createRecvTransport(AS CONSUMER)
- The connection is made to the created consumerTransport
*/
console.log(
"mediasoup VIDEO RTP SEND transport connected: %s:%d <--> %s:%d (%s)",
rtpTransport.tuple.localIp,
rtpTransport.tuple.localPort,
rtpTransport.tuple.remoteIp,
rtpTransport.tuple.remotePort,
rtpTransport.tuple.protocol
);
console.log(
"mediasoup VIDEO RTCP SEND transport connected: %s:%d <--> %s:%d (%s)",
rtpTransport.rtcpTuple.localIp,
rtpTransport.rtcpTuple.localPort,
rtpTransport.rtcpTuple.remoteIp,
rtpTransport.rtcpTuple.remotePort,
rtpTransport.rtcpTuple.protocol
);
const rtpConsumer = await rtpTransport.consume({
// producerId: global.mediasoup.webrtc.videoProducer.id,
producerId: producer.id,
// rtpCapabilities: router.rtpCapabilities,
rtpCapabilities: router[callId].rtpCapabilities,
paused: true,
});
// console.log('🟡 producerId:', producer.id, 'rtpCapabilities:', router[callId].rtpCapabilities, 'paused:', true);
await startRecordingFfmpeg();
// await startRecordingGstreamer();
rtpConsumer.resume();
})
// see client's socket.emit('transport-recv-connect', ...)
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 {
const callId = socketDetails[socket.id];
console.log(`[transport-recv-connect] socket.id ${socket.id} | callId ${callId}`);
await videoCalls[callId].consumerTransport.connect({ dtlsParameters });
console.log('consume', rtpCapabilities, callId);
// check if the router can consume the specified producer
if (router[callId].canConsume({
producerId: producer.id,
rtpCapabilities
})) {
// transport can now consume and return a consumer
consumer = await consumerTransport.consume({
producerId: producer.id,
rtpCapabilities,
paused: true,
})
consumer.on('transportclose', () => {
console.log('transport close from consumer', callId)
// closeRoom(callId)
delete router[callId]
})
consumer.on('producerclose', () => {
console.log('producer of consumer closed', callId)
// https://mediasoup.org/documentation/v3/mediasoup/api/#router-close
router[callId].close()
delete router[callId]
})
// 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,
}
// send the parameters to the client
callback({ params })
}
} catch (error) {
console.log(`ERROR | transport-recv-connect | callId ${socketDetails[socket.id]} | ERROR`);
console.log(error.message)
callback({
params: {
error: error
}
})
}
})
/*
- The customer consumes after successfully connecting to consumerTransport
- 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] callId', callId);
// Check if the router can consume the specified producer
if (videoCalls[callId].router.canConsume({
producerId: videoCalls[callId].producer.id,
rtpCapabilities
})) {
console.log('[consume] Can consume', callId);
// Transport can now consume and return a consumer
videoCalls[callId].consumer = await videoCalls[callId].consumerTransport.consume({
producerId: videoCalls[callId].producer.id,
rtpCapabilities,
paused: true,
});
// https://mediasoup.org/documentation/v3/mediasoup/api/#consumer-on-transportclose
videoCalls[callId].consumer.on('transportclose', () => {
const callId = socketDetails[socket.id];
console.log('transport close from consumer', callId);
closeCall();
});
// https://mediasoup.org/documentation/v3/mediasoup/api/#consumer-on-producerclose
videoCalls[callId].consumer.on('producerclose', () => {
const callId = socketDetails[socket.id];
console.log('producer of consumer closed', callId);
closeCall();
});
// From the consumer extract the following params to send back to the Client
const params = {
id: videoCalls[callId].consumer.id,
producerId: videoCalls[callId].producer.id,
kind: videoCalls[callId].consumer.kind,
rtpParameters: videoCalls[callId].consumer.rtpParameters,
};
// Send the parameters to the client
callback({ params });
} else {
console.log(`[canConsume] Can't consume | callId ${callId}`);
}
} catch (error) {
console.log(`ERROR | consume | callId ${callId} | ${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}`)
await videoCalls[callId].consumer.resume();
} catch (error) {
console.log(`ERROR | consumer-resume | callId ${socketDetails[socket.id]} | ${error.message}`);
}
});
});
console.log(`[consumer-resume]`)
await consumer.resume()
})
})
/*
- 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) => {
try {
console.log('[createWebRtcTransportLayer] callId', callId);
@ -343,40 +565,49 @@ const createWebRtcTransportLayer = async (callId, callback) => {
enableUdp: true,
enableTcp: true,
preferUdp: true,
};
initialAvailableOutgoingBitrate: 300000
}
// console.log('webRtcTransport_options', webRtcTransport_options);
// console.log('router', router, '| router[callId]', router[callId]);
// https://mediasoup.org/documentation/v3/mediasoup/api/#router-createWebRtcTransport
let transport = await videoCalls[callId].router.createWebRtcTransport(webRtcTransport_options)
let transport = await router[callId].createWebRtcTransport(webRtcTransport_options)
console.log(`callId: ${callId} | transport id: ${transport.id}`)
// Handler for when DTLS(Datagram Transport Layer Security) changes
transport.on('dtlsstatechange', dtlsState => {
console.log(`transport | dtlsstatechange | calldId ${callId} | dtlsState ${dtlsState}`);
if (dtlsState === 'closed') {
transport.close();
transport.close()
}
});
})
// Handler if the transport layer has closed (for various reasons)
transport.on('close', () => {
console.log(`transport | closed | calldId ${callId}`);
});
console.log('transport closed')
})
const params = {
id: transport.id,
iceParameters: transport.iceParameters,
iceCandidates: transport.iceCandidates,
dtlsParameters: transport.dtlsParameters,
};
}
// Send back to the client the params
callback({ params });
console.log('params', params);
// Set transport to producerTransport or consumerTransport
return transport;
// send back to the client the following prameters
callback({
// https://mediasoup.org/documentation/v3/mediasoup-client/api/#TransportOptions
params
})
return transport
} catch (error) {
console.log(`ERROR | createWebRtcTransportLayer | callId ${callId} | ${error.message}`);
callback({ params: { error } });
console.log('[createWebRtcTransportLayer] ERROR', JSON.stringify(error));
callback({
params: {
error: error
}
})
}
}

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",

View File

@ -20808,7 +20808,7 @@ const getLocalStream = () => {
})
.then(streamSuccess)
.catch(error => {
console.log(error.message)
console.log('getLocalStream', error)
})
}
@ -20903,7 +20903,7 @@ const createSendTransport = () => {
})
producerTransport.on('produce', async (parameters, callback, errback) => {
console.log(parameters)
console.log('produce', parameters)
try {
// tell the server to create a Producer
@ -20913,7 +20913,7 @@ const createSendTransport = () => {
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.
@ -21009,7 +21009,6 @@ const createRecvTransport = async () => {
}
const resetCallSettings = () => {
socket.emit('transportclose', { callId })
localVideo.srcObject = null
remoteVideo.srcObject = null
consumer = null
@ -21072,7 +21071,7 @@ const closeCall = () => {
const closeCallBtn = document.getElementById('btnCloseCall')
closeCallBtn.setAttribute('disabled', '')
// Reset settings and send closeTransport to video server
// Reset settings
resetCallSettings()
}

View File

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

View File

@ -149,7 +149,7 @@ const getLocalStream = () => {
})
.then(streamSuccess)
.catch(error => {
console.log(error.message)
console.log('getLocalStream', error)
})
}
@ -244,7 +244,7 @@ const createSendTransport = () => {
})
producerTransport.on('produce', async (parameters, callback, errback) => {
console.log(parameters)
console.log('produce', parameters)
try {
// tell the server to create a Producer
@ -254,7 +254,7 @@ const createSendTransport = () => {
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.