mirror of
https://github.com/Monadical-SAS/reflector.git
synced 2025-12-21 04:39:06 +00:00
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
This commit is contained in:
369
docs/jitsi.md
Normal file
369
docs/jitsi.md
Normal file
@@ -0,0 +1,369 @@
|
||||
# Jitsi Integration for Reflector
|
||||
|
||||
This document contains research and planning notes for integrating Jitsi Meet as a replacement for Whereby in Reflector.
|
||||
|
||||
## Overview
|
||||
|
||||
Jitsi Meet is an open-source video conferencing solution that can replace Whereby in Reflector, providing:
|
||||
- Cost reduction (no per-minute charges)
|
||||
- Direct recording access via Jibri
|
||||
- Real-time event webhooks
|
||||
- Full customization and control
|
||||
|
||||
## Current Whereby Integration Analysis
|
||||
|
||||
### Architecture
|
||||
1. **Room Creation**: User creates a "room" template in Reflector DB with settings
|
||||
2. **Meeting Creation**: `/rooms/{room_name}/meeting` endpoint calls Whereby API to create meeting
|
||||
3. **Recording**: Whereby handles recording automatically to S3 bucket
|
||||
4. **Webhooks**: Whereby sends events for participant tracking
|
||||
|
||||
### Database Structure
|
||||
```python
|
||||
# Room = Template/Configuration
|
||||
class Room:
|
||||
id, name, user_id
|
||||
recording_type, recording_trigger # cloud, automatic-2nd-participant
|
||||
webhook_url, webhook_secret
|
||||
|
||||
# Meeting = Actual Whereby Meeting Instance
|
||||
class Meeting:
|
||||
id # Whereby meetingId
|
||||
room_name # Generated by Whereby
|
||||
room_url, host_room_url # Whereby URLs
|
||||
num_clients # Updated via webhooks
|
||||
```
|
||||
|
||||
## Jitsi Components
|
||||
|
||||
### Core Architecture
|
||||
- **Jitsi Meet**: Web frontend (Next.js + React)
|
||||
- **Prosody**: XMPP server for messaging/rooms
|
||||
- **Jicofo**: Conference focus (orchestration)
|
||||
- **JVB**: Videobridge (media routing)
|
||||
- **Jibri**: Recording service
|
||||
- **Jigasi**: SIP gateway (optional, for phone dial-in)
|
||||
|
||||
### Exposure Requirements
|
||||
- **Web service**: 443/80 (frontend)
|
||||
- **JVB**: 10000/UDP (media streams) - **MUST EXPOSE**
|
||||
- **Prosody**: 5280 (BOSH/WebSocket) - can proxy via web
|
||||
- **Jicofo, Jibri, Jigasi**: Internal only
|
||||
|
||||
## Recording with Jibri
|
||||
|
||||
### How Jibri Works
|
||||
- Each Jibri instance handles **one recording at a time**
|
||||
- Records mixed audio/video to MP4 format
|
||||
- Uses Chrome headless + ffmpeg for capture
|
||||
- Supports finalize scripts for post-processing
|
||||
|
||||
### Jibri Pool for Scaling
|
||||
- Multiple Jibri instances join "jibribrewery" MUC
|
||||
- Jicofo distributes recording requests to available instances
|
||||
- Automatic load balancing and failover
|
||||
|
||||
```yaml
|
||||
# Multiple Jibri instances
|
||||
jibri1:
|
||||
environment:
|
||||
- JIBRI_INSTANCE_ID=jibri1
|
||||
- JIBRI_BREWERY_MUC=jibribrewery
|
||||
|
||||
jibri2:
|
||||
environment:
|
||||
- JIBRI_INSTANCE_ID=jibri2
|
||||
- JIBRI_BREWERY_MUC=jibribrewery
|
||||
```
|
||||
|
||||
### Recording Automation Options
|
||||
1. **Environment Variables**: `ENABLE_RECORDING=1`, `AUTO_RECORDING=1`
|
||||
2. **URL Parameters**: `?config.autoRecord=true`
|
||||
3. **JWT Token**: Include recording permissions in JWT
|
||||
4. **API Control**: `api.executeCommand('startRecording')`
|
||||
|
||||
### Post-Processing Integration
|
||||
```bash
|
||||
#!/bin/bash
|
||||
# finalize.sh - runs after recording completion
|
||||
RECORDING_FILE=$1
|
||||
MEETING_METADATA=$2
|
||||
ROOM_NAME=$3
|
||||
|
||||
# Copy to Reflector-accessible location
|
||||
cp "$RECORDING_FILE" /shared/reflector-uploads/
|
||||
|
||||
# Trigger Reflector processing
|
||||
curl -X POST "http://reflector-api:8000/v1/transcripts/process" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "{
|
||||
\"file_path\": \"/shared/reflector-uploads/$(basename $RECORDING_FILE)\",
|
||||
\"room_name\": \"$ROOM_NAME\",
|
||||
\"source\": \"jitsi\"
|
||||
}"
|
||||
```
|
||||
|
||||
## React Integration
|
||||
|
||||
### Official React SDK
|
||||
```bash
|
||||
npm i @jitsi/react-sdk
|
||||
```
|
||||
|
||||
```jsx
|
||||
import { JitsiMeeting } from '@jitsi/react-sdk'
|
||||
|
||||
<JitsiMeeting
|
||||
room="meeting-room"
|
||||
serverURL="https://your-jitsi.domain"
|
||||
jwt="your-jwt-token"
|
||||
config={{
|
||||
startWithAudioMuted: true,
|
||||
fileRecordingsEnabled: true,
|
||||
autoRecord: true
|
||||
}}
|
||||
onParticipantJoined={(participant) => {
|
||||
// Track participant events
|
||||
}}
|
||||
onRecordingStatusChanged={(status) => {
|
||||
// Handle recording events
|
||||
}}
|
||||
/>
|
||||
```
|
||||
|
||||
## Authentication & Room Control
|
||||
|
||||
### JWT-Based Access Control
|
||||
```python
|
||||
def generate_jitsi_jwt(payload):
|
||||
return jwt.encode({
|
||||
"aud": "jitsi",
|
||||
"iss": "reflector",
|
||||
"sub": "reflector-user",
|
||||
"room": payload["room"],
|
||||
"exp": int(payload["exp"].timestamp()),
|
||||
"context": {
|
||||
"user": {
|
||||
"name": payload["user_name"],
|
||||
"moderator": payload.get("moderator", False)
|
||||
},
|
||||
"features": {
|
||||
"recording": payload.get("recording", True)
|
||||
}
|
||||
}
|
||||
}, JITSI_JWT_SECRET)
|
||||
```
|
||||
|
||||
### Prevent Anonymous Room Creation
|
||||
```bash
|
||||
# Environment configuration
|
||||
ENABLE_AUTH=1
|
||||
ENABLE_GUESTS=0
|
||||
AUTH_TYPE=jwt
|
||||
JWT_APP_ID=reflector
|
||||
JWT_APP_SECRET=your-secret-key
|
||||
```
|
||||
|
||||
## Webhook Integration
|
||||
|
||||
### Real-time Events via Prosody
|
||||
Custom event-sync module can send webhooks for:
|
||||
- Participant join/leave
|
||||
- Recording start/stop
|
||||
- Room creation/destruction
|
||||
- Mute/unmute events
|
||||
|
||||
```lua
|
||||
-- mod_event_sync.lua
|
||||
module:hook("muc-occupant-joined", function(event)
|
||||
send_event({
|
||||
type = "participant_joined",
|
||||
room = event.room.jid,
|
||||
participant = {
|
||||
nick = event.occupant.nick,
|
||||
jid = event.occupant.jid,
|
||||
},
|
||||
timestamp = os.time(),
|
||||
});
|
||||
end);
|
||||
```
|
||||
|
||||
### Jibri Recording Webhooks
|
||||
```bash
|
||||
# Environment variable
|
||||
JIBRI_WEBHOOK_SUBSCRIBERS=https://your-reflector.com/webhooks/jibri
|
||||
```
|
||||
|
||||
## Proposed Reflector Integration
|
||||
|
||||
### Modified Database Schema
|
||||
```python
|
||||
class Meeting(BaseModel):
|
||||
id: str # Our generated meeting ID
|
||||
room_name: str # Generated: reflector-{room.name}-{timestamp}
|
||||
room_url: str # https://jitsi.domain/room_name?jwt=token
|
||||
host_room_url: str # Same but with moderator JWT
|
||||
# Add Jitsi-specific fields
|
||||
jitsi_jwt: str # JWT token
|
||||
jitsi_room_id: str # Internal room identifier
|
||||
recording_status: str # pending, recording, completed
|
||||
recording_file_path: Optional[str]
|
||||
```
|
||||
|
||||
### API Replacement
|
||||
```python
|
||||
# Replace whereby.py with jitsi.py
|
||||
async def create_meeting(room_name_prefix: str, end_date: datetime, room: Room):
|
||||
# Generate unique room name
|
||||
jitsi_room = f"reflector-{room.name}-{int(time.time())}"
|
||||
|
||||
# Generate JWT tokens
|
||||
user_jwt = generate_jwt(room=jitsi_room, moderator=False, exp=end_date)
|
||||
host_jwt = generate_jwt(room=jitsi_room, moderator=True, exp=end_date)
|
||||
|
||||
return {
|
||||
"meetingId": generate_uuid4(), # Our ID
|
||||
"roomName": jitsi_room,
|
||||
"roomUrl": f"https://jitsi.domain/{jitsi_room}?jwt={user_jwt}",
|
||||
"hostRoomUrl": f"https://jitsi.domain/{jitsi_room}?jwt={host_jwt}",
|
||||
"startDate": datetime.now().isoformat(),
|
||||
"endDate": end_date.isoformat(),
|
||||
}
|
||||
```
|
||||
|
||||
### Webhook Endpoints
|
||||
```python
|
||||
# Replace whereby webhook with jitsi webhooks
|
||||
@router.post("/jitsi/events")
|
||||
async def jitsi_events_webhook(event_data: dict):
|
||||
event_type = event_data.get("event")
|
||||
room_name = event_data.get("room", "").split("@")[0]
|
||||
|
||||
meeting = await Meeting.get_by_room(room_name)
|
||||
|
||||
if event_type == "muc-occupant-joined":
|
||||
# Update participant count
|
||||
meeting.num_clients += 1
|
||||
|
||||
elif event_type == "jibri-recording-on":
|
||||
meeting.recording_status = "recording"
|
||||
|
||||
elif event_type == "jibri-recording-off":
|
||||
meeting.recording_status = "processing"
|
||||
await process_meeting_recording.delay(meeting.id)
|
||||
|
||||
@router.post("/jibri/recording-complete")
|
||||
async def recording_complete(data: dict):
|
||||
# Handle finalize script webhook
|
||||
room_name = data.get("room_name")
|
||||
file_path = data.get("file_path")
|
||||
|
||||
meeting = await Meeting.get_by_room(room_name)
|
||||
meeting.recording_file_path = file_path
|
||||
meeting.recording_status = "completed"
|
||||
|
||||
# Start Reflector processing
|
||||
await process_recording_for_transcription(meeting.id, file_path)
|
||||
```
|
||||
|
||||
## Deployment with Docker
|
||||
|
||||
### Official docker-jitsi-meet
|
||||
```bash
|
||||
# Download official release
|
||||
wget $(wget -q -O - https://api.github.com/repos/jitsi/docker-jitsi-meet/releases/latest | grep zip | cut -d\" -f4)
|
||||
|
||||
# Setup
|
||||
mkdir -p ~/.jitsi-meet-cfg/{web,transcripts,prosody/config,prosody/prosody-plugins-custom,jicofo,jvb,jigasi,jibri}
|
||||
./gen-passwords.sh # Generate secure passwords
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
### Coolify Integration
|
||||
```yaml
|
||||
services:
|
||||
web:
|
||||
ports: ["80:80", "443:443"]
|
||||
jvb:
|
||||
ports: ["10000:10000/udp"] # Must expose for media
|
||||
jibri1:
|
||||
environment:
|
||||
- JIBRI_INSTANCE_ID=jibri1
|
||||
- JIBRI_FINALIZE_RECORDING_SCRIPT_PATH=/config/finalize.sh
|
||||
jibri2:
|
||||
environment:
|
||||
- JIBRI_INSTANCE_ID=jibri2
|
||||
```
|
||||
|
||||
## Benefits vs Whereby
|
||||
|
||||
### Cost & Control
|
||||
✅ **No per-minute charges** - significant cost savings
|
||||
✅ **Full recording control** - direct file access
|
||||
✅ **Custom branding** - complete UI control
|
||||
✅ **Self-hosted** - no vendor lock-in
|
||||
|
||||
### Technical Advantages
|
||||
✅ **Real-time events** - immediate webhook notifications
|
||||
✅ **Rich participant metadata** - detailed tracking
|
||||
✅ **JWT security** - token-based access with expiration
|
||||
✅ **Multiple recording formats** - audio-only options
|
||||
✅ **Scalable architecture** - horizontal Jibri scaling
|
||||
|
||||
### Integration Benefits
|
||||
✅ **Same API surface** - minimal changes to existing code
|
||||
✅ **React SDK** - better frontend integration
|
||||
✅ **Direct processing** - no S3 download delays
|
||||
✅ **Event-driven architecture** - better real-time capabilities
|
||||
|
||||
## Implementation Plan
|
||||
|
||||
1. **Deploy Jitsi Stack** - Set up docker-jitsi-meet with multiple Jibri instances
|
||||
2. **Create jitsi.py** - Replace whereby.py with Jitsi API functions
|
||||
3. **Update Database** - Add Jitsi-specific fields to Meeting model
|
||||
4. **Webhook Integration** - Replace Whereby webhooks with Jitsi events
|
||||
5. **Frontend Updates** - Replace Whereby embed with Jitsi React SDK
|
||||
6. **Testing & Migration** - Gradual rollout with fallback to Whereby
|
||||
|
||||
## Recording Limitations & Considerations
|
||||
|
||||
### Current Limitations
|
||||
- **Mixed audio only** - Jibri doesn't separate participant tracks natively
|
||||
- **One recording per Jibri** - requires multiple instances for concurrent recordings
|
||||
- **Chrome dependency** - Jibri uses headless Chrome for recording
|
||||
|
||||
### Metadata Capabilities
|
||||
✅ **Participant join/leave timestamps** - via webhooks
|
||||
✅ **Speaking time tracking** - via audio level events
|
||||
✅ **Meeting duration** - precise timing
|
||||
✅ **Room-specific data** - custom metadata in JWT
|
||||
|
||||
### Alternative Recording Methods
|
||||
- **Local recording** - browser-based, per-participant
|
||||
- **Custom recording** - lib-jitsi-meet for individual streams
|
||||
- **Third-party solutions** - Recall.ai, Otter.ai integrations
|
||||
|
||||
## Security Considerations
|
||||
|
||||
### JWT Configuration
|
||||
- **Room-specific tokens** - limit access to specific rooms
|
||||
- **Time-based expiration** - automatic cleanup
|
||||
- **Feature permissions** - control recording, moderation rights
|
||||
- **User identification** - embed user metadata in tokens
|
||||
|
||||
### Access Control
|
||||
- **No anonymous rooms** - all rooms require valid JWT
|
||||
- **API-only creation** - prevent direct room access
|
||||
- **Webhook verification** - HMAC signature validation
|
||||
|
||||
## Next Steps
|
||||
|
||||
1. **Deploy test Jitsi instance** - validate recording pipeline
|
||||
2. **Prototype jitsi.py** - create equivalent API functions
|
||||
3. **Test webhook integration** - ensure event delivery works
|
||||
4. **Performance testing** - validate multiple concurrent recordings
|
||||
5. **Migration strategy** - plan gradual transition from Whereby
|
||||
|
||||
---
|
||||
|
||||
*This document serves as the comprehensive planning and research notes for Jitsi integration in Reflector. It should be updated as implementation progresses and new insights are discovered.*
|
||||
474
docs/video_platforms.md
Normal file
474
docs/video_platforms.md
Normal file
@@ -0,0 +1,474 @@
|
||||
# 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:
|
||||
|
||||
```python
|
||||
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:
|
||||
|
||||
```python
|
||||
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:
|
||||
|
||||
```python
|
||||
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
|
||||
|
||||
```python
|
||||
# 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
|
||||
|
||||
```python
|
||||
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:
|
||||
|
||||
```python
|
||||
# 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:
|
||||
|
||||
```python
|
||||
# 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
|
||||
```python
|
||||
# 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:
|
||||
|
||||
```python
|
||||
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:
|
||||
|
||||
```python
|
||||
@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:
|
||||
|
||||
```python
|
||||
# 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:
|
||||
|
||||
```python
|
||||
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:
|
||||
|
||||
```python
|
||||
@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
|
||||
|
||||
```python
|
||||
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**
|
||||
|
||||
```python
|
||||
# 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**
|
||||
|
||||
```python
|
||||
# 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**
|
||||
|
||||
```python
|
||||
# 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**
|
||||
|
||||
```python
|
||||
# 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**
|
||||
```python
|
||||
# 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
|
||||
@@ -30,8 +30,9 @@ def get_available_platforms() -> list[str]:
|
||||
|
||||
# Auto-register built-in platforms
|
||||
def _register_builtin_platforms():
|
||||
# Will be populated as we add platforms
|
||||
pass
|
||||
from .jitsi import JitsiClient
|
||||
|
||||
register_platform("jitsi", JitsiClient)
|
||||
|
||||
|
||||
_register_builtin_platforms()
|
||||
|
||||
Reference in New Issue
Block a user