mirror of https://github.com/OpenVidu/openvidu.git
openvidu-server: webhook
parent
d72063b97d
commit
9c4941de9a
|
@ -19,7 +19,8 @@ package io.openvidu.server;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.MalformedURLException;
|
import java.net.MalformedURLException;
|
||||||
import java.util.Arrays;
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import javax.annotation.PostConstruct;
|
import javax.annotation.PostConstruct;
|
||||||
|
|
||||||
|
@ -39,6 +40,7 @@ import org.springframework.context.event.EventListener;
|
||||||
|
|
||||||
import io.openvidu.client.OpenViduException;
|
import io.openvidu.client.OpenViduException;
|
||||||
import io.openvidu.client.OpenViduException.Code;
|
import io.openvidu.client.OpenViduException.Code;
|
||||||
|
import io.openvidu.server.cdr.CDRLogger;
|
||||||
import io.openvidu.server.cdr.CDRLoggerFile;
|
import io.openvidu.server.cdr.CDRLoggerFile;
|
||||||
import io.openvidu.server.cdr.CallDetailRecord;
|
import io.openvidu.server.cdr.CallDetailRecord;
|
||||||
import io.openvidu.server.config.HttpHandshakeInterceptor;
|
import io.openvidu.server.config.HttpHandshakeInterceptor;
|
||||||
|
@ -64,6 +66,7 @@ import io.openvidu.server.rpc.RpcNotificationService;
|
||||||
import io.openvidu.server.utils.CommandExecutor;
|
import io.openvidu.server.utils.CommandExecutor;
|
||||||
import io.openvidu.server.utils.GeoLocationByIp;
|
import io.openvidu.server.utils.GeoLocationByIp;
|
||||||
import io.openvidu.server.utils.GeoLocationByIpDummy;
|
import io.openvidu.server.utils.GeoLocationByIpDummy;
|
||||||
|
import io.openvidu.server.webhook.CDRLoggerWebhook;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* OpenVidu Server application
|
* OpenVidu Server application
|
||||||
|
@ -129,10 +132,15 @@ public class OpenViduServer implements JsonRpcConfigurer {
|
||||||
@Bean
|
@Bean
|
||||||
@ConditionalOnMissingBean
|
@ConditionalOnMissingBean
|
||||||
public CallDetailRecord cdr() {
|
public CallDetailRecord cdr() {
|
||||||
if (this.openviduConfig.isCdrEnabled()) {
|
List<CDRLogger> loggers = new ArrayList<>();
|
||||||
|
if (openviduConfig.isCdrEnabled()) {
|
||||||
log.info("OpenVidu CDR is enabled");
|
log.info("OpenVidu CDR is enabled");
|
||||||
|
loggers.add(new CDRLoggerFile());
|
||||||
}
|
}
|
||||||
return new CallDetailRecord(Arrays.asList(new CDRLoggerFile()));
|
if (openviduConfig.isWebhookEnabled()) {
|
||||||
|
loggers.add(new CDRLoggerWebhook(openviduConfig));
|
||||||
|
}
|
||||||
|
return new CallDetailRecord(loggers);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
|
|
|
@ -23,7 +23,7 @@ public class CDREvent {
|
||||||
|
|
||||||
protected String sessionId;
|
protected String sessionId;
|
||||||
protected Long timeStamp;
|
protected Long timeStamp;
|
||||||
private CDREventName eventName;
|
protected CDREventName eventName;
|
||||||
|
|
||||||
public CDREvent(CDREventName eventName, String sessionId, Long timeStamp) {
|
public CDREvent(CDREventName eventName, String sessionId, Long timeStamp) {
|
||||||
this.eventName = eventName;
|
this.eventName = eventName;
|
||||||
|
|
|
@ -33,8 +33,8 @@ public class CDREventParticipant extends CDREventEnd {
|
||||||
}
|
}
|
||||||
|
|
||||||
// participantLeft
|
// participantLeft
|
||||||
public CDREventParticipant(CDREventParticipant event, EndReason reason) {
|
public CDREventParticipant(CDREventParticipant event, EndReason reason, Long timestamp) {
|
||||||
super(CDREventName.participantLeft, event.getSessionId(), event.getTimestamp(), reason);
|
super(CDREventName.participantLeft, event.getSessionId(), event.getTimestamp(), reason, timestamp);
|
||||||
this.participant = event.participant;
|
this.participant = event.participant;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -25,7 +25,7 @@ import io.openvidu.server.recording.Recording;
|
||||||
|
|
||||||
public class CDREventRecording extends CDREventEnd {
|
public class CDREventRecording extends CDREventEnd {
|
||||||
|
|
||||||
private Recording recording;
|
protected Recording recording;
|
||||||
|
|
||||||
// recordingStarted
|
// recordingStarted
|
||||||
public CDREventRecording(String sessionId, Recording recording) {
|
public CDREventRecording(String sessionId, Recording recording) {
|
||||||
|
@ -34,9 +34,9 @@ public class CDREventRecording extends CDREventEnd {
|
||||||
}
|
}
|
||||||
|
|
||||||
// recordingStopped
|
// recordingStopped
|
||||||
public CDREventRecording(CDREventRecording event, Recording recording, EndReason reason) {
|
public CDREventRecording(CDREventRecording event, Recording recording, EndReason reason, Long timestamp) {
|
||||||
super(CDREventName.recordingStopped, event == null ? recording.getSessionId() : event.getSessionId(),
|
super(CDREventName.recordingStopped, event == null ? recording.getSessionId() : event.getSessionId(),
|
||||||
event == null ? recording.getCreatedAt() : event.getTimestamp(), reason);
|
event == null ? recording.getCreatedAt() : event.getTimestamp(), reason, timestamp);
|
||||||
this.recording = recording;
|
this.recording = recording;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,54 @@
|
||||||
|
/*
|
||||||
|
* (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.cdr;
|
||||||
|
|
||||||
|
import com.google.gson.JsonObject;
|
||||||
|
|
||||||
|
import io.openvidu.java.client.Recording.Status;
|
||||||
|
import io.openvidu.server.core.EndReason;
|
||||||
|
import io.openvidu.server.recording.Recording;
|
||||||
|
|
||||||
|
public class CDREventRecordingStatus extends CDREventRecording {
|
||||||
|
|
||||||
|
private Status status;
|
||||||
|
|
||||||
|
public CDREventRecordingStatus(String sessionId, Recording recording, Status status) {
|
||||||
|
super(sessionId, recording);
|
||||||
|
this.eventName = CDREventName.recordingStatusChanged;
|
||||||
|
this.status = status;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CDREventRecordingStatus(CDREventRecording recordingStartedEvent, Recording recording, EndReason finalReason,
|
||||||
|
long timestamp, Status status) {
|
||||||
|
super(recordingStartedEvent, recording, finalReason, timestamp);
|
||||||
|
this.eventName = CDREventName.recordingStatusChanged;
|
||||||
|
this.status = status;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Status getStatus() {
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public JsonObject toJson() {
|
||||||
|
JsonObject json = super.toJson();
|
||||||
|
json.addProperty("status", this.status.name());
|
||||||
|
return json;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -31,8 +31,8 @@ public class CDREventSession extends CDREventEnd {
|
||||||
}
|
}
|
||||||
|
|
||||||
// sessionDestroyed
|
// sessionDestroyed
|
||||||
public CDREventSession(CDREventSession event, EndReason reason) {
|
public CDREventSession(CDREventSession event, EndReason reason, Long timestamp) {
|
||||||
super(CDREventName.sessionDestroyed, event.getSessionId(), event.getTimestamp(), reason);
|
super(CDREventName.sessionDestroyed, event.getSessionId(), event.getTimestamp(), reason, timestamp);
|
||||||
this.session = event.session;
|
this.session = event.session;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -41,8 +41,8 @@ public class CDREventWebrtcConnection extends CDREventEnd implements Comparable<
|
||||||
}
|
}
|
||||||
|
|
||||||
// webrtcConnectionDestroyed
|
// webrtcConnectionDestroyed
|
||||||
public CDREventWebrtcConnection(CDREventWebrtcConnection event, EndReason reason) {
|
public CDREventWebrtcConnection(CDREventWebrtcConnection event, EndReason reason, Long timestamp) {
|
||||||
super(CDREventName.webrtcConnectionDestroyed, event.getSessionId(), event.getTimestamp(), reason);
|
super(CDREventName.webrtcConnectionDestroyed, event.getSessionId(), event.getTimestamp(), reason, timestamp);
|
||||||
this.streamId = event.streamId;
|
this.streamId = event.streamId;
|
||||||
this.participant = event.participant;
|
this.participant = event.participant;
|
||||||
this.mediaOptions = event.mediaOptions;
|
this.mediaOptions = event.mediaOptions;
|
||||||
|
|
|
@ -28,6 +28,4 @@ public interface CDRLogger {
|
||||||
|
|
||||||
public void log(SessionSummary sessionSummary);
|
public void log(SessionSummary sessionSummary);
|
||||||
|
|
||||||
public boolean canBeDisabled();
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -40,9 +40,4 @@ public class CDRLoggerFile implements CDRLogger {
|
||||||
public void log(SessionSummary sessionSummary) {
|
public void log(SessionSummary sessionSummary) {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean canBeDisabled() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,6 +26,7 @@ import java.util.concurrent.ConcurrentSkipListSet;
|
||||||
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
|
||||||
|
import io.openvidu.java.client.Recording.Status;
|
||||||
import io.openvidu.server.config.OpenviduConfig;
|
import io.openvidu.server.config.OpenviduConfig;
|
||||||
import io.openvidu.server.core.EndReason;
|
import io.openvidu.server.core.EndReason;
|
||||||
import io.openvidu.server.core.MediaOptions;
|
import io.openvidu.server.core.MediaOptions;
|
||||||
|
@ -49,6 +50,7 @@ import io.openvidu.server.summary.SessionSummary;
|
||||||
* - 'webrtcConnectionDestroyed' {sessionId, timestamp, participantId, startTime, duration, connection, [receivingFrom], audioEnabled, videoEnabled, [videoSource], [videoFramerate], reason}
|
* - 'webrtcConnectionDestroyed' {sessionId, timestamp, participantId, startTime, duration, connection, [receivingFrom], audioEnabled, videoEnabled, [videoSource], [videoFramerate], reason}
|
||||||
* - 'recordingStarted' {sessionId, timestamp, id, name, hasAudio, hasVideo, resolution, recordingLayout, size}
|
* - 'recordingStarted' {sessionId, timestamp, id, name, hasAudio, hasVideo, resolution, recordingLayout, size}
|
||||||
* - 'recordingStopped' {sessionId, timestamp, id, name, hasAudio, hasVideo, resolution, recordingLayout, size}
|
* - 'recordingStopped' {sessionId, timestamp, id, name, hasAudio, hasVideo, resolution, recordingLayout, size}
|
||||||
|
* - 'recordingStatusChanged' {sessionId, timestamp, id, name, hasAudio, hasVideo, resolution, recordingLayout, size, status}
|
||||||
*
|
*
|
||||||
* PROPERTIES VALUES:
|
* PROPERTIES VALUES:
|
||||||
*
|
*
|
||||||
|
@ -71,6 +73,7 @@ import io.openvidu.server.summary.SessionSummary;
|
||||||
* - resolution string
|
* - resolution string
|
||||||
* - recordingLayout: string
|
* - recordingLayout: string
|
||||||
* - size: number
|
* - size: number
|
||||||
|
* - status: string
|
||||||
* - webrtcConnectionDestroyed.reason: "unsubscribe", "unpublish", "disconnect", "networkDisconnect", "mediaServerDisconnect", "openviduServerStopped"
|
* - webrtcConnectionDestroyed.reason: "unsubscribe", "unpublish", "disconnect", "networkDisconnect", "mediaServerDisconnect", "openviduServerStopped"
|
||||||
* - participantLeft.reason: "unsubscribe", "unpublish", "disconnect", "networkDisconnect", "openviduServerStopped"
|
* - participantLeft.reason: "unsubscribe", "unpublish", "disconnect", "networkDisconnect", "openviduServerStopped"
|
||||||
* - sessionDestroyed.reason: "lastParticipantLeft", "openviduServerStopped"
|
* - sessionDestroyed.reason: "lastParticipantLeft", "openviduServerStopped"
|
||||||
|
@ -117,7 +120,8 @@ public class CallDetailRecord {
|
||||||
public void recordSessionDestroyed(String sessionId, EndReason reason) {
|
public void recordSessionDestroyed(String sessionId, EndReason reason) {
|
||||||
CDREventSession e = this.sessions.remove(sessionId);
|
CDREventSession e = this.sessions.remove(sessionId);
|
||||||
if (e != null) {
|
if (e != null) {
|
||||||
CDREventSession eventSessionEnd = new CDREventSession(e, RecordingManager.finalReason(reason));
|
CDREventSession eventSessionEnd = new CDREventSession(e, RecordingManager.finalReason(reason),
|
||||||
|
System.currentTimeMillis());
|
||||||
this.log(eventSessionEnd);
|
this.log(eventSessionEnd);
|
||||||
|
|
||||||
// Summary: log closed session
|
// Summary: log closed session
|
||||||
|
@ -134,7 +138,7 @@ public class CallDetailRecord {
|
||||||
|
|
||||||
public void recordParticipantLeft(Participant participant, String sessionId, EndReason reason) {
|
public void recordParticipantLeft(Participant participant, String sessionId, EndReason reason) {
|
||||||
CDREventParticipant e = this.participants.remove(participant.getParticipantPublicId());
|
CDREventParticipant e = this.participants.remove(participant.getParticipantPublicId());
|
||||||
CDREventParticipant eventParticipantEnd = new CDREventParticipant(e, reason);
|
CDREventParticipant eventParticipantEnd = new CDREventParticipant(e, reason, System.currentTimeMillis());
|
||||||
this.log(eventParticipantEnd);
|
this.log(eventParticipantEnd);
|
||||||
|
|
||||||
// Summary: update final user ended connection
|
// Summary: update final user ended connection
|
||||||
|
@ -152,7 +156,7 @@ public class CallDetailRecord {
|
||||||
public void stopPublisher(String participantPublicId, String streamId, EndReason reason) {
|
public void stopPublisher(String participantPublicId, String streamId, EndReason reason) {
|
||||||
CDREventWebrtcConnection eventPublisherEnd = this.publications.remove(participantPublicId);
|
CDREventWebrtcConnection eventPublisherEnd = this.publications.remove(participantPublicId);
|
||||||
if (eventPublisherEnd != null) {
|
if (eventPublisherEnd != null) {
|
||||||
eventPublisherEnd = new CDREventWebrtcConnection(eventPublisherEnd, reason);
|
eventPublisherEnd = new CDREventWebrtcConnection(eventPublisherEnd, reason, System.currentTimeMillis());
|
||||||
this.log(eventPublisherEnd);
|
this.log(eventPublisherEnd);
|
||||||
|
|
||||||
// Summary: update final user ended publisher
|
// Summary: update final user ended publisher
|
||||||
|
@ -180,7 +184,8 @@ public class CallDetailRecord {
|
||||||
eventSubscriberEnd = it.next();
|
eventSubscriberEnd = it.next();
|
||||||
if (senderPublicId.equals(eventSubscriberEnd.receivingFrom)) {
|
if (senderPublicId.equals(eventSubscriberEnd.receivingFrom)) {
|
||||||
it.remove();
|
it.remove();
|
||||||
eventSubscriberEnd = new CDREventWebrtcConnection(eventSubscriberEnd, reason);
|
eventSubscriberEnd = new CDREventWebrtcConnection(eventSubscriberEnd, reason,
|
||||||
|
System.currentTimeMillis());
|
||||||
this.log(eventSubscriberEnd);
|
this.log(eventSubscriberEnd);
|
||||||
|
|
||||||
// Summary: update final user ended subscriber
|
// Summary: update final user ended subscriber
|
||||||
|
@ -196,23 +201,35 @@ public class CallDetailRecord {
|
||||||
CDREventRecording recordingStartedEvent = new CDREventRecording(sessionId, recording);
|
CDREventRecording recordingStartedEvent = new CDREventRecording(sessionId, recording);
|
||||||
this.recordings.putIfAbsent(recording.getId(), recordingStartedEvent);
|
this.recordings.putIfAbsent(recording.getId(), recordingStartedEvent);
|
||||||
this.log(new CDREventRecording(sessionId, recording));
|
this.log(new CDREventRecording(sessionId, recording));
|
||||||
|
this.recordRecordingStatusChanged(sessionId, recording, Status.started);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void recordRecordingStopped(String sessionId, Recording recording, EndReason reason) {
|
public void recordRecordingStopped(String sessionId, Recording recording, EndReason reason) {
|
||||||
CDREventRecording recordingStartedEvent = this.recordings.remove(recording.getId());
|
CDREventRecording recordingStartedEvent = this.recordings.remove(recording.getId());
|
||||||
|
final long timestamp = System.currentTimeMillis();
|
||||||
CDREventRecording recordingStoppedEvent = new CDREventRecording(recordingStartedEvent, recording,
|
CDREventRecording recordingStoppedEvent = new CDREventRecording(recordingStartedEvent, recording,
|
||||||
RecordingManager.finalReason(reason));
|
RecordingManager.finalReason(reason), timestamp);
|
||||||
this.log(recordingStoppedEvent);
|
this.log(recordingStoppedEvent);
|
||||||
|
|
||||||
|
this.recordRecordingStatusChanged(recordingStartedEvent, recording, RecordingManager.finalReason(reason),
|
||||||
|
timestamp, Status.stopped);
|
||||||
|
|
||||||
// Summary: update ended recording
|
// Summary: update ended recording
|
||||||
sessionManager.getAccumulatedRecordings(sessionId).add(recordingStoppedEvent);
|
sessionManager.getAccumulatedRecordings(sessionId).add(recordingStoppedEvent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void recordRecordingStatusChanged(String sessionId, Recording recording, Status status) {
|
||||||
|
this.log(new CDREventRecordingStatus(sessionId, recording, status));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void recordRecordingStatusChanged(CDREventRecording recordingStartedEvent, Recording recording,
|
||||||
|
EndReason finalReason, long timestamp, Status status) {
|
||||||
|
this.log(new CDREventRecordingStatus(recordingStartedEvent, recording, finalReason, timestamp, status));
|
||||||
|
}
|
||||||
|
|
||||||
private void log(CDREvent event) {
|
private void log(CDREvent event) {
|
||||||
this.loggers.forEach(logger -> {
|
this.loggers.forEach(logger -> {
|
||||||
if (openviduConfig.isCdrEnabled() || !logger.canBeDisabled()) {
|
|
||||||
logger.log(event);
|
logger.log(event);
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -37,9 +37,11 @@ import com.github.dockerjava.api.model.Volume;
|
||||||
|
|
||||||
import io.openvidu.client.OpenViduException;
|
import io.openvidu.client.OpenViduException;
|
||||||
import io.openvidu.client.OpenViduException.Code;
|
import io.openvidu.client.OpenViduException.Code;
|
||||||
|
import io.openvidu.java.client.Recording.Status;
|
||||||
import io.openvidu.java.client.RecordingLayout;
|
import io.openvidu.java.client.RecordingLayout;
|
||||||
import io.openvidu.java.client.RecordingProperties;
|
import io.openvidu.java.client.RecordingProperties;
|
||||||
import io.openvidu.server.OpenViduServer;
|
import io.openvidu.server.OpenViduServer;
|
||||||
|
import io.openvidu.server.cdr.CallDetailRecord;
|
||||||
import io.openvidu.server.config.OpenviduConfig;
|
import io.openvidu.server.config.OpenviduConfig;
|
||||||
import io.openvidu.server.core.EndReason;
|
import io.openvidu.server.core.EndReason;
|
||||||
import io.openvidu.server.core.Participant;
|
import io.openvidu.server.core.Participant;
|
||||||
|
@ -63,8 +65,8 @@ public class ComposedRecordingService extends RecordingService {
|
||||||
private DockerManager dockerManager;
|
private DockerManager dockerManager;
|
||||||
|
|
||||||
public ComposedRecordingService(RecordingManager recordingManager, RecordingDownloader recordingDownloader,
|
public ComposedRecordingService(RecordingManager recordingManager, RecordingDownloader recordingDownloader,
|
||||||
OpenviduConfig openviduConfig) {
|
OpenviduConfig openviduConfig, CallDetailRecord cdr) {
|
||||||
super(recordingManager, recordingDownloader, openviduConfig);
|
super(recordingManager, recordingDownloader, openviduConfig, cdr);
|
||||||
this.dockerManager = new DockerManager();
|
this.dockerManager = new DockerManager();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -97,6 +99,7 @@ public class ComposedRecordingService extends RecordingService {
|
||||||
return this.stopRecordingWithVideo(session, recording, reason);
|
return this.stopRecordingWithVideo(session, recording, reason);
|
||||||
} else {
|
} else {
|
||||||
recording = this.sealRecordingMetadataFileAsProcessing(recording);
|
recording = this.sealRecordingMetadataFileAsProcessing(recording);
|
||||||
|
this.cdr.recordRecordingStatusChanged(session.getSessionId(), recording, Status.processing);
|
||||||
return this.stopRecordingAudioOnly(session, recording, reason, 0);
|
return this.stopRecordingAudioOnly(session, recording, reason, 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -56,6 +56,7 @@ import io.openvidu.client.OpenViduException.Code;
|
||||||
import io.openvidu.client.internal.ProtocolElements;
|
import io.openvidu.client.internal.ProtocolElements;
|
||||||
import io.openvidu.java.client.Recording.OutputMode;
|
import io.openvidu.java.client.Recording.OutputMode;
|
||||||
import io.openvidu.java.client.RecordingProperties;
|
import io.openvidu.java.client.RecordingProperties;
|
||||||
|
import io.openvidu.server.cdr.CallDetailRecord;
|
||||||
import io.openvidu.server.config.OpenviduConfig;
|
import io.openvidu.server.config.OpenviduConfig;
|
||||||
import io.openvidu.server.core.EndReason;
|
import io.openvidu.server.core.EndReason;
|
||||||
import io.openvidu.server.core.Participant;
|
import io.openvidu.server.core.Participant;
|
||||||
|
@ -92,6 +93,9 @@ public class RecordingManager {
|
||||||
@Autowired
|
@Autowired
|
||||||
private KmsManager kmsManager;
|
private KmsManager kmsManager;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private CallDetailRecord cdr;
|
||||||
|
|
||||||
protected Map<String, Recording> startingRecordings = new ConcurrentHashMap<>();
|
protected Map<String, Recording> startingRecordings = new ConcurrentHashMap<>();
|
||||||
protected Map<String, Recording> startedRecordings = new ConcurrentHashMap<>();
|
protected Map<String, Recording> startedRecordings = new ConcurrentHashMap<>();
|
||||||
protected Map<String, Recording> sessionsRecordings = new ConcurrentHashMap<>();
|
protected Map<String, Recording> sessionsRecordings = new ConcurrentHashMap<>();
|
||||||
|
@ -113,8 +117,8 @@ public class RecordingManager {
|
||||||
RecordingManager.IMAGE_TAG = openviduConfig.getOpenViduRecordingVersion();
|
RecordingManager.IMAGE_TAG = openviduConfig.getOpenViduRecordingVersion();
|
||||||
|
|
||||||
this.dockerManager = new DockerManager();
|
this.dockerManager = new DockerManager();
|
||||||
this.composedRecordingService = new ComposedRecordingService(this, recordingDownloader, openviduConfig);
|
this.composedRecordingService = new ComposedRecordingService(this, recordingDownloader, openviduConfig, cdr);
|
||||||
this.singleStreamRecordingService = new SingleStreamRecordingService(this, recordingDownloader, openviduConfig);
|
this.singleStreamRecordingService = new SingleStreamRecordingService(this, recordingDownloader, openviduConfig, cdr);
|
||||||
|
|
||||||
log.info("Recording module required: Downloading openvidu/openvidu-recording:"
|
log.info("Recording module required: Downloading openvidu/openvidu-recording:"
|
||||||
+ openviduConfig.getOpenViduRecordingVersion() + " Docker image (350MB aprox)");
|
+ openviduConfig.getOpenViduRecordingVersion() + " Docker image (350MB aprox)");
|
||||||
|
|
|
@ -26,6 +26,7 @@ import io.openvidu.client.OpenViduException;
|
||||||
import io.openvidu.client.OpenViduException.Code;
|
import io.openvidu.client.OpenViduException.Code;
|
||||||
import io.openvidu.java.client.RecordingLayout;
|
import io.openvidu.java.client.RecordingLayout;
|
||||||
import io.openvidu.java.client.RecordingProperties;
|
import io.openvidu.java.client.RecordingProperties;
|
||||||
|
import io.openvidu.server.cdr.CallDetailRecord;
|
||||||
import io.openvidu.server.config.OpenviduConfig;
|
import io.openvidu.server.config.OpenviduConfig;
|
||||||
import io.openvidu.server.core.EndReason;
|
import io.openvidu.server.core.EndReason;
|
||||||
import io.openvidu.server.core.Session;
|
import io.openvidu.server.core.Session;
|
||||||
|
@ -41,13 +42,15 @@ public abstract class RecordingService {
|
||||||
protected OpenviduConfig openviduConfig;
|
protected OpenviduConfig openviduConfig;
|
||||||
protected RecordingManager recordingManager;
|
protected RecordingManager recordingManager;
|
||||||
protected RecordingDownloader recordingDownloader;
|
protected RecordingDownloader recordingDownloader;
|
||||||
|
protected CallDetailRecord cdr;
|
||||||
protected CustomFileManager fileWriter = new CustomFileManager();
|
protected CustomFileManager fileWriter = new CustomFileManager();
|
||||||
|
|
||||||
RecordingService(RecordingManager recordingManager, RecordingDownloader recordingDownloader,
|
RecordingService(RecordingManager recordingManager, RecordingDownloader recordingDownloader,
|
||||||
OpenviduConfig openviduConfig) {
|
OpenviduConfig openviduConfig, CallDetailRecord cdr) {
|
||||||
this.recordingManager = recordingManager;
|
this.recordingManager = recordingManager;
|
||||||
this.recordingDownloader = recordingDownloader;
|
this.recordingDownloader = recordingDownloader;
|
||||||
this.openviduConfig = openviduConfig;
|
this.openviduConfig = openviduConfig;
|
||||||
|
this.cdr = cdr;
|
||||||
}
|
}
|
||||||
|
|
||||||
public abstract Recording startRecording(Session session, RecordingProperties properties) throws OpenViduException;
|
public abstract Recording startRecording(Session session, RecordingProperties properties) throws OpenViduException;
|
||||||
|
|
|
@ -52,7 +52,9 @@ import com.google.gson.JsonObject;
|
||||||
|
|
||||||
import io.openvidu.client.OpenViduException;
|
import io.openvidu.client.OpenViduException;
|
||||||
import io.openvidu.client.OpenViduException.Code;
|
import io.openvidu.client.OpenViduException.Code;
|
||||||
|
import io.openvidu.java.client.Recording.Status;
|
||||||
import io.openvidu.java.client.RecordingProperties;
|
import io.openvidu.java.client.RecordingProperties;
|
||||||
|
import io.openvidu.server.cdr.CallDetailRecord;
|
||||||
import io.openvidu.server.config.OpenviduConfig;
|
import io.openvidu.server.config.OpenviduConfig;
|
||||||
import io.openvidu.server.core.EndReason;
|
import io.openvidu.server.core.EndReason;
|
||||||
import io.openvidu.server.core.Participant;
|
import io.openvidu.server.core.Participant;
|
||||||
|
@ -71,8 +73,8 @@ public class SingleStreamRecordingService extends RecordingService {
|
||||||
private final String INDIVIDUAL_STREAM_METADATA_FILE = ".stream.";
|
private final String INDIVIDUAL_STREAM_METADATA_FILE = ".stream.";
|
||||||
|
|
||||||
public SingleStreamRecordingService(RecordingManager recordingManager, RecordingDownloader recordingDownloader,
|
public SingleStreamRecordingService(RecordingManager recordingManager, RecordingDownloader recordingDownloader,
|
||||||
OpenviduConfig openviduConfig) {
|
OpenviduConfig openviduConfig, CallDetailRecord cdr) {
|
||||||
super(recordingManager, recordingDownloader, openviduConfig);
|
super(recordingManager, recordingDownloader, openviduConfig, cdr);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -131,6 +133,7 @@ public class SingleStreamRecordingService extends RecordingService {
|
||||||
@Override
|
@Override
|
||||||
public Recording stopRecording(Session session, Recording recording, EndReason reason) {
|
public Recording stopRecording(Session session, Recording recording, EndReason reason) {
|
||||||
recording = this.sealRecordingMetadataFileAsProcessing(recording);
|
recording = this.sealRecordingMetadataFileAsProcessing(recording);
|
||||||
|
this.cdr.recordRecordingStatusChanged(session.getSessionId(), recording, Status.processing);
|
||||||
return this.stopRecording(session, recording, reason, 0);
|
return this.stopRecording(session, recording, reason, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,59 @@
|
||||||
|
/*
|
||||||
|
* (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.webhook;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import io.openvidu.server.cdr.CDREvent;
|
||||||
|
import io.openvidu.server.cdr.CDRLogger;
|
||||||
|
import io.openvidu.server.config.OpenviduConfig;
|
||||||
|
import io.openvidu.server.kurento.endpoint.KmsEvent;
|
||||||
|
import io.openvidu.server.summary.SessionSummary;
|
||||||
|
|
||||||
|
public class CDRLoggerWebhook implements CDRLogger {
|
||||||
|
|
||||||
|
private Logger log = LoggerFactory.getLogger(CDRLoggerWebhook.class);
|
||||||
|
|
||||||
|
private HttpWebhookSender webhookSender;
|
||||||
|
|
||||||
|
public CDRLoggerWebhook(OpenviduConfig openviduConfig) {
|
||||||
|
this.webhookSender = new HttpWebhookSender(openviduConfig.getOpenViduWebhookEndpoint(),
|
||||||
|
openviduConfig.getOpenViduWebhookHeaders(), openviduConfig.getOpenViduWebhookEvents());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void log(CDREvent event) {
|
||||||
|
try {
|
||||||
|
this.webhookSender.sendHttpPostCallback(event);
|
||||||
|
} catch (IOException e) {
|
||||||
|
log.error("Error sending webhook event: {}", e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void log(KmsEvent event) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void log(SessionSummary sessionSummary) {
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,160 @@
|
||||||
|
/*
|
||||||
|
* (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.webhook;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.UnsupportedEncodingException;
|
||||||
|
import java.security.KeyManagementException;
|
||||||
|
import java.security.KeyStoreException;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.security.cert.CertificateException;
|
||||||
|
import java.security.cert.X509Certificate;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import javax.net.ssl.SSLContext;
|
||||||
|
|
||||||
|
import org.apache.http.Header;
|
||||||
|
import org.apache.http.HttpHeaders;
|
||||||
|
import org.apache.http.HttpResponse;
|
||||||
|
import org.apache.http.client.ClientProtocolException;
|
||||||
|
import org.apache.http.client.HttpClient;
|
||||||
|
import org.apache.http.client.config.RequestConfig;
|
||||||
|
import org.apache.http.client.methods.HttpPost;
|
||||||
|
import org.apache.http.conn.ssl.NoopHostnameVerifier;
|
||||||
|
import org.apache.http.entity.StringEntity;
|
||||||
|
import org.apache.http.impl.client.HttpClientBuilder;
|
||||||
|
import org.apache.http.message.BasicHeader;
|
||||||
|
import org.apache.http.ssl.SSLContextBuilder;
|
||||||
|
import org.apache.http.ssl.TrustStrategy;
|
||||||
|
import org.apache.http.util.EntityUtils;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import com.google.gson.JsonObject;
|
||||||
|
|
||||||
|
import io.openvidu.server.cdr.CDREvent;
|
||||||
|
import io.openvidu.server.cdr.CDREventName;
|
||||||
|
|
||||||
|
public class HttpWebhookSender {
|
||||||
|
|
||||||
|
private static final Logger log = LoggerFactory.getLogger(HttpWebhookSender.class);
|
||||||
|
|
||||||
|
private HttpClient httpClient;
|
||||||
|
private String httpEndpoint;
|
||||||
|
private List<Header> headers;
|
||||||
|
private List<CDREventName> events;
|
||||||
|
|
||||||
|
public HttpWebhookSender(String httpEndpoint, List<Header> headers, List<CDREventName> events) {
|
||||||
|
this.httpEndpoint = httpEndpoint;
|
||||||
|
this.headers = headers;
|
||||||
|
|
||||||
|
boolean contentTypeHeaderAdded = false;
|
||||||
|
for (Header header : this.headers) {
|
||||||
|
if (HttpHeaders.CONTENT_TYPE.equals(header.getName()) && "application/json".equals(header.getValue())) {
|
||||||
|
contentTypeHeaderAdded = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!contentTypeHeaderAdded) {
|
||||||
|
headers.add(new BasicHeader(HttpHeaders.CONTENT_TYPE, "application/json"));
|
||||||
|
}
|
||||||
|
|
||||||
|
this.events = events;
|
||||||
|
|
||||||
|
TrustStrategy trustStrategy = new TrustStrategy() {
|
||||||
|
@Override
|
||||||
|
public boolean isTrusted(X509Certificate[] chain, String authType) throws CertificateException {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
SSLContext sslContext;
|
||||||
|
|
||||||
|
try {
|
||||||
|
sslContext = new SSLContextBuilder().loadTrustMaterial(null, trustStrategy).build();
|
||||||
|
} catch (KeyManagementException | NoSuchAlgorithmException | KeyStoreException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
RequestConfig.Builder requestBuilder = RequestConfig.custom();
|
||||||
|
requestBuilder = requestBuilder.setConnectTimeout(30000);
|
||||||
|
requestBuilder = requestBuilder.setConnectionRequestTimeout(30000);
|
||||||
|
|
||||||
|
this.httpClient = HttpClientBuilder.create().setDefaultRequestConfig(requestBuilder.build())
|
||||||
|
.setConnectionTimeToLive(30, TimeUnit.SECONDS).setSSLHostnameVerifier(NoopHostnameVerifier.INSTANCE)
|
||||||
|
.setSSLContext(sslContext).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws IOException If: A) The HTTP connection cannot be established to the
|
||||||
|
* endpoint B) The response received from the endpoint is
|
||||||
|
* not 200
|
||||||
|
*/
|
||||||
|
public void sendHttpPostCallback(CDREvent event) throws IOException {
|
||||||
|
|
||||||
|
if (!this.events.contains(event.getEventName())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
HttpPost request = new HttpPost(httpEndpoint);
|
||||||
|
|
||||||
|
StringEntity params = null;
|
||||||
|
try {
|
||||||
|
JsonObject jsonEvent = event.toJson();
|
||||||
|
jsonEvent.addProperty("event", event.getEventName().name());
|
||||||
|
params = new StringEntity(jsonEvent.toString());
|
||||||
|
} catch (UnsupportedEncodingException e) {
|
||||||
|
log.error("Cannot create StringEntity from JSON CDREvent. Default HTTP charset is not supported");
|
||||||
|
}
|
||||||
|
|
||||||
|
for (Header header : this.headers) {
|
||||||
|
request.setHeader(header);
|
||||||
|
}
|
||||||
|
request.setEntity(params);
|
||||||
|
|
||||||
|
HttpResponse response = null;
|
||||||
|
try {
|
||||||
|
response = this.httpClient.execute(request);
|
||||||
|
int statusCode = response.getStatusLine().getStatusCode();
|
||||||
|
if ((statusCode == org.apache.http.HttpStatus.SC_OK)) {
|
||||||
|
log.info("Event {} successfully posted to uri {}", event.getEventName().name(), this.httpEndpoint);
|
||||||
|
} else {
|
||||||
|
log.error("Unexpected HTTP status from callback endpoint {}: expected 200, received {}", httpEndpoint,
|
||||||
|
statusCode);
|
||||||
|
throw new IOException("Unexpected HTTP status " + statusCode);
|
||||||
|
}
|
||||||
|
} catch (ClientProtocolException e) {
|
||||||
|
String message = "ClientProtocolException posting event [" + event.getEventName().name() + "] to endpoint "
|
||||||
|
+ httpEndpoint + ": " + e.getMessage();
|
||||||
|
log.error(message);
|
||||||
|
throw new ClientProtocolException(message);
|
||||||
|
} catch (IOException e) {
|
||||||
|
String message = "IOException posting event [" + event.getEventName().name() + "] to endpoint "
|
||||||
|
+ httpEndpoint + ": " + e.getMessage();
|
||||||
|
log.error(message);
|
||||||
|
throw new IOException(message);
|
||||||
|
} finally {
|
||||||
|
if (response != null) {
|
||||||
|
EntityUtils.consumeQuietly(response.getEntity());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -17,10 +17,10 @@ openvidu.secret: MY_SECRET
|
||||||
openvidu.cdr: false
|
openvidu.cdr: false
|
||||||
openvidu.cdr.path: log
|
openvidu.cdr.path: log
|
||||||
|
|
||||||
openvidu.webhook: true
|
openvidu.webhook: false
|
||||||
openvidu.webhook.endpoint: https://localhost/openvidu-endpoint
|
openvidu.webhook.endpoint:
|
||||||
openvidu.webhook.headers: [\"Authorization:\ Basic\ T1BFTlZJRFVBUFA6TVlfU0VDUkVU\"]
|
openvidu.webhook.headers:
|
||||||
openvidu.webhook.events: [\"sessionCreated\",\"sessionDestroyed\",\"recordingStatusChanged\"]
|
openvidu.webhook.events: [\"sessionCreated\",\"sessionDestroyed\",\"participantJoined\",\"participantLeft\",\"webrtcConnectionCreated\",\"webrtcConnectionDestroyed\",\"recordingStatusChanged\"]
|
||||||
|
|
||||||
openvidu.recording: false
|
openvidu.recording: false
|
||||||
openvidu.recording.version: 2.9.0
|
openvidu.recording.version: 2.9.0
|
||||||
|
|
|
@ -57,10 +57,10 @@ node('container') {
|
||||||
sh(script: '''#!/bin/bash
|
sh(script: '''#!/bin/bash
|
||||||
if [ "$DOCKER_RECORDING_VERSION" != "default" ]; then
|
if [ "$DOCKER_RECORDING_VERSION" != "default" ]; then
|
||||||
echo "Using custom openvidu-recording tag: $DOCKER_RECORDING_VERSION"
|
echo "Using custom openvidu-recording tag: $DOCKER_RECORDING_VERSION"
|
||||||
cd openvidu/openvidu-server/target && java -jar -Dopenvidu.publicurl=https://172.17.0.1:4443/ -Dopenvidu.recording=true -Dopenvidu.recording.version=$DOCKER_RECORDING_VERSION openvidu-server-*.jar &> /openvidu-server.log &
|
cd openvidu/openvidu-server/target && java -jar -Dopenvidu.publicurl=https://172.17.0.1:4443/ -Dopenvidu.recording=true -Dopenvidu.recording.version=$DOCKER_RECORDING_VERSION -Dopenvidu.webhook=true -Dopenvidu.webhook.endpoint=http://172.17.0.1:7777/webhook/ openvidu-server-*.jar &> /openvidu-server.log &
|
||||||
else
|
else
|
||||||
echo "Using default openvidu-recording tag"
|
echo "Using default openvidu-recording tag"
|
||||||
cd openvidu/openvidu-server/target && java -jar -Dopenvidu.publicurl=https://172.17.0.1:4443/ -Dopenvidu.recording=true openvidu-server-*.jar &> /openvidu-server.log &
|
cd openvidu/openvidu-server/target && java -jar -Dopenvidu.publicurl=https://172.17.0.1:4443/ -Dopenvidu.recording=true -Dopenvidu.webhook=true -Dopenvidu.webhook.endpoint=http://172.17.0.1:7777/webhook/ openvidu-server-*.jar &> /openvidu-server.log &
|
||||||
fi
|
fi
|
||||||
'''.stripIndent())
|
'''.stripIndent())
|
||||||
sh 'until $(curl --insecure --output /dev/null --silent --head --fail https://OPENVIDUAPP:MY_SECRET@localhost:4443/); do echo "Waiting for openvidu-server..."; sleep 2; done'
|
sh 'until $(curl --insecure --output /dev/null --silent --head --fail https://OPENVIDUAPP:MY_SECRET@localhost:4443/); do echo "Waiting for openvidu-server..."; sleep 2; done'
|
||||||
|
|
|
@ -164,6 +164,11 @@
|
||||||
<version>${version.openvidu.java.client}</version>
|
<version>${version.openvidu.java.client}</version>
|
||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-web</artifactId>
|
||||||
|
<version>${version.spring-boot}</version>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
</project>
|
</project>
|
||||||
|
|
|
@ -102,6 +102,7 @@ import io.openvidu.test.browsers.FirefoxUser;
|
||||||
import io.openvidu.test.browsers.OperaUser;
|
import io.openvidu.test.browsers.OperaUser;
|
||||||
import io.openvidu.test.e2e.utils.CommandLineExecutor;
|
import io.openvidu.test.e2e.utils.CommandLineExecutor;
|
||||||
import io.openvidu.test.e2e.utils.CustomHttpClient;
|
import io.openvidu.test.e2e.utils.CustomHttpClient;
|
||||||
|
import io.openvidu.test.e2e.utils.CustomWebhook;
|
||||||
import io.openvidu.test.e2e.utils.MultimediaFileMetadata;
|
import io.openvidu.test.e2e.utils.MultimediaFileMetadata;
|
||||||
import io.openvidu.test.e2e.utils.Unzipper;
|
import io.openvidu.test.e2e.utils.Unzipper;
|
||||||
|
|
||||||
|
@ -2725,6 +2726,70 @@ public class OpenViduTestAppE2eTest {
|
||||||
gracefullyLeaveParticipants(2);
|
gracefullyLeaveParticipants(2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("Webhook test")
|
||||||
|
void webhookTest() throws Exception {
|
||||||
|
isRecordingTest = true;
|
||||||
|
|
||||||
|
setupBrowser("chrome");
|
||||||
|
|
||||||
|
log.info("Webhook test");
|
||||||
|
|
||||||
|
CountDownLatch initLatch = new CountDownLatch(1);
|
||||||
|
CustomWebhook.main(new String[0], initLatch);
|
||||||
|
|
||||||
|
try {
|
||||||
|
|
||||||
|
if (!initLatch.await(30, TimeUnit.SECONDS)) {
|
||||||
|
Assert.fail("Tiemout waiting for webhook springboot app to start");
|
||||||
|
CustomWebhook.shutDown();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
user.getDriver().findElement(By.id("add-user-btn")).click();
|
||||||
|
user.getDriver().findElement(By.id("session-settings-btn-0")).click();
|
||||||
|
Thread.sleep(1000);
|
||||||
|
user.getDriver().findElement(By.id("recording-mode-select")).click();
|
||||||
|
Thread.sleep(500);
|
||||||
|
user.getDriver().findElement(By.id("option-ALWAYS")).click();
|
||||||
|
Thread.sleep(500);
|
||||||
|
user.getDriver().findElement(By.id("output-mode-select")).click();
|
||||||
|
Thread.sleep(500);
|
||||||
|
user.getDriver().findElement(By.id("option-INDIVIDUAL")).click();
|
||||||
|
Thread.sleep(500);
|
||||||
|
user.getDriver().findElement(By.id("save-btn")).click();
|
||||||
|
Thread.sleep(1000);
|
||||||
|
|
||||||
|
user.getDriver().findElement(By.className("join-btn")).click();
|
||||||
|
|
||||||
|
CustomWebhook.waitForEvent("sessionCreated", 2);
|
||||||
|
CustomWebhook.waitForEvent("participantJoined", 2);
|
||||||
|
CustomWebhook.waitForEvent("webrtcConnectionCreated", 2);
|
||||||
|
JsonObject event = CustomWebhook.waitForEvent("recordingStatusChanged", 10);
|
||||||
|
|
||||||
|
Assert.assertEquals("Wrong recording status in webhook event", "started", event.get("status").getAsString());
|
||||||
|
|
||||||
|
user.getDriver().findElement(By.id("session-api-btn-0")).click();
|
||||||
|
Thread.sleep(1000);
|
||||||
|
user.getDriver().findElement(By.id("close-session-btn")).click();
|
||||||
|
user.getDriver().findElement(By.id("close-dialog-btn")).click();
|
||||||
|
Thread.sleep(1000);
|
||||||
|
|
||||||
|
CustomWebhook.waitForEvent("webrtcConnectionDestroyed", 2);
|
||||||
|
CustomWebhook.waitForEvent("participantLeft", 2);
|
||||||
|
event = CustomWebhook.waitForEvent("recordingStatusChanged", 2);
|
||||||
|
Assert.assertEquals("Wrong recording status in webhook event", "processing", event.get("status").getAsString());
|
||||||
|
event = CustomWebhook.waitForEvent("recordingStatusChanged", 2);
|
||||||
|
Assert.assertEquals("Wrong recording status in webhook event", "stopped", event.get("status").getAsString());
|
||||||
|
|
||||||
|
CustomWebhook.waitForEvent("sessionDestroyed", 2);
|
||||||
|
|
||||||
|
} finally {
|
||||||
|
CustomWebhook.shutDown();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
private void listEmptyRecordings() {
|
private void listEmptyRecordings() {
|
||||||
// List existing recordings (empty)
|
// List existing recordings (empty)
|
||||||
user.getDriver().findElement(By.id("list-recording-btn")).click();
|
user.getDriver().findElement(By.id("list-recording-btn")).click();
|
||||||
|
|
|
@ -0,0 +1,91 @@
|
||||||
|
/*
|
||||||
|
* (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.test.e2e.utils;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.BlockingQueue;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.concurrent.CountDownLatch;
|
||||||
|
import java.util.concurrent.LinkedBlockingDeque;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import org.springframework.boot.SpringApplication;
|
||||||
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
|
import org.springframework.boot.context.event.ApplicationReadyEvent;
|
||||||
|
import org.springframework.context.ConfigurableApplicationContext;
|
||||||
|
import org.springframework.context.event.EventListener;
|
||||||
|
import org.springframework.web.bind.annotation.RequestBody;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
import com.google.gson.JsonObject;
|
||||||
|
import com.google.gson.JsonParser;
|
||||||
|
|
||||||
|
@SpringBootApplication
|
||||||
|
public class CustomWebhook {
|
||||||
|
|
||||||
|
private static ConfigurableApplicationContext context;
|
||||||
|
|
||||||
|
public static CountDownLatch initLatch;
|
||||||
|
private static Map<String, BlockingQueue<JsonObject>> events = new ConcurrentHashMap<>();
|
||||||
|
private static JsonParser jsonParser = new JsonParser();
|
||||||
|
|
||||||
|
public static void main(String[] args, CountDownLatch initLatch) {
|
||||||
|
CustomWebhook.initLatch = initLatch;
|
||||||
|
SpringApplication app = new SpringApplication(CustomWebhook.class);
|
||||||
|
app.setDefaultProperties(Collections.singletonMap("server.port", "7777"));
|
||||||
|
CustomWebhook.context = app.run(args);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void shutDown() {
|
||||||
|
CustomWebhook.context.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static JsonObject waitForEvent(String eventName, int maxSecondsWait) throws Exception {
|
||||||
|
if (events.get(eventName) == null) {
|
||||||
|
events.put(eventName, new LinkedBlockingDeque<>());
|
||||||
|
}
|
||||||
|
JsonObject event = CustomWebhook.events.get(eventName).poll(maxSecondsWait, TimeUnit.SECONDS);
|
||||||
|
if (event == null) {
|
||||||
|
throw new Exception("Timeout waiting for Webhook " + eventName);
|
||||||
|
} else {
|
||||||
|
return event;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
public class GreetingController {
|
||||||
|
@RequestMapping("/webhook")
|
||||||
|
public void greeting(@RequestBody String eventString) {
|
||||||
|
JsonObject event = (JsonObject) jsonParser.parse(eventString);
|
||||||
|
final String eventName = event.get("event").getAsString();
|
||||||
|
System.out.println(event.toString());
|
||||||
|
if (events.get(eventName) == null) {
|
||||||
|
events.put(eventName, new LinkedBlockingDeque<>());
|
||||||
|
}
|
||||||
|
CustomWebhook.events.get(eventName).add(event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventListener(ApplicationReadyEvent.class)
|
||||||
|
public void doSomethingAfterStartup() {
|
||||||
|
CustomWebhook.initLatch.countDown();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -11,16 +11,18 @@
|
||||||
</mat-select>
|
</mat-select>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
<mat-form-field>
|
<mat-form-field>
|
||||||
<mat-select placeholder="RecordingMode" [(ngModel)]="sessionProperties.recordingMode">
|
<mat-select placeholder="RecordingMode" [(ngModel)]="sessionProperties.recordingMode"
|
||||||
|
id="recording-mode-select">
|
||||||
<mat-option *ngFor="let enumerator of enumToArray(recordingMode)" [value]="enumerator">
|
<mat-option *ngFor="let enumerator of enumToArray(recordingMode)" [value]="enumerator">
|
||||||
{{ enumerator }}
|
<span [attr.id]="'option-' + enumerator">{{ enumerator }}</span>
|
||||||
</mat-option>
|
</mat-option>
|
||||||
</mat-select>
|
</mat-select>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
<mat-form-field>
|
<mat-form-field>
|
||||||
<mat-select placeholder="DefaultOutputMode" [(ngModel)]="sessionProperties.defaultOutputMode">
|
<mat-select placeholder="DefaultOutputMode" [(ngModel)]="sessionProperties.defaultOutputMode"
|
||||||
|
id="output-mode-select">
|
||||||
<mat-option *ngFor="let enumerator of enumToArray(defaultOutputMode)" [value]="enumerator">
|
<mat-option *ngFor="let enumerator of enumToArray(defaultOutputMode)" [value]="enumerator">
|
||||||
{{ enumerator }}
|
<span [attr.id]="'option-' + enumerator">{{ enumerator }}</span>
|
||||||
</mat-option>
|
</mat-option>
|
||||||
</mat-select>
|
</mat-select>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
|
@ -31,8 +33,10 @@
|
||||||
</mat-option>
|
</mat-option>
|
||||||
</mat-select>
|
</mat-select>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
<mat-form-field *ngIf="this.sessionProperties.defaultOutputMode === 'COMPOSED' && this.sessionProperties.defaultRecordingLayout === 'CUSTOM'">
|
<mat-form-field
|
||||||
<input matInput placeholder="DefaultCustomLayout" type="text" [(ngModel)]="sessionProperties.defaultCustomLayout">
|
*ngIf="this.sessionProperties.defaultOutputMode === 'COMPOSED' && this.sessionProperties.defaultRecordingLayout === 'CUSTOM'">
|
||||||
|
<input matInput placeholder="DefaultCustomLayout" type="text"
|
||||||
|
[(ngModel)]="sessionProperties.defaultCustomLayout">
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
<mat-form-field>
|
<mat-form-field>
|
||||||
<input matInput placeholder="CustomSessionId" type="text" [(ngModel)]="sessionProperties.customSessionId">
|
<input matInput placeholder="CustomSessionId" type="text" [(ngModel)]="sessionProperties.customSessionId">
|
||||||
|
@ -73,26 +77,32 @@
|
||||||
<label class="label" style="margin-bottom: 8px">Kurento config</label>
|
<label class="label" style="margin-bottom: 8px">Kurento config</label>
|
||||||
<div id="kurento-config-div">
|
<div id="kurento-config-div">
|
||||||
<mat-form-field style="width: 39%; margin-right: 5px">
|
<mat-form-field style="width: 39%; margin-right: 5px">
|
||||||
<input matInput placeholder="Max recv" type="number" [(ngModel)]="tokenOptions.kurentoOptions.videoMaxRecvBandwidth">
|
<input matInput placeholder="Max recv" type="number"
|
||||||
|
[(ngModel)]="tokenOptions.kurentoOptions.videoMaxRecvBandwidth">
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
<mat-form-field style="width: 39%">
|
<mat-form-field style="width: 39%">
|
||||||
<input matInput placeholder="Min recv" type="number" [(ngModel)]="tokenOptions.kurentoOptions.videoMinRecvBandwidth">
|
<input matInput placeholder="Min recv" type="number"
|
||||||
|
[(ngModel)]="tokenOptions.kurentoOptions.videoMinRecvBandwidth">
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
<mat-form-field style="width: 39%; margin-right: 5px">
|
<mat-form-field style="width: 39%; margin-right: 5px">
|
||||||
<input matInput placeholder="Max send" type="number" [(ngModel)]="tokenOptions.kurentoOptions.videoMaxSendBandwidth">
|
<input matInput placeholder="Max send" type="number"
|
||||||
|
[(ngModel)]="tokenOptions.kurentoOptions.videoMaxSendBandwidth">
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
<mat-form-field style="width: 39%">
|
<mat-form-field style="width: 39%">
|
||||||
<input matInput placeholder="Min send" type="number" [(ngModel)]="tokenOptions.kurentoOptions.videoMinSendBandwidth">
|
<input matInput placeholder="Min send" type="number"
|
||||||
|
[(ngModel)]="tokenOptions.kurentoOptions.videoMinSendBandwidth">
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
<mat-chip-list *ngIf="filters.length > 0">
|
<mat-chip-list *ngIf="filters.length > 0">
|
||||||
<mat-chip style="height: 20px" *ngFor="let filterName of filters" (click)="filters.splice(filters.indexOf(filterName, 1))">{{filterName}}</mat-chip>
|
<mat-chip style="height: 20px" *ngFor="let filterName of filters"
|
||||||
|
(click)="filters.splice(filters.indexOf(filterName, 1))">{{filterName}}</mat-chip>
|
||||||
</mat-chip-list>
|
</mat-chip-list>
|
||||||
<mat-form-field style="width: 70%">
|
<mat-form-field style="width: 70%">
|
||||||
<input matInput placeholder="Allowed filter" id="allowed-filter-input" type="text" [(ngModel)]="filterName">
|
<input matInput placeholder="Allowed filter" id="allowed-filter-input" type="text" [(ngModel)]="filterName">
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
<button id="add-allowed-filter-btn" mat-icon-button style="width: 24px; height: 24px; line-height: 24px;"
|
<button id="add-allowed-filter-btn" mat-icon-button style="width: 24px; height: 24px; line-height: 24px;"
|
||||||
title="Add allowed filter" (click)="filters.push(filterName); filterName = '';">
|
title="Add allowed filter" (click)="filters.push(filterName); filterName = '';">
|
||||||
<mat-icon style="font-size: 18px; line-height: 18px; width: 18px; height: 18px" aria-label="Add allowed filter">add_circle</mat-icon>
|
<mat-icon style="font-size: 18px; line-height: 18px; width: 18px; height: 18px"
|
||||||
|
aria-label="Add allowed filter">add_circle</mat-icon>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -108,6 +118,7 @@
|
||||||
|
|
||||||
<mat-dialog-actions>
|
<mat-dialog-actions>
|
||||||
<button id="cancel-btn" mat-button [mat-dialog-close]="undefined">CANCEL</button>
|
<button id="cancel-btn" mat-button [mat-dialog-close]="undefined">CANCEL</button>
|
||||||
<button id="save-btn" mat-button [mat-dialog-close]="{sessionProperties: sessionProperties, turnConf: turnConf, manualTurnConf: manualTurnConf, tokenOptions: generateTokenOptions(), customToken: customToken}">SAVE</button>
|
<button id="save-btn" mat-button
|
||||||
|
[mat-dialog-close]="{sessionProperties: sessionProperties, turnConf: turnConf, manualTurnConf: manualTurnConf, tokenOptions: generateTokenOptions(), customToken: customToken}">SAVE</button>
|
||||||
</mat-dialog-actions>
|
</mat-dialog-actions>
|
||||||
</div>
|
</div>
|
||||||
|
|
Loading…
Reference in New Issue