构建简单的 MPEG-DASH 流媒体播放器

转自:http://msdn.microsoft.com/zh-cn/library/windows/apps/dn551368.aspx


使用媒体源扩展 API 构建 MPEG-DASH 播放器以将文件传输到 HTML5 视频元素。你可以在 MSDN 示例网站上找到示例代码。

入门

W3C 规范中描述的媒体源扩展 (MSE) 将基于缓存的源选项添加到 HTML5 视频,以支持传输。以前,你需要在播放之前下载完整视频文件或使用加载项(如 Silverlight 或 Adobe Flash)流式传输媒体。使用 MSE,无需任何加载项。MediaSource 对象取代文件 URL 作为视频对象上的 src。源缓存被附加到 MediaSource 对象,并填充分段文件中的媒体数据。

分段的文件既可以是一系列小的单个文件,也可以是按顺序下载和播放的一个具有索引节的大文件。如果你的应用可以捕捉小分段,它更易于执行其他任务,例如将广告或其他内容插入视频。

可通过多种方法构建 MPEG-DASH 播放器。我们的方法使用 SegmentList 技术。这意味着我们可从单个在内部分段的 MP4 源文件中下载和播放视频文件。此示例请求、下载和播放以 10 秒的间隔时间划分的小分段视频,而不是下载整个文件。

此处的代码使用媒体源扩展 (MSE) API 和 HTML5 video 元素。因为 MSE 规范仍在变化中并且某些浏览器使用前缀,因此该示例已构建为专门在 Internet Explorer 11 中运行。该示例也仅使用 MSE,因此对于部署,你可能想要为不支持 HTML5 视频和 MSE 的浏览器提供备用方案(例如 Adobe Flash 或 Silverlight)。

MSE 相关快速教程

要使用 MSE API,请执行以下步骤:

  1. 在页面的 HTML 部分中定义 HTML5 video 元素。
  2. 使用 JavaScript 创建 MediaSource 对象。
  3. 使用 createObjectURL 创建虚拟 URL,并将 MediaSource 对象作为源。
  4. 将虚拟 URL 分配到视频元素的 src 属性。
  5. 使用 addSourceBuffer 创建 SourceBuffer,包含你添加的 MIME 类型的视频。
  6. 从媒体文件联机获取视频初始化分段,并使用 appendBuffer 将其添加到 SourceBuffer 中。
  7. 从媒体文件获取视频数据的分段,并使用 appendBuffer 将其附加到 SourceBuffer 中。
  8. 在 video 元素上调用 play 方法。
  9. 重复步骤 7 直到完成。
  10. 清除。

这是使用 MSE 的基本步骤列表。但是,你的播放器需要了解传入的视频文件类型以及分段的开始和结束位置。MPEG-DASH 规范描述了媒体演示描述 (MPD) 文件,以提供有关该视频的信息。

关于示例

我们构建的示例是简单的 MPEG-DASH 播放器。它使用单个文件并从使用 XHR 的文件中检索视频数据的字节范围。它最初检索媒体演示描述文件,并显示它包括的参数。这些参数是“已报告的值”,如下图所示。若要使播放器的格式保持一致,我们针对任何输入使用 CSS 以将视频大小设置为 640x480 像素。如果传入的视频大于 640x480,则缩小;如果小于该像素,则放大。

构建简单的 MPEG-DASH 流媒体播放器_第1张图片

播放视频时,“当前值”将得到更新。“索引”是当前的分段号(在 segmentList 数组中计算)。“分段长度”是刚刚返回的分段的实际时长。实际分段长度通常与 MPD 文件描述的分段长度不同。“视频时间”是视频文件中正在播放的当前位置。无需显示任何值;我们提供的这些值仅供参考。 在部署的网页上,你可能不会显示大部分值。

若要运行播放器,请参阅联机示例。

媒体演示描述 (MPD) 文件

MPEG-DASH MPD 是 XML 文件,其中包含了播放视频文件所需的所有信息的说明。我们仅使用 MPD 架构中提供的几个功能。该示例在单个文件中检索视频 MIME 类型、宽度和高度、分段持续时间,以及分段偏移(以字节为单位)列表。

我们使用的 MPD 文件是使用 MP4box 命令行实用程序创建的。MP4Box 是 GPAC 的一种开源多媒体打包工具,用于创建 DASH 分段的 MP4 文件和关联的 MPD 文件。若要获取有关 MP4Box 的详细信息并下载二进制文件,请参阅 GPAC MP4Box 或查看 GPAC 通用文档。

若要创建分段的 MP4 文件和关联的 MPD 文件,请先安装 MP4Box。然后,使用以下语法在命令行上调用 MP4Box:

mp4box -dash 10000 -frag 1000 -rap path\yourfile.mp4

MP4Box 使用附加的 _dash 创建两个文件:MP4 文件和 MPD 文件。在此示例中,它使用 10 秒分段和 1 秒片段创建 yourfile_dash.mp4 和 yourfile_dash.mpd -rap 标记告知 MP4Box 在关键帧上或编码序列的开始处尝试创建分段。虽然我们要求创建 10 秒分段,但每个分段的实际持续时间可能不同。我们以后将了解如何处理此问题。 有关 MPD 文件 的详细信息,请参阅 MPEG-DASH 教程。

注意  MPD 文件可以包含任何扩展。例如,如果你从可阻止包含未知扩展名的文件的网站进行发布,你可以将 .mpd 扩展更改为 .xml。我们已执行此操作,以为联机示例解决阻止问题。

HTML5 视频元素

该示例的 HTML 部分非常简单。它包含 video 元素、Input Element 字段、button 和一些用于显示结果的<div> 区域。该视频元素具有 autoplay 属性集,但不具有 src 或 controls 属性。可以通过我们从 MPD 文件获取的信息设置视频元素源,并使用“播放”按钮完成播放/暂停控制。使用 addEventListener() 而不是元素自身的 onclick() 事件在 JavaScript 代码中处理“播放”按钮的 click 事件。

HTML
<div id="grid">
  <div id="col1">
  <label>Enter .mpd file: 
      <input type="text" id="filename" value="sample_dash.xml" />
    </label> <button id="load">Play</button><br />
         
    <!-- Some areas to display info and content -->
    <div id="mydiv">
      <span id="myspan"><br />This demo requires Internet Explorer 11</span>
    </div>
    <div id="videoInfo"></div>
    <div>&nbsp;</div>
    <div id="curInfo"> 
      <h3>Current values:</h3>
      <ul>
        <li>Index: <span id="curIndex"></span> of <span id="numIndexes"></span></li>
        <li>Segment length: <span id="segLength"></span></li>
        <li>Video time: <span id="curTime"></span></li>
      </ul>
    </div> 
  </div>
  <div id="col2">
    <!-- Video element -->
    <video id="myVideo" autoplay="autoplay" >No video available</video>
          <div id="description">
            This example uses HTML5 video, Media Source Extensions, and MPEG-DASH files.<br /> 
            For more info see <a href="http://go.microsoft.com/fwlink/p/?LinkID=390962">Building a simple MPEG-DASH streaming player</a>. 
          </div>
  </div>
</div>


在页面的 <body> 中,页面的体系结构将 <script> 标记放置在 HTML 代码下。这可以确保在脚本开始运行之前,HTML 元素已完成加载,从而提高页面效率。

处理播放和暂停

若要在示例中播放 DASH 文件,请单击“播放”按钮。因为内部控件在视频元素上处于关闭状态,因此“播放”按钮的事件处理程序可播放或暂停当前视频。“播放”按钮的行为遵循以下条件:

  • 如果视频元素已暂停(处于其初始状态)且之前未加载 MPD 文件,处理程序将调用 getData() 函数以加载和分析 MPD 文件。
  • 如果视频已暂停,但已加载文件并且未发生更改,处理程序将仅调用 play 方法。
  • 如果输入字段中的文件夹已发生更改,并且视频已暂停,将加载处理程序,然后播放新的文件。
  • 如果视频正在播放,处理程序将调用 pause 方法,以便用户可以停止和开始该视频。
HTML
// Click event handler for load button    
playButton.addEventListener("click", function () {
  //  If video is paused then check for file change
  if (videoElement.paused == true) {
    // Retrieve mpd file, and set up video
    var curMpd = document.getElementById("filename").value;
    //  If current mpd file is different then last mpd file, load it.
    if (curMpd != lastMpd) {
      //  Cancel display of current video position
      window.cancelAnimationFrame(requestId);
      lastMpd = curMpd;
      getData(curMpd);
    } else {
      //  No change, just play
      videoElement.play();
    }
  } else {
    //  Video was playing, now pause it
    videoElement.pause();
  }
}, false);


若要使按钮标签与视频元素的状态保持同步,paused 和 playing 事件用于处理在“播放”和“暂停”之间切换按钮的标签。

HTML
// Handler to switch button text to Play
videoElement.addEventListener("pause", function () {
  playButton.innerText = "Play";
}, false);

// Handler to switch button text to pause
videoElement.addEventListener("playing", function () {
  playButton.innerText = "Pause";
}, false);


获取 .mpd 文件和 DASH 参数

MPD 文件是使用 DASH 的核心。 MPD 是 XML 文件,介绍了如何对媒体分段、类型和编解码器(此处是 MP4)、比特率、长度、视频的基本分段大小。某些 MPD 文件包括音频信息,你可以为视频和音频播放器将内容拆分到单独的流媒体中。此处显示的示例仅使用一个同时用于视频和音频的缓存。

HTML
// Gets the mpd file and parses it    
function getData(url) {
  if (url !== "") {
    var xhr = new XMLHttpRequest(); // Set up xhr request
    xhr.open("GET", url, true); // Open the request          
    xhr.responseType = "text"; // Set the type of response expected
    xhr.send();

    //  Asynchronously wait for the data to return
    xhr.onreadystatechange = function () {
      if (xhr.readyState == xhr.DONE) {
        var tempoutput = xhr.response;
        var parser = new DOMParser(); //  Create a parser object 

        // Create an xml document from the .mpd file for searching
        var xmlData = parser.parseFromString(tempoutput, "text/xml", 0);
        log("parsing mpd file");

        // Get and display the parameters of the .mpd file
        getFileType(xmlData);

        // Set up video object, buffers, etc  
        setupVideo();

        // Initialize a few variables on reload
        clearVars();
      }
    }

    // Report errors if they happen during xhr
    xhr.addEventListener("error", function (e) {
      log("Error: " + e + " Could not load url.");
    }, false);
  }
}


此示例使用 XMLHttpRequest 对象以将 MPD 文件检索到响应属性 tempoutput 中。我们创建 DOMParser 对象以将 MPD 文件数据分析到 XML 文档中。我们需要可与 querySelectorAll 和 getAttribute 方法一起使用的文档 (xmlData),以将 XML 节点的值提取到 MPD 文件中。

HTML
// Retrieve parameters from our stored .mpd file
function getFileType(data) {
  try {
    file = data.querySelectorAll("BaseURL")[0].textContent.toString();
    var rep = data.querySelectorAll("Representation");
    type = rep[0].getAttribute("mimeType");
    codecs = rep[0].getAttribute("codecs");
    width = rep[0].getAttribute("width");
    height = rep[0].getAttribute("height");
    bandwidth = rep[0].getAttribute("bandwidth");

    var ini = data.querySelectorAll("Initialization");
    initialization = ini[0].getAttribute("range");
    segments = data.querySelectorAll("SegmentURL");

    // Get the length of the video per the .mpd file
    //   since the video.duration will always say infinity
    var period = data.querySelectorAll("Period");
    var vidTempDuration = period[0].getAttribute("duration");
    vidDuration = parseDuration(vidTempDuration); // display length

    var segList = data.querySelectorAll("SegmentList");
    segDuration = segList[0].getAttribute("duration");

  } catch (er) {
    log(er);
    return;
  }
  showTypes();  // Display parameters 
}

// Display parameters from the .mpd file
function showTypes() {
  var display = document.getElementById("myspan");
  var spanData;
  spanData = "<h3>Reported values:</h3><ul><li>Media file: " + file + "</li>";
  spanData += "<li>Type: " + type + "</li>";
  spanData += "<li>Codecs: " + codecs + "</li>";
  spanData += "<li>Width: " + width + " -- Height: " + height + "</li>";
  spanData += "<li>Bandwidth: " + bandwidth + "</li>";
  spanData += "<li>Initialization Range: " + initialization + "</li>";
  spanData += "<li>Segment length: " + segDuration / 1000 + " seconds</li>";
  spanData += "<li>" + vidDuration + "</li>";
  spanData += "</ul>";
  display.innerHTML = spanData;
  document.getElementById("numIndexes").innerHTML = segments.length;
  document.getElementById("curInfo").style.display = "block";
  document.getElementById("curInfo").style.display = "block";
}


getData() 函数调用 getFileType() 函数,后者使用 MPD 文件中的信息填充全局变量。 然后我们调用showTypes() 函数以向屏幕显示参数。

设置视频和缓存

分析完 MPD 文件后,播放器将检索并播放在 MPD 文件中指定的媒体内容。播放器可创建包含每个分段的范围的 segmentList 数组。使用 index 变量可访问此数组。

当获取和播放视频数据时,计时非常重要。在仅以一个分辨率播放的应用中,你放置在缓存中的分段数量并不重要;但是,你不会希望占用太多内存。当播放一个包含多个质量级别的文件时,你要更谨慎一点。如果你因网络速度较慢而播放了低分辨率的视频,你一定希望在下次网络速度提高时准备好下载更高分辨率的视频分段。在这种情况下,你可能不希望获取离当前播放分段还有很大一段距离的分段。

播放进程如下所示:

  1. 将视频的初始化分段下载到缓存,然后进行播放。
  2. 将视频的某个分段下载到缓存,然后进行播放。
  3. 重复步骤 2,直到播放完所有分段。

DASH 媒体分段下载并附加到缓存中,然后由 HTML5 audio 或 video 元素播放。MediaSource 缓存替代适用于这些元素的 src 的文件 URL。addSourceBuffer 方法创建缓存并将其添加到 MediaSource 对象。removeSourceBuffer 从 MediaSource 对象中删除现有 SourceBufferappendBuffer 方法将媒体数据添加到 SourceBuffer

HTML
// Create mediaSource and initialize video 
function setupVideo() {
  clearLog(); // Clear console log

  //  Create the media source 
  if (window.MediaSource) {
    mediaSource = new window.MediaSource();
   } else {
    log("mediasource or syntax not supported");
    return;
  }
  var url = URL.createObjectURL(mediaSource);
  videoElement.pause();
  videoElement.src = url;
  videoElement.width = width;
  videoElement.height = height;

  // Wait for event that tells us that our media source object is 
  //   ready for a buffer to be added.
  mediaSource.addEventListener('sourceopen', function (e) {
    try {
      videoSource = mediaSource.addSourceBuffer('video/mp4');
      initVideo(initialization, file);           
    } catch (e) {
      log('Exception calling addSourceBuffer for video', e);
      return;
    }
  },false);



若要从一个视频文件中获取单个分段,我们使用 setRequestHeader 在该文件中为每个分段指定字节范围。XHRresponse 属性类型可以转换为 Uint8Array 并附加到源缓存。

示例中的 initVideo() 函数可下载 .MP4 文件中的初始化分段,并将其放置在 SourceBuffer 中。XHR 请求是异步的,因此若要确保在正确的时间调用函数,请使用 readystatechange 事件。引发 readystatechange 时,将检查 readyState 属性。如果它等于 xhr.DONE,response 属性(媒体数据)将作为 Uint8Array 添加到源缓存中。


你可能感兴趣的:(构建简单的 MPEG-DASH 流媒体播放器)