Skip to content

Instantly share code, notes, and snippets.

@rizwan92
Last active August 21, 2019 10:09
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save rizwan92/52ad61f28a97059d074eee8982e67408 to your computer and use it in GitHub Desktop.
Save rizwan92/52ad61f28a97059d074eee8982e67408 to your computer and use it in GitHub Desktop.
React-native-openVdu-web-RTC
/**
* 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