一触即发

Windows Phone 7.5 的后台音频

查尔斯 · Petzold

下载代码示例

Charles Petzold
回旧时代的 MS-DOS,程序员可以用一种称为终止和留下来的居民 (也称为 TSR) 技术实现任务切换原油窗的体。TSR 程序安装挂钩到键盘中断或其它操作系统的机制,然后终止,离开在内存中的程序准备踢进的行动中,当用户按下特定的组合键或其他感兴趣的东西发生了。

MS-DOS 不是有能力处理甚至这一级别的基本任务切换,因此这些 Tsr 创建一些严重的问题。他们会相互冲突,经常崩溃的整个操作系统。这是我们中的一些欢迎补充和后来替换 MS-DOS 窗口的主要原因之一。

我提到这个古代历史,因为我要向您展示如何发挥 Windows Phone 7.5 的应用程序中,从背景中的音乐文件,我知道开发者倾向于认为在框外。通常,这是一件好事,但你不应该使用此技术的播放音乐文件以外的其他目的。这样做可能会导致您的应用程序 Windows Phone 市场所拒绝。

你瞧的技术只是播放声音或音乐文件从 Web 位置或独立的存储。如果您的应用程序需要播放歌曲从手机的正常的音乐库,你可以使用我讨论在上期的 MediaLibrary 和 MediaPlayer 类 (msdn.microsoft.com/magazine/hh708760)。

应用程序加上 DLL

Windows Phone 7.5,在协助一个应用程序后台运行的对象称为代理。要播放背景音乐文件,您可以使用从 AudioPlayerAgent 派生的类。此类必须是由该程序所引用的动态链接库。程序代码不会本身运行在后台 ; 什么在后台运行,是此从 AudioPlayerAgent 派生的类。

这篇文章是一个名为 SimpleBackgroundAudio 的 Visual Studio 解决方案包含在可下载的代码。为清楚起见,此程序包含只是最小的获取背景音频工作所需的代码量。通过指定 Windows Phone 应用程序,然后 Windows Phone 7.1,我从新建项目对话框创建解决方案。(7.1 指定内部使用的 Windows Phone 操作系统和应用程序 Windows Phone,但它意味着更常见的 7.5 指定相同)。

SimpleBackgroundAudio 项目的网页类添加一个按钮,该按钮的 Click 处理程序:

void OnPlayButtonClick(object sender, RoutedEventArgs args)
{
  AudioTrack audioTrack = 
    new AudioTrack(new Uri("http://www.archive.org/.../Iv.Presto.mp3"), 
                   "Symphony No.
9: 4th Movement", 
                   "Felix Weingartner", 
                   "Beethoven: Symphony No.
9", 
                   null, 
                   null, 
                   EnabledPlayerControls.Pause);
  BackgroundAudioPlayer.Instance.Track = audioTrack;
}

