本文介绍如何使用 AudioPlaybackConnection 启用蓝牙连接的远程设备在本地计算机上播放音频。
音频源可以将音频流传输到 Windows 设备,支持如将电脑配置为蓝牙扬声器以便用户可以通过手机播放音频等场景。 该实现使用 OS 中的蓝牙组件来处理传入的音频数据,并在系统上的系统音频终结点上播放它,例如内置的电脑扬声器或有线耳机。 基础蓝牙 A2DP 接收器的启用由负责最终用户方案的应用管理,而不是由系统管理。
AudioPlaybackConnection 类用于启用和禁用来自远程设备的连接以及创建连接,从而允许远程音频播放开始。
添加用户界面
对于本文中的示例,我们将使用以下简单的 XAML UI 来定义 ListView 控件以显示可用的远程设备、用于显示连接状态的 TextBlock ,以及用于启用、禁用和打开连接的三个按钮。
<Grid x:Name="MainGrid" Loaded="MainGrid_Loaded">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<StackPanel Orientation="Horizontal">
<TextBlock Text="Connection state: "/>
<TextBlock x:Name="ConnectionState" Grid.Row="0" Text="Disconnected."/>
</StackPanel>
<ListView x:Name="DeviceListView" ItemsSource="{x:Bind devices}" Grid.Row="1">
<ListView.ItemTemplate>
<DataTemplate x:DataType="enumeration:DeviceInformation">
<StackPanel Orientation="Horizontal" Margin="6">
<SymbolIcon Symbol="Audio" Margin="0,0,12,0"/>
<StackPanel>
<TextBlock Text="{x:Bind Name}" FontWeight="Bold"/>
</StackPanel>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<StackPanel Orientation="Vertical" Grid.Row="2">
<Button x:Name="EnableAudioPlaybackConnectionButton" Content="Enable Audio Playback Connection" Click="EnableAudioPlaybackConnectionButton_Click"/>
<Button x:Name="ReleaseAudioPlaybackConnectionButton" Content="Release Audio Playback Connection" Click="ReleaseAudioPlaybackConnectionButton_Click"/>
<Button x:Name="OpenAudioPlaybackConnectionButtonButton" Content="Open Connection" Click="OpenAudioPlaybackConnectionButtonButton_Click" IsEnabled="False"/>
</StackPanel>
</Grid>
使用 DeviceWatcher 监视远程设备
使用 DeviceWatcher 类可以检测连接的设备。 AudioPlaybackConnection.GetDeviceSelector 方法返回一个字符串,告知设备观察程序要监视的设备类型。 将此字符串传递到 DeviceWatcher 构造函数中。
对于设备观察程序启动时连接的每台设备以及设备观察程序运行时连接的任何设备,都会引发 DeviceWatcher.Added 事件。 如果以前连接的设备断开连接,则会引发 DeviceWatcher.Removed 事件。
调用 DeviceWatcher.Start 开始监视支持音频播放连接的已连接设备。 在此示例中,加载 UI 中的主 网格 控件时,我们将启动设备管理器。 有关使用 DeviceWatcher 的详细信息,请参阅 枚举设备。
private void MainGrid_Loaded(object sender, RoutedEventArgs e)
{
audioPlaybackConnections = new Dictionary<string, AudioPlaybackConnection>();
// Start watching for paired Bluetooth devices.
deviceWatcher = DeviceInformation.CreateWatcher(AudioPlaybackConnection.GetDeviceSelector());
// Register event handlers before starting the watcher.
deviceWatcher.Added += DeviceWatcher_Added;
deviceWatcher.Removed += DeviceWatcher_Removed;
deviceWatcher.Start();
}
在设备观察程序的 “添加 ”事件中,每个发现的设备都由 DeviceInformation 对象表示。 将每个发现的设备添加到绑定到 UI 中的 ListView 控件的可观察集合。
private ObservableCollection<Windows.Devices.Enumeration.DeviceInformation> devices =
new ObservableCollection<Windows.Devices.Enumeration.DeviceInformation>();
private void DeviceWatcher_Added(DeviceWatcher sender, DeviceInformation args)
{
// Collections bound to the UI are updated in the UI thread.
DispatcherQueue.TryEnqueue( () =>
{
this.devices.Add(args);
});
}
启用和释放音频播放连接
在与设备打开连接之前,必须启用连接。 这会通知系统,有一个新的应用程序希望将远程设备的音频在电脑上播放,但在连接打开之前音频不会开始播放,具体将在后续步骤中展示。
在“ 启用音频播放连接 ”按钮的单击处理程序中,获取与 ListView 控件中当前选定的设备关联的设备 ID。 此示例维护已启用的 AudioPlaybackConnection 对象的字典。 此方法首先检查所选设备的字典中是否存在条目。 接下来,该方法尝试通过调用 TryCreateFromId 并传入所选设备 ID,为所选设备创建 AudioPlaybackConnection。
如果成功创建连接,请将新的 AudioPlaybackConnection 对象添加到应用的字典中,为对象的 StateChanged 事件注册处理程序,并调用 StartAsync 以通知系统已启用新连接。
private Dictionary<String, AudioPlaybackConnection> audioPlaybackConnections;
private async void EnableAudioPlaybackConnectionButton_Click(object sender, RoutedEventArgs e)
{
if (!(DeviceListView.SelectedItem is null))
{
var selectedDeviceId = (DeviceListView.SelectedItem as DeviceInformation).Id;
if (!audioPlaybackConnections.ContainsKey(selectedDeviceId))
{
// Create the audio playback connection from the selected device id and add it to the dictionary.
// This will result in allowing incoming connections from the remote device.
var playbackConnection = AudioPlaybackConnection.TryCreateFromId(selectedDeviceId);
if (playbackConnection != null)
{
// The device has an available audio playback connection.
playbackConnection.StateChanged += PlaybackConnection_StateChanged; ;
audioPlaybackConnections.Add(selectedDeviceId, playbackConnection);
await playbackConnection.StartAsync();
OpenAudioPlaybackConnectionButtonButton.IsEnabled = true;
}
}
}
}
打开音频播放连接
在上一步中,创建了音频播放连接,但在通过调用 Open 或 OpenAsync 打开连接之前,声音才会开始播放。 在 “打开音频播放连接” 按钮的单击处理程序中,获取当前选定的设备,并使用 ID 从应用程序的连接字典中检索 AudioPlaybackConnection。 等待对 OpenAsync 的调用并检查返回的 AudioPlaybackConnectionOpenResultStatus 对象的 Status 值,以查看连接是否已成功打开,如果是,请更新连接状态文本框。
private async void OpenAudioPlaybackConnectionButtonButton_Click(object sender, RoutedEventArgs e)
{
var selectedDevice = (DeviceListView.SelectedItem as DeviceInformation).Id;
AudioPlaybackConnection selectedConnection;
if (this.audioPlaybackConnections.TryGetValue(selectedDevice, out selectedConnection))
{
if ((await selectedConnection.OpenAsync()).Status == AudioPlaybackConnectionOpenResultStatus.Success)
{
// Notify that the AudioPlaybackConnection is connected.
ConnectionState.Text = "Connected";
}
else
{
// Notify that the connection attempt did not succeed.
ConnectionState.Text = "Disconnected (attempt failed)";
}
}
}
监视音频播放连接状态
每当连接状态发生更改时,都引发 AudioPlaybackConnection.ConnectionStateChanged 事件。 在此示例中,此事件的处理程序将更新状态文本框。 请记住,在调用 DispatcherQueue.TryEnqueue 中更新 UI,以确保在 UI 线程上进行更新。
private void PlaybackConnection_StateChanged(AudioPlaybackConnection sender, object args)
{
DispatcherQueue.TryEnqueue( () =>
{
if (sender.State == AudioPlaybackConnectionState.Closed)
{
ConnectionState.Text = "Disconnected";
}
else if (sender.State == AudioPlaybackConnectionState.Opened)
{
ConnectionState.Text = "Connected";
}
else
{
ConnectionState.Text = "Unknown";
}
});
}
释放连接并处理已删除的设备
此示例提供“ 发布音频播放连接 ”按钮,允许用户释放音频播放连接。 在此事件的处理程序中,我们获取当前选定的设备,并使用设备的 ID 以便在字典中找到 AudioPlaybackConnection。 调用 Dispose 释放引用并释放任何关联的资源,并从字典中删除连接。
private void ReleaseAudioPlaybackConnectionButton_Click(object sender, RoutedEventArgs e)
{
// Check if an audio playback connection was already created for the selected device Id. If it was then release its reference to deactivate it.
// The underlying transport is deactivated when all references are released.
if (!(DeviceListView.SelectedItem is null))
{
var selectedDeviceId = (DeviceListView.SelectedItem as DeviceInformation).Id;
if (audioPlaybackConnections.ContainsKey(selectedDeviceId))
{
AudioPlaybackConnection connectionToRemove = audioPlaybackConnections[selectedDeviceId];
connectionToRemove.Dispose();
this.audioPlaybackConnections.Remove(selectedDeviceId);
// Notify that the media device has been deactivated.
ConnectionState.Text = "Disconnected";
OpenAudioPlaybackConnectionButtonButton.IsEnabled = false;
}
}
}
应在启用或打开连接时妥善处理设备被删除的情况。 为此,请为设备监视器的 DeviceWatcher.Removed 事件实现处理程序。 首先,已删除设备的 ID 用于从绑定到应用的 ListView 控件的可观察集合中删除设备。 接下来,如果与此设备关联的连接位于应用的字典中, 则调用 Dispose 以释放关联的资源,然后从字典中删除连接。 所有这些操作都是在调用 DispatcherQueue.TryEnqueue 时完成的,以确保在 UI 线程上执行 UI 更新。
private void DeviceWatcher_Removed(DeviceWatcher sender, DeviceInformationUpdate args)
{
// Collections bound to the UI are updated in the UI thread.
DispatcherQueue.TryEnqueue( () =>
{
// Find the device for the given id and remove it from the list.
foreach (DeviceInformation device in this.devices)
{
if (device.Id == args.Id)
{
devices.Remove(device);
break;
}
}
if (audioPlaybackConnections.ContainsKey(args.Id))
{
AudioPlaybackConnection connectionToRemove = audioPlaybackConnections[args.Id];
connectionToRemove.Dispose();
audioPlaybackConnections.Remove(args.Id);
}
});
}
相关主题