mirror of https://github.com/OpenVidu/openvidu.git
openvidu-server: RPC reconnect method
parent
46f0ad564e
commit
13e3b52ff6
|
@ -155,6 +155,9 @@ public abstract class SessionManager {
|
||||||
public abstract Participant publishIpcam(Session session, MediaOptions mediaOptions, String serverMetadata)
|
public abstract Participant publishIpcam(Session session, MediaOptions mediaOptions, String serverMetadata)
|
||||||
throws Exception;
|
throws Exception;
|
||||||
|
|
||||||
|
public abstract void reconnectStream(Participant participant, String streamId, String sdpOffer,
|
||||||
|
Integer transactionId);
|
||||||
|
|
||||||
public abstract String getParticipantPrivateIdFromStreamId(String sessionId, String streamId)
|
public abstract String getParticipantPrivateIdFromStreamId(String sessionId, String streamId)
|
||||||
throws OpenViduException;
|
throws OpenViduException;
|
||||||
|
|
||||||
|
|
|
@ -34,6 +34,7 @@ import org.kurento.client.Filter;
|
||||||
import org.kurento.client.IceCandidate;
|
import org.kurento.client.IceCandidate;
|
||||||
import org.kurento.client.MediaElement;
|
import org.kurento.client.MediaElement;
|
||||||
import org.kurento.client.MediaPipeline;
|
import org.kurento.client.MediaPipeline;
|
||||||
|
import org.kurento.client.PassThrough;
|
||||||
import org.kurento.client.internal.server.KurentoServerException;
|
import org.kurento.client.internal.server.KurentoServerException;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
@ -87,7 +88,7 @@ public class KurentoParticipant extends Participant {
|
||||||
if (!OpenViduRole.SUBSCRIBER.equals(participant.getToken().getRole())) {
|
if (!OpenViduRole.SUBSCRIBER.equals(participant.getToken().getRole())) {
|
||||||
// Initialize a PublisherEndpoint
|
// Initialize a PublisherEndpoint
|
||||||
this.publisher = new PublisherEndpoint(endpointType, this, participant.getParticipantPublicId(),
|
this.publisher = new PublisherEndpoint(endpointType, this, participant.getParticipantPublicId(),
|
||||||
this.session.getPipeline(), this.openviduConfig);
|
this.session.getPipeline(), this.openviduConfig, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (Participant other : session.getParticipants()) {
|
for (Participant other : session.getParticipants()) {
|
||||||
|
@ -100,24 +101,25 @@ public class KurentoParticipant extends Participant {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void createPublishingEndpoint(MediaOptions mediaOptions) {
|
public void createPublishingEndpoint(MediaOptions mediaOptions, String streamId) {
|
||||||
String type = mediaOptions.hasVideo() ? mediaOptions.getTypeOfVideo() : "MICRO";
|
String type = mediaOptions.hasVideo() ? mediaOptions.getTypeOfVideo() : "MICRO";
|
||||||
final String publisherStreamId = IdentifierPrefixes.STREAM_ID + type.substring(0, Math.min(type.length(), 3))
|
if (streamId == null) {
|
||||||
+ "_" + RandomStringUtils.randomAlphabetic(1).toUpperCase() + RandomStringUtils.randomAlphanumeric(3)
|
streamId = IdentifierPrefixes.STREAM_ID + type.substring(0, Math.min(type.length(), 3)) + "_"
|
||||||
+ "_" + this.getParticipantPublicId();
|
+ RandomStringUtils.randomAlphabetic(1).toUpperCase() + RandomStringUtils.randomAlphanumeric(3)
|
||||||
publisher.setStreamId(publisherStreamId);
|
+ "_" + this.getParticipantPublicId();
|
||||||
publisher.setEndpointName(publisherStreamId);
|
}
|
||||||
|
publisher.setStreamId(streamId);
|
||||||
|
publisher.setEndpointName(streamId);
|
||||||
publisher.setMediaOptions(mediaOptions);
|
publisher.setMediaOptions(mediaOptions);
|
||||||
publisher.createEndpoint(publisherLatch);
|
publisher.createEndpoint(publisherLatch);
|
||||||
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.getEndpoint().setName(streamId);
|
||||||
this.publisher.getEndpoint().setName(publisherStreamId);
|
|
||||||
|
|
||||||
endpointConfig.addEndpointListeners(this.publisher, "publisher");
|
endpointConfig.addEndpointListeners(this.publisher, "publisher");
|
||||||
|
|
||||||
// Remove streamId from publisher's map
|
// Put streamId in publisher's map
|
||||||
this.session.publishedStreamIds.putIfAbsent(this.getPublisherStreamId(), this.getParticipantPrivateId());
|
this.session.publishedStreamIds.putIfAbsent(this.getPublisherStreamId(), this.getParticipantPrivateId());
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -155,6 +157,10 @@ public class KurentoParticipant extends Participant {
|
||||||
return this.publisher;
|
return this.publisher;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public SubscriberEndpoint getSubscriber(String senderPublicId) {
|
||||||
|
return this.subscribers.get(senderPublicId);
|
||||||
|
}
|
||||||
|
|
||||||
public Collection<SubscriberEndpoint> getSubscribers() {
|
public Collection<SubscriberEndpoint> getSubscribers() {
|
||||||
return this.subscribers.values();
|
return this.subscribers.values();
|
||||||
}
|
}
|
||||||
|
@ -171,7 +177,7 @@ public class KurentoParticipant extends Participant {
|
||||||
return session;
|
return session;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String publishToRoom(SdpType sdpType, String sdpString, boolean doLoopback) {
|
public String publishToRoom(SdpType sdpType, String sdpString, boolean doLoopback, boolean silent) {
|
||||||
log.info("PARTICIPANT {}: Request to publish video in room {} (sdp type {})", this.getParticipantPublicId(),
|
log.info("PARTICIPANT {}: Request to publish video in room {} (sdp type {})", this.getParticipantPublicId(),
|
||||||
this.session.getSessionId(), sdpType);
|
this.session.getSessionId(), sdpType);
|
||||||
log.trace("PARTICIPANT {}: Publishing Sdp ({}) is {}", this.getParticipantPublicId(), sdpType, sdpString);
|
log.trace("PARTICIPANT {}: Publishing Sdp ({}) is {}", this.getParticipantPublicId(), sdpType, sdpString);
|
||||||
|
@ -188,8 +194,10 @@ public class KurentoParticipant extends Participant {
|
||||||
this.recordingManager.startOneIndividualStreamRecording(session, null, null, this);
|
this.recordingManager.startOneIndividualStreamRecording(session, null, null, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
endpointConfig.getCdr().recordNewPublisher(this, session.getSessionId(), publisher.getStreamId(),
|
if (!silent) {
|
||||||
publisher.getMediaOptions(), publisher.createdAt());
|
endpointConfig.getCdr().recordNewPublisher(this, session.getSessionId(), publisher.getStreamId(),
|
||||||
|
publisher.getMediaOptions(), publisher.createdAt());
|
||||||
|
}
|
||||||
|
|
||||||
return sdpResponse;
|
return sdpResponse;
|
||||||
}
|
}
|
||||||
|
@ -199,12 +207,12 @@ public class KurentoParticipant extends Participant {
|
||||||
this.session.getSessionId());
|
this.session.getSessionId());
|
||||||
final MediaOptions mediaOptions = this.getPublisher().getMediaOptions();
|
final MediaOptions mediaOptions = this.getPublisher().getMediaOptions();
|
||||||
releasePublisherEndpoint(reason, kmsDisconnectionTime);
|
releasePublisherEndpoint(reason, kmsDisconnectionTime);
|
||||||
resetPublisherEndpoint(mediaOptions);
|
resetPublisherEndpoint(mediaOptions, null);
|
||||||
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, boolean silent) {
|
||||||
final String senderName = sender.getParticipantPublicId();
|
final String senderName = sender.getParticipantPublicId();
|
||||||
|
|
||||||
log.info("PARTICIPANT {}: Request to receive media from {} in room {}", this.getParticipantPublicId(),
|
log.info("PARTICIPANT {}: Request to receive media from {} in room {}", this.getParticipantPublicId(),
|
||||||
|
@ -272,7 +280,7 @@ public class KurentoParticipant extends Participant {
|
||||||
log.info("PARTICIPANT {}: Is now receiving video from {} in room {}", this.getParticipantPublicId(),
|
log.info("PARTICIPANT {}: Is now receiving video from {} in room {}", this.getParticipantPublicId(),
|
||||||
senderName, this.session.getSessionId());
|
senderName, this.session.getSessionId());
|
||||||
|
|
||||||
if (!ProtocolElements.RECORDER_PARTICIPANT_PUBLICID.equals(this.getParticipantPublicId())) {
|
if (!silent && !ProtocolElements.RECORDER_PARTICIPANT_PUBLICID.equals(this.getParticipantPublicId())) {
|
||||||
endpointConfig.getCdr().recordNewSubscriber(this, this.session.getSessionId(),
|
endpointConfig.getCdr().recordNewSubscriber(this, this.session.getSessionId(),
|
||||||
sender.getPublisherStreamId(), sender.getParticipantPublicId(), subscriber.createdAt());
|
sender.getPublisherStreamId(), sender.getParticipantPublicId(), subscriber.createdAt());
|
||||||
}
|
}
|
||||||
|
@ -287,12 +295,12 @@ public class KurentoParticipant extends Participant {
|
||||||
log.error("Exception connecting subscriber endpoint " + "to publisher endpoint", e);
|
log.error("Exception connecting subscriber endpoint " + "to publisher endpoint", e);
|
||||||
}
|
}
|
||||||
this.subscribers.remove(senderName);
|
this.subscribers.remove(senderName);
|
||||||
releaseSubscriberEndpoint((KurentoParticipant) sender, subscriber, null);
|
releaseSubscriberEndpoint((KurentoParticipant) sender, subscriber, null, false);
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void cancelReceivingMedia(KurentoParticipant senderKurentoParticipant, EndReason reason) {
|
public void cancelReceivingMedia(KurentoParticipant senderKurentoParticipant, EndReason reason, boolean silent) {
|
||||||
final String senderName = senderKurentoParticipant.getParticipantPublicId();
|
final String senderName = senderKurentoParticipant.getParticipantPublicId();
|
||||||
|
|
||||||
log.info("PARTICIPANT {}: cancel receiving media from {}", this.getParticipantPublicId(), senderName);
|
log.info("PARTICIPANT {}: cancel receiving media from {}", this.getParticipantPublicId(), senderName);
|
||||||
|
@ -301,7 +309,7 @@ public class KurentoParticipant extends Participant {
|
||||||
log.warn("PARTICIPANT {}: Trying to cancel receiving video from user {}. "
|
log.warn("PARTICIPANT {}: Trying to cancel receiving video from user {}. "
|
||||||
+ "But there is no such subscriber endpoint.", this.getParticipantPublicId(), senderName);
|
+ "But there is no such subscriber endpoint.", this.getParticipantPublicId(), senderName);
|
||||||
} else {
|
} else {
|
||||||
releaseSubscriberEndpoint(senderKurentoParticipant, subscriberEndpoint, reason);
|
releaseSubscriberEndpoint(senderKurentoParticipant, subscriberEndpoint, reason, silent);
|
||||||
log.info("PARTICIPANT {}: stopped receiving media from {} in room {}", this.getParticipantPublicId(),
|
log.info("PARTICIPANT {}: stopped receiving media from {} in room {}", this.getParticipantPublicId(),
|
||||||
senderName, this.session.getSessionId());
|
senderName, this.session.getSessionId());
|
||||||
}
|
}
|
||||||
|
@ -321,9 +329,10 @@ public class KurentoParticipant extends Participant {
|
||||||
final SubscriberEndpoint subscriber = entry.getValue();
|
final SubscriberEndpoint subscriber = entry.getValue();
|
||||||
it.remove();
|
it.remove();
|
||||||
if (subscriber != null && subscriber.getEndpoint() != null) {
|
if (subscriber != null && subscriber.getEndpoint() != null) {
|
||||||
|
|
||||||
releaseSubscriberEndpoint(
|
releaseSubscriberEndpoint(
|
||||||
(KurentoParticipant) this.session.getParticipantByPublicId(remoteParticipantName), subscriber,
|
(KurentoParticipant) this.session.getParticipantByPublicId(remoteParticipantName), subscriber,
|
||||||
reason);
|
reason, false);
|
||||||
log.debug("PARTICIPANT {}: Released subscriber endpoint to {}", this.getParticipantPublicId(),
|
log.debug("PARTICIPANT {}: Released subscriber endpoint to {}", this.getParticipantPublicId(),
|
||||||
remoteParticipantName);
|
remoteParticipantName);
|
||||||
} else {
|
} else {
|
||||||
|
@ -413,7 +422,7 @@ public class KurentoParticipant extends Participant {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void releaseSubscriberEndpoint(KurentoParticipant senderKurentoParticipant, SubscriberEndpoint subscriber,
|
private void releaseSubscriberEndpoint(KurentoParticipant senderKurentoParticipant, SubscriberEndpoint subscriber,
|
||||||
EndReason reason) {
|
EndReason reason, boolean silent) {
|
||||||
final String senderName = senderKurentoParticipant.getParticipantPublicId();
|
final String senderName = senderKurentoParticipant.getParticipantPublicId();
|
||||||
if (subscriber != null) {
|
if (subscriber != null) {
|
||||||
|
|
||||||
|
@ -424,40 +433,44 @@ public class KurentoParticipant extends Participant {
|
||||||
|
|
||||||
releaseElement(senderName, subscriber.getEndpoint());
|
releaseElement(senderName, subscriber.getEndpoint());
|
||||||
|
|
||||||
// Stop PlayerEndpoint of IP CAM if last subscriber disconnected
|
if (!silent) {
|
||||||
final PublisherEndpoint senderPublisher = senderKurentoParticipant.publisher;
|
|
||||||
if (senderPublisher != null) {
|
// Stop PlayerEndpoint of IP CAM if last subscriber disconnected
|
||||||
// If no PublisherEndpoint, then it means that the publisher already closed it
|
final PublisherEndpoint senderPublisher = senderKurentoParticipant.publisher;
|
||||||
final KurentoMediaOptions options = (KurentoMediaOptions) senderPublisher.getMediaOptions();
|
if (senderPublisher != null) {
|
||||||
if (options.onlyPlayWithSubscribers != null && options.onlyPlayWithSubscribers) {
|
// If no PublisherEndpoint, then it means that the publisher already closed it
|
||||||
synchronized (senderPublisher) {
|
final KurentoMediaOptions options = (KurentoMediaOptions) senderPublisher.getMediaOptions();
|
||||||
senderPublisher.numberOfSubscribers--;
|
if (options.onlyPlayWithSubscribers != null && options.onlyPlayWithSubscribers) {
|
||||||
if (senderPublisher.isPlayerEndpoint() && senderPublisher.numberOfSubscribers == 0) {
|
synchronized (senderPublisher) {
|
||||||
try {
|
senderPublisher.numberOfSubscribers--;
|
||||||
senderPublisher.getPlayerEndpoint().stop();
|
if (senderPublisher.isPlayerEndpoint() && senderPublisher.numberOfSubscribers == 0) {
|
||||||
log.info("IP Camera stream {} feed is now disabled because there are no subscribers",
|
try {
|
||||||
senderPublisher.getStreamId());
|
senderPublisher.getPlayerEndpoint().stop();
|
||||||
} catch (Exception e) {
|
log.info(
|
||||||
log.info("Error while disabling feed for IP camera {}: {}",
|
"IP Camera stream {} feed is now disabled because there are no subscribers",
|
||||||
senderPublisher.getStreamId(), e.getMessage());
|
senderPublisher.getStreamId());
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.info("Error while disabling feed for IP camera {}: {}",
|
||||||
|
senderPublisher.getStreamId(), e.getMessage());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (!ProtocolElements.RECORDER_PARTICIPANT_PUBLICID.equals(this.getParticipantPublicId())) {
|
if (!ProtocolElements.RECORDER_PARTICIPANT_PUBLICID.equals(this.getParticipantPublicId())) {
|
||||||
endpointConfig.getCdr().stopSubscriber(this.getParticipantPublicId(), senderName,
|
endpointConfig.getCdr().stopSubscriber(this.getParticipantPublicId(), senderName,
|
||||||
subscriber.getStreamId(), reason);
|
subscriber.getStreamId(), reason);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
log.warn("PARTICIPANT {}: Trying to release subscriber endpoint for '{}' but is null",
|
log.warn("PARTICIPANT {}: Trying to release subscriber endpoint for '{}' but is null",
|
||||||
this.getParticipantPublicId(), senderName);
|
this.getParticipantPublicId(), senderName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void releaseElement(final String senderName, final MediaElement element) {
|
void releaseElement(final String senderName, final MediaElement element) {
|
||||||
final String eid = element.getId();
|
final String eid = element.getId();
|
||||||
try {
|
try {
|
||||||
element.release(new Continuation<Void>() {
|
element.release(new Continuation<Void>() {
|
||||||
|
@ -488,11 +501,12 @@ public class KurentoParticipant extends Participant {
|
||||||
return this.publisher.getStreamId();
|
return this.publisher.getStreamId();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void resetPublisherEndpoint(MediaOptions mediaOptions) {
|
public void resetPublisherEndpoint(MediaOptions mediaOptions, PassThrough passThru) {
|
||||||
log.info("Resetting publisher endpoint for participant {}", this.getParticipantPublicId());
|
log.info("Resetting publisher endpoint for participant {}", this.getParticipantPublicId());
|
||||||
this.publisher = new PublisherEndpoint(endpointType, this, this.getParticipantPublicId(),
|
this.publisher = new PublisherEndpoint(endpointType, this, this.getParticipantPublicId(),
|
||||||
this.session.getPipeline(), this.openviduConfig);
|
this.session.getPipeline(), this.openviduConfig, passThru);
|
||||||
this.publisher.setMediaOptions(mediaOptions);
|
this.publisher.setMediaOptions(mediaOptions);
|
||||||
|
this.publisherLatch = new CountDownLatch(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -108,7 +108,7 @@ public class KurentoSession extends Session {
|
||||||
if (participant.equals(subscriber)) {
|
if (participant.equals(subscriber)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
((KurentoParticipant) subscriber).cancelReceivingMedia((KurentoParticipant) participant, reason);
|
((KurentoParticipant) subscriber).cancelReceivingMedia((KurentoParticipant) participant, reason, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
log.debug("SESSION {}: Unsubscribed other participants {} from the publisher {}", sessionId,
|
log.debug("SESSION {}: Unsubscribed other participants {} from the publisher {}", sessionId,
|
||||||
|
@ -187,7 +187,7 @@ public class KurentoSession extends Session {
|
||||||
log.debug("SESSION {}: Cancel receiving media from participant '{}' for other participant", this.sessionId,
|
log.debug("SESSION {}: Cancel receiving media from participant '{}' for other participant", this.sessionId,
|
||||||
participant.getParticipantPublicId());
|
participant.getParticipantPublicId());
|
||||||
for (Participant other : participants.values()) {
|
for (Participant other : participants.values()) {
|
||||||
((KurentoParticipant) other).cancelReceivingMedia(removedParticipant, reason);
|
((KurentoParticipant) other).cancelReceivingMedia(removedParticipant, reason, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -329,8 +329,9 @@ public class KurentoSession extends Session {
|
||||||
}
|
}
|
||||||
getParticipants().forEach(p -> {
|
getParticipants().forEach(p -> {
|
||||||
if (!OpenViduRole.SUBSCRIBER.equals(p.getToken().getRole())) {
|
if (!OpenViduRole.SUBSCRIBER.equals(p.getToken().getRole())) {
|
||||||
((KurentoParticipant) p)
|
|
||||||
.resetPublisherEndpoint(mediaOptionsMap.get(p.getParticipantPublicId()));
|
((KurentoParticipant) p).resetPublisherEndpoint(mediaOptionsMap.get(p.getParticipantPublicId()),
|
||||||
|
null);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
log.info(
|
log.info(
|
||||||
|
|
|
@ -33,6 +33,7 @@ import org.apache.commons.lang3.RandomStringUtils;
|
||||||
import org.kurento.client.GenericMediaElement;
|
import org.kurento.client.GenericMediaElement;
|
||||||
import org.kurento.client.IceCandidate;
|
import org.kurento.client.IceCandidate;
|
||||||
import org.kurento.client.ListenerSubscription;
|
import org.kurento.client.ListenerSubscription;
|
||||||
|
import org.kurento.client.PassThrough;
|
||||||
import org.kurento.jsonrpc.Props;
|
import org.kurento.jsonrpc.Props;
|
||||||
import org.kurento.jsonrpc.message.Request;
|
import org.kurento.jsonrpc.message.Request;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
|
@ -281,7 +282,7 @@ public class KurentoSessionManager extends SessionManager {
|
||||||
SdpType sdpType = kurentoOptions.isOffer ? SdpType.OFFER : SdpType.ANSWER;
|
SdpType sdpType = kurentoOptions.isOffer ? SdpType.OFFER : SdpType.ANSWER;
|
||||||
KurentoSession kSession = kParticipant.getSession();
|
KurentoSession kSession = kParticipant.getSession();
|
||||||
|
|
||||||
kParticipant.createPublishingEndpoint(mediaOptions);
|
kParticipant.createPublishingEndpoint(mediaOptions, null);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* for (MediaElement elem : kurentoOptions.mediaElements) {
|
* for (MediaElement elem : kurentoOptions.mediaElements) {
|
||||||
|
@ -305,7 +306,7 @@ public class KurentoSessionManager extends SessionManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sdpAnswer = kParticipant.publishToRoom(sdpType, kurentoOptions.sdpOffer, kurentoOptions.doLoopback);
|
sdpAnswer = kParticipant.publishToRoom(sdpType, kurentoOptions.sdpOffer, kurentoOptions.doLoopback, false);
|
||||||
|
|
||||||
if (sdpAnswer == null) {
|
if (sdpAnswer == null) {
|
||||||
OpenViduException e = new OpenViduException(Code.MEDIA_SDP_ERROR_CODE,
|
OpenViduException e = new OpenViduException(Code.MEDIA_SDP_ERROR_CODE,
|
||||||
|
@ -416,7 +417,7 @@ public class KurentoSessionManager extends SessionManager {
|
||||||
"User '" + senderName + " not streaming media in session '" + session.getSessionId() + "'");
|
"User '" + senderName + " not streaming media in session '" + session.getSessionId() + "'");
|
||||||
}
|
}
|
||||||
|
|
||||||
sdpAnswer = kParticipant.receiveMediaFrom(senderParticipant, sdpOffer);
|
sdpAnswer = kParticipant.receiveMediaFrom(senderParticipant, sdpOffer, false);
|
||||||
if (sdpAnswer == null) {
|
if (sdpAnswer == null) {
|
||||||
throw new OpenViduException(Code.MEDIA_SDP_ERROR_CODE,
|
throw new OpenViduException(Code.MEDIA_SDP_ERROR_CODE,
|
||||||
"Unable to generate SDP answer when subscribing '" + participant.getParticipantPublicId()
|
"Unable to generate SDP answer when subscribing '" + participant.getParticipantPublicId()
|
||||||
|
@ -448,7 +449,7 @@ public class KurentoSessionManager extends SessionManager {
|
||||||
"User " + senderName + " not found in session " + session.getSessionId());
|
"User " + senderName + " not found in session " + session.getSessionId());
|
||||||
}
|
}
|
||||||
|
|
||||||
kParticipant.cancelReceivingMedia((KurentoParticipant) sender, EndReason.unsubscribe);
|
kParticipant.cancelReceivingMedia((KurentoParticipant) sender, EndReason.unsubscribe, false);
|
||||||
|
|
||||||
sessionEventsHandler.onUnsubscribe(participant, transactionId, null);
|
sessionEventsHandler.onUnsubscribe(participant, transactionId, null);
|
||||||
}
|
}
|
||||||
|
@ -924,6 +925,57 @@ public class KurentoSessionManager extends SessionManager {
|
||||||
return kParticipant;
|
return kParticipant;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void reconnectStream(Participant participant, String streamId, String sdpOffer, Integer transactionId) {
|
||||||
|
KurentoParticipant kParticipant = (KurentoParticipant) participant;
|
||||||
|
KurentoSession kSession = kParticipant.getSession();
|
||||||
|
|
||||||
|
if (streamId.equals(participant.getPublisherStreamId())) {
|
||||||
|
|
||||||
|
// Reconnect publisher
|
||||||
|
final KurentoMediaOptions kurentoOptions = (KurentoMediaOptions) kParticipant.getPublisher()
|
||||||
|
.getMediaOptions();
|
||||||
|
|
||||||
|
// 1) Disconnect broken PublisherEndpoint from its PassThrough
|
||||||
|
PublisherEndpoint publisher = kParticipant.getPublisher();
|
||||||
|
final PassThrough passThru = publisher.disconnectFromPassThrough();
|
||||||
|
|
||||||
|
// 2) Destroy the broken PublisherEndpoint and nothing else
|
||||||
|
if (publisher.kmsWebrtcStatsThread != null) {
|
||||||
|
publisher.kmsWebrtcStatsThread.cancel(true);
|
||||||
|
}
|
||||||
|
kParticipant.releaseElement(participant.getParticipantPublicId(), publisher.getEndpoint());
|
||||||
|
|
||||||
|
// 3) Create a new PublisherEndpoint connecting it to the previous PassThrough
|
||||||
|
kParticipant.resetPublisherEndpoint(kurentoOptions, passThru);
|
||||||
|
kParticipant.createPublishingEndpoint(kurentoOptions, streamId);
|
||||||
|
SdpType sdpType = kurentoOptions.isOffer ? SdpType.OFFER : SdpType.ANSWER;
|
||||||
|
String sdpAnswer = kParticipant.publishToRoom(sdpType, sdpOffer, kurentoOptions.doLoopback, true);
|
||||||
|
|
||||||
|
sessionEventsHandler.onPublishMedia(participant, participant.getPublisherStreamId(),
|
||||||
|
kParticipant.getPublisher().createdAt(), kSession.getSessionId(), kurentoOptions, sdpAnswer,
|
||||||
|
new HashSet<Participant>(), transactionId, null);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
// Reconnect subscriber
|
||||||
|
String senderPrivateId = kSession.getParticipantPrivateIdFromStreamId(streamId);
|
||||||
|
if (senderPrivateId != null) {
|
||||||
|
KurentoParticipant sender = (KurentoParticipant) kSession.getParticipantByPrivateId(senderPrivateId);
|
||||||
|
kParticipant.cancelReceivingMedia(sender, null, true);
|
||||||
|
String sdpAnswer = kParticipant.receiveMediaFrom(sender, sdpOffer, true);
|
||||||
|
if (sdpAnswer == null) {
|
||||||
|
throw new OpenViduException(Code.MEDIA_SDP_ERROR_CODE,
|
||||||
|
"Unable to generate SDP answer when reconnecting subscriber to '" + streamId + "'");
|
||||||
|
}
|
||||||
|
sessionEventsHandler.onSubscribe(participant, kSession, sdpAnswer, transactionId, null);
|
||||||
|
} else {
|
||||||
|
throw new OpenViduException(Code.USER_NOT_STREAMING_ERROR_CODE,
|
||||||
|
"Stream '" + streamId + "' does not exist in Session '" + kSession.getSessionId() + "'");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getParticipantPrivateIdFromStreamId(String sessionId, String streamId) {
|
public String getParticipantPrivateIdFromStreamId(String sessionId, String streamId) {
|
||||||
Session session = this.getSession(sessionId);
|
Session session = this.getSession(sessionId);
|
||||||
|
|
|
@ -24,6 +24,7 @@ import java.util.concurrent.ConcurrentLinkedQueue;
|
||||||
import java.util.concurrent.CountDownLatch;
|
import java.util.concurrent.CountDownLatch;
|
||||||
import java.util.concurrent.Future;
|
import java.util.concurrent.Future;
|
||||||
|
|
||||||
|
import org.kurento.client.BaseRtpEndpoint;
|
||||||
import org.kurento.client.Continuation;
|
import org.kurento.client.Continuation;
|
||||||
import org.kurento.client.Endpoint;
|
import org.kurento.client.Endpoint;
|
||||||
import org.kurento.client.ErrorEvent;
|
import org.kurento.client.ErrorEvent;
|
||||||
|
@ -32,7 +33,7 @@ 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.MediaPipeline;
|
import org.kurento.client.MediaPipeline;
|
||||||
import org.kurento.client.OnIceCandidateEvent;
|
import org.kurento.client.PassThrough;
|
||||||
import org.kurento.client.PlayerEndpoint;
|
import org.kurento.client.PlayerEndpoint;
|
||||||
import org.kurento.client.RtpEndpoint;
|
import org.kurento.client.RtpEndpoint;
|
||||||
import org.kurento.client.SdpEndpoint;
|
import org.kurento.client.SdpEndpoint;
|
||||||
|
@ -155,7 +156,8 @@ public abstract class MediaEndpoint {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return the internal endpoint ({@link RtpEndpoint} or {@link WebRtcEndpoint})
|
* @return the internal endpoint ({@link RtpEndpoint} or {@link WebRtcEndpoint}
|
||||||
|
* or {@link PlayerEndpoint})
|
||||||
*/
|
*/
|
||||||
public Endpoint getEndpoint() {
|
public Endpoint getEndpoint() {
|
||||||
if (this.isWeb()) {
|
if (this.isWeb()) {
|
||||||
|
@ -167,6 +169,13 @@ public abstract class MediaEndpoint {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public BaseRtpEndpoint getBaseRtpEndpoint() {
|
||||||
|
if (this.isWeb()) {
|
||||||
|
return this.webEndpoint;
|
||||||
|
}
|
||||||
|
return this.endpoint;
|
||||||
|
}
|
||||||
|
|
||||||
public long createdAt() {
|
public long createdAt() {
|
||||||
return this.createdAt;
|
return this.createdAt;
|
||||||
}
|
}
|
||||||
|
|
|
@ -77,15 +77,18 @@ public class PublisherEndpoint extends MediaEndpoint {
|
||||||
public int numberOfSubscribers = 0;
|
public int numberOfSubscribers = 0;
|
||||||
|
|
||||||
public PublisherEndpoint(EndpointType endpointType, KurentoParticipant owner, String endpointName,
|
public PublisherEndpoint(EndpointType endpointType, KurentoParticipant owner, String endpointName,
|
||||||
MediaPipeline pipeline, OpenviduConfig openviduConfig) {
|
MediaPipeline pipeline, OpenviduConfig openviduConfig, PassThrough passThru) {
|
||||||
super(endpointType, owner, endpointName, pipeline, openviduConfig, log);
|
super(endpointType, owner, endpointName, pipeline, openviduConfig, log);
|
||||||
|
this.passThru = passThru;
|
||||||
}
|
}
|
||||||
|
|
||||||
@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();
|
if (this.passThru == null) {
|
||||||
passThruSubscription = registerElemErrListener(passThru);
|
passThru = new PassThrough.Builder(getPipeline()).build();
|
||||||
|
passThruSubscription = registerElemErrListener(passThru);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -404,6 +407,11 @@ public class PublisherEndpoint extends MediaEndpoint {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public synchronized PassThrough disconnectFromPassThrough() {
|
||||||
|
this.internalSinkDisconnect(this.getWebEndpoint(), this.passThru);
|
||||||
|
return this.passThru;
|
||||||
|
}
|
||||||
|
|
||||||
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()) {
|
||||||
|
|
|
@ -162,6 +162,9 @@ public class RpcHandler extends DefaultJsonRpcHandler<JsonObject> {
|
||||||
case ProtocolElements.REMOVEFILTEREVENTLISTENER_METHOD:
|
case ProtocolElements.REMOVEFILTEREVENTLISTENER_METHOD:
|
||||||
removeFilterEventListener(rpcConnection, request);
|
removeFilterEventListener(rpcConnection, request);
|
||||||
break;
|
break;
|
||||||
|
case ProtocolElements.RECONNECTSTREAM_METHOD:
|
||||||
|
reconnectStream(rpcConnection, request);
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
log.error("Unrecognized request {}", request);
|
log.error("Unrecognized request {}", request);
|
||||||
break;
|
break;
|
||||||
|
@ -602,6 +605,23 @@ public class RpcHandler extends DefaultJsonRpcHandler<JsonObject> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void reconnectStream(RpcConnection rpcConnection, Request<JsonObject> request) {
|
||||||
|
Participant participant;
|
||||||
|
try {
|
||||||
|
participant = sanityCheckOfSession(rpcConnection, "reconnectStream");
|
||||||
|
} catch (OpenViduException e) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
String streamId = getStringParam(request, ProtocolElements.RECONNECTSTREAM_STREAM_PARAM);
|
||||||
|
String sdpOffer = getStringParam(request, ProtocolElements.RECONNECTSTREAM_SDPOFFER_PARAM);
|
||||||
|
try {
|
||||||
|
sessionManager.reconnectStream(participant, streamId, sdpOffer, request.getId());
|
||||||
|
} catch (OpenViduException e) {
|
||||||
|
this.notificationService.sendErrorResponse(participant.getParticipantPrivateId(), request.getId(),
|
||||||
|
new JsonObject(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void leaveRoomAfterConnClosed(String participantPrivateId, EndReason reason) {
|
public void leaveRoomAfterConnClosed(String participantPrivateId, EndReason reason) {
|
||||||
try {
|
try {
|
||||||
sessionManager.evictParticipant(this.sessionManager.getParticipant(participantPrivateId), null, null,
|
sessionManager.evictParticipant(this.sessionManager.getParticipant(participantPrivateId), null, null,
|
||||||
|
@ -670,18 +690,25 @@ public class RpcHandler extends DefaultJsonRpcHandler<JsonObject> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void afterReconnection(Session rpcSession) throws Exception {
|
||||||
|
log.info("After reconnection for WebSocket session: {}", rpcSession.getSessionId());
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void handleTransportError(Session rpcSession, Throwable exception) throws Exception {
|
public void handleTransportError(Session rpcSession, Throwable exception) throws Exception {
|
||||||
log.error("Transport exception for WebSocket session: {} - Exception: {}", rpcSession.getSessionId(),
|
if (rpcSession != null) {
|
||||||
exception.getMessage());
|
log.error("Transport exception for WebSocket session: {} - Exception: {}", rpcSession.getSessionId(),
|
||||||
if ("IOException".equals(exception.getClass().getSimpleName())
|
exception.getMessage());
|
||||||
&& "Broken pipe".equals(exception.getCause().getMessage())) {
|
if ("IOException".equals(exception.getClass().getSimpleName()) && exception.getCause() != null
|
||||||
log.warn("Parcipant with private id {} unexpectedly closed the websocket", rpcSession.getSessionId());
|
&& "Broken pipe".equals(exception.getCause().getMessage())) {
|
||||||
}
|
log.warn("Parcipant with private id {} unexpectedly closed the websocket", rpcSession.getSessionId());
|
||||||
if ("EOFException".equals(exception.getClass().getSimpleName())) {
|
}
|
||||||
// Store WebSocket connection interrupted exception for this web socket to
|
if ("EOFException".equals(exception.getClass().getSimpleName())) {
|
||||||
// automatically evict the participant on "afterConnectionClosed" event
|
// Store WebSocket connection interrupted exception for this web socket to
|
||||||
this.webSocketEOFTransportError.put(rpcSession.getSessionId(), true);
|
// automatically evict the participant on "afterConnectionClosed" event
|
||||||
|
this.webSocketEOFTransportError.put(rpcSession.getSessionId(), true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue