立即动手尝试

使用 Silverlight Media Framework 构建自定义播放器

Ben Rush

流媒体已在网络上无处不在。似乎所有人(从新闻站点到社交网络再到隔壁的邻居)都在享受在线视频体验。由于用户群的不断攀升,大多数站点需要以一种值得信赖、用户友好的方式为其客户提供高品质视频,通常为高品质带宽感知 视频。

在线媒体传输体验的核心要素是播放器本身。播放器是用户与之发生交互的介质,它可以驱使用户获得在线体验的每一个要素。播放器如此备受关注,毫无疑问,基于 Web 的现代媒体播放器在实现时势必要比几年前的老款播放器复杂得多。因此,开发人员需要一个稳健的框架来构建播放器。

Silverlight Media Framework (SMF) 是一个开源项目,由 Microsoft 在 2009 年 Microsoft 专业开发人员大会上发布。此项目是一个可扩展的、具备高度伸缩性的 Silverlight 视频框架,它直接解决了开发人员和设计人员的需求,为其提供了稳定的内核来创建自己的播放器。Silverlight Media Framework 的核心代码已根据从 NBC Olympics 和 Sunday Night Football Web 视频项目中所学的课程经过优化。

本文将介绍 SMF 的基本要素,演示如何将 SMF 集成到您自己的播放器项目中,并通过一个简单项目向您逐步介绍如何使用 SMF 打造自定义播放器体验。我将向您展示如何使用 SMF 的日志记录、设置和事件处理功能。最后,我将创建一个播放器应用程序,该应用程序会在当前的视频播完后显示建议您日后观看的视频。

SMF 入门

开始前,您需要先从 Codeplex (smf.codeplex.com) 下载框架。您还需要下载平滑流式处理播放器开发工具包 (iis.net/expand/smoothplayer) 并在使用 SMF 的任何项目中参考它。平滑流式处理播放器开发工具包不属于 SMF,它是完全独立的封闭源组件。不过,SMF 可以利用该工具包中的一组核心功能,特别是视频播放器本身。在撰写本文档时,平滑流式处理播放器开发工具包为 beta 2 版本。

SMF 包含大量 Microsoft .NET 程序集(如图 1 所示),每个程序集都构成整个框架的不同功能部分。

图 1 Silverlight Media Framework 程序集

核心程序集是 Microsoft.SilverlightMediaFramework.dll,它由框架其余部分引用的众多实用程序类和类型组成。使用 SMF 的任意部分时,也必须引用 Microsoft.SilverlightMediaFramework.dll 程序集。

Microsoft.SilverlightMediaFramework.Data 命名空间提供帮助程序类,以使用播放器外部的数据以及封装播放器内部的数据。该数据可以是任何格式的通用数据,但也可以是播放器自身的设置信息。Microsoft.SilverlightMediaFramework.Data.Settings 是另一个命名空间,用于显示类型和处理播放器设置。

除了用于设置的数据以外,您最想要与之发生交互的 Data 命名空间中的类型是流外 DataClient 类,它可以从外部源检索数据。如果您想要下载和使用播放器外部的数据,请引用此程序集。

SMF 播放器包含稳健的 Microsoft.SilverlightMediaFramework.Logging 框架,该框架使用回调模式。在这种模式下,写入日志记录基础结构会引发事件。您可以使用日志记录系统注册自己的回调方法,这些回调一旦激活,便会执行其他操作,如向 Web 服务发送信息或在文本框中显示信息。如果您想要使用 SMF 的内置日志记录工具,请引用此程序集。

Microsoft.SilverlightMediaFramework.Player 程序集实现播放器本身。它还提供播放器所需的大量控件,如洗带器、音量控件和时间线标记。默认的 SMF 播放器界面布局简洁、雅致,对于任何需要 Silverlight 播放器的项目来说,都是一个良好的开端。但是,SMF 中定义的所有控件的核心都是控制模板概念,因此每个控件都可以使用 Expression Blend 或 Visual Studio 等工具作为主题。

