partey_workadventure/front/src/WebRtc/ScreenSharingPeer.ts
Gregoire Parant 15be76655f Add todo to improve status management
Signed-off-by: Gregoire Parant <g.parant@thecodingmachine.com>
2022-01-28 18:42:48 +01:00

199 lines
6.7 KiB
TypeScript

import type * as SimplePeerNamespace from "simple-peer";
import type { RoomConnection } from "../Connexion/RoomConnection";
import { MESSAGE_TYPE_CONSTRAINT, PeerStatus } from "./VideoPeer";
import type { UserSimplePeerInterface } from "./SimplePeer";
import { Readable, readable, writable, Writable } from "svelte/store";
import { getIceServersConfig } from "../Components/Video/utils";
import { highlightedEmbedScreen } from "../Stores/EmbedScreensStore";
import { isMediaBreakpointUp } from "../Utils/BreakpointsUtils";
const Peer: SimplePeerNamespace.SimplePeer = require("simple-peer");
/**
* A peer connection used to transmit video / audio signals between 2 peers.
*/
export class ScreenSharingPeer extends Peer {
/**
* Whether this connection is currently receiving a video stream from a remote user.
*/
private isReceivingStream: boolean = false;
public toClose: boolean = false;
public _connected: boolean = false;
public readonly userId: number;
public readonly uniqueId: string;
public readonly streamStore: Readable<MediaStream | null>;
public readonly statusStore: Writable<PeerStatus>;
constructor(
user: UserSimplePeerInterface,
initiator: boolean,
public readonly userName: string,
private connection: RoomConnection,
stream: MediaStream | null
) {
super({
initiator,
config: {
iceServers: getIceServersConfig(user),
},
});
this.userId = user.userId;
this.uniqueId = "screensharing_" + this.userId;
this.streamStore = readable<MediaStream | null>(null, (set) => {
const onStream = (stream: MediaStream | null) => {
highlightedEmbedScreen.highlight({
type: "streamable",
embed: this,
});
set(stream);
};
const onData = (chunk: Buffer) => {
// We unfortunately need to rely on an event to let the other party know a stream has stopped.
// It seems there is no native way to detect that.
// TODO: we might rely on the "ended" event: https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/ended_event
const message = JSON.parse(chunk.toString("utf8"));
if (message.streamEnded !== true) {
console.error("Unexpected message on screen sharing peer connection");
return;
}
set(null);
};
this.on("stream", onStream);
this.on("data", onData);
return () => {
this.off("stream", onStream);
this.off("data", onData);
};
});
this.statusStore = writable<PeerStatus>("connecting", (set) => {
const onConnect = () => {
set("connected");
};
const onError = () => {
set("error");
};
const onClose = () => {
set("closed");
};
this.on("connect", onConnect);
this.on("error", onError);
this.on("close", onClose);
return () => {
this.off("connect", onConnect);
this.off("error", onError);
this.off("close", onClose);
};
});
//start listen signal for the peer connection
this.on("signal", (data: unknown) => {
this.sendWebrtcScreenSharingSignal(data);
});
this.on("stream", (stream: MediaStream) => {
this.stream(stream);
});
this.on("close", () => {
this._connected = false;
this.toClose = true;
this.destroy();
});
// eslint-disable-next-line @typescript-eslint/no-explicit-any
this.on("error", (err: any) => {
console.error(`screen sharing error => ${this.userId} => ${err.code}`, err);
});
this.on("connect", () => {
this._connected = true;
console.info(`connect => ${this.userId}`);
});
this.once("finish", () => {
this._onFinish();
});
if (stream) {
this.addStream(stream);
}
}
private sendWebrtcScreenSharingSignal(data: unknown) {
try {
this.connection.sendWebrtcScreenSharingSignal(data, this.userId);
} catch (e) {
console.error(`sendWebrtcScreenSharingSignal => ${this.userId}`, e);
}
}
/**
* Sends received stream to screen.
*/
private stream(stream?: MediaStream) {
if (!stream) {
this.isReceivingStream = false;
} else {
//Check if the peer connection is already connected status. In this case, the status store must be set to 'connected'.
//In the case or player A send stream and player B send a stream, it's same peer connection, also the status must be changed to connect.
//TODO add event listening when the stream is ready for displaying and change the status
if (this._connected) {
this.statusStore.set("connected");
}
this.isReceivingStream = true;
}
}
public isReceivingScreenSharingStream(): boolean {
return this.isReceivingStream;
}
public destroy(error?: Error): void {
try {
this._connected = false;
if (!this.toClose) {
return;
}
// FIXME: I don't understand why "Closing connection with" message is displayed TWICE before "Nb users in peerConnectionArray"
// I do understand the method closeConnection is called twice, but I don't understand how they manage to run in parallel.
//console.log('Closing connection with '+userId);
super.destroy(error);
//console.log('Nb users in peerConnectionArray '+this.PeerConnectionArray.size);
} catch (err) {
console.error("ScreenSharingPeer::destroy", err);
}
}
_onFinish() {
if (this.destroyed) return;
const destroySoon = () => {
this.destroy();
};
if (this._connected) {
destroySoon();
} else {
this.once("connect", destroySoon);
}
}
public stopPushingScreenSharingToRemoteUser(stream: MediaStream) {
this.removeStream(stream);
this.write(
new Buffer(
JSON.stringify({
type: MESSAGE_TYPE_CONSTRAINT,
streamEnded: true,
isMobile: isMediaBreakpointUp("md"),
})
)
);
}
}