'use strict'; import React from 'react'; import PropTypes from 'prop-types'; import ClipboardButton from 'react-clipboard.js'; import browser from 'bowser'; import TransitionAppear from './TransitionAppear'; import LocalVideo from './LocalVideo'; import RemoteVideo from './RemoteVideo'; import Stats from './Stats'; import Logger from '../Logger'; import * as utils from '../utils'; import Client from '../Client'; const logger = new Logger('Room'); const STATS_INTERVAL = 1000; export default class Room extends React.Component { constructor(props) { super(props); this.state = { peers : {}, localStream : null, localVideoResolution : null, // qvga / vga / hd / fullhd. multipleWebcams : false, webcamType : null, connectionState : null, remoteStreams : {}, showStats : false, stats : null, activeSpeakerId : null }; // Mounted flag this._mounted = false; // Client instance this._client = null; // Timer to retrieve RTC stats. this._statsTimer = null; } render() { let props = this.props; let state = this.state; let numPeers = Object.keys(state.remoteStreams).length; return (
{}} // Avoid link action. > invite people to this room
{ Object.keys(state.remoteStreams).map((msid) => { let stream = state.remoteStreams[msid]; let peer; for (let peerId of Object.keys(state.peers)) { peer = state.peers[peerId]; if (peer.msids.indexOf(msid) !== -1) break; } if (!peer) return; return ( ); }) }
{state.showStats ? :
}
); } componentDidMount() { // Set flag this._mounted = true; // Run the client this._runClient(); } componentWillUnmount() { let state = this.state; // Unset flag this._mounted = false; // Close client this._client.removeAllListeners(); this._client.close(); // Close local MediaStream if (state.localStream) utils.closeMediaStream(state.localStream); } handleRoomLinkCopied() { logger.debug('handleRoomLinkCopied()'); this.props.onNotify( { level : 'success', position : 'tr', title : 'Room URL copied to the clipboard', message : 'Share it with others to join this room' }); } handleLocalMute(value) { logger.debug('handleLocalMute() [value:%s]', value); let micTrack = this.state.localStream.getAudioTracks()[0]; if (!micTrack) return Promise.reject(new Error('no audio track')); micTrack.enabled = !value; return Promise.resolve(); } handleLocalWebcamToggle(value) { logger.debug('handleLocalWebcamToggle() [value:%s]', value); return Promise.resolve() .then(() => { if (value) return this._client.addVideo(); else return this._client.removeVideo(); }) .then(() => { let localStream = this.state.localStream; this.setState({ localStream }); }); } handleLocalWebcamChange() { logger.debug('handleLocalWebcamChange()'); this._client.changeWebcam(); } handleLocalResolutionChange() { logger.debug('handleLocalResolutionChange()'); this._client.changeVideoResolution(); } handleStatsClose() { logger.debug('handleStatsClose()'); this.setState({ showStats: false }); this._stopStats(); } handleClickShowStats() { logger.debug('handleClickShowStats()'); this.setState({ showStats: true }); this._startStats(); } handleDisableRemoteVideo(msid) { logger.debug('handleDisableRemoteVideo() [msid:"%s"]', msid); return this._client.disableRemoteVideo(msid); } handleEnableRemoteVideo(msid) { logger.debug('handleEnableRemoteVideo() [msid:"%s"]', msid); return this._client.enableRemoteVideo(msid); } _runClient() { let peerId = this.props.peerId; let roomId = this.props.roomId; logger.debug('_runClient() [peerId:"%s", roomId:"%s"]', peerId, roomId); this._client = new Client(peerId, roomId); this._client.on('localstream', (stream, resolution) => { this.setState( { localStream : stream, localVideoResolution : resolution }); }); this._client.on('join', () => { // Clear remote streams (for reconnections). this.setState({ remoteStreams: {} }); this.props.onNotify( { level : 'success', title : 'Yes!', message : 'You are in the room!', image : '/resources/images/room.svg', imageWidth : 80, imageHeight : 80 }); // Start retrieving WebRTC stats (unless mobile or Edge). if (utils.isDesktop() && !browser.msedge) { this.setState({ showStats: true }); setTimeout(() => { this._startStats(); }, STATS_INTERVAL / 2); } }); this._client.on('close', (error) => { // Clear remote streams (for reconnections) and more stuff. this.setState( { remoteStreams : {}, activeSpeakerId : null }); if (error) { this.props.onNotify( { level : 'error', title : 'Error', message : error.message }); } // Stop retrieving WebRTC stats. this._stopStats(); }); this._client.on('disconnected', () => { // Clear remote streams (for reconnections). this.setState({ remoteStreams: {} }); this.props.onNotify( { level : 'error', title : 'Warning', message : 'app disconnected' }); // Stop retrieving WebRTC stats. this._stopStats(); }); this._client.on('numwebcams', (num) => { this.setState( { multipleWebcams : (num > 1 ? true : false) }); }); this._client.on('webcamtype', (type) => { this.setState({ webcamType: type }); }); this._client.on('peers', (peers) => { let peersObject = {}; for (let peer of peers) { peersObject[peer.id] = peer; } this.setState({ peers: peersObject }); }); this._client.on('addpeer', (peer) => { this.props.onNotify( { level : 'success', message : `${peer.id} joined the room` }); let peers = this.state.peers; peers[peer.id] = peer; this.setState({ peers }); }); this._client.on('updatepeer', (peer) => { let peers = this.state.peers; peers[peer.id] = peer; this.setState({ peers }); }); this._client.on('removepeer', (peer) => { this.props.onNotify( { level : 'info', message : `${peer.id} left the room` }); let peers = this.state.peers; delete peers[peer.id]; this.setState({ peers }); }); this._client.on('connectionstate', (state) => { this.setState({ connectionState: state }); }); this._client.on('addstream', (stream) => { let remoteStreams = this.state.remoteStreams; remoteStreams[stream.id] = stream; this.setState({ remoteStreams }); }); this._client.on('removestream', (stream) => { let remoteStreams = this.state.remoteStreams; delete remoteStreams[stream.id]; this.setState({ remoteStreams }); }); this._client.on('addtrack', () => { let remoteStreams = this.state.remoteStreams; this.setState({ remoteStreams }); }); this._client.on('removetrack', () => { let remoteStreams = this.state.remoteStreams; this.setState({ remoteStreams }); }); this._client.on('forcestreamsupdate', () => { // Just firef for Firefox due to bug: // https://bugzilla.mozilla.org/show_bug.cgi?id=1347578 this.forceUpdate(); }); this._client.on('activespeaker', (peer) => { this.setState( { activeSpeakerId : (peer ? peer.id : null) }); }); } _startStats() { logger.debug('_startStats()'); getStats.call(this); function getStats() { this._client.getStats() .then((stats) => { if (!this._mounted) return; this.setState({ stats }); this._statsTimer = setTimeout(() => { getStats.call(this); }, STATS_INTERVAL); }) .catch((error) => { logger.error('getStats() failed: %o', error); this.setState({ stats: null }); // this._statsTimer = setTimeout(() => // { // getStats.call(this); // }, STATS_INTERVAL); }); } } _stopStats() { logger.debug('_stopStats()'); this.setState({ stats: null }); clearTimeout(this._statsTimer); } } Room.propTypes = { peerId : PropTypes.string.isRequired, roomId : PropTypes.string.isRequired, onNotify : PropTypes.func.isRequired, onHideNotification : PropTypes.func.isRequired };