Включение воспроизведения звука с устройств, подключенных удаленно по Bluetooth

В этой статье показано, как использовать AudioPlaybackConnection , чтобы разрешить удаленным устройствам, подключенным по Bluetooth, воспроизводить звук на локальном компьютере.

Начиная с Windows 10, удаленные источники звука версии 2004 могут передавать звук на устройства Windows, что позволяет выполнять такие сценарии, как настройка компьютера для работы как динамик Bluetooth и предоставление пользователям возможности слышать звук со своего телефона. Реализация использует компоненты Bluetooth в ОПЕРАЦИОННОй системе для обработки входящих звуковых данных и воспроизведения их на конечных точках аудио системы в системе, таких как встроенные динамики пк или проводные наушники. Включение базового приемника Bluetooth A2DP управляется приложениями, которые отвечают за сценарий конечного пользователя, а не системой.

Класс AudioPlaybackConnection используется для включения и отключения подключений с удаленного устройства, а также для создания подключения, что позволяет начать удаленное воспроизведение звука.

Добавление пользовательского интерфейса

В примерах в этой статье мы будем использовать следующий простой пользовательский интерфейс XAML, который определяет элемент управления 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 возвращает строку, которая сообщает наблюдателю за устройствами, для каких типов устройств следует watch. Передайте эту строку в конструктор DeviceWatcher .

Событие DeviceWatcher.Added возникает для каждого устройства, подключенного при запуске наблюдателя за устройствами, а также для любого устройства, подключенного во время работы наблюдателя за устройствами. Событие DeviceWatcher.Removed возникает, если ранее подключенное устройство отключается.

Вызовите DeviceWatcher.Start , чтобы начать наблюдение за подключенными устройствами, поддерживающими подключения воспроизведения звука. В этом примере мы запустим диспетчер устройств при загрузке элемента управления "Сетка main" в пользовательском интерфейсе. Дополнительные сведения об использовании DeviceWatcher см. в разделе Перечисление устройств.

private void MainGrid_Loaded(object sender, RoutedEventArgs e)
{
    audioPlaybackConnections = new Dictionary<string, AudioPlaybackConnection>();

    // Start watching for paired Bluetooth devices. 
    this.deviceWatcher = DeviceInformation.CreateWatcher(AudioPlaybackConnection.GetDeviceSelector());

    // Register event handlers before starting the watcher. 
    this.deviceWatcher.Added += this.DeviceWatcher_Added;
    this.deviceWatcher.Removed += this.DeviceWatcher_Removed;

    this.deviceWatcher.Start();
}

В событии Добавлено наблюдателя за устройствами каждое обнаруженное устройство представлено объектом DeviceInformation . Добавьте каждое обнаруженное устройство в наблюдаемую коллекцию, привязанную к элементу управления ListView в пользовательском интерфейсе.

private ObservableCollection<Windows.Devices.Enumeration.DeviceInformation> devices =
    new ObservableCollection<Windows.Devices.Enumeration.DeviceInformation>();

private async void DeviceWatcher_Added(DeviceWatcher sender, DeviceInformation deviceInfo)
{
    // Collections bound to the UI are updated in the UI thread. 
    await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, async () =>
    {
        this.devices.Add(deviceInfo);
    });
}

Включение и освобождение подключений воспроизведения звука

Перед открытием подключения к устройству его необходимо включить. Это сообщает системе о том, что существует новое приложение, которое хочет воспроизводить звук с удаленного устройства на компьютере, но звук не начнет воспроизводиться до тех пор, пока подключение не будет открыто, как показано на следующем шаге.

В обработчике нажатия кнопки Включить подключение к воспроизведению звука получите идентификатор устройства, связанный с текущим выбранным устройством в элементе управления ListView . В этом примере ведется словарь включенных объектов AudioPlaybackConnection . Этот метод сначала проверяет наличие записи в словаре для выбранного устройства. Затем метод пытается создать AudioPlaybackConnection для выбранного устройства путем вызова TryCreateFromId и передачи выбранного идентификатора устройства.

Если подключение успешно создано, добавьте новый объект 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 (!this.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 += this.AudioPlaybackConnection_ConnectionStateChanged;
                this.audioPlaybackConnections.Add(selectedDeviceId, playbackConnection);
                await playbackConnection.StartAsync();
                OpenAudioPlaybackConnectionButtonButton.IsEnabled = true;
            }
        }
    }
}

Открытие подключения к воспроизведению звука

На предыдущем шаге было создано подключение к воспроизведению звука, но звук не начинает воспроизводиться, пока подключение не будет открыто путем вызова Open или OpenAsync. В обработчике нажатия кнопки Открыть подключение воспроизведения звука получите текущее выбранное устройство и используйте идентификатор, чтобы получить AudioPlaybackConnection из словаря подключений приложения. Дождитесь вызова OpenAsync и проверка значение состояния возвращенного объекта AudioPlaybackConnectionOpenResultStatus, чтобы узнать, было ли подключение успешно открыто, и, если да, обновите текстовое поле состояния подключения.

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 возникает при каждом изменении состояния подключения. В этом примере обработчик этого события обновляет текстовое поле состояния. Не забудьте обновить пользовательский интерфейс внутри вызова Dispatcher.RunAsync , чтобы убедиться, что обновление выполняется в потоке пользовательского интерфейса.

private async void AudioPlaybackConnection_ConnectionStateChanged(AudioPlaybackConnection sender, object args)
{
    await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
    {
        if (sender.State == AudioPlaybackConnectionState.Closed)
        {
            ConnectionState.Text = "Disconnected";
        }
        else if (sender.State == AudioPlaybackConnectionState.Opened)
        {
            ConnectionState.Text = "Connected";
        }
        else
        {
            ConnectionState.Text = "Unknown";
        }
    });
}

Освобождение подключений и обработка удаленных устройств

В этом примере предоставляется кнопка "Освободить подключение к воспроизведению звука ", позволяющая пользователю освободить подключение к воспроизведению звука. В обработчике для этого события мы получаем текущее выбранное устройство и используем идентификатор устройства для поиска 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 наблюдателя за устройством . Во-первых, идентификатор удаленного устройства используется для удаления устройства из наблюдаемой коллекции, привязанной к элементу управления ListView приложения. Затем, если подключение, связанное с этим устройством, находится в словаре приложения, вызывается Dispose , чтобы освободить связанные ресурсы, а затем подключение удаляется из словаря. Все это выполняется в вызове Dispatcher.RunAsync , чтобы убедиться, что обновления пользовательского интерфейса выполняются в потоке пользовательского интерфейса.

private async void DeviceWatcher_Removed(DeviceWatcher sender, DeviceInformationUpdate deviceInfoUpdate)
{
    // Collections bound to the UI are updated in the UI thread. 
    await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
    {
        // Find the device for the given id and remove it from the list. 
        foreach (DeviceInformation device in this.devices)
        {
            if (device.Id == deviceInfoUpdate.Id)
            {
                this.devices.Remove(device);
                break;
            }
        }

        if (audioPlaybackConnections.ContainsKey(deviceInfoUpdate.Id))
        {
            AudioPlaybackConnection connectionToRemove = audioPlaybackConnections[deviceInfoUpdate.Id];
            connectionToRemove.Dispose();
            this.audioPlaybackConnections.Remove(deviceInfoUpdate.Id);
        }
    });
}

Воспроизведение мультимедиа