openvidu-recording tutorials: HTML update. e2e test for recording-java
This commit is contained in:
parent
ba5602f370
commit
b28ab51c7d
1
openvidu-recording-java/.gitignore
vendored
1
openvidu-recording-java/.gitignore
vendored
@ -22,3 +22,4 @@
|
||||
hs_err_pid*
|
||||
|
||||
target/
|
||||
.vscode/*
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
<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>
|
||||
|
||||
@ -13,7 +14,7 @@
|
||||
<parent>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-parent</artifactId>
|
||||
<version>1.4.1.RELEASE</version>
|
||||
<version>2.1.2.RELEASE</version>
|
||||
</parent>
|
||||
|
||||
<properties>
|
||||
@ -21,6 +22,11 @@
|
||||
<java.version>1.8</java.version>
|
||||
<start-class>io.openvidu.recording.java.App</start-class>
|
||||
<docker.image.prefix>openvidu</docker.image.prefix>
|
||||
|
||||
<!-- Test dependencies versions -->
|
||||
<junit.jupiter.version>5.0.3</junit.jupiter.version>
|
||||
<junit.platform.version>1.3.2</junit.platform.version>
|
||||
<selenium-jupiter.version>3.1.0</selenium-jupiter.version>
|
||||
</properties>
|
||||
|
||||
<build>
|
||||
@ -36,7 +42,7 @@
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>springloaded</artifactId>
|
||||
<version>1.2.6.RELEASE</version>
|
||||
<version>2.1.2.RELEASE</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</plugin>
|
||||
@ -48,35 +54,11 @@
|
||||
<mainClass>${start-class}</mainClass>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
<plugin>
|
||||
<groupId>com.spotify</groupId>
|
||||
<artifactId>docker-maven-plugin</artifactId>
|
||||
<version>0.2.3</version>
|
||||
<configuration>
|
||||
<imageName>${docker.image.prefix}/${project.artifactId}</imageName>
|
||||
<dockerDirectory>src/main/docker</dockerDirectory>
|
||||
<resources>
|
||||
<resource>
|
||||
<targetPath>/</targetPath>
|
||||
<directory>${project.build.directory}</directory>
|
||||
<include>${project.build.finalName}.war</include>
|
||||
</resource>
|
||||
</resources>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
<dependencies>
|
||||
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
@ -86,17 +68,59 @@
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-devtools</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.googlecode.json-simple</groupId>
|
||||
<artifactId>json-simple</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.openvidu</groupId>
|
||||
<artifactId>openvidu-java-client</artifactId>
|
||||
<version>2.8.1</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter-api</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.github.bonigarcia</groupId>
|
||||
<artifactId>selenium-jupiter</artifactId>
|
||||
<version>${selenium-jupiter.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.github.bonigarcia</groupId>
|
||||
<artifactId>webdrivermanager</artifactId>
|
||||
<version>2.2.0</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.junit.platform</groupId>
|
||||
<artifactId>junit-platform-runner</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter-engine</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.google.guava</groupId>
|
||||
<artifactId>guava</artifactId>
|
||||
<version>23.0</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<!--<dependency> <groupId>com.googlecode.mp4parser</groupId> <artifactId>isoparser</artifactId>
|
||||
<version>1.1.22</version> <scope>test</scope> </dependency> -->
|
||||
<!--<dependency> <groupId>net.bramp.ffmpeg</groupId> <artifactId>ffmpeg</artifactId>
|
||||
<version>0.6.2</version> </dependency> -->
|
||||
<dependency>
|
||||
<groupId>ws.schild</groupId>
|
||||
<artifactId>jave-all-deps</artifactId>
|
||||
<version>2.4.5</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>ws.schild</groupId>
|
||||
<artifactId>jave-core</artifactId>
|
||||
<version>2.4.5</version>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
</project>
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
package io.openvidu.recording.java;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
@ -92,37 +91,43 @@ public class MyRestController {
|
||||
// Return the response to the client
|
||||
return new ResponseEntity<>(responseJson, HttpStatus.OK);
|
||||
|
||||
} catch (Exception e) {
|
||||
// If error generate an error message and return it to client
|
||||
return getErrorResponse(e);
|
||||
} catch (OpenViduJavaClientException e1) {
|
||||
// If internal error generate an error message and return it to client
|
||||
return getErrorResponse(e1);
|
||||
} catch (OpenViduHttpException e2) {
|
||||
if (404 == e2.getStatus()) {
|
||||
// Invalid sessionId (user left unexpectedly). Session object is not valid
|
||||
// anymore. Clean collections and continue as new session
|
||||
this.mapSessions.remove(sessionName);
|
||||
this.mapSessionNamesTokens.remove(sessionName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
// New session
|
||||
System.out.println("New session " + sessionName);
|
||||
try {
|
||||
// New session
|
||||
System.out.println("New session " + sessionName);
|
||||
try {
|
||||
|
||||
// Create a new OpenVidu Session
|
||||
Session session = this.openVidu.createSession();// new
|
||||
// SessionProperties.Builder().customSessionId("CUSTOMSESSIONID").defaultRecordingLayout(RecordingLayout.CUSTOM).defaultCustomLayout("CUSTOM/LAYOUT").recordingMode(RecordingMode.ALWAYS).build());
|
||||
// Generate a new token with the recently created tokenOptions
|
||||
String token = session.generateToken(tokenOptions);
|
||||
// Create a new OpenVidu Session
|
||||
Session session = this.openVidu.createSession();// new
|
||||
// SessionProperties.Builder().customSessionId("CUSTOMSESSIONID").defaultRecordingLayout(RecordingLayout.CUSTOM).defaultCustomLayout("CUSTOM/LAYOUT").recordingMode(RecordingMode.ALWAYS).build());
|
||||
// Generate a new token with the recently created tokenOptions
|
||||
String token = session.generateToken(tokenOptions);
|
||||
|
||||
// Store the session and the token in our collections
|
||||
this.mapSessions.put(sessionName, session);
|
||||
this.mapSessionNamesTokens.put(sessionName, new ConcurrentHashMap<>());
|
||||
this.mapSessionNamesTokens.get(sessionName).put(token, role);
|
||||
// Store the session and the token in our collections
|
||||
this.mapSessions.put(sessionName, session);
|
||||
this.mapSessionNamesTokens.put(sessionName, new ConcurrentHashMap<>());
|
||||
this.mapSessionNamesTokens.get(sessionName).put(token, role);
|
||||
|
||||
// Prepare the response with the sessionId and the token
|
||||
responseJson.put(0, token);
|
||||
// Prepare the response with the sessionId and the token
|
||||
responseJson.put(0, token);
|
||||
|
||||
// Return the response to the client
|
||||
return new ResponseEntity<>(responseJson, HttpStatus.OK);
|
||||
// Return the response to the client
|
||||
return new ResponseEntity<>(responseJson, HttpStatus.OK);
|
||||
|
||||
} catch (Exception e) {
|
||||
// If error generate an error message and return it to client
|
||||
return getErrorResponse(e);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// If error generate an error message and return it to client
|
||||
return getErrorResponse(e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -21,27 +21,48 @@ function joinSession() {
|
||||
|
||||
// --- 3) Specify the actions when events take place in the session ---
|
||||
|
||||
session.on('connectionCreated', event => {
|
||||
pushEvent(event);
|
||||
});
|
||||
|
||||
session.on('connectionDestroyed', event => {
|
||||
pushEvent(event);
|
||||
});
|
||||
|
||||
// On every new Stream received...
|
||||
session.on('streamCreated', (event) => {
|
||||
session.on('streamCreated', event => {
|
||||
pushEvent(event);
|
||||
|
||||
// Subscribe to the Stream to receive it
|
||||
// HTML video will be appended to element with 'video-container' id
|
||||
var subscriber = session.subscribe(event.stream, 'video-container');
|
||||
|
||||
// When the HTML video has been appended to DOM...
|
||||
subscriber.on('videoElementCreated', (event) => {
|
||||
subscriber.on('videoElementCreated', event => {
|
||||
pushEvent(event);
|
||||
// Add a new HTML element for the user's name and nickname over its video
|
||||
updateNumVideos(1);
|
||||
});
|
||||
|
||||
// When the HTML video has been appended to DOM...
|
||||
subscriber.on('videoElementDestroyed', (event) => {
|
||||
subscriber.on('videoElementDestroyed', event => {
|
||||
pushEvent(event);
|
||||
// Add a new HTML element for the user's name and nickname over its video
|
||||
updateNumVideos(-1);
|
||||
});
|
||||
|
||||
// When the subscriber stream has started playing media...
|
||||
subscriber.on('streamPlaying', event => {
|
||||
pushEvent(event);
|
||||
});
|
||||
});
|
||||
|
||||
session.on('sessionDisconnected', (event) => {
|
||||
session.on('streamDestroyed', event => {
|
||||
pushEvent(event);
|
||||
});
|
||||
|
||||
session.on('sessionDisconnected', event => {
|
||||
pushEvent(event);
|
||||
if (event.reason !== 'disconnect') {
|
||||
removeUser();
|
||||
}
|
||||
@ -53,6 +74,14 @@ function joinSession() {
|
||||
}
|
||||
});
|
||||
|
||||
session.on('recordingStarted', event => {
|
||||
pushEvent(event);
|
||||
});
|
||||
|
||||
session.on('recordingStopped', event => {
|
||||
pushEvent(event);
|
||||
});
|
||||
|
||||
// --- 4) Connect to the session passing the retrieved token and some more data from
|
||||
// the client (in this case a JSON with the nickname chosen by the user) ---
|
||||
|
||||
@ -70,28 +99,62 @@ function joinSession() {
|
||||
var publisher = OV.initPublisher('video-container', {
|
||||
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
|
||||
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
|
||||
});
|
||||
|
||||
// --- 7) Specify the actions when events take place in our publisher ---
|
||||
|
||||
// When the publisher stream has started playing media...
|
||||
publisher.on('accessAllowed', event => {
|
||||
pushEvent({
|
||||
type: 'accessAllowed'
|
||||
});
|
||||
});
|
||||
|
||||
publisher.on('accessDenied', event => {
|
||||
pushEvent(event);
|
||||
});
|
||||
|
||||
publisher.on('accessDialogOpened', event => {
|
||||
pushEvent({
|
||||
type: 'accessDialogOpened'
|
||||
});
|
||||
});
|
||||
|
||||
publisher.on('accessDialogClosed', event => {
|
||||
pushEvent({
|
||||
type: 'accessDialogClosed'
|
||||
});
|
||||
});
|
||||
|
||||
// When the publisher stream has started playing media...
|
||||
publisher.on('streamCreated', event => {
|
||||
pushEvent(event);
|
||||
});
|
||||
|
||||
// When our HTML video has been added to DOM...
|
||||
publisher.on('videoElementCreated', (event) => {
|
||||
publisher.on('videoElementCreated', event => {
|
||||
pushEvent(event);
|
||||
updateNumVideos(1);
|
||||
$(event.element).prop('muted', true); // Mute local video
|
||||
});
|
||||
|
||||
// When the HTML video has been appended to DOM...
|
||||
publisher.on('videoElementDestroyed', (event) => {
|
||||
publisher.on('videoElementDestroyed', event => {
|
||||
pushEvent(event);
|
||||
// Add a new HTML element for the user's name and nickname over its video
|
||||
updateNumVideos(-1);
|
||||
});
|
||||
|
||||
// When the publisher stream has started playing media...
|
||||
publisher.on('streamPlaying', event => {
|
||||
pushEvent(event);
|
||||
});
|
||||
|
||||
// --- 8) Publish your stream ---
|
||||
|
||||
@ -128,8 +191,8 @@ function getToken(callback) {
|
||||
sessionName: sessionName
|
||||
},
|
||||
'Request of TOKEN gone WRONG:',
|
||||
(response) => {
|
||||
token = response[0]; // Get token from response
|
||||
res => {
|
||||
token = res[0]; // Get token from response
|
||||
console.warn('Request of TOKEN gone WELL (TOKEN:' + token + ')');
|
||||
callback(token); // Continue the join operation
|
||||
}
|
||||
@ -144,7 +207,7 @@ function removeUser() {
|
||||
token: token
|
||||
},
|
||||
'User couldn\'t be removed from session',
|
||||
(response) => {
|
||||
res => {
|
||||
console.warn("You have been removed from session " + sessionName);
|
||||
}
|
||||
);
|
||||
@ -157,7 +220,7 @@ function closeSession() {
|
||||
sessionName: sessionName
|
||||
},
|
||||
'Session couldn\'t be closed',
|
||||
(response) => {
|
||||
res => {
|
||||
console.warn("Session " + sessionName + " has been closed");
|
||||
}
|
||||
);
|
||||
@ -170,9 +233,9 @@ function fetchInfo() {
|
||||
sessionName: sessionName
|
||||
},
|
||||
'Session couldn\'t be fetched',
|
||||
(response) => {
|
||||
res => {
|
||||
console.warn("Session info has been fetched");
|
||||
$('#text-area').text(JSON.stringify(response, null, "\t"));
|
||||
$('#textarea-http').text(JSON.stringify(res, null, "\t"));
|
||||
}
|
||||
);
|
||||
}
|
||||
@ -182,9 +245,9 @@ function fetchAll() {
|
||||
'GET',
|
||||
'api/fetch-all', {},
|
||||
'All session info couldn\'t be fetched',
|
||||
(response) => {
|
||||
res => {
|
||||
console.warn("All session info has been fetched");
|
||||
$('#text-area').text(JSON.stringify(response, null, "\t"));
|
||||
$('#textarea-http').text(JSON.stringify(res, null, "\t"));
|
||||
}
|
||||
);
|
||||
}
|
||||
@ -197,7 +260,7 @@ function forceDisconnect() {
|
||||
connectionId: document.getElementById('forceValue').value
|
||||
},
|
||||
'Connection couldn\'t be closed',
|
||||
(response) => {
|
||||
res => {
|
||||
console.warn("Connection has been closed");
|
||||
}
|
||||
);
|
||||
@ -211,14 +274,14 @@ function forceUnpublish() {
|
||||
streamId: document.getElementById('forceValue').value
|
||||
},
|
||||
'Stream couldn\'t be closed',
|
||||
(response) => {
|
||||
res => {
|
||||
console.warn("Stream has been closed");
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function httpRequest(method, url, body, errorMsg, callback) {
|
||||
$('#text-area').text('');
|
||||
$('#textarea-http').text('');
|
||||
var http = new XMLHttpRequest();
|
||||
http.open(method, url, true);
|
||||
http.setRequestHeader('Content-type', 'application/json');
|
||||
@ -236,16 +299,16 @@ function httpRequest(method, url, body, errorMsg, callback) {
|
||||
} else {
|
||||
console.warn(errorMsg + ' (' + http.status + ')');
|
||||
console.warn(http.responseText);
|
||||
$('#text-area').text(errorMsg + ": HTTP " + http.status + " (" + http.responseText + ")");
|
||||
$('#textarea-http').text(errorMsg + ": HTTP " + http.status + " (" + http.responseText + ")");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function startRecording() {
|
||||
var outputMode = document.querySelector('input[name="outputMode"]:checked').value;
|
||||
var hasAudio = !!document.querySelector("#has-audio-checkbox:checked");
|
||||
var hasVideo = !!document.querySelector("#has-video-checkbox:checked");
|
||||
var outputMode = $('input[name=outputMode]:checked').val();
|
||||
var hasAudio = $('#has-audio-checkbox').prop('checked');
|
||||
var hasVideo = $('#has-video-checkbox').prop('checked');
|
||||
httpRequest(
|
||||
'POST',
|
||||
'api/recording/start', {
|
||||
@ -255,11 +318,11 @@ function startRecording() {
|
||||
hasVideo: hasVideo
|
||||
},
|
||||
'Start recording WRONG',
|
||||
(response) => {
|
||||
console.log(response);
|
||||
document.getElementById('forceRecordingId').value = response.id;
|
||||
res => {
|
||||
console.log(res);
|
||||
document.getElementById('forceRecordingId').value = res.id;
|
||||
checkBtnsRecordings();
|
||||
$('#text-area').text(JSON.stringify(response, null, "\t"));
|
||||
$('#textarea-http').text(JSON.stringify(res, null, "\t"));
|
||||
}
|
||||
);
|
||||
}
|
||||
@ -272,9 +335,9 @@ function stopRecording() {
|
||||
recording: forceRecordingId
|
||||
},
|
||||
'Stop recording WRONG',
|
||||
(response) => {
|
||||
console.log(response);
|
||||
$('#text-area').text(JSON.stringify(response, null, "\t"));
|
||||
res => {
|
||||
console.log(res);
|
||||
$('#textarea-http').text(JSON.stringify(res, null, "\t"));
|
||||
}
|
||||
);
|
||||
}
|
||||
@ -287,9 +350,9 @@ function deleteRecording() {
|
||||
recording: forceRecordingId
|
||||
},
|
||||
'Delete recording WRONG',
|
||||
() => {
|
||||
res => {
|
||||
console.log("DELETE ok");
|
||||
$('#text-area').text("DELETE ok");
|
||||
$('#textarea-http').text("DELETE ok");
|
||||
}
|
||||
);
|
||||
}
|
||||
@ -300,9 +363,9 @@ function getRecording() {
|
||||
'GET',
|
||||
'api/recording/get/' + forceRecordingId, {},
|
||||
'Get recording WRONG',
|
||||
(response) => {
|
||||
console.log(response);
|
||||
$('#text-area').text(JSON.stringify(response, null, "\t"));
|
||||
res => {
|
||||
console.log(res);
|
||||
$('#textarea-http').text(JSON.stringify(res, null, "\t"));
|
||||
}
|
||||
);
|
||||
}
|
||||
@ -312,9 +375,9 @@ function listRecordings() {
|
||||
'GET',
|
||||
'api/recording/list', {},
|
||||
'List recordings WRONG',
|
||||
(response) => {
|
||||
console.log(response);
|
||||
$('#text-area').text(JSON.stringify(response, null, "\t"));
|
||||
res => {
|
||||
console.log(res);
|
||||
$('#textarea-http').text(JSON.stringify(res, null, "\t"));
|
||||
}
|
||||
);
|
||||
}
|
||||
@ -325,6 +388,8 @@ function listRecordings() {
|
||||
|
||||
/* APPLICATION BROWSER METHODS */
|
||||
|
||||
events = '';
|
||||
|
||||
window.onbeforeunload = function () { // Gracefully leave session
|
||||
if (session) {
|
||||
removeUser();
|
||||
@ -373,4 +438,18 @@ function checkBtnsRecordings() {
|
||||
}
|
||||
}
|
||||
|
||||
function pushEvent(event) {
|
||||
events += (!events ? '' : '\n') + event.type;
|
||||
$('#textarea-events').text(events);
|
||||
}
|
||||
|
||||
function clearHttpTextarea() {
|
||||
$('#textarea-http').text('');
|
||||
}
|
||||
|
||||
function clearEventsTextarea() {
|
||||
$('#textarea-events').text('');
|
||||
events = '';
|
||||
}
|
||||
|
||||
/* APPLICATION BROWSER METHODS */
|
||||
@ -60,7 +60,7 @@
|
||||
<input class="form-control" type="text" id="sessionName" value="SessionA" required>
|
||||
</p>
|
||||
<p class="text-center">
|
||||
<button class="btn btn-lg btn-success" onclick="joinSession()">Join!</button>
|
||||
<button class="btn btn-lg btn-success" id="join-btn" onclick="joinSession()">Join!</button>
|
||||
</p>
|
||||
</form>
|
||||
<hr>
|
||||
@ -89,10 +89,10 @@
|
||||
<input class="btn btn-md" type="button" id="buttonStartRecording" onmouseup="startRecording()" value="Start recording">
|
||||
<form>
|
||||
<label class="radio-inline">
|
||||
<input type="radio" name="outputMode" value="COMPOSED" checked>COMPOSED
|
||||
<input type="radio" name="outputMode" value="COMPOSED" id="radio-composed" checked>COMPOSED
|
||||
</label>
|
||||
<label class="radio-inline">
|
||||
<input type="radio" name="outputMode" value="INDIVIDUAL">INDIVIDUAL
|
||||
<input type="radio" name="outputMode" value="INDIVIDUAL" id="radio-individual">INDIVIDUAL
|
||||
</label>
|
||||
</form>
|
||||
<form>
|
||||
@ -115,7 +115,16 @@
|
||||
disabled>
|
||||
<input class="form-control" id="forceRecordingId" type="text" onkeyup="checkBtnsRecordings()">
|
||||
</div>
|
||||
<textarea id="text-area" readonly="true" class="form-control" name="comment">HTTP responses...</textarea>
|
||||
<div class="textarea-container" id="textarea-http-container">
|
||||
<button type="button" class="btn btn-outline-secondary" id="clear-http-btn" onclick="clearHttpTextarea()">Clear</button>
|
||||
<span>HTTP responses</span>
|
||||
<textarea id="textarea-http" readonly="true" class="form-control" name="textarea-http"></textarea>
|
||||
</div>
|
||||
<div class="textarea-container" id="textarea-events-container">
|
||||
<button type="button" class="btn btn-outline-secondary" id="clear-events-btn" onclick="clearEventsTextarea()">Clear</button>
|
||||
<span>OpenVidu events</span>
|
||||
<textarea id="textarea-events" readonly="true" class="form-control" name="textarea-events"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -250,21 +250,21 @@ a:hover .demo-logo {
|
||||
|
||||
#video-container {
|
||||
width: 100%;
|
||||
max-height: 45%;
|
||||
display: inline-block;
|
||||
max-height: 42%;
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#video-container video.two {
|
||||
width: 50%;
|
||||
max-width: 50%;
|
||||
}
|
||||
|
||||
#video-container video.three {
|
||||
width: 33.33%;
|
||||
max-width: 33.33%;
|
||||
}
|
||||
|
||||
#video-container video.four {
|
||||
width: 25%;
|
||||
max-width: 25%;
|
||||
}
|
||||
|
||||
#video-container div {
|
||||
@ -294,10 +294,10 @@ a:hover .demo-logo {
|
||||
}
|
||||
|
||||
video {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
max-height: 100%;
|
||||
object-fit: cover;
|
||||
width: auto;
|
||||
height: auto;
|
||||
max-height: 100%;
|
||||
object-fit: scale-down;
|
||||
}
|
||||
|
||||
#session {
|
||||
@ -353,11 +353,11 @@ table i {
|
||||
}
|
||||
|
||||
#recording-btns {
|
||||
display: flow-root;
|
||||
padding-left: 15px;
|
||||
padding-top: 20px;
|
||||
width: 100%;
|
||||
height: 40%;
|
||||
display: inline-block;
|
||||
padding-left: 15px;
|
||||
padding-top: 20px;
|
||||
width: 100%;
|
||||
height: 40%;
|
||||
}
|
||||
|
||||
#recording-btns .btns {
|
||||
@ -371,19 +371,57 @@ table i {
|
||||
|
||||
#recording-btns .btns form {
|
||||
display: inline;
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
#recording-btns #text-area {
|
||||
display: inline;
|
||||
width: 100%;
|
||||
#recording-btns textarea {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.textarea-container {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
height: 74%;
|
||||
margin-top: 20px;
|
||||
resize: none;
|
||||
}
|
||||
|
||||
#textarea-http-container {
|
||||
width: 69%;
|
||||
}
|
||||
|
||||
#textarea-events-container {
|
||||
width: 29%;
|
||||
}
|
||||
|
||||
.textarea-container button {
|
||||
position: absolute;
|
||||
top: 1px;
|
||||
right: 1px;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.textarea-container span {
|
||||
position: absolute;
|
||||
bottom: 1px;
|
||||
right: 1px;
|
||||
padding: 3px;
|
||||
border-bottom-right-radius: 4px;
|
||||
z-index: 1;
|
||||
color: #a5a5a5;
|
||||
background-color: #ededee;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.textarea-container textarea {
|
||||
height: 100%;
|
||||
resize: none;
|
||||
}
|
||||
|
||||
.vertical-separator-bottom {
|
||||
width: 2px;
|
||||
height: 34px;
|
||||
display: inline;
|
||||
display: inline-block;
|
||||
background-color: #cbcbcb;
|
||||
margin: 0 8px 0 8px;
|
||||
margin-bottom: -12px;
|
||||
|
||||
@ -1,38 +0,0 @@
|
||||
package io.openvidu.js.java.test;
|
||||
|
||||
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 );
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,19 @@
|
||||
package io.openvidu.recording.java.test;
|
||||
|
||||
import org.junit.platform.runner.JUnitPlatform;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.Assert;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
/**
|
||||
* Unit test for openvidu-recording-java
|
||||
*/
|
||||
@RunWith(JUnitPlatform.class)
|
||||
public class AppTest {
|
||||
|
||||
@Test
|
||||
void testApp() {
|
||||
Assert.assertTrue(true);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,337 @@
|
||||
/*
|
||||
* (C) Copyright 2017-2019 OpenVidu (https://openvidu.io/)
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
package io.openvidu.recording.java.test;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.apache.commons.io.FileUtils;
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.junit.Assert;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.junit.platform.runner.JUnitPlatform;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.openqa.selenium.By;
|
||||
import org.openqa.selenium.Dimension;
|
||||
import org.openqa.selenium.WebDriver;
|
||||
import org.openqa.selenium.chrome.ChromeDriver;
|
||||
import org.openqa.selenium.chrome.ChromeOptions;
|
||||
import org.openqa.selenium.remote.DesiredCapabilities;
|
||||
import org.openqa.selenium.remote.RemoteWebDriver;
|
||||
import org.openqa.selenium.support.ui.ExpectedCondition;
|
||||
import org.openqa.selenium.support.ui.ExpectedConditions;
|
||||
import org.openqa.selenium.support.ui.WebDriverWait;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import io.github.bonigarcia.seljup.SeleniumExtension;
|
||||
import io.github.bonigarcia.wdm.WebDriverManager;
|
||||
import io.openvidu.java.client.OpenVidu;
|
||||
import io.openvidu.java.client.OpenViduHttpException;
|
||||
import io.openvidu.java.client.OpenViduJavaClientException;
|
||||
import io.openvidu.java.client.Recording;
|
||||
import io.openvidu.java.client.Recording.OutputMode;
|
||||
import ws.schild.jave.EncoderException;
|
||||
import ws.schild.jave.MultimediaInfo;
|
||||
import ws.schild.jave.MultimediaObject;
|
||||
|
||||
/**
|
||||
* E2E tests for openvidu-java-recording app
|
||||
*
|
||||
* mvn -Dtest=AppTestE2e -DAPP_URL=https://localhost:5000/
|
||||
* -DOPENVIDU_URL=https://localhost:4443/ -DOPENVIDU_SECRET=MY_SECRET
|
||||
* -DNUMBER_OF_ATTEMPTS=30 -DRECORDING_DURATION=5 -DDURATION_THRESHOLD=5 test
|
||||
*
|
||||
* @author Pablo Fuente (pablofuenteperez@gmail.com)
|
||||
*/
|
||||
@DisplayName("E2E tests for openvidu-java-recording")
|
||||
@ExtendWith(SeleniumExtension.class)
|
||||
@RunWith(JUnitPlatform.class)
|
||||
public class AppTestE2e {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(AppTestE2e.class);
|
||||
|
||||
static String OPENVIDU_SECRET = "MY_SECRET";
|
||||
static String OPENVIDU_URL = "https://localhost:4443/";
|
||||
static String APP_URL = "https://localhost:5000/";
|
||||
static int NUMBER_OF_ATTEMPTS = 10;
|
||||
static int RECORDING_DURATION = 5; // seconds
|
||||
static double DURATION_THRESHOLD = 10.0; // seconds
|
||||
|
||||
protected WebDriver driver;
|
||||
protected WebDriverWait waiter;
|
||||
private static OpenVidu OV;
|
||||
|
||||
boolean deleteRecordings = true;
|
||||
|
||||
@BeforeAll()
|
||||
static void setupAll() {
|
||||
|
||||
WebDriverManager.chromedriver().setup();
|
||||
|
||||
String appUrl = System.getProperty("APP_URL");
|
||||
if (appUrl != null) {
|
||||
APP_URL = appUrl;
|
||||
}
|
||||
log.info("Using URL {} to connect to openvidu-recording-java app", APP_URL);
|
||||
|
||||
String openviduUrl = System.getProperty("OPENVIDU_URL");
|
||||
if (openviduUrl != null) {
|
||||
OPENVIDU_URL = openviduUrl;
|
||||
}
|
||||
log.info("Using URL {} to connect to openvidu-server", OPENVIDU_URL);
|
||||
|
||||
String openvidusecret = System.getProperty("OPENVIDU_SECRET");
|
||||
if (openvidusecret != null) {
|
||||
OPENVIDU_SECRET = openvidusecret;
|
||||
}
|
||||
log.info("Using secret {} to connect to openvidu-server", OPENVIDU_SECRET);
|
||||
|
||||
String numberOfAttempts = System.getProperty("NUMBER_OF_ATTEMPTS");
|
||||
if (numberOfAttempts != null) {
|
||||
NUMBER_OF_ATTEMPTS = Integer.parseInt(numberOfAttempts);
|
||||
}
|
||||
log.info("Number of attempts: {}", NUMBER_OF_ATTEMPTS);
|
||||
|
||||
String recordingDuration = System.getProperty("RECORDING_DURATION");
|
||||
if (recordingDuration != null) {
|
||||
RECORDING_DURATION = Integer.parseInt(recordingDuration);
|
||||
}
|
||||
log.info("Recording duration: {} s", RECORDING_DURATION);
|
||||
|
||||
String durationThreshold = System.getProperty("DURATION_THRESHOLD");
|
||||
if (durationThreshold != null) {
|
||||
DURATION_THRESHOLD = Double.parseDouble(durationThreshold);
|
||||
}
|
||||
log.info("Duration threshold: {} s", DURATION_THRESHOLD);
|
||||
|
||||
try {
|
||||
log.info("Cleaning folder /opt/openvidu/recordings");
|
||||
FileUtils.cleanDirectory(new File("/opt/openvidu/recordings"));
|
||||
} catch (IOException e) {
|
||||
log.error(e.getMessage());
|
||||
}
|
||||
OV = new OpenVidu(OPENVIDU_URL, OPENVIDU_SECRET);
|
||||
}
|
||||
|
||||
@AfterEach()
|
||||
void dispose() {
|
||||
try {
|
||||
OV.fetch();
|
||||
} catch (OpenViduJavaClientException | OpenViduHttpException e1) {
|
||||
log.error("Error fetching sessions: {}", e1.getMessage());
|
||||
}
|
||||
OV.getActiveSessions().forEach(session -> {
|
||||
try {
|
||||
session.close();
|
||||
log.info("Session {} successfully closed", session.getSessionId());
|
||||
} catch (OpenViduJavaClientException | OpenViduHttpException e2) {
|
||||
log.error("Error closing session: {}", e2.getMessage());
|
||||
}
|
||||
});
|
||||
if (deleteRecordings) {
|
||||
try {
|
||||
OV.listRecordings().forEach(recording -> {
|
||||
if (recording.getStatus().equals(Recording.Status.started)) {
|
||||
try {
|
||||
OV.stopRecording(recording.getId());
|
||||
log.info("Recording {} successfully stopped", recording.getId());
|
||||
} catch (OpenViduJavaClientException | OpenViduHttpException e) {
|
||||
log.error("Error stopping recording {}: {}", recording.getId(), e.getMessage());
|
||||
}
|
||||
}
|
||||
try {
|
||||
OV.deleteRecording(recording.getId());
|
||||
log.info("Recording {} successfully deleted", recording.getId());
|
||||
} catch (OpenViduJavaClientException | OpenViduHttpException e1) {
|
||||
log.error("Error deleting recording {}: {}", recording.getId(), e1.getMessage());
|
||||
}
|
||||
});
|
||||
} catch (OpenViduJavaClientException | OpenViduHttpException e2) {
|
||||
log.error("Error listing recordings: {}", e2.getMessage());
|
||||
}
|
||||
}
|
||||
driver.quit();
|
||||
}
|
||||
|
||||
void setupBrowser(String browser) {
|
||||
DesiredCapabilities capabilities = DesiredCapabilities.chrome();
|
||||
capabilities.setAcceptInsecureCerts(true);
|
||||
|
||||
ChromeOptions options = new ChromeOptions();
|
||||
options.addArguments("--use-fake-ui-for-media-stream");
|
||||
options.addArguments("--use-fake-device-for-media-stream");
|
||||
options.addArguments("--ignore-certificate-errors");
|
||||
options.addArguments("--autoplay-policy=no-user-gesture-required");
|
||||
capabilities.setCapability(ChromeOptions.CAPABILITY, options);
|
||||
|
||||
String REMOTE_URL = System.getProperty("REMOTE_URL_CHROME");
|
||||
if (REMOTE_URL != null) {
|
||||
log.info("Using URL {} to connect to remote web driver", REMOTE_URL);
|
||||
try {
|
||||
this.driver = new RemoteWebDriver(new URL(REMOTE_URL), capabilities);
|
||||
} catch (MalformedURLException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
} else {
|
||||
log.info("Using local web driver");
|
||||
this.driver = new ChromeDriver(options);
|
||||
}
|
||||
this.driver.manage().timeouts().setScriptTimeout(20, TimeUnit.SECONDS);
|
||||
this.waiter = new WebDriverWait(this.driver, 10);
|
||||
this.driver.manage().window().setSize(new Dimension(1920, 1080));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Composed recording test")
|
||||
void composedRecordingTest() throws Exception {
|
||||
|
||||
boolean durationDifferenceAcceptable = true;
|
||||
int i = 0;
|
||||
|
||||
double realTimeDuration = 0;
|
||||
double entityDuration = 0;
|
||||
String videoFile = "";
|
||||
|
||||
setupBrowser("chrome");
|
||||
driver.get(APP_URL);
|
||||
|
||||
driver.findElement(By.id("join-btn")).click();
|
||||
waitUntilEvents("connectionCreated", "videoElementCreated", "accessAllowed", "streamCreated", "streamPlaying");
|
||||
|
||||
driver.findElement(By.id("has-video-checkbox")).click();
|
||||
|
||||
while (durationDifferenceAcceptable && (i < NUMBER_OF_ATTEMPTS)) {
|
||||
|
||||
log.info("----------");
|
||||
log.info("Attempt {}", i + 1);
|
||||
log.info("----------");
|
||||
|
||||
driver.findElement(By.id("buttonStartRecording")).click();
|
||||
|
||||
waitUntilEvents("recordingStarted");
|
||||
|
||||
Thread.sleep(RECORDING_DURATION * 1000);
|
||||
|
||||
driver.findElement(By.id("buttonStopRecording")).click();
|
||||
waitUntilEvents("recordingStopped");
|
||||
|
||||
Recording rec = OV.listRecordings().get(0);
|
||||
|
||||
String extension = rec.getOutputMode().equals(OutputMode.COMPOSED) && rec.hasVideo() ? ".mp4" : ".webm";
|
||||
|
||||
videoFile = "/opt/openvidu/recordings/" + rec.getId() + "/" + rec.getName() + extension;
|
||||
realTimeDuration = getRealTimeDuration(videoFile);
|
||||
entityDuration = rec.getDuration();
|
||||
|
||||
double differenceInDurations = (double) Math.abs(realTimeDuration - entityDuration);
|
||||
|
||||
log.info("Real media file duration: {} s", realTimeDuration);
|
||||
log.info("Entity file duration: {} s", entityDuration);
|
||||
log.info("Difference between durations: {} s", differenceInDurations);
|
||||
|
||||
durationDifferenceAcceptable = differenceInDurations < DURATION_THRESHOLD;
|
||||
i++;
|
||||
|
||||
if (durationDifferenceAcceptable) {
|
||||
// Delete acceptable recording
|
||||
try {
|
||||
OV.deleteRecording(rec.getId());
|
||||
log.info("Recording {} was acceptable and is succesfully deleted", rec.getId());
|
||||
} catch (OpenViduJavaClientException | OpenViduHttpException e) {
|
||||
log.error("Error deleteing acceptable recording {}: {}", rec.getId(), e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
if (i == NUMBER_OF_ATTEMPTS) {
|
||||
log.info("Media file recorded with Composite has not exceeded the duration threshold ({} s) in {} attempts",
|
||||
DURATION_THRESHOLD, NUMBER_OF_ATTEMPTS);
|
||||
} else {
|
||||
log.error(
|
||||
"Real video duration recorded with Composite ({} s) exceeds threshold of {} s compared to entity duration ({} s), in file {}",
|
||||
realTimeDuration, DURATION_THRESHOLD, entityDuration, videoFile);
|
||||
deleteRecordings = false;
|
||||
Assert.fail("Real video duration recorded with Composite (" + realTimeDuration + " s) exceeds threshold of "
|
||||
+ DURATION_THRESHOLD + " s compared to entity duration (" + entityDuration + " s), in file "
|
||||
+ videoFile);
|
||||
}
|
||||
}
|
||||
|
||||
private void waitUntilEvents(String... events) {
|
||||
waiter.until(eventsToBe(events));
|
||||
driver.findElement(By.id("clear-events-btn")).click();
|
||||
waiter.until(ExpectedConditions.textToBePresentInElementLocated(By.id("textarea-events"), ""));
|
||||
}
|
||||
|
||||
private ExpectedCondition<Boolean> eventsToBe(String... events) {
|
||||
final Map<String, Integer> expectedEvents = new HashMap<>();
|
||||
for (String event : events) {
|
||||
Integer currentNumber = expectedEvents.get(event);
|
||||
if (currentNumber == null) {
|
||||
expectedEvents.put(event, 1);
|
||||
} else {
|
||||
expectedEvents.put(event, currentNumber++);
|
||||
}
|
||||
}
|
||||
return new ExpectedCondition<Boolean>() {
|
||||
@Override
|
||||
public Boolean apply(WebDriver driver) {
|
||||
boolean eventsCorrect = true;
|
||||
String events = driver.findElement(By.id("textarea-events")).getText();
|
||||
|
||||
for (Entry<String, Integer> entry : expectedEvents.entrySet()) {
|
||||
eventsCorrect = eventsCorrect
|
||||
&& StringUtils.countMatches(events, entry.getKey()) == entry.getValue();
|
||||
if (!eventsCorrect) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return eventsCorrect;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return " OpenVidu events " + expectedEvents.toString();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private double getRealTimeDuration(String pathToVideoFile) {
|
||||
long time = 0;
|
||||
File source = new File(pathToVideoFile);
|
||||
try {
|
||||
MultimediaObject media = new MultimediaObject(source);
|
||||
MultimediaInfo info = media.getInfo();
|
||||
time = info.getDuration();
|
||||
} catch (EncoderException e) {
|
||||
log.error("Error getting MultimediaInfo from file {}: {}", pathToVideoFile, e.getMessage());
|
||||
}
|
||||
return (double) time / 1000;
|
||||
}
|
||||
|
||||
}
|
||||
14
openvidu-recording-java/src/test/resources/logback.xml
Normal file
14
openvidu-recording-java/src/test/resources/logback.xml
Normal file
@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<configuration>
|
||||
<timestamp key="myTimestamp" timeReference="contextBirth"
|
||||
datePattern="HH-mm-ss" />
|
||||
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<layout class="ch.qos.logback.classic.PatternLayout">
|
||||
<Pattern>[%p] %d [%.12t] %c \(%M\) - %msg%n</Pattern>
|
||||
</layout>
|
||||
</appender>
|
||||
<root>
|
||||
<level value="INFO" />
|
||||
<appender-ref ref="STDOUT" />
|
||||
</root>
|
||||
</configuration>
|
||||
@ -21,27 +21,48 @@ function joinSession() {
|
||||
|
||||
// --- 3) Specify the actions when events take place in the session ---
|
||||
|
||||
session.on('connectionCreated', event => {
|
||||
pushEvent(event);
|
||||
});
|
||||
|
||||
session.on('connectionDestroyed', event => {
|
||||
pushEvent(event);
|
||||
});
|
||||
|
||||
// On every new Stream received...
|
||||
session.on('streamCreated', (event) => {
|
||||
session.on('streamCreated', event => {
|
||||
pushEvent(event);
|
||||
|
||||
// Subscribe to the Stream to receive it
|
||||
// HTML video will be appended to element with 'video-container' id
|
||||
var subscriber = session.subscribe(event.stream, 'video-container');
|
||||
|
||||
// When the HTML video has been appended to DOM...
|
||||
subscriber.on('videoElementCreated', (event) => {
|
||||
subscriber.on('videoElementCreated', event => {
|
||||
pushEvent(event);
|
||||
// Add a new HTML element for the user's name and nickname over its video
|
||||
updateNumVideos(1);
|
||||
});
|
||||
|
||||
// When the HTML video has been appended to DOM...
|
||||
subscriber.on('videoElementDestroyed', (event) => {
|
||||
subscriber.on('videoElementDestroyed', event => {
|
||||
pushEvent(event);
|
||||
// Add a new HTML element for the user's name and nickname over its video
|
||||
updateNumVideos(-1);
|
||||
});
|
||||
|
||||
// When the subscriber stream has started playing media...
|
||||
subscriber.on('streamPlaying', event => {
|
||||
pushEvent(event);
|
||||
});
|
||||
});
|
||||
|
||||
session.on('sessionDisconnected', (event) => {
|
||||
session.on('streamDestroyed', event => {
|
||||
pushEvent(event);
|
||||
});
|
||||
|
||||
session.on('sessionDisconnected', event => {
|
||||
pushEvent(event);
|
||||
if (event.reason !== 'disconnect') {
|
||||
removeUser();
|
||||
}
|
||||
@ -53,6 +74,14 @@ function joinSession() {
|
||||
}
|
||||
});
|
||||
|
||||
session.on('recordingStarted', event => {
|
||||
pushEvent(event);
|
||||
});
|
||||
|
||||
session.on('recordingStopped', event => {
|
||||
pushEvent(event);
|
||||
});
|
||||
|
||||
// --- 4) Connect to the session passing the retrieved token and some more data from
|
||||
// the client (in this case a JSON with the nickname chosen by the user) ---
|
||||
|
||||
@ -70,28 +99,62 @@ function joinSession() {
|
||||
var publisher = OV.initPublisher('video-container', {
|
||||
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
|
||||
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
|
||||
});
|
||||
|
||||
// --- 7) Specify the actions when events take place in our publisher ---
|
||||
|
||||
// When the publisher stream has started playing media...
|
||||
publisher.on('accessAllowed', event => {
|
||||
pushEvent({
|
||||
type: 'accessAllowed'
|
||||
});
|
||||
});
|
||||
|
||||
publisher.on('accessDenied', event => {
|
||||
pushEvent(event);
|
||||
});
|
||||
|
||||
publisher.on('accessDialogOpened', event => {
|
||||
pushEvent({
|
||||
type: 'accessDialogOpened'
|
||||
});
|
||||
});
|
||||
|
||||
publisher.on('accessDialogClosed', event => {
|
||||
pushEvent({
|
||||
type: 'accessDialogClosed'
|
||||
});
|
||||
});
|
||||
|
||||
// When the publisher stream has started playing media...
|
||||
publisher.on('streamCreated', event => {
|
||||
pushEvent(event);
|
||||
});
|
||||
|
||||
// When our HTML video has been added to DOM...
|
||||
publisher.on('videoElementCreated', (event) => {
|
||||
publisher.on('videoElementCreated', event => {
|
||||
pushEvent(event);
|
||||
updateNumVideos(1);
|
||||
$(event.element).prop('muted', true); // Mute local video
|
||||
});
|
||||
|
||||
// When the HTML video has been appended to DOM...
|
||||
publisher.on('videoElementDestroyed', (event) => {
|
||||
publisher.on('videoElementDestroyed', event => {
|
||||
pushEvent(event);
|
||||
// Add a new HTML element for the user's name and nickname over its video
|
||||
updateNumVideos(-1);
|
||||
});
|
||||
|
||||
// When the publisher stream has started playing media...
|
||||
publisher.on('streamPlaying', event => {
|
||||
pushEvent(event);
|
||||
});
|
||||
|
||||
// --- 8) Publish your stream ---
|
||||
|
||||
@ -128,8 +191,8 @@ function getToken(callback) {
|
||||
sessionName: sessionName
|
||||
},
|
||||
'Request of TOKEN gone WRONG:',
|
||||
(response) => {
|
||||
token = response[0]; // Get token from response
|
||||
res => {
|
||||
token = res[0]; // Get token from response
|
||||
console.warn('Request of TOKEN gone WELL (TOKEN:' + token + ')');
|
||||
callback(token); // Continue the join operation
|
||||
}
|
||||
@ -144,7 +207,7 @@ function removeUser() {
|
||||
token: token
|
||||
},
|
||||
'User couldn\'t be removed from session',
|
||||
(response) => {
|
||||
res => {
|
||||
console.warn("You have been removed from session " + sessionName);
|
||||
}
|
||||
);
|
||||
@ -157,7 +220,7 @@ function closeSession() {
|
||||
sessionName: sessionName
|
||||
},
|
||||
'Session couldn\'t be closed',
|
||||
(response) => {
|
||||
res => {
|
||||
console.warn("Session " + sessionName + " has been closed");
|
||||
}
|
||||
);
|
||||
@ -170,9 +233,9 @@ function fetchInfo() {
|
||||
sessionName: sessionName
|
||||
},
|
||||
'Session couldn\'t be fetched',
|
||||
(response) => {
|
||||
res => {
|
||||
console.warn("Session info has been fetched");
|
||||
$('#text-area').text(JSON.stringify(response, null, "\t"));
|
||||
$('#textarea-http').text(JSON.stringify(res, null, "\t"));
|
||||
}
|
||||
);
|
||||
}
|
||||
@ -182,9 +245,9 @@ function fetchAll() {
|
||||
'GET',
|
||||
'api/fetch-all', {},
|
||||
'All session info couldn\'t be fetched',
|
||||
(response) => {
|
||||
res => {
|
||||
console.warn("All session info has been fetched");
|
||||
$('#text-area').text(JSON.stringify(response, null, "\t"));
|
||||
$('#textarea-http').text(JSON.stringify(res, null, "\t"));
|
||||
}
|
||||
);
|
||||
}
|
||||
@ -197,7 +260,7 @@ function forceDisconnect() {
|
||||
connectionId: document.getElementById('forceValue').value
|
||||
},
|
||||
'Connection couldn\'t be closed',
|
||||
(response) => {
|
||||
res => {
|
||||
console.warn("Connection has been closed");
|
||||
}
|
||||
);
|
||||
@ -211,14 +274,14 @@ function forceUnpublish() {
|
||||
streamId: document.getElementById('forceValue').value
|
||||
},
|
||||
'Stream couldn\'t be closed',
|
||||
(response) => {
|
||||
res => {
|
||||
console.warn("Stream has been closed");
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function httpRequest(method, url, body, errorMsg, callback) {
|
||||
$('#text-area').text('');
|
||||
$('#textarea-http').text('');
|
||||
var http = new XMLHttpRequest();
|
||||
http.open(method, url, true);
|
||||
http.setRequestHeader('Content-type', 'application/json');
|
||||
@ -236,16 +299,16 @@ function httpRequest(method, url, body, errorMsg, callback) {
|
||||
} else {
|
||||
console.warn(errorMsg + ' (' + http.status + ')');
|
||||
console.warn(http.responseText);
|
||||
$('#text-area').text(errorMsg + ": HTTP " + http.status + " (" + http.responseText + ")");
|
||||
$('#textarea-http').text(errorMsg + ": HTTP " + http.status + " (" + http.responseText + ")");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function startRecording() {
|
||||
var outputMode = document.querySelector('input[name="outputMode"]:checked').value;
|
||||
var hasAudio = !!document.querySelector("#has-audio-checkbox:checked");
|
||||
var hasVideo = !!document.querySelector("#has-video-checkbox:checked");
|
||||
var outputMode = $('input[name=outputMode]:checked').val();
|
||||
var hasAudio = $('#has-audio-checkbox').prop('checked');
|
||||
var hasVideo = $('#has-video-checkbox').prop('checked');
|
||||
httpRequest(
|
||||
'POST',
|
||||
'api/recording/start', {
|
||||
@ -255,11 +318,11 @@ function startRecording() {
|
||||
hasVideo: hasVideo
|
||||
},
|
||||
'Start recording WRONG',
|
||||
(response) => {
|
||||
console.log(response);
|
||||
document.getElementById('forceRecordingId').value = response.id;
|
||||
res => {
|
||||
console.log(res);
|
||||
document.getElementById('forceRecordingId').value = res.id;
|
||||
checkBtnsRecordings();
|
||||
$('#text-area').text(JSON.stringify(response, null, "\t"));
|
||||
$('#textarea-http').text(JSON.stringify(res, null, "\t"));
|
||||
}
|
||||
);
|
||||
}
|
||||
@ -272,9 +335,9 @@ function stopRecording() {
|
||||
recording: forceRecordingId
|
||||
},
|
||||
'Stop recording WRONG',
|
||||
(response) => {
|
||||
console.log(response);
|
||||
$('#text-area').text(JSON.stringify(response, null, "\t"));
|
||||
res => {
|
||||
console.log(res);
|
||||
$('#textarea-http').text(JSON.stringify(res, null, "\t"));
|
||||
}
|
||||
);
|
||||
}
|
||||
@ -287,9 +350,9 @@ function deleteRecording() {
|
||||
recording: forceRecordingId
|
||||
},
|
||||
'Delete recording WRONG',
|
||||
() => {
|
||||
res => {
|
||||
console.log("DELETE ok");
|
||||
$('#text-area').text("DELETE ok");
|
||||
$('#textarea-http').text("DELETE ok");
|
||||
}
|
||||
);
|
||||
}
|
||||
@ -300,9 +363,9 @@ function getRecording() {
|
||||
'GET',
|
||||
'api/recording/get/' + forceRecordingId, {},
|
||||
'Get recording WRONG',
|
||||
(response) => {
|
||||
console.log(response);
|
||||
$('#text-area').text(JSON.stringify(response, null, "\t"));
|
||||
res => {
|
||||
console.log(res);
|
||||
$('#textarea-http').text(JSON.stringify(res, null, "\t"));
|
||||
}
|
||||
);
|
||||
}
|
||||
@ -312,9 +375,9 @@ function listRecordings() {
|
||||
'GET',
|
||||
'api/recording/list', {},
|
||||
'List recordings WRONG',
|
||||
(response) => {
|
||||
console.log(response);
|
||||
$('#text-area').text(JSON.stringify(response, null, "\t"));
|
||||
res => {
|
||||
console.log(res);
|
||||
$('#textarea-http').text(JSON.stringify(res, null, "\t"));
|
||||
}
|
||||
);
|
||||
}
|
||||
@ -325,6 +388,8 @@ function listRecordings() {
|
||||
|
||||
/* APPLICATION BROWSER METHODS */
|
||||
|
||||
events = '';
|
||||
|
||||
window.onbeforeunload = function () { // Gracefully leave session
|
||||
if (session) {
|
||||
removeUser();
|
||||
@ -373,4 +438,18 @@ function checkBtnsRecordings() {
|
||||
}
|
||||
}
|
||||
|
||||
function pushEvent(event) {
|
||||
events += (!events ? '' : '\n') + event.type;
|
||||
$('#textarea-events').text(events);
|
||||
}
|
||||
|
||||
function clearHttpTextarea() {
|
||||
$('#textarea-http').text('');
|
||||
}
|
||||
|
||||
function clearEventsTextarea() {
|
||||
$('#textarea-events').text('');
|
||||
events = '';
|
||||
}
|
||||
|
||||
/* APPLICATION BROWSER METHODS */
|
||||
@ -60,7 +60,7 @@
|
||||
<input class="form-control" type="text" id="sessionName" value="SessionA" required>
|
||||
</p>
|
||||
<p class="text-center">
|
||||
<button class="btn btn-lg btn-success" onclick="joinSession()">Join!</button>
|
||||
<button class="btn btn-lg btn-success" id="join-btn" onclick="joinSession()">Join!</button>
|
||||
</p>
|
||||
</form>
|
||||
<hr>
|
||||
@ -89,10 +89,10 @@
|
||||
<input class="btn btn-md" type="button" id="buttonStartRecording" onmouseup="startRecording()" value="Start recording">
|
||||
<form>
|
||||
<label class="radio-inline">
|
||||
<input type="radio" name="outputMode" value="COMPOSED" checked>COMPOSED
|
||||
<input type="radio" name="outputMode" value="COMPOSED" id="radio-composed" checked>COMPOSED
|
||||
</label>
|
||||
<label class="radio-inline">
|
||||
<input type="radio" name="outputMode" value="INDIVIDUAL">INDIVIDUAL
|
||||
<input type="radio" name="outputMode" value="INDIVIDUAL" id="radio-individual">INDIVIDUAL
|
||||
</label>
|
||||
</form>
|
||||
<form>
|
||||
@ -115,7 +115,16 @@
|
||||
disabled>
|
||||
<input class="form-control" id="forceRecordingId" type="text" onkeyup="checkBtnsRecordings()">
|
||||
</div>
|
||||
<textarea id="text-area" readonly="true" class="form-control" name="comment">HTTP responses...</textarea>
|
||||
<div class="textarea-container" id="textarea-http-container">
|
||||
<button type="button" class="btn btn-outline-secondary" id="clear-http-btn" onclick="clearHttpTextarea()">Clear</button>
|
||||
<span>HTTP responses</span>
|
||||
<textarea id="textarea-http" readonly="true" class="form-control" name="textarea-http"></textarea>
|
||||
</div>
|
||||
<div class="textarea-container" id="textarea-events-container">
|
||||
<button type="button" class="btn btn-outline-secondary" id="clear-events-btn" onclick="clearEventsTextarea()">Clear</button>
|
||||
<span>OpenVidu events</span>
|
||||
<textarea id="textarea-events" readonly="true" class="form-control" name="textarea-events"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -250,21 +250,21 @@ a:hover .demo-logo {
|
||||
|
||||
#video-container {
|
||||
width: 100%;
|
||||
max-height: 45%;
|
||||
display: inline-block;
|
||||
max-height: 42%;
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#video-container video.two {
|
||||
width: 50%;
|
||||
max-width: 50%;
|
||||
}
|
||||
|
||||
#video-container video.three {
|
||||
width: 33.33%;
|
||||
max-width: 33.33%;
|
||||
}
|
||||
|
||||
#video-container video.four {
|
||||
width: 25%;
|
||||
max-width: 25%;
|
||||
}
|
||||
|
||||
#video-container div {
|
||||
@ -294,10 +294,10 @@ a:hover .demo-logo {
|
||||
}
|
||||
|
||||
video {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
max-height: 100%;
|
||||
object-fit: cover;
|
||||
width: auto;
|
||||
height: auto;
|
||||
max-height: 100%;
|
||||
object-fit: scale-down;
|
||||
}
|
||||
|
||||
#session {
|
||||
@ -353,11 +353,11 @@ table i {
|
||||
}
|
||||
|
||||
#recording-btns {
|
||||
display: flow-root;
|
||||
padding-left: 15px;
|
||||
padding-top: 20px;
|
||||
width: 100%;
|
||||
height: 40%;
|
||||
display: inline-block;
|
||||
padding-left: 15px;
|
||||
padding-top: 20px;
|
||||
width: 100%;
|
||||
height: 40%;
|
||||
}
|
||||
|
||||
#recording-btns .btns {
|
||||
@ -371,19 +371,57 @@ table i {
|
||||
|
||||
#recording-btns .btns form {
|
||||
display: inline;
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
#recording-btns #text-area {
|
||||
display: inline;
|
||||
width: 100%;
|
||||
#recording-btns textarea {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.textarea-container {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
height: 74%;
|
||||
margin-top: 20px;
|
||||
resize: none;
|
||||
}
|
||||
|
||||
#textarea-http-container {
|
||||
width: 69%;
|
||||
}
|
||||
|
||||
#textarea-events-container {
|
||||
width: 29%;
|
||||
}
|
||||
|
||||
.textarea-container button {
|
||||
position: absolute;
|
||||
top: 1px;
|
||||
right: 1px;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.textarea-container span {
|
||||
position: absolute;
|
||||
bottom: 1px;
|
||||
right: 1px;
|
||||
padding: 3px;
|
||||
border-bottom-right-radius: 4px;
|
||||
z-index: 1;
|
||||
color: #a5a5a5;
|
||||
background-color: #ededee;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.textarea-container textarea {
|
||||
height: 100%;
|
||||
resize: none;
|
||||
}
|
||||
|
||||
.vertical-separator-bottom {
|
||||
width: 2px;
|
||||
height: 34px;
|
||||
display: inline;
|
||||
display: inline-block;
|
||||
background-color: #cbcbcb;
|
||||
margin: 0 8px 0 8px;
|
||||
margin-bottom: -12px;
|
||||
|
||||
@ -1,9 +1,7 @@
|
||||
/* CONFIGURATION */
|
||||
|
||||
var OpenVidu = require('openvidu-node-client').OpenVidu;
|
||||
var Session = require('openvidu-node-client').Session;
|
||||
var OpenViduRole = require('openvidu-node-client').OpenViduRole;
|
||||
var TokenOptions = require('openvidu-node-client').TokenOptions;
|
||||
|
||||
// Check launch arguments: must receive openvidu-server URL and the secret
|
||||
if (process.argv.length != 4) {
|
||||
@ -93,41 +91,50 @@ app.post('/api/get-token', function (req, res) {
|
||||
})
|
||||
.catch(error => {
|
||||
console.error(error);
|
||||
if (error.message === "404") {
|
||||
delete mapSessions[sessionName];
|
||||
delete mapSessionNamesTokens[sessionName];
|
||||
newSession(sessionName, tokenOptions, res);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// New session
|
||||
console.log('New session ' + sessionName);
|
||||
|
||||
// Create a new OpenVidu Session asynchronously
|
||||
OV.createSession()
|
||||
.then(session => {
|
||||
// Store the new Session in the collection of Sessions
|
||||
mapSessions[sessionName] = session;
|
||||
// Store a new empty array in the collection of tokens
|
||||
mapSessionNamesTokens[sessionName] = [];
|
||||
|
||||
// Generate a new token asynchronously with the recently created tokenOptions
|
||||
session.generateToken(tokenOptions)
|
||||
.then(token => {
|
||||
|
||||
// Store the new token in the collection of tokens
|
||||
mapSessionNamesTokens[sessionName].push(token);
|
||||
|
||||
// Return the Token to the client
|
||||
res.status(200).send({
|
||||
0: token
|
||||
});
|
||||
})
|
||||
.catch(error => {
|
||||
console.error(error);
|
||||
});
|
||||
})
|
||||
.catch(error => {
|
||||
console.error(error);
|
||||
});
|
||||
newSession(sessionName, tokenOptions, res);
|
||||
}
|
||||
});
|
||||
|
||||
function newSession(sessionName, tokenOptions, res) {
|
||||
// New session
|
||||
console.log('New session ' + sessionName);
|
||||
|
||||
// Create a new OpenVidu Session asynchronously
|
||||
OV.createSession()
|
||||
.then(session => {
|
||||
// Store the new Session in the collection of Sessions
|
||||
mapSessions[sessionName] = session;
|
||||
// Store a new empty array in the collection of tokens
|
||||
mapSessionNamesTokens[sessionName] = [];
|
||||
|
||||
// Generate a new token asynchronously with the recently created tokenOptions
|
||||
session.generateToken(tokenOptions)
|
||||
.then(token => {
|
||||
|
||||
// Store the new token in the collection of tokens
|
||||
mapSessionNamesTokens[sessionName].push(token);
|
||||
|
||||
// Return the Token to the client
|
||||
res.status(200).send({
|
||||
0: token
|
||||
});
|
||||
})
|
||||
.catch(error => {
|
||||
console.error(error);
|
||||
});
|
||||
})
|
||||
.catch(error => {
|
||||
console.error(error);
|
||||
});
|
||||
}
|
||||
|
||||
// Remove user from session
|
||||
app.post('/api/remove-user', function (req, res) {
|
||||
// Retrieve params from POST body
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user