From e4d74b15d2e72d869556312be3e8cefbdad40006 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20Monnom?= Date: Fri, 6 Feb 2026 15:01:08 -0800 Subject: [PATCH 1/3] Update _ffi_client.py --- livekit-rtc/livekit/rtc/_ffi_client.py | 85 +++++++++++++++----------- 1 file changed, 49 insertions(+), 36 deletions(-) diff --git a/livekit-rtc/livekit/rtc/_ffi_client.py b/livekit-rtc/livekit/rtc/_ffi_client.py index e7abef81..39875e07 100644 --- a/livekit-rtc/livekit/rtc/_ffi_client.py +++ b/livekit-rtc/livekit/rtc/_ffi_client.py @@ -34,22 +34,27 @@ atexit.register(_resource_files.close) +def _lib_name(): + if platform.system() == "Linux": + return "liblivekit_ffi.so" + elif platform.system() == "Darwin": + return "liblivekit_ffi.dylib" + elif platform.system() == "Windows": + return "livekit_ffi.dll" + return None + + def get_ffi_lib(): # allow to override the lib path using an env var libpath = os.environ.get("LIVEKIT_LIB_PATH", "").strip() if libpath: return ctypes.CDLL(libpath) - if platform.system() == "Linux": - libname = "liblivekit_ffi.so" - elif platform.system() == "Darwin": - libname = "liblivekit_ffi.dylib" - elif platform.system() == "Windows": - libname = "livekit_ffi.dll" - else: + libname = _lib_name() + if libname is None: raise Exception( - f"no ffi library found for platform {platform.system()}. \ - Set LIVEKIT_LIB_PATH to specify a the lib path" + f"no ffi library found for platform {platform.system()}. " + "Set LIVEKIT_LIB_PATH to specify the lib path" ) res = importlib.resources.files("livekit.rtc.resources") / libname @@ -58,32 +63,8 @@ def get_ffi_lib(): return ctypes.CDLL(str(path)) -ffi_lib = get_ffi_lib() ffi_cb_fnc = ctypes.CFUNCTYPE(None, ctypes.POINTER(ctypes.c_uint8), ctypes.c_size_t) -# C function types -ffi_lib.livekit_ffi_initialize.argtypes = [ - ffi_cb_fnc, - ctypes.c_bool, - ctypes.c_char_p, - ctypes.c_char_p, -] - -ffi_lib.livekit_ffi_request.argtypes = [ - ctypes.POINTER(ctypes.c_ubyte), - ctypes.c_size_t, - ctypes.POINTER(ctypes.POINTER(ctypes.c_ubyte)), - ctypes.POINTER(ctypes.c_size_t), -] -ffi_lib.livekit_ffi_request.restype = ctypes.c_uint64 - -ffi_lib.livekit_ffi_drop_handle.argtypes = [ctypes.c_uint64] -ffi_lib.livekit_ffi_drop_handle.restype = ctypes.c_bool - - -ffi_lib.livekit_ffi_dispose.argtypes = [] -ffi_lib.livekit_ffi_dispose.restype = None - INVALID_HANDLE = 0 @@ -102,7 +83,9 @@ def disposed(self) -> bool: def dispose(self) -> None: if self.handle != INVALID_HANDLE and not self._disposed: self._disposed = True - assert ffi_lib.livekit_ffi_drop_handle(ctypes.c_uint64(self.handle)) + assert FfiClient.instance._ffi_lib.livekit_ffi_drop_handle( + ctypes.c_uint64(self.handle) + ) def __repr__(self) -> str: return f"FfiHandle({self.handle})" @@ -214,10 +197,40 @@ def __init__(self) -> None: self._lock = threading.RLock() self._queue = FfiQueue[proto_ffi.FfiEvent]() - ffi_lib.livekit_ffi_initialize( + try: + self._ffi_lib = get_ffi_lib() + except Exception as e: + libname = _lib_name() or "livekit_ffi" + raise ImportError( + "failed to load %s: %s\n" + "Install the livekit package with: pip install livekit\n" + "Or set LIVEKIT_LIB_PATH to the path of the native library." + % (libname, e) + ) from None + self._ffi_lib.livekit_ffi_initialize.argtypes = [ + ffi_cb_fnc, + ctypes.c_bool, + ctypes.c_char_p, + ctypes.c_char_p, + ] + self._ffi_lib.livekit_ffi_request.argtypes = [ + ctypes.POINTER(ctypes.c_ubyte), + ctypes.c_size_t, + ctypes.POINTER(ctypes.POINTER(ctypes.c_ubyte)), + ctypes.POINTER(ctypes.c_size_t), + ] + self._ffi_lib.livekit_ffi_request.restype = ctypes.c_uint64 + self._ffi_lib.livekit_ffi_drop_handle.argtypes = [ctypes.c_uint64] + self._ffi_lib.livekit_ffi_drop_handle.restype = ctypes.c_bool + self._ffi_lib.livekit_ffi_dispose.argtypes = [] + self._ffi_lib.livekit_ffi_dispose.restype = None + + self._ffi_lib.livekit_ffi_initialize( ffi_event_callback, True, b"python", __version__.encode("ascii") ) + ffi_lib = self._ffi_lib + @atexit.register def _dispose_lk_ffi(): ffi_lib.livekit_ffi_dispose() @@ -233,7 +246,7 @@ def request(self, req: proto_ffi.FfiRequest) -> proto_ffi.FfiResponse: resp_ptr = ctypes.POINTER(ctypes.c_ubyte)() resp_len = ctypes.c_size_t() - handle = ffi_lib.livekit_ffi_request( + handle = self._ffi_lib.livekit_ffi_request( data, proto_len, ctypes.byref(resp_ptr), ctypes.byref(resp_len) ) assert handle != INVALID_HANDLE From 4f70eac07fafe1c2a586f301a645b315ad41865c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20Monnom?= Date: Fri, 6 Feb 2026 15:04:14 -0800 Subject: [PATCH 2/3] ruff --- livekit-rtc/livekit/rtc/_ffi_client.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/livekit-rtc/livekit/rtc/_ffi_client.py b/livekit-rtc/livekit/rtc/_ffi_client.py index 39875e07..4380b201 100644 --- a/livekit-rtc/livekit/rtc/_ffi_client.py +++ b/livekit-rtc/livekit/rtc/_ffi_client.py @@ -83,9 +83,7 @@ def disposed(self) -> bool: def dispose(self) -> None: if self.handle != INVALID_HANDLE and not self._disposed: self._disposed = True - assert FfiClient.instance._ffi_lib.livekit_ffi_drop_handle( - ctypes.c_uint64(self.handle) - ) + assert FfiClient.instance._ffi_lib.livekit_ffi_drop_handle(ctypes.c_uint64(self.handle)) def __repr__(self) -> str: return f"FfiHandle({self.handle})" @@ -204,8 +202,7 @@ def __init__(self) -> None: raise ImportError( "failed to load %s: %s\n" "Install the livekit package with: pip install livekit\n" - "Or set LIVEKIT_LIB_PATH to the path of the native library." - % (libname, e) + "Or set LIVEKIT_LIB_PATH to the path of the native library." % (libname, e) ) from None self._ffi_lib.livekit_ffi_initialize.argtypes = [ ffi_cb_fnc, From 68fb667d64ff1d885b394edb557cc3bd8badbd0d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20Monnom?= Date: Fri, 6 Feb 2026 15:10:26 -0800 Subject: [PATCH 3/3] upgrade ruff & format --- livekit-rtc/livekit/rtc/participant.py | 4 +--- tests/rtc/test_e2e.py | 18 ++++++++++++------ 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/livekit-rtc/livekit/rtc/participant.py b/livekit-rtc/livekit/rtc/participant.py index 22b07435..d61e79a2 100644 --- a/livekit-rtc/livekit/rtc/participant.py +++ b/livekit-rtc/livekit/rtc/participant.py @@ -318,9 +318,7 @@ async def perform_rpc( queue = FfiClient.instance.queue.subscribe() try: resp = FfiClient.instance.request(req) - cb = await queue.wait_for( - lambda e: (e.perform_rpc.async_id == resp.perform_rpc.async_id) - ) + cb = await queue.wait_for(lambda e: e.perform_rpc.async_id == resp.perform_rpc.async_id) finally: FfiClient.instance.queue.unsubscribe(queue) diff --git a/tests/rtc/test_e2e.py b/tests/rtc/test_e2e.py index c248cd1d..cd8497c3 100644 --- a/tests/rtc/test_e2e.py +++ b/tests/rtc/test_e2e.py @@ -314,9 +314,11 @@ def on_room2_track_unpublished( await room1.connect(url, token1) await assert_eventually( - lambda: len(events["connection_state_changed"]) > 0 - and events["connection_state_changed"][-1] - == f"room1-{rtc.ConnectionState.CONN_CONNECTED}", + lambda: ( + len(events["connection_state_changed"]) > 0 + and events["connection_state_changed"][-1] + == f"room1-{rtc.ConnectionState.CONN_CONNECTED}" + ), message="room1 connection_state_changed event not fired or did not reach CONN_CONNECTED state", ) @@ -370,9 +372,13 @@ def on_room2_track_unpublished( await room1.disconnect() await assert_eventually( - lambda: lambda: len(events["connection_state_changed"]) > 0 - and events["connection_state_changed"][-1] - == f"room1-{rtc.ConnectionState.CONN_DISCONNECTED}", + lambda: ( + lambda: ( + len(events["connection_state_changed"]) > 0 + and events["connection_state_changed"][-1] + == f"room1-{rtc.ConnectionState.CONN_DISCONNECTED}" + ) + ), message="room1 disconnected event not fired", )