After spending far too many hours trying to find the root cause of why this was happening, I gave up and wrote a shell script.
First, we need a way to find and change audio devices from the command line. Luckily, developer Deweller made a small utility to do exactly that: https://github.com/deweller/switchaudio-osx. It can be installed via Homebrew or MacPorts if you have either installed; alternately, download the Intel or Apple Silicon binary from MacPorts directly, and copy switchaudiosource
into /usr/local/bin
; it should work as-is with no dependencies.
I wrote the following script. You'll want to edit the names of speakerA
and SpeakerB
.
#!/bin/sh
while sleep 2
do
speakerA="SRS-XB12"
speakerB="Built-in Output"
switchAndRewind() {
echo $1 > /tmp/audioSwitcherSavedState.txt
if [ "$(switchaudiosource -c)" != "$2" ]
then
for device in {output,system}
do
SwitchAudioSource -t "$device" -s "$2"
done
osascript -e 'if application "QuickTime Player" is not running then return' -e 'tell application "QuickTime Player"' -e 'repeat with i from 1 to count of every document' -e 'if document i is playing and duration of document i > 0 then set current time of document i to (current time of document i) - 2' -e 'end repeat' -e 'end tell'
fi
}
if ! cat /tmp/audioSwitcherSavedState.txt
then
switchaudiosource -a | grep $speakerA && echo "connected" > /tmp/audioSwitcherSavedState.txt || echo "disconnected" > /tmp/audioSwitcherSavedState.txt
elif [ "$(cat /tmp/audioSwitcherSavedState.txt)" == "disconnected" ] && switchaudiosource -a | grep $speakerA
then
switchAndRewind "connected" "$speakerA"
elif [ "$(cat /tmp/audioSwitcherSavedState.txt)" == "connected" ] && ! switchaudiosource -a | grep $speakerA
then
switchAndRewind "disconnected" "$speakerB"
fi
done
This script does the following every 2 seconds:
- If it doesn't already exist, create a temporary text file which records whether
speakerA
is connected to the Mac.
- If
speakerA
was previously disconnected, and is now connected, switch to speakerA
.
- If
speakerA
was previously connected, and is now disconnected, switch to SpeakerB
(my internal speakers).
- In either of the above two cases, we also update the temporary file, so the code will only run once per connection or disconnection. And, if Quicktime is currently playing any audio, we'll rewind the file a few seconds so I don't miss anything as devices are switching.
In order for this script to be useful, I needed it to start automatically, so I made a LaunchAgent. I decided I wanted this to live as a single, self-contained file, so I put all the code on one (unreadable) line and dumped it directly into my LaunchAgent plist. Note that because LaunchAgents need to know the full path to most binaries, this LaunchAgent assumes that the switchaudiosource
binary lives in /usr/local/bin/
.
<!--
Changes audio output device to "SRS-XB12" when a device by that name is connected; changes audio output device back to "Built-in Output" when a device named "SRS-XB12" is disconnected. Edit these names to whatever is appropriate for your sound setup.
Requires SwitchAudioSource to be present in /usr/local/bin/
-->
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>wowfunhappy.audio-switcher</string>
<key>ProgramArguments</key>
<array>
<string>/bin/sh</string>
<string>-c</string>
<string>while sleep 2; do speakerA="SRS-XB12" ; speakerB="Built-in Output" ; switchAndRewind() { echo $1 > /tmp/audioSwitcherSavedState.txt ; if [ "$(/usr/local/bin/switchaudiosource -c)" != "$2" ] ; then for device in {output,system} ; do /usr/local/bin/SwitchAudioSource -t "$device" -s "$2" ; done ; osascript -e 'if application "QuickTime Player" is not running then return' -e 'tell application "QuickTime Player"' -e 'repeat with i from 1 to count of every document' -e 'if document i is playing and duration of document i > 0 then set current time of document i to (current time of document i) - 5' -e 'end repeat' -e 'end tell' ; fi ; } ; if ! cat /tmp/audioSwitcherSavedState.txt ; then /usr/local/bin/switchaudiosource -a | grep $speakerA && echo "connected" > /tmp/audioSwitcherSavedState.txt || echo "disconnected" > /tmp/audioSwitcherSavedState.txt ; elif [ "$(cat /tmp/audioSwitcherSavedState.txt)" == "disconnected" ] && /usr/local/bin/switchaudiosource -a | grep $speakerA ; then switchAndRewind "connected" "$speakerA" ; elif [ "$(cat /tmp/audioSwitcherSavedState.txt)" == "connected" ] && ! /usr/local/bin/switchaudiosource -a | grep $speakerA ; then switchAndRewind "disconnected" "$speakerB" ; fi ; done</string>
</array>
<key>LimitLoadToSessionType</key>
<array>
<string>Aqua</string>
</array>
<key>ProcessType</key>
<string>Background</string>
<key>RunAtLoad</key>
<true/>
<key>KeepAlive</key>
<true/>
</dict>
</plist>
Save the above as ~/Library/LaunchAgents/wowfunhappy.audio-switcher.plist
and log out and back in, and audio should switch automatically.