Last active
May 16, 2019 18:14
-
-
Save jmaguirrei/32878d691c306bce2849b3c2b5276869 to your computer and use it in GitHub Desktop.
WebRTC React Boilerplate (Agora.io)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { | |
initService, | |
subscribeStreamEvents, | |
joinChannel, | |
streamConfig, | |
streamInit, | |
manageAudio, | |
manageVideo, | |
streamClose, | |
} from '../methods'; | |
/* ------------------------------------------------------------------------------------------------ | |
startPeer | |
------------------------------------------------------------------------------------------------ */ | |
// 'this' is the react component | |
export function startPeer() { | |
const { isMicOn, isVideoOn } = this.props; | |
initService(this, 'peer') | |
.then(() => subscribeStreamEvents(this)) | |
.then(() => joinChannel(this, 'peer')) | |
.then(() => streamConfig(this, 'peer')) | |
.then(() => streamInit(this, 'peer')) | |
.then(() => manageAudio(this, isMicOn ? 'enable' : 'disable')) | |
.then(() => manageVideo(this, isVideoOn ? 'enable' : 'disable')) | |
.catch(console.log); | |
} | |
/* ------------------------------------------------------------------------------------------------ | |
startScreen | |
------------------------------------------------------------------------------------------------ */ | |
// 'this' is the react component | |
export function startScreen() { | |
if (this.screenStartedCount === 0) { | |
this.screenStartedCount++; | |
initService(this, 'screen') | |
.then(() => joinChannel(this, 'screen')) | |
.then(() => streamConfig(this, 'screen')) | |
.then(() => streamInit(this, 'screen')) | |
.catch(this.props.onScreenShareDeny); | |
} | |
} | |
/* ------------------------------------------------------------------------------------------------ | |
stopScreen | |
------------------------------------------------------------------------------------------------ */ | |
// 'this' is the react component | |
export function stopScreen() { | |
this.screenStartedCount = 0; | |
this.removeStream(this.props.screenShareUID); | |
streamClose(this, 'screen').catch(console.log); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import _ from 'lodash'; | |
/* ------------------------------------------------------------------------------------------------ | |
addStream | |
------------------------------------------------------------------------------------------------ */ | |
const MAX_PEER_NUM = 3; | |
// 'this' is the react component | |
export function addStream(stream) { | |
const { screenShareUID } = this.props; | |
const repetition = this.state.streamList.some(item => item.getId() === stream.getId()); | |
if (!repetition) { | |
const clonedStream = [ ...this.state.streamList ]; | |
const peerNum = _.reject(clonedStream, item => item.getId() === screenShareUID).length; | |
if (peerNum < MAX_PEER_NUM) { | |
this.updateState({ | |
streamList: [ ...clonedStream, stream ], | |
}); | |
} | |
} | |
} | |
/* ------------------------------------------------------------------------------------------------ | |
removeStream | |
------------------------------------------------------------------------------------------------ */ | |
// 'this' is the react component | |
export function removeStream(uid) { | |
this.state.streamList.map((item, index) => { | |
if (item.getId() === uid) { | |
item.close(); | |
let element = document.querySelector('#ag-item-' + uid); | |
if (element) element.parentNode.removeChild(element); | |
let clone = [ ...this.state.streamList ]; | |
clone.splice(index, 1); | |
this.updateState({ | |
streamList: clone, | |
}); | |
} | |
}); | |
} | |
/* ------------------------------------------------------------------------------------------------ | |
updateStream | |
------------------------------------------------------------------------------------------------ */ | |
import { updateStreamStyle } from './updateStreamStyle'; | |
const screenStyle = { | |
width: '100%', | |
height: '100%', | |
}; | |
// 'self' is the react component | |
export const updateStream = self => { | |
const { | |
screenShareUID, | |
videoNode_id, | |
screenShareNode_id, | |
peerStyle, | |
} = self.props; | |
const videoWrapper = document.getElementById(videoNode_id); | |
const screenWrapper = document.getElementById(screenShareNode_id); | |
self.state.streamList.map(item => { | |
const id = item.getId(); | |
const node_id = `ag-item-${id}`; | |
const mode = id === screenShareUID ? 'screen' : 'peer'; | |
const wrapper = mode === 'screen' ? screenWrapper : videoWrapper; | |
const style = mode === 'screen' ? screenStyle : peerStyle; | |
const findElement = document.getElementById(node_id); | |
if (!findElement && item.play) { | |
const newElement = document.createElement('section'); | |
newElement.setAttribute('id', node_id); | |
wrapper.appendChild(newElement); | |
updateStreamStyle(node_id, style); | |
item.play(node_id); | |
} | |
if (item.player && item.player.resize) item.player.resize(); | |
}); | |
}; | |
/* ------------------------------------------------------------------------------------------------ | |
updateStreamStyle | |
------------------------------------------------------------------------------------------------ */ | |
export const updateStreamStyle = (node_id, style) => { | |
const element = document.getElementById(node_id); | |
if (element) { | |
_.forEach(style, (val, key) => { | |
element.style[key] = val; | |
}); | |
} | |
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* ------------------------------------------------------------------------------------------------ | |
initService | |
------------------------------------------------------------------------------------------------ */ | |
export function initService(self, mode) { | |
const appId = process.env.WEBRTC_AGORA_APP_ID; | |
self.AgoraRTC = window.AgoraRTC; | |
self.AgoraRTC.Logger.setLogLevel(self.AgoraRTC.Logger.ERROR); | |
const newClient = self.AgoraRTC.createClient({ mode: self.transcode }); | |
if (mode === 'screen') self.shareClient = newClient; | |
if (mode === 'peer') self.client = newClient; | |
return new Promise(resolve => { | |
newClient.init(appId, () => { | |
resolve('initService success'); | |
}); | |
}); | |
} | |
/* ------------------------------------------------------------------------------------------------ | |
joinChannel | |
------------------------------------------------------------------------------------------------ */ | |
export function joinChannel(self, mode) { | |
const { peerUID, screenShareUID, meeting_id } = self.props; | |
const appId = process.env.WEBRTC_AGORA_APP_ID; | |
const client = mode === 'screen' ? self.shareClient : self.client; | |
return new Promise(resolve => { | |
client.join( | |
appId, | |
meeting_id, // channel | |
mode === 'peer' ? peerUID : screenShareUID, // uid | |
uid => { | |
if (mode === 'screen') self.shareUid = uid; | |
if (mode === 'peer') self.uid = uid; | |
resolve('joinChannel success'); | |
}, | |
); | |
}); | |
} | |
/* ------------------------------------------------------------------------------------------------ | |
manageAudio | |
------------------------------------------------------------------------------------------------ */ | |
export function manageAudio(self, option) { | |
const { localStream } = self; | |
if (!localStream) return; | |
if (option === 'enable') { | |
self.localStream.enableAudio(); | |
} else if (option === 'disable') { | |
self.localStream.disableAudio(); | |
} | |
} | |
import { updateStreamStyle } from '../dom'; | |
/* ------------------------------------------------------------------------------------------------ | |
manageVideo | |
------------------------------------------------------------------------------------------------ */ | |
export function manageVideo(self, option) { | |
const { localStream } = self; | |
if (!localStream) return; | |
const node_id = `ag-item-${self.uid}`; | |
if (option === 'enable') { | |
self.localStream.enableVideo(); | |
updateStreamStyle(node_id, { display: 'block' }); | |
} else if (option === 'disable') { | |
self.localStream.disableVideo(); | |
updateStreamStyle(node_id, { display: 'none' }); | |
} | |
} | |
/* ------------------------------------------------------------------------------------------------ | |
streamClose | |
------------------------------------------------------------------------------------------------ */ | |
export function streamClose(self, mode) { | |
const client = mode === 'screen' ? self.shareClient : self.client; | |
return new Promise((resolve, reject) => { | |
if (client) { | |
if (mode === 'peer' && self.localStream) { | |
client.unpublish(self.localStream); | |
self.localStream.close(); | |
} | |
if (mode === 'screen' && self.shareStream) { | |
client.unpublish(self.shareStream); | |
self.shareStream.close(); | |
} | |
client.leave(() => { | |
resolve('Client succeed to leave.'); | |
}, () => { | |
reject('Client failed to leave.'); | |
}); | |
} | |
}); | |
} | |
/* ------------------------------------------------------------------------------------------------ | |
streamConfig | |
------------------------------------------------------------------------------------------------ */ | |
export function streamConfig(self, mode) { | |
return new Promise(resolve => { | |
if (mode === 'screen') { | |
const stream = self.AgoraRTC.createStream({ | |
streamID: self.shareUid, | |
video: false, | |
audio: false, | |
screen: true, | |
// chrome | |
extensionId: 'minllpmhdgpndnkomcoccfekfegnlikg', | |
/* | |
for production use create a copy of the plugin in chrome extensions store | |
and point it to your server in the manifest.json | |
Example used in development tobe able to access local camera: | |
"externally_connectable": { | |
"matches": [ "*://127.0.0.1/*" ] | |
}, | |
*/ | |
// firefox | |
mediaSource: 'application' // 'screen', 'application', 'window' | |
}); | |
stream.setVideoProfile(self.shareVideoProfile); | |
self.shareStream = stream; | |
resolve('streamConfig screen-share done'); | |
} else { | |
// audio-video | |
const stream = self.AgoraRTC.createStream({ | |
streamID: self.uid, | |
video: true, | |
audio: true, | |
screen: false, | |
}); | |
stream.setVideoProfile(self.videoProfile); | |
self.localStream = stream; | |
resolve('streamConfig audio-video done'); | |
} | |
}); | |
} | |
/* ------------------------------------------------------------------------------------------------ | |
streamInit | |
------------------------------------------------------------------------------------------------ */ | |
export function streamInit(self, mode) { | |
const client = mode === 'screen' ? self.shareClient : self.client; | |
const stream = mode === 'screen' ? self.shareStream : self.localStream; | |
return new Promise((resolve, reject) => { | |
stream.init(() => { | |
self.addStream(stream); | |
client.publish(stream, err => { | |
reject('Publish local stream error: ' + err); | |
}); | |
resolve('streamInit success'); | |
}, error => { | |
reject(error); | |
}); | |
}); | |
} | |
/* ------------------------------------------------------------------------------------------------ | |
subscribeStreamEvents | |
------------------------------------------------------------------------------------------------ */ | |
export function subscribeStreamEvents(self) { | |
self.client.on('stream-added', evt => { | |
console.log('New stream added: ' + evt.stream.getId()); | |
self.client.subscribe(evt.stream, err => { | |
console.log('Subscribe stream failed', err); | |
}); | |
}); | |
self.client.on('peer-leave', evt => { | |
console.log('Peer has left: ' + evt.uid); | |
self.removeStream(evt.uid); | |
}); | |
self.client.on('stream-subscribed', evt => { | |
console.log('Got stream-subscribed: ' + evt.stream.getId()); | |
self.addStream(evt.stream); | |
}); | |
self.client.on('stream-removed', evt => { | |
console.log('Stream removed: ' + evt.stream.getId()); | |
self.removeStream(evt.stream.getId()); | |
}); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import _ from 'lodash'; | |
import React from 'react'; | |
import { | |
streamClose, | |
manageAudio, | |
manageVideo, | |
} from './methods'; | |
import { | |
addStream, | |
removeStream, | |
updateStream, | |
} from './dom'; | |
import { | |
startPeer, | |
startScreen, | |
stopScreen, | |
} from './actions'; | |
/* | |
Component Props | |
isMicOn: local Stream mic on/off (boolean), | |
isVideoOn: local Stream video on/off (boolean), | |
isMyScreenShared: share Stream on/off (boolean), | |
meeting_id: channel name, | |
peerUID: uid of local Stream, | |
peerStyle: object to style the video peers, | |
screenShareUID: uid of share Stream, use 1 as default and make peers bigger than 1 | |
screenShareNode_id: dom node where screen will be inserted, | |
videoNode_id: dom node where videos will be inserted, | |
syncStreamList: informs external state about actual connected peers (it's a callback), | |
onScreenShareDeny: callback, | |
*/ | |
export class WebRTC extends React.Component { | |
AgoraRTC = null; | |
uid = null; | |
client = null; | |
localStream = null; | |
shareUid = null; | |
shareClient = null; | |
shareStream = null; | |
screenStartedCount = 0; | |
transcode = 'interop'; | |
videoProfile = '360p_6'; | |
shareVideoProfile = '720p_2'; | |
state = { | |
streamList: [], | |
}; | |
updateState = ({ streamList }) => { | |
const { syncStreamList } = this.props; | |
this.setState({ streamList }, () => { | |
const streamList_ids = _.map(streamList, item => item.getId()); | |
syncStreamList({ streamList_ids }); | |
}); | |
}; | |
constructor(props) { | |
super(props); | |
this.addStream = addStream.bind(this); | |
this.removeStream = removeStream.bind(this); | |
this.startPeer = startPeer.bind(this); | |
this.stopScreen = stopScreen.bind(this); | |
this.startScreen = startScreen.bind(this); | |
} | |
componentDidMount() { | |
window.AgoraRTCComponent = this; | |
this.startPeer(); | |
if (this.props.isMyScreenShared) this.startScreen(); | |
} | |
componentWillReceiveProps(nextProps) { | |
const { isMicOn, isVideoOn, isMyScreenShared } = nextProps; | |
manageAudio(this, isMicOn ? 'enable' : 'disable'); | |
manageVideo(this, isVideoOn ? 'enable' : 'disable'); | |
if (isMyScreenShared) this.startScreen(); | |
if (this.props.isMyScreenShared && !isMyScreenShared) this.stopScreen(); | |
} | |
componentWillUnmount() { | |
streamClose(this, 'peer').catch(console.log); | |
streamClose(this, 'screen').catch(console.log); | |
} | |
componentDidUpdate() { | |
updateStream(this); | |
} | |
render() { | |
return null; | |
} | |
} | |
export default WebRTC; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment