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