构建和引用 SMF

SMF 作为单个 .zip 文件下载,其中包含一个解决方案文件、每个输出库对应的项目,以及用于运行和验证播放器自身的测试项目。

SMF 依赖于平滑流式处理播放器开发工具包。要引用该工具包,请将平滑流式处理程序集 (Microsoft.Web.Media.SmoothStreaming.dll) 移到 SMF 项目的 \Lib 文件夹中。

然后,在 Visual Studio 中打开 SMF 解决方案并进行构建,创建所有需要利用该框架的程序集。要确认是否一切都如期进行,请按 F5 开始进行调试。系统将构建该解决方案并执行 Microsoft.SilverlightMediaFramework.Test.Web 目标,向您显示对“Big Buck Bunny”视频进行流式处理的默认 SMF 播放器(参见图 2)。注意,默认播放器是否已包含洗带的位置元素、播放/停止/暂停按钮、音量控件、全屏控件等。

图 2 SMF 播放器和 Big Buck Bunny 视频

下一步是创建自己的独立 Silverlight 项目并利用其所包含的 SMF 功能。在 Visual Studio 中,单击“文件”|“新建”|“项目”|“Silverlight 应用程序”。调用 SMFPlayerTest 解决方案,然后单击“确定”。将弹出一个模式对话框,询问您是否要将 Silverlight 应用程序托管到新的网站中。单击“确定”,您将看到一个基本的 Silverlight 应用程序解决方案,该解决方案由两个项目组成,分别是 SMFPlayerTest 和 SMFPlayerTest.Web。

最后一步是从新建项目中引用平滑流式处理播放器开发工具包和 SMF 程序集。将输出 SMF 程序集和平滑流式处理播放器开发工具包从 SMF 解决方案的 Debug 文件夹中复制并粘贴到新项目中(如图 3 所示)。现在,新解决方案将包含利用 SMF 所需的所有程序集引用。

图 3 引用所需的程序集

显示播放器

要开始使用 SMF,请将 SMF 播放器的命名空间添加到 MainPage.xaml 页中。这样做可以确保所有引用都可以正确地解析:

xmlns:p="clr-namespace:Microsoft.SilverlightMediaFramework.Player;assembly=Microsoft.SilverlightMediaFramework.Player"

现在,将播放器的 XAML 插入此页面的 LayoutRoot Grid 控件中: 

<Grid x:Name="LayoutRoot">
  <p:Player>
  </p:Player>
</Grid>

按 F5 将启用项目并打开 SMF 播放器。但是,因为播放器不知道要播放哪些内容,所以它不会执行任何操作。您所得到的只是一个播放器,不包含任何要播放的内容。

SMF 使用 SmoothStreamingMediaElement(位于平滑流式处理播放器开发工具包中)播放视频。从 SmoothStreamingMediaElement 中,SMF 会继承自有的播放器,名为 CoreSmoothStreamingMediaElement。如果希望播放器对内容进行流式处理,则需要此对象。确保将 SmoothStreamingSource 属性设置为一个有效的平滑流媒体 URL: 

<Grid x:Name="LayoutRoot">
  <p:Player>
    <p:CoreSmoothStreamingMediaElement
      AutoPlay="True"
      SmoothStreamingSource="replace with address to content here"/>
  </p:Player>
</Grid>

如前所述,Microsoft 提供“Big Buck Bunny”视频流示例,开发人员可以用它来测试 Silverlight 项目。要使用这个测试流,请将 CoreSmoothStreamingMediaElement 上的 SmoothStreamingSource 属性设置为:

http://video3.smoothhd.com.edgesuite.net/ondemand/Big%20Buck%20Bunny%20Adaptive.ism/Manifest

