diff --git a/BGMApp/BGMApp/BGMAppDelegate.mm b/BGMApp/BGMApp/BGMAppDelegate.mm index c2bccba6..369d9921 100644 --- a/BGMApp/BGMApp/BGMAppDelegate.mm +++ b/BGMApp/BGMApp/BGMAppDelegate.mm @@ -369,12 +369,18 @@ - (void) initVolumesMenuSection { - (void) applicationWillTerminate:(NSNotification*)aNotification { #pragma unused (aNotification) - + DebugMsg("BGMAppDelegate::applicationWillTerminate"); + // Deactivate control sync and playthrough before changing the default device back. This + // prevents the default device change from triggering volume sync listeners that could leave + // the output device at the wrong volume. See + // https://github.com/kyleneideck/BackgroundMusic/issues/841 + [audioDevices prepareForTermination]; + // Change the user's default output device back. NSError* error = [audioDevices unsetBGMDeviceAsOSDefault]; - + if (error) { [self showSetDeviceAsDefaultError:error message:@"Failed to reset your system's audio output device." diff --git a/BGMApp/BGMApp/BGMAudioDeviceManager.h b/BGMApp/BGMApp/BGMAudioDeviceManager.h index 382bddd0..c01ce8cb 100644 --- a/BGMApp/BGMApp/BGMAudioDeviceManager.h +++ b/BGMApp/BGMApp/BGMAudioDeviceManager.h @@ -63,6 +63,11 @@ static const int kBGMErrorCode_ReturningEarly = 2; // Replace BGMDevice as the default device with the output device - (NSError* __nullable) unsetBGMDeviceAsOSDefault; +// Prepare for app termination by deactivating control sync and playthrough before the default +// device is changed back. This prevents stale volume state from being left on the output device. +// Must be called before unsetBGMDeviceAsOSDefault. +- (void) prepareForTermination; + #ifdef __cplusplus // The virtual device published by BGMDriver. - (BGMBackgroundMusicDevice) bgmDevice; diff --git a/BGMApp/BGMApp/BGMAudioDeviceManager.mm b/BGMApp/BGMApp/BGMAudioDeviceManager.mm index 7eb62879..9ccc5581 100644 --- a/BGMApp/BGMApp/BGMAudioDeviceManager.mm +++ b/BGMApp/BGMApp/BGMAudioDeviceManager.mm @@ -128,6 +128,33 @@ - (NSError* __nullable) setBGMDeviceAsOSDefault { return nil; } +- (void) prepareForTermination { + DebugMsg("BGMAudioDeviceManager::prepareForTermination: Deactivating control sync and " + "playthrough before termination"); + + @try { + [stateLock lock]; + + // Deactivate control sync BEFORE changing the default device. This removes the volume/mute + // listeners so that the default device change doesn't trigger any stale volume sync that + // could leave the output device at the wrong volume. + // See https://github.com/kyleneideck/BackgroundMusic/issues/841 + BGMLogAndSwallowExceptions("BGMAudioDeviceManager::prepareForTermination", [&] { + deviceControlSync.Deactivate(); + }); + + // Stop playthrough so we don't try to pass audio during shutdown. + BGMLogAndSwallowExceptions("BGMAudioDeviceManager::prepareForTermination", [&] { + playThrough.Deactivate(); + }); + BGMLogAndSwallowExceptions("BGMAudioDeviceManager::prepareForTermination", [&] { + playThrough_UISounds.Deactivate(); + }); + } @finally { + [stateLock unlock]; + } +} + - (NSError* __nullable) unsetBGMDeviceAsOSDefault { // Copy the devices so we can call the HAL without holding stateLock. See startPlayThroughSync. BGMBackgroundMusicDevice* bgmDeviceCopy; diff --git a/BGMApp/BGMApp/BGMTermination.mm b/BGMApp/BGMApp/BGMTermination.mm index 75afa37a..9b1ef759 100644 --- a/BGMApp/BGMApp/BGMTermination.mm +++ b/BGMApp/BGMApp/BGMTermination.mm @@ -122,6 +122,10 @@ // it's better for things to work even if BGMXPCHelper isn't installed. if(sAudioDevices) { + // Deactivate control sync and playthrough before changing the default device back. + // This prevents the default device change from triggering volume sync that could leave + // the output device at the wrong volume. + [sAudioDevices prepareForTermination]; [sAudioDevices unsetBGMDeviceAsOSDefault]; } } diff --git a/BGMApp/BGMApp/_uninstall-non-interactive.sh b/BGMApp/BGMApp/_uninstall-non-interactive.sh index 46dad07c..c905488d 100755 --- a/BGMApp/BGMApp/_uninstall-non-interactive.sh +++ b/BGMApp/BGMApp/_uninstall-non-interactive.sh @@ -133,6 +133,29 @@ osascript -e 'tell application id "com.apple.finder" || rm -rf "${trash_dir}" \ || true +# Clean up BGMDevice entries from macOS audio preferences to prevent persistent low volume +# after uninstall. See https://github.com/kyleneideck/BackgroundMusic/issues/841 +echo "Cleaning up audio device preferences." +for plist_dir in "$HOME/Library/Preferences" "/Library/Preferences/Audio"; do + for plist in "com.apple.audio.DeviceSettings.plist" "com.apple.audio.SystemSettings.plist"; do + plist_path="${plist_dir}/${plist}" + if [ -f "${plist_path}" ]; then + # Remove entries keyed by BGMDevice UIDs + for uid in "BGMDevice" "BGMDevice_UISounds"; do + defaults delete "${plist_path}" "${uid}" &>/dev/null || true + done + fi + done +done +# Also clean up ByHost audio preferences +for byhost_plist in "$HOME/Library/Preferences/ByHost"/com.apple.audio.*.plist; do + if [ -f "${byhost_plist}" ]; then + for uid in "BGMDevice" "BGMDevice_UISounds"; do + defaults delete "${byhost_plist}" "${uid}" &>/dev/null || true + done + fi +done + echo "Restarting Core Audio." # Wait a little because moving files to the trash plays a short sound. sleep 2