From 2f59d9281ce8de337fd255772859d6a8ab2e0544 Mon Sep 17 00:00:00 2001 From: leo <260626284+cameledev@users.noreply.github.com> Date: Mon, 20 Jul 2026 15:50:42 +0200 Subject: [PATCH] wip --- CHANGELOG.md | 1 + src/backend/core/models.py | 9 ++++++- .../recording/services/recording_events.py | 25 +++++++++++++++++-- src/backend/core/services/livekit_events.py | 18 ++++++++++--- 4 files changed, 47 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9eb62c0a..b895eeb8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -33,6 +33,7 @@ and this project adheres to - 🩹(backend) identify externally provisioned users to PostHog - 🐛(backend) fix info panel crash for unregistered rooms - ♿️(frontend) focus side panel container on open #1452 +- 🩹(backend) handle failed and aborted egresses ## [1.23.0] - 2026-07-08 diff --git a/src/backend/core/models.py b/src/backend/core/models.py index fbfcf6be..5cf7d155 100644 --- a/src/backend/core/models.py +++ b/src/backend/core/models.py @@ -57,6 +57,7 @@ class RecordingStatusChoices(models.TextChoices): STOPPED = "stopped", _("Stopped") SAVED = "saved", _("Saved") ABORTED = "aborted", _("Aborted") + FAILED = "failed", _("Failed") FAILED_TO_START = "failed_to_start", _("Failed to Start") FAILED_TO_STOP = "failed_to_stop", _("Failed to Stop") NOTIFICATION_SUCCEEDED = "notification_succeeded", _("Notification succeeded") @@ -80,6 +81,7 @@ class RecordingStatusChoices(models.TextChoices): cls.ABORTED, cls.EXTERNAL_PROCESS_SUCCESSFUL, cls.EXTERNAL_PROCESS_FAILED, + cls.FAILED, cls.FAILED_TO_START, cls.FAILED_TO_STOP, } @@ -87,7 +89,12 @@ class RecordingStatusChoices(models.TextChoices): @classmethod def is_unsuccessful(cls, status): """Determine if the recording status represents an unsuccessful state.""" - return status in {cls.ABORTED, cls.FAILED_TO_START, cls.FAILED_TO_STOP} + return status in { + cls.ABORTED, + cls.FAILED, + cls.FAILED_TO_START, + cls.FAILED_TO_STOP, + } class RecordingModeChoices(models.TextChoices): diff --git a/src/backend/core/recording/services/recording_events.py b/src/backend/core/recording/services/recording_events.py index 3bebac0d..2d88e15e 100644 --- a/src/backend/core/recording/services/recording_events.py +++ b/src/backend/core/recording/services/recording_events.py @@ -26,14 +26,16 @@ class RecordingEventsService: @staticmethod def handle_update(recording: Recording, egress_status): - """Handle egress status updates and sync recording state to room metadata.""" + """Handle egress update and sync recording state to room metadata. + + Egress updates are sent for statuses EGRESS_ACTIVE and EGRESS_ENDING. + """ room_name = str(recording.room.id) status_mapping = { api.EgressStatus.EGRESS_ACTIVE: "started", api.EgressStatus.EGRESS_ENDING: "saving", - api.EgressStatus.EGRESS_ABORTED: "aborted", } recording_status = status_mapping.get(egress_status) @@ -59,6 +61,13 @@ class RecordingEventsService: notification_type = notification_mapping.get(recording.mode) if not notification_type: + logger.warning( + "Could not find notification type for: " + "room=%s, recording_id=%s, mode=%s", + recording.room.id, + recording.id, + recording.mode, + ) return try: @@ -79,6 +88,18 @@ class RecordingEventsService: f"recording limit reached (recording_id={recording.id})" ) from e + @staticmethod + def handle_failed(recording: Recording): + """Set recording status to failed, matching egress status.""" + recording.status = models.RecordingStatusChoices.FAILED + recording.save() + + @staticmethod + def handle_aborted(recording: Recording): + """Set recording status to aborted, matching egress status.""" + recording.status = models.RecordingStatusChoices.ABORTED + recording.save() + @staticmethod def handle_complete(recording: Recording): """Notify external services and save recording.""" diff --git a/src/backend/core/services/livekit_events.py b/src/backend/core/services/livekit_events.py index 5852f6d4..63354de9 100644 --- a/src/backend/core/services/livekit_events.py +++ b/src/backend/core/services/livekit_events.py @@ -200,7 +200,21 @@ class LiveKitEventsService: f"Failed to process limit reached event for recording {recording}" ) from e - # Fallback for completion when no MinIO/S3 webhooks are configured + if ( + data.egress_info.status == api.EgressStatus.EGRESS_ABORTED + and recording.status == models.RecordingStatusChoices.ACTIVE + ): + return self.recording_events.handle_aborted(recording) + + if ( + data.egress_info.status == api.EgressStatus.EGRESS_FAILED + and recording.status == models.RecordingStatusChoices.ACTIVE + ): + return self.recording_events.handle_failed(recording) + + # Fallback for completion when no MinIO/S3 webhooks are configured. + # If RECORDING_STORAGE_EVENT_ENABLE is True, completion is handled + # by ``on_storage_event_received``. if ( not settings.RECORDING_STORAGE_EVENT_ENABLE ) and data.egress_info.status in [ @@ -216,8 +230,6 @@ class LiveKitEventsService: recording.id, ) - # Silently ignoring EGRESS_ABORTED, EGRESS_FAILED - def _handle_room_started(self, data): """Handle 'room_started' event."""