前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >全面进阶 H5 直播(下)

全面进阶 H5 直播(下)

原创
作者头像
腾讯IVWEB团队
修改2017-07-13 11:35:57
5.6K0
修改2017-07-13 11:35:57
举报

本文作者:ivweb villainthr

接 《 全面进阶 H5 直播(上)

Media Source Extensions

在没有 MSE 出现之前,前端对 video 的操作,仅仅局限在对视频文件的操作,而并不能对视频流做任何相关的操作。现在 MSE 提供了一系列的接口,使开发者可以直接提供 media stream。

那 MSE 是如何完成视频流的加载和播放呢?

入门实例

这可以参考 google 的 MSE 简介

代码语言:javascript
复制
 var vidElement = document.querySelector('video');

if (window.MediaSource) {
  var mediaSource = new MediaSource();
  vidElement.src = URL.createObjectURL(mediaSource);
  mediaSource.addEventListener('sourceopen', sourceOpen);
} else {
  console.log("The Media Source Extensions API is not supported.")
}

function sourceOpen(e) {
  URL.revokeObjectURL(vidElement.src);
  var mime = 'video/webm; codecs="opus, vp9"';
  var mediaSource = e.target;
  var sourceBuffer = mediaSource.addSourceBuffer(mime);
  var videoUrl = 'droid.webm';
  fetch(videoUrl)
    .then(function(response) {
      return response.arrayBuffer();
    })
    .then(function(arrayBuffer) {
      sourceBuffer.addEventListener('updateend', function(e) {
        if (!sourceBuffer.updating && mediaSource.readyState === 'open') {
          mediaSource.endOfStream();
        }
      });
      sourceBuffer.appendBuffer(arrayBuffer);
    });
}

可以从上面的代码看出,一套完整的执行代码,不仅需要使用 MSE 而且,还有一下这些相关的 API。

  • HTMLVideoElement.getVideoPlaybackQuality()
  • SourceBuffer
  • SourceBufferList
  • TextTrack.sourceBuffer
  • TrackDefault
  • TrackDefaultList
  • URL.createObjectURL()
  • VideoPlaybackQuality
  • VideoTrack.sourceBuffer

我们简单讲解一下上面的流程。根据 google 的阐述,整个过程可以为:

  • 第一步,通过异步拉取数据。
  • 第二步,通过 MediaSource 处理数据。
  • 第三步,将数据流交给 audio/video 标签进行播放。

而中间传递的数据都是通过 Buffer的形式来进行传递的。

中间有个需要注意的点,MS 的实例通过 URL.createObjectURL() 创建的 url 并不会同步连接到 video.src。换句话说,URL.createObjectURL() 只是将底层的流(MS)和 video.src 连接中间者,一旦两者连接到一起之后,该对象就没用了。

那么什么时候 MS 才会和 video.src 连接到一起呢?

创建实例都是同步的,但是底层流和 video.src 的连接时异步的。MS 提供了一个sourceopen 事件给我们进行这项异步处理。一旦连接到一起之后,该 URL object 就没用了,处于内存节省的目的,可以使用 URL.revokeObjectURL(vidElement.src) 销毁指定的

代码语言:javascript
复制
URL object。 
mediaSource.addEventListener('sourceopen', sourceOpen);

function sourceOpen(){
    URL.revokeObjectURL(vidElement.src)
}

MS 对流的解析

MS 提供了我们对底层音视频流的处理,那一开始我们怎么决定以何种格式进行编解码呢?

这里,可以使用addSourceBuffer(mime)来设置相关的编码器:

代码语言:javascript
复制
var mime = 'video/webm; codecs="opus, vp9"';  
  var sourceBuffer = mediaSource.addSourceBuffer(mime);

然后通过,异步拉取相关的音视频流:

代码语言:javascript
复制
fetch(url)
.then(res=>{
    return res.arrayBuffer();
})
.then(buffer=>{
    sourceBuffer.appendBuffer(buffer);
})

