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).
|
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.
|
||||||
|
|
||||||
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
|
|
||||||
```
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@svelte-toolkit/jitsi",
|
"name": "@svelte-toolkit/jitsi",
|
||||||
"version": "0.0.3",
|
"version": "0.1.1",
|
||||||
|
"license": "BSD-3-Clause",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://gitea.auvem.com/svelte-toolkit/jitsi.git"
|
"url": "https://gitea.auvem.com/svelte-toolkit/jitsi.git"
|
||||||
|
|||||||
@@ -1,18 +1,22 @@
|
|||||||
<!-- @component
|
<!-- @component
|
||||||
The Jitsi component is a lightweight wrapper that manages loading the Jitsi Meet External API
|
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
|
and joining a Jitsi meeting room. It accepts configuration options and several callbacks that
|
||||||
when the API is ready for use.
|
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
|
WARNING: While properties are reactive, in most cases changing them after the API has
|
||||||
External API, changing properties after initialization will NOT update the Jitsi instance.
|
initialized will trigger a complete re-render of the component, which will destroy the
|
||||||
Instead, use the getAPI() method to interact with the Jitsi instance directly.
|
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">
|
<script lang="ts">
|
||||||
import { onMount, type Snippet } from 'svelte';
|
import { onMount, type Snippet } from 'svelte';
|
||||||
import type {
|
import type {
|
||||||
JitsiMeetExternalAPIOptions,
|
JitsiMeetExternalAPIOptions,
|
||||||
JitsiMeetExternalAPI
|
JitsiMeetExternalAPI,
|
||||||
|
VideoConferenceJoinedEvent,
|
||||||
|
VideoConferenceLeftEvent
|
||||||
} from '$lib/jitsi-iframe-api.d.ts';
|
} from '$lib/jitsi-iframe-api.d.ts';
|
||||||
import type { ClassValue } from 'svelte/elements';
|
import type { ClassValue } from 'svelte/elements';
|
||||||
|
|
||||||
@@ -32,45 +36,72 @@ Instead, use the getAPI() method to interact with the Jitsi instance directly.
|
|||||||
*/
|
*/
|
||||||
class?: ClassValue;
|
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
|
* Callback function that is called when the Jitsi API is loaded. Can
|
||||||
* be used to provide a loading indicator until the API is ready.
|
* be used to provide a loading indicator until the API is ready.
|
||||||
* @param api The Jitsi Meet External API instance.
|
* @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 {
|
let {
|
||||||
domain = 'https://meet.jit.si',
|
domain = 'https://meet.jit.si',
|
||||||
options,
|
options,
|
||||||
class: classValue,
|
class: classValue,
|
||||||
children,
|
loading,
|
||||||
onready
|
holdLoadingUntilJoined = false,
|
||||||
|
joinedOverlay,
|
||||||
|
onapiready,
|
||||||
|
onjoin,
|
||||||
|
onleave
|
||||||
}: Props = $props();
|
}: 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);
|
/** 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);
|
let api = $state<JitsiMeetExternalAPI | null>(null);
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
// check if we have a duplicate container
|
// 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.');
|
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') {
|
if (typeof window.JitsiMeetExternalAPI === 'undefined') {
|
||||||
console.error('Jitsi Meet External API is not available on the window object.');
|
console.error('Jitsi Meet External API is not available on the window object.');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
ready = true;
|
apiReady = 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,
|
||||||
@@ -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.');
|
console.error('Failed to initialize Jitsi Meet External API.');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Call the onready callback if provided
|
// Call the onapiready callback if provided
|
||||||
onready?.(api);
|
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>
|
</script>
|
||||||
|
|
||||||
<svelte:head>
|
<svelte:head>
|
||||||
<script {src} {onload}></script>
|
<script {src} onload={handleScriptLoad}></script>
|
||||||
</svelte:head>
|
</svelte:head>
|
||||||
|
|
||||||
<div id="jitsi-container" bind:this={container} class={[classValue]}>
|
<div class={['jitsi-container', classValue]}>
|
||||||
{#if !ready}
|
{#if showLoading}
|
||||||
{@render children?.()}
|
<div class="overlay-container">{@render loading?.()}</div>
|
||||||
{/if}
|
{/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>
|
</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 {
|
interface ErrorOccurredEvent {
|
||||||
|
error: {
|
||||||
/** Additional error details. */
|
/** Additional error details. */
|
||||||
details?: Record<string, unknown>;
|
details?: Record<string, unknown>;
|
||||||
/** The error message. */
|
/** The error message. */
|
||||||
@@ -1440,6 +1441,7 @@ interface ErrorOccurredEvent {
|
|||||||
type: ErrorType;
|
type: ErrorType;
|
||||||
/** Whether this is a fatal error which triggered a reconnect overlay. */
|
/** Whether this is a fatal error which triggered a reconnect overlay. */
|
||||||
isFatal: boolean;
|
isFatal: boolean;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
interface KnockingParticipantEvent {
|
interface KnockingParticipantEvent {
|
||||||
|
|||||||
Reference in New Issue
Block a user