33
44import pytest
55from aiortc .codecs .h264 import H264Encoder
6+ from aiortc .codecs .opus import OpusEncoder
67from aiortc .codecs .vpx import Vp8Encoder
78
89from getstream .video .rtc .encoders_patches import (
10+ STREAM_OPUS_DEFAULT_BITRATE ,
11+ STREAM_OPUS_DEFAULT_COMPRESSION_LEVEL ,
12+ STREAM_OPUS_DEFAULT_LAYOUT ,
913 STREAM_VIDEO_DEFAULT_BITRATE ,
1014 STREAM_VIDEO_MAX_BITRATE ,
1115 STREAM_VIDEO_MIN_BITRATE ,
1216 StreamH264Encoder ,
17+ StreamOpusEncoder ,
1318 StreamVp8Encoder ,
1419 patch_sender_encoder ,
1520)
@@ -68,6 +73,31 @@ def test_accepts_in_range(self):
6873 assert enc .target_bitrate == 2_000_000
6974
7075
76+ class TestStreamOpusEncoder :
77+ def test_defaults (self ):
78+ enc = StreamOpusEncoder ()
79+ assert enc .codec .bit_rate == STREAM_OPUS_DEFAULT_BITRATE
80+ assert enc .codec .layout .name == STREAM_OPUS_DEFAULT_LAYOUT
81+ assert enc .codec .options ["compression_level" ] == str (
82+ STREAM_OPUS_DEFAULT_COMPRESSION_LEVEL
83+ )
84+ assert enc .codec .options ["application" ] == "voip"
85+
86+ def test_custom_bitrate (self ):
87+ enc = StreamOpusEncoder (bitrate = 32_000 )
88+ assert enc .codec .bit_rate == 32_000
89+
90+ def test_custom_layout_updates_resampler (self ):
91+ """Resampler layout must track codec layout — encode() asserts on it."""
92+ enc = StreamOpusEncoder (layout = "stereo" )
93+ assert enc .codec .layout .name == "stereo"
94+ assert enc .resampler .layout .name == "stereo"
95+
96+ def test_custom_compression_level (self ):
97+ enc = StreamOpusEncoder (compression_level = 10 )
98+ assert enc .codec .options ["compression_level" ] == "10"
99+
100+
71101class TestBitratePatchDisabled :
72102 @pytest .mark .parametrize ("env_val" , [None , "" , "1" , "true" , "yes" , "on" ])
73103 def test_enabled_by_default (self , monkeypatch , env_val ):
@@ -165,6 +195,25 @@ async def _orig_coro(codec):
165195
166196 assert isinstance (sender ._RTCRtpSender__encoder , H264Cls )
167197
198+ @pytest .mark .asyncio
199+ async def test_installs_opus_encoder (self ):
200+ sender = MagicMock ()
201+ sender ._RTCRtpSender__encoder = None
202+
203+ async def _orig_coro (codec ):
204+ return None
205+
206+ sender ._next_encoded_frame = _orig_coro
207+ patch_sender_encoder (sender )
208+
209+ codec = MagicMock ()
210+ codec .mimeType = "audio/opus"
211+ await sender ._next_encoded_frame (codec )
212+
213+ from getstream .video .rtc .encoders_patches import StreamOpusEncoder as OpusCls
214+
215+ assert isinstance (sender ._RTCRtpSender__encoder , OpusCls )
216+
168217 @pytest .mark .asyncio
169218 async def test_does_not_replace_existing_encoder (self ):
170219 """Already-set encoder is not replaced."""
@@ -183,15 +232,25 @@ async def _orig_coro(codec):
183232 await sender ._next_encoded_frame (codec )
184233 assert sender ._RTCRtpSender__encoder is existing_encoder
185234
186- def test_noop_when_encoders_none (self , monkeypatch ):
187- """patch_sender_encoder is a no-op when encoder classes failed to load."""
235+ @pytest .mark .asyncio
236+ async def test_skips_mime_when_encoder_class_is_none (self , monkeypatch ):
237+ """If a Stream* class failed to load, that mime branch leaves the encoder unset."""
188238 import getstream .video .rtc .encoders_patches as mod
189239
190240 monkeypatch .setattr (mod , "StreamVp8Encoder" , None )
191241 sender = MagicMock ()
192- orig = sender ._next_encoded_frame
242+ sender ._RTCRtpSender__encoder = None
243+
244+ async def _orig_coro (codec ):
245+ return None
246+
247+ sender ._next_encoded_frame = _orig_coro
193248 mod .patch_sender_encoder (sender )
194- assert sender ._next_encoded_frame is orig
249+
250+ codec = MagicMock ()
251+ codec .mimeType = "video/VP8"
252+ await sender ._next_encoded_frame (codec )
253+ assert sender ._RTCRtpSender__encoder is None
195254
196255
197256class TestUpstreamAssumptions :
@@ -216,6 +275,11 @@ def test_upstream_assumptions_hold(self):
216275 "H264Encoder name-mangled __target_bitrate changed"
217276 )
218277
278+ # OpusEncoder still exposes the public attrs StreamOpusEncoder overrides.
279+ opus = OpusEncoder ()
280+ assert hasattr (opus , "codec" ), "OpusEncoder lost codec"
281+ assert hasattr (opus , "resampler" ), "OpusEncoder lost resampler"
282+
219283 # RTCRtpSender has _next_encoded_frame and uses __encoder
220284 import inspect
221285
0 commit comments