|
33 | 33 | from .track import RemoteAudioTrack, RemoteVideoTrack |
34 | 34 | from .track_publication import RemoteTrackPublication, TrackPublication |
35 | 35 | from .transcription import TranscriptionSegment |
| 36 | +from .data_stream import ( |
| 37 | + TextStreamReader, |
| 38 | + ByteStreamReader, |
| 39 | + TextStreamHandler, |
| 40 | + ByteStreamHandler, |
| 41 | +) |
36 | 42 |
|
37 | 43 |
|
38 | 44 | EventTypes = Literal[ |
@@ -140,6 +146,11 @@ def __init__(self, loop: Optional[asyncio.AbstractEventLoop] = None) -> None: |
140 | 146 | self._first_sid_future = asyncio.Future[str]() |
141 | 147 | self._local_participant: LocalParticipant | None = None |
142 | 148 |
|
| 149 | + self._text_stream_readers: Dict[str, TextStreamReader] = {} |
| 150 | + self._byte_stream_readers: Dict[str, ByteStreamReader] = {} |
| 151 | + self._text_stream_handlers: Dict[str, TextStreamHandler] = {} |
| 152 | + self._byte_stream_handlers: Dict[str, ByteStreamHandler] = {} |
| 153 | + |
143 | 154 | def __del__(self) -> None: |
144 | 155 | if self._ffi_handle is not None: |
145 | 156 | FfiClient.instance.queue.unsubscribe(self._ffi_queue) |
@@ -398,6 +409,28 @@ def on_participant_connected(participant): |
398 | 409 | # start listening to room events |
399 | 410 | self._task = self._loop.create_task(self._listen_task()) |
400 | 411 |
|
| 412 | + def register_byte_stream_handler(self, topic: str, handler: ByteStreamHandler): |
| 413 | + existing_handler = self._byte_stream_handlers.get(topic) |
| 414 | + if existing_handler is None: |
| 415 | + self._byte_stream_handlers[topic] = handler |
| 416 | + else: |
| 417 | + raise ValueError("byte stream handler for topic '%s' already set" % topic) |
| 418 | + |
| 419 | + def unregister_byte_stream_handler(self, topic: str): |
| 420 | + if self._byte_stream_handlers.get(topic): |
| 421 | + self._byte_stream_handlers.pop(topic) |
| 422 | + |
| 423 | + def register_text_stream_handler(self, topic: str, handler: TextStreamHandler): |
| 424 | + existing_handler = self._text_stream_handlers.get(topic) |
| 425 | + if existing_handler is None: |
| 426 | + self._text_stream_handlers[topic] = handler |
| 427 | + else: |
| 428 | + raise ValueError("text stream handler for topic '%s' already set" % topic) |
| 429 | + |
| 430 | + def unregister_text_stream_handler(self, topic: str): |
| 431 | + if self._text_stream_handlers.get(topic): |
| 432 | + self._text_stream_handlers.pop(topic) |
| 433 | + |
401 | 434 | async def disconnect(self) -> None: |
402 | 435 | """Disconnects from the room.""" |
403 | 436 | if not self.isconnected(): |
@@ -714,28 +747,77 @@ def _on_room_event(self, event: proto_room.RoomEvent): |
714 | 747 | elif which == "reconnected": |
715 | 748 | self.emit("reconnected") |
716 | 749 | elif which == "stream_header_received": |
717 | | - self.local_participant._handle_stream_header( |
| 750 | + self._handle_stream_header( |
718 | 751 | event.stream_header_received.header, |
719 | 752 | event.stream_header_received.participant_identity, |
720 | 753 | ) |
721 | 754 | elif which == "stream_chunk_received": |
722 | 755 | task = asyncio.create_task( |
723 | | - self.local_participant._handle_stream_chunk( |
724 | | - event.stream_chunk_received.chunk |
725 | | - ) |
| 756 | + self._handle_stream_chunk(event.stream_chunk_received.chunk) |
726 | 757 | ) |
727 | 758 | self._data_stream_tasks.add(task) |
728 | 759 | task.add_done_callback(self._data_stream_tasks.discard) |
729 | 760 |
|
730 | 761 | elif which == "stream_trailer_received": |
731 | 762 | task = asyncio.create_task( |
732 | | - self.local_participant._handle_stream_trailer( |
733 | | - event.stream_trailer_received.trailer |
734 | | - ) |
| 763 | + self._handle_stream_trailer(event.stream_trailer_received.trailer) |
735 | 764 | ) |
736 | 765 | self._data_stream_tasks.add(task) |
737 | 766 | task.add_done_callback(self._data_stream_tasks.discard) |
738 | 767 |
|
| 768 | + def _handle_stream_header( |
| 769 | + self, header: proto_room.DataStream.Header, participant_identity: str |
| 770 | + ): |
| 771 | + stream_type = header.WhichOneof("content_header") |
| 772 | + if stream_type == "text_header": |
| 773 | + text_stream_handler = self._text_stream_handlers.get(header.topic) |
| 774 | + if text_stream_handler is None: |
| 775 | + logging.info( |
| 776 | + "ignoring text stream with topic '%s', no callback attached", |
| 777 | + header.topic, |
| 778 | + ) |
| 779 | + return |
| 780 | + |
| 781 | + text_reader = TextStreamReader(header) |
| 782 | + self._text_stream_readers[header.stream_id] = text_reader |
| 783 | + text_stream_handler(text_reader, participant_identity) |
| 784 | + elif stream_type == "byte_header": |
| 785 | + logging.warning("received byte header, %s", header.stream_id) |
| 786 | + byte_stream_handler = self._byte_stream_handlers.get(header.topic) |
| 787 | + if byte_stream_handler is None: |
| 788 | + logging.info( |
| 789 | + "ignoring byte stream with topic '%s', no callback attached", |
| 790 | + header.topic, |
| 791 | + ) |
| 792 | + return |
| 793 | + |
| 794 | + byte_reader = ByteStreamReader(header) |
| 795 | + self._byte_stream_readers[header.stream_id] = byte_reader |
| 796 | + byte_stream_handler(byte_reader, participant_identity) |
| 797 | + else: |
| 798 | + logging.warning("received unknown header type, %s", stream_type) |
| 799 | + pass |
| 800 | + |
| 801 | + async def _handle_stream_chunk(self, chunk: proto_room.DataStream.Chunk): |
| 802 | + text_reader = self._text_stream_readers.get(chunk.stream_id) |
| 803 | + file_reader = self._byte_stream_readers.get(chunk.stream_id) |
| 804 | + |
| 805 | + if text_reader: |
| 806 | + await text_reader._on_chunk_update(chunk) |
| 807 | + elif file_reader: |
| 808 | + await file_reader._on_chunk_update(chunk) |
| 809 | + |
| 810 | + async def _handle_stream_trailer(self, trailer: proto_room.DataStream.Trailer): |
| 811 | + text_reader = self._text_stream_readers.get(trailer.stream_id) |
| 812 | + file_reader = self._byte_stream_readers.get(trailer.stream_id) |
| 813 | + |
| 814 | + if text_reader: |
| 815 | + await text_reader._on_stream_close(trailer) |
| 816 | + self._text_stream_readers.pop(trailer.stream_id) |
| 817 | + elif file_reader: |
| 818 | + await file_reader._on_stream_close(trailer) |
| 819 | + self._byte_stream_readers.pop(trailer.stream_id) |
| 820 | + |
739 | 821 | async def _drain_rpc_invocation_tasks(self) -> None: |
740 | 822 | if self._rpc_invocation_tasks: |
741 | 823 | for task in self._rpc_invocation_tasks: |
|
0 commit comments