partey_workadventure/front/src/WebRtc/JitsiFactory.ts

314 lines
9.8 KiB
TypeScript
Raw Normal View History

2021-09-01 14:55:29 +02:00
import { JITSI_URL } from "../Enum/EnvironmentVariable";
2022-02-10 11:40:36 +01:00
import { coWebsiteManager } from "./CoWebsiteManager";
2021-09-01 14:55:29 +02:00
import { requestedCameraState, requestedMicrophoneState } from "../Stores/MediaStore";
import { get } from "svelte/store";
2022-02-10 11:40:36 +01:00
import type { CoWebsite } from "./CoWebsite/CoWesbite";
import CancelablePromise from "cancelable-promise";
2021-02-17 18:49:55 +01:00
interface jitsiConfigInterface {
2021-09-01 14:55:29 +02:00
startWithAudioMuted: boolean;
startWithVideoMuted: boolean;
prejoinPageEnabled: boolean;
2022-01-18 14:37:35 +01:00
disableDeepLinking: boolean;
2021-02-17 18:49:55 +01:00
}
2021-09-01 17:55:23 +02:00
interface JitsiOptions {
jwt?: string;
roomName: string;
width: string;
height: string;
parentNode: HTMLElement;
configOverwrite: jitsiConfigInterface;
interfaceConfigOverwrite: typeof defaultInterfaceConfig;
onload?: Function;
}
interface JitsiApi {
executeCommand: (command: string, ...args: Array<unknown>) => void;
addListener: (type: string, callback: Function) => void;
removeListener: (type: string, callback: Function) => void;
dispose: () => void;
}
declare global {
interface Window {
JitsiMeetExternalAPI: new (domain: string, options: JitsiOptions) => JitsiApi;
}
}
2021-09-01 14:55:29 +02:00
const getDefaultConfig = (): jitsiConfigInterface => {
return {
startWithAudioMuted: !get(requestedMicrophoneState),
startWithVideoMuted: !get(requestedCameraState),
2021-09-01 14:55:29 +02:00
prejoinPageEnabled: false,
2022-01-18 14:37:35 +01:00
disableDeepLinking: false,
2021-09-01 14:55:29 +02:00
};
};
2021-02-09 14:17:48 +01:00
2021-02-17 18:49:55 +01:00
const mergeConfig = (config?: object) => {
const currentDefaultConfig = getDefaultConfig();
2021-09-01 14:55:29 +02:00
if (!config) {
2021-02-17 18:49:55 +01:00
return currentDefaultConfig;
}
return {
2021-02-18 09:38:27 +01:00
...currentDefaultConfig,
...config,
2021-09-01 14:55:29 +02:00
startWithAudioMuted: (config as jitsiConfigInterface).startWithAudioMuted
? true
: currentDefaultConfig.startWithAudioMuted,
startWithVideoMuted: (config as jitsiConfigInterface).startWithVideoMuted
? true
: currentDefaultConfig.startWithVideoMuted,
prejoinPageEnabled: (config as jitsiConfigInterface).prejoinPageEnabled
? true
: currentDefaultConfig.prejoinPageEnabled,
};
};
2021-02-17 18:49:55 +01:00
2021-02-09 20:31:49 +01:00
const defaultInterfaceConfig = {
SHOW_CHROME_EXTENSION_BANNER: false,
MOBILE_APP_PROMO: false,
HIDE_INVITE_MORE_HEADER: true,
DISABLE_JOIN_LEAVE_NOTIFICATIONS: true,
DISABLE_VIDEO_BACKGROUND: true,
// Note: hiding brand does not seem to work, we probably need to put this on the server side.
SHOW_BRAND_WATERMARK: false,
SHOW_JITSI_WATERMARK: false,
SHOW_POWERED_BY: false,
SHOW_PROMOTIONAL_CLOSE_PAGE: false,
SHOW_WATERMARK_FOR_GUESTS: false,
TOOLBAR_BUTTONS: [
2021-09-01 14:55:29 +02:00
"microphone",
"camera",
"closedcaptions",
"desktop",
/*'embedmeeting',*/ "fullscreen",
"fodeviceselection",
"hangup",
"profile",
"chat",
"recording",
"livestreaming",
"etherpad",
"sharedvideo",
"settings",
"raisehand",
"videoquality",
"filmstrip",
/*'invite',*/ "feedback",
"stats",
"shortcuts",
"tileview",
"videobackgroundblur",
"download",
"help",
"mute-everyone" /*'security'*/,
],
};
const slugify = (...args: (string | number)[]): string => {
2021-09-01 14:55:29 +02:00
const value = args.join(" ");
return value
2021-09-01 14:55:29 +02:00
.normalize("NFD") // split an accented letter in the base letter and the accent
.replace(/[\u0300-\u036f]/g, "") // remove all previously split accents
.toLowerCase()
.trim()
2021-09-01 14:55:29 +02:00
.replace(/[^a-z0-9 ]/g, "") // remove all chars not letters, numbers and spaces (to be replaced)
.replace(/\s+/g, "-"); // separator
};
class JitsiFactory {
2021-09-01 17:55:23 +02:00
private jitsiApi?: JitsiApi;
private audioCallback = this.onAudioChange.bind(this);
private videoCallback = this.onVideoChange.bind(this);
private jitsiScriptLoaded: boolean = false;
/**
* Slugifies the room name and prepends the room name with the instance
*/
public getRoomName(roomName: string, instance: string): string {
2021-09-01 14:55:29 +02:00
return slugify(instance.replace("/", "-") + "-" + roomName);
}
2022-02-10 11:40:36 +01:00
public start(
2021-09-01 14:55:29 +02:00
roomName: string,
playerName: string,
jwt?: string,
config?: object,
interfaceConfig?: object,
2022-01-05 10:30:27 +01:00
jitsiUrl?: string
2022-02-10 11:40:36 +01:00
): CancelablePromise<HTMLIFrameElement> {
return new CancelablePromise((resolve, reject, cancel) => {
// Jitsi meet external API maintains some data in local storage
// which is sent via the appData URL parameter when joining a
// conference. Problem is that this data grows indefinitely. Thus
// after some time the URLs get so huge that loading the iframe
// becomes slow and eventually breaks completely. Thus lets just
// clear jitsi local storage before starting a new conference.
window.localStorage.removeItem("jitsiLocalStorage");
const domain = jitsiUrl || JITSI_URL;
if (domain === undefined) {
throw new Error("Missing JITSI_URL environment variable or jitsiUrl parameter in the map.");
2022-01-05 10:30:27 +01:00
}
2022-02-10 11:40:36 +01:00
const loadScript = this.loadJitsiScript(domain).then(() => {
const options: JitsiOptions = {
roomName: roomName,
jwt: jwt,
width: "100%",
height: "100%",
parentNode: coWebsiteManager.getCoWebsiteBuffer(),
configOverwrite: mergeConfig(config),
interfaceConfigOverwrite: { ...defaultInterfaceConfig, ...interfaceConfig },
};
if (!options.jwt) {
delete options.jwt;
}
const timemout = setTimeout(() => doResolve(), 2000); //failsafe in case the iframe is deleted before loading or too long to load
const doResolve = (): void => {
clearTimeout(timemout);
const iframe = coWebsiteManager
.getCoWebsiteBuffer()
.querySelector<HTMLIFrameElement>('[id*="jitsi" i]');
if (iframe && this.jitsiApi) {
this.jitsiApi.addListener("videoConferenceLeft", () => {
this.closeOrUnload();
});
this.jitsiApi.addListener("readyToClose", () => {
this.closeOrUnload();
});
return resolve(iframe);
}
};
this.jitsiApi = undefined;
options.onload = () => doResolve(); //we want for the iframe to be loaded before triggering animations.
this.jitsiApi = new window.JitsiMeetExternalAPI(domain, options);
this.jitsiApi.executeCommand("displayName", playerName);
this.jitsiApi.addListener("audioMuteStatusChanged", this.audioCallback);
this.jitsiApi.addListener("videoMuteStatusChanged", this.videoCallback);
});
cancel(() => {
loadScript.cancel();
});
});
}
2022-02-10 11:40:36 +01:00
private closeOrUnload = function () {
const coWebsite = coWebsiteManager.searchJitsi();
if (!coWebsite) {
return;
}
if (coWebsite.isClosable()) {
coWebsiteManager.closeCoWebsite(coWebsite);
2022-01-18 14:37:35 +01:00
} else {
2022-02-10 11:40:36 +01:00
coWebsiteManager.unloadCoWebsite(coWebsite).catch((err) => {
console.error("Cannot unload co-website from the Jitsi factory", err);
});
2022-01-18 14:37:35 +01:00
}
};
public restart() {
if (!this.jitsiApi) {
return;
}
this.jitsiApi.addListener("audioMuteStatusChanged", this.audioCallback);
this.jitsiApi.addListener("videoMuteStatusChanged", this.videoCallback);
const coWebsite = coWebsiteManager.searchJitsi();
if (!coWebsite) {
this.destroy();
return;
}
this.jitsiApi.addListener("videoConferenceLeft", () => {
2022-02-10 11:40:36 +01:00
this.closeOrUnload();
2022-01-18 14:37:35 +01:00
});
this.jitsiApi.addListener("readyToClose", () => {
2022-02-10 11:40:36 +01:00
this.closeOrUnload();
2022-01-18 14:37:35 +01:00
});
}
2021-10-07 14:44:15 +02:00
public stop() {
2021-09-01 14:55:29 +02:00
if (!this.jitsiApi) {
2020-10-31 14:04:55 +01:00
return;
}
2021-10-07 14:44:15 +02:00
2021-09-01 14:55:29 +02:00
this.jitsiApi.removeListener("audioMuteStatusChanged", this.audioCallback);
this.jitsiApi.removeListener("videoMuteStatusChanged", this.videoCallback);
2022-01-18 14:37:35 +01:00
}
public destroy() {
if (!this.jitsiApi) {
return;
}
this.stop();
this.jitsiApi?.dispose();
}
2021-02-17 19:21:37 +01:00
2021-09-01 14:55:29 +02:00
private onAudioChange({ muted }: { muted: boolean }): void {
if (muted) {
requestedMicrophoneState.disableMicrophone();
} else {
requestedMicrophoneState.enableMicrophone();
2021-02-17 19:21:37 +01:00
}
}
2021-02-17 19:21:37 +01:00
2021-09-01 14:55:29 +02:00
private onVideoChange({ muted }: { muted: boolean }): void {
if (muted) {
requestedCameraState.disableWebcam();
} else {
requestedCameraState.enableWebcam();
2021-02-17 19:21:37 +01:00
}
}
2022-02-10 11:40:36 +01:00
private loadJitsiScript(domain: string): CancelablePromise<void> {
return new CancelablePromise<void>((resolve, reject, cancel) => {
if (this.jitsiScriptLoaded) {
resolve();
return;
}
this.jitsiScriptLoaded = true;
// Load Jitsi if the environment variable is set.
2021-09-01 14:55:29 +02:00
const jitsiScript = document.createElement("script");
jitsiScript.src = "https://" + domain + "/external_api.js";
jitsiScript.onload = () => {
resolve();
2021-09-01 14:55:29 +02:00
};
jitsiScript.onerror = () => {
reject();
2021-09-01 14:55:29 +02:00
};
document.head.appendChild(jitsiScript);
2022-02-10 11:40:36 +01:00
cancel(() => {
jitsiScript.remove();
});
2021-09-01 14:55:29 +02:00
});
}
}
export const jitsiFactory = new JitsiFactory();