Skip to content

Instantly share code, notes, and snippets.

@richardszalay
Last active February 10, 2016 03:01
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save richardszalay/6260553 to your computer and use it in GitHub Desktop.
Save richardszalay/6260553 to your computer and use it in GitHub Desktop.
Works around a known platform issue whereby disconnecting a Bluetooth headset causes the audio to play through the loud speaker, even if the track is currently paused. Includes support for GDR2.
public class AudioPlayer
{
// Note "static" !
static readonly PlatformBluetoothIssueWorkaround bluetoothIssueWorkaround = new PlatformBluetoothIssueWorkaround();
protected override void OnPlayStateChanged(BackgroundAudioPlayer player, AudioTrack track, PlayState playState)
{
if (bluetoothIssueWorkaround.HandlePlayStateChanged(player, playState))
{
NotifyComplete();
return;
}
// Process as normal
}
protected override void OnUserAction(BackgroundAudioPlayer player, AudioTrack track, UserAction action, object param)
{
bluetoothIssueWorkaround.HandleUserAction(player, action);
// Process as normal
}
}
/*
Copyright (C) 2013 Richard Szalay
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
IN THE SOFTWARE.
*/
namespace RichardSzalay.Phone.BackgroundAudio
{
using System;
using System.Collections.Generic;
using Microsoft.Phone.BackgroundAudio;
/// <summary>
/// Works around a known platform issue whereby disconnecting a Bluetooth headset causes the audio to play through the loud speaker, even if the track is currently paused
/// </summary>
/// <see cref="http://social.msdn.microsoft.com/Forums/wpapps/en-US/48d29436-6b9e-4a8f-8322-677b08c0443e/disconnect-bluetooth-and-paused-audio-starts-to-play"/>
/// <see cref="http://social.msdn.microsoft.com/Forums/wpapps/en-US/74c80efe-5af3-473e-baa0-87330c9f6160/disconnecting-from-bluetooth-when-music-is-paused-triggers-a-play-command"/>
/// <see cref="http://stackoverflow.com/questions/13953787/control-wp7-and-wp8-app-with-bluetooth-headset"/>
public class PlatformBluetoothIssueWorkaround
{
readonly UserAction[] KnownBluetoothIssueUserActionSequence = new[] { UserAction.Pause, UserAction.Seek, UserAction.Play };
readonly List<UserAction> recordedBluetoothIssueActions = new List<UserAction>();
readonly PlayState[] KnownBluetoothIssuePlayStateSequence = new[] { PlayState.Paused, PlayState.TrackReady };
readonly List<PlayState> recordedBluetoothIssueStates = new List<PlayState>();
double bluetoothSpeakerRecordedVolume = 0;
/// <summary>
/// Gets whether a Bluetooth disconnect issue is currently detected
/// </summary>
public bool IssueDetected { get; private set; }
/// <summary>To be called from AudioPlayerAgent.OnUserAction</summary>
/// <returns>true if a Bluetooth disconnect issue was detected; false otherwise</returns>
public bool HandleUserAction(BackgroundAudioPlayer player, UserAction action)
{
if (ShouldHandlePlayStateSequence)
{
return false;
}
IssueDetected = (HandleSequenceEntry(KnownBluetoothIssueUserActionSequence, recordedBluetoothIssueActions, action, player));
if (IssueDetected)
{
RecordPlaybackVolume(player);
}
return IssueDetected;
}
/// <summary>To be called from AudioPlayerAgent.OnPlayStateChanged</summary>
/// <returns>true if a Bluetooth disconnect issue was recovered from; false otherwise</returns>
public bool HandlePlayStateChanged(BackgroundAudioPlayer player, PlayState playState)
{
if (ShouldHandlePlayStateSequence &&
HandleSequenceEntry(KnownBluetoothIssuePlayStateSequence, recordedBluetoothIssueStates, playState, player))
{
return true;
}
if (IssueDetected)
{
IssueDetected = false;
player.Pause();
RestorePlaybackVolume(player);
return true;
}
return false;
}
private bool HandleSequenceEntry<T>(T[] matchSequence, List<T> recordedSequence, T newValue, BackgroundAudioPlayer player)
{
if (matchSequence[recordedSequence.Count].Equals(newValue))
{
recordedSequence.Add(newValue);
if (recordedSequence.Count == matchSequence.Length)
{
recordedSequence.Clear();
return true;
}
}
else
{
recordedSequence.Clear();
if (matchSequence[0].Equals(newValue))
{
recordedSequence.Add(newValue);
}
}
return false;
}
void RecordPlaybackVolume(BackgroundAudioPlayer player)
{
bluetoothSpeakerRecordedVolume = player.Volume;
player.Volume = 0D;
}
void RestorePlaybackVolume(BackgroundAudioPlayer player)
{
player.Volume = bluetoothSpeakerRecordedVolume;
}
// When true, the PlayState sequence will be used. Otherwise, the UserAction sequence will be used.
private bool ShouldHandlePlayStateSequence
{
get
{
return IsGeneralDistributionRelease2(Environment.OSVersion.Version);
}
}
static readonly Version MinGdr2Version = new Version(8, 0, 10327, 77);
/// <summary>Determines whether the supplied OS Version represents WP8 GDR2 or greater</summary>
public static bool IsGeneralDistributionRelease2(Version osVersion)
{
return osVersion >= MinGdr2Version;
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment