Compare commits

...

3 Commits

Author SHA1 Message Date
Elijah Duffy
7f60048a9d update ConfigOverwrite to match latest Jitsi 2026-02-27 13:23:29 -08:00
Elijah Duffy
c912ec8387 0.0.3 2026-02-04 16:51:32 -08:00
Elijah Duffy
f6ad918d00 add onload & fallback rendering
fixes API trying to initialize before it has loaded
2026-02-04 16:51:24 -08:00
3 changed files with 596 additions and 16 deletions

View File

@@ -1,6 +1,6 @@
{ {
"name": "@svelte-toolkit/jitsi", "name": "@svelte-toolkit/jitsi",
"version": "0.0.2", "version": "0.0.3",
"repository": { "repository": {
"type": "git", "type": "git",
"url": "https://gitea.auvem.com/svelte-toolkit/jitsi.git" "url": "https://gitea.auvem.com/svelte-toolkit/jitsi.git"

View File

@@ -1,5 +1,15 @@
<!-- @component
The Jitsi component is a lightweight wrapper that manages loading the Jitsi Meet External API
and joining a Jitsi meeting room. It accepts configuration options and provides a callback
when the API is ready for use.
WARNING: Assume that ALL properties are NOT reactive. Due to the nature of the Jitsi Meet
External API, changing properties after initialization will NOT update the Jitsi instance.
Instead, use the getAPI() method to interact with the Jitsi instance directly.
-->
<script lang="ts"> <script lang="ts">
import { onMount } from 'svelte'; import { onMount, type Snippet } from 'svelte';
import type { import type {
JitsiMeetExternalAPIOptions, JitsiMeetExternalAPIOptions,
JitsiMeetExternalAPI JitsiMeetExternalAPI
@@ -21,13 +31,30 @@
* Additional CSS classes to apply to the Jitsi container. * Additional CSS classes to apply to the Jitsi container.
*/ */
class?: ClassValue; class?: ClassValue;
/**
* Children are rendered inside the Jitsi container until the API is initialized.
*/
children?: Snippet;
/**
* Callback function that is called when the Jitsi API is loaded. Can
* be used to provide a loading indicator until the API is ready.
* @param api The Jitsi Meet External API instance.
*/
onready?: (api: JitsiMeetExternalAPI) => void;
} }
let { domain = 'https://meet.jit.si', options, class: classValue }: Props = $props(); let {
domain = 'https://meet.jit.si',
options,
class: classValue,
children,
onready
}: Props = $props();
const src = $derived(`${new URL('external_api.js', domain).toString()}`); const src = $derived(`${new URL('external_api.js', domain).toString()}`);
let container: HTMLDivElement; let container: HTMLDivElement;
let ready = $state(false);
let api = $state<JitsiMeetExternalAPI | null>(null); let api = $state<JitsiMeetExternalAPI | null>(null);
onMount(() => { onMount(() => {
@@ -35,13 +62,27 @@
if (document.querySelectorAll('#jitsi-container').length > 1) { if (document.querySelectorAll('#jitsi-container').length > 1) {
console.warn('Duplicate Jitsi container detected. This may lead to unexpected behavior.'); console.warn('Duplicate Jitsi container detected. This may lead to unexpected behavior.');
} }
});
const onload = () => {
if (typeof window.JitsiMeetExternalAPI === 'undefined') {
console.error('Jitsi Meet External API is not available on the window object.');
return;
}
ready = true;
// Initialize the Jitsi Meet External API // Initialize the Jitsi Meet External API
api = new window.JitsiMeetExternalAPI(new URL(domain).host, { api = new window.JitsiMeetExternalAPI(new URL(domain).host, {
...options, ...options,
parentNode: container parentNode: container
}); });
}); if (api === null) {
console.error('Failed to initialize Jitsi Meet External API.');
return;
}
// Call the onready callback if provided
onready?.(api);
};
/** /**
* Get the Jitsi Meet External API instance. * Get the Jitsi Meet External API instance.
@@ -53,7 +94,11 @@
</script> </script>
<svelte:head> <svelte:head>
<script {src}></script> <script {src} {onload}></script>
</svelte:head> </svelte:head>
<div id="jitsi-container" bind:this={container} class={[classValue]}></div> <div id="jitsi-container" bind:this={container} class={[classValue]}>
{#if !ready}
{@render children?.()}
{/if}
</div>

View File

@@ -438,28 +438,521 @@ export interface JitsiMeetExternalAPIOptions {
} }
interface ConfigOverwrite { interface ConfigOverwrite {
// Audio settings
startWithAudioMuted?: boolean; startWithAudioMuted?: boolean;
startWithVideoMuted?: boolean; startAudioMuted?: number;
disableDeepLinking?: boolean; startAudioOnly?: boolean;
prejoinPageEnabled?: boolean; startSilent?: boolean;
enableNoisyMicDetection?: boolean; enableNoisyMicDetection?: boolean;
enableOpusRed?: boolean;
audioQuality?: {
stereo?: boolean;
opusMaxAverageBitrate?: number | null;
enableOpusDtx?: boolean;
};
noiseSuppression?: {
krisp?: {
enabled?: boolean;
logProcessStats?: boolean;
debugLogs?: boolean;
useBVC?: boolean;
bufferOverflowMS?: number;
};
};
disableAEC?: boolean;
disableAGC?: boolean;
disableAP?: boolean;
disableNS?: boolean;
// Video settings
startWithVideoMuted?: boolean;
startVideoMuted?: number;
maxFullResolutionParticipants?: number;
disableSimulcast?: boolean;
disableResponsiveTiles?: boolean;
constraints?: {
video?: {
height?: {
ideal?: number;
max?: number;
min?: number;
};
};
};
desktopSharingFrameRate?: {
min?: number;
max?: number;
};
screenShareSettings?: {
desktopPreferCurrentTab?: boolean;
desktopSystemAudio?: 'include' | 'exclude';
desktopSurfaceSwitching?: 'include' | 'exclude';
desktopDisplaySurface?: string;
desktopSelfBrowserSurface?: 'include' | 'exclude';
};
videoQuality?: {
codecPreferenceOrder?: ('AV1' | 'VP9' | 'VP8' | 'H264')[];
mobileCodecPreferenceOrder?: ('AV1' | 'VP9' | 'VP8' | 'H264')[];
screenshareCodec?: string;
mobileScreenshareCodec?: string;
enableAdaptiveMode?: boolean;
minHeightForQualityLvl?: Record<number, 'low' | 'standard' | 'high'>;
};
// Prejoin configuration (replaces prejoinPageEnabled)
prejoinConfig?: {
enabled?: boolean;
hideDisplayName?: boolean;
hideExtraJoinButtons?: ('no-audio' | 'by-phone')[];
preCallTestEnabled?: boolean;
preCallTestICEUrl?: string;
showHangUp?: boolean;
};
/** @deprecated Use prejoinConfig.enabled instead */
prejoinPageEnabled?: boolean;
// Lobby configuration
lobby?: {
autoKnock?: boolean;
enableChat?: boolean;
showHangUp?: boolean;
};
/** @deprecated Use lobby.autoKnock instead */
autoKnockLobby?: boolean;
/** @deprecated Use lobby.enableChat instead */
enableLobbyChat?: boolean;
// Security UI configuration
securityUi?: {
hideLobbyButton?: boolean;
disableLobbyPassword?: boolean;
};
/** @deprecated Use securityUi.hideLobbyButton instead */
hideLobbyButton?: boolean;
// Deep linking configuration
deeplinking?: {
disabled?: boolean;
hideLogo?: boolean;
desktop?: {
appName?: string;
appScheme?: string;
download?: {
linux?: string;
macos?: string;
windows?: string;
};
enabled?: boolean;
};
ios?: {
appName?: string;
appScheme?: string;
downloadLink?: string;
};
android?: {
appName?: string;
appScheme?: string;
downloadLink?: string;
appPackage?: string;
fDroidUrl?: string;
};
};
/** @deprecated Use deeplinking.disabled instead */
disableDeepLinking?: boolean;
// Welcome page configuration
welcomePage?: {
disabled?: boolean;
customUrl?: string;
};
// Recording configuration
recordings?: {
recordAudioAndVideo?: boolean;
suggestRecording?: boolean;
showPrejoinWarning?: boolean;
showRecordingLink?: boolean;
requireConsent?: boolean;
skipConsentInMeeting?: boolean;
consentLearnMoreLink?: string;
};
recordingService?: {
enabled?: boolean;
sharingEnabled?: boolean;
hideStorageWarning?: boolean;
};
localRecording?: {
disable?: boolean;
notifyAllParticipants?: boolean;
disableSelfRecording?: boolean;
};
// Live streaming configuration
liveStreaming?: {
enabled?: boolean;
termsLink?: string;
dataPrivacyLink?: string;
validatorRegExpString?: string;
helpLink?: string;
};
// Transcription configuration
transcription?: {
enabled?: boolean;
useAppLanguage?: boolean;
preferredLanguage?: string;
customLanguages?: Record<string, string>;
autoTranscribeOnRecord?: boolean;
autoCaptionOnTranscribe?: boolean;
disableClosedCaptions?: boolean;
};
// Toolbar configuration
toolbarButtons?: ToolbarButton[];
mainToolbarButtons?: ToolbarButton[][];
reducedUImainToolbarButtons?: ToolbarButton[];
reducedUIEnabled?: boolean;
buttonsWithNotifyClick?: (ToolbarButton | ButtonWithNotifyClick)[];
participantMenuButtonsWithNotifyClick?: (string | ButtonWithNotifyClick)[];
hiddenPremeetingButtons?: (
| 'microphone'
| 'camera'
| 'select-background'
| 'invite'
| 'settings'
)[];
customToolbarButtons?: Array<{
icon: string;
id: string;
text: string;
backgroundColor?: string;
}>;
customParticipantMenuButtons?: Array<{ icon: string; id: string; text: string }>;
toolbar?: {
initialTimeout?: number;
timeout?: number;
alwaysVisible?: boolean;
autoHideWhileChatIsOpen?: boolean;
};
// UI settings
enableClosePage?: boolean; enableClosePage?: boolean;
disableInviteFunctions?: boolean; disableInviteFunctions?: boolean;
disableModeratorIndicator?: boolean; disableModeratorIndicator?: boolean;
enableLobbyChat?: boolean;
hideLobbyButton?: boolean;
enableInsecureRoomNameWarning?: boolean; enableInsecureRoomNameWarning?: boolean;
toolbarButtons?: ToolbarButton[]; disableReactions?: boolean;
buttonsWithNotifyClick?: (ToolbarButton | ButtonWithNotifyClick)[]; disableReactionsModeration?: boolean;
mouseMoveCallbackInterval?: number; disableReactionsInChat?: boolean;
faceLandmarks?: { disablePolls?: boolean;
faceCenteringThreshold?: number; disableChat?: boolean;
disableSelfDemote?: boolean;
disableSelfView?: boolean;
disableSelfViewSettings?: boolean;
showChatPermissionsModeratorSetting?: boolean;
disableShortcuts?: boolean;
disableInitialGUM?: boolean;
disable1On1Mode?: boolean | null;
defaultLocalDisplayName?: string;
defaultRemoteDisplayName?: string;
hideDisplayName?: boolean;
hideDominantSpeakerBadge?: boolean;
defaultLanguage?: string;
disableProfile?: boolean;
hideEmailInSettings?: boolean;
requireDisplayName?: boolean;
readOnlyName?: boolean;
enableWebHIDFeature?: boolean;
doNotStoreRoom?: boolean;
disableLocalVideoFlip?: boolean;
doNotFlipLocalVideo?: boolean;
disableVirtualBackground?: boolean;
disableAddingBackgroundImages?: boolean;
backgroundAlpha?: number;
disableTileView?: boolean;
disableTileEnlargement?: boolean;
hideConferenceSubject?: boolean;
hideConferenceTimer?: boolean;
hideRecordingLabel?: boolean;
hideParticipantsStats?: boolean;
subject?: string;
localSubject?: string;
disableChatSmileys?: boolean;
disableFilmstripAutohiding?: boolean;
disableCameraTintForeground?: boolean;
// Filmstrip configuration
filmstrip?: {
disabled?: boolean;
disableResizable?: boolean;
disableStageFilmstrip?: boolean;
stageFilmstripParticipants?: number;
disableTopPanel?: boolean;
minParticipantCountForTopPanel?: number;
initialWidth?: number;
alwaysShowResizeBar?: boolean;
}; };
// Tile view configuration
tileView?: {
disabled?: boolean;
numberOfVisibleTiles?: number;
};
// Participants pane configuration
participantsPane?: {
enabled?: boolean;
hideModeratorSettingsTab?: boolean;
hideMoreActionsButton?: boolean;
hideMuteAllButton?: boolean;
};
// Breakout rooms configuration
breakoutRooms?: {
hideAddRoomButton?: boolean;
hideAutoAssignButton?: boolean;
hideJoinRoomButton?: boolean;
};
// Raised hands configuration
raisedHands?: {
disableLowerHandByModerator?: boolean;
disableLowerHandNotification?: boolean;
disableNextSpeakerNotification?: boolean;
disableRemoveRaisedHandOnFocus?: boolean;
};
disableRemoveRaisedHandOnFocus?: boolean;
// Speaker stats configuration
speakerStats?: {
disabled?: boolean;
disableSearch?: boolean;
order?: ('role' | 'name' | 'hasLeft')[];
};
// Connection indicators configuration
connectionIndicators?: {
autoHide?: boolean;
autoHideTimeout?: number;
disabled?: boolean;
disableDetails?: boolean;
inactiveDisabled?: boolean;
};
// Remote video menu configuration
remoteVideoMenu?: {
disabled?: boolean;
disableDemote?: boolean;
disableKick?: boolean;
disableGrantModerator?: boolean;
disablePrivateChat?: 'all' | 'allow-moderator-chat' | 'disable-visitor-chat';
};
disableRemoteMute?: boolean;
// Face landmarks configuration
faceLandmarks?: {
enableFaceCentering?: boolean;
enableFaceExpressionsDetection?: boolean;
enableDisplayFaceExpressions?: boolean;
enableRTCStats?: boolean;
faceCenteringThreshold?: number;
captureInterval?: number;
};
// Notification settings
notificationTimeouts?: {
short?: number;
medium?: number;
long?: number;
extraLong?: number;
sticky?: number;
};
notifications?: string[];
disabledNotifications?: string[];
// Conference info configuration
conferenceInfo?: {
alwaysVisible?: string[];
autoHide?: string[];
};
// Giphy configuration
giphy?: {
enabled?: boolean;
sdkKey?: string;
displayMode?: 'tile' | 'chat' | 'all';
tileTime?: number;
rating?: 'g' | 'pg' | 'pg-13' | 'r';
};
// Whiteboard configuration
whiteboard?: {
enabled?: boolean;
collabServerBaseUrl?: string;
userLimit?: number;
limitUrl?: string;
};
// E2EE configuration
e2ee?: {
labels?: {
description?: string;
label?: string;
tooltip?: string;
warning?: string;
};
externallyManagedKey?: boolean;
};
// Visitors configuration
visitors?: {
enableMediaOnPromote?: {
audio?: boolean;
video?: boolean;
};
hideVisitorCountForVisitors?: boolean;
showJoinMeetingDialog?: boolean;
};
// P2P configuration
p2p?: {
enabled?: boolean;
iceTransportPolicy?: 'all' | 'relay';
codecPreferenceOrder?: ('AV1' | 'VP9' | 'VP8' | 'H264')[];
mobileCodecPreferenceOrder?: ('AV1' | 'VP9' | 'VP8' | 'H264')[];
screenshareCodec?: string;
mobileScreenshareCodec?: string;
backToP2PDelay?: number;
stunServers?: Array<{ urls: string }>;
};
// Testing/experimental features
testing?: {
assumeBandwidth?: boolean;
enableAV1ForFF?: boolean;
enableCodecSelectionAPI?: boolean;
p2pTestMode?: boolean;
testMode?: boolean;
noAutoPlayVideo?: boolean;
skipInterimTranscriptions?: boolean;
dumpTranscript?: boolean;
debugAudioLevels?: boolean;
failICE?: boolean;
showSpotConsentDialog?: boolean;
};
// Analytics configuration
analytics?: {
disabled?: boolean;
rtcstatsEnabled?: boolean;
rtcstatsStoreLogs?: boolean;
rtcstatsEndpoint?: string;
rtcstatsPollInterval?: number;
rtcstatsSendSdp?: boolean;
watchRTCEnabled?: boolean;
};
// Gravatar configuration
gravatar?: {
baseUrl?: string;
disabled?: boolean;
};
// Legal URLs
legalUrls?: {
helpCentre?: string;
privacy?: string;
terms?: string;
};
// Other settings
apiLogLevels?: LogLevel[]; apiLogLevels?: LogLevel[];
mouseMoveCallbackInterval?: number;
channelLastN?: number;
startLastN?: number;
useHostPageLocalStorage?: boolean;
disableRtx?: boolean;
enableTcc?: boolean;
enableRemb?: boolean;
enableForcedReload?: boolean;
useTurnUdp?: boolean;
enableEncodedTransformSupport?: boolean;
disableThirdPartyRequests?: boolean;
enableCalendarIntegration?: boolean;
notifyOnConferenceDestruction?: boolean;
feedbackPercentage?: number;
roomPasswordNumberOfDigits?: number | false;
noticeMessage?: string;
enableTalkWhileMuted?: boolean;
forceTurnRelay?: boolean;
hideLoginButton?: boolean;
disableWebrtcStats?: boolean;
disableShowMoreStats?: boolean;
etherpad_base?: string;
openSharedDocumentOnJoin?: boolean;
screenshotCapture?: {
enabled?: boolean;
mode?: 'recording' | 'always';
};
webrtcIceUdpDisable?: boolean;
webrtcIceTcpDisable?: boolean;
disableBeforeUnloadHandlers?: boolean;
// Logging configuration
logging?: {
defaultLogLevel?: 'trace' | 'debug' | 'info' | 'log' | 'warn' | 'error';
disableLogCollector?: boolean;
loggers?: Record<string, string>;
};
// File sharing configuration
fileSharing?: {
apiUrl?: string;
enabled?: boolean;
maxFileSize?: number;
};
// Dropbox integration
dropbox?: {
appKey?: string;
redirectURI?: string;
};
// Dynamic branding
dynamicBrandingUrl?: string;
// Shared video allowed domains
sharedVideoAllowedURLDomains?: string[];
// CORS avatar URLs
corsAvatarURLs?: string[];
// Recording limit
recordingLimit?: {
limit?: number;
appName?: string;
appURL?: string;
};
// Chrome extension banner
chromeExtensionBanner?: {
url?: string;
edgeUrl?: string;
chromeExtensionsInfo?: Array<{
id: string;
path: string;
}>;
};
// Allow any additional config options
[key: string]: unknown; [key: string]: unknown;
} }
/**
* @deprecated Most interfaceConfig options are being migrated to config.js.
* Use configOverwrite instead where possible.
*/
interface InterfaceConfigOverwrite { interface InterfaceConfigOverwrite {
/** @deprecated Use config.disableModeratorIndicator instead */
DISABLE_DOMINANT_SPEAKER_INDICATOR?: boolean; DISABLE_DOMINANT_SPEAKER_INDICATOR?: boolean;
TILE_VIEW_MAX_COLUMNS?: number; TILE_VIEW_MAX_COLUMNS?: number;
SHOW_JITSI_WATERMARK?: boolean; SHOW_JITSI_WATERMARK?: boolean;
@@ -467,6 +960,7 @@ interface InterfaceConfigOverwrite {
SHOW_BRAND_WATERMARK?: boolean; SHOW_BRAND_WATERMARK?: boolean;
SHOW_POWERED_BY?: boolean; SHOW_POWERED_BY?: boolean;
SHOW_PROMOTIONAL_CLOSE_PAGE?: boolean; SHOW_PROMOTIONAL_CLOSE_PAGE?: boolean;
/** @deprecated Use config.toolbarButtons instead */
TOOLBAR_BUTTONS?: ToolbarButton[]; TOOLBAR_BUTTONS?: ToolbarButton[];
SETTINGS_SECTIONS?: SettingsSection[]; SETTINGS_SECTIONS?: SettingsSection[];
VIDEO_LAYOUT_FIT?: 'both' | 'width' | 'height'; VIDEO_LAYOUT_FIT?: 'both' | 'width' | 'height';
@@ -474,8 +968,44 @@ interface InterfaceConfigOverwrite {
FILM_STRIP_MAX_HEIGHT?: number; FILM_STRIP_MAX_HEIGHT?: number;
MOBILE_APP_PROMO?: boolean; MOBILE_APP_PROMO?: boolean;
HIDE_INVITE_MORE_HEADER?: boolean; HIDE_INVITE_MORE_HEADER?: boolean;
/** @deprecated Use config.disabledSounds instead */
DISABLE_JOIN_LEAVE_NOTIFICATIONS?: boolean; DISABLE_JOIN_LEAVE_NOTIFICATIONS?: boolean;
DISABLE_VIDEO_BACKGROUND?: boolean; DISABLE_VIDEO_BACKGROUND?: boolean;
DEFAULT_BACKGROUND?: string;
DEFAULT_WELCOME_PAGE_LOGO_URL?: string;
DEFAULT_LOGO_URL?: string;
JITSI_WATERMARK_LINK?: string;
BRAND_WATERMARK_LINK?: string;
APP_NAME?: string;
NATIVE_APP_NAME?: string;
PROVIDER_NAME?: string;
LANG_DETECTION?: boolean;
ENFORCE_NOTIFICATION_AUTO_DISMISS_TIMEOUT?: number;
MAXIMUM_ZOOMING_COEFFICIENT?: number;
SUPPORT_URL?: string;
CONNECTION_INDICATOR_AUTO_HIDE_ENABLED?: boolean;
CONNECTION_INDICATOR_AUTO_HIDE_TIMEOUT?: number;
CONNECTION_INDICATOR_DISABLED?: boolean;
AUTO_PIN_LATEST_SCREEN_SHARE?: string;
DISABLE_FOCUS_INDICATOR?: boolean;
DISABLE_PRESENCE_STATUS?: boolean;
DISABLE_TRANSCRIPTION_SUBTITLES?: boolean;
DISABLE_RINGING?: boolean;
AUDIO_LEVEL_PRIMARY_COLOR?: string;
AUDIO_LEVEL_SECONDARY_COLOR?: string;
FILMSTRIP_MAX_HEIGHT?: number;
GENERATE_ROOMNAMES_ON_WELCOME_PAGE?: boolean;
HIDE_DEEP_LINKING_LOGO?: boolean;
INITIAL_TOOLBAR_TIMEOUT?: number;
TOOLBAR_TIMEOUT?: number;
TOOLBAR_ALWAYS_VISIBLE?: boolean;
LOCAL_THUMBNAIL_RATIO?: number;
REMOTE_THUMBNAIL_RATIO?: number;
LIVE_STREAMING_HELP_LINK?: string;
POLICY_LOGO?: string;
RECENT_LIST_ENABLED?: boolean;
SHOW_CHROME_EXTENSION_BANNER?: boolean;
VIDEO_QUALITY_LABEL_DISABLED?: boolean;
[key: string]: unknown; [key: string]: unknown;
} }
@@ -1278,21 +1808,26 @@ type ToolbarButton =
| 'dock-iframe' | 'dock-iframe'
| 'download' | 'download'
| 'embedmeeting' | 'embedmeeting'
| 'end-meeting'
| 'etherpad' | 'etherpad'
| 'feedback' | 'feedback'
| 'filmstrip' | 'filmstrip'
| 'fullscreen' | 'fullscreen'
| 'hangup' | 'hangup'
| 'hangup-menu'
| 'help' | 'help'
| 'highlight' | 'highlight'
| 'invite' | 'invite'
| 'linktosalesforce' | 'linktosalesforce'
| 'livestreaming' | 'livestreaming'
| 'microphone' | 'microphone'
| 'mute-everyone'
| 'mute-video-everyone'
| 'noisesuppression' | 'noisesuppression'
| 'participants-pane' | 'participants-pane'
| 'profile' | 'profile'
| 'raisehand' | 'raisehand'
| 'reactions'
| 'recording' | 'recording'
| 'security' | 'security'
| 'select-background' | 'select-background'