Svelte Integration Guide
This guide shows how to integrate MediaFox with Svelte applications for a reactive Media Player experience.
Installation
First, install MediaFox and mediabunny in your Svelte project:
bash
bun add @mediafox/core mediabunny
# or
npm install @mediafox/core mediabunny
# or
yarn add @mediafox/core mediabunnyBasic Svelte Component
Here's a basic Svelte component that wraps MediaFox:
svelte
<!-- VideoPlayer.svelte -->
<script lang="ts">
import { onMount, onDestroy } from 'svelte';
import { writable } from 'svelte/store';
import { MediaFox, type PlayerState } from '@mediafox/core';
export let src: string;
export let autoplay = false;
export let muted = false;
let canvasElement: HTMLCanvasElement;
let player: MediaFox | null = null;
// Reactive stores for player state
const playerState = writable<PlayerState | null>(null);
const isPlaying = writable(false);
const currentTime = writable(0);
const duration = writable(0);
const volume = writable(1);
onMount(async () => {
if (canvasElement) {
player = new MediaFox({
canvas: canvasElement,
autoplay,
muted
});
// Subscribe to state changes
player.store.subscribe((state) => {
playerState.set(state);
isPlaying.set(state.playing);
currentTime.set(state.currentTime);
duration.set(state.duration);
volume.set(state.volume);
});
// Load the source
await player.load(src);
}
});
onDestroy(() => {
if (player) {
player.destroy();
}
});
function togglePlay() {
if (player) {
if ($isPlaying) {
player.pause();
} else {
player.play();
}
}
}
function seek(event: Event) {
if (player) {
const target = event.target as HTMLInputElement;
const time = parseFloat(target.value);
player.seek(time);
}
}
function changeVolume(event: Event) {
if (player) {
const target = event.target as HTMLInputElement;
const vol = parseFloat(target.value);
player.volume = vol;
}
}
// Reactive statement to update src
$: if (player && src) {
player.load(src);
}
</script>
<div class="video-player">
<canvas bind:this={canvasElement}></canvas>
<div class="controls">
<button on:click={togglePlay}>
{$isPlaying ? 'Pause' : 'Play'}
</button>
<input
type="range"
min="0"
max={$duration}
value={$currentTime}
on:input={seek}
class="seek-bar"
/>
<span class="time">
{Math.floor($currentTime)}s / {Math.floor($duration)}s
</span>
<input
type="range"
min="0"
max="1"
step="0.1"
value={$volume}
on:input={changeVolume}
class="volume-bar"
/>
</div>
</div>
<style>
.video-player {
display: flex;
flex-direction: column;
gap: 10px;
}
canvas {
width: 100%;
height: auto;
background: black;
}
.controls {
display: flex;
align-items: center;
gap: 10px;
padding: 10px;
background: #f0f0f0;
border-radius: 5px;
}
.seek-bar {
flex: 1;
}
.volume-bar {
width: 100px;
}
button {
padding: 8px 16px;
background: #007bff;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}
button:hover {
background: #0056b3;
}
</style>Advanced Svelte Component with Stores
For more complex applications, you can create dedicated stores for different aspects:
svelte
<!-- AdvancedVideoPlayer.svelte -->
<script lang="ts">
import { onMount, onDestroy } from 'svelte';
import { createPlayerStore } from './stores/playerStore';
import { createControlsStore } from './stores/controlsStore';
import VideoControls from './VideoControls.svelte';
import VideoInfo from './VideoInfo.svelte';
export let src: string;
export let config: any = {};
let canvasElement: HTMLCanvasElement;
const playerStore = createPlayerStore();
const controlsStore = createControlsStore();
onMount(async () => {
if (canvasElement) {
await playerStore.initialize(canvasElement, config);
await playerStore.load(src);
}
});
onDestroy(() => {
playerStore.destroy();
});
// Reactive statement for source changes
$: if ($playerStore.player && src) {
playerStore.load(src);
}
</script>
<div class="advanced-player">
<div class="video-container">
<canvas bind:this={canvasElement}></canvas>
{#if $controlsStore.showControls}
<VideoControls
{playerStore}
{controlsStore}
/>
{/if}
</div>
<VideoInfo
state={$playerStore.state}
tracks={$playerStore.tracks}
/>
</div>
<style>
.advanced-player {
position: relative;
width: 100%;
}
.video-container {
position: relative;
width: 100%;
background: black;
}
canvas {
width: 100%;
height: auto;
display: block;
}
</style>Custom Stores
Create dedicated stores for better state management:
typescript
// stores/playerStore.ts
import { writable, derived } from 'svelte/store';
import { MediaFox, type PlayerState } from '@mediafox/core';
export function createPlayerStore() {
const { subscribe, set, update } = writable({
player: null as MediaFox | null,
state: null as PlayerState | null,
tracks: [] as any[],
loading: false,
error: null as Error | null
});
return {
subscribe,
async initialize(canvas: HTMLCanvasElement, config: any = {}) {
update(store => ({ ...store, loading: true }));
try {
const player = new MediaFox({
canvas,
...config
});
player.store.subscribe((state) => {
update(store => ({ ...store, state }));
});
player.on('tracksChanged', (tracks) => {
update(store => ({ ...store, tracks }));
});
update(store => ({
...store,
player,
loading: false
}));
} catch (error) {
update(store => ({
...store,
error: error as Error,
loading: false
}));
}
},
async load(src: string) {
update(store => {
if (store.player) {
store.player.load(src);
}
return store;
});
},
play() {
update(store => {
if (store.player) {
store.player.play();
}
return store;
});
},
pause() {
update(store => {
if (store.player) {
store.player.pause();
}
return store;
});
},
seek(time: number) {
update(store => {
if (store.player) {
store.player.seek(time);
}
return store;
});
},
destroy() {
update(store => {
if (store.player) {
store.player.destroy();
}
return { ...store, player: null };
});
}
};
}typescript
// stores/controlsStore.ts
import { writable } from 'svelte/store';
export function createControlsStore() {
const { subscribe, set, update } = writable({
showControls: true,
fullscreen: false,
volume: 1,
muted: false,
playbackRate: 1
});
return {
subscribe,
toggleControls() {
update(store => ({
...store,
showControls: !store.showControls
}));
},
toggleFullscreen() {
update(store => ({
...store,
fullscreen: !store.fullscreen
}));
},
setVolume(volume: number) {
update(store => ({ ...store, volume }));
},
toggleMute() {
update(store => ({
...store,
muted: !store.muted
}));
},
setPlaybackRate(rate: number) {
update(store => ({ ...store, playbackRate: rate }));
}
};
}Derived Stores
Use derived stores for computed values:
typescript
// stores/derivedStores.ts
import { derived } from 'svelte/store';
import type { Readable } from 'svelte/store';
export function createDerivedStores(playerStore: any): {
isPlaying: Readable<boolean>;
progress: Readable<number>;
timeRemaining: Readable<number>;
formattedTime: Readable<string>;
} {
const isPlaying = derived(
playerStore,
$playerStore => $playerStore.state?.playing ?? false
);
const progress = derived(
playerStore,
$playerStore => {
const state = $playerStore.state;
if (!state || state.duration === 0) return 0;
return (state.currentTime / state.duration) * 100;
}
);
const timeRemaining = derived(
playerStore,
$playerStore => {
const state = $playerStore.state;
if (!state) return 0;
return state.duration - state.currentTime;
}
);
const formattedTime = derived(
playerStore,
$playerStore => {
const state = $playerStore.state;
if (!state) return '0:00 / 0:00';
const current = formatTime(state.currentTime);
const total = formatTime(state.duration);
return `${current} / ${total}`;
}
);
return {
isPlaying,
progress,
timeRemaining,
formattedTime
};
}
function formatTime(seconds: number): string {
const mins = Math.floor(seconds / 60);
const secs = Math.floor(seconds % 60);
return `${mins}:${secs.toString().padStart(2, '0')}`;
}Event Handling
Handle player events reactively:
svelte
<script lang="ts">
import { onMount } from 'svelte';
import { MediaFox } from '@mediafox/core';
let player: MediaFox;
let events: string[] = [];
onMount(() => {
player = new MediaFox({ canvas: canvasElement });
// Listen to all events
player.on('play', () => addEvent('Play started'));
player.on('pause', () => addEvent('Playback paused'));
player.on('seeking', () => addEvent('Seeking...'));
player.on('seeked', () => addEvent('Seek completed'));
player.on('ended', () => addEvent('Playback ended'));
player.on('error', (error) => addEvent(`Error: ${error.message}`));
player.on('loadstart', () => addEvent('Loading started'));
player.on('loadeddata', () => addEvent('Data loaded'));
player.on('canplay', () => addEvent('Can start playing'));
player.on('timeupdate', (time) => {
// Throttle time updates
if (Math.floor(time) !== Math.floor(lastTime)) {
addEvent(`Time: ${Math.floor(time)}s`);
lastTime = time;
}
});
});
function addEvent(message: string) {
events = [...events.slice(-9), `${new Date().toLocaleTimeString()}: ${message}`];
}
let lastTime = 0;
let canvasElement: HTMLCanvasElement;
</script>
<div>
<canvas bind:this={canvasElement}></canvas>
<div class="events-log">
<h3>Events Log</h3>
{#each events as event}
<div class="event">{event}</div>
{/each}
</div>
</div>SvelteKit Integration
For SvelteKit applications, handle SSR properly:
svelte
<!-- +page.svelte -->
<script lang="ts">
import { browser } from '$app/environment';
import { onMount } from 'svelte';
import VideoPlayer from '$lib/components/VideoPlayer.svelte';
let mounted = false;
onMount(() => {
mounted = true;
});
</script>
{#if browser && mounted}
<VideoPlayer
src="/video.mp4"
autoplay={false}
muted={true}
/>
{:else}
<div class="loading">Loading Media Player...</div>
{/if}TypeScript Support
MediaFox provides full TypeScript support. Create typed components:
svelte
<script lang="ts">
import type { MediaFox, PlayerState, PlayerEvents } from '@mediafox/core';
interface PlayerProps {
src: string;
autoplay?: boolean;
muted?: boolean;
onStateChange?: (state: PlayerState) => void;
onEvent?: (event: keyof PlayerEvents, data: any) => void;
}
export let src: PlayerProps['src'];
export let autoplay: PlayerProps['autoplay'] = false;
export let muted: PlayerProps['muted'] = false;
export let onStateChange: PlayerProps['onStateChange'] = undefined;
export let onEvent: PlayerProps['onEvent'] = undefined;
let player: MediaFox;
let state: PlayerState | null = null;
// Rest of component logic...
</script>Best Practices
- Store Management: Use dedicated stores for different concerns (player, controls, UI state)
- Memory Management: Always destroy the player in
onDestroy - SSR: Check for browser environment before initializing
- Error Handling: Implement proper error boundaries and user feedback
- Performance: Use derived stores for computed values to avoid unnecessary reactivity
- TypeScript: Leverage MediaFox's type definitions for better development experience
Common Patterns
Loading States
svelte
<script lang="ts">
let loading = true;
let error: Error | null = null;
async function loadVideo() {
try {
loading = true;
await player.load(src);
} catch (err) {
error = err as Error;
} finally {
loading = false;
}
}
</script>
{#if loading}
<div class="spinner">Loading...</div>
{:else if error}
<div class="error">Error: {error.message}</div>
{:else}
<!-- Player content -->
{/if}Keyboard Shortcuts
svelte
<script lang="ts">
function handleKeydown(event: KeyboardEvent) {
if (!player) return;
switch (event.code) {
case 'Space':
event.preventDefault();
player.playing ? player.pause() : player.play();
break;
case 'ArrowLeft':
event.preventDefault();
player.seek(player.currentTime - 10);
break;
case 'ArrowRight':
event.preventDefault();
player.seek(player.currentTime + 10);
break;
}
}
</script>
<svelte:window on:keydown={handleKeydown} />This guide provides the foundation for integrating MediaFox with Svelte applications, offering both simple and advanced patterns for different use cases.
