Migrated openvidu-recording-node to Livekit
This commit is contained in:
parent
0f1fea4a0d
commit
8d46598f48
4
openvidu-recording-node/.gitignore
vendored
4
openvidu-recording-node/.gitignore
vendored
@ -58,3 +58,7 @@ typings/
|
||||
.env
|
||||
|
||||
.vscode/
|
||||
*.mp4
|
||||
*.mov
|
||||
*.ogg
|
||||
|
||||
|
||||
790
openvidu-recording-node/package-lock.json
generated
790
openvidu-recording-node/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -4,6 +4,7 @@
|
||||
"description": "",
|
||||
"main": "server.js",
|
||||
"scripts": {
|
||||
"start": "node server.js",
|
||||
"test": "ng test"
|
||||
},
|
||||
"repository": {
|
||||
@ -17,8 +18,10 @@
|
||||
},
|
||||
"homepage": "https://github.com/OpenVidu/openvidu-tutorials#readme",
|
||||
"dependencies": {
|
||||
"body-parser": "1.19.0",
|
||||
"express": "4.17.1",
|
||||
"openvidu-node-client": "2.27.0"
|
||||
"body-parser": "1.20.0",
|
||||
"cors": "2.8.5",
|
||||
"dotenv": "16.0.1",
|
||||
"express": "4.18.1",
|
||||
"livekit-server-sdk": "1.2.2"
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,415 +1,289 @@
|
||||
var OV;
|
||||
var session;
|
||||
|
||||
var sessionName;
|
||||
var LivekitClient = window.LivekitClient;
|
||||
var room;
|
||||
var myRoomName;
|
||||
var token;
|
||||
var nickname;
|
||||
var numVideos = 0;
|
||||
|
||||
var localVideoPublication;
|
||||
var localAudioPublication;
|
||||
|
||||
/* OPENVIDU METHODS */
|
||||
|
||||
function joinSession() {
|
||||
|
||||
function joinRoom() {
|
||||
// --- 0) Change the button ---
|
||||
|
||||
document.getElementById("join-btn").disabled = true;
|
||||
document.getElementById("join-btn").innerHTML = "Joining...";
|
||||
|
||||
getToken(function () {
|
||||
document.getElementById('join-btn').disabled = true;
|
||||
document.getElementById('join-btn').innerHTML = 'Joining...';
|
||||
const myParticipantName = `Participant${Math.floor(Math.random() * 100)}`;
|
||||
const myRoomName = $('#roomName').val();
|
||||
|
||||
// --- 1) Get an OpenVidu object ---
|
||||
room = new LivekitClient.Room();
|
||||
|
||||
OV = new OpenVidu();
|
||||
|
||||
// --- 2) Init a session ---
|
||||
|
||||
session = OV.initSession();
|
||||
|
||||
// --- 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 => {
|
||||
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 => {
|
||||
pushEvent(event);
|
||||
// Add a new HTML element for the user's name and nickname over its video
|
||||
room.on(
|
||||
LivekitClient.RoomEvent.TrackSubscribed,
|
||||
(track, publication, participant) => {
|
||||
const element = track.attach();
|
||||
element.id = track.sid;
|
||||
document.getElementById('video-container').appendChild(element);
|
||||
if (track.kind === 'video') {
|
||||
var audioTrackId;
|
||||
var videoTrackId;
|
||||
participant.getTracks().forEach((track) => {
|
||||
if (track.kind === 'audio') {
|
||||
audioTrackId = track.trackInfo.sid;
|
||||
} else if (track.kind === 'video') {
|
||||
videoTrackId = track.trackInfo.sid;
|
||||
}
|
||||
});
|
||||
addIndividualRecordingButton(element.id, videoTrackId, audioTrackId);
|
||||
updateNumVideos(1);
|
||||
});
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// When the HTML video has been appended to DOM...
|
||||
subscriber.on('videoElementDestroyed', event => {
|
||||
pushEvent(event);
|
||||
// Add a new HTML element for the user's name and nickname over its video
|
||||
// On every new Track destroyed...
|
||||
room.on(
|
||||
LivekitClient.RoomEvent.TrackUnsubscribed,
|
||||
(track, publication, participant) => {
|
||||
track.detach();
|
||||
document.getElementById(track.sid)?.remove();
|
||||
if (track.kind === 'video') {
|
||||
// removeUserData(participant);
|
||||
updateNumVideos(-1);
|
||||
});
|
||||
|
||||
// When the subscriber stream has started playing media...
|
||||
subscriber.on('streamPlaying', event => {
|
||||
pushEvent(event);
|
||||
});
|
||||
});
|
||||
|
||||
session.on('streamDestroyed', event => {
|
||||
pushEvent(event);
|
||||
});
|
||||
|
||||
session.on('sessionDisconnected', event => {
|
||||
pushEvent(event);
|
||||
if (event.reason !== 'disconnect') {
|
||||
removeUser();
|
||||
}
|
||||
if (event.reason !== 'sessionClosedByServer') {
|
||||
session = null;
|
||||
numVideos = 0;
|
||||
$('#join').show();
|
||||
$('#session').hide();
|
||||
}
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
session.on('recordingStarted', event => {
|
||||
pushEvent(event);
|
||||
});
|
||||
room.on(LivekitClient.RoomEvent.RecordingStatusChanged, (isRecording) => {
|
||||
console.log('Recording status changed: ' + status);
|
||||
if (!isRecording) {
|
||||
listRecordings();
|
||||
}
|
||||
});
|
||||
|
||||
session.on('recordingStopped', event => {
|
||||
pushEvent(event);
|
||||
});
|
||||
getToken(myRoomName, myParticipantName).then(async (token) => {
|
||||
const livekitUrl = getLivekitUrlFromMetadata(token);
|
||||
|
||||
// On every asynchronous exception...
|
||||
session.on('exception', (exception) => {
|
||||
console.warn(exception);
|
||||
});
|
||||
try {
|
||||
await room.connect(livekitUrl, token);
|
||||
|
||||
// --- 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) ---
|
||||
var participantName = $('#user').val();
|
||||
$('#room-title').text(myRoomName);
|
||||
$('#join').hide();
|
||||
$('#room').show();
|
||||
|
||||
session.connect(token)
|
||||
.then(() => {
|
||||
const [audioPublication, videoPublication] = await Promise.all([
|
||||
room.localParticipant.setMicrophoneEnabled(true),
|
||||
room.localParticipant.setCameraEnabled(true),
|
||||
]);
|
||||
localVideoPublication = videoPublication;
|
||||
localAudioPublication = audioPublication;
|
||||
|
||||
// --- 5) Set page layout for active call ---
|
||||
|
||||
$('#session-title').text(sessionName);
|
||||
$('#join').hide();
|
||||
$('#session').show();
|
||||
|
||||
// --- 6) Get your own camera stream ---
|
||||
|
||||
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
|
||||
});
|
||||
|
||||
// --- 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 => {
|
||||
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 => {
|
||||
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 ---
|
||||
|
||||
session.publish(publisher);
|
||||
|
||||
})
|
||||
.catch(error => {
|
||||
console.warn('There was an error connecting to the session:', error.code, error.message);
|
||||
enableBtn();
|
||||
});
|
||||
console.log('Connected to room ' + myRoomName);
|
||||
const element = videoPublication.track.attach();
|
||||
element.id = videoPublication.track.sid;
|
||||
document.getElementById('video-container').appendChild(element);
|
||||
addIndividualRecordingButton(
|
||||
element.id,
|
||||
videoPublication.track.sid,
|
||||
audioPublication.track.sid
|
||||
);
|
||||
updateNumVideos(1);
|
||||
} catch (error) {
|
||||
console.warn(
|
||||
'There was an error connecting to the room:',
|
||||
error.code,
|
||||
error.message
|
||||
);
|
||||
enableBtn();
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
function leaveSession() {
|
||||
function leaveRoom() {
|
||||
room.disconnect();
|
||||
room = null;
|
||||
|
||||
$('#video-container').empty();
|
||||
numVideos = 0;
|
||||
|
||||
$('#join').show();
|
||||
$('#room').hide();
|
||||
|
||||
// --- 9) Leave the session by calling 'disconnect' method over the Session object ---
|
||||
session.disconnect();
|
||||
enableBtn();
|
||||
|
||||
}
|
||||
|
||||
/* OPENVIDU METHODS */
|
||||
|
||||
function enableBtn (){
|
||||
document.getElementById("join-btn").disabled = false;
|
||||
document.getElementById("join-btn").innerHTML = "Join!";
|
||||
function enableBtn() {
|
||||
document.getElementById('join-btn').disabled = false;
|
||||
document.getElementById('join-btn').innerHTML = 'Join!';
|
||||
}
|
||||
|
||||
/* APPLICATION REST METHODS */
|
||||
|
||||
function getToken(callback) {
|
||||
sessionName = $("#sessionName").val(); // Video-call chosen by the user
|
||||
|
||||
httpRequest(
|
||||
'POST',
|
||||
'recording-node/api/get-token', {
|
||||
sessionName: sessionName
|
||||
},
|
||||
'Request of TOKEN gone WRONG:',
|
||||
res => {
|
||||
token = res[0]; // Get token from response
|
||||
console.warn('Request of TOKEN gone WELL (TOKEN:' + token + ')');
|
||||
callback(token); // Continue the join operation
|
||||
}
|
||||
);
|
||||
function getToken(roomName, participantName) {
|
||||
return new Promise((resolve, reject) => {
|
||||
// Video-call chosen by the user
|
||||
httpRequest(
|
||||
'POST',
|
||||
'token',
|
||||
{ roomName, participantName },
|
||||
'Error generating token',
|
||||
(response) => resolve(response.token)
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
function removeUser() {
|
||||
httpRequest(
|
||||
'POST',
|
||||
'recording-node/api/remove-user', {
|
||||
sessionName: sessionName,
|
||||
token: token
|
||||
},
|
||||
'User couldn\'t be removed from session',
|
||||
res => {
|
||||
console.warn("You have been removed from session " + sessionName);
|
||||
}
|
||||
);
|
||||
}
|
||||
async function httpRequest(method, url, body, errorMsg, successCallback) {
|
||||
try {
|
||||
const response = await fetch(url, {
|
||||
method,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: method === 'GET' ? undefined : JSON.stringify(body),
|
||||
});
|
||||
|
||||
function closeSession() {
|
||||
httpRequest(
|
||||
'DELETE',
|
||||
'recording-node/api/close-session', {
|
||||
sessionName: sessionName
|
||||
},
|
||||
'Session couldn\'t be closed',
|
||||
res => {
|
||||
console.warn("Session " + sessionName + " has been closed");
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function fetchInfo() {
|
||||
httpRequest(
|
||||
'POST',
|
||||
'recording-node/api/fetch-info', {
|
||||
sessionName: sessionName
|
||||
},
|
||||
'Session couldn\'t be fetched',
|
||||
res => {
|
||||
console.warn("Session info has been fetched");
|
||||
$('#textarea-http').text(JSON.stringify(res, null, "\t"));
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function fetchAll() {
|
||||
httpRequest(
|
||||
'GET',
|
||||
'recording-node/api/fetch-all', {},
|
||||
'All session info couldn\'t be fetched',
|
||||
res => {
|
||||
console.warn("All session info has been fetched");
|
||||
$('#textarea-http').text(JSON.stringify(res, null, "\t"));
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function forceDisconnect() {
|
||||
httpRequest(
|
||||
'DELETE',
|
||||
'recording-node/api/force-disconnect', {
|
||||
sessionName: sessionName,
|
||||
connectionId: document.getElementById('forceValue').value
|
||||
},
|
||||
'Connection couldn\'t be closed',
|
||||
res => {
|
||||
console.warn("Connection has been closed");
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function forceUnpublish() {
|
||||
httpRequest(
|
||||
'DELETE',
|
||||
'recording-node/api/force-unpublish', {
|
||||
sessionName: sessionName,
|
||||
streamId: document.getElementById('forceValue').value
|
||||
},
|
||||
'Stream couldn\'t be closed',
|
||||
res => {
|
||||
console.warn("Stream has been closed");
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function httpRequest(method, url, body, errorMsg, callback) {
|
||||
$('#textarea-http').text('');
|
||||
var http = new XMLHttpRequest();
|
||||
http.open(method, url, true);
|
||||
http.setRequestHeader('Content-type', 'application/json');
|
||||
http.addEventListener('readystatechange', processRequest, false);
|
||||
http.send(JSON.stringify(body));
|
||||
|
||||
function processRequest() {
|
||||
if (http.readyState == 4) {
|
||||
if (http.status == 200) {
|
||||
try {
|
||||
callback(JSON.parse(http.responseText));
|
||||
} catch (e) {
|
||||
callback(e);
|
||||
}
|
||||
} else {
|
||||
console.warn(errorMsg + ' (' + http.status + ')');
|
||||
console.warn(http.responseText);
|
||||
$('#textarea-http').text(errorMsg + ": HTTP " + http.status + " (" + http.responseText + ")");
|
||||
}
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
successCallback(data);
|
||||
} else {
|
||||
console.warn(errorMsg);
|
||||
console.warn('Error: ' + response.statusText);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
function startRecording() {
|
||||
var outputMode = $('input[name=outputMode]:checked').val();
|
||||
function startComposedRecording() {
|
||||
var hasAudio = $('#has-audio-checkbox').prop('checked');
|
||||
var hasVideo = $('#has-video-checkbox').prop('checked');
|
||||
|
||||
httpRequest(
|
||||
'POST',
|
||||
'recording-node/api/recording/start', {
|
||||
session: session.sessionId,
|
||||
outputMode: outputMode,
|
||||
hasAudio: hasAudio,
|
||||
hasVideo: hasVideo
|
||||
'recordings/start',
|
||||
{
|
||||
roomName: room.roomInfo.name,
|
||||
outputMode: 'COMPOSED',
|
||||
videoOnly: hasVideo && !hasAudio,
|
||||
audioOnly: hasAudio && !hasVideo,
|
||||
},
|
||||
'Start recording WRONG',
|
||||
res => {
|
||||
(res) => {
|
||||
console.log(res);
|
||||
document.getElementById('forceRecordingId').value = res.id;
|
||||
checkBtnsRecordings();
|
||||
$('#textarea-http').text(JSON.stringify(res, null, "\t"));
|
||||
$('#textarea-http').text(JSON.stringify(res, null, '\t'));
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function stopRecording() {
|
||||
var forceRecordingId = document.getElementById('forceRecordingId').value;
|
||||
function startIndividualRecording(videoTrackId, audioTrackId) {
|
||||
return new Promise((resolve, reject) => {
|
||||
httpRequest(
|
||||
'POST',
|
||||
'recordings/start',
|
||||
{
|
||||
roomName: room.roomInfo.name,
|
||||
outputMode: 'INDIVIDUAL',
|
||||
audioTrackId,
|
||||
videoTrackId,
|
||||
},
|
||||
'Start recording WRONG',
|
||||
(res) => {
|
||||
console.log(res);
|
||||
$('#textarea-http').text(JSON.stringify(res.info, null, '\t'));
|
||||
resolve(res);
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
function stopRecording(id) {
|
||||
var forceRecordingId = id ? id : $('#forceRecordingId').val();
|
||||
httpRequest(
|
||||
'POST',
|
||||
'recording-node/api/recording/stop', {
|
||||
recording: forceRecordingId
|
||||
'recordings/stop',
|
||||
{
|
||||
recordingId: forceRecordingId,
|
||||
},
|
||||
'Stop recording WRONG',
|
||||
res => {
|
||||
(res) => {
|
||||
console.log(res);
|
||||
$('#textarea-http').text(JSON.stringify(res, null, "\t"));
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function deleteRecording() {
|
||||
var forceRecordingId = document.getElementById('forceRecordingId').value;
|
||||
httpRequest(
|
||||
'DELETE',
|
||||
'recording-node/api/recording/delete', {
|
||||
recording: forceRecordingId
|
||||
},
|
||||
'Delete recording WRONG',
|
||||
res => {
|
||||
console.log("DELETE ok");
|
||||
$('#textarea-http').text("DELETE ok");
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function getRecording() {
|
||||
var forceRecordingId = document.getElementById('forceRecordingId').value;
|
||||
httpRequest(
|
||||
'GET',
|
||||
'recording-node/api/recording/get/' + forceRecordingId, {},
|
||||
'Get recording WRONG',
|
||||
res => {
|
||||
console.log(res);
|
||||
$('#textarea-http').text(JSON.stringify(res, null, "\t"));
|
||||
$('#forceRecordingId').val('');
|
||||
$('#textarea-http').text(JSON.stringify(res.info, null, '\t'));
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function listRecordings() {
|
||||
httpRequest(
|
||||
'GET',
|
||||
'recording-node/api/recording/list', {},
|
||||
'List recordings WRONG',
|
||||
res => {
|
||||
console.log(res);
|
||||
$('#textarea-http').text(JSON.stringify(res, null, "\t"));
|
||||
httpRequest('GET', 'recordings/list', {}, 'List recordings WRONG', (res) => {
|
||||
console.log(res);
|
||||
$('#recording-list').empty();
|
||||
if (res.recordings && res.recordings.length > 0) {
|
||||
res.recordings.forEach((recording) => {
|
||||
var li = document.createElement('li');
|
||||
var a = document.createElement('a');
|
||||
a.href = recording.path;
|
||||
a.target = '_blank';
|
||||
a.appendChild(document.createTextNode(recording.name));
|
||||
li.appendChild(a);
|
||||
$('#recording-list').append(li);
|
||||
});
|
||||
$('#delete-recordings-btn').prop('disabled', res.recordings.length === 0);
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
function deleteRecordings() {
|
||||
httpRequest('DELETE', 'recordings', {}, 'Delete recordings WRONG', (res) => {
|
||||
console.log(res);
|
||||
$('#recording-list').empty();
|
||||
$('#delete-recordings-btn').prop('disabled', true);
|
||||
$('#textarea-http').text(JSON.stringify(res, null, '\t'));
|
||||
});
|
||||
}
|
||||
|
||||
/* APPLICATION REST METHODS */
|
||||
|
||||
|
||||
|
||||
/* APPLICATION BROWSER METHODS */
|
||||
|
||||
events = '';
|
||||
|
||||
window.onbeforeunload = function () { // Gracefully leave session
|
||||
if (session) {
|
||||
window.onbeforeunload = function () {
|
||||
// Gracefully leave room
|
||||
if (room) {
|
||||
removeUser();
|
||||
leaveSession();
|
||||
leaveRoom();
|
||||
}
|
||||
};
|
||||
|
||||
function getLivekitUrlFromMetadata(token) {
|
||||
if (!token) throw new Error('Trying to get metadata from an empty token');
|
||||
try {
|
||||
const base64Url = token.split('.')[1];
|
||||
const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/');
|
||||
const jsonPayload = decodeURIComponent(
|
||||
window
|
||||
.atob(base64)
|
||||
.split('')
|
||||
.map((c) => {
|
||||
return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2);
|
||||
})
|
||||
.join('')
|
||||
);
|
||||
|
||||
const payload = JSON.parse(jsonPayload);
|
||||
if (!payload?.metadata) throw new Error('Token does not contain metadata');
|
||||
const metadata = JSON.parse(payload.metadata);
|
||||
return metadata.livekitUrl;
|
||||
} catch (error) {
|
||||
throw new Error('Error decoding and parsing token: ' + error);
|
||||
}
|
||||
}
|
||||
|
||||
@ -432,40 +306,43 @@ function updateNumVideos(i) {
|
||||
}
|
||||
}
|
||||
|
||||
function checkBtnsForce() {
|
||||
if (document.getElementById("forceValue").value === "") {
|
||||
document.getElementById('buttonForceUnpublish').disabled = true;
|
||||
document.getElementById('buttonForceDisconnect').disabled = true;
|
||||
} else {
|
||||
document.getElementById('buttonForceUnpublish').disabled = false;
|
||||
document.getElementById('buttonForceDisconnect').disabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
function checkBtnsRecordings() {
|
||||
if (document.getElementById("forceRecordingId").value === "") {
|
||||
document.getElementById('buttonGetRecording').disabled = true;
|
||||
if (document.getElementById('forceRecordingId').value === '') {
|
||||
document.getElementById('buttonStopRecording').disabled = true;
|
||||
document.getElementById('buttonDeleteRecording').disabled = true;
|
||||
} else {
|
||||
document.getElementById('buttonGetRecording').disabled = false;
|
||||
document.getElementById('buttonStopRecording').disabled = false;
|
||||
document.getElementById('buttonDeleteRecording').disabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
function pushEvent(event) {
|
||||
events += (!events ? '' : '\n') + event.type;
|
||||
$('#textarea-events').text(events);
|
||||
function addIndividualRecordingButton(elementId, videoTrackId, audioTrackId) {
|
||||
const div = document.createElement('div');
|
||||
|
||||
var button = document.createElement('button');
|
||||
// button.id = elementId + '-button';
|
||||
button.className = 'recording-track-button btn btn-sm';
|
||||
|
||||
button.innerHTML = 'Record Track';
|
||||
button.style = 'position: absolute; left: 0; z-index: 1000;';
|
||||
|
||||
button.onclick = async () => {
|
||||
if (button.innerHTML === 'Record Track') {
|
||||
button.innerHTML = 'Stop Recording';
|
||||
button.className = 'recording-track-button btn btn-sm btn-danger';
|
||||
var res = await startIndividualRecording(videoTrackId, audioTrackId);
|
||||
button.id = res.info.egressId;
|
||||
} else {
|
||||
button.innerHTML = 'Record Track';
|
||||
button.className = 'recording-track-button btn btn-sm';
|
||||
stopRecording(button.id);
|
||||
}
|
||||
};
|
||||
div.appendChild(button);
|
||||
var element = document.getElementById(elementId);
|
||||
element.parentNode.insertBefore(div, element.nextSibling);
|
||||
}
|
||||
|
||||
function clearHttpTextarea() {
|
||||
$('#textarea-http').text('');
|
||||
}
|
||||
|
||||
function clearEventsTextarea() {
|
||||
$('#textarea-events').text('');
|
||||
events = '';
|
||||
}
|
||||
|
||||
/* APPLICATION BROWSER METHODS */
|
||||
/* APPLICATION BROWSER METHODS */
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 81 KiB |
@ -17,7 +17,7 @@
|
||||
<!-- Bootstrap -->
|
||||
|
||||
<link rel="styleSheet" href="style.css" type="text/css" media="screen">
|
||||
<script src="openvidu-browser-2.27.0.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/livekit-client/dist/livekit-client.umd.min.js"></script>
|
||||
<script src="app.js"></script>
|
||||
<script>
|
||||
$(document).ready(function () {
|
||||
@ -35,11 +35,11 @@
|
||||
<div class="navbar-header">
|
||||
<a class="navbar-brand" href="/">
|
||||
<img class="demo-logo" src="images/openvidu_vert_white_bg_trans_cropped.png" /> Recording Node</a>
|
||||
<a class="navbar-brand nav-icon" href="https://github.com/OpenVidu/openvidu-tutorials/tree/master/openvidu-recording-node"
|
||||
<a class="navbar-brand nav-icon" href="https://github.com/OpenVidu/openvidu-livekit-tutorials/tree/master/openvidu-recording-node"
|
||||
title="GitHub Repository" target="_blank">
|
||||
<i class="fa fa-github" aria-hidden="true"></i>
|
||||
</a>
|
||||
<a class="navbar-brand nav-icon" href="http://www.docs.openvidu.io/en/stable/tutorials/openvidu-recording-node/" title="Documentation"
|
||||
<a class="navbar-brand nav-icon" href="#" title="Documentation"
|
||||
target="_blank">
|
||||
<i class="fa fa-book" aria-hidden="true"></i>
|
||||
</a>
|
||||
@ -53,48 +53,30 @@
|
||||
<img src="images/openvidu_grey_bg_transp_cropped.png" />
|
||||
</div>
|
||||
<div id="join-dialog" class="jumbotron">
|
||||
<h1>Join a video session</h1>
|
||||
<h1>Join a video room</h1>
|
||||
<form class="form-group" onsubmit="return false">
|
||||
<p>
|
||||
<label>Session</label>
|
||||
<input class="form-control" type="text" id="sessionName" value="SessionA" required>
|
||||
<label>Room</label>
|
||||
<input class="form-control" type="text" id="roomName" value="RoomA" required>
|
||||
</p>
|
||||
<p class="text-center">
|
||||
<button class="btn btn-lg btn-success" id="join-btn" onclick="joinSession()">Join!</button>
|
||||
<button class="btn btn-lg btn-success" id="join-btn" onclick="joinRoom()">Join!</button>
|
||||
</p>
|
||||
</form>
|
||||
<hr>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="session" style="display: none">
|
||||
<div id="session-header">
|
||||
<h1 id="session-title"></h1>
|
||||
<input class="btn btn-sm btn-danger" type="button" id="buttonCloseSession" onmouseup="closeSession()" value="Close session">
|
||||
<input class="btn btn-sm btn-danger" type="button" id="buttonLeaveSession" onmouseup="removeUser(); leaveSession()"
|
||||
value="Leave session">
|
||||
<div class="vertical-separator-top"></div>
|
||||
<input class="form-control" id="forceValue" type="text" onkeyup="checkBtnsForce()">
|
||||
<input class="btn btn-sm" type="button" id="buttonForceUnpublish" onmouseup="forceUnpublish()" value="Force unpublish"
|
||||
disabled>
|
||||
<input class="btn btn-sm" type="button" id="buttonForceDisconnect" onmouseup="forceDisconnect()" value="Force disconnect"
|
||||
disabled>
|
||||
<div class="vertical-separator-top"></div>
|
||||
<input class="btn btn-sm" type="button" id="buttonFetchInfo" onmouseup="fetchInfo()" value="Fetch info">
|
||||
<input class="btn btn-sm" type="button" id="buttonFetchAll" onmouseup="fetchAll()" value="Fetch all">
|
||||
<div id="room" style="display: none">
|
||||
<div id="room-header">
|
||||
<h1 id="room-title"></h1>
|
||||
<input class="btn btn-sm btn-danger" type="button" id="buttonLeaveRoom" onmouseup="leaveRoom()"
|
||||
value="Leave room">
|
||||
</div>
|
||||
<div id="video-container" class="col-md-12"></div>
|
||||
<div id="recording-btns">
|
||||
<div class="btns">
|
||||
<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" id="radio-composed" checked>COMPOSED
|
||||
</label>
|
||||
<label class="radio-inline">
|
||||
<input type="radio" name="outputMode" value="INDIVIDUAL" id="radio-individual">INDIVIDUAL
|
||||
</label>
|
||||
</form>
|
||||
<input class="btn btn-md" type="button" id="buttonStartRecording" onmouseup="startComposedRecording()" value="Start Composed recording">
|
||||
<form>
|
||||
<label class="checkbox-inline">
|
||||
<input type="checkbox" id="has-audio-checkbox" checked>Has audio
|
||||
@ -107,11 +89,7 @@
|
||||
<div class="btns">
|
||||
<input class="btn btn-md" type="button" id="buttonListRecording" onmouseup="listRecordings()" value="List recordings">
|
||||
<div class="vertical-separator-bottom"></div>
|
||||
<input class="btn btn-md" type="button" id="buttonGetRecording" onmouseup="getRecording()" value="Get recording"
|
||||
disabled>
|
||||
<input class="btn btn-md" type="button" id="buttonStopRecording" onmouseup="stopRecording()" value="Stop recording"
|
||||
disabled>
|
||||
<input class="btn btn-md" type="button" id="buttonDeleteRecording" onmouseup="deleteRecording()" value="Delete recording"
|
||||
<input class="btn btn-md btn-danger" type="button" id="buttonStopRecording" onmouseup="stopRecording()" value="Stop recording"
|
||||
disabled>
|
||||
<input class="form-control" id="forceRecordingId" type="text" onkeyup="checkBtnsRecordings()">
|
||||
</div>
|
||||
@ -120,10 +98,10 @@
|
||||
<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 class="textarea-container" id="recordings-list-container">
|
||||
<button type="button" class="btn btn-md btn-danger" id="delete-recordings-btn" onclick="deleteRecordings()" disabled>Delete All</button>
|
||||
<span>Recordings list</span>
|
||||
<ul id="recording-list"></ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -131,7 +109,7 @@
|
||||
|
||||
<footer class="footer">
|
||||
<div class="container">
|
||||
<div class="text-muted">OpenVidu © 2022</div>
|
||||
<div class="text-muted">OpenVidu © 2023</div>
|
||||
<a href="http://www.openvidu.io/" target="_blank">
|
||||
<img class="openvidu-logo" src="images/openvidu_globe_bg_transp_cropped.png" />
|
||||
</a>
|
||||
|
||||
File diff suppressed because one or more lines are too long
@ -222,27 +222,27 @@ a:hover .demo-logo {
|
||||
margin-top: 100px;
|
||||
}
|
||||
|
||||
#session-header {
|
||||
#room-header {
|
||||
margin-bottom: 20px;
|
||||
height: 8%;
|
||||
margin-top: 70px;
|
||||
}
|
||||
|
||||
#session-header form {
|
||||
#room-header form {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
#session-header input.btn {
|
||||
#room-header input.btn {
|
||||
float: right;
|
||||
margin-top: 20px;
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
#session-title {
|
||||
#room-title {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
#session-header .form-control {
|
||||
#room-header .form-control {
|
||||
width: initial;
|
||||
float: right;
|
||||
margin: 18px 0px 0px 5px;
|
||||
@ -300,12 +300,12 @@ video {
|
||||
object-fit: scale-down;
|
||||
}
|
||||
|
||||
#session {
|
||||
#room {
|
||||
height: 100%;
|
||||
padding-bottom: 80px;
|
||||
}
|
||||
|
||||
#session img {
|
||||
#room img {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
display: inline-block;
|
||||
@ -313,7 +313,7 @@ video {
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
#session #video-container img {
|
||||
#room #video-container img {
|
||||
position: relative;
|
||||
float: left;
|
||||
width: 50%;
|
||||
@ -387,11 +387,12 @@ table i {
|
||||
}
|
||||
|
||||
#textarea-http-container {
|
||||
width: 69%;
|
||||
width: 59%;
|
||||
}
|
||||
|
||||
#textarea-events-container {
|
||||
width: 29%;
|
||||
#recordings-list-container {
|
||||
width: 39%;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.textarea-container button {
|
||||
|
||||
@ -1,373 +1,200 @@
|
||||
/* CONFIGURATION */
|
||||
|
||||
var OpenVidu = require('openvidu-node-client').OpenVidu;
|
||||
var OpenViduRole = require('openvidu-node-client').OpenViduRole;
|
||||
|
||||
require('dotenv').config(
|
||||
!!process.env.CONFIG ? { path: process.env.CONFIG } : {}
|
||||
);
|
||||
// For demo purposes we ignore self-signed certificate
|
||||
process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0"
|
||||
process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';
|
||||
|
||||
// Node imports
|
||||
var express = require('express');
|
||||
var fs = require('fs');
|
||||
var httpServer = (useSSL) ? require('https') : require('http');
|
||||
var bodyParser = require('body-parser'); // Pull information from HTML POST (express4)
|
||||
var app = express(); // Create our app with express
|
||||
const express = require('express');
|
||||
const fs = require('fs');
|
||||
var path = require('path');
|
||||
const https = require('https');
|
||||
const bodyParser = require('body-parser');
|
||||
const AccessToken = require('livekit-server-sdk').AccessToken;
|
||||
const EgressClient = require('livekit-server-sdk').EgressClient;
|
||||
const cors = require('cors');
|
||||
const app = express();
|
||||
|
||||
// Environment variable: PORT where the node server is listening
|
||||
var SERVER_PORT = process.env.SERVER_PORT || 5000;
|
||||
// Environment variable: URL where our OpenVidu server is listening
|
||||
var OPENVIDU_URL = process.env.OPENVIDU_URL || process.argv[2] || 'http://localhost:4443';
|
||||
// Environment variable: secret shared with our OpenVidu server
|
||||
var OPENVIDU_SECRET = process.env.OPENVIDU_SECRET || process.argv[3] || 'MY_SECRET';
|
||||
|
||||
var useSSL = (process.env.USE_SSL === 'false') ? false : true
|
||||
|
||||
|
||||
// Entrypoint to OpenVidu Node Client SDK
|
||||
var OV = new OpenVidu(OPENVIDU_URL, OPENVIDU_SECRET);
|
||||
|
||||
// Collection to pair session names with OpenVidu Session objects
|
||||
var mapSessions = {};
|
||||
// Collection to pair session names with tokens
|
||||
var mapSessionNamesTokens = {};
|
||||
const SERVER_PORT = process.env.SERVER_PORT || 5000;
|
||||
// Environment variable: api key shared with our LiveKit deployment
|
||||
const LIVEKIT_API_KEY = process.env.LIVEKIT_API_KEY || 'devkey';
|
||||
// Environment variable: api secret shared with our LiveKit deployment
|
||||
const LIVEKIT_API_SECRET = process.env.LIVEKIT_API_SECRET || 'secret';
|
||||
// Environment variable: url of our LiveKit deployment
|
||||
const LIVEKIT_URL = process.env.LIVEKIT_URL || 'ws://localhost:7880';
|
||||
// Environment variable: path where the recordings will be stored
|
||||
const RECORDINGS_PATH = process.env.RECORDINGS_PATH || '/recordings';
|
||||
|
||||
// Listen (start app with node server.js)
|
||||
var options = (useSSL) ? {
|
||||
key: fs.readFileSync('openvidukey.pem'),
|
||||
cert: fs.readFileSync('openviducert.pem')
|
||||
} : {}
|
||||
const options = {
|
||||
key: fs.readFileSync('openvidukey.pem'),
|
||||
cert: fs.readFileSync('openviducert.pem'),
|
||||
};
|
||||
|
||||
// Server configuration
|
||||
app.use(express.static(__dirname + '/public')); // Set the static files location
|
||||
app.use(bodyParser.urlencoded({
|
||||
'extended': 'true'
|
||||
})); // Parse application/x-www-form-urlencoded
|
||||
app.use(bodyParser.json()); // Parse application/json
|
||||
app.use(bodyParser.json({
|
||||
type: 'application/vnd.api+json'
|
||||
})); // Parse application/vnd.api+json as json
|
||||
const livekitUrlHostname = LIVEKIT_URL.replace(/^ws:/, 'http:').replace(
|
||||
/^wss:/,
|
||||
'https:'
|
||||
);
|
||||
const egressClient = new EgressClient(
|
||||
livekitUrlHostname,
|
||||
LIVEKIT_API_KEY,
|
||||
LIVEKIT_API_SECRET
|
||||
);
|
||||
|
||||
// Enable CORS support
|
||||
app.use(
|
||||
cors({
|
||||
origin: '*',
|
||||
})
|
||||
);
|
||||
|
||||
httpServer.createServer(options, app).listen(SERVER_PORT, () => {
|
||||
console.log(`App listening with ${(useSSL) ? "https": "http"} on port ${SERVER_PORT}`);
|
||||
console.log(`OPENVIDU_URL: ${OPENVIDU_URL}`);
|
||||
console.log(`OPENVIDU_SECRET: ${OPENVIDU_SECRET}`);
|
||||
// Set the static files location
|
||||
app.use(express.static(__dirname + '/public'));
|
||||
// Parse application/x-www-form-urlencoded
|
||||
app.use(
|
||||
bodyParser.urlencoded({
|
||||
extended: 'true',
|
||||
})
|
||||
);
|
||||
// Parse application/json
|
||||
app.use(bodyParser.json());
|
||||
|
||||
// Parse application/vnd.api+json as json
|
||||
app.use(
|
||||
bodyParser.json({
|
||||
type: 'application/vnd.api+json',
|
||||
})
|
||||
);
|
||||
|
||||
https.createServer(options, app).listen(SERVER_PORT, () => {
|
||||
console.log(`App listening on port ${SERVER_PORT}`);
|
||||
console.log(`LIVEKIT API KEY: ${LIVEKIT_API_KEY}`);
|
||||
console.log(`LIVEKIT API SECRET: ${LIVEKIT_API_SECRET}`);
|
||||
console.log(`LIVEKIT URL: ${LIVEKIT_URL}`);
|
||||
console.log();
|
||||
console.log('Access the app at https://localhost:' + SERVER_PORT);
|
||||
});
|
||||
|
||||
|
||||
/* Session API */
|
||||
|
||||
// Get token (add new user to session)
|
||||
app.post('/recording-node/api/get-token', function (req, res) {
|
||||
// The video-call to connect
|
||||
var sessionName = req.body.sessionName;
|
||||
app.post('/token', (req, res) => {
|
||||
const { roomName, participantName } = req.body;
|
||||
|
||||
// Role associated to this user
|
||||
var role = OpenViduRole.PUBLISHER;
|
||||
console.log(
|
||||
`Getting a token for room '${roomName}' and participant '${participantName}'`
|
||||
);
|
||||
|
||||
console.log("Getting a token | {sessionName}={" + sessionName + "}");
|
||||
if (!roomName || !participantName) {
|
||||
res
|
||||
.status(400)
|
||||
.json({ message: 'roomName and participantName are required' });
|
||||
return;
|
||||
}
|
||||
|
||||
// Build connectionProperties object with PUBLISHER role
|
||||
var connectionProperties = {
|
||||
role: role
|
||||
}
|
||||
|
||||
if (mapSessions[sessionName]) {
|
||||
// Session already exists
|
||||
console.log('Existing session ' + sessionName);
|
||||
|
||||
// Get the existing Session from the collection
|
||||
var mySession = mapSessions[sessionName];
|
||||
|
||||
// Generate a new Connection asynchronously with the recently created connectionProperties
|
||||
mySession.createConnection(connectionProperties)
|
||||
.then(connection => {
|
||||
|
||||
// Store the new token in the collection of tokens
|
||||
mapSessionNamesTokens[sessionName].push(connection.token);
|
||||
|
||||
// Return the token to the client
|
||||
res.status(200).send({
|
||||
0: connection.token
|
||||
});
|
||||
})
|
||||
.catch(error => {
|
||||
console.error(error);
|
||||
if (error.message === "404") {
|
||||
delete mapSessions[sessionName];
|
||||
delete mapSessionNamesTokens[sessionName];
|
||||
newSession(sessionName, connectionProperties, res);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
newSession(sessionName, connectionProperties, res);
|
||||
}
|
||||
const at = new AccessToken(LIVEKIT_API_KEY, LIVEKIT_API_SECRET, {
|
||||
identity: participantName,
|
||||
// add metadata to the token, which will be available in the participant's metadata
|
||||
metadata: JSON.stringify({ livekitUrl: LIVEKIT_URL }),
|
||||
});
|
||||
at.addGrant({
|
||||
roomJoin: true,
|
||||
room: roomName,
|
||||
});
|
||||
res.status(200).json({ token: at.toJwt() });
|
||||
});
|
||||
|
||||
function newSession(sessionName, connectionProperties, 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 connection asynchronously with the recently created connectionProperties
|
||||
session.createConnection(connectionProperties)
|
||||
.then(connection => {
|
||||
|
||||
// Store the new token in the collection of tokens
|
||||
mapSessionNamesTokens[sessionName].push(connection.token);
|
||||
|
||||
// Return the Token to the client
|
||||
res.status(200).send({
|
||||
0: connection.token
|
||||
});
|
||||
})
|
||||
.catch(error => {
|
||||
console.error(error);
|
||||
});
|
||||
})
|
||||
.catch(error => {
|
||||
console.error(error);
|
||||
});
|
||||
}
|
||||
|
||||
// Remove user from session
|
||||
app.post('/recording-node/api/remove-user', function (req, res) {
|
||||
// Retrieve params from POST body
|
||||
var sessionName = req.body.sessionName;
|
||||
var token = req.body.token;
|
||||
console.log('Removing user | {sessionName, token}={' + sessionName + ', ' + token + '}');
|
||||
|
||||
// If the session exists
|
||||
if (mapSessions[sessionName] && mapSessionNamesTokens[sessionName]) {
|
||||
var tokens = mapSessionNamesTokens[sessionName];
|
||||
var index = tokens.indexOf(token);
|
||||
|
||||
// If the token exists
|
||||
if (index !== -1) {
|
||||
// Token removed
|
||||
tokens.splice(index, 1);
|
||||
console.log(sessionName + ': ' + tokens.toString());
|
||||
} else {
|
||||
var msg = 'Problems in the app server: the TOKEN wasn\'t valid';
|
||||
console.log(msg);
|
||||
res.status(500).send(msg);
|
||||
}
|
||||
if (tokens.length == 0) {
|
||||
// Last user left: session must be removed
|
||||
console.log(sessionName + ' empty!');
|
||||
delete mapSessions[sessionName];
|
||||
}
|
||||
res.status(200).send();
|
||||
} else {
|
||||
var msg = 'Problems in the app server: the SESSION does not exist';
|
||||
console.log(msg);
|
||||
res.status(500).send(msg);
|
||||
}
|
||||
});
|
||||
|
||||
// Close session
|
||||
app.delete('/recording-node/api/close-session', function (req, res) {
|
||||
// Retrieve params from POST body
|
||||
var sessionName = req.body.sessionName;
|
||||
console.log("Closing session | {sessionName}=" + sessionName);
|
||||
|
||||
// If the session exists
|
||||
if (mapSessions[sessionName]) {
|
||||
var session = mapSessions[sessionName];
|
||||
session.close();
|
||||
delete mapSessions[sessionName];
|
||||
delete mapSessionNamesTokens[sessionName];
|
||||
res.status(200).send();
|
||||
} else {
|
||||
var msg = 'Problems in the app server: the SESSION does not exist';
|
||||
console.log(msg);
|
||||
res.status(500).send(msg);
|
||||
}
|
||||
});
|
||||
|
||||
// Fetch session info
|
||||
app.post('/recording-node/api/fetch-info', function (req, res) {
|
||||
// Retrieve params from POST body
|
||||
var sessionName = req.body.sessionName;
|
||||
console.log("Fetching session info | {sessionName}=" + sessionName);
|
||||
|
||||
// If the session exists
|
||||
if (mapSessions[sessionName]) {
|
||||
mapSessions[sessionName].fetch()
|
||||
.then(changed => {
|
||||
console.log("Any change: " + changed);
|
||||
res.status(200).send(sessionToJson(mapSessions[sessionName]));
|
||||
})
|
||||
.catch(error => res.status(400).send(error.message));
|
||||
} else {
|
||||
var msg = 'Problems in the app server: the SESSION does not exist';
|
||||
console.log(msg);
|
||||
res.status(500).send(msg);
|
||||
}
|
||||
});
|
||||
|
||||
// Fetch all session info
|
||||
app.get('/recording-node/api/fetch-all', function (req, res) {
|
||||
console.log("Fetching all session info");
|
||||
OV.fetch()
|
||||
.then(changed => {
|
||||
var sessions = [];
|
||||
OV.activeSessions.forEach(s => {
|
||||
sessions.push(sessionToJson(s));
|
||||
});
|
||||
console.log("Any change: " + changed);
|
||||
res.status(200).send(sessions);
|
||||
})
|
||||
.catch(error => res.status(400).send(error.message));
|
||||
});
|
||||
|
||||
// Force disconnect
|
||||
app.delete('/recording-node/api/force-disconnect', function (req, res) {
|
||||
// Retrieve params from POST body
|
||||
var sessionName = req.body.sessionName;
|
||||
var connectionId = req.body.connectionId;
|
||||
// If the session exists
|
||||
if (mapSessions[sessionName]) {
|
||||
mapSessions[sessionName].forceDisconnect(connectionId)
|
||||
.then(() => res.status(200).send())
|
||||
.catch(error => res.status(400).send(error.message));
|
||||
} else {
|
||||
var msg = 'Problems in the app server: the SESSION does not exist';
|
||||
console.log(msg);
|
||||
res.status(500).send(msg);
|
||||
}
|
||||
});
|
||||
|
||||
// Force unpublish
|
||||
app.delete('/recording-node/api/force-unpublish', function (req, res) {
|
||||
// Retrieve params from POST body
|
||||
var sessionName = req.body.sessionName;
|
||||
var streamId = req.body.streamId;
|
||||
// If the session exists
|
||||
if (mapSessions[sessionName]) {
|
||||
mapSessions[sessionName].forceUnpublish(streamId)
|
||||
.then(() => res.status(200).send())
|
||||
.catch(error => res.status(400).send(error.message));
|
||||
} else {
|
||||
var msg = 'Problems in the app server: the SESSION does not exist';
|
||||
console.log(msg);
|
||||
res.status(500).send(msg);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
|
||||
/* Recording API */
|
||||
|
||||
// Start recording
|
||||
app.post('/recording-node/api/recording/start', function (req, res) {
|
||||
// Retrieve params from POST body
|
||||
var recordingProperties = {
|
||||
outputMode: req.body.outputMode,
|
||||
hasAudio: req.body.hasAudio,
|
||||
hasVideo: req.body.hasVideo,
|
||||
}
|
||||
var sessionId = req.body.session;
|
||||
console.log("Starting recording | {sessionId}=" + sessionId);
|
||||
|
||||
OV.startRecording(sessionId, recordingProperties)
|
||||
.then(recording => res.status(200).send(recording))
|
||||
.catch(error => res.status(400).send(error.message));
|
||||
app.post('/recordings/start', async function (req, res) {
|
||||
const {
|
||||
roomName,
|
||||
outputMode,
|
||||
videoOnly,
|
||||
audioOnly,
|
||||
audioTrackId,
|
||||
videoTrackId,
|
||||
} = req.body;
|
||||
const output = {
|
||||
fileType: 0, // file type chosen based on codecs
|
||||
filepath: `/recordings/${roomName}-${new Date().getTime()}`,
|
||||
disableManifest: true,
|
||||
};
|
||||
console.log('Starting recording', roomName);
|
||||
try {
|
||||
let egressInfo;
|
||||
if (outputMode === 'COMPOSED') {
|
||||
console.log('Starting COMPOSED recording', roomName);
|
||||
egressInfo = await egressClient.startRoomCompositeEgress(
|
||||
roomName,
|
||||
output,
|
||||
{
|
||||
layout: 'grid',
|
||||
audioOnly,
|
||||
videoOnly,
|
||||
}
|
||||
);
|
||||
} else if (outputMode === 'INDIVIDUAL') {
|
||||
console.log('Starting INDIVIDUAL recording', roomName);
|
||||
egressInfo = await egressClient.startTrackCompositeEgress(
|
||||
roomName,
|
||||
output,
|
||||
{
|
||||
audioTrackId,
|
||||
videoTrackId,
|
||||
}
|
||||
);
|
||||
} else {
|
||||
res.status(400).json({ message: 'outputMode is required' });
|
||||
return;
|
||||
}
|
||||
res.status(200).json({ message: 'recording started', info: egressInfo });
|
||||
} catch (error) {
|
||||
console.log('Error starting recording', error);
|
||||
res.status(200).json({ message: 'error starting recording' });
|
||||
}
|
||||
});
|
||||
|
||||
// Stop recording
|
||||
app.post('/recording-node/api/recording/stop', function (req, res) {
|
||||
// Retrieve params from POST body
|
||||
var recordingId = req.body.recording;
|
||||
console.log("Stopping recording | {recordingId}=" + recordingId);
|
||||
app.post('/recordings/stop', async function (req, res) {
|
||||
const recordingId = req.body.recordingId;
|
||||
try {
|
||||
if (!recordingId) {
|
||||
res.status(400).json({ message: 'recordingId is required' });
|
||||
return;
|
||||
}
|
||||
|
||||
OV.stopRecording(recordingId)
|
||||
.then(recording => res.status(200).send(recording))
|
||||
.catch(error => res.status(400).send(error.message));
|
||||
});
|
||||
|
||||
// Delete recording
|
||||
app.delete('/recording-node/api/recording/delete', function (req, res) {
|
||||
// Retrieve params from DELETE body
|
||||
var recordingId = req.body.recording;
|
||||
console.log("Deleting recording | {recordingId}=" + recordingId);
|
||||
|
||||
OV.deleteRecording(recordingId)
|
||||
.then(() => res.status(200).send())
|
||||
.catch(error => res.status(400).send(error.message));
|
||||
});
|
||||
|
||||
// Get recording
|
||||
app.get('/recording-node/api/recording/get/:recordingId', function (req, res) {
|
||||
// Retrieve params from GET url
|
||||
var recordingId = req.params.recordingId;
|
||||
console.log("Getting recording | {recordingId}=" + recordingId);
|
||||
|
||||
OV.getRecording(recordingId)
|
||||
.then(recording => res.status(200).send(recording))
|
||||
.catch(error => res.status(400).send(error.message));
|
||||
console.log(`Stopping recording ${recordingId}`);
|
||||
const egressInfo = await egressClient.stopEgress(recordingId);
|
||||
res.status(200).json({ message: 'recording stopped', info: egressInfo });
|
||||
} catch (error) {
|
||||
console.log('Error stopping recording', error);
|
||||
res.status(200).json({ message: 'error stopping recording' });
|
||||
}
|
||||
});
|
||||
|
||||
// List all recordings
|
||||
app.get('/recording-node/api/recording/list', function (req, res) {
|
||||
console.log("Listing recordings");
|
||||
|
||||
OV.listRecordings()
|
||||
.then(recordings => res.status(200).send(recordings))
|
||||
.catch(error => res.status(400).send(error.message));
|
||||
app.get('/recordings/list', function (req, res) {
|
||||
const recordings = [];
|
||||
fs.readdirSync(RECORDINGS_PATH, { recursive: true }).forEach((value) => {
|
||||
// copy file to public folder for development purposes
|
||||
fs.copyFileSync(`${RECORDINGS_PATH}/${value}`, `public/${value}`);
|
||||
const newRec = { name: value, path: `/${value}` };
|
||||
recordings.push(newRec);
|
||||
});
|
||||
console.log(recordings);
|
||||
res.status(200).json({ recordings });
|
||||
});
|
||||
|
||||
function sessionToJson(session) {
|
||||
var json = {};
|
||||
json.sessionId = session.sessionId;
|
||||
json.createdAt = session.createdAt;
|
||||
json.customSessionId = !!session.properties.customSessionId ? session.properties.customSessionId : "";
|
||||
json.recording = session.recording;
|
||||
json.mediaMode = session.properties.mediaMode;
|
||||
json.recordingMode = session.properties.recordingMode;
|
||||
json.defaultRecordingProperties = session.properties.defaultRecordingProperties;
|
||||
var connections = {};
|
||||
connections.numberOfElements = session.activeConnections.length;
|
||||
var jsonArrayConnections = [];
|
||||
session.activeConnections.forEach(con => {
|
||||
var c = {};
|
||||
c.connectionId = con.connectionId;
|
||||
c.createdAt = con.createdAt;
|
||||
c.role = con.role;
|
||||
c.serverData = con.serverData;
|
||||
c.record = con.record;
|
||||
c.token = con.token;
|
||||
c.clientData = con.clientData;
|
||||
var pubs = [];
|
||||
con.publishers.forEach(p => {
|
||||
jsonP = {};
|
||||
jsonP.streamId = p.streamId;
|
||||
jsonP.createdAt = p.createdAt
|
||||
jsonP.hasAudio = p.hasAudio;
|
||||
jsonP.hasVideo = p.hasVideo;
|
||||
jsonP.audioActive = p.audioActive;
|
||||
jsonP.videoActive = p.videoActive;
|
||||
jsonP.frameRate = p.frameRate;
|
||||
jsonP.typeOfVideo = p.typeOfVideo;
|
||||
jsonP.videoDimensions = p.videoDimensions;
|
||||
pubs.push(jsonP);
|
||||
});
|
||||
var subs = [];
|
||||
con.subscribers.forEach(s => {
|
||||
subs.push(s);
|
||||
});
|
||||
c.publishers = pubs;
|
||||
c.subscribers = subs;
|
||||
jsonArrayConnections.push(c);
|
||||
});
|
||||
connections.content = jsonArrayConnections;
|
||||
json.connections = connections;
|
||||
return json;
|
||||
}
|
||||
// Delete all recordings
|
||||
app.delete('/recordings', function (req, res) {
|
||||
fs.readdirSync(RECORDINGS_PATH, { recursive: true }).forEach((value) => {
|
||||
fs.unlinkSync(`${RECORDINGS_PATH}/${value}`);
|
||||
if (fs.existsSync(`public/${value}`)) {
|
||||
fs.unlinkSync(`public/${value}`);
|
||||
}
|
||||
});
|
||||
res.status(200).json({ message: 'All recordings deleted' });
|
||||
});
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user