请再次按 F5 构建并运行该项目。浏览器将和以前一样使用相同的播放器执行操作,但是这次,“Big Buck Bunny”视频将在播放器安全加载好后开始进行流式处理。如果您的任务是创建一个基本的 Silverlight 播放器对内容进行流式处理,您已实现此目标。

但是,SMF 的功能远不止我们现在所看到的这些。让我们添加一些基本的日志记录。

在播放器中进行日志记录

在 SMF 中进行日志记录很简单,记录事件时,会引发一个 LogReceived 事件。您为此事件注册了一个事件处理程序,因此每次引发一个日志记录事件都会收到一则相应的通知。要对此通知采取何种措施取决于您;您可以在播放器的一个新窗口中显示它、在引发特定事件时筛选事件并通知 Web 服务,或者采取任何当前情况下所需的措施。

LogReceived 事件在 Logger 类中静态定义(在 Microsoft.SilverlightMediaFramework.Logging.dll 中定义),因此您可以在项目的任意位置注册日志记录事件。下面是一个在 SMFPlayerTest 项目的 MainPage.xaml 文件中注册和定义事件处理程序的示例:

public partial class MainPage : UserControl {
  public MainPage() {
    InitializeComponent();

    Logger.LogReceived += 
      new EventHandler<SimpleEventArgs<Log>>(
      Logger_LogReceived);
  }

  void Logger_LogReceived(object sender, 
    Microsoft.SilverlightMediaFramework.SimpleEventArgs<Log> e) {
    throw new NotImplementedException();
  }
}

SMF 会立即引发多个事件。要查看这些事件,请在 Logger_LogReceived 方法中创建一个断点,然后再次在 Debug 模式下运行播放器。几乎同时,系统会立即点击您的断点,从而让您可以逐步了解方法的参数并查看传递给它的信息。 

日志事件数据被封装在一个特定的消息对象中,该对象的类型必须继承自一个名为 Log 的抽象类。此抽象日志类型包含三个属性:Sender、Message 和 TimeStamp。Sender 会引用引发事件的对象。Message 是包含日志记录事件文本的 System.String 类型的对象。TimeStamp 只包含日志记录对象首次实例化的日期和时间。作为第二个参数传递给您的事件处理程序的 SimpleEventArgs<> 对象包含一个通过其 Result 属性指向 Log 对象的引用。 

要引发一个日志事件,只需对从 Log 基类继承的类型进行实例化,然后将此类型传递给 Logger 类型上静态定义的 Log 方法即可。该框架提供一个已从 Log 基类型继承的 DebugLog 类。但是,DebugLog 类型的特别之处在于,如果 Silverlight 项目要引用的库使用 Debug 版 SMF 创建,将 DebugLog 类型传递给 SMF 日志记录框架将引发一个相应的日志记录事件(从而调用您的事件处理程序)。另一方面,Release 版的 SMF 则会忽略任何对 Log 方法的调用,该调用会获取已传递的 DebugLog 类。简而言之,如果您拥有调试声明,您只需使用 Dbug 版,将 DebugLog 对象用作日志事件参数;否则,您将需要构造从抽象 Log 类型继承的属于自己的类型。

下面是一个通过 SMF 事件系统引发 Listening 事件的示例,方法是:先对 DebugLog 对象进行实例化,然后将它传递给 Logger 的静态 Log 方法(确保平滑流式处理播放器开发工具包文件在 Debug 设置下构建):

public MainPage() {
  InitializeComponent();

  Logger.LogReceived += 
  new EventHandler<SimpleEventArgs<Log>>(
    Logger_LogReceived);

  Logger.Log(new DebugLog { 
    Message = "Listening!", Sender = this }); 
}

从 Player 类继承

虽然日志记录是播放器的核心功能,但是从 SMF Player 类型继承并开始对其进行扩展时,唯一可以访问的却是 SMF 播放功能。

要了解该功能的工作原理,您需要创新一个从 Player 类型继承的名为 SMFPlayer 的新类。

新的 SMFPlayer 类如下所示:

namespace SMFPlayerTest {
  public class SMFPlayer : Player {
    public override void OnApplyTemplate() {
      base.OnApplyTemplate();
    }
  }
}

每个 FrameworkElement 类(如 SMF 中的 Player)都有一个在引发 ApplyTemplate 事件时调用的 OnApplyTemplate 方法。该方法通常可用作初始化 FrameworkElement 类型的有用起点。

在这种情况下,我可以从新的 SMFPlayer 类中覆盖默认的 OnApplyTemplate 方法。要证明执行的是新的 SMFPlayer 类型,而不是默认的 Player 类型,您可以在覆盖中设置一个断点。在 Visual Studio 中调试播放器时,Silverlight 会在执行 SMFPlayer 时遇到此断点。

现在,请更新 MainPage.xaml 文件以使用新的播放器类。首先,将播放器的命名空间添加到已引用的命名空间列表中(就像您以前对播放器命名空间所做的那样):

xmlns:smf="clr-namespace:SMFPlayerTest"

然后,只需在 XAML 中更新 Player 标记即可使用 SMFPlayer,而不是 Player:

<Grid x:Name="LayoutRoot">
  <smf:SMFPlayer>
    <p:CoreSmoothStreamingMediaElement
      AutoPlay="true"
      SmoothStreamingSource="http://..."/>
  </smf:SMFPlayer>
</Grid>

接下来,对 DebugLog 类进行实例化并将它传递给 Log 方法,如前所示。这样做将触发以前为其注册事件处理程序的事件:

public override void OnApplyTemplate() {
  Logger.Log(new DebugLog {
    Message = "Hello from OnApplyTemplate!",
    Sender = this
    });

  base.OnApplyTemplate();
}

要专门从事件处理程序中侦听此事件,请筛选 DebugLog 对象本身的 Message 属性。在下例中,查找任何包含“OnApplyTemplate”的消息:

void Logger_LogReceived(
  object sender, SimpleEventArgs<Log> e) {
  if (e.Result.Message.Contains("OnApplyTemplate")) {
    return;
  }
}

使用设置数据

一个用于处理设置的成熟框架对大部分大型软件项目而言至关重要。SMF 中用于处理设置的代码构建于 Microsoft.SilverlightMediaFramework.Data.dll 程序集基础上,您可以借助该代码下载通用的外部数据。SMF 的设置层使用此基础结构来访问和下载驻留在 Web 服务器上的特定格式的 XML 设置文件。成功下载和读取设置数据后,SMF 设置层会使用 SettingsBase 对象将其封装起来,然后使用该对象的方法检索设置值。

SettingsBase 类,正如其名称所指,用作一个更具体的类的基础,该类可以强类型化的方式访问您的设置值。下面是一个继承自 SettingsBase 的类的示例。它有两个属性,一个用于检索视频播放器源 URL,另一个用于检索指示视频播放器是需要自动启动还是等待查看者按播放按钮的布尔值:

namespace SMFPlayerTest {
  public class SMFPlayerTestSettings : SettingsBase {
    public Uri VideoPlayerSource {
      get { return new Uri(
        GetParameterValue("videoSource")); }
    }

    public bool? AutoStartVideo {
      get { return GetParameterBoolean(
        "autoStart"); }
    }
  }
}

属性方法使用 SettingsBase 类实现的功能来检查已载入此类型(通过稍后讨论的机制)的设置名称/值对的基础集合。这提供了一种类型安全、对 IntelliSense 友好的设置信息检索方法。

现在,在 SMFPlayerTest.Web 项目中创建一个新 XML 文件,将其命名为 SMFPlayerSettings.xml,然后向其添加以下项:

<?xml version="1.0" encoding="utf-8" ?>
<settings>
  <Parameters>
    <Parameter 
      Name="videoSource" 
      Value="http://video3.smoothhd.com.edgesuite.net/ondemand/Big%20Buck%20Bunny%20Adaptive.ism/Manifest"/>
    <Parameter Name="autoStart" Value="True"/>
  </Parameters>
</settings>

然后,创建一个要向其中加载 XML 设置的 SettingsClient 对象。SettingsClient 会获取一个指向设置文件的 URI:

m_settingsGetter = new SettingsClient(
  new Uri("http://localhost:10205/SMFPlayerSettings.xml"));

检索设置数据是一个异步过程,因此必须在 SettingsClient 上为 RequestCompleted 方法指定一个回调方法:

m_settingsGetter.RequestCompleted += 
  new EventHandler<SimpleEventArgs<SettingsBase>>
  (m_settingsGetter_RequestCompleted);

最后一步是在 SettingsClient 对象上调用无参数 Fetch 方法。检索到数据后,将调用 settingsGetter_RequestCompleted 事件处理程序,并将 SettingsBase 对象传递给它:

void m_settingsGetter_RequestCompleted(
  object sender, SimpleEventArgs<SettingsBase> e) {

  SettingsBase settingsBase = e.Result;
  return; 
}

传递给 settingsGetter_RequestCompleted 方法的 SettingsBase 对象加载了底层框架从 SMFPlayerSettings.xml 文件解析的名称/值对。为了将此数据加载到 SMFPlayerTestSettings 对象中,您只需调用 Merge 方法,该方法会将 SettingsBase 派生的对象中的设置信息与另一个对象中的设置信息结合起来:

SettingsBase settingsBase = e.Result;
m_settings.Merge(settingsBase);

this.mediaElement.SmoothStreamingSource = 
  m_settings.VideoPlayerSource;
this.mediaElement.AutoPlay = 
  (bool)m_settings.AutoStartVideo; 

return;

您不再需要对 XAML 页中 CoreSmoothStreamingMediaElement 上的 AutoPlay 和 SmoothStreamingSource 属性进行硬编码,因为播放器设置已从 OnApplyTemplate 方法中下载下来。您只需对播放器 XAML 执行以下操作:

<Grid x:Name="LayoutRoot">
  <smf:SMFPlayer>
    <p:CoreSmoothStreamingMediaElement/>
  </smf:SMFPlayer>
</Grid>

运行播放器时,将加载所有设置数据,回调会将值加载到播放器的媒体元素中,视频将开始像以前一样进行流式处理。

扩展 SMF 播放器

在许多著名的视频站点上,当视频播放完成时,您将看到一列类似视频或建议观看的视频。为了说明 SMF 播放器扩展多么容易实现,我们将为您逐步介绍在项目中构建一个类似的建议观看功能。

首先将 x:Name 属性添加到 MainPage.xaml 文件中的 Player 元素:

<Grid x:Name="LayoutRoot">
  <smf:SMFPlayer x:Name="myPlayer">
    <p:CoreSmoothStreamingMediaElement/>
  </smf:SMFPlayer>
</Grid>

这可以更加容易地在 Visual Studio 和 Expression Blend 中按名称引用 SMFPlayer 对象。

现在,在“解决方案资源管理器”中右键单击 MainPage.xaml,然后选择“在 Expression Blend 中打开”。将启动 Expression Blend 3 并显示 SMF 播放器的设计界面。在“对象和时间线”部分,您将在可视对象树中找到 myPlayer 节点,该节点对应于以前为 SMFPlayer 对象指定的名称。目标是创建 SMFPlayer 的模板,然后在该模板中添加三个“建议”按钮。通过使用 Expression Blend 中的模板,您可以添加、编辑或删除播放器中内置的控件。

要创建模板,请在“对象和时间线”窗口中右键单击 myPlayer,然后选择“编辑模板”|“编辑副本”。将会显示“创建样式资源”对话框,单击“确定”。要在视频播放器的顶部插入三个按钮,请在“工具”窗口中对每个要添加的按钮双击按钮图标。现在,三个按钮应会在组成播放器模板的控件树中显示(参见图 4)。

图 4 添加到控件树的按钮控件

在树中选择所有三个按钮,访问控件的属性窗口,并将水平和垂直对齐设置为居中对齐(参见图 5),这样按钮便会位于视频播放器的中间位置。

图 5 设置按钮控件对齐

按钮为默认大小,并相互堆叠。将每个按钮的宽度设置为 400,高度设置为 75。然后调整边距,使第一个按钮偏移底部 175 像素,第二个按钮偏移顶部 175 像素,第三个按钮无边距偏移。最终结果将如图 6 所示。

图 6 Expression Blend 中的居中按钮

要确定按钮正好位于播放器上,请保存 Expression Blend 中所有打开的文件,然后返回 Visual Studio。Visual Studio 可能会提示您重新加载由 Expression Blend 更改的文档。如果出现此提示,请单击“确定”。在 Visual Studio 中,按 F5 在“调试”模式下重新启动 SMF 播放器。现在,播放器应会在视频屏幕的中间显示三个按钮,如图 7 所示。

图 7 SMF 播放器中的居中按钮

挂接事件处理程序

现在,事件处理程序必须与按钮相关联。要从代码引用按钮,您需要为这些按钮指定名称,您可以通过“属性”选项卡中的“名称”文本框来实现。为简便起见,请将这些按钮命名为 Button1、Button2 和 Button3。完成后,需要更新“对象和时间线”窗口,便会显示与可视树中的按钮图标相邻的按钮名称。

在每个按钮的“属性”选项卡中,您将发现一个用于指定可视组件的事件处理程序的“事件”按钮。选择其中一个按钮,单击“属性”选项卡中的“事件”按钮,然后双击“单击文本框以在 MainPage.xaml.cs 中自动生成一个事件处理程序”。现在,每个按钮的属性窗口将包含一个为其 Click 事件指定的事件处理程序(参见图 8),MainPage.xaml.cs 文件将包含为每个按钮的 Click 事件指定的事件处理程序。

图 8 设置事件处理程序

现在,您可以调试播放器。单击屏幕上的任意按钮将引发一个 Click 事件,该事件现在由 MainPage.xaml.cs 中自动生成的方法处理。

建议的视频

现在,请使用这些按钮启用建议的视频功能。下面的 XML 将显示这些建议:

<?xml version="1.0" encoding="utf-8" ?>
<Suggestions>
  <Suggestion DisplayName="A suggestion" Url=""/>
  <Suggestion DisplayName="Another suggestion" Url=""/>
  <Suggestion DisplayName="My final suggestion" Url=""/>
</Suggestions>

Url 属性的值将指定单击此按钮时播放器要加载的视频,DisplayName 属性是要写在该按钮上的文本。在 SMFPlayerTest.Web 项目中用名称 Suggestions.xml 保存此文件。

DataClient 类型(在 Microsoft.SilverlightMediaFramework.Data 命名空间中)将用于下载 XML 文档和以类型安全的方式显示内容。要以强类型样式显示从 XML 文件中读取的每个“建议”,请在 Silverlight 项目中创建一个名为 SMFPlayerTestSuggestion 的类:

namespace SMFPlayerTest {
  public class SMFPlayerTestSuggestion {
    public string DisplayName;
    public Uri Url; 
  }
}

和 SettingsBase 一样,DataClient 想要派生自某个类,该类可从 XML 内容(此时为一个 SMFPlayerTestSuggestion 对象数组)以强类型样式显示数据。

在 SMFPlayerTest 对象中创建另一个名为 SMFPlayerTestDataClient 的类:

namespace SMFPlayerTest {
  public class SMFPlayerTestDataClient : 
    DataClient<SMFPlayerTestSuggestion[]> {

    public SMFPlayerTestDataClient(Uri Url) : base(Url) { }

    protected override void OnRequestCompleted(
      object sender, SimpleEventArgs<string> e) {

      throw new NotImplementedException();
    }
  }
}

