Added openvidu react tutorial

This commit is contained in:
Unknown 2018-09-06 14:47:34 +02:00
parent 60dac7c25c
commit 4ac72dc2d2
19 changed files with 18510 additions and 0 deletions

21
openvidu-insecure-react/.gitignore vendored Normal file
View File

@ -0,0 +1,21 @@
# See https://help.github.com/ignore-files/ for more about ignoring files.
# dependencies
/node_modules
# testing
/coverage
# production
/build
# misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local
npm-debug.log*
yarn-debug.log*
yarn-error.log*

File diff suppressed because it is too large Load Diff

15146
openvidu-insecure-react/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,18 @@
{
"name": "openvidu-insecure-react",
"version": "0.1.0",
"private": true,
"dependencies": {
"axios": "^0.18.0",
"openvidu-browser": "^2.4.0",
"react": "^16.4.2",
"react-dom": "^16.4.2",
"react-scripts": "1.1.4"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test --env=jsdom",
"eject": "react-scripts eject"
}
}

View File

@ -0,0 +1,76 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="theme-color" content="#000000">
<!--
manifest.json provides metadata used when your web app is added to the
homescreen on Android. See https://developers.google.com/web/fundamentals/engage-and-retain/web-app-manifest/
-->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json">
<link rel="shortcut icon" href="./resources/images/favicon.ico">
<!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<!-- Bootstrap -->
<script src="https://code.jquery.com/jquery-3.3.1.min.js" integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8="
crossorigin="anonymous"></script>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u"
crossorigin="anonymous">
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa"
crossorigin="anonymous"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css">
<!-- Bootstrap -->
<title>openvidu-insecure-react App</title>
</head>
<body>
<noscript>
You need to enable JavaScript to run this app.
</noscript>
<nav class="navbar navbar-default">
<div class="container">
<div class="navbar-header">
<a class="navbar-brand" href="/">
<img class="demo-logo" src="resources/images/openvidu_vert_white_bg_trans_cropped.png" /> Insecure React</a>
<a class="navbar-brand nav-icon" href="https://github.com/OpenVidu/openvidu-tutorials/tree/master/openvidu-insecure-react" title="GitHub Repository" target="_blank">
<i class="fa fa-github" aria-hidden="true"></i>
</a>
<a class="navbar-brand nav-icon" href="http://www.openvidu.io/docs/tutorials/openvidu-insecure-react/" title="Documentation" target="_blank">
<i class="fa fa-book" aria-hidden="true"></i>
</a>
</div>
</div>
</nav>
<div id="root"></div>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.
You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag.
To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
<footer class="footer">
<div class="container">
<div class="text-muted">OpenVidu © 2018</div>
<a href="http://www.openvidu.io/" target="_blank">
<img class="openvidu-logo" src="resources/images/openvidu_globe_bg_transp_cropped.png" />
</a>
</div>
</footer>
</body>
</html>

View File

@ -0,0 +1,15 @@
{
"short_name": "React App",
"name": "Create React App Sample",
"icons": [
{
"src": "favicon.ico",
"sizes": "64x64 32x32 24x24 16x16",
"type": "image/x-icon"
}
],
"start_url": "./index.html",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#ffffff"
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

View File

@ -0,0 +1,3 @@
.stream-container {
padding: 0;
}

View File

@ -0,0 +1,338 @@
import React, { Component } from 'react';
import axios from 'axios';
import './App.css';
import {OpenVidu} from 'openvidu-browser';
import UserVideoComponent from './UserVideoComponent';
const OPENVIDU_SERVER_URL = 'https://' + window.location.hostname + ':4443';
const OPENVIDU_SERVER_SECRET = 'MY_SECRET';
class App extends Component {
constructor(props) {
super(props);
this.state = {
mySessionId: 'SessionA',
myUserName: 'Participant' + Math.floor(Math.random() * 100),
session: undefined,
mainStreamManager: undefined,
publisher: undefined,
subscribers: [],
};
this.joinSession = this.joinSession.bind(this);
this.leaveSession = this.leaveSession.bind(this);
this.handleChangeSessionId = this.handleChangeSessionId.bind(this);
this.handleChangeUserName = this.handleChangeUserName.bind(this);
this.handleMainVideoStream = this.handleMainVideoStream.bind(this);
this.onbeforeunload = this.onbeforeunload.bind(this);
}
componentDidMount() {
window.addEventListener('beforeunload', this.onbeforeunload);
}
componentWillUnmount() {
window.removeEventListener('beforeunload', this.onbeforeunload);
}
onbeforeunload(event) {
this.leaveSession();
}
handleChangeSessionId(e) {
this.setState({
mySessionId: e.target.value,
});
}
handleChangeUserName(e) {
this.setState({
myUserName: e.target.value,
});
}
handleMainVideoStream(stream) {
this.setState({
mainStreamManager: stream,
});
}
deleteSubscriber(streamManager) {
let subscribers = this.state.subscribers;
let index = subscribers.indexOf(streamManager, 0);
if (index > -1) {
subscribers.splice(index, 1);
this.setState({
subscriber: subscribers,
});
}
}
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;
// --- 3) Specify the actions when events take place in the session ---
// On every new Stream received...
mySession.on('streamCreated', (event) => {
// Subscribe to the Stream to receive it. Second parameter is undefined
// so OpenVidu doesn't create an HTML video by its own
var 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) => {
// 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) => {
// 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(() => {
// --- 5) Get your own camera stream ---
// 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, {
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'
mirror: false, // Whether to mirror your local video or not
});
// --- 6) Publish your stream ---
mySession.publish(publisher);
// Set the main video in the page to display our webcam and store our Publisher
this.setState({
mainStreamManager: publisher,
publisher: publisher,
});
})
.catch((error) => {
console.log('There was an error connecting to the session:', error.code, error.message);
});
});
},
);
}
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...
this.OV = null;
this.setState({
session: undefined,
subscribers: [],
mySessionId: 'SessionA',
myUserName: 'Participant' + Math.floor(Math.random() * 100),
mainStreamManager: undefined,
publisher: undefined
});
}
render() {
const mySessionId = this.state.mySessionId;
const myUserName = this.state.myUserName;
return (
<div className="container">
{this.state.session === undefined ? (
<div id="join">
<div id="img-div">
<img src="resources/images/openvidu_grey_bg_transp_cropped.png" alt="OpenVidu logo" />
</div>
<div id="join-dialog" className="jumbotron vertical-center">
<h1> Join a video session </h1>
<form className="form-group" onSubmit={this.joinSession}>
<p>
<label>Participant: </label>
<input
className="form-control"
type="text"
id="userName"
value={myUserName}
onChange={this.handleChangeUserName}
required
/>
</p>
<p>
<label> Session: </label>
<input
className="form-control"
type="text"
id="sessionId"
value={mySessionId}
onChange={this.handleChangeSessionId}
required
/>
</p>
<p className="text-center">
<input className="btn btn-lg btn-success" name="commit" type="submit" value="JOIN" />
</p>
</form>
</div>
</div>
) : null}
{this.state.session !== undefined ? (
<div id="session">
<div id="session-header">
<h1 id="session-title">{mySessionId}</h1>
<input
className="btn btn-large btn-danger"
type="button"
id="buttonLeaveSession"
onClick={this.leaveSession}
value="Leave session"
/>
</div>
{this.state.mainStreamManager !== undefined ? (
<div id="main-video" className="col-md-6">
<UserVideoComponent streamManager={this.state.mainStreamManager} />
</div>
) : null}
<div id="video-container" className="col-md-6">
{this.state.publisher !== undefined ? (
<div className="stream-container col-md-6 col-xs-6">
<UserVideoComponent
streamManager={this.state.publisher}
mainVideoStream={this.handleMainVideoStream}
/>
</div>
) : null}
{this.state.subscribers.map((sub, i) => (
<div key={i} className="stream-container col-md-6 col-xs-6">
<UserVideoComponent streamManager={sub} mainVideoStream={this.handleMainVideoStream} />
</div>
))}
</div>
</div>
) : null}
</div>
);
}
/**
* --------------------------
* SERVER-SIDE RESPONSIBILITY
* --------------------------
* These methods retrieve the mandatory user token from OpenVidu Server.
* This behaviour 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));
}
createSession(sessionId) {
return new Promise((resolve, reject) => {
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',
},
})
.then((response) => {
console.log('CREATE SESION', response);
resolve(response.data.id);
})
.catch((response) => {
var error = Object.assign({}, response);
if (error.response.status === 409) {
resolve(sessionId);
} else {
console.log(error);
console.warn(
'No connection to OpenVidu Server. This may be a certificate error at ' +
OPENVIDU_SERVER_URL,
);
if (
window.confirm(
'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 +
'"',
)
) {
window.location.assign(OPENVIDU_SERVER_URL + '/accept-certificate');
}
}
});
});
}
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));
});
}
}
export default App;

View File

@ -0,0 +1,9 @@
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
it('renders without crashing', () => {
const div = document.createElement('div');
ReactDOM.render(<App />, div);
ReactDOM.unmountComponentAtNode(div);
});

View File

@ -0,0 +1,27 @@
import React, { Component } from 'react';
export default class OpenViduVideoComponent extends Component {
constructor(props) {
super(props);
console.log(props);
this.videoRef = React.createRef();
}
/*componentWillReceiveProps(props) {
if (props && !!this.videoRef) {
this.props.streamManager.addVideoElement(this.videoRef.current);
}
}*/
componentDidMount() {
if (this.props && !!this.videoRef) {
this.props.streamManager.addVideoElement(this.videoRef.current);
}
}
render() {
return <video autoPlay={true} ref={this.videoRef} />;
}
}

View File

@ -0,0 +1,19 @@
video {
width: 100%;
height: auto;
float: left;
cursor: pointer;
}
.streamcomponent div {
position: absolute;
background: #f8f8f8;
padding-left: 5px;
padding-right: 5px;
color: #777777;
font-weight: bold;
border-bottom-right-radius: 4px;
}
p{
margin: 0;
}

View File

@ -0,0 +1,38 @@
import React, { Component } from 'react';
import './UserVideo.css';
import OpenViduVideoComponent from './OvVideo';
export default class UserVideoComponent extends Component {
constructor(props) {
super(props);
this.handleVideoClicked = this.handleVideoClicked.bind(this);
}
getNicknameTag() {
// Gets the nickName of the user
return JSON.parse(this.props.streamManager.stream.connection.data).clientData;
}
handleVideoClicked(event) {
// Triggers event for the parent component to update its main video display (other UserVideoComponent)
if (this.props.mainVideoStream) {
this.props.mainVideoStream(this.props.streamManager);
}
}
render() {
return (
<div>
{this.props.streamManager !== undefined ? (
<div className="streamcomponent" onClick={this.handleVideoClicked}>
<OpenViduVideoComponent streamManager={this.props.streamManager} />
<div>
<p>{this.getNicknameTag()}</p>
</div>
</div>
) : null}
</div>
);
}
}

View File

@ -0,0 +1,231 @@
html {
position: relative;
min-height: 100%;
}
nav {
height: 50px;
width: 100%;
z-index: 1;
background-color: #4d4d4d !important;
border-color: #4d4d4d !important;
border-top-right-radius: 0 !important;
border-top-left-radius: 0 !important;
}
.navbar-header {
width: 100%;
}
.nav-icon {
padding: 5px 15px 5px 15px;
float: right;
}
nav a {
color: #ccc !important;
}
nav i.fa {
font-size: 40px;
color: #ccc;
}
nav a:hover {
color: #a9a9a9 !important;
}
nav i.fa:hover {
color: #a9a9a9;
}
#main-container {
padding-bottom: 80px;
}
.vertical-center {
position: absolute;
top: 56%;
left: 50%;
transform: translate(-50%, -50%);
}
.horizontal-center {
margin: 0 auto;
}
.form-control {
color: #0088aa;
font-weight: bold;
}
.form-control:focus {
border-color: #0088aa;
-webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 8px rgba(0, 136, 170, 0.6);
box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 8px rgba(0, 136, 170, 0.6);
}
input.btn {
font-weight: bold;
}
.btn {
font-weight: bold !important;
}
.btn-success {
background-color: #06d362 !important;
border-color: #06d362;
}
.btn-success:hover {
background-color: #1abd61 !important;
border-color: #1abd61;
}
.footer {
position: absolute;
bottom: 0;
width: 100%;
height: 60px;
background-color: #4d4d4d;
}
.footer .text-muted {
margin: 20px 0;
float: left;
color: #ccc;
}
.openvidu-logo {
height: 35px;
float: right;
margin: 12px 0;
-webkit-transition: all 0.1s ease-in-out;
-moz-transition: all 0.1s ease-in-out;
-o-transition: all 0.1s ease-in-out;
transition: all 0.1s ease-in-out;
}
.openvidu-logo:hover {
-webkit-filter: grayscale(0.5);
filter: grayscale(0.5);
}
.demo-logo {
margin: 0;
height: 22px;
float: left;
padding-right: 8px;
}
a:hover .demo-logo {
-webkit-filter: brightness(0.7);
filter: brightness(0.7);
}
#join-dialog h1 {
color: #4d4d4d;
font-weight: bold;
text-align: center;
}
#img-div {
text-align: center;
position: absolute;
top: 19%;
left: 50%;
transform: translate(-50%, -50%);
}
#img-div img {
height: 15%;
width: 42.5%;
}
#join-dialog label {
color: #0088aa;
}
#join-dialog input.btn {
margin-top: 15px;
}
#session-header {
margin-bottom: 20px;
}
#session-title {
display: inline-block;
}
#buttonLeaveSession {
float: right;
margin-top: 20px;
}
#video-container video {
position: relative;
float: left;
cursor: pointer;
}
#video-container p {
display: inline-block;
background: #f8f8f8;
padding-right: 5px;
padding-left: 5px;
color: #777777;
font-weight: bold;
border-bottom-right-radius: 4px;
}
/*video {
width: 100%;
height: auto;
}*/
#main-video p {
position: absolute;
display: inline-block;
background: #f8f8f8;
padding-right: 5px;
padding-left: 5px;
left: 0;
font-size: 22px;
color: #777777;
font-weight: bold;
border-bottom-right-radius: 4px;
}
#main-video video {
cursor: initial;
}
#session img {
width: 100%;
height: auto;
display: inline-block;
object-fit: contain;
vertical-align: baseline;
}
#session #video-container img {
position: relative;
float: left;
width: 50%;
cursor: pointer;
object-fit: cover;
height: 180px;
}
/* xs ans md screen resolutions*/
@media screen and (max-width: 991px) {
.vertical-center {
width: 85%;
padding-top: 10px;
top: 57%;
}
}

View File

@ -0,0 +1,8 @@
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import registerServiceWorker from './registerServiceWorker';
ReactDOM.render(<App />, document.getElementById('root'));
registerServiceWorker();

View File

@ -0,0 +1,117 @@
// In production, we register a service worker to serve assets from local cache.
// This lets the app load faster on subsequent visits in production, and gives
// it offline capabilities. However, it also means that developers (and users)
// will only see deployed updates on the "N+1" visit to a page, since previously
// cached resources are updated in the background.
// To learn more about the benefits of this model, read https://goo.gl/KwvDNy.
// This link also includes instructions on opting out of this behavior.
const isLocalhost = Boolean(
window.location.hostname === 'localhost' ||
// [::1] is the IPv6 localhost address.
window.location.hostname === '[::1]' ||
// 127.0.0.1/8 is considered localhost for IPv4.
window.location.hostname.match(
/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
)
);
export default function register() {
if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
// The URL constructor is available in all browsers that support SW.
const publicUrl = new URL(process.env.PUBLIC_URL, window.location);
if (publicUrl.origin !== window.location.origin) {
// Our service worker won't work if PUBLIC_URL is on a different origin
// from what our page is served on. This might happen if a CDN is used to
// serve assets; see https://github.com/facebookincubator/create-react-app/issues/2374
return;
}
window.addEventListener('load', () => {
const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
if (isLocalhost) {
// This is running on localhost. Lets check if a service worker still exists or not.
checkValidServiceWorker(swUrl);
// Add some additional logging to localhost, pointing developers to the
// service worker/PWA documentation.
navigator.serviceWorker.ready.then(() => {
console.log(
'This web app is being served cache-first by a service ' +
'worker. To learn more, visit https://goo.gl/SC7cgQ'
);
});
} else {
// Is not local host. Just register service worker
registerValidSW(swUrl);
}
});
}
}
function registerValidSW(swUrl) {
navigator.serviceWorker
.register(swUrl)
.then(registration => {
registration.onupdatefound = () => {
const installingWorker = registration.installing;
installingWorker.onstatechange = () => {
if (installingWorker.state === 'installed') {
if (navigator.serviceWorker.controller) {
// At this point, the old content will have been purged and
// the fresh content will have been added to the cache.
// It's the perfect time to display a "New content is
// available; please refresh." message in your web app.
console.log('New content is available; please refresh.');
} else {
// At this point, everything has been precached.
// It's the perfect time to display a
// "Content is cached for offline use." message.
console.log('Content is cached for offline use.');
}
}
};
};
})
.catch(error => {
console.error('Error during service worker registration:', error);
});
}
function checkValidServiceWorker(swUrl) {
// Check if the service worker can be found. If it can't reload the page.
fetch(swUrl)
.then(response => {
// Ensure service worker exists, and that we really are getting a JS file.
if (
response.status === 404 ||
response.headers.get('content-type').indexOf('javascript') === -1
) {
// No service worker found. Probably a different app. Reload the page.
navigator.serviceWorker.ready.then(registration => {
registration.unregister().then(() => {
window.location.reload();
});
});
} else {
// Service worker found. Proceed as normal.
registerValidSW(swUrl);
}
})
.catch(() => {
console.log(
'No internet connection found. App is running in offline mode.'
);
});
}
export function unregister() {
if ('serviceWorker' in navigator) {
navigator.serviceWorker.ready.then(registration => {
registration.unregister();
});
}
}