4、媒体流操作:MediaStream与MediaStreamTrack、视频轨道控制、音频轨道控制、媒体流录制
各位同学,欢迎来到第四章。这一章我们聊聊媒体流操作。
说实话,媒体流是WebRTC的“血液”。没有它,你连个画面都传不过去。我刚开始接触WebRTC时,以为拿到一个MediaStream对象就完事了。后来才发现,里面的MediaStreamTrack才是真正干活的“小兵”。
这一章,我会带你拆解这两个核心对象。然后手把手教你控制视频轨道、音频轨道。最后,我们还会把媒体流录下来,存成文件。
嗯,内容有点多,但都很实用。我们开始吧。
4.1 MediaStream 与 MediaStreamTrack 的关系
先搞清楚这两个东西是什么。
MediaStream 是一个容器。它里面可以装多个 MediaStreamTrack。每个 MediaStreamTrack 代表一种类型的媒体数据,比如视频或音频。
我打个比方:MediaStream 就像一根HDMI线,而 MediaStreamTrack 就是线里面传输的视频信号和音频信号。你拔掉视频信号,画面就没了;你拔掉音频信号,声音就没了。
核心概念:
MediaStream:媒体流的集合,可以包含多个轨道。MediaStreamTrack:单个媒体轨道,要么是视频,要么是音频。- 一个
MediaStream可以包含多个MediaStreamTrack,但通常我们只用一个视频轨道和一个音频轨道。
下面这张图,帮你理清它们的关系:
你看,一个 MediaStream 里可以同时包含视频和音频轨道。每个轨道都有自己的属性,比如是否启用、是否静音。
4.2 获取媒体流:从摄像头和麦克风
获取媒体流,最常用的方法是 navigator.mediaDevices.getUserMedia()。这个方法会请求用户授权,然后返回一个 MediaStream 对象。
我个人习惯在调用时,明确指定需要视频还是音频。不要一股脑全要,免得用户觉得你“贪心”。
// 请求视频和音频
async function startStream() {
try {
const stream = await navigator.mediaDevices.getUserMedia({
video: true,
audio: true
});
// 把流绑定到 video 元素上
const video = document.getElementById('localVideo');
video.srcObject = stream;
} catch (err) {
console.error('获取媒体流失败:', err);
}
}
小技巧: 如果你只需要视频,可以只传 { video: true }。这样用户就不会看到麦克风授权弹窗。我在做纯视频会议时,经常这么干。
4.3 视频轨道控制
拿到 MediaStream 后,我们可以通过 getVideoTracks() 方法获取视频轨道。然后就能控制它的开关、分辨率、帧率等。
我曾经在一个项目中,需要根据网络状况动态调整视频质量。说白了,就是通过修改视频轨道的约束条件来实现的。
4.3.1 启用/禁用视频轨道
调用 track.enabled = false 可以关闭视频轨道。注意,这不会释放摄像头,只是停止发送数据。摄像头指示灯可能还亮着。
const videoTrack = stream.getVideoTracks()[0];
// 关闭视频
videoTrack.enabled = false;
// 重新开启
videoTrack.enabled = true;
注意: 如果你真的想释放摄像头,应该调用 track.stop()。调用后,摄像头指示灯会熄灭。但 stop() 是永久性的,不能再重新开启,除非重新调用 getUserMedia。
4.3.2 切换摄像头(前后置)
在移动端,经常需要切换前置和后置摄像头。怎么做?其实很简单:停止当前视频轨道,然后用新的约束重新获取流。
async function switchCamera() {
// 假设当前是前置摄像头
const constraints = {
video: { facingMode: { exact: 'environment' } }, // 后置
audio: true
};
// 停止旧轨道
stream.getTracks().forEach(track => track.stop());
// 获取新流
stream = await navigator.mediaDevices.getUserMedia(constraints);
video.srcObject = stream;
}
嗯,这里要注意:facingMode 的值可以是 'user'(前置)或 'environment'(后置)。但有些浏览器不支持 exact,所以最好加个降级方案。
4.4 音频轨道控制
音频轨道的控制逻辑和视频类似。但有几个特殊点,我单独拎出来说。
4.4.1 静音与取消静音
音频轨道的 enabled 属性,控制的是“是否发送音频数据”。设置为 false 就是静音。
const audioTrack = stream.getAudioTracks()[0];
// 静音
audioTrack.enabled = false;
// 取消静音
audioTrack.enabled = true;
你可能会问:这和 HTMLMediaElement.muted 有什么区别?
区别大了。 track.enabled = false 是停止发送音频数据,远端完全听不到。而 video.muted = true 只是本地听不到,数据还在发送。我在做通话测试时,经常用这个来模拟远端静音。
4.4.2 音量检测(选读)
有时候我们需要检测用户是否在说话。比如,实现“谁在说话”的指示器。这需要用到 AnalyserNode,但那是 Web Audio API 的范畴。这里简单提一下思路:
- 从
MediaStream创建AudioContext和MediaStreamSource。 - 连接一个
AnalyserNode。 - 定时读取频域数据,计算音量大小。
代码略长,我就不贴了。感兴趣的同学可以查一下 getByteTimeDomainData 的用法。
4.5 媒体流录制
录制媒体流,是 WebRTC 应用中的一个常见需求。比如录制会议内容、录制教学视频等。
浏览器提供了 MediaRecorder API,专门干这个事。它可以把 MediaStream 中的数据编码成视频文件(如 WebM)。
4.5.1 基本录制流程
let mediaRecorder;
let recordedChunks = [];
function startRecording(stream) {
// 指定 MIME 类型,浏览器支持度不同
const options = { mimeType: 'video/webm; codecs=vp9' };
mediaRecorder = new MediaRecorder(stream, options);
mediaRecorder.ondataavailable = (event) => {
if (event.data.size > 0) {
recordedChunks.push(event.data);
}
};
mediaRecorder.onstop = () => {
// 生成 Blob 并下载
const blob = new Blob(recordedChunks, { type: 'video/webm' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'recording.webm';
a.click();
URL.revokeObjectURL(url);
};
mediaRecorder.start(1000); // 每秒触发一次 ondataavailable
}
function stopRecording() {
mediaRecorder.stop();
}
关键点:
start(timeslice):传入时间片(毫秒),会定期触发ondataavailable。如果不传,则只在stop()时触发一次。ondataavailable:每次触发时,把数据块存起来。onstop:录制结束时,把所有数据块合并成一个 Blob,然后下载。
4.5.2 录制时的注意事项
我在实际项目中发现几个坑,分享给你:
- MIME 类型兼容性:不是所有浏览器都支持
video/webm; codecs=vp9。建议先检测:MediaRecorder.isTypeSupported()。 - 录制时长:如果录制时间很长,
recordedChunks数组会占用大量内存。可以考虑边录边上传,或者分段录制。 - 音频采样率:录制时,音频的采样率可能会被改变。如果需要高保真,最好在
getUserMedia时指定sampleRate约束。
我的经验: 有一次录制会议,发现录出来的视频没有声音。排查了半天,原来是 MediaRecorder 的 mimeType 不支持音频编码。后来改用 video/webm; codecs=vp8,opus 就好了。所以,记得检查 isTypeSupported。
4.6 本章小结
这一章,我们深入了媒体流的内部。
你学会了:
MediaStream和MediaStreamTrack的关系。- 如何获取、启用、禁用视频和音频轨道。
- 如何切换摄像头。
- 如何使用
MediaRecorder录制媒体流。
这些操作,是构建实时通信应用的基础。下一章,我们会把这些知识串起来,做一个完整的视频通话 Demo。
嗯,今天就到这里。记得动手敲代码,光看是学不会的。