원격 Bluetooth 연결 디바이스에서 오디오 재생 사용

이 문서에서는 AudioPlaybackConnection을 사용하여 Bluetooth가 연결된 원격 디바이스가 로컬 컴퓨터에서 오디오를 재생할 수 있도록 하는 방법을 보여줍니다.

Windows 10부터 버전 2004 원격 오디오 소스는 Windows 디바이스로 오디오를 스트리밍할 수 있으므로 PC가 Bluetooth 스피커처럼 작동하도록 구성하고 사용자가 휴대폰에서 오디오를 들을 수 있는 시나리오를 가능하게 합니다. 이를 구현하여 OS의 Bluetooth 구성 요소를 사용하여 수신 오디오 데이터를 처리하고 기본 제공 PC 스피커 또는 유선 헤드폰과 같은 시스템의 시스템 오디오 엔드포인트에서 재생합니다. 기본 Bluetooth A2DP 싱크의 사용은 시스템이 아닌 최종 사용자 시나리오를 담당하는 앱에서 관리됩니다.

AudioPlaybackConnection 클래스는 원격 디바이스에서 연결을 사용하거나 사용하지 않도록 설정하고 연결을 만드는 데 사용되어 원격 오디오 재생을 시작할 수 있습니다.

사용자 인터페이스 추가

이 문서의 예제에서는 사용 가능한 원격 디바이스를 표시하는ListView 컨트롤, 연결 상태를 표시하는 TextBlock 및 연결을 활성화, 비활성화 및 열기 위한 세 개의 단추를 정의하는 다음과 같은 간단한 XAML UI를 사용합니다.

<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의 기본 Grid 컨트롤이 로드될 때 디바이스 관리자를 시작합니다. 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 개체로 표시됩니다. 검색된 각 디바이스를 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에서 재생되기를 원하는 새 응용 프로그램이 있지만 연결이 열릴 때까지 오디오가 재생을 시작하지 않는다는 것을 시스템에 알리며, 이는 이후 단계에서 표시됩니다.

오디오 재생 연결 사용 단추의 클릭 처리기에서, 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를 호출하여 연결이 열릴 때까지 소리 재생이 시작되지 않습니다. 오디오 재생 연결 열기 단추에서 처리기를 클릭하고, 현재 선택한 디바이스를 가져와 ID를 사용하여 앱의 연결 사전에서 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에 대한 호출 내에서 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";
        }
    });
}

연결 해제 및 제거된 디바이스 처리

이 예제에서는 사용자가 오디오 재생 연결을 해제할 수 있도록 오디오 재생 연결 해제 단추를 제공합니다. 이 이벤트에 대한 처리기에서 현재 선택한 디바이스를 가져와 디바이스의 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 이벤트에 대한 처리기를 구현합니다. 먼저 제거된 디바이스의 ID를 사용하여 앱의 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);
        }
    });
}

미디어 재생