diff --git a/src/audio/audio_manager.cpp b/src/audio/audio_manager.cpp index d7cc723..a62c14c 100644 --- a/src/audio/audio_manager.cpp +++ b/src/audio/audio_manager.cpp @@ -512,9 +512,19 @@ namespace radiantwave::audio return false; } - // Must be called with mutex_ held. Destroys the current mixer and creates a - // fresh one on the given device, recreating the two track slots and - // resuming the active track if one was playing. + // Must be called with mutex_ held. Creates a fresh mixer on the given + // device, recreating the two track slots and resuming the active track if + // one was playing. + // + // The new mixer is created BEFORE the current one is destroyed. Reversing + // the order (destroy-then-reopen) quits the whole SDL audio subsystem the + // moment the last device closes: MIX_DestroyMixer() follows up with + // SDL_QuitSubSystem(SDL_INIT_AUDIO), which tears down SDL's device registry + // and renumbers every enumerated device id on the way back up. The id this + // switch was asked to open is then stale and the reopen fails with + // "Invalid audio device instance ID". Keeping the current device open while + // the replacement opens also means a failed switch leaves the current + // output playing instead of going permanently mute. bool AudioManager::recreateMixerOnDevice(SDL_AudioDeviceID targetId) { // Capture the playback state we want to preserve across the switch. @@ -523,7 +533,37 @@ namespace radiantwave::audio ? slots_[activeSlot_].fadeInDuration : 0.0f; - // Tear down the existing mixer (its tracks die with it). + // Open the replacement mixer on the target device first, while the + // current device (and SDL's device registry) is still alive. + MIX_Mixer *newMixer = MIX_CreateMixerDevice(targetId, nullptr); + if (!newMixer) + { + std::string err = SDL_GetError(); + logger::Logger::instance().error( + "Audio", + "Could not open audio output device " + + mapToFriendlyDeviceName(resolvePlaybackDeviceName(targetId)) + + "; keeping the current device (MIX_CreateMixerDevice error: " + + err + ")"); + return false; + } + + MIX_Track *newTracks[2]; + for (int i = 0; i < 2; ++i) + { + newTracks[i] = MIX_CreateTrack(newMixer); + } + if (!newTracks[0] || !newTracks[1]) + { + MIX_DestroyMixer(newMixer); + logger::Logger::instance().error( + "Audio", "Could not create mixer tracks; keeping the current device (SDL error: " + + std::string(SDL_GetError()) + ")"); + return false; + } + + // Tear down the existing mixer (its tracks die with it) and adopt the + // new one. for (int i = 0; i < 2; ++i) { slots_[i].track = nullptr; @@ -539,22 +579,9 @@ namespace radiantwave::audio mixer_ = nullptr; } - mixer_ = MIX_CreateMixerDevice(targetId, nullptr); - if (!mixer_) - { - initialized_ = false; - return false; - } - - slots_[0].track = MIX_CreateTrack(mixer_); - slots_[1].track = MIX_CreateTrack(mixer_); - if (!slots_[0].track || !slots_[1].track) - { - MIX_DestroyMixer(mixer_); - mixer_ = nullptr; - initialized_ = false; - return false; - } + mixer_ = newMixer; + slots_[0].track = newTracks[0]; + slots_[1].track = newTracks[1]; float gain = enabled_ ? (volume_ / 100.0f) : 0.0f; MIX_SetMixerGain(mixer_, gain); diff --git a/src/audio/audio_manager.h b/src/audio/audio_manager.h index 082a693..33ee175 100644 --- a/src/audio/audio_manager.h +++ b/src/audio/audio_manager.h @@ -111,8 +111,11 @@ private: // Recreates the mixer on the given playback device id (0 = system default). // Called with mutex_ held. Preserves playback state: whichever track was // playing on the active slot is reloaded and resumed at full gain, an - // in-progress fade is restarted. Returns false on failure (previous mixer is - // left destroyed but the app remains safe to keep running muted). + // in-progress fade is restarted. The replacement mixer is opened before the + // current one is destroyed so SDL's device ids stay valid (destroy-first + // quits the whole audio subsystem and orphans the id being opened). + // Returns false on failure without touching the current mixer, so the + // existing output keeps playing. bool recreateMixerOnDevice(SDL_AudioDeviceID targetId); config::ConfigManager* configManager_ = nullptr; diff --git a/src/audio_settings_page.cpp b/src/audio_settings_page.cpp index f28aa3a..90f9738 100644 --- a/src/audio_settings_page.cpp +++ b/src/audio_settings_page.cpp @@ -288,7 +288,7 @@ void AudioSettingsPage::applyDeviceSelection() { if (ok) { statusMessage_ = "Switched to " + devices_[currentDeviceIndex_].friendly_name; } else { - statusMessage_ = "Device unavailable - using system default"; + statusMessage_ = "Device unavailable - kept the current output"; } refreshDeviceAndTrackLists(); }