Last active
August 21, 2019 10:09
-
-
Save rizwan92/52ad61f28a97059d074eee8982e67408 to your computer and use it in GitHub Desktop.
React-native-openVdu-web-RTC
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
/** | |
* Sample React Native App | |
* https://github.com/facebook/react-native | |
* | |
* @format | |
* @flow | |
*/ | |
import React, { Component } from "react"; | |
import { | |
Platform, | |
TextInput, | |
ScrollView, | |
Button, | |
Alert, | |
Linking, | |
StyleSheet, | |
Text, | |
View, | |
Image, | |
PermissionsAndroid | |
} from "react-native"; | |
import { OpenVidu } from "openvidu-browser"; | |
import { RTCView } from "./node_modules/openvidu-browser/node_modules/react-native-webrtc"; | |
import axios from "axios"; | |
import OpenViduImage from './openvidu_grey_bg_transp_cropped.png'; | |
const OPENVIDU_SERVER_URL = "https://192.168.1.21:4443"; | |
const OPENVIDU_SERVER_SECRET = "MY_SECRET"; | |
type Props = {}; | |
export default class App extends Component<Props> { | |
constructor(props) { | |
super(props); | |
this.state = { | |
mySessionId: "SessionA", | |
myUserName: "Participant" + Math.floor(Math.random() * 100), | |
session: undefined, | |
mainStreamManager: undefined, | |
subscribers: [], | |
role: "PUBLISHER", | |
mirror: true, | |
videoSource: undefined, | |
camera: true | |
}; | |
} | |
componentDidMount() {} | |
componentWillUnmount() { | |
this.leaveSession(); | |
} | |
async checkAndroidPermissions() { | |
try { | |
const camera = await PermissionsAndroid.request( | |
PermissionsAndroid.PERMISSIONS.CAMERA, | |
{ | |
title: "Camera Permission", | |
message: "OpenVidu needs access to your camera", | |
buttonNeutral: "Ask Me Later", | |
buttonNegative: "Cancel", | |
buttonPositive: "OK" | |
} | |
); | |
const audio = await PermissionsAndroid.request( | |
PermissionsAndroid.PERMISSIONS.RECORD_AUDIO, | |
{ | |
title: "Aduio Permission", | |
message: "OpenVidu needs access to your microphone", | |
buttonNeutral: "Ask Me Later", | |
buttonNegative: "Cancel", | |
buttonPositive: "OK" | |
} | |
); | |
const storage = await PermissionsAndroid.request( | |
PermissionsAndroid.PERMISSIONS.WRITE_EXTERNAL_STORAGE, | |
{ | |
title: "STORAGE", | |
message: "OpenVidu needs access to your storage ", | |
buttonNeutral: "Ask Me Later", | |
buttonNegative: "Cancel", | |
buttonPositive: "OK" | |
} | |
); | |
if (camera === PermissionsAndroid.RESULTS.GRANTED) { | |
console.log("You can use the camera"); | |
} else { | |
console.log("Camera permission denied"); | |
} | |
if (audio === PermissionsAndroid.RESULTS.GRANTED) { | |
console.log("You can use the audio"); | |
} else { | |
console.log("audio permission denied"); | |
} | |
if (storage === PermissionsAndroid.RESULTS.GRANTED) { | |
console.log("You can use the storage"); | |
} else { | |
console.log("storage permission denied"); | |
} | |
} catch (err) { | |
console.warn(err); | |
} | |
} | |
joinSession() { | |
// --- 1) Get an OpenVidu object --- | |
this.OV = new OpenVidu(); | |
// --- 2) Init a session --- | |
this.setState( | |
{ | |
session: this.OV.initSession(), | |
}, | |
() => { | |
var mySession = this.state.session; | |
console.log(mySession); | |
// --- 3) Specify the actions when events take place in the session --- | |
// On every new Stream received... | |
mySession.on("streamCreated", event => { | |
console.log("on stream created"); | |
console.log(event); | |
// Subscribe to the Stream to receive it. Second parameter is undefined | |
// so OpenVidu doesn't create an HTML video by its own | |
const subscriber = mySession.subscribe(event.stream, undefined); | |
var subscribers = this.state.subscribers; | |
subscribers.push(subscriber); | |
// Update the state with the new subscribers | |
this.setState({ | |
subscribers: subscribers | |
}); | |
}); | |
// On every Stream destroyed... | |
mySession.on("streamDestroyed", event => { | |
console.log('stream destroyed'); | |
console.log(event); | |
event.preventDefault(); | |
// Remove the stream from 'subscribers' array | |
this.deleteSubscriber(event.stream.streamManager); | |
}); | |
// --- 4) Connect to the session with a valid user token --- | |
// 'getToken' method is simulating what your server-side should do. | |
// 'token' parameter should be retrieved and returned by your own backend | |
this.getToken() | |
.then(token => { | |
console.log("getting token"); | |
console.log(token); | |
console.log('username ' + this.state.myUserName); | |
// First param is the token got from OpenVidu Server. Second param can be retrieved by every user on event | |
// 'streamCreated' (property Stream.connection.data), and will be appended to DOM as the user's nickname | |
mySession | |
.connect(token, { clientData: this.state.myUserName }) | |
.then(() => { | |
console.log('onconnectetd'); | |
if (Platform.OS == "android") { | |
this.checkAndroidPermissions(); | |
} | |
console.log('role' + this.state.role); | |
// --- 5) Get your own camera stream --- | |
if (this.state.role !== "SUBSCRIBER") { | |
const properties = { | |
audioSource: undefined, // The source of audio. If undefined default microphone | |
videoSource: undefined, // The source of video. If undefined default webcam | |
publishAudio: true, // Whether you want to start publishing with your audio unmuted or not | |
publishVideo: true, // Whether you want to start publishing with your video enabled or not | |
resolution: "640x480", // The resolution of your video | |
frameRate: 30, // The frame rate of your video | |
insertMode: "APPEND" // How the video is inserted in the target element 'video-container' | |
}; | |
// Init a publisher passing undefined as targetElement (we don't want OpenVidu to insert a video | |
// element: we will manage it on our own) and with the desired properties | |
let publisher = this.OV.initPublisher(undefined, properties); | |
// --- 6) Publish your stream --- | |
// Set the main video in the page to display our webcam and store our Publisher | |
this.setState({ | |
mainStreamManager: publisher, | |
videoSource: !properties.videoSource | |
? "1" | |
: properties.videoSource // 0: back camera | 1: user camera | | |
}); | |
mySession.publish(publisher); | |
} | |
}) | |
.catch(error => { | |
console.log( | |
"There was an error connecting to the session:", | |
error.code, | |
error.message | |
); | |
console.log(error); | |
}); | |
}) | |
.catch(error => console.log("Error", error)); | |
} | |
); | |
} | |
getNicknameTag(stream) { | |
// Gets the nickName of the user | |
if ( | |
stream.connection && | |
JSON.parse(stream.connection.data) && | |
JSON.parse(stream.connection.data).clientData | |
) { | |
return JSON.parse(stream.connection.data).clientData; | |
} | |
return ""; | |
} | |
deleteSubscriber(streamManager) { | |
setTimeout(() => { | |
let subscribers = this.state.subscribers; | |
const index = subscribers.indexOf(streamManager, 0); | |
if (index > -1) { | |
subscribers.splice(index, 1); | |
this.setState({ | |
subscribers: subscribers | |
}); | |
} | |
}); | |
} | |
leaveSession() { | |
// --- 7) Leave the session by calling 'disconnect' method over the Session object --- | |
const mySession = this.state.session; | |
if (mySession) { | |
mySession.disconnect(); | |
} | |
// Empty all properties... | |
setTimeout(() => { | |
this.OV = null; | |
this.setState({ | |
session: undefined, | |
subscribers: [], | |
mySessionId: "SessionA", | |
myUserName: "Participant" + Math.floor(Math.random() * 100), | |
mainStreamManager: undefined, | |
publisher: undefined | |
}); | |
}); | |
} | |
toggleCamera() { | |
/** | |
* _switchCamera() Method provided by react-native-webrtc: | |
* This function allows to switch the front / back cameras in a video track on the fly, without the need for adding / removing tracks or renegotiating | |
*/ | |
this.state.mainStreamManager.stream | |
.getMediaStream() | |
.getVideoTracks()[0] | |
._switchCamera(); | |
this.setState({ mirror: !this.state.mirror }); | |
/** | |
* Traditional way: | |
* Renegotiating stream and init new publisher to change the camera | |
*/ | |
/* | |
this.OV.getDevices().then(devices => { | |
console.log("DEVICES => ", devices); | |
let device = devices.filter(device => device.kind === 'videoinput' && device.deviceId !== this.state.videoSource)[0] | |
const properties = { | |
audioSource: undefined, | |
videoSource: device.deviceId, | |
publishAudio: true, | |
publishVideo: true, | |
resolution: '640x480', | |
frameRate: 30, | |
insertMode: 'APPEND', | |
} | |
let publisher = this.OV.initPublisher(undefined, properties); | |
this.state.session.unpublish(this.state.mainStreamManager); | |
this.setState({ | |
videoSource : device.deviceId, | |
mainStreamManager: publisher, | |
mirror: !this.state.mirror | |
}); | |
this.state.session.publish(publisher); | |
}); | |
*/ | |
} | |
muteUnmuteCamera() { | |
this.state.mainStreamManager.publishVideo(!this.state.camera); | |
this.setState({ camera: !this.state.camera }); | |
} | |
render() { | |
return ( | |
<ScrollView> | |
{this.state.mainStreamManager ? ( | |
<View> | |
<View style={styles.container}> | |
<Text>Session: {this.state.mySessionId}</Text> | |
<Text> | |
{this.getNicknameTag(this.state.mainStreamManager.stream)} | |
</Text> | |
<RTCView | |
zOrder={0} | |
objectFit="cover" | |
mirror={this.state.mirror} | |
ref={rtcVideo => { | |
if (rtcVideo) { | |
this.state.mainStreamManager.addVideoElement(rtcVideo); | |
} | |
}} | |
style={styles.selfView} | |
/> | |
</View> | |
<View> | |
<View style={styles.button}> | |
<Button | |
onLongPress={() => this.toggleCamera()} | |
onPress={() => this.toggleCamera()} | |
title="Toggle Camera" | |
color="#841584" | |
/> | |
</View> | |
<View style={styles.button}> | |
<Button | |
onLongPress={() => this.muteUnmuteCamera()} | |
onPress={() => this.muteUnmuteCamera()} | |
title={this.state.camera ? "Mute Camera" : "Unmute Camera"} | |
color="#00cbff" | |
/> | |
</View> | |
<View style={styles.button}> | |
<Button | |
onLongPress={() => this.leaveSession()} | |
onPress={() => this.leaveSession()} | |
title="Leave Session" | |
color="#ff0000" | |
/> | |
</View> | |
</View> | |
</View> | |
) : ( | |
<View> | |
<View | |
style={{ | |
justifyContent: "center", | |
alignItems: "center", | |
padding: 20 | |
}} | |
> | |
<Image style={styles.img} source={OpenViduImage} /> | |
</View> | |
<View style={{ justifyContent: "center", alignItems: "center" }}> | |
<TextInput | |
style={{ | |
width: "90%", | |
height: 40, | |
borderColor: "gray", | |
borderWidth: 1 | |
}} | |
onChangeText={mySessionId => this.setState({ mySessionId })} | |
value={this.state.mySessionId} | |
/> | |
</View> | |
<View style={styles.button}> | |
<Button | |
onLongPress={() => this.joinSession()} | |
onPress={() => this.joinSession()} | |
title="Join" | |
color="#841584" | |
/> | |
</View> | |
</View> | |
)} | |
<View | |
style={[styles.container, { flexDirection: "row", flexWrap: "wrap" }]} | |
> | |
{this.state.subscribers.map((item, index) => { | |
if (item) { | |
return ( | |
<View key={index}> | |
<Text>{this.getNicknameTag(item.stream)}</Text> | |
<RTCView | |
zOrder={0} | |
objectFit="cover" | |
style={styles.remoteView} | |
ref={rtcVideo => { | |
if (rtcVideo) { | |
item.addVideoElement(rtcVideo); | |
} | |
}} | |
/> | |
</View> | |
); | |
} | |
})} | |
</View> | |
</ScrollView> | |
); | |
} | |
/** | |
* -------------------------- | |
* SERVER-SIDE RESPONSIBILITY | |
* -------------------------- | |
* These methods retrieve the mandatory user token from OpenVidu Server. | |
* This behavior MUST BE IN YOUR SERVER-SIDE IN PRODUCTION (by using | |
* the API REST, openvidu-java-client or openvidu-node-client): | |
* 1) Initialize a session in OpenVidu Server (POST /api/sessions) | |
* 2) Generate a token in OpenVidu Server (POST /api/tokens) | |
* 3) The token must be consumed in Session.connect() method | |
*/ | |
getToken() { | |
return this.createSession(this.state.mySessionId) | |
.then(sessionId => this.createToken(sessionId)) | |
.catch(error => console.log(error)); | |
} | |
createSession(sessionId) { | |
return new Promise(resolve => { | |
var data = JSON.stringify({ customSessionId: sessionId }); | |
axios | |
.post(OPENVIDU_SERVER_URL + "/api/sessions", data, { | |
headers: { | |
Authorization: | |
"Basic " + btoa("OPENVIDUAPP:" + OPENVIDU_SERVER_SECRET), | |
"Content-Type": "application/json", | |
Accept: "application/json" | |
} | |
}) | |
.then(response => { | |
console.log("CREATE SESION", response); | |
resolve(response.data.id); | |
}) | |
.catch(response => { | |
console.log(response); | |
var error = Object.assign({}, response); | |
if (!error.response) { | |
console.error('Network error: ', error); | |
if (error.request && error.request._response) { | |
console.error( | |
"Response of the request: ", | |
error.request._response | |
); | |
} | |
} else if ( | |
error.response && | |
error.response.status && | |
error.response.status === 409 | |
) { | |
console.log("RESOLVING WITH SESSIONID, 409"); | |
resolve(sessionId); | |
} else { | |
console.warn( | |
"No connection to OpenVidu Server. This may be a certificate error at " + | |
OPENVIDU_SERVER_URL | |
); | |
Alert.alert( | |
"No connection to OpenVidu Server.", | |
'This may be a certificate error at "' + | |
OPENVIDU_SERVER_URL + | |
'"\n\nClick OK to navigate and accept it. ' + | |
'If no certificate warning is shown, then check that your OpenVidu Server is up and running at "' + | |
OPENVIDU_SERVER_URL + | |
'"', | |
[ | |
{ | |
text: "Cancel", | |
onPress: () => console.log("Cancel Pressed"), | |
style: "cancel" | |
}, | |
{ | |
text: "OK", | |
onPress: () => | |
Linking.openURL( | |
OPENVIDU_SERVER_URL + "/accept-certificate" | |
).catch(err => console.error("An error occurred", err)) | |
} | |
], | |
{ cancelable: false } | |
); | |
} | |
}); | |
}); | |
} | |
createToken(sessionId) { | |
return new Promise((resolve, reject) => { | |
var data = JSON.stringify({ session: sessionId }); | |
axios | |
.post(OPENVIDU_SERVER_URL + "/api/tokens", data, { | |
headers: { | |
Authorization: | |
"Basic " + btoa("OPENVIDUAPP:" + OPENVIDU_SERVER_SECRET), | |
"Content-Type": "application/json" | |
} | |
}) | |
.then(response => { | |
console.log("TOKEN", response); | |
resolve(response.data.token); | |
}) | |
.catch(error => reject(error)); | |
}); | |
} | |
} | |
const styles = StyleSheet.create({ | |
container: { | |
justifyContent: "center", | |
alignItems: "center", | |
flex: 1, | |
paddingTop: Platform.OS == "ios" ? 20 : 0 | |
}, | |
selfView: { | |
width: "100%", | |
height: 300 | |
}, | |
remoteView: { | |
width: 150, | |
height: 150 | |
}, | |
button: { | |
padding: 10 | |
}, | |
img: { | |
flex: 1, | |
width: 400, | |
height: 200 | |
}, | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment