From 1fe016451141def05829067e3d3f0e835a77ba86 Mon Sep 17 00:00:00 2001 From: Ryan Gaus Date: Tue, 2 Jun 2026 16:07:06 -0400 Subject: [PATCH 1/2] feat: add missing connection quality field --- livekit-rtc/livekit/rtc/__init__.py | 2 +- livekit-rtc/livekit/rtc/participant.py | 33 ++++++++++++++++++++++++++ livekit-rtc/livekit/rtc/room.py | 15 +++++++++--- tests/rtc/test_connection_quality.py | 29 ++++++++++++++++++++++ 4 files changed, 75 insertions(+), 4 deletions(-) create mode 100644 tests/rtc/test_connection_quality.py diff --git a/livekit-rtc/livekit/rtc/__init__.py b/livekit-rtc/livekit/rtc/__init__.py index 961e133b..d850835b 100644 --- a/livekit-rtc/livekit/rtc/__init__.py +++ b/livekit-rtc/livekit/rtc/__init__.py @@ -27,7 +27,6 @@ DisconnectReason, ) from ._proto.room_pb2 import ( - ConnectionQuality, ConnectionState, ContinualGatheringPolicy, DataPacketKind, @@ -58,6 +57,7 @@ KeyProviderOptions, ) from .participant import ( + ConnectionQuality, LocalParticipant, Participant, RemoteParticipant, diff --git a/livekit-rtc/livekit/rtc/participant.py b/livekit-rtc/livekit/rtc/participant.py index fd259ac1..d682e6a7 100644 --- a/livekit-rtc/livekit/rtc/participant.py +++ b/livekit-rtc/livekit/rtc/participant.py @@ -17,6 +17,7 @@ import ctypes import asyncio import datetime +import enum import os import mimetypes import aiofiles @@ -29,6 +30,9 @@ from ._proto.room_pb2 import ( TrackPublishOptions, ) +from ._proto.room_pb2 import ( + ConnectionQuality as ProtoConnectionQuality, +) from ._proto.room_pb2 import ( TranscriptionSegment as ProtoTranscriptionSegment, ) @@ -89,10 +93,29 @@ def __init__(self, message: str) -> None: self.message = message +class ConnectionQuality(enum.IntEnum): + """Connection quality reported for a participant.""" + + QUALITY_UNKNOWN = -1 + """No connection-quality update has been reported yet (no proto equivalent).""" + QUALITY_POOR = ProtoConnectionQuality.QUALITY_POOR + QUALITY_GOOD = ProtoConnectionQuality.QUALITY_GOOD + QUALITY_EXCELLENT = ProtoConnectionQuality.QUALITY_EXCELLENT + QUALITY_LOST = ProtoConnectionQuality.QUALITY_LOST + + +def _connection_quality_from_proto(value: int) -> ConnectionQuality: + try: + return ConnectionQuality(value) + except ValueError: + return ConnectionQuality.QUALITY_UNKNOWN + + class Participant(ABC): def __init__(self, owned_info: proto_participant.OwnedParticipant) -> None: self._info = owned_info.info self._ffi_handle = FfiHandle(owned_info.handle.id) + self._connection_quality = ConnectionQuality.QUALITY_UNKNOWN @property @abstractmethod @@ -152,6 +175,16 @@ def permissions(self) -> proto_participant.ParticipantPermission: """The participant's permissions within the room.""" return self._info.permission + @property + def connection_quality(self) -> ConnectionQuality: + """The participant's most recently reported connection quality. + + Returns ``ConnectionQuality.QUALITY_UNKNOWN`` until the first + connection-quality update is received. Updated automatically whenever a + ``connection_quality_changed`` event fires for this participant. + """ + return self._connection_quality + @property def disconnect_reason( self, diff --git a/livekit-rtc/livekit/rtc/room.py b/livekit-rtc/livekit/rtc/room.py index e3619d9c..70484d92 100644 --- a/livekit-rtc/livekit/rtc/room.py +++ b/livekit-rtc/livekit/rtc/room.py @@ -33,7 +33,12 @@ from ._proto.rpc_pb2 import RpcMethodInvocationEvent from ._utils import BroadcastQueue from .e2ee import E2EEManager, E2EEOptions -from .participant import LocalParticipant, Participant, RemoteParticipant +from .participant import ( + LocalParticipant, + Participant, + RemoteParticipant, + _connection_quality_from_proto, +) from .track import RemoteAudioTrack, RemoteVideoTrack from .track_publication import RemoteTrackPublication, TrackPublication from .transcription import TranscriptionSegment @@ -897,12 +902,16 @@ def _on_room_event(self, event: proto_room.RoomEvent) -> None: ) elif which == "connection_quality_changed": identity = event.connection_quality_changed.participant_identity - # TODO: pass participant identity participant = self._retrieve_participant(identity) + quality = _connection_quality_from_proto( + event.connection_quality_changed.quality + ) + if participant: + participant._connection_quality = quality self.emit( "connection_quality_changed", participant, - event.connection_quality_changed.quality, + quality, ) elif which == "transcription_received": transcription = event.transcription_received diff --git a/tests/rtc/test_connection_quality.py b/tests/rtc/test_connection_quality.py new file mode 100644 index 00000000..14553961 --- /dev/null +++ b/tests/rtc/test_connection_quality.py @@ -0,0 +1,29 @@ +"""Unit tests for the local ConnectionQuality enum and the participant property.""" + +from livekit import rtc +from livekit.rtc._proto import participant_pb2 as proto_participant +from livekit.rtc._proto import room_pb2 as proto_room +from livekit.rtc.participant import _connection_quality_from_proto + + +def test_enum_values_align_with_proto() -> None: + CQ = rtc.ConnectionQuality + assert CQ.QUALITY_UNKNOWN == -1 + assert CQ.QUALITY_POOR == proto_room.ConnectionQuality.QUALITY_POOR + assert CQ.QUALITY_GOOD == proto_room.ConnectionQuality.QUALITY_GOOD + assert CQ.QUALITY_EXCELLENT == proto_room.ConnectionQuality.QUALITY_EXCELLENT + assert CQ.QUALITY_LOST == proto_room.ConnectionQuality.QUALITY_LOST + # backward compat: still int-comparable like the old proto enum export + assert CQ.QUALITY_GOOD == 1 + + +def test_from_proto_mapping() -> None: + assert _connection_quality_from_proto(1) is rtc.ConnectionQuality.QUALITY_GOOD + assert _connection_quality_from_proto(99) is rtc.ConnectionQuality.QUALITY_UNKNOWN + + +def test_participant_default_quality_is_unknown() -> None: + owned = proto_participant.OwnedParticipant() + owned.info.identity = "test" + participant = rtc.RemoteParticipant(owned) + assert participant.connection_quality is rtc.ConnectionQuality.QUALITY_UNKNOWN From e3b80e22a440b0a000ca2037cba97b5a7b7cac17 Mon Sep 17 00:00:00 2001 From: Ryan Gaus Date: Tue, 2 Jun 2026 16:16:07 -0400 Subject: [PATCH 2/2] fix: run make format --- livekit-rtc/livekit/rtc/room.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/livekit-rtc/livekit/rtc/room.py b/livekit-rtc/livekit/rtc/room.py index 70484d92..b76201b7 100644 --- a/livekit-rtc/livekit/rtc/room.py +++ b/livekit-rtc/livekit/rtc/room.py @@ -903,9 +903,7 @@ def _on_room_event(self, event: proto_room.RoomEvent) -> None: elif which == "connection_quality_changed": identity = event.connection_quality_changed.participant_identity participant = self._retrieve_participant(identity) - quality = _connection_quality_from_proto( - event.connection_quality_changed.quality - ) + quality = _connection_quality_from_proto(event.connection_quality_changed.quality) if participant: participant._connection_quality = quality self.emit(