(backend) add LiveKit egress_ended fallback for saving recordings

Recording lifecycle previously relied exclusively on the MinIO storage-hook
endpoint to transition from STOPPED to SAVED, which made the system dependent
on MinIO bucket notifications and lifecycle configuration. Introduce support
for LiveKit egress_ended webhook (EGRESS_COMPLETE, EGRESS_LIMIT_REACHED) as
 an alternative finalization mechanism for self-hosted deployments that do
 not use MinIO / S3 hooks.

Change behavior of existing configuation RECORDING_STORAGE_EVENT_ENABLE.
When the LiveKit mechanism is enabled (RECORDING_STORAGE_EVENT_ENABLE=False),
RecordingEventsService.handle_complete is triggered from
LiveKitEventsService._handle_egress_ended.
This commit is contained in:
leo
2026-06-24 17:44:04 +02:00
committed by aleb_the_flash
parent 6b7cd8ab2e
commit aee1847303
9 changed files with 254 additions and 29 deletions
+1 -1
View File
@@ -58,7 +58,7 @@ class EventParser(Protocol):
def parse(self, data: Dict) -> StorageEvent:
"""Extract storage event data from raw dictionary input."""
def validate(self, data: StorageEvent) -> None:
def validate(self, data: StorageEvent) -> str:
"""Verify storage event data meets all requirements."""
def get_recording_id(self, data: Dict) -> str:
@@ -8,6 +8,7 @@ from livekit import api
from core import models, utils
from core.models import Recording
from core.recording.event.notification import notification_service
logger = getLogger(__name__)
@@ -16,6 +17,10 @@ class RecordingEventsError(Exception):
"""Recording event handling fails."""
class RecordingNotSavableError(Exception):
"""Recording cannot be saved because it is either in an error state or has already been saved"""
class RecordingEventsService:
"""Handles recording-related LiveKit webhook events."""
@@ -73,3 +78,23 @@ class RecordingEventsService:
f"Failed to notify participants in room '{recording.room.id}' about "
f"recording limit reached (recording_id={recording.id})"
) from e
@staticmethod
def handle_complete(recording: Recording):
"""Notify external services and save recording."""
if not recording.is_savable():
raise RecordingNotSavableError
# Attempt to notify external services about the recording
# This is a non-blocking operation - failures are logged but don't interrupt the flow
notification_succeeded = notification_service.notify_external_services(
recording
)
recording.status = (
models.RecordingStatusChoices.NOTIFICATION_SUCCEEDED
if notification_succeeded
else models.RecordingStatusChoices.SAVED
)
recording.save()