openvidu-ipcameras

This commit is contained in:
pabloFuente 2019-10-31 14:31:04 +01:00
parent ed489a9685
commit 01eb95591c
11 changed files with 602 additions and 0 deletions

24
openvidu-ipcameras/.gitignore vendored Normal file
View File

@ -0,0 +1,24 @@
# Compiled class file
*.class
# Log file
*.log
# BlueJ files
*.ctxt
# Mobile Tools for Java (J2ME)
.mtj.tmp/
# Package Files #
*.jar
*.war
*.ear
*.zip
*.tar.gz
*.rar
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
hs_err_pid*
target/

View File

@ -0,0 +1,59 @@
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.0.RELEASE</version>
</parent>
<groupId>io.openvidu</groupId>
<artifactId>openvidu-ipcameras</artifactId>
<version>1.0.0</version>
<packaging>jar</packaging>
<name>openvidu-ipcameras</name>
<url>https://github.com/OpenVidu/openvidu-tutorials/tree/master/openvidu-ipcameras</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>io.openvidu</groupId>
<artifactId>openvidu-java-client</artifactId>
<version>2.11.0</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,60 @@
package io.openvidu.ipcameras;
import java.net.MalformedURLException;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.PropertySource;
/**
* Run this application with the following command:
*
* java -jar -Dopenvidu-url=https://your.url.com -Dopenvidu-secret=your_secret openvidu-ipcameras-1.0.0.jar
*
* @author Pablo Fuente (pablofuenteperez@gmail.com)
*/
@SpringBootApplication
@PropertySource("classpath:application.properties")
public class App {
private static final Logger log = LoggerFactory.getLogger(App.class);
static String OPENVIDU_URL;
static String OPENVIDU_SECRET;
static Map<String, String> IP_CAMERAS = new HashMap<String, String>() {
{
put("Beach camera 1", "rtsp://b1.dnsdojo.com:1935/live/sys3.stream");
put("Beach camera 2", "rtsp://b1.dnsdojo.com:1935/live/sys5.stream");
put("City", "rtsp://91.191.213.49:554/live_mpeg4.sdp");
}
};
public static void main(String[] args) {
// Start application
ConfigurableApplicationContext context = SpringApplication.run(App.class, args);
OPENVIDU_URL = context.getEnvironment().getProperty("openvidu-url");
OPENVIDU_SECRET = context.getEnvironment().getProperty("openvidu-secret");
// Check OPENVIDU_URL parameter
try {
new URL(OPENVIDU_URL).toURI();
OPENVIDU_URL = OPENVIDU_URL.endsWith("/") ? OPENVIDU_URL : (OPENVIDU_URL + "/");
} catch (MalformedURLException | URISyntaxException e) {
log.error("Parameter \"openvidu-url\" ({}) has not a valid URL format", OPENVIDU_URL);
System.exit(1);
}
log.info("OpenVidu URL is {}", OPENVIDU_URL);
log.info("OpenVidu secret is {}", OPENVIDU_SECRET);
log.info("Camera list is {}", IP_CAMERAS.toString());
}
}

View File

@ -0,0 +1,135 @@
package io.openvidu.ipcameras;
import java.util.List;
import java.util.Map.Entry;
import java.util.NoSuchElementException;
import java.util.stream.Collectors;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import io.openvidu.java.client.OpenVidu;
import io.openvidu.java.client.OpenViduHttpException;
import io.openvidu.java.client.OpenViduJavaClientException;
import io.openvidu.java.client.Session;
import io.openvidu.java.client.SessionProperties;
/**
* Rest controller that offers a single entry point ("/"), where users can
* request for a token to enter the OpenVidu session if their credentials are
* right. First time a user provides the required credentials, the OpenVidu
* session will be created and the cameras will be published just before
* generating and returning the user's token
*
* @author Pablo Fuente (pablofuenteperez@gmail.com)
*/
@Controller
public class MyRestController {
private static final Logger log = LoggerFactory.getLogger(App.class);
private final String USER_CREDENTIALS = "PASSWORD";
private final String SESSION_ID = "MySurveillanceSession";
// OpenVidu objects
private OpenVidu OV;
private Session session;
// A simple HTTP client to perform OpenVidu REST API operations that are not
// available yet in OpenVidu Java Client
private final SimpleHttpClient httpClient = new SimpleHttpClient();
@RequestMapping(value = "/")
public String subscribe(@RequestParam(name = "credentials", required = false) String credentials, Model model) {
try {
checkCredentials(credentials);
} catch (Exception e) {
return generateError(model, "Wrong credentials");
}
// Create our surveillance session if not available yet
if (OV == null || session == null) {
try {
createOpenViduSession();
publishCameras();
} catch (OpenViduJavaClientException | OpenViduHttpException e) {
return generateError(model,
"Error sending request to OpenVidu Server: " + e.getCause() + ". " + e.getMessage());
}
}
// Generate a token for the user
String token = null;
try {
token = this.session.generateToken();
} catch (OpenViduJavaClientException | OpenViduHttpException e) {
return generateError(model,
"Error creating OpenVidu token for session " + SESSION_ID + ": " + e.getMessage());
}
model.addAttribute("token", token);
return "index";
}
private void createOpenViduSession() throws OpenViduJavaClientException, OpenViduHttpException {
// Init OpenVidu entrypoint object
OV = new OpenVidu(App.OPENVIDU_URL, App.OPENVIDU_SECRET);
// Get active sessions from OpenVidu Server
OV.fetch();
try {
// See if our surveillance session is already created in OpenVidu Server
session = OV.getActiveSessions().stream().filter(s -> s.getSessionId().equals(SESSION_ID)).findFirst()
.get();
log.info("Session {} already existed in OpenViduU Server", SESSION_ID);
} catch (NoSuchElementException e) {
// Create our surveillance session if it does not exist yet in OpenVidu Server
log.info("Session {} does not in OpenViduU Servery yet. Creating it...", SESSION_ID);
SessionProperties properties = new SessionProperties.Builder().customSessionId(SESSION_ID).build();
session = OV.createSession(properties);
log.info("Session {} created", SESSION_ID);
}
}
private void publishCameras() throws OpenViduJavaClientException, OpenViduHttpException {
// See if we have already published any of our cameras
// We fetch our only session current status and search for connections with
// platform "IPCAM". Finally we get their server data field with the camera name
session.fetch();
List<String> alreadyPublishedCameras = session.getActiveConnections().stream()
.filter(connection -> "IPCAM".equals(connection.getPlatform()))
.map(connection -> connection.getServerData()).collect(Collectors.toList());
for (Entry<String, String> cameraMapEntry : App.IP_CAMERAS.entrySet()) {
try {
String cameraUri = cameraMapEntry.getValue();
String cameraName = cameraMapEntry.getKey();
if (!alreadyPublishedCameras.contains(cameraName)) {
// Publish the camera only if it is not already published
httpClient.publishIpCamera(SESSION_ID, cameraUri, cameraName, true, true);
}
} catch (Exception e) {
log.error("Error publishing camera {}", cameraMapEntry.getKey());
}
}
}
private void checkCredentials(String credentials) throws Exception {
// Dummy security: if not expected string, then throw error
if (!credentials.equals(USER_CREDENTIALS)) {
throw new Exception();
}
}
private String generateError(Model model, String message) {
log.error(message);
model.addAttribute("error", message);
return "index";
}
}

View File

@ -0,0 +1,104 @@
package io.openvidu.ipcameras;
import java.io.IOException;
import java.security.KeyManagementException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.Base64;
import java.util.concurrent.TimeUnit;
import javax.net.ssl.SSLContext;
import org.apache.http.HttpHeaders;
import org.apache.http.HttpResponse;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.conn.ssl.NoopHostnameVerifier;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.ssl.SSLContextBuilder;
import org.apache.http.ssl.TrustStrategy;
import org.apache.http.util.EntityUtils;
import org.json.simple.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Simple HTTP client able to send REST API requests to insecure servers
* (self-signed certificates are accepted). It only implements a single method
* to publish an IP camera to an OpenVidu Server session
*
* @author Pablo Fuente (pablofuenteperez@gmail.com)
*/
public class SimpleHttpClient {
private static final Logger log = LoggerFactory.getLogger(App.class);
private CloseableHttpClient httpClient;
public SimpleHttpClient() {
TrustStrategy trustStrategy = new TrustStrategy() {
@Override
public boolean isTrusted(X509Certificate[] chain, String authType) throws CertificateException {
return true;
}
};
SSLContext sslContext;
try {
sslContext = new SSLContextBuilder().loadTrustMaterial(null, trustStrategy).build();
} catch (KeyManagementException | NoSuchAlgorithmException | KeyStoreException e) {
throw new RuntimeException(e);
}
RequestConfig.Builder requestBuilder = RequestConfig.custom();
requestBuilder = requestBuilder.setConnectTimeout(30000);
requestBuilder = requestBuilder.setConnectionRequestTimeout(30000);
httpClient = HttpClientBuilder.create().setDefaultRequestConfig(requestBuilder.build())
.setConnectionTimeToLive(30, TimeUnit.SECONDS).setSSLHostnameVerifier(NoopHostnameVerifier.INSTANCE)
.setSSLContext(sslContext).build();
}
public void publishIpCamera(String sessionId, String rtspUri, String cameraName, boolean adaptativeBitrate,
boolean onlyPlayWhenSubscribers) throws Exception {
HttpPost request = new HttpPost(App.OPENVIDU_URL + "api/sessions/" + sessionId + "/connection");
JSONObject json = new JSONObject();
json.put("rtspUri", rtspUri);
json.put("data", cameraName);
json.put("adaptativeBitrate", adaptativeBitrate);
json.put("onlyPlayWithSubscribers", onlyPlayWhenSubscribers);
StringEntity params = new StringEntity(json.toString());
request.setHeader(HttpHeaders.CONTENT_TYPE, "application/json");
request.setHeader(HttpHeaders.AUTHORIZATION,
"Basic " + Base64.getEncoder().encodeToString(("OPENVIDUAPP:" + App.OPENVIDU_SECRET).getBytes()));
request.setEntity(params);
HttpResponse response;
try {
response = this.httpClient.execute(request);
} catch (IOException e) {
log.error("Error publishing IP camera {}: {}", rtspUri, e.getMessage());
throw new Exception(e.getMessage(), e.getCause());
}
try {
int statusCode = response.getStatusLine().getStatusCode();
if ((statusCode == org.apache.http.HttpStatus.SC_OK)) {
log.info("IP camera '{}' published", rtspUri);
} else {
log.error("Error publishing IP camera {}: Http status {}", rtspUri, statusCode);
throw new Exception(Integer.toString(statusCode));
}
} finally {
EntityUtils.consumeQuietly(response.getEntity());
}
}
}

View File

@ -0,0 +1,9 @@
server.port: 8080
server.ssl.enabled: true
server.ssl.key-store: classpath:openvidu-selfsigned.jks
server.ssl.key-store-password: openvidu
server.ssl.key-store-type: JKS
server.ssl.key-alias: openvidu-selfsigned
openvidu-url: https://localhost:4443/
openvidu-secret: MY_SECRET

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,44 @@
form {
display: inline-block;
}
.cameras {
margin-top: 50px;
}
.camera-name {
text-align: center;
margin-top: 30px;
margin-bottom: 10px;
}
.video-container {
position: relative;
text-align: center;
}
video {
width: 50%;
}
.loader {
border: 16px solid #f3f3f3;
border-top: 16px solid #0088aa;
border-radius: 50%;
width: 100px;
height: 100px;
animation: spin 2s linear infinite;
position: absolute;
left: calc(50% - 66px);
top: calc(50% - 66px);
}
@keyframes spin {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}

View File

@ -0,0 +1,128 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>OpenVidu IP cameras demo</title>
<link rel="styleSheet" href="style.css" type="text/css" media="screen">
</link>
<script src="openvidu-browser-2.12.0-SNAPSHOT.min.js"></script>
</head>
<body>
<h1>OpenVidu IP cameras demo</h1>
<form class="form-group" action="/" method="get">
<input type="text" name="credentials" placeholder="credentials" required="true" value="PASSWORD"></input>
<button type="submit">Subscribe to cameras</button>
</form>
<button onclick="unsubscribe()">Unsubscribe from cameras</button>
<div id="cameras"></div>
</body>
<script th:inline="javascript">
// OpenVidu objects
var OV
var session;
var videosContainer = document.getElementById('cameras');
// Get all the attributes from the template in Thymeleaf style
var error = [[${error}]];
var token = [[${token}]];
if (!!error) {
alert(error);
} else if (!!token) {
// Subscribe to OpenVidu session only when token is available
subscribe(token);
}
function subscribe() {
// Initialize OpenVidu and Session objects
OV = new OpenVidu();
session = OV.initSession();
// On every new Stream received...
session.on('streamCreated', event => {
// Subscribe to the Stream to receive it
// HTML video will be appended to a new element created inside <div id='cameras'>
var videoDiv = document.createElement('div');
var stream = event.stream;
videoDiv.classList.add('video-container');
videoDiv.id = stream.streamId;
videosContainer.appendChild(videoDiv);
// Append video inside our brand new <div> element
var subscriber = session.subscribe(stream, videoDiv);
// When the HTML video has been appended to DOM...
subscriber.on('videoElementCreated', ev => {
// ...append camera name on top of video
var cameraName = document.createElement('div');
cameraName.innerText = stream.connection.data;
cameraName.classList.add('camera-name');
ev.element.parentNode.insertBefore(cameraName, ev.element);
// ...start loader
var loader = document.createElement('div');
loader.classList.add('loader');
ev.element.parentNode.insertBefore(loader, ev.element.nextSibling);
});
// When the HTML video starts playing...
subscriber.on('streamPlaying', ev => {
// ...remove loader
var cameraVideoElement = subscriber.videos[0].video;
cameraVideoElement.parentNode.removeChild(cameraVideoElement.nextSibling);
// ... mute video if browser blocked autoplay
autoplayMutedVideoIfBlocked(cameraVideoElement);
});
// When the HTML video has been removed from DOM...
subscriber.on('videoElementDestroyed', ev => {
// ...remove the HTML elements related to the destroyed video
var videoContainer = document.getElementById(stream.streamId);
videoContainer.parentNode.removeChild(videoContainer);
});
});
// Connect to session. We will receive all necessary events when success
session.connect(token)
.catch(error => {
var msg = 'There was an error connecting to the session. Code: ' + error.code + '. Message: ' + error
.message;
console.error(msg);
alert(msg);
});
}
function unsubscribe() {
if (!!session) {
// Leave OpenVidu Session
session.disconnect();
}
}
function autoplayMutedVideoIfBlocked(video) {
// Browser can block video playback if it is auto played without user interaction
// One solution is to mute the video and let the user know
video.controls = true;
var promise = video.play();
if (promise !== undefined) {
promise.then(() => {
// Autoplay started
}).catch(error => {
// The video must play muted until user hits play button
video.muted = true;
video.play();
});
}
}
// Unsubscribe from OpenVidu session when leaving the page
window.addEventListener("beforeunload", () => {
unsubscribe();
});
</script>
</html>

View File

@ -0,0 +1,38 @@
package es.codeurjc.openvidu.ipcameras;
import junit.framework.Test;
import junit.framework.TestCase;
import junit.framework.TestSuite;
/**
* Unit test for simple App.
*/
public class AppTest
extends TestCase
{
/**
* Create the test case
*
* @param testName name of the test case
*/
public AppTest( String testName )
{
super( testName );
}
/**
* @return the suite of tests being tested
*/
public static Test suite()
{
return new TestSuite( AppTest.class );
}
/**
* Rigourous Test :-)
*/
public void testApp()
{
assertTrue( true );
}
}