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 asyncio
16
- from typing import Optional , AsyncIterator
18
+ from dataclasses import dataclass
19
+ from typing import Any , AsyncIterator , Optional
17
20
18
- from ._ffi_client import FfiHandle , FfiClient
21
+ from ._ffi_client import FfiClient , FfiHandle
19
22
from ._proto import audio_frame_pb2 as proto_audio_frame
20
23
from ._proto import ffi_pb2 as proto_ffi
24
+ from ._proto .track_pb2 import TrackSource
21
25
from ._utils import RingQueue , task_done_logger
22
26
from .audio_frame import AudioFrame
27
+ from .participant import Participant
23
28
from .track import Track
24
- from dataclasses import dataclass
25
29
26
30
27
31
@dataclass
@@ -39,30 +43,95 @@ def __init__(
39
43
capacity : int = 0 ,
40
44
sample_rate : int = 48000 ,
41
45
num_channels : int = 1 ,
46
+ ** kwargs ,
42
47
) -> None :
43
- self ._track = track
48
+ self ._track : Track | None = track
49
+ self ._sample_rate = sample_rate
50
+ self ._num_channels = num_channels
44
51
self ._loop = loop or asyncio .get_event_loop ()
45
52
self ._ffi_queue = FfiClient .instance .queue .subscribe (self ._loop )
46
- self ._queue : RingQueue [AudioFrameEvent ] = RingQueue (capacity )
47
-
48
- req = proto_ffi .FfiRequest ()
49
- new_audio_stream = req .new_audio_stream
50
- new_audio_stream .track_handle = track ._ffi_handle .handle
51
- new_audio_stream .type = proto_audio_frame .AudioStreamType .AUDIO_STREAM_NATIVE
52
- new_audio_stream .sample_rate = sample_rate
53
- new_audio_stream .num_channels = num_channels
54
- resp = FfiClient .instance .request (req )
55
-
56
- stream_info = resp .new_audio_stream .stream
57
- self ._ffi_handle = FfiHandle (stream_info .handle .id )
58
- self ._info = stream_info
53
+ self ._queue : RingQueue [AudioFrameEvent | None ] = RingQueue (capacity )
59
54
60
55
self ._task = self ._loop .create_task (self ._run ())
61
56
self ._task .add_done_callback (task_done_logger )
62
57
58
+ stream : Any = None
59
+ if "participant" in kwargs :
60
+ stream = self ._create_owned_stream_from_participant (
61
+ participant = kwargs ["participant" ], track_source = kwargs ["track_source" ]
62
+ )
63
+ else :
64
+ stream = self ._create_owned_stream ()
65
+ self ._ffi_handle = FfiHandle (stream .handle .id )
66
+ self ._info = stream .info
67
+
68
+ @classmethod
69
+ def from_participant (
70
+ cls ,
71
+ * ,
72
+ participant : Participant ,
73
+ track_source : TrackSource .ValueType ,
74
+ loop : Optional [asyncio .AbstractEventLoop ] = None ,
75
+ capacity : int = 0 ,
76
+ sample_rate : int = 48000 ,
77
+ num_channels : int = 1 ,
78
+ ) -> AudioStream :
79
+ return AudioStream (
80
+ participant = participant ,
81
+ track_source = track_source ,
82
+ loop = loop ,
83
+ capacity = capacity ,
84
+ track = None , # type: ignore
85
+ sample_rate = sample_rate ,
86
+ num_channels = num_channels ,
87
+ )
88
+
89
+ @classmethod
90
+ def from_track (
91
+ cls ,
92
+ * ,
93
+ track : Track ,
94
+ loop : Optional [asyncio .AbstractEventLoop ] = None ,
95
+ capacity : int = 0 ,
96
+ sample_rate : int = 48000 ,
97
+ num_channels : int = 1 ,
98
+ ) -> AudioStream :
99
+ return AudioStream (
100
+ track = track ,
101
+ loop = loop ,
102
+ capacity = capacity ,
103
+ sample_rate = sample_rate ,
104
+ num_channels = num_channels ,
105
+ )
106
+
63
107
def __del__ (self ) -> None :
64
108
FfiClient .instance .queue .unsubscribe (self ._ffi_queue )
65
109
110
+ def _create_owned_stream (self ) -> Any :
111
+ req = proto_ffi .FfiRequest ()
112
+ new_audio_stream = req .new_audio_stream
113
+ new_audio_stream .track_handle = self ._track ._ffi_handle .handle
114
+ new_audio_stream .sample_rate = self ._sample_rate
115
+ new_audio_stream .num_channels = self ._num_channels
116
+ new_audio_stream .type = proto_audio_frame .AudioStreamType .AUDIO_STREAM_NATIVE
117
+ resp = FfiClient .instance .request (req )
118
+ return resp .new_audio_stream .stream
119
+
120
+ def _create_owned_stream_from_participant (
121
+ self , participant : Participant , track_source : TrackSource .ValueType
122
+ ) -> Any :
123
+ req = proto_ffi .FfiRequest ()
124
+ audio_stream_from_participant = req .audio_stream_from_participant
125
+ audio_stream_from_participant .participant_handle = (
126
+ participant ._ffi_handle .handle
127
+ )
128
+ audio_stream_from_participant .type = (
129
+ proto_audio_frame .AudioStreamType .AUDIO_STREAM_NATIVE
130
+ )
131
+ audio_stream_from_participant .track_source = track_source
132
+ resp = FfiClient .instance .request (req )
133
+ return resp .audio_stream_from_participant .stream
134
+
66
135
async def _run (self ):
67
136
while True :
68
137
event = await self ._ffi_queue .wait_for (self ._is_event )
@@ -74,6 +143,7 @@ async def _run(self):
74
143
event = AudioFrameEvent (frame )
75
144
self ._queue .put (event )
76
145
elif audio_event .HasField ("eos" ):
146
+ self ._queue .put (None )
77
147
break
78
148
79
149
FfiClient .instance .queue .unsubscribe (self ._ffi_queue )
@@ -91,4 +161,9 @@ def __aiter__(self) -> AsyncIterator[AudioFrameEvent]:
91
161
async def __anext__ (self ) -> AudioFrameEvent :
92
162
if self ._task .done ():
93
163
raise StopAsyncIteration
94
- return await self ._queue .get ()
164
+
165
+ item = await self ._queue .get ()
166
+ if item is None :
167
+ raise StopAsyncIteration
168
+
169
+ return item
0 commit comments