Appearance
Pre-Call Component
- File: src/pages/private/events/[eventId]/_cmps/precall.svelte
Overview
The Pre-Call component manages the pre-meeting setup interface for video conferencing. It handles device selection, audio/video preview, and participant waiting room functionality.
Core Functions
1. Media Device Management
svelte
// Get media stream from devices
export async function getStream(audioSource?: string, videoSource?: string) {
try {
const constraints = {
video: videoSource ? { exact: videoSource } : true,
audio: audioSource ? { exact: audioSource } : true,
};
const stream = await openMediaDevices(constraints);
return { stream, success: true };
} catch (error) {
return { error, success: false };
}
}
// Enumerate available devices
export async function getDevices() {
try {
const devices = await navigator.mediaDevices.enumerateDevices();
return {
audioInput: devices.filter(x => x.kind === "audioinput"),
audioOutput: devices.filter(x => x.kind === "audiooutput"),
videoInput: devices.filter(x => x.kind === "videoinput"),
success: true
};
} catch (error) {
return { error, success: false };
}
}2. Audio Output Management
svelte
export async function attachSinkId(
element: HTMLAudioElement,
sinkId: string
) {
try {
if (typeof element.sinkId !== "undefined") {
await element.setSinkId(sinkId);
return { success: true, message: "Done" };
}
return {
success: false,
message: "Browser does not support output device selection"
};
} catch (error) {
return { success: false, message: error.message || error, error };
}
}State Management
1. Device State
svelte
let cameras = [];
let selectedCamera;
let mics = [];
let selectedMic;
let speakers = [];
let selectedSpeaker;
let isSupported;
let error;2. Stream State
svelte
let localVideoStream: MediaStream;
let localAudioStream: MediaStream;
let cameraOn: boolean;
let micOn: boolean;
let noAudio = true;
let noVideo = true;
let noSpeaker = true;3. Participant State
svelte
export let event = {
title: "Event Title",
startDate: new Date(),
endDate: new Date(),
timeText: null,
};
export let precallUsers = [];
export let incallUsers = [];
export let maxUserCount = 4;Event Handlers
1. Device Control
svelte
async function toggleMic() {
micOn = !micOn;
if (localAudioStream && micOn) return;
if (!localAudioStream && !micOn) return;
if (micOn) {
await getAudioPreview();
if (video) video.muted = false;
} else {
localAudioStream.getTracks().forEach(x => {
x.enabled = false;
x.stop();
});
localAudioStream = null;
if (video) video.muted = true;
}
}
function toggleVideo() {
if (!hasVideo || !localVideoStream) return;
cameraOn = !cameraOn;
localVideoStream.getVideoTracks()
.forEach(track => track.enabled = cameraOn);
}2. Stream Management
svelte
async function start() {
if (localVideoStream) {
localVideoStream.getTracks().forEach(x => x.stop());
}
const videoSource = selectedCamera;
const constraints = {
audio: true,
video: { deviceId: videoSource ? { exact: videoSource } : undefined },
};
// Handle audio and video streams separately
let audioStream: MediaStream;
let videoStream: MediaStream;
try {
// Get audio stream
audioStream = await navigator.mediaDevices.getUserMedia({ audio: true });
noAudio = false;
} catch (e) {
handleAudioError(e);
}
try {
// Get video stream
videoStream = await navigator.mediaDevices.getUserMedia({
video: { deviceId: videoSource ? { exact: videoSource } : undefined },
});
noVideo = false;
} catch (e) {
handleVideoError(e);
}
// Combine streams
const outputTracks = [
...(audioStream?.getTracks() || []),
...(videoStream?.getTracks() || [])
];
const stream = new MediaStream(outputTracks);
await onStream(stream);
}UI Components
svelte
<div class="bg-[#2d4e8c] flex-grow grid place-items-center w-screen h-screen">
<div class="max-w-xl">
<!-- Video Preview -->
<div class="flex" class:justify-around={noVideo}>
<!-- Video Element -->
<div class="flex-grow bg-black mr-2 relative border-gray-400">
<video autoplay bind:this={video} muted />
<!-- Controls -->
<div class="bg-gray-400 absolute left-0 bottom-0 w-full bg-opacity-50">
<!-- Mic and Camera Controls -->
</div>
</div>
<!-- Device Selection -->
<div class="text-gray-400 text-sm">
<!-- Camera Selection -->
<!-- Microphone Selection -->
<!-- Speaker Selection -->
</div>
</div>
<!-- Event Info -->
<div class="text-white text-center mt-8">
<div>{event.title}</div>
<div class="text-sm">{event.timeText}</div>
</div>
<!-- Participant List -->
{#each callUsers as { message, users, countLeft }}
<!-- Participant Avatars -->
{/each}
<!-- Action Buttons -->
<div class="px-4 py-3 sm:px-6 sm:flex sm:flex-row-reverse">
<!-- Cancel and Join Buttons -->
</div>
</div>
</div>Lifecycle Management
svelte
onMount(async () => {
// Load event data
const ret = await getEvent(eventId);
if (!ret.success) {
showError(ret.message);
return;
}
event = { ...ret.data };
// Initialize media devices
try {
await start();
await getAudioPreview();
await refreshDevices();
if (!error) autoRefreshDevices();
} catch (e) {
console.log(e);
return;
}
});
onDestroy(() => {
if (!error) {
navigator.mediaDevices.removeEventListener("devicechange", refreshDevices);
}
});