SMFPlayerTestDataClient 继承自 DataClient 并将其模板参数设置为一个 SMFPlayerTestSuggestion 类型数组。DataClient 基类提供登录网络并下载外部 XML 文件所需的所有异步联网逻辑。但是,等内容下载完后,DataClient 基类将调用 OnRequestCompleted 并希望随后会执行所有 XML 数据处理过程。换言之,DataClient 基类会下载内容,实现程序则负责对内容进行处理。

下面是 OnRequestCompleted 的更完整的实现过程:

protected override void OnRequestCompleted(
  object sender, SimpleEventArgs<string> e) {

  XDocument doc = XDocument.Parse(e.Result);
  List<SMFPlayerTestSuggestion> suggestions = 
    new List<SMFPlayerTestSuggestion>();
  foreach (XElement element in doc.Descendants("Suggestion")) {
    suggestions.Add(new SMFPlayerTestSuggestion {
      DisplayName = element.Attribute("DisplayName").GetValue(),
      Url = element.Attribute("Url").GetValueAsUri()
    });
  }

  base.OnFetchCompleted(suggestions.ToArray()); 
}

为简便起见,我已在此实现过程中使用 LINQ to XML 来解析 XML 中所需的元素和属性。当检索到每个 Suggestion 节点中的 DisplayName 和 Url 属性值后,将对 SMFPlayerTestSuggestion 对象进行实例化并指定相应的值。

最后一步是调用 OnFetchCompleted 事件。SMFPlayerTestDataClient 的外部客户会向 FetchCompleted 事件注册事件处理程序,以便在建议的视频数据下载完成后发出通知。因为 OnRequestCompleted 已以类型安全的方式打包 XML 数据,每个事件处理程序将收到一个便利的 SMFPlayerTestSuggestion 对象数组,各自对应 DataClient 基类下载的 XML 文档中的每个 Suggestion 元素。

底层 DataClient 提供一种名为 Fetch 的方法。一旦调用该方法,便会开始异步下载内容。要在视频结束时开始下载建议数据,请将名为 mediaElement_MediaEnded 的事件处理程序附加到 MediaElement 对象上的 MediaEnded 事件:

void mediaElement_MediaEnded(
  object sender, RoutedEventArgs e) {

  m_client = new SMFPlayerTestDataClient(
    new Uri("http://localhost:10205/Suggestions.xml"));
  m_client.FetchCompleted += 
    new EventHandler<SimpleEventArgs<
    SMFPlayerTestSuggestion[]>>(m_client_FetchCompleted);
  m_client.Fetch(); 
}

mediaElement_MediaEnded 方法会创建一个 SMFPlayerTestDataClient 类型的实例,为 FetchCompleted 事件指定另一个事件处理程序,然后调用 Fetch 开始下载过程。通过调用以前在 OnRequestCompleted 中实现的 OnFetchCompleted(内容下载完成后由 DataClient 基类调用),将会调用 FetchCompleted 处理程序。

实现在 mediaElement_MediaEnded 中注册的 suggestion_FetchCompleted,会获取强类型的 Suggestion 数据数组,并为每个按钮指定一个 Suggestion:

void m_client_FetchCompleted(
  object sender, SimpleEventArgs<
  SMFPlayerTestSuggestion[]> e) {
  for (int c = 1; c <= 3; c++) {
    Button btn = (Button)GetTemplateChild(
      "Button" + c.ToString());
    btn.Tag = e.Result[c - 1].Url;
    btn.Content = 
      e.Result[c - 1].DisplayName; 
  }
}

底层 FrameworkElement 类型上的方法 GetTemplateChild,可以引用 MainPage XAML 中定义的每个按钮。对于每个按钮,将为 Content 属性指定显示文本,并为 Tag 属性指定 URI。然后,每个按钮的单击事件处理程序可以从 Tag 属性中拖出 URI 并将 URL 指定给播放器的 MediaElement 以播放流媒体:

