livekit 1.2.1__py3-none-win_arm64.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. livekit/rtc/__init__.py +264 -0
  2. livekit/rtc/_ffi_client.py +321 -0
  3. livekit/rtc/_proto/__init__.py +0 -0
  4. livekit/rtc/_proto/audio_frame_pb2.py +159 -0
  5. livekit/rtc/_proto/audio_frame_pb2.pyi +1560 -0
  6. livekit/rtc/_proto/capture_pb2.py +70 -0
  7. livekit/rtc/_proto/capture_pb2.pyi +559 -0
  8. livekit/rtc/_proto/data_stream_pb2.py +171 -0
  9. livekit/rtc/_proto/data_stream_pb2.pyi +1498 -0
  10. livekit/rtc/_proto/data_track_pb2.py +120 -0
  11. livekit/rtc/_proto/data_track_pb2.pyi +1073 -0
  12. livekit/rtc/_proto/e2ee_pb2.py +81 -0
  13. livekit/rtc/_proto/e2ee_pb2.pyi +591 -0
  14. livekit/rtc/_proto/ffi_pb2.py +55 -0
  15. livekit/rtc/_proto/ffi_pb2.pyi +1246 -0
  16. livekit/rtc/_proto/handle_pb2.py +27 -0
  17. livekit/rtc/_proto/handle_pb2.pyi +51 -0
  18. livekit/rtc/_proto/participant_pb2.py +45 -0
  19. livekit/rtc/_proto/participant_pb2.pyi +313 -0
  20. livekit/rtc/_proto/room_pb2.py +311 -0
  21. livekit/rtc/_proto/room_pb2.pyi +3136 -0
  22. livekit/rtc/_proto/rpc_pb2.py +47 -0
  23. livekit/rtc/_proto/rpc_pb2.pyi +261 -0
  24. livekit/rtc/_proto/stats_pb2.py +123 -0
  25. livekit/rtc/_proto/stats_pb2.pyi +1514 -0
  26. livekit/rtc/_proto/track_pb2.py +76 -0
  27. livekit/rtc/_proto/track_pb2.pyi +544 -0
  28. livekit/rtc/_proto/track_publication_pb2.py +39 -0
  29. livekit/rtc/_proto/track_publication_pb2.pyi +145 -0
  30. livekit/rtc/_proto/video_frame_pb2.py +97 -0
  31. livekit/rtc/_proto/video_frame_pb2.pyi +838 -0
  32. livekit/rtc/_utils.py +187 -0
  33. livekit/rtc/apm.py +119 -0
  34. livekit/rtc/audio_filter.py +23 -0
  35. livekit/rtc/audio_frame.py +242 -0
  36. livekit/rtc/audio_mixer.py +247 -0
  37. livekit/rtc/audio_resampler.py +174 -0
  38. livekit/rtc/audio_source.py +191 -0
  39. livekit/rtc/audio_stream.py +354 -0
  40. livekit/rtc/data_stream.py +640 -0
  41. livekit/rtc/data_track.py +296 -0
  42. livekit/rtc/e2ee.py +320 -0
  43. livekit/rtc/encoded_video.py +127 -0
  44. livekit/rtc/event_emitter.py +215 -0
  45. livekit/rtc/frame_processor.py +37 -0
  46. livekit/rtc/jupyter.py +68 -0
  47. livekit/rtc/log.py +3 -0
  48. livekit/rtc/media_devices.py +596 -0
  49. livekit/rtc/participant.py +1171 -0
  50. livekit/rtc/platform_audio.py +426 -0
  51. livekit/rtc/py.typed +0 -0
  52. livekit/rtc/resources/LICENSE.md +4055 -0
  53. livekit/rtc/resources/__init__.py +1 -0
  54. livekit/rtc/resources/jupyter-html/index.html +101 -0
  55. livekit/rtc/resources/livekit_ffi.dll +0 -0
  56. livekit/rtc/resources/livekit_ffi.h +42 -0
  57. livekit/rtc/room.py +1304 -0
  58. livekit/rtc/rpc.py +244 -0
  59. livekit/rtc/synchronizer.py +202 -0
  60. livekit/rtc/track.py +264 -0
  61. livekit/rtc/track_publication.py +145 -0
  62. livekit/rtc/transcription.py +19 -0
  63. livekit/rtc/utils.py +138 -0
  64. livekit/rtc/version.py +1 -0
  65. livekit/rtc/video_frame.py +347 -0
  66. livekit/rtc/video_source.py +74 -0
  67. livekit/rtc/video_stream.py +191 -0
  68. livekit-1.2.1.dist-info/METADATA +80 -0
  69. livekit-1.2.1.dist-info/RECORD +71 -0
  70. livekit-1.2.1.dist-info/WHEEL +5 -0
  71. livekit-1.2.1.dist-info/top_level.txt +1 -0
