From 563eff119303b572ce1f43378417ea8a80daebf6 Mon Sep 17 00:00:00 2001 From: Yorick Smilda Date: Wed, 7 Feb 2024 11:32:21 +0100 Subject: [PATCH] Implement `GstWebRTCAPI` as class instead of global instance Part-of: --- net/webrtc/gstwebrtc-api/index.html | 33 +- net/webrtc/gstwebrtc-api/package.json | 2 +- net/webrtc/gstwebrtc-api/src/config.js | 9 +- .../gstwebrtc-api/src/consumer-session.js | 32 +- net/webrtc/gstwebrtc-api/src/gstwebrtc-api.js | 606 +++++++++--------- net/webrtc/gstwebrtc-api/src/index.js | 9 +- .../gstwebrtc-api/src/producer-session.js | 46 +- .../gstwebrtc-api/src/remote-controller.js | 22 +- net/webrtc/gstwebrtc-api/src/session-state.js | 2 +- .../gstwebrtc-api/src/webrtc-session.js | 54 +- 10 files changed, 391 insertions(+), 424 deletions(-) diff --git a/net/webrtc/gstwebrtc-api/index.html b/net/webrtc/gstwebrtc-api/index.html index 8fe2a643..c7241fb0 100644 --- a/net/webrtc/gstwebrtc-api/index.html +++ b/net/webrtc/gstwebrtc-api/index.html @@ -220,13 +220,7 @@ } diff --git a/net/webrtc/gstwebrtc-api/package.json b/net/webrtc/gstwebrtc-api/package.json index 924cf8ad..f06af648 100644 --- a/net/webrtc/gstwebrtc-api/package.json +++ b/net/webrtc/gstwebrtc-api/package.json @@ -1,6 +1,6 @@ { "name": "gstwebrtc-api", - "version": "1.0.1", + "version": "2.0.0", "description": "Javascript API to integrate GStreamer WebRTC streams (webrtcsrc/webrtcsink) in a web browser", "keywords": [ "webrtc", diff --git a/net/webrtc/gstwebrtc-api/src/config.js b/net/webrtc/gstwebrtc-api/src/config.js index 4b4d77af..9b726fc1 100644 --- a/net/webrtc/gstwebrtc-api/src/config.js +++ b/net/webrtc/gstwebrtc-api/src/config.js @@ -11,17 +11,16 @@ /** * GStreamer WebRTC configuration. - *

You can override default values by defining configuration before receiving the DOMContentLoaded event.
- * Once the DOMContentLoaded event triggered, changing configuration will have no effect.

+ *

The config can be passed on creation of the GstWebRTCAPI class.

*

For example: *

  *     const signalingProtocol = window.location.protocol.startsWith("https") ? "wss" : "ws";
- *     window.gstWebRTCConfig = {
+ *     const api = new GstWebRTCAPI({
  *         meta: { name: `WebClient-${Date.now()}` },
  *         signalingServerUrl: `${signalingProtocol}://${window.location.host}/webrtc`
- *     };
+ *     });
  * 

- * @typedef {object} gstWebRTCConfig + * @typedef {object} GstWebRTCConfig * @property {object} meta=null - Client free-form information that will be exchanged with all peers through the * signaling meta property, its content depends on your application. * @property {string} signalingServerUrl=ws://127.0.0.1:8443 - The WebRTC signaling server URL. diff --git a/net/webrtc/gstwebrtc-api/src/consumer-session.js b/net/webrtc/gstwebrtc-api/src/consumer-session.js index c20e43d3..713e57c1 100644 --- a/net/webrtc/gstwebrtc-api/src/consumer-session.js +++ b/net/webrtc/gstwebrtc-api/src/consumer-session.js @@ -15,28 +15,28 @@ import RemoteController from "./remote-controller.js"; /** * Event name: "streamsChanged".
- * Triggered when the underlying media streams of a {@link gstWebRTCAPI.ConsumerSession} change. - * @event gstWebRTCAPI#StreamsChangedEvent + * Triggered when the underlying media streams of a {@link GstWebRTCAPI.ConsumerSession} change. + * @event GstWebRTCAPI#StreamsChangedEvent * @type {external:Event} - * @see gstWebRTCAPI.ConsumerSession#streams + * @see GstWebRTCAPI.ConsumerSession#streams */ /** * Event name: "remoteControllerChanged".
- * Triggered when the underlying remote controller of a {@link gstWebRTCAPI.ConsumerSession} changes. - * @event gstWebRTCAPI#RemoteControllerChangedEvent + * Triggered when the underlying remote controller of a {@link GstWebRTCAPI.ConsumerSession} changes. + * @event GstWebRTCAPI#RemoteControllerChangedEvent * @type {external:Event} - * @see gstWebRTCAPI.ConsumerSession#remoteController + * @see GstWebRTCAPI.ConsumerSession#remoteController */ /** - * @class gstWebRTCAPI.ConsumerSession + * @class GstWebRTCAPI.ConsumerSession * @hideconstructor * @classdesc Consumer session managing a peer-to-peer WebRTC channel between a remote producer and this client * instance. - *

Call {@link gstWebRTCAPI#createConsumerSession} to create a ConsumerSession instance.

- * @extends {gstWebRTCAPI.WebRTCSession} - * @fires {@link gstWebRTCAPI#event:StreamsChangedEvent} - * @fires {@link gstWebRTCAPI#event:RemoteControllerChangedEvent} + *

Call {@link GstWebRTCAPI#createConsumerSession} to create a ConsumerSession instance.

+ * @extends {GstWebRTCAPI.WebRTCSession} + * @fires {@link GstWebRTCAPI#event:StreamsChangedEvent} + * @fires {@link GstWebRTCAPI#event:RemoteControllerChangedEvent} */ export default class ConsumerSession extends WebRTCSession { constructor(peerId, comChannel) { @@ -55,7 +55,7 @@ export default class ConsumerSession extends WebRTCSession { /** * The array of remote media streams consumed locally through this WebRTC channel. - * @member {external:MediaStream[]} gstWebRTCAPI.ConsumerSession#streams + * @member {external:MediaStream[]} GstWebRTCAPI.ConsumerSession#streams * @readonly */ get streams() { @@ -65,7 +65,7 @@ export default class ConsumerSession extends WebRTCSession { /** * The remote controller associated with this WebRTC consumer session. Value may be null if consumer session * has no remote controller. - * @member {gstWebRTCAPI.RemoteController} gstWebRTCAPI.ConsumerSession#remoteController + * @member {GstWebRTCAPI.RemoteController} GstWebRTCAPI.ConsumerSession#remoteController * @readonly */ get remoteController() { @@ -77,10 +77,10 @@ export default class ConsumerSession extends WebRTCSession { * This method must be called after creating the consumer session in order to start receiving the remote streams. * It registers this consumer session to the signaling server and gets ready to receive audio/video streams. *

Even on success, streaming can fail later if any error occurs during or after connection. In order to know - * the effective streaming state, you should be listening to the [error]{@link gstWebRTCAPI#event:ErrorEvent}, - * [stateChanged]{@link gstWebRTCAPI#event:StateChangedEvent} and/or [closed]{@link gstWebRTCAPI#event:ClosedEvent} + * the effective streaming state, you should be listening to the [error]{@link GstWebRTCAPI#event:ErrorEvent}, + * [stateChanged]{@link GstWebRTCAPI#event:StateChangedEvent} and/or [closed]{@link GstWebRTCAPI#event:ClosedEvent} * events.

- * @method gstWebRTCAPI.ConsumerSession#connect + * @method GstWebRTCAPI.ConsumerSession#connect * @returns {boolean} true in case of success (may fail later during or after connection) or false in case of * immediate error (wrong session state or no connection to the signaling server). */ diff --git a/net/webrtc/gstwebrtc-api/src/gstwebrtc-api.js b/net/webrtc/gstwebrtc-api/src/gstwebrtc-api.js index 1ac1fc33..19c7abb3 100644 --- a/net/webrtc/gstwebrtc-api/src/gstwebrtc-api.js +++ b/net/webrtc/gstwebrtc-api/src/gstwebrtc-api.js @@ -13,367 +13,337 @@ import defaultConfig from "./config.js"; import ComChannel from "./com-channel.js"; import SessionState from "./session-state.js"; -const apiState = { - config: null, - channel: null, - producers: {}, - connectionListeners: [], - producersListeners: [] -}; +/** + * @class GstWebRTCAPI + * @classdesc The API entry point that manages a WebRTC. + */ +export default class GstWebRTCAPI { + /** + * @constructor GstWebRTCAPI + * @param {GstWebRTCConfig} [userConfig] - The user configuration.
Only the parameters different from the default + * ones need to be provided. + */ + constructor(userConfig) { + this._channel = null; + this._producers = {}; + this._connectionListeners = []; + this._producersListeners = []; -/** - * @interface gstWebRTCAPI.ConnectionListener - */ -/** - * Callback method called when this client connects to the WebRTC signaling server. - * The callback implementation should not throw any exception. - * @method gstWebRTCAPI.ConnectionListener#connected - * @abstract - * @param {string} clientId - The unique identifier of this WebRTC client.
This identifier is provided by the - * signaling server to uniquely identify each connected peer. - */ -/** - * Callback method called when this client disconnects from the WebRTC signaling server. - * The callback implementation should not throw any exception. - * @method gstWebRTCAPI.ConnectionListener#disconnected - * @abstract - */ + const config = Object.assign({}, defaultConfig); + if (userConfig && (typeof (userConfig) === "object")) { + Object.assign(config, userConfig); + } -/** - * Registers a connection listener that will be called each time the WebRTC API connects to or disconnects from the - * signaling server. - * @function - * @memberof gstWebRTCAPI - * @instance - * @param {gstWebRTCAPI.ConnectionListener} listener - The connection listener to register. - * @returns {boolean} true in case of success (or if the listener was already registered), or false if the listener - * doesn't implement all callback functions and cannot be registered. - */ -function registerConnectionListener(listener) { - if (!listener || (typeof (listener) !== "object") || - (typeof (listener.connected) !== "function") || - (typeof (listener.disconnected) !== "function")) { - return false; + if (typeof (config.meta) !== "object") { + config.meta = null; + } + + this._config = config; + this.connectChannel(); } - if (!apiState.connectionListeners.includes(listener)) { - apiState.connectionListeners.push(listener); - } + /** + * @interface GstWebRTCAPI.ConnectionListener + */ + /** + * Callback method called when this client connects to the WebRTC signaling server. + * The callback implementation should not throw any exception. + * @method GstWebRTCAPI.ConnectionListener#connected + * @abstract + * @param {string} clientId - The unique identifier of this WebRTC client.
This identifier is provided by the + * signaling server to uniquely identify each connected peer. + */ + /** + * Callback method called when this client disconnects from the WebRTC signaling server. + * The callback implementation should not throw any exception. + * @method GstWebRTCAPI.ConnectionListener#disconnected + * @abstract + */ - return true; -} + /** + * Registers a connection listener that will be called each time the WebRTC API connects to or disconnects from the + * signaling server. + * @method GstWebRTCAPI#registerConnectionListener + * @param {GstWebRTCAPI.ConnectionListener} listener - The connection listener to register. + * @returns {boolean} true in case of success (or if the listener was already registered), or false if the listener + * doesn't implement all callback functions and cannot be registered. + */ + registerConnectionListener(listener) { + if (!listener || (typeof (listener) !== "object") || + (typeof (listener.connected) !== "function") || + (typeof (listener.disconnected) !== "function")) { + return false; + } + + if (!this._connectionListeners.includes(listener)) { + this._connectionListeners.push(listener); + } -/** - * Unregisters a connection listener.
- * The removed listener will never be called again and can be garbage collected. - * @function - * @memberof gstWebRTCAPI - * @instance - * @param {gstWebRTCAPI.ConnectionListener} listener - The connection listener to unregister. - * @returns {boolean} true if the listener is found and unregistered, or false if the listener was not previously - * registered. - */ -function unregisterConnectionListener(listener) { - const idx = apiState.connectionListeners.indexOf(listener); - if (idx >= 0) { - apiState.connectionListeners.splice(idx, 1); return true; } - return false; -} + /** + * Unregisters a connection listener.
+ * The removed listener will never be called again and can be garbage collected. + * @method GstWebRTCAPI#unregisterConnectionListener + * @param {GstWebRTCAPI.ConnectionListener} listener - The connection listener to unregister. + * @returns {boolean} true if the listener is found and unregistered, or false if the listener was not previously + * registered. + */ + unregisterConnectionListener(listener) { + const idx = this._connectionListeners.indexOf(listener); + if (idx >= 0) { + this._connectionListeners.splice(idx, 1); + return true; + } -/** - * Unregisters all previously registered connection listeners. - * @function - * @memberof gstWebRTCAPI - * @instance - */ -function unregisterAllConnectionListeners() { - apiState.connectionListeners = []; -} - -/** - * Creates a new producer session. - *

You can only create a producer session at once.
- * To request streaming from a new stream you will first need to close the previous producer session.

- *

You can only request a producer session while you are connected to the signaling server. You can use the - * {@link gstWebRTCAPI.ConnectionListener} interface and {@link gstWebRTCAPI#registerConnectionListener} function to - * listen to the connection state.

- * @function - * @memberof gstWebRTCAPI - * @instance - * @param {external:MediaStream} stream - The audio/video stream to offer as a producer through WebRTC. - * @returns {gstWebRTCAPI.ProducerSession} The created producer session or null in case of error. To start streaming, - * you still need to call {@link gstWebRTCAPI.ProducerSession#start} after adding on the returned session all the event - * listeners you may need. - */ -function createProducerSession(stream) { - if (apiState.channel) { - return apiState.channel.createProducerSession(stream); - } else { - return null; - } -} - -/** - * Information about a remote producer registered by the signaling server. - * @typedef {object} gstWebRTCAPI.Producer - * @readonly - * @property {string} id - The remote producer unique identifier set by the signaling server (always non-empty). - * @property {object} meta - Free-form object containing extra information about the remote producer (always non-null, - * but may be empty). Its content depends on your application. - */ - -/** - * Gets the list of all remote WebRTC producers available on the signaling server. - *

The remote producers list is only populated once you've connected to the signaling server. You can use the - * {@link gstWebRTCAPI.ConnectionListener} interface and {@link gstWebRTCAPI#registerConnectionListener} function to - * listen to the connection state.

- * @function - * @memberof gstWebRTCAPI - * @instance - * @returns {gstWebRTCAPI.Producer[]} The list of remote WebRTC producers available. - */ -function getAvailableProducers() { - return Object.values(apiState.producers); -} - -/** - * @interface gstWebRTCAPI.ProducersListener - */ -/** - * Callback method called when a remote producer is added on the signaling server. - * The callback implementation should not throw any exception. - * @method gstWebRTCAPI.ProducersListener#producerAdded - * @abstract - * @param {gstWebRTCAPI.Producer} producer - The remote producer added on server-side. - */ -/** - * Callback method called when a remote producer is removed from the signaling server. - * The callback implementation should not throw any exception. - * @method gstWebRTCAPI.ProducersListener#producerRemoved - * @abstract - * @param {gstWebRTCAPI.Producer} producer - The remote producer removed on server-side. - */ - -/** - * Registers a producers listener that will be called each time a producer is added or removed on the signaling - * server. - * @function - * @memberof gstWebRTCAPI - * @instance - * @param {gstWebRTCAPI.ProducersListener} listener - The producer listener to register. - * @returns {boolean} true in case of success (or if the listener was already registered), or false if the listener - * doesn't implement all callback functions and cannot be registered. - */ -function registerProducersListener(listener) { - if (!listener || (typeof (listener) !== "object") || - (typeof (listener.producerAdded) !== "function") || - (typeof (listener.producerRemoved) !== "function")) { return false; } - if (!apiState.producersListeners.includes(listener)) { - apiState.producersListeners.push(listener); + /** + * Unregisters all previously registered connection listeners. + * @method GstWebRTCAPI#unregisterAllConnectionListeners + */ + unregisterAllConnectionListeners() { + this._connectionListeners = []; } - return true; -} + /** + * Creates a new producer session. + *

You can only create one producer session at a time.
+ * To request streaming from a new stream you will first need to close the previous producer session.

+ *

You can only request a producer session while you are connected to the signaling server. You can use the + * {@link GstWebRTCAPI.ConnectionListener} interface and {@link GstWebRTCAPI#registerConnectionListener} method to + * listen to the connection state.

+ * @method GstWebRTCAPI#createProducerSession + * @param {external:MediaStream} stream - The audio/video stream to offer as a producer through WebRTC. + * @returns {GstWebRTCAPI.ProducerSession} The created producer session or null in case of error. To start streaming, + * you still need to call {@link GstWebRTCAPI.ProducerSession#start} after adding on the returned session all the event + * listeners you may need. + */ + createProducerSession(stream) { + if (this._channel) { + return this._channel.createProducerSession(stream); + } + return null; + } + + /** + * Information about a remote producer registered by the signaling server. + * @typedef {object} GstWebRTCAPI.Producer + * @readonly + * @property {string} id - The remote producer unique identifier set by the signaling server (always non-empty). + * @property {object} meta - Free-form object containing extra information about the remote producer (always non-null, + * but may be empty). Its content depends on your application. + */ + + /** + * Gets the list of all remote WebRTC producers available on the signaling server. + *

The remote producers list is only populated once you've connected to the signaling server. You can use the + * {@link GstWebRTCAPI.ConnectionListener} interface and {@link GstWebRTCAPI#registerConnectionListener} method to + * listen to the connection state.

+ * @method GstWebRTCAPI#getAvailableProducers + * @returns {GstWebRTCAPI.Producer[]} The list of remote WebRTC producers available. + */ + getAvailableProducers() { + return Object.values(this._producers); + } + + /** + * @interface GstWebRTCAPI.ProducersListener + */ + /** + * Callback method called when a remote producer is added on the signaling server. + * The callback implementation should not throw any exception. + * @method GstWebRTCAPI.ProducersListener#producerAdded + * @abstract + * @param {GstWebRTCAPI.Producer} producer - The remote producer added on server-side. + */ + /** + * Callback method called when a remote producer is removed from the signaling server. + * The callback implementation should not throw any exception. + * @method GstWebRTCAPI.ProducersListener#producerRemoved + * @abstract + * @param {GstWebRTCAPI.Producer} producer - The remote producer removed on server-side. + */ + + /** + * Registers a producers listener that will be called each time a producer is added or removed on the signaling + * server. + * @method GstWebRTCAPI#registerProducersListener + * @param {GstWebRTCAPI.ProducersListener} listener - The producer listener to register. + * @returns {boolean} true in case of success (or if the listener was already registered), or false if the listener + * doesn't implement all callback functions and cannot be registered. + */ + registerProducersListener(listener) { + if (!listener || (typeof (listener) !== "object") || + (typeof (listener.producerAdded) !== "function") || + (typeof (listener.producerRemoved) !== "function")) { + return false; + } + + if (!this._producersListeners.includes(listener)) { + this._producersListeners.push(listener); + } -/** - * Unregisters a producers listener.
- * The removed listener will never be called again and can be garbage collected. - * @function - * @memberof gstWebRTCAPI - * @instance - * @param {gstWebRTCAPI.ProducersListener} listener - The producers listener to unregister. - * @returns {boolean} true if the listener is found and unregistered, or false if the listener was not previously - * registered. - */ -function unregisterProducersListener(listener) { - const idx = apiState.producersListeners.indexOf(listener); - if (idx >= 0) { - apiState.producersListeners.splice(idx, 1); return true; } - return false; -} + /** + * Unregisters a producers listener.
+ * The removed listener will never be called again and can be garbage collected. + * @method GstWebRTCAPI#unregisterProducersListener + * @param {GstWebRTCAPI.ProducersListener} listener - The producers listener to unregister. + * @returns {boolean} true if the listener is found and unregistered, or false if the listener was not previously + * registered. + */ + unregisterProducersListener(listener) { + const idx = this._producersListeners.indexOf(listener); + if (idx >= 0) { + this._producersListeners.splice(idx, 1); + return true; + } -/** - * Unregisters all previously registered producers listeners. - * @function - * @memberof gstWebRTCAPI - * @instance - */ -function unregisterAllProducersListeners() { - apiState.producersListeners = []; -} + return false; + } -/** - * Creates a consumer session by connecting the local client to a remote WebRTC producer. - *

You can only create one consumer session per remote producer.

- *

You can only request a new consumer session while you are connected to the signaling server. You can use the - * {@link gstWebRTCAPI.ConnectionListener} interface and {@link gstWebRTCAPI#registerConnectionListener} function to - * listen to the connection state.

- * @function - * @memberof gstWebRTCAPI - * @instance - * @param {string} producerId - The unique identifier of the remote producer to connect to. - * @returns {gstWebRTCAPI.ConsumerSession} The WebRTC session between the selected remote producer and this local - * consumer, or null in case of error. To start connecting and receiving the remote streams, you still need to call - * {@link gstWebRTCAPI.ConsumerSession#connect} after adding on the returned session all the event listeners you may - * need. - */ -function createConsumerSession(producerId) { - if (apiState.channel) { - return apiState.channel.createConsumerSession(producerId); - } else { + /** + * Unregisters all previously registered producers listeners. + * @method GstWebRTCAPI#unregisterAllProducersListeners + */ + unregisterAllProducersListeners() { + this._producersListeners = []; + } + + /** + * Creates a consumer session by connecting the local client to a remote WebRTC producer. + *

You can only create one consumer session per remote producer.

+ *

You can only request a new consumer session while you are connected to the signaling server. You can use the + * {@link GstWebRTCAPI.ConnectionListener} interface and {@link GstWebRTCAPI#registerConnectionListener} method to + * listen to the connection state.

+ * @method GstWebRTCAPI#createConsumerSession + * @param {string} producerId - The unique identifier of the remote producer to connect to. + * @returns {GstWebRTCAPI.ConsumerSession} The WebRTC session between the selected remote producer and this local + * consumer, or null in case of error. To start connecting and receiving the remote streams, you still need to call + * {@link GstWebRTCAPI.ConsumerSession#connect} after adding on the returned session all the event listeners you may + * need. + */ + createConsumerSession(producerId) { + if (this._channel) { + return this._channel.createConsumerSession(producerId); + } return null; } -} -/** - * The GStreamer WebRTC Javascript API. - * @namespace gstWebRTCAPI - */ -const gstWebRTCAPI = Object.freeze({ - SessionState: SessionState, - registerConnectionListener: registerConnectionListener, - unregisterConnectionListener: unregisterConnectionListener, - unregisterAllConnectionListeners: unregisterAllConnectionListeners, - createProducerSession: createProducerSession, - getAvailableProducers: getAvailableProducers, - registerProducersListener: registerProducersListener, - unregisterProducersListener: unregisterProducersListener, - unregisterAllProducersListeners: unregisterAllProducersListeners, - createConsumerSession: createConsumerSession -}); - -function triggerConnected(clientId) { - for (const listener of apiState.connectionListeners) { - try { - listener.connected(clientId); - } catch (ex) { - console.error("a listener callback should not throw any exception", ex); + connectChannel() { + if (this._channel) { + const oldChannel = this._channel; + this._channel = null; + oldChannel.close(); + for (const key in this._producers) { + this.triggerProducerRemoved(key); + } + this._producers = {}; + this.triggerDisconnected(); } - } -} -function triggerDisconnected() { - for (const listener of apiState.connectionListeners) { - try { - listener.disconnected(); - } catch (ex) { - console.error("a listener callback should not throw any exception", ex); - } - } -} + this._channel = new ComChannel( + this._config.signalingServerUrl, + this._config.meta, + this._config.webrtcConfig + ); -function triggerProducerAdded(producer) { - if (producer.id in apiState.producers) { - return; + this._channel.addEventListener("error", (event) => { + if (event.target === this._channel) { + console.error(event.message, event.error); + } + }); + + this._channel.addEventListener("closed", (event) => { + if (event.target !== this._channel) { + return; + } + this._channel = null; + for (const key in this._producers) { + this.triggerProducerRemoved(key); + } + this._producers = {}; + this.triggerDisconnected(); + if (this._config.reconnectionTimeout > 0) { + window.setTimeout(() => { + this.connectChannel(); + }, this._config.reconnectionTimeout); + } + }); + + this._channel.addEventListener("ready", (event) => { + if (event.target === this._channel) { + this.triggerConnected(this._channel.channelId); + } + }); + + this._channel.addEventListener("producerAdded", (event) => { + if (event.target === this._channel) { + this.triggerProducerAdded(event.detail); + } + }); + + this._channel.addEventListener("producerRemoved", (event) => { + if (event.target === this._channel) { + this.triggerProducerRemoved(event.detail.id); + } + }); } - apiState.producers[producer.id] = producer; - for (const listener of apiState.producersListeners) { - try { - listener.producerAdded(producer); - } catch (ex) { - console.error("a listener callback should not throw any exception", ex); - } - } -} - -function triggerProducerRemoved(producerId) { - if (producerId in apiState.producers) { - const producer = apiState.producers[producerId]; - delete apiState.producers[producerId]; - - for (const listener of apiState.producersListeners) { + triggerConnected(clientId) { + for (const listener of this._connectionListeners) { try { - listener.producerRemoved(producer); + listener.connected(clientId); } catch (ex) { console.error("a listener callback should not throw any exception", ex); } } } -} -function connectChannel() { - if (apiState.channel) { - const oldChannel = apiState.channel; - apiState.channel = null; - oldChannel.close(); - for (const key in apiState.producers) { - triggerProducerRemoved(key); - } - apiState.producers = {}; - triggerDisconnected(); - } - - apiState.channel = new ComChannel( - apiState.config.signalingServerUrl, - apiState.config.meta, - apiState.config.webrtcConfig); - - apiState.channel.addEventListener("error", (event) => { - if (event.target === apiState.channel) { - console.error(event.message, event.error); - } - }); - - apiState.channel.addEventListener("closed", (event) => { - if (event.target === apiState.channel) { - apiState.channel = null; - for (const key in apiState.producers) { - triggerProducerRemoved(key); - } - apiState.producers = {}; - triggerDisconnected(); - - if (apiState.config.reconnectionTimeout > 0) { - window.setTimeout(connectChannel, apiState.config.reconnectionTimeout); + triggerDisconnected() { + for (const listener of this._connectionListeners) { + try { + listener.disconnected(); + } catch (ex) { + console.error("a listener callback should not throw any exception", ex); } } - }); + } - apiState.channel.addEventListener("ready", (event) => { - if (event.target === apiState.channel) { - triggerConnected(apiState.channel.channelId); + triggerProducerAdded(producer) { + if (producer.id in this._producers) { + return; } - }); - apiState.channel.addEventListener("producerAdded", (event) => { - if (event.target === apiState.channel) { - triggerProducerAdded(event.detail); + this._producers[producer.id] = producer; + for (const listener of this._producersListeners) { + try { + listener.producerAdded(producer); + } catch (ex) { + console.error("a listener callback should not throw any exception", ex); + } } - }); + } - apiState.channel.addEventListener("producerRemoved", (event) => { - if (event.target === apiState.channel) { - triggerProducerRemoved(event.detail.id); + triggerProducerRemoved(producerId) { + if (producerId in this._producers) { + const producer = this._producers[producerId]; + delete this._producers[producerId]; + + for (const listener of this._producersListeners) { + try { + listener.producerRemoved(producer); + } catch (ex) { + console.error("a listener callback should not throw any exception", ex); + } + } } - }); + } } -function start(userConfig) { - if (apiState.config) { - throw new Error("GstWebRTC API is already started"); - } - - const config = Object.assign({}, defaultConfig); - if (userConfig && (typeof (userConfig) === "object")) { - Object.assign(config, userConfig); - } - - if (typeof (config.meta) !== "object") { - config.meta = null; - } - - apiState.config = config; - connectChannel(); -} - -export { gstWebRTCAPI, start }; +GstWebRTCAPI.SessionState = SessionState; diff --git a/net/webrtc/gstwebrtc-api/src/index.js b/net/webrtc/gstwebrtc-api/src/index.js index 71a6b4a3..29119a57 100644 --- a/net/webrtc/gstwebrtc-api/src/index.js +++ b/net/webrtc/gstwebrtc-api/src/index.js @@ -10,7 +10,7 @@ */ import "webrtc-adapter"; -import { gstWebRTCAPI, start } from "./gstwebrtc-api.js"; +import GstWebRTCAPI from "./gstwebrtc-api.js"; /** * @external MediaStream @@ -49,9 +49,6 @@ import { gstWebRTCAPI, start } from "./gstwebrtc-api.js"; * @see https://developer.mozilla.org/en-US/docs/Web/API/HTMLVideoElement */ -if (!window.gstWebRTCAPI) { - window.gstWebRTCAPI = gstWebRTCAPI; - window.addEventListener("DOMContentLoaded", () => { - start(window.gstWebRTCConfig); - }); +if (!window.GstWebRTCAPI) { + window.GstWebRTCAPI = GstWebRTCAPI; } diff --git a/net/webrtc/gstwebrtc-api/src/producer-session.js b/net/webrtc/gstwebrtc-api/src/producer-session.js index b5790338..b09c608b 100644 --- a/net/webrtc/gstwebrtc-api/src/producer-session.js +++ b/net/webrtc/gstwebrtc-api/src/producer-session.js @@ -13,10 +13,10 @@ import WebRTCSession from "./webrtc-session.js"; import SessionState from "./session-state.js"; /** - * @class gstWebRTCAPI.ClientSession + * @class GstWebRTCAPI.ClientSession * @hideconstructor * @classdesc Client session representing a link between a remote consumer and a local producer session. - * @extends {gstWebRTCAPI.WebRTCSession} + * @extends {GstWebRTCAPI.WebRTCSession} */ class ClientSession extends WebRTCSession { constructor(peerId, sessionId, comChannel, stream) { @@ -102,33 +102,33 @@ class ClientSession extends WebRTCSession { /** * Event name: "clientConsumerAdded".
- * Triggered when a remote consumer peer connects to a local {@link gstWebRTCAPI.ProducerSession}. - * @event gstWebRTCAPI#ClientConsumerAddedEvent + * Triggered when a remote consumer peer connects to a local {@link GstWebRTCAPI.ProducerSession}. + * @event GstWebRTCAPI#ClientConsumerAddedEvent * @type {external:CustomEvent} - * @property {gstWebRTCAPI.ClientSession} detail - The WebRTC session associated with the added consumer peer. - * @see gstWebRTCAPI.ProducerSession + * @property {GstWebRTCAPI.ClientSession} detail - The WebRTC session associated with the added consumer peer. + * @see GstWebRTCAPI.ProducerSession */ /** * Event name: "clientConsumerRemoved".
- * Triggered when a remote consumer peer disconnects from a local {@link gstWebRTCAPI.ProducerSession}. - * @event gstWebRTCAPI#ClientConsumerRemovedEvent + * Triggered when a remote consumer peer disconnects from a local {@link GstWebRTCAPI.ProducerSession}. + * @event GstWebRTCAPI#ClientConsumerRemovedEvent * @type {external:CustomEvent} - * @property {gstWebRTCAPI.ClientSession} detail - The WebRTC session associated with the removed consumer peer. - * @see gstWebRTCAPI.ProducerSession + * @property {GstWebRTCAPI.ClientSession} detail - The WebRTC session associated with the removed consumer peer. + * @see GstWebRTCAPI.ProducerSession */ /** - * @class gstWebRTCAPI.ProducerSession + * @class GstWebRTCAPI.ProducerSession * @hideconstructor * @classdesc Producer session managing the streaming out of a local {@link external:MediaStream}.
* It manages all underlying WebRTC connections to each peer client consuming the stream. - *

Call {@link gstWebRTCAPI#createProducerSession} to create a ProducerSession instance.

+ *

Call {@link GstWebRTCAPI#createProducerSession} to create a ProducerSession instance.

* @extends {external:EventTarget} - * @fires {@link gstWebRTCAPI#event:ErrorEvent} - * @fires {@link gstWebRTCAPI#event:StateChangedEvent} - * @fires {@link gstWebRTCAPI#event:ClosedEvent} - * @fires {@link gstWebRTCAPI#event:ClientConsumerAddedEvent} - * @fires {@link gstWebRTCAPI#event:ClientConsumerRemovedEvent} + * @fires {@link GstWebRTCAPI#event:ErrorEvent} + * @fires {@link GstWebRTCAPI#event:StateChangedEvent} + * @fires {@link GstWebRTCAPI#event:ClosedEvent} + * @fires {@link GstWebRTCAPI#event:ClientConsumerAddedEvent} + * @fires {@link GstWebRTCAPI#event:ClientConsumerRemovedEvent} */ export default class ProducerSession extends EventTarget { constructor(comChannel, stream) { @@ -142,7 +142,7 @@ export default class ProducerSession extends EventTarget { /** * The local stream produced out by this session. - * @member {external:MediaStream} gstWebRTCAPI.ProducerSession#stream + * @member {external:MediaStream} GstWebRTCAPI.ProducerSession#stream * @readonly */ get stream() { @@ -151,7 +151,7 @@ export default class ProducerSession extends EventTarget { /** * The current producer session state. - * @member {gstWebRTCAPI.SessionState} gstWebRTCAPI.ProducerSession#state + * @member {GstWebRTCAPI.SessionState} GstWebRTCAPI.ProducerSession#state * @readonly */ get state() { @@ -163,10 +163,10 @@ export default class ProducerSession extends EventTarget { * This method must be called after creating the producer session in order to start streaming. It registers this * producer session to the signaling server and gets ready to serve peer requests from consumers. *

Even on success, streaming can fail later if any error occurs during or after connection. In order to know - * the effective streaming state, you should be listening to the [error]{@link gstWebRTCAPI#event:ErrorEvent}, - * [stateChanged]{@link gstWebRTCAPI#event:StateChangedEvent} and/or [closed]{@link gstWebRTCAPI#event:ClosedEvent} + * the effective streaming state, you should be listening to the [error]{@link GstWebRTCAPI#event:ErrorEvent}, + * [stateChanged]{@link GstWebRTCAPI#event:StateChangedEvent} and/or [closed]{@link GstWebRTCAPI#event:ClosedEvent} * events.

- * @method gstWebRTCAPI.ProducerSession#start + * @method GstWebRTCAPI.ProducerSession#start * @returns {boolean} true in case of success (may fail later during or after connection) or false in case of * immediate error (wrong session state or no connection to the signaling server). */ @@ -203,7 +203,7 @@ export default class ProducerSession extends EventTarget { * Terminates the producer session.
* It immediately disconnects all peer consumers attached to this producer session and unregisters the producer * from the signaling server. - * @method gstWebRTCAPI.ProducerSession#close + * @method GstWebRTCAPI.ProducerSession#close */ close() { if (this._state !== SessionState.closed) { diff --git a/net/webrtc/gstwebrtc-api/src/remote-controller.js b/net/webrtc/gstwebrtc-api/src/remote-controller.js index 25f94808..939e40e6 100644 --- a/net/webrtc/gstwebrtc-api/src/remote-controller.js +++ b/net/webrtc/gstwebrtc-api/src/remote-controller.js @@ -65,7 +65,7 @@ function getModifiers(event) { } /** - * @class gstWebRTCAPI.RemoteController + * @class GstWebRTCAPI.RemoteController * @hideconstructor * @classdesc Manages a specific WebRTC data channel created by a remote GStreamer webrtcsink producer and offering * remote control of the producer through @@ -76,10 +76,10 @@ function getModifiers(event) { *

You can attach an {@link external:HTMLVideoElement} to the remote controller, then all mouse and keyboard events * emitted by this element will be automatically relayed to the remote producer.

* @extends {external:EventTarget} - * @fires {@link gstWebRTCAPI#event:ErrorEvent} - * @fires {@link gstWebRTCAPI#event:ClosedEvent} - * @see gstWebRTCAPI.ConsumerSession#remoteController - * @see gstWebRTCAPI.RemoteController#attachVideoElement + * @fires {@link GstWebRTCAPI#event:ErrorEvent} + * @fires {@link GstWebRTCAPI#event:ClosedEvent} + * @see GstWebRTCAPI.ConsumerSession#remoteController + * @see GstWebRTCAPI.RemoteController#attachVideoElement * @see https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/tree/main/net/webrtc/gstwebrtc-api#produce-a-gstreamer-interactive-webrtc-stream-with-remote-control */ export default class RemoteController extends EventTarget { @@ -114,7 +114,7 @@ export default class RemoteController extends EventTarget { /** * The underlying WebRTC data channel connected to a remote GStreamer webrtcsink producer offering remote control. * The value may be null if the remote controller has been closed. - * @member {external:RTCDataChannel} gstWebRTCAPI.RemoteController#rtcDataChannel + * @member {external:RTCDataChannel} GstWebRTCAPI.RemoteController#rtcDataChannel * @readonly */ get rtcDataChannel() { @@ -123,7 +123,7 @@ export default class RemoteController extends EventTarget { /** * The consumer session associated with this remote controller. - * @member {gstWebRTCAPI.ConsumerSession} gstWebRTCAPI.RemoteController#consumerSession + * @member {GstWebRTCAPI.ConsumerSession} GstWebRTCAPI.RemoteController#consumerSession * @readonly */ get consumerSession() { @@ -133,9 +133,9 @@ export default class RemoteController extends EventTarget { /** * The video element that is currently used to send all mouse and keyboard events to the remote producer. Value may * be null if no video element is attached. - * @member {external:HTMLVideoElement} gstWebRTCAPI.RemoteController#videoElement + * @member {external:HTMLVideoElement} GstWebRTCAPI.RemoteController#videoElement * @readonly - * @see gstWebRTCAPI.RemoteController#attachVideoElement + * @see GstWebRTCAPI.RemoteController#attachVideoElement */ get videoElement() { return this._videoElement; @@ -145,7 +145,7 @@ export default class RemoteController extends EventTarget { * Associates a video element with this remote controller.
* When a video element is attached to this remote controller, all mouse and keyboard events emitted by this * element will be sent to the remote GStreamer webrtcink producer. - * @method gstWebRTCAPI.RemoteController#attachVideoElement + * @method GstWebRTCAPI.RemoteController#attachVideoElement * @param {external:HTMLVideoElement|null} element - the video element to use to relay mouse and keyboard events, * or null to detach any previously attached element. If the provided element parameter is not null and not a * valid instance of an {@link external:HTMLVideoElement}, then the method does nothing. @@ -183,7 +183,7 @@ export default class RemoteController extends EventTarget { * Closes the remote controller channel.
* It immediately shuts down the underlying WebRTC data channel connected to a remote GStreamer webrtcsink * producer and detaches any video element that may be used to relay mouse and keyboard events. - * @method gstWebRTCAPI.RemoteController#close + * @method GstWebRTCAPI.RemoteController#close */ close() { this.attachVideoElement(null); diff --git a/net/webrtc/gstwebrtc-api/src/session-state.js b/net/webrtc/gstwebrtc-api/src/session-state.js index 075307d4..b7babf91 100644 --- a/net/webrtc/gstwebrtc-api/src/session-state.js +++ b/net/webrtc/gstwebrtc-api/src/session-state.js @@ -12,7 +12,7 @@ /** * Session states enumeration.
* Session state always increases from idle to closed and never switches backwards. - * @typedef {enum} gstWebRTCAPI.SessionState + * @typedef {enum} GstWebRTCAPI.SessionState * @readonly * @property {0} idle - Default state when creating a new session, goes to connecting when starting * the session. diff --git a/net/webrtc/gstwebrtc-api/src/webrtc-session.js b/net/webrtc/gstwebrtc-api/src/webrtc-session.js index e10241fe..6e3c004b 100644 --- a/net/webrtc/gstwebrtc-api/src/webrtc-session.js +++ b/net/webrtc/gstwebrtc-api/src/webrtc-session.js @@ -15,53 +15,53 @@ import SessionState from "./session-state.js"; * Event name: "error".
* Triggered when any kind of error occurs. *

When emitted by a session, it is in general an unrecoverable error. Normally, the session is automatically closed - * but in the specific case of a {@link gstWebRTCAPI.ProducerSession}, when the error occurs on an underlying - * {@link gstWebRTCAPI.ClientSession} between the producer session and a remote client consuming the streamed media, - * then only the failing {@link gstWebRTCAPI.ClientSession} is closed. The producer session can keep on serving the + * but in the specific case of a {@link GstWebRTCAPI.ProducerSession}, when the error occurs on an underlying + * {@link GstWebRTCAPI.ClientSession} between the producer session and a remote client consuming the streamed media, + * then only the failing {@link GstWebRTCAPI.ClientSession} is closed. The producer session can keep on serving the * other consumer peers.

- * @event gstWebRTCAPI#ErrorEvent + * @event GstWebRTCAPI#ErrorEvent * @type {external:ErrorEvent} * @property {string} message - The error message. * @property {external:Error} error - The error exception. - * @see gstWebRTCAPI.WebRTCSession - * @see gstWebRTCAPI.RemoteController + * @see GstWebRTCAPI.WebRTCSession + * @see GstWebRTCAPI.RemoteController */ /** * Event name: "stateChanged".
* Triggered each time a session state changes. - * @event gstWebRTCAPI#StateChangedEvent + * @event GstWebRTCAPI#StateChangedEvent * @type {external:Event} - * @see gstWebRTCAPI.WebRTCSession#state + * @see GstWebRTCAPI.WebRTCSession#state */ /** * Event name: "rtcPeerConnectionChanged".
* Triggered each time a session internal {@link external:RTCPeerConnection} changes. This can occur during the session * connecting state when the peer-to-peer WebRTC connection is established, and when closing the - * {@link gstWebRTCAPI.WebRTCSession}. - * @event gstWebRTCAPI#RTCPeerConnectionChangedEvent + * {@link GstWebRTCAPI.WebRTCSession}. + * @event GstWebRTCAPI#RTCPeerConnectionChangedEvent * @type {external:Event} - * @see gstWebRTCAPI.WebRTCSession#rtcPeerConnection + * @see GstWebRTCAPI.WebRTCSession#rtcPeerConnection */ /** * Event name: "closed".
* Triggered when a session is definitively closed (then it can be garbage collected as session instances are not * reusable). - * @event gstWebRTCAPI#ClosedEvent + * @event GstWebRTCAPI#ClosedEvent * @type {external:Event} */ /** - * @class gstWebRTCAPI.WebRTCSession + * @class GstWebRTCAPI.WebRTCSession * @hideconstructor * @classdesc Manages a WebRTC session between a producer and a consumer (peer-to-peer channel). * @extends {external:EventTarget} - * @fires {@link gstWebRTCAPI#event:ErrorEvent} - * @fires {@link gstWebRTCAPI#event:StateChangedEvent} - * @fires {@link gstWebRTCAPI#event:RTCPeerConnectionChangedEvent} - * @fires {@link gstWebRTCAPI#event:ClosedEvent} - * @see gstWebRTCAPI.ConsumerSession - * @see gstWebRTCAPI.ProducerSession - * @see gstWebRTCAPI.ClientSession + * @fires {@link GstWebRTCAPI#event:ErrorEvent} + * @fires {@link GstWebRTCAPI#event:StateChangedEvent} + * @fires {@link GstWebRTCAPI#event:RTCPeerConnectionChangedEvent} + * @fires {@link GstWebRTCAPI#event:ClosedEvent} + * @see GstWebRTCAPI.ConsumerSession + * @see GstWebRTCAPI.ProducerSession + * @see GstWebRTCAPI.ClientSession */ export default class WebRTCSession extends EventTarget { constructor(peerId, comChannel) { @@ -76,7 +76,7 @@ export default class WebRTCSession extends EventTarget { /** * Unique identifier of the remote peer to which this session is connected. - * @member {string} gstWebRTCAPI.WebRTCSession#peerId + * @member {string} GstWebRTCAPI.WebRTCSession#peerId * @readonly */ get peerId() { @@ -87,8 +87,8 @@ export default class WebRTCSession extends EventTarget { * Unique identifier of this session (defined by the signaling server).
* The local session ID equals "" until it is created on server side. This is done during the connection handshake. * The local session ID is guaranteed to be valid and to correctly reflect the signaling server value once - * session state has switched to {@link gstWebRTCAPI.SessionState#streaming}. - * @member {string} gstWebRTCAPI.WebRTCSession#sessionId + * session state has switched to {@link GstWebRTCAPI.SessionState#streaming}. + * @member {string} GstWebRTCAPI.WebRTCSession#sessionId * @readonly */ get sessionId() { @@ -97,7 +97,7 @@ export default class WebRTCSession extends EventTarget { /** * The current WebRTC session state. - * @member {gstWebRTCAPI.SessionState} gstWebRTCAPI.WebRTCSession#state + * @member {GstWebRTCAPI.SessionState} GstWebRTCAPI.WebRTCSession#state * @readonly */ get state() { @@ -107,9 +107,9 @@ export default class WebRTCSession extends EventTarget { /** * The internal {@link external:RTCPeerConnection} used to manage the underlying WebRTC connection with session * peer. Value may be null if session has no active WebRTC connection. You can listen to the - * {@link gstWebRTCAPI#event:RTCPeerConnectionChangedEvent} event to be informed when the connection is established + * {@link GstWebRTCAPI#event:RTCPeerConnectionChangedEvent} event to be informed when the connection is established * or destroyed. - * @member {external:RTCPeerConnection} gstWebRTCAPI.WebRTCSession#rtcPeerConnection + * @member {external:RTCPeerConnection} GstWebRTCAPI.WebRTCSession#rtcPeerConnection * @readonly */ get rtcPeerConnection() { @@ -120,7 +120,7 @@ export default class WebRTCSession extends EventTarget { * Terminates the WebRTC session.
* It immediately disconnects the remote peer attached to this session and unregisters the session from the * signaling server. - * @method gstWebRTCAPI.WebRTCSession#close + * @method GstWebRTCAPI.WebRTCSession#close */ close() { if (this._state !== SessionState.closed) {