KMS events stored for each MediaEndpoint

pull/88/merge
pabloFuente 2018-06-19 14:28:04 +02:00
parent 2c90f65535
commit 65cdea70f6
9 changed files with 645 additions and 564 deletions

View File

@ -27,11 +27,6 @@ public class Participant {
private String serverMetadata = ""; // Metadata provided on server side private String serverMetadata = ""; // Metadata provided on server side
private Token token; // Token associated to this participant private Token token; // Token associated to this participant
protected boolean audioActive = true;
protected boolean videoActive = true;
protected String typeOfVideo; // CAMERA, SCREEN
protected int frameRate;
protected boolean streaming = false; protected boolean streaming = false;
protected volatile boolean closed; protected volatile boolean closed;
@ -98,38 +93,6 @@ public class Participant {
this.streaming = streaming; this.streaming = streaming;
} }
public boolean isAudioActive() {
return audioActive;
}
public void setAudioActive(boolean active) {
this.audioActive = active;
}
public boolean isVideoActive() {
return videoActive;
}
public void setVideoActive(boolean active) {
this.videoActive = active;
}
public String getTypeOfVideo() {
return this.typeOfVideo;
}
public void setTypeOfVideo(String typeOfVideo) {
this.typeOfVideo = typeOfVideo;
}
public int getFrameRate() {
return this.frameRate;
}
public void setFrameRate(int frameRate) {
this.frameRate = frameRate;
}
public String getPublisherStremId() { public String getPublisherStremId() {
return null; return null;
} }

View File

@ -39,6 +39,7 @@ import io.openvidu.client.internal.ProtocolElements;
import io.openvidu.server.cdr.CallDetailRecord; import io.openvidu.server.cdr.CallDetailRecord;
import io.openvidu.server.config.InfoHandler; import io.openvidu.server.config.InfoHandler;
import io.openvidu.server.config.OpenviduConfig; import io.openvidu.server.config.OpenviduConfig;
import io.openvidu.server.kurento.core.KurentoParticipant;
import io.openvidu.server.recording.Recording; import io.openvidu.server.recording.Recording;
import io.openvidu.server.rpc.RpcNotificationService; import io.openvidu.server.rpc.RpcNotificationService;
@ -94,17 +95,20 @@ public class SessionEventsHandler {
existingParticipant.getFullMetadata()); existingParticipant.getFullMetadata());
if (existingParticipant.isStreaming()) { if (existingParticipant.isStreaming()) {
KurentoParticipant kParticipant = (KurentoParticipant) existingParticipant;
JsonObject stream = new JsonObject(); JsonObject stream = new JsonObject();
stream.addProperty(ProtocolElements.JOINROOM_PEERSTREAMID_PARAM, stream.addProperty(ProtocolElements.JOINROOM_PEERSTREAMID_PARAM,
existingParticipant.getPublisherStremId()); existingParticipant.getPublisherStremId());
stream.addProperty(ProtocolElements.JOINROOM_PEERSTREAMAUDIOACTIVE_PARAM, stream.addProperty(ProtocolElements.JOINROOM_PEERSTREAMAUDIOACTIVE_PARAM,
existingParticipant.isAudioActive()); kParticipant.getPublisherMediaOptions().audioActive);
stream.addProperty(ProtocolElements.JOINROOM_PEERSTREAMVIDEOACTIVE_PARAM, stream.addProperty(ProtocolElements.JOINROOM_PEERSTREAMVIDEOACTIVE_PARAM,
existingParticipant.isVideoActive()); kParticipant.getPublisherMediaOptions().videoActive);
stream.addProperty(ProtocolElements.JOINROOM_PEERSTREAMTYPEOFVIDEO_PARAM, stream.addProperty(ProtocolElements.JOINROOM_PEERSTREAMTYPEOFVIDEO_PARAM,
existingParticipant.getTypeOfVideo()); kParticipant.getPublisherMediaOptions().typeOfVideo);
stream.addProperty(ProtocolElements.JOINROOM_PEERSTREAMFRAMERATE_PARAM, stream.addProperty(ProtocolElements.JOINROOM_PEERSTREAMFRAMERATE_PARAM,
existingParticipant.getFrameRate()); kParticipant.getPublisherMediaOptions().frameRate);
JsonArray streamsArray = new JsonArray(); JsonArray streamsArray = new JsonArray();
streamsArray.add(stream); streamsArray.add(stream);

View File

@ -47,6 +47,7 @@ import io.openvidu.server.config.InfoHandler;
import io.openvidu.server.core.MediaOptions; import io.openvidu.server.core.MediaOptions;
import io.openvidu.server.core.Participant; import io.openvidu.server.core.Participant;
import io.openvidu.server.kurento.MutedMediaType; import io.openvidu.server.kurento.MutedMediaType;
import io.openvidu.server.kurento.endpoint.KmsEvent;
import io.openvidu.server.kurento.endpoint.MediaEndpoint; import io.openvidu.server.kurento.endpoint.MediaEndpoint;
import io.openvidu.server.kurento.endpoint.PublisherEndpoint; import io.openvidu.server.kurento.endpoint.PublisherEndpoint;
import io.openvidu.server.kurento.endpoint.SdpType; import io.openvidu.server.kurento.endpoint.SdpType;
@ -70,13 +71,13 @@ public class KurentoParticipant extends Participant {
private final ConcurrentMap<String, Filter> filters = new ConcurrentHashMap<>(); private final ConcurrentMap<String, Filter> filters = new ConcurrentHashMap<>();
private final ConcurrentMap<String, SubscriberEndpoint> subscribers = new ConcurrentHashMap<String, SubscriberEndpoint>(); private final ConcurrentMap<String, SubscriberEndpoint> subscribers = new ConcurrentHashMap<String, SubscriberEndpoint>();
public KurentoParticipant(Participant participant, KurentoSession kurentoSession, MediaPipeline pipeline, InfoHandler infoHandler, CallDetailRecord CDR) { public KurentoParticipant(Participant participant, KurentoSession kurentoSession, MediaPipeline pipeline,
InfoHandler infoHandler, CallDetailRecord CDR) {
super(participant.getParticipantPrivateId(), participant.getParticipantPublicId(), participant.getToken(), super(participant.getParticipantPrivateId(), participant.getParticipantPublicId(), participant.getToken(),
participant.getClientMetadata()); participant.getClientMetadata());
this.session = kurentoSession; this.session = kurentoSession;
this.pipeline = pipeline; this.pipeline = pipeline;
this.publisher = new PublisherEndpoint(webParticipant, this, participant.getParticipantPublicId(), this.publisher = new PublisherEndpoint(webParticipant, this, participant.getParticipantPublicId(), pipeline);
pipeline);
for (Participant other : session.getParticipants()) { for (Participant other : session.getParticipants()) {
if (!other.getParticipantPublicId().equals(this.getParticipantPublicId())) { if (!other.getParticipantPublicId().equals(this.getParticipantPublicId())) {
@ -93,10 +94,11 @@ public class KurentoParticipant extends Participant {
if (getPublisher().getEndpoint() == null) { if (getPublisher().getEndpoint() == null) {
throw new OpenViduException(Code.MEDIA_ENDPOINT_ERROR_CODE, "Unable to create publisher endpoint"); throw new OpenViduException(Code.MEDIA_ENDPOINT_ERROR_CODE, "Unable to create publisher endpoint");
} }
publisher.setMediaOptions(mediaOptions);
String publisherStreamId = this.getParticipantPublicId() + "_" + String publisherStreamId = this.getParticipantPublicId() + "_"
(mediaOptions.videoActive ? mediaOptions.typeOfVideo : "MICRO") + "_" + + (mediaOptions.videoActive ? mediaOptions.typeOfVideo : "MICRO") + "_"
RandomStringUtils.random(5, true, false).toUpperCase(); + RandomStringUtils.random(5, true, false).toUpperCase();
this.publisher.getEndpoint().addTag("name", publisherStreamId); this.publisher.getEndpoint().addTag("name", publisherStreamId);
addEndpointListeners(this.publisher); addEndpointListeners(this.publisher);
@ -174,6 +176,10 @@ public class KurentoParticipant extends Participant {
return this.publisher; return this.publisher;
} }
public MediaOptions getPublisherMediaOptions() {
return this.publisher.getMediaOptions();
}
public KurentoSession getSession() { public KurentoSession getSession() {
return session; return session;
} }
@ -230,18 +236,16 @@ public class KurentoParticipant extends Participant {
log.info("PARTICIPANT {}: unpublishing media stream from room {}", this.getParticipantPublicId(), log.info("PARTICIPANT {}: unpublishing media stream from room {}", this.getParticipantPublicId(),
this.session.getSessionId()); this.session.getSessionId());
releasePublisherEndpoint(reason); releasePublisherEndpoint(reason);
this.publisher = new PublisherEndpoint(webParticipant, this, this.getParticipantPublicId(), this.publisher = new PublisherEndpoint(webParticipant, this, this.getParticipantPublicId(), pipeline);
pipeline); log.info("PARTICIPANT {}: released publisher endpoint and left it initialized (ready for future streaming)",
log.info(
"PARTICIPANT {}: released publisher endpoint and left it initialized (ready for future streaming)",
this.getParticipantPublicId()); this.getParticipantPublicId());
} }
public String receiveMediaFrom(Participant sender, String sdpOffer) { public String receiveMediaFrom(Participant sender, String sdpOffer) {
final String senderName = sender.getParticipantPublicId(); final String senderName = sender.getParticipantPublicId();
log.info("PARTICIPANT {}: Request to receive media from {} in room {}", this.getParticipantPublicId(), senderName, log.info("PARTICIPANT {}: Request to receive media from {} in room {}", this.getParticipantPublicId(),
this.session.getSessionId()); senderName, this.session.getSessionId());
log.trace("PARTICIPANT {}: SdpOffer for {} is {}", this.getParticipantPublicId(), senderName, sdpOffer); log.trace("PARTICIPANT {}: SdpOffer for {} is {}", this.getParticipantPublicId(), senderName, sdpOffer);
if (senderName.equals(this.getParticipantPublicId())) { if (senderName.equals(this.getParticipantPublicId())) {
@ -300,8 +304,8 @@ public class KurentoParticipant extends Participant {
try { try {
String sdpAnswer = subscriber.subscribe(sdpOffer, kSender.getPublisher()); String sdpAnswer = subscriber.subscribe(sdpOffer, kSender.getPublisher());
log.trace("PARTICIPANT {}: Subscribing SdpAnswer is {}", this.getParticipantPublicId(), sdpAnswer); log.trace("PARTICIPANT {}: Subscribing SdpAnswer is {}", this.getParticipantPublicId(), sdpAnswer);
log.info("PARTICIPANT {}: Is now receiving video from {} in room {}", this.getParticipantPublicId(), senderName, log.info("PARTICIPANT {}: Is now receiving video from {} in room {}", this.getParticipantPublicId(),
this.session.getSessionId()); senderName, this.session.getSessionId());
if (!ProtocolElements.RECORDER_PARTICIPANT_PUBLICID.equals(this.getParticipantPublicId())) { if (!ProtocolElements.RECORDER_PARTICIPANT_PUBLICID.equals(this.getParticipantPublicId())) {
CDR.recordNewSubscriber(this, this.session.getSessionId(), sender.getParticipantPublicId()); CDR.recordNewSubscriber(this, this.session.getSessionId(), sender.getParticipantPublicId());
@ -330,8 +334,8 @@ public class KurentoParticipant extends Participant {
+ "But there is no such subscriber endpoint.", this.getParticipantPublicId(), senderName); + "But there is no such subscriber endpoint.", this.getParticipantPublicId(), senderName);
} else { } else {
releaseSubscriberEndpoint(senderName, subscriberEndpoint, reason); releaseSubscriberEndpoint(senderName, subscriberEndpoint, reason);
log.info("PARTICIPANT {}: stopped receiving media from {} in room {}", this.getParticipantPublicId(), senderName, log.info("PARTICIPANT {}: stopped receiving media from {} in room {}", this.getParticipantPublicId(),
this.session.getSessionId()); senderName, this.session.getSessionId());
} }
} }
@ -416,17 +420,23 @@ public class KurentoParticipant extends Participant {
* id of another user * id of another user
* @return the endpoint instance * @return the endpoint instance
*/ */
public SubscriberEndpoint getNewOrExistingSubscriber(String remotePublicId) { public SubscriberEndpoint getNewOrExistingSubscriber(String senderPublicId) {
SubscriberEndpoint sendingEndpoint = new SubscriberEndpoint(webParticipant, this, remotePublicId, pipeline);
SubscriberEndpoint existingSendingEndpoint = this.subscribers.putIfAbsent(remotePublicId, sendingEndpoint); KurentoParticipant kSender = (KurentoParticipant) this.session.getParticipantByPublicId(senderPublicId);
SubscriberEndpoint sendingEndpoint = new SubscriberEndpoint(webParticipant, this, senderPublicId, pipeline);
SubscriberEndpoint existingSendingEndpoint = this.subscribers.putIfAbsent(senderPublicId, sendingEndpoint);
if (existingSendingEndpoint != null) { if (existingSendingEndpoint != null) {
sendingEndpoint = existingSendingEndpoint; sendingEndpoint = existingSendingEndpoint;
log.trace("PARTICIPANT {}: Already exists a subscriber endpoint to user {}", this.getParticipantPublicId(), log.trace("PARTICIPANT {}: Already exists a subscriber endpoint to user {}", this.getParticipantPublicId(),
remotePublicId); senderPublicId);
} else { } else {
log.debug("PARTICIPANT {}: New subscriber endpoint to user {}", this.getParticipantPublicId(), log.debug("PARTICIPANT {}: New subscriber endpoint to user {}", this.getParticipantPublicId(),
remotePublicId); senderPublicId);
} }
sendingEndpoint.setMediaOptions(kSender.getPublisherMediaOptions());
return sendingEndpoint; return sendingEndpoint;
} }
@ -522,120 +532,153 @@ public class KurentoParticipant extends Participant {
* System.out.println(msg); this.infoHandler.sendInfo(msg); }); * System.out.println(msg); this.infoHandler.sendInfo(msg); });
*/ */
/*endpoint.getWebEndpoint().addErrorListener((event) -> { /*
String msg = " Error (PUBLISHER) -> " + "ERRORCODE: " + event.getErrorCode() * endpoint.getWebEndpoint().addErrorListener((event) -> { String msg =
+ " | DESCRIPTION: " + event.getDescription() + " | TIMESTAMP: " + System.currentTimeMillis(); * " Error (PUBLISHER) -> " + "ERRORCODE: " +
log.debug(msg); * event.getErrorCode() + " | DESCRIPTION: " + event.getDescription() +
this.infoHandler.sendInfo(msg); * " | TIMESTAMP: " + System.currentTimeMillis(); log.debug(msg);
}); * this.infoHandler.sendInfo(msg); });
*
* endpoint.getWebEndpoint().addMediaFlowInStateChangeListener((event) -> {
* String msg1 = " Media flow in state change (" +
* endpoint.getEndpoint().getTag("name") + ") -> " + "STATE: " +
* event.getState() + " | SOURCE: " + event.getSource().getName() + " | PAD: " +
* event.getPadName() + " | MEDIATYPE: " + event.getMediaType() +
* " | TIMESTAMP: " + System.currentTimeMillis();
*
* endpoint.flowInMedia.put(event.getSource().getName() + "/" +
* event.getMediaType(), event.getSource());
*
* String msg2;
*
* if (endpoint.flowInMedia.values().size() != 2) { msg2 =
* " THERE ARE LESS FLOW IN MEDIA'S THAN EXPECTED IN " +
* endpoint.getEndpoint().getTag("name") + " (" +
* endpoint.flowInMedia.values().size() + ")"; } else { msg2 =
* " NUMBER OF FLOW IN MEDIA'S IS NOW CORRECT IN " +
* endpoint.getEndpoint().getTag("name") + " (" +
* endpoint.flowInMedia.values().size() + ")"; }
*
* log.debug(msg1); log.debug(msg2); this.infoHandler.sendInfo(msg1);
* this.infoHandler.sendInfo(msg2); });
*
* endpoint.getWebEndpoint().addMediaFlowOutStateChangeListener((event) -> {
* String msg1 = " Media flow out state change (" +
* endpoint.getEndpoint().getTag("name") + ") -> " + "STATE: " +
* event.getState() + " | SOURCE: " + event.getSource().getName() + " | PAD: " +
* event.getPadName() + " | MEDIATYPE: " + event.getMediaType() +
* " | TIMESTAMP: " + System.currentTimeMillis();
*
* endpoint.flowOutMedia.put(event.getSource().getName() + "/" +
* event.getMediaType(), event.getSource());
*
* String msg2;
*
* if (endpoint.flowOutMedia.values().size() != 2) { msg2 =
* " THERE ARE LESS FLOW OUT MEDIA'S THAN EXPECTED IN " +
* endpoint.getEndpoint().getTag("name") + " (" +
* endpoint.flowOutMedia.values().size() + ")"; } else { msg2 =
* " NUMBER OF FLOW OUT MEDIA'S IS NOW CORRECT IN " +
* endpoint.getEndpoint().getTag("name") + " (" +
* endpoint.flowOutMedia.values().size() + ")"; }
*
* log.debug(msg1); log.debug(msg2); this.infoHandler.sendInfo(msg1);
* this.infoHandler.sendInfo(msg2); });
*
* endpoint.getWebEndpoint().addMediaSessionStartedListener((event) -> { String
* msg = " Media session started (" +
* endpoint.getEndpoint().getTag("name") + ") | TIMESTAMP: " +
* System.currentTimeMillis(); log.debug(msg); this.infoHandler.sendInfo(msg);
* });
*
* endpoint.getWebEndpoint().addMediaSessionTerminatedListener((event) -> {
* String msg = " Media session terminated (" +
* endpoint.getEndpoint().getTag("name") + ") | TIMESTAMP: " +
* System.currentTimeMillis(); log.debug(msg); this.infoHandler.sendInfo(msg);
* });
*
* endpoint.getWebEndpoint().addMediaStateChangedListener((event) -> { String
* msg = " Media state changed (" +
* endpoint.getEndpoint().getTag("name") + ") from " + event.getOldState() +
* " to " + event.getNewState(); log.debug(msg); this.infoHandler.sendInfo(msg);
* });
*
* endpoint.getWebEndpoint().addConnectionStateChangedListener((event) -> {
* String msg = " Connection state changed (" +
* endpoint.getEndpoint().getTag("name") + ") from " + event.getOldState() +
* " to " + event.getNewState() + " | TIMESTAMP: " + System.currentTimeMillis();
* log.debug(msg); this.infoHandler.sendInfo(msg); });
*
* endpoint.getWebEndpoint().addIceCandidateFoundListener((event) -> { String
* msg = " ICE CANDIDATE FOUND (" +
* endpoint.getEndpoint().getTag("name") + "): CANDIDATE: " +
* event.getCandidate().getCandidate() + " | TIMESTAMP: " +
* System.currentTimeMillis(); log.debug(msg); this.infoHandler.sendInfo(msg);
* });
*
* endpoint.getWebEndpoint().addIceComponentStateChangeListener((event) -> {
* String msg = " ICE COMPONENT STATE CHANGE (" +
* endpoint.getEndpoint().getTag("name") + "): for component " +
* event.getComponentId() + " - STATE: " + event.getState() + " | TIMESTAMP: " +
* System.currentTimeMillis(); log.debug(msg); this.infoHandler.sendInfo(msg);
* });
*
* endpoint.getWebEndpoint().addIceGatheringDoneListener((event) -> { String msg
* = " ICE GATHERING DONE! (" +
* endpoint.getEndpoint().getTag("name") + ")" + " | TIMESTAMP: " +
* System.currentTimeMillis(); log.debug(msg); this.infoHandler.sendInfo(msg);
* });
*/
endpoint.getWebEndpoint().addMediaFlowInStateChangeListener((event) -> { endpoint.getWebEndpoint().addMediaFlowInStateChangeListener(event -> {
String msg1 = " Media flow in state change (" + endpoint.getEndpoint().getTag("name") String msg1 = "Media flow in state change (" + endpoint.getEndpoint().getTag("name") + ") -> " + "STATE: "
+ ") -> " + "STATE: " + event.getState() + " | SOURCE: " + event.getSource().getName() + " | PAD: " + event.getState() + " | SOURCE: " + event.getSource().getName() + " | PAD: " + event.getPadName()
+ event.getPadName() + " | MEDIATYPE: " + event.getMediaType() + " | TIMESTAMP: " + " | MEDIATYPE: " + event.getMediaType() + " | TIMESTAMP: " + System.currentTimeMillis();
+ System.currentTimeMillis();
endpoint.flowInMedia.put(event.getSource().getName() + "/" + event.getMediaType(), event.getSource()); endpoint.flowInMedia.put(event.getSource().getName(), event.getMediaType());
if (endpoint.getMediaOptions().audioActive && endpoint.getMediaOptions().videoActive
String msg2; && endpoint.flowInMedia.values().size() == 2) {
endpoint.kmsEvents.add(new KmsEvent(event));
if (endpoint.flowInMedia.values().size() != 2) { } else if (endpoint.flowInMedia.values().size() == 1) {
msg2 = " THERE ARE LESS FLOW IN MEDIA'S THAN EXPECTED IN " endpoint.kmsEvents.add(new KmsEvent(event));
+ endpoint.getEndpoint().getTag("name") + " (" + endpoint.flowInMedia.values().size() + ")";
} else {
msg2 = " NUMBER OF FLOW IN MEDIA'S IS NOW CORRECT IN "
+ endpoint.getEndpoint().getTag("name") + " (" + endpoint.flowInMedia.values().size() + ")";
} }
log.debug(msg1); log.info(msg1);
log.debug(msg2);
this.infoHandler.sendInfo(msg1); this.infoHandler.sendInfo(msg1);
this.infoHandler.sendInfo(msg2);
}); });
endpoint.getWebEndpoint().addMediaFlowOutStateChangeListener((event) -> { endpoint.getWebEndpoint().addMediaFlowOutStateChangeListener(event -> {
String msg1 = " Media flow out state change (" + endpoint.getEndpoint().getTag("name") String msg1 = "Media flow out state change (" + endpoint.getEndpoint().getTag("name") + ") -> " + "STATE: "
+ ") -> " + "STATE: " + event.getState() + " | SOURCE: " + event.getSource().getName() + " | PAD: " + event.getState() + " | SOURCE: " + event.getSource().getName() + " | PAD: " + event.getPadName()
+ event.getPadName() + " | MEDIATYPE: " + event.getMediaType() + " | TIMESTAMP: " + " | MEDIATYPE: " + event.getMediaType() + " | TIMESTAMP: " + System.currentTimeMillis();
+ System.currentTimeMillis();
endpoint.flowOutMedia.put(event.getSource().getName() + "/" + event.getMediaType(), event.getSource()); endpoint.flowOutMedia.put(event.getSource().getName(), event.getMediaType());
if (endpoint.getMediaOptions().audioActive && endpoint.getMediaOptions().videoActive
String msg2; && endpoint.flowOutMedia.values().size() == 2) {
endpoint.kmsEvents.add(new KmsEvent(event));
if (endpoint.flowOutMedia.values().size() != 2) { } else if (endpoint.flowOutMedia.values().size() == 1) {
msg2 = " THERE ARE LESS FLOW OUT MEDIA'S THAN EXPECTED IN " endpoint.kmsEvents.add(new KmsEvent(event));
+ endpoint.getEndpoint().getTag("name") + " (" + endpoint.flowOutMedia.values().size() + ")";
} else {
msg2 = " NUMBER OF FLOW OUT MEDIA'S IS NOW CORRECT IN "
+ endpoint.getEndpoint().getTag("name") + " (" + endpoint.flowOutMedia.values().size() + ")";
} }
log.debug(msg1); log.info(msg1);
log.debug(msg2);
this.infoHandler.sendInfo(msg1); this.infoHandler.sendInfo(msg1);
this.infoHandler.sendInfo(msg2);
}); });
endpoint.getWebEndpoint().addMediaSessionStartedListener((event) -> { endpoint.getWebEndpoint().addIceGatheringDoneListener(event -> {
String msg = " Media session started (" + endpoint.getEndpoint().getTag("name") endpoint.kmsEvents.add(new KmsEvent(event));
+ ") | TIMESTAMP: " + System.currentTimeMillis();
log.debug(msg);
this.infoHandler.sendInfo(msg);
}); });
endpoint.getWebEndpoint().addMediaSessionTerminatedListener((event) -> { endpoint.getWebEndpoint().addConnectionStateChangedListener(event -> {
String msg = " Media session terminated (" + endpoint.getEndpoint().getTag("name") endpoint.kmsEvents.add(new KmsEvent(event));
+ ") | TIMESTAMP: " + System.currentTimeMillis();
log.debug(msg);
this.infoHandler.sendInfo(msg);
}); });
endpoint.getWebEndpoint().addMediaStateChangedListener((event) -> { endpoint.getWebEndpoint().addNewCandidatePairSelectedListener(event -> {
String msg = " Media state changed (" + endpoint.getEndpoint().getTag("name") + ") from "
+ event.getOldState() + " to " + event.getNewState();
log.debug(msg);
this.infoHandler.sendInfo(msg);
});
endpoint.getWebEndpoint().addConnectionStateChangedListener((event) -> {
String msg = " Connection state changed (" + endpoint.getEndpoint().getTag("name")
+ ") from " + event.getOldState() + " to " + event.getNewState() + " | TIMESTAMP: "
+ System.currentTimeMillis();
log.debug(msg);
this.infoHandler.sendInfo(msg);
});
endpoint.getWebEndpoint().addIceCandidateFoundListener((event) -> {
String msg = " ICE CANDIDATE FOUND (" + endpoint.getEndpoint().getTag("name")
+ "): CANDIDATE: " + event.getCandidate().getCandidate() + " | TIMESTAMP: "
+ System.currentTimeMillis();
log.debug(msg);
this.infoHandler.sendInfo(msg);
});
endpoint.getWebEndpoint().addIceComponentStateChangeListener((event) -> {
String msg = " ICE COMPONENT STATE CHANGE (" + endpoint.getEndpoint().getTag("name")
+ "): for component " + event.getComponentId() + " - STATE: " + event.getState() + " | TIMESTAMP: "
+ System.currentTimeMillis();
log.debug(msg);
this.infoHandler.sendInfo(msg);
});
endpoint.getWebEndpoint().addIceGatheringDoneListener((event) -> {
String msg = " ICE GATHERING DONE! (" + endpoint.getEndpoint().getTag("name") + ")"
+ " | TIMESTAMP: " + System.currentTimeMillis();
log.debug(msg);
this.infoHandler.sendInfo(msg);
});*/
endpoint.getWebEndpoint().addNewCandidatePairSelectedListener((event) -> {
endpoint.selectedLocalIceCandidate = event.getCandidatePair().getLocalCandidate(); endpoint.selectedLocalIceCandidate = event.getCandidatePair().getLocalCandidate();
endpoint.selectedRemoteIceCandidate = event.getCandidatePair().getRemoteCandidate(); endpoint.selectedRemoteIceCandidate = event.getCandidatePair().getRemoteCandidate();
String msg = "ICE CANDIDATE SELECTED (" + endpoint.getEndpoint().getTag("name") endpoint.kmsEvents.add(new KmsEvent(event));
+ "): LOCAL CANDIDATE: " + endpoint.selectedLocalIceCandidate + String msg = "ICE CANDIDATE SELECTED (" + endpoint.getEndpoint().getTag("name") + "): LOCAL CANDIDATE: "
" | REMOTE CANDIDATE: " + endpoint.selectedRemoteIceCandidate + + endpoint.selectedLocalIceCandidate + " | REMOTE CANDIDATE: " + endpoint.selectedRemoteIceCandidate
" | TIMESTAMP: " + System.currentTimeMillis(); + " | TIMESTAMP: " + System.currentTimeMillis();
log.warn(msg); log.warn(msg);
this.infoHandler.sendInfo(msg); this.infoHandler.sendInfo(msg);
}); });

View File

@ -268,11 +268,6 @@ public class KurentoSessionManager extends SessionManager {
session.newPublisher(participant); session.newPublisher(participant);
kurentoParticipant.setAudioActive(kurentoOptions.audioActive);
kurentoParticipant.setVideoActive(kurentoOptions.videoActive);
kurentoParticipant.setTypeOfVideo(kurentoOptions.typeOfVideo);
kurentoParticipant.setFrameRate(kurentoOptions.frameRate);
participants = kurentoParticipant.getSession().getParticipants(); participants = kurentoParticipant.getSession().getParticipants();
if (sdpAnswer != null) { if (sdpAnswer != null) {

View File

@ -0,0 +1,14 @@
package io.openvidu.server.kurento.endpoint;
import org.kurento.client.MediaEvent;
public class KmsEvent {
long timestamp;
MediaEvent event;
public KmsEvent(MediaEvent event) {
this.event = event;
this.timestamp = System.currentTimeMillis();
}
}

View File

@ -19,9 +19,12 @@ package io.openvidu.server.kurento.endpoint;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.Map; import java.util.Map;
import java.util.Queue;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.CountDownLatch; import java.util.concurrent.CountDownLatch;
import org.json.simple.JSONArray;
import org.json.simple.JSONObject; import org.json.simple.JSONObject;
import org.kurento.client.Continuation; import org.kurento.client.Continuation;
import org.kurento.client.ErrorEvent; import org.kurento.client.ErrorEvent;
@ -29,8 +32,8 @@ import org.kurento.client.EventListener;
import org.kurento.client.IceCandidate; import org.kurento.client.IceCandidate;
import org.kurento.client.ListenerSubscription; import org.kurento.client.ListenerSubscription;
import org.kurento.client.MediaElement; import org.kurento.client.MediaElement;
import org.kurento.client.MediaObject;
import org.kurento.client.MediaPipeline; import org.kurento.client.MediaPipeline;
import org.kurento.client.MediaType;
import org.kurento.client.OnIceCandidateEvent; import org.kurento.client.OnIceCandidateEvent;
import org.kurento.client.RtpEndpoint; import org.kurento.client.RtpEndpoint;
import org.kurento.client.SdpEndpoint; import org.kurento.client.SdpEndpoint;
@ -40,6 +43,7 @@ import org.slf4j.LoggerFactory;
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.core.MediaOptions;
import io.openvidu.server.core.Participant; import io.openvidu.server.core.Participant;
import io.openvidu.server.kurento.MutedMediaType; import io.openvidu.server.kurento.MutedMediaType;
import io.openvidu.server.kurento.core.KurentoParticipant; import io.openvidu.server.kurento.core.KurentoParticipant;
@ -70,11 +74,13 @@ public abstract class MediaEndpoint {
private MutedMediaType muteType; private MutedMediaType muteType;
public Map<String, MediaObject> flowInMedia = new ConcurrentHashMap<>(); private MediaOptions mediaOptions;
public Map<String, MediaObject> flowOutMedia = new ConcurrentHashMap<>(); public Map<String, MediaType> flowInMedia = new ConcurrentHashMap<>();
public Map<String, MediaType> flowOutMedia = new ConcurrentHashMap<>();
public String selectedLocalIceCandidate; public String selectedLocalIceCandidate;
public String selectedRemoteIceCandidate; public String selectedRemoteIceCandidate;
public Queue<KmsEvent> kmsEvents = new ConcurrentLinkedQueue<>();
/** /**
* Constructor to set the owner, the endpoint's name and the media pipeline. * Constructor to set the owner, the endpoint's name and the media pipeline.
@ -98,6 +104,14 @@ public abstract class MediaEndpoint {
this.setMediaPipeline(pipeline); this.setMediaPipeline(pipeline);
} }
public MediaOptions getMediaOptions() {
return mediaOptions;
}
public void setMediaOptions(MediaOptions mediaOptions) {
this.mediaOptions = mediaOptions;
}
public boolean isWeb() { public boolean isWeb() {
return web; return web;
} }
@ -495,6 +509,16 @@ public abstract class MediaEndpoint {
json.put("webrtcTagName", this.getEndpoint().getTag("name")); json.put("webrtcTagName", this.getEndpoint().getTag("name"));
json.put("localCandidate", this.selectedLocalIceCandidate); json.put("localCandidate", this.selectedLocalIceCandidate);
json.put("remoteCandidate", this.selectedRemoteIceCandidate); json.put("remoteCandidate", this.selectedRemoteIceCandidate);
JSONArray jsonArray = new JSONArray();
for (KmsEvent event : this.kmsEvents) {
JSONObject jsonKmsEvent = new JSONObject();
jsonKmsEvent.put(event.event.getType(), event.timestamp);
jsonArray.add(jsonKmsEvent);
}
json.put("events", jsonArray);
return json; return json;
} }
} }

View File

@ -44,410 +44,422 @@ import io.openvidu.server.kurento.core.KurentoParticipant;
* @author <a href="mailto:rvlad@naevatec.com">Radu Tom Vlad</a> * @author <a href="mailto:rvlad@naevatec.com">Radu Tom Vlad</a>
*/ */
public class PublisherEndpoint extends MediaEndpoint { public class PublisherEndpoint extends MediaEndpoint {
private final static Logger log = LoggerFactory.getLogger(PublisherEndpoint.class); private final static Logger log = LoggerFactory.getLogger(PublisherEndpoint.class);
private PassThrough passThru = null; private PassThrough passThru = null;
private ListenerSubscription passThruSubscription = null; private ListenerSubscription passThruSubscription = null;
private Map<String, MediaElement> elements = new HashMap<String, MediaElement>(); private Map<String, MediaElement> elements = new HashMap<String, MediaElement>();
private LinkedList<String> elementIds = new LinkedList<String>(); private LinkedList<String> elementIds = new LinkedList<String>();
private boolean connected = false; private boolean connected = false;
private Map<String, ListenerSubscription> elementsErrorSubscriptions = private Map<String, ListenerSubscription> elementsErrorSubscriptions = new HashMap<String, ListenerSubscription>();
new HashMap<String, ListenerSubscription>();
public PublisherEndpoint(boolean web, KurentoParticipant owner, public PublisherEndpoint(boolean web, KurentoParticipant owner, String endpointName, MediaPipeline pipeline) {
String endpointName, MediaPipeline pipeline) { super(web, owner, endpointName, pipeline, log);
super(web, owner, endpointName, pipeline, log); }
}
@Override @Override
protected void internalEndpointInitialization(final CountDownLatch endpointLatch) { protected void internalEndpointInitialization(final CountDownLatch endpointLatch) {
super.internalEndpointInitialization(endpointLatch); super.internalEndpointInitialization(endpointLatch);
passThru = new PassThrough.Builder(getPipeline()).build(); passThru = new PassThrough.Builder(getPipeline()).build();
passThruSubscription = registerElemErrListener(passThru); passThruSubscription = registerElemErrListener(passThru);
} }
@Override @Override
public synchronized void unregisterErrorListeners() { public synchronized void unregisterErrorListeners() {
super.unregisterErrorListeners(); super.unregisterErrorListeners();
unregisterElementErrListener(passThru, passThruSubscription); unregisterElementErrListener(passThru, passThruSubscription);
for (String elemId : elementIds) { for (String elemId : elementIds) {
unregisterElementErrListener(elements.get(elemId), elementsErrorSubscriptions.remove(elemId)); unregisterElementErrListener(elements.get(elemId), elementsErrorSubscriptions.remove(elemId));
} }
} }
/** /**
* @return all media elements created for this publisher, except for the main element ( * @return all media elements created for this publisher, except for the main
* {@link WebRtcEndpoint}) * element ( {@link WebRtcEndpoint})
*/ */
public synchronized Collection<MediaElement> getMediaElements() { public synchronized Collection<MediaElement> getMediaElements() {
if (passThru != null) { if (passThru != null) {
elements.put(passThru.getId(), passThru); elements.put(passThru.getId(), passThru);
} }
return elements.values(); return elements.values();
} }
/** /**
* Initializes this media endpoint for publishing media and processes the SDP offer or answer. If * Initializes this media endpoint for publishing media and processes the SDP
* the internal endpoint is an {@link WebRtcEndpoint}, it first registers an event listener for * offer or answer. If the internal endpoint is an {@link WebRtcEndpoint}, it
* the ICE candidates and instructs the endpoint to start gathering the candidates. If required, * first registers an event listener for the ICE candidates and instructs the
* it connects to itself (after applying the intermediate media elements and the * endpoint to start gathering the candidates. If required, it connects to
* {@link PassThrough}) to allow loopback of the media stream. * itself (after applying the intermediate media elements and the
* * {@link PassThrough}) to allow loopback of the media stream.
* @param sdpType indicates the type of the sdpString (offer or answer) *
* @param sdpString offer or answer from the remote peer * @param sdpType
* @param doLoopback loopback flag * indicates the type of the sdpString (offer or answer)
* @param loopbackAlternativeSrc alternative loopback source * @param sdpString
* @param loopbackConnectionType how to connect the loopback source * offer or answer from the remote peer
* @return the SDP response (the answer if processing an offer SDP, otherwise is the updated offer * @param doLoopback
* generated previously by this endpoint) * loopback flag
*/ * @param loopbackAlternativeSrc
public synchronized String publish(SdpType sdpType, String sdpString, boolean doLoopback, * alternative loopback source
MediaElement loopbackAlternativeSrc, MediaType loopbackConnectionType) { * @param loopbackConnectionType
registerOnIceCandidateEventListener(); * how to connect the loopback source
if (doLoopback) { * @return the SDP response (the answer if processing an offer SDP, otherwise is
if (loopbackAlternativeSrc == null) { * the updated offer generated previously by this endpoint)
connect(this.getEndpoint(), loopbackConnectionType); */
} else { public synchronized String publish(SdpType sdpType, String sdpString, boolean doLoopback,
connectAltLoopbackSrc(loopbackAlternativeSrc, loopbackConnectionType); MediaElement loopbackAlternativeSrc, MediaType loopbackConnectionType) {
} registerOnIceCandidateEventListener();
} else { if (doLoopback) {
innerConnect(); if (loopbackAlternativeSrc == null) {
} connect(this.getEndpoint(), loopbackConnectionType);
String sdpResponse = null; } else {
switch (sdpType) { connectAltLoopbackSrc(loopbackAlternativeSrc, loopbackConnectionType);
case ANSWER: }
sdpResponse = processAnswer(sdpString); } else {
break; innerConnect();
case OFFER: }
sdpResponse = processOffer(sdpString); String sdpResponse = null;
break; switch (sdpType) {
default: case ANSWER:
throw new OpenViduException(Code.MEDIA_SDP_ERROR_CODE, "Sdp type not supported: " + sdpType); sdpResponse = processAnswer(sdpString);
} break;
gatherCandidates(); case OFFER:
return sdpResponse; sdpResponse = processOffer(sdpString);
} break;
default:
throw new OpenViduException(Code.MEDIA_SDP_ERROR_CODE, "Sdp type not supported: " + sdpType);
}
gatherCandidates();
return sdpResponse;
}
public synchronized String preparePublishConnection() { public synchronized String preparePublishConnection() {
return generateOffer(); return generateOffer();
} }
public synchronized void connect(MediaElement sink) { public synchronized void connect(MediaElement sink) {
if (!connected) { if (!connected) {
innerConnect(); innerConnect();
} }
internalSinkConnect(passThru, sink); internalSinkConnect(passThru, sink);
} }
public synchronized void connect(MediaElement sink, MediaType type) { public synchronized void connect(MediaElement sink, MediaType type) {
if (!connected) { if (!connected) {
innerConnect(); innerConnect();
} }
internalSinkConnect(passThru, sink, type); internalSinkConnect(passThru, sink, type);
} }
public synchronized void disconnectFrom(MediaElement sink) { public synchronized void disconnectFrom(MediaElement sink) {
internalSinkDisconnect(passThru, sink); internalSinkDisconnect(passThru, sink);
} }
public synchronized void disconnectFrom(MediaElement sink, MediaType type) { public synchronized void disconnectFrom(MediaElement sink, MediaType type) {
internalSinkDisconnect(passThru, sink, type); internalSinkDisconnect(passThru, sink, type);
} }
/** /**
* Changes the media passing through a chain of media elements by applying the specified * Changes the media passing through a chain of media elements by applying the
* element/shaper. The element is plugged into the stream only if the chain has been initialized * specified element/shaper. The element is plugged into the stream only if the
* (a.k.a. media streaming has started), otherwise it is left ready for when the connections * chain has been initialized (a.k.a. media streaming has started), otherwise it
* between elements will materialize and the streaming begins. * is left ready for when the connections between elements will materialize and
* * the streaming begins.
* @param shaper {@link MediaElement} that will be linked to the end of the chain (e.g. a filter) *
* @return the element's id * @param shaper
* @throws OpenViduException if thrown, the media element was not added * {@link MediaElement} that will be linked to the end of the chain
*/ * (e.g. a filter)
public String apply(MediaElement shaper) throws OpenViduException { * @return the element's id
return apply(shaper, null); * @throws OpenViduException
} * if thrown, the media element was not added
*/
public String apply(MediaElement shaper) throws OpenViduException {
return apply(shaper, null);
}
/** /**
* Same as {@link #apply(MediaElement)}, can specify the media type that will be streamed through * Same as {@link #apply(MediaElement)}, can specify the media type that will be
* the shaper element. * streamed through the shaper element.
* *
* @param shaper {@link MediaElement} that will be linked to the end of the chain (e.g. a filter) * @param shaper
* @param type indicates which type of media will be connected to the shaper * {@link MediaElement} that will be linked to the end of the chain
* ({@link MediaType}), if * (e.g. a filter)
* null then the connection is mixed * @param type
* @return the element's id * indicates which type of media will be connected to the shaper
* @throws OpenViduException if thrown, the media element was not added * ({@link MediaType}), if null then the connection is mixed
*/ * @return the element's id
public synchronized String apply(MediaElement shaper, MediaType type) throws OpenViduException { * @throws OpenViduException
String id = shaper.getId(); * if thrown, the media element was not added
if (id == null) { */
throw new OpenViduException(Code.MEDIA_WEBRTC_ENDPOINT_ERROR_CODE, public synchronized String apply(MediaElement shaper, MediaType type) throws OpenViduException {
"Unable to connect media element with null id"); String id = shaper.getId();
} if (id == null) {
if (elements.containsKey(id)) { throw new OpenViduException(Code.MEDIA_WEBRTC_ENDPOINT_ERROR_CODE,
throw new OpenViduException(Code.MEDIA_WEBRTC_ENDPOINT_ERROR_CODE, "Unable to connect media element with null id");
"This endpoint already has a media element with id " + id); }
} if (elements.containsKey(id)) {
MediaElement first = null; throw new OpenViduException(Code.MEDIA_WEBRTC_ENDPOINT_ERROR_CODE,
if (!elementIds.isEmpty()) { "This endpoint already has a media element with id " + id);
first = elements.get(elementIds.getFirst()); }
} MediaElement first = null;
if (connected) { if (!elementIds.isEmpty()) {
if (first != null) { first = elements.get(elementIds.getFirst());
internalSinkConnect(first, shaper, type); }
} else { if (connected) {
internalSinkConnect(this.getEndpoint(), shaper, type); if (first != null) {
} internalSinkConnect(first, shaper, type);
internalSinkConnect(shaper, passThru, type); } else {
} internalSinkConnect(this.getEndpoint(), shaper, type);
elementIds.addFirst(id); }
elements.put(id, shaper); internalSinkConnect(shaper, passThru, type);
elementsErrorSubscriptions.put(id, registerElemErrListener(shaper)); }
return id; elementIds.addFirst(id);
} elements.put(id, shaper);
elementsErrorSubscriptions.put(id, registerElemErrListener(shaper));
return id;
}
/** /**
* Removes the media element object found from the media chain structure. The object is released. * Removes the media element object found from the media chain structure. The
* If the chain is connected, both adjacent remaining elements will be interconnected. * object is released. If the chain is connected, both adjacent remaining
* * elements will be interconnected.
* @param shaper {@link MediaElement} that will be removed from the chain *
* @throws OpenViduException if thrown, the media element was not removed * @param shaper
*/ * {@link MediaElement} that will be removed from the chain
public synchronized void revert(MediaElement shaper) throws OpenViduException { * @throws OpenViduException
revert (shaper, true); * if thrown, the media element was not removed
} */
public synchronized void revert(MediaElement shaper) throws OpenViduException {
revert(shaper, true);
}
public synchronized void revert(MediaElement shaper, boolean releaseElement) throws public synchronized void revert(MediaElement shaper, boolean releaseElement) throws OpenViduException {
OpenViduException { final String elementId = shaper.getId();
final String elementId = shaper.getId(); if (!elements.containsKey(elementId)) {
if (!elements.containsKey(elementId)) { throw new OpenViduException(Code.MEDIA_ENDPOINT_ERROR_CODE,
throw new OpenViduException(Code.MEDIA_ENDPOINT_ERROR_CODE, "This endpoint (" + getEndpointName() + ") has no media element with id " + elementId);
"This endpoint (" + getEndpointName() + ") has no media element with id " + elementId); }
}
MediaElement element = elements.remove(elementId); MediaElement element = elements.remove(elementId);
unregisterElementErrListener(element, elementsErrorSubscriptions.remove(elementId)); unregisterElementErrListener(element, elementsErrorSubscriptions.remove(elementId));
// careful, the order in the elems list is reverted // careful, the order in the elems list is reverted
if (connected) { if (connected) {
String nextId = getNext(elementId); String nextId = getNext(elementId);
String prevId = getPrevious(elementId); String prevId = getPrevious(elementId);
// next connects to prev // next connects to prev
MediaElement prev = null; MediaElement prev = null;
MediaElement next = null; MediaElement next = null;
if (nextId != null) { if (nextId != null) {
next = elements.get(nextId); next = elements.get(nextId);
} else { } else {
next = this.getEndpoint(); next = this.getEndpoint();
} }
if (prevId != null) { if (prevId != null) {
prev = elements.get(prevId); prev = elements.get(prevId);
} else { } else {
prev = passThru; prev = passThru;
} }
internalSinkConnect(next, prev); internalSinkConnect(next, prev);
} }
elementIds.remove(elementId); elementIds.remove(elementId);
if (releaseElement) { if (releaseElement) {
element.release(new Continuation<Void>() { element.release(new Continuation<Void>() {
@Override @Override
public void onSuccess(Void result) throws Exception { public void onSuccess(Void result) throws Exception {
log.trace("EP {}: Released media element {}", getEndpointName(), elementId); log.trace("EP {}: Released media element {}", getEndpointName(), elementId);
} }
@Override @Override
public void onError(Throwable cause) throws Exception { public void onError(Throwable cause) throws Exception {
log.error("EP {}: Failed to release media element {}", getEndpointName(), elementId, cause); log.error("EP {}: Failed to release media element {}", getEndpointName(), elementId, cause);
} }
}); });
} }
} }
@Override @Override
public synchronized void mute(MutedMediaType muteType) { public synchronized void mute(MutedMediaType muteType) {
MediaElement sink = passThru; MediaElement sink = passThru;
if (!elements.isEmpty()) { if (!elements.isEmpty()) {
String sinkId = elementIds.peekLast(); String sinkId = elementIds.peekLast();
if (!elements.containsKey(sinkId)) { if (!elements.containsKey(sinkId)) {
throw new OpenViduException(Code.MEDIA_ENDPOINT_ERROR_CODE, throw new OpenViduException(Code.MEDIA_ENDPOINT_ERROR_CODE,
"This endpoint (" + getEndpointName() + ") has no media element with id " + sinkId "This endpoint (" + getEndpointName() + ") has no media element with id " + sinkId
+ " (should've been connected to the internal ep)"); + " (should've been connected to the internal ep)");
} }
sink = elements.get(sinkId); sink = elements.get(sinkId);
} else { } else {
log.debug("Will mute connection of WebRTC and PassThrough (no other elems)"); log.debug("Will mute connection of WebRTC and PassThrough (no other elems)");
} }
switch (muteType) { switch (muteType) {
case ALL: case ALL:
internalSinkDisconnect(this.getEndpoint(), sink); internalSinkDisconnect(this.getEndpoint(), sink);
break; break;
case AUDIO: case AUDIO:
internalSinkDisconnect(this.getEndpoint(), sink, MediaType.AUDIO); internalSinkDisconnect(this.getEndpoint(), sink, MediaType.AUDIO);
break; break;
case VIDEO: case VIDEO:
internalSinkDisconnect(this.getEndpoint(), sink, MediaType.VIDEO); internalSinkDisconnect(this.getEndpoint(), sink, MediaType.VIDEO);
break; break;
} }
resolveCurrentMuteType(muteType); resolveCurrentMuteType(muteType);
} }
@Override @Override
public synchronized void unmute() { public synchronized void unmute() {
MediaElement sink = passThru; MediaElement sink = passThru;
if (!elements.isEmpty()) { if (!elements.isEmpty()) {
String sinkId = elementIds.peekLast(); String sinkId = elementIds.peekLast();
if (!elements.containsKey(sinkId)) { if (!elements.containsKey(sinkId)) {
throw new OpenViduException(Code.MEDIA_ENDPOINT_ERROR_CODE, throw new OpenViduException(Code.MEDIA_ENDPOINT_ERROR_CODE,
"This endpoint (" + getEndpointName() + ") has no media element with id " + sinkId "This endpoint (" + getEndpointName() + ") has no media element with id " + sinkId
+ " (should've been connected to the internal ep)"); + " (should've been connected to the internal ep)");
} }
sink = elements.get(sinkId); sink = elements.get(sinkId);
} else { } else {
log.debug("Will unmute connection of WebRTC and PassThrough (no other elems)"); log.debug("Will unmute connection of WebRTC and PassThrough (no other elems)");
} }
internalSinkConnect(this.getEndpoint(), sink); internalSinkConnect(this.getEndpoint(), sink);
setMuteType(null); setMuteType(null);
} }
private String getNext(String uid) { private String getNext(String uid) {
int idx = elementIds.indexOf(uid); int idx = elementIds.indexOf(uid);
if (idx < 0 || idx + 1 == elementIds.size()) { if (idx < 0 || idx + 1 == elementIds.size()) {
return null; return null;
} }
return elementIds.get(idx + 1); return elementIds.get(idx + 1);
} }
private String getPrevious(String uid) { private String getPrevious(String uid) {
int idx = elementIds.indexOf(uid); int idx = elementIds.indexOf(uid);
if (idx <= 0) { if (idx <= 0) {
return null; return null;
} }
return elementIds.get(idx - 1); return elementIds.get(idx - 1);
} }
private void connectAltLoopbackSrc(MediaElement loopbackAlternativeSrc, private void connectAltLoopbackSrc(MediaElement loopbackAlternativeSrc, MediaType loopbackConnectionType) {
MediaType loopbackConnectionType) { if (!connected) {
if (!connected) { innerConnect();
innerConnect(); }
} internalSinkConnect(loopbackAlternativeSrc, this.getEndpoint(), loopbackConnectionType);
internalSinkConnect(loopbackAlternativeSrc, this.getEndpoint(), loopbackConnectionType); }
}
private void innerConnect() { private void innerConnect() {
if (this.getEndpoint() == null) { if (this.getEndpoint() == null) {
throw new OpenViduException(Code.MEDIA_ENDPOINT_ERROR_CODE, throw new OpenViduException(Code.MEDIA_ENDPOINT_ERROR_CODE,
"Can't connect null endpoint (ep: " + getEndpointName() + ")"); "Can't connect null endpoint (ep: " + getEndpointName() + ")");
} }
MediaElement current = this.getEndpoint(); MediaElement current = this.getEndpoint();
String prevId = elementIds.peekLast(); String prevId = elementIds.peekLast();
while (prevId != null) { while (prevId != null) {
MediaElement prev = elements.get(prevId); MediaElement prev = elements.get(prevId);
if (prev == null) { if (prev == null) {
throw new OpenViduException(Code.MEDIA_ENDPOINT_ERROR_CODE, throw new OpenViduException(Code.MEDIA_ENDPOINT_ERROR_CODE,
"No media element with id " + prevId + " (ep: " + getEndpointName() + ")"); "No media element with id " + prevId + " (ep: " + getEndpointName() + ")");
} }
internalSinkConnect(current, prev); internalSinkConnect(current, prev);
current = prev; current = prev;
prevId = getPrevious(prevId); prevId = getPrevious(prevId);
} }
internalSinkConnect(current, passThru); internalSinkConnect(current, passThru);
connected = true; connected = true;
} }
private void internalSinkConnect(final MediaElement source, final MediaElement sink) { private void internalSinkConnect(final MediaElement source, final MediaElement sink) {
source.connect(sink, new Continuation<Void>() { source.connect(sink, new Continuation<Void>() {
@Override @Override
public void onSuccess(Void result) throws Exception { public void onSuccess(Void result) throws Exception {
log.debug("EP {}: Elements have been connected (source {} -> sink {})", getEndpointName(), log.debug("EP {}: Elements have been connected (source {} -> sink {})", getEndpointName(),
source.getId(), sink.getId()); source.getId(), sink.getId());
} }
@Override @Override
public void onError(Throwable cause) throws Exception { public void onError(Throwable cause) throws Exception {
log.warn("EP {}: Failed to connect media elements (source {} -> sink {})", log.warn("EP {}: Failed to connect media elements (source {} -> sink {})", getEndpointName(),
getEndpointName(), source.getId(), sink.getId(), cause); source.getId(), sink.getId(), cause);
} }
}); });
} }
/** /**
* Same as {@link #internalSinkConnect(MediaElement, MediaElement)}, but can specify the type of * Same as {@link #internalSinkConnect(MediaElement, MediaElement)}, but can
* the media that will be streamed. * specify the type of the media that will be streamed.
* *
* @param source * @param source
* @param sink * @param sink
* @param type if null, {@link #internalSinkConnect(MediaElement, MediaElement)} will be used * @param type
* instead * if null, {@link #internalSinkConnect(MediaElement, MediaElement)}
* @see #internalSinkConnect(MediaElement, MediaElement) * will be used instead
*/ * @see #internalSinkConnect(MediaElement, MediaElement)
private void internalSinkConnect(final MediaElement source, final MediaElement sink, */
final MediaType type) { private void internalSinkConnect(final MediaElement source, final MediaElement sink, final MediaType type) {
if (type == null) { if (type == null) {
internalSinkConnect(source, sink); internalSinkConnect(source, sink);
} else { } else {
source.connect(sink, type, new Continuation<Void>() { source.connect(sink, type, new Continuation<Void>() {
@Override @Override
public void onSuccess(Void result) throws Exception { public void onSuccess(Void result) throws Exception {
log.debug("EP {}: {} media elements have been connected (source {} -> sink {})", log.debug("EP {}: {} media elements have been connected (source {} -> sink {})", getEndpointName(),
getEndpointName(), type, source.getId(), sink.getId()); type, source.getId(), sink.getId());
} }
@Override @Override
public void onError(Throwable cause) throws Exception { public void onError(Throwable cause) throws Exception {
log.warn("EP {}: Failed to connect {} media elements (source {} -> sink {})", log.warn("EP {}: Failed to connect {} media elements (source {} -> sink {})", getEndpointName(),
getEndpointName(), type, source.getId(), sink.getId(), cause); type, source.getId(), sink.getId(), cause);
} }
}); });
} }
} }
private void internalSinkDisconnect(final MediaElement source, final MediaElement sink) { private void internalSinkDisconnect(final MediaElement source, final MediaElement sink) {
source.disconnect(sink, new Continuation<Void>() { source.disconnect(sink, new Continuation<Void>() {
@Override @Override
public void onSuccess(Void result) throws Exception { public void onSuccess(Void result) throws Exception {
log.debug("EP {}: Elements have been disconnected (source {} -> sink {})", log.debug("EP {}: Elements have been disconnected (source {} -> sink {})", getEndpointName(),
getEndpointName(), source.getId(), sink.getId()); source.getId(), sink.getId());
} }
@Override @Override
public void onError(Throwable cause) throws Exception { public void onError(Throwable cause) throws Exception {
log.warn("EP {}: Failed to disconnect media elements (source {} -> sink {})", log.warn("EP {}: Failed to disconnect media elements (source {} -> sink {})", getEndpointName(),
getEndpointName(), source.getId(), sink.getId(), cause); source.getId(), sink.getId(), cause);
} }
}); });
} }
/** /**
* Same as {@link #internalSinkDisconnect(MediaElement, MediaElement)}, but can specify the type * Same as {@link #internalSinkDisconnect(MediaElement, MediaElement)}, but can
* of the media that will be disconnected. * specify the type of the media that will be disconnected.
* *
* @param source * @param source
* @param sink * @param sink
* @param type if null, {@link #internalSinkConnect(MediaElement, MediaElement)} will be used * @param type
* instead * if null, {@link #internalSinkConnect(MediaElement, MediaElement)}
* @see #internalSinkConnect(MediaElement, MediaElement) * will be used instead
*/ * @see #internalSinkConnect(MediaElement, MediaElement)
private void internalSinkDisconnect(final MediaElement source, final MediaElement sink, */
final MediaType type) { private void internalSinkDisconnect(final MediaElement source, final MediaElement sink, final MediaType type) {
if (type == null) { if (type == null) {
internalSinkDisconnect(source, sink); internalSinkDisconnect(source, sink);
} else { } else {
source.disconnect(sink, type, new Continuation<Void>() { source.disconnect(sink, type, new Continuation<Void>() {
@Override @Override
public void onSuccess(Void result) throws Exception { public void onSuccess(Void result) throws Exception {
log.debug("EP {}: {} media elements have been disconnected (source {} -> sink {})", log.debug("EP {}: {} media elements have been disconnected (source {} -> sink {})",
getEndpointName(), type, source.getId(), sink.getId()); getEndpointName(), type, source.getId(), sink.getId());
} }
@Override @Override
public void onError(Throwable cause) throws Exception { public void onError(Throwable cause) throws Exception {
log.warn("EP {}: Failed to disconnect {} media elements (source {} -> sink {})", log.warn("EP {}: Failed to disconnect {} media elements (source {} -> sink {})", getEndpointName(),
getEndpointName(), type, source.getId(), sink.getId(), cause); type, source.getId(), sink.getId(), cause);
} }
}); });
} }
} }
} }

View File

@ -179,8 +179,10 @@ export class TestScenariosComponent implements OnInit, OnDestroy {
private startSession() { private startSession() {
for (const user of this.users) { for (const user of this.users) {
this.getToken().then(token => { this.getToken().then(token => {
const startTimeForUser = Date.now();
const OV = new OpenVidu(); const OV = new OpenVidu();
if (this.turnConf === 'freeice') { if (this.turnConf === 'freeice') {
@ -206,9 +208,9 @@ export class TestScenariosComponent implements OnInit, OnDestroy {
.find(s => s.connectionId === session.connection.connectionId).subs .find(s => s.connectionId === session.connection.connectionId).subs
.find(s => s.streamManager.stream.connection.connectionId === subscriber.stream.connection.connectionId); .find(s => s.streamManager.stream.connection.connectionId === subscriber.stream.connection.connectionId);
if (!!error) { if (!!error) {
subAux.state['errorConnecting'] = Date.now(); subAux.state['errorConnecting'] = (Date.now() - startTimeForUser);
} else { } else {
subAux.state['connected'] = Date.now(); subAux.state['connected'] = (Date.now() - startTimeForUser);
} }
}); });
@ -217,16 +219,18 @@ export class TestScenariosComponent implements OnInit, OnDestroy {
this.subscribers.push({ this.subscribers.push({
connectionId: session.connection.connectionId, connectionId: session.connection.connectionId,
subs: [{ subs: [{
startTime: startTimeForUser,
connectionId: session.connection.connectionId, connectionId: session.connection.connectionId,
streamManager: subscriber, streamManager: subscriber,
state: { 'connecting': Date.now() } state: { 'connecting': (Date.now() - startTimeForUser) }
}] }]
}); });
} else { } else {
sub.subs.push({ sub.subs.push({
startTime: startTimeForUser,
connectionId: session.connection.connectionId, connectionId: session.connection.connectionId,
streamManager: subscriber, streamManager: subscriber,
state: { 'connecting': Date.now() } state: { 'connecting': (Date.now() - startTimeForUser) }
}); });
} }
@ -234,7 +238,7 @@ export class TestScenariosComponent implements OnInit, OnDestroy {
this.subscribers this.subscribers
.find(s => s.connectionId === session.connection.connectionId).subs .find(s => s.connectionId === session.connection.connectionId).subs
.find(s => s.streamManager.stream.connection.connectionId === subscriber.stream.connection.connectionId) .find(s => s.streamManager.stream.connection.connectionId === subscriber.stream.connection.connectionId)
.state['playing'] = Date.now(); .state['playing'] = (Date.now() - startTimeForUser);
}); });
}); });
} }
@ -245,19 +249,20 @@ export class TestScenariosComponent implements OnInit, OnDestroy {
const publisher = OV.initPublisher(undefined, this.publisherProperties); const publisher = OV.initPublisher(undefined, this.publisherProperties);
const publisherWrapper = { const publisherWrapper = {
startTime: startTimeForUser,
connectionId: session.connection.connectionId, connectionId: session.connection.connectionId,
streamManager: publisher, streamManager: publisher,
state: { 'connecting': Date.now() } state: { 'connecting': (Date.now() - startTimeForUser) }
}; };
publisher.on('streamCreated', () => { publisher.on('streamCreated', () => {
publisherWrapper.state['connected'] = Date.now(); publisherWrapper.state['connected'] = (Date.now() - startTimeForUser);
}); });
publisher.on('streamPlaying', () => { publisher.on('streamPlaying', () => {
publisherWrapper.state['playing'] = Date.now(); publisherWrapper.state['playing'] = (Date.now() - startTimeForUser);
}); });
session.publish(publisher).catch(() => { session.publish(publisher).catch(() => {
publisherWrapper.state['errorConnecting'] = Date.now(); publisherWrapper.state['errorConnecting'] = (Date.now() - startTimeForUser);
}); });
this.publishers.push(publisherWrapper); this.publishers.push(publisherWrapper);
@ -308,6 +313,7 @@ export class TestScenariosComponent implements OnInit, OnDestroy {
if (event.streamManager.remote) { if (event.streamManager.remote) {
newReport = { newReport = {
connectionId: event.connectionId, connectionId: event.connectionId,
startTime: event.startTime,
streamId: event.streamManager.stream.streamId, streamId: event.streamManager.stream.streamId,
state: event.state, state: event.state,
candidatePairSelectedByBrowser: { candidatePairSelectedByBrowser: {
@ -318,8 +324,10 @@ export class TestScenariosComponent implements OnInit, OnDestroy {
localCandidate: {}, localCandidate: {},
remoteCandidate: {} remoteCandidate: {}
}, },
iceCandidatesSentByBrowser: event.streamManager.stream.getLocalIceCandidateList(), iceCandidatesSentByBrowser:
iceCandidatesReceivedByBrowser: event.streamManager.stream.getRemoteIceCandidateList() event.streamManager.stream.getLocalIceCandidateList().map((c: RTCIceCandidate) => c.candidate),
iceCandidatesReceivedByBrowser:
event.streamManager.stream.getRemoteIceCandidateList().map((c: RTCIceCandidate) => c.candidate),
}; };
this.report.streamsIn.count++; this.report.streamsIn.count++;
@ -327,6 +335,7 @@ export class TestScenariosComponent implements OnInit, OnDestroy {
} else { } else {
newReport = { newReport = {
connectionId: event.connectionId, connectionId: event.connectionId,
startTime: event.startTime,
streamId: event.streamManager.stream.streamId, streamId: event.streamManager.stream.streamId,
state: event.state, state: event.state,
candidatePairSelectedByBrowser: { candidatePairSelectedByBrowser: {
@ -337,8 +346,10 @@ export class TestScenariosComponent implements OnInit, OnDestroy {
localCandidate: {}, localCandidate: {},
remoteCandidate: {} remoteCandidate: {}
}, },
iceCandidatesSentByBrowser: event.streamManager.stream.getLocalIceCandidateList(), iceCandidatesSentByBrowser:
iceCandidatesReceivedByBrowser: event.streamManager.stream.getRemoteIceCandidateList() event.streamManager.stream.getLocalIceCandidateList().map((c: RTCIceCandidate) => c.candidate),
iceCandidatesReceivedByBrowser:
event.streamManager.stream.getRemoteIceCandidateList().map((c: RTCIceCandidate) => c.candidate)
}; };
this.report.streamsOut.count++; this.report.streamsOut.count++;
@ -370,6 +381,12 @@ export class TestScenariosComponent implements OnInit, OnDestroy {
localCandidate: this.parseRemoteCandidatePair(streamOutRemoteInfo.localCandidate), localCandidate: this.parseRemoteCandidatePair(streamOutRemoteInfo.localCandidate),
remoteCandidate: this.parseRemoteCandidatePair(streamOutRemoteInfo.remoteCandidate) remoteCandidate: this.parseRemoteCandidatePair(streamOutRemoteInfo.remoteCandidate)
}; };
report.serverEvents = streamOutRemoteInfo.events;
for (const ev of report.serverEvents) {
for (const key of Object.keys(ev)) {
ev[key] = Number(ev[key]) - report.startTime;
}
}
}); });
this.report.streamsIn.items.forEach(report => { this.report.streamsIn.items.forEach(report => {
@ -383,6 +400,12 @@ export class TestScenariosComponent implements OnInit, OnDestroy {
localCandidate: this.parseRemoteCandidatePair(streamInRemoteInfo.localCandidate), localCandidate: this.parseRemoteCandidatePair(streamInRemoteInfo.localCandidate),
remoteCandidate: this.parseRemoteCandidatePair(streamInRemoteInfo.remoteCandidate) remoteCandidate: this.parseRemoteCandidatePair(streamInRemoteInfo.remoteCandidate)
}; };
report.serverEvents = streamInRemoteInfo.events;
for (const ev of report.serverEvents) {
for (const key of Object.keys(ev)) {
ev[key] = Number(ev[key]) - report.startTime;
}
}
}); });
this.stringifyAllReports = JSON.stringify(this.report, null, '\t'); this.stringifyAllReports = JSON.stringify(this.report, null, '\t');

View File

@ -31,8 +31,9 @@ export class TableVideoComponent implements AfterViewInit, DoCheck {
ngAfterViewInit() { ngAfterViewInit() {
this.playingTimeout = setTimeout(() => { this.playingTimeout = setTimeout(() => {
if (!this.state['playing']) { if (!this.state['playing']) {
this.state['timeoutPlaying'] = Date.now(); this.state['timeoutPlaying'] = Date.now() - this.streamManager.startTime;
this.readyForReport.emit({ this.readyForReport.emit({
startTime: this.streamManager.startTime,
connectionId: this.streamManager.connectionId, connectionId: this.streamManager.connectionId,
state: this.state, state: this.state,
streamManager: this.streamManager.streamManager streamManager: this.streamManager.streamManager
@ -48,6 +49,7 @@ export class TableVideoComponent implements AfterViewInit, DoCheck {
if (this.success() || this.fail()) { if (this.success() || this.fail()) {
clearTimeout(this.playingTimeout); clearTimeout(this.playingTimeout);
this.readyForReport.emit({ this.readyForReport.emit({
startTime: this.streamManager.startTime,
connectionId: this.streamManager.connectionId, connectionId: this.streamManager.connectionId,
state: this.state, state: this.state,
streamManager: this.streamManager.streamManager streamManager: this.streamManager.streamManager
@ -78,6 +80,7 @@ export class TableVideoComponent implements AfterViewInit, DoCheck {
} }
export interface StreamManagerWrapper { export interface StreamManagerWrapper {
startTime: number;
connectionId: string; connectionId: string;
streamManager: StreamManager; streamManager: StreamManager;
state: any; state: any;