diff --git a/app.js b/app.js index 1f3a1a0..bda877e 100644 --- a/app.js +++ b/app.js @@ -14,8 +14,11 @@ 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 let router = {} @@ -42,6 +45,257 @@ httpsServer.listen(process.env.PORT, () => { console.log('Listening on port:', process.env.PORT) }) +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?) @@ -58,8 +312,8 @@ const peers = io.of('/mediasoup') const createWorker = async () => { worker = await mediasoup.createWorker({ - rtcMinPort: 2000, - rtcMaxPort: 2020, + rtcMinPort: 32256, + rtcMaxPort: 65535, }) console.log(`[createWorker] worker pid ${worker.pid}`) @@ -81,17 +335,31 @@ 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", }, }, ] @@ -149,7 +417,7 @@ peers.on('connection', async socket => { }) // see client's socket.emit('transport-produce', ...) - socket.on('transport-produce', async ({ kind, rtpParameters, appData }, callback) => { + socket.on('transport-produce', async ({ kind, rtpParameters, callId }, callback) => { // call produce based on the prameters from the client producer = await producerTransport.produce({ kind, @@ -173,6 +441,51 @@ peers.on('connection', async socket => { 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 } + }); + await rtpTransport.connect({ + ip: "127.0.0.1", + port: 5006, + rtcpPort: 5007, + }); + + 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', ...) @@ -252,6 +565,7 @@ const createWebRtcTransportLayer = async (callId, callback) => { enableUdp: true, enableTcp: true, preferUdp: true, + initialAvailableOutgoingBitrate: 300000 } // console.log('webRtcTransport_options', webRtcTransport_options); diff --git a/package-lock.json b/package-lock.json index 59ffca5..3a8f145 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,6 +12,7 @@ "@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", @@ -127,6 +128,47 @@ "node": ">=12" } }, + "node_modules/@derhuerst/http-basic": { + "version": "8.2.4", + "resolved": "https://registry.npmjs.org/@derhuerst/http-basic/-/http-basic-8.2.4.tgz", + "integrity": "sha512-F9rL9k9Xjf5blCz8HsJRO4diy111cayL2vkY2XE4r4t3n0yPXVYy3KD3nJ1qbrSn9743UWSXH4IwuCa/HWlGFw==", + "dependencies": { + "caseless": "^0.12.0", + "concat-stream": "^2.0.0", + "http-response-object": "^3.0.1", + "parse-cache-control": "^1.0.1" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@derhuerst/http-basic/node_modules/concat-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-2.0.0.tgz", + "integrity": "sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==", + "engines": [ + "node >= 6.0" + ], + "dependencies": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.0.2", + "typedarray": "^0.0.6" + } + }, + "node_modules/@derhuerst/http-basic/node_modules/readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/@jridgewell/gen-mapping": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", @@ -2208,7 +2250,6 @@ "version": "6.0.2", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", - "dev": true, "dependencies": { "debug": "4" }, @@ -2220,7 +2261,6 @@ "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, "dependencies": { "ms": "2.1.2" }, @@ -2236,8 +2276,7 @@ "node_modules/agent-base/node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, "node_modules/amp": { "version": "0.3.1", @@ -2906,6 +2945,11 @@ } ] }, + "node_modules/caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==" + }, "node_modules/chalk": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", @@ -3721,6 +3765,14 @@ "url": "https://github.com/fb55/entities?sponsor=1" } }, + "node_modules/env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "engines": { + "node": ">=6" + } + }, "node_modules/error-ex": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", @@ -3985,6 +4037,21 @@ "integrity": "sha512-GDqVQezKzRABdeqflsgMr7ktzgF9CyS+p2oe0jJqUY6izSSbhPIQJDpoU4PtGcD7VPM9xh/dVrTu6z1nwgmEGw==", "dev": true }, + "node_modules/ffmpeg-static": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/ffmpeg-static/-/ffmpeg-static-5.0.2.tgz", + "integrity": "sha512-rYeA04AGbvxbUxov6Cn/KDKIzw2rmzwPlgHoqn837NZt0xPdOVA9mJMILz9IX27R45hhSlXS6nThk85XxDivLg==", + "hasInstallScript": true, + "dependencies": { + "@derhuerst/http-basic": "^8.2.0", + "env-paths": "^2.2.0", + "https-proxy-agent": "^5.0.0", + "progress": "^2.0.3" + }, + "engines": { + "node": ">=16" + } + }, "node_modules/file-uri-to-path": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-2.0.0.tgz", @@ -4603,6 +4670,19 @@ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "dev": true }, + "node_modules/http-response-object": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/http-response-object/-/http-response-object-3.0.2.tgz", + "integrity": "sha512-bqX0XTF6fnXSQcEJ2Iuyr75yVakyjIDCqroJQ/aHfSdlM743Cwqoi2nDYMzLGWUcuTWGWy8AAvOKXTfiv6q9RA==", + "dependencies": { + "@types/node": "^10.0.3" + } + }, + "node_modules/http-response-object/node_modules/@types/node": { + "version": "10.17.60", + "resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.60.tgz", + "integrity": "sha512-F0KIgDJfy2nA3zMLmWGKxcH2ZVEtCZXHHdOQs2gSaQ27+lNeEfGxzkIw90aXswATX7AZ33tahPbzy6KAfUreVw==" + }, "node_modules/httpolyglot": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/httpolyglot/-/httpolyglot-0.1.2.tgz", @@ -4621,7 +4701,6 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", - "dev": true, "dependencies": { "agent-base": "6", "debug": "4" @@ -4634,7 +4713,6 @@ "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, "dependencies": { "ms": "2.1.2" }, @@ -4650,8 +4728,7 @@ "node_modules/https-proxy-agent/node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, "node_modules/iconv-lite": { "version": "0.4.24", @@ -6028,6 +6105,11 @@ "safe-buffer": "^5.1.1" } }, + "node_modules/parse-cache-control": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parse-cache-control/-/parse-cache-control-1.0.1.tgz", + "integrity": "sha512-60zvsJReQPX5/QP0Kzfd/VrpjScIQ7SHBW6bFCYfEP+fp0Eppr1SHhIO5nd1PjZtvclzSzES9D/p5nFJurwfWg==" + }, "node_modules/parse-json": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", @@ -6483,6 +6565,14 @@ "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", "dev": true }, + "node_modules/progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/promptly": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/promptly/-/promptly-2.2.0.tgz", @@ -7296,7 +7386,6 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "dev": true, "dependencies": { "safe-buffer": "~5.2.0" } @@ -7775,8 +7864,7 @@ "node_modules/typedarray": { "version": "0.0.6", "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", - "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==", - "dev": true + "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==" }, "node_modules/typescript": { "version": "4.7.4", @@ -7918,8 +8006,7 @@ "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", - "dev": true + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" }, "node_modules/utility-types": { "version": "3.10.0", @@ -8287,6 +8374,40 @@ "@jridgewell/trace-mapping": "0.3.9" } }, + "@derhuerst/http-basic": { + "version": "8.2.4", + "resolved": "https://registry.npmjs.org/@derhuerst/http-basic/-/http-basic-8.2.4.tgz", + "integrity": "sha512-F9rL9k9Xjf5blCz8HsJRO4diy111cayL2vkY2XE4r4t3n0yPXVYy3KD3nJ1qbrSn9743UWSXH4IwuCa/HWlGFw==", + "requires": { + "caseless": "^0.12.0", + "concat-stream": "^2.0.0", + "http-response-object": "^3.0.1", + "parse-cache-control": "^1.0.1" + }, + "dependencies": { + "concat-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-2.0.0.tgz", + "integrity": "sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==", + "requires": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.0.2", + "typedarray": "^0.0.6" + } + }, + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + } + } + }, "@jridgewell/gen-mapping": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", @@ -9642,7 +9763,6 @@ "version": "6.0.2", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", - "dev": true, "requires": { "debug": "4" }, @@ -9651,7 +9771,6 @@ "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, "requires": { "ms": "2.1.2" } @@ -9659,8 +9778,7 @@ "ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" } } }, @@ -10229,6 +10347,11 @@ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001375.tgz", "integrity": "sha512-kWIMkNzLYxSvnjy0hL8w1NOaWNr2rn39RTAVyIwcw8juu60bZDWiF1/loOYANzjtJmy6qPgNmn38ro5Pygagdw==" }, + "caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==" + }, "chalk": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", @@ -10912,6 +11035,11 @@ "resolved": "https://registry.npmjs.org/entities/-/entities-3.0.1.tgz", "integrity": "sha512-WiyBqoomrwMdFG1e0kqvASYfnlb0lp8M5o5Fw2OFq1hNZxxcNk8Ik0Xm7LxzBhuidnZB/UtBqVCgUz3kBOP51Q==" }, + "env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==" + }, "error-ex": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", @@ -11120,6 +11248,17 @@ "integrity": "sha512-GDqVQezKzRABdeqflsgMr7ktzgF9CyS+p2oe0jJqUY6izSSbhPIQJDpoU4PtGcD7VPM9xh/dVrTu6z1nwgmEGw==", "dev": true }, + "ffmpeg-static": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/ffmpeg-static/-/ffmpeg-static-5.0.2.tgz", + "integrity": "sha512-rYeA04AGbvxbUxov6Cn/KDKIzw2rmzwPlgHoqn837NZt0xPdOVA9mJMILz9IX27R45hhSlXS6nThk85XxDivLg==", + "requires": { + "@derhuerst/http-basic": "^8.2.0", + "env-paths": "^2.2.0", + "https-proxy-agent": "^5.0.0", + "progress": "^2.0.3" + } + }, "file-uri-to-path": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-2.0.0.tgz", @@ -11558,6 +11697,21 @@ } } }, + "http-response-object": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/http-response-object/-/http-response-object-3.0.2.tgz", + "integrity": "sha512-bqX0XTF6fnXSQcEJ2Iuyr75yVakyjIDCqroJQ/aHfSdlM743Cwqoi2nDYMzLGWUcuTWGWy8AAvOKXTfiv6q9RA==", + "requires": { + "@types/node": "^10.0.3" + }, + "dependencies": { + "@types/node": { + "version": "10.17.60", + "resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.60.tgz", + "integrity": "sha512-F0KIgDJfy2nA3zMLmWGKxcH2ZVEtCZXHHdOQs2gSaQ27+lNeEfGxzkIw90aXswATX7AZ33tahPbzy6KAfUreVw==" + } + } + }, "httpolyglot": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/httpolyglot/-/httpolyglot-0.1.2.tgz", @@ -11573,7 +11727,6 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", - "dev": true, "requires": { "agent-base": "6", "debug": "4" @@ -11583,7 +11736,6 @@ "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, "requires": { "ms": "2.1.2" } @@ -11591,8 +11743,7 @@ "ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" } } }, @@ -12659,6 +12810,11 @@ "safe-buffer": "^5.1.1" } }, + "parse-cache-control": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parse-cache-control/-/parse-cache-control-1.0.1.tgz", + "integrity": "sha512-60zvsJReQPX5/QP0Kzfd/VrpjScIQ7SHBW6bFCYfEP+fp0Eppr1SHhIO5nd1PjZtvclzSzES9D/p5nFJurwfWg==" + }, "parse-json": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", @@ -13012,6 +13168,11 @@ "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", "dev": true }, + "progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==" + }, "promptly": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/promptly/-/promptly-2.2.0.tgz", @@ -13680,7 +13841,6 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "dev": true, "requires": { "safe-buffer": "~5.2.0" } @@ -14013,8 +14173,7 @@ "typedarray": { "version": "0.0.6", "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", - "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==", - "dev": true + "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==" }, "typescript": { "version": "4.7.4", @@ -14120,8 +14279,7 @@ "util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", - "dev": true + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" }, "utility-types": { "version": "3.10.0", diff --git a/package.json b/package.json index 79fd9b8..939dd3b 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,7 @@ "@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", diff --git a/public/bundle.js b/public/bundle.js index 0b0166f..2ed4f58 100644 --- a/public/bundle.js +++ b/public/bundle.js @@ -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 @@ -21057,12 +21056,12 @@ const connectRecvTransport = async () => { 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', + 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) @@ -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() } diff --git a/public/config.js b/public/config.js index d12c148..f3fae13 100644 --- a/public/config.js +++ b/public/config.js @@ -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', } \ No newline at end of file diff --git a/public/index.js b/public/index.js index affb71b..ea68f0d 100644 --- a/public/index.js +++ b/public/index.js @@ -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.