Processar arquivos de mídia em segundo plano

Esse artigo mostra como usar o MediaProcessingTrigger e uma tarefa em segundo plano para processar arquivos de mídia em segundo plano.

O aplicativo de exemplo descrito nsse artigo permite que o usuário selecione um arquivo de mídia de entrada para transcodificar e especificar um arquivo de saída para o resultado da transcodificação. Em seguida, uma tarefa em segundo plano é iniciada para executar a operação de transcodificação. O MediaProcessingTrigger destina-se a dar suporte a muitos cenários de processamento de mídia diferentes além da transcodificação, incluindo a renderização de composições de mídia em disco e o carregamento de arquivos de mídia processados após a conclusão do processamento.

Para obter informações mais detalhadas sobre os diferentes recursos do aplicativo Universal do Windows utilizados neste exemplo, consulte:

Criar uma tarefa em segundo plano de processamento de mídia

Para adicionar uma tarefa em segundo plano à sua solução existente no Microsoft Visual Studio, insira um nome para seu comp

  1. No menu Arquivo, selecione Adicionar e, em seguida, Novo Projeto....
  2. Selecione o tipo de projeto Componente do Windows Runtime (Universal do Windows).
  3. Insira um nome para seu novo projeto de componente. Esse exemplo usa o nome de projeto MediaProcessingBackgroundTask.
  4. Clique no OK.

No Gerenciador de Soluções, clique com o botão direito do mouse no ícone do arquivo "Class1.cs" criado por padrão e selecione Renomear. Renomeie o arquivo como "MediaProcessingTask.cs". Quando o Visual Studio perguntar se você deseja renomear todas as referências para essa classe, clique em Sim.

No arquivo de classe renomeado, adicione o seguinte usando diretivas para incluir esses namespaces em seu projeto.

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;

Atualize sua declaração de classe para que sua classe seja herdada do IBackgroundTask.

public sealed class MediaProcessingTask : IBackgroundTask
{

Adicione as seguintes variáveis de membro à sua classe:

  • Um IBackgroundTaskInstance que será usado para atualizar o aplicativo em primeiro plano com o progresso da tarefa em segundo plano.
  • Um BackgroundTaskDeferral que impede o sistema de desligar sua tarefa em segundo plano enquanto a transcodificação de mídia está sendo executada de forma assíncrona.
  • Um objeto CancellationTokenSource que pode ser usado para cancelar a operação de transcodificação assíncrona.
  • O objeto MediaTranscoder que será usado para transcodificar arquivos de mídia.
IBackgroundTaskInstance backgroundTaskInstance;
BackgroundTaskDeferral deferral;
CancellationTokenSource cancelTokenSource = new CancellationTokenSource();
MediaTranscoder transcoder;

O sistema chama o método Executar de uma tarefa em segundo plano quando a tarefa é iniciada. Defina o objeto IBackgroundTask passado para o método para a variável de membro correspondente. Registre um manipulador para o evento Canceled, que será gerado se o sistema precisar desligar a tarefa em segundo plano. Em seguida, defina a propriedade Progress como zero.

Em seguida, chame o método GetDeferral do objeto de tarefa em segundo plano para obter um adiamento. Isso informa ao sistema para não desligar sua tarefa porque você está executando operações assíncronas.

Em seguida, chame o método auxiliar TranscodeFileAsync, que é definido na próxima seção. Se isso for concluído com êxito, um método auxiliar será chamado para iniciar uma notificação do sistema para alertar o usuário de que a transcodificação está concluída.

No final do método Run, chame Complete no objeto de adiamento para informar ao sistema que sua tarefa em segundo plano está concluída e pode ser encerrada.

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

No método auxiliar TranscodeFileAsync, os nomes de arquivo para os arquivos de entrada e saída para as operações de transcodificação são recuperados deLocalSettings para seu aplicativo. Esses valores serão definidos pelo aplicativo em primeiro plano. Crie um objeto StorageFile para os arquivos de entrada e saída e, em seguida, crie um perfil de codificação a ser usado para transcodificação.

Chame PrepareFileTranscodeAsync, passando o arquivo de entrada, o arquivo de saída e o perfil de codificação. O objeto PrepareTranscodeResult retornado dessa chamada permite que você saiba se a transcodificação pode ser executada. Se a propriedade CanTranscode for verdadeira, chame TranscodeAsync para executar a operação de transcodificação.

O método AsTask permite que você acompanhe o progresso da operação assíncrona ou cancele-a. Crie um novo objeto Progress, especificando as unidades de progresso desejadas e o nome do método que será chamado para notificar você sobre o progresso atual da tarefa. Passe o objeto Progress para o métodoAsTask junto com o token de cancelamento que permite cancelar a tarefa.

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

No método usado para criar o objeto Progress na etapa anterior, Progress, defina o progresso da instância da tarefa em segundo plano. Isso passará o progresso para o aplicativo em primeiro plano, se ele estiver em execução.

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

O método auxiliar SendToastNotification cria uma nova notificação do sistema obtendo um documento XML de modelo para um sistema que tem apenas conteúdo de texto. O elemento de texto do XML do sistema é definido e, em seguida, um novo objeto ToastNotification é criado a partir do documento XML. Por fim, o sistema é mostrado ao usuário chamando 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);
}

