使用 MediaPlayer 播放音频和视频Play audio and video with MediaPlayer

本文介绍了如何在通用 Windows 应用中使用 MediaPlayer 类播放媒体。This article shows you how to play media in your Universal Windows app using the MediaPlayer class. 使用 Windows 10 版本1607,对媒体播放 Api 进行了重大改进,包括适用于后台音频的简化单进程设计、与系统媒体传输控件的自动集成 (SMTC) 、同步多个媒体播放器的功能、将视频帧呈现给 Windows 用户界面的功能,以及用于在内容中创建和安排媒体中断的简单界面。With Windows 10, version 1607, significant improvements were made to the media playback APIs, including a simplified single-process design for background audio, automatic integration with the System Media Transport Controls (SMTC), the ability to synchronize multiple media players, the ability to render video frames to a Windows.UI.Composition surface, and an easy interface for creating and scheduling media breaks in your content. 若要充分利用这些改进功能,推荐用于播放媒体的最佳做法是将 MediaPlayer 类(而非 MediaElement)用于媒体播放。To take advantage of these improvements, the recommended best practice for playing media is to use the MediaPlayer class instead of MediaElement for media playback. 引入了轻型 XAML 控件 MediaPlayerElement,该控件允许你在 XAML 页面中呈现媒体内容。The lightweight XAML control, MediaPlayerElement, has been introduced to allow you render media content in a XAML page. MediaElement 提供的许多播放控件和状态 API 现在都可通过新 MediaPlaybackSession 对象获取。Many of the playback control and status APIs provided by MediaElement are now available through the new MediaPlaybackSession object. MediaElement 将继续运行以支持向后兼容,但不会向此类添加其他功能。MediaElement continues to function to support backwards compatibility, but no additional features will be added to this class.

本文将向你介绍典型的媒体播放应用所使用的 MediaPlayer 功能。This article will walk you through the MediaPlayer features that a typical media playback app will use. 请注意,MediaPlayerMediaSource 类用作所有媒体项目的容器。Note that MediaPlayer uses the MediaSource class as a container for all media items. 此类允许你加载并播放来自许多不同源的媒体,这些来源包括本地文件、内存流和网络源等,但使用的都是同一界面。This class allows you to load and play media from many different sources, including local files, memory streams, and network sources, all using the same interface. 此外,还有更高级的类能够与 MediaSource 一同使用,例如 MediaPlaybackItemMediaPlaybackList,这些类提供更加高级的功能,例如播放列表,以及通过音频、视频和元数据多轨道管理媒体源的功能。There are also higher-level classes that work with MediaSource, such as MediaPlaybackItem and MediaPlaybackList, that provide more advanced features like playlists and the ability to manage media sources with multiple audio, video, and metadata tracks. 有关 MediaSource 和相关 API 的详细信息,请参阅媒体项、播放列表和曲目For more information on MediaSource and related APIs, see Media items, playlists, and tracks.

备注

Windows 10 N 和 Windows 10 KN 版本不包括使用 MediaPlayer 进行播放所需的媒体功能。Windows 10 N and Windows 10 KN editions do not include the media features required to use MediaPlayer for playback. 可以手动安装这些功能。These features can be installed manually. 有关详细信息,请参阅 Windows 10 N 和 Windows 10 KN 版本的媒体功能包For more information, see Media feature pack for Windows 10 N and Windows 10 KN editions.

使用 MediaPlayer 播放媒体文件Play a media file with MediaPlayer

通过 MediaPlayer 进行基本媒体播放非常易于实现。Basic media playback with MediaPlayer is very simple to implement. 首先,创建 MediaPlayer 类的新实例。First, create a new instance of the MediaPlayer class. 应用可以同时使用多个 MediaPlayer 实例。Your app can have multiple MediaPlayer instances active at once. 接下来,将播放器的 Source 属性设为实现 IMediaPlaybackSource 的对象,例如 MediaSourceMediaPlaybackItemMediaPlaybackListNext, set the Source property of the player to an object that implements the IMediaPlaybackSource, such as a MediaSource, a MediaPlaybackItem, or a MediaPlaybackList. 在本例中,MediaSource 创建于应用本地存储中的文件,MediaPlaybackItem 创建于源,随后分配到播放器的 Source 属性。In this example, a MediaSource is created from a file in the app's local storage, and then a MediaPlaybackItem is created from the source and then assigned to the player's Source property.

MediaElement 不同,默认情况下,MediaPlayer 不会自动开始播放。Unlike MediaElement, MediaPlayer does not automatically begin playback by default. 可以通过调用 Play、将 AutoPlay 属性设为 True,或者等到用户使用内置媒体控件启动播放时开始播放。You can start playback by calling Play, by setting the AutoPlay property to true, or waiting for the user to initiate playback with the built-in media controls.

mediaPlayer = new MediaPlayer();
mediaPlayer.Source = MediaSource.CreateFromUri(new Uri("ms-appx:///Assets/example_video.mkv"));
mediaPlayer.Play();

当应用结束使用 MediaPlayer 后,应该调用 Close 方法(对应 C# 中的 Dispose)清理播放器使用的资源。When your app is done using a MediaPlayer, you should call the Close method (projected to Dispose in C#) to clean up the resources used by the player.

mediaPlayer.Dispose();

使用 MediaPlayerElement 在 XAML 中呈现视频Use MediaPlayerElement to render video in XAML

可以在 MediaPlayer 中播放媒体,无需在 XAML 中显示,但是许多媒体播放应用会希望在 XAML 页面中呈现媒体。You can play media in a MediaPlayer without displaying it in XAML, but many media playback apps will want to render the media in a XAML page. 为此,请使用轻型 MediaPlayerElement 控件。To do this, use the lightweight MediaPlayerElement control. MediaElement 一样,MediaPlayerElement 支持指定是否应显示内置传输控件。Like MediaElement, MediaPlayerElement allows you to specify whether the built-in transport controls should be shown.

<MediaPlayerElement x:Name="_mediaPlayerElement" AreTransportControlsEnabled="False" HorizontalAlignment="Stretch"  Grid.Row="0"/>

可以通过调用 SetMediaPlayer,设置绑定元素的 MediaPlayer 实例。You can set the MediaPlayer instance that the element is bound to by calling SetMediaPlayer.

_mediaPlayerElement.SetMediaPlayer(mediaPlayer);

还可以在 MediaPlayerElement 上设置播放源,元素将自动创建可以使用 MediaPlayer 属性访问的新 MediaPlayer 实例。You can also set the playback source on the MediaPlayerElement and the element will automatically create a new MediaPlayer instance that you can access using the MediaPlayer property.

_mediaPlayerElement.Source = MediaSource.CreateFromUri(new Uri("ms-appx:///Assets/example_video.mkv"));
mediaPlayer = _mediaPlayerElement.MediaPlayer;
mediaPlayer.Play();

备注

如果你通过将 IsEnabled 设置为 false 禁用 MediaPlayerMediaPlaybackCommandManager,它将中断 MediaPlayerMediaPlayerElement 提供的 TransportControls 之间的链接,以致内置传输控件不再自动控制播放器的播放。If you disable the MediaPlaybackCommandManager of the MediaPlayer by setting IsEnabled to false, it will break the link between the MediaPlayer the TransportControls provided by the MediaPlayerElement, so the built-in transport controls will no longer automatically control the playback of the player. 作为替代方法,你必须实现自己的控件才能控制 MediaPlayerInstead, you must implement your own controls to control the MediaPlayer.

MediaPlayer 常见任务Common MediaPlayer tasks

本部分介绍如何使用 MediaPlayer 的一些功能。This section shows you how to use some of the features of the MediaPlayer.

设置音频类别Set the audio category

MediaPlayerAudioCategory 属性设为 MediaPlayerAudioCategory 枚举的其中一个值,让系统知道播放的媒体种类。Set the AudioCategory property of a MediaPlayer to one of the values of the MediaPlayerAudioCategory enumeration to let the system know what kind of media you are playing. 游戏应该将其音乐流归类为 GameMedia,以使游戏音乐在其他应用程序在后台播放音乐时自动静音。Games should categorize their music streams as GameMedia so that game music mutes automatically if another application plays music in the background. 音乐或视频应用程序应该将它们的流归类为 MediaMovie,以使它们能够优先于 GameMedia 流。Music or video applications should categorize their streams as Media or Movie so they will take priority over GameMedia streams.

mediaPlayer.AudioCategory = MediaPlayerAudioCategory.Media;

输出到特定音频终结点Output to a specific audio endpoint

默认情况下,MediaPlayer 的音频输出路由到系统的默认音频终结点,但是可以指定 MediaPlayer 应该用于输出的特定音频终结点。By default, the audio output from a MediaPlayer is routed to the default audio endpoint for the system, but you can specify a specific audio endpoint that the MediaPlayer should use for output. 在以下示例中,MediaDevice.GetAudioRenderSelector 返回了唯一标识设备音频呈现类别的字符串。In the example below, MediaDevice.GetAudioRenderSelector returns a string that uniquely idenfies the audio render category of devices. 接下来,调用 DeviceInformation 方法 FindAllAsync 获取所选类型的所有可用设备列表。Next, the DeviceInformation method FindAllAsync is called to get a list of all available devices of the selected type. 可以通过编程方式确定希望使用的设备,或者将返回的设备添加到 ComboBox 以允许用户选择设备。You may programmatically determine which device you want to use or add the returned devices to a ComboBox to allow the user to select a device.

string audioSelector = MediaDevice.GetAudioRenderSelector();
var outputDevices = await DeviceInformation.FindAllAsync(audioSelector);
foreach (var device in outputDevices)
{
    var deviceItem = new ComboBoxItem();
    deviceItem.Content = device.Name;
    deviceItem.Tag = device;
    _audioDeviceComboBox.Items.Add(deviceItem);
}

在用于设备组合框的 SelectionChanged 事件中,MediaPlayerAudioDevice 属性设置为所选设备,该属性存储在 ComboBoxItemTag 属性中。In the SelectionChanged event for the devices combo box, the AudioDevice property of the MediaPlayer is set to the selected device, which was stored in the Tag property of the ComboBoxItem.

private void _audioDeviceComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
    DeviceInformation selectedDevice = (DeviceInformation)((ComboBoxItem)_audioDeviceComboBox.SelectedItem).Tag;
    if (selectedDevice != null)
    {
        mediaPlayer.AudioDevice = selectedDevice;
    }
}

播放会话Playback session

如前文所述,许多由 MediaElement 类公开的函数都已移到 MediaPlaybackSession 类。As described previously in this article, many of the functions that are exposed by the MediaElement class have been moved to the MediaPlaybackSession class. 这包括有关播放器播放状态的信息,例如当前播放位置、播放器是已暂停还是正在播放,以及当前播放速度。This includes information about the playback state of the player, such as the current playback position, whether the player is paused or playing, and the current playback speed. MediaPlaybackSession 还会提供几个事件,用于在状态更改时通知用户,这些更改包括所播放内容的当前缓冲和下载状态,以及当前播放视频内容的自然大小和纵横比。MediaPlaybackSession also provides several events to notify you when the state changes, including the current buffering and download status of content being played and the natural size and aspect ratio of the currently playing video content.

以下示例演示了如何实现向前跳过 10 秒内容的按钮单击处理程序。The following example shows you how to implement a button click handler that skips 10 seconds forward in the content. 首先,使用 PlaybackSession 属性检索播放器的 MediaPlaybackSession 对象。First, the MediaPlaybackSession object for the player is retrieved with the PlaybackSession property. 接下来,将 Position 属性设置为当前播放位置向后 10 秒的值。Next the Position property is set to the current playback position plus 10 seconds.

private void _skipForwardButton_Click(object sender, RoutedEventArgs e)
{
    var session = mediaPlayer.PlaybackSession;
    session.Position = session.Position + TimeSpan.FromSeconds(10);
}

下一个示例介绍通过设置会话的 PlaybackRate 属性,使用切换按钮在正常播放速度和 2 倍播放速度之间切换。The next example illustrates using a toggle button to toggle between normal playback speed and 2X speed by setting the PlaybackRate property of the session.

private void _speedToggleButton_Checked(object sender, RoutedEventArgs e)
{
    mediaPlayer.PlaybackSession.PlaybackRate = 2.0;
}
private void _speedToggleButton_Unchecked(object sender, RoutedEventArgs e)
{
    mediaPlayer.PlaybackSession.PlaybackRate = 1.0;
}

从 Windows 10 版本 1803 开始,可以对在 MediaPlayer 中显示的视频设置旋转(以 90 度为增量)。Starting with Windows 10, version 1803, you can set the rotation with which video is presented in the MediaPlayer in increments of 90 degrees.

mediaPlayer.PlaybackSession.PlaybackRotation = MediaRotation.Clockwise90Degrees;

检测预期和非预期缓冲Detect expected and unexpected buffering

上一节中所述的 MediaPlaybackSession 对象提供了两个事件(BufferingStartedBufferingEnded)用于检测当前播放的媒体文件何时开始和结束缓冲。The MediaPlaybackSession object described in the previous section provides two events for detecting when the currently playing media file begins and ends buffering, BufferingStarted and BufferingEnded. 这允许更新 UI 以向用户显示正在发生缓冲。This allows you to update your UI to show the user that buffering is occurring. 当媒体文件首次打开或用户切换到播放列表中的新项时,需要进行初始缓冲。Initial buffering is expected when a media file is first opened or when the user switches to a new item in a playlist. 当网络速度降低或提供内容的内容管理系统遇到技术问题时,可能会发生非预期缓冲。Unexpected buffering can occur when the network speed degrades or if the content management system providing the content experiences technical issues. 从 RS3 开始,可以使用 BufferingStarted 事件来确定缓冲事件是预期的还是非预期的并会中断播放。Starting with RS3, you can use the BufferingStarted event to determine if the buffering event is expected or if it is unexpected and interrupting playback. 此信息可以用作应用或媒体传送服务的遥测数据。You can use this information as telemetry data for your app or media delivery service.

BufferingStartedBufferingEnded 事件注册处理程序以接收缓冲状态通知。Register handlers for the BufferingStarted and BufferingEnded events to receive buffering state notifications.

mediaPlayer.PlaybackSession.BufferingStarted += MediaPlaybackSession_BufferingStarted;
mediaPlayer.PlaybackSession.BufferingEnded += MediaPlaybackSession_BufferingEnded;

BufferingStarted 事件处理程序中,将传入事件的事件参数投放到 MediaPlaybackSessionBufferingStartedEventArgs 对象并检查 IsPlaybackInterruption 属性。In the BufferingStarted event handler, cast the event args passed into the event to a MediaPlaybackSessionBufferingStartedEventArgs object and check the IsPlaybackInterruption property. 如果此值为 true,则触发事件的缓冲为非预期并中断播放。If this value is true, the buffering that triggered the event is unexpected and interrupting playback. 否则为预期初始缓冲。Otherwise, it is expected initial buffering.

private void MediaPlaybackSession_BufferingStarted(MediaPlaybackSession sender, object args)
{
    MediaPlaybackSessionBufferingStartedEventArgs bufferingStartedEventArgs = args as MediaPlaybackSessionBufferingStartedEventArgs;
    if (bufferingStartedEventArgs != null && bufferingStartedEventArgs.IsPlaybackInterruption)
    {
        // update the playback quality telemetry report to indicate that
        // playback was interrupted
    }

    // update the UI to indicate that playback is buffering
}
private void MediaPlaybackSession_BufferingEnded(MediaPlaybackSession sender, object args)
{
    // update the UI to indicate that playback is no longer buffering
}

使用收缩手势缩放视频Pinch and zoom video

MediaPlayer 支持在视频内容中指定应该呈现的源矩形,这可有效地支持视频放大。MediaPlayer allows you to specify the source rectangle within video content that should be rendered, effectively allowing you to zoom into video. 所指定的矩形与规范化矩形 (0,0,1,1) 成比例,其中 0,0 是帧的左上角,1,1 指定帧的全宽和全高。The rectangle you specify is relative to a normalized rectangle (0,0,1,1) where 0,0 is the upper left hand of the frame and 1,1 specifies the full width and height of the frame. 因此,如果要设置缩放矩形以呈现视频的右上角象限,需指定矩形 (.5,0,.5,.5)。So, for example, to set the zoom rectangle so that the top-right quadrant of the video is rendered, you would specify the rectangle (.5,0,.5,.5). 请务必检查这些值,确保源矩形位于 (0,0,1,1) 规范化矩形内。It is important that you check your values to make sure that your source rectangle is within the (0,0,1,1) normalized rectangle. 尝试设置超出此范围的值将引发异常。Attempting to set a value outside of this range will cause an exception to be thrown.

若要使用多点触控手势实现收缩以缩放,必须先指定要支持的手势。To implement pinch and zoom using multi-touch gestures, you must first specify which gestures you want to support. 在本例中,请求了缩放和平移手势。In this example, scale and translate gestures are requested. 发生一个订阅手势即会引发 ManipulationDelta 事件。The ManipulationDelta event is raised when one of the subscribed gestures occurs. DoubleTapped 事件用于将缩放重置为完整的帧。The DoubleTapped event will be used to reset the zoom to the full frame.

_mediaPlayerElement.ManipulationMode = ManipulationModes.Scale | ManipulationModes.TranslateX | ManipulationModes.TranslateY;
_mediaPlayerElement.ManipulationDelta += _mediaPlayerElement_ManipulationDelta;
_mediaPlayerElement.DoubleTapped += _mediaPlayerElement_DoubleTapped;

接下来,声明存储当前缩放源矩形的 Rect 对象。Next, declare a Rect object that will store the current zoom source rectangle.

Rect _sourceRect = new Rect(0, 0, 1, 1);

ManipulationDelta 处理程序调整缩放矩形的缩放或平移。The ManipulationDelta handler adjusts either the scale or the translation of the zoom rectangle. 如果增量缩放值不为 1,则意味着用户执行了收缩手势。If the delta scale value is not 1, it means that the user performed a pinch gesture. 如果值大于 1,源矩形应变小以放大内容。If the value is greater than 1, the source rectangle should be made smaller to zoom into the content. 如果值小于 1,则应使源矩形变大以缩小。在设置新的缩放值之前,将检查生成的矩形以确保其完全位于 (0,0,1,1) 限制内。If the value is less than 1, then the source rectangle should be made bigger to zoom out. Before setting the new scale values, the resulting rectangle is checked to make sure it lies entirely within the (0,0,1,1) limits.

如果缩放值为 1,则处理平移手势。If the scale value is 1, then the translation gesture is handled. 矩形仅根据手势的像素数除以控件的宽度和高度所得结果平移。The rectangle is simply translated by the number of pixels in gesture divided by the width and height of the control. 同样,检查生成的矩形,确保它位于 (0,0,1,1) 边界内。Again, the resulting rectangle is checked to make sure it lies within the (0,0,1,1) bounds.

最后,将 MediaPlaybackSessionNormalizedSourceRect 设置为新调整的矩形,从而指定视频帧中应该呈现的区域。Finally, the NormalizedSourceRect of the MediaPlaybackSession is set to the newly adjusted rectangle, specifying the area within the video frame that should be rendered.

private void _mediaPlayerElement_ManipulationDelta(object sender, ManipulationDeltaRoutedEventArgs e)
{

    if (e.Delta.Scale != 1)
    {
        var halfWidth = _sourceRect.Width / 2;
        var halfHeight = _sourceRect.Height / 2;

        var centerX = _sourceRect.X + halfWidth;
        var centerY = _sourceRect.Y + halfHeight;

        var scale = e.Delta.Scale;
        var newHalfWidth = (_sourceRect.Width * e.Delta.Scale) / 2;
        var newHalfHeight = (_sourceRect.Height * e.Delta.Scale) / 2;

        if (centerX - newHalfWidth > 0 && centerX + newHalfWidth <= 1.0 &&
            centerY - newHalfHeight > 0 && centerY + newHalfHeight <= 1.0)
        {
            _sourceRect.X = centerX - newHalfWidth;
            _sourceRect.Y = centerY - newHalfHeight;
            _sourceRect.Width *= e.Delta.Scale;
            _sourceRect.Height *= e.Delta.Scale;
        }
    }
    else
    {
        var translateX = -1 * e.Delta.Translation.X / _mediaPlayerElement.ActualWidth;
        var translateY = -1 * e.Delta.Translation.Y / _mediaPlayerElement.ActualHeight;

        if (_sourceRect.X + translateX >= 0 && _sourceRect.X + _sourceRect.Width + translateX <= 1.0 &&
            _sourceRect.Y + translateY >= 0 && _sourceRect.Y + _sourceRect.Height + translateY <= 1.0)
        {
            _sourceRect.X += translateX;
            _sourceRect.Y += translateY;
        }
    }

    mediaPlayer.PlaybackSession.NormalizedSourceRect = _sourceRect;
}

DoubleTapped 事件处理程序中,源矩形重新设为 (0,0,1,1),以呈现整个视频帧。In the DoubleTapped event handler, the source rectangle is set back to (0,0,1,1) to cause the entire video frame to be rendered.

private void _mediaPlayerElement_DoubleTapped(object sender, DoubleTappedRoutedEventArgs e)
{
    _sourceRect = new Rect(0, 0, 1, 1);
    mediaPlayer.PlaybackSession.NormalizedSourceRect = _sourceRect;
}

注意 本部分介绍触控输入。NOTE This section describes touch input. 触摸板发送指针事件,并且不会发送操作事件。Touchpad sends pointer events and will not send Manipulation events.

处理基于策略的播放降级Handling policy-based playback degradation

在某些情况下,系统可能会根据策略而不是性能问题对媒体项的播放进行降级,例如降低分辨率(压缩)。In some circumstances the system may degrade the playback of a media item, such as reducing the resolution (constriction), based on a policy rather than a performance issue. 例如,如果使用未签名的视频驱动程序播放视频,则系统可能会对视频进行降级。For example, video may be degraded by the system if it is being played using an unsigned video driver. 可以调用 MediaPlaybackSession.GetOutputDegradationPolicyState 来确定是否以及为什么会发生这种基于策略的降级,并提醒用户或记录原因用于遥测。You can call MediaPlaybackSession.GetOutputDegradationPolicyState to determine if and why this policy-based degredation is occurring and alert the user or record the reason for telemetry purposes.

下面的示例显示了 MediaPlayer.MediaOpened 事件的处理程序的实现,该事件是在播放机打开新媒体项时引发的。The following example shows an implementation of a handler for the MediaPlayer.MediaOpened event that is raised when the player opens a new media item. 在传递给处理程序的 MediaPlayer 上调用 GetOutputDegradationPolicyStateGetOutputDegradationPolicyState is called on the MediaPlayer passed into the handler. VideoConstrictionReason 的值指示视频被压缩的策略原因。The value of VideoConstrictionReason indicates the policy reason that the video is constricted. 如果值不是 None,则此示例记录降级原因用于遥测。If the value isn't None, this example logs the degradation reason for telemetry purposes. 此示例还显示将当前播放的 AdaptiveMediaSource 的比特率设置为最低带宽以节省数据使用量,因为视频被压缩,并且不会以高分辨率显示。This example also shows setting the bitrate of the AdaptiveMediaSource currently being played to the lowest bandwidth to save data usage, since the video is constricted and won't be displayed at high resolution anyway. 有关使用 AdaptiveMediaSource 的详细信息,请参阅自适应流式处理For more information on using AdaptiveMediaSource, see Adaptive streaming.

private void MediaPlayer_MediaOpened(MediaPlayer sender, object args)
{
    MediaPlaybackSessionOutputDegradationPolicyState info = sender.PlaybackSession.GetOutputDegradationPolicyState();

    if (info.VideoConstrictionReason != MediaPlaybackSessionVideoConstrictionReason.None)
    {
        // Switch to lowest bitrate to save bandwidth
        adaptiveMediaSource.DesiredMaxBitrate = adaptiveMediaSource.AvailableBitrates[0];

        // Log the degradation reason or show a message to the user
        System.Diagnostics.Debug.WriteLine("Logging constriction reason: " + info.VideoConstrictionReason);
    }
}

使用 MediaPlayerSurface 将视频呈现到 Windows.UI.Composition 界面Use MediaPlayerSurface to render video to a Windows.UI.Composition surface

从 Windows 10 版本 1607 开始,可以使用 MediaPlayer 将视频呈现到 ICompositionSurface,这可以支持播放器与 Windows.UI.Composition 命名空间中的 API 进行互操作。Starting with Windows 10, version 1607, you can use MediaPlayer to render video to an to render video to an ICompositionSurface, which allows the player to interoperate with the APIs in the Windows.UI.Composition namespace. 合成框架允许你在 XAML 与低级别 DirectX 图形 API 之间的可视化层处理图形。The composition framework allows you to work with graphics in the visual layer between XAML and the low-level DirectX graphics APIs. 这可以使任意 XAML 控件都能够执行呈现视频等方案。This enables scenarios like rendering video into any XAML control. 有关使用合成 API 的详细信息,请参阅可视化层For more information on using the composition APIs, see Visual Layer.

以下示例介绍了如何将视频播放器内容呈现到 Canvas 控件。The following example illustrates how to render video player content onto a Canvas control. 本例中特定于媒体播放器的调用为 SetSurfaceSizeGetSurfaceThe media player-specific calls in this example are SetSurfaceSize and GetSurface. SetSurfaceSize 告知系统为呈现内容应该分配的缓冲区大小。SetSurfaceSize tells the system the size of the buffer that should be allocated for rendering content. GetSurfaceCompositor 视作参数,并且检索 MediaPlayerSurface 类的实例。GetSurface takes a Compositor as an arguemnt and retreives an instance of the MediaPlayerSurface class. 使用此类可以访问用于通过 CompositionSurface 属性创建表面和公开表面自身的 MediaPlayerCompositorThis class provides access to the MediaPlayer and Compositor used to create the surface and exposes the surface itself through the CompositionSurface property.

本例中的其余代码创建视频要呈现到的 SpriteVisual,还将大小设为显示视觉效果的画布元素大小。The rest of the code in this example creates a SpriteVisual to which the video is rendered and sets the size to the size of the canvas element that will display the visual. 接下来,从 MediaPlayerSurface 创建 CompositionBrush,并将其分配到视觉效果的 Brush 属性。Next a CompositionBrush is created from the MediaPlayerSurface and assigned to the Brush property of the visual. 然后,创建 ContainerVisual,在它的可视树顶部插入 SpriteVisualNext a ContainerVisual is created and the SpriteVisual is inserted at the top of its visual tree. 最后,调用 SetElementChildVisual,将容器视觉效果分配到 CanvasFinally, SetElementChildVisual is called to assign the container visual to the Canvas.

mediaPlayer.SetSurfaceSize(new Size(_compositionCanvas.ActualWidth, _compositionCanvas.ActualHeight));

var compositor = ElementCompositionPreview.GetElementVisual(this).Compositor;
MediaPlayerSurface surface = mediaPlayer.GetSurface(compositor);

SpriteVisual spriteVisual = compositor.CreateSpriteVisual();
spriteVisual.Size =
    new System.Numerics.Vector2((float)_compositionCanvas.ActualWidth, (float)_compositionCanvas.ActualHeight);

CompositionBrush brush = compositor.CreateSurfaceBrush(surface.CompositionSurface);
spriteVisual.Brush = brush;

ContainerVisual container = compositor.CreateContainerVisual();
container.Children.InsertAtTop(spriteVisual);

ElementCompositionPreview.SetElementChildVisual(_compositionCanvas, container);

使用 MediaTimelineController 跨多台播放器同步内容。Use MediaTimelineController to synchronize content across multiple players.

如前文所述,应用可以同时使用多个 MediaPlayer 对象。As discussed previously in this article, your app can have several MediaPlayer objects active at a time. 默认情况下,所创建的每个 MediaPlayer 都独立操作。By default, each MediaPlayer you create operates independently. 在某些情况下(例如同步视频的解说音轨),你可能希望同步多台播放器的状态、播放位置和播放速度。For some scenarios, such as synchronizing a commentary track to a video, you may want to synchronize the player state, playback position, and playback speed of multiple players. 从 Windows 10 版本 1607 开始,可以使用 MediaTimelineController 类实现这种行为。Starting with Windows 10, version 1607, you can implement this behavior by using the MediaTimelineController class.

实现播放控件Implement playback controls

以下示例展示了如何使用 MediaTimelineController 控制 MediaPlayer 的两个实例。The following example shows how to use a MediaTimelineController to control two instances of MediaPlayer. 首先,MediaPlayer 的每个实例均进行实例化,并且 Source 设置为媒体文件。First, each instance of the MediaPlayer is instantiated and the Source is set to a media file. 接下来,创建新 MediaTimelineControllerNext, a new MediaTimelineController is created. 对于每个 MediaPlayer,将 IsEnabled 属性设为 False 禁用与每台播放器关联的 MediaPlaybackCommandManagerFor each MediaPlayer, the MediaPlaybackCommandManager associated with each player is disabled by setting the IsEnabled property to false. 然后,将 TimelineController 属性设置为时间线控制器对象。And then the TimelineController property is set to the timeline controller object.

MediaTimelineController _mediaTimelineController;
mediaPlayer = new MediaPlayer();
mediaPlayer.Source = MediaSource.CreateFromUri(new Uri("ms-appx:///Assets/example_video.mkv"));
_mediaPlayerElement.SetMediaPlayer(mediaPlayer);


_mediaPlayer2 = new MediaPlayer();
_mediaPlayer2.Source = MediaSource.CreateFromUri(new Uri("ms-appx:///Assets/example_video_2.mkv"));
_mediaPlayerElement2.SetMediaPlayer(_mediaPlayer2);

_mediaTimelineController = new MediaTimelineController();

mediaPlayer.CommandManager.IsEnabled = false;
mediaPlayer.TimelineController = _mediaTimelineController;

_mediaPlayer2.CommandManager.IsEnabled = false;
_mediaPlayer2.TimelineController = _mediaTimelineController;

警告MediaPlaybackCommandManagerMediaPlayer 和系统媒体传输控件 (SMTC) 之间提供自动集成,但是这种自动集成无法和使用 MediaTimelineController 控制的媒体播放器一起使用。Caution The MediaPlaybackCommandManager provides automatic integration between MediaPlayer and the System Media Transport Controls (SMTC), but this automatic integration can't be used with media players that are controlled with a MediaTimelineController. 因此,必须先禁用媒体播放器的命令管理器才能设置播放器的时间线控制器。Therefore you must disable the command manager for the media player before setting the player's timeline controller. 否则将引发异常,并且显示以下消息:“由于对象的当前状态,附加媒体时间线控制器的操作被阻止”。Failure to do so will result in an exception being thrown with the following message: "Attaching Media Timeline Controller is blocked because of the current state of the object." 有关媒体播放器与 SMTC 集成的详细信息,请参阅与系统媒体传输控件集成For more information on media player integration with the SMTC, see Integrate with the System Media Transport Controls. 使用 MediaTimelineController 时仍然可以手动控制 SMTC。If you are using a MediaTimelineController you can still control the SMTC manually. 有关详细信息,请参阅手动控制系统媒体传输控件For more information, see Manual control of the System Media Transport Controls.

MediaTimelineController 附加到一个或多个媒体播放器后,可以通过使用控制器公开的方法控制播放状态。Once you have attached a MediaTimelineController to one or more media players, you can control the playback state by using the methods exposed by the controller. 以下示例调用 Start 使所有关联的媒体播放器从媒体开头处开始播放。The following example calls Start to begin playback of all associated media players at the beginning of the media.

private void PlayButton_Click(object sender, RoutedEventArgs e)
{
    _mediaTimelineController.Start();
}

本例介绍暂停并恢复所有附加的媒体播放器。This example illustrates pausing and resuming all of the attached media players.

private void PauseButton_Click(object sender, RoutedEventArgs e)
{
    if(_mediaTimelineController.State == MediaTimelineControllerState.Running)
    {
        _mediaTimelineController.Pause();
        _pauseButton.Content = "Resume";
    }
    else
    {
        _mediaTimelineController.Resume();
        _pauseButton.Content = "Pause";
    }
}

为了使所有连接的媒体播放器快进,请将播放速度设置为大于 1 的值。To fast-forward all connected media players, set the playback speed to a value greater that 1.

private void FastForwardButton_Click(object sender, RoutedEventArgs e)
{
    _mediaTimelineController.ClockRate = 2.0;
}

下一个示例介绍如何使用 Slider 控件显示时间线控制器的当前播放位置,该位置与某一个连接的媒体播放器播放的内容持续时间有关。The next example shows how to use a Slider control to show the current playback position of the timeline controller relative to the duration of the content of one of the connected media players. 首先,创建新 MediaSource,并且为媒体源的 OpenOperationCompleted 注册处理程序。First, a new MediaSource is created and a handler for the OpenOperationCompleted of the media source is registered.

var mediaSource = MediaSource.CreateFromUri(new Uri("ms-appx:///Assets/example_video.mkv"));
mediaSource.OpenOperationCompleted += MediaSource_OpenOperationCompleted;
mediaPlayer.Source = mediaSource;
_mediaPlayerElement.SetMediaPlayer(mediaPlayer);

使用 OpenOperationCompleted 处理程序有机会发现媒体源内容的持续时间。The OpenOperationCompleted handler is used as an opportunity to discover the duration of the media source content. 确定持续时间后,Slider 控件的最大值设置为媒体项的总秒数。Once the duration is determined, the maximum value of the Slider control is set to the total number of seconds of the media item. 此值在 RunAsync 调用中设置,以确保它在 UI 线程上运行。The value is set inside a call to RunAsync to make sure it is run on the UI thread.

TimeSpan _duration;
private async void MediaSource_OpenOperationCompleted(MediaSource sender, MediaSourceOpenOperationCompletedEventArgs args)
{
    _duration = sender.Duration.GetValueOrDefault();

    await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
    {
        _positionSlider.Minimum = 0;
        _positionSlider.Maximum = _duration.TotalSeconds;
        _positionSlider.StepFrequency = 1;
    }); 
}

接下来,为时间线控制器的 PositionChanged 事件注册处理程序。Next, a handler for the timeline controller's PositionChanged event is registered. 系统将定期调用它,频率大约为每秒 4 次。This is called periodically by the system, approximately 4 times per second.

_mediaTimelineController.PositionChanged += _mediaTimelineController_PositionChanged;

PositionChanged 的处理程序中,更新滑块值以反映时间线控制器的当前位置。In the handler for PositionChanged, the slider value is updated to reflect the current position of the timeline controller.

private async void _mediaTimelineController_PositionChanged(MediaTimelineController sender, object args)
{
    if (_duration != TimeSpan.Zero)
    {
        await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
        {
            _positionSlider.Value = sender.Position.TotalSeconds / (float)_duration.TotalSeconds;
        });
    }
}

使播放位置偏离时间线位置Offset the playback position from the timeline position

在某些情况下,与时间线控制器关联的一个或多个媒体播放器的播放位置可能需要偏离其他播放器。In some cases you may want the playback position of one or more media players associated with a timeline controller to be offset from the other players. 为此,可以设置要偏离的 MediaPlayer 对象的 TimelineControllerPositionOffset 属性。You can do this by setting the TimelineControllerPositionOffset property of the MediaPlayer object you want to be offset. 以下示例使用两个媒体播放器的内容持续时间设置两个滑块控件的最小值和最大值,以增加或减少项目的时间长度。The following example uses the durations of the content of two media players to set the minimum and maximum values of two slider control to plus and minus the length of the item.

_timelineOffsetSlider1.Minimum = -1 * _duration.TotalSeconds;
_timelineOffsetSlider1.Maximum = _duration.TotalSeconds;
_timelineOffsetSlider1.StepFrequency = 1;

_timelineOffsetSlider2.Minimum = -1 * _duration2.TotalSeconds;
_timelineOffsetSlider2.Maximum = _duration2.TotalSeconds;
_timelineOffsetSlider2.StepFrequency = 1;

在每个滑块的 ValueChanged 事件中,将每个播放器的 TimelineControllerPositionOffset 设置为相应值。In the ValueChanged event for each slider, the TimelineControllerPositionOffset for each player is set to the corresponding value.

private void _timelineOffsetSlider1_ValueChanged(object sender, RangeBaseValueChangedEventArgs e)
{
    mediaPlayer.TimelineControllerPositionOffset = TimeSpan.FromSeconds(_timelineOffsetSlider1.Value);
}

private void _timelineOffsetSlider2_ValueChanged(object sender, RangeBaseValueChangedEventArgs e)
{
    _mediaPlayer2.TimelineControllerPositionOffset = TimeSpan.FromSeconds(_timelineOffsetSlider2.Value);
}

请注意,如果播放器的偏离值映射到反向播放位置,剪辑将保持暂停状态,直到偏离值达到零后才开始播放。Note that if the offset value of a player maps to a negative playback position, the clip will remain paused until the offset reaches zero and then playback will begin. 同样地,如果偏离值映射到的播放位置大于媒体项的持续时间,将显示最后一帧,就像单个媒体播放器播放完内容一样。Likewise, if the offset value maps to a playback position greater than the duration of the media item, the final frame will be shown, just as it does when a single media player reached the end of its content.

使用 MediaPlayer 播放球面视频Play spherical video with MediaPlayer

从 Windows 10 版本 1703 开始,MediaPlayer 支持用于球面视频播放的 equirectangular 投影。Starting with Windows 10, version 1703, MediaPlayer supports equirectangular projection for spherical video playback. 由于只要视频编码受支持,MediaPlayer 便会呈现视频,因此球面视频内容与常规的平面视频没什么不同。Spherical video content is no different from regular, flat video in that MediaPlayer will render the video as long as the video encoding is supported. 对于包含的元数据标记指定视频使用 equirectangular 投影的球面视频,MediaPlayer 可以使用指定视野和视图方向呈现视频。For spherical video that contains a metadata tag that specifies that the video uses equirectangular projection, MediaPlayer can render the video using a specified field-of-view and view orientation. 这样可实现使用头盔显示屏的虚拟现实视频播放或是仅仅允许用户使用鼠标或键盘输入在球面视频中四处平移这类方案。This enables scenarios such as virtual reality video playback with a head-mounted display or simply allowing the user to pan around within spherical video content using the mouse or keyboard input.

若要播放球面视频,请使用本文前面介绍的用于播放视频内容的步骤。To play back spherical video, use the steps for playing back video content described previously in this article. 另外一步是注册 MediaPlayer 事件的处理程序。The one additional step is to register a handler for the MediaPlayer.MediaOpened event. 此事件使你可以启用并控制球面视频播放参数。This event gives you an opportunity to enable and control the spherical video playback parameters.

mediaPlayer = new MediaPlayer();
mediaPlayer.MediaOpened += _mediaPlayer_MediaOpened;
mediaPlayer.Source = MediaSource.CreateFromUri(new Uri("ms-appx:///Assets/example_video_spherical.mp4"));
_mediaPlayerElement.SetMediaPlayer(mediaPlayer);
mediaPlayer.Play();

MediaOpened 处理程序中,首先通过检查 PlaybackSession.SphericalVideoProjection.FrameFormat 属性来检查新打开的媒体项的帧格式。In the MediaOpened handler, first check the frame format of the newly opened media item by checking the PlaybackSession.SphericalVideoProjection.FrameFormat property. 如果此值为 SphericaVideoFrameFormat.Equirectangular,则系统可以自动投影视频内容。If this value is SphericaVideoFrameFormat.Equirectangular, then the system can automatically project the video content. 首先,将 PlaybackSession.SphericalVideoProjection.IsEnabled 属性设置为 trueFirst, set the PlaybackSession.SphericalVideoProjection.IsEnabled property to true. 还可以调整媒体播放器会用于投影视频内容的属性(如视图方向和视野)。You can also adjust properties such as the view orientation and field of view that the media player will use to project the video content. 在此示例中,通过设置 HorizontalFieldOfViewInDegrees 属性,将视野设置为较宽值 120 度。In this example, the field of view is set to a wide value of 120 degrees by setting the HorizontalFieldOfViewInDegrees property.

如果视频内容是球面的,但采用 equirectangular 以外的格式,则可以通过使用媒体播放器的帧服务器模式接收和处理各个帧来实现自己的投影算法。If the video content is spherical, but is in a format other than equirectangular, you can implement your own projection algorithm using the media player's frame server mode to receive and process individual frames.

private void _mediaPlayer_MediaOpened(MediaPlayer sender, object args)
{
    if (sender.PlaybackSession.SphericalVideoProjection.FrameFormat == SphericalVideoFrameFormat.Equirectangular)
    {
        sender.PlaybackSession.SphericalVideoProjection.IsEnabled = true;
        sender.PlaybackSession.SphericalVideoProjection.HorizontalFieldOfViewInDegrees = 120;

    }
    else if (sender.PlaybackSession.SphericalVideoProjection.FrameFormat == SphericalVideoFrameFormat.Unsupported)
    {
        // If the spherical format is unsupported, you can use frame server mode to implement a custom projection
    }
}

以下示例代码演示如何使用向左和向右箭头键来调整球面视频视图方向。The following example code illustrates how to adjust the spherical video view orientation using the left and right arrow keys.

protected override void OnKeyDown(KeyRoutedEventArgs e)
{
    if (mediaPlayer.PlaybackSession.SphericalVideoProjection.FrameFormat != SphericalVideoFrameFormat.Equirectangular)
    {
        return;
    }

    switch (e.Key)
    {
        case Windows.System.VirtualKey.Right:
            mediaPlayer.PlaybackSession.SphericalVideoProjection.ViewOrientation *= Quaternion.CreateFromYawPitchRoll(.1f, 0, 0);
            break;
        case Windows.System.VirtualKey.Left:
            mediaPlayer.PlaybackSession.SphericalVideoProjection.ViewOrientation *= Quaternion.CreateFromYawPitchRoll(-.1f, 0, 0);
            break;
    }
}

如果应用支持视频的播放列表,则可能要在 UI 中标识包含球面视频的播放项。If your app supports playlists of video, you may want to identify playback items that contain spherical video in your UI. 媒体播放列表在文章媒体项、播放列表和轨中进行了详细讨论。Media playlists are discussed in detail in the article, Media items, playlists, and tracks. 以下示例演示如何创建新播放列表、添加项以及为 MediaPlaybackItem.VideoTracksChanged 事件(解析媒体项的视频轨时发生)注册处理程序。The following example shows creating a new playlist, adding an item, and registering a handler for the MediaPlaybackItem.VideoTracksChanged event, which occurs when the video tracks for a media item are resolved.

var playbackList = new MediaPlaybackList();
var item = new MediaPlaybackItem(MediaSource.CreateFromUri(new Uri("ms-appx:///Assets/RIFTCOASTER HD_injected.mp4")));
item.VideoTracksChanged += Item_VideoTracksChanged;
playbackList.Items.Add(item);
mediaPlayer.Source = playbackList;

VideoTracksChanged 事件处理程序中,通过调用 VideoTrack.GetEncodingProperties 来获取任何已添加视频轨的编码属性。In the VideoTracksChanged event handler, get the encoding properties for any added video tracks by calling VideoTrack.GetEncodingProperties. 如果编码属性的 SphericalVideoFrameFormat 属性是 SphericaVideoFrameFormat.None 之外的值,则视频轨包含球面视频,可以相应地更新 UI(如果选择)。If the SphericalVideoFrameFormat property of the encoding properties is a value other than SphericaVideoFrameFormat.None, then the video track contains spherical video and you can update your UI accordingly if you choose.

private void Item_VideoTracksChanged(MediaPlaybackItem sender, IVectorChangedEventArgs args)
{
    if (args.CollectionChange != CollectionChange.ItemInserted)
    {
        return;
    }
    foreach (var videoTrack in sender.VideoTracks)
    {
        if (videoTrack.GetEncodingProperties().SphericalVideoFrameFormat != SphericalVideoFrameFormat.None)
        {
            // Optionally indicate in the UI that this item contains spherical video
        }
    }
}

在帧服务器模式中使用 MediaPlayerUse MediaPlayer in frame server mode

从 Windows 10 版本 1703 开始,可以在帧服务器模式中使用 MediaPlayerStarting with Windows 10, version 1703, you can use MediaPlayer in frame server mode. 在此模式中,MediaPlayer 不会自动将帧呈现到关联的 MediaPlayerElementIn this mode, the MediaPlayer does not automatically render frames to an associated MediaPlayerElement. 相反,应用会将当前帧从 MediaPlayer 复制到实现 IDirect3DSurface 的对象。Instead, your app copies the current frame from the MediaPlayer to an object that implements IDirect3DSurface. 此功能实现的主要方案是使用像素着色器处理 MediaPlayer 提供的视频帧。The primary scenario this feature enables is using pixel shaders to process video frames provided by the MediaPlayer. 应用负责在处理之后显示每个帧,如通过在 XAML Image 控件中显示帧。Your app is responsible for displaying each frame after processing, such as by showing the frame in a XAML Image control.

在以下示例中,会初始化新 MediaPlayer 并加载视频内容。In the following example, a new MediaPlayer is initialized and video content is loaded. 接下来,会注册 VideoFrameAvailable 的处理程序。Next, a handler for VideoFrameAvailable is registered. 通过将 MediaPlayer 对象的 IsVideoFrameServerEnabled 属性设置为 true 来启用帧服务器模式。Frame server mode is enabled by setting the MediaPlayer object's IsVideoFrameServerEnabled property to true. 最后,使用 Play 调用开始媒体播放。Finally, media playback is started with a call to Play.

mediaPlayer = new MediaPlayer();
mediaPlayer.Source = MediaSource.CreateFromUri(new Uri("ms-appx:///Assets/example_video.mkv"));
mediaPlayer.VideoFrameAvailable += mediaPlayer_VideoFrameAvailable;
mediaPlayer.IsVideoFrameServerEnabled = true;
mediaPlayer.Play();

下一个示例演示 VideoFrameAvailable 的处理程序,它使用 Win2D 将简单模糊效果添加到视频的每个帧,然后在 XAML Image 控件中显示经过处理的帧。The next example shows a handler for VideoFrameAvailable that uses Win2D to add a simple blur effect to each frame of a video and then displays the processed frames in a XAML Image control.

每当调用 VideoFrameAvailable 处理程序时,都会使用 CopyFrameToVideoSurface 方法将帧的内容复制到 IDirect3DSurfaceWhenever the VideoFrameAvailable handler is called, the CopyFrameToVideoSurface method is used to copy the contents of the frame to an IDirect3DSurface. 还可以使用 CopyFrameToStereoscopicVideoSurfaces 将 3D 内容复制到两个图面,以便单独处理左眼和右眼内容。You can also use CopyFrameToStereoscopicVideoSurfaces to copy 3D content into two surfaces, for processing the left eye and right eye content separately. 为了获取实现 IDirect3DSurface 的对象,此示例创建一个 SoftwareBitmap,然后使用该对象创建实现所需接口的 Win2D CanvasBitmapTo get an object that implements IDirect3DSurface this example creates a SoftwareBitmap and then uses that object to create a Win2D CanvasBitmap, which implements the necessary interface. CanvasImageSource 是 Win2D 对象,可以用作 Image 控件的源,因此会新建一个并设置为将在其中显示内容的 Image 的源。A CanvasImageSource is a Win2D object that can be used as the source for an Image control, so a new one is created and set as the source for the Image in which the content will be displayed. 接下来创建一个 CanvasDrawingSessionNext, a CanvasDrawingSession is created. 这由 Win2D 用于呈现模糊效果。This is used by Win2D to render the blur effect.

实例化了所有所需对象之后,会调用 CopyFrameToVideoSurface,它将当前帧从 MediaPlayer 复制到 CanvasBitmap 中。Once all of the necessary objects have been instantiated, CopyFrameToVideoSurface is called, which copies the current frame from the MediaPlayer into the CanvasBitmap. 接下来,创建一个 Win2D GaussianBlurEffect,其 CanvasBitmap 设置为操作的源。Next, a Win2D GaussianBlurEffect is created, with the CanvasBitmap set as the source of the operation. 最后,调用 CanvasDrawingSession.DrawImage 以将应用了模糊效果的源图像绘制到与 Image 控件关联的 CanvasImageSource 中,从而在 UI 中绘制它。Finally, CanvasDrawingSession.DrawImage is called to draw the source image, with the blur effect applied, into the CanvasImageSource that has been associated with Image control, causing it to be drawn in the UI.

private async void mediaPlayer_VideoFrameAvailable(MediaPlayer sender, object args)
{
    CanvasDevice canvasDevice = CanvasDevice.GetSharedDevice();

    await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
    {
        if(frameServerDest == null)
        {
            // FrameServerImage in this example is a XAML image control
            frameServerDest = new SoftwareBitmap(BitmapPixelFormat.Rgba8, (int)FrameServerImage.Width, (int)FrameServerImage.Height, BitmapAlphaMode.Ignore);
        }
        if(canvasImageSource == null)
        {
            canvasImageSource = new CanvasImageSource(canvasDevice, (int)FrameServerImage.Width, (int)FrameServerImage.Height, DisplayInformation.GetForCurrentView().LogicalDpi);//96); 
            FrameServerImage.Source = canvasImageSource;
        }

        using (CanvasBitmap inputBitmap = CanvasBitmap.CreateFromSoftwareBitmap(canvasDevice, frameServerDest))
        using (CanvasDrawingSession ds = canvasImageSource.CreateDrawingSession(Windows.UI.Colors.Black))
        {

            mediaPlayer.CopyFrameToVideoSurface(inputBitmap);

            var gaussianBlurEffect = new GaussianBlurEffect
            {
                Source = inputBitmap,
                BlurAmount = 5f,
                Optimization = EffectOptimization.Speed
            };

            ds.DrawImage(gaussianBlurEffect);

        }
    });
}

private void FrameServerSubtitlesButton_Click(object sender, RoutedEventArgs e)
{

    mediaPlayer = new MediaPlayer();
    var source = MediaSource.CreateFromUri(new Uri("ms-appx:///Assets/example_video.mkv"));
    var item = new MediaPlaybackItem(source);

    item.TimedMetadataTracksChanged += Item_TimedMetadataTracksChanged;


    mediaPlayer.Source = item;
    mediaPlayer.VideoFrameAvailable += mediaPlayer_VideoFrameAvailable_Subtitle;
    mediaPlayer.IsVideoFrameServerEnabled = true;
    mediaPlayer.Play();

    mediaPlayer.IsMuted = true;

}

private void Item_TimedMetadataTracksChanged(MediaPlaybackItem sender, IVectorChangedEventArgs args)
{
    if(sender.TimedMetadataTracks.Count > 0)
    {
        sender.TimedMetadataTracks.SetPresentationMode(0, TimedMetadataTrackPresentationMode.PlatformPresented);
    }
}

private async void mediaPlayer_VideoFrameAvailable_Subtitle(MediaPlayer sender, object args)
{
    CanvasDevice canvasDevice = CanvasDevice.GetSharedDevice();

    await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
    {
        if (frameServerDest == null)
        {
            // FrameServerImage in this example is a XAML image control
            frameServerDest = new SoftwareBitmap(BitmapPixelFormat.Rgba8, (int)FrameServerImage.Width, (int)FrameServerImage.Height, BitmapAlphaMode.Ignore);
        }
        if (canvasImageSource == null)
        {
            canvasImageSource = new CanvasImageSource(canvasDevice, (int)FrameServerImage.Width, (int)FrameServerImage.Height, DisplayInformation.GetForCurrentView().LogicalDpi);//96); 
            FrameServerImage.Source = canvasImageSource;
        }

        using (CanvasBitmap inputBitmap = CanvasBitmap.CreateFromSoftwareBitmap(canvasDevice, frameServerDest))
        {
            using (CanvasDrawingSession ds = canvasImageSource.CreateDrawingSession(Windows.UI.Colors.Black))
            {

                mediaPlayer.CopyFrameToVideoSurface(inputBitmap);

                //Rect subtitleTargetRect = new Rect(0, 0, inputBitmap.Bounds.Width, inputBitmap.Bounds.Bottom * .1);
                Rect subtitleTargetRect = new Rect(0, 0, 100, 100);

                mediaPlayer.RenderSubtitlesToSurface(inputBitmap);//, subtitleTargetRect);

                //var gaussianBlurEffect = new GaussianBlurEffect
                //{
                //    Source = inputBitmap,
                //    BlurAmount = 5f,
                //    Optimization = EffectOptimization.Speed
                //};

                //ds.DrawImage(gaussianBlurEffect);

                ds.DrawImage(inputBitmap);
            }
        }
    });
}

有关 Win2D 的详细信息,请参阅 Win2D GitHub 存储库For more information on Win2D, see the Win2D GitHub repository. 若要尝试上面显示的示例代码,需要按照以下说明将 Win2D NuGet 程序包添加到项目。To try out the sample code shown above, you will need to add the Win2D NuGet package to your project with the following instructions.

将 Win2D NuGet 程序包添加到你的效果项目To add the Win2D NuGet package to your effect project

  1. 在“解决方案资源管理器”中,右键单击项目并选择“管理 NuGet 包”。 In Solution Explorer, right-click your project and select Manage NuGet Packages.
  2. 在窗口顶部,选择 " 浏览 " 选项卡。At the top of the window, select the Browse tab.
  3. 在搜索框中,输入 Win2DIn the search box, enter Win2D.
  4. 选择 " Win2D",然后在右窗格中选择 " 安装 "。Select Win2D.uwp, and then select Install in the right pane.
  5. " 查看更改 " 对话框显示要安装的程序包。The Review Changes dialog shows you the package to be installed. 单击 “确定”Click OK.
  6. 接受程序包许可证。Accept the package license.

检测系统的音频级别更改并做出响应Detect and respond to audio level changes by the system

从 Windows 10 版本 1803 开始,应用可检测到系统何时降低或静音当前播放的 MediaPlayer 的音频级别。Starting with Windows 10, version 1803, your app can detect when the system lowers or mutes the audio level of a currently playing MediaPlayer. 例如,当警报响起时,系统可能降低(或者“闪避”)音频播放级别。For example, the system may lower, or "duck", the audio playback level when an alarm is ringing. 如果应用没有在应用清单中声明 backgroundMediaPlayback 功能,系统将在应用进入后台时将其静音。The system will mute your app when it goes into the background if your app has not declared the backgroundMediaPlayback capability in the app manifest. AudioStateMonitor 类可用于注册以接收系统修改音频流的音量时出现的事件。The AudioStateMonitor class allows you to register to receive an event when the system modifies the volume of an audio stream. 访问 MediaPlayerAudioStateMonitor 属性并为 SoundLevelChanged事件注册处理程序,以在系统更改该 MediaPlayer 的音频级别时收到通知。Access the AudioStateMonitor property of a MediaPlayer and register a handler for the SoundLevelChanged event to be notified when the audio level for that MediaPlayer is changed by the system.

mediaPlayer.AudioStateMonitor.SoundLevelChanged += AudioStateMonitor_SoundLevelChanged;

在处理 SoundLevelChanged 事件时,可以根据正在播放的内容的类型采取不同的操作。When handling the SoundLevelChanged event, you may take different actions depending on the type of content being played. 如果当前正在播放音乐,可能希望在音量闪避时继续播放音乐。If you are currently playing music, then you may want to let the music continue to play while the volume is ducked. 但是,如果正在播放播客,那么可能希望在音频闪避时暂停播放,这样用户就不会错过任何内容。If you are playing a podcast, however, you likely want to pause playback while the audio is ducked so the user doesn't miss any of the content.

此示例声明一个变量,该变量跟踪当前播放的内容是否为播客,假设在为 MediaPlayer 选择内容时将其设置为适当的值。This example declares a variable to track whether the currently playing content is a podcast, it is assumed that you set this to the appropriate value when selecting the content for the MediaPlayer. 我们还创建类变量来跟踪在音频级别发生变化时何时以编程方式暂停播放。We also create a class variable to track when we pause playback programmatically when the audio level changes.

bool isPodcast;
bool isPausedDueToAudioStateMonitor;

SoundLevelChanged 事件处理程序中,检查 AudioStateMonitor 发件人的 SoundLevel 属性,以确定新的音量。In the SoundLevelChanged event handler, check the SoundLevel property of the AudioStateMonitor sender to determine the new sound level. 此示例检查新的音量是否为完整音量,即系统已停止静音或闪避音量,或者音量是否已经降低,但正在播放非播客内容。This example checks to see if the new sound level is full volume, meaning the system has stopped muting or ducking the volume, or if the sound level has been lowered but is playing non-podcast content. 如果发生其中任何一种情况,并且内容以前以编程方式暂停,则恢复播放。If either of these are true and the content was previously paused programmatically, playback is resumed. 如果新的音量为静音,或者如果当前内容是播客,并且音量较低,则暂停播放,并将变量设置为跟踪,暂停以编程方式启动。If the new sound level is muted or if the current content is a podcast and the sound level is low, playback is paused, and the variable is set to track that the pause was initiated programatically.

private void AudioStateMonitor_SoundLevelChanged(Windows.Media.Audio.AudioStateMonitor sender, object args)
{
    if ((sender.SoundLevel == SoundLevel.Full) || (sender.SoundLevel == SoundLevel.Low && !isPodcast))
    {
        if (isPausedDueToAudioStateMonitor)
        {
            mediaPlayer.Play();
            isPausedDueToAudioStateMonitor = false;
        }
    }
    else if ((sender.SoundLevel == SoundLevel.Muted) ||
         (sender.SoundLevel == SoundLevel.Low && isPodcast))
    {
        if (mediaPlayer.PlaybackSession.PlaybackState == MediaPlaybackState.Playing)
        {
            mediaPlayer.Pause();
            isPausedDueToAudioStateMonitor = true;
        }
    }

}

用户可以决定想要暂停还是继续播放,即使系统闪避音频。The user may decide that they want to pause or continue playback, even if the audio is ducked by the system. 此示例显示播放和暂停按钮的事件处理程序。This example shows event handlers for a play and a pause button. 在暂停按钮中单击处理程序暂停,如果播放已经以编程方式暂停,则更新变量以指示用户已暂停内容。In the pause button click handler is paused, if playback had already been paused programmatically, then we update the variable to indicate that the user has paused the content. 在播放按钮中单击处理程序,继续播放并清除跟踪变量。In the play button click handler, we resume playback and clear our tracking variable.

private void PauseButton_User_Click(object sender, RoutedEventArgs e)
{
    if (isPausedDueToAudioStateMonitor)
    {
        isPausedDueToAudioStateMonitor = false;
    }
    else
    {
        mediaPlayer.Pause();
    }
}

public void PlayButton_User_Click()
{
    isPausedDueToAudioStateMonitor = false;
    mediaPlayer.Play();
}