Skip to content

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
    └── ParticipantSettings

Core 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(),
  });
};

Released under the MIT License.