バックグラウンドでのメディア ファイルの処理

この記事では、MediaProcessingTrigger とバックグラウンド タスクを使って、バックグラウンドでメディア ファイルを処理する方法について説明します。

この記事で説明するサンプル アプリを使うと、ユーザーは入力メディア ファイルを選んでコード変換し、コード変換結果の出力ファイルを指定できます。 次に、バックグラウンド タスクが起動してコード変換操作が実行されます。 MediaProcessingTrigger は、コード変換だけでなく、さまざまなメディア処理シナリオ (ディスクへのメディアコンポジションのレンダリング、処理の完了後の処理済みメディア ファイルのアップロードなど) をサポートすることを目的としています。

このサンプルで利用されているさまざまなユニバーサル Windows アプリ機能について詳しくは、次をご覧ください。

メディア処理のバックグラウンド タスクの作成

Microsoft Visual Studio で既存のソリューションにバックグラウンド タスクを追加するには、コンポーネントの名前を入力します。

  1. [ファイル] メニューの [追加] をクリックし、[新しいプロジェクト] をクリックします。
  2. プロジェクトの種類として [Windows ランタイム コンポーネント (ユニバーサル Windows)] を選びます。
  3. 新しいコンポーネント プロジェクトの名前を入力します。 この例では、プロジェクト名として MediaProcessingBackgroundTask を使います。
  4. [OK] をクリックします。

ソリューション エクスプローラーで、既定で作成された "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。メディアのコード変換が非同期的に実行されている間、システムがバックグラウンド タスクをシャットダウンしないようにします。
  • CancellationTokenSource オブジェクト。非同期コード変換操作を取り消すために使うことができます。
  • MediaTranscoder オブジェクト。メディア ファイルのコード変換に使われます。
IBackgroundTaskInstance backgroundTaskInstance;
BackgroundTaskDeferral deferral;
CancellationTokenSource cancelTokenSource = new CancellationTokenSource();
MediaTranscoder transcoder;

タスクが起動すると、システムはバックグラウンド タスクの Run メソッドを呼び出します。 メソッドに渡される IBackgroundTask オブジェクトを、対応するメンバー変数に設定します。 システムがバックグラウンド タスクをシャットダウンする必要がある場合に発生する Canceled イベントのハンドラーを登録します。 次に、Progress プロパティを 0 に設定します。

次に、バックグラウンド タスク オブジェクトの 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 ヘルパー メソッドで、コード変換操作の入力ファイルと出力ファイルのファイル名がアプリの 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 ヘルパー メソッドは、テキスト コンテンツしかないトーストのテンプレート XML ドキュメントを取得することによって新しいトースト通知を作成します。 トースト XML のテキスト要素が設定された後、XML ドキュメントから新しい ToastNotification オブジェクトが作成されます。 最後に、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);
}

システムがバックグラウンド タスクをキャンセルしたときに呼び出される 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. [サポートされる宣言][バックグラウンド タスク] 項目が選択されていることを確認します。 [プロパティ]で、[Media processing] (メディア処理) チェック ボックスをオンにします。
  5. [エントリ ポイント] テキスト ボックスで、バックグラウンド テストの名前空間とクラス名を、ピリオドで区切って指定します。 この例では、エントリは次のとおりです。
MediaProcessingBackgroundTask.MediaProcessingTask

次に、フォアグラウンド アプリにバックグラウンド タスクへの参照を追加する必要があります。

  1. ソリューション エクスプローラーのフォアグラウンド アプリ プロジェクトで、[参照] フォルダーを右クリックし、[参照の追加] を選択します。
  2. [プロジェクト] ノードを展開し、[ソリューション] を選択します。
  3. バックグラウンド タスク プロジェクトの横のボックスをオンにし、[OK] をクリックします。

この例のコードの残りの部分をフォアグラウンド アプリに追加する必要があります。 まず、次の名前空間をプロジェクトに追加する必要があります。

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 プロパティで指定した名前を持つすべてのタスクで Unregister を呼び出すことにより、以前に登録したタスクを必ず登録解除してください。

Register を呼び出してバックグラウンド タスクを登録します。 Completed イベントと 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;
}

一般的なアプリでは、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");
}