livekit 1.1.20__tar.gz → 1.2.0__tar.gz

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 (93) hide show
  1. livekit-1.2.0/PKG-INFO +80 -0
  2. livekit-1.2.0/README.md +51 -0
  3. {livekit-1.1.20 → livekit-1.2.0}/livekit/rtc/__init__.py +20 -1
  4. {livekit-1.1.20 → livekit-1.2.0}/livekit/rtc/_ffi_client.py +20 -8
  5. livekit-1.2.0/livekit/rtc/_proto/capture_pb2.py +70 -0
  6. {livekit-1.1.20 → livekit-1.2.0}/livekit/rtc/_proto/capture_pb2.pyi +134 -13
  7. {livekit-1.1.20 → livekit-1.2.0}/livekit/rtc/_utils.py +17 -0
  8. {livekit-1.1.20 → livekit-1.2.0}/livekit/rtc/audio_mixer.py +53 -6
  9. {livekit-1.1.20 → livekit-1.2.0}/livekit/rtc/data_stream.py +13 -12
  10. livekit-1.2.0/livekit/rtc/encoded_video.py +127 -0
  11. {livekit-1.1.20 → livekit-1.2.0}/livekit/rtc/participant.py +50 -0
  12. {livekit-1.1.20 → livekit-1.2.0}/livekit/rtc/track.py +4 -1
  13. livekit-1.2.0/livekit/rtc/version.py +1 -0
  14. {livekit-1.1.20 → livekit-1.2.0}/livekit/rtc/video_frame.py +4 -3
  15. livekit-1.2.0/livekit.egg-info/PKG-INFO +80 -0
  16. {livekit-1.1.20 → livekit-1.2.0}/livekit.egg-info/SOURCES.txt +5 -0
  17. livekit-1.2.0/tests/test_broadcast_queue.py +98 -0
  18. livekit-1.2.0/tests/test_data_stream_writer.py +84 -0
  19. livekit-1.2.0/tests/test_ffi_client.py +44 -0
  20. livekit-1.2.0/tests/test_ffi_handle_lifecycle.py +91 -0
  21. livekit-1.1.20/PKG-INFO +0 -35
  22. livekit-1.1.20/README.md +0 -6
  23. livekit-1.1.20/livekit/rtc/_proto/capture_pb2.py +0 -62
  24. livekit-1.1.20/livekit/rtc/version.py +0 -1
  25. livekit-1.1.20/livekit.egg-info/PKG-INFO +0 -35
  26. {livekit-1.1.20 → livekit-1.2.0}/livekit/rtc/_proto/__init__.py +0 -0
  27. {livekit-1.1.20 → livekit-1.2.0}/livekit/rtc/_proto/audio_frame_pb2.py +0 -0
  28. {livekit-1.1.20 → livekit-1.2.0}/livekit/rtc/_proto/audio_frame_pb2.pyi +0 -0
  29. {livekit-1.1.20 → livekit-1.2.0}/livekit/rtc/_proto/data_stream_pb2.py +0 -0
  30. {livekit-1.1.20 → livekit-1.2.0}/livekit/rtc/_proto/data_stream_pb2.pyi +0 -0
  31. {livekit-1.1.20 → livekit-1.2.0}/livekit/rtc/_proto/data_track_pb2.py +0 -0
  32. {livekit-1.1.20 → livekit-1.2.0}/livekit/rtc/_proto/data_track_pb2.pyi +0 -0
  33. {livekit-1.1.20 → livekit-1.2.0}/livekit/rtc/_proto/e2ee_pb2.py +0 -0
  34. {livekit-1.1.20 → livekit-1.2.0}/livekit/rtc/_proto/e2ee_pb2.pyi +0 -0
  35. {livekit-1.1.20 → livekit-1.2.0}/livekit/rtc/_proto/ffi_pb2.py +0 -0
  36. {livekit-1.1.20 → livekit-1.2.0}/livekit/rtc/_proto/ffi_pb2.pyi +0 -0
  37. {livekit-1.1.20 → livekit-1.2.0}/livekit/rtc/_proto/handle_pb2.py +0 -0
  38. {livekit-1.1.20 → livekit-1.2.0}/livekit/rtc/_proto/handle_pb2.pyi +0 -0
  39. {livekit-1.1.20 → livekit-1.2.0}/livekit/rtc/_proto/participant_pb2.py +0 -0
  40. {livekit-1.1.20 → livekit-1.2.0}/livekit/rtc/_proto/participant_pb2.pyi +0 -0
  41. {livekit-1.1.20 → livekit-1.2.0}/livekit/rtc/_proto/room_pb2.py +0 -0
  42. {livekit-1.1.20 → livekit-1.2.0}/livekit/rtc/_proto/room_pb2.pyi +0 -0
  43. {livekit-1.1.20 → livekit-1.2.0}/livekit/rtc/_proto/rpc_pb2.py +0 -0
  44. {livekit-1.1.20 → livekit-1.2.0}/livekit/rtc/_proto/rpc_pb2.pyi +0 -0
  45. {livekit-1.1.20 → livekit-1.2.0}/livekit/rtc/_proto/stats_pb2.py +0 -0
  46. {livekit-1.1.20 → livekit-1.2.0}/livekit/rtc/_proto/stats_pb2.pyi +0 -0
  47. {livekit-1.1.20 → livekit-1.2.0}/livekit/rtc/_proto/track_pb2.py +0 -0
  48. {livekit-1.1.20 → livekit-1.2.0}/livekit/rtc/_proto/track_pb2.pyi +0 -0
  49. {livekit-1.1.20 → livekit-1.2.0}/livekit/rtc/_proto/track_publication_pb2.py +0 -0
  50. {livekit-1.1.20 → livekit-1.2.0}/livekit/rtc/_proto/track_publication_pb2.pyi +0 -0
  51. {livekit-1.1.20 → livekit-1.2.0}/livekit/rtc/_proto/video_frame_pb2.py +0 -0
  52. {livekit-1.1.20 → livekit-1.2.0}/livekit/rtc/_proto/video_frame_pb2.pyi +0 -0
  53. {livekit-1.1.20 → livekit-1.2.0}/livekit/rtc/apm.py +0 -0
  54. {livekit-1.1.20 → livekit-1.2.0}/livekit/rtc/audio_filter.py +0 -0
  55. {livekit-1.1.20 → livekit-1.2.0}/livekit/rtc/audio_frame.py +0 -0
  56. {livekit-1.1.20 → livekit-1.2.0}/livekit/rtc/audio_resampler.py +0 -0
  57. {livekit-1.1.20 → livekit-1.2.0}/livekit/rtc/audio_source.py +0 -0
  58. {livekit-1.1.20 → livekit-1.2.0}/livekit/rtc/audio_stream.py +0 -0
  59. {livekit-1.1.20 → livekit-1.2.0}/livekit/rtc/data_track.py +0 -0
  60. {livekit-1.1.20 → livekit-1.2.0}/livekit/rtc/e2ee.py +0 -0
  61. {livekit-1.1.20 → livekit-1.2.0}/livekit/rtc/event_emitter.py +0 -0
  62. {livekit-1.1.20 → livekit-1.2.0}/livekit/rtc/frame_processor.py +0 -0
  63. {livekit-1.1.20 → livekit-1.2.0}/livekit/rtc/jupyter.py +0 -0
  64. {livekit-1.1.20 → livekit-1.2.0}/livekit/rtc/log.py +0 -0
  65. {livekit-1.1.20 → livekit-1.2.0}/livekit/rtc/media_devices.py +0 -0
  66. {livekit-1.1.20 → livekit-1.2.0}/livekit/rtc/platform_audio.py +0 -0
  67. {livekit-1.1.20 → livekit-1.2.0}/livekit/rtc/py.typed +0 -0
  68. {livekit-1.1.20 → livekit-1.2.0}/livekit/rtc/resources/__init__.py +0 -0
  69. {livekit-1.1.20 → livekit-1.2.0}/livekit/rtc/resources/jupyter-html/index.html +0 -0
  70. {livekit-1.1.20 → livekit-1.2.0}/livekit/rtc/room.py +0 -0
  71. {livekit-1.1.20 → livekit-1.2.0}/livekit/rtc/rpc.py +0 -0
  72. {livekit-1.1.20 → livekit-1.2.0}/livekit/rtc/synchronizer.py +0 -0
  73. {livekit-1.1.20 → livekit-1.2.0}/livekit/rtc/track_publication.py +0 -0
  74. {livekit-1.1.20 → livekit-1.2.0}/livekit/rtc/transcription.py +0 -0
  75. {livekit-1.1.20 → livekit-1.2.0}/livekit/rtc/utils.py +0 -0
  76. {livekit-1.1.20 → livekit-1.2.0}/livekit/rtc/video_source.py +0 -0
  77. {livekit-1.1.20 → livekit-1.2.0}/livekit/rtc/video_stream.py +0 -0
  78. {livekit-1.1.20 → livekit-1.2.0}/livekit.egg-info/dependency_links.txt +0 -0
  79. {livekit-1.1.20 → livekit-1.2.0}/livekit.egg-info/requires.txt +0 -0
  80. {livekit-1.1.20 → livekit-1.2.0}/livekit.egg-info/top_level.txt +0 -0
  81. {livekit-1.1.20 → livekit-1.2.0}/pyproject.toml +0 -0
  82. {livekit-1.1.20 → livekit-1.2.0}/setup.cfg +0 -0
  83. {livekit-1.1.20 → livekit-1.2.0}/setup.py +0 -0
  84. {livekit-1.1.20 → livekit-1.2.0}/tests/test_audio.py +0 -0
  85. {livekit-1.1.20 → livekit-1.2.0}/tests/test_audio_stream_init.py +0 -0
  86. {livekit-1.1.20 → livekit-1.2.0}/tests/test_audio_stream_room_lifecycle.py +0 -0
  87. {livekit-1.1.20 → livekit-1.2.0}/tests/test_change_video_quality.py +0 -0
  88. {livekit-1.1.20 → livekit-1.2.0}/tests/test_dc.py +0 -0
  89. {livekit-1.1.20 → livekit-1.2.0}/tests/test_e2ee.py +0 -0
  90. {livekit-1.1.20 → livekit-1.2.0}/tests/test_e2ee_per_participant.py +0 -0
  91. {livekit-1.1.20 → livekit-1.2.0}/tests/test_e2ee_shared_key.py +0 -0
  92. {livekit-1.1.20 → livekit-1.2.0}/tests/test_rpc_interceptors.py +0 -0
  93. {livekit-1.1.20 → livekit-1.2.0}/tests/test_video.py +0 -0