No manipulador do evento Canceled, que é chamado quando o sistema cancela sua tarefa em segundo plano, você pode registrar o erro para fins de telemetria.

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

Registrar e iniciar a tarefa em segundo plano

Antes de iniciar a tarefa em segundo plano do seu aplicativo em primeiro plano, você deve atualizar o arquivo Package.appmanifest do aplicativo em primeiro plano para informar ao sistema que seu aplicativo usa uma tarefa em segundo plano.

  1. No Gerenciador de Soluções, clique duas vezes no ícone de arquivo Package.appmanifest para abrir o editor de manifesto.
  2. Selecione a guia Declarações.
  3. Em Declarações Disponíveis, selecione Tarefas em Segundo Plano e clique em Adicionar.
  4. Em Declarações Compatíveis, verifique se o item Tarefas em Segundo Plano está selecionado. Em Propriedades, selecione a caixa de seleção para Processamento de mídia.
  5. Na caixa de texto Ponto de Entrada, especifique o namespace e o nome da classe para o teste em segundo plano, separados por um período. Para esse exemplo, a entrada é:
MediaProcessingBackgroundTask.MediaProcessingTask

Em seguida, você precisa adicionar uma referência à sua tarefa em segundo plano ao seu aplicativo em primeiro plano.

  1. No Gerenciador de Soluções, em seu projeto de aplicativo em primeiro plano, clique com o botão direito do mouse na pasta Referências e selecione Adicionar Referência....
  2. Expanda o nó Projetos e selecione Solução.
  3. Marque a caixa ao lado do projeto de tarefa em segundo plano e clique em OK.

O restante do código nesse exemplo deve ser adicionado ao seu aplicativo em primeiro plano. Primeiro, você precisará adicionar os namespaces a seguir ao seu projeto.

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

Em seguida, adicione as variáveis de membro a seguir necessárias para registrar a tarefa em segundo plano.

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

O método auxiliar PickFilesToTranscode usa um FileOpenPicker e um FileSavePickerpara abrir os arquivos de entrada e saída para transcodificação. O usuário pode selecionar arquivos em um local ao qual seu aplicativo não tem acesso. Para garantir que sua tarefa em segundo plano possa abrir os arquivos, adicione-os aoFutureAccessList para seu aplicativo.

Por fim, defina entradas para os nomes de arquivo de entrada e saída no LocalSettings para seu aplicativo. A tarefa em segundo plano recupera os nomes de arquivo desse local.

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

Para registrar a tarefa em segundo plano, crie um novo MediaProcessingTrigger e um novoBackgroundTaskBuilder. Defina o nome do construtor de tarefas em segundo plano para que você possa identificá-lo mais tarde. Defina o TaskEntryPoint com o mesmo namespace e cadeia de caracteres de nome de classe que você usou no arquivo de manifesto. Defina a propriedade Triggerpara a instância de MediaProcessingTrigger .

Antes de registrar a tarefa, certifique-se de cancelar o registro de tarefas registradas anteriormente, fazendo loop pela coleção AllTasks e chamando Cancelar registro em qualquer tarefa que tenha o nome especificado na propriedadeBackgroundTaskBuilder.Name.

Registre a tarefa em segundo plano chamando Register. Registre identificadores para os eventosCompletede Progress.

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

Um aplicativo típico registrará sua tarefa em segundo plano quando o aplicativo for inicialmente iniciado, como no evento OnNavigatedTo.

Inicie a tarefa em segundo plano chamando o métodoMediaProcessingTrigger do MediaProcessingTrigger do objeto. O objeto MediaProcessingTriggerResult retornado por esse método permite que você saiba se a tarefa em segundo plano foi iniciada com êxito e, caso contrário, permite que você saiba por que a tarefa em segundo plano não foi iniciada.

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

}

Um aplicativo típico iniciará a tarefa em segundo plano em resposta à interação do usuário, como no evento Click de um controle de interface do usuário.

O identificador de eventos OnProgress é chamado quando a tarefa em segundo plano atualiza o progresso da operação. Você pode usar essa oportunidade para atualizar sua interface do usuário com informações de progresso.

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

O identificador de eventos OnCompleted é chamado quando a tarefa em segundo plano termina de ser executada. Essa é outra oportunidade de atualizar sua interface do usuário para fornecer informações de status ao usuário.

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