Compare commits
10 Commits
7f60048a9d
...
v0.1.1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dccf4b01f7 | ||
|
|
99264dca61 | ||
|
|
b7c619c872 | ||
|
|
408d3635f6 | ||
|
|
ff2dd60a95 | ||
|
|
bb07744081 | ||
|
|
8c57fd9d7b | ||
|
|
4d9cd25b19 | ||
|
|
2cff9c79a7 | ||
|
|
beea7fcb17 |
66
README.md
66
README.md
@@ -1,65 +1,3 @@
|
||||
# Svelte library
|
||||
# Jitsi iFrame Svelte Integration
|
||||
|
||||
Everything you need to build a Svelte library, powered by [`sv`](https://npmjs.com/package/sv).
|
||||
|
||||
Read more about creating a library [in the docs](https://svelte.dev/docs/kit/packaging).
|
||||
|
||||
## Creating a project
|
||||
|
||||
If you're seeing this, you've probably already done this step. Congrats!
|
||||
|
||||
```sh
|
||||
# create a new project in the current directory
|
||||
npx sv create
|
||||
|
||||
# create a new project in my-app
|
||||
npx sv create my-app
|
||||
```
|
||||
|
||||
To recreate this project with the same configuration:
|
||||
|
||||
```sh
|
||||
# recreate this project
|
||||
pnpm dlx sv create --template library --types ts --add prettier eslint devtools-json --install pnpm jitsi
|
||||
```
|
||||
|
||||
## Developing
|
||||
|
||||
Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server:
|
||||
|
||||
```sh
|
||||
npm run dev
|
||||
|
||||
# or start the server and open the app in a new browser tab
|
||||
npm run dev -- --open
|
||||
```
|
||||
|
||||
Everything inside `src/lib` is part of your library, everything inside `src/routes` can be used as a showcase or preview app.
|
||||
|
||||
## Building
|
||||
|
||||
To build your library:
|
||||
|
||||
```sh
|
||||
npm pack
|
||||
```
|
||||
|
||||
To create a production version of your showcase app:
|
||||
|
||||
```sh
|
||||
npm run build
|
||||
```
|
||||
|
||||
You can preview the production build with `npm run preview`.
|
||||
|
||||
> To deploy your app, you may need to install an [adapter](https://svelte.dev/docs/kit/adapters) for your target environment.
|
||||
|
||||
## Publishing
|
||||
|
||||
Go into the `package.json` and give your package the desired name through the `"name"` option. Also consider adding a `"license"` field and point it to a `LICENSE` file which you can create from a template (one popular option is the [MIT license](https://opensource.org/license/mit/)).
|
||||
|
||||
To publish your library to [npm](https://www.npmjs.com):
|
||||
|
||||
```sh
|
||||
npm publish
|
||||
```
|
||||
This package provides a component and typed API for the Jitsi Meet iFrame. If you prefer to build your own component, refer directly to the exported type definitions for the Jitsi iFrame API.
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"name": "@svelte-toolkit/jitsi",
|
||||
"version": "0.0.3",
|
||||
"version": "0.1.1",
|
||||
"license": "BSD-3-Clause",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://gitea.auvem.com/svelte-toolkit/jitsi.git"
|
||||
|
||||
@@ -1,18 +1,22 @@
|
||||
<!-- @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.
|
||||
and joining a Jitsi meeting room. It accepts configuration options and several callbacks that
|
||||
allow you to respond to basic meeting events such as joining and leaving. For advanced event
|
||||
handling, use the getAPI() method to interact with the Jitsi instance directly.
|
||||
|
||||
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.
|
||||
WARNING: While properties are reactive, in most cases changing them after the API has
|
||||
initialized will trigger a complete re-render of the component, which will destroy the
|
||||
existing Jitsi instance and interrupt any ongoing meetings. Use the getAPI() method to
|
||||
interact with the Jitsi instance directly without causing a re-render.
|
||||
-->
|
||||
|
||||
<script lang="ts">
|
||||
import { onMount, type Snippet } from 'svelte';
|
||||
import type {
|
||||
JitsiMeetExternalAPIOptions,
|
||||
JitsiMeetExternalAPI
|
||||
JitsiMeetExternalAPI,
|
||||
VideoConferenceJoinedEvent,
|
||||
VideoConferenceLeftEvent
|
||||
} from '$lib/jitsi-iframe-api.d.ts';
|
||||
import type { ClassValue } from 'svelte/elements';
|
||||
|
||||
@@ -32,45 +36,72 @@ Instead, use the getAPI() method to interact with the Jitsi instance directly.
|
||||
*/
|
||||
class?: ClassValue;
|
||||
/**
|
||||
* Children are rendered inside the Jitsi container until the API is initialized.
|
||||
* Optional snippet rendered until the API is initialized and the meeting
|
||||
* is joined. Can be used to provide a loading indicator or placeholder content.
|
||||
*/
|
||||
children?: Snippet;
|
||||
loading?: Snippet;
|
||||
/** Whether to hold loading state until the meeting is joined (default: false). */
|
||||
holdLoadingUntilJoined?: boolean;
|
||||
/**
|
||||
* Optional snippet rendered after the API is initialized and the meeting is joined.
|
||||
* Can be used to provide additional UI elements or controls related to the meeting.
|
||||
* Overlay has pointer-events disabled to allow interaction with the underlying Jitsi
|
||||
* interface.
|
||||
*/
|
||||
joinedOverlay?: Snippet<[api: JitsiMeetExternalAPI]>;
|
||||
/**
|
||||
* 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;
|
||||
onapiready?: (api: JitsiMeetExternalAPI) => void;
|
||||
/** Callback triggered when the local user joins the meeting. */
|
||||
onjoin?: (ev: VideoConferenceJoinedEvent) => void;
|
||||
/** Callback triggered when the local user leaves the meeting. */
|
||||
onleave?: (ev: VideoConferenceLeftEvent) => void;
|
||||
}
|
||||
|
||||
let {
|
||||
domain = 'https://meet.jit.si',
|
||||
options,
|
||||
class: classValue,
|
||||
children,
|
||||
onready
|
||||
loading,
|
||||
holdLoadingUntilJoined = false,
|
||||
joinedOverlay,
|
||||
onapiready,
|
||||
onjoin,
|
||||
onleave
|
||||
}: Props = $props();
|
||||
|
||||
const src = $derived(`${new URL('external_api.js', domain).toString()}`);
|
||||
|
||||
let container: HTMLDivElement;
|
||||
let ready = $state(false);
|
||||
/** tracks whether the Jitsi API is ready */
|
||||
let apiReady = $state(false);
|
||||
/** tracks whether the local user has joined the meeting */
|
||||
let joinedMeeting = $state(false);
|
||||
/** tracks whether the local user has left the meeting */
|
||||
let leftMeeting = $state(false);
|
||||
/** whether to display the loading state */
|
||||
const showLoading = $derived(holdLoadingUntilJoined ? !joinedMeeting : !apiReady);
|
||||
|
||||
let api = $state<JitsiMeetExternalAPI | null>(null);
|
||||
|
||||
onMount(() => {
|
||||
// check if we have a duplicate container
|
||||
if (document.querySelectorAll('#jitsi-container').length > 1) {
|
||||
if (document.querySelectorAll('#jitsi-iframe').length > 1) {
|
||||
console.warn('Duplicate Jitsi container detected. This may lead to unexpected behavior.');
|
||||
}
|
||||
});
|
||||
|
||||
const onload = () => {
|
||||
/** handles Jitsi script load event */
|
||||
const handleScriptLoad = () => {
|
||||
if (typeof window.JitsiMeetExternalAPI === 'undefined') {
|
||||
console.error('Jitsi Meet External API is not available on the window object.');
|
||||
return;
|
||||
}
|
||||
|
||||
ready = true;
|
||||
apiReady = true;
|
||||
// Initialize the Jitsi Meet External API
|
||||
api = new window.JitsiMeetExternalAPI(new URL(domain).host, {
|
||||
...options,
|
||||
@@ -80,8 +111,18 @@ Instead, use the getAPI() method to interact with the Jitsi instance directly.
|
||||
console.error('Failed to initialize Jitsi Meet External API.');
|
||||
return;
|
||||
}
|
||||
// Call the onready callback if provided
|
||||
onready?.(api);
|
||||
// Call the onapiready callback if provided
|
||||
onapiready?.(api);
|
||||
|
||||
// Attach event listeners for meeting join and leave events
|
||||
api.addListener('videoConferenceJoined', (ev: VideoConferenceJoinedEvent) => {
|
||||
joinedMeeting = true;
|
||||
onjoin?.(ev);
|
||||
});
|
||||
api.addListener('videoConferenceLeft', (ev: VideoConferenceLeftEvent) => {
|
||||
leftMeeting = true;
|
||||
onleave?.(ev);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -94,11 +135,43 @@ Instead, use the getAPI() method to interact with the Jitsi instance directly.
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<script {src} {onload}></script>
|
||||
<script {src} onload={handleScriptLoad}></script>
|
||||
</svelte:head>
|
||||
|
||||
<div id="jitsi-container" bind:this={container} class={[classValue]}>
|
||||
{#if !ready}
|
||||
{@render children?.()}
|
||||
<div class={['jitsi-container', classValue]}>
|
||||
{#if showLoading}
|
||||
<div class="overlay-container">{@render loading?.()}</div>
|
||||
{/if}
|
||||
|
||||
{#if joinedMeeting && api && joinedOverlay}
|
||||
<div class="overlay-container passthrough">{@render joinedOverlay(api)}</div>
|
||||
{/if}
|
||||
|
||||
<div
|
||||
id="jitsi-iframe"
|
||||
bind:this={container}
|
||||
class="fill-container"
|
||||
style={leftMeeting ? 'display: none;' : undefined}
|
||||
></div>
|
||||
</div>
|
||||
|
||||
<style lang="css">
|
||||
.jitsi-container {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.fill-container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
min-width: 0px;
|
||||
}
|
||||
|
||||
.overlay-container {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
}
|
||||
|
||||
.passthrough {
|
||||
pointer-events: none;
|
||||
}
|
||||
</style>
|
||||
|
||||
2
src/lib/jitsi-iframe-api.d.ts
vendored
2
src/lib/jitsi-iframe-api.d.ts
vendored
@@ -1430,6 +1430,7 @@ interface FaceLandmarkDetectedEvent {
|
||||
}
|
||||
|
||||
interface ErrorOccurredEvent {
|
||||
error: {
|
||||
/** Additional error details. */
|
||||
details?: Record<string, unknown>;
|
||||
/** The error message. */
|
||||
@@ -1440,6 +1441,7 @@ interface ErrorOccurredEvent {
|
||||
type: ErrorType;
|
||||
/** Whether this is a fatal error which triggered a reconnect overlay. */
|
||||
isFatal: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
interface KnockingParticipantEvent {
|
||||
|
||||
Reference in New Issue
Block a user