Обработка файлов мультимедиа в фоновом режиме

В этой статье показано, как использовать 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, который позволяет системе завершать фоновую задачу во время асинхронного выполнения транскодирования мультимедиа.
  • Объект CancelTokenSource, который можно использовать для отмены асинхронной операции перекодирования.
  • Объект MediaTranscoder, который будет использоваться для перекодирования файлов мультимедиа.
IBackgroundTaskInstance backgroundTaskInstance;
BackgroundTaskDeferral deferral;
CancellationTokenSource cancelTokenSource = new CancellationTokenSource();
MediaTranscoder transcoder;

Система вызывает метод Run фоновой задачи при запуске задачи. Задайте объект IBackgroundTask, переданный методу в соответствующую переменную-член. Зарегистрируйте обработчик для события "Отмена" , который будет вызван, если системе необходимо завершить фоновую задачу. Затем задайте для свойства 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 имена файлов входных и выходных файлов для операций транскодирования извлекаются из локального Параметры для приложения. Эти значения будут заданы приложением переднего плана. Создайте объект служба хранилища File для входных и выходных файлов, а затем создайте профиль кодирования для перекодирования.

Вызовите 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 устанавливается, а затем создается новый объект ToastNotification из XML-документа. Наконец, всплывающее уведомление отображается пользователю путем вызова 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);
}

В обработчике события "Отменено ", вызываемом при отмене фоновой задачи, можно записать ошибку в целях телеметрии.

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. В разделе "Поддерживаемые объявления" убедитесь, что выбран элемент фоновых задач. В разделе "Свойства" выберите поле проверка box для обработки мультимедиа.
  5. В текстовом поле точки входа укажите пространство имен и имя класса для фонового теста, разделенное точкой. В этом примере используется запись:
MediaProcessingBackgroundTask.MediaProcessingTask

Затем необходимо добавить ссылку на фоновую задачу в приложение переднего плана.

  1. В Обозреватель решений в проекте приложения переднего плана щелкните правой кнопкой мыши папку "Ссылки" и выберите "Добавить ссылку...".
  2. Разверните узел "Проекты" и выберите "Решение".
  3. Установите флажок рядом с проектом фоновой задачи и нажмите кнопку "ОК".

Остальная часть кода в этом примере должна быть добавлена в приложение переднего плана. Сначала необходимо добавить в проект следующие пространства имен.

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

Затем добавьте следующие переменные-члены, необходимые для регистрации фоновой задачи.

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

Вспомогательный метод PickFilesToTranscode использует FileOpenPicker и FileSavePicker для открытия входных и выходных файлов для перекодирования. Пользователь может выбрать файлы в расположении, к которому ваше приложение не имеет доступа. Чтобы убедиться, что фоновая задача может открывать файлы, добавьте их в FutureAccessList для приложения.

Наконец, задайте записи для имен входных и выходных файлов в локальном Параметры для приложения. Фоновая задача извлекает имена файлов из этого расположения.

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 .

Запустите фоновую задачу, вызвав метод RequestAsync объекта MediaProcessingTrigger. Объект 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);
        }
    }

}

Обычное приложение запустит фоновую задачу в ответ на взаимодействие с пользователем, например в событии Click элемента управления пользовательского интерфейса.

Обработчик событий OnProgress вызывается, когда фоновая задача обновляет ход выполнения операции. Эту возможность можно использовать для обновления пользовательского интерфейса с информацией о ходе выполнения.

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

Обработчик событий OnCompleted вызывается после завершения фоновой задачи. Это еще одна возможность обновить пользовательский интерфейс, чтобы предоставить пользователю сведения о состоянии.

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