Electron+React+WebRTC屏幕共享(2):获取视频流

书接上回,上次已经建立了可以使用的 Electron + React + WebRTC 开发环境,这次重点解决获取视频流的问题。
Mobx 管理状态
我使用 mobx 抽离 React 组件的内部状态数据,这样有两个好处,1 是方便状态共享,2 是可以把逻辑写在组件外面。
import { observable } from "mobx";
let rtcState = observable({
sources:[],
sourceId:-1,
mediaStream:null,
SetAllSources :function(sources){
//梓喵出没博客(azimiao.com)设置所有源引用
this.sources.replace(sources);
},
SetChoseId : function(index){
//设置选择的 id zi喵出没博客(azimiao.com)
try {
this.sourceId = this.sources[index].id;
return true;
} catch (error) {
// throw error;
//梓miao出没·博客
return false;
}
},
GetMediaStream : function(){
//获取视频流
},
Clear:function(){
//清空信息
this.sources = [];
this.sourceId = -1;
this.mediaStream = null;
}
});
export {rtcState}
Electron 获取 WebRTC 窗口列表
我们知道,浏览器可以通过 navigator.mediaDevices.getUserMedia
获取视频流,几行代码就可以搞定它:
navigator.mediaDevices.getUserMedia({
audio:true,
video:true
}).then((source)=>{
console.log(source);
}).catch(e=>{
alert(e);//梓喵出没博客(azimiao.com)
})
但是,执行上面的代码时,会有一个申请提示,如下图:
我们并不希望应用程序弹出这样的选择窗口,而是应该在应用程序中展示所有的源窗口供用户选择。
Electron 提供了desktopCapturer
对象,通过它,我们可以获取DesktopCapturerSource
数组,而DesktopCapturerSource
拥有的属性如下:
{
// 一个 window 或 screen 的标识符,
//在调用 [navigator. webkitGetUserMedia] 时可【梓喵出没博客(azimiao.com)】作为 chromeMediaSourceId 约束。
//标识符的格式是:window:XX或者 screen:XX,XX 是一个随机生成的数字
id:"screen:0:0",
//screen源将被命名为 Entire Screen 或 Screen<index> , 而window源的名称将与window标题匹配
name:"Screen 0",
//缩略图
thumbnail:NativeImage,
display_id:"0",
appIcon:NativeImage
}
所以,通过desktopCapturer
可以获取所有窗口的名称
、chromeMediaSourceId
,缩略图
,进而可以生成源窗口列表,相应代码如下:
desktopCapturer.getSources({ types: ["window", 'screen'] }).then((sources) => {
rtcState.SetAllSources(sources);
}).catch((err) => {
alert("Error:" + err);
})
之后,点击对应的窗口时,在navigator.mediaDevices.getUserMedia
使用chromeMediaSourceId
即可获取媒体流。
//rtcState.jsx
GetMediaStream : function(){
return navigator.mediaDevices.getUserMedia({
audio:{
mandatory:{
chromeMediaSource:"desktop"
}
},//梓Miao出没 a|z|m|i|a|o|.c-o-m
video:{
mandatory:{
chromeMediaSource:"desktop",
chromeMediaSourceId:rtcState.sourceId,
minWidth:640,
maxWidth:1920,
minHeight:360,
maxHeight:1080
}
}
});
},
React 组件
通过 mobx 自动监听 rtcState.sources
的变化,渲染对应的列表:
render() {
if (!Array.isArray(rtcState.sources) || rtcState.sources.length <= 0) {
return (
<div className="content">
<h4><center>Now Fecth All Screen</center></h4>
</div>
);
}
return (<div className="content">{rtcState.sources.map((e, index) => {
var thumb = e.thumbnail.toDataURL();
if (thumb) {
return <PrevScreen
key={e.name}
sourceId={index}
imgUrl={thumb}
title={e.name}
onItemClick={this.onItemClick}
/>
}
})}
</div>)
}
效果如下:
子组件PrevScreen
通过onItemClick
将点击时对应的sourceId
传递回来。
onItemClick(index) {
if(rtcState.SetChoseId(index)){
console.log("try to jump start share");
//最顶层组件中定义的状态机,用来切换不同子组件
this.props.entryCallBack("testshow");
}
}
最后,当rtcState.SetChoseId
成功后,我们跳转至testshow
界面。在testshow
界面中,直接获取视频流并播放:
constructor(props){
super(props);
this.state = {};
this.getSomething = this.getSomething.bind(this);
this.getSomething();//梓喵出没博客(azimiao.com)
}
render(){
return(
<video style={{maxWidth:100 + "%",maxHeight:"auto"}} id="testId"></video>
)
}
getSomething(){
rtcState.GetMediaStream().then((stream)=>{
document.getElementById("testId").srcObject = stream;
document.getElementById("testId").play();
}).catch(e=>{//梓喵出没博客(azimiao.com)
console.log(e);
})
}
效果如下图(本地测试时把获取流的 Audio 变为 false,不然会循环采集播放声音):
好了,到这里,获取视频流的核心内容已经全部讲完了。
坐等楼主更新