partey_workadventure/front/src/WebRtc/JitsiFactory.ts

252 lines
8.3 KiB
TypeScript
Raw Normal View History

2021-09-01 14:55:29 +02:00
import { JITSI_URL } from "../Enum/EnvironmentVariable";
2022-01-04 16:48:47 +01:00
import { CoWebsite, coWebsiteManager } from "./CoWebsiteManager";
2021-09-01 14:55:29 +02:00
import { requestedCameraState, requestedMicrophoneState } from "../Stores/MediaStore";
import { get } from "svelte/store";
2021-02-17 18:49:55 +01:00
interface jitsiConfigInterface {
2021-09-01 14:55:29 +02:00
startWithAudioMuted: boolean;
startWithVideoMuted: boolean;
prejoinPageEnabled: 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,
};
};
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);
}
2021-09-01 14:55:29 +02:00
public start(
roomName: string,
playerName: string,
jwt?: string,
config?: object,
interfaceConfig?: object,
jitsiUrl?: string,
jitsiWidth?: number
2022-01-04 16:48:47 +01:00
): Promise<CoWebsite> {
return coWebsiteManager.addCoWebsite(
2021-11-24 15:29:12 +01:00
async (cowebsiteDiv) => {
// 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.");
2021-10-25 18:42:51 +02:00
}
2021-11-24 15:29:12 +01:00
await this.loadJitsiScript(domain);
const options: JitsiOptions = {
roomName: roomName,
jwt: jwt,
width: "100%",
height: "100%",
parentNode: cowebsiteDiv,
configOverwrite: mergeConfig(config),
interfaceConfigOverwrite: { ...defaultInterfaceConfig, ...interfaceConfig },
};
if (!options.jwt) {
delete options.jwt;
}
return new Promise((resolve, reject) => {
const doResolve = (): void => {
const iframe = cowebsiteDiv.querySelector<HTMLIFrameElement>('[id*="jitsi" i]');
if (iframe === null) {
throw new Error("Could not find Jitsi Iframe");
}
resolve(iframe);
};
options.onload = () => doResolve(); //we want for the iframe to be loaded before triggering animations.
setTimeout(() => doResolve(), 2000); //failsafe in case the iframe is deleted before loading or too long to load
this.jitsiApi = new window.JitsiMeetExternalAPI(domain, options);
this.jitsiApi.executeCommand("displayName", playerName);
this.jitsiApi.addListener("audioMuteStatusChanged", this.audioCallback);
this.jitsiApi.addListener("videoMuteStatusChanged", this.videoCallback);
});
},
jitsiWidth,
0
);
}
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
const jitsiCoWebsite = coWebsiteManager.searchJitsi();
if (jitsiCoWebsite) {
2022-01-04 16:48:47 +01:00
coWebsiteManager.closeJitsi().catch((e) => console.error(e));
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);
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
}
}
private async loadJitsiScript(domain: string): Promise<void> {
return new Promise<void>((resolve, reject) => {
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);
2021-09-01 14:55:29 +02:00
});
}
}
export const jitsiFactory = new JitsiFactory();