MediaSourceExtension

Media Source Extensions

Media Source API,正式称为Media Source Extensions (MSE),提供了基于web且不依赖插件播放流媒体的功能。使用MSE,可以通过JavaScript创建媒体流MediaSource,并使用和元素播放。
MSE可以让我们使用一个 MediaSource 对象来替代通常的单个媒体文件的 src 值给HTMLMediaElement元素。
MSE 让我们能够根据内容获取的大小和频率,或是内存占用详情(例如什么时候缓存被回收),进行更加精准地控制。
MSE为可扩展API构建自适应码率播放器的出现奠定了基础,例如DASH 、 HLS播放器。

使用createObjectURL将MediaSource和video标签连接起来

var mediaSource = new MediaSource;
//console.log(mediaSource.readyState); // closed
video.src = URL.createObjectURL(mediaSource);
mediaSource.addEventListener('sourceopen', sourceOpen);
function sourceOpen(){
    URL.revokeObjectURL(video.src)
}

创建MediaSource对象,通过URL.createObjectURL()将MediaSource对象转换为url,然后赋值给媒体元素的src。MS对象的sourceopen事件触发时,代表MS对象和媒体元素两者已经连接在一起,URL.createObjectURL创建的url就没有作用可以销毁。

Media Source API
支持事件:

  • sourceopen 绑定到媒体元素后开始触发
  • sourceclosed 未绑定到媒体元素后开始触发
  • sourceended 所有数据接收完成后触发

对应的属性mediaSource.readyState:

  • open MSE 实例,已经绑定到了媒体元素上,等待接受数据或者正在接受数据
  • closed MSE 实例未绑定到了媒体元素上。MS刚创建时就是该状态。
  • ended MSE 实例,已经绑定到了媒体元素上, 并且所有数据都已经接受到了。当endOfStream()执行完成,会变为该状态。

设置编码类型mime 字符串,创建SourceBuffer

function sourceOpen(e) {  
  URL.revokeObjectURL(videoMp4.src);
  var mime = 'video/mp4; codecs="avc1.42E01E, mp4a.40.2"';
  // e.target refers to the mediaSource instance.
  // Store it in a variable so it can be used in a closure.
  var mediaSource = e.target;
  // addSourceBuffer只能在readyState为open时调用
  var sourceBuffer = mediaSource.addSourceBuffer(mime);
  // Fetch and process the video.
}

首先,前面的 video/mp4 代表这是一段 mp4 格式封装的视频,同理也存在类似 video/webm、audio/mpeg、audio/mp4 这样的 mime 格式。一般情况下,可以通过 canPlayType 这个方法来判断浏览器是否支持当前格式。
后面的这一段 codecs="…" 比较特别,以逗号相隔,分为两段:

  1. 第一段,‘avc1.42E01E’,即它用于告诉浏览器关于视频编解码的一些重要信息,诸如编码方式、分辨率、帧率、码率以及对解码器解码能力的要求。在这个例子中,**‘avc1’ **代表视频采用 H.264 编码,随后是一个分隔点,之后是 3 个两位的十六进制的数,这 3 个十六进制数分别代表:
    AVCProfileIndication(42)
    profile_compability(E0)
    AVCLevelIndication(1E)
    第一个用于标识 H.264 的 profile,后两个用于标识视频对于解码器的要求,只是浏览器用于判断自身的解码能力能否满足需求,所以不需要和视频完全对应,更高也是可以的。
    对于一个 mp4 视频,可以使用 mp4file 这样的命令行工具来查看这些参数:mp4file --dump xxx.mp4

  2. codecs 的第二段 ‘mp4a.40.2’,这一段信息是关于音频部分的,代表视频的音频部分采用了 AAC LC 标准:‘mp4a’ 代表此视频的音频部分采用 MPEG-4 压缩编码。随后是一个分隔点,和一个十六进制数(40),这是 ObjectTypeIndication,40 对应的是 Audio ISO/IEC 14496-3 标准。然后又是一个分隔点,和一个十进制数2,这是 MPEG-4 Audio Object Type,是一种 H.264 视频中常用的音频编码规范。
    https://tools.ietf.org/html/rfc6381

SourceBuffer简介

SourceBuffer 是由 mediaSource 创建,并直接和 HTMLMediaElement 接触。简单来说,它就是一个流的容器,里面提供的 append(),remove() 来进行流的操作,它可以包含一个或者多个 media segments。

1 添加/移除 buffer

1.1 appendBuffer

appendBuffer可以动态地向 MediaSource 中添加视频/音频片段(对于一个 MediaSource,可以同时存在多个 SourceBuffer)
如果视频很长,存在多个chunk 的话,就需要不停地向 SourceBuffer 中加入新的 chunk。这里就需要注意一个问题了,即 appendBuffer 是异步执行的,在完成前,不能 append 新的 chunk,而是应该监听 SourceBuffer 上的 updateend 事件,确定空闲后,再加入新的 chunk:

sourceBuffer.addEventListener('updateend', () => {
    // 这个时候才能加入新 chunk
    // 先设定新chunk加入的位置,比如第20秒处
    sourceBuffer.timestampOffset = 20
    // 然后加入
    sourceBuffer.appendBuffer(newBuffer)
}

当然,并不是所有的 Buffer 都能随便添加给指定的 SB,这里面是需要条件和相关顺序的。

  • 该 buffer,必须满足 MIME 限定的类型
  • 该 buffer,必须包含 initialization segments(IS) 和 media segments(MS)

在添加 Buffer 的时候,你需要了解你所采用的 mode 是哪种类型,sequence 或者 segments。这两种是完全两种不同的添加方式。

  1. segments:这种方式是直接根据 MP4 文件中的 pts 来决定播放的位置和顺序,它的添加方式极其简单,只需要判断 updating === false,然后,直接通过 appendBuffer 添加即可。

  2. sequence:
    如果你是采用这种方式进行添加 Buffer 进行播放的话,那么你也就没必要了解 FMP4 格式,而是了解 MP4 格式。因为,该模式下,SB 是根据具体添加的位置来进行播放的。所以,如果你是 FMP4 的话,有可能就有点不适合了。针对 sequence 来说,每段 buffer 都必须有自己本身的指定时长,每段 buffer 不需要参考的 baseDts,即,他们直接可以毫无关联。那 sequence 具体怎么操作呢?
    简单来说,在每一次添加过后,都需要根据指定 SB 上的 timestampOffset。该属性,是用来控制具体 Buffer 的播放时长和位置的。

if (!sb.updating) {
    let MS = this._mergeBuffer(media.tmpBuffer);
           
    sb.appendBuffer(MS); // ****

    sb.timestampOffset += lib.duration; // ****
    media.tmpBuffer = [];
}

1.2 remove

在 SB 中,简单的来说,就是移除指定的时间范围内 buffer。需要用到的 API 为:
remove(double start, unrestricted double end);

具体的步骤为:
找到具体需要移除的 segment。
得到其开始(start)的时间戳(以 s 为单位)
得到其结束(end)的时间戳(以 s 为单位)
此时,updating 为 true,表明正在移除
完成之后,出发 updateend 事件

1.3 abort

清空当前 SB 中所有的 segment,常用的场景为:当用户移动进度条,而此时 fetch 已经获取前一次的 media segments,那么可以使用 abort 放弃该操作,转而请求新的 media segments

2 属性

2.1 mode

上面说过,SB(SourceBuffer) 里面存储的是 media segments(就是你每次通过 append 添加进去的流片段)。SB.mode 有两种格式:
segments: 乱序排放。通过 timestamps 来标识其具体播放的顺序。比如:20s的 buffer,30s 的 buffer 等。表示 A/V 的播放时根据你视频播放流中的 pts 来决定,该模式也是最常使用的。因为音视频播放中,最重要的就是 pts 的排序。因为,pts 可以决定播放的时长和顺序,如果一旦 A/V 的 pts 错开,有可能就会造成 A/V sync drift。
sequence: 按序排放。通过 appendBuffer 的顺序来决定每个 mode 添加的顺序。timestamps 根据 sequence 自动产生。还需要注意,在播放的同时,你需要告诉 SB这段 segment 有多长,也就是该段 Buffer 的实际偏移量。而该段偏移量就是由 timestampOffset 决定的。整个过程用代码描述一下就是:

sb.appendBuffer(media.segment);
sb.timestampOffset += media.duration;

SB.mode的默认值是根据传进来的media segments决定的,当 media segments 天生自带 timestamps,那么 mode 就为 segments ,否则为 sequence。所以,一般情况下,我们是不用管它的值。不过,你可以在后面,将 segments 设置为 sequence 这个是没毛病的。反之,将 sequence 设置为 segments 就有问题了。代码如下:

sb.appendBuffer(media.segment);
sb.timestampOffset += media.duration;

如果你想手动更改 mode 也是可以的,不过需要注意几个先决条件:
对应的 SB.updating 必须为 false.
如果该 parent MS 处于 ended 状态,则会手动将 MS readyState 变为 open 的状态。

2.2 buffered

返回一个 TimeRange 对象。用来表示当前被存储在 SB 中的 buffer。

2.3 updating

返回 Boolean,表示当前 SB 是否正在被更新。例如: appendBuffer(), appendStream(), remove() 调用时。

3. 事件

在 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。
注意上面有两个事件比较类似:update 和 updateend。都是表示处理的结束,不同的是,update 比 updateend 先触发。

MediaSource简介

1. isTypeSupported

isTypeSupported是静态方法,主要是用来检测 MS 是否支持某个特定的编码和容器盒子。例如:

MediaSource.isTypeSupported('video/mp4; codecs="avc1.42E01E, mp4a.40.2"')

2. addSourceBuffer

用来返回一个具体的视频流 SB,接受一个 mimeType 表示该流的编码格式

3. removeSourceBuffer

removeSourceBuffer用来移除某个 sourceBuffer。比如当前流已经结束,那么你就没必要再保留当前 SB 来占用空间,可以直接移除。示例代码为:

mediaSource.removeSourceBuffer(sourceBuffer);

4. endOfStream

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

  var mediaSource = this;
  var sourceBuffer = mediaSource.addSourceBuffer(mimeCodec);
  fetchAB(asssourceopenetURL, function (buf) {
    sourceBuffer.addEventListener('updateend', function (_) {
      mediaSource.endOfStream(); // 结束当前的接受
      video.play(); // 可以播放当前获得的流
    });
    sourceBuffer.appendBuffer(buf);
  });

5.sourceBuffers

sourceBuffers 是 MS 实例上的一个属性,它返回的是一个 SourceBufferList 的对象,里面可以获取当前 MS 上挂载的所有 SB。不过,只有当 MS 为 open 状态的时候,它才可以访问。具体使用为:

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

6. activeSourceBuffers

sourceBuffers 是 MS 实例上的一个属性,它返回的是一个 SourceBufferList 的对象,里面可以获取当前 MS 上挂载的所有 SB。不过,只有当 MS 为 open 状态的时候,它才可以访问

7. sourceclose事件触发条件

sourceclose 是在 media 元素和 MS 断开的时候,才会触发。那这个怎么断开呢?难道直接将 media 的元素的 src 直接设置为 null 就 OK 了吗?MS 会这么简单么?实际上并不,如果要手动触发 sourceclose 事件的话,则需要下列步骤:
将 readyState 设置为 closed
将 MS.duration 设置为 NaN
移除 activeSourceBuffers 上的所有 Buffer
触发 activeSourceBuffers 的 removesourcebuffer 事件
移除 sourceBuffers 上的 SourceBuffer。
触发 sourceBuffers 的 removesourcebuffer 事件
触发 MediaSource 的 sourceclose 事件

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值