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,354 @@
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 asyncio
18
+ import json
19
+ from dataclasses import dataclass
20
+ from typing import Any, AsyncIterator, Optional
21
+
22
+ from ._ffi_client import FfiClient, FfiHandle
23
+ from ._proto import audio_frame_pb2 as proto_audio_frame
24
+ from ._proto import ffi_pb2 as proto_ffi
25
+ from ._proto.track_pb2 import TrackSource
26
+ from ._utils import RingQueue, task_done_logger
27
+ from .audio_frame import AudioFrame
28
+ from .log import logger
29
+ from .participant import Participant
30
+ from .track import Track
31
+ from .frame_processor import FrameProcessor
32
+
33
+
34
+ @dataclass
35
+ class AudioFrameEvent:
36
+ """An event representing a received audio frame.
37
+
38
+ Attributes:
39
+ frame (AudioFrame): The received audio frame.
40
+ """
41
+
42
+ frame: AudioFrame
43
+
44
+
45
+ @dataclass
46
+ class NoiseCancellationOptions:
47
+ module_id: str
48
+ options: dict[str, Any]
49
+
50
+
51
+ class AudioStream:
52
+ """An asynchronous audio stream for receiving audio frames from a participant or track.
53
+
54
+ The `AudioStream` class provides an asynchronous iterator over audio frames received from
55
+ a specific track or participant. It allows you to receive audio frames in real-time with
56
+ customizable sample rates and channel configurations.
57
+ """
58
+
59
+ def __init__(
60
+ self,
61
+ track: Track,
62
+ loop: Optional[asyncio.AbstractEventLoop] = None,
63
+ capacity: int = 0,
64
+ sample_rate: int = 48000,
65
+ num_channels: int = 1,
66
+ frame_size_ms: int | None = None,
67
+ noise_cancellation: Optional[NoiseCancellationOptions | FrameProcessor[AudioFrame]] = None,
68
+ auto_close_noise_cancellation: bool = True,
69
+ **kwargs: Any,
70
+ ) -> None:
71
+ """Initialize an `AudioStream` instance.
72
+
73
+ Args:
74
+ track (Optional[Track]): The audio track from which to receive audio. If not provided,
75
+ you must specify `participant` and `track_source` in `kwargs`.
76
+ loop (Optional[asyncio.AbstractEventLoop], optional): The event loop to use.
77
+ Defaults to the current event loop.
78
+ capacity (int, optional): The capacity of the internal frame queue. Defaults to 0 (unbounded).
79
+ sample_rate (int, optional): The sample rate for the audio stream in Hz.
80
+ Defaults to 48000.
81
+ num_channels (int, optional): The number of audio channels. Defaults to 1.
82
+ noise_cancellation (Optional[NoiseCancellationOptions | FrameProcessor[AudioFrame]], optional):
83
+ If noise cancellation is used, pass a `NoiseCancellationOptions` or `FrameProcessor[AudioFrame]` instance
84
+ created by the noise cancellation module.
85
+ auto_close_noise_cancellation (bool):
86
+ When the audio stream closes, should the FrameProcessor's close method be run? If
87
+ False, then the frame processor can be reused with another AudioStream.
88
+
89
+ Example:
90
+ ```python
91
+ audio_stream = AudioStream(
92
+ track=audio_track,
93
+ sample_rate=44100,
94
+ num_channels=2,
95
+ )
96
+
97
+ audio_stream = AudioStream.from_track(
98
+ track=audio_track,
99
+ sample_rate=44100,
100
+ num_channels=2,
101
+ )
102
+ ```
103
+ """
104
+ self._track: Track | None = track
105
+ self._sample_rate = sample_rate
106
+ self._num_channels = num_channels
107
+ self._frame_size_ms = frame_size_ms
108
+ self._loop = loop or asyncio.get_event_loop()
109
+ # Subscribe before creating the stream so none of its events are missed. Its
110
+ # handle is only known once the request returns; until then every audio stream
111
+ # event is let through, and _run drops the ones of other streams.
112
+ handle_id: int | None = None
113
+ self._ffi_queue = FfiClient.instance.queue.subscribe(
114
+ self._loop,
115
+ filter_fn=lambda e: (
116
+ e.WhichOneof("message") == "audio_stream_event"
117
+ and (handle_id is None or e.audio_stream_event.stream_handle == handle_id)
118
+ ),
119
+ )
120
+ self._queue: RingQueue[AudioFrameEvent | None] = RingQueue(capacity)
121
+
122
+ self._audio_filter_module: str | None = None
123
+ self._audio_filter_options: dict[str, Any] | None = None
124
+ self._processor: FrameProcessor[AudioFrame] | None = None
125
+ self._processor_auto_close = True
126
+ if isinstance(noise_cancellation, NoiseCancellationOptions):
127
+ self._audio_filter_module = noise_cancellation.module_id
128
+ self._audio_filter_options = noise_cancellation.options
129
+ elif isinstance(noise_cancellation, FrameProcessor):
130
+ self._processor = noise_cancellation
131
+ self._processor_auto_close = auto_close_noise_cancellation
132
+
133
+ stream: Any = None
134
+ if "participant" in kwargs:
135
+ stream = self._create_owned_stream_from_participant(
136
+ participant=kwargs["participant"], track_source=kwargs["track_source"]
137
+ )
138
+ else:
139
+ stream = self._create_owned_stream()
140
+ self._ffi_handle = FfiHandle(stream.handle.id)
141
+ handle_id = stream.handle.id
142
+ self._info = stream.info
143
+
144
+ # Start _run only after _ffi_handle is set, so a failure above doesn't orphan
145
+ # a task that dereferences an unassigned _ffi_handle.
146
+ self._task = self._loop.create_task(self._run())
147
+ self._task.add_done_callback(task_done_logger)
148
+
149
+ if self._track is not None:
150
+ self._track._register_audio_stream(self)
151
+
152
+ @classmethod
153
+ def from_participant(
154
+ cls,
155
+ *,
156
+ participant: Participant,
157
+ track_source: TrackSource.ValueType,
158
+ loop: Optional[asyncio.AbstractEventLoop] = None,
159
+ capacity: int = 0,
160
+ sample_rate: int = 48000,
161
+ num_channels: int = 1,
162
+ frame_size_ms: int | None = None,
163
+ noise_cancellation: Optional[NoiseCancellationOptions | FrameProcessor[AudioFrame]] = None,
164
+ auto_close_noise_cancellation: bool = True,
165
+ ) -> AudioStream:
166
+ """Create an `AudioStream` from a participant's audio track.
167
+
168
+ Args:
169
+ participant (Participant): The participant from whom to receive audio.
170
+ track_source (TrackSource.ValueType): The source of the audio track (e.g., microphone, screen share).
171
+ loop (Optional[asyncio.AbstractEventLoop], optional): The event loop to use. Defaults to the current event loop.
172
+ capacity (int, optional): The capacity of the internal frame queue. Defaults to 0 (unbounded).
173
+ sample_rate (int, optional): The sample rate for the audio stream in Hz. Defaults to 48000.
174
+ num_channels (int, optional): The number of audio channels. Defaults to 1.
175
+ noise_cancellation (Optional[NoiseCancellationOptions], optional):
176
+ If noise cancellation is used, pass a `NoiseCancellationOptions` instance
177
+ created by the noise cancellation module.
178
+
179
+ Returns:
180
+ AudioStream: An instance of `AudioStream` that can be used to receive audio frames.
181
+
182
+ Example:
183
+ ```python
184
+ audio_stream = AudioStream.from_participant(
185
+ participant=participant,
186
+ track_source=TrackSource.MICROPHONE,
187
+ sample_rate=24000,
188
+ num_channels=1,
189
+ )
190
+ ```
191
+ """
192
+ return AudioStream(
193
+ participant=participant,
194
+ track_source=track_source,
195
+ loop=loop,
196
+ capacity=capacity,
197
+ track=None, # type: ignore
198
+ sample_rate=sample_rate,
199
+ num_channels=num_channels,
200
+ frame_size_ms=frame_size_ms,
201
+ noise_cancellation=noise_cancellation,
202
+ auto_close_noise_cancellation=auto_close_noise_cancellation,
203
+ )
204
+
205
+ @classmethod
206
+ def from_track(
207
+ cls,
208
+ *,
209
+ track: Track,
210
+ loop: Optional[asyncio.AbstractEventLoop] = None,
211
+ capacity: int = 0,
212
+ sample_rate: int = 48000,
213
+ num_channels: int = 1,
214
+ frame_size_ms: int | None = None,
215
+ noise_cancellation: Optional[NoiseCancellationOptions | FrameProcessor[AudioFrame]] = None,
216
+ auto_close_noise_cancellation: bool = False,
217
+ ) -> AudioStream:
218
+ """Create an `AudioStream` from an existing audio track.
219
+
220
+ Args:
221
+ track (Track): The audio track from which to receive audio.
222
+ loop (Optional[asyncio.AbstractEventLoop], optional): The event loop to use. Defaults to the current event loop.
223
+ capacity (int, optional): The capacity of the internal frame queue. Defaults to 0 (unbounded).
224
+ sample_rate (int, optional): The sample rate for the audio stream in Hz. Defaults to 48000.
225
+ num_channels (int, optional): The number of audio channels. Defaults to 1.
226
+ noise_cancellation (Optional[NoiseCancellationOptions | FrameProcessor[AudioFrame]], optional):
227
+ If noise cancellation is used, pass a `NoiseCancellationOptions` or `FrameProcessor[AudioFrame]` instance
228
+ created by the noise cancellation module.
229
+ auto_close_noise_cancellation (bool):
230
+ When the audio stream closes, leaves the FrameProcessor in an unclosed state so it
231
+ can be used with another AudioStream.
232
+
233
+ Returns:
234
+ AudioStream: An instance of `AudioStream` that can be used to receive audio frames.
235
+
236
+ Example:
237
+ ```python
238
+ audio_stream = AudioStream.from_track(
239
+ track=audio_track,
240
+ sample_rate=44100,
241
+ num_channels=2,
242
+ )
243
+ ```
244
+ """
245
+ return AudioStream(
246
+ track=track,
247
+ loop=loop,
248
+ capacity=capacity,
249
+ sample_rate=sample_rate,
250
+ num_channels=num_channels,
251
+ frame_size_ms=frame_size_ms,
252
+ noise_cancellation=noise_cancellation,
253
+ auto_close_noise_cancellation=auto_close_noise_cancellation,
254
+ )
255
+
256
+ def __del__(self) -> None:
257
+ # _ffi_queue is unset if __init__ raised early or the instance was built via __new__
258
+ ffi_queue = getattr(self, "_ffi_queue", None)
259
+ if ffi_queue is not None:
260
+ FfiClient.instance.queue.unsubscribe(ffi_queue)
261
+
262
+ def _create_owned_stream(self) -> Any:
263
+ assert self._track is not None
264
+ req = proto_ffi.FfiRequest()
265
+ new_audio_stream = req.new_audio_stream
266
+ new_audio_stream.track_handle = self._track._ffi_handle.handle
267
+ new_audio_stream.sample_rate = self._sample_rate
268
+ new_audio_stream.num_channels = self._num_channels
269
+ new_audio_stream.queue_size_frames = 0
270
+ if self._frame_size_ms:
271
+ new_audio_stream.frame_size_ms = self._frame_size_ms
272
+ new_audio_stream.type = proto_audio_frame.AudioStreamType.AUDIO_STREAM_NATIVE
273
+ if self._audio_filter_module is not None:
274
+ new_audio_stream.audio_filter_module_id = self._audio_filter_module
275
+ if self._audio_filter_options is not None:
276
+ new_audio_stream.audio_filter_options = json.dumps(self._audio_filter_options)
277
+ resp = FfiClient.instance.request(req)
278
+ return resp.new_audio_stream.stream
279
+
280
+ def _create_owned_stream_from_participant(
281
+ self, participant: Participant, track_source: TrackSource.ValueType
282
+ ) -> Any:
283
+ req = proto_ffi.FfiRequest()
284
+ audio_stream_from_participant = req.audio_stream_from_participant
285
+ audio_stream_from_participant.participant_handle = participant._ffi_handle.handle
286
+ audio_stream_from_participant.sample_rate = self._sample_rate
287
+ audio_stream_from_participant.num_channels = self._num_channels
288
+ audio_stream_from_participant.queue_size_frames = 0
289
+ audio_stream_from_participant.type = proto_audio_frame.AudioStreamType.AUDIO_STREAM_NATIVE
290
+ audio_stream_from_participant.track_source = track_source
291
+ if self._frame_size_ms:
292
+ audio_stream_from_participant.frame_size_ms = self._frame_size_ms
293
+
294
+ if self._audio_filter_module is not None:
295
+ audio_stream_from_participant.audio_filter_module_id = self._audio_filter_module
296
+ if self._audio_filter_options is not None:
297
+ audio_stream_from_participant.audio_filter_options = json.dumps(
298
+ self._audio_filter_options
299
+ )
300
+ resp = FfiClient.instance.request(req)
301
+ return resp.audio_stream_from_participant.stream
302
+
303
+ async def _run(self) -> None:
304
+ while True:
305
+ event = await self._ffi_queue.wait_for(self._is_event)
306
+ audio_event: proto_audio_frame.AudioStreamEvent = event.audio_stream_event
307
+
308
+ if audio_event.HasField("frame_received"):
309
+ owned_buffer_info = audio_event.frame_received.frame
310
+ frame = AudioFrame._from_owned_info(owned_buffer_info)
311
+ if self._processor is not None and self._processor.enabled:
312
+ try:
313
+ frame = self._processor._process(frame)
314
+ except Exception:
315
+ logger.warning(
316
+ "Frame processing failed, passing through original frame",
317
+ exc_info=True,
318
+ )
319
+ event = AudioFrameEvent(frame)
320
+ self._queue.put(event)
321
+ elif audio_event.HasField("eos"):
322
+ self._queue.put(None)
323
+ break
324
+
325
+ FfiClient.instance.queue.unsubscribe(self._ffi_queue)
326
+
327
+ async def aclose(self) -> None:
328
+ """Asynchronously close the audio stream.
329
+
330
+ This method cleans up resources associated with the audio stream and waits for
331
+ any pending operations to complete.
332
+ """
333
+ if self._track is not None:
334
+ self._track._unregister_audio_stream(self)
335
+ self._ffi_handle.dispose()
336
+ await self._task
337
+ if self._processor is not None and self._processor_auto_close:
338
+ self._processor._close()
339
+
340
+ def _is_event(self, e: proto_ffi.FfiEvent) -> bool:
341
+ return e.audio_stream_event.stream_handle == self._ffi_handle.handle
342
+
343
+ def __aiter__(self) -> AsyncIterator[AudioFrameEvent]:
344
+ return self
345
+
346
+ async def __anext__(self) -> AudioFrameEvent:
347
+ if self._task.done():
348
+ raise StopAsyncIteration
349
+
350
+ item = await self._queue.get()
351
+ if item is None:
352
+ raise StopAsyncIteration
353
+
354
+ return item