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";
|
2020-10-23 16:16:30 +02:00
|
|
|
|
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 => {
|
2021-02-16 18:25:23 +01:00
|
|
|
return {
|
2021-05-19 11:17:43 +02:00
|
|
|
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 = {
|
2020-10-23 16:16:30 +02:00
|
|
|
SHOW_CHROME_EXTENSION_BANNER: false,
|
|
|
|
MOBILE_APP_PROMO: false,
|
|
|
|
|
|
|
|
HIDE_INVITE_MORE_HEADER: true,
|
2021-02-10 11:13:47 +01:00
|
|
|
DISABLE_JOIN_LEAVE_NOTIFICATIONS: true,
|
2021-02-10 11:20:05 +01:00
|
|
|
DISABLE_VIDEO_BACKGROUND: true,
|
2020-10-23 16:16:30 +02:00
|
|
|
|
|
|
|
// 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'*/,
|
2020-10-23 16:16:30 +02:00
|
|
|
],
|
|
|
|
};
|
|
|
|
|
2021-02-10 11:40:59 +01:00
|
|
|
const slugify = (...args: (string | number)[]): string => {
|
2021-09-01 14:55:29 +02:00
|
|
|
const value = args.join(" ");
|
2021-02-10 11:40:59 +01:00
|
|
|
|
|
|
|
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
|
2021-02-10 11:40:59 +01:00
|
|
|
.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
|
|
|
|
};
|
2021-02-10 11:40:59 +01:00
|
|
|
|
2020-10-23 16:16:30 +02:00
|
|
|
class JitsiFactory {
|
2021-09-01 17:55:23 +02:00
|
|
|
private jitsiApi?: JitsiApi;
|
2020-10-26 15:07:34 +01:00
|
|
|
private audioCallback = this.onAudioChange.bind(this);
|
|
|
|
private videoCallback = this.onVideoChange.bind(this);
|
2021-03-16 20:37:12 +01:00
|
|
|
private jitsiScriptLoaded: boolean = false;
|
2021-02-10 11:13:47 +01:00
|
|
|
|
2021-02-10 11:40:59 +01:00
|
|
|
/**
|
|
|
|
* 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-02-10 11:40:59 +01:00
|
|
|
}
|
|
|
|
|
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();
|
|
|
|
});
|
|
|
|
});
|
2020-10-23 16:16:30 +02:00
|
|
|
}
|
|
|
|
|
2022-02-10 11:40:36 +01:00
|
|
|
private closeOrUnload = function () {
|
|
|
|
const coWebsite = coWebsiteManager.searchJitsi();
|
|
|
|
if (!coWebsite) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (coWebsite.isClosable()) {
|
2022-02-08 11:19:29 +01:00
|
|
|
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();
|
2020-10-23 16:16:30 +02:00
|
|
|
this.jitsiApi?.dispose();
|
2021-05-19 11:17:43 +02:00
|
|
|
}
|
2021-02-17 19:21:37 +01:00
|
|
|
|
2021-09-01 14:55:29 +02:00
|
|
|
private onAudioChange({ muted }: { muted: boolean }): void {
|
2021-05-19 11:17:43 +02:00
|
|
|
if (muted) {
|
2021-05-18 16:38:56 +02:00
|
|
|
requestedMicrophoneState.disableMicrophone();
|
2021-05-19 11:17:43 +02:00
|
|
|
} else {
|
2021-05-18 16:38:56 +02:00
|
|
|
requestedMicrophoneState.enableMicrophone();
|
2021-02-17 19:21:37 +01:00
|
|
|
}
|
2021-05-19 11:17:43 +02:00
|
|
|
}
|
2021-02-17 19:21:37 +01:00
|
|
|
|
2021-09-01 14:55:29 +02:00
|
|
|
private onVideoChange({ muted }: { muted: boolean }): void {
|
2021-05-19 11:17:43 +02:00
|
|
|
if (muted) {
|
2021-05-18 16:38:56 +02:00
|
|
|
requestedCameraState.disableWebcam();
|
2021-05-19 11:17:43 +02:00
|
|
|
} else {
|
2021-05-18 16:38:56 +02:00
|
|
|
requestedCameraState.enableWebcam();
|
2021-02-17 19:21:37 +01:00
|
|
|
}
|
2020-10-23 16:16:30 +02:00
|
|
|
}
|
2020-10-26 15:07:34 +01:00
|
|
|
|
2022-02-10 11:40:36 +01:00
|
|
|
private loadJitsiScript(domain: string): CancelablePromise<void> {
|
|
|
|
return new CancelablePromise<void>((resolve, reject, cancel) => {
|
2021-03-16 20:37:12 +01:00
|
|
|
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";
|
2021-03-16 20:37:12 +01:00
|
|
|
jitsiScript.onload = () => {
|
|
|
|
resolve();
|
2021-09-01 14:55:29 +02:00
|
|
|
};
|
2021-03-16 20:37:12 +01:00
|
|
|
jitsiScript.onerror = () => {
|
|
|
|
reject();
|
2021-09-01 14:55:29 +02:00
|
|
|
};
|
2021-03-16 20:37:12 +01:00
|
|
|
|
|
|
|
document.head.appendChild(jitsiScript);
|
2022-02-10 11:40:36 +01:00
|
|
|
|
|
|
|
cancel(() => {
|
|
|
|
jitsiScript.remove();
|
|
|
|
});
|
2021-09-01 14:55:29 +02:00
|
|
|
});
|
2021-03-16 20:37:12 +01:00
|
|
|
}
|
2020-10-23 16:16:30 +02:00
|
|
|
}
|
|
|
|
|
2021-02-10 11:13:47 +01:00
|
|
|
export const jitsiFactory = new JitsiFactory();
|