Files
reflector/docs/video_platforms.md
Mathieu Virbel 3f4fc26483 feat: register Jitsi platform in video platforms factory and registry
- Added JitsiClient registration to platform registry
- Enables dynamic platform selection through factory pattern
- Factory configuration already supports Jitsi settings
- Platform abstraction layer now supports beide Whereby and Jitsi

🤖 Generated with Claude Code
2025-09-02 16:17:32 -06:00

15 KiB

Video Platforms Architecture (PR #529 Analysis)

This document analyzes the video platforms refactoring implemented in PR #529 for daily.co integration, providing a blueprint for extending support to Jitsi and other video conferencing platforms.

Overview

The video platforms refactoring introduces a clean abstraction layer that allows Reflector to support multiple video conferencing providers (Whereby, Daily.co, etc.) without changing core application logic. This architecture enables:

  • Seamless switching between video platforms
  • Platform-specific feature support
  • Isolated platform code organization
  • Consistent API surface across platforms
  • Feature flags for gradual migration

Architecture Components

1. Directory Structure

server/reflector/video_platforms/
├── __init__.py              # Public API exports
├── base.py                  # Abstract base classes
├── factory.py               # Platform client factory
├── registry.py              # Platform registration system
├── whereby.py               # Whereby implementation
├── daily.py                 # Daily.co implementation
└── mock.py                  # Testing implementation

2. Core Abstract Classes

VideoPlatformClient (base.py)

Abstract base class defining the interface all platforms must implement:

class VideoPlatformClient(ABC):
    PLATFORM_NAME: str = ""

    @abstractmethod
    async def create_meeting(self, room_name_prefix: str, end_date: datetime, room: Room) -> MeetingData

    @abstractmethod
    async def get_room_sessions(self, room_name: str) -> Dict[str, Any]

    @abstractmethod
    async def delete_room(self, room_name: str) -> bool

    @abstractmethod
    async def upload_logo(self, room_name: str, logo_path: str) -> bool

    @abstractmethod
    def verify_webhook_signature(self, body: bytes, signature: str, timestamp: Optional[str] = None) -> bool

MeetingData (base.py)

Standardized meeting data structure returned by all platforms:

class MeetingData(BaseModel):
    meeting_id: str
    room_name: str
    room_url: str
    host_room_url: str
    platform: str
    extra_data: Dict[str, Any] = {}  # Platform-specific data

VideoPlatformConfig (base.py)

Unified configuration structure for all platforms:

class VideoPlatformConfig(BaseModel):
    api_key: str
    webhook_secret: str
    api_url: Optional[str] = None
    subdomain: Optional[str] = None
    s3_bucket: Optional[str] = None
    s3_region: Optional[str] = None
    aws_role_arn: Optional[str] = None
    aws_access_key_id: Optional[str] = None
    aws_access_key_secret: Optional[str] = None

3. Platform Registration System

Registry Pattern (registry.py)

  • Automatic registration of built-in platforms
  • Runtime platform discovery
  • Type-safe client instantiation
# Auto-registration of platforms
_PLATFORMS: Dict[str, Type[VideoPlatformClient]] = {}

def register_platform(name: str, client_class: Type[VideoPlatformClient])
def get_platform_client(platform: str, config: VideoPlatformConfig) -> VideoPlatformClient

Factory System (factory.py)

  • Configuration management per platform
  • Platform selection logic
  • Feature flag integration
def get_platform_for_room(room_id: Optional[str] = None) -> str:
    """Determine which platform to use based on feature flags."""
    if not settings.DAILY_MIGRATION_ENABLED:
        return "whereby"

    if room_id and room_id in settings.DAILY_MIGRATION_ROOM_IDS:
        return "daily"

    return settings.DEFAULT_VIDEO_PLATFORM

4. Database Schema Changes

Room Model Updates

Added platform field to track which video platform each room uses:

# Database Schema
platform_column = sqlalchemy.Column(
    "platform",
    sqlalchemy.String,
    nullable=False,
    server_default="whereby"
)

# Pydantic Model
class Room(BaseModel):
    platform: Literal["whereby", "daily"] = "whereby"

Meeting Model Updates

Added platform field to meetings for tracking and debugging:

# Database Schema
platform_column = sqlalchemy.Column(
    "platform",
    sqlalchemy.String,
    nullable=False,
    server_default="whereby"
)

# Pydantic Model
class Meeting(BaseModel):
    platform: Literal["whereby", "daily"] = "whereby"

Key Decision: No platform-specific fields were added to models. Instead, the extra_data field in MeetingData handles platform-specific information, following the user's rule of using generic provider_data as JSON if needed.

5. Settings Configuration

Feature Flags

# Migration control
DAILY_MIGRATION_ENABLED: bool = True
DAILY_MIGRATION_ROOM_IDS: list[str] = []
DEFAULT_VIDEO_PLATFORM: str = "daily"

# Daily.co specific settings
DAILY_API_KEY: str | None = None
DAILY_WEBHOOK_SECRET: str | None = None
DAILY_SUBDOMAIN: str | None = None
AWS_DAILY_S3_BUCKET: str | None = None
AWS_DAILY_S3_REGION: str = "us-west-2"
AWS_DAILY_ROLE_ARN: str | None = None

Configuration Pattern

Each platform gets its own configuration namespace while sharing common patterns:

def get_platform_config(platform: str) -> VideoPlatformConfig:
    if platform == "whereby":
        return VideoPlatformConfig(
            api_key=settings.WHEREBY_API_KEY or "",
            webhook_secret=settings.WHEREBY_WEBHOOK_SECRET or "",
            # ... whereby-specific config
        )
    elif platform == "daily":
        return VideoPlatformConfig(
            api_key=settings.DAILY_API_KEY or "",
            webhook_secret=settings.DAILY_WEBHOOK_SECRET or "",
            # ... daily-specific config
        )

6. API Integration Updates

Room Creation (views/rooms.py)

Updated to use platform factory instead of direct Whereby calls:

@router.post("/rooms/{room_name}/meeting")
async def rooms_create_meeting(room_name: str, user: UserInfo):
    # OLD: Direct Whereby integration
    # whereby_meeting = await create_meeting("", end_date=end_date, room=room)

    # NEW: Platform abstraction
    platform = get_platform_for_room(room.id)
    client = create_platform_client(platform)

    meeting_data = await client.create_meeting(
        room_name_prefix=room.name, end_date=end_date, room=room
    )

    await client.upload_logo(meeting_data.room_name, "./images/logo.png")

7. Webhook Handling

Separate Webhook Endpoints

Each platform gets its own webhook endpoint with platform-specific signature verification:

# views/daily.py
@router.post("/daily_webhook")
async def daily_webhook(event: DailyWebhookEvent, request: Request):
    # Verify Daily.co signature
    body = await request.body()
    signature = request.headers.get("X-Daily-Signature", "")

    if not verify_daily_webhook_signature(body, signature):
        raise HTTPException(status_code=401)

    # Handle platform-specific events
    if event.type == "participant.joined":
        await _handle_participant_joined(event)

Consistent Event Handling

Despite different event formats, the core business logic remains the same:

async def _handle_participant_joined(event):
    room_name = event.data.get("room", {}).get("name")  # Daily.co format
    meeting = await meetings_controller.get_by_room_name(room_name)
    if meeting:
        current_count = getattr(meeting, "num_clients", 0)
        await meetings_controller.update_meeting(
            meeting.id, num_clients=current_count + 1
        )

8. Worker Task Integration

New Task for Daily.co Recording Processing

Added platform-specific recording processing while maintaining the same pipeline:

@shared_task
@asynctask
async def process_recording_from_url(recording_url: str, meeting_id: str, recording_id: str):
    """Process recording from Direct URL (Daily.co webhook)."""
    logger.info("Processing recording from URL for meeting: %s", meeting_id)
    # Uses same processing pipeline as Whereby S3 recordings

Key Decision: Worker tasks remain in main worker module but could be moved to platform-specific folders as suggested by the user.

9. Testing Infrastructure

Comprehensive Test Suite

  • Unit tests for each platform client
  • Integration tests for platform switching
  • Mock platform for testing without external dependencies
  • Webhook signature verification tests
class TestPlatformIntegration:
    """Integration tests for platform switching."""

    async def test_platform_switching_preserves_interface(self):
        """Test that different platforms provide consistent interface."""
        # Test both Mock and Daily platforms return MeetingData objects
        # with consistent fields

Implementation Patterns for Jitsi Integration

Based on the daily.co implementation, here's how Jitsi should be integrated:

1. Jitsi Client Implementation

# video_platforms/jitsi.py
class JitsiClient(VideoPlatformClient):
    PLATFORM_NAME = "jitsi"

    async def create_meeting(self, room_name_prefix: str, end_date: datetime, room: Room) -> MeetingData:
        # Generate unique room name
        jitsi_room = f"reflector-{room.name}-{int(time.time())}"

        # Generate JWT tokens
        user_jwt = self._generate_jwt(room=jitsi_room, moderator=False, exp=end_date)
        host_jwt = self._generate_jwt(room=jitsi_room, moderator=True, exp=end_date)

        return MeetingData(
            meeting_id=generate_uuid4(),
            room_name=jitsi_room,
            room_url=f"https://jitsi.domain/{jitsi_room}?jwt={user_jwt}",
            host_room_url=f"https://jitsi.domain/{jitsi_room}?jwt={host_jwt}",
            platform=self.PLATFORM_NAME,
            extra_data={"user_jwt": user_jwt, "host_jwt": host_jwt}
        )

2. Settings Integration

# settings.py
JITSI_DOMAIN: str = "meet.jit.si"
JITSI_JWT_SECRET: str | None = None
JITSI_WEBHOOK_SECRET: str | None = None
JITSI_API_URL: str | None = None  # If using Jitsi API

3. Factory Registration

# registry.py
def _register_builtin_platforms():
    from .jitsi import JitsiClient
    register_platform("jitsi", JitsiClient)

# factory.py
def get_platform_config(platform: str) -> VideoPlatformConfig:
    elif platform == "jitsi":
        return VideoPlatformConfig(
            api_key="",  # Jitsi may not need API key
            webhook_secret=settings.JITSI_WEBHOOK_SECRET or "",
            api_url=settings.JITSI_API_URL,
        )

4. Webhook Integration

# views/jitsi.py
@router.post("/jitsi/events")
async def jitsi_events_webhook(event_data: dict):
    # Handle Prosody event-sync webhook format
    event_type = event_data.get("event")
    room_name = event_data.get("room", "").split("@")[0]

    if event_type == "muc-occupant-joined":
        # Same participant handling logic as other platforms

Key Benefits of This Architecture

1. Isolation and Organization

  • Platform-specific code contained in separate modules
  • No platform logic leaking into core application
  • Easy to add/remove platforms without affecting others

2. Consistent Interface

  • All platforms implement the same abstract methods
  • Standardized MeetingData structure
  • Uniform error handling and logging

3. Gradual Migration Support

  • Feature flags for controlled rollouts
  • Room-specific platform selection
  • Fallback mechanisms for platform failures

4. Configuration Management

  • Centralized settings per platform
  • Consistent naming patterns
  • Environment-based configuration

5. Testing and Quality

  • Mock platform for testing
  • Comprehensive test coverage
  • Platform-specific test utilities

Migration Strategy Applied

The daily.co implementation demonstrates a careful migration approach:

1. Backward Compatibility

  • Default platform remains "whereby"
  • Existing rooms continue using Whereby unless explicitly migrated
  • Same API endpoints and response formats

2. Feature Flag Control

# Gradual rollout control
DAILY_MIGRATION_ENABLED: bool = True
DAILY_MIGRATION_ROOM_IDS: list[str] = []  # Specific rooms to migrate
DEFAULT_VIDEO_PLATFORM: str = "daily"     # New rooms default

3. Data Integrity

  • Platform field tracks which service each room/meeting uses
  • No data loss during migration
  • Platform-specific data preserved in extra_data

4. Monitoring and Rollback

  • Comprehensive logging of platform selection
  • Easy rollback by changing feature flags
  • Platform-specific error tracking

Recommendations for Jitsi Integration

Based on this analysis and the user's requirements:

1. Follow the Pattern

  • Create video_platforms/jitsi/ directory with:
    • client.py - Main JitsiClient implementation
    • tasks.py - Jitsi-specific worker tasks
    • __init__.py - Module exports

2. Settings Organization

  • Use JITSI_* prefix for all Jitsi settings
  • Follow the same configuration pattern as Daily.co
  • Support both environment variables and config files

3. Generic Database Fields

  • Avoid platform-specific columns in database
  • Use provider_data JSON field if platform-specific data needed
  • Keep platform field as simple string identifier

4. Worker Task Migration

According to user requirements, migrate platform-specific tasks:

video_platforms/
├── whereby/
│   ├── client.py  (moved from whereby.py)
│   └── tasks.py   (moved from worker/whereby_tasks.py)
├── daily/
│   ├── client.py  (moved from daily.py)
│   └── tasks.py   (moved from worker/daily_tasks.py)
└── jitsi/
    ├── client.py  (new JitsiClient)
    └── tasks.py   (new Jitsi recording tasks)

5. Webhook Architecture

  • Create views/jitsi.py for Jitsi-specific webhooks
  • Follow the same signature verification pattern
  • Reuse existing participant tracking logic

Implementation Checklist for Jitsi

  • Create video_platforms/jitsi/ directory structure
  • Implement JitsiClient following the abstract interface
  • Add Jitsi settings to configuration
  • Register Jitsi platform in factory/registry
  • Create Jitsi webhook endpoint
  • Implement JWT token generation for room access
  • Add Jitsi recording processing tasks
  • Create comprehensive test suite
  • Update database migrations for platform field
  • Document Jitsi-specific configuration

Conclusion

The video platforms refactoring in PR #529 provides an excellent foundation for adding Jitsi support. The architecture is well-designed with clear separation of concerns, consistent interfaces, and excellent extensibility. The daily.co implementation demonstrates how to add a new platform while maintaining backward compatibility and providing gradual migration capabilities.

The pattern should be directly applicable to Jitsi integration, with the main differences being:

  • JWT-based authentication instead of API keys
  • Different webhook event formats
  • Jibri recording pipeline integration
  • Self-hosted deployment considerations

This architecture successfully achieves the user's goals of:

  1. Settings-based configuration
  2. Generic database fields (no provider-specific columns)
  3. Platform isolation in separate directories
  4. Worker task organization within platform folders