Compartilhar via


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

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

A partir do Windows 10, as 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 o áudio de seus telefones. 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, como alto-falantes integrados do PC 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 seguinte interface do usuário XAML simples que define o controle ListView para exibir dispositivos remotos disponíveis, um TextBlock para exibir o status da 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 o 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 do dispositivo quais tipos de dispositivos observar. Passe essa cadeia de caracteres para o construtor DeviceWatcher .

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

Chame DeviceWatcher.Start para começar a observar os dispositivos conectados que dão suporte a conexões de reprodução de áudio. Neste exemplo, iniciaremos o gerenciador de dispositivos quando o controle Grid principal na interface do usuário for carregado. Para obter mais informações sobre como usar o 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 do dispositivo, 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);
    });
}

Ativar 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 PC, 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á existe 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;
            }
        }
    }
}

Abra 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 do botão 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 verifique 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 do 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 de 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";
        }
    });
}

Libere as conexões e manuseie os 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 desse 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á ativada ou aberta. Para fazer isso, implemente um manipulador para o evento DeviceWatcher.Removed do observador do dispositivo. 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