在后台处理媒体文件

本文介绍如何借助 MediaProcessingTrigger 与后台任务来处理媒体文件。

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

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

创建媒体处理后台任务

若要在 Microsoft Visual Studio 中向现有解决方案添加后台任务,请输入公司的名称

  1. “文件 ”菜单中,选择“ 添加 ”,然后选择“ 新建项目...”
  2. 选择项目类型 Windows 运行时组件(通用 Windows)。
  3. 输入新组件项目的名称。 此示例使用项目名称 MediaProcessingBackgroundTask
  4. 单击“确定”。

解决方案资源管理器中,右键单击默认创建的“Class1.cs”文件的图标,然后选择“ 重命名”。 将文件重命名为“MediaProcessingTask.cs”。 当 Visual Studio 询问是否要重命名对此类的所有引用时,请单击“是”

在重命名的类文件中,使用 指令将以下 所在的命名空间包含到你的项目中。

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 backgroundTaskInstance;
BackgroundTaskDeferral deferral;
CancellationTokenSource cancelTokenSource = new CancellationTokenSource();
MediaTranscoder transcoder;

系统在任务启动时调用后台任务的 方法。 将传递到方法的 IBackgroundTask 对象设置为相应的成员变量。 为 Canceled 事件注册一个处理程序,如果系统需要关闭后台任务,该事件将被触发。 然后,将 Progress 属性设置为零。

接下来,调用后台任务对象的 GetDeferral 方法以获取延迟。 这告诉系统不要关闭你的任务,因为你正在执行异步操作。

接下来,调用帮助程序方法 TranscodeFileAsync,该方法在下一节中定义。 如果成功完成,将调用帮助程序方法以启动 Toast 通知,以提醒用户转码已完成。

Run 方法结束时,在延迟对象上调用 完成 方法,以便让系统知道后台任务已完成并可以终止。

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 帮助程序方法通过获取仅包含文本内容的 Toast 模板 XML 文档来创建新的 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;
}

若要注册后台任务,请创建新的 MediaProcessingTrigger 和新的 BackgroundTaskBuilder。 设置后台任务生成器的名称,以便稍后可以标识它。 将 TaskEntryPoint 设置为清单文件中所用的同一命名空间和类名字符串。 将 Trigger 属性设置为 MediaProcessingTrigger 实例。

在注册任务之前,请确保通过循环访问 AllTasks 集合,对具有在 BackgroundTaskBuilder.Name 属性中指定的名称的任何任务调用 注销,以注销任何以前注册的任务。

通过调用 登记来注册后台任务。 为 完成的 事件和 进行中的 事件注册处理程序。

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