Implement GstWebRTCAPI as class instead of global instance

Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/merge_requests/1373>
This commit is contained in:
Yorick Smilda 2024-02-07 11:32:21 +01:00 committed by GStreamer Marge Bot
parent 594400a7f5
commit 563eff1193
10 changed files with 391 additions and 424 deletions

View file

@ -220,13 +220,7 @@
}
</style>
<script>
const signalingProtocol = window.location.protocol.startsWith("https") ? "wss" : "ws";
window.gstWebRTCConfig = {
meta: { name: `WebClient-${Date.now()}` },
signalingServerUrl: `${signalingProtocol}://${window.location.host}/webrtc`
};
function initCapture() {
function initCapture(api) {
const captureSection = document.getElementById("capture");
const clientIdElement = captureSection.querySelector(".client-id");
const videoElement = captureSection.getElementsByTagName("video")[0];
@ -235,7 +229,7 @@
connected: function(clientId) { clientIdElement.textContent = clientId; },
disconnected: function() { clientIdElement.textContent = "none"; }
};
gstWebRTCAPI.registerConnectionListener(listener);
api.registerConnectionListener(listener);
document.getElementById("capture-button").addEventListener("click", (event) => {
event.preventDefault();
@ -249,7 +243,7 @@
video: { width: 1280, height: 720 }
};
navigator.mediaDevices.getUserMedia(constraints).then((stream) => {
const session = gstWebRTCAPI.createProducerSession(stream);
const session = api.createProducerSession(stream);
if (session) {
captureSection._producerSession = session;
@ -270,7 +264,7 @@
session.addEventListener("stateChanged", (event) => {
if ((captureSection._producerSession === session) &&
(event.target.state === gstWebRTCAPI.SessionState.streaming)) {
(event.target.state === GstWebRTCAPI.SessionState.streaming)) {
videoElement.srcObject = stream;
videoElement.play().catch(() => {});
captureSection.classList.remove("starting");
@ -306,7 +300,7 @@
});
}
function initRemoteStreams() {
function initRemoteStreams(api) {
const remoteStreamsElement = document.getElementById("remote-streams");
const listener = {
@ -347,7 +341,7 @@
if (entryElement._consumerSession) {
entryElement._consumerSession.close();
} else {
const session = gstWebRTCAPI.createConsumerSession(producerId);
const session = api.createConsumerSession(producerId);
if (session) {
entryElement._consumerSession = session;
@ -408,8 +402,8 @@
}
};
gstWebRTCAPI.registerProducersListener(listener);
for (const producer of gstWebRTCAPI.getAvailableProducers()) {
api.registerProducersListener(listener);
for (const producer of api.getAvailableProducers()) {
listener.producerAdded(producer);
}
}
@ -422,8 +416,15 @@
}
});
initCapture();
initRemoteStreams();
const signalingProtocol = window.location.protocol.startsWith("https") ? "wss" : "ws";
const gstWebRTCConfig = {
meta: { name: `WebClient-${Date.now()}` },
signalingServerUrl: `${signalingProtocol}://${window.location.host}/webrtc`,
};
const api = new GstWebRTCAPI(gstWebRTCConfig);
initCapture(api);
initRemoteStreams(api);
});
</script>
</head>

View file

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

View file

@ -11,17 +11,16 @@
/**
* GStreamer WebRTC configuration.
* <p>You can override default values by defining configuration before receiving the <i>DOMContentLoaded</i> event.<br>
* Once the <i>DOMContentLoaded</i> event triggered, changing configuration will have no effect.</p>
* <p>The config can be passed on creation of the GstWebRTCAPI class.</p>
* <p>For example:
* <pre>
* 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`
* };
* });
* </pre></p>
* @typedef {object} gstWebRTCConfig
* @typedef {object} GstWebRTCConfig
* @property {object} meta=null - Client free-form information that will be exchanged with all peers through the
* signaling <i>meta</i> property, its content depends on your application.
* @property {string} signalingServerUrl=ws://127.0.0.1:8443 - The WebRTC signaling server URL.

View file

@ -15,28 +15,28 @@ import RemoteController from "./remote-controller.js";
/**
* Event name: "streamsChanged".<br>
* 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".<br>
* 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.
* <p>Call {@link gstWebRTCAPI#createConsumerSession} to create a ConsumerSession instance.</p>
* @extends {gstWebRTCAPI.WebRTCSession}
* @fires {@link gstWebRTCAPI#event:StreamsChangedEvent}
* @fires {@link gstWebRTCAPI#event:RemoteControllerChangedEvent}
* <p>Call {@link GstWebRTCAPI#createConsumerSession} to create a ConsumerSession instance.</p>
* @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.
* <p>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.</p>
* @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).
*/

View file

@ -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.<br>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.<br>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.<br>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.<br>
* 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.<br>
* 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.
* <p>You can only create a producer session at once.<br>
* To request streaming from a new stream you will first need to close the previous producer session.</p>
* <p>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.</p>
* @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.
* <p>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.</p>
* @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.
* <p>You can only create one producer session at a time.<br>
* To request streaming from a new stream you will first need to close the previous producer session.</p>
* <p>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.</p>
* @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.
* <p>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.</p>
* @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.<br>
* 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.<br>
* 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.
* <p>You can only create one consumer session per remote producer.</p>
* <p>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.</p>
* @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.
* <p>You can only create one consumer session per remote producer.</p>
* <p>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.</p>
* @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;

View file

@ -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;
}

View file

@ -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".<br>
* 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".<br>
* 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}.<br>
* It manages all underlying WebRTC connections to each peer client consuming the stream.
* <p>Call {@link gstWebRTCAPI#createProducerSession} to create a ProducerSession instance.</p>
* <p>Call {@link GstWebRTCAPI#createProducerSession} to create a ProducerSession instance.</p>
* @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.
* <p>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.</p>
* @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.<br>
* 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) {

View file

@ -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) {
* <p>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.</p>
* @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.<br>
* 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.<br>
* 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);

View file

@ -12,7 +12,7 @@
/**
* Session states enumeration.<br>
* 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 <i>connecting</i> when starting
* the session.

View file

@ -15,53 +15,53 @@ import SessionState from "./session-state.js";
* Event name: "error".<br>
* Triggered when any kind of error occurs.
* <p>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.</p>
* @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".<br>
* 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".<br>
* 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".<br>
* 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).<br>
* 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.<br>
* 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) {