Skip to content

Instantly share code, notes, and snippets.

@jmaguirrei
Last active May 16, 2019 18:14
Show Gist options
  • Save jmaguirrei/32878d691c306bce2849b3c2b5276869 to your computer and use it in GitHub Desktop.
Save jmaguirrei/32878d691c306bce2849b3c2b5276869 to your computer and use it in GitHub Desktop.
WebRTC React Boilerplate (Agora.io)
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);
}
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;
});
}
};
/* ------------------------------------------------------------------------------------------------
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());
});
}
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