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

使用 JavaScript 进行 HTTP 流式传输:AJAX 视频播放器

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.69/5 (21投票s)

2006年10月25日

8分钟阅读

viewsIcon

399931

downloadIcon

20831

在本文中,我们使用 JavaScript 模拟数据流,并讨论其在执行插件相关任务方面的潜力。

目录

引言

流式传输通常是指一种用于媒体内容或动态数据的传输系统,在这种系统中,在数据正在传输时开始处理会很有益。实际上,HTTP 并非为数据流式传输而设计。HTTP 通信是无状态的,它们通过 TCP/IP 进行,端点之间没有连续连接。通常,HTTP 响应会被缓冲而不是流式传输。HTTP 1.1 通过 keep-alive 报头增加了对流式传输的支持,因此数据可以被流式传输,但出于性能考虑,大多数实现(包括 ASP.NET)倾向于缓冲内容,发送它,然后关闭连接。因此,很少有实际应用使用 HTTP 进行数据流式传输,通常会构建一个额外的协议来处理重连和错误检测。然而,这并不构成问题,因为在需要的地方可以使用其他基于 UDP 的协议进行流式传输。

那么,为什么我们需要通过 HTTP 进行数据流式传输呢?因为我们是在 HTTP 上构建我们的 Web 应用程序。播放视频片段、显示 RSS 字段以及更新时间敏感数据如今被认为是网页的常见功能,但我们仍然受制于 HTTP 的功能。这时,浏览器就会利用插件来克服这些限制,同时也带来了新的麻烦!

插件是在应用程序的上下文之外执行的。与超文本标记语言或 JavaScript 不同,插件主要是编译后的二进制文件,难以自定义。这还不包括页面中使用插件所涉及的安全、可访问性、平台独立性和 Web 标准问题。虽然对于富内容页面而言,使用插件似乎不可避免,但 AJAX 在我看来带来了巨大的希望。尽管 JavaScript 语言(截至目前最新版本是 Firefox 2 中的 1.7 版本)还不能完全执行与插件相关的任务,但我相信未来的版本可以提供足够的浏览器集成和支持库,从而消除使用编译型插件的需要。我知道这听起来非常抽象,因此我决定编写一个 AJAX 应用程序,它能执行插件最常见的任务:视频播放器。这个 AJAX 视频播放器是一个基于脚本的原型视频播放器,可在支持 Base64 编码图像(几乎所有现代浏览器,但 IE 除外)的 JavaScript 启用 HTML 浏览器中运行。AJAX 视频播放器可以向不同平台和浏览器的各种用户广播实时(使用 XML 服务)或缓存的视频流(XML 文件)。

实时演示:观看 Snoopi 在南瓜地里的画面,请访问 AJAX Video Player

使用代码

背景

视频是由一系列帧图像组成的,这些图像按顺序显示。如果我们能在浏览器中获取视频片段的所有帧,就可以按帧率逐帧播放,这样视频就播放起来了!听起来是个好主意,让我们看看如何将它转化为实际的 Web 应用程序。根据我们的计划,我们将工作分解为更小的步骤:

第一步:获取视频文件或实时流的帧、帧率和其他必要信息

第二步:通过 HTTP 将我们的帧传输到客户端浏览器。

第三步:在客户端进行帧动画播放,响应用户交互,并在需要时请求更多帧。

第一步:获取视频片段的帧、帧率和其他属性

如果您有使用 Microsoft DirectShow Editing Services(代号 Dexter)编写应用程序的经验,这听起来会非常熟悉。在 Windows 环境中,传统上使用 C++ 和 Dexter Type Library 来访问 DirectShow COM 对象来捕获静态帧。要在 .NET Framework 中做到这一点,您可以创建一个 DexterLib 的互操作程序集,该程序集在 VS 2005 的 COM 引用下列出。但是,要弄清楚如何将 C++ 代码转换为 C# .NET 需要付出很多努力。问题出现在您需要将指针引用作为参数传递给本机函数时,CLR 不直接支持指针,因为指针的内存位置会在每次垃圾回收后发生变化。您可以在 CodeProject 或其他地方找到许多关于如何使用 DirectShow 的文章,我们在这里力求简化。我们的目标是将视频文件转换为 Bitmap 数组,我尽量将其写得简短,当然您也可以编写自己的代码来从实时流中提取 Bitmap,并在发送之前将其缓冲。

基本上,我们在 .NET 中使用 DirectShow 将视频转换为帧有两种选择:

  • 编辑互操作程序集,将类型引用从指针更改为 C# .NET 类型。
  • 使用 `unsafe` 关键字来使用指针。

我们选择了 `unsafe`(读作“快速”)方法。这意味着我们将帧提取到 .NET 管理范围之外。重要的是要提到,托管并不总是意味着更好,而不安全并不一定意味着不安全!

MediaDetClass mediaClass = new MediaDetClass(); 
_AMMediaType mediaType; 
... //load the video file

int outputStreams = mediaClass.OutputStreams;
outFrameRate=0.0; 
for (int i = 0; i < outputStreams; i++) 
{ 
  mediaClass.CurrentStream = i; 
  try{ 
     //If it can the get the framerate, it's enough,

     //we accept the video file otherwise it throws an exception here

     outFrameRate = mediaClass.FrameRate; 
       .......
     //get the attributes here

        .....
  
     }catch 
    { // Not a valid meddia type? go to the next outputstream } 

} 
// No frame rate? 

if (outFrameRate==0.0)
    throw new NotSupportedException( " The program is unable" + 
                                     " to read the video file."); 
// we have a framerate? move on... 

... 
//Create an array to hold Bitmaps and intilize 

//other objects to store information...


unsafe { 
    ... 
    // create a byte pointer to store the BitmapBits   

    ...
   while (currentStreamPos < endPosition) 
   { 
      mediaClass.GetBitmapBits(currentStreamPos, ref bufferSize, 
                               ref *ptrRefFramesBuffer, 
                               outClipSize.Width, outClipSize.Height); 
   ...  
   //add frame Bitmap to the frameArray

    ...
  }
}
...

第二步:通过 HTTP 传输提取的数据

到目前为止,我们已经将视频转换为了 Bitmap 帧数组。下一步是将我们的帧通过 HTTP 全部传输到客户端浏览器。如果我们能直接将 Bitmap 数据发送到客户端,那就太好了,但我们做不到。HTTP 设计用于传输文本字符,这意味着您的浏览器只读取 HTML 页面字符集中定义的字符。任何超出此编码范围的内容都无法直接显示。

为了完成这一步,我们使用 Base64 编码将 Bitmap 转换为 ASCII 字符。传统上,Base64 编码用于在电子邮件中嵌入对象。几乎所有现代浏览器,包括 Gecko 浏览器、Opera、Safari 和 KDE(但不是 IE!),都支持 data: URI scheme 标准来显示 Base64 编码的图像。太棒了!现在,我们的帧已准备好通过 HTTP 传输。

System.IO.MemoryStream memory = new System.IO.MemoryStream();
while (currentStreamPos < endPosition) 
{
  ...
  // Save the Bitmpas somewhere in the (managed) memory 

  vdeoBitmaps.Save(memory, System.Drawing.Imaging.ImageFormat.Jpeg); 
  //Convert it to Base64

  strFrameArray[frameCount] = System.Convert.ToBase64String(memory.ToArray()); 
 //Get ready for the next one

  memory.Seek(0, System.IO.SeekOrigin.Begin); 
 }
memory.Close();
...

但是,我们不能只是将编码后的帧作为一长串字符串发送出去。我们创建一个 XML 文档,其中包含我们的帧和关于视频的其他信息,然后将其发送到客户端。这样,浏览器就可以将我们的帧作为 DOM XML 对象接收,并轻松地在它们之间导航。试想一下,编辑以 XML 格式存储的视频是多么容易。

<?xml version="1.0" encoding="utf-8"?>
  <Clip name="Snoopi">
     <Frame_Rate>14.9850224700412</Frame_Rate>
        <Clip_Size>{Width=160, Height=120}</Clip_Size>
        <Stream_Length>6.4731334</Stream_Length>
     <Frame ID="96">/9j/4AAQSkZJRgABAQEAYAB.... </Frame>
     ....
  </Clip>

这种格式也有其自身的缺点。转换为 Base64 编码 XML 文件的视频比它们的二进制等效文件大 10%(主要是 AVI 文件)到 300% 或更多(一些 WMV 文件)。

图 1 显示了我们的 AJAX 视频播放器应用程序中的数据流。

图 1:AJAX 视频播放器中的数据流

如果您使用的是 XML 文件,甚至不需要 Web 服务器,您可以从本地目录打开 HTML,它应该可以正常工作!我在文章的下载文件中包含了一个可执行文件,可以将您的视频文件转换为 XML 文档,然后可以在浏览器中显示。然而,使用大文件和高分辨率视频不是个好主意!

好了,现在我们可以像处理任何其他类型的 XML 文件一样发送我们的“Base64 编码视频”XML 文档了。谁说 XML 文件一定要是枯燥的记录集呢?!

第三步:在客户端进行帧动画播放

图 2 显示了 JavaScript VideoPlayer 对象中的应用程序流程。我已尽可能注释了代码。您可以在本文提供的下载中查看源代码。

图 2:AJAX 视频播放器中的应用程序流程

结论

在本文中,我们使用 JavaScript 模拟了数据流,并讨论了它在执行插件相关任务方面的巨大潜力,尽管它还需要一段时间才能在实际应用中取代插件。功能更强大、支持更好 OOP 的 JavaScript 对每个人都有益。在开发 JavaScript 功能时,许多保守的预防措施都被考虑在内,仅仅是为了给各种插件打开大门!Java 到 JavaScript 的编译器已经开始尽可能多地利用 JavaScript 的潜力。Google ( GWT ) 和 Mozilla ( 这里 ) 已经发布了出色的编译器,可以将您的 Java 代码转换为 JavaScript。

将来某一天 :) 在另一篇文章中,我们将讨论 JavaScript 如何通过松散耦合视图(HTML 页面)和控制器(XML 服务中的类)来改进 Web 应用程序的 MVC 设计模式。在某些场景下,JavaScript 可以完全取代您的 Web 控件,让您无需经历 ASPX 页面生命周期。

附录 - 相关链接

© . All rights reserved.