为 SMF (Silverlight Media Framework) 创建字幕/隐藏字幕插件





0/5 (0投票)
本文将指导您创建一个自定义的 IMarkerProvider 实现,该实现允许在 SMF (Silverlight Media Framework) 中显示 SAMI 字幕。
引言
Silverlight Media Framework (简称 SMF),现已成为 Microsoft Media Platform: Player Framework 的一部分,是一个功能丰富的用于在 Silverlight 中开发媒体播放器的框架。它包含相当全面且可定制的 UI,并开箱即用地支持相当数量的视频格式和传输方式。但目前,在字幕/隐藏字幕方面确实有所欠缺。本文将指导您创建自定义字幕插件,以支持您喜欢的任何格式。
代码:前言
- 截至撰写本文时,MMPPF 版本 2.5 是最新的“稳定”版本。所有开发都基于此版本进行。
- 由于这是从一个大型闭源商业项目中提取出来的,根据公司规定等原因,我无法提供完整的源代码...对此我感到抱歉!
- 我将从创建一个基类开始,如果您只想支持一种格式,这个基类可能不太有用,但我发誓这不是因为过度设计!再说一次,这是从一个更大的代码库中提取的片段,该代码库支持不止一种格式。
- 下载部分将不包含在本文的范围内。在我的例子中,我有一个复杂的实用程序类,可以处理下载和本地
IsolatedStorage
缓存。您可以选择在插件内部或外部处理下载,或者使用本地文件,无论哪种方式最适合您。此处提供了一个简单的下载解决方案。 - 由于代码还比较“新鲜”,因此仅进行了适度测试,可能仍存在一些错误。请在评论中报告!
SMF 插件基础
在编写 SMF 插件之前,您可以阅读一些 官方文档
- 《插件开发指南》很有用,它概述了创建插件的过程并提供了一些细节,但并不完全。
- API 文档 (CHM) 经常派上用场,但有时会过时(例如,请参阅“架构”页面:没有
IMarkerProvider
!) - 源代码有时才是真正的帮助,特别是它包含一些有趣的示例,在本例中是“
Samples.MarkerProvider
”。
由于 SMF 插件使用 MEF (Managed Extensibility Framework),熟悉这个工具会很有帮助。如果您只是想开始,这不是必需的,但它可以在调试插件加载、并发或性能问题时提供很大帮助。
实现
通用基类:SubTitlePlugin
IPlugin
接口包含很多成员(见图)。幸运的是,您可能使用 Visual Studio 2008 或 2010 来处理 Silverlight,因此您可以使用出色的“实现接口”功能节省大量的打字时间。

那么,让我们开始编写代码吧!首先,我将创建一个名为 SubTitlePlugin
的新空类,将其设为 abstract
,并实现 Microsoft.SilverlightMediaFramework.Plugins.IMarkerProvider
。此类将提供字幕插件的所有通用功能,从而减少处理实际插件时的代码“冗余”。 *这将会是大量的“必需样板代码”。* 无论如何,如果您不复制代码,您会想右键单击 IMarkerProvider
并选择“实现接口”=>“实现接口”在 Visual Studio 中。这将为所有接口成员、属性、方法和事件生成存根。现在,让我们填写我们的第一个 public
方法。
/// <summary>
/// Start the retrieval of markers.
/// This is asynchronous.
/// </summary>
public void BeginRetrievingMarkers()
{
if (Source == null)
throw new ArgumentNullException("This requires a valid Source to download from");
_cancelRequested = false;
#if USE_DOWNLOAD_MANAGER
// Initiate file download.
var localFileName = GetFileName(Source);
DownloadManager.DownloadFile(url = Source, localFileName = localFileName,
source = this, maxAge = null, completed = ReadSubtitles, error = DownloadError);
#elif USE_WEBCLIENT
// Basic WebClient version:
var client = new WebClient();
client.DownloadStringCompleted += client_DownloadStringCompleted;
client.DownloadStringAsync(Source);
#endif
}
我在这里提供了一个基于 WebClient
的基本检索方法,您可以将其用于此目的,但在接下来的步骤中,我将使用我自定义的基于 DownloadManager
的代码。 DownloadManager
方法参数应该是不言自明的,不过,有趣的是,一旦文件成功下载,它将调用 ReadSubtitles
,这是我们下面将要介绍的下一个方法,以及一些 private
成员和另一个回调方法 DownloadError
。如果您使用的是 WebClient
方法,主要区别在于您获得的是一个 string
结果而不是 byte[]
。
private bool _cancelRequested;
private MediaMarkerCollection<MediaMarker> _subtitles;
private void ReadSubtitles(object source, byte[] data)
{
if (_cancelRequested)
{
_cancelRequested = false;
return;
}
try
{
_subtitles = LoadMarkers(data);
}
catch (Exception ex)
{
if (RetrieveMarkersFailed != null)
RetrieveMarkersFailed(this, ex);
return;
}
if (NewMarkers != null)
NewMarkers(this, _subtitles);
}
private void DownloadError(object source, Exception error)
{
if (RetrieveMarkersFailed != null)
RetrieveMarkersFailed(this, error);
}
这是我们插件的一个非常重要的方法。它调用 abstract protected
LoadMarkers
方法,该方法将执行我们所选格式的实际解析。它还处理一些强制性的事件处理,特别是 NewMarkers
调用,此时插件会通知 SMFPlayer
它有一些内容可提供,还记得接口名 IMarkerProvider
吗?如果您忘记此调用,用户将**看不到任何内容**!您的插件可能正确解析了插件,但它们永远不会显示在播放器中!
注意:如果您不喜欢无休止的 if (x != null) x(...);
事件触发构造,SMF 源代码包含一个扩展方法 IfNotNull
,您可能想查看一下。
现在我们有了 SubTitlePlugin
的核心代码!其余大部分是自动属性和事件,除了下面的两个方法:Load
和 Unload
。顾名思义,它们分别包含初始化和清理代码。
/// <summary>
/// Loads the plug-in.
/// </summary>
public void Load()
{
try
{
DoLoadPlugin();
IsLoaded = true;
if (PluginLoaded != null)
PluginLoaded(this);
}
catch (Exception ex)
{
if (PluginLoadFailed != null)
PluginLoadFailed(this, ex);
}
}
/// <summary>
/// Unloads the plug-in.
/// </summary>
public void Unload()
{
try
{
DoUnloadPlugin();
_subtitles = null;
IsLoaded = false;
if (PluginUnloaded != null)
PluginUnloaded(this);
}
catch (Exception ex)
{
if (PluginUnloadFailed != null)
PluginUnloadFailed(this, ex);
}
}
这些方法非常直接:它们处理事件触发,并提供两个 protected virtual
(因此是可选的)方法,允许派生类执行特定的加载/卸载任务。
基类到此为止。您可以在 此处的源代码包中找到它。
具体子类:SamiProvider
到目前为止,我们还没有用到任何与 MEF 相关的内容。基类不是可实例化的,因此不需要 MEF 装饰器。另一方面,我们的具体格式类需要一些装饰器“魔法”才能在 SMFPlayer
查找插件时被找到。《插件开发指南》在此提供了很好的信息。
因此,让我们创建一个名为 SamiProvider
的第二个类,它将派生自 SubTitlePlugin
。这是该类的全部代码
/// <summary>
/// This class provides SAMI subtitle support as a SMF player plugin.
/// </summary>
[ExportMarkerProvider(
PluginName = PluginName,
PluginDescription = PluginDescription,
PluginVersion = PluginVersion,
SupportsPolling = false,
SupportedFormat = "SAMI")]
[PartCreationPolicy(CreationPolicy.NonShared)]
public class SamiProvider : SubTitlePlugin
{
#region MetaData
private const string PluginName = "SAMICaptionsPlugin";
private const string TypeAttribute = "type";
private const string SubTypeAttribute = "subtype";
private const string PluginDescription =
"Provides SMF with the ability to parse and display SAMI captions.";
private const string PluginVersion = "1.0";
#endregion
/// <summary>
/// Load the markers from the data.
/// </summary>
/// <param name="data">A SAMI file as a byte array</param>
/// <returns>A collection of markers suitable for use with SMF</returns>
protected override MediaMarkerCollection<MediaMarker> LoadMarkers(byte[] data)
{
SamiLoader loader = new SamiLoader();
return loader.ParseSAMI(data);
}
}
注意: 整个 MEF 部分 heavily 基于前面提到的官方示例(MMPPF 源代码中的“Samples.MarkerProvider
”)。
第一个装饰器是 ExportMarkerProvider
。它的前三个参数来自 IPlugin
,它们只是插件的文本标识。其他参数是 IMarkerProvider
特有的。
- 对于这一类插件,
SupportsPolling
为false
,它在SubTitlePlugin
中被禁用(参见PollingInterval
),对于基于文件的、下载的字幕,它不相关。如果使用例如标记流,它会很有用。 - 最后一个参数
SupportedFormat
实际上是所有参数中最重要的。正是这个参数允许选择此插件来处理 SAMI 字幕。
第二个装饰器 PartCreationPolicy
配置了插件的实例化。有关详细信息,我将引导您到 万能的 MSDN。在这里,我们说我们希望每个字幕文件有一个插件实例。这一点很重要,因为检索和解析是异步的,所以我们不希望多个请求干扰我们的插件。
SamiLoader
类是我将下载的数据解析为 MediaMarkers
集合的地方。真正的诀窍在于使用 CaptionRegion
。您应该为每个您希望显示的连续文本块创建一个。
使用插件
现在我们有了创建插件的结构,但它们还不会被激活。在我的项目中,我正在使用一个 CustomPlaylistItem
,但为了简单起见,我将只解释使用的方法。如果您对自定义播放列表项感兴趣,可以在 源代码 中找到它。
void SetupMarkers(PlaylistItem playListItem, string subtitleUrl)
{
string format = "SAMI";
var markers = new MarkerResource { Source = new Uri(subtitleUrl), Format = format };
if (playListItem.MarkerResources != null)
{
playListItem.MarkerResources.Clear();
}
else
{
playListItem.MarkerResources = new List<MarkerResource>();
}
playListItem.MarkerResources.Add(markers);
}
关键点在于 format
规范。这将允许选择正确的插件。如果您有多个插件,可以通过在此处传递相应的 string
来选择与您的字幕格式匹配的插件。
历史
- 2011 年 9 月 20 日 - 首个版本