livekit-1.2.0/PKG-INFO ADDED
@@ -0,0 +1,80 @@
1
+ Metadata-Version: 2.4
2
+ Name: livekit
3
+ Version: 1.2.0
4
+ Summary: Python Real-time SDK for LiveKit
5
+ Author-email: LiveKit <support@livekit.io>
6
+ License-Expression: Apache-2.0
7
+ Project-URL: Documentation, https://docs.livekit.io
8
+ Project-URL: Website, https://livekit.io/
9
+ Project-URL: Source, https://github.com/livekit/python-sdks/
10
+ Keywords: webrtc,realtime,audio,video,livekit
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: Topic :: Multimedia :: Sound/Audio
13
+ Classifier: Topic :: Multimedia :: Video
14
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.9
17
+ Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Programming Language :: Python :: 3.13
21
+ Classifier: Programming Language :: Python :: 3.14
22
+ Classifier: Programming Language :: Python :: 3 :: Only
23
+ Requires-Python: >=3.9.0
24
+ Description-Content-Type: text/markdown
25
+ Requires-Dist: protobuf>=5
26
+ Requires-Dist: types-protobuf>=5
27
+ Requires-Dist: aiofiles>=24
28
+ Requires-Dist: numpy>=1.26
29
+
30
+ # LiveKit SDK for Python
31
+
32
+ Python SDK to integrate LiveKit's real-time video, audio, and data capabilities into your Python applications using WebRTC. Designed for use with [LiveKit Agents](https://github.com/livekit/agents) to build powerful voice AI apps.
33
+
34
+ See https://docs.livekit.io/ for more information.
35
+
36
+ ## Publishing pre-encoded video
37
+
38
+ Use `EncodedVideoSource` when an upstream encoder already provides compressed
39
+ frames. The source uses the same native passthrough encoder as the Rust SDK;
40
+ Python does not decode or re-encode the video.
41
+
42
+ ```python
43
+ from livekit import rtc
44
+
45
+ source = rtc.EncodedVideoSource(width=640, height=360)
46
+ track = rtc.LocalVideoTrack.create_video_track("camera", source)
47
+ await room.local_participant.publish_track(
48
+ track,
49
+ rtc.TrackPublishOptions(
50
+ video_codec=rtc.VideoCodec.VP9,
51
+ video_encoder=rtc.VideoEncoderBackend.ENCODER_BACKEND_PRE_ENCODED,
52
+ simulcast=False,
53
+ ),
54
+ )
55
+ # Supply each complete encoded frame at its playback time.
56
+ source.capture_frame(rtc.EncodedVideoFrame(
57
+ data=encoded_vp9_frame,
58
+ width=640,
59
+ height=360,
60
+ codec=rtc.VideoCodec.VP9,
61
+ frame_type=rtc.EncodedFrameType.ENCODED_FRAME_KEY,
62
+ timestamp_us=capture_timestamp_us,
63
+ ))
64
+ feedback = source.take_feedback()
65
+ # Forward feedback.keyframe_requested and feedback.rate_control to your encoder.
66
+ # At shutdown, unpublish the track and release the source:
67
+ await room.local_participant.unpublish_track(track.sid)
68
+ await source.aclose()
69
+ ```
70
+
71
+ Supported codecs are H.264, H.265, VP8, VP9 and AV1, subject to receiver support.
72
+ Submit complete access units (Annex B for H.264/H.265), not WebM/MP4 container
73
+ bytes or RTP packets. A container must first be demuxed; demuxing extracts
74
+ compressed frames and does not reconstruct pixels. The codec must match the
75
+ publication and stay fixed, and simulcast must be disabled.
76
+
77
+ The caller controls pacing and handles keyframe and bitrate feedback. In
78
+ particular, a prerecorded file cannot generate a new keyframe on demand for a
79
+ late subscriber or after packet loss. WebM's auxiliary alpha data is not part of
80
+ the VP9 color access unit and is not transported by this API.
@@ -0,0 +1,51 @@
1
+ # LiveKit SDK for Python
2
+
3
+ Python SDK to integrate LiveKit's real-time video, audio, and data capabilities into your Python applications using WebRTC. Designed for use with [LiveKit Agents](https://github.com/livekit/agents) to build powerful voice AI apps.
4
+
5
+ See https://docs.livekit.io/ for more information.
6
+
7
+ ## Publishing pre-encoded video
8
+
9
+ Use `EncodedVideoSource` when an upstream encoder already provides compressed
10
+ frames. The source uses the same native passthrough encoder as the Rust SDK;
11
+ Python does not decode or re-encode the video.
12
+
13
+ ```python
14
+ from livekit import rtc
15
+
16
+ source = rtc.EncodedVideoSource(width=640, height=360)
17
+ track = rtc.LocalVideoTrack.create_video_track("camera", source)
18
+ await room.local_participant.publish_track(
19
+ track,
20
+ rtc.TrackPublishOptions(
21
+ video_codec=rtc.VideoCodec.VP9,
22
+ video_encoder=rtc.VideoEncoderBackend.ENCODER_BACKEND_PRE_ENCODED,
23
+ simulcast=False,
24
+ ),
25
+ )
26
+ # Supply each complete encoded frame at its playback time.
27
+ source.capture_frame(rtc.EncodedVideoFrame(
28
+ data=encoded_vp9_frame,
29
+ width=640,
30
+ height=360,
31
+ codec=rtc.VideoCodec.VP9,
32
+ frame_type=rtc.EncodedFrameType.ENCODED_FRAME_KEY,
33
+ timestamp_us=capture_timestamp_us,
34
+ ))
35
+ feedback = source.take_feedback()
36
+ # Forward feedback.keyframe_requested and feedback.rate_control to your encoder.
37
+ # At shutdown, unpublish the track and release the source:
38
+ await room.local_participant.unpublish_track(track.sid)
39
+ await source.aclose()
40
+ ```
41
+
42
+ Supported codecs are H.264, H.265, VP8, VP9 and AV1, subject to receiver support.
43
+ Submit complete access units (Annex B for H.264/H.265), not WebM/MP4 container
44
+ bytes or RTP packets. A container must first be demuxed; demuxing extracts
45
+ compressed frames and does not reconstruct pixels. The codec must match the
46
+ publication and stay fixed, and simulcast must be disabled.
47
+
48
+ The caller controls pacing and handles keyframe and bitrate feedback. In
49
+ particular, a prerecorded file cannot generate a new keyframe on demand for a
50
+ late subscriber or after packet loss. WebM's auxiliary alpha data is not part of
51
+ the VP9 color access unit and is not transported by this API.
@@ -35,6 +35,7 @@ from ._proto.room_pb2 import (
35
35
  SimulateScenarioKind,
36
36
  TrackPublishOptions,
37
37
  VideoEncoding,
38
+ VideoEncoderBackend,
38
39
  )
39
40
  from ._proto.track_pb2 import (
40
41
  FrameMetadataFeature,
@@ -43,7 +44,13 @@ from ._proto.track_pb2 import (
43
44
  TrackSource,
44
45
  ParticipantTrackPermission,
45
46
  )
46
- from ._proto.video_frame_pb2 import FrameMetadata, VideoBufferType, VideoCodec, VideoRotation
47
+ from ._proto.video_frame_pb2 import (
48
+ EncodedFrameType,
49
+ FrameMetadata,
50
+ VideoBufferType,
51
+ VideoCodec,
52
+ VideoRotation,
53
+ )
47
54
  from ._proto.track_publication_pb2 import VideoQuality
48
55
  from .audio_frame import AudioFrame
49
56
  from .audio_source import AudioSource
@@ -95,6 +102,12 @@ from .version import __version__
95
102
  from .video_frame import (
96
103
  VideoFrame,
97
104
  )
105
+ from .encoded_video import (
106
+ EncodedVideoFrame,
107
+ EncodedVideoSource,
108
+ EncodedVideoSourceFeedback,
109
+ EncodedRateControl,
110
+ )
98
111
  from .video_source import VideoSource
99
112
  from .video_stream import VideoFrameEvent, VideoStream
100
113
  from .audio_resampler import AudioResampler, AudioResamplerQuality
@@ -138,6 +151,12 @@ from .data_track import (
138
151
  from .frame_processor import FrameProcessor
139
152
 
140
153
  __all__ = [
154
+ "EncodedFrameType",
155
+ "EncodedVideoFrame",
156
+ "EncodedVideoSource",
157
+ "EncodedVideoSourceFeedback",
158
+ "EncodedRateControl",
159
+ "VideoEncoderBackend",
141
160
  "ConnectionQuality",
142
161
  "ConnectionState",
143
162
  "DataPacketKind",
@@ -12,6 +12,8 @@
12
12
  # See the License for the specific language governing permissions and
13
13
  # limitations under the License.
14
14
 
15
+ from __future__ import annotations
16
+
15
17
  import signal
16
18
  import asyncio
17
19
  import sys
@@ -24,6 +26,7 @@ import os
24
26
  import platform
25
27
  import atexit
26
28
  import threading
29
+ import weakref
27
30
  from typing import Callable, Generic, List, Optional, TypeVar
28
31
 
29
32
  from ._proto import ffi_pb2 as proto_ffi
@@ -68,10 +71,14 @@ ffi_cb_fnc = ctypes.CFUNCTYPE(None, ctypes.POINTER(ctypes.c_uint8), ctypes.c_siz
68
71
  INVALID_HANDLE = 0
69
72
 
70
73
 
74
+ _live_handles: weakref.WeakSet[FfiHandle] = weakref.WeakSet()
75
+
76
+
71
77
  class FfiHandle:
72
78
  def __init__(self, handle: int) -> None:
73
79
  self.handle = handle
74
80
  self._disposed = False
81
+ _live_handles.add(self)
75
82
 
76
83
  def __del__(self) -> None:
77
84
  self.dispose()
@@ -93,7 +100,7 @@ class FfiHandle:
93
100
  if self.handle != INVALID_HANDLE and not self._disposed:
94
101
  self._disposed = True
95
102
  ffi = FfiClient._instance
96
- if ffi is None or ffi._pid != os.getpid():
103
+ if ffi is None or ffi._pid != os.getpid() or getattr(FfiClient, "_ffi_disposed", False):
97
104
  return
98
105
  dropped = ffi._ffi_lib.livekit_ffi_drop_handle(ctypes.c_uint64(self.handle))
99
106
  assert dropped
@@ -223,6 +230,7 @@ def to_python_level(level: proto_ffi.LogLevel.ValueType) -> Optional[int]:
223
230
 
224
231
  class FfiClient:
225
232
  _instance: Optional["FfiClient"] = None
233
+ _ffi_disposed: bool = False
226
234
 
227
235
  @classproperty
228
236
  def instance(cls) -> "FfiClient":
@@ -231,6 +239,7 @@ class FfiClient:
231
239
  return cls._instance
232
240
 
233
241
  def __init__(self) -> None:
242
+ FfiClient._ffi_disposed = False
234
243
  self._pid = os.getpid()
235
244
  self._lock = threading.RLock()
236
245
  self._queue = FfiQueue[proto_ffi.FfiEvent]()
@@ -266,14 +275,17 @@ class FfiClient:
266
275
  ffi_event_callback, True, b"python", __version__.encode("ascii")
267
276
  )
268
277
 
269
- ffi_lib = self._ffi_lib
270
- init_pid = self._pid
271
-
272
278
  @atexit.register
273
279
  def _dispose_lk_ffi() -> None:
274
- if os.getpid() != init_pid:
275
- return
276
- ffi_lib.livekit_ffi_dispose()
280
+ self._dispose()
281
+
282
+ def _dispose(self) -> None:
283
+ if self._pid != os.getpid() or FfiClient._ffi_disposed:
284
+ return
285
+ FfiClient._ffi_disposed = True
286
+ for handle in list(_live_handles):
287
+ handle.mark_consumed()
288
+ self._ffi_lib.livekit_ffi_dispose()
277
289
 
278
290
  @property
279
291
  def queue(self) -> FfiQueue[proto_ffi.FfiEvent]:
@@ -288,7 +300,7 @@ class FfiClient:
288
300
  )
289
301
  proto_data = req.SerializeToString()
290
302
  proto_len = len(proto_data)
291
- data = (ctypes.c_ubyte * proto_len)(*proto_data)
303
+ data = ctypes.cast(ctypes.c_char_p(proto_data), ctypes.POINTER(ctypes.c_ubyte))
292
304
 
293
305
  resp_ptr = ctypes.POINTER(ctypes.c_ubyte)()
294
306
  resp_len = ctypes.c_size_t()
@@ -0,0 +1,70 @@
1
+ # -*- coding: utf-8 -*-
2
+ # Generated by the protocol buffer compiler. DO NOT EDIT!
3
+ # source: capture.proto
4
+ # Protobuf Python Version: 4.25.1
5
+ """Generated protocol buffer code."""
6
+ from google.protobuf import descriptor as _descriptor
7
+ from google.protobuf import descriptor_pool as _descriptor_pool
8
+ from google.protobuf import symbol_database as _symbol_database
9
+ from google.protobuf.internal import builder as _builder
10
+ # @@protoc_insertion_point(imports)
11
+
12
+ _sym_db = _symbol_database.Default()
13
+
14
+
15
+ from . import handle_pb2 as handle__pb2
16
+ from . import room_pb2 as room__pb2
17
+ from . import video_frame_pb2 as video__frame__pb2
18
+
19
+
20
+ DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\rcapture.proto\x12\rlivekit.proto\x1a\x0chandle.proto\x1a\nroom.proto\x1a\x11video_frame.proto\"l\n\x14GstreamerRateControl\x12\x0f\n\x07\x65lement\x18\x01 \x02(\t\x12\x10\n\x08property\x18\x02 \x02(\t\x12\x31\n\x04unit\x18\x03 \x02(\x0e\x32#.livekit.proto.GstreamerBitrateUnit\"\xcd\x01\n\x1aGstreamerVideoSourceConfig\x12\x10\n\x08pipeline\x18\x01 \x02(\t\x12(\n\x05\x63odec\x18\x02 \x01(\x0e\x32\x19.livekit.proto.VideoCodec\x12\x38\n\nresolution\x18\x03 \x01(\x0b\x32$.livekit.proto.VideoSourceResolution\x12\x39\n\x0crate_control\x18\x04 \x01(\x0b\x32#.livekit.proto.GstreamerRateControl\"\x94\x01\n\x18PatternVideoSourceConfig\x12\x38\n\nresolution\x18\x01 \x02(\x0b\x32$.livekit.proto.VideoSourceResolution\x12\x15\n\rframerate_fps\x18\x02 \x02(\r\x12\'\n\x07pattern\x18\x03 \x02(\x0e\x32\x16.livekit.proto.Pattern\"i\n\x16\x43lockVideoSourceConfig\x12\x38\n\nresolution\x18\x01 \x02(\x0b\x32$.livekit.proto.VideoSourceResolution\x12\x15\n\rframerate_fps\x18\x02 \x02(\r\"\xa7\x02\n\x11\x43\x61ptureSourceInfo\x12.\n\x04kind\x18\x01 \x02(\x0e\x32 .livekit.proto.CaptureSourceKind\x12\x38\n\nresolution\x18\x02 \x02(\x0b\x32$.livekit.proto.VideoSourceResolution\x12(\n\x05\x63odec\x18\x03 \x01(\x0e\x32\x19.livekit.proto.VideoCodec\x12G\n\x1brecommended_publish_options\x18\x04 \x02(\x0b\x32\".livekit.proto.TrackPublishOptions\x12\x35\n\x0cvideo_source\x18\x05 \x02(\x0b\x32\x1f.livekit.proto.OwnedVideoSource\"s\n\x12OwnedCaptureSource\x12-\n\x06handle\x18\x01 \x02(\x0b\x32\x1d.livekit.proto.FfiOwnedHandle\x12.\n\x04info\x18\x02 \x02(\x0b\x32 .livekit.proto.CaptureSourceInfo\"\xf1\x01\n\x17NewCaptureSourceRequest\x12>\n\tgstreamer\x18\x01 \x01(\x0b\x32).livekit.proto.GstreamerVideoSourceConfigH\x00\x12:\n\x07pattern\x18\x02 \x01(\x0b\x32\'.livekit.proto.PatternVideoSourceConfigH\x00\x12\x36\n\x05\x63lock\x18\x05 \x01(\x0b\x32%.livekit.proto.ClockVideoSourceConfigH\x00\x12\x18\n\x10request_async_id\x18\x03 \x01(\x04\x42\x08\n\x06\x63onfig\",\n\x18NewCaptureSourceResponse\x12\x10\n\x08\x61sync_id\x18\x01 \x02(\x04\"}\n\x18NewCaptureSourceCallback\x12\x10\n\x08\x61sync_id\x18\x01 \x02(\x04\x12\x0f\n\x05\x65rror\x18\x02 \x01(\tH\x00\x12\x33\n\x06source\x18\x03 \x01(\x0b\x32!.livekit.proto.OwnedCaptureSourceH\x00\x42\t\n\x07message\"-\n\x13StartCaptureRequest\x12\x16\n\x0e\x63\x61pture_handle\x18\x01 \x02(\x04\"%\n\x14StartCaptureResponse\x12\r\n\x05\x65rror\x18\x01 \x01(\t\",\n\x12StopCaptureRequest\x12\x16\n\x0e\x63\x61pture_handle\x18\x01 \x02(\x04\"$\n\x13StopCaptureResponse\x12\r\n\x05\x65rror\x18\x01 \x01(\t\"T\n\x0f\x43\x61ptureFinished\x12\x17\n\x0f\x66rames_captured\x18\x01 \x02(\x04\x12(\n\x04\x65xit\x18\x02 \x02(\x0e\x32\x1a.livekit.proto.CaptureExit\"L\n\x0c\x43\x61ptureError\x12\r\n\x05\x65rror\x18\x01 \x02(\t\x12-\n\x04kind\x18\x02 \x02(\x0e\x32\x1f.livekit.proto.CaptureErrorKind\"\x99\x01\n\x12\x43\x61ptureSourceEvent\x12\x16\n\x0e\x63\x61pture_handle\x18\x01 \x02(\x04\x12\x32\n\x08\x66inished\x18\x02 \x01(\x0b\x32\x1e.livekit.proto.CaptureFinishedH\x00\x12,\n\x05\x65rror\x18\x03 \x01(\x0b\x32\x1b.livekit.proto.CaptureErrorH\x00\x42\t\n\x07message*W\n\x14GstreamerBitrateUnit\x12\x1e\n\x1aGSTREAMER_BITRATE_UNIT_BPS\x10\x00\x12\x1f\n\x1bGSTREAMER_BITRATE_UNIT_KBPS\x10\x01*1\n\x07Pattern\x12\x14\n\x10PATTERN_GRADIENT\x10\x00\x12\x10\n\x0cPATTERN_LOGO\x10\x01*I\n\x11\x43\x61ptureSourceKind\x12\x18\n\x14\x43\x41PTURE_SOURCE_PIXEL\x10\x00\x12\x1a\n\x16\x43\x41PTURE_SOURCE_ENCODED\x10\x01*G\n\x0b\x43\x61ptureExit\x12\x18\n\x14\x43\x41PTURE_EXIT_STOPPED\x10\x00\x12\x1e\n\x1a\x43\x41PTURE_EXIT_END_OF_STREAM\x10\x01*n\n\x10\x43\x61ptureErrorKind\x12\x1d\n\x19\x43\x41PTURE_ERROR_KIND_SOURCE\x10\x00\x12\x1a\n\x16\x43\x41PTURE_ERROR_KIND_RTC\x10\x01\x12\x1f\n\x1b\x43\x41PTURE_ERROR_KIND_PANICKED\x10\x02\x42\x10\xaa\x02\rLiveKit.Proto')
21
+
22
+ _globals = globals()
23
+ _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
24
+ _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'capture_pb2', _globals)
25
+ if _descriptor._USE_C_DESCRIPTORS == False:
26
+ _globals['DESCRIPTOR']._options = None
27
+ _globals['DESCRIPTOR']._serialized_options = b'\252\002\rLiveKit.Proto'
28
+ _globals['_GSTREAMERBITRATEUNIT']._serialized_start=1975
29
+ _globals['_GSTREAMERBITRATEUNIT']._serialized_end=2062
30
+ _globals['_PATTERN']._serialized_start=2064
31
+ _globals['_PATTERN']._serialized_end=2113
32
+ _globals['_CAPTURESOURCEKIND']._serialized_start=2115
33
+ _globals['_CAPTURESOURCEKIND']._serialized_end=2188
34
+ _globals['_CAPTUREEXIT']._serialized_start=2190
35
+ _globals['_CAPTUREEXIT']._serialized_end=2261
36
+ _globals['_CAPTUREERRORKIND']._serialized_start=2263
37
+ _globals['_CAPTUREERRORKIND']._serialized_end=2373
38
+ _globals['_GSTREAMERRATECONTROL']._serialized_start=77
39
+ _globals['_GSTREAMERRATECONTROL']._serialized_end=185
40
+ _globals['_GSTREAMERVIDEOSOURCECONFIG']._serialized_start=188
41
+ _globals['_GSTREAMERVIDEOSOURCECONFIG']._serialized_end=393
42
+ _globals['_PATTERNVIDEOSOURCECONFIG']._serialized_start=396
43
+ _globals['_PATTERNVIDEOSOURCECONFIG']._serialized_end=544
44
+ _globals['_CLOCKVIDEOSOURCECONFIG']._serialized_start=546
45
+ _globals['_CLOCKVIDEOSOURCECONFIG']._serialized_end=651
46
+ _globals['_CAPTURESOURCEINFO']._serialized_start=654
47
+ _globals['_CAPTURESOURCEINFO']._serialized_end=949
48
+ _globals['_OWNEDCAPTURESOURCE']._serialized_start=951
49
+ _globals['_OWNEDCAPTURESOURCE']._serialized_end=1066
50
+ _globals['_NEWCAPTURESOURCEREQUEST']._serialized_start=1069
51
+ _globals['_NEWCAPTURESOURCEREQUEST']._serialized_end=1310
52
+ _globals['_NEWCAPTURESOURCERESPONSE']._serialized_start=1312
53
+ _globals['_NEWCAPTURESOURCERESPONSE']._serialized_end=1356
54
+ _globals['_NEWCAPTURESOURCECALLBACK']._serialized_start=1358
55
+ _globals['_NEWCAPTURESOURCECALLBACK']._serialized_end=1483
56
+ _globals['_STARTCAPTUREREQUEST']._serialized_start=1485
57
+ _globals['_STARTCAPTUREREQUEST']._serialized_end=1530
58
+ _globals['_STARTCAPTURERESPONSE']._serialized_start=1532
59
+ _globals['_STARTCAPTURERESPONSE']._serialized_end=1569
60
+ _globals['_STOPCAPTUREREQUEST']._serialized_start=1571
61
+ _globals['_STOPCAPTUREREQUEST']._serialized_end=1615
62
+ _globals['_STOPCAPTURERESPONSE']._serialized_start=1617
63
+ _globals['_STOPCAPTURERESPONSE']._serialized_end=1653
64
+ _globals['_CAPTUREFINISHED']._serialized_start=1655
65
+ _globals['_CAPTUREFINISHED']._serialized_end=1739
66
+ _globals['_CAPTUREERROR']._serialized_start=1741
67
+ _globals['_CAPTUREERROR']._serialized_end=1817
68
+ _globals['_CAPTURESOURCEEVENT']._serialized_start=1820
69
+ _globals['_CAPTURESOURCEEVENT']._serialized_end=1973
70
+ # @@protoc_insertion_point(module_scope)
@@ -33,18 +33,16 @@ else:
33
33
 
34
34
  DESCRIPTOR: google.protobuf.descriptor.FileDescriptor
35
35
 
36
- class _Pattern:
36
+ class _GstreamerBitrateUnit:
37
37
  ValueType = typing.NewType("ValueType", builtins.int)
38
38
  V: typing_extensions.TypeAlias = ValueType
39
39
 
40
- class _PatternEnumTypeWrapper(google.protobuf.internal.enum_type_wrapper._EnumTypeWrapper[_Pattern.ValueType], builtins.type):
40
+ class _GstreamerBitrateUnitEnumTypeWrapper(google.protobuf.internal.enum_type_wrapper._EnumTypeWrapper[_GstreamerBitrateUnit.ValueType], builtins.type):
41
41
  DESCRIPTOR: google.protobuf.descriptor.EnumDescriptor
42
- PATTERN_GRADIENT: _Pattern.ValueType # 0
43
- """Animated color gradient."""
44
- PATTERN_LOGO: _Pattern.ValueType # 1
45
- """Bouncing LiveKit logo."""
42
+ GSTREAMER_BITRATE_UNIT_BPS: _GstreamerBitrateUnit.ValueType # 0
43
+ GSTREAMER_BITRATE_UNIT_KBPS: _GstreamerBitrateUnit.ValueType # 1
46
44
 
47
- class Pattern(_Pattern, metaclass=_PatternEnumTypeWrapper):
45
+ class GstreamerBitrateUnit(_GstreamerBitrateUnit, metaclass=_GstreamerBitrateUnitEnumTypeWrapper):
48
46
  """Capture sources (livekit-capture)
49
47
 
50
48
  A capture source owns a media producer (e.g. a GStreamer pipeline) and the
@@ -61,9 +59,27 @@ class Pattern(_Pattern, metaclass=_PatternEnumTypeWrapper):
61
59
  3. The capture runs until StopCaptureRequest, end of stream, or an error;
62
60
  a CaptureSourceEvent is delivered exactly once when it ends.
63
61
 
64
- Test patterns built into livekit-capture.
62
+ Bitrate unit expected by a GStreamer encoder property.
65
63
  """
66
64
 
65
+ GSTREAMER_BITRATE_UNIT_BPS: GstreamerBitrateUnit.ValueType # 0
66
+ GSTREAMER_BITRATE_UNIT_KBPS: GstreamerBitrateUnit.ValueType # 1
67
+ global___GstreamerBitrateUnit = GstreamerBitrateUnit
68
+
69
+ class _Pattern:
70
+ ValueType = typing.NewType("ValueType", builtins.int)
71
+ V: typing_extensions.TypeAlias = ValueType
72
+
73
+ class _PatternEnumTypeWrapper(google.protobuf.internal.enum_type_wrapper._EnumTypeWrapper[_Pattern.ValueType], builtins.type):
74
+ DESCRIPTOR: google.protobuf.descriptor.EnumDescriptor
75
+ PATTERN_GRADIENT: _Pattern.ValueType # 0
76
+ """Animated color gradient."""
77
+ PATTERN_LOGO: _Pattern.ValueType # 1
78
+ """Bouncing LiveKit logo."""
79
+
80
+ class Pattern(_Pattern, metaclass=_PatternEnumTypeWrapper):
81
+ """Test patterns built into livekit-capture."""
82
+
67
83
  PATTERN_GRADIENT: Pattern.ValueType # 0
68
84
  """Animated color gradient."""
69
85
  PATTERN_LOGO: Pattern.ValueType # 1
@@ -110,6 +126,104 @@ CAPTURE_EXIT_END_OF_STREAM: CaptureExit.ValueType # 1
110
126
  """The producer reached the end of its stream."""
111
127
  global___CaptureExit = CaptureExit
112
128
 
129
+ class _CaptureErrorKind:
130
+ ValueType = typing.NewType("ValueType", builtins.int)
131
+ V: typing_extensions.TypeAlias = ValueType
132
+
133
+ class _CaptureErrorKindEnumTypeWrapper(google.protobuf.internal.enum_type_wrapper._EnumTypeWrapper[_CaptureErrorKind.ValueType], builtins.type):
134
+ DESCRIPTOR: google.protobuf.descriptor.EnumDescriptor
135
+ CAPTURE_ERROR_KIND_SOURCE: _CaptureErrorKind.ValueType # 0
136
+ """The capture source failed or violated its contract."""
137
+ CAPTURE_ERROR_KIND_RTC: _CaptureErrorKind.ValueType # 1
138
+ """The RTC video source rejected a frame."""
139
+ CAPTURE_ERROR_KIND_PANICKED: _CaptureErrorKind.ValueType # 2
140
+ """The pump thread panicked."""
141
+
142
+ class CaptureErrorKind(_CaptureErrorKind, metaclass=_CaptureErrorKindEnumTypeWrapper):
143
+ """Why a capture ended with an error."""
144
+
145
+ CAPTURE_ERROR_KIND_SOURCE: CaptureErrorKind.ValueType # 0
146
+ """The capture source failed or violated its contract."""
147
+ CAPTURE_ERROR_KIND_RTC: CaptureErrorKind.ValueType # 1
148
+ """The RTC video source rejected a frame."""
149
+ CAPTURE_ERROR_KIND_PANICKED: CaptureErrorKind.ValueType # 2
150
+ """The pump thread panicked."""
151
+ global___CaptureErrorKind = CaptureErrorKind
152
+
153
+ @typing.final
154
+ class GstreamerRateControl(google.protobuf.message.Message):
155
+ """Binding from WebRTC rate-control targets to a GStreamer encoder property."""
156
+
157
+ DESCRIPTOR: google.protobuf.descriptor.Descriptor
158
+
159
+ ELEMENT_FIELD_NUMBER: builtins.int
160
+ PROPERTY_FIELD_NUMBER: builtins.int
161
+ UNIT_FIELD_NUMBER: builtins.int
162
+ element: builtins.str
163
+ """Name of the encoder element in the pipeline (e.g. `lk_encoder`)."""
164
+ property: builtins.str
165
+ """Bitrate property to set on the element (e.g. `bitrate` for x264enc,
166
+ `target-bitrate` for vp8enc/vp9enc).
167
+ """
168
+ unit: global___GstreamerBitrateUnit.ValueType
169
+ """Unit the property expects."""
170
+ def __init__(
171
+ self,
172
+ *,
173
+ element: builtins.str | None = ...,
174
+ property: builtins.str | None = ...,
175
+ unit: global___GstreamerBitrateUnit.ValueType | None = ...,
176
+ ) -> None: ...
177
+ def HasField(self, field_name: typing.Literal["element", b"element", "property", b"property", "unit", b"unit"]) -> builtins.bool: ...
178
+ def ClearField(self, field_name: typing.Literal["element", b"element", "property", b"property", "unit", b"unit"]) -> None: ...
179
+
180
+ global___GstreamerRateControl = GstreamerRateControl
181
+
182
+ @typing.final
183
+ class GstreamerVideoSourceConfig(google.protobuf.message.Message):
184
+ """Encoded ingest from a GStreamer pipeline."""
185
+
186
+ DESCRIPTOR: google.protobuf.descriptor.Descriptor
187
+
188
+ PIPELINE_FIELD_NUMBER: builtins.int
189
+ CODEC_FIELD_NUMBER: builtins.int
190
+ RESOLUTION_FIELD_NUMBER: builtins.int
191
+ RATE_CONTROL_FIELD_NUMBER: builtins.int
192
+ pipeline: builtins.str
193
+ """GStreamer launch description for the encoded producer pipeline.
194
+ Must contain `appsink name=lk_appsink`, or leave exactly one encoded
195
+ video source pad unlinked for the source to attach one to.
196
+ """
197
+ codec: video_frame_pb2.VideoCodec.ValueType
198
+ """Codec expected from the pipeline; inferred from pipeline caps when
199
+ omitted.
200
+ """
201
+ @property
202
+ def resolution(self) -> video_frame_pb2.VideoSourceResolution:
203
+ """Encoded frame resolution. When omitted, it is discovered from the
204
+ pipeline's negotiated caps; when set, the pipeline output is verified
205
+ against it.
206
+ """
207
+
208
+ @property
209
+ def rate_control(self) -> global___GstreamerRateControl:
210
+ """Forwards WebRTC rate-control targets to an encoder element's bitrate
211
+ property. Without this, the pipeline encodes at a fixed bitrate.
212
+ """
213
+
214
+ def __init__(
215
+ self,
216
+ *,
217
+ pipeline: builtins.str | None = ...,
218
+ codec: video_frame_pb2.VideoCodec.ValueType | None = ...,
219
+ resolution: video_frame_pb2.VideoSourceResolution | None = ...,
220
+ rate_control: global___GstreamerRateControl | None = ...,
221
+ ) -> None: ...
222
+ def HasField(self, field_name: typing.Literal["codec", b"codec", "pipeline", b"pipeline", "rate_control", b"rate_control", "resolution", b"resolution"]) -> builtins.bool: ...
223
+ def ClearField(self, field_name: typing.Literal["codec", b"codec", "pipeline", b"pipeline", "rate_control", b"rate_control", "resolution", b"resolution"]) -> None: ...
224
+
225
+ global___GstreamerVideoSourceConfig = GstreamerVideoSourceConfig
226
+
113
227
  @typing.final
114
228
  class PatternVideoSourceConfig(google.protobuf.message.Message):
115
229
  """Test pattern rendered on the GPU."""
@@ -241,24 +355,28 @@ class NewCaptureSourceRequest(google.protobuf.message.Message):
241
355
 
242
356
  DESCRIPTOR: google.protobuf.descriptor.Descriptor
243
357
 
358
+ GSTREAMER_FIELD_NUMBER: builtins.int
244
359
  PATTERN_FIELD_NUMBER: builtins.int
245
360
  CLOCK_FIELD_NUMBER: builtins.int
246
361
  REQUEST_ASYNC_ID_FIELD_NUMBER: builtins.int
247
362
  request_async_id: builtins.int
248
363
  @property
364
+ def gstreamer(self) -> global___GstreamerVideoSourceConfig: ...
365
+ @property
249
366
  def pattern(self) -> global___PatternVideoSourceConfig: ...
250
367
  @property
251
368
  def clock(self) -> global___ClockVideoSourceConfig: ...
252
369
  def __init__(
253
370
  self,
254
371
  *,
372
+ gstreamer: global___GstreamerVideoSourceConfig | None = ...,
255
373
  pattern: global___PatternVideoSourceConfig | None = ...,
256
374
  clock: global___ClockVideoSourceConfig | None = ...,
257
375
  request_async_id: builtins.int | None = ...,
258
376
  ) -> None: ...
259
- def HasField(self, field_name: typing.Literal["clock", b"clock", "config", b"config", "pattern", b"pattern", "request_async_id", b"request_async_id"]) -> builtins.bool: ...
260
- def ClearField(self, field_name: typing.Literal["clock", b"clock", "config", b"config", "pattern", b"pattern", "request_async_id", b"request_async_id"]) -> None: ...
261
- def WhichOneof(self, oneof_group: typing.Literal["config", b"config"]) -> typing.Literal["pattern", "clock"] | None: ...
377
+ def HasField(self, field_name: typing.Literal["clock", b"clock", "config", b"config", "gstreamer", b"gstreamer", "pattern", b"pattern", "request_async_id", b"request_async_id"]) -> builtins.bool: ...
378
+ def ClearField(self, field_name: typing.Literal["clock", b"clock", "config", b"config", "gstreamer", b"gstreamer", "pattern", b"pattern", "request_async_id", b"request_async_id"]) -> None: ...
379
+ def WhichOneof(self, oneof_group: typing.Literal["config", b"config"]) -> typing.Literal["gstreamer", "pattern", "clock"] | None: ...
262
380
 
263
381
  global___NewCaptureSourceRequest = NewCaptureSourceRequest
264
382
 
@@ -397,14 +515,17 @@ class CaptureError(google.protobuf.message.Message):
397
515
  DESCRIPTOR: google.protobuf.descriptor.Descriptor
398
516
 
399
517
  ERROR_FIELD_NUMBER: builtins.int
518
+ KIND_FIELD_NUMBER: builtins.int
400
519
  error: builtins.str
520
+ kind: global___CaptureErrorKind.ValueType
401
521
  def __init__(
402
522
  self,
403
523
  *,
404
524
  error: builtins.str | None = ...,
525
+ kind: global___CaptureErrorKind.ValueType | None = ...,
405
526
  ) -> None: ...
406
- def HasField(self, field_name: typing.Literal["error", b"error"]) -> builtins.bool: ...
407
- def ClearField(self, field_name: typing.Literal["error", b"error"]) -> None: ...
527
+ def HasField(self, field_name: typing.Literal["error", b"error", "kind", b"kind"]) -> builtins.bool: ...
528
+ def ClearField(self, field_name: typing.Literal["error", b"error", "kind", b"kind"]) -> None: ...
408
529
 
409
530
  global___CaptureError = CaptureError
410
531
 
@@ -113,6 +113,19 @@ class Queue(asyncio.Queue[T]):
113
113
 
114
114
  self.task_done()
115
115
 
116
+ def release(self) -> None:
117
+ """Mark every unfinished item as done.
118
+
119
+ Lets a ``join()`` that is already waiting on this queue return once there
120
+ is no consumer left for it.
121
+ """
122
+ while True:
123
+ try:
124
+ self.task_done()
125
+ except ValueError:
126
+ # task_done() raises once every item is accounted for
127
+ return
128
+
116
129
 
117
130
  class BroadcastQueue(Generic[T]):
118
131
  """Queue with multiple subscribers."""
@@ -135,6 +148,10 @@ class BroadcastQueue(Generic[T]):
135
148
 
136
149
  def unsubscribe(self, queue: Queue[T]) -> None:
137
150
  self._subscribers.remove(queue)
151
+ # A join() may already hold this queue. Events that are still queued, or that
152
+ # the subscriber took but never finished, have no consumer anymore, so
153
+ # release them instead of leaving the joiner waiting forever.
154
+ queue.release()
138
155
 
139
156
  async def join(self) -> None:
140
157
  async with self._lock: