65.9K
CodeProject 正在变化。 阅读更多。
Home

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

emptyStarIconemptyStarIconemptyStarIconemptyStarIconemptyStarIcon

0/5 (0投票)

2011年9月20日

CPOL

6分钟阅读

viewsIcon

42518

downloadIcon

362

本文将指导您创建一个自定义的 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,因此您可以使用出色的“实现接口”功能节省大量的打字时间。

IMarkerProvider members

那么,让我们开始编写代码吧!首先,我将创建一个名为 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 的核心代码!其余大部分是自动属性和事件,除了下面的两个方法:LoadUnload。顾名思义,它们分别包含初始化和清理代码。

/// <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 特有的。

  • 对于这一类插件,SupportsPollingfalse,它在 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 日 - 首个版本
© . All rights reserved.