Skip to content

expose rtc session stats #429

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
May 8, 2025
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion livekit-api/livekit/api/room_service.py
Original file line number Diff line number Diff line change
@@ -215,7 +215,11 @@ async def forward_participant(self, forward: ForwardParticipantRequest) -> None:
SVC,
"ForwardParticipant",
forward,
self._auth_header(VideoGrants(room_admin=True, room=forward.room, destination_room=forward.destination_room)),
self._auth_header(
VideoGrants(
room_admin=True, room=forward.room, destination_room=forward.destination_room
)
),
ForwardParticipantResponse,
)

11 changes: 10 additions & 1 deletion livekit-rtc/livekit/rtc/__init__.py
Original file line number Diff line number Diff line change
@@ -50,7 +50,15 @@
Participant,
RemoteParticipant,
)
from .room import ConnectError, DataPacket, Room, RoomOptions, RtcConfiguration, SipDTMF
from .room import (
ConnectError,
DataPacket,
Room,
RoomOptions,
RtcConfiguration,
SipDTMF,
RtcStats,
)
from .track import (
AudioTrack,
LocalAudioTrack,
@@ -123,6 +131,7 @@
"RoomOptions",
"RtcConfiguration",
"SipDTMF",
"RtcStats",
"DataPacket",
"LocalAudioTrack",
"LocalVideoTrack",
32 changes: 32 additions & 0 deletions livekit-rtc/livekit/rtc/room.py
Original file line number Diff line number Diff line change
@@ -24,6 +24,7 @@
from ._proto import ffi_pb2 as proto_ffi
from ._proto import participant_pb2 as proto_participant
from ._proto import room_pb2 as proto_room
from ._proto import stats_pb2 as proto_stats
from ._proto.room_pb2 import ConnectionState
from ._proto.track_pb2 import TrackKind
from ._proto.rpc_pb2 import RpcMethodInvocationEvent
@@ -120,6 +121,12 @@ class SipDTMF:
"""Participant who sent the DTMF digit. None when sent by a server SDK."""


@dataclass
class RtcStats:
publisher_stats: list[proto_stats.RtcStats]
subscriber_stats: list[proto_stats.RtcStats]


class ConnectError(Exception):
def __init__(self, message: str):
self.message = message
@@ -408,6 +415,30 @@ def on_participant_connected(participant):
# start listening to room events
self._task = self._loop.create_task(self._listen_task())

async def get_rtc_stats(self) -> RtcStats:
if not self.isconnected():
raise RuntimeError("the room isn't connected")

req = proto_ffi.FfiRequest()
req.get_session_stats.room_handle = self._ffi_handle.handle # type: ignore

queue = FfiClient.instance.queue.subscribe()
try:
resp = FfiClient.instance.request(req)
cb: proto_ffi.FfiEvent = await queue.wait_for(
lambda e: e.get_session_stats.async_id == resp.get_session_stats.async_id
)
finally:
FfiClient.instance.queue.unsubscribe(queue)

if cb.get_session_stats.error:
raise RuntimeError(cb.get_session_stats.error)

publisher_stats = list(cb.get_session_stats.result.publisher_stats)
subscriber_stats = list(cb.get_session_stats.result.subscriber_stats)

return RtcStats(publisher_stats=publisher_stats, subscriber_stats=subscriber_stats)

def register_byte_stream_handler(self, topic: str, handler: ByteStreamHandler):
existing_handler = self._byte_stream_handlers.get(topic)
if existing_handler is None:
@@ -446,6 +477,7 @@ async def disconnect(self) -> None:
await queue.wait_for(lambda e: e.disconnect.async_id == resp.disconnect.async_id)
finally:
FfiClient.instance.queue.unsubscribe(queue)

await self._task
FfiClient.instance.queue.unsubscribe(self._ffi_queue)