@@ -0,0 +1,264 @@
1
+ # Copyright 2023 LiveKit, Inc.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ """LiveKit SDK for Python
16
+ `pip install livekit`
17
+
18
+ See https://docs.livekit.io/home/client/connect/#installing-the-livekit-sdk for more information.
19
+ """
20
+
21
+ from ._proto import stats_pb2 as stats
22
+ from ._proto.e2ee_pb2 import EncryptionState, EncryptionType, KeyDerivationFunction
23
+ from ._proto.participant_pb2 import (
24
+ ParticipantKind,
25
+ ParticipantKindDetail,
26
+ ParticipantState,
27
+ DisconnectReason,
28
+ )
29
+ from ._proto.room_pb2 import (
30
+ ConnectionState,
31
+ ContinualGatheringPolicy,
32
+ DataPacketKind,
33
+ IceServer,
34
+ IceTransportType,
35
+ SimulateScenarioKind,
36
+ TrackPublishOptions,
37
+ VideoEncoding,
38
+ VideoEncoderBackend,
39
+ )
40
+ from ._proto.track_pb2 import (
41
+ FrameMetadataFeature,
42
+ StreamState,
43
+ TrackKind,
44
+ TrackSource,
45
+ ParticipantTrackPermission,
46
+ )
47
+ from ._proto.video_frame_pb2 import (
48
+ EncodedFrameType,
49
+ FrameMetadata,
50
+ VideoBufferType,
51
+ VideoCodec,
52
+ VideoRotation,
53
+ )
54
+ from ._proto.track_publication_pb2 import VideoQuality
55
+ from .audio_frame import AudioFrame
56
+ from .audio_source import AudioSource
57
+ from .audio_stream import AudioFrameEvent, AudioStream, NoiseCancellationOptions
58
+ from .audio_filter import AudioFilter
59
+ from .e2ee import (
60
+ E2EEManager,
61
+ E2EEOptions,
62
+ FrameCryptor,
63
+ KeyProvider,
64
+ KeyProviderOptions,
65
+ )
66
+ from .participant import (
67
+ ConnectionQuality,
68
+ DegradationPreference,
69
+ LocalParticipant,
70
+ Participant,
71
+ RemoteParticipant,
72
+ )
73
+ from .room import (
74
+ ConnectError,
75
+ DataPacket,
76
+ DataStreamOptions,
77
+ Room,
78
+ RoomOptions,
79
+ RtcConfiguration,
80
+ SipDTMF,
81
+ RtcStats,
82
+ )
83
+ from .track import (
84
+ AudioTrack,
85
+ LocalAudioTrack,
86
+ LocalTrack,
87
+ LocalVideoTrack,
88
+ RemoteAudioTrack,
89
+ RemoteTrack,
90
+ RemoteVideoTrack,
91
+ Track,
92
+ VideoTrack,
93
+ )
94
+ from .event_emitter import EventEmitter
95
+ from .track_publication import (
96
+ LocalTrackPublication,
97
+ RemoteTrackPublication,
98
+ TrackPublication,
99
+ )
100
+ from .transcription import Transcription, TranscriptionSegment
101
+ from .version import __version__
102
+ from .video_frame import (
103
+ VideoFrame,
104
+ )
105
+ from .encoded_video import (
106
+ EncodedVideoFrame,
107
+ EncodedVideoSource,
108
+ EncodedVideoSourceFeedback,
109
+ EncodedRateControl,
110
+ )
111
+ from .video_source import VideoSource
112
+ from .video_stream import VideoFrameEvent, VideoStream
113
+ from .audio_resampler import AudioResampler, AudioResamplerQuality
114
+ from .audio_mixer import AudioMixer
115
+ from .apm import AudioProcessingModule
116
+ from .platform_audio import (
117
+ PlatformAudio,
118
+ PlatformAudioSource,
119
+ PlatformAudioOptions,
120
+ PlatformAudioError,
121
+ AudioDeviceInfo,
122
+ )
123
+
124
+ try:
125
+ from .media_devices import MediaDevices as MediaDevices
126
+
127
+ _HAS_MEDIA_DEVICES = True
128
+ except Exception: # pragma: no cover - optional dependency (sounddevice)
129
+ _HAS_MEDIA_DEVICES = False
130
+ from .utils import combine_audio_frames
131
+ from .rpc import RpcCallInfo, RpcError, RpcInterceptor, RpcInvocationData
132
+ from .synchronizer import AVSynchronizer
133
+ from .data_stream import (
134
+ TextStreamInfo,
135
+ ByteStreamInfo,
136
+ TextStreamReader,
137
+ TextStreamWriter,
138
+ ByteStreamWriter,
139
+ ByteStreamReader,
140
+ StreamError,
141
+ )
142
+ from .data_track import (
143
+ LocalDataTrack,
144
+ RemoteDataTrack,
145
+ DataTrackStream,
146
+ DataTrackFrame,
147
+ DataTrackInfo,
148
+ PushFrameError,
149
+ SubscribeDataTrackError,
150
+ )
151
+ from .frame_processor import FrameProcessor
152
+
153
+ __all__ = [
154
+ "EncodedFrameType",
155
+ "EncodedVideoFrame",
156
+ "EncodedVideoSource",
157
+ "EncodedVideoSourceFeedback",
158
+ "EncodedRateControl",
159
+ "VideoEncoderBackend",
160
+ "ConnectionQuality",
161
+ "ConnectionState",
162
+ "DataPacketKind",
163
+ "DegradationPreference",
164
+ "TrackPublishOptions",
165
+ "IceTransportType",
166
+ "ContinualGatheringPolicy",
167
+ "IceServer",
168
+ "EncryptionType",
169
+ "EncryptionState",
170
+ "KeyDerivationFunction",
171
+ "StreamState",
172
+ "TrackKind",
173
+ "TrackSource",
174
+ "FrameMetadataFeature",
175
+ "ParticipantTrackPermission",
176
+ "VideoBufferType",
177
+ "VideoRotation",
178
+ "FrameMetadata",
179
+ "stats",
180
+ "AudioFrame",
181
+ "AudioSource",
182
+ "AudioStream",
183
+ "NoiseCancellationOptions",
184
+ "AudioFilter",
185
+ "AudioFrameEvent",
186
+ "LocalParticipant",
187
+ "Participant",
188
+ "ParticipantKind",
189
+ "ParticipantKindDetail",
190
+ "ParticipantState",
191
+ "DisconnectReason",
192
+ "RemoteParticipant",
193
+ "ConnectError",
194
+ "Room",
195
+ "RoomOptions",
196
+ "DataStreamOptions",
197
+ "RtcConfiguration",
198
+ "SimulateScenarioKind",
199
+ "SipDTMF",
200
+ "RtcStats",
201
+ "DataPacket",
202
+ "LocalAudioTrack",
203
+ "LocalVideoTrack",
204
+ "RemoteAudioTrack",
205
+ "RemoteVideoTrack",
206
+ "Track",
207
+ "LocalTrack",
208
+ "RemoteTrack",
209
+ "AudioTrack",
210
+ "VideoTrack",
211
+ "E2EEManager",
212
+ "E2EEOptions",
213
+ "KeyProviderOptions",
214
+ "KeyProvider",
215
+ "FrameCryptor",
216
+ "LocalTrackPublication",
217
+ "RemoteTrackPublication",
218
+ "TrackPublication",
219
+ "Transcription",
220
+ "TranscriptionSegment",
221
+ "VideoCodec",
222
+ "VideoEncoding",
223
+ "VideoQuality",
224
+ "VideoFrame",
225
+ "VideoFrameEvent",
226
+ "VideoSource",
227
+ "VideoStream",
228
+ "AudioMixer",
229
+ "AudioResampler",
230
+ "AudioResamplerQuality",
231
+ "RpcError",
232
+ "RpcInvocationData",
233
+ "RpcCallInfo",
234
+ "RpcInterceptor",
235
+ "EventEmitter",
236
+ "combine_audio_frames",
237
+ "AVSynchronizer",
238
+ "TextStreamInfo",
239
+ "ByteStreamInfo",
240
+ "TextStreamReader",
241
+ "TextStreamWriter",
242
+ "ByteStreamReader",
243
+ "ByteStreamWriter",
244
+ "StreamError",
245
+ "AudioProcessingModule",
246
+ "PlatformAudio",
247
+ "PlatformAudioSource",
248
+ "PlatformAudioOptions",
249
+ "PlatformAudioError",
250
+ "AudioDeviceInfo",
251
+ "FrameProcessor",
252
+ "LocalDataTrack",
253
+ "RemoteDataTrack",
254
+ "DataTrackStream",
255
+ "DataTrackFrame",
256
+ "DataTrackInfo",
257
+ "PushFrameError",
258
+ "SubscribeDataTrackError",
259
+ "__version__",
260
+ ]
261
+
262
+ # add MediaDevices if available
263
+ if _HAS_MEDIA_DEVICES:
264
+ __all__.append("MediaDevices")
@@ -0,0 +1,321 @@
1
+ # Copyright 2023 LiveKit, Inc.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ from __future__ import annotations
16
+
17
+ import signal
18
+ import asyncio
19
+ import sys
20
+ from contextlib import ExitStack
21
+ import ctypes
22
+ import importlib.resources
23
+ from .version import __version__
24
+ import logging
25
+ import os
26
+ import platform
27
+ import atexit
28
+ import threading
29
+ import weakref
30
+ from typing import Callable, Generic, List, Optional, TypeVar
31
+
32
+ from ._proto import ffi_pb2 as proto_ffi
33
+ from ._utils import Queue, classproperty
34
+ from .log import logger
35
+
36
+ _resource_files = ExitStack()
37
+ atexit.register(_resource_files.close)
38
+
39
+
40
+ def _lib_name() -> Optional[str]:
41
+ if platform.system() == "Linux":
42
+ return "liblivekit_ffi.so"
43
+ elif platform.system() == "Darwin":
44
+ return "liblivekit_ffi.dylib"
45
+ elif platform.system() == "Windows":
46
+ return "livekit_ffi.dll"
47
+ return None
48
+
49
+
50
+ def get_ffi_lib() -> ctypes.CDLL:
51
+ # allow to override the lib path using an env var
52
+ libpath = os.environ.get("LIVEKIT_LIB_PATH", "").strip()
53
+ if libpath:
54
+ return ctypes.CDLL(libpath)
55
+
56
+ libname = _lib_name()
57
+ if libname is None:
58
+ raise Exception(
59
+ f"no ffi library found for platform {platform.system()}. "
60
+ "Set LIVEKIT_LIB_PATH to specify the lib path"
61
+ )
62
+
63
+ res = importlib.resources.files("livekit.rtc.resources") / libname
64
+ ctx = importlib.resources.as_file(res)
65
+ path = _resource_files.enter_context(ctx)
66
+ return ctypes.CDLL(str(path))
67
+
68
+
69
+ ffi_cb_fnc = ctypes.CFUNCTYPE(None, ctypes.POINTER(ctypes.c_uint8), ctypes.c_size_t)
70
+
71
+ INVALID_HANDLE = 0
72
+
73
+
74
+ _live_handles: weakref.WeakSet[FfiHandle] = weakref.WeakSet()
75
+
76
+
77
+ class FfiHandle:
78
+ def __init__(self, handle: int) -> None:
79
+ self.handle = handle
80
+ self._disposed = False
81
+ _live_handles.add(self)
82
+
83
+ def __del__(self) -> None:
84
+ self.dispose()
85
+
86
+ @property
87
+ def disposed(self) -> bool:
88
+ return self._disposed
89
+
90
+ def mark_consumed(self) -> None:
91
+ """Marks the handle as already released on the native side.
92
+
93
+ Some requests take ownership of their handle (``take_handle``) rather
94
+ than borrowing it. After issuing one, the handle must not be dropped
95
+ again, so this suppresses the drop that dispose/GC would otherwise do.
96
+ """
97
+ self._disposed = True
98
+
99
+ def dispose(self) -> None:
100
+ if self.handle != INVALID_HANDLE and not self._disposed:
101
+ self._disposed = True
102
+ ffi = FfiClient._instance
103
+ if ffi is None or ffi._pid != os.getpid() or getattr(FfiClient, "_ffi_disposed", False):
104
+ return
105
+ dropped = ffi._ffi_lib.livekit_ffi_drop_handle(ctypes.c_uint64(self.handle))
106
+ assert dropped
107
+
108
+ def __repr__(self) -> str:
109
+ return f"FfiHandle({self.handle})"
110
+
111
+
112
+ T = TypeVar("T")
113
+
114
+
115
+ class FfiQueue(Generic[T]):
116
+ def __init__(self) -> None:
117
+ self._lock = threading.RLock()
118
+ # Format: (queue, loop, filter_fn or None)
119
+ self._subscribers: List[
120
+ tuple[Queue[T], asyncio.AbstractEventLoop, Optional[Callable[[T], bool]]]
121
+ ] = []
122
+
123
+ def put(self, item: T) -> None:
124
+ with self._lock:
125
+ for queue, loop, filter_fn in self._subscribers:
126
+ # If filter provided, skip items that don't match
127
+ if filter_fn is not None:
128
+ try:
129
+ if not filter_fn(item):
130
+ continue
131
+ except Exception:
132
+ pass # On filter error, deliver the item
133
+
134
+ try:
135
+ loop.call_soon_threadsafe(queue.put_nowait, item)
136
+ except Exception as e:
137
+ # this could happen if user closes the runloop without unsubscribing first
138
+ # it's not good when it does occur, but we should not fail the entire runloop
139
+ logger.error("error putting to queue: %s", e)
140
+
141
+ def subscribe(
142
+ self,
143
+ loop: Optional[asyncio.AbstractEventLoop] = None,
144
+ filter_fn: Optional[Callable[[T], bool]] = None,
145
+ ) -> Queue[T]:
146
+ """Subscribe to FFI events.
147
+
148
+ Args:
149
+ loop: Event loop to use (defaults to current).
150
+ filter_fn: Optional filter function. If provided, only items where
151
+ filter_fn(item) returns True will be delivered.
152
+ If None, receives all events (original behavior).
153
+
154
+ Returns:
155
+ Queue to receive events from.
156
+ """
157
+ with self._lock:
158
+ queue = Queue[T]()
159
+ loop = loop or asyncio.get_event_loop()
160
+ self._subscribers.append((queue, loop, filter_fn))
161
+ return queue
162
+
163
+ def unsubscribe(self, queue: Queue[T]) -> None:
164
+ with self._lock:
165
+ # looping here is ok, since we don't expect a lot of subscribers
166
+ for i, (q, _, _) in enumerate(self._subscribers):
167
+ if q == queue:
168
+ self._subscribers.pop(i)
169
+ break
170
+
171
+
172
+ @ctypes.CFUNCTYPE(None, ctypes.POINTER(ctypes.c_uint8), ctypes.c_size_t) # type: ignore[untyped-decorator]
173
+ def ffi_event_callback(
174
+ data_ptr: ctypes.POINTER(ctypes.c_uint8), # type: ignore
175
+ data_len: ctypes.c_size_t,
176
+ ) -> None:
177
+ event_data = ctypes.string_at(data_ptr, int(data_len))
178
+ event = proto_ffi.FfiEvent()
179
+ event.ParseFromString(event_data)
180
+
181
+ which = event.WhichOneof("message")
182
+ if which == "logs":
183
+ for record in event.logs.records:
184
+ level = to_python_level(record.level)
185
+ debug_env = os.environ.get("LIVEKIT_RTC_DEBUG", "").strip().lower()
186
+ rtc_debug = debug_env in ("true", "1")
187
+
188
+ if level == logging.DEBUG and not rtc_debug:
189
+ # ignore the rtc debug logs by default
190
+ if record.target == "libwebrtc" or record.target.startswith("livekit"):
191
+ continue
192
+
193
+ if level is not None:
194
+ logger.log(
195
+ level,
196
+ "%s:%s:%s - %s",
197
+ record.target,
198
+ record.line,
199
+ record.module_path,
200
+ record.message,
201
+ )
202
+
203
+ return # no need to queue the logs
204
+ elif which == "panic":
205
+ print("FFI Panic: ", event.panic.message, file=sys.stderr, flush=True)
206
+ # We are in a unrecoverable state, terminate the process
207
+ os.kill(os.getpid(), signal.SIGTERM)
208
+ return
209
+
210
+ FfiClient.instance.queue.put(event)
211
+
212
+
213
+ def to_python_level(level: proto_ffi.LogLevel.ValueType) -> Optional[int]:
214
+ if level == proto_ffi.LogLevel.LOG_ERROR:
215
+ return logging.ERROR
216
+ elif level == proto_ffi.LogLevel.LOG_WARN:
217
+ return logging.WARN
218
+ elif level == proto_ffi.LogLevel.LOG_INFO:
219
+ return logging.INFO
220
+ elif level == proto_ffi.LogLevel.LOG_DEBUG:
221
+ return logging.DEBUG
222
+ elif level == proto_ffi.LogLevel.LOG_TRACE:
223
+ # Don't show TRACE logs inside DEBUG, it is too verbos
224
+ # Python's logging doesn't have a TRACE level
225
+ # return logging.DEBUG
226
+ pass
227
+
228
+ return None
229
+
230
+
231
+ class FfiClient:
232
+ _instance: Optional["FfiClient"] = None
233
+ _ffi_disposed: bool = False
234
+ _instance_lock = threading.Lock()
235
+
236
+ @classproperty
237
+ def instance(cls) -> "FfiClient":
238
+ if cls._instance is not None:
239
+ return cls._instance
240
+ # threads that race here would each get their own client and lose events sent to the other
241
+ with cls._instance_lock:
242
+ if cls._instance is None:
243
+ cls._instance = FfiClient()
244
+ return cls._instance
245
+
246
+ def __init__(self) -> None:
247
+ FfiClient._ffi_disposed = False
248
+ self._pid = os.getpid()
249
+ self._lock = threading.RLock()
250
+ self._queue = FfiQueue[proto_ffi.FfiEvent]()
251
+
252
+ try:
253
+ self._ffi_lib = get_ffi_lib()
254
+ except Exception as e:
255
+ libname = _lib_name() or "livekit_ffi"
256
+ raise ImportError(
257
+ "failed to load %s: %s\n"
258
+ "Install the livekit package with: pip install livekit\n"
259
+ "Or set LIVEKIT_LIB_PATH to the path of the native library." % (libname, e)
260
+ ) from None
261
+ self._ffi_lib.livekit_ffi_initialize.argtypes = [
262
+ ffi_cb_fnc,
263
+ ctypes.c_bool,
264
+ ctypes.c_char_p,
265
+ ctypes.c_char_p,
266
+ ]
267
+ self._ffi_lib.livekit_ffi_request.argtypes = [
268
+ ctypes.POINTER(ctypes.c_ubyte),
269
+ ctypes.c_size_t,
270
+ ctypes.POINTER(ctypes.POINTER(ctypes.c_ubyte)),
271
+ ctypes.POINTER(ctypes.c_size_t),
272
+ ]
273
+ self._ffi_lib.livekit_ffi_request.restype = ctypes.c_uint64
274
+ self._ffi_lib.livekit_ffi_drop_handle.argtypes = [ctypes.c_uint64]
275
+ self._ffi_lib.livekit_ffi_drop_handle.restype = ctypes.c_bool
276
+ self._ffi_lib.livekit_ffi_dispose.argtypes = []
277
+ self._ffi_lib.livekit_ffi_dispose.restype = None
278
+
279
+ self._ffi_lib.livekit_ffi_initialize(
280
+ ffi_event_callback, True, b"python", __version__.encode("ascii")
281
+ )
282
+
283
+ @atexit.register
284
+ def _dispose_lk_ffi() -> None:
285
+ self._dispose()
286
+
287
+ def _dispose(self) -> None:
288
+ if self._pid != os.getpid() or FfiClient._ffi_disposed:
289
+ return
290
+ FfiClient._ffi_disposed = True
291
+ for handle in list(_live_handles):
292
+ handle.mark_consumed()
293
+ self._ffi_lib.livekit_ffi_dispose()
294
+
295
+ @property
296
+ def queue(self) -> FfiQueue[proto_ffi.FfiEvent]:
297
+ return self._queue
298
+
299
+ def request(self, req: proto_ffi.FfiRequest) -> proto_ffi.FfiResponse:
300
+ if self._pid != os.getpid():
301
+ raise RuntimeError(
302
+ "livekit.rtc was used in a parent process before fork(); the native "
303
+ "runtime cannot be used across fork(). Do not create or connect a Room "
304
+ "(or use any livekit.rtc object) before forking child processes."
305
+ )
306
+ proto_data = req.SerializeToString()
307
+ proto_len = len(proto_data)
308
+ data = ctypes.cast(ctypes.c_char_p(proto_data), ctypes.POINTER(ctypes.c_ubyte))
309
+
310
+ resp_ptr = ctypes.POINTER(ctypes.c_ubyte)()
311
+ resp_len = ctypes.c_size_t()
312
+ handle = self._ffi_lib.livekit_ffi_request(
313
+ data, proto_len, ctypes.byref(resp_ptr), ctypes.byref(resp_len)
314
+ )
315
+ assert handle != INVALID_HANDLE
316
+
317
+ resp_data = ctypes.string_at(resp_ptr, resp_len.value)
318
+ resp = proto_ffi.FfiResponse()
319
+ resp.ParseFromString(resp_data)
320
+ FfiHandle(handle).dispose()
321
+ return resp
File without changes