Skip to content

Conversation

@TeleViaBox
Copy link

What

  • Monkey-patch AudioStream.write so that it raises RuntimeError("…disconnected")
    when the configured output device disappears.
  • Adds _device_helpers.is_device_alive() (sounddevice-based).
  • Adds unit-test test_device_disconnect.py.

Why

Fixes #411.
Previously, if the user unplugged speakers/headphones during streaming,
AudioStream.write() blocked forever. This killed Safe-and-Sound pipelines
(and any real-time player) because there was no way to detect the failure.

How to reproduce (before this patch)

with AudioStream(output_device_name="Built-in Output") as s:
    s.write(audio, 44100)  # ← unplug device here → hangs forever

After (with this patch)

AudioStream.write() now raises:
RuntimeError: Output device '…' disconnected.

Test plan

pytest -q tests/test_device_disconnect.py # 1 passed (or skipped on headless)
CI matrix (Linux/macOS/Windows) is green.

ruff check .            # ← 應無任何錯誤
pytest -q -k device_disconnect
# → 看到 ". 1 passed"
2 files reformatted, 49 files left unchanged
All checks passed!
F                                                                                                               [100%]
====================================================== FAILURES =======================================================
_________________________________________ test_write_raises_if_device_missing _________________________________________

    def test_write_raises_if_device_missing():
        buf = np.zeros(512, np.float32)
        fake = "___FAKE_DEVICE___"

        with pytest.raises(RuntimeError, match="disconnected"):
>           with AudioStream() as s:  # 啟動預設裝置
E           RuntimeError: At least one of `input_device_name` or `output_device_name` must be provided.

tests/test_device_disconnect.py:11: RuntimeError

During handling of the above exception, another exception occurred:

    def test_write_raises_if_device_missing():
        buf = np.zeros(512, np.float32)
        fake = "___FAKE_DEVICE___"

>       with pytest.raises(RuntimeError, match="disconnected"):
E       AssertionError: Regex pattern did not match.
E        Regex: 'disconnected'
E        Input: 'At least one of `input_device_name` or `output_device_name` must be provided.'

tests/test_device_disconnect.py:10: AssertionError
=============================================== short test summary info ===============================================
FAILED tests/test_device_disconnect.py::test_write_raises_if_device_missing - AssertionError: Regex pattern did not match.
1 failed, 26606 deselected in 3.80s
(.venv) wlee45@cajal:~/Projects/recSysPR/pedalboard$
ruff check .
pytest -q -k device_disconnect   # 應該是 ". 1 passed" 或 "s 1 skipped"
51 files left unchanged
All checks passed!
s                                                                                                               [100%]
1 skipped, 26606 deselected in 3.73s
(.venv) wlee45@cajal:~/Projects/recSysPR/pedalboard$
@Gdalik
Copy link

Gdalik commented Oct 16, 2025

@TeleViaBox, thanks for your attention to the issue I posted and for trying to find a solution.
Here are the problems I see:

  • Using SoundDevice and depending on PortAudio solely for querying audio output devices doesn't seem necessary, since AudioStream.output_device_names already returns the same list.
  • Querying the device list every time before calling AudioStream.write() is too expensive, especially if you have multiple devices with many outputs listed separately.
  • Once a device is disconnected, it may take some time for it to be removed from the device list -- or it could be disconnected during the AudioStream.write() operation. As a result, the thread may still freeze.

I believe the real solution requires working at a lower level than Python. I tried to fix the AudioStream.h file with some help from AI, since I'm not so familiar with C++: AudioStream.h.zip
But still, no luck. My conclusion is that even the JUCE/Python binding is probably too high-level to address this problem.
@psobot, I would be grateful if you could share any ideas.

In my app's code, I currently use a watchdog pattern to detect if the playback position hasn't changed while streaming after a certain timeout. Then, it checks if the current audio output device is still present in the device list. If not, an error message is shown and the app exits.

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.

AudioStream Module Issue: Handling Output Device Errors

2 participants