mirror of
https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs.git
synced 2024-05-20 17:28:49 +00:00
237 lines
7.9 KiB
JavaScript
237 lines
7.9 KiB
JavaScript
/*
|
|
* gstwebrtc-api
|
|
*
|
|
* Copyright (C) 2022 Igalia S.L. <info@igalia.com>
|
|
* Author: Loïc Le Page <llepage@igalia.com>
|
|
*
|
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
*/
|
|
|
|
import WebRTCSession from "./webrtc-session.js";
|
|
import SessionState from "./session-state.js";
|
|
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
|
|
* @type {external:Event}
|
|
* @see GstWebRTCAPI.ConsumerSession#streams
|
|
*/
|
|
/**
|
|
* Event name: "remoteControllerChanged".<br>
|
|
* Triggered when the underlying remote controller of a {@link GstWebRTCAPI.ConsumerSession} changes.
|
|
* @event GstWebRTCAPI#RemoteControllerChangedEvent
|
|
* @type {external:Event}
|
|
* @see GstWebRTCAPI.ConsumerSession#remoteController
|
|
*/
|
|
|
|
/**
|
|
* @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}
|
|
*/
|
|
export default class ConsumerSession extends WebRTCSession {
|
|
constructor(peerId, comChannel) {
|
|
super(peerId, comChannel);
|
|
this._streams = [];
|
|
this._remoteController = null;
|
|
|
|
this.addEventListener("closed", () => {
|
|
this._streams = [];
|
|
|
|
if (this._remoteController) {
|
|
this._remoteController.close();
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* The array of remote media streams consumed locally through this WebRTC channel.
|
|
* @member {external:MediaStream[]} GstWebRTCAPI.ConsumerSession#streams
|
|
* @readonly
|
|
*/
|
|
get streams() {
|
|
return this._streams;
|
|
}
|
|
|
|
/**
|
|
* 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
|
|
* @readonly
|
|
*/
|
|
get remoteController() {
|
|
return this._remoteController;
|
|
}
|
|
|
|
/**
|
|
* Connects the consumer session to its remote producer.<br>
|
|
* 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}
|
|
* events.</p>
|
|
* @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).
|
|
*/
|
|
connect() {
|
|
if (!this._comChannel || (this._state === SessionState.closed)) {
|
|
return false;
|
|
}
|
|
|
|
if (this._state !== SessionState.idle) {
|
|
return true;
|
|
}
|
|
|
|
const msg = {
|
|
type: "startSession",
|
|
peerId: this._peerId
|
|
};
|
|
if (!this._comChannel.send(msg)) {
|
|
this.dispatchEvent(new ErrorEvent("error", {
|
|
message: "cannot connect consumer session",
|
|
error: new Error("cannot send startSession message to signaling server")
|
|
}));
|
|
|
|
this.close();
|
|
return false;
|
|
}
|
|
|
|
this._state = SessionState.connecting;
|
|
this.dispatchEvent(new Event("stateChanged"));
|
|
return true;
|
|
}
|
|
|
|
onSessionStarted(peerId, sessionId) {
|
|
if ((this._peerId === peerId) && (this._state === SessionState.connecting) && !this._sessionId) {
|
|
this._sessionId = sessionId;
|
|
}
|
|
}
|
|
|
|
onSessionPeerMessage(msg) {
|
|
if ((this._state === SessionState.closed) || !this._comChannel || !this._sessionId) {
|
|
return;
|
|
}
|
|
|
|
if (!this._rtcPeerConnection) {
|
|
const connection = new RTCPeerConnection(this._comChannel.webrtcConfig);
|
|
this._rtcPeerConnection = connection;
|
|
|
|
connection.ontrack = (event) => {
|
|
if ((this._rtcPeerConnection === connection) && event.streams && (event.streams.length > 0)) {
|
|
if (this._state === SessionState.connecting) {
|
|
this._state = SessionState.streaming;
|
|
this.dispatchEvent(new Event("stateChanged"));
|
|
}
|
|
|
|
let streamsChanged = false;
|
|
for (const stream of event.streams) {
|
|
if (!this._streams.includes(stream)) {
|
|
this._streams.push(stream);
|
|
streamsChanged = true;
|
|
}
|
|
}
|
|
|
|
if (streamsChanged) {
|
|
this.dispatchEvent(new Event("streamsChanged"));
|
|
}
|
|
}
|
|
};
|
|
|
|
connection.ondatachannel = (event) => {
|
|
const rtcDataChannel = event.channel;
|
|
if (rtcDataChannel && (rtcDataChannel.label === "input")) {
|
|
if (this._remoteController) {
|
|
const previousController = this._remoteController;
|
|
this._remoteController = null;
|
|
previousController.close();
|
|
}
|
|
|
|
const remoteController = new RemoteController(rtcDataChannel, this);
|
|
this._remoteController = remoteController;
|
|
this.dispatchEvent(new Event("remoteControllerChanged"));
|
|
|
|
remoteController.addEventListener("closed", () => {
|
|
if (this._remoteController === remoteController) {
|
|
this._remoteController = null;
|
|
this.dispatchEvent(new Event("remoteControllerChanged"));
|
|
}
|
|
});
|
|
}
|
|
};
|
|
|
|
connection.onicecandidate = (event) => {
|
|
if ((this._rtcPeerConnection === connection) && event.candidate && this._comChannel) {
|
|
this._comChannel.send({
|
|
type: "peer",
|
|
sessionId: this._sessionId,
|
|
ice: event.candidate.toJSON()
|
|
});
|
|
}
|
|
};
|
|
|
|
this.dispatchEvent(new Event("rtcPeerConnectionChanged"));
|
|
}
|
|
|
|
if (msg.sdp) {
|
|
this._rtcPeerConnection.setRemoteDescription(msg.sdp).then(() => {
|
|
if (this._rtcPeerConnection) {
|
|
return this._rtcPeerConnection.createAnswer();
|
|
} else {
|
|
return null;
|
|
}
|
|
}).then((desc) => {
|
|
if (this._rtcPeerConnection && desc) {
|
|
return this._rtcPeerConnection.setLocalDescription(desc);
|
|
} else {
|
|
return null;
|
|
}
|
|
}).then(() => {
|
|
if (this._rtcPeerConnection && this._comChannel) {
|
|
const sdp = {
|
|
type: "peer",
|
|
sessionId: this._sessionId,
|
|
sdp: this._rtcPeerConnection.localDescription.toJSON()
|
|
};
|
|
if (!this._comChannel.send(sdp)) {
|
|
throw new Error("cannot send local SDP configuration to WebRTC peer");
|
|
}
|
|
}
|
|
}).catch((ex) => {
|
|
if (this._state !== SessionState.closed) {
|
|
this.dispatchEvent(new ErrorEvent("error", {
|
|
message: "an unrecoverable error occurred during SDP handshake",
|
|
error: ex
|
|
}));
|
|
|
|
this.close();
|
|
}
|
|
});
|
|
} else if (msg.ice) {
|
|
const candidate = new RTCIceCandidate(msg.ice);
|
|
this._rtcPeerConnection.addIceCandidate(candidate).catch((ex) => {
|
|
if (this._state !== SessionState.closed) {
|
|
this.dispatchEvent(new ErrorEvent("error", {
|
|
message: "an unrecoverable error occurred during ICE handshake",
|
|
error: ex
|
|
}));
|
|
|
|
this.close();
|
|
}
|
|
});
|
|
} else {
|
|
throw new Error(`invalid empty peer message received from consumer session ${this._sessionId}`);
|
|
}
|
|
}
|
|
}
|