MSE -- MediaSource 的前端使用

Published: · LastMod: March 26, 2023 · 1214 words

Media Source Extensions 🔗

Media Source Extensions,缩写 MSE https://w3c.github.io/media-source/

平时我们开发中加入视频或者音频,都是使用video、audio组件,附加一个src属性

这种形式开发一般的没有问题,但是如果要做到动态改变清晰度、动态增加视频广告、等等的功能,就做不了了

看看b站

image.png

是一个blob url

平时在开发的时候,也遇到过blob url的情况

URL.createObjectURL最终返回的也是一个blob url

1
 const blob = URL.createObjectURL(file.value.files[0]);

右键打开,啥也没有

image.png

这里就用到MediaSource

原理 🔗

具体的内部实现我也不理解,这里给一个官方的原理图

image.png

实现 🔗

已知我们有个按钮按一下,需要播放对应音频

用vue3写一个demo

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
const audioRef = ref(document.createElement("audio"));
const mediaSource = new MediaSource();

onMounted(() => {
  audioRef.value.src = URL.createObjectURL(mediaSource);
  mediaSource.addEventListener("sourceopen", onSourceOpen);
});
function onSourceOpen() {
      //
}

这里在mounted之后在进行监听回调

sourceBuffer

声明一个全局变量sourceBuffer

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
let sourceBuffer

const mimeCodec = 'audio/mpeg'

function onSourceOpen() {
    //
    sourceBuffer = mediaSource.addSourceBuffer(mimeCodec);
    sourceBuffer.addEventListener("updateend", function (_) {
      // ...

      console.log(mediaSource.readyState); // ended
    });
    sourceBuffer.addEventListener("error", function (error) {
      console.log(error);
    });
}

只有在mediaSource状态是open的情况下才能调用addSourceBuffer方法

mimeCodec这里定义的是audio/mpeg, 具体依情况来设定

https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types/Common_types

模版中声明一个button, 并写出对应的play方法

1
2
3
<template>
  <button @click="play">play</button>
</template>

play执行时我们获取远程的音频文件

1
2
3
4
5
6
7
function play(){
  fetch("http://127.0.0.1:8081/1111.mp3", {})
    .then((res) => {
      console.log("res", res);

    });
}

在获取到流文件的时候,我们需要加入到sourceBuffer中,sourceBuffer有个appendBuffer方法

需要加入ArrayBuffer格式的返回

fetch重新写一下

1
2
3
4
5
fetch("http://127.0.0.1:8081/1111.mp3", {})
      .then((res) => res.arrayBuffer())
      .then((res) => {
        sourceBuffer.appendBuffer(res);
      });

在sourceBuffer添加完毕的时候播放音频

1
2
3
4
sourceBuffer.addEventListener("updateend", function (_) {
        mediaSource.endOfStream();
        audioRef.value.play();
});

mediaSource.endOfStream方法用于结束当前mediaSource,表示流已经结束,不再添加数据

codecs 🔗

codecs时媒体源的编码格式,视频中mp4和avi肯定不是一个格式,音频中mp3和ogg等也不是一个编码格式

https://developer.mozilla.org/en-US/docs/Web/Media/Formats/codecs_parameter

这里会出现格式不支持的情况,但是直接使用文件url的时候又可以

MSE中支持的格式和直接播放时支持的格式会不一样,可能MSE作为一个扩展,有版权问题吧

全部代码 🔗

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
<template>
  <div>
    <!-- <audio ref="audioRef"></audio> -->
    <button @click="play">play</button>
  </div>
</template>

<script>
import { defineComponent, onMounted, ref } from "@vue/composition-api";

export default defineComponent({
  name: "DemoInput",
  setup() {
    const audioRef = ref(document.createElement("audio"));

    const mediaSource = new MediaSource();
    let mimeCodec = `audio/mpeg`;

    onMounted(() => {
      audioRef.value.muted = true;
      audioRef.value.autoplay = true;
      audioRef.value.controls = true;
      audioRef.value.src = URL.createObjectURL(mediaSource);
      mediaSource.addEventListener("sourceopen", onSourceOpen);
    });

    const getRemoteFile = () => {
      const sourceBuffer = mediaSource.addSourceBuffer(mimeCodec);

      fetch("http://127.0.0.1:8081/1111.mp3", {})
        .then((res) => res.arrayBuffer())
        .then((res) => {
          console.log("res", res);
          sourceBuffer.addEventListener("updateend", function (_) {
            mediaSource.endOfStream();

            audioRef.value.play();
            audioRef.value.muted = false;

            console.log(mediaSource.readyState); // ended
          });
          sourceBuffer.addEventListener("error", function (error) {
            console.log(error);
          });

          sourceBuffer.appendBuffer(res);
        });
    };

    function play() {
      getRemoteFile();
    }

    function onSourceOpen() {
      //
    }

    return {
      audioRef,
      play,
    };
  },
});
</script>

References 🔗

https://w3c.github.io/media-source/

https://developer.mozilla.org/en-US/docs/Web/API/MediaSource

https://developer.mozilla.org/en-US/docs/Web/API/SourceBuffer

https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types/Common_types

https://joshuatz.com/posts/2020/appending-videos-in-javascript-with-mediasource-buffers/

https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch#response_objects

Issues 🔗

1. play() failed because the user didn’t interact with the document first 🔗

代码中不能出现加载页面后直接播放的逻辑,浏览器防止开发者打断用户,需要用户进行手动操作播放,除非设置媒体实例为muted = true时,可以播放

https://developer.mozilla.org/en-US/docs/Web/Media/Autoplay_guide

2. Failed to execute ‘appendBuffer’ on ‘SourceBuffer’: Overload resolution failed. 🔗

buffer格式不对

https://stackoverflow.com/questions/71488781/uncaught-typeerror-failed-to-execute-appendbuffer-on-sourcebuffer-overload