mediaSourceExtension

demo

迅雷直播 中审查元素,可以看到视频元素会看到它的 src 是一个 Blob URL

<video
  preload="metadata"
  src="blob:http://live.xunlei.com/79a71646-6b99-419c-a3c3-d7c171266aca"
></video>

与普通的 html5 video 的区别

  1. 直接访问 src 的链接,无法播放视频
  2. 无法右键保存到本地
使用 html5 video使用 MSE
使用 html5 video使用 MSE
  1. 支持分片加载,加快视频打开速度,节省流量

比如打开一个 197 MB 的视频文件,使用分片加载用户体验会更好。

一次性加载完毕

bufferAll

分片加载,每次加载一部分

bufferWhenNeeded

使用 URL.createObjectURL(blob) 创建 Blob URL

在每次调用 createObjectURL() 方法时,都会创建一个新的 URL 对象,即使你已经用相同的对象作为参数创建过。当不再需要这些 URL 对象时,每个对象必须通过调用 URL.revokeObjectURL() 方法来释放。浏览器会在文档退出的时候自动释放它们,但是为了获得最佳性能和内存使用状况,你应该在安全的时机主动释放掉它们。

// blob = scheme ":" origin "/" UUID
let url = URL.createObjectURL(new Blob([], { type: 'image/png' }))
let img = document.createElement('img')
img.src = url
img.onload = function () {
  window.URL.revokeObjectURL(url)
}
document.body.appendChild(img)
// <img src="blob:https://developer.mozilla.org/0495d5c4-5b70-4960-8bdb-3b761d3b1c56">

createObjectURL 的兼容性

caniuse?search=createObjectURL

demo,更多例子参考 使用 Web 应用程序中的文件

Media Source Extensions

允许 JavaScript 动态构建 <audio><video> 的媒体流。它定义了一个 MediaSource 对象,可以作为 HTMLMediaElement 的媒体数据源。 MediaSource 对象具有一个或多个 SourceBuffer 对象。应用程序将数据段附加到 SourceBuffer 对象,并可以根据系统性能和其他因素调整附加数据的质量。来自 SourceBuffer 对象的数据被管理为用于解码和播放的音频,视频和文本数据的轨道缓冲器。

pipeline_model

HTML 的媒体支持浏览器兼容情况

Supported_media_formats#浏览器兼容情况

判断给定的 MIME 类型 是否被当前的浏览器支持

let mimeCodec = 'video/mp4; codecs="avc1.42E01E, mp4a.40.2"'
let isMediaSourceSupported =
  'MediaSource' in window && MediaSource.isTypeSupported(mimeCodec)

if (isMediaSourceSupported) {
  let mediaSource = new MediaSource()
  // ...
} else {
  console.error('Unsupported MIME type or codec: ', mimeCodec)
}

MediaSource.isTypeSupported('mime; codecs=""'); 需要指定 codecs

transcoding

并非所有浏览器都支持所有编解码器,所以我们需要对资源进行转码。这里推荐使用 FFmpeg

ffmpeg -i input.wav output.mp3
ffmpeg -i input.y4m -i input.wav output.webm

获取资源的 codecs 及资源碎片化

下载 Bento4

Bento4 SDK 包含几个使用 SDK API 构建的命令行应用程序/工具。这些包括:

程序名描述
mp4info显示有关 MP4 文件的高级信息,包括所有曲目和编解码器详细信息
mp4dump显示 MP4 文件的整个原子/框结构
mp4fragment从非碎片文件创建碎片化 MP4 文件或重新碎片已碎片化的文件
mp4split将碎片化的 MP4 文件拆分为离散文件

查看资源的 Codecs

./mp4info.exe src/pirates.mp4 | grep Codecs
#   Codecs String: mp4a.40.2
#   Codecs String: avc1.4D401F

那么 mimeCodec = 'avc1.4D401F, mp4a.40.2'

查看资源碎片化状态

./mp4info.exe ./src/pirates.mp4 | grep fragments
#fragments:  no

资源碎片化

./mp4fragment.exe src/pirates.mp4 src/pirates_fragment.mp4
#found regular I-frame interval: 17496 frames (at 23.976 frames per second)
#auto-detected fragment duration too large, using default

再次查看资源碎片化状态

./mp4info.exe ./src/pirates_fragmented.mp4 | grep fragments

#fragments:  yes
#    sample count with fragments: 61991
#    duration with fragments:     63478784
#    duration with fragments:     1439428 (ms)
#    sample count with fragments: 34511
#    duration with fragments:     69091022
#    duration with fragments:     1439396 (ms)

使用 MSE 播放二进制数据流

var video = document.querySelector('video')

var assetURL = 'frag_bunny.mp4'
// Need to be specific for Blink regarding codecs
// ./mp4info frag_bunny.mp4 | grep Codec
var mimeCodec = 'video/mp4; codecs="avc1.42E01E, mp4a.40.2"'

if ('MediaSource' in window && MediaSource.isTypeSupported(mimeCodec)) {
  var mediaSource = new MediaSource()
  //console.log(mediaSource.readyState); // closed
  video.src = URL.createObjectURL(mediaSource)
  mediaSource.addEventListener('sourceopen', sourceOpen)
} else {
  console.error('Unsupported MIME type or codec: ', mimeCodec)
}

function sourceOpen(_) {
  //console.log(this.readyState); // open
  var mediaSource = this
  var sourceBuffer = mediaSource.addSourceBuffer(mimeCodec)
  fetchAB(assetURL, function (buf) {
    sourceBuffer.addEventListener('updateend', function (_) {
      mediaSource.endOfStream()
      video.play()
      //console.log(mediaSource.readyState); // ended
    })
    sourceBuffer.appendBuffer(buf)
  })
}

function fetchAB(url, cb) {
  console.log(url)
  var xhr = new XMLHttpRequest()
  xhr.open('get', url)
  xhr.responseType = 'arraybuffer'
  xhr.onload = function () {
    cb(xhr.response)
  }
  xhr.send()
}

替换本地资源后,报 MediaSource 错误:

Uncaught DOMException: Failed to execute 'endOfStream' on 'MediaSource': The MediaSource's readyState is not 'open'.

原因是因为视频资源不是 fragmented ,需要进行转换,操作流程参考上一节。

注意

  1. prevent-html5-video-from-being-downloaded-right-click-saved?

最简单的方式移除视频标签的右键“保存”选项

$(document).ready(function () {
  $('#videoElementID').bind('contextmenu', function () {
    return false
  })
})

This does not help however if JavaScript is disabled in the browser

  1. Firefox 61 上报错(QuotaExceededError: The quota has been exceeded.)

firefox 会限制 fragment 的大小。firefox 需要 fragment 小于 20M。chrome & Edge 无限制.

阅读链接