Habilitar reprodução de áudio de dispositivos conectados remotamente por Bluetooth

Este artigo mostra como usar AudioPlaybackConnection para permitir que dispositivos remotos conectados a Bluetooth reproduzam áudio no computador local.

A partir do Windows 10, fontes de áudio remotas da versão 2004 podem transmitir áudio para dispositivos Windows, permitindo cenários como configurar um computador para se comportar como um alto-falante Bluetooth e permitir que os usuários ouçam áudio de seu telefone. A implementação usa os componentes Bluetooth no sistema operacional para processar dados de áudio de entrada e reproduzi-los nos pontos de extremidade de áudio do sistema no sistema, como alto-falantes de computador internos ou fones de ouvido com fio. A habilitação do coletor Bluetooth A2DP subjacente é gerenciada por aplicativos, que são responsáveis pelo cenário do usuário final, e não pelo sistema.

A classe AudioPlaybackConnection é usada para habilitar e desabilitar conexões de um dispositivo remoto, bem como para criar a conexão, permitindo que a reprodução remota de áudio comece.

Adicionar uma interface do usuário

Para os exemplos neste artigo, usaremos a interface do usuário XAML simples a seguir, que define o controle ListView para exibir dispositivos remotos disponíveis, um TextBlock para exibir status de conexão e três botões para habilitar, desabilitar e abrir conexões.

<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>

Usar DeviceWatcher para monitorar dispositivos remotos

A classe DeviceWatcher permite detectar dispositivos conectados. O método AudioPlaybackConnection.GetDeviceSelector retorna uma cadeia de caracteres que informa ao observador de dispositivos para quais tipos de dispositivos watch. Passe essa cadeia de caracteres para o construtor DeviceWatcher .

O evento DeviceWatcher.Added é gerado para cada dispositivo conectado quando o observador de dispositivo é iniciado, bem como para qualquer dispositivo conectado enquanto o observador de dispositivo está em execução. O evento DeviceWatcher.Removed será gerado se um dispositivo conectado anteriormente se desconectar.

Chame DeviceWatcher.Start para começar a observar dispositivos conectados que dão suporte a conexões de reprodução de áudio. Neste exemplo, iniciaremos o gerenciador de dispositivos quando o controle main Grid na interface do usuário for carregado. Para obter mais informações sobre como usar DeviceWatcher, consulte Enumerar Dispositivos.

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();
}

No evento Added do observador de dispositivos, cada dispositivo descoberto é representado por um objeto DeviceInformation . Adicione cada dispositivo descoberto a uma coleção observável associada ao controle ListView na interface do usuário.

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);
    });
}

Habilitar e liberar conexões de reprodução de áudio

Antes de abrir uma conexão com um dispositivo, a conexão deve ser habilitada. Isso informa ao sistema que há um novo aplicativo que deseja que o áudio do dispositivo remoto seja reproduzido no computador, mas o áudio não começa a ser reproduzido até que a conexão seja aberta, o que é mostrado em uma etapa posterior.

No manipulador de cliques do botão Habilitar Conexão de Reprodução de Áudio , obtenha a ID do dispositivo associada ao dispositivo selecionado no momento no controle ListView . Este exemplo mantém um dicionário de objetos AudioPlaybackConnection que foram habilitados. Esse método primeiro verifica se já há uma entrada no dicionário para o dispositivo selecionado. Em seguida, o método tenta criar um AudioPlaybackConnection para o dispositivo selecionado chamando TryCreateFromId e passando a ID do dispositivo selecionado.

Se a conexão for criada com êxito, adicione o novo objeto AudioPlaybackConnection ao dicionário do aplicativo, registre um manipulador para o evento StateChanged do objeto e chameStartAsync para notificar o sistema de que a nova conexão está habilitada.

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;
            }
        }
    }
}

Abrir a conexão de reprodução de áudio

Na etapa anterior, uma conexão de reprodução de áudio foi criada, mas o som não começa a ser reproduzido até que a conexão seja aberta chamando Open ou OpenAsync. No manipulador de cliques abrir conexão de reprodução de áudio , obtenha o dispositivo selecionado no momento e use a ID para recuperar o AudioPlaybackConnection do dicionário de conexões do aplicativo. Aguarde uma chamada para OpenAsync e marcar o valor Status do objeto AudioPlaybackConnectionOpenResultStatus retornado para ver se a conexão foi aberta com êxito e, em caso afirmativo, atualize a caixa de texto estado da conexão.

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)";
        }
    }
}

Monitorar o estado da conexão de reprodução de áudio

O evento AudioPlaybackConnection.ConnectionStateChanged é gerado sempre que o estado da conexão é alterado. Neste exemplo, o manipulador desse evento atualiza a caixa de texto status. Lembre-se de atualizar a interface do usuário dentro de uma chamada para Dispatcher.RunAsync para garantir que a atualização seja feita no thread da interface do usuário.

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";
        }
    });
}

Liberar conexões e manipular dispositivos removidos

Este exemplo fornece um botão Liberar Conexão de Reprodução de Áudio para permitir que o usuário libere uma conexão de reprodução de áudio. No manipulador para esse evento, obtemos o dispositivo selecionado no momento e usamos a ID do dispositivo para pesquisar o AudioPlaybackConnection no dicionário. Chame Dispose para liberar a referência e liberar todos os recursos associados e remover a conexão do dicionário.


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;
        }
    }
}

Você deve lidar com o caso em que um dispositivo é removido enquanto uma conexão está habilitada ou aberta. Para fazer isso, implemente um manipulador para o evento DeviceWatcher.Removed do observador de dispositivos. Primeiro, a ID do dispositivo removido é usada para remover o dispositivo da coleção observável associada ao controle ListView do aplicativo. Em seguida, se uma conexão associada a esse dispositivo estiver no dicionário do aplicativo, Dispose será chamado para liberar os recursos associados e, em seguida, a conexão será removida do dicionário. Tudo isso é feito em uma chamada para Dispatcher.RunAsync para garantir que as atualizações da interface do usuário sejam executadas no thread da interface do usuário.

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);
        }
    });
}

Reprodução de mídia