Procesar archivos multimedia en segundo plano

Este artículo muestra cómo utilizar MediaProcessingTrigger y una tarea en segundo plano para procesar archivos multimedia en segundo plano.

La aplicación de ejemplo descrita en este artículo permite al usuario seleccionar un archivo multimedia de entrada para transcodificar y especificar un archivo de salida para el resultado de la transcodificación. A continuación, se inicia una tarea en segundo plano para realizar la operación de transcodificación. El MediaProcessingTrigger está pensado para admitir muchos escenarios diferentes de procesamiento de medios además de la transcodificación, incluyendo el renderizado de composiciones de medios en disco y la carga de archivos de medios procesados una vez finalizado el procesamiento.

Para obtener información más detallada sobre las distintas características de las aplicaciones universales de Windows utilizadas en este ejemplo, consulte:

Crear una tarea en segundo plano de tratamiento de medios

Para agregar una tarea en segundo plano a su solución existente en Microsoft Visual Studio, introduzca un nombre para su equipo

  1. En el menú Archivo, seleccione Agregar, y luego, Nuevo proyecto....
  2. Seleccione el tipo de proyecto Componente de Windows Runtime (Windows universal).
  3. Introduzca un nombre para su nuevo proyecto de componentes. Este ejemplo utiliza el nombre de proyecto MediaProcessingBackgroundTask.
  4. Haga clic en Aceptar.

En el Explorador de soluciones, haga clic con el botón derecho en el icono del archivo "Class1.cs" que se crea por defecto y seleccione Cambiar nombre. Cambie el nombre del archivo a "MediaProcessingTask.cs". Cuando Visual Studio le pregunte si desea cambiar el nombre de todas las referencias a esta clase, haga clic en .

En el archivo de clase renombrado, agregue las siguientes directivas de uso para incluir estos espacios de nombres en su proyecto.

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;

Actualice su declaración de clase para que su clase herede de IBackgroundTask.

public sealed class MediaProcessingTask : IBackgroundTask
{

Agregue las siguientes variables miembro a su clase:

  • Una IBackgroundTaskInstance que se utilizará para actualizar la aplicación en primer plano con el progreso de la tarea en segundo plano.
  • Un BackgroundTaskDeferral que evita que el sistema cierre su tarea en segundo plano mientras se realiza la transcodificación de medios de forma asíncrona.
  • Un objeto CancellationTokenSource que puede utilizarse para cancelar la operación de transcodificación asíncrona.
  • El objeto MediaTranscoder que se utilizará para transcodificar los archivos multimedia.
IBackgroundTaskInstance backgroundTaskInstance;
BackgroundTaskDeferral deferral;
CancellationTokenSource cancelTokenSource = new CancellationTokenSource();
MediaTranscoder transcoder;

El sistema llama al método Ejecutar de una tarea en segundo plano cuando se lanza la tarea. Establezca el objeto IBackgroundTask pasado al método en la variable miembro correspondiente. Registra un controlador para el evento Cancelado, que se activará si el sistema necesita cerrar la tarea en segundo plano. A continuación, establezca la propiedad Progreso en cero.

A continuación, llame al método GetDeferral del objeto de tarea en segundo plano para obtener un aplazamiento. Esto indica al sistema que no cierre su tarea porque está realizando operaciones asíncronas.

A continuación, llame al método de ayuda TranscodeFileAsync, que se define en la siguiente sección. Si se completa con éxito, se llama a un método de ayuda para lanzar una notificación emergente para alertar al usuario de que la transcodificación se ha completado.

Al final del método Ejecutar, llame a Completar en el objeto de aplazamiento para que el sistema sepa que su tarea en segundo plano se ha completado y puede finalizar.

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

En el método de ayuda TranscodeFileAsync, los nombres de los archivos de entrada y salida para las operaciones de transcodificación se recuperan de LocalSettings para su aplicación. Estos valores serán establecidos por su aplicación en primer plano. Cree un objeto StorageFile para los archivos de entrada y salida, y luego, cree un perfil de codificación para utilizarlo en la transcodificación.

Llame a PrepareFileTranscodeAsync, pasando el archivo de entrada, el archivo de salida y el perfil de codificación. El objeto PrepareTranscodeResult devuelto por esta llamada permite saber si se puede realizar la transcodificación. Si la propiedad CanTranscode es verdadera, llame a TranscodeAsync para realizar la operación de TranscodeAsync.

El método AsTaskpermite seguir el progreso de la operación asíncrona o cancelarla. Cree un nuevo objeto Progreso, especificando las unidades de progreso que desee y el nombre del método que será llamado para notificarte el progreso actual de la tarea. Pase el objeto Progreso al método AsTask junto con el token de cancelación que le permite cancelar la tarea.

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

En el método que utilizó para crear el objeto Progreso en el paso anterior, Progreso, establezca el progreso de la instancia de tarea en segundo plano. Esto pasará el progreso a la aplicación en primer plano, si se está ejecutando.

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

El método de ayuda SendToastNotification crea una nueva notificación de brindis obteniendo un documento XML de plantilla para un brindis que solo tiene contenido de texto. Se establece el elemento de texto de una notificación XML, y luego, se crea un nuevo objeto ToastNotification a partir del documento XML. Por último, la notifiación se muestra al usuario llamando a 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);
}

En el manejador del evento Cancelado, que se llama cuando el sistema cancela su tarea en segundo plano, puede registrar el error con fines de telemetría.

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

Registrar e iniciar la tarea en segundo plano

Antes de que pueda iniciar la tarea en segundo plano desde su aplicación en primer plano, debe actualizar el archivo Package.appmanifest de su aplicación en primer plano para que el sistema sepa que su aplicación utiliza una tarea en segundo plano.

  1. En el Explorador de soluciones, haga doble clic en el icono del archivo Package.appmanifest para abrir el editor de manifiestos.
  2. Selecciona la pestaña Declaraciones.
  3. En Declaraciones disponibles, seleccione Tareas en segundo plano y haga clic en Agregar.
  4. En Declaraciones admitidas, asegúrese de que está seleccionado el elemento Tareas en segundo plano. En Propiedades, active la casilla de Procesamiento multimedia.
  5. En el cuadro de texto Punto de entrada, especifique el espacio de nombres y el nombre de la clase para su prueba en segundo plano, separados por un punto. En este ejemplo, la entrada es:
MediaProcessingBackgroundTask.MediaProcessingTask

A continuación, tiene que agregar una referencia a su tarea en segundo plano a su aplicación en primer plano.

  1. En el Explorador de soluciones, en el proyecto de la aplicación en primer plano, haga clic con el botón derecho en la carpeta Referencias y seleccione Agregar referencia....
  2. Expanda el nodo Proyectos y seleccione Solución.
  3. Marque la casilla junto a su proyecto de tarea en segundo plano y haga clic en Aceptar.

El resto del código de este ejemplo debe agregarse a su aplicación en primer plano. En primer lugar, tendrá que agregar los siguientes espacios de nombres a su proyecto.

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

A continuación, agregue las siguientes variables miembro necesarias para registrar la tarea en segundo plano.

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

El método de ayuda PickFilesToTranscode utiliza un FileOpenPicker y un FileSavePickerpara abrir los archivos de entrada y salida para la transcodificación. El usuario puede seleccionar archivos en una ubicación a la que su aplicación no tiene acceso. Para asegurarse de que su tarea en segundo plano puede abrir los archivos, agréguelos a la lista FutureAccessList de su aplicación.

Por último, establece entradas para los nombres de los archivos de entrada y salida en LocalSettings para su aplicación. La tarea en segundo plano recupera los nombres de archivo de esta ubicación.

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 la tarea en segundo plano, cree un nuevo MediaProcessingTrigger y un nuevo BackgroundTaskBuilder. Establezca el nombre del generador de tareas en segundo plano para poder identificarlo posteriormente. Establezca el TaskEntryPoint con la misma cadena de espacio de nombres y nombre de clase que utilizó en el archivo de manifiesto. Establezca la propiedad Desencadenador a la instancia MediaProcessingTrigger.

Antes de registrar la tarea, asegúrese de anular el registro de cualquier tarea previamente registrada recorriendo la colección AllTasks y llamando a Unregister en cualquier tarea que tenga el nombre especificado en la BackgroundTaskBuilder.Name.

Registre la tarea en segundo plano llamando a Registrar. Registrar manejadores para los eventos Completado y Progreso.

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

Una aplicación típica registrará su tarea en segundo plano cuando se inicie la aplicación, por ejemplo, en el evento OnNavigatedTo.

Inicie la tarea en segundo plano llamando al método RequestAsync del objeto MediaProcessingTrigger. El objeto MediaProcessingTriggerResult devuelto por este método le permite saber si la tarea en segundo plano se inició correctamente y, en caso contrario, le permite saber por qué no se inició la tarea en segundo plano.

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

}

Una aplicación típica lanzará la tarea en segundo plano en respuesta a la interacción del usuario, como en el evento Clic de un control UI.

El manejador de eventos OnProgress es llamado cuando la tarea en segundo plano actualiza el progreso de la operación. Puede aprovechar esta oportunidad para actualizar su interfaz de usuario con información sobre el progreso.

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

El manejador de eventos OnCompleted es llamado cuando la tarea en segundo plano ha terminado de ejecutarse. Esta es otra oportunidad para actualizar su interfaz de usuario para dar información de estado al usuario.

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