private void Button1_Click(
  object sender, System.Windows.RoutedEventArgs e) {

  Uri redirectUrl = (Uri)((Button)sender).Tag;
  myPlayer.MediaElement.SmoothStreamingSource = 
    redirectUrl; 
}

显示按钮

最后一步是隐藏按钮,直到当前进行流式处理的视频完成,这时按钮将变为可见。当用户单击某个按钮时,这些按钮会再次隐藏起来。

在 Visual Studio 中,可以通过为 SMFPlayer 类添加两个 TemplateVisualState 属性来对其进行编辑:

[TemplateVisualState(Name = "Hide", GroupName = "SuggestionStates")]
[TemplateVisualState(Name = "Show", GroupName = "SuggestionStates")]
public class SMFPlayer : Player

TemplateVisualState 是一个特别强大的属性,可以定义对象处于可视状态。当可视状态变得有效时,Silverlight 将更新属于指定类的可视元素的属性,如子按钮控件的可视性。

要设置当前的可视状态,请使用 VisualStateManager 类的静态 GoToState 方法(本机 Silverlight 类型)。TemplateVisualState 的 GroupName 属性喜欢组合状态,TemplateVisualState 的 Name 属性则会指定各个状态。

返回 Expression Blend。在 myPlayer 模板中,单击设计器窗口正上方的 myPlayer,然后单击“编辑模板”|“编辑当前值”。单击“状态”选项卡,并向下滚动 SuggestionStates,如图 9 所示。

图 9 SuggestionStates 的可视状态

属性创建的两个 SuggestionStates 分别显示为“隐藏”和“显示”。如果单击“隐藏”,则左侧会显示一个红色圆圈,表明 Expression Blend 正在记录设计器中所做的任何属性更改。Expression Blend 会继续记录属性更改,直到再次单击“隐藏”,此时红色记录圈会消失。

当 Expression Blend 积极记录“隐藏”可视状态时,请将按钮设置为“折叠”。在“对象和时间线”窗口下选择所有三个按钮,并在“属性”选项卡中选择“折叠”作为其“可视性”。通过再次单击“隐藏”按钮,停止记录“隐藏”可视状态。现在,单击“显示”,一个红色圆圈将显示在“显示”可视状态的左侧。通过单击“可视性”下拉菜单右侧的“高级属性选项”按钮,并选择“记录当前值”,这时可以明确将可视性状态记录为“可视”。保存所有打开的文档,再次返回 Visual Studio。

本机 Silverlight 类 VisualStateManager 可用于明确设置当前有效的可视状态。在播放器的 OnApplyTemplate 方法中,将“隐藏”设置为当前有效的可视状态:

VisualStateManager.GoToState(this, "Hide", true);

在 suggestion_FetchCompleted 中,将“显示”设置为当前有效的状态,以在流式处理结束以及“建议”数据下载完成时显示按钮:

VisualStateManager.GoToState(this, "Show", true);

要在单击按钮(或重新播放最初的流媒体)时隐藏按钮,请为 MediaElement 的 MediaOpened 事件创建一个新事件处理程序,并将可视状态设置为“隐藏”。

最后一次启动并调试播放器。您将看到按钮不可见,直到视频播放完毕,这时按钮将变为可见。单击某按钮,可将播放器导航到按钮对应的“建议”设置中指定的任意 URL。

利用 Codeplex 上的 SMF 项目空间,您可以访问代码库、文档、讨论内容和 Issue Tracker。欢迎您积极投稿。投入更多的项目创意,每个人都能享受到更加优秀的项目成果。

Ben Rush 是一名具有 18 年从业经验的资深软件开发工程师,专门研究 Microsoft .NET 框架和 Microsoft 相关技术。他喜欢智能编码和快速骑自行车。