Preventing memory growth if we are not consuming the track.

This commit is contained in:
Filipi Fuchter
2025-09-18 16:16:10 -03:00
parent d20e4125f6
commit 199bf72945

View File

@@ -95,15 +95,20 @@ class SmallWebRTCTrack:
enable/disable control and frame discarding for audio and video streams. enable/disable control and frame discarding for audio and video streams.
""" """
def __init__(self, track: MediaStreamTrack): def __init__(self, receiver):
"""Initialize the WebRTC track wrapper. """Initialize the WebRTC track wrapper.
Args: Args:
track: The underlying MediaStreamTrack to wrap. receiver: The RemoteStreamTrack receiver instance.
index: The index of the track in the transceiver (0 for mic, 1 for cam, 2 for screen)
""" """
self._track = track self._receiver = receiver
# Configuring the receiver for not consuming the track by default to prevent memory grow
self._receiver._enabled = False
self._track = receiver.track
self._enabled = True self._enabled = True
self._last_recv_time: float = 0.0
self._idle_task: Optional[asyncio.Task] = None
self._idle_timeout: float = 2.0 # seconds before discarding old frames
def set_enabled(self, enabled: bool) -> None: def set_enabled(self, enabled: bool) -> None:
"""Enable or disable the track. """Enable or disable the track.
@@ -138,13 +143,44 @@ class SmallWebRTCTrack:
async def recv(self) -> Optional[Frame]: async def recv(self) -> Optional[Frame]:
"""Receive the next frame from the track. """Receive the next frame from the track.
Enables the internal receiving state and starts idle watcher.
Returns: Returns:
The next frame, except for video tracks, where it returns the frame only if the track is enabled, otherwise, returns None. The next frame, except for video tracks, where it returns the frame only if the track is enabled, otherwise, returns None.
""" """
self._receiver._enabled = True
self._last_recv_time = time.time()
# start idle watcher if not already running
if not self._idle_task or self._idle_task.done():
self._idle_task = asyncio.create_task(self._idle_watcher())
if not self._enabled and self._track.kind == "video": if not self._enabled and self._track.kind == "video":
return None return None
return await self._track.recv() return await self._track.recv()
async def _idle_watcher(self):
"""Disable receiving if idle for more than _idle_timeout."""
while self._receiver._enabled:
await asyncio.sleep(self._idle_timeout)
idle_duration = time.time() - self._last_recv_time
if idle_duration >= self._idle_timeout:
# discard old frames from the receiver queue to prevent growth
logger.debug(
f"Disabling receiver for {self._track.kind} track to prevent memory growth."
)
await self.discard_old_frames()
self._receiver._enabled = False
def stop(self):
"""Stop receiving frames from the track."""
self._receiver._enabled = False
if self._idle_task:
self._idle_task.cancel()
self._idle_task = None
if self._track:
self._track.stop()
def __getattr__(self, name): def __getattr__(self, name):
"""Forward attribute access to the underlying track. """Forward attribute access to the underlying track.
@@ -526,8 +562,8 @@ class SmallWebRTCConnection(BaseObject):
logger.warning("No audio transceiver is available") logger.warning("No audio transceiver is available")
return None return None
track = transceivers[AUDIO_TRANSCEIVER_INDEX].receiver.track receiver = transceivers[AUDIO_TRANSCEIVER_INDEX].receiver
audio_track = SmallWebRTCTrack(track) if track else None audio_track = SmallWebRTCTrack(receiver) if receiver else None
self._track_map[AUDIO_TRANSCEIVER_INDEX] = audio_track self._track_map[AUDIO_TRANSCEIVER_INDEX] = audio_track
return audio_track return audio_track
@@ -548,8 +584,8 @@ class SmallWebRTCConnection(BaseObject):
logger.warning("No video transceiver is available") logger.warning("No video transceiver is available")
return None return None
track = transceivers[VIDEO_TRANSCEIVER_INDEX].receiver.track receiver = transceivers[VIDEO_TRANSCEIVER_INDEX].receiver
video_track = SmallWebRTCTrack(track) if track else None video_track = SmallWebRTCTrack(receiver) if receiver else None
self._track_map[VIDEO_TRANSCEIVER_INDEX] = video_track self._track_map[VIDEO_TRANSCEIVER_INDEX] = video_track
return video_track return video_track
@@ -570,8 +606,8 @@ class SmallWebRTCConnection(BaseObject):
logger.warning("No screen video transceiver is available") logger.warning("No screen video transceiver is available")
return None return None
track = transceivers[SCREEN_VIDEO_TRANSCEIVER_INDEX].receiver.track receiver = transceivers[SCREEN_VIDEO_TRANSCEIVER_INDEX].receiver
video_track = SmallWebRTCTrack(track) if track else None video_track = SmallWebRTCTrack(receiver) if receiver else None
self._track_map[SCREEN_VIDEO_TRANSCEIVER_INDEX] = video_track self._track_map[SCREEN_VIDEO_TRANSCEIVER_INDEX] = video_track
return video_track return video_track