在 Microsoft.Phone.BackgroundAudio 命名空间中的音频曲目和 BackgroundAudioPlayer 的类。 (这我已经缩短在这里,以满足该杂志的列宽度) 的 URL 引用包含最后的贝多芬的交响乐号移动互联网档案馆 (archive.org) 上的一个 MP3 文件 9 由费利克斯 · 魏因加特纳 1935年录制中进行。 (您可以另外指定一个 URL 地址 ; 的独立存储中的文件, 使用 Uri 构造函数的 UriKind.Relative 参数如果真是这样。未来三个音频曲目的构造函数的参数提供专辑、 艺术家和跟踪信息。

BackgroundAudioPlayer 类是有点类似的 MediaElement 或 MediaPlayer 播放音频文件,在前台的类。 BackgroundAudioPlayer 有没有构造函数 ; 相反,您获取与静态实例属性的唯一实例。 在此代码中,跟踪属性设置为刚刚创建的音频曲目对象。

您可以编译并运行该程序,但它不会做任何事。 要使 BackgroundAudioPlayer 唱,您需要一个 DLL 包含从 AudioPlayerAgent 派生的类。 Visual Studio 可以为您创建此类和库的项目。 我的程序,为我右键单击在 Visual Studio 中的 SimpleBackgroundAudio 解决方案名称、 从菜单中选择添加新项目,然后从模板列表中选择 Windows Phone 音频回放代理。 我命名为 SimpleAudioPlaybackAgent,这个新的项目。

Visual Studio 创建具有一个名为 AudioPlayer AudioPlayerAgent,初始化的骨架的几种方法从派生类的库项目。 这是在后台运行的类。

非常重要:从对此 DLL 的应用程序创建一个引用 ! 为此,我右键单击引用部分下的 SimpleBackgroundAudio 项目,选定的添加引用,然后在对话框中我选择项目选项卡,然后 SimpleAudioPlaybackAgent 项目。

AudioPlayerAgent 导数,你要修改至少两个方法:OnPlayStateChanged 和 OnUserAction。 完整的 AudioPlayer 类,从这个项目 (减去使用指令和评论) 所示图 1

图 1 中 SimpleBackgroundAudio 的 AudioPlayer 类

namespace SimpleAudioPlaybackAgent
{
  public class AudioPlayer : AudioPlayerAgent
  {
    protected override void OnPlayStateChanged(BackgroundAudioPlayer player,
                                      AudioTrack track, PlayState playState)
    {
      switch (playState)
      {
        case PlayState.TrackReady:
          player.Play();
          break;

        case PlayState.TrackEnded:
          player.Track = null;
          break;
      }
      NotifyComplete();
    }
    protected override void OnUserAction(BackgroundAudioPlayer player, 
                                      AudioTrack track, UserAction action, 
                                      object param)
    {
      switch (action)
      {
        case UserAction.Pause:
          player.Pause();
          break;

        case UserAction.Play:
          player.Play();
          break;
      }
      NotifyComplete();
    }

    protected override void OnError(BackgroundAudioPlayer player, 
                                      AudioTrack track, Exception error, 
                                      bool isFatal)
    {
        base.OnError(player, track, error, isFatal);
        NotifyComplete();
    }
    protected override void OnCancel()
    {
        base.OnCancel();
        NotifyComplete();
    }
  }
}

首先看看 OnPlayStateChanged 重写。 BackgroundAudioPlayer 的 PlayState 属性更改时调用此方法。 第一个参数是相同的 BackgroundAudioPlayer 引用的程序代码 ; 最后一个参数是 PlayState 枚举的成员。

当程序 Click 事件处理程序中设置了跟踪属性的 BackgroundAudioPlayer 时,BackgroundAudioPlayer 访问音乐文件通过互联网 ; SimpleAudioPlaybackAgent DLL 被加载和 OnPlayStateChanged 方法最终获取调用的 playState 参数设置为 PlayState.TrackReady。 它是 OnPlayStateChanged 的 BackgroundAudioPlayer 对象,开始播放的曲目的播放方法调用的责任。

如果手机的定期音乐播放器出现打的东西时,它将被停止。 此时,您可以导航到其他程序或甚至将其终止按下手机上的后退按钮,和音乐将继续播放。

您可以在 Windows Phone 模拟器,运行此程序,但你要试试实际的手机上,所以您可以按手机的音量控制。 这将激活下拉一点已知紫外作为通用卷控制 (C),这是背景音频的重要组成部分。 紫外 C 显示正在播放的曲目名称和艺术家,基于的应用程序提供的音频曲目构造函数的参数。 紫外 C 还显示按钮上一曲目、 暂停和下一曲目。 在此程序中,只有暂停按钮启用了因为那是我在音频曲目构造函数的最后一个参数中的指定。 当您按暂停按钮时,它暂停和播放之间切换。

AudioPlayer 类处理这些暂停和播放的命令,在所示的 OnUserAction 重写图 1。 UserAction 参数指示用户按下的特定按钮。 OnUserAction 响应通过 BackgroundAudioPlayer 对象中调用相应的方法。

当播放完轨道时,OnPlayStateChanged 获取 PlayState.TrackEnded 的呼叫。 通过设置为 null,而从紫外 C 中移除的项的 BackgroundAudioPlayer 的轨道属性的方法做出响应。 如果需要,可以返回到应用程序并开始重新播放的音乐。

对 NotifyComplete 的调用与 OnPlayStateChanged 和 OnUserAction 缔结的通知:此为必需项。 此外注意到两种方法包括调用基类方法。 这些基类调用是 Visual Studio,您创建的模板的一部分,但我遇到了问题 OnUserAction 重写基方法被调用时。 从微软的背景音频的示例代码 (可用在线从 Windows Phone 7.5 文档区域) 还不包括调用基方法。

很奇怪的 DLL

当尝试背景音频,你要保持在 Visual Studio 中的输出窗口上的手表。 从调试器运行 SimpleBackgroundAudio 时,输出窗口将列出所有加载在手机上运行该程序,以及程序本身,这是 SimpleBackgroundAudio.dll 的系统库。 在输出窗口中列出这些库的行开始改为"用户界面任务",指示该程序。

当您点击按钮在 SimpleBackgroundAudio 中的时,您将看到许多相同的正在加载的 Dll — — 但现在与每行开头的单词"后台任务"。这些 Dll 包含应用程序 DLL 以及 SimpleAudioPlaybackAgent.dll。 这些 Dll 的加载是时间的它需要一点点开始播放你点击按钮后音乐的原因之一。

您尝试播放背景音频的程序,就可能要在所有方法重写在 AudioPlayer 类中,插入 Debug.WriteLine 语句,然后研究他们实时的行为,在输出窗口中。

您也可能想用另一个 Debug.WriteLine 语句创建一个 AudioPlayer 类的构造函数。 你会发现每当调用需要制成 OnPlayStateChanged 或 OnUserAction AudioPlayer 的一个新实例被实例化。 每个调用获取一个新的实例 !

这个简单的事实已有深刻的含义:如果您需要此 AudioPlayer 类保持调用 OnPlayStateChanged 和 OnUserAction 之间的信息,你必须保持该信息­中的静态字段或属性的信息。

如果您需要从 SimpleBackgroundAudio 程序中的类的信息传递到 SimpleAudioPlaybackAgent 库中 AudioPlayer 类吗? 它看起来是合理的在 AudioPlayer 类中,定义一个公共的静态方法,然后从首页或另一个类的程序中调用该方法。 事实上你可以,但它不会做你想要什么:此方法从保存的任何东西不会可用期间调用 OnPlayStateChanged 和 OnUserAction。

为什么不会可用? 记得在 Visual Studio 输出窗口中的用户界面任务和任务背景型号。 这些都是两个单独的进程。 您的程序中引用的 dll 实例不是在后台运行的实例相同,因此将在用户界面任务和后台任务之间共享此 DLL 中的类中不甚至静态数据。

在测试时从调试器的后台音频应用程序在 Visual Studio 中,您会遇到有些额外的古怪。 当您退出程序,发起了一些背景音频时,一直播放音频和 Visual Studio 指示代码仍在运行。 若要继续编辑您的程序,你要停止调试直接从 Visual Studio 中,即使在当时音乐及将经常保持上。 在开发背景音频程序中,您可能会发现自己经常从手机卸载程序。

提高应用程序

SimpleBackgroundAudio 的程序具有很大的问题。 虽然有开始音乐的播放按钮,它有没有办法暂停或关闭它。 它甚至不知道音乐的结论或其他的一切都在发生。 是的一个用户总是可以调用 UVC 控制背景音频,但开始播放音乐的任何程序还应包括其自己把它关掉的控件。

这些增强功能都包含在项目中名为播放列表­播放器。 顾名思义,这个程序播放一系列连续音轨的 — — 在这种情况下,从克劳德 · 德彪西的前奏曲,12 的小钢琴曲书 1,,因为在 1949 年由阿尔弗雷德 · 科尔托执行可从互联网档案馆的录音。

本来想创建程序中的所有音频曲目对象,然后交给他们关闭背景音频 DLL。 这似乎是更广义的程序结构,但我发现它没有工作,因为应用程序正在访问不同的实例不是在后台运行的 dll。 相反,我写的 AudioPlayer 类本身创建音频曲目的所有对象,并将它们存储在称为播放列表的泛型列表。

为了使程序在某种程度上更具挑战性,我决定播放列表不会循环:我不想跳从上次的曲目,第一首曲目,或第一次到了最后。 为此原因,音频曲目的构造函数的第一个有的最后一个参数相结合的 EnabledPlayerControls.Pause 和 EnabledPlayerControls.SkipNext 的标志 ; 最后一个音频曲目结合的暂停和 SkipPrevious 的标志。 所有其他人都有所有三个标志。 这是如何的不同轨道的紫外 C 中启用了三个按钮。

图 2 显示的 OnPlayStateChanged 和 OnUserAction 的覆盖。 在 OnPlayStateChanged,一个轨道结束时,BackgroundAudioPlayer 的跟踪属性设置下一曲目。 OnUserAction 重写 UVC 命令通过将跟踪属性设置为播放列表中的上一个或下一个音频曲目的上一曲目和下一曲目的句柄。

图 2 中 PlaylistPlayer 的 AudioPlayer 覆盖

static List<AudioTrack> playlist = new List<AudioTrack>();
static int currentTrack = 0;

...
protected override void OnPlayStateChanged(BackgroundAudioPlayer player, 
  AudioTrack track, PlayState playState)
{
  switch (playState)
  {
    case PlayState.TrackReady:
      player.Play();
      break;

    case PlayState.TrackEnded:
      if (currentTrack < playlist.Count - 1)
      {
        currentTrack += 1;
        player.Track = playlist[currentTrack];
      }
      else
      {
        player.Track = null;
      }
      break;
  }
  NotifyComplete();
}

protected override void OnUserAction(BackgroundAudioPlayer player, 
  AudioTrack track, UserAction action, object param)
{
  switch (action)
  {
    case UserAction.Play:
      if (player.Track == null)
      {
        currentTrack = 0;
        player.Track = playlist[currentTrack];
      }
      else
      {
        player.Play();
      }
      break;

    case UserAction.Pause:
      player.Pause();
      break;

    case UserAction.SkipNext:
      if (currentTrack < playlist.Count - 1)
      {
        currentTrack += 1;
        player.Track = playlist[currentTrack];
      }
      else
      {
        player.Track = null;
      }
      break;

    case UserAction.SkipPrevious:
      if (currentTrack > 0)
      {
        currentTrack -= 1;
        player.Track = playlist[currentTrack];
      }
      else
      {
        player.Track = null;
      }
      break;

    case UserAction.Seek:
      player.Position = (TimeSpan)param;
      break;
  }
  NotifyComplete();
}

程序的网页类有一系列的四个标准应用程序栏按钮可以执行 UVC 相同的功能。 它还将设置以使用当前的跟踪信息,更新屏幕的 BackgroundAudioPlayer PlayStateChanged 事件的事件处理程序和 CompositionTarget.Rendering 处理程序可以使用当前的跟踪进度更新滑块中所示图 3

The PlaylistPlayer Program
图 3 PlaylistPlayer 程序

启用和禁用应用程序栏按钮的逻辑是相当简单的:上一曲目和下一曲目按钮启用基于 PlayerControls 属性的当前的音频曲目 ; 因此,他们应符合 UVC。 如果玩家玩 ;,启用暂停按钮 如果玩家已暂停,则会启用播放按钮。 如果当前轨道为 null,戏已经启用,并且所有其他按钮被禁用。

四个应用程序栏按钮的单击处理程序使调用 BackgroundAudioPlayer 方法 SkipPrevious、 播放、 暂停和 SkipNext,分别。 很重要的是要了解这些方法调用不直接控制音乐回放的运作。 相反,这些方法调用的程序触发 OnUserAction 在 AudioPlayer 类中,调用,它是 AudioPlayer,它实际上开始和停止音乐中的代码。

这是有些特别,因为它意味着 BackgroundAudioPlayer 的播放和暂停的方法调用的行为以不同的方式取决于是否他们叫做从程序或 OnUserAction 重写。

我还添加了滑块移动到轨道中的特定位置的 ValueChanged 处理程序。 该处理程序计算轨道的新位置,并将适当的 TimeSpan 对象设置为 BackgroundAudioPlayer 的位置属性。 类似于电话播放和暂停的情况,设置此属性不会更改轨道的位置。 相反,它调用了 OnUserAction 重写中产生 AudioPlayer 与 UserAction.Seek 和编码 param 参数中的 TimeSpan 操作参数。 OnUserAction 重写,然后将此 TimeSpan 对象设置为 BackgroundAudioPlayer,位置属性,这是什么实际造成跳转。

在实践中,此滑块工作正常时,你只需要点击它向前或向后在轨道中了 10%。 如果您尝试将其滑,寻求的多个调用似乎建立,,,结果是发声的混乱。 我非常喜欢使用常规的滚动条,而不是一个滑块,因为,然后我可以等 EndScroll 事件,该事件当用户停止操纵控制。 不幸的是,我从未能够说服工作了,Windows Phone 滚动条。

局限性

很有趣,请参阅如何 Windows Phone 7.5 给了程序员更直接访问手机的硬件 (例如,饲料的视频),以及执行一些后台处理的能力。 但我不能帮助有一块的背景音频此实现中缺少的思维。

假设程序要让用户从列表中的音乐文件播放序列中选取。 该程序不能交掉整个列表的 dll,所以它需要花 BackgroundAudioPlayer 跟踪属性设置每个跟踪结束时的责任。 但发生这种情况只有当该程序运行在前台。

它是可能的应用程序和通过音频曲目类的标记属性的字符串的背景 DLL 之间传递一些信息。 但不要打扰音频曲目从派生新类,希望的将额外的信息传递给该 DLL:音频曲目对象传递到该 DLL 将覆盖为应用程序创建的副本。

幸运的是,该 DLL 已经访问应用程序的区域的独立存储,以便程序可以保存一个描述该播放列表的小文件,然后,DLL 可以从文件访问播放列表。 为此列的奖励计划是一个名为 PlaylistFilePlayer,其中演示了一种简单的方法,这种技术的项目。

查尔斯 · Petzold 是长期的 MSDN 杂志特约编辑。他的新书"编程 Windows Phone 7"(微软出版社,2010年) 是可免费下载在 bit.ly/cpebookpdf

多亏了以下的技术专家审查这篇文章: 马克 · 霍普金斯