Поделиться через


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

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

Вызов DeviceWatcher.Start , чтобы начать наблюдение за подключенными устройствами, поддерживающими подключения к воспроизведению звука. В этом примере мы запустите диспетчер устройств при загрузке основного элемента управления Сетки в пользовательском интерфейсе. Дополнительные сведения об использовании 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);
        }
    });
}

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