如果视频已经传完了,而相关的 Buffer 还在占用内存,这时候,就需要我们显示的中断当前的 Buffer 内容。那么最终我们的异步处理结果变为:

代码语言:javascript
复制
fetch(url)
.then(res=>{
    return res.arrayBuffer();
})
.then(function(arrayBuffer) {
      sourceBuffer.addEventListener('updateend', function(e) {
      // 是否有持续更新的流
        if (!sourceBuffer.updating && mediaSource.readyState === 'open') {
        // 没有,则中断连接
          mediaSource.endOfStream();
        }
      });
      sourceBuffer.appendBuffer(arrayBuffer);
    });

上面我们大致了解了一下关于 Media Source Extensions 的大致流程,但里面的细节我们还没有细讲。接下来,我们来具体看一下 MSE 一篮子的生态技术包含哪些内容。首先是,MediaSource

MediaSource

MS(MediaSource) 可以理解为多个视频流的管理工具。以前,我们只能下载一个清晰度的流,并且不能平滑切换低画质或者高画质的流,而现在我们可以利用 MS 实现这里特性。我们先来简单了解一下他的 API。

MS 的创建

创建一个 MS: var mediaSource = new MediaSource();

相关方法

addSourceBuffer()

该是用来返回一个具体的视频流,接受一个 mimeType 表示该流的编码格式。例如:

代码语言:javascript
复制
 var mimeType = 'video/mp4; codecs="avc1.42E01E, mp4a.40.2"';
var sourceBuffer = mediaSource.addSourceBuffer(mimeType);

sourceBuffer 是直接和视频流有交集的 API。例如:

代码语言:javascript
复制
function sourceOpen (_) {
  var mediaSource = this;
  var sourceBuffer = mediaSource.addSourceBuffer(mimeCodec);
  fetchAB(assetURL, function (buf) {
    sourceBuffer.addEventListener('updateend', function (_) {
      mediaSource.endOfStream();
      video.play();
    });
    // 通过 fetch 添加视频 Buffer
    sourceBuffer.appendBuffer(buf);
  });
};

它通过appendBuffer直接添加视频流,实现播放。不过,在使用 addSourceBuffer 创建之前,还需要保证当前浏览器是否支持该编码格式。

removeSourceBuffer()

用来移除某个 sourceBuffer。移除也主要是考虑性能原因,将不需要的流移除以节省相应的空间,格式为: mediaSource.removeSourceBuffer(sourceBuffer);

endOfStream()

用来表示接受的视频流的停止,注意,这里并不是断开,相当于只是下好了一部分视频,然后你可以进行播放。此时,MS 的状态变为:ended。例如:

代码语言:javascript
复制
 var mediaSource = this;
  var sourceBuffer = mediaSource.addSourceBuffer(mimeCodec);
  fetchAB(assetURL, function (buf) {
    sourceBuffer.addEventListener('updateend', function (_) {
      mediaSource.endOfStream(); // 结束当前的接受
      video.play(); // 可以播放当前获得的流
    });
    sourceBuffer.appendBuffer(buf);
  });
isTypeSupported()

该是用来检测当前浏览器是否支持指定视频格式的解码。格式为: var isItSupported = mediaSource.isTypeSupported(mimeType); // 返回值为 Boolean

mimeType 可以为 type 或者 type + codec。

例如:

代码语言:javascript
复制
 // 不同的浏览器支持不一样,不过基本的类型都支持。
MediaSource.isTypeSupported('audio/mp3'); // false,这里应该为 audio/mpeg 
MediaSource.isTypeSupported('video/mp4'); // true
MediaSource.isTypeSupported('video/mp4; codecs="avc1.4D4028, mp4a.40.2"'); // true

这里有一份具体的 mimeType 参考列表。

MS 的状态

当 MS 从创建开始,都会自带一个readyState 属性,用来表示其当前打开的状态。MS 有三个状态:

  • closed: 当前 MS 没有和 media element(比如:video.src) 相关联。创建时,MS 就是该状态。
  • open: source 打开,并且准备接受通过 sourceBuffer.appendBuffer 添加的数据。
  • ended: 当 endOfStream() 执行完成,会变为该状态,此时,source 依然和 media element 连接。 var mediaSource = new MediaSource; mediaSource.readyState; // 默认为 closed 当由 closed 变为 open 状态时,需要监听 sourceopen 事件。 video.src = URL.createObjectURL(mediaSource); mediaSource.addEventListener('sourceopen', sourceOpen); MS 针对这几个状态变化,提供了相关的事件:sourceopensourceendedsourceclose
  • sourceopen: 当 "closed" to "open" 或者 "ended" to "open" 时触发。
  • sourceended: 当 "open" to "ended" 时触发。
  • sourceclose: 当 "open" to "closed" 或者 "ended" to "closed" 时触发。

MS 还提供了其他的监听事件 sourceopen,sourceended,sourceclose,updatestart,update,updateend,error,abort,addsourcebuffer,removesourcebuffer. 这里主要选了比较重要的,其他的可以参考官方文档。

MS 属性

比较常用的属性有: duration,readyState。

  • duration: 获得当前媒体播放的时间,既可以设置(get),也可以获取(set)。单位为 s(秒) mediaSource.duration = 5.5; // 设置媒体流播放的时间 var myDuration = mediaSource.duration; // 获得媒体流开始播放的时间 在实际应用中为: sourceBuffer.addEventListener('updateend', function (_) { mediaSource.endOfStream(); mediaSource.duration = 120; // 设置当前流播放的时间 video.play(); });
  • readyState: 获得当前 MS 的状态。取值上面已经讲过了:closedopenendedvar mediaSource = new MediaSource; //此时的 mediaSource.readyState 状态为 closed 以及: sourceBuffer.addEventListener('updateend', function (_) { mediaSource.endOfStream(); // 调用该方法后结果为:ended video.play(); }); 除了上面两个属性外,还有 sourceBuffersactiveSourceBuffers这两个属性。用来返回通过 addSourceBuffer() 创建的 SourceBuffer 数组。这没啥过多的难度。

接下来我们就来看一下靠底层的sourceBuffer

SourceBuffer

SourceBuffer 是由mediaSource 创建,并直接和 HTMLMediaElement接触。简单来说,它就是一个流的容器,里面提供的 append()remove()来进行流的操作,它可以包含一个或者多个 media segments。同样,接下来,我们再来看一下该构造函数上的基本属性和内容。

基础内容

前面说过 sourceBuffer 主要是一个用来存放流的容器,那么,它是怎么存放的,它存放的内容是啥,有没有顺序等等。这些都是 sourceBuffer 最最根本的问题。OK,接下来,我们来看一下的它的基本架构有些啥。

参考 W3C,可以基本了解到里面的内容为:

代码语言:javascript
复制
 interface SourceBuffer : EventTarget {
             attribute AppendMode          mode;
    readonly attribute boolean             updating;
    readonly attribute TimeRanges          buffered;
             attribute double              timestampOffset;
    readonly attribute AudioTrackList      audioTracks;
    readonly attribute VideoTrackList      videoTracks;
    readonly attribute TextTrackList       textTracks;
             attribute double              appendWindowStart;
             attribute unrestricted double appendWindowEnd;
             attribute EventHandler        onupdatestart;
             attribute EventHandler        onupdate;
             attribute EventHandler        onupdateend;
             attribute EventHandler        onerror;
             attribute EventHandler        onabort;
    void appendBuffer(BufferSource data);
    void abort();
    void remove(double start, unrestricted double end);
};

上面这些属性决定了其 sourceBuffer 整个基础。

首先是 mode。上面说过,SB(SourceBuffer) 里面存储的是 media segments(就是你每次通过 append 添加进去的流片段)。SB.mode 有两种格式:

  • segments: 乱序排放。通过 timestamps 来标识其具体播放的顺序。比如:20s的 buffer,30s 的 buffer 等。
  • sequence: 按序排放。通过 appendBuffer 的顺序来决定每个 mode 添加的顺序。timestamps 根据 sequence 自动产生。

那么上面两个哪个是默认值呢?

看情况,讲真,没骗你。

media segments 天生自带timestamps,那么 mode 就为 segments ,否则为 sequence。所以,一般情况下,我们是不用管它的值。不过,你可以在后面,将 segments 设置为 sequence 这个是没毛病的。反之,将 sequence 设置为 segments 就有问题了。

代码语言:javascript
复制
var bufferMode = sourceBuffer.mode;
if (bufferMode == 'segments') {
  sourceBuffer.mode = 'sequence';
}

然后另外两个就是 buffered 和 updating。

  • buffered:返回一个 timeRange 对象。用来表示当前被存储在 SB 中的 buffer。
  • updating: 返回 Boolean,表示当前 SB 是否正在被更新。例如: SourceBuffer.appendBuffer(), SourceBuffer.appendStream(), SourceBuffer.remove() 调用时。

另外还有一些其他的相关属性,比如 textTracks,timestampOffset,trackDefaults,这里就不多说了。实际上,SB 是一个事件驱动的对象,一些常见的处理,都是在具体的事件中完成的。那么它又有哪些事件呢?

事件触发

在 SB 中,相关事件触发包括:

  • updatestart: 当 updating 由 false 变为 true。
  • update:当 append()/remove() 方法被成功调用完成时,updating 由 true 变为 false。
  • updateend: append()/remove() 已经结束
  • error: 在 append() 过程中发生错误,updating 由 true 变为 false。
  • abort: 当 append()/remove() 过程中,使用 abort() 方法废弃时,会触发。此时,updating 由 true 变为 false。

注意上面有两个事件比较类似:updateupdateend。都是表示处理的结束,不同的是,update 比 updateend 先触发。

代码语言:javascript
复制
sourceBuffer.addEventListener('updateend', function (e) {
    // 当指定的 buffer 加载完后,就可以开始播放
      mediaSource.endOfStream();
      video.play();
    });

相关方法

SB 处理流的方法就是 +/- : appendBuffer, remove。另外还有一个中断处理函数 abort()

  • appendBuffer(ArrayBuffer):用来添加 ArrayBuffer。该 ArrayBuffer 一般是通过 fetch 的 response.arrayBuffer(); 来获取的。
  • remove(start, end): 用来移除具体某段的 media segments。 @param start/end: 都是时间单位(s)。用来表示具体某段的 media segments 的范围。
  • abort(): 用来放弃当前 append 流的操作。不过,该方法的业务场景也比较有限。它只能用在当 SB 正在更新流的时候。即,此时通过 fetch,已经接受到新流,并且使用 appendBuffer 添加,此为开始的时间。然后到 updateend 事件触发之前,这段时间之内调用abort()。有一个业务场景是,当用户移动进度条,而,此时 fetch 已经获取前一次的 media segments,那么可以使用 abort放弃该操作,转而请求新的 media segments。具体可以参考:abort 使用

上面主要介绍了处理音视频流需要用的 Web 技术,后面章节,我们接入实战,具体来讲一下,如何做到使用 MSE 进行 remux 和 demux。

原文链接:http://www.ivweb.io/topic/58de6dcb4813a86006c9052a

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Media Source Extensions
    • 入门实例
      • MS 对流的解析
      • MediaSource
        • MS 的创建
          • 相关方法
            • addSourceBuffer()
            • removeSourceBuffer()
            • endOfStream()
            • isTypeSupported()
        • MS 的状态
          • MS 属性
          • SourceBuffer
            • 基础内容
              • 事件触发
                • 相关方法
                相关产品与服务
                容器服务
                腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
                领券
                问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档