在后台处理媒体文件Process media files in the background

本文说明如何使用 MediaProcessingTrigger 和后台任务在后台处理媒体文件。This article shows you how to use the MediaProcessingTrigger and a background task to process media files in the background.

本文中介绍的示例应用允许用户选择要转换代码的输入媒体文件,并指定用于转换结果代码的输出文件。The example app described in this article allows the user to select an input media file to transcode and specify an output file for the transcoding result. 然后,启动后台任务以执行转换代码操作。Then, a background task is launched to perform the transcoding operation. MediaProcessingTrigger 旨在支持转换代码之外的许多不同媒体处理方案,包括将媒体组合呈现到磁盘,以及在完成处理后上载已处理的媒体文件。The MediaProcessingTrigger is intended to support many different media processing scenarios besides transcoding, including rendering media compositions to disk and uploading processed media files after processing is complete.

有关此示例中利用的不同通用 Windows 应用功能的更多详细信息,请参阅:For more detailed information on the different Universal Windows app features utilized in this sample, see:

创建用于处理后台任务的媒体Create a media processing background task

若要在 Microsoft Visual Studio 中将后台任务添加到现有解决方案,请输入你的组件的名称To add a background task to your existing solution in Microsoft Visual Studio, Enter a name for your comp

  1. 从 " 文件 " 菜单中选择 " 添加 ",然后选择 " 新建项目 ..."。From the File menu, select Add and then New Project....
  2. 选择项目类型 **Windows 运行时组件 " (通用 Windows) **"。Select the project type Windows Runtime Component (Universal Windows).
  3. 输入新的组件项目的名称。Enter a name for your new component project. 此示例使用 MediaProcessingBackgroundTask 项目名称。This example uses the project name MediaProcessingBackgroundTask.
  4. 单击“确定”。Click OK.

解决方案资源管理器中,右键单击默认创建的 "Class1.cs" 文件的图标,然后选择 " 重命名"。In Solution Explorer, right-click the icon for the "Class1.cs" file that is created by default and select Rename. 将该文件重命名为“MediaProcessingTask.cs”。Rename the file to "MediaProcessingTask.cs". 当 Visual Studio 询问是否想要重命名对此类的所有引用时,请单击When Visual Studio asks if you want to rename all of the references to this class, click Yes.

在重命名的类文件中,添加以下 using 指令以在你的项目中包含这些命名空间。In the renamed class file, add the following using directives to include these namespaces in your project.

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;

更新类声明以使你的类继承自 IBackgroundTaskUpdate your class declaration to make your class inherit from IBackgroundTask.

public sealed class MediaProcessingTask : IBackgroundTask
{

将下列成员变量添加到你的类:Add the following member variables to your class:

  • IBackgroundTaskInstance,将用于通过后台任务的进度更新前台应用。An IBackgroundTaskInstance that will be used to update the foreground app with the progress of the background task.
  • BackgroundTaskDeferral,用于在以异步方式执行媒体转换代码时,防止系统关闭你的后台任务。A BackgroundTaskDeferral that keeps the system from shutting down your background task while media transcoding is being performed asynchronously.
  • CancellationTokenSource 对象,可用于取消异步转换代码操作。A CancellationTokenSource object that can be used to cancel the asynchronous transcoding operation.
  • MediaTranscoder 对象,将用于转换媒体文件代码。The MediaTranscoder object that will be used to transcode media files.
IBackgroundTaskInstance backgroundTaskInstance;
BackgroundTaskDeferral deferral;
CancellationTokenSource cancelTokenSource = new CancellationTokenSource();
MediaTranscoder transcoder;

启动任务时,系统将调用后台任务的 Run 方法。The system calls Run method of a background task when the task is launched. 将传入该方法的 IBackgroundTask 对象设置为相应的成员变量。Set the IBackgroundTask object passed into the method to the corresponding member variable. 注册 Canceled 事件的处理程序,在系统需要关闭后台任务时将引发该事件。Register a handler for the Canceled event, which will be raised if the system needs to shut down the background task. 然后,将 Progress 属性设置为零。Then, set the Progress property to zero.

接下来,调用后台任务对象的 GetDeferral 方法来获取延迟。Next, call the background task object's GetDeferral method to obtain a deferral. 这将告知系统不要关闭你的任务,因为你正在执行异步操作。This tells the system not to shut down your task because you are performing asynchronous operations.

接下来,调用帮助程序方法 TranscodeFileAsync,将在下一节中定义此方法。Next, call the helper method TranscodeFileAsync, which is defined in the next section. 如果此操作成功完成,将调用帮助程序方法以启动 Toast 通知,从而提醒用户转换代码已完成。If that completes successfully, a helper method is called to launch a toast notification to alert the user that transcoding is complete.

Run 方法的末尾,将调用延迟对象上的 Complete,以让系统获知你的后台任务已完成并且可以将其终止。At the end of the Run method, call Complete on the deferral object to let the system know that your background task is complete and can be terminated.

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 检索用于转换代码操作的输入和输出文件的文件名。In the TranscodeFileAsync helper method, the file names for the input and output files for the transcoding operations are retrieved from the LocalSettings for your app. 这些值将由你的前台应用进行设置。These values will be set by your foreground app. 创建用于输入和输出文件的 StorageFile 对象,然后创建一个用于转换代码的编码配置文件。Create a StorageFile object for the input and output files and then create an encoding profile to use for transcoding.

调用 PrepareFileTranscodeAsync,从而传入输入文件、输出文件和编码配置文件。Call PrepareFileTranscodeAsync, passing in the input file, output file, and encoding profile. 从此调用返回的 PrepareTranscodeResult 对象使你获知是否可以执行转换代码。The PrepareTranscodeResult object returned from this call lets you know if transcoding can be performed. 如果 CanTranscode 属性为 true,将调用 TranscodeAsync 以执行转换代码操作。If the CanTranscode property is true, call TranscodeAsync to perform the transcoding operation.

AsTask 方法允许你跟踪异步操作进度,或将其取消。The AsTask method enables you to track the progress the asynchronous operation or cancel it. 创建新的 Progress 对象,从而指定所需的进度单元以及方法的名称,可调用该方法来通知你任务的当前进度。Create a new Progress object, specifying the units of progress you desire and the name of the method that will be called to notify you of the current progress of the task. AsTask 方法传入 Progress 对象以及允许你取消任务的取消标记。Pass the Progress object into the AsTask method along with the cancellation token that allows you to cancel the task.

  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 的方法中,设置后台任务实例的进度。In the method you used to create the Progress object in the previous step, Progress, set the progress of the background task instance. 这会将该进度传入前台应用中(如果它正在运行)。This will pass the progress to the foreground app, if it is running.

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

SendToastNotification 帮助程序方法通过获取模板 XML 文档为只含有文本内容的 Toast 创建新的 Toast 通知。The SendToastNotification helper method creates a new toast notification by getting a template XML document for a toast that only has text content. 将设置 Toast XML 的文本元素,然后从 XML 文档创建新的 ToastNotification 对象。The text element of the toast XML is set and then a new ToastNotification object is created from the XML document. 最后,通过调用 ToastNotifier.Show 向用户显示 Toast。Finally, the toast is shown to the user by calling 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 事件(在系统取消后台任务时调用)的处理程序中,可出于遥测目的记录错误。In the handler for the Canceled event, which is called when the system cancels your background task, you can log the error for telemetry purposes.

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

注册和启动后台任务Register and launch the background task

可以从前台应用启动后台任务之前,你必须更新前台应用的 Package.appmanifest 文件,才能让系统知道你的应用可以使用后台任务。Before you can launch the background task from your foreground app, you must update your foreground app's Package.appmanifest file to let the system know that your app uses a background task.

  1. 解决方案资源管理器中,双击 appmanifest 文件图标以打开清单编辑器。In Solution Explorer, double-click the Package.appmanifest file icon to open the manifest editor.
  2. 选择“声明”**** 选项卡。Select the Declarations tab.
  3. 可用声明中,选择后台任务,然后单击添加From Available Declarations, select Background Tasks and click Add.
  4. 支持的声明下,确保已选择后台任务项。Under Supported Declarations make sure that the Background Tasks item is selected. 属性下,选中媒体处理的复选框。Under Properties, select the checkbox for Media processing.
  5. 入口点文本框中,为你的后台测试指定命名空间和类名称,以句点分隔。In the Entry Point text box, specify the namespace and class name for your background test, separated by a period. 对于此示例,该项是:For this example, the entry is:
MediaProcessingBackgroundTask.MediaProcessingTask

接下来,你需要将对后台任务的引用添加到前台应用。Next, you need to add a reference to your background task to your foreground app.

  1. 解决方案资源管理器的前台应用程序项目下,右键单击 " 引用 " 文件夹,然后选择 " 添加引用 ..."。In Solution Explorer, under your foreground app project, right-click the References folder and select Add Reference....
  2. 展开 " 项目 " 节点并选择 " 解决方案"。Expand the Projects node and select Solution.
  3. 选中后台任务项目旁边的框,然后单击确定Check the box next to your background task project and click OK.

应将此示例中代码的其余部分添加到前台应用。The rest of the code in this example should be added to your foreground app. 首先,需要将以下命名空间添加到你的项目。First, you will need to add the following namespaces to your project.

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

接下来,添加注册后台任务所需的以下成员变量。Next, add the following member variables that are needed to register the background task.

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

PickFilesToTranscode 帮助程序方法使用 FileOpenPickerFileSavePicker 打开输入和输出文件以进行转换代码。The PickFilesToTranscode helper method uses a FileOpenPicker and a FileSavePicker to open the input and output files for transcoding. 用户可能选择你的应用无权访问的位置中的文件。The user may select files in a location that your app does not have access to. 若要确保你的后台任务可以打开这些文件,请将它们添加到你的应用的 FutureAccessList 中。To make sure your background task can open the files, add them to the FutureAccessList for your app.

最后,设置你的应用的 LocalSettings 中的输入和输出文件名的项目。Finally, set entries for the input and output file names in the LocalSettings for your app. 后台任务从该位置检索文件名。The background task retrieves the file names from this location.

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

若要注册后台任务,请创建新的 MediaProcessingTriggerBackgroundTaskBuilderTo register the background task, create a new MediaProcessingTrigger and a new BackgroundTaskBuilder. 设置后台任务生成器的名称,以便可以稍后标识它。Set the name of the background task builder so that you can identify it later. TaskEntryPoint 设置为清单文件中使用的同一命名空间和类名称字符串。Set the TaskEntryPoint to the same namespace and class name string you used in the manifest file. Trigger 属性设置为 MediaProcessingTrigger 实例。Set the Trigger property to the MediaProcessingTrigger instance.

注册该任务之前,确保通过循环浏览 AllTasks 集合,并在具有在 BackgroundTaskBuilder.Name 属性中指定的名称的任何任务上调用 Unregister,取消注册任何先前注册的任务。Before registering the task, make sure you unregister any previously registered tasks by looping through the AllTasks collection and calling Unregister on any tasks that have the name you specified in the BackgroundTaskBuilder.Name property.

通过调用 Register 注册后台任务。Register the background task by calling Register. CompletedProgress 事件注册处理程序。Register handlers for the Completed and Progress events.

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 事件中。A typical app will register their background task when the app is initially launched, such as in the OnNavigatedTo event.

通过调用 MediaProcessingTrigger 对象的 RequestAsync 方法来启动后台任务。Launch the background task by calling the MediaProcessingTrigger object's RequestAsync method. 此方法返回的 MediaProcessingTriggerResult 对象让你知道后台任务是否已成功启动,如果未成功启动,则让你知道后台任务未启动的原因。The MediaProcessingTriggerResult object returned by this method lets you know whether the background task was started successfully, and if not, lets you know why the background task wasn't launched.

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 事件中所示。A typical app will launch the background task in response to user interaction, such as in the Click event of a UI control.

当后台任务更新该操作的进度时,将调用 OnProgress 事件处理程序。The OnProgress event handler is called when the background task updates the progress of the operation. 你可以利用此机会使用进度信息更新你的 UI。You can use this opportunity to update your UI with progress information.

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

当后台任务完成运行时,将调用 OnCompleted 事件处理程序。The OnCompleted event handler is called when the background task has finished running. 这是更新你的 UI 以向你的用户提供状态信息的另一个机会。This is another opportunity to update your UI to give status information to the user.

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