Skip to content

Conversation

@chenosaurus
Copy link
Contributor

@chenosaurus chenosaurus commented Sep 9, 2025

  • Add a new class MediaDevices that provides centralized access to local audio input/outputs.
  • Enables AEC loop when using both mic & speaker.
  • Add examples for publishing local mic & full duplex audio.

@chenosaurus chenosaurus marked this pull request as ready for review September 24, 2025 22:18
Copy link
Member

@theomonnom theomonnom left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice!!

except StopAsyncIteration:
exhausted = True
break
# AudioStream may yield either AudioFrame or AudioFrameEvent; unwrap if needed
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The correct typing should be:

self, stream: AsyncIterator[AudioFrame | AudioFrameEvent], buf: np.ndarray

stream_kwargs["mapping"] = mapping
elif mapping is not None:
logging.getLogger(__name__).warning(
"sounddevice.InputStream does not support 'mapping' in this version; "
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we have to support that? or only latest?

input_channel_index,
)

input_stream = sd.InputStream(**stream_kwargs)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Ideally we keep the typed args (no dict)

def open_output(
self,
*,
apm_for_reverse: Optional[AudioProcessingModule] = None,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be exposed to the user. Let's make this an internal detail

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do you mean should not?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes sry, it shouldn't

from typing import Any, AsyncIterator, Optional

import numpy as np
import sounddevice as sd # type: ignore[import-not-found, import-untyped]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we make sure to import sounddevice lazily

self._track_streams[track.sid] = (stream, stream_iterator)
self._mixer.add_stream(stream_iterator)

async def remove_track(self, track: Track) -> None:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should this method be sync instead of async? like add_track

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it awaits stream.aclose() in that function

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I’m thinking more in terms of API design rather than the underlying reasons. For consistency, I’d make both methods async

@chenosaurus chenosaurus merged commit 5b1a979 into main Nov 21, 2025
16 checks passed
@chenosaurus chenosaurus deleted the dc/media_devices branch November 21, 2025 00:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants