1

For some reason, when I connect my Sony Bluetooth speaker to my Mac, my Mac usually does not automatically set this Bluetooth speaker as my audio output device. Audio continues playing from my internal speakers until I manually click the audio icon in the menu bar and set "SRS-XB12" as my output device.

Furthermore, when I disconnect this speaker, my Mac sets the audio output device to my USB Microphone, rather than back to my internal speakers.

When I connect my Bluetooth speaker, I want my Mac to automatically start playing audio from that speaker. When I disconnect my speaker, I want it to play audio from the internal speakers. How can I make that happen?

1 Answer 1

0

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 &amp;&amp; echo "connected" > /tmp/audioSwitcherSavedState.txt || echo "disconnected" > /tmp/audioSwitcherSavedState.txt ; elif [ "$(cat /tmp/audioSwitcherSavedState.txt)" == "disconnected" ] &amp;&amp; /usr/local/bin/switchaudiosource -a | grep $speakerA ; then switchAndRewind "connected" "$speakerA" ; elif [ "$(cat /tmp/audioSwitcherSavedState.txt)" == "connected" ] &amp;&amp; ! /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.

11
  • For such a sort time interval, would it not be more efficient to keep the same script running and just sleep between iterations?  After all, the interval doesn't need to be precise. And shell startup isn't free (especially for a heavyweight shell such as bash), and will run all the startup scripts &c every time.
    – gidds
    Nov 7, 2021 at 23:21
  • @gidds Good point, I will look at that! I should also probably switch to sh now that you mention it. Nov 8, 2021 at 2:47
  • @gidds Yes, that actually is noticeably better—the audio switches more quickly even after I moved it from 1 down to 2 seconds! Thank you! Nov 8, 2021 at 4:06
  • That's grossly horrifying! You might consider some more elegant solutions such as creating a background app that registers an audio object property listener, maybe something modeled off of this. Or at the very least set the launchagent to only run on bluetooth connection changes, which you can do hackily by watchpath of com.apple.bluetooth.plist or more elegantly by using undocument launchd launchevents on iokit bluetooth
    – 1110101001
    Nov 10, 2021 at 21:15
  • @1110101001 Thanks for the automute link, I'll take a look. I absolutely agree my current solution is gross, but it's where I arrived after many months of frustration; it works reliably and and uses < 0.01% CPU. Watching com.apple.bluetooth.plist doesn't work consistently, and while I did try several iokit Bluetooth launchevents, nothing seemed to fire at the right time. Nov 11, 2021 at 21:27

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .