使用 MediaPlayer 播放音訊和視訊Play audio and video with MediaPlayer

本文說明如何使用 MediaPlayer 類別在您的通用 Windows app 中播放媒體。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 類別來播放媒體,而不是 MediaElementTo 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.

本文會逐步說明一般媒體播放 App 中將使用的 MediaPlayer 功能。This article will walk you through the MediaPlayer features that a typical media playback app will use. 請注意,MediaPlayer 對於所有媒體項目都是使用 MediaSource 類別當作容器。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 版本的 Media Feature PackFor 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 的物件,例如 MediaSourceMediaPlaybackItem,或 MediaPlaybackListNext, set the Source property of the player to an object that implements the IMediaPlaybackSource, such as a MediaSource, a MediaPlaybackItem, or a MediaPlaybackList. 在此範例中,MediaSource 是由 App 本機存放區中的檔案建立,然後 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.

不同於 MediaElementMediaPlayer 預設不會自動開始播放。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();

當您的 App 不再使用 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();

在 XAML 中使用 MediaPlayerElement 轉譯視訊Use MediaPlayerElement to render video in XAML

您可以在 MediaPlayer 中播放媒體,而不在 XAML 中顯示,但許多媒體播放 app 將會想要在 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,它將會破壞 MediaPlayer 和由 MediaPlayerElement 所提供的 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. 首先,播放器的 MediaPlaybackSession 物件是和 PlaybackSession 屬性一同抓取。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 物件提供兩個事件來偵測目前播放媒體檔案何時開始與結束緩衝:BufferingStartedBufferingEndedThe 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. GetSurface 會接受 Compositor 作為引數,並抓取 MediaPlayerSurface 類別的執行個體。GetSurface takes a Compositor as an arguemnt and retreives an instance of the MediaPlayerSurface class. 這個類別會提供 MediaPlayerCompositor 的存取權,以用來建立表面,並透過 CompositionSurface 屬性來公開表面本身。This 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.

如本文中先前所討論,您的 App 可以同時有數個作用中的 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;

注意MediaPlaybackCommandManager 提供 MediaPlayer 和系統媒體傳輸控制項 (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支援進行球面視訊播放的等距長方投影。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. 如果球面視訊包含指定視訊使用等距長方投影的中繼資料標記,則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. MediaOpened 事件的處理常式。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. 在此範例中,視野範圍設定為 120 度的寬幅值,方法是設定HorizontalFieldOfViewInDegrees屬性。In this example, the field of view is set to a wide value of 120 degrees by setting the HorizontalFieldOfViewInDegrees property.

如果視訊內容是球面,但為等距長方以外的格式,則您可以使用媒體播放程式的畫面伺服器模式實作自己的投影演算法,來接收和處理個別畫面。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.GetEncodingPropertiesIn 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不會自動向相關MediaPlayerElement轉譯畫面。In 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屬性設定為trueFrame 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 CanvasBitmap,以實作所需的介面。To 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是可作為Image控制項來源的 Win2D 物件,因此會建立新項目,並將其設定為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複製到CanvasBitmapOnce 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. 如果其中一項為 true 且先前已以程式設計方式暫停內容,將會繼續播放。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();
}