在后台处理媒体文件

本文向你显示了如何使用 MediaProcessingTrigger 和后台任务在后台处理媒体文件。

本文中介绍的示例应用允许用户选择要转换代码的输入媒体文件,并指定用于转换结果代码的输出文件。 然后,启动后台任务以执行转换代码操作。 MediaProcessingTrigger 旨在支持转换代码之外的许多不同媒体处理方案,包括将媒体组合呈现到磁盘,以及在完成处理后上载已处理的媒体文件。

有关此示例中利用的不同通用 Windows 应用功能的更多详细信息,请参阅:

创建用于处理后台任务的媒体

若要在 Microsoft Visual Studio 中将后台任务添加到现有解决方案,请输入你的组件的名称

  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,将在下一节中定义此方法。 如果此操作成功完成,将调用帮助程序方法以启动 Toast 通知,从而提醒用户转换代码已完成。

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 对象,从而指定所需的进度单元以及方法的名称,可调用该方法来通知你任务的当前进度。 向 AsTask 方法传入 Progress 对象以及允许你取消任务的取消标记。

  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 文档为只含有文本内容的 Toast 创建新的 Toast 通知。 将设置 Toast XML 的文本元素,然后从 XML 文档创建新的 ToastNotification 对象。 最后,通过调用 ToastNotifier.Show 向用户显示 Toast。

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

若要注册后台任务,请创建新的 MediaProcessingTriggerBackgroundTaskBuilder。 设置后台任务生成器的名称,以便可以稍后标识它。 将 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");
}