In virtual healthcare, a video consultation is the direct link between a patient and their clinical care team. Unlike consumer video apps, a clinical telehealth platform cannot afford connection failures or permission confusion. A patient struggling to authorize their camera or audio during their scheduled slot is not just an inconvenience—it represents lost clinical time and delayed patient care.

To guarantee a seamless experience, modern patient portals must implement an advanced, resilient WebRTC Pre-Call Diagnostic suite. In this article, we will examine how our telehealth patient platform constructs a comprehensive pre-call checking pipeline in React, leveraging navigator.mediaDevices for camera and microphone testing, the Web Audio API for speaker verification, and Medplum's FHIR Binary API for pre-consultation file-sharing.


Diagnostic Pipeline Architecture

Before allowing the patient to click "Join Consultation," the portal guides them through a four-stage environment checklist:

  1. Permission Harvesting: Requests camera and microphone authorization programmatically, capturing the active streams.
  2. Visual Loopback Feed: Maps the local camera stream to a HTML5 video loopback component so patients can verify lighting and angles.
  3. Web Audio Proximity Check: Runs an in-memory sound generation test using the Web Audio API to verify the speakers without relying on continuous loud sound effects.
  4. FHIR File-Sharing Sync: Allows patients to drag-and-drop clinical files, instantly syncing them as FHIR Binary resources.

Step 1: Permission Acquisition & Camera Preview

To acquire local media, we use the standard WebRTC MediaDevices API. A common mistake is requesting permissions without closing active tracks immediately after the test, causing hardware lockouts when the actual calling library attempts to launch. We avoid this by capturing the stream for preview and exposing it safely:

const runDeviceTests = useCallback(async (): Promise<void> => {
    // 1. Request WebRTC video and audio streams
    try {
        const stream = await navigator.mediaDevices.getUserMedia({ 
            video: true, 
            audio: true 
        });
        setCameraStream(stream);
        setDeviceTests(prev => ({ 
            ...prev, 
            camera: 'passed', 
            microphone: 'passed' 
        }));
    } catch (err) {
        console.error('WebRTC hardware access denied:', err);
        setDeviceTests(prev => ({
            ...prev,
            camera: 'failed',
            microphone: 'failed',
        }));
    }
}, []);

Inside the render tree, we bind the active stream directly to the source object of a standard HTML <video> element to render a real-time preview:

useEffect(() => {
    if (videoRef.current && cameraStream) {
        videoRef.current.srcObject = cameraStream;
    }
}, [cameraStream]);

Step 2: Speaker Diagnostics with Web Audio API

Testing speaker outputs programmatically without playing annoying media loops is exceptionally challenging. We solve this by initializing a localized AudioContext, creating a short, silent high-frequency oscillator signal, and connecting it briefly to the system's active audio destination to verify the output hardware pathway is active and unblocked:

const testSpeakers = async (): Promise<boolean> => {
    try {
        // Initialize Web Audio context
        const audioCtx = new AudioContext();
        const oscillator = audioCtx.createOscillator();
        
        // Connect oscillator to destination output
        oscillator.connect(audioCtx.destination);
        oscillator.start();
        
        // Stop instantly after a sub-second pulse
        oscillator.stop(audioCtx.currentTime + 0.01);
        await audioCtx.close();
        return true;
    } catch (err) {
        console.error('AudioContext diagnostic failed:', err);
        return false;
    }
};

Step 3: Pre-Call FHIR Document Uploads

To let patients share test results or photos before the meeting starts, we integrate a secure file-upload zone. The platform converts uploaded files directly into FHIR Binary resources using the medplum.createBinary endpoint, updating the client portal dynamically:

const handleFileUpload = useCallback(async (files: FileList): Promise<void> => {
    if (!medplum) return;

    for (let i = 0; i < files.length; i++) {
        const file = files[i];
        const tempId = `temp-${Date.now()}-${i}`;

        // Inject uploading status placeholder
        setUploadedFiles(prev => [...prev, {
            id: tempId,
            name: file.name,
            contentType: file.type,
            uploading: true,
        }]);

        try {
            // Upload to Medplum FHIR Binary storage
            const binary = await medplum.createBinary(file, file.name, file.type);
            setUploadedFiles(prev => prev.map(f =>
                f.id === tempId
                    ? { ...f, id: binary.id || tempId, uploading: false }
                    : f
            ));
        } catch (err) {
            console.error('Medplum Binary creation failed:', err);
            setUploadedFiles(prev => prev.filter(f => f.id !== tempId));
        }
    }
}, [medplum]);

Diagnostic Best Practices

  1. Track Disposal: Always shut down active preview tracks programmatically (by invoking track.stop() on all stream tracks) before transitioning the user into the active video composite to avoid device conflicts.
  2. Silent Audio Verification: Use sub-second oscillator checks to safely verify system speaker configurations without introducing loud, startling sound effects.
  3. Drag-and-Drop Zones: Always prevent default browser routing on dragover/drop events in file zones to guarantee a premium upload experience.

Implementing structured WebRTC hardware diagnostics and Medplum FHIR file uploads ensures patients arrive at their clinical consultation with working gear and synchronized medical history, maximizing the efficiency of every clinical encounter.