미디어 캐스팅

이 문서에서는 유니버설 Windows 앱에서 원격 디바이스로 미디어를 캐스팅하는 방법을 보여줍니다.

MediaPlayerElement를 사용한 내장형 미디어 캐스팅

유니버설 Windows 앱에서 미디어를 캐스팅하는 가장 간단한 방법은 MediaPlayerElement 컨트롤의 기본 제공 캐스팅 기능을 사용하는 것입니다.

사용자가 MediaPlayerElement 컨트롤에서 재생할 비디오 파일을 열 수 있도록 하려면 프로젝트에 다음 네임스페이스를 추가합니다.

using Windows.Storage;
using Windows.Storage.Pickers;
using Windows.Storage.Streams;
using Windows.Media.Core;

앱의 XAML 파일에서 MediaPlayerElement를 추가하고 AreTransportControlsEnabled를 true로 설정합니다.

<MediaPlayerElement Name="mediaPlayerElement"  MinHeight="100" MaxWidth="600" HorizontalAlignment="Stretch" AreTransportControlsEnabled="True"/>

사용자가 파일 선택 작업을 시작할 수 있도록 하는 단추를 추가합니다.

<Button x:Name="openButton" Click="openButton_Click" Content="Open"/>

단추에 대한 Click 이벤트 처리기에서 FileOpenPickerr의 새 인스턴스를 만들고, FileTypeFilter 컬렉션에 비디오 파일 형식을 추가하고, 시작 위치를 사용자의 비디오 라이브러리로 설정합니다.

PickSingleFileAsync를 호출하여 파일 선택기 대화 상자를 시작합니다. 이 메서드가 반환되면 결과는 비디오 파일을 나타내는 StorageFile 개체입니다. 사용자가 선택 작업을 취소하는 경우 파일이 null이 아닌지 확인합니다. 파일의 OpenAsync 메서드를 호출하여 파일에 대한 IRandomAccessStream을 가져옵니다. 마지막으로 CreateFromStorageFile을 호출하여 선택한 파일에서 새 MediaSource 개체를 만들고 MediaPlayerElement 개체의 Source 속성에 할당하여 비디오 파일을 컨트롤의 비디오 원본으로 만듭니다.

private async void openButton_Click(object sender, RoutedEventArgs e)
{
    //Create a new picker
    FileOpenPicker filePicker = new FileOpenPicker();

    //Add filetype filters.  In this case wmv and mp4.
    filePicker.FileTypeFilter.Add(".wmv");
    filePicker.FileTypeFilter.Add(".mp4");

    //Set picker start location to the video library
    filePicker.SuggestedStartLocation = PickerLocationId.VideosLibrary;

    //Retrieve file from picker
    StorageFile file = await filePicker.PickSingleFileAsync();

    //If we got a file, load it into the media lement
    if (file != null)
    {
        mediaPlayerElement.Source = MediaSource.CreateFromStorageFile(file);
        mediaPlayerElement.MediaPlayer.Play();
    }
}

비디오가 MediaPlayerElement에 로드되면 사용자는 전송 컨트롤의 캐스팅 단추를 눌러 로드된 미디어를 캐스팅할 디바이스를 선택할 수 있는 기본 제공 대화 상자를 시작할 수 있습니다.

mediaelement casting button

참고 항목

Windows 10 버전 1607부터 MediaPlayer 클래스를 사용하여 미디어 항목을 재생하는 것이 좋습니다. MediaPlayerElement는 XAML 페이지에서 MediaPlayer의 콘텐츠를 렌더링하는 데 사용되는 경량 XAML 컨트롤입니다. MediaElement 컨트롤은 이전 버전과의 호환성을 위해 계속 지원됩니다. MediaPlayerMediaPlayerElement를 사용하여 미디어 콘텐츠를 재생하는 방법에 대한 자세한 내용은 MediaPlayer를 사용하여 오디오 및 비디오 재생을 참조하세요. MediaSource 및 관련 API를 사용하여 미디어 콘텐츠를 사용하는 방법에 대한 자세한 내용은 미디어 항목, 재생 목록 및 트랙을 참조하세요.

CastingDevicePicker를 사용한 미디어 캐스팅

디바이스에 미디어를 캐스팅하는 두 번째 방법은 CastingDevicePicker를 사용하는 것입니다. 이 클래스를 사용하려면 프로젝트에 Windows.Media.Casting 네임스페이스를 포함합니다.

using Windows.Media.Casting;

CastingDevicePicker 개체에 대한 멤버 변수를 선언합니다.

CastingDevicePicker castingPicker;

페이지가 초기화되면 캐스팅 선택기의 새 인스턴스를 만들고 FilterSupportsVideo 속성으로 설정하여 선택기에서 나열된 캐스팅 디바이스가 비디오를 지원해야 함을 나타냅니다. 캐스팅할 디바이스를 선택할 때 발생하는 CastingDeviceSelected 이벤트에 대한 처리기를 등록합니다.

//Initialize our picker object
castingPicker = new CastingDevicePicker();

//Set the picker to filter to video capable casting devices
castingPicker.Filter.SupportsVideo = true;

//Hook up device selected event
castingPicker.CastingDeviceSelected += CastingPicker_CastingDeviceSelected;

XAML 파일에서 사용자가 선택기를 시작할 수 있도록 단추를 추가합니다.

<Button x:Name="castPickerButton" Content="Cast Button" Click="castPickerButton_Click"/>

단추에 대한 Click 이벤트 처리기에서 TransformToVisual을 호출하여 다른 요소를 기준으로 UI 요소의 변환을 가져옵니다. 이 예제에서 변환은 애플리케이션 창의 시각적 루트를 기준으로 캐스트 선택기 단추의 위치입니다. CastingDevicePicker 개체의 Show 메서드를 호출하여 캐스팅 선택기 대화 상자를 시작합니다. 캐스트 선택기 버튼의 위치와 크기를 지정하여 시스템에서 사용자가 누른 버튼에서 대화 상자를 띄울 수 있도록 합니다.

private void castPickerButton_Click(object sender, RoutedEventArgs e)
{
    //Retrieve the location of the casting button
    GeneralTransform transform = castPickerButton.TransformToVisual(Window.Current.Content as UIElement);
    Point pt = transform.TransformPoint(new Point(0, 0));

    //Show the picker above our casting button
    castingPicker.Show(new Rect(pt.X, pt.Y, castPickerButton.ActualWidth, castPickerButton.ActualHeight),
        Windows.UI.Popups.Placement.Above);
}

CastingDeviceSelected 이벤트 처리기에서 사용자가 선택한 캐스팅 디바이스를 나타내는 이벤트 인수의 SelectedCastingDevice 속성의 CreateCastingConnection 메서드를 호출합니다. ErrorOccurredStateChanged 이벤트에 대한 처리기를 등록합니다. 마지막으로 RequestStartCastingAsync를 호출하여 캐스팅을 시작하고, 결과를 MediaPlayerElement 컨트롤의 MediaPlayer 개체의 GetAsCastingSource 메서드에 전달하여 캐스팅할 미디어가 MediaPlayerElement와 연결된 MediaPlayer의 콘텐츠임을 지정합니다.

참고 항목

캐스팅 연결은 UI 스레드에서 시작해야 합니다. CastingDeviceSelected는 UI 스레드에서 호출되지 않으므로 이러한 호출을 CoreDispatcher.RunAsync 호출 내에 배치해야 하므로 UI 스레드에서 호출됩니다.

private async void CastingPicker_CastingDeviceSelected(CastingDevicePicker sender, CastingDeviceSelectedEventArgs args)
{
    //Casting must occur from the UI thread.  This dispatches the casting calls to the UI thread.
    await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, async () =>
    {
        //Create a casting conneciton from our selected casting device
        CastingConnection connection = args.SelectedCastingDevice.CreateCastingConnection();

        //Hook up the casting events
        connection.ErrorOccurred += Connection_ErrorOccurred;
        connection.StateChanged += Connection_StateChanged;

        //Cast the content loaded in the media element to the selected casting device
        await connection.RequestStartCastingAsync(mediaPlayerElement.MediaPlayer.GetAsCastingSource());
    });
}

ErrorOccurredStateChanged 이벤트 처리기에서 사용자에게 현재 캐스팅 상태 알리도록 UI를 업데이트해야 합니다. 이러한 이벤트는 다음 섹션에서 사용자 지정 캐스팅 디바이스 선택기를 만드는 방법에 대해 자세히 설명합니다.

private async void Connection_StateChanged(CastingConnection sender, object args)
{
    await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
    {
        ShowMessageToUser("Casting Connection State Changed: " + sender.State);
    });
}

private async void Connection_ErrorOccurred(CastingConnection sender, CastingConnectionErrorOccurredEventArgs args)
{
    await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
    {
        ShowMessageToUser("Casting Connection State Changed: " + sender.State);
    });
}

사용자 지정 디바이스 선택기를 사용하여 미디어 캐스팅

다음 섹션에서는 캐스팅 디바이스를 열거하고 코드에서 연결을 시작하여 사용자 고유의 캐스팅 디바이스 선택기 UI를 만드는 방법을 설명합니다.

사용 가능한 캐스팅 디바이스를 열거하려면 프로젝트에 Windows.Devices.Enumeration 네임스페이스를 포함합니다.

using Windows.Devices.Enumeration;

XAML 페이지에 다음 컨트롤을 추가하여 이 예제의 기본 UI를 구현합니다.

  • 사용 가능한 캐스팅 디바이스를 찾는 디바이스 감시자를 시작하는 단추입니다.
  • 사용자에게 캐스팅 열거형이 진행 중이라는 피드백을 제공하는 ProgressRing 컨트롤입니다.
  • 검색된 캐스팅 디바이스를 나열하는 ListBox입니다. 캐스팅 디바이스 개체를 컨트롤에 직접 할당하고 FriendlyName 속성을 계속 표시할 수 있도록 컨트롤에 대한 ItemTemplate을 정의합니다.
  • 사용자가 캐스팅 디바이스의 연결을 끊을 수 있도록 하는 단추입니다.
<Button x:Name="startWatcherButton" Content="Watcher Button" Click="startWatcherButton_Click"/>
<ProgressRing x:Name="watcherProgressRing" IsActive="False"/>
<ListBox x:Name="castingDevicesListBox" MaxWidth="300" HorizontalAlignment="Left" SelectionChanged="castingDevicesListBox_SelectionChanged">
    <!--Listbox content is bound to the FriendlyName field of our casting devices-->
    <ListBox.ItemTemplate>
        <DataTemplate>
            <TextBlock Text="{Binding Path=FriendlyName}"/>
        </DataTemplate>
    </ListBox.ItemTemplate>
</ListBox>
<Button x:Name="disconnectButton" Content="Disconnect" Click="disconnectButton_Click" Visibility="Collapsed"/>

코드 숨김에서 DeviceWatcherCastingConnection에 대한 멤버 변수를 선언합니다.

DeviceWatcher deviceWatcher;
CastingConnection castingConnection;

startWatcherButton에 대한 Click 처리기에서 먼저 단추를 사용하지 않도록 설정하여 디바이스 열거가 진행되는 동안 진행률 링을 활성화하여 UI를 업데이트합니다. 캐스팅 디바이스의 목록 상자를 선택 취소합니다.

다음으로 DeviceInformation.CreateWatcher를 호출하여 디바이스 감시자를 만듭니다. 이 메서드는 다양한 유형의 디바이스를 감시하는 데 사용할 수 있습니다. CastingDevice.GetDeviceSelector에서 반환된 디바이스 선택기 문자열을 사용하여 비디오 캐스팅을 지원하는 디바이스를 감시하도록 지정합니다.

마지막으로 추가됨, 제거됨, EnumerationCompleted, 및 정지됨 이벤트에 대한 이벤트 처리기를 등록합니다.

private void startWatcherButton_Click(object sender, RoutedEventArgs e)
{
    startWatcherButton.IsEnabled = false;
    watcherProgressRing.IsActive = true;

    castingDevicesListBox.Items.Clear();

    //Create our watcher and have it find casting devices capable of video casting
    deviceWatcher = DeviceInformation.CreateWatcher(CastingDevice.GetDeviceSelector(CastingPlaybackTypes.Video));

    //Register for watcher events
    deviceWatcher.Added += DeviceWatcher_Added;
    deviceWatcher.Removed += DeviceWatcher_Removed;
    deviceWatcher.EnumerationCompleted += DeviceWatcher_EnumerationCompleted;
    deviceWatcher.Stopped += DeviceWatcher_Stopped;

    //Start the watcher
    deviceWatcher.Start();
}

추가됨 이벤트는 감시자가 새 디바이스를 검색할 때 발생합니다. 이 이벤트의 처리기에서 CastingDevice.FromIdAsync를 호출하고 처리기에 전달된 DeviceInformation 개체에 포함된 검색된 캐스팅 디바이스의 ID를 전달하여 새 CastingDevice 개체를 만듭니다.

CastingDeviceListBox에 CastingDevice를 추가하여 사용자가 선택할 수 있도록 합니다. XAML에 정의된 ItemTemplate때문에 FriendlyName 속성은 목록 상자의 항목 텍스트로 사용됩니다. 이 이벤트 처리기는 UI 스레드에서 호출되지 않으므로 CoreDispatcher.RunAsync 호출 내에서 UI를 업데이트해야 합니다.

private async void DeviceWatcher_Added(DeviceWatcher sender, DeviceInformation args)
{
    await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, async () =>
    {
        //Add each discovered device to our listbox
        CastingDevice addedDevice = await CastingDevice.FromIdAsync(args.Id);
        castingDevicesListBox.Items.Add(addedDevice);
    });
}

제거된 이벤트는 감시자가 캐스팅 디바이스가 더 이상 존재하지 않는 것을 감지할 때 발생합니다. 처리기에 전달된 Added 개체의 ID 속성을 목록 상자의 Items 컬렉션에 있는 각 Added의 ID와 비교합니다. ID가 일치하는 경우 컬렉션에서 해당 개체를 제거합니다. UI가 업데이트되고 있으므로 RunAsync 호출 내에서 이 호출을 수행해야 합니다.

private async void DeviceWatcher_Removed(DeviceWatcher sender, DeviceInformationUpdate args)
{
    await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
    {
        foreach (CastingDevice currentDevice in castingDevicesListBox.Items)
        {
            if (currentDevice.Id == args.Id)
            {
                castingDevicesListBox.Items.Remove(currentDevice);
            }
        }
    });
}

감시자가 디바이스 검색을 완료하면 EnumerationCompleted 이벤트가 발생합니다. 이 이벤트의 처리기에서 사용자에게 디바이스 열거가 완료되었음을 알리도록 UI를 업데이트하고 Stop을 호출하여 디바이스 감시자를 중지합니다.

private async void DeviceWatcher_EnumerationCompleted(DeviceWatcher sender, object args)
{
    await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
    {
        //If enumeration completes, update UI and transition watcher to the stopped state
        ShowMessageToUser("Watcher completed enumeration of devices");
        deviceWatcher.Stop();
    });
}

중지된 이벤트는 디바이스 감시자가 중지를 마쳤을 때 발생합니다. 이 이벤트의 처리기에서 ProgressRing 컨트롤을 중지하고 사용자가 디바이스 열거 프로세스를 다시 시작할 수 있도록 startWatcherButton을 다시 활성화합니다.

private async void DeviceWatcher_Stopped(DeviceWatcher sender, object args)
{
    await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
    {
        //Update UX when the watcher stops
        startWatcherButton.IsEnabled = true;
        watcherProgressRing.IsActive = false;
    });
}

사용자가 목록 상자 에서 캐스팅 디바이스 중 하나를 선택하면 SelectionChanged 이벤트가 발생합니다. 이 처리기 내에서 캐스팅 연결이 생성되고 캐스팅이 시작됩니다.

먼저 디바이스 열거가 미디어 캐스팅을 방해하지 않도록 디바이스 감시자가 중지되었는지 확인합니다. 사용자가 선택한 CastingDevice 개체에서 CreateCastingConnection을 호출하여 캐스팅 연결을 만듭니다. StateChangedErrorOccurred 이벤트에 대한 이벤트 처리기를 추가합니다.

RequestStartCastingAsync를 호출하여 미디어 캐스팅을 시작하고 MediaPlayer 메서드 GetAsCastingSource를 호출하여 반환된 캐스팅 소스를 전달합니다. 마지막으로 사용자가 미디어 캐스팅을 중지할 수 있도록 연결 끊기 단추를 표시합니다.

private async void castingDevicesListBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
    if (castingDevicesListBox.SelectedItem != null)
    {
        //When a device is selected, first thing we do is stop the watcher so it's search doesn't conflict with streaming
        if (deviceWatcher.Status != DeviceWatcherStatus.Stopped)
        {
            deviceWatcher.Stop();
        }

        //Create a new casting connection to the device that's been selected
        castingConnection = ((CastingDevice)castingDevicesListBox.SelectedItem).CreateCastingConnection();

        //Register for events
        castingConnection.ErrorOccurred += Connection_ErrorOccurred;
        castingConnection.StateChanged += Connection_StateChanged;

        //Cast the loaded video to the selected casting device.
        await castingConnection.RequestStartCastingAsync(mediaPlayerElement.MediaPlayer.GetAsCastingSource());
        disconnectButton.Visibility = Visibility.Visible;
    }
}

상태 변경 처리기에서 수행하는 작업은 캐스팅 연결의 새 상태에 따라 달라집니다.

  • 상태가 연결된 or 렝더링되면 ProgressRing 컨트롤이 비활성 상태이고 연결 끊기 단추가 표시되는지 확인합니다.
  • 상태가 연결이 끊어진 경우 목록 상자에서 현재 캐스팅 디바이스의 선택을 취소하고 ProgressRing 컨트롤을 비활성 상태로 만들고 연결 끊기 단추를 숨깁니다.
  • 상태가 커넥트된 경우 ProgressRing 컨트롤을 활성화하고 연결 끊기 단추를 숨깁니다.
  • 상태가 연결이 끊긴 경우 ProgressRing 컨트롤을 활성화하고 연결 끊기 단추를 숨깁니다.
private async void Connection_StateChanged(CastingConnection sender, object args)
{
    await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
    {
        //Update the UX based on the casting state
        if (sender.State == CastingConnectionState.Connected || sender.State == CastingConnectionState.Rendering)
        {
            disconnectButton.Visibility = Visibility.Visible;
            watcherProgressRing.IsActive = false;
        }
        else if (sender.State == CastingConnectionState.Disconnected)
        {
            disconnectButton.Visibility = Visibility.Collapsed;
            castingDevicesListBox.SelectedItem = null;
            watcherProgressRing.IsActive = false;
        }
        else if (sender.State == CastingConnectionState.Connecting)
        {
            disconnectButton.Visibility = Visibility.Collapsed;
            ShowMessageToUser("Connecting");
            watcherProgressRing.IsActive = true;
        }
        else
        {
            //Disconnecting is the remaining state
            disconnectButton.Visibility = Visibility.Collapsed;
            watcherProgressRing.IsActive = true;
        }
    });
}

ErrorOccurred 이벤트에 대한 처리기에서 사용자에게 캐스팅 오류가 발생했음을 알리도록 UI를 업데이트하고 목록 상자에서 현재 CastingDevice 개체의 선택을 취소합니다.

private async void Connection_ErrorOccurred(CastingConnection sender, CastingConnectionErrorOccurredEventArgs args)
{
    await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
    {
        //Clear the selection in the listbox on an error
        ShowMessageToUser("Casting Error: " + args.Message);
        castingDevicesListBox.SelectedItem = null;
    });
}

마지막으로 연결 끊기 단추에 대한 처리기를 구현합니다. CastingConnection 개체의 DisconnectAsync 메서드를 호출하여 미디어 캐스팅을 중지하고 캐스팅 디바이스에서 연결을 끊습니다. 이 호출은 CoreDispatcher.RunAsync를 호출하여 UI 스레드로 디스패치되어야 합니다.

private async void disconnectButton_Click(object sender, RoutedEventArgs e)
{
    if (castingConnection != null)
    {
        //When disconnect is clicked, the casting conneciton is disconnected.  The video should return locally to the media element.
        await castingConnection.DisconnectAsync();
    }
}