Verarbeiten von Mediendateien im Hintergrund

In diesem Artikel wird beschrieben, wie Sie den MediaProcessingTrigger und eine Hintergrundaufgabe verwenden, um Mediendateien im Hintergrund zu verarbeiten.

Die in diesem Artikel beschriebene Beispiel-App ermöglicht es Benutzer*innen, eine Eingabemediendatei zum Transcodieren auszuwählen und eine Ausgabedatei für das Transcodierungsergebnis anzugeben. Anschließend wird eine Hintergrundaufgabe gestartet, um den Transcodierungsvorgang auszuführen. Der MediaProcessingTrigger soll neben der Transcodierung viele verschiedene Medienverarbeitungsszenarien unterstützen, darunter das Rendern von Medienkompositionen auf einem Datenträger und das Hochladen verarbeiteter Mediendateien nach Abschluss der Verarbeitung.

Ausführlichere Informationen zu den verschiedenen Features einer universellen Windows-App, die in diesem Beispiel verwendet werden, finden Sie in den folgenden Artikeln:

Erstellen einer Hintergrundaufgabe für die Medienverarbeitung

Um Ihrer vorhandenen Projektmappe in Microsoft Visual Studio eine Hintergrundaufgabe hinzuzufügen, geben Sie einen Namen für Ihre Comp-Datei ein.

  1. Wählen Sie im Menü Datei die Option Hinzufügen und dann Neues Projekt aus.
  2. Wählen Sie den Projekttyp Komponente für Windows-Runtime (Universal Windows) aus.
  3. Geben Sie einen Namen für Ihr neues Komponentenprojekt ein. In diesem Beispiel wird der Projektname MediaProcessingBackgroundTask verwendet.
  4. Klicken Sie auf OK.

Klicken Sie im Projektmappen-Explorer mit der rechten Maustaste auf das Symbol für die Datei „Class1.cs“, die standardmäßig erstellt wird, und wählen Sie Umbenennen aus. Benennen Sie die Datei in „MediaProcessingTask.cs“ um. Wenn Sie von Visual Studio gefragt werden, ob Sie alle Verweise auf diese Klasse umbenennen möchten, klicken Sie auf Ja.

Fügen Sie in der umbenannten Klassendatei die folgenden using-Anweisungen hinzu, um diese Namespaces in Ihr Projekt einzuschließen.

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;

Aktualisieren Sie die Klassendeklaration, damit Ihre Klasse von IBackgroundTask erbt.

public sealed class MediaProcessingTask : IBackgroundTask
{

Fügen Sie Ihrer Klasse die folgenden Membervariablen hinzu:

  • Eine IBackgroundTaskInstance, die zum Aktualisieren der Vordergrund-App mit dem Status der Hintergrundaufgabe verwendet wird
  • Eine BackgroundTaskDeferral, die verhindert, dass das System Ihre Hintergrundaufgabe beendet, während asynchron eine Medientranscodierung ausgeführt wird
  • Ein CancellationTokenSource-Objekt, das zum Abbrechen des asynchronen Transcodierungsvorgangs verwendet werden kann
  • Das MediaTranscoder-Objekt, das zum Transcodieren von Mediendateien verwendet wird
IBackgroundTaskInstance backgroundTaskInstance;
BackgroundTaskDeferral deferral;
CancellationTokenSource cancelTokenSource = new CancellationTokenSource();
MediaTranscoder transcoder;

Das System ruft die Run-Methode einer Hintergrundaufgabe auf, wenn die Aufgabe gestartet wird. Legen Sie das IBackgroundTask-Objekt, das an die Methode übergeben wird, auf die entsprechende Membervariable fest. Registrieren Sie einen Handler für das Canceled-Ereignis, das ausgelöst wird, wenn das System die Hintergrundaufgabe herunterfahren muss. Legen Sie dann die Progress-Eigenschaft auf NULL fest.

Rufen Sie als Nächstes die GetDeferral-Methode des Hintergrundaufgabenobjekts auf, um eine Verzögerung abzurufen. Dadurch wird das System angewiesen, Ihre Aufgabe nicht herunterzufahren, weil Sie asynchrone Vorgänge ausführen.

Rufen Sie als Nächstes die TranscodeFileAsync-Hilfsmethode auf, die im nächsten Abschnitt beschrieben wird. Wenn dieser Aufruf erfolgreich abgeschlossen wird, wird eine Hilfsmethode aufgerufen, um eine Popupbenachrichtigung zu starten, die Benutzer*innen darüber informiert, dass die Transcodierung abgeschlossen ist.

Rufen Sie am Ende der Run-Methode Complete für das deferral-Objekt auf, um dem System mitzuteilen, dass die Hintergrundaufgabe abgeschlossen ist und beendet werden kann.

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

In der TranscodeFileAsync-Hilfsmethode werden die Dateinamen für die Eingabe- und Ausgabedateien der Transcodierungsvorgänge aus den LocalSettings für Ihre App abgerufen. Diese Werte werden von Ihrer Vordergrund-App festgelegt. Erstellen Sie ein StorageFile-Objekt für die Eingabe- und Ausgabedateien, und erstellen Sie dann ein Codierungsprofil, das für die Transcodierung verwendet werden soll.

Rufen Sie PrepareFileTranscodeAsync auf, und übergeben Sie die Eingabedatei, die Ausgabedatei und das Codierungsprofil. Das PrepareTranscodeResult-Objekt, das von diesem Aufruf zurückgegeben wird, teilt Ihnen mit, ob die Transcodierung ausgeführt werden kann. Wenn die CanTranscode-Eigenschaft „true“ ist, rufen Sie TranscodeAsync auf, um den Transcodierungsvorgang auszuführen.

Mit der AsTask-Methode können Sie den Status des asynchronen Vorgangs nachverfolgen oder den Vorgang abbrechen. Erstellen Sie ein neues Progress-Objekt, und geben Sie die gewünschten Statuseinheiten und den Namen der Methode an, die aufgerufen wird, um Sie über den aktuellen Status der Aufgabe zu informieren. Übergeben Sie das Progress-Objekt zusammen mit dem Abbruchtoken, mit dem Sie die Aufgabe abbrechen können, an die AsTask-Methode.

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

Legen Sie in der Methode, die Sie im vorherigen Schritt zum Erstellen des Progress-Objekts verwendet haben, den Status der Hintergrundaufgabeninstanz fest. Dadurch wird der Status an die Vordergrund-App übergeben, wenn diese ausgeführt wird.

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

Die SendToastNotification-Hilfsmethode erstellt eine neue Popupbenachrichtigung, indem ein XML-Vorlagendokument für ein Popup abgerufen wird, das nur Text enthält. Das Textelement des XML-Popupdokuments wird festgelegt, und anschließend wird ein neues ToastNotification-Objekt aus dem XML-Dokument erstellt. Zum Schluss wird das Popup für Benutzer*innen angezeigt, indem ToastNotifier.Show aufgerufen wird.

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

Im Handler für das Canceled-Ereignis, das aufgerufen wird, wenn das System die Hintergrundaufgabe abbricht, können Sie den Fehler zu Telemetriezwecken protokollieren.

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

Registrieren und Starten der Hintergrundaufgabe

Bevor Sie die Hintergrundaufgabe in der Vordergrund-App starten können, müssen Sie die Datei „Package.appmanifest“ der Vordergrund-App aktualisieren, um dem System mitzuteilen, dass Ihre App eine Hintergrundaufgabe verwendet.

  1. Doppelklicken Sie im Projektmappen-Explorer auf die Datei „Package.appxmanifest“, um den Manifest-Editor zu öffnen.
  2. Klicken Sie auf die Registerkarte Deklarationen.
  3. Wählen Sie unter Verfügbare Deklarationen die Option Hintergrundaufgaben aus, und klicken Sie auf Hinzufügen.
  4. Stellen Sie unter Unterstützte Deklarationen sicher, dass das Element Hintergrundaufgaben ausgewählt ist. Aktivieren Sie unter Eigenschaften das Kontrollkästchen für Medienverarbeitung.
  5. Geben Sie im Textfeld Einstiegspunkt den Namespace und den Klassennamen für die Hintergrundaufgabe getrennt durch einen Punkt an. In diesem Beispiel lautet der Eintrag wie folgt:
MediaProcessingBackgroundTask.MediaProcessingTask

Als Nächstes müssen Sie Ihrer Hintergrundaufgabe einen Verweis auf die Vordergrund-App hinzufügen.

  1. Klicken Sie im Projektmappen-Explorer unter dem Vordergrund-App-Projekt mit der rechten Maustaste auf den Ordner Verweise, und wählen Sie Verweis hinzufügen aus.
  2. Erweitern Sie den Knoten Projekte, und wählen Sie Projektmappe aus.
  3. Aktivieren Sie das Kontrollkästchen neben dem Hintergrundaufgabenprojekt, und klicken Sie auf OK.

Der restliche Code in diesem Beispiel sollte der Vordergrund-App hinzugefügt werden. Zuerst müssen Sie Ihrem Projekt die folgenden Namespaces hinzufügen.

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

Fügen Sie als Nächstes die folgenden Membervariablen hinzu, die zum Registrieren der Hintergrundaufgabe erforderlich sind.

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

Die PickFilesToTranscode-Hilfsmethode verwendet FileOpenPicker und FileSavePicker, um die Eingabe- und Ausgabedateien für die Transcodierung zu öffnen. Benutzer*innen können Dateien an einem Speicherort auswählen, auf den Ihre App nicht zugreifen kann. Um sicherzustellen, dass Ihre Hintergrundaufgabe die Dateien öffnen kann, fügen Sie sie der FutureAccessList für Ihre App hinzu.

Legen Sie zum Schluss Einträge für die Eingabe- und Ausgabedateinamen in den LocalSettings für Ihre App fest. Die Hintergrundaufgabe ruft die Dateinamen von diesem Speicherort ab.

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

Um die Hintergrundaufgabe zu registrieren, erstellen Sie einen neuen MediaProcessingTrigger und einen neuen BackgroundTaskBuilder. Legen Sie für den Hintergrundaufgaben-Generator einen Namen fest, damit Sie ihn später identifizieren können. Legen Sie den TaskEntryPoint auf dieselbe Namespace- und Klassennamenzeichenfolge fest, die Sie in der Manifestdatei verwendet haben. Legen Sie die Trigger-Eigenschaft auf die MediaProcessingTrigger-Instanz fest.

Stellen Sie vor dem Registrieren der Aufgabe sicher, dass Sie die Registrierung aller zuvor registrierten Aufgaben aufheben, indem Sie eine Schleife durch die AllTasks-Sammlung ausführen und Unregister für alle Aufgaben aufrufen, die den Namen aufweisen, den Sie in der BackgroundTaskBuilder.Name-Eigenschaft angegeben haben.

Registrieren Sie die Hintergrundaufgabe, indem Sie Register aufrufen. Registrieren Sie Handler für die Ereignisse Completed und 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;
}

Eine typische App registriert die Hintergrundaufgabe beim erstmaligen Start der App, z. B. im OnNavigatedTo-Ereignis.

Starten Sie die Hintergrundaufgabe, indem Sie die RequestAsync-Methode des MediaProcessingTrigger-Objekts aufrufen. Das von dieser Methode zurückgegebene MediaProcessingTriggerResult-Objekt informiert Sie darüber, ob die Hintergrundaufgabe erfolgreich gestartet wurde, und falls dies nicht der Fall ist, warum sie nicht gestartet wurde.

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

}

Eine typische App startet die Hintergrundaufgabe als Reaktion auf eine Benutzerinteraktion, z. B. im Click-Ereignis eines Steuerelements der Benutzeroberfläche.

Der OnProgress-Ereignishandler wird aufgerufen, wenn die Hintergrundaufgabe den Status des Vorgangs aktualisiert. Sie können diese Gelegenheit nutzen, um Ihre Benutzeroberfläche mit Statusinformationen zu aktualisieren.

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

Der OnCompleted-Ereignishandler wird aufgerufen, wenn die Ausführung der Hintergrundaufgabe abgeschlossen ist. Dies ist eine weitere Möglichkeit, die Benutzeroberfläche zu aktualisieren, um Statusinformationen für die Benutzer*innen anzuzeigen.

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