Skip to content

Commit

Permalink
openvidu-server: audio-only and video-only recordings
Browse files Browse the repository at this point in the history
  • Loading branch information
pabloFuente committed Jan 24, 2019
1 parent e46aa16 commit 496d33b
Show file tree
Hide file tree
Showing 17 changed files with 593 additions and 214 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -27,28 +27,30 @@ public static enum Code {

TRANSPORT_ERROR_CODE(803), TRANSPORT_RESPONSE_ERROR_CODE(802), TRANSPORT_REQUEST_ERROR_CODE(801),

MEDIA_TYPE_RECORDING_PROPERTIES_ERROR_CODE(308), MEDIA_MUTE_ERROR_CODE(307), MEDIA_NOT_A_WEB_ENDPOINT_ERROR_CODE(306), MEDIA_RTP_ENDPOINT_ERROR_CODE(
305), MEDIA_WEBRTC_ENDPOINT_ERROR_CODE(
304), MEDIA_ENDPOINT_ERROR_CODE(303), MEDIA_SDP_ERROR_CODE(302), MEDIA_GENERIC_ERROR_CODE(301),
MEDIA_TYPE_STREAM_INCOMPATIBLE_WITH_RECORDING_PROPERTIES_ERROR_CODE(309),
MEDIA_TYPE_RECORDING_PROPERTIES_ERROR_CODE(308), MEDIA_MUTE_ERROR_CODE(307),
MEDIA_NOT_A_WEB_ENDPOINT_ERROR_CODE(306), MEDIA_RTP_ENDPOINT_ERROR_CODE(305),
MEDIA_WEBRTC_ENDPOINT_ERROR_CODE(304), MEDIA_ENDPOINT_ERROR_CODE(303), MEDIA_SDP_ERROR_CODE(302),
MEDIA_GENERIC_ERROR_CODE(301),

ROOM_CANNOT_BE_CREATED_ERROR_CODE(204), ROOM_CLOSED_ERROR_CODE(203), ROOM_NOT_FOUND_ERROR_CODE(
202), ROOM_GENERIC_ERROR_CODE(201),
ROOM_CANNOT_BE_CREATED_ERROR_CODE(204), ROOM_CLOSED_ERROR_CODE(203), ROOM_NOT_FOUND_ERROR_CODE(202),
ROOM_GENERIC_ERROR_CODE(201),

USER_NOT_STREAMING_ERROR_CODE(105), EXISTING_USER_IN_ROOM_ERROR_CODE(104), USER_CLOSED_ERROR_CODE(
103), USER_NOT_FOUND_ERROR_CODE(102), USER_GENERIC_ERROR_CODE(10),
USER_NOT_STREAMING_ERROR_CODE(105), EXISTING_USER_IN_ROOM_ERROR_CODE(104), USER_CLOSED_ERROR_CODE(103),
USER_NOT_FOUND_ERROR_CODE(102), USER_GENERIC_ERROR_CODE(10),

USER_UNAUTHORIZED_ERROR_CODE(401), ROLE_NOT_FOUND_ERROR_CODE(402), SESSIONID_CANNOT_BE_CREATED_ERROR_CODE(
403), TOKEN_CANNOT_BE_CREATED_ERROR_CODE(404), EXISTING_FILTER_ALREADY_APPLIED_ERROR_CODE(405),
USER_UNAUTHORIZED_ERROR_CODE(401), ROLE_NOT_FOUND_ERROR_CODE(402), SESSIONID_CANNOT_BE_CREATED_ERROR_CODE(403),
TOKEN_CANNOT_BE_CREATED_ERROR_CODE(404), EXISTING_FILTER_ALREADY_APPLIED_ERROR_CODE(405),
FILTER_NOT_APPLIED_ERROR_CODE(406), FILTER_EVENT_LISTENER_NOT_FOUND(407),

USER_METADATA_FORMAT_INVALID_ERROR_CODE(500),

SIGNAL_FORMAT_INVALID_ERROR_CODE(600), SIGNAL_TO_INVALID_ERROR_CODE(601), SIGNAL_MESSAGE_INVALID_ERROR_CODE(
602),
SIGNAL_FORMAT_INVALID_ERROR_CODE(600), SIGNAL_TO_INVALID_ERROR_CODE(601),
SIGNAL_MESSAGE_INVALID_ERROR_CODE(602),

RECORDING_PATH_NOT_VALID(708), RECORDING_FILE_EMPTY_ERROR(707), RECORDING_DELETE_ERROR_CODE(
706), RECORDING_LIST_ERROR_CODE(705), RECORDING_STOP_ERROR_CODE(704), RECORDING_START_ERROR_CODE(
703), RECORDING_REPORT_ERROR_CODE(702), RECORDING_COMPLETION_ERROR_CODE(701);
RECORDING_PATH_NOT_VALID(708), RECORDING_FILE_EMPTY_ERROR(707), RECORDING_DELETE_ERROR_CODE(706),
RECORDING_LIST_ERROR_CODE(705), RECORDING_STOP_ERROR_CODE(704), RECORDING_START_ERROR_CODE(703),
RECORDING_REPORT_ERROR_CODE(702), RECORDING_COMPLETION_ERROR_CODE(701);

private int value;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -121,15 +121,15 @@ public RecordingProperties.Builder resolution(String resolution) {
* Call this method to specify whether or not to record the audio track
*/
public RecordingProperties.Builder hasAudio(boolean hasAudio) {
this.hasAudio = true;
this.hasAudio = hasAudio;
return this;
}

/**
* Call this method to specify whether or not to record the video track
*/
public RecordingProperties.Builder hasVideo(boolean hasVideo) {
this.hasVideo = true;
this.hasVideo = hasVideo;
return this;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ public JsonObject toJson() {
json.addProperty("id", this.recording.getId());
json.addProperty("name", this.recording.getName());
json.addProperty("outputMode", this.recording.getOutputMode().name());
if (io.openvidu.java.client.Recording.OutputMode.COMPOSED.equals(this.recording.getOutputMode())) {
if (io.openvidu.java.client.Recording.OutputMode.COMPOSED.equals(this.recording.getOutputMode()) && this.recording.hasVideo()) {
json.addProperty("resolution", this.recording.getResolution());
json.addProperty("recordingLayout", this.recording.getRecordingLayout().name());
if (RecordingLayout.CUSTOM.equals(this.recording.getRecordingLayout())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@
* - webrtcConnectionDestroyed.reason: "unsubscribe", "unpublish", "disconnect", "networkDisconnect", "openviduServerStopped"
* - participantLeft.reason: "unsubscribe", "unpublish", "disconnect", "networkDisconnect", "openviduServerStopped"
* - sessionDestroyed.reason: "lastParticipantLeft", "openviduServerStopped"
* - recordingStopped.reason: "recordingStoppedByServer", "lastParticipantLeft", "sessionClosedByServer", "openviduServerStopped"
* - recordingStopped.reason: "recordingStoppedByServer", "lastParticipantLeft", "sessionClosedByServer", "automaticStop", "openviduServerStopped"
*
* [OPTIONAL_PROPERTIES]:
* - receivingFrom: only if connection = "INBOUND"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
import io.openvidu.server.coturn.TurnCredentials;
import io.openvidu.server.kurento.core.KurentoTokenOptions;
import io.openvidu.server.recording.service.RecordingManager;
import io.openvidu.server.utils.FormatChecker;

public abstract class SessionManager {

Expand All @@ -65,6 +66,8 @@ public abstract class SessionManager {
@Autowired
protected CoturnCredentialsService coturnCredentialsService;

public FormatChecker formatChecker = new FormatChecker();

protected ConcurrentMap<String, Session> sessions = new ConcurrentHashMap<>();
protected ConcurrentMap<String, SessionProperties> sessionProperties = new ConcurrentHashMap<>();
protected ConcurrentMap<String, Long> sessionCreationTime = new ConcurrentHashMap<>();
Expand Down Expand Up @@ -223,7 +226,7 @@ public String newToken(String sessionId, ParticipantRole role, String serverMeta
new ConcurrentHashMap<>());
if (map != null) {

if (!isMetadataFormatCorrect(serverMetadata)) {
if (!formatChecker.isServerMetadataFormatCorrect(serverMetadata)) {
log.error("Data invalid format");
throw new OpenViduException(Code.GENERIC_ERROR_CODE, "Data invalid format");
}
Expand Down Expand Up @@ -317,10 +320,6 @@ public boolean isInsecureParticipant(String participantPrivateId) {
return false;
}

public boolean isMetadataFormatCorrect(String metadata) {
return true;
}

public void newInsecureParticipant(String participantPrivateId) {
this.insecureUsers.put(participantPrivateId, true);
}
Expand Down Expand Up @@ -443,7 +442,7 @@ public Set<Participant> closeSession(String sessionId, String reason) {
return participants;
}

protected void closeSessionAndEmptyCollections(Session session, String reason) {
public void closeSessionAndEmptyCollections(Session session, String reason) {

if (openviduConfig.isRecordingModuleEnabled()
&& this.recordingManager.sessionIsBeingRecorded(session.getSessionId())) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -238,7 +238,7 @@ public String publishToRoom(SdpType sdpType, String sdpString, boolean doLoopbac
this.session.getSessionId());

if (this.openviduConfig.isRecordingModuleEnabled()
&& this.recordingManager.sessionIsBeingRecordedIndividual(session.getSessionId())) {
&& this.recordingManager.sessionIsBeingRecorded(session.getSessionId())) {
this.recordingManager.startOneIndividualStreamRecording(session, null, null, this);
}

Expand Down Expand Up @@ -438,7 +438,7 @@ private void releasePublisherEndpoint(String reason) {
this.session.publishedStreamIds.remove(this.getPublisherStreamId());

if (this.openviduConfig.isRecordingModuleEnabled()
&& this.recordingManager.sessionIsBeingRecordedIndividual(session.getSessionId())) {
&& this.recordingManager.sessionIsBeingRecorded(session.getSessionId())) {
this.recordingManager.stopOneIndividualStreamRecording(session.getSessionId(),
this.getPublisherStreamId());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -172,12 +172,14 @@ public synchronized void leaveRoom(Participant participant, Integer transactionI
if (remainingParticipants.isEmpty()) {
if (openviduConfig.isRecordingModuleEnabled()
&& MediaMode.ROUTED.equals(session.getSessionProperties().mediaMode())
&& this.recordingManager.sessionIsBeingRecordedIndividual(sessionId)) {
// Start countdown to stop recording if RecordingMode.MANUAL (will be aborted if
// a Publisher starts before timeout)
log.info("Last participant left. Starting 2 minutes countdown for stopping recording of session {}",
sessionId);
recordingManager.initAutomaticRecordingStopThread(session.getSessionId());
&& (this.recordingManager.sessionIsBeingRecordedIndividual(sessionId)
|| (this.recordingManager.sessionIsBeingRecordedComposed(sessionId)
&& this.recordingManager.sessionIsBeingRecordedOnlyAudio(sessionId)))) {
// Start countdown to stop recording if INDIVIDUAL mode or COMPOSED audio-only
// (will be aborted if a Publisher starts before timeout)
log.info("Last participant left. Starting {} seconds countdown for stopping recording of session {}",
this.openviduConfig.getOpenviduRecordingAutostopTimeout(), sessionId);
recordingManager.initAutomaticRecordingStopThread(session);
} else {
log.info("No more participants in session '{}', removing it and closing it", sessionId);
this.closeSessionAndEmptyCollections(session, reason);
Expand All @@ -186,6 +188,7 @@ public synchronized void leaveRoom(Participant participant, Integer transactionI
} else if (remainingParticipants.size() == 1 && openviduConfig.isRecordingModuleEnabled()
&& MediaMode.ROUTED.equals(session.getSessionProperties().mediaMode())
&& this.recordingManager.sessionIsBeingRecordedComposed(sessionId)
&& !this.recordingManager.sessionIsBeingRecordedOnlyAudio(sessionId)
&& ProtocolElements.RECORDER_PARTICIPANT_PUBLICID
.equals(remainingParticipants.iterator().next().getParticipantPublicId())) {
if (RecordingMode.ALWAYS.equals(session.getSessionProperties().recordingMode())) {
Expand All @@ -198,9 +201,9 @@ public synchronized void leaveRoom(Participant participant, Integer transactionI
} else if (RecordingMode.MANUAL.equals(session.getSessionProperties().recordingMode())) {
// Start countdown to stop recording if RecordingMode.MANUAL (will be aborted if
// a Publisher starts before timeout)
log.info("Last participant left. Starting 2 minutes countdown for stopping recording of session {}",
sessionId);
recordingManager.initAutomaticRecordingStopThread(session.getSessionId());
log.info("Last participant left. Starting {} seconds countdown for stopping recording of session {}",
this.openviduConfig.getOpenviduRecordingAutostopTimeout(), sessionId);
recordingManager.initAutomaticRecordingStopThread(session);
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
/*
* (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.server.recording;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;

import org.kurento.client.Composite;
import org.kurento.client.ErrorEvent;
import org.kurento.client.EventListener;
import org.kurento.client.HubPort;
import org.kurento.client.MediaProfileSpecType;
import org.kurento.client.RecorderEndpoint;
import org.kurento.client.RecordingEvent;
import org.kurento.client.StoppedEvent;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import io.openvidu.client.OpenViduException;
import io.openvidu.client.OpenViduException.Code;
import io.openvidu.server.kurento.core.KurentoSession;
import io.openvidu.server.kurento.endpoint.PublisherEndpoint;

public class CompositeWrapper {

private static final Logger log = LoggerFactory.getLogger(CompositeWrapper.class);

KurentoSession session;
Composite composite;
RecorderEndpoint recorderEndpoint;
HubPort compositeToRecorderHubPort;
Map<String, HubPort> hubPorts = new ConcurrentHashMap<>();
Map<String, PublisherEndpoint> publisherEndpoints = new ConcurrentHashMap<>();

AtomicBoolean isRecording = new AtomicBoolean(false);
long startTime;
long endTime;
long size;

public CompositeWrapper(KurentoSession session, String path) {
this.session = session;
this.composite = new Composite.Builder(session.getPipeline()).build();
this.recorderEndpoint = new RecorderEndpoint.Builder(composite.getMediaPipeline(), path)
.withMediaProfile(MediaProfileSpecType.WEBM_AUDIO_ONLY).build();
this.compositeToRecorderHubPort = new HubPort.Builder(composite).build();
this.compositeToRecorderHubPort.connect(recorderEndpoint);
}

public synchronized void startCompositeRecording(CountDownLatch startLatch) {

this.recorderEndpoint.addRecordingListener(new EventListener<RecordingEvent>() {
@Override
public void onEvent(RecordingEvent event) {
startTime = System.currentTimeMillis();
log.info("Recording started event for audio-only RecorderEndpoint of Composite in session {}",
session.getSessionId());
startLatch.countDown();
}
});

this.recorderEndpoint.addErrorListener(new EventListener<ErrorEvent>() {
@Override
public void onEvent(ErrorEvent event) {
log.error(event.getErrorCode() + " " + event.getDescription());
}
});

this.recorderEndpoint.record();
}

public synchronized void stopCompositeRecording(CountDownLatch stopLatch) {
this.recorderEndpoint.addStoppedListener(new EventListener<StoppedEvent>() {
@Override
public void onEvent(StoppedEvent event) {
endTime = System.currentTimeMillis();
log.info("Recording stopped event for audio-only RecorderEndpoint of Composite in session {}",
session.getSessionId());
recorderEndpoint.release();
stopLatch.countDown();
}
});
this.recorderEndpoint.stop();
}

public void connectPublisherEndpoint(PublisherEndpoint endpoint) throws OpenViduException {
HubPort hubPort = new HubPort.Builder(composite).build();
endpoint.connect(hubPort);
String streamId = endpoint.getOwner().getPublisherStreamId();
this.hubPorts.put(streamId, hubPort);
this.publisherEndpoints.put(streamId, endpoint);

if (isRecording.compareAndSet(false, true)) {
// First user publishing. Starting RecorderEndpoint
final CountDownLatch startLatch = new CountDownLatch(1);
this.startCompositeRecording(startLatch);
try {
if (!startLatch.await(5, TimeUnit.SECONDS)) {
log.error("Error waiting for RecorderEndpoint of Composite to start in session {}",
session.getSessionId());
throw new OpenViduException(Code.RECORDING_START_ERROR_CODE,
"Couldn't initialize RecorderEndpoint of Composite");
}
log.info("RecorderEnpoint of Composite is now recording for session {}", session.getSessionId());
} catch (InterruptedException e) {
log.error("Exception while waiting for state change", e);
}
}

log.info("Composite for session {} has now {} connected publishers", this.session.getSessionId(),
this.composite.getChildren().size() - 1);
}

public void disconnectPublisherEndpoint(String streamId) {
HubPort hubPort = this.hubPorts.remove(streamId);
PublisherEndpoint publisherEndpoint = this.publisherEndpoints.remove(streamId);
publisherEndpoint.disconnectFrom(hubPort);
hubPort.release();
log.info("Composite for session {} has now {} connected publishers", this.session.getSessionId(),
this.composite.getChildren().size() - 1);
}

public void disconnectAllPublisherEndpoints() {
this.publisherEndpoints.keySet().forEach(streamId -> {
PublisherEndpoint endpoint = this.publisherEndpoints.get(streamId);
HubPort hubPort = this.hubPorts.get(streamId);
endpoint.disconnectFrom(hubPort);
hubPort.release();
});
this.hubPorts.clear();
this.publisherEndpoints.clear();
this.composite.release();
}

public long getDuration() {
return this.endTime - this.startTime;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -23,19 +23,19 @@

public class RecorderEndpointWrapper {

RecorderEndpoint recorder;
String connectionId;
String recordingId;
String streamId;
String clientData;
String serverData;
boolean hasAudio;
boolean hasVideo;
String typeOfVideo;

long startTime;
long endTime;
long size;
private RecorderEndpoint recorder;
private String connectionId;
private String recordingId;
private String streamId;
private String clientData;
private String serverData;
private boolean hasAudio;
private boolean hasVideo;
private String typeOfVideo;

private long startTime;
private long endTime;
private long size;

public RecorderEndpointWrapper(RecorderEndpoint recorder, String connectionId, String recordingId, String streamId,
String clientData, String serverData, boolean hasAudio, boolean hasVideo, String typeOfVideo) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,9 +70,8 @@ public Recording(JsonObject json) {
io.openvidu.java.client.Recording.OutputMode outputMode = io.openvidu.java.client.Recording.OutputMode
.valueOf(json.get("outputMode").getAsString());
RecordingProperties.Builder builder = new RecordingProperties.Builder().name(json.get("name").getAsString())
.outputMode(outputMode).hasAudio(json.get("hasAudio").getAsBoolean())
.hasVideo(json.get("hasVideo").getAsBoolean());
if (io.openvidu.java.client.Recording.OutputMode.COMPOSED.equals(outputMode)) {
.outputMode(outputMode).hasAudio(this.hasAudio).hasVideo(this.hasVideo);
if (io.openvidu.java.client.Recording.OutputMode.COMPOSED.equals(outputMode) && this.hasVideo) {
this.resolution = json.get("resolution").getAsString();
builder.resolution(this.resolution);
RecordingLayout recordingLayout = RecordingLayout.valueOf(json.get("recordingLayout").getAsString());
Expand Down Expand Up @@ -189,7 +188,8 @@ public JsonObject toJson() {
json.addProperty("id", this.id);
json.addProperty("name", this.recordingProperties.name());
json.addProperty("outputMode", this.getOutputMode().name());
if (io.openvidu.java.client.Recording.OutputMode.COMPOSED.equals(this.recordingProperties.outputMode())) {
if (io.openvidu.java.client.Recording.OutputMode.COMPOSED.equals(this.recordingProperties.outputMode())
&& this.hasVideo) {
json.addProperty("resolution", this.resolution);
json.addProperty("recordingLayout", this.recordingProperties.recordingLayout().name());
if (RecordingLayout.CUSTOM.equals(this.recordingProperties.recordingLayout())) {
Expand Down
Loading

0 comments on commit 496d33b

Please sign in to comment.