Skip to content

Commit bdeadba

Browse files
1egomanclaude
andauthored
feat(rtc): add LocalParticipant.send_bytes (#858)
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
1 parent c8ef30b commit bdeadba

2 files changed

Lines changed: 84 additions & 0 deletions

File tree

‎livekit-rtc/livekit/rtc/participant.py‎

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -974,6 +974,56 @@ async def send_file(
974974

975975
return _byte_stream_info_from_proto(cb.send_file.info)
976976

977+
async def send_bytes(
978+
self,
979+
data: bytes,
980+
*,
981+
topic: str = "",
982+
name: str | None = None,
983+
mime_type: str | None = None,
984+
destination_identities: Optional[List[str]] = None,
985+
attributes: Optional[Dict[str, str]] = None,
986+
stream_id: str | None = None,
987+
compress: bool = True,
988+
) -> ByteStreamInfo:
989+
"""
990+
Sends an in-memory buffer as a complete byte stream in one call.
991+
Unlike stream_bytes(), the whole payload is known up front, so short payloads can be
992+
sent as a single packet and larger ones compressed, when every recipient supports it.
993+
`name` defaults to "unknown" and `mime_type` to "application/octet-stream".
994+
"""
995+
req = proto_ffi.FfiRequest()
996+
req.send_bytes.local_participant_handle = self._ffi_handle.handle
997+
req.send_bytes.bytes = data
998+
options = req.send_bytes.options
999+
options.topic = topic
1000+
if name is not None:
1001+
options.name = name
1002+
if mime_type is not None:
1003+
options.mime_type = mime_type
1004+
if attributes:
1005+
options.attributes.update(attributes)
1006+
if destination_identities:
1007+
options.destination_identities.extend(destination_identities)
1008+
if stream_id is not None:
1009+
options.id = stream_id
1010+
options.compress = compress
1011+
options.sender_identity = self.identity
1012+
1013+
queue = FfiClient.instance.queue.subscribe()
1014+
try:
1015+
resp = FfiClient.instance.request(req)
1016+
cb: proto_ffi.FfiEvent = await queue.wait_for(
1017+
lambda e: e.send_bytes.async_id == resp.send_bytes.async_id
1018+
)
1019+
finally:
1020+
FfiClient.instance.queue.unsubscribe(queue)
1021+
1022+
if cb.send_bytes.HasField("error"):
1023+
raise StreamError(cb.send_bytes.error.description)
1024+
1025+
return _byte_stream_info_from_proto(cb.send_bytes.info)
1026+
9771027
async def publish_data_track(
9781028
self,
9791029
*,

‎tests/rtc/test_e2e_data_streams.py‎

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,40 @@ async def read() -> None:
192192
await asyncio.gather(receiver.disconnect(), sender.disconnect())
193193

194194

195+
@pytest.mark.asyncio
196+
@skip_if_no_credentials() # type: ignore[untyped-decorator]
197+
async def test_send_bytes_small() -> None:
198+
"""Small in-memory buffer via send_bytes: info assertions + round trip."""
199+
receiver, sender = await connect_rooms(unique_room_name("ds-send-bytes-small"))
200+
try:
201+
bytes_to_send = b"hello world"
202+
received: asyncio.Future[bytes] = asyncio.get_event_loop().create_future()
203+
204+
def handler(reader: rtc.ByteStreamReader, participant_identity: str) -> None:
205+
assert participant_identity == "sender"
206+
207+
async def read() -> None:
208+
chunks = [chunk async for chunk in reader]
209+
received.set_result(b"".join(chunks))
210+
211+
asyncio.create_task(read())
212+
213+
receiver.register_byte_stream_handler("some-topic", handler)
214+
215+
info = await sender.local_participant.send_bytes(
216+
bytes_to_send, topic="some-topic", name="hello.bin"
217+
)
218+
assert info.stream_id
219+
assert info.topic == "some-topic"
220+
assert info.name == "hello.bin"
221+
assert info.size == len(bytes_to_send)
222+
assert info.mime_type == "application/octet-stream"
223+
224+
assert await asyncio.wait_for(received, timeout=5.0) == bytes_to_send
225+
finally:
226+
await asyncio.gather(receiver.disconnect(), sender.disconnect())
227+
228+
195229
@pytest.mark.asyncio
196230
@skip_if_no_credentials() # type: ignore[untyped-decorator]
197231
async def test_send_text_large_compressible() -> None:

0 commit comments

Comments
 (0)