大家好。你们经常在网站上使用摄像头或麦克风吗?或者,作为用户,你们自己也使用过?今天我们就来谈谈如何使用它以及它可能带来的问题。
在谈到网站使用用户设备时,我们需要考虑以下因素:
- 它应能在大多数浏览器上运行
- 必须能在手机上运行
- 必须对用户友好
要使用用户设备,就必须使用媒体捕捉和流 API(媒体流),即 MediaDevices。我们将创建一个类来使用该 API,并在其中添加我们需要的逻辑。
export class MediaDevices {
private readonly mediaDevices: MediaDevices = navigator.mediaDevices;
// Requests a list of the currently available media input and output devices, such as microphones, cameras, headsets, and so forth
public getUserDevices(): Promise<MediaDeviceInfo[]> {
return this.mediaDevices.enumerateDevices();
}
// Prompts the user for permission to use a media input which produces a MediaStream with tracks containing the requested types of media.
public getUserMedia(constraints?: MediaStreamConstraints): Promise<MediaStream> {
return this.mediaDevices.getUserMedia(constraints)
}
}
此时此刻,我们可以说,只要把这些方法结合起来,就可以享受生活了,但遗憾的是,事情并没有那么简单。
为什么需要这么多浏览器?
并非所有浏览器都有相同的接收 MediaStream 的方法。举例来说,可以使用以下几种(最常用的几种):
- 基于 Chromium 的 Safari – getUserMedia
- Firefox 浏览器 – mozGetUserMedia
- 旧版 Chrome 浏览器 – webkitGetUserMedia
- *让我们忘掉 IE 吧。
这看起来使用起来不是很方便,让我们更新一下 getUserMedia 方法。
public getUserMedia(constraints?: MediaStreamConstraints): Promise<MediaStream> {
return (
this.mediaDevices.getUserMedia?.(constraints) ||
this.mediaDevices.webkitGetUserMedia?.(constraints) ||
this.mediaDevices.mozGetUserMedia?.(constraints)
)
}
如果你像我一样使用 TypeScript,可以创建一个 global.d.ts 文件并添加以下内容,以避免出现方法缺失的问题。
type GetUserMediaMethod = (...args: Parameters<MediaDevices['getUserMedia']>) => ReturnType<MediaDevices['getUserMedia']>;
// Update Global MediaDevices interfact
interface MediaDevices {
// Old chrome browsers
webkitGetUserMedia?: GetUserMediaMethod;
// Firefox
mozGetUserMedia?: GetUserMediaMethod;
}
你还可以编写 getUserMedia 多填充代码,以支持最老版本的浏览器。
if (navigator.mediaDevices === undefined) {
navigator.mediaDevices = {};
}
if (navigator.mediaDevices.getUserMedia === undefined) {
navigator.mediaDevices.getUserMedia = function (constraints) {
const getUserMedia =
navigator.webkitGetUserMedia ||
navigator.mozGetUserMedia ||
navigator.getUserMedia;
if (!getUserMedia) {
return Promise.reject(
new Error("getUserMedia is not implemented in this browser")
);
}
return new Promise(function (resolve, reject) {
getUserMedia.call(navigator, constraints, resolve, reject);
});
};
}
这可以在类中或其他地方描述。最重要的是,应在调用 MediaDevices 类的 getUserMedia 方法之前调用该方法。
那么手机呢?
使用手机时,原理是一样的。不过,我在手机上遇到了麻烦。
在某些设备上,getUserMedia 方法可能会被拒绝,并出现 “无法启动视频源 “的错误。
这是我给你的建议:在获取新的 MediaStream 之前,请始终停止前一个 MediaStream。
mediaStream.getTracks().forEach((track) => track.stop());
or
mediaStream.getAudioTracks().forEach((track) => track.stop());
or
mediaStream.getVideoTracks().forEach((track) => track.stop());
class MediaDevices {
//...
private currentMediaStream: MediaStream;
//...
public stopCurrentMediaStream(): void {
this.currentMediaStream?.getTracks().forEach((track) => track.stop());
}
}
在我的情况下,只有在前一个数据流停止后才能更换摄像头。如果您的摄像机已在另一个标签页中使用,也会出现这个问题。遗憾的是,这只能通过在另一个选项卡中禁用摄像机来解决。
使用音频输出
当我们使用麦克风工作时,有时可能希望能听到自己的声音。或者,假设我们有一个听音乐的网站,我们希望将声音输出到扬声器。Web API 为我们提供了以下 setSinkId 函数。该函数适用于 HTMLMediaElement(HTMLVideoElement、HTMLAudioElement)和 AudioContext。不过,我们在使用时要小心,因为虽然这个函数很有用,但它并不适用于所有浏览器。
就我而言,我使用了 AudioContext 来播放来自设备的音频,这取决于用户的选择。我们需要检查浏览器中是否有此功能。我们还需要检查我们使用的是什么 ID。如果在 Chrome 浏览器中没有过滤默认设备,则需要插入一个空字符串,因为 setSinkId 不会理解默认设备 ID 是什么。
function setAudioOutput(audioContext: AudioContext, sinkId: string = ''): void {
const deviceSinkId = sinkId === 'default' ? '' : sinkId;
if ('setSinkId' in audioContext) {
audioContext.setSinkId(id);
}
}
如果浏览器中的 AudioContext 可用该函数,则条件将返回 true。SinkId 是 Chrome 浏览器的一种变通方法。因为它已选择了默认设备,其 id 将是默认的,所以 setSinkId 方法无法理解这样的 id。
结论
目前,在浏览器中处理用户设备并不困难。但是,当我们开始扩大使用设备的范围时,就会有很多陷阱。不过,我们了解得越多,分享的信息越多,大家就会越轻松!
作者:Vladislav Stepanov
本文来自作者投稿,版权归原作者所有。如需转载,请注明出处:https://www.nxrte.com/jishu/webrtc/35336.html