mediasoup-demo的网页增加选择摄像头功能

mediasoup-demo是mediasoup官方提供的一个BS视频会议完整示例。但是有一点不好的地方在于无法选择摄像头,只能是固定使用获取到的摄像头列表的第一个摄像头。这个不实用,有时候单机插多个摄像头来调试也很不方便。

作为C++工程师完全不会前端,居然还是被我蒙出来了。

这篇文章写给两类人:

  1. 第一类是前端工程师,只是因为刚刚接触mediasoup-demo,还不熟悉,那么我会告诉你需要改哪些文件,按什么逻辑改,你写出来的代码,调整的UI,肯定比我好看。
  2. 第二类是非前端工程师,我会完整给出怎么去改,同时尝试让你了解为什么要改这些文件和写这些代码。

做出来的效果如图,选择摄像头之后,单击打开关闭摄像头的按钮(摄像机图标的按钮),先关掉当前打开的摄像头,然后再单击一次这个按钮,重新打开摄像头就是打开下拉列表选择的摄像头了。

主要思路、涉及到的文件、对象(前端工程师看完应该就可以自己动手改了):

1)只需要改两个文件:

app/lib/components/Me.jsx
app/stylus/components/Me.styl

这两个文件就是左下角本端的视频框。我在Me.jsx里增加了select标签,然后抱着能用就行的态度在Me.styl里抄了一下按钮的样式作为select标签的样式。

2)如何获取摄像头列表:

在Me.jsx里有获取RoomClient这个对象,变量名是roomClient。静音按钮、开关摄像头等的实现都是调用这个对象的方法的。这个对象的实现在app/lib/RoomClient.js里。RoomClient里有一个成员变量_webcams,存储着“摄像头设备ID”到“摄像头设备对象”(MediaDeviceInfos)的映射(Map)。

3)如何设置要使用的摄像头:

还是在RoomClient里有一个成员变量_webcam(和之前的map只差了最后的s),当前正在使用的摄像头就存储在_webcam的device成员里,存放的就是“摄像头设备对象”(MediaDeviceInfos)。

4)实现

使用什么页面元素显示摄像头列表、放在哪里、什么样式,在什么事件加载这个摄像头列表到UI上,选中的时候怎么将选中的摄像头的“摄像头设备对象”赋值给roomClient._webcam.device,就各显神通了。

给非前端工程师:

非前端工程师接下来跟我开始魔改,只求能用,不保证好看,也不保证是稳定的。

1)先修改app/lib/components/Me.jsx文件,增加select标签,即下拉列表框

按我的理解,这个文件负责生成左下角本地端的html元素,同时包含这些html元素的js代码。核心的代码是render()函数

