本文介绍如何借助 MediaProcessingTrigger 与后台任务来处理媒体文件。
本文中所述的示例应用允许用户选择要转码的输入媒体文件,并为转码结果指定输出文件。 然后,启动后台任务以执行转码操作。 MediaProcessingTrigger 旨在支持多种不同的媒体处理方案,除了转码之外,还包括将媒体组合呈现到磁盘,并在处理完成后上传已处理的媒体文件。
有关此示例中利用的不同通用 Windows 应用功能的更多详细信息,请参阅:
创建媒体处理后台任务
若要在 Microsoft Visual Studio 中向现有解决方案添加后台任务,请输入公司的名称
- 在 “文件 ”菜单中,选择“ 添加 ”,然后选择“ 新建项目...”。
- 选择项目类型 Windows 运行时组件(通用 Windows)。
- 输入新组件项目的名称。 此示例使用项目名称 MediaProcessingBackgroundTask。
- 单击“确定”。
在 解决方案资源管理器中,右键单击默认创建的“Class1.cs”文件的图标,然后选择“ 重命名”。 将文件重命名为“MediaProcessingTask.cs”。 当 Visual Studio 询问是否要重命名对此类的所有引用时,请单击“是”。
在重命名的类文件中,使用 指令将以下
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;
系统在任务启动时调用后台任务的 方法。 将传递到方法的 IBackgroundTask 对象设置为相应的成员变量。 为 Canceled 事件注册一个处理程序,如果系统需要关闭后台任务,该事件将被触发。 然后,将 Progress 属性设置为零。
接下来,调用后台任务对象的 GetDeferral 方法以获取延迟。 这告诉系统不要关闭你的任务,因为你正在执行异步操作。
接下来,调用帮助程序方法 TranscodeFileAsync,该方法在下一节中定义。 如果成功完成,将调用帮助程序方法以启动 Toast 通知,以提醒用户转码已完成。
在 Run 方法结束时,在延迟对象上调用 完成 方法,以便让系统知道后台任务已完成并可以终止。
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 帮助程序方法通过获取仅包含文本内容的 Toast 模板 XML 文档来创建新的 Toast 通知。 设置 Toast XML 文档的文本元素,然后从 XML 文档创建一个新的 ToastNotification 对象。 最后,调用 ToastNotifier.Show来显示 Toast 给用户。
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 文件,让系统知道你的应用使用后台任务。
- 在 解决方案资源管理器中,双击 Package.appmanifest 文件图标以打开清单编辑器。
- 选择 声明 选项卡。
- 从 可用声明中,选择 后台任务 并单击 “添加”。
- 在 支持声明 中,确保选择 后台任务 项。 在 属性中,选中 媒体处理的复选框。
- 在 入口点 文本框中,指定后台测试的命名空间和类名称,用句点分隔。 对于此示例,条目为:
MediaProcessingBackgroundTask.MediaProcessingTask
接下来,需要将后台任务的引用添加到前台应用中。
- 在解决方案资源管理器中的你的前台应用程序项目下,右键单击 引用 文件夹,并选择 添加引用...。
- 展开 项目 节点,然后选择 解决方案。
- 选中后台工作项目旁边的框,然后点击“确定”。
本示例中的其余代码应添加到前台应用。 首先,需要将以下命名空间添加到项目中。
using Windows.ApplicationModel.Background;
using Windows.Storage;
接下来,添加注册后台任务所需的以下成员变量。
MediaProcessingTrigger mediaProcessingTrigger;
string backgroundTaskBuilderName = "TranscodingBackgroundTask";
BackgroundTaskRegistration taskRegistration;
PickFilesToTranscode 帮助程序方法使用 FileOpenPicker 和 FileSavePicker 打开用于转码的输入和输出文件。 用户可以在应用无权访问的位置选择文件。 确保您的后台任务可以打开文件,请将这些文件添加到应用的 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 属性中指定的名称的任何任务调用 注销,以注销任何以前注册的任务。
通过调用 登记来注册后台任务。 为 完成的 事件和 进行中的 事件注册处理程序。
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");
}