音视频的录制音视频的分为服务端录制和客户端录制,下面主要讲的是利用webrtc进行客户端录制的方式。
因为WebRTC 录制音视频流之后,最终是通过 Blob 对象将数据保存成多媒体文件的,

  • Blob 对象表示一个不可变、原始数据的类文件对象。它的数据可以按文本或二进制的格式进行读取,也可以转换成 ReadableStream 来用于数据操作。

  • Blob 表示的不一定是JavaScript原生格式的数据。File接口基于Blob,继承了 blob 的功能并将其扩展使其支持用户系统上的文件。

  • 要从其他非blob对象和数据构造一个 Blob,请使用 Blob() 构造函数。要创建一个 blob 数据的子集 blob,请使用 slice()方法。要获取用户文件系统上的文件对应的 Blob 对象。

  • 接受 Blob 对象的API也被列在 File 文档中。

了解完Blob的特性之后呢, 我们进入正题。

获取元素声明变量

let mediaRecorder;
let recordedBlobs;

const recordedVideo = document.querySelector('video#recorded');
const recordButton = document.querySelector('button#record');
const playButton = document.querySelector('button#play');
const downloadButton = document.querySelector('button#download');

绑定点击事件

  • URL.createObjectURL() 静态方法会创建一个 DOMString,其中包含一个表示参数中给出的对象的URL。这个 URL 的生命周期和创建它的窗口中的 document 绑定。这个新的URL 对象表示指定的 File 对象或 Blob 对象。

  • URL.revokeObjectURL() 静态方法用来释放一个之前已经存在的、通过调用 URL.createObjectURL() 创建的 URL 对象。当你结束使用某个 URL 对象之后,应该通过调用这个方法来让浏览器知道不用在内存中继续保留对这个文件的引用了。你可以在 sourceopen 被处理之后的任何时候调用 revokeObjectURL()。这是因为 createObjectURL() 仅仅意味着将一个媒体元素的 src 属性关联到一个 MediaSource 对象上去。调用revokeObjectURL() 使这个潜在的对象回到原来的地方,允许平台在合适的时机进行垃圾收集。
recordButton.addEventListener('click', () => {
    if (recordButton.textContent === 'Start Recording') {
        startRecording();
    } else {
        stopRecording();
        recordButton.textContent = 'Start Recording';
        playButton.disabled = false;
        downloadButton.disabled = false;
    }
});

playButton.addEventListener('click', () => {
    const superBuffer = new Blob(recordedBlobs);
    recordedVideo.src = null;
    recordedVideo.srcObject = null;
    recordedVideo.src = window.URL.createObjectURL(superBuffer);
    recordedVideo.controls = true;
    recordedVideo.play();
});

downloadButton.addEventListener('click', () => {
    const blob = new Blob(recordedBlobs, {
        type: 'video/webm'
    });
    const url = window.URL.createObjectURL(blob);
    const a = document.createElement('a');
    a.style.display = 'none';
    a.href = url;
    a.download = 'test.webm';
    document.body.appendChild(a);
    a.click();
    setTimeout(() => {
        document.body.removeChild(a);
        window.URL.revokeObjectURL(url);
    }, 100);
});

document.querySelector('button#start').addEventListener('click', async () => {
    const constraints = {
        audio: {},
        video: {
            width: 1280,
            height: 720
        }
    };
    console.log('Using media constraints:', constraints);
    await init(constraints);
});

点击开始初始化

  • MediaDevices.getUserMedia() 会提示用户给予使用媒体输入的许可,媒体输入会产生一个MediaStream,里面包含了请求的媒体类型的轨道。此流可以包含一个视频轨道(来自硬件或者虚拟视频源,比如相机、视频采集设备和屏幕共享服务等等)、一个音频轨道(同样来自硬件或虚拟音频源,比如麦克风、A/D转换器等等),也可能是其它轨道类型。

  • 它返回一个 Promise 对象,成功后会resolve回调一个 MediaStream 对象。若用户拒绝了使用权限,或者需要的媒体源不可用,promisereject回调一个 PermissionDeniedError 或者 NotFoundError
async function init(constraints) {
    try {
        const stream = await navigator.mediaDevices.getUserMedia(constraints);
        handleSuccess(stream);
    } catch (e) {
        console.error('navigator.getUserMedia error:', e);
    }
}

function handleSuccess(stream) {
    recordButton.disabled = false;
    console.log('getUserMedia() got stream:', stream);
    window.stream = stream;
    const gumVideo = document.querySelector('video#gum');
    gumVideo.srcObject = stream;
}

开始记录方法

function startRecording() {
    recordedBlobs = [];
    try {
        mediaRecorder = new MediaRecorder(window.stream);
    } catch (e) {
        console.error('Exception while creating MediaRecorder:', e);
        return;
    }

    recordButton.textContent = 'Stop Recording';
    playButton.disabled = true;
    downloadButton.disabled = true;
    mediaRecorder.onstop = (event) => {
        console.log('Recorder stopped: ', event);
        console.log('Recorded Blobs: ', recordedBlobs);
    };
    mediaRecorder.ondataavailable = handleDataAvailable;
    mediaRecorder.start();
}

function handleDataAvailable(event) {
    if (event.data && event.data.size > 0) {
        recordedBlobs.push(event.data);
    }
}

HTML

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <meta id="theme-color" name="theme-color" content="#ffffff">
    <link rel="stylesheet" href="./index.css">
</head>

<body>
    <div id="container">
        <video id="gum" playsinline autoplay muted></video>
        <video id="recorded" playsinline loop></video>
        <div>
            <button id="start">Start camera</button>
            <button id="record" disabled>Start Recording</button>
            <button id="play" disabled>Play</button>
            <button id="download" disabled>Download</button>
        </div>
    </div>
    <script src="./main.js" async></script>
</body>
</html>

CSS

button {
    background-color: #d84a38;
    border: none;
    border-radius: 2px;
    color: white;
    font-family: 'Roboto', sans-serif;
    font-size: 0.8em;
    margin: 0 0 1em 0;
    padding: 0.5em 0.7em 0.6em 0.7em;
}

button:active {
    background-color: #cf402f;
}

button:hover {
    background-color: #cf402f;
}

button[disabled] {
    color: #ccc;
}

button[disabled]:hover {
    background-color: #d84a38;
}

div#container {
    margin: 0 auto 0 auto;
    max-width: 60em;
    padding: 1em 1.5em 1.3em 1.5em;
}

video {
    background: #222;
    margin: 0 0 20px 0;
    --width: 100%;
    width: var(--width);
    height: calc(var(--width) * 0.75);
}

webRTC功能是非常强大的,关于webrtc和直播还有很多技术需要我们去研究,上续呢是DEMO的示例代码,感兴趣的小伙伴可以亲自试一试。