使用 MediaPlayer 播放音訊和視訊

本文說明如何使用 MediaPlayer 類別在通用 Windows 應用程式中播放媒體。 Windows 10 版本 1607 已大幅改善媒體播放 API,包括簡化的背景音訊單一程式設計、自動與系統媒體傳輸控制項 (SMTC) 整合、同步處理多個媒體播放程式的能力、將視訊畫面轉譯至 Windows.UI.Composition 介面的能力,以及輕鬆建立及排程內容中的媒體中斷介面。 若要利用這些改進功能,播放媒體的建議最佳做法是使用 MediaPlayer 類別,而不是 MediaElement 進行媒體播放。 引入了輕量級 XAML 控制項 MediaPlayerElement,讓您在 XAML 頁面中呈現媒體內容。 MediaElement 所提供的許多播放控制項和狀態 API 現在可透過新的 MediaPlaybackSession 物件取得。 MediaElement 會繼續運作以支援回溯相容性,但不會將其他功能新增至此類別。

本文將逐步引導您完成一般媒體播放應用程式將使用的 MediaPlayer 功能。 請注意,MediaPlayer 會使用 MediaSource 類別做為所有媒體專案的容器。 這個類別可讓您從許多不同的來源載入和播放媒體,包括本機檔案、記憶體串流和網路來源,全都使用相同的介面。 還有使用 MediaSource 的較高層級類別,例如 MediaPlaybackItemMediaPlaybackList,可提供更進階的功能,例如播放清單,以及使用多個音訊、視訊和中繼資料曲目管理媒體來源的能力。 如需 MediaSource 和相關 API 的詳細資訊,請參閱媒體項目、播放清單與曲目

注意

Windows 10 N 和 Windows 10 KN 版本不包含使用 MediaPlayer 播放所需的媒體功能。 這些功能可以手動安裝。 如需詳細資訊,請參閱 Windows 10 N 和 Windows 10 KN 版本的媒體功能套件

使用 MediaPlayer 播放媒體檔案

使用 MediaPlayer 執行的基本媒體播放非常簡單。 首先,建立 MediaPlayer 類別的新執行個體。 您的應用程式可以有多個 MediaPlayer 執行個體同時使用中。 接下來,將播放器的 Source 屬性設定為實作 IMediaPlaybackSource 的物件,例如 MediaSourceMediaPlaybackItemMediaPlaybackList。 在此範例中,MediaSource 是從應用程式本機儲存體中的檔案建立,然後從來源建立 MediaPlaybackItem,然後指派給播放器的 Source 屬性。

不同於 MediaElementMediaPlayer 預設不會自動開始播放。 您可以呼叫 Play、將 AutoPlay 屬性設定為 true,或等候使用者使用內建媒體控制項起始播放,以開始播放。

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

當您的應用程式使用 MediaPlayer 完成後,您應該呼叫 Close 方法 (在 C# 中投射為 Dispose) 來清理播放器使用的資源。

mediaPlayer.Dispose();

使用 MediaPlayerElement 在 XAML 中轉譯視訊

您可以在 MediaPlayer 播放媒體,而不要在 XAML 中顯示媒體,但許多媒體播放應用程式會想要在 XAML 頁面中轉譯媒體。 若要這樣做,請使用輕量型 MediaPlayerElement 控制項。 如同 MediaElementMediaPlayerElement 可讓您指定是否應該顯示內建傳輸控制項。

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

您可以藉由呼叫 SetMediaPlayer 來設定元素所繫結的 MediaPlayer 執行個體。

_mediaPlayerElement.SetMediaPlayer(mediaPlayer);

您也可以在 MediaPlayerElement上設定播放來源,該元素將自動建立新的 MediaPlayer 執行個體,您可以使用 MediaPlayer 屬性存取該執行個體。

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

注意

如果您藉由將 IsEnabled 設定為 false 來停用 MediaPlayerMediaPlaybackCommandManager,它會中斷 MediaPlayer 以及 MediaPlayerElement 提供之 TransportControls 間的連結,因此內建的傳輸控制項將不再自動控制播放程式的播放。 因此,您必須實作自己的控制項來控制 MediaPlayer

常見的 MediaPlayer 工作

本節說明如何使用 MediaPlayer 的某些功能。

設定音訊類別

MediaPlayerAudioCategory 屬性設定為 MediaPlayerAudioCategory 列舉的值之一,讓系統知道您正在播放哪種媒體。 遊戲應該將其音樂串流分類為 GameMedia,讓遊戲音樂在背景中播放音樂時自動靜音。 音樂或視訊應用程式應該將其串流分類為媒體電影,使其優先於 GameMedia 串流。

mediaPlayer.AudioCategory = MediaPlayerAudioCategory.Media;

輸出至特定音訊端點

在預設情況下,MediaPlayer 的音訊輸出會路由傳送至系統的預設音訊端點,但您可以指定 MediaPlayer 應該用於輸出的特定音訊端點。 在下列範例中,MediaDevice.GetAudioRenderSelector 會傳回唯一的字串,以唯一方式呈現裝置的音訊轉譯類別。 接下來,會呼叫 DeviceInformation 方法 FindAllAsync,以取得所選類型所有可用裝置的清單。 您可以以程式設計方式判斷要使用哪個裝置,或將傳回的裝置新增至 ComboBox,以允許使用者選取裝置。

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 屬性中。

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

播放工作階段

如本文先前所述,MediaElement 類別公開的許多函式已移至 MediaPlaybackSession 類別。 這包括播放程式播放狀態的相關資訊,例如目前的播放位置、播放程式是暫停還是播放,以及目前的播放速度。 MediaPlaybackSession 也會提供數個事件,讓您在狀態變更時通知您,包括正在播放內容的目前緩衝和下載狀態,以及目前播放視訊內容的自然大小和外觀比例。

下列範例示範如何實作在內容中略過 10 秒的按鈕按一下處理常式。 首先,會使用 PlaybackSession 屬性擷取播放器的 MediaPlaybackSession 物件。 接下來,Position 屬性會設定為目前的播放位置加上 10 秒。

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

下一個範例說明如何藉由設定工作階段的 PlaybackRate 屬性,使用切換按鈕在一般播放速度與 2X 速度之間切換。

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 度增量呈現視訊的旋轉方式。

mediaPlayer.PlaybackSession.PlaybackRotation = MediaRotation.Clockwise90Degrees;

偵測預期和非預期的緩衝

上一節所述的 MediaPlaybackSession 物件提供兩個事件來偵測目前播放的媒體檔案何時開始和結束緩衝處理、BufferingStartedBufferingEnded。 這可讓您更新 UI,以顯示正在緩衝處理的使用者。 當媒體檔案第一次開啟或使用者切換至播放清單中的新項目時,預期會進行初始緩衝處理。 當網路速度降低或提供內容的內容管理系統遇到技術問題時,可能會發生非預期的緩衝處理。 從 RS3 開始,您可以使用 BufferingStarted 事件來判斷緩衝事件是否預期或是否非預期且中斷播放。 您可以使用此資訊做為應用程式或媒體傳遞服務的遙測資料。

註冊 BufferingStartedBufferingEnded 事件的處理常式,以接收緩衝狀態通知。

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

BufferingStarted 事件處理常式中,將傳入事件的事件引數轉換為 MediaPlaybackSessionBufferingStartedEventArgs 物件,並檢查 IsPlaybackInterruption 屬性。 如果此值為 true,則觸發事件的緩衝處理是非預期的,而且會中斷播放。 否則,預期會進行初始緩衝處理。

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
}

捏合與縮放視訊

MediaPlayer 可讓您在應轉譯的視訊內容中指定來源矩形,有效地讓您放大視訊。 您指定的矩形是相對於標準化矩形 (0,0,1,1),其中 0,0 是畫面的左上角,1,1 指定畫面的完整寬度和高度。 例如,若要設定縮放矩形,讓視訊的右上方象限轉譯,您可以指定矩形 (.5,0,.5,.5)。 請務必檢查值,以確定來源矩形位於 (0,0,1,1) 正規化矩形內。 嘗試設定超出此範圍的值會導致擲回例外狀況。

若要使用多點觸控手勢實作捏合和縮放,您必須先指定您想要支援的手勢。 在此範例中,會要求縮放和翻譯手勢。 當其中一個已訂閱的手勢發生時,會引發 ManipulationDelta 事件。 DoubleTapped 事件將用來將縮放重設為完整畫面。

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

接下來,宣告將儲存目前縮放來源矩形的 Rect 物件。

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

ManipulationDelta 處理常式會調整縮放矩形的縮放比例或翻譯。 如果差異小數字數值不是 1,表示使用者執行捏合手勢。 如果值大於 1,則來源矩形應該較小以放大內容。 如果值小於 1,則來源矩形應該放大以縮小。設定新的刻度值之前,會檢查產生的矩形,以確保其完全位於 (0,0,1,1) 限制內。

如果縮放值為 1,則會處理翻譯手勢。 矩形只會由手勢中的像素數除以控制項的寬度和高度來翻譯。 同樣地,會檢查產生的矩形,以確定其位於 (0,0,1,1) 界限內。

最後,MediaPlaybackSessionNormalizedSourceRect 會設定為新調整的矩形,並指定應該轉譯之視訊畫面內的區域。

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),導致轉譯整個視訊畫面。

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

注意:本節描述觸控輸入。 觸控板會傳送指標事件,且不會傳送 Manipulation 事件。

處理以原則為基礎的播放降低

在某些情況下,系統會根據原則而非效能問題來降低媒體專案的播放,例如減少解析度 (限制)。 例如,如果使用未簽署的視訊驅動程式播放視訊,系統可能會降級視訊。 您可以呼叫 MediaPlaybackSession.GetOutputDegradationPolicyState 來確定是否以及為何發生這種基於原則的降級,並提醒使用者或記錄原因以用於遙測目的。

下列範例示範當播放程序開啟新媒體專案時所引發之 MediaPlayer.MediaOpened 事件的處理常式實作。 在傳遞至處理常式的 MediaPlayer 上呼叫 GetOutputDegradationPolicyStateVideoConstrictionReason 的值表示視訊受限制的原則原因。 如果值不是 None,此範例會記錄遙測用途的降級原因。 此範例也會顯示目前播放 AdaptiveMediaSource 的位元速率設定為最低的頻寬,以節省資料使用量,因為視訊會受到限制,而且無論如何都不會以高解析度顯示。 如需使用AdaptiveMediaSource 的詳細資訊,請參閱自適性串流

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 介面

從 Windows 10 版本 1607 開始,您可以使用 MediaPlayer 將視訊轉譯,以將視訊轉譯為 ICompositionSurface,讓播放器能夠與 Windows.UI.Composition 命名空間中的 API 交互操作。 組合架構可讓您在 XAML 與低階 DirectX 圖形 API 之間的視覺層中使用圖形。 這可讓您將視訊轉譯成任何 XAML 控制項等案例。 如需使用組合 API 的詳細資訊,請參閱視覺層

下列範例說明如何將視訊播放程式內容轉譯至 Canvas 控制項。 此範例中的媒體播放器特定呼叫是 SetSurfaceSizeGetSurfaceSetSurfaceSize 會告訴系統應該配置用於轉譯內容的緩衝區大小。 GetSurface會採用 Compositor 做為引數,並取回 MediaPlayerSurface 類別的執行個體。 這個類別可讓您存取用來建立介面的 MediaPlayerCompositor,並透過 CompositionSurface 屬性公開介面本身。

此範例中的其餘程式碼會建立轉譯視訊的 SpriteVisual,並將顯示視覺效果的畫布元素大小設定為大小。 接下來,CompositionBrush 會從 MediaPlayerSurface 建立,並指派給視覺效果的 Brush 屬性。 接下來會建立 ContainerVisual,並將 SpriteVisual 插入其視覺化樹狀結構頂端。 最後,會呼叫 SetElementChildVisual,將容器視覺效果指派給 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 跨多個播放器同步處理內容。

如本文先前所述,您的應用程式一次可以有數個 MediaPlayer 物件使用中。 在預設情況下,您建立的每個 MediaPlayer 都會獨立運作。 對於某些案例,例如將解說曲目同步到視訊中,您可能會想要同步多個播放器的播放器狀態、播放位置和播放速度。 從 Windows 10 版本 1607 開始,您可以使用 MediaTimelineController 類別來實作此行為。

實作播放控制項

下列範例示範如何使用 MediaTimelineController 來控制 MediaPlayer 的兩個執行個案。 首先,會具現化 MediaPlayer 的每個執行個案,並將 Source 設定為媒體檔案。 接下來,會建立新的 MediaTimelineController。 針對每個 MediaPlayer,將 IsEnabled 屬性設定為 false,會停用與每個播放器相關聯的 MediaPlaybackCommandManager。 然後 TimelineController 屬性會設定為時間軸控制器物件。

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 控制的媒體播放器搭配使用。 因此,您必須先停用媒體播放器的命令管理員,才能設定播放器的時間軸控制器。 若無法這麼做,將會導致擲回例外狀況,並出現下列訊息:「附加媒體時間軸控制器因為物件的目前狀態而遭到封鎖」。如需媒體播放器與 SMTC 整合的詳細資訊,請參閱與系統媒體傳輸控制項整合。 如果您使用 MediaTimelineController,您仍然可以手動控制 SMTC。 有關詳細資訊,請參閱系統媒體傳輸控制器的手動控制項

MediaTimelineController 附加至一或多個媒體播放器之後,您可以使用控制器公開的方法來控制播放狀態。 下列範例會呼叫 Start,開始在媒體開頭播放所有相關聯的媒體播放器。

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

此範例說明暫停和繼續所有附加媒體播放器。

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 的值。

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

下一個範例示範如何使用 Slider 控制項來顯示時間軸控制器的目前播放位置,相對於其中一個連線媒體播放器的內容持續時間。 首先,會建立新的 MediaSource,並註冊媒體來源的 OpenOperationCompleted 處理常式。

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

OpenOperationCompleted 處理常式可用來探索媒體來源內容的持續時間。 判斷持續時間之後,Slider 控制項的最大值會設定為媒體項目的總秒數。 此值是在對 RunAsync 的呼叫內設定,以確保它在 UI 執行緒上執行。

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 事件的處理常式。 系統會定期呼叫此作業,大約每秒 4 次。

_mediaTimelineController.PositionChanged += _mediaTimelineController_PositionChanged;

PositionChanged 的處理常式中,滑桿值會更新,以反映時間軸控制器的目前位置。

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

從時間軸位置位移播放位置

在某些情況下,您可能會想要讓與時間軸控制器相關聯的一或多個媒體播放器的播放位置與其他播放器位移。 您可以藉由設定要位移之 MediaPlayer 物件的 TimelineControllerPositionOffset 屬性來執行此動作。 下列範例會使用兩個媒體播放器內容的持續時間,將兩個滑桿控制器的最小值和最大值設定為加減項目的長度。

_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 都會設定為對應的值。

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

請注意,如果播放器的位移值對應到負播放位置,短片會保持暫停,直到位移達到零,然後播放將會開始。 同樣地,如果位移值對應到大於媒體項目持續時間的播放位置,則會顯示最終畫面,就像單一媒體播放器到達內容結尾時一樣。

使用 MediaPlayer 播放球形視訊

從 Windows 10 版本 1703 開始,MediaPlayer 支援球形視訊播放的等號投影。 球形視訊內容與常規的平面視訊沒有什麼不同,只要支援視訊編碼,MediaPlayer 就會轉譯視訊。 對於包含中繼資料標記的球形視訊,指定視訊使用等距投影,MediaPlayer 可以使用指定的視野和檢視方向轉譯視訊。 這使得諸如使用頭戴式顯示器進行虛擬實境視訊播放,或簡單地允許使用者使用滑鼠或鍵盤輸入在球形視訊內容中平移等案例成為可能。

若要播放球形視頻,請使用本文前面描述的播放視訊內容的步驟。 另一個步驟是註冊 MediaPlayer.MediaOpened 事件的處理常式。 此事件可讓您啟用和控制球形視訊播放參數。

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 屬性,以檢查新開啟媒體項目的畫面格式。 如果此值為 SphericaVideoFrameFormat.Equirectangular,則系統可以自動投影視訊內容。 首先,將 PlaybackSession.SphericalVideoProjection.IsEnabled 屬性設定為 true。 您也可以調整媒體播放器用於投影視訊內容的屬性,例如檢視方向和視野。 在此範例中,透過設定 HorizontalFieldOfViewInDegrees 屬性,將視野設定為 120 度的寬值。

如果視訊內容是球形的,但格式不是等距矩形,您可以使用媒體播放器的畫面伺服器模式來接收和處理各個畫面來實作自己的投影演算法。

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

下列範例程式碼說明如何使用向左鍵和向右鍵調整球形視訊檢視方向。

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 中識別包含球形視訊的播放項目。 媒體播放清單會在文章、媒體項目、播放清單與曲目中詳細討論。 下列範例示範建立新的播放清單、新增項目,以及註冊 MediaPlaybackItem.VideoTracksChanged 事件的處理常式,此事件會在解析媒體項目的視訊曲目時發生。

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 來取得任何新增視訊曲目的編碼屬性。 如果編碼屬性的 SphericalVideoFrameFormat 屬性是 SphericaVideoFrameFormat.None 以外的值,則視訊曲目包含球形視頻,您可以選擇相應地更新 UI。

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

在畫面伺服器模式中使用 MediaPlayer

從 Windows 10 版本 1703 開始,您可以在畫面伺服器模式中使用 MediaPlayer。 在此模式中,MediaPlayer 不會自動將畫面轉譯至相關聯的 MediaPlayerElement。 相反地,您的應用程式會將目前畫面從 MediaPlayer 複製到實作 IDirect3DSurface 的物件。 此功能啟用的主要案例是使用像素著色器來處理 MediaPlayer 所提供的視訊畫面。 您的應用程式負責在處理之後顯示每個畫面,例如在 XAML 影像控制項中顯示畫面。

在下列範例中,會初始化新的 MediaPlayer,並載入視訊內容。 接下來,會註冊 VideoFrameAvailable 的處理常式。 將 MediaPlayer 物件的 IsVideoFrameServerEnabled 屬性設定為 true,以啟用畫面伺服器模式。 最後,媒體播放會以呼叫 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 影像控制項中顯示已處理的畫面。

每當呼叫 VideoFrameAvailable 處理常式時,CopyFrameToVideoSurface 方法會用來將畫面的內容複製到 IDirect3DSurface。 您也可以使用 CopyFrameToStereoscopicVideoSurfaces 將 3D 內容複製到兩個表面,以便分別處理左眼和右眼內容。 若要取得實作 IDirect3DSurface 的物件,本範例會建立 SoftwareBitmap,然後使用該物件來建立 Win2D CanvasBitmap,以實作必要的介面。 CanvasImageSource 是 Win2D 物件,可用來做為 Image 控制項的來源,因此會建立新的物件,並設定為顯示內容的 Image 來源。 接下來,會建立 CanvasDrawingSession。 這是 Win2D 用來轉譯模糊效果。

一旦具現化所有必要的物件之後,會呼叫 CopyFrameToVideoSurface,它會將目前畫面從 MediaPlayer 複製到 CanvasBitmap。 接下來,會建立 Win2D GaussianBlurEffect,並將 CanvasBitmap 設定為作業的來源。 最後,呼叫 CanvasDrawingSession.DrawImage 來繪製已套用模糊效果的來源影像到與 Image 控制項相關聯的 CanvasImageSource 中,導致它在 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 儲存庫。 若要試用上述範例程式碼,您必須使用下列指示,將 Win2D NuGet 套件新增至您的專案。

將 Win2D NuGet 套件新增到效果專案

  1. [方案總管] 中,以滑鼠右鍵按一下專案,然後選取 [管理 NuGet 套件]
  2. 在視窗頂部,選取瀏覽索引標籤。
  3. 在搜尋方塊中輸入 Win2D
  4. 選取 Win2D.uwp,然後選取右窗格中的安裝
  5. 檢閱變更對話方塊會顯示要安裝的套件。 按一下 [確定]
  6. 接受套件授權。

偵測並回應系統的音訊層級變更

從 Windows 10 版本 1803 開始,您的應用程式可以偵測系統何時降低或靜音目前播放 MediaPlayer 的音訊層級。 例如,當警示響起時,系統可能會降低或「躲避」音訊播放層級。 如果您的應用程式未在應用程式清單中宣告 backgroundMediaPlayback 功能,系統將在您的應用程式進入背景時將其靜音。 AudioStateMonitor 類別可讓您註冊,以在系統修改音訊串流音量時接收事件。 存取 MediaPlayerAudioStateMonitor 屬性,並註冊 SoundLevelChanged 事件的處理常式,以在系統變更 MediaPlayer 的音訊層級時收到通知。

mediaPlayer.AudioStateMonitor.SoundLevelChanged += AudioStateMonitor_SoundLevelChanged;

處理 SoundLevelChanged 事件時,您可以根據所播放的內容類型採取不同的動作。 如果您目前正在播放音樂,則您可能想要讓音樂在音量被躲避時繼續播放。 不過,如果您正在播放播客,您可能會想要暫停播放,而音訊會遭到躲避,讓使用者不會錯過任何內容。

此範例宣告一個變數來追蹤目前播放的內容是否為播客,假設您在為 MediaPlayer 選取內容時將其設定為適當的值。 我們也會建立類別變數,以在音訊層級變更時,以程式設計方式暫停播放。

bool isPodcast;
bool isPausedDueToAudioStateMonitor;

SoundLevelChanged 事件處理常式中,檢查 AudioStateMonitor 傳送方的 SoundLevel 屬性以決定新的聲音等級。 本範例會檢查新的聲音等級是否為完整音量,這表示系統已停止靜音或躲避音量,或聲音等級是否已降低,但正在播放非播客內容。 如果其中一項為 true,且內容先前是以程式設計方式暫停,則會繼續播放。 如果新的聲音等級為靜音,或目前的內容是播客,且聲音等級很低,則會暫停播放,且變數設定為追蹤已以程序設計方式起始暫停。

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

}

使用者可能會決定要暫停或繼續播放,即使音訊被系統躲避也一樣。 此範例顯示播放和暫停按鈕的事件處理常式。 在暫停按鈕按下處理常式中,如果已以程式設計方式暫停播放,我們會更新變數,指出使用者已暫停內容。 在播放按鈕按一下處理常式中,我們會繼續播放並清除追蹤變數。

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