Bluetooth 接続のリモート デバイスからオーディオ再生を有効にする

この記事では、AudioPlaybackConnection を使用して、Bluetooth 接続されたリモート デバイスでローカル マシンのオーディオを再生できるようにする方法について説明します。

Windows 10 以降では、バージョン 2004 のリモート オーディオ ソースはオーディオを Windows デバイスにストリーム配信できます。これにより、Bluetooth スピーカーのように動作するように PC を構成し、ユーザーが電話でオーディオを聞けるようにするなどのシナリオが可能になります。 この実装では、OS の Bluetooth コンポーネントを使用して受信オーディオ データを処理し、内蔵 PC スピーカーや有線ヘッドホンなどのシステムのオーディオ エンドポイントで再生します。 基になる Bluetooth A2DP シンクの有効化は、システムによってではなく、エンド ユーザーのシナリオに対応するアプリによって管理されます。

AudioPlaybackConnection クラスは、リモート デバイスからの接続を有効または無効にしたり、接続を作成したりするために使用されます。これによって、リモート オーディオの再生を開始できます。

ユーザー インターフェイスの追加

この記事の例では、以下の単純な XAML UI を使用します。そこでは、使用可能なリモート デバイスを表示する ListView コントロール、接続状態を表示する TextBlock、および接続の有効化、無効化、およびオープンのための 3 つのボタンを定義します。

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

オーディオ再生接続の有効化と解放

デバイスとの接続を開く前に、接続を有効にする必要があります。 これによって、リモート デバイスのオーディオを PC で再生しようとする新しいアプリケーションがあることがシステムに通知されますが、接続が開かれるまでオーディオの再生は開始されません。接続を開く操作については、後の手順で説明します。

[Enable Audio Playback Connection] (オーディオ再生接続を有効にする) ボタンのクリック ハンドラーで、ListView コントロールで現在選択されているデバイスに関連付けられているデバイス ID を取得します。 この例では、有効になっている AudioPlaybackConnection オブジェクトのディクショナリを保持します。 このメソッドは、まず、選択したデバイスのディクショナリにエントリが既に存在するかどうかを確認します。 次に、このメソッドは、TryCreateFromId を呼び出し、選択したデバイス ID を渡して、選択したデバイスに対する 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;
            }
        }
    }
}

オーディオ再生接続を開く

前の手順ではオーディオ再生接続が作成されましたが、Open または OpenAsync を呼び出して接続が開かれるまで、音声の再生は開始されません。 [Open Audio Playback Connection] (オーディオ再生接続を開く) ボタンでハンドラーをクリックして、現在選択されているデバイスを取得し、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 イベントが発生します。 この例では、このイベントのハンドラーによって、状態のテキスト ボックスが更新されます。 UI スレッドで更新が確実に行われるように、Dispatcher.RunAsync の呼び出し内の 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";
        }
    });
}

接続を解放し、削除されたデバイスを処理する

この例では、ユーザーがオーディオ再生接続を解放できるように、[Release Audio Playback Connection] (オーディオ再生接続の解放) ボタンを用意します。 このイベントのハンドラーで、現在選択されているデバイスを取得し、デバイスの ID を使用してディクショナリ内の 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 コントロールにバインドされている監視可能なコレクションからデバイスを削除するために、削除されたデバイスの ID が使用されます。 次に、このデバイスに関連付けられている接続がアプリのディクショナリ内にある場合は、関連付けられているリソースを解放するために Dispose が呼び出された後、ディクショナリから接続が削除されます。 これらはすべて、UI の更新が UI スレッドで実行されるようにするために、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);
        }
    });
}

メディア再生