opentok-rvc
v1.0.9
Published
Opentok react video chat which support stream connection and signalling
Downloads
152
Maintainers
Readme
opentok-rvc
React components for OpenTok.js
Features included
- React js
- semantic react ui
- opentok session, stream connection
- opentok peer to peer video chat
- How to send and recieve signals in opentok
- Defined the properties used for opentok publisher and subscriber
- Show username in video chat container
- Get userdevices used in opentok session(video devices and audio devices)
- Check screen sharing capability
- Take screenshot
Contents
Install
Add opentok-rvc
as a dependency of your application:
npm install --save opentok-rvc
Then include opentok.js
before your application:
Usage
Include opentok.min.js in index.html
<script src="https://static.opentok.com/v2/js/opentok.min.js"></script>
config.js
Define you opentok configuration in config.js
To check single room video connection you need TOKEN and PROVIDER_TOKEN also you can avoid SUBSCRIBER_TOKEN_A and SUBSCRIBER_TOKEN_B.
const SAMPLE_SERVER_BASE_URL = 'YOUR_SERVER_BASE_URL';
const API_KEY = 'API_KEY';
const SESSION_ID = 'SESSION_ID';
const TOKEN = 'TOKEN';
const PROVIDER_TOKEN = 'PROVIDER_TOKEN';
const SUBSCRIBER_TOKEN_A = 'SUBSCRIBER_TOKEN_A';
const SUBSCRIBER_TOKEN_B = 'SUBSCRIBER_TOKEN_B';
export {
SAMPLE_SERVER_BASE_URL,
API_KEY,
SESSION_ID,
TOKEN,
PROVIDER_TOKEN,
SUBSCRIBER_TOKEN_A,
SUBSCRIBER_TOKEN_B
};
App.js
In App.js please import the opentok-rvc components and semantic-ui-react components
import React from 'react';
import { OTSession, OTPublisher, OTStreams, OTSubscriber } from 'opentok-rvc';
import { Container, Segment, Header, Checkbox, Form, Icon, Popup } from 'semantic-ui-react';
import 'semantic-ui-css/semantic.min.css';
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
error: null,
connection: 'Connecting',
publishAudio: true,
publishVideo: true,
publishVideoSource: 'camera',
subscribeToAudio: true,
subscribeToVideo: true,
fitMode: 'cover',
dimension: 100,
resolution: '640x480',
type: 'DEFAULT',
token: this.props.credentials.token,
providerToken: this.props.credentials.providerToken,
subscriberTokenA: this.props.credentials.subscriberTokenA,
subscriberTokenB: this.props.credentials.subscriberTokenB,
signal: {
type: '',
data: ''
},
text: '',
callActionType: '',
audioDevices: [],
videoDevices: [],
publisherName: 'John'
};
this.otSession = React.createRef();
this.setUserToken = this.setUserToken.bind(this);
this.handleChange = this.handleChange.bind(this);
this.onSignalSend = this.onSignalSend.bind(this);
this.onCallAction = this.onCallAction.bind(this);
this.changeVideoDimension = this.changeVideoDimension.bind(this);
this.changeVideoResolution = this.changeVideoResolution.bind(this);
this.changeFitmode = this.changeFitmode.bind(this);
this.sessionEventHandlers = {
sessionConnected: () => {
this.setState({ connection: 'Connected' });
},
sessionDisconnected: () => {
this.setState({ connection: 'Disconnected' });
},
sessionReconnected: () => {
this.setState({ connection: 'Reconnected' });
},
sessionReconnecting: () => {
this.setState({ connection: 'Reconnecting' });
}
};
this.publisherEventHandlers = {
accessDenied: () => {
console.log('User denied access to media source');
},
streamCreated: () => {
console.log('Publisher stream created');
},
streamDestroyed: ({ reason }) => {
console.log(`Publisher stream destroyed because: ${reason}`);
}
};
this.subscriberEventHandlers = {
videoEnabled: () => {
console.log('Subscriber video enabled');
},
videoDisabled: () => {
console.log('Subscriber video disabled');
},
};
}
onSessionError = error => {
console.log(error);
};
onPublish = () => {
console.log('Publish Success');
};
onPublishError = error => {
this.setState({ publishVideoSource: 'camera' });
let errMsg = '';
if (typeof error === 'object') {
errMsg = error.originalMessage;
} else {
errMsg = error
}
this.setState({ error: errMsg });
console.log(error);
};
onSubscribe = () => {
console.log('Subscribe Success');
};
onSubscribeError = error => {
this.setState({ error });
};
// set media devices used
onMediaDevices = (devices) => {
const audioDevices = [];
const videoDevices = [];
devices.forEach(device => {
if(device.kind === 'audioInput') {
audioDevices.push(device);
} else {
videoDevices.push(device);
}
this.setState({ audioDevices: audioDevices })
this.setState({ videoDevices: videoDevices })
});
}
// check screen sharing is possible or not
checkScreenSharing = (response) => {
if (!response.supported || response.extensionRegistered === false) {
// This browser does not support screen sharing
console.log('This browser does not support screen sharing');
alert('This browser does not support screen sharing');
} else if (response.extensionInstalled === false) {
// Prompt to install the extension
console.log('Prompt to install the extension');
prompt("Please install the extension for screenshare", "Opentok react video chat");
} else {
// Screen sharing is available.
console.log('Screen sharing is available');
}
}
togglePublishAudio = () => {
this.setState(state => ({
publishAudio: !state.publishAudio
}));
};
togglePublishVideo = () => {
this.setState(state => ({
publishVideo: !state.publishVideo
}));
};
toggleSubscribeToAudio = () => {
this.setState(state => ({
subscribeToAudio: !state.subscribeToAudio
}));
};
toggleSubscribeToVideo = () => {
this.setState(state => ({
subscribeToVideo: !state.subscribeToVideo
}));
};
changeVideoSource = (publishVideoSource) => {
(this.state.publishVideoSource !== 'camera') ? this.setState({ publishVideoSource: 'camera' }) : this.setState({ publishVideoSource: 'screen' })
}
changeVideoDimension = (event) => {
this.setState({ dimension: event.target.value })
}
changeVideoResolution = (event) => {
this.setState({ resolution: event.target.value })
}
changeFitmode = (event) => {
this.setState({ fitMode: event.target.value })
};
toggleToken = () => {
let publisherName = this.state.type === 'DEFAULT' ? 'John' : 'Manu';
let userType = this.state.type === 'DEFAULT' ? 'PROVIDER' : 'DEFAULT';
let token = this.state.type === 'DEFAULT' ? this.props.credentials.providerToken : this.props.credentials.token;
this.setState({ type: userType });
this.setState({ token: token });
this.setState({ publisherName: publisherName });
}
setUserToken = (event) => {
let token = this.props.credentials.providerToken;
let type = event.target.value;
if (type === 'select') {
alert('Please select user type');
return;
} else if (type === 'provider') {
token = this.state.providerToken;
} else if (type === 'subscriberA') {
token = this.state.subscriberTokenA;
} else {
token = this.state.subscriberTokenB;
}
this.setState({ type: type })
this.setState({ token: token })
}
handleChange(event) {
this.setState({ text: event.target.value });
}
// send opentok signal
onSignalSend = () => {
let value = this.state.text;
this.otSession.current.sessionHelper.session.signal({
type: 'msg',
data: value
}, function signalCallback(error) {
if (error) {
console.error('Error sending signal:', error.name, error.message);
} else {
console.log(value);
}
});
}
// recieve opentok signal
onSignalRecieve = (signal) => {
console.log('onSignalRecieve');
console.log(signal);
this.setState({ callActionType: signal.data })
}
// change video call action
onCallAction = (type) => {
this.otSession.current.sessionHelper.session.signal({
type: 'msg',
data: type
}, function signalCallback(error) {
if (error) {
console.error('Error sending signal:', error.name, error.message);
} else {
console.log(type);
}
});
}
_renderBtns = () => {
if (this.state.type === 'DEFAULT') {
if (this.state.callActionType === "" ||
this.state.callActionType === "STOP_CALL" ||
this.state.callActionType === "DECLINE_CALL") {
return (
<Popup
trigger={<Icon circular name='phone' size="large" color="blue" onClick={() => this.onCallAction('CALL_INITIATED')} />}
content='Make Call'
position='top center'
/>
)
} else if (this.state.callActionType === "CALL_INITIATED") {
return (
<Popup
trigger={<Icon circular name='phone' size="large" color="red" onClick={() => this.onCallAction('STOP_CALL')} />}
content='Cancel Call'
position='top center'
/>
)
} else if (this.state.callActionType === "ACCEPT_CALL") {
return (
<Popup
trigger={<Icon circular name='phone' size="large" color="red" onClick={() => this.onCallAction('STOP_CALL')} />}
content='Stop Call'
position='top center'
/>
)
}
} else {
if (this.state.callActionType === "CALL_INITIATED") {
return (
<>
<Popup
trigger={<Icon circular name='phone' size="large" color="teal" onClick={() => this.onCallAction('ACCEPT_CALL')} />}
content='Accept Call'
position='top center'
/>
<Popup
trigger={<Icon circular name='phone' size="large" color="red" onClick={() => this.onCallAction('DECLINE_CALL')} />}
content='Decline Call'
position='top center'
/>
</>
)
}
}
}
render() {
const { apiKey, sessionId } = this.props.credentials;
const { error, connection, token, publishAudio, publishVideo, subscribeToAudio, subscribeToVideo, publishVideoSource, fitMode, callActionType, type, dimension, resolution, publisherName } = this.state;
return (
<Container className="app-container">
<div>
<Segment color='red'>
<div onClick={() => this.toggleToken()}>
<Header as='h3'>Opentok react video chat</Header>
<div>Toggle Token</div>
<Checkbox toggle />
<div>Current Type: {type}</div>
<div>Current Token: {token}</div>
</div>
{/* <div>
<label>
Select user chat role:
<select value={this.state.type} onChange={this.setUserToken}>
<option value="select">select</option>
<option value="provdier">Provider</option>
<option value="subscriberA">Subscriber A</option>
<option value="subscriberB">Subscriber B</option>
</select>
</label>
</div> */}
</Segment>
<Segment color='teal'>
Resolution: {this.state.resolution}
<div id="sessionStatus">Session Status: {connection}</div>
{error ? (
<div className="error">
<strong>Error:</strong> {error}
</div>
) : null}
</Segment>
<Segment color='yellow'>
<input type="text" placeholder="Enter signal message" value={this.state.text} onChange={this.handleChange} /><br />
<button onClick={this.onSignalSend}>SEND</button>
</Segment>
<Segment color="blue" className="video-segment">
<OTSession
ref={this.otSession}
apiKey={apiKey}
sessionId={sessionId}
token={token}
onError={this.onSessionError}
eventHandlers={this.sessionEventHandlers}
onSignalRecieve={this.onSignalRecieve}
onMediaDevices={this.onMediaDevices}
checkScreenSharing={this.checkScreenSharing}
>
<OTPublisher
publisherName={publisherName}
properties={{
insertMode: "append",
publishAudio: publishAudio,
publishVideo: publishVideo,
videoSource: publishVideoSource === 'screen' ? 'screen' : undefined,
audioFallbackEnabled: true,
showControls: true,
width: dimension,
height: dimension,
maxResolution: { width: 1920, height: 1080 },
facingMode: 'right', // user. environment, left, right
fitMode: fitMode, // cover, contain,
frameRate: 30,
insertDefaultUI: true,
// resolution: '640x480'
resolution: resolution
}}
onPublish={this.props.onPublish}
onError={this.onPublishError}
eventHandlers={this.publisherEventHandlers}
/>
{
callActionType === 'ACCEPT_CALL' &&
<OTStreams>
<OTSubscriber
properties={{
insertMode: "append",
subscribeToAudio: subscribeToAudio,
subscribeToVideo: subscribeToVideo,
// preferredFrameRate: 15,
showControls: true,
fitMode: fitMode,
width: 100,
height: 100,
resolution: resolution
}}
onSubscribe={this.onSubscribe}
onError={this.onSubscribeError}
eventHandlers={this.subscriberEventHandlers}
/>
</OTStreams>
}
</OTSession>
<div className="btn-container">
{this._renderBtns()}
<Popup
trigger={publishAudio ? <Icon circular name='unmute' size="large" onClick={this.togglePublishAudio} /> : <Icon circular name='mute' size="large" onClick={this.togglePublishAudio} />}
content='Toggle Audio'
position='top center'
/>
<Popup
trigger={publishVideo ? <Icon circular name='video' size="large" onClick={this.togglePublishVideo} /> : <Icon circular name='video' size="large" onClick={this.togglePublishVideo} />}
content='Toggle Video'
position='top center'
/>
<Popup
trigger={publishVideoSource ? <Icon circular name='tv' size="large" onClick={this.changeVideoSource} /> : <Icon circular name='tv' size="large" onClick={this.changeVideoSource} />}
content='Share your screen'
position='top center'
/>
{
callActionType === "ACCEPT_CALL" &&
<>
<Popup
trigger={subscribeToAudio ? <Icon circular name='unmute' size="large" onClick={this.toggleSubscribeToAudio} /> : <Icon circular name='mute' size="large" onClick={this.toggleSubscribeToAudio} />}
content='Toggle Subscribe to Audio'
position='top center'
/>
<Popup
trigger={subscribeToVideo ? <Icon circular name='video' size="large" onClick={this.toggleSubscribeToVideo} /> : <Icon circular name='video' size="large" onClick={this.toggleSubscribeToVideo} />}
content='Toggle Subscribe to Video'
position='top center'
/>
</>
}
<Popup
trigger={<Icon circular name='setting' size="large" />}
content={<Form onSubmit={this.handleSubmit}>
<Header size='small'>Video</Header>
<Form.Field>
<label>
Video dimension
<select value={dimension} onChange={this.changeVideoDimension}>
<option value="50">50</option>
<option value="100">100</option>
<option value="200">200</option>
</select>
</label>
<label>
Video quality
<select value={resolution} onChange={this.changeVideoResolution}>
<option value="320x240">Default</option>
<option value="640x480">Low Definition</option>
<option value="1280x720">High Definition</option>
</select>
</label>
</Form.Field>
<Form.Field>
<label>
Webcam
<select>
{
this.state.videoDevices.map(device =>
<option key={device.deviceId} value={device.deviceId}>{device.label}</option>
)
}
</select>
</label>
</Form.Field>
<Form.Field>
<label>
Video mode
<select value={fitMode} onChange={this.changeFitmode}>
<option value="cover">Cover</option>
<option value="contain">Contain</option>
</select>
</label>
</Form.Field>
<Header size='small'>Audio</Header>
<Form.Field>
<label>
Microphone
<select>
{
this.state.audioDevices.map(device =>
<option key={device.deviceId} value={device.deviceId}>{device.label}</option>
)
}
</select>
</label>
</Form.Field>
</Form>}
position='top center'
hoverable={true}
/>
</div>
</Segment>
</div>
</Container>
);
}
}
export default App;
Source code
* git clone https://github.com/vishnu8429/opentok-rvc.git
* cd opentok-react/
* switch branch to 'opentok-rvc-semantic-react-ui'
* Modify code in src/
* npm install
* npm start