Update: This post has been updated to include support for GDR2.

There's an issue on Windows Phone (7 and 8) whereby disconnecting a Bluetooth headset will cause the current track to play through the loud speaker, even if the track was paused. Not many people talk about the issue because the number of audio apps represent a pretty small percentage of the marketplace and, sadly, those who have worked around it in the past attempt to use it as a marketing advantage rather than share the knowledge on the problem.

For those who don't care how I solved it, feel free to jump directly to the MIT licensed Gist

Solving the problem requires some trickery, since there are no APIs to notify apps of the connected state of Bluetooth headsets. Even so, identifying the scenario is quite basic, with the AudioPlayerAgent.OnUserAction method being called three times in quick succession: Pause, Seek, Play. The only gotcha here is that each call is made on a different instance of the AudioPlayer, so any state needs to be static.

It turns out identifying the issue is only half the problem. I actually tried several methods of stopping the play from occurring from directly within OnUserAction, including mapping the Play action to a Pause and even ignoring the action completely, but none of my attempts prevented the audio from playing.

The solution, in the end, was to detect the issue in OnUserAction and then correct it in OnPlayerStateChanged by immediately calling Pause. This caused the audio to be heard very briefly, which could be worked around by temporarily setting the volume to 0 when the issue was detected.

The implementation could be improved in the following ways, though whether it's worth doing I'll leave to you:

  • The detection sequence could, in theory, be accidentally reproduced by user action and thus detection should be given a time limit. However, since I couldn't reproduce the sequence through seeking, I decided not to include it.
  • The small, ~50ms, gap where the volume is set to 0 could be worked around by capturing the current position on detection and setting it after the pause in the workaround

Enter GDR2

When Microsoft released GDR2 (General Distribution Release 2, the second major update to WP8), it came with claims that they had fixed the Bluetooth issue. Unfortunately, all it did was break my workaround. The reason is that, now, the user actions aren't sent; only the PlayStateChanged sequence of Paused, TrackReady. Unfortunately, the sample code that comes with a new "audio agent" (which remains in the Podcaster source, as I'm sure it does many other applications) automatically starts playing on TrackReady. I've updated the Gist, as well as the code below, to cater for this alternative sequence of events.

Below is the (MIT licensed) source for the workaround, as well as a bare-bones AudioPlayer implementation that uses it. I recommend getting the code from the GitHub Gist as I might forget this post if I make any improvements.

Example usage: