# Whereby to Daily.co Migration Feasibility Analysis ## Executive Summary After analysis of the current Whereby integration and Daily.co's capabilities, migrating to Daily.co is technically feasible. The migration can be done in phases: 1. **Phase 1**: Feature parity with current implementation (standard cloud recording) 2. **Phase 2**: Enhanced capabilities with raw-tracks recording for improved diarization ### Current Implementation Analysis Based on code review: - **Webhook handling**: The current webhook handler (`server/reflector/views/whereby.py`) only tracks `num_clients`, not individual participants - **Focus management**: The frontend has 70+ lines managing focus between Whereby embed and consent dialog - **Participant tracking**: No participant names or IDs are captured in the current implementation - **Recording type**: Cloud recording to S3 in MP4 format with mixed audio ### Migration Approach **Phase 1**: 1:1 feature replacement maintaining current functionality: - Standard cloud recording (same as current Whereby implementation) - Same recording workflow: Video platform → S3 → Reflector processing - No changes to existing diarization or transcription pipeline **Phase 2**: Enhanced capabilities (future implementation): - Raw-tracks recording for speaker-separated audio - Improved diarization with participant-to-audio mapping - Per-participant transcription accuracy ## Current Whereby Integration Analysis ### Backend Integration #### Core API Module (`server/reflector/whereby.py`) - **Meeting Creation**: Creates rooms with S3 recording configuration - **Session Monitoring**: Tracks meeting status via room sessions API - **Logo Upload**: Handles branding for meetings - **Key Functions**: ```python create_meeting(room_name, logo_s3_url) -> dict monitor_room_session(meeting_link) -> dict upload_logo(file_stream, content_type) -> str ``` #### Webhook Handler (`server/reflector/views/whereby.py`) - **Endpoint**: `/v1/whereby_webhook` - **Security**: HMAC signature validation - **Events Handled**: - `room.participant.joined` - `room.participant.left` - **Pain Point**: Delay between actual join/leave and webhook delivery #### Room Management (`server/reflector/views/rooms.py`) - Creates meetings via Whereby API - Stores meeting data in database - Manages recording lifecycle ### Frontend Integration #### Main Room Component (`www/app/[roomName]/page.tsx`) - Uses `@whereby.com/browser-sdk` (v3.3.4) - Implements custom `` element - Handles recording consent - Focus management for accessibility #### Configuration - Environment Variables: - `WHEREBY_API_URL`, `WHEREBY_API_KEY`, `WHEREBY_WEBHOOK_SECRET` - AWS S3 credentials for recordings - Recording workflow: Whereby → S3 → Reflector processing pipeline ## Daily.co Capabilities Analysis ### REST API Features #### Room Management ``` POST /rooms - Create room with configuration GET /rooms/:name/presence - Real-time participant data POST /rooms/:name/recordings/start - Start recording ``` #### Recording Options ```json { "enable_recording": "raw-tracks" // Key feature for diarization } ``` #### Webhook Events - `participant.joined` / `participant.left` - `waiting-participant.joined` / `waiting-participant.left` - `recording.started` / `recording.ready-to-download` - `recording.error` ### React SDK (@daily-co/daily-react) #### Modern Hook-based Architecture ```jsx // Participant tracking const participantIds = useParticipantIds({ filter: 'remote' }); const [username, videoState] = useParticipantProperty(id, ['user_name', 'tracks.video.state']); // Recording management const { isRecording, startRecording, stopRecording } = useRecording(); // Real-time participant data const participants = useParticipants(); ``` ## Feature Comparison | Feature | Whereby | Daily.co | |---------|---------|----------| | **Room Creation** | REST API | REST API | | **Recording Types** | Cloud (MP4) | Cloud (MP4), Local, Raw-tracks | | **S3 Integration** | Direct upload | Direct upload with IAM roles | | **Frontend Integration** | Custom element | React hooks or iframe | | **Webhooks** | HMAC verified | HMAC verified | | **Participant Data** | Via webhooks | Via webhooks + Presence API | | **Recording Trigger** | Automatic/manual | Automatic/manual | ## Migration Plan ### Phase 1: Backend API Client #### 1.1 Create Daily.co API Client (`server/reflector/daily.py`) ```python from datetime import datetime import httpx from reflector.db.rooms import Room from reflector.settings import settings class DailyClient: def __init__(self): self.base_url = "https://api.daily.co/v1" self.headers = { "Authorization": f"Bearer {settings.DAILY_API_KEY}", "Content-Type": "application/json" } self.timeout = 10 async def create_meeting(self, room_name_prefix: str, end_date: datetime, room: Room) -> dict: """Create a Daily.co room matching current Whereby functionality.""" data = { "name": f"{room_name_prefix}-{datetime.now().strftime('%Y%m%d%H%M%S')}", "privacy": "private" if room.is_locked else "public", "properties": { "enable_recording": "cloud", # Same as Whereby "enable_chat": True, "enable_screenshare": True, "start_video_off": False, "start_audio_off": False, "exp": int(end_date.timestamp()), "enable_recording_ui": False, # We handle consent ourselves } } # Configure S3 bucket for recordings (same as Whereby) if room.recording_type == "cloud": data["properties"]["recording_bucket"] = { "bucket_name": settings.AWS_S3_BUCKET, "bucket_region": settings.AWS_REGION, "assume_role_arn": settings.AWS_DAILY_ROLE_ARN, "path": f"recordings/{data['name']}" } async with httpx.AsyncClient() as client: response = await client.post( f"{self.base_url}/rooms", headers=self.headers, json=data, timeout=self.timeout ) response.raise_for_status() room_data = response.json() # Return in Whereby-compatible format return { "roomUrl": room_data["url"], "hostRoomUrl": room_data["url"] + "?t=" + room_data["config"]["token"], "roomName": room_data["name"], "meetingId": room_data["id"] } async def get_room_sessions(self, room_name: str) -> dict: """Get room session data (similar to Whereby's insights).""" async with httpx.AsyncClient() as client: response = await client.get( f"{self.base_url}/rooms/{room_name}", headers=self.headers, timeout=self.timeout ) response.raise_for_status() return response.json() ``` #### 1.2 Update Webhook Handler (`server/reflector/views/daily.py`) ```python import hmac import json from datetime import datetime from hashlib import sha256 from fastapi import APIRouter, HTTPException, Request from pydantic import BaseModel from reflector.db.meetings import meetings_controller from reflector.settings import settings router = APIRouter() class DailyWebhookEvent(BaseModel): type: str id: str ts: int data: dict def verify_daily_webhook(body: bytes, signature: str) -> bool: """Verify Daily.co webhook signature.""" expected = hmac.new( settings.DAILY_WEBHOOK_SECRET.encode(), body, sha256 ).hexdigest() return hmac.compare_digest(expected, signature) @router.post("/daily") async def daily_webhook(event: DailyWebhookEvent, request: Request): # Verify webhook signature body = await request.body() signature = request.headers.get("X-Daily-Signature", "") if not verify_daily_webhook(body, signature): raise HTTPException(status_code=401, detail="Invalid webhook signature") # Handle participant events if event.type == "participant.joined": meeting = await meetings_controller.get_by_room_name(event.data["room_name"]) if meeting: # Update participant info immediately await meetings_controller.add_participant( meeting.id, participant_id=event.data["participant"]["user_id"], name=event.data["participant"]["user_name"], joined_at=datetime.fromtimestamp(event.ts / 1000) ) elif event.type == "participant.left": meeting = await meetings_controller.get_by_room_name(event.data["room_name"]) if meeting: await meetings_controller.remove_participant( meeting.id, participant_id=event.data["participant"]["user_id"], left_at=datetime.fromtimestamp(event.ts / 1000) ) elif event.type == "recording.ready-to-download": # Process cloud recording (same as Whereby) meeting = await meetings_controller.get_by_room_name(event.data["room_name"]) if meeting: # Queue standard processing task from reflector.worker.tasks import process_recording process_recording.delay( meeting_id=meeting.id, recording_url=event.data["download_link"], recording_id=event.data["recording_id"] ) return {"status": "ok"} ``` ### Phase 2: Frontend Components #### 2.1 Replace Whereby SDK with Daily React First, update dependencies: ```bash # Remove Whereby yarn remove @whereby.com/browser-sdk # Add Daily.co yarn add @daily-co/daily-react @daily-co/daily-js ``` #### 2.2 New Room Component (`www/app/[roomName]/page.tsx`) ```tsx "use client"; import { useCallback, useEffect, useRef, useState } from "react"; import { DailyProvider, useDaily, useParticipantIds, useRecording, useDailyEvent, useLocalParticipant, } from "@daily-co/daily-react"; import { Box, Button, Text, VStack, HStack, Spinner } from "@chakra-ui/react"; import { toaster } from "../components/ui/toaster"; import useRoomMeeting from "./useRoomMeeting"; import { useRouter } from "next/navigation"; import { notFound } from "next/navigation"; import useSessionStatus from "../lib/useSessionStatus"; import { useRecordingConsent } from "../recordingConsentContext"; import DailyIframe from "@daily-co/daily-js"; // Daily.co Call Interface Component function CallInterface() { const daily = useDaily(); const { isRecording, startRecording, stopRecording } = useRecording(); const localParticipant = useLocalParticipant(); const participantIds = useParticipantIds({ filter: "remote" }); // Real-time participant tracking useDailyEvent("participant-joined", useCallback((event) => { console.log(`${event.participant.user_name} joined the call`); // No need for webhooks - we have immediate access! }, [])); useDailyEvent("participant-left", useCallback((event) => { console.log(`${event.participant.user_name} left the call`); }, [])); return ( {/* Daily.co automatically handles the video/audio UI */} {/* Recording status indicator */} {isRecording && ( Recording )} {/* Participant count with real-time data */} Participants: {participantIds.length + 1} ); } // Main Room Component with Daily.co Integration export default function Room({ params }: { params: { roomName: string } }) { const roomName = params.roomName; const meeting = useRoomMeeting(roomName); const router = useRouter(); const { isLoading, isAuthenticated } = useSessionStatus(); const [dailyUrl, setDailyUrl] = useState(null); const [callFrame, setCallFrame] = useState(null); // Initialize Daily.co call useEffect(() => { if (!meeting?.response?.room_url) return; const frame = DailyIframe.createCallObject({ showLeaveButton: true, showFullscreenButton: true, }); frame.on("left-meeting", () => { router.push("/browse"); }); setCallFrame(frame); setDailyUrl(meeting.response.room_url); return () => { frame.destroy(); }; }, [meeting?.response?.room_url, router]); if (isLoading) { return ( ); } if (!dailyUrl || !callFrame) { return null; } return ( ); } ### Phase 3: Testing & Validation For Phase 1 (feature parity), the existing processing pipeline remains unchanged: 1. Daily.co records meeting to S3 (same as Whereby) 2. Webhook notifies when recording is ready 3. Existing pipeline downloads and processes the MP4 file 4. Current diarization and transcription tools continue to work Key validation points: - Recording format matches (MP4 with mixed audio) - S3 paths are compatible - Processing pipeline requires no changes - Transcript quality remains the same ## Future Enhancement: Raw-Tracks Recording (Phase 2) ### Raw-Tracks Processing for Enhanced Diarization Daily.co's raw-tracks recording provides individual audio streams per participant, enabling: ```python @shared_task def process_daily_raw_tracks(meeting_id: str, recording_id: str, tracks: list): """Process Daily.co raw-tracks with perfect speaker attribution.""" for track in tracks: participant_id = track["participant_id"] participant_name = track["participant_name"] track_url = track["download_url"] # Download individual participant audio response = download_track(track_url) # Process with known speaker identity transcript = transcribe_audio( audio_data=response.content, speaker_id=participant_id, speaker_name=participant_name ) # Store with accurate speaker mapping save_transcript_segment( meeting_id=meeting_id, speaker_id=participant_id, text=transcript.text, timestamps=transcript.timestamps ) ``` ### Benefits of Raw-Tracks (Future) 1. **Deterministic Speaker Attribution**: Each audio track is already speaker-separated 2. **Improved Transcription Accuracy**: Clean audio without cross-talk 3. **Parallel Processing**: Process multiple speakers simultaneously 4. **Better Metrics**: Accurate talk-time per participant ### Phase 4: Database & Configuration #### 4.1 Environment Variable Updates Update `.env` files: ```bash # Remove Whereby variables # WHEREBY_API_URL=https://api.whereby.dev/v1 # WHEREBY_API_KEY=your-whereby-key # WHEREBY_WEBHOOK_SECRET=your-whereby-secret # AWS_WHEREBY_S3_BUCKET=whereby-recordings # AWS_WHEREBY_ACCESS_KEY_ID=whereby-key # AWS_WHEREBY_ACCESS_KEY_SECRET=whereby-secret # Add Daily.co variables DAILY_API_KEY=your-daily-api-key DAILY_WEBHOOK_SECRET=your-daily-webhook-secret AWS_DAILY_S3_BUCKET=daily-recordings AWS_DAILY_ROLE_ARN=arn:aws:iam::123456789:role/daily-recording-role AWS_REGION=us-west-2 ``` #### 4.2 Database Migration ```sql -- Alembic migration to support Daily.co -- server/alembic/versions/xxx_migrate_to_daily.py def upgrade(): # Add platform field to support gradual migration op.add_column('rooms', sa.Column('platform', sa.String(), server_default='whereby')) op.add_column('meetings', sa.Column('platform', sa.String(), server_default='whereby')) # No other schema changes needed for feature parity def downgrade(): op.drop_column('meetings', 'platform') op.drop_column('rooms', 'platform') ``` #### 4.3 Settings Update (`server/reflector/settings.py`) ```python class Settings(BaseSettings): # Remove Whereby settings # WHEREBY_API_URL: str = "https://api.whereby.dev/v1" # WHEREBY_API_KEY: str # WHEREBY_WEBHOOK_SECRET: str # AWS_WHEREBY_S3_BUCKET: str # AWS_WHEREBY_ACCESS_KEY_ID: str # AWS_WHEREBY_ACCESS_KEY_SECRET: str # Add Daily.co settings DAILY_API_KEY: str DAILY_WEBHOOK_SECRET: str AWS_DAILY_S3_BUCKET: str AWS_DAILY_ROLE_ARN: str AWS_REGION: str = "us-west-2" # Daily.co room URL pattern DAILY_ROOM_URL_PATTERN: str = "https://{subdomain}.daily.co/{room_name}" DAILY_SUBDOMAIN: str = "reflector" # Your Daily.co subdomain ``` ## Technical Differences ### Phase 1 Implementation 1. **Frontend**: Replace `` custom element with Daily.co React components or iframe 2. **Backend**: Create Daily.co API client matching Whereby's functionality 3. **Webhooks**: Map Daily.co events to existing database operations 4. **Recording**: Maintain same MP4 format and S3 storage ### Phase 2 Capabilities (Future) 1. **Raw-tracks recording**: Individual audio streams per participant 2. **Presence API**: Real-time participant data without webhook delays 3. **Transcription API**: Built-in transcription services 4. **Advanced recording options**: Multiple formats and layouts ## Risks and Mitigation ### Risk 1: API Differences - **Mitigation**: Create abstraction layer to minimize changes - Comprehensive testing of all endpoints ### Risk 2: Recording Format Changes - **Mitigation**: Build adapter for raw-tracks processing - Maintain backward compatibility during transition ### Risk 3: User Experience Changes - **Mitigation**: A/B testing with gradual rollout - Feature parity checklist before full migration ## Recommendation Migration to Daily.co is technically feasible and can be implemented in phases: ### Phase 1: Feature Parity - Replace Whereby with Daily.co maintaining exact same functionality - Use standard cloud recording (MP4 to S3) - No changes to processing pipeline ### Phase 2: Enhanced Capabilities (Future) - Enable raw-tracks recording for improved diarization - Implement participant-level audio processing - Add real-time features using Presence API ## Next Steps 1. Set up Daily.co account and obtain API credentials 2. Implement feature flag system for gradual migration 3. Create Daily.co API client matching Whereby functionality 4. Update frontend to support both platforms 5. Test thoroughly before rollout --- *Analysis based on current codebase review and API documentation comparison.*