12 class Me extends React.Component
13 {
	   // 省略一些内容
21     
22 	   render()
23 	   {
	       // 注意这里的roomClient,从this.props获取到了RoomClient对象
24 	   	   const {
25 	   	   	   roomClient,
26 	   	   	   connected,
27 	   	   	   me,
28 	   	   	   audioProducer,
29 	   	   	   videoProducer,
30 	   	   	   faceDetection,
31 	   	   	   onSetStatsPeerId
32 	   	   } = this.props;

最初render函数就从this.props中获取到了一堆对象,其中就有我们后面要用到的roomClient。

const { xxx, yyy } = this.props;

这样的语句,应该是等价于下面这样拆开来的写法的(如果不是的话,请当我没说)

const xxx = this.props.xxx;
const yyy = this.props.yyy;

而且从this.props中获取到的时候变量就是已经赋值的,可以使用的状态了。roomClient对象我们后面再来用。

然后拉到render函数的最后,return的部分:

75	return (
76		<div
77			data-component='Me'
78			ref={(node) => (this._rootNode = node)}
79			data-tip={tip}
80			data-tip-disable={!tip}
81		>
82			<If condition={connected}>
83				<div className='controls'>
84					<div
85						className={classnames('button', 'mic', micState)}
86						onClick={() =>
87						{
88							micState === 'on'
89								? roomClient.muteMic()
90								: roomClient.unmuteMic();
91						}}
92 					/>
        // ......
168		</div>
169	);

这里可以看到,render函数最后是返回html标签和一些内嵌js,这些返回的html标签最终在浏览器加载页面的时候会被前端框架的代码嵌入到app/index.html中,而内嵌js最终会整合在一起生成server/public/mediasoup-demo-app.js。

既然是在render中返回html标签,那么要添加下拉列表的标签自然也就写在这里,我在mic也就是麦克风的按钮前面增加了select标签

75	return (
76		<div
77			data-component='Me'
78			ref={(node) => (this._rootNode = node)}
79			data-tip={tip}
80			data-tip-disable={!tip}
81		>
82			<If condition={connected}>
83				<div className='controls'>
					<!-- 增加select标签,className是select.cameralist -->
+					<select
                        <!-- id是cameraListSelect -->
+						id='cameraListSelect'
+						className={classnames('select', 'cameralist')}
+					/>
+
84					<div
85						className={classnames('button', 'mic', micState)}
86						onClick={() =>
87						{
88							micState === 'on'
89								? roomClient.muteMic()
90								: roomClient.unmuteMic();
91						}}
92 					/>
        // ......
168		</div>
169	);

这里只是加了标签,后面会讲到如何写js代码加载设备列表,以及设备选择之后怎么设置。

注意className,以mic的为例

className={classnames('button', 'mic', micState)}

micState是一个变量,从roomClient读取出来,就是当前麦克风是否开启的状态,可取值on、off、unsupported,那么className就有.button.mic.on、.button.mic.off、.button.mic.unsupported三种,注意.button.mic.on这个名称,在下面Me.styl设置的样式就会跟这个对应上。

2)修改app/stylus/components/Me.styl文件,为下拉列表select标签设置样式

styl是一种简化的css语法,经过框架处理之后形成css控制UI的样式。我在看的时候参考了如下的这篇文章(只需要看styl是什么东西,知道语法规则即可):

styl类型文件css,Stylus: 让你更简洁的完成cssicon-default.png?t=M3K6https://blog.csdn.net/weixin_42664597/article/details/119401528

借用他的一张图:

a7649d0604a5b1c09fdea0f38bd0f6a5.png

可以看出styl和生成的css的对应关系,可以概括为css不要冒号,不要分号,不要部分花括号、改用缩进描述层级关系,就是styl了。

为了方便非前端工程师理解,这里将原版的Me.styl文件的内容简化,并写了注释

1	[data-component='Me'] {
3		height: 100%;
4		width: 100%;

		// 视频上方控件栏的样式
6		> .controls {
			// 还设置了别的属性
9			top: 0;
10			left: 0;

			// 控件栏里按钮的总样式
18			> .button {
				// 还设置了别的属性
20				margin: 4px;

				// 设置了.button在PC浏览器的样式,宽28px、高28px
32				+desktop() {
33					width: 28px;
34					height: 28px;
					// 还有别的样式
40				}

				// 设置了.button在手机浏览器的样式,宽26px、高26px
42				+mobile() {
43					width: 26px;
44					height: 26px;
45				}
			
				// .button的“禁用”样式,设置了透明度等
51				&.disabled {
					// 还设置了别的属性
53					opacity: 0.5;
54				}

				// .button的“启用”样式,设置了背景色
56				&.on {
57					background-color: rgba(#fff, 0.85);
58				}

				// 单独描述“麦克风按钮”的样式
60				&.mic {
					// “启用”时样式,样式名完整是.button.mic.on,设置了背景图
61					&.on {
62						background-image: url('/resources/images/icon_mic_black_on.svg');
63					}
					// 还有其他的状态的图标
73				}
			// ......
112 		}
113 	}
114	}

注意.button{ &.mic { &.on { ... } } } 是声明麦克风按钮启用时的样式,将中间的{}&去掉,.button.mic.on就是前面Me.jsx的className了,两者就是这样关联上的。

然后我们开始修改,css的属性我也看不懂,但是看不懂、不会写可以抄啊。直接将按钮的样式抄上去,而且不分状态。我将我加的部分贴出来,大家可以自行理解。

1	[data-component='Me'] {
2		position: relative;
3		height: 100%;
4		width: 100%;
5	
6		> .controls {
7			position: absolute;
8			z-index: 10;
9			top: 0;
10			left: 0;
11			right: 0;
12			display: flex;
13			flex-direction: row;
14			justify-content: flex-end;
15			align-items: center;
16			pointer-events: none;
		
			// 这一节就是我加的.select.cameralist的样式,或许有很多没用的,但是能正常显示就好
+18			> .select {
				// 属性照搬下面.button,包括desktop和mobile这些都是
+19				flex: 0 0 auto;
+20				margin: 4px;
+21				margin-left: 0;
+22				border-radius: 2px;
+23				pointer-events: auto;
+24				background-position: center;
+25				background-size: 75%;
+26				background-repeat: no-repeat;
+27				background-color: rgba(#000, 0.5);
+28				cursor: pointer;
+29				transition-property: opacity, background-color;
+30				transition-duration: 0.15s;
+31	
+32				+desktop() {
+33					width: 80px;
+34					height: 28px;
+35					opacity: 0.85;
+36	
+37					&:hover {
+38						opacity: 1;
+39					}
+40				}
+41	
+42				+mobile() {
+43					width: 80px;
+44					height: 26px;
+45				}
+46	
+47				&.unsupported {
+48					pointer-events: none;
+49				}
+50	
+51				&.disabled {
+52					pointer-events: none;
+53					opacity: 0.5;
+54				}
+55	
+56				&.on {
+57					background-color: rgba(#fff, 0.85);
+58				}
+59	
+60				&.cameralist {
+61					
+62				}
+63			}

65			> .button {
66				flex: 0 0 auto;
67				margin: 4px;
68				margin-left: 0;
69				border-radius: 2px;
70				pointer-events: auto;
71				background-position: center;
72				background-size: 75%;
73				background-repeat: no-repeat;
74				background-color: rgba(#000, 0.5);
75				cursor: pointer;
76				transition-property: opacity, background-color;
77				transition-duration: 0.15s;
78
79				+desktop() {
80					width: 28px;
81					height: 28px;
82					opacity: 0.85;
83
84					&:hover {
85						opacity: 1;
86					}
87				}
	// ......

3)修改app/stylus/components/Me.jsx文件,添加加载设备列表和选中之后设置设备的js代码

本来想select标签有没有下拉列表弹出时触发的事件的,但是没找到,所以用了onFocus获取焦点的时候加载设备列表;列表项选中的事件是onChange。

前面定义了select标签的id是cameraListSelect,是为了在js里能获取到select标签的对象,给它动态添加下拉列表项(是不是有更好更方便的办法呢?没仔细研究)。

下面给出onFocus加载设备列表,onChange设置使用的摄像头的js代码:

<select
    id='cameraListSelect'
	className={classnames('select', 'cameralist')}
	onFocus={() =>
	{
		const options = document.getElementById('cameraListSelect').options;
		
		options.length = 0;
		
		roomClient._updateWebcams().then(function() 
		{
			for (const camera of roomClient._webcams.values())
			{
				options.add(new Option(camera.label, camera.deviceId));
			}
		});
	}}
	onChange={() =>
	{
		const cameralist = document.getElementById('cameraListSelect');
		const sel = cameralist.options[cameralist.selectedIndex].value;

		roomClient._webcam.device = roomClient._webcams.get(sel);
	}}
/>

select标签的下拉列表存储在options成员里,onFocus的第一步就是获取cameraListSelect标签对象和它的options成员。

然后length清0,将下拉列表清空。roomClient._webcams前面说了是“摄像头设备ID”到“摄像头设备对象”的映射(Map),调用values获取“摄像头设备对象”的列表,使用for循环遍历,语法不懂不要紧,在其他js或jsx文件里看看,搜索一下for是怎么写的就照着写。

注意:直接访问roomClient._webcams可能没加载,因此调用roomClient._updateWebcams()方法获取roomClient._webcams,而roomClient._updateWebcams()方法有async关键字,是异步执行的,内嵌的js函数不是async函数,不能用await关键字转同步调用,所以使用async函数返回的Promise对象的then回调,在异步函数执行完之后再for循环设置列表

options里面要添加的是Option对象,第一个参数camera.label是摄像头的可见名称,camera.deviceId是摄像头的一个唯一ID,作为每个列表项的值。

onChange的目标就是获取select标签当前选择的项,将对应的摄像头唯一ID拿出来,然后调用roomClient._webcams的get方法获取“摄像头设备对象”(MediaDeviceInfos),赋值给roomClient._webcam.device就大功告成了。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值