共用方式為


啟用來自遠端藍牙連線裝置的音訊播放

本文示範如何使用 AudioPlaybackConnection 讓藍牙連線的遠端裝置能夠播放本機上的音訊。

從 Windows 10 版本 2004 開始,遠端音訊來源可以將音訊串流至 Windows 裝置,讓電腦能夠像藍牙喇叭一樣運作,以及讓使用者從手機聽到音訊等案例。 實作會使用作業系統中的藍牙元件來處理傳入的音訊資料,並在系統上的系統音訊端點上播放,例如內建電腦喇叭或有線耳機。 基礎藍牙 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. 
    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();
}

在裝置監看員的 Added 事件中,每個探索到的裝置都會以 DeviceInformation 物件表示。 將每個探索到的裝置新增至繫結至 UI 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 控制項中目前選取的裝置關聯的裝置 ID。 此範例維護已啟用的 AudioPlaybackConnection 物件的字典。 此方法會先檢查所選裝置的字典中是否有項目。 接下來,方法會呼叫 TryCreateFromId 並傳入選取的裝置識別碼,嘗試為選取的裝置建立 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 (!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;
            }
        }
    }
}

開啟音訊播放連線

在上一個步驟中,已建立音訊播放連線,但在呼叫 OpenOpenAsync 開啟連線之前,音效才會開始播放。 在開啟音訊播放連線按鈕按一下處理常式,取得目前選取的裝置並使用 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 事件。 在此範例中,此事件的處理常式會更新狀態文字方塊。 請記得在呼叫 Dispatcher.RunAsync 內更新 UI,以確定已在 UI 執行緒上進行更新。

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 內完成,以確保 UI 更新是在 UI 執行緒上執行。

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

媒體播放