媒体转换

本文向你演示了如何将媒体从通用 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 事件处理程序中,创建 FileOpenPicker 的新实例、将视频文件类型添加到 FileTypeFilter 集合,并将起始位置设置为用户的视频库。

调用 PickSingleFileAsync 以启动“文件选取器”对话框。 当此方法返回时,结果是表示视频文件的 StorageFile 对象。 检查以确保文件不是 null,如果用户取消选取操作,该文件将是 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 转换按钮

注意

自 Windows 10 版本 1607 起,建议使用 MediaPlayer 类播放媒体项。 MediaPlayerElement 是一种轻型 XAML 控件,用于在 XAML 页面中呈现 MediaPlayer 的内容。 为了实现向后兼容性,继续支持 MediaElement 控件。 有关使用 MediaPlayerMediaPlayerElement 播放媒体内容的详细信息,请参阅使用 MediaPlayer 播放音频和视频。 有关使用 MediaSource 和相关 API 处理媒体内容的信息,请参阅媒体项、播放列表和轨

通过 CastingDevicePicker 的媒体转换

将媒体转换到设备的第二个方法是使用 CastingDevicePicker。 若要使用此类型,请在项目中包括 Windows.Media.Casting 命名空间。

using Windows.Media.Casting;

声明 CastingDevicePicker 对象的成员变量。

CastingDevicePicker castingPicker;

初始化你的页面时,创建转换选取器的新实例并将 Filter 设置为 SupportsVideo 属性,以指示由选取器列出的转换设备应支持视频。 为 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 线程上启动转换连接。 由于未在 UI 线程上调用 CastingDeviceSelected,因此你必须将这些调用放置在对 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。 为控件定义 ItemTemplate,以便我们可以将转换设备对象直接分配给该控件,并仍然显示 FriendlyName 属性。
  • 允许用户断开转换设备连接的按钮。
<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;

startWatcherButtonClick 处理程序中,首先通过禁用该按钮,并在正在运行设备枚举时激活进度环来更新 UI。 清除转换设备的列表框。

接下来,通过调用 DeviceInformation.CreateWatcher 创建设备观察程序。 此方法可用于观察许多不同类型的设备。 指明你想要通过使用 CastingDevice.GetDeviceSelector 返回的设备选择器字符串,观察支持视频转换的设备。

最后,为 AddedRemovedEnumerationCompletedStopped 事件注册事件处理程序。

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 事件。 在此事件的处理程序中,通过调用 CastingDevice.FromIdAsync 并传入发现的转换设备的 ID 来创建新的 CastingDevice 对象,它包含在已传入到处理程序中的 DeviceInformation 对象中。

CastingDevice 添加到转换设备 ListBox,以便用户可以选择它。 由于在 XAML 中定义的 ItemTemplateFriendlyName 属性将用作列表框中的项文本。 因为未在 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);
    });
}

当观察程序检测到转换设备不再存在时,将引发 Removed 事件。 比较已传入到处理程序中 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();
    });
}

当设备观察程序已停止时,应引发 Stopped 事件。 在此事件的处理程序中,停止 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;
    }
}

在状态发生更改的处理程序中,要采取的操作取决于转换连接的新状态:

  • 如果状态为 ConnectedRendering,确保 ProgressRing 控件处于非活动状态,并且断开连接按钮可见。
  • 如果状态为 Disconnected,取消选择列表框中的当前转换设备、使 ProgressRing 控件处于非活动状态,并隐藏断开连接按钮。
  • 如果状态为 Connecting,使 ProgressRing 控件处于活动状态,并隐藏断开连接按钮。
  • 如果状态为 Disconnecting,使 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();
    }
}