媒體項目、播放清單與曲目

本文說明如何使用 MediaSource 類別,其提供從本機或遠端檔案等不同來源參考和播放媒體的常見方式,並公開存取媒體資料的通用模型,而不論基礎媒體格式為何。 MediaPlaybackItem 類別擴展了 MediaSource 的功能,可讓您管理媒體項目中包含的多個音訊、視訊和中繼資料曲目並進行選取。 MediaPlaybackList 可讓您從一個或多個媒體播放項目建立播放清單。

建立及播放 MediaSource

透過呼叫該類別公開的處理站方法之一來建立 MediaSource 的新執行個體:

建立 MediaSource 後,您可以透過設定 Source 屬性來使用 MediaPlayer 播放它。 從 Windows 10 版本 1607 開始,可以透過呼叫 SetMediaPlayerMediaPlayer 指派給 MediaPlayerElement,以便在 XAML 頁面中呈現媒體播放器內容。 這是勝過使用 MediaElement 的首選方法。 有關使用 MediaPlayer 的詳細資訊,請參閱使用 MediaPlayer 播放音訊和視訊

以下範例示範如何使用 MediaSourceMediaPlayer 中播放使用者選取的媒體檔案。

您將需要包含 Windows.Media.CoreWindows.Media.Playback 命名空間才能完成此案例。

using Windows.Media.Core;
using Windows.Media.Playback;

宣告 MediaSource 類型的變數。 針對本文中的範例,媒體來源會宣告為類別成員,以便從多個位置存取它。

MediaSource _mediaSource;

宣告一個變數來儲存 MediaPlayer 物件,如果要在 XAML 中呈現媒體內容,請將 MediaPlayerElement 控制項新增至頁面。

MediaPlayer _mediaPlayer;
<MediaPlayerElement x:Name="mediaPlayerElement"/>

若要允許使用者選擇要播放的媒體檔案,請使用 FileOpenPicker。 使用從選取器的 PickSingleFileAsync 方法傳回的 StorageFile 物件,透過呼叫 MediaSource.CreateFromStorageFile 來初始化新的 MediaObject。 最後,透過呼叫 SetPlaybackSource 方法將媒體來源設定為 MediaElement 的播放來源。

//Create a new picker
var filePicker = new Windows.Storage.Pickers.FileOpenPicker();

//make a collection of all video types you want to support (for testing we are adding just 3).
string[] fileTypes = new string[] {".wmv", ".mp4", ".mkv"};   
   
//Add your fileTypes to the FileTypeFilter list of filePicker.
foreach (string fileType in fileTypes)
{
    filePicker.FileTypeFilter.Add(fileType);
}

//Set picker start location to the video library
filePicker.SuggestedStartLocation = PickerLocationId.VideosLibrary;

//Retrieve file from picker
StorageFile file = await filePicker.PickSingleFileAsync();

if (!(file is null))
{
    _mediaSource = MediaSource.CreateFromStorageFile(file);
    _mediaPlayer = new MediaPlayer();
    _mediaPlayer.Source = _mediaSource;
    mediaPlayerElement.SetMediaPlayer(_mediaPlayer);
}

預設情況下,設定媒體來源後,MediaPlayer 不會自動開始播放。 您可以呼叫 Play 手動開始播放。

_mediaPlayer.Play();

您也可以將 MediaPlayerAutoPlay 屬性設為 true,以告訴播放器在設定媒體來源後立即開始播放。

_mediaPlayer.AutoPlay = true;

從 DownloadOperation 建立 MediaSource

從 Windows 版本 1803 開始,您可以從 DownloadOperation 建立 MediaSource 物件。

StorageFile destinationFile = await KnownFolders.VideosLibrary.CreateFileAsync("file.mp4", CreationCollisionOption.GenerateUniqueName);

var downloader = new BackgroundDownloader();
var downloadOperation = downloader.CreateDownload(new Uri("http://server.com/file.mp4"), destinationFile);
MediaSource mediaSource =
      MediaSource.CreateFromDownloadOperation(downloadOperation);

請注意,雖然您可以從下載中建立 MediaSource,而無需啟動它或將其 IsRandomAccessRequired 屬性設為 true,但在嘗試將 MediaSource 附加到 MediaPlayerMediaPlayerElement 播放之前,您必須執行這兩件事。

downloadOperation.IsRandomAccessRequired = true;
var startAsyncTask = downloadOperation.StartAsync().AsTask();
mediaPlayerElement.Source = mediaSource;

使用 MediaPlaybackItem 處理多個音訊、視訊和中繼資料曲目

使用 MediaSource 進行播放很方便,因為它提供了一種從不同類型的來源播放媒體的通用方法,但可以透過從 MediaSource 建立 MediaPlaybackItem 來存取更進階的行為。 這包括存取和管理媒體項目的多個音訊、視訊和資料曲目的能力。

宣告變數以儲存 MediaPlaybackItem

MediaPlaybackItem _mediaPlaybackItem;

透過呼叫建構函式並傳入初始化的 MediaSource 物件來建立 MediaPlaybackItem

如果您的應用程式支援媒體播放項目中的多個音訊、視訊或資料曲目,請為 AudioTracksChangedVideoTracksChangedTimedMetadataTracksChanged 事件註冊事件處理常式。

最後,將 MediaElementMediaPlayer 的播放來源設定為 MediaPlaybackItem

_mediaSource = MediaSource.CreateFromStorageFile(file);
_mediaPlaybackItem = new MediaPlaybackItem(_mediaSource);

_mediaPlaybackItem.AudioTracksChanged += PlaybackItem_AudioTracksChanged;
_mediaPlaybackItem.VideoTracksChanged += MediaPlaybackItem_VideoTracksChanged;
_mediaPlaybackItem.TimedMetadataTracksChanged += MediaPlaybackItem_TimedMetadataTracksChanged;

_mediaPlayer = new MediaPlayer();
_mediaPlayer.Source = _mediaPlaybackItem;
mediaPlayerElement.SetMediaPlayer(_mediaPlayer);

注意

MediaSource 只能與單一 MediaPlaybackItem 關聯。 從來源建立 MediaPlaybackItem 之後,嘗試從相同來源建立另一個播放項目會導致錯誤。 此外,從媒體來源建立 MediaPlaybackItem 後,您無法直接將 MediaSource 物件設定為 MediaPlayer 的來源,而應使用 MediaPlaybackItem

VideoTracksChanged 事件在包含多個視訊曲目的 MediaPlaybackItem 被指定為播放來源後引發,如果項目的視訊曲目清單發生變更,則可以再次引發該事件。 此事件的處理常式可讓您更新 UI,讓使用者在可用的曲目之間切換。 此範例會使用 ComboBox 來顯示可用的視訊曲目。

<ComboBox x:Name="videoTracksComboBox" SelectionChanged="videoTracksComboBox_SelectionChanged"/>

VideoTracksChanged 處理常式中,循環播放播放項目的 VideoTracks 清單中的所有曲目。 針對每個曲目,會建立新的 ComboBoxItem。 如果追蹤還沒有標籤,則會從追蹤索引產生標籤。 下拉式方塊項目的 Tag 屬性設定為曲目索引,以便以後可以識別。 最後,項目會新增至下拉式方塊。 請注意,這些作業是在 CoreDispatcher.RunAsync 呼叫內執行,因為 UI 執行緒上必須進行所有 UI 變更,而且此事件會在不同的執行緒上引發。

private async void MediaPlaybackItem_VideoTracksChanged(MediaPlaybackItem sender, IVectorChangedEventArgs args)
{
    await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
    {
        videoTracksComboBox.Items.Clear();
        for (int index = 0; index < sender.VideoTracks.Count; index++)
        {
            var videoTrack = sender.VideoTracks[index];
            ComboBoxItem item = new ComboBoxItem();
            item.Content = String.IsNullOrEmpty(videoTrack.Label) ? $"Track {index}" : videoTrack.Label;
            item.Tag = index;
            videoTracksComboBox.Items.Add(item);
        }
    });
}

在下拉式方塊的 SelectionChanged 處理常式中,從所選項目的 Tag 屬性中擷取曲目索引。 設定媒體播放項目的 VideoTracks 清單的 SelectedIndex 屬性,會導致 MediaElementMediaPlayer 將使用中視訊曲目切換到指定索引。

private void videoTracksComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
    int trackIndex = (int)((ComboBoxItem)((ComboBox)sender).SelectedItem).Tag;
    _mediaPlaybackItem.VideoTracks.SelectedIndex = trackIndex;
}

管理具有多個音訊曲目的媒體項目與管理視訊曲目完全相同。 處理 AudioTracksChanged 以使用播放項目的 AudioTracks 清單中找到的音訊曲目更新您的 UI。 當使用者選取音訊曲目時,設定 AudioTracks 清單的 SelectedIndex 屬性以使 MediaElementMediaPlayer 將使用中音訊曲目切換到指定索引。

<ComboBox x:Name="audioTracksComboBox" SelectionChanged="audioTracksComboBox_SelectionChanged"/>
private async void PlaybackItem_AudioTracksChanged(MediaPlaybackItem sender, IVectorChangedEventArgs args)
{
    await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
    {
        audioTracksComboBox.Items.Clear();
        for (int index = 0; index < sender.AudioTracks.Count; index++)
        {
            var audioTrack = sender.AudioTracks[index];
            ComboBoxItem item = new ComboBoxItem();
            item.Content = String.IsNullOrEmpty(audioTrack.Label) ? $"Track {index}" : audioTrack.Label;
            item.Tag = index;
            videoTracksComboBox.Items.Add(item);
        }
    });
}
private void audioTracksComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
    int trackIndex = (int)((ComboBoxItem)((ComboBox)sender).SelectedItem).Tag;
    _mediaPlaybackItem.AudioTracks.SelectedIndex = trackIndex;
}

除了音訊和視訊之外,MediaPlaybackItem 物件還可以包含零個或多個 TimedMetadataTrack 物件。 計時中繼資料曲目可以包含副標題或標題文字,或可能包含應用程式專屬的自訂資料。 計時中繼資料曲目包含由繼承自 IMediaCue 的物件表示的提示清單,例如 DataCueTimedTextCue。 每個提示都有開始時間和持續時間,可決定提示何時啟動,以及時間長度。

與音訊曲目和視訊曲目類似,可以透過處理 MediaPlaybackItemTimedMetadataTracksChanged 事件來發現媒體項目的計時中繼資料曲目。 不過,使用計時中繼資料曲目時,使用者可能想要一次啟用多個中繼資料追蹤。 此外,視您的應用程式案例而定,您可能想要自動啟用或停用中繼資料追蹤,而不需使用者介入。 出於說明目的,此範例為媒體項目中的每個中繼資料曲目新增一個 ToggleButton,以允許使用者啟用和停用曲目。每個按鈕的 Tag 屬性設定為關聯中繼資料曲目的索引,以便在切換按鈕時可以識別它。

<StackPanel x:Name="MetadataButtonPanel" Orientation="Horizontal"/>
private async void MediaPlaybackItem_TimedMetadataTracksChanged(MediaPlaybackItem sender, IVectorChangedEventArgs args)
{
    await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
    {
        for (int index = 0; index < sender.TimedMetadataTracks.Count; index++)
        {
            var timedMetadataTrack = sender.TimedMetadataTracks[index];

            ToggleButton toggle = new ToggleButton()
            {
                Content = String.IsNullOrEmpty(timedMetadataTrack.Label) ? $"Track {index}" : timedMetadataTrack.Label,
                Tag = (uint)index
            };
            toggle.Checked += Toggle_Checked;
            toggle.Unchecked += Toggle_Unchecked;

            MetadataButtonPanel.Children.Add(toggle);
        }
    });
}

因為一次可以有一個以上的中繼資料追蹤,因此您不只設定中繼資料追蹤清單的使用中索引。 相反地,呼叫 MediaPlaybackItem 物件的 SetPresentationMode 方法,傳入要切換的曲目的索引,然後提供TimedMetadataTrackPresentationMode 列舉中的值。 您選擇的呈現模式取決於您的應用程式的實作。 在此範例中,中繼資料曲目在啟用時設定為 PlatformPresented。 對於文字型的曲目,這意味著系統將自動在曲目中顯示文字提示。當切換按鈕關閉時,呈現模式將設定為 Disabled,這意味著不顯示任何文字,也不會引發任何提示事件。 本文稍後會討論提示事件。

private void Toggle_Checked(object sender, RoutedEventArgs e) =>         
    _mediaPlaybackItem.TimedMetadataTracks.SetPresentationMode((uint)((ToggleButton)sender).Tag,
        TimedMetadataTrackPresentationMode.PlatformPresented);
private void Toggle_Unchecked(object sender, RoutedEventArgs e) =>         
    _mediaPlaybackItem.TimedMetadataTracks.SetPresentationMode((uint)((ToggleButton)sender).Tag,
        TimedMetadataTrackPresentationMode.Disabled);

當您處理中繼資料曲目時,您可以透過存取 CuesActiveCues 屬性來存取曲目內的提示集。 您可以這麼做來更新 UI,以顯示媒體項目的提示位置。

開啟媒體項目時處理不支援的轉碼器和未知的錯誤

從 Windows 10 版本 1607 開始,您可以檢查應用程式執行所在的裝置上是否支援播放媒體項目所需的轉碼器。 在 MediaPlaybackItem 曲目變更事件 (例如 AudioTracksChanged) 的事件處理常式中,首先檢查曲目變更是否為新曲目的插入。如果是這樣,您可以透過使用 IVectorChangedEventArgs.Index 參數中傳遞的索引以及 MediaPlaybackItem 參數的相應曲目集合 (例如 AudioTracks 集合) 來取得要插入的曲目的參考。

取得插入曲目的參考後,請檢查曲目的 SupportInfo 屬性的 DecoderStatus。 如果值為 FullySupported,則播放播放曲目所需的適當轉碼器會出現在裝置上。 如果值為 Degraded,則系統可以播放曲目,但播放會以某種方式降級。 例如,5.1 音訊曲目可能會改成 2 通道立體聲播放。 如果是這種情況,您可能會想要更新 UI,以警示使用者降級的問題。 如果該值為 UnsupportedSubtypeUnsupportedEncoderProperties,則根本無法使用裝置上的目前轉碼器播放該曲目。 您可能想要提醒使用者並略過項目的播放,或實作 UI 以允許使用者下載正確的轉碼器。 曲目的 GetEncodingProperties 方法可用來判斷播放所需的轉碼器。

最後,您可以註冊追蹤的 OpenFailed 事件,如果裝置上支援追蹤,但因為管線中未知的錯誤而無法開啟,將會引發此事件。

private async void SnippetAudioTracksChanged_CodecCheck(MediaPlaybackItem sender, IVectorChangedEventArgs args)
{
    if (args.CollectionChange == CollectionChange.ItemInserted)
    {
        var insertedTrack = sender.AudioTracks[(int)args.Index];

        var decoderStatus = insertedTrack.SupportInfo.DecoderStatus;
        if (decoderStatus != MediaDecoderStatus.FullySupported)
        {
            if (decoderStatus == MediaDecoderStatus.Degraded)
            {
                ShowMessageToUser($"Track {insertedTrack.Name} can play but playback will be degraded. {insertedTrack.SupportInfo.DegradationReason}");
            }
            else
            {
                // status is MediaDecoderStatus.UnsupportedSubtype or MediaDecoderStatus.UnsupportedEncoderProperties
                ShowMessageToUser($"Track {insertedTrack.Name} uses an unsupported media format.");
            }

            Windows.Media.MediaProperties.AudioEncodingProperties props = insertedTrack.GetEncodingProperties();
            await HelpUserInstallCodec(props);
        }
        else
        {
            insertedTrack.OpenFailed += InsertedTrack_OpenFailed;
        }
    }

}

OpenFailed 事件處理常式中,您可以檢查 MediaSource 狀態是否未知,如果是,則可以透過程式設計方式選取要播放的不同曲目、允許使用者選擇不同的曲目或放棄播放。

private async void InsertedTrack_OpenFailed(AudioTrack sender, AudioTrackOpenFailedEventArgs args)
{
    LogError(args.ExtendedError.HResult);

    if (sender.SupportInfo.MediaSourceStatus == MediaSourceStatus.Unknown)
    {
        await SelectAnotherTrackOrSkipPlayback(sender.PlaybackItem);
    }
}

設定系統媒體傳輸控制項所使用的顯示屬性

從 Windows 10 版本 1607 開始,預設情況下,MediaPlayer 中播放的媒體會自動整合到系統媒體傳輸控制項 (SMTC) 中。 您可以更新 MediaPlaybackItem 的顯示屬性,以指定 SMTC 所顯示的中繼資料。 透過呼叫 GetDisplayProperties 來取得表示項目顯示屬性的物件。 透過設定 Type 屬性來設定播放項目是音樂還是影片。 然後,設定物件的 VideoPropertiesMusicProperties 的屬性。 呼叫 ApplyDisplayProperties,將項目的屬性更新為您提供的值。 一般而言,應用程式會從 Web 服務動態擷取顯示值,但下列範例會使用硬式編碼的值來說明此程序。

MediaItemDisplayProperties props = mediaPlaybackItem.GetDisplayProperties();
props.Type = Windows.Media.MediaPlaybackType.Video;
props.VideoProperties.Title = "Video title";
props.VideoProperties.Subtitle = "Video subtitle";
props.VideoProperties.Genres.Add("Documentary");
mediaPlaybackItem.ApplyDisplayProperties(props);
props = mediaPlaybackItem.GetDisplayProperties();
props.Type = Windows.Media.MediaPlaybackType.Music;
props.MusicProperties.Title = "Song title";
props.MusicProperties.Artist = "Song artist";
props.MusicProperties.Genres.Add("Polka");
mediaPlaybackItem.ApplyDisplayProperties(props);

使用 TimedTextSource 新增外部計時文字

在某些情況下,您可能會有外部檔案,其中包含與媒體項目相關聯的計時文字,例如包含不同地區設定字幕的個別檔案。 使用 TimedTextSource 類別從串流或 URI 載入外部計時文字檔案。

此範例使用 Dictionary 集合來儲存媒體項目的計時文字來源清單,使用來源 URI 和 TimedTextSource 物件做為鍵/值配對,以便在解析曲目後識別曲目。

Dictionary<TimedTextSource, Uri> timedTextSourceMap;

透過呼叫 CreateFromUri 為每個外部計時文字檔案建立一個新的 TimedTextSource。 將項目新增至計時文字來源的字典。 新增 TimedTextSource.Resolved 事件的處理常式,以處理項目載入失敗的情況,或在繫項目載入成功後設定其他屬性。

透過將所有 TimedTextSource 物件新增至 ExternalTimedTextSources 集合,將它們註冊到 MediaSource。 請注意,外部計時文字來源直接新增至 MediaSource,而不是從來源建立的 MediaPlaybackItem。 若要更新 UI 以反映外部文字曲目,請註冊並處理 TimedMetadataTracksChanged 事件,如本文前面所述。

// Create the TimedTextSource and add entry to URI map
var timedTextSourceUri_En = new Uri("http://contoso.com/MyClipTimedText_en.srt");
var timedTextSource_En = TimedTextSource.CreateFromUri(timedTextSourceUri_En);
timedTextSourceMap[timedTextSource_En] = timedTextSourceUri_En;
timedTextSource_En.Resolved += TimedTextSource_Resolved;

var timedTextSourceUri_Pt = new Uri("http://contoso.com/MyClipTimedText_pt.srt");
var timedTextSource_Pt = TimedTextSource.CreateFromUri(timedTextSourceUri_Pt);
timedTextSourceMap[timedTextSource_Pt] = timedTextSourceUri_Pt;
timedTextSource_Pt.Resolved += TimedTextSource_Resolved;

// Add the TimedTextSource to the MediaSource
_mediaSource.ExternalTimedTextSources.Add(timedTextSource_En);
_mediaSource.ExternalTimedTextSources.Add(timedTextSource_Pt);

_mediaPlaybackItem = new MediaPlaybackItem(_mediaSource);
_mediaPlaybackItem.TimedMetadataTracksChanged += MediaPlaybackItem_TimedMetadataTracksChanged;

_mediaPlayer = new MediaPlayer();
_mediaPlayer.Source = _mediaPlaybackItem;
mediaPlayerElement.SetMediaPlayer(_mediaPlayer);

TimedTextSource.Resolved 事件的處理常式中,檢查傳遞到處理常式的 TimedTextSourceResolveResultEventArgsError 屬性,以確定在嘗試載入定時文字資料時是否發生錯誤。 如果項目已成功解析,您可以使用此處理常式來更新已解析曲目的其他屬性。此範例根據先前儲存在 Dictionary 中的 URI 為每個曲目新增標籤。

private void TimedTextSource_Resolved(TimedTextSource sender, TimedTextSourceResolveResultEventArgs args)
{
    var timedTextSourceUri = timedTextSourceMap[sender];

    if (!(args.Error is null))
    {
        // Show that there was an error in your UI
        ShowMessageToUser($"There was an error resolving track: {timedTextSourceUri}");
        return;
    }

    // Add a label for each resolved track
    var timedTextSourceUriString = timedTextSourceUri.AbsoluteUri;
    if (timedTextSourceUriString.Contains("_en"))
    {
        args.Tracks[0].Label = "English";
    }
    else if (timedTextSourceUriString.Contains("_pt"))
    {
        args.Tracks[0].Label = "Portuguese";
    }
}

如需 Windows 支援的計時文字格式清單,請參閱支援的編解碼器

新增其他中繼資料曲目

您可以在程式碼中動態建立自訂中繼資料曲目,並將其與媒體來源產生關聯。 您建立的曲目可以包含副標題或標題文字,也可以包含您專屬的應用程式資料。

透過呼叫建構函式並指定 ID、語言識別碼和 TimedMetadataKind 列舉中的值來建立新的 TimedMetadataTrack。 註冊 CueEnteredCueExited 事件的處理常式。 當到達提示的開始時間,以及提示的持續時間已分別過期時,就會引發這些事件。

建立一個適合您建立的中繼資料曲目類型的新提示物件,並設定該曲目的 ID、開始時間和持續時間。 此範例建立一個資料曲目,因此會產生一組 DataCue 物件,並為每個提示提供一個包含應用程式特定資料的緩衝區。 若要註冊新曲目,請將其新增至 MediaSource 物件的 ExternalTimedMetadataTracks 集合中。

從 Windows 10 版本 1703 開始,DataCue.Properties 屬性會公開一個 PropertySet,可用來將自訂屬性儲存在可在 CueEnteredCueExited 事件中擷取的鍵/資料配對中。

TimedMetadataTrack metadataTrack = new TimedMetadataTrack("ID_0", "en-us", TimedMetadataKind.Data);
metadataTrack.Label = "Custom data track";
metadataTrack.CueEntered += MetadataTrack_DataCueEntered;
metadataTrack.CueExited += MetadataTrack_CueExited;

// Example cue data
string data = "Cue data";
byte[] bytes = new byte[data.Length * sizeof(char)];
System.Buffer.BlockCopy(data.ToCharArray(), 0, bytes, 0, bytes.Length);
Windows.Storage.Streams.IBuffer buffer = bytes.AsBuffer();

for (int i = 0; i < 10; i++)
{
    DataCue cue = new DataCue();
    cue.Id = "ID_" + i;
    cue.Data = buffer;
    cue.Properties["AdUrl"] = "http://contoso.com/ads/123";
    cue.StartTime = TimeSpan.FromSeconds(3 + i * 3);
    cue.Duration = TimeSpan.FromSeconds(2);

    metadataTrack.AddCue(cue);
}

_mediaSource.ExternalTimedMetadataTracks.Add(metadataTrack);

只要關聯曲目具有 ApplicationPresentedHiddenPlatformPresented 的呈現模式,當到達提示的開始時間時就會引發 CueEntered 事件。當曲目的呈現模式被 Disabled 時,不會為中繼資料曲目引發提示事件。 此範例只會將與提示相關聯的自訂資料輸出至偵錯視窗。

private void MetadataTrack_DataCueEntered(TimedMetadataTrack sender, MediaCueEventArgs args)
{
    DataCue cue = (DataCue)args.Cue;
    string data = System.Text.Encoding.Unicode.GetString(cue.Data.ToArray());
    System.Diagnostics.Debug.WriteLine("Cue entered: " + data);
    System.Diagnostics.Debug.WriteLine("Custom prop value: " + cue.Properties["AdUrl"]);
}

此範例透過在建立曲目時指定TimedMetadataKind.Caption,並使用 TimedTextCue 物件向曲目新增提示來新增自訂文字曲目。

TimedMetadataTrack metadataTrack = new TimedMetadataTrack("TrackID_0", "en-us", TimedMetadataKind.Caption);
metadataTrack.Label = "Custom text track";
metadataTrack.CueEntered += MetadataTrack_TextCueEntered;

for (int i = 0; i < 10; i++)
{
    TimedTextCue cue = new TimedTextCue()
    {
        Id = "TextCueID_" + i,
        StartTime = TimeSpan.FromSeconds(i * 3),
        Duration = TimeSpan.FromSeconds(2)
    };

    cue.Lines.Add(new TimedTextLine() { Text = "This is a custom timed text cue." });
    metadataTrack.AddCue(cue);
}

_mediaSource.ExternalTimedMetadataTracks.Add(metadataTrack);
private void MetadataTrack_TextCueEntered(TimedMetadataTrack sender, MediaCueEventArgs args)
{
    TimedTextCue cue = (TimedTextCue)args.Cue;
    System.Diagnostics.Debug.WriteLine("Cue entered: " + cue.Id + " " + cue.Lines[0].Text);
}

使用 MediaPlaybackList 播放媒體項目清單

MediaPlaybackList 可讓您建立媒體項目的播放清單,這些媒體項目由 MediaPlaybackItem 物件表示。

注意:MediaPlaybackList 中的項目使用無縫播放進行呈現。 系統會使用 MP3 或 AAC 編碼檔案中提供的中繼資料來判斷無間距播放所需的延遲或填補補償。 如果 MP3 或 AAC 編碼檔案不提供此中繼資料,則系統會試探性地確定延遲或填補。 對於不失真格式,例如 PCM、FLAC 或 ALAC,系統不採取任何操作,因為這些編碼器不會引入延遲或填補。

若要開始使用,請宣告變數來儲存您的 MediaPlaybackList

MediaPlaybackList _mediaPlaybackList;

使用本文前面描述的相同程序為要新增至清單中的每個媒體項目建立 MediaPlaybackItem。 初始化MediaPlaybackList 物件,並將媒體播放項目新增至其中。 為 CurrentItemChanged 事件註冊一個處理常式。 此事件可讓您更新 UI,以反映目前正在播放的媒體項目。 您也可以註冊 ItemOpened 事件 (當清單中的項目成功開啟時引發) 和 ItemFailed 事件 (當清單中的項目無法開啟時引發)。

從 Windows 10 版本 1703 開始,可以透過設定 MaxPlayedItemsToKeepOpen 屬性來指定 MediaPlaybackList 中系統在播放後保持開啟狀態的 MediaPlaybackItem 物件的最大數量。 當 MediaPlaybackItem 保持開啟時,當使用者切換到該項目時,該項目的播放可以立即開始,因為該項目不需要重新載入。 但是讓項目保持開啟也會增加應用程式的記憶體使用量,因此在設定此值時,您應該考慮回應性和記憶體使用量之間的平衡。

若要啟用清單播放,請將 MediaPlayer 的播放來源設定為 MediaPlaybackList

_mediaPlaybackList = new MediaPlaybackList();

var files = await filePicker.PickMultipleFilesAsync();

foreach (var file in files)
{
    var mediaPlaybackItem = new MediaPlaybackItem(MediaSource.CreateFromStorageFile(file));
    _mediaPlaybackList.Items.Add(mediaPlaybackItem);
}

_mediaPlaybackList.CurrentItemChanged += MediaPlaybackList_CurrentItemChanged;
_mediaPlaybackList.ItemOpened += MediaPlaybackList_ItemOpened;
_mediaPlaybackList.ItemFailed += MediaPlaybackList_ItemFailed;

_mediaPlaybackList.MaxPlayedItemsToKeepOpen = 3;

_mediaPlayer = new MediaPlayer();
_mediaPlayer.Source = _mediaPlaybackList;
mediaPlayerElement.SetMediaPlayer(_mediaPlayer);

CurrentItemChanged 事件處理常式中,更新 UI 以反映目前播放的項目,可以使用傳遞到事件中的 CurrentMediaPlaybackItemChangedEventArgs 物件的 NewItem 屬性來擷取該項目。 請記住,如果您從此事件更新 UI,則應在 CoreDispatcher.RunAsync 的呼叫中執行此作業,以便在 UI 執行緒上進行更新。

從 Windows 10 版本 1703 開始,您可以檢查 CurrentMediaPlaybackItemChangedEventArgs.Reason 屬性以取得指示項目變更原因的值,例如應用程式以程式設計方式切換項目、先前播放的項目已結束或發生錯誤。

private void MediaPlaybackList_CurrentItemChanged(MediaPlaybackList sender, CurrentMediaPlaybackItemChangedEventArgs args) => 
    LogTelemetryData($"CurrentItemChanged reason: {args.Reason.ToString()}");

呼叫 MovePreviousMoveNext 使媒體播放器播放 MediaPlaybackList 中的上一個或下一個項目。

private void prevButton_Click(object sender, RoutedEventArgs e) =>  _mediaPlaybackList.MovePrevious();
private void nextButton_Click(object sender, RoutedEventArgs e) => _mediaPlaybackList.MoveNext();

設定 ShuffleEnabled 屬性,以指定媒體播放器是否應該以隨機順序播放清單中的項目。

private async void shuffleButton_Click(object sender, RoutedEventArgs e)
{
    _mediaPlaybackList.ShuffleEnabled = !_mediaPlaybackList.ShuffleEnabled;

    await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
    {
        shuffleButton.FontWeight =
            _mediaPlaybackList.ShuffleEnabled ? Windows.UI.Text.FontWeights.Bold : Windows.UI.Text.FontWeights.Light;
    });
}

設定 AutoRepeatEnabled 屬性以指定媒體播放器是否應循環播放清單。

private async void autoRepeatButton_Click(object sender, RoutedEventArgs e)
{
    _mediaPlaybackList.AutoRepeatEnabled = !_mediaPlaybackList.AutoRepeatEnabled;

    await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
    {
        autoRepeatButton.FontWeight =
            _mediaPlaybackList.AutoRepeatEnabled ? Windows.UI.Text.FontWeights.Bold : Windows.UI.Text.FontWeights.Light;
    });
}

處理播放清單中的媒體項目失敗

當清單中的項目無法開啟時,將引發 ItemFailed 事件。 傳遞到處理常式的 MediaPlaybackItemError 物件的 ErrorCode 屬性會盡可能列舉失敗的具體原因,包括網路錯誤、解碼錯誤或加密錯誤。

private void MediaPlaybackList_ItemFailed(MediaPlaybackList sender, MediaPlaybackItemFailedEventArgs args)
{
    LogError(args.Error.ErrorCode.ToString());
    LogError(args.Error.ExtendedError.HResult);
}

停用播放清單中的項目播放

從 Windows 10 版本 1703 開始,可以透過將 MediaPlaybackItemIsDisabledInPlaybackList 屬性設為 false 來停用 MediaPlaybackItemList 中的一項或多項的播放。

這項功能的典型案例是播放從網際網路串流播放音樂的應用程式。 應用程式可以聽取裝置的網路連線狀態變更,並停用未完全下載的項目播放。 在下列範例中,為 NetworkInformation.NetworkStatusChanged 事件註冊了一個處理常式。

Windows.Networking.Connectivity.NetworkInformation.NetworkStatusChanged += NetworkInformation_NetworkStatusChanged;

NetworkStatusChanged 的處理常式中,檢查 GetInternetConnectionProfile 是否傳回 null,這表示網路未連線。 如果是這種情況,請循環播放清單中的所有項目,如果該項目的 TotalDownloadProgress 小於 1,則表示該項目尚未完全下載,則停用該項目。 如果已啟用網路連線,請循環查看播放清單中的所有項目,並啟用每個項目。

private void NetworkInformation_NetworkStatusChanged(object sender)
{
    if (Windows.Networking.Connectivity.NetworkInformation.GetInternetConnectionProfile() == null)
    {
        // Check download status of each item in the list. (TotalDownloadProgress < 1 means not completely downloaded)
        foreach (var item in _mediaPlaybackList.Items)
        {
            if (item.TotalDownloadProgress < 1)
            {
                item.IsDisabledInPlaybackList = true;
            }
        }
    }
    else
    {
        // Connected to internet, re-enable all playlist items
        foreach (var item in _mediaPlaybackList.Items)
        {
            item.IsDisabledInPlaybackList = true;
        }
    }
}

使用 MediaBinder 延遲播放清單中的專案媒體內容繫結

在前面的範例中,從檔案、URL 或串流建立 MediaSource,然後建立 MediaPlaybackItem 並將其新增至 MediaPlaybackList。 在某些情況下,例如,如果使用者因檢視內容而索費,您可能會想要延遲擷取 MediaSource 的內容,直到播放清單中的項目準備好實際播放為止。 若要實作此案例,請建立 MediaBinder 類別的執行個體。 將 Token 屬性設定為應用程式定義的字串,該字串標識要延遲擷取的內容,然後為 Binding 事件註冊處理常式。 接下來,透過呼叫 MediaSource.CreateFromMediaBinderBinder 建立 MediaSource。 然後,從 MediaSource 建立一個 MediaPlaybackItem 並將其像往常一樣新增到播放清單中。

_mediaPlaybackList = new MediaPlaybackList();

var binder = new MediaBinder();
binder.Token = "MyBindingToken1";
binder.Binding += Binder_Binding;
_mediaPlaybackList.Items.Add(new MediaPlaybackItem(MediaSource.CreateFromMediaBinder(binder)));

binder = new MediaBinder();
binder.Token = "MyBindingToken2";
binder.Binding += Binder_Binding;
_mediaPlaybackList.Items.Add(new MediaPlaybackItem(MediaSource.CreateFromMediaBinder(binder)));

_mediaPlayer = new MediaPlayer();
_mediaPlayer.Source = _mediaPlaybackList;
mediaPlayerElement.SetMediaPlayer(_mediaPlayer);

當系統決定需要擷取與 MediaBinder 關聯的內容時,它將引發 Binding 事件。 在此事件的處理常式中,您可以從傳遞到該事件的 MediaBindingEventArgs 中擷取 MediaBinder 執行個體。 擷取您為 Token 屬性指定的字串,並使用它來確定應擷取哪些內容。 MediaBindingEventArgs 提供了以幾種不同表示方式設定繫結內容的方法,包括 SetStorageFileSetStreamSetStreamReferenceSetUri

private void Binder_Binding(MediaBinder sender, MediaBindingEventArgs args)
{
    // Get a deferral if you need to perform async operations
    // var deferral = args.GetDeferral();

    var contentUri = new Uri("http://contoso.com/media/" + args.MediaBinder.Token);
    args.SetUri(contentUri);

    // Call complete after your async operations are complete
    // deferral.Complete();
}

請注意,如果您在 Binding 事件處理常式中執行非同步操作 (例如 Web 要求),則應呼叫 MediaBindingEventArgs.GetDeferral 方法來指示系統等待作業完成後再繼續。 在作業完成之後呼叫 Deferral.Complete,以指示系統繼續。

從 Windows 10 版本 1703 開始,可以透過呼叫 SetAdaptiveMediaSource 提供 AdaptiveMediaSource 做為繫結內容。 如需在應用程式中使用自適性串流的詳細資訊,請參閱 Adaptive streaming