媒体转换Media casting

本文向你演示了如何将媒体从通用 Windows 应用转换到远程设备。This article shows you how to cast media to remote devices from a Universal Windows app.

通过 MediaPlayerElement 的内置媒体转换Built-in media casting with MediaPlayerElement

从通用 Windows 应用转换媒体的最简单方法是使用 MediaPlayerElement 控件的内置转换功能。The simplest way to cast media from a Universal Windows app is to use the built-in casting capability of the MediaPlayerElement control.

若要允许用户打开一个要在 MediaPlayerElement 控件中播放的视频文件,请将以下命名空间添加到你的项目。To allow the user to open a video file to be played in the MediaPlayerElement control, add the following namespaces to your project.

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

在应用的 XAML 文件中,添加 MediaPlayerElement 并将 AreTransportControlsEnabled 设置为 true。In your app's XAML file, add a MediaPlayerElement and set AreTransportControlsEnabled to true.

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

添加按钮以让用户启动选取文件操作。Add a button to let the user initiate picking a file.

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

在按钮的 Click 事件处理程序中,创建 FileOpenPicker 的新实例、将视频文件类型添加到 FileTypeFilter 集合,并将起始位置设置为用户的视频库。In the Click event handler for the button, create a new instance of the FileOpenPicker, add video file types to the FileTypeFilter collection, and set the starting location to the user's videos library.

调用 PickSingleFileAsync 以启动“文件选取器”对话框。Call PickSingleFileAsync to launch the file picker dialog. 当此方法返回时,结果是表示视频文件的 StorageFile 对象。When this method returns, the result is a StorageFile object representing the video file. 检查以确保文件不是 null,如果用户取消选取操作,该文件将是 null。Check to make sure the file isn't null, which it will be if the user cancels the picking operation. 调用文件的 OpenAsync 方法来为该文件获取一个 IRandomAccessStreamCall the file's OpenAsync method to get an IRandomAccessStream for the file. 最后,通过调用 CreateFromStorageFile 从选定文件创建新的 MediaSource 对象,并将其分配给 MediaPlayerElement 对象的 Source 属性以使视频文件成为控件的视频源。Finally, create a new MediaSource object from the selected file by calling CreateFromStorageFile and assign it to the MediaPlayerElement object's Source property to make the video file the video source for the control.

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 中加载视频后,用户只需按传输控件上的转换按钮即可启动内置对话框,他们可以在该对话框中选择已加载媒体将转换到的设备。Once the video is loaded in the MediaPlayerElement, the user can simply press the casting button on the transport controls to launch a built-in dialog that allows them to choose a device to which the loaded media will be cast.

MediaElement 转换按钮

备注

自 Windows 10 版本 1607 起,建议使用 MediaPlayer 类播放媒体项。Starting with Windows 10, version 1607, it is recommended that you use the MediaPlayer class to play media items. MediaPlayerElement 是一种轻型 XAML 控件,用于在 XAML 页面中呈现 MediaPlayer 的内容。The MediaPlayerElement is a lightweight XAML control that is used to render the content of a MediaPlayer in a XAML page. 为了实现向后兼容性,继续支持 MediaElement 控件。The MediaElement control continues to be supported for backwards compatibility. 有关使用 MediaPlayerMediaPlayerElement 播放媒体内容的详细信息,请参阅使用 MediaPlayer 播放音频和视频For more information on using MediaPlayer and MediaPlayerElement to play media content, see Play audio and video with MediaPlayer. 有关使用 MediaSource 和相关 API 处理媒体内容的信息,请参阅媒体项、播放列表和轨For information on using MediaSource and related APIs to work with media content, see Media items, playlists, and tracks.

通过 CastingDevicePicker 的媒体转换Media casting with the CastingDevicePicker

将媒体转换到设备的第二个方法是使用 CastingDevicePickerA second way to cast media to a device is to use the CastingDevicePicker. 若要使用此类型,请在项目中包括 Windows.Media.Casting 命名空间。To use this class, include the Windows.Media.Casting namespace in your project.

using Windows.Media.Casting;

声明 CastingDevicePicker 对象的成员变量。Declare a member variable for the CastingDevicePicker object.

CastingDevicePicker castingPicker;

初始化你的页面时,创建转换选取器的新实例并将 Filter 设置为 SupportsVideo 属性,以指示由选取器列出的转换设备应支持视频。When you page is initialized, create a new instance of the casting picker and set the Filter to SupportsVideo property to indicate that the casting devices listed by the picker should support video. CastingDeviceSelected 事件注册处理程序,它将在用户选取某台设备进行转换时引发。Register a handler for the CastingDeviceSelected event, which is raised when the user picks a device for casting.

//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 文件中,添加按钮以允许用户启动选取器。In your XAML file, add a button to allow the user to launch the picker.

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

在按钮的 Click 事件处理程序中,调用 TransformToVisual 以获取相对于另一个元素的 UI 元素转换。In the Click event handler for the button, call TransformToVisual to get the transform of a UI element relative to another element. 在此示例中,转换是相对于应用程序窗口的可视根的转换选取器按钮的位置。In this example, the transform is the position of the cast picker button relative to the visual root of the application window. 调用 CastingDevicePicker 对象的 Show 方法,以启动“转换选取器”对话框。Call the Show method of the CastingDevicePicker object to launch the casting picker dialog. 指定转换选取器按钮的位置和尺寸,以便系统可以使对话框在用户按下该按钮时弹出。Specify the location and dimensions of the cast picker button so that the system can make the dialog fly out from the button that the user pressed.

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 方法,该方法表示用户所选的转换设备。In the CastingDeviceSelected event handler, call the CreateCastingConnection method of the SelectedCastingDevice property of the event args, which represents the casting device selected by the user. ErrorOccurredStateChanged 事件注册处理程序。Register handlers for the ErrorOccurred and StateChanged events. 最后,调用 RequestStartCastingAsync 以开始转换,从而将结果传入 MediaPlayerElement 控件的 MediaPlayer 对象的 GetAsCastingSource 方法,以便指定要转换的媒体是与 MediaPlayerElement 关联的 MediaPlayer 的内容。Finally, call RequestStartCastingAsync to begin casting, passing in the result to the MediaPlayerElement control's MediaPlayer object's GetAsCastingSource method to specify that the media to be cast is the content of the MediaPlayer associated with the MediaPlayerElement.

备注

必须在 UI 线程上启动转换连接。The casting connection must be initiated on the UI thread. 由于未在 UI 线程上调用 CastingDeviceSelected,因此你必须将这些调用放置在对 CoreDispatcher.RunAsync(可使这些调用在 UI 线程上调用)的调用内。Since the CastingDeviceSelected is not called on the UI thread, you must place these calls inside a call to CoreDispatcher.RunAsync which causes them to be called on the UI thread.

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 以通知用户当前转换状态。In the ErrorOccurred and StateChanged event handlers, you should update your UI to inform the user of the current casting status. 在有关创建自定义转换设备选取器的下一节中详细讨论了这些事件。These events are discussed in detail in the following section on creating a custom casting device picker.

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

通过自定义设备选取器的媒体转换Media casting with a custom device picker

下一节介绍了如何通过从你的代码枚举转换设备和启动连接,创建你自己的转换设备选取器 UI。The following section describes how to create your own casting device picker UI by enumerating the casting devices and initiating the connection from your code.

若要枚举可用的转换设备,请在你的项目中包括 Windows.Devices.Enumeration 命名空间。To enumerate the available casting devices, include the Windows.Devices.Enumeration namespace in your project.

using Windows.Devices.Enumeration;

将以下控件添加到 XAML 页面,以实现此示例的基本 UI:Add the following controls to your XAML page to implement the rudimentary UI for this example:

  • 用于启动查找可用转换设备的设备观察程序的按钮。A button to start the device watcher that looks for available casting devices.
  • 用于向正在运行转换枚举的用户提供反馈的 ProgressRing 控件。A ProgressRing control to provide feedback to the user that casting enumeration is ongoing.
  • 用于列出发现的转换设备的 ListBoxA ListBox to list the discovered casting devices. 为控件定义 ItemTemplate,以便我们可以将转换设备对象直接分配给该控件,并仍然显示 FriendlyName 属性。Define an ItemTemplate for the control so that we can assign the casting device objects directly to the control and still display the FriendlyName property.
  • 允许用户断开转换设备连接的按钮。A button to allow the user to disconnect the casting device.
<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 的成员变量。In your code behind, declare member variables for the DeviceWatcher and the CastingConnection.

DeviceWatcher deviceWatcher;
CastingConnection castingConnection;

startWatcherButtonClick 处理程序中,首先通过禁用该按钮,并在正在运行设备枚举时激活进度环来更新 UI。In the Click handler for the startWatcherButton, first update the UI by disabling the button and making the progress ring active while device enumeration is ongoing. 清除转换设备的列表框。Clear the list box of casting devices.

接下来,通过调用 DeviceInformation.CreateWatcher 创建设备观察程序。Next, create a device watcher by calling DeviceInformation.CreateWatcher. 此方法可用于观察许多不同类型的设备。This method can be used to watch for many different types of devices. 指明你想要通过使用 CastingDevice.GetDeviceSelector 返回的设备选择器字符串,观察支持视频转换的设备。Specify that you want to watch for devices that support video casting by using the device selector string returned by CastingDevice.GetDeviceSelector.

最后,为 AddedRemovedEnumerationCompletedStopped 事件注册事件处理程序。Finally, register event handlers for the Added, Removed, EnumerationCompleted, and Stopped events.

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

当观察程序发现新设备时,将引发 Added 事件。The Added event is raised when a new device is discovered by the watcher. 在此事件的处理程序中,通过调用 CastingDevice.FromIdAsync 并传入发现的转换设备的 ID 来创建新的 CastingDevice 对象,它包含在已传入到处理程序中的 DeviceInformation 对象中。In the handler for this event, create a new CastingDevice object by calling CastingDevice.FromIdAsync and passing in the ID of the discovered casting device, which is contained in the DeviceInformation object passed into the handler.

CastingDevice 添加到转换设备 ListBox,以便用户可以选择它。Add the CastingDevice to the casting device ListBox so that the user can select it. 由于在 XAML 中定义的 ItemTemplateFriendlyName 属性将用作列表框中的项文本。Because of the ItemTemplate defined in the XAML, the FriendlyName property will be used as the item text for in the list box. 因为未在 UI 线程上调用此事件处理程序,因此你必须在对 CoreDispatcher.RunAsync 的调用内更新 UI。Because this event handler is not called on the UI thread, you must update the UI from within a call to CoreDispatcher.RunAsync.

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

当观察程序检测到转换设备不再存在时,将引发 Removed 事件。The Removed event is raised when the watcher detects that a casting device is no longer present. 比较已传入到处理程序中 Added 对象的 ID 属性与列表框的 Items 集合中每个 Added 的 ID。Compare the ID property of the Added object passed into the handler to the ID of each Added in the list box's Items collection. 如果 ID 匹配,则从集合中删除该对象。If the ID matches, remove that object from the collection. 同样,因为 UI 已得到更新,因此必须从 RunAsync 调用内进行此调用。Again, because the UI is being updated, this call must be made from within a RunAsync call.

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 事件。The EnumerationCompleted event is raised when the watcher has finished detecting devices. 在此事件的处理程序中,更新 UI 以让用户知道设备枚举已完成,并通过调用 Stop 停止设备观察程序。In the handler for this event, update the UI to let the user know that device enumeration has completed and stop the device watcher by calling 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();
    });
}

当设备观察程序已停止时,应引发 Stopped 事件。The Stopped event is raised when the device watcher has finished stopping. 在此事件的处理程序中,停止 ProgressRing 控件并重新启用 startWatcherButton,以便用户可以重新启动设备枚举过程。In the handler for this event, stop the ProgressRing control and reenable the startWatcherButton so that the user can restart the device enumeration process.

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 事件。When the user selects one of the casting devices from the list box, the SelectionChanged event is raised. 它位于将创建转换连接并且将启动转换的这一处理程序中。It is within this handler that the casting connection will be created and casting will be started.

首先,请确保设备观察程序已停止,以便设备枚举不会干扰媒体转换。First, make sure the device watcher is stopped so that device enumeration doesn't interfere with media casting. 通过在用户所选的 CastingDevice 对象上调用 CreateCastingConnection 来创建转换连接。Create a casting connection by calling CreateCastingConnection on the CastingDevice object selected by the user. StateChangedErrorOccurred 事件添加事件处理程序。Add event handlers for the StateChanged and ErrorOccurred events.

通过调用 RequestStartCastingAsync 启动媒体转换,从而传入通过调用 MediaPlayer 方法 GetAsCastingSource 返回的转换源。Start media casting by calling RequestStartCastingAsync, passing in the casting source returned by calling the MediaPlayer method GetAsCastingSource. 最后,使断开连接按钮可见,以允许用户停止媒体转换。Finally, make the disconnect button visible to allow the user to stop media casting.

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

在状态发生更改的处理程序中,要采取的操作取决于转换连接的新状态:In the state changed handler, the action you take depends on the new state of the casting connection:

  • 如果状态为 ConnectedRendering,确保 ProgressRing 控件处于非活动状态,并且断开连接按钮可见。If the state is Connected or Rendering, make sure the ProgressRing control is inactive and the disconnect button is visible.
  • 如果状态为 Disconnected,取消选择列表框中的当前转换设备、使 ProgressRing 控件处于非活动状态,并隐藏断开连接按钮。If the state is Disconnected, unselect the current casting device in the list box, make the ProgressRing control inactive, and hide the disconnect button.
  • 如果状态为 Connecting,使 ProgressRing 控件处于活动状态,并隐藏断开连接按钮。If the state is Connecting, make the ProgressRing control active and hide the disconnect button.
  • 如果状态为 Disconnecting,使 ProgressRing 控件处于活动状态,并隐藏断开连接按钮。If the state is Disconnecting, make the ProgressRing control active and hide the disconnect button.
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 对象。In the handler for the ErrorOccurred event, update your UI to let the user know that a casting error occurred and unselect the current CastingDevice object in the list box.

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

最后,实现断开连接按钮的处理程序。Finally, implement the handler for the disconnect button. 通过调用 CastingConnection 对象的 DisconnectAsync 方法,停止媒体转换并从转换设备断开连接。Stop media casting and disconnect from the casting device by calling the CastingConnection object's DisconnectAsync method. 此调用必须已通过调用 CoreDispatcher.RunAsync 调度到 UI 线程。This call must be dispatched to the UI thread by calling CoreDispatcher.RunAsync.

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