From c52c288df0c87c1c366c152d47ee5ed4de7f3fc2 Mon Sep 17 00:00:00 2001 From: Alessandro Toppi Date: Tue, 23 Nov 2021 17:55:39 +0100 Subject: [PATCH 1/5] Add UNIX dgram sockets transport --- README.md | 28 +++- examples/browser/bundle.sh | 4 +- package-lock.json | 59 +++++++ package.json | 5 +- src/connection.js | 4 + src/transport-unix.js | 330 +++++++++++++++++++++++++++++++++++++ 6 files changed, 426 insertions(+), 4 deletions(-) create mode 100644 src/transport-unix.js diff --git a/README.md b/README.md index f5a7705..68eaeef 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ Janode is a Node.js, browser compatible, adapter for the [Janus WebRTC server](https://github.com/meetecho/janus-gateway). -Internally uses WebSockets to connect to Janus. +Internally uses WebSockets or Unix DGRAM Sockets to connect to Janus. The library wraps the Janus core API, the Janus Admin API and some of the most popular plugins APIs. @@ -75,6 +75,32 @@ const data = await admin.listSessions(); ``` +## Switching to other transports + +The kind of transport used for a connection depends on the protocol/scheme defined in the `url` field of the configuration. + +```js +/* Use UNIX DGRAM Sockets */ +const admin = await Janode.connect({ + is_admin: true, + address: { + url: 'unix://tmp/janusapi', + apisecret: 'secret' + } +}); +``` + +```js +/* Use WebSockets */ +const admin = await Janode.connect({ + is_admin: true, + address: { + url: 'ws://127.0.0.1:7188/', + apisecret: 'secret' + } +}); +``` + ## Installation ```bash diff --git a/examples/browser/bundle.sh b/examples/browser/bundle.sh index 561064b..9d13bba 100755 --- a/examples/browser/bundle.sh +++ b/examples/browser/bundle.sh @@ -16,7 +16,9 @@ cat < app.js module.exports = { Janode: require('../../src/janode.js'), EchoTestPlugin: require('../../src/plugins/echotest-plugin.js') }; EOF -browserify app.js --standalone $EXPORTED_OBJECT -o $BUILD_DIR/$BUNDLE_FILENAME --dg=false +browserify app.js --standalone $EXPORTED_OBJECT -o $BUILD_DIR/$BUNDLE_FILENAME \ + --dg=false \ + --ignore "../../src/transport-unix.js" cp ./app/*.* $DEPLOY_DIR diff --git a/package-lock.json b/package-lock.json index 9fdf8fa..6092428 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,12 +10,26 @@ "license": "ISC", "dependencies": { "isomorphic-ws": "^4.0.1", + "unix-dgram": "^2.0.4", "ws": "^8.0.0" }, "engines": { "node": ">=12" } }, + "node_modules/bindings": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", + "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", + "dependencies": { + "file-uri-to-path": "1.0.0" + } + }, + "node_modules/file-uri-to-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", + "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==" + }, "node_modules/isomorphic-ws": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/isomorphic-ws/-/isomorphic-ws-4.0.1.tgz", @@ -24,6 +38,24 @@ "ws": "*" } }, + "node_modules/nan": { + "version": "2.15.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.15.0.tgz", + "integrity": "sha512-8ZtvEnA2c5aYCZYd1cvgdnU6cqwixRoYg70xPLWUws5ORTa/lnw+u4amixRS/Ac5U5mQVgp9pnlSUnbNWFaWZQ==" + }, + "node_modules/unix-dgram": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/unix-dgram/-/unix-dgram-2.0.4.tgz", + "integrity": "sha512-7tpK6x7ls7J7pDrrAU63h93R0dVhRbPwiRRCawR10cl+2e1VOvF3bHlVJc6WI1dl/8qk5He673QU+Ogv7bPNaw==", + "hasInstallScript": true, + "dependencies": { + "bindings": "^1.3.0", + "nan": "^2.13.2" + }, + "engines": { + "node": ">=0.10.48" + } + }, "node_modules/ws": { "version": "8.2.3", "resolved": "https://registry.npmjs.org/ws/-/ws-8.2.3.tgz", @@ -46,12 +78,39 @@ } }, "dependencies": { + "bindings": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", + "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", + "requires": { + "file-uri-to-path": "1.0.0" + } + }, + "file-uri-to-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", + "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==" + }, "isomorphic-ws": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/isomorphic-ws/-/isomorphic-ws-4.0.1.tgz", "integrity": "sha512-BhBvN2MBpWTaSHdWRb/bwdZJ1WaehQ2L1KngkCkfLUGF0mAWAT1sQUQacEmQ0jXkFw/czDXPNQSL5u2/Krsz1w==", "requires": {} }, + "nan": { + "version": "2.15.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.15.0.tgz", + "integrity": "sha512-8ZtvEnA2c5aYCZYd1cvgdnU6cqwixRoYg70xPLWUws5ORTa/lnw+u4amixRS/Ac5U5mQVgp9pnlSUnbNWFaWZQ==" + }, + "unix-dgram": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/unix-dgram/-/unix-dgram-2.0.4.tgz", + "integrity": "sha512-7tpK6x7ls7J7pDrrAU63h93R0dVhRbPwiRRCawR10cl+2e1VOvF3bHlVJc6WI1dl/8qk5He673QU+Ogv7bPNaw==", + "requires": { + "bindings": "^1.3.0", + "nan": "^2.13.2" + } + }, "ws": { "version": "8.2.3", "resolved": "https://registry.npmjs.org/ws/-/ws-8.2.3.tgz", diff --git a/package.json b/package.json index 78728a9..ad38292 100644 --- a/package.json +++ b/package.json @@ -28,8 +28,9 @@ "src/plugins/*.js" ], "dependencies": { - "ws": "^8.0.0", - "isomorphic-ws": "^4.0.1" + "isomorphic-ws": "^4.0.1", + "unix-dgram": "^2.0.4", + "ws": "^8.0.0" }, "engines": { "node": ">=12" diff --git a/src/connection.js b/src/connection.js index ed20346..3cc38f5 100644 --- a/src/connection.js +++ b/src/connection.js @@ -13,6 +13,7 @@ const LOG_NS = '[connection.js]'; const { getNumericID, checkUrl, newIterator } = require('./utils/utils.js'); const { JANODE, JANUS, isResponseData, isErrorData } = require('./protocol.js'); const WsTransport = require('./transport-ws.js'); +const UnixTransport = require('./transport-unix.js'); const JanodeSession = require('./session.js'); const TransactionManager = require('./tmanager.js'); @@ -107,6 +108,9 @@ class Connection extends EventEmitter { if (checkUrl(server_config.getAddress()[0].url, ['ws', 'wss', 'ws+unix', 'wss+unix'])) { transport = new WsTransport(this); } + if (checkUrl(server_config.getAddress()[0].url, ['file'])) { + transport = new UnixTransport(this); + } if (transport) this._transport = transport; } catch (error) { Logger.error(`${LOG_NS} ${this.name} error while initializing transport (${error.message})`); diff --git a/src/transport-unix.js b/src/transport-unix.js new file mode 100644 index 0000000..586900f --- /dev/null +++ b/src/transport-unix.js @@ -0,0 +1,330 @@ +'use strict'; + +/** + * This module contains the Unix Sockets transport implementation. + * @module transport-unix + * @private + */ + +const { Buffer } = require('buffer'); +const { unlinkSync } = require('fs'); + +/* External dependency with Unix dgram sockets implementation */ +const unix = require('unix-dgram'); + +const Logger = require('./utils/logger.js'); +const LOG_NS = '[transport-unix.js]'; +const { delayOp } = require('./utils/utils.js'); + +/** + * Class representing a connection through Unix dgram sockets transport.
+ * + * In case of failure a connection will be retried according to the configuration (time interval and + * times to attempt). At every attempt, if multiple addresses are available for Janus, the next address + * will be tried. An error will be raised only if the maxmimum number of attempts have been reached.
+ * + * @private + */ +class TransportUnix { + /** + * Create a connection through Unix dgram socket. + * + * @param {module:connection~Connection} connection - The parent Janode connection + */ + constructor(connection) { + /** + * The parent Janode connection. + * + * @type {module:connection~Connection} + */ + this._connection = connection; + + /** + * The internal Unix Socket. + * + * @type {module:unix-dgram~Socket} + */ + this._socket = null; + + /** + * The local file to bind the socket to. + */ + this._local_bind = `/tmp/.janode-${connection.id}`; + + /** + * Internal counter for connection attempts. + * + * @type {number} + */ + this._attempts = 0; + + /** + * A boolean flag indicating that the connection is being opened. + * + * @type {boolean} + */ + this._opening = false; + + /** + * A boolean flag indicating that the connection has been opened. + * + * @type {boolean} + */ + this._opened = false; + + /** + * A boolean flag indicating that the connection is being closed. + * + * @type {boolean} + */ + this._closing = false; + + /** + * A boolean flag indicating that the connection has been closed. + * + * @type {boolean} + */ + this._closed = false; // true if socket has been closed after being opened + + /** + * A numerical identifier assigned for logging purposes. + * + * @type {number} + */ + this.id = connection.id; + + /** + * A more descriptive, not unique string (used for logging). + * + * @type {string} + */ + this.name = `[${this.id}]`; + } + + /** + * Initialize the internal socket. + * + * @returns {Promise} + */ + async _initUnixSocket() { + Logger.info(`${LOG_NS} ${this.name} trying connection with ${this._connection._address_iterator.currElem().url}`); + + return new Promise((resolve, reject) => { + let socket; + + let connected = false; + let bound = false; + + try { + socket = unix.createSocket('unix_dgram'); + } catch (error) { + Logger.error(`${LOG_NS} ${this.name} unix socket create error (${error.message})`); + reject(error); + return; + } + + socket.on('error', error => { + Logger.error(`${LOG_NS} ${this.name} unix socket error (${error.message})`); + if (error.errno < 0) { + this._close(); + } + reject(error); + }); + + socket.on('connect', _ => { + Logger.info(`${LOG_NS} ${this.name} unix socket connected`) + connected = true; + if (bound && connected) resolve(this); + }); + + socket.on('listening', _ => { + Logger.info(`${LOG_NS} ${this.name} unix socket bound`); + /* Resolve the promise and return this connection */ + bound = true; + socket.connect(this._connection._address_iterator.currElem().url.split('file://')[1]); + if (bound && connected) resolve(this); + }); + + socket.on('message', buf => { + const data = buf.toString(); + Logger.debug(`${LOG_NS} ${this.name} ${data}`); + this._connection._handleMessage(JSON.parse(data)); + }); + + socket.on('writable', _ => { + Logger.warn(`${LOG_NS} ${this.name} unix socket writable notification`); + }); + + socket.on('congestion', _buf => { + Logger.warn(`${LOG_NS} ${this.name} unix socket congestion notification`); + }); + + this._socket = socket; + + try { unlinkSync(this._local_bind); } catch (error) { } + socket.bind(this._local_bind); + }); + } + + /** + * Internal helper to open a unix socket connection. + * In case of error retry the connection with another address from the available pool. + * If maximum number of attempts is reached, throws an error. + * + * @returns {module:unix-dgram~Socket} The unix socket + */ + async _attemptOpen() { + /* Reset status at every attempt */ + this._opened = false; + this._closing = false; + this._closed = false; + + try { + const conn = await this._initUnixSocket(); + this._opening = false; + this._opened = true; + return conn; + } + catch (error) { + /* In case of error notifies the user, but try with another address */ + this._attempts++; + /* Get the max number of attempts from the configuration */ + if (this._attempts >= this._connection._config.getMaxRetries()) { + this._opening = false; + const err = new Error('attempt limit exceeded'); + Logger.error(`${LOG_NS} ${this.name} socket connection failed, ${err.message}`); + throw error; + } + Logger.error(`${LOG_NS} ${this.name} socket connection failed, will try again in ${this._connection._config.getRetryTimeSeconds()} seconds...`); + /* Wait an amount of seconds specified in the configuration */ + await delayOp(this._connection._config.getRetryTimeSeconds() * 1000); + /* Make shift the circular iterator */ + this._connection._address_iterator.nextElem(); + return this._attemptOpen(); + } + } + + _close() { + if (!this._socket) return; + Logger.info(`${LOG_NS} ${this.name} closing unix transport`); + try { + this._socket.close(); + } catch (error) { + Logger.error(`${LOG_NS} ${this.name} error while closing unix socket (${error.message})`); + } + + try { + unlinkSync(this._local_bind); + } catch (error) { + Logger.error(`${LOG_NS} ${this.name} error while unlinking fd (${error.message})`); + } + /* removeAllListeners is only supported on the node ws module */ + if (typeof this._socket.removeAllListeners === 'function') this._socket.removeAllListeners(); + this._socket = null; + this._connection._signalClose(this._closing); + this._closing = false; + this._closed = true; + } + + /** + * Open a transport connection. This is called from parent connection. + * + * @returns {Promise} A promise resolving with the Janode connection + */ + async open() { + /* Check the flags before attempting a connection */ + let error; + if (this._opening) error = new Error('unable to open, unix socket is already being opened'); + else if (this._opened) error = new Error('unable to open, unix socket has already been opened'); + else if (this._closed) error = new Error('unable to open, unix socket has already been closed'); + + if (error) { + Logger.error(`${LOG_NS} ${this.name} ${error.message}`); + throw error; + } + + /* Set the starting status */ + this._opening = true; + this._attempts = 0; + + /* Use internal helper */ + return this._attemptOpen(); + } + + /** + * Get the remote Janus hostname. + * It is called from the parent connection. + * + * @returns {string} The hostname of the Janus server + */ + getRemoteHostname() { + if (this._opened) { + return (this._connection._address_iterator.currElem().url.split('file://')[1]); + } + return null; + } + + /** + * Gracefully close the connection. + * It is called from the parent connection. + * + * @returns {Promise} + */ + async close() { + /* Check the status flags before */ + let error; + if (!this._opened) error = new Error('unable to close, unix socket has never been opened'); + else if (this._closing) error = new Error('unable to close, unix socket is already being closed'); + else if (this._closed) error = new Error('unable to close, unix socket has already been closed'); + + if (error) { + Logger.error(`${LOG_NS} ${this.name} ${error.message}`); + throw error; + } + + this._closing = true; + + return this._close(); + } + + /** + * Send a request from this connection. + * It is called from the parent connection. + * + * @param {object} request - The request to be sent + * @returns {Promise} A promise resolving with a response from Janus + */ + async send(request) { + /* Check connection status */ + let error; + if (!this._opened) error = new Error('unable to send request because unix socket has not been opened'); + else if (this._closed) error = new Error('unable to send request because unix socket has been closed'); + + if (error) { + Logger.error(`${LOG_NS} ${this.name} ${error.message}`); + throw error; + } + + /* Stringify the message */ + const string_req = JSON.stringify(request); + const buf = Buffer.from(string_req, 'utf-8'); + + return new Promise((resolve, reject) => { + this._socket.send(buf, error => { + if (error) { + Logger.error(`${LOG_NS} ${this.name} unix socket send error (${error.message})`); + if (error.errno < 0) { + this._close(); + } + reject(error); + return; + } + Logger.debug(`${LOG_NS} ${this.name} ${string_req}`); + resolve(); + }); + }); + } + +} + +module.exports = TransportUnix; \ No newline at end of file From 1279a371f08df608e0739a45323e09793c476603 Mon Sep 17 00:00:00 2001 From: Alessandro Toppi Date: Wed, 24 Nov 2021 12:49:11 +0100 Subject: [PATCH 2/5] Fix README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 68eaeef..f69cf58 100644 --- a/README.md +++ b/README.md @@ -84,7 +84,7 @@ The kind of transport used for a connection depends on the protocol/scheme defin const admin = await Janode.connect({ is_admin: true, address: { - url: 'unix://tmp/janusapi', + url: 'file://tmp/janusapi', apisecret: 'secret' } }); From bfae6a364ecfc75516565ee1d5da9e0d4a1b7192 Mon Sep 17 00:00:00 2001 From: Alessandro Toppi Date: Wed, 24 Nov 2021 13:17:12 +0100 Subject: [PATCH 3/5] Move unix-dgram to optional dependencies --- package-lock.json | 32 +++++++++++++++++++++----------- package.json | 4 +++- 2 files changed, 24 insertions(+), 12 deletions(-) diff --git a/package-lock.json b/package-lock.json index 6092428..f249512 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,17 +10,20 @@ "license": "ISC", "dependencies": { "isomorphic-ws": "^4.0.1", - "unix-dgram": "^2.0.4", "ws": "^8.0.0" }, "engines": { "node": ">=12" + }, + "optionalDependencies": { + "unix-dgram": "^2.0.4" } }, "node_modules/bindings": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", + "optional": true, "dependencies": { "file-uri-to-path": "1.0.0" } @@ -28,7 +31,8 @@ "node_modules/file-uri-to-path": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", - "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==" + "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", + "optional": true }, "node_modules/isomorphic-ws": { "version": "4.0.1", @@ -41,13 +45,15 @@ "node_modules/nan": { "version": "2.15.0", "resolved": "https://registry.npmjs.org/nan/-/nan-2.15.0.tgz", - "integrity": "sha512-8ZtvEnA2c5aYCZYd1cvgdnU6cqwixRoYg70xPLWUws5ORTa/lnw+u4amixRS/Ac5U5mQVgp9pnlSUnbNWFaWZQ==" + "integrity": "sha512-8ZtvEnA2c5aYCZYd1cvgdnU6cqwixRoYg70xPLWUws5ORTa/lnw+u4amixRS/Ac5U5mQVgp9pnlSUnbNWFaWZQ==", + "optional": true }, "node_modules/unix-dgram": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/unix-dgram/-/unix-dgram-2.0.4.tgz", "integrity": "sha512-7tpK6x7ls7J7pDrrAU63h93R0dVhRbPwiRRCawR10cl+2e1VOvF3bHlVJc6WI1dl/8qk5He673QU+Ogv7bPNaw==", "hasInstallScript": true, + "optional": true, "dependencies": { "bindings": "^1.3.0", "nan": "^2.13.2" @@ -57,9 +63,9 @@ } }, "node_modules/ws": { - "version": "8.2.3", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.2.3.tgz", - "integrity": "sha512-wBuoj1BDpC6ZQ1B7DWQBYVLphPWkm8i9Y0/3YdHjHKHiohOJ1ws+3OccDWtH+PoC9DZD5WOTrJvNbWvjS6JWaA==", + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.3.0.tgz", + "integrity": "sha512-Gs5EZtpqZzLvmIM59w4igITU57lrtYVFneaa434VROv4thzJyV6UjIL3D42lslWlI+D4KzLYnxSwtfuiO79sNw==", "engines": { "node": ">=10.0.0" }, @@ -82,6 +88,7 @@ "version": "1.5.0", "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", + "optional": true, "requires": { "file-uri-to-path": "1.0.0" } @@ -89,7 +96,8 @@ "file-uri-to-path": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", - "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==" + "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", + "optional": true }, "isomorphic-ws": { "version": "4.0.1", @@ -100,21 +108,23 @@ "nan": { "version": "2.15.0", "resolved": "https://registry.npmjs.org/nan/-/nan-2.15.0.tgz", - "integrity": "sha512-8ZtvEnA2c5aYCZYd1cvgdnU6cqwixRoYg70xPLWUws5ORTa/lnw+u4amixRS/Ac5U5mQVgp9pnlSUnbNWFaWZQ==" + "integrity": "sha512-8ZtvEnA2c5aYCZYd1cvgdnU6cqwixRoYg70xPLWUws5ORTa/lnw+u4amixRS/Ac5U5mQVgp9pnlSUnbNWFaWZQ==", + "optional": true }, "unix-dgram": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/unix-dgram/-/unix-dgram-2.0.4.tgz", "integrity": "sha512-7tpK6x7ls7J7pDrrAU63h93R0dVhRbPwiRRCawR10cl+2e1VOvF3bHlVJc6WI1dl/8qk5He673QU+Ogv7bPNaw==", + "optional": true, "requires": { "bindings": "^1.3.0", "nan": "^2.13.2" } }, "ws": { - "version": "8.2.3", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.2.3.tgz", - "integrity": "sha512-wBuoj1BDpC6ZQ1B7DWQBYVLphPWkm8i9Y0/3YdHjHKHiohOJ1ws+3OccDWtH+PoC9DZD5WOTrJvNbWvjS6JWaA==", + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.3.0.tgz", + "integrity": "sha512-Gs5EZtpqZzLvmIM59w4igITU57lrtYVFneaa434VROv4thzJyV6UjIL3D42lslWlI+D4KzLYnxSwtfuiO79sNw==", "requires": {} } } diff --git a/package.json b/package.json index ad38292..1a52d7a 100644 --- a/package.json +++ b/package.json @@ -29,9 +29,11 @@ ], "dependencies": { "isomorphic-ws": "^4.0.1", - "unix-dgram": "^2.0.4", "ws": "^8.0.0" }, + "optionalDependencies": { + "unix-dgram": "^2.0.4" + }, "engines": { "node": ">=12" }, From f23c94fde8049c67c5d1ed10d5621e0b91ced2c2 Mon Sep 17 00:00:00 2001 From: Alessandro Toppi Date: Mon, 20 Dec 2021 12:50:32 +0100 Subject: [PATCH 4/5] Add talking events to VideoRoom plugin --- examples/videoroom/html/videoroom-client.js | 9 ++++- examples/videoroom/src/index.js | 5 +++ src/plugins/videoroom-plugin.js | 41 +++++++++++++++++++-- 3 files changed, 50 insertions(+), 5 deletions(-) diff --git a/examples/videoroom/html/videoroom-client.js b/examples/videoroom/html/videoroom-client.js index a9c7b1c..bc47487 100644 --- a/examples/videoroom/html/videoroom-client.js +++ b/examples/videoroom/html/videoroom-client.js @@ -196,7 +196,7 @@ function _listRooms() { }); } -function _create({ room, description, max_publishers = 6, audiocodec = 'opus', videocodec = 'vp8', permanent = false }) { +function _create({ room, description, max_publishers = 6, audiocodec = 'opus', videocodec = 'vp8', talking_events = false, talking_level_threshold = 25, talking_packets_threshold = 100, permanent = false }) { socket.emit('create', { data: { room, @@ -204,6 +204,9 @@ function _create({ room, description, max_publishers = 6, audiocodec = 'opus', v max_publishers, audiocodec, videocodec, + talking_events, + talking_level_threshold, + talking_packets_threshold, permanent, }, _id: getId(), @@ -324,6 +327,10 @@ socket.on('participants-list', ({ data }) => { console.log('participants list', data); }); +socket.on('talking', ({ data }) => { + console.log('talking notify', data); +}); + socket.on('kicked', ({ data }) => { console.log('participant kicked', data); if (data.feed) { diff --git a/examples/videoroom/src/index.js b/examples/videoroom/src/index.js index 233e060..83866f1 100644 --- a/examples/videoroom/src/index.js +++ b/examples/videoroom/src/index.js @@ -72,6 +72,7 @@ async function initBackEnd() { session.once(Janode.EVENT.SESSION_DESTROYED, () => { Logger.info(`${LOG_NS} session ${session.id} destroyed`); + janodeSession = null; }); const handle = await session.attach(VideoRoomPlugin); @@ -180,6 +181,10 @@ function initFrontEnd() { replyEvent(socket, 'display', evtdata); }); + pubHandle.on(VideoRoomPlugin.EVENT.VIDEOROOM_TALKING, evtdata => { + replyEvent(socket, 'talking', evtdata); + }); + pubHandle.on(VideoRoomPlugin.EVENT.VIDEOROOM_KICKED, evtdata => { const handle = clientHandles.getHandleByFeed(evtdata.feed); clientHandles.removeHandleByFeed(evtdata.feed); diff --git a/src/plugins/videoroom-plugin.js b/src/plugins/videoroom-plugin.js index 2c6ae5c..436ac06 100644 --- a/src/plugins/videoroom-plugin.js +++ b/src/plugins/videoroom-plugin.js @@ -51,6 +51,7 @@ const PLUGIN_EVENT = { UNPUBLISHED: 'videoroom_unpublished', LEAVING: 'videoroom_leaving', KICKED: 'videoroom_kicked', + TALKING: 'videoroom_talking', ALLOWED: 'videoroom_allowed', EXISTS: 'videoroom_exists', ROOMS_LIST: 'videoroom_list', @@ -167,11 +168,12 @@ class VideoRoomHandle extends Handle { janode_event.data.feed = message_data.id; janode_event.data.description = message_data.description; - janode_event.data.publishers = message_data.publishers.map(({ id, display }) => { + janode_event.data.publishers = message_data.publishers.map(({ id, display, talking }) => { const pub = { feed: id, display, }; + if (typeof talking !== 'undefined') pub.talking = talking; return pub; }); janode_event.event = PLUGIN_EVENT.PUB_JOINED; @@ -197,12 +199,13 @@ class VideoRoomHandle extends Handle { /* Participants list */ case 'participants': - janode_event.data.participants = message_data.participants.map(({ id, display, publisher }) => { + janode_event.data.participants = message_data.participants.map(({ id, display, publisher, talking }) => { const peer = { feed: id, display, publisher, }; + if (typeof talking !== 'undefined') peer.talking = talking; return peer; }); janode_event.event = PLUGIN_EVENT.PARTICIPANTS_LIST; @@ -284,6 +287,15 @@ class VideoRoomHandle extends Handle { janode_event.event = PLUGIN_EVENT.RTP_FWD_LIST; break; + /* Talking events */ + case 'talking': + case 'stopped-talking': + janode_event.data.feed = message_data.id; + janode_event.data.talking = (videoroom === 'talking'); + janode_event.data.audio_level = message_data['audio-level-dBov-avg']; + janode_event.event = PLUGIN_EVENT.TALKING; + break; + /* Generic events (error, notifications ...) */ case 'event': /* VideoRoom Error */ @@ -305,11 +317,12 @@ class VideoRoomHandle extends Handle { /* Publisher list notification */ if (message_data.publishers) { janode_event.event = PLUGIN_EVENT.PUB_LIST; - janode_event.data.publishers = message_data.publishers.map(({ id, display }) => { + janode_event.data.publishers = message_data.publishers.map(({ id, display, talking }) => { const pub = { feed: id, display, }; + if (typeof talking !== 'undefined') pub.talking = talking; return pub; }); break; @@ -855,6 +868,9 @@ class VideoRoomHandle extends Handle { * @param {number} [params.fir_freq] - The PLI interval in seconds * @param {string} [params.audiocodec] - Comma separated list of allowed audio codecs * @param {string} [params.videocodec] - Comma separated list of allowed video codecs + * @param {boolean} [params.talking_events] - True to enable talking events + * @param {number} [params.talking_level_threshold] - Audio level threshold for talking events in the range [0, 127] + * @param {number} [params.talking_packets_threshold] - Audio packets threshold for talking events * @param {boolean} [params.record] - Wheter to enable recording of any publisher * @param {string} [params.rec_dir] - Folder where recordings should be stored * @param {boolean} [params.videoorient] - Whether the video-orientation RTP extension must be negotiated @@ -862,7 +878,7 @@ class VideoRoomHandle extends Handle { * @returns {Promise} */ async create({ room = 0, description, max_publishers, permanent, is_private, secret, pin, bitrate, - bitrate_cap, fir_freq, audiocodec, videocodec, record, rec_dir, videoorient, h264_profile }) { + bitrate_cap, fir_freq, audiocodec, videocodec, talking_events, talking_level_threshold, talking_packets_threshold, record, rec_dir, videoorient, h264_profile }) { const body = { request: REQUEST_CREATE, room, @@ -878,6 +894,9 @@ class VideoRoomHandle extends Handle { if (typeof fir_freq === 'number') body.fir_freq = fir_freq; if (typeof audiocodec === 'string') body.audiocodec = audiocodec; if (typeof videocodec === 'string') body.videocodec = videocodec; + if (typeof talking_events === 'boolean') body.audiolevel_event = talking_events; + if (typeof talking_level_threshold === 'number' && talking_level_threshold >= 0 && talking_level_threshold <= 127) body.audio_level_average = talking_level_threshold; + if (typeof talking_packets_threshold === 'number' && talking_packets_threshold > 0) body.audio_active_packets = talking_packets_threshold; if (typeof record === 'boolean') body.record = record; if (typeof rec_dir === 'string') body.rec_dir = rec_dir; if (typeof videoorient === 'boolean') body.videoorient_ext = videoorient; @@ -1079,6 +1098,7 @@ class VideoRoomHandle extends Handle { * @property {number|string} participants[].feed - Feed identifier of the participant * @property {string} [participants[].display] - The participant display name, if available * @property {boolean} participants[].publisher - Whether the user is an active publisher in the room + * @property {boolean} [participants[].talking] - True if participant is talking */ /** @@ -1230,6 +1250,7 @@ class VideoRoomHandle extends Handle { * @property {string} EVENT.VIDEOROOM_LEAVING {@link module:videoroom-plugin~VIDEOROOM_LEAVING} * @property {string} EVENT.VIDEOROOM_DISPLAY {@link module:videoroom-plugin~VIDEOROOM_DISPLAY} * @property {string} EVENT.VIDEOROOM_KICKED {@link module:videoroom-plugin~VIDEOROOM_KICKED} + * @property {string} EVENT.VIDEOROOM_TALKING {@link module:videoroom-plugin~VIDEOROOM_TALKING} * @property {string} EVENT.VIDEOROOM_ERROR {@link module:videoroom-plugin~VIDEOROOM_ERROR} */ module.exports = { @@ -1306,6 +1327,18 @@ module.exports = { */ VIDEOROOM_SLOWLINK: PLUGIN_EVENT.SLOW_LINK, + /** + * Notify if the current user is talking. + * + * @event module:videoroom-plugin~VideoRoomHandle#event:VIDEOROOM_TALKING + * @type {object} + * @property {number|string} room - The involved room + * @property {number|string} feed - The feed of the peer this talking notification refers to + * @property {boolean} talking - True if the participant is talking + * @property {number} audio_level - The audio level of the participant in the range [0,127] + */ + VIDEOROOM_TALKING: PLUGIN_EVENT.TALKING, + /** * A feed has been kicked out. * From 8c0d436de7bc317a7ec75a496b6ee6b4f251aacd Mon Sep 17 00:00:00 2001 From: Alessandro Toppi Date: Tue, 21 Dec 2021 16:43:52 +0100 Subject: [PATCH 5/5] Bump version to v1.5.2 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index a6f23cb..b3fe70e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "janode", - "version": "1.5.1", + "version": "1.5.2", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "janode", - "version": "1.5.1", + "version": "1.5.2", "license": "ISC", "dependencies": { "isomorphic-ws": "^4.0.1", diff --git a/package.json b/package.json index afb6928..e292626 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "janode", "description": "Meetecho adapter for the Janus WebRTC Server", - "version": "1.5.1", + "version": "1.5.2", "keywords": [ "janus", "webrtc",