在后台处理媒体文件
本文向你显示了如何使用 MediaProcessingTrigger 和后台任务在后台处理媒体文件。
本文中介绍的示例应用允许用户选择要转换代码的输入媒体文件,并指定用于转换结果代码的输出文件。 然后,启动后台任务以执行转换代码操作。 MediaProcessingTrigger 旨在支持转换代码之外的许多不同媒体处理方案,包括将媒体组合呈现到磁盘,以及在完成处理后上载已处理的媒体文件。
有关此示例中利用的不同通用 Windows 应用功能的更多详细信息,请参阅:
创建用于处理后台任务的媒体
若要在 Microsoft Visual Studio 中将后台任务添加到现有解决方案,请输入你的组件的名称
- 在“文件”菜单上,依次选择“添加”和“新建项目...”。
- 选择“Windows 运行时组件(通用 Windows)”项目类型。
- 输入新的组件项目的名称。 此示例使用 MediaProcessingBackgroundTask 项目名称。
- 单击“确定”。
在“解决方案资源管理器”,右键单击默认情况下创建的“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 属性设置为零。
接下来,调用后台任务对象的 GetDeferral 方法来获取延迟。 这将告知系统不要关闭你的任务,因为你正在执行异步操作。
接下来,调用帮助程序方法 TranscodeFileAsync,将在下一节中定义此方法。 如果此操作成功完成,将调用帮助程序方法以启动 Toast 通知,从而提醒用户转换代码已完成。
在 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 对象,从而指定所需的进度单元以及方法的名称,可调用该方法来通知你任务的当前进度。 向 AsTask 方法传入 Progress 对象以及允许你取消任务的取消标记。
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 文档为只含有文本内容的 Toast 创建新的 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 属性中指定的名称的任何任务上调用 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");
}