在背景處理媒體檔案

本文說明如何使用 MediaProcessingTrigger 和背景工作在背景處理媒體檔案。

本文所述的範例應用程式可讓使用者選取輸入媒體檔案進行轉碼,並指定轉碼結果的輸出檔案。 然後,會啟動背景工作來執行轉碼作業。 MediaProcessingTrigger 的目的是除了轉碼之外,還支援許多不同的媒體處理案例,包括將媒體組合轉譯至磁碟,並在處理完成後上傳已處理的媒體檔案。

如需此範例中所使用之不同通用 Windows 應用程式功能的詳細資訊,請參閱:

建立媒體處理背景工作

若要在 Microsoft Visual Studio 中將背景工作新增至現有的方案,請輸入您的 comp 名稱

  1. [檔案] 功能表中,選取 [新增],然後選取 [新增專案...]
  2. 選取專案類型 Windows 執行階段元件 (通用 Windows)
  3. 輸入新元件專案的名稱。 此範例會使用專案名稱 MediaProcessingBackgroundTask
  4. 按一下 [確定]。

[方案總管] 中,以滑鼠右鍵按一下預設建立的「Class1.cs」檔案圖示,然後選取 [重新命名]。 將檔案重新命名為「MediaProcessingTask.cs」。 當 Visual Studio 詢問您是否要重新命名此類別的所有參考時,請按一下 [是]

在重新命名的類別檔案中,新增下列 using 指示詞,以在專案中包含這些命名空間。

using Windows.ApplicationModel.Background;
using Windows.Storage;
using Windows.UI.Notifications;
using Windows.Data.Xml.Dom;
using Windows.Media.MediaProperties;
using Windows.Media.Transcoding;
using System.Threading;

更新類別宣告,讓您的類別繼承自 IBackgroundTask

public sealed class MediaProcessingTask : IBackgroundTask
{

將下列成員變數新增至類別:

  • IBackgroundTaskInstance 用來使用背景工作的進度來更新前景應用程式。
  • BackgroundTaskDeferral 可讓系統在以非同步方式執行媒體轉碼時關閉背景工作。
  • CancellationTokenSource 物件,可用來取消非同步轉碼作業。
  • MediaTranscoder 物件用來轉碼媒體檔案。
IBackgroundTaskInstance backgroundTaskInstance;
BackgroundTaskDeferral deferral;
CancellationTokenSource cancelTokenSource = new CancellationTokenSource();
MediaTranscoder transcoder;

系統會在啟動工作時呼叫背景工作的 Run 方法。 將傳遞至方法的 IBackgroundTask 物件設定為對應的成員變數。 註冊 Canceled 事件的處理常式,如果系統需要關閉背景工作,將會引發此處理常式。 然後,將 Progress 屬性設定為零。

接下來,呼叫背景工作物件的 GetDeferral 方法以取得延遲。 這會告知系統不要關閉您的工作,因為您正在執行非同步作業。

接下來,呼叫協助程式方法 TranscodeFileAsync,其定義於下一節中。 如果成功完成,則會呼叫協助程式方法來啟動快顯通知,以警示使用者轉碼已完成。

Run 方法結束時,在延遲物件上呼叫Complete,讓系統知道您的背景工作已完成,而且可以終止。

public async void Run(IBackgroundTaskInstance taskInstance)
{
    Debug.WriteLine("In background task Run method");

    backgroundTaskInstance = taskInstance;
    taskInstance.Canceled += new BackgroundTaskCanceledEventHandler(OnCanceled);
    taskInstance.Progress = 0;

    deferral = taskInstance.GetDeferral();
    Debug.WriteLine("Background " + taskInstance.Task.Name + " is called @ " + (DateTime.Now).ToString());

    try
    {
        await TranscodeFileAsync();
        ApplicationData.Current.LocalSettings.Values["TranscodingStatus"] = "Completed Successfully";
        SendToastNotification("File transcoding complete.");

    }
    catch (Exception e)
    {
        Debug.WriteLine("Exception type: {0}", e.ToString());
        ApplicationData.Current.LocalSettings.Values["TranscodingStatus"] = "Error ocurred: " + e.ToString();
    }


    deferral.Complete();
}

TranscodeFileAsync 協助程式方法中,會從應用程式的 LocalSettings 擷取轉碼作業輸入和輸出檔案的檔案名。 這些值將由前景應用程式設定。 建立輸入和輸出檔案的 StorageFile 物件,然後建立編碼設定檔以用於轉碼。

呼叫 PrepareFileTranscodeAsync,傳入輸入檔案、輸出檔案和編碼設定檔。 從這個呼叫傳回的 PrepareTranscodeResult 物件可讓您知道是否可以執行轉碼。 如果 CanTranscode 屬性為 true,請呼叫 TranscodeAsync 來執行轉碼作業。

AsTask 方法可讓您追蹤非同步作業的進度或取消進度。 建立新的 Progress 物件,並指定您想要的進度單位,以及將呼叫以通知您工作目前進度的方法名稱。 將 Progress 物件傳遞至 AsTask 方法,以及可讓您取消工作的取消權杖。

  private async Task TranscodeFileAsync()
  {
      transcoder = new MediaTranscoder();

      try
      {
          var settings = ApplicationData.Current.LocalSettings;

          settings.Values["TranscodingStatus"] = "Started";

          var inputFileName = ApplicationData.Current.LocalSettings.Values["InputFileName"] as string;
          var outputFileName = ApplicationData.Current.LocalSettings.Values["OutputFileName"] as string;

          if (inputFileName == null || outputFileName == null)
          {
              return;
          }


          // retrieve the transcoding information
          var inputFile = await Windows.Storage.StorageFile.GetFileFromPathAsync(inputFileName);
          var outputFile = await Windows.Storage.StorageFile.GetFileFromPathAsync(outputFileName);

          // create video encoding profile                
          MediaEncodingProfile encodingProfile = MediaEncodingProfile.CreateMp4(VideoEncodingQuality.HD720p);

          Debug.WriteLine("PrepareFileTranscodeAsync");
          settings.Values["TranscodingStatus"] = "Preparing to transcode ";
          PrepareTranscodeResult preparedTranscodeResult = await transcoder.PrepareFileTranscodeAsync(
              inputFile, 
              outputFile, 
              encodingProfile);

          if (preparedTranscodeResult.CanTranscode)
          {
              var startTime = TimeSpan.FromMilliseconds(DateTime.Now.Millisecond);
              Debug.WriteLine("Starting transcoding @" + startTime);

              var progress = new Progress<double>(TranscodeProgress);
              settings.Values["TranscodingStatus"] = "Transcoding ";
              settings.Values["ProcessingFileName"] = inputFileName;
              await preparedTranscodeResult.TranscodeAsync().AsTask(cancelTokenSource.Token, progress);

          }
          else
          {
              Debug.WriteLine("Source content could not be transcoded.");
              Debug.WriteLine("Transcode status: " + preparedTranscodeResult.FailureReason.ToString());
              var endTime = TimeSpan.FromMilliseconds(DateTime.Now.Millisecond);
              Debug.WriteLine("End time = " + endTime);
          }
      }
      catch (Exception e)
      {
          Debug.WriteLine("Exception type: {0}", e.ToString());
          throw;
      }
  }

在您在上一個步驟中用來建立 Progress 物件的 方法中,Progress,設定背景工作執行個體的進度。 如果進度正在執行,則會將進度傳遞至前景應用程式。

void TranscodeProgress(double percent)
{
    Debug.WriteLine("Transcoding progress:  " + percent.ToString().Split('.')[0] + "%");
    backgroundTaskInstance.Progress = (uint)percent;
}

SendToastNotification 協助程式方法會取得只有文字內容的快顯通知範本 XML 文件,以建立新的快顯通知。 快顯通知 XML 的文字元素會設定,然後從 XML 文件建立新的 ToastNotification 物件。 最後,呼叫 ToastNotifier.Show 向使用者顯示快顯通知。

private void SendToastNotification(string toastMessage)
{
    ToastTemplateType toastTemplate = ToastTemplateType.ToastText01;
    XmlDocument toastXml = ToastNotificationManager.GetTemplateContent(toastTemplate);

    //Supply text content for your notification
    XmlNodeList toastTextElements = toastXml.GetElementsByTagName("text");
    toastTextElements[0].AppendChild(toastXml.CreateTextNode(toastMessage));

    //Create the toast notification based on the XML content you've specified.
    ToastNotification toast = new ToastNotification(toastXml);

    //Send your toast notification.
    ToastNotificationManager.CreateToastNotifier().Show(toast);
}

Canceled 事件的處理常式中,當系統取消背景工作時呼叫,您可以針對遙測目的記錄錯誤。

private void OnCanceled(IBackgroundTaskInstance sender, BackgroundTaskCancellationReason reason)
{
    Debug.WriteLine("Background " + sender.Task.Name + " Cancel Requested..." + reason.ToString());
}

註冊並啟動背景工作

您必須先更新前景應用程式的 Package.appmanifest 檔案,讓系統知道您的應用程式使用背景工作,才能從前景應用程式啟動背景工作。

  1. [方案總管] 中,按兩下 Package.appmanifest 檔案圖示以開啟資訊清單編輯器。
  2. 選取 [宣告] 索引標籤。
  3. [可用的宣告] 中,選取 [背景工作],然後按一下 [新增]
  4. [支援的宣告] 下,確定已選取 [背景工作] 項目。 在 [屬性] 底下,選取 [媒體處理] 核取方塊。
  5. [進入點] 文字方塊中,指定背景測試的命名空間和類別名稱,並以句號分隔。 在此範例中,項目為:
MediaProcessingBackgroundTask.MediaProcessingTask

接下來,您必須將背景工作的參考新增至前景應用程式。

  1. [方案總管] 中,於前景應用程式專案底下,以滑鼠右鍵按一下 [參考] 資料夾,然後選取 [新增參考...]
  2. 展開 [專案] 節點,然後選取 [方案]
  3. 核取背景工作專案旁的方塊,然後按一下 [確定]

此範例中的其餘程式碼應該新增至前景應用程式。 首先,您必須將下列命名空間新增至專案。

using Windows.ApplicationModel.Background;
using Windows.Storage;

接下來,新增註冊背景工作所需的下列成員變數。

MediaProcessingTrigger mediaProcessingTrigger;
string backgroundTaskBuilderName = "TranscodingBackgroundTask";
BackgroundTaskRegistration taskRegistration;

PickFilesToTranscode 協助程式方法會使用 FileOpenPickerFileSavePicker 來開啟用於轉碼的輸入和輸出檔案。 使用者可以選取應用程式無法存取之位置的檔案。 若要確定您的背景工作可以開啟檔案,請將其新增至應用程式的 FutureAccessList

最後,在應用程式的 LocalSettings 中設定輸入和輸出檔案名稱的項目。 背景工作會從這個位置擷取檔案名稱。

private async void PickFilesToTranscode()
{
    var openPicker = new Windows.Storage.Pickers.FileOpenPicker();

    openPicker.SuggestedStartLocation = Windows.Storage.Pickers.PickerLocationId.VideosLibrary;
    openPicker.FileTypeFilter.Add(".wmv");
    openPicker.FileTypeFilter.Add(".mp4");

    StorageFile source = await openPicker.PickSingleFileAsync();

    var savePicker = new Windows.Storage.Pickers.FileSavePicker();

    savePicker.SuggestedStartLocation =
        Windows.Storage.Pickers.PickerLocationId.VideosLibrary;

    savePicker.DefaultFileExtension = ".mp4";
    savePicker.SuggestedFileName = "New Video";

    savePicker.FileTypeChoices.Add("MPEG4", new string[] { ".mp4" });

    StorageFile destination = await savePicker.PickSaveFileAsync();

    if(source == null || destination == null)
    {
        return;
    }

    var storageItemAccessList = Windows.Storage.AccessCache.StorageApplicationPermissions.FutureAccessList;
    storageItemAccessList.Add(source);
    storageItemAccessList.Add(destination);

    ApplicationData.Current.LocalSettings.Values["InputFileName"] = source.Path;
    ApplicationData.Current.LocalSettings.Values["OutputFileName"] = destination.Path;
}

若要註冊背景工作,請建立新的 MediaProcessingTrigger 和新的 BackgroundTaskBuilder。 設定背景工作產生器的名稱,以便稍後加以識別。 將 TaskEntryPoint 設定為您在資訊清單檔案中使用的相同命名空間和類別名稱字串。 將 Trigger 屬性設定為 MediaProcessingTrigger 執行個體。

註冊工作之前,請透過迴圈處理 AllTasks 集合,並在任何具有您在 BackgroundTaskBuilder.Name 屬性中所指定名稱的工作上呼叫 Unregister,確定您取消註冊任何先前註冊的工作。

呼叫 Register 來註冊背景工作。 註冊 CompletedProgress 事件的處理常式。

private void RegisterBackgroundTask()
{
    // New a MediaProcessingTrigger
    mediaProcessingTrigger = new MediaProcessingTrigger();

    var builder = new BackgroundTaskBuilder();

    builder.Name = backgroundTaskBuilderName;
    builder.TaskEntryPoint = "MediaProcessingBackgroundTask.MediaProcessingTask";
    builder.SetTrigger(mediaProcessingTrigger);

    // unregister old ones
    foreach (var cur in BackgroundTaskRegistration.AllTasks)
    {
        if (cur.Value.Name == backgroundTaskBuilderName)
        {
            cur.Value.Unregister(true);
        }
    }

    taskRegistration = builder.Register();
    taskRegistration.Progress += new BackgroundTaskProgressEventHandler(OnProgress);
    taskRegistration.Completed += new BackgroundTaskCompletedEventHandler(OnCompleted);

    return;
}

一般應用程式會在應用程式一開始啟動時註冊其背景工作,例如 OnNavigatedTo 事件。

呼叫 MediaProcessingTrigger 物件的 RequestAsync 方法來啟動背景工作。 這個方法傳回的 MediaProcessingTriggerResult 物件可讓您知道背景工作是否已成功啟動,如果不是,則可讓您知道背景工做為何未啟動。

private async void LaunchBackgroundTask()
{
    var success = true;

    if (mediaProcessingTrigger != null)
    {
        MediaProcessingTriggerResult activationResult;
        activationResult = await mediaProcessingTrigger.RequestAsync();

        switch (activationResult)
        {
            case MediaProcessingTriggerResult.Allowed:
                // Task starting successfully
                break;

            case MediaProcessingTriggerResult.CurrentlyRunning:
            // Already Triggered

            case MediaProcessingTriggerResult.DisabledByPolicy:
            // Disabled by system policy

            case MediaProcessingTriggerResult.UnknownError:
                // All other failures
                success = false;
                break;
        }

        if (!success)
        {
            // Unregister the media processing trigger background task
            taskRegistration.Unregister(true);
        }
    }

}

一般應用程式會啟動背景工作,以回應使用者互動,例如在 UI 控制項的 Click 事件中。

當背景工作更新作業進度時,會呼叫 OnProgress 事件處理常式。 您可以使用這個機會,以進度資訊更新 UI。

private void OnProgress(IBackgroundTaskRegistration task, BackgroundTaskProgressEventArgs args)
{
    string progress = "Progress: " + args.Progress + "%";
    Debug.WriteLine(progress);
}

當背景工作完成執行時,會呼叫 OnCompleted 事件處理常式。 這是更新 UI 以向使用者提供狀態資訊的另一個機會。

private void OnCompleted(IBackgroundTaskRegistration task, BackgroundTaskCompletedEventArgs args)
{
    Debug.WriteLine(" background task complete");
}