doc update (vibe)

This commit is contained in:
Igor Loskutov
2025-10-10 10:57:35 -04:00
parent 446cb748ae
commit 0fcf8b6875
5 changed files with 145 additions and 54 deletions

101
PLAN.md
View File

@@ -213,15 +213,28 @@ server/reflector/db/recordings.py # Recording model
### Step 1.3: Define Standard Data Models
**Create `server/reflector/platform_types.py` (separate file to avoid circular imports):**
```python
"""Platform type definitions.
Separate file to prevent circular import issues when db models and
video platform code need to reference the Platform type.
"""
from typing import Literal
Platform = Literal["whereby", "daily"]
```
**Create `server/reflector/video_platforms/models.py`:**
```python
from datetime import datetime
from typing import Literal, Optional
from typing import Any, Dict, Optional
from pydantic import BaseModel, Field
Platform = Literal["whereby", "daily"]
from reflector.platform_types import Platform
class MeetingData(BaseModel):
@@ -976,7 +989,9 @@ DEFAULT_VIDEO_PLATFORM: Literal["whereby", "daily"] = "whereby" # Default to Wh
### Step 2.8: Update Database Schema
**Create migration: `server/migrations/versions/YYYYMMDDHHMMSS_add_platform_support.py`**
**Create migration: `server/migrations/versions/<alembic-id>_add_platform_support.py`**
Note: Alembic generates revision IDs automatically (e.g., `1e49625677e4_add_platform_support.py`)
```bash
cd server
@@ -1239,22 +1254,23 @@ class DailyClient(VideoPlatformClient):
}
# Configure recording if enabled
if room.recording_type == "cloud":
data["properties"]["enable_recording"] = "cloud"
# Configure S3 recording destination if bucket configured
if self.config.s3_bucket and self.config.aws_role_arn:
data["properties"]["recordings_bucket"] = {
"bucket_name": self.config.s3_bucket,
"bucket_region": self.config.s3_region,
"assume_role_arn": self.config.aws_role_arn,
"allow_api_access": True,
}
elif room.recording_type == "local":
data["properties"]["enable_recording"] = "local"
# NOTE: Daily.co always uses "raw-tracks" for better transcription quality
# (multiple WebM files instead of single MP4)
if room.recording_type != "none":
data["properties"]["enable_recording"] = "raw-tracks"
else:
data["properties"]["enable_recording"] = False
# Configure S3 bucket for recordings
# NOTE: Not checking room.recording_type - figure out later if conditional needed
assert self.config.s3_bucket, "S3 bucket must be configured"
data["properties"]["recordings_bucket"] = {
"bucket_name": self.config.s3_bucket,
"bucket_region": self.config.s3_region,
"assume_role_arn": self.config.aws_role_arn,
"allow_api_access": True,
}
# Make API request
async with httpx.AsyncClient() as client:
response = await client.post(
@@ -1370,8 +1386,51 @@ class DailyClient(VideoPlatformClient):
# Constant-time comparison
return hmac.compare_digest(expected_sig, signature)
async def create_meeting_token(self, room_name: str, enable_recording: bool) -> str:
"""Create JWT meeting token with optional auto-recording.
Daily.co supports token-based meeting configuration, which allows
per-participant settings like auto-starting cloud recording.
This is used instead of room-level recording config for more control.
"""
data = {"properties": {"room_name": room_name}}
if enable_recording:
data["properties"]["start_cloud_recording"] = True
async with httpx.AsyncClient() as client:
response = await client.post(
f"{self.BASE_URL}/meeting-tokens",
headers=self.headers,
json=data,
timeout=self.TIMEOUT,
)
response.raise_for_status()
return response.json()["token"]
```
**Token-Based Auto-Recording (Critical Addition)**
After creating a Daily.co meeting, append JWT token to URLs for auto-recording:
```python
# In rooms.py after create_meeting
if meeting.platform == "daily" and room.recording_trigger != "none":
client = create_platform_client(meeting.platform)
token = await client.create_meeting_token(
meeting.room_name, enable_recording=True
)
meeting.room_url += f"?t={token}"
meeting.host_room_url += f"?t={token}"
```
**Why tokens instead of room config:**
- Room-level `enable_recording` only enables the capability
- Token with `start_cloud_recording: true` actually starts it
- Provides per-participant control (future: host-only recording)
### Step 3.2: Register Daily.co Client
**Update `server/reflector/video_platforms/__init__.py`:**
@@ -1585,6 +1644,11 @@ app.include_router(daily.router, prefix="/v1/daily", tags=["daily"])
### Step 3.4: Create Recording Processing Task
**⚠️ NEXT STEP - NOT YET IMPLEMENTED**
The `process_recording_from_url` task described below is the next implementation step.
It handles downloading Daily.co recordings from webhook URLs into the existing transcription pipeline.
**Update `server/reflector/worker/process.py`:**
```python
@@ -2342,6 +2406,7 @@ export DEFAULT_VIDEO_PLATFORM=whereby
## Appendix A: File Checklist
### Backend Files (New)
- [ ] `server/reflector/platform_types.py` (Platform literal type - separate to avoid circular imports)
- [ ] `server/reflector/video_platforms/__init__.py`
- [ ] `server/reflector/video_platforms/base.py`
- [ ] `server/reflector/video_platforms/models.py`
@@ -2351,7 +2416,7 @@ export DEFAULT_VIDEO_PLATFORM=whereby
- [ ] `server/reflector/video_platforms/daily.py`
- [ ] `server/reflector/video_platforms/mock.py`
- [ ] `server/reflector/views/daily.py`
- [ ] `server/migrations/versions/YYYYMMDDHHMMSS_add_platform_support.py`
- [ ] `server/migrations/versions/<alembic-id>_add_platform_support.py` (e.g., `1e49625677e4_...`)
### Backend Files (Modified)
- [ ] `server/reflector/settings.py`

View File

@@ -5,7 +5,8 @@ It allows seamless switching between providers (Whereby, Daily.co, etc.) without
changing the core application logic.
"""
from .base import MeetingData, VideoPlatformClient, VideoPlatformConfig
from .base import VideoPlatformClient
from .models import MeetingData, VideoPlatformConfig
from .registry import get_platform_client, register_platform
__all__ = [

View File

@@ -1,39 +1,14 @@
from abc import ABC, abstractmethod
from datetime import datetime
from typing import TYPE_CHECKING, Any, Dict, Literal, Optional
from pydantic import BaseModel
from typing import TYPE_CHECKING, Any, Dict, Optional
from reflector.platform_types import Platform
from .models import MeetingData, VideoPlatformConfig
if TYPE_CHECKING:
from reflector.db.rooms import Room
RecordingType = Literal["none", "local", "cloud"]
class MeetingData(BaseModel):
meeting_id: str
room_name: str
room_url: str
host_room_url: str
platform: Platform
extra_data: Dict[str, Any] = {}
class VideoPlatformConfig(BaseModel):
"""Configuration for a video platform."""
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
class VideoPlatformClient(ABC):
"""Abstract base class for video platform integrations."""

View File

@@ -58,13 +58,14 @@ class DailyClient(VideoPlatformClient):
}
# Configure S3 bucket for recordings
if room.recording_type != self.RECORDING_NONE and self.config.s3_bucket:
data["properties"]["recordings_bucket"] = {
"bucket_name": self.config.s3_bucket,
"bucket_region": self.config.s3_region,
"assume_role_arn": self.config.aws_role_arn,
"allow_api_access": True,
}
# NOTE: Not checking room.recording_type - figure out later if conditional needed
assert self.config.s3_bucket, "S3 bucket must be configured"
data["properties"]["recordings_bucket"] = {
"bucket_name": self.config.s3_bucket,
"bucket_region": self.config.s3_region,
"assume_role_arn": self.config.aws_role_arn,
"allow_api_access": True,
}
from reflector.logger import logger

View File

@@ -0,0 +1,49 @@
"""Video platform data models.
Standard data models used across all video platform implementations.
"""
from typing import Any, Dict, Literal, Optional
from pydantic import BaseModel, Field
from reflector.platform_types import Platform
RecordingType = Literal["none", "local", "cloud"]
class MeetingData(BaseModel):
"""Standardized meeting data returned by all providers."""
platform: Platform
meeting_id: str = Field(description="Platform-specific meeting identifier")
room_url: str = Field(description="URL for participants to join")
host_room_url: str = Field(description="URL for hosts (may be same as room_url)")
room_name: str = Field(description="Human-readable room name")
extra_data: Dict[str, Any] = Field(default_factory=dict)
class Config:
json_schema_extra = {
"example": {
"platform": "whereby",
"meeting_id": "12345678",
"room_url": "https://subdomain.whereby.com/room-20251008120000",
"host_room_url": "https://subdomain.whereby.com/room-20251008120000?roomKey=abc123",
"room_name": "room-20251008120000",
}
}
class VideoPlatformConfig(BaseModel):
"""Platform-agnostic configuration model."""
api_key: str
webhook_secret: str
api_url: Optional[str] = None
subdomain: Optional[str] = None # Whereby/Daily subdomain
s3_bucket: Optional[str] = None
s3_region: Optional[str] = None
# Whereby uses access keys, Daily uses IAM role
aws_access_key_id: Optional[str] = None
aws_access_key_secret: Optional[str] = None
aws_role_arn: Optional[str] = None