TIP
在学习 WebRTC 音视频开发前,我们先来学习了解一下音频、视频等媒体数据的获取与播放。了解一下入门级的 API 的使用。
前言
大家好,我是小鑫同学。一位从事过Android开发、混合开发,现在长期从事前端开发的编程爱好者,我觉得在编程之路上最重要的是知识的分享,所谓三人行必有我师。所以我开始在社区持续输出我所了解到、学习到、工作中遇到的各种编程知识,欢迎有想法、有同感的伙伴加我fe-xiaoxin微信交流~
写作背景:
在学习 WebRTC 音视频开发前,我们先来学习了解一下音频、视频等媒体数据的获取与播放。了解一下入门级的 API 的使用。
API 介绍:
MediaDevices:
https://developer.mozilla.org/zh-CN/docs/Web/API/MediaDevices
这个 API 提供了如何访问媒体数据输入的设备,如 PC 电脑的摄像头、麦克风、屏幕共享等。
MediaDevices.getUserMedia():
通过传入不同的约束提条件来获取到不同的媒体数据流,前提的用户授权使用的情况下。使用到摄像头、麦克风时浏览器就会对用户提示并希望得到用户授权。
MediaDevices.getDisplayMedia():
在得到用户授权后可以选择屏幕、窗口、Chrome 标签页进行媒体流的获取。在获取 Mac 屏幕/窗口进行屏幕共享的时候需要在 Mac 的【偏好设置】=>【安全与隐私】=>【隐私】=>【屏幕录制】中勾选允许Chrome。
获取摄像头视频数据流:
- 设置约束条件,允许视频,禁用音频:
const constraints: MediaStreamConstraints = { audio: false, video: true };
const constraints: MediaStreamConstraints = { audio: false, video: true };
- 同步调用getUserMedia得到媒体流:
const stream = await navigator.mediaDevices.getUserMedia(constraints);
const stream = await navigator.mediaDevices.getUserMedia(constraints);
- 通过媒体流得到设备的信息,如设备名称:
const videoTracks = stream.getVideoTracks();
console.log(videoTracks[0].label);
const videoTracks = stream.getVideoTracks();
console.log(videoTracks[0].label);
- 通过 video 标签播放媒体流,这里不适用 src 属性,要使用 srcObject 属性,这个属性是HTMLMediaElement 类的其中一员,所以我们在 TypeScript 编写代码的时候在泛型约束时指定了它:
const video = ref<HTMLMediaElement>();
const openCamera = async () => {
try {
const constraints: MediaStreamConstraints = { audio: false, video: true };
const stream = await navigator.mediaDevices.getUserMedia(constraints);
const videoTracks = stream.getVideoTracks();
console.log(videoTracks[0].label);
video.value!.srcObject = stream;
} catch (error) {
message.error(`获取用户媒体数据错误:${error}`);
}
};
// video元素定义
<video ref="video" autoplay></video>
const video = ref<HTMLMediaElement>();
const openCamera = async () => {
try {
const constraints: MediaStreamConstraints = { audio: false, video: true };
const stream = await navigator.mediaDevices.getUserMedia(constraints);
const videoTracks = stream.getVideoTracks();
console.log(videoTracks[0].label);
video.value!.srcObject = stream;
} catch (error) {
message.error(`获取用户媒体数据错误:${error}`);
}
};
// video元素定义
<video ref="video" autoplay></video>
获取麦克风音频数据流:
与上面获取摄像头媒体数据类似,只是使用不同的约束与不同的播放标签,请看完整代码:
const audio = ref<HTMLMediaElement>();
const openMicrophone = async () => {
try {
const constraints: MediaStreamConstraints = { audio: true, video: false };
const stream = await navigator.mediaDevices.getUserMedia(constraints);
const audioTracks = stream.getAudioTracks();
console.log(audioTracks[0].label);
audio.value!.srcObject = stream;
} catch (error) {
message.error(`获取用户媒体数据错误:${error}`);
}
};
// audio元素定义
<audio ref="audio" autoplay controls></audio>
const audio = ref<HTMLMediaElement>();
const openMicrophone = async () => {
try {
const constraints: MediaStreamConstraints = { audio: true, video: false };
const stream = await navigator.mediaDevices.getUserMedia(constraints);
const audioTracks = stream.getAudioTracks();
console.log(audioTracks[0].label);
audio.value!.srcObject = stream;
} catch (error) {
message.error(`获取用户媒体数据错误:${error}`);
}
};
// audio元素定义
<audio ref="audio" autoplay controls></audio>
截取视频流输入到画布:
在进行播放摄像头数据的过程中我们可以截取其中的一个画面输出到画布中,这个案例工作前需要正常得到摄像头返回的媒体数据流:
因为我们要得到 video 标签的一个宽高来设置我们的画布尺寸,所以在泛型约束的时候我们增加一个HTMLVideoElement 类型约束:
const captureVideo = async () => {
const videoSource = video.value as HTMLVideoElement;
canvas.value!.width = videoSource.videoWidth;
canvas.value!.height = videoSource.videoHeight;
canvas.value
?.getContext("2d")
?.drawImage(videoSource, 0, 0, canvas.value!.width, canvas.value!.height);
};
// canvas元素定义
<canvas ref="canvas"></canvas>
const captureVideo = async () => {
const videoSource = video.value as HTMLVideoElement;
canvas.value!.width = videoSource.videoWidth;
canvas.value!.height = videoSource.videoHeight;
canvas.value
?.getContext("2d")
?.drawImage(videoSource, 0, 0, canvas.value!.width, canvas.value!.height);
};
// canvas元素定义
<canvas ref="canvas"></canvas>
通过浏览器共享屏幕:
我们这里有少许的不同,我们需要使用 getDisplayMedia,但是同样将得到的媒体流交由 video 标签播放:
const shareScreen = async () => {
try {
const stream = await navigator.mediaDevices.getDisplayMedia({
video: true,
});
const videoTracks = stream.getVideoTracks();
console.log(videoTracks[0].label);
video.value!.srcObject = stream;
} catch (error) {
console.log((error as Error).name);
message.error(`获取用户媒体数据错误:${error}`);
}
};
// video元素定义
<video ref="video" autoplay></video>
const shareScreen = async () => {
try {
const stream = await navigator.mediaDevices.getDisplayMedia({
video: true,
});
const videoTracks = stream.getVideoTracks();
console.log(videoTracks[0].label);
video.value!.srcObject = stream;
} catch (error) {
console.log((error as Error).name);
message.error(`获取用户媒体数据错误:${error}`);
}
};
// video元素定义
<video ref="video" autoplay></video>
注:我们上面有提到的,屏幕录制的权限,一定要在系统偏好中提前设置,要不你的看到 NotAllowedError: Permission denied by system 的❌错误提示。
结语:
通过上面 4 个场景用例我们熟悉了在浏览器中通过规范的 API 来实现媒体数据的读取与播放,大大节省了音视频在 web 端开发的难度,明天继续学!
如果看完觉得有收获,欢迎点赞、评论、分享支持一下。你的支持和肯定,是我坚持写作的动力~
最后可以关注我@小鑫同学。欢迎点此扫码加我微信fe-xiaoxin交流,共同进步(还可以帮你fix🐛)~