Appearance
Call View Component
- File: src/components/app/call/callView.svelte
Overview
The Call View component serves as the central interface for video conferencing functionality. It integrates with the Pexip video conferencing platform to provide real-time video/audio communication, screen sharing, recording, and participant management.
Component Structure
1. Component Hierarchy
text
CallView
├── Navigators
├── RecordingAlert
├── PexipCallPanel
│ ├── VideoGrid
│ ├── ParticipantList
│ └── CallControls
└── ControlPanel
├── DeviceSettings
├── Gallery
└── ParticipantSettingsCore Features
1. Call Management
svelte
// Call Initialization
async function start() {
pexipConfig = new PexipClientConfig();
pexipConfig.audio_source = noAudio ? false : (selectedMic || null);
pexipConfig.video_source = noVideo ? false : (selectedCamera || null);
pexipConfig.call_tag = user.id.toString();
pexip.makeCall(pexipConfig, config, user.name, null, null);
}
// Call Termination
function onEndCall() {
if (me && me.role === "chair") {
if (pexip.participants.length > 1 &&
!pexip.participants.some(x => x.role === "chair" && x.uuid != me.uuid)) {
showExitDialog = true;
return;
}
}
pexip?.leaveRoom();
}2. Media Stream Management
svelte
// Local Stream Configuration
async function getLocalStream() {
let constraints: any = {};
// Video Constraints
if (!noVideo) {
constraints.video = {
mandatory: { minHeight: "720" },
deviceId: selectedCamera ? { exact: selectedCamera } : null
};
}
// Audio Constraints
if (!noAudio) {
constraints.audio = {
deviceId: selectedMic ? { exact: selectedMic } : "default",
autoGainControl: !enableLowFrequencies,
echoCancellation: !enableLowFrequencies,
noiseSuppression: !enableLowFrequencies
};
}
return await navigator.mediaDevices.getUserMedia(constraints);
}
// Stream Event Handling
async function callback(kind: string, value: any) {
switch (kind) {
case "remoteStream":
remoteStream = value;
break;
case "localStream":
userStream = value;
break;
case "presentationStream":
presentationStream = value;
break;
// ... other stream cases
}
}3. Device Control
svelte
// Camera Control
async function onCameraChanged({ detail }) {
if (detail.selectedCamera === camera) return;
getLocalStream()
.then((stream) => {
const [videoTrack] = stream.getVideoTracks();
const sender = pexip.p.call.pc
.getSenders()
.find((x) => x.track.kind === videoTrack.kind);
sender.replaceTrack(videoTrack);
camera = detail.selectedCamera;
userStream = stream;
});
}
// Microphone Control
function onMicChanged({ detail }) {
getLocalStream()
.then((stream) => {
const [audioTrack] = stream.getAudioTracks();
if (enableLowFrequencies) {
audioTrack.contentHint = "music"
pexip.disableStudioSound()
} else {
audioTrack.contentHint = ""
pexip.enableStudioSound()
}
const sender = pexip.p.call.pc
.getSenders()
.find((x) => x.track.kind === audioTrack.kind);
sender.replaceTrack(audioTrack);
mic = detail.selectedMic;
userStream = stream;
});
}4. Screen Sharing
svelte
async function onToggleScreenShare() {
if (localPresentationStream) {
localPresentationStream.getTracks().forEach((x) => x.stop());
localPresentationStream = null;
pexip.stopPresentation();
} else {
try {
if (presentationStream && !overrideScreenshare) {
confirmScreenshare = true;
return;
}
const screen = await navigator.mediaDevices.getDisplayMedia({
video: true,
audio: false,
});
localPresentationStream = screen;
pexip.startPresentation(screen);
} catch (e) {
console.log("screen capture error", e);
}
}
}5. Recording Management
svelte
async function beginRecording(cnt: number) {
recordingState = RecordingState.initializing;
var ret = await startRecording(event.id, user.id);
if (ret.success) {
recordingState = RecordingState.starting;
var pexipRet = await pexip.dial(ret.data);
if (pexipRet?.status !== "success") {
ret = await stopRecording(event.id);
return {
success: false,
message: "Unable to start the recording at the moment.",
};
}
recordingState = RecordingState.started;
return { success: true, message: "Recording started successfully" };
}
}State Management
1. Call State
svelte
// Core State
let pexip: PexipClient;
let me: IParticipant;
let participants: IParticipant[] = [];
// Stream State
let userStream: MediaStream;
let remoteStream: MediaStream;
let presentationStream: MediaStream;
let localPresentationStream: MediaStream;
// Device State
let camera = selectedCamera;
let mic = selectedMic;
let isPeripheralConnected = false;
let isPeripheralLoading = false;2. UI State
svelte
// Control Panel State
let controlPanelMode = Mode.None;
// Modal States
let showExitDialog = false;
let showRequestHostAccess = false;
let confirmScreenshare = false;
// Recording State
let recordingState = RecordingState.none;Event Handling
1. Device Events
svelte
// Audio Control
function onAudioOff() {
pexip.muteAudio();
}
function onAudioOn() {
pexip.unmuteAudio();
}
// Video Control
async function onVideoOff() {
cameraOn = false;
await pexip.muteVideo();
}
async function onVideoOn() {
cameraOn = true;
await pexip.unmuteVideo();
}2. Participant Events
svelte
async function toggleRaiseHand() {
if (!me) return;
if (me.buzz_time) {
await pexip.lowerHand();
} else {
await pexip.raiseHand();
}
}UI Implementation
1. Main Layout
svelte
<div class="w-full h-full flex">
<!-- Navigators -->
<!-- Recording Alerts -->
<!-- Video Conference Panel -->
<div class="flex-grow">
<PexipCallPanel
{pexip}
{me}
{event}
{userStream}
{remoteStream}
{presentationStream}
{localPresentationStream}
videoOn={cameraOn}
{isPeripheralConnected}
audioOn={micOn}
{isRecording}
{isPresenting}
{noVideo}
{noAudio}
{isPeripheralLoading}
{contacts}
{peripherals}
<!-- Event Handlers -->
/>
</div>
<!-- Control Panel -->
<div>
<ControlPanel
{pexip}
{me}
bind:mode={controlPanelMode}
{participants}
{contacts}
{event}
bind:selectedCamera
bind:selectedSpeaker
bind:selectedMic
bind:enableLowFrequencies
bind:activeShareId
bind:files={sharedFiles}
bind:selectedPeripheralId
bind:peripherals
<!-- Event Handlers -->
/>
</div>
</div>2. Modal Dialogs
svelte
<!-- Exit Dialog -->
{#if showExitDialog}
<Modal
title="Exit Meeting"
bind:open={showExitDialog}
on:close={(_) => (showExitDialog = false)}
showCloseButton={true}
buttons={exitButtons}
dangerous
showIcon={false}
closeOnOutsideClick
>
<p>
If you leave, the meeting will end shortly because you are the only host.
If you'd like the meeting to continue, please promote someone to host
</p>
</Modal>
{/if}
<!-- Host Access Dialog -->
{#if showRequestHostAccess}
<Modal
title="Request Host Access"
bind:open={showRequestHostAccess}
on:close={(_) => (showRequestHostAccess = false)}
showCloseButton={true}
showIcon={false}
buttons={requestHostButtons}
closeOnOutsideClick
>
<p>Do you want to request for Host access in the meeting?</p>
<p class="mt-4">
This will give you access to features like
<span class="font-bold">Recording</span> and
<span class="font-bold">Remote Control</span>
</p>
</Modal>
{/if}Lifecycle Management
svelte
// Component Initialization
onMount(async () => {
pexip = new PexipClient(initPexip(), config, callback, servers);
start();
await startGallery();
});
// Component Cleanup
onDestroy(async () => {
pexip?.leaveRoom();
await stopGallery();
});
// Window Unload Handler
window.onunload = async (e: BeforeUnloadEvent) => {
worker.postMessage({
action: "release",
request: pexip.releaseTokenRequest(),
});
};