Java tutorial
/* * (C) Copyright 2017-2019 OpenVidu (https://openvidu.io/) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package io.openvidu.server.kurento.core; import java.util.Arrays; import java.util.Collections; import java.util.HashSet; import java.util.NoSuchElementException; import java.util.Set; import org.kurento.client.GenericMediaElement; import org.kurento.client.IceCandidate; import org.kurento.client.ListenerSubscription; import org.kurento.jsonrpc.Props; import org.kurento.jsonrpc.message.Request; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.google.gson.JsonParser; import com.google.gson.JsonSyntaxException; import io.openvidu.client.OpenViduException; import io.openvidu.client.OpenViduException.Code; import io.openvidu.client.internal.ProtocolElements; import io.openvidu.java.client.MediaMode; import io.openvidu.java.client.RecordingLayout; import io.openvidu.java.client.RecordingMode; import io.openvidu.java.client.RecordingProperties; import io.openvidu.java.client.SessionProperties; import io.openvidu.server.core.EndReason; import io.openvidu.server.core.MediaOptions; import io.openvidu.server.core.Participant; import io.openvidu.server.core.Session; import io.openvidu.server.core.SessionManager; import io.openvidu.server.kurento.endpoint.KurentoFilter; import io.openvidu.server.kurento.endpoint.PublisherEndpoint; import io.openvidu.server.kurento.endpoint.SdpType; import io.openvidu.server.kurento.kms.Kms; import io.openvidu.server.kurento.kms.KmsManager; import io.openvidu.server.rpc.RpcHandler; import io.openvidu.server.utils.JsonUtils; public class KurentoSessionManager extends SessionManager { private static final Logger log = LoggerFactory.getLogger(KurentoSessionManager.class); @Autowired private KmsManager kmsManager; @Autowired private KurentoSessionEventsHandler kurentoSessionEventsHandler; @Autowired private KurentoParticipantEndpointConfig kurentoEndpointConfig; @Override public synchronized void joinRoom(Participant participant, String sessionId, Integer transactionId) { Set<Participant> existingParticipants = null; try { KurentoSession kSession = (KurentoSession) sessions.get(sessionId); if (kSession == null) { // First user connecting to the session Session sessionNotActive = sessionsNotActive.remove(sessionId); if (sessionNotActive == null && this.isInsecureParticipant(participant.getParticipantPrivateId())) { // Insecure user directly call joinRoom RPC method, without REST API use sessionNotActive = new Session(sessionId, new SessionProperties.Builder().mediaMode(MediaMode.ROUTED) .recordingMode(RecordingMode.ALWAYS) .defaultRecordingLayout(RecordingLayout.BEST_FIT).build(), openviduConfig, recordingManager); } Kms lessLoadedKms = null; try { lessLoadedKms = this.kmsManager.getLessLoadedKms(); } catch (NoSuchElementException e) { // Restore session not active this.cleanCollections(sessionId); this.storeSessionNotActive(sessionNotActive); throw new OpenViduException(Code.ROOM_CANNOT_BE_CREATED_ERROR_CODE, "There is no available media server where to initialize session '" + sessionId + "'"); } log.info("KMS less loaded is {} with a load of {}", lessLoadedKms.getUri(), lessLoadedKms.getLoad()); kSession = createSession(sessionNotActive, lessLoadedKms); } if (kSession.isClosed()) { log.warn("'{}' is trying to join session '{}' but it is closing", participant.getParticipantPublicId(), sessionId); throw new OpenViduException(Code.ROOM_CLOSED_ERROR_CODE, "'" + participant.getParticipantPublicId() + "' is trying to join session '" + sessionId + "' but it is closing"); } existingParticipants = getParticipants(sessionId); kSession.join(participant); } catch (OpenViduException e) { log.warn("PARTICIPANT {}: Error joining/creating session {}", participant.getParticipantPublicId(), sessionId, e); sessionEventsHandler.onParticipantJoined(participant, sessionId, null, transactionId, e); } if (existingParticipants != null) { sessionEventsHandler.onParticipantJoined(participant, sessionId, existingParticipants, transactionId, null); } } @Override public synchronized boolean leaveRoom(Participant participant, Integer transactionId, EndReason reason, boolean closeWebSocket) { log.debug("Request [LEAVE_ROOM] ({})", participant.getParticipantPublicId()); boolean sessionClosedByLastParticipant = false; KurentoParticipant kParticipant = (KurentoParticipant) participant; KurentoSession session = kParticipant.getSession(); String sessionId = session.getSessionId(); if (session.isClosed()) { log.warn("'{}' is trying to leave from session '{}' but it is closing", participant.getParticipantPublicId(), sessionId); throw new OpenViduException(Code.ROOM_CLOSED_ERROR_CODE, "'" + participant.getParticipantPublicId() + "' is trying to leave from session '" + sessionId + "' but it is closing"); } session.leave(participant.getParticipantPrivateId(), reason); // Update control data structures if (sessionidParticipantpublicidParticipant.get(sessionId) != null) { Participant p = sessionidParticipantpublicidParticipant.get(sessionId) .remove(participant.getParticipantPublicId()); if (this.coturnCredentialsService.isCoturnAvailable()) { this.coturnCredentialsService.deleteUser(p.getToken().getTurnCredentials().getUsername()); } if (sessionidTokenTokenobj.get(sessionId) != null) { sessionidTokenTokenobj.get(sessionId).remove(p.getToken().getToken()); } boolean stillParticipant = false; for (Session s : sessions.values()) { if (s.getParticipantByPrivateId(p.getParticipantPrivateId()) != null) { stillParticipant = true; break; } } if (!stillParticipant) { insecureUsers.remove(p.getParticipantPrivateId()); } } showTokens(); // Close Session if no more participants Set<Participant> remainingParticipants = null; try { remainingParticipants = getParticipants(sessionId); } catch (OpenViduException e) { log.info("Possible collision when closing the session '{}' (not found)", sessionId); remainingParticipants = Collections.emptySet(); } sessionEventsHandler.onParticipantLeft(participant, sessionId, remainingParticipants, transactionId, null, reason); if (!EndReason.sessionClosedByServer.equals(reason)) { // If session is closed by a call to "DELETE /api/sessions" do NOT stop the // recording. Will be stopped after in method // "SessionManager.closeSessionAndEmptyCollections" if (remainingParticipants.isEmpty()) { if (openviduConfig.isRecordingModuleEnabled() && MediaMode.ROUTED.equals(session.getSessionProperties().mediaMode()) && (this.recordingManager.sessionIsBeingRecorded(sessionId))) { // Start countdown to stop recording. Will be aborted if a Publisher starts // before timeout log.info( "Last participant left. Starting {} seconds countdown for stopping recording of session {}", this.openviduConfig.getOpenviduRecordingAutostopTimeout(), sessionId); recordingManager.initAutomaticRecordingStopThread(session); } else { log.info("No more participants in session '{}', removing it and closing it", sessionId); this.closeSessionAndEmptyCollections(session, reason); sessionClosedByLastParticipant = true; showTokens(); } } else if (remainingParticipants.size() == 1 && openviduConfig.isRecordingModuleEnabled() && MediaMode.ROUTED.equals(session.getSessionProperties().mediaMode()) && this.recordingManager.sessionIsBeingRecorded(sessionId) && ProtocolElements.RECORDER_PARTICIPANT_PUBLICID .equals(remainingParticipants.iterator().next().getParticipantPublicId())) { // Start countdown log.info( "Last participant left. Starting {} seconds countdown for stopping recording of session {}", this.openviduConfig.getOpenviduRecordingAutostopTimeout(), sessionId); recordingManager.initAutomaticRecordingStopThread(session); } } // Finally close websocket session if required if (closeWebSocket) { sessionEventsHandler.closeRpcSession(participant.getParticipantPrivateId()); } return sessionClosedByLastParticipant; } /** * Represents a client's request to start streaming her local media to anyone * inside the room. The media elements should have been created using the same * pipeline as the publisher's. The streaming media endpoint situated on the * server can be connected to itself thus realizing what is known as a loopback * connection. The loopback is performed after applying all additional media * elements specified as parameters (in the same order as they appear in the * params list). * <p> * <br/> * <strong>Dev advice:</strong> Send notifications to the existing participants * in the room to inform about the new stream that has been published. Answer to * the peer's request by sending it the SDP response (answer or updated offer) * generated by the WebRTC endpoint on the server. * * @param participant Participant publishing video * @param MediaOptions configuration of the stream to publish * @param transactionId identifier of the Transaction * @throws OpenViduException on error */ @Override public void publishVideo(Participant participant, MediaOptions mediaOptions, Integer transactionId) throws OpenViduException { Set<Participant> participants = null; String sdpAnswer = null; KurentoMediaOptions kurentoOptions = (KurentoMediaOptions) mediaOptions; KurentoParticipant kParticipant = (KurentoParticipant) participant; log.debug( "Request [PUBLISH_MEDIA] isOffer={} sdp={} " + "loopbackAltSrc={} lpbkConnType={} doLoopback={} mediaElements={} ({})", kurentoOptions.isOffer, kurentoOptions.sdpOffer, kurentoOptions.loopbackAlternativeSrc, kurentoOptions.loopbackConnectionType, kurentoOptions.doLoopback, kurentoOptions.mediaElements, participant.getParticipantPublicId()); SdpType sdpType = kurentoOptions.isOffer ? SdpType.OFFER : SdpType.ANSWER; KurentoSession kSession = kParticipant.getSession(); kParticipant.createPublishingEndpoint(mediaOptions); /* * for (MediaElement elem : kurentoOptions.mediaElements) { * kurentoParticipant.getPublisher().apply(elem); } */ KurentoTokenOptions kurentoTokenOptions = participant.getToken().getKurentoTokenOptions(); if (kurentoOptions.getFilter() != null && kurentoTokenOptions != null) { if (kurentoTokenOptions.isFilterAllowed(kurentoOptions.getFilter().getType())) { this.applyFilterInPublisher(kParticipant, kurentoOptions.getFilter()); } else { OpenViduException e = new OpenViduException(Code.FILTER_NOT_APPLIED_ERROR_CODE, "Error applying filter for publishing user " + participant.getParticipantPublicId() + ". The token has no permissions to apply filter " + kurentoOptions.getFilter().getType()); log.error("PARTICIPANT {}: Error applying filter. The token has no permissions to apply filter {}", participant.getParticipantPublicId(), kurentoOptions.getFilter().getType(), e); sessionEventsHandler.onPublishMedia(participant, null, System.currentTimeMillis(), kSession.getSessionId(), mediaOptions, sdpAnswer, participants, transactionId, e); throw e; } } sdpAnswer = kParticipant.publishToRoom(sdpType, kurentoOptions.sdpOffer, kurentoOptions.doLoopback, kurentoOptions.loopbackAlternativeSrc, kurentoOptions.loopbackConnectionType); if (sdpAnswer == null) { OpenViduException e = new OpenViduException(Code.MEDIA_SDP_ERROR_CODE, "Error generating SDP response for publishing user " + participant.getParticipantPublicId()); log.error("PARTICIPANT {}: Error publishing media", participant.getParticipantPublicId(), e); sessionEventsHandler.onPublishMedia(participant, null, kParticipant.getPublisher().createdAt(), kSession.getSessionId(), mediaOptions, sdpAnswer, participants, transactionId, e); } if (this.openviduConfig.isRecordingModuleEnabled() && MediaMode.ROUTED.equals(kSession.getSessionProperties().mediaMode()) && kSession.getActivePublishers() == 0) { if (RecordingMode.ALWAYS.equals(kSession.getSessionProperties().recordingMode()) && !recordingManager.sessionIsBeingRecorded(kSession.getSessionId()) && !kSession.recordingManuallyStopped.get()) { // Start automatic recording for sessions configured with RecordingMode.ALWAYS new Thread(() -> { recordingManager.startRecording(kSession, new RecordingProperties.Builder().name("") .outputMode(kSession.getSessionProperties().defaultOutputMode()) .recordingLayout(kSession.getSessionProperties().defaultRecordingLayout()) .customLayout(kSession.getSessionProperties().defaultCustomLayout()).build()); }).start(); } else if (RecordingMode.MANUAL.equals(kSession.getSessionProperties().recordingMode()) && recordingManager.sessionIsBeingRecorded(kSession.getSessionId())) { // Abort automatic recording stop (user published before timeout) log.info("Participant {} published before timeout finished. Aborting automatic recording stop", participant.getParticipantPublicId()); boolean stopAborted = recordingManager.abortAutomaticRecordingStopThread(kSession, EndReason.automaticStop); if (stopAborted) { log.info("Automatic recording stopped successfully aborted"); } else { log.info("Automatic recording stopped couldn't be aborted. Recording of session {} has stopped", kSession.getSessionId()); } } } kSession.newPublisher(participant); participants = kParticipant.getSession().getParticipants(); if (sdpAnswer != null) { sessionEventsHandler.onPublishMedia(participant, participant.getPublisherStreamId(), kParticipant.getPublisher().createdAt(), kSession.getSessionId(), mediaOptions, sdpAnswer, participants, transactionId, null); } } @Override public void unpublishVideo(Participant participant, Participant moderator, Integer transactionId, EndReason reason) { try { KurentoParticipant kParticipant = (KurentoParticipant) participant; KurentoSession session = kParticipant.getSession(); log.debug("Request [UNPUBLISH_MEDIA] ({})", participant.getParticipantPublicId()); if (!participant.isStreaming()) { log.warn( "PARTICIPANT {}: Requesting to unpublish video of user {} " + "in session {} but user is not streaming media", moderator != null ? moderator.getParticipantPublicId() : participant.getParticipantPublicId(), participant.getParticipantPublicId(), session.getSessionId()); throw new OpenViduException(Code.USER_NOT_STREAMING_ERROR_CODE, "Participant '" + participant.getParticipantPublicId() + "' is not streaming media"); } kParticipant.unpublishMedia(reason, 0); session.cancelPublisher(participant, reason); Set<Participant> participants = session.getParticipants(); sessionEventsHandler.onUnpublishMedia(participant, participants, moderator, transactionId, null, reason); } catch (OpenViduException e) { log.warn("PARTICIPANT {}: Error unpublishing media", participant.getParticipantPublicId(), e); sessionEventsHandler.onUnpublishMedia(participant, new HashSet<>(Arrays.asList(participant)), moderator, transactionId, e, null); } } @Override public void subscribe(Participant participant, String senderName, String sdpOffer, Integer transactionId) { String sdpAnswer = null; Session session = null; try { log.debug("Request [SUBSCRIBE] remoteParticipant={} sdpOffer={} ({})", senderName, sdpOffer, participant.getParticipantPublicId()); KurentoParticipant kParticipant = (KurentoParticipant) participant; session = ((KurentoParticipant) participant).getSession(); Participant senderParticipant = session.getParticipantByPublicId(senderName); if (senderParticipant == null) { log.warn( "PARTICIPANT {}: Requesting to recv media from user {} " + "in session {} but user could not be found", participant.getParticipantPublicId(), senderName, session.getSessionId()); throw new OpenViduException(Code.USER_NOT_FOUND_ERROR_CODE, "User '" + senderName + " not found in session '" + session.getSessionId() + "'"); } if (!senderParticipant.isStreaming()) { log.warn( "PARTICIPANT {}: Requesting to recv media from user {} " + "in session {} but user is not streaming media", participant.getParticipantPublicId(), senderName, session.getSessionId()); throw new OpenViduException(Code.USER_NOT_STREAMING_ERROR_CODE, "User '" + senderName + " not streaming media in session '" + session.getSessionId() + "'"); } sdpAnswer = kParticipant.receiveMediaFrom(senderParticipant, sdpOffer); if (sdpAnswer == null) { throw new OpenViduException(Code.MEDIA_SDP_ERROR_CODE, "Unable to generate SDP answer when subscribing '" + participant.getParticipantPublicId() + "' to '" + senderName + "'"); } } catch (OpenViduException e) { log.error("PARTICIPANT {}: Error subscribing to {}", participant.getParticipantPublicId(), senderName, e); sessionEventsHandler.onSubscribe(participant, session, null, transactionId, e); } if (sdpAnswer != null) { sessionEventsHandler.onSubscribe(participant, session, sdpAnswer, transactionId, null); } } @Override public void unsubscribe(Participant participant, String senderName, Integer transactionId) { log.debug("Request [UNSUBSCRIBE] remoteParticipant={} ({})", senderName, participant.getParticipantPublicId()); KurentoParticipant kParticipant = (KurentoParticipant) participant; Session session = ((KurentoParticipant) participant).getSession(); Participant sender = session.getParticipantByPublicId(senderName); if (sender == null) { log.warn( "PARTICIPANT {}: Requesting to unsubscribe from user {} " + "in session {} but user could not be found", participant.getParticipantPublicId(), senderName, session.getSessionId()); throw new OpenViduException(Code.USER_NOT_FOUND_ERROR_CODE, "User " + senderName + " not found in session " + session.getSessionId()); } kParticipant.cancelReceivingMedia(senderName, EndReason.unsubscribe); sessionEventsHandler.onUnsubscribe(participant, transactionId, null); } @Override public void sendMessage(Participant participant, String message, Integer transactionId) { try { JsonObject messageJson = new JsonParser().parse(message).getAsJsonObject(); KurentoParticipant kParticipant = (KurentoParticipant) participant; sessionEventsHandler.onSendMessage(participant, messageJson, getParticipants(kParticipant.getSession().getSessionId()), transactionId, null); } catch (JsonSyntaxException | IllegalStateException e) { throw new OpenViduException(Code.SIGNAL_FORMAT_INVALID_ERROR_CODE, "Provided signal object '" + message + "' has not a valid JSON format"); } } @Override public void streamPropertyChanged(Participant participant, Integer transactionId, String streamId, String property, JsonElement newValue, String reason) { KurentoParticipant kParticipant = (KurentoParticipant) participant; streamId = kParticipant.getPublisherStreamId(); MediaOptions streamProperties = kParticipant.getPublisherMediaOptions(); Boolean hasAudio = streamProperties.hasAudio(); Boolean hasVideo = streamProperties.hasVideo(); Boolean audioActive = streamProperties.isAudioActive(); Boolean videoActive = streamProperties.isVideoActive(); String typeOfVideo = streamProperties.getTypeOfVideo(); Integer frameRate = streamProperties.getFrameRate(); String videoDimensions = streamProperties.getVideoDimensions(); KurentoFilter filter = streamProperties.getFilter(); switch (property) { case "audioActive": audioActive = newValue.getAsBoolean(); break; case "videoActive": videoActive = newValue.getAsBoolean(); break; case "videoDimensions": videoDimensions = newValue.getAsString(); break; } kParticipant.setPublisherMediaOptions(new MediaOptions(hasAudio, hasVideo, audioActive, videoActive, typeOfVideo, frameRate, videoDimensions, filter)); sessionEventsHandler.onStreamPropertyChanged(participant, transactionId, kParticipant.getSession().getParticipants(), streamId, property, newValue, reason); } @Override public void onIceCandidate(Participant participant, String endpointName, String candidate, int sdpMLineIndex, String sdpMid, Integer transactionId) { try { KurentoParticipant kParticipant = (KurentoParticipant) participant; log.debug("Request [ICE_CANDIDATE] endpoint={} candidate={} " + "sdpMLineIdx={} sdpMid={} ({})", endpointName, candidate, sdpMLineIndex, sdpMid, participant.getParticipantPublicId()); kParticipant.addIceCandidate(endpointName, new IceCandidate(candidate, sdpMid, sdpMLineIndex)); sessionEventsHandler.onRecvIceCandidate(participant, transactionId, null); } catch (OpenViduException e) { log.error("PARTICIPANT {}: Error receiving ICE " + "candidate (epName={}, candidate={})", participant.getParticipantPublicId(), endpointName, candidate, e); sessionEventsHandler.onRecvIceCandidate(participant, transactionId, e); } } /** * Creates a session with the already existing not-active session in the * indicated KMS, if it doesn't already exist * * @throws OpenViduException in case of error while creating the session */ public KurentoSession createSession(Session sessionNotActive, Kms kms) throws OpenViduException { KurentoSession session = (KurentoSession) sessions.get(sessionNotActive.getSessionId()); if (session != null) { throw new OpenViduException(Code.ROOM_CANNOT_BE_CREATED_ERROR_CODE, "Session '" + session.getSessionId() + "' already exists"); } session = new KurentoSession(sessionNotActive, kms, kurentoSessionEventsHandler, kurentoEndpointConfig, kmsManager.destroyWhenUnused()); KurentoSession oldSession = (KurentoSession) sessions.putIfAbsent(session.getSessionId(), session); if (oldSession != null) { log.warn("Session '{}' has just been created by another thread", session.getSessionId()); return oldSession; } // Also associate the KurentoSession with the Kms kms.addKurentoSession(session); log.warn("No session '{}' exists yet. Created one on KMS '{}'", session.getSessionId(), kms.getUri()); sessionEventsHandler.onSessionCreated(session); return session; } @Override public boolean evictParticipant(Participant evictedParticipant, Participant moderator, Integer transactionId, EndReason reason) throws OpenViduException { boolean sessionClosedByLastParticipant = false; if (evictedParticipant != null) { KurentoParticipant kParticipant = (KurentoParticipant) evictedParticipant; Set<Participant> participants = kParticipant.getSession().getParticipants(); sessionClosedByLastParticipant = this.leaveRoom(kParticipant, null, reason, false); this.sessionEventsHandler.onForceDisconnect(moderator, evictedParticipant, participants, transactionId, null, reason); sessionEventsHandler.closeRpcSession(evictedParticipant.getParticipantPrivateId()); } else { if (moderator != null && transactionId != null) { this.sessionEventsHandler.onForceDisconnect(moderator, evictedParticipant, new HashSet<>(Arrays.asList(moderator)), transactionId, new OpenViduException(Code.USER_NOT_FOUND_ERROR_CODE, "Connection not found when calling 'forceDisconnect'"), null); } } return sessionClosedByLastParticipant; } @Override public KurentoMediaOptions generateMediaOptions(Request<JsonObject> request) throws OpenViduException { String sdpOffer = RpcHandler.getStringParam(request, ProtocolElements.PUBLISHVIDEO_SDPOFFER_PARAM); boolean hasAudio = RpcHandler.getBooleanParam(request, ProtocolElements.PUBLISHVIDEO_HASAUDIO_PARAM); boolean hasVideo = RpcHandler.getBooleanParam(request, ProtocolElements.PUBLISHVIDEO_HASVIDEO_PARAM); Boolean audioActive = null, videoActive = null; String typeOfVideo = null, videoDimensions = null; Integer frameRate = null; KurentoFilter kurentoFilter = null; try { audioActive = RpcHandler.getBooleanParam(request, ProtocolElements.PUBLISHVIDEO_AUDIOACTIVE_PARAM); } catch (RuntimeException noParameterFound) { } try { videoActive = RpcHandler.getBooleanParam(request, ProtocolElements.PUBLISHVIDEO_VIDEOACTIVE_PARAM); } catch (RuntimeException noParameterFound) { } try { typeOfVideo = RpcHandler.getStringParam(request, ProtocolElements.PUBLISHVIDEO_TYPEOFVIDEO_PARAM); } catch (RuntimeException noParameterFound) { } try { videoDimensions = RpcHandler.getStringParam(request, ProtocolElements.PUBLISHVIDEO_VIDEODIMENSIONS_PARAM); } catch (RuntimeException noParameterFound) { } try { frameRate = RpcHandler.getIntParam(request, ProtocolElements.PUBLISHVIDEO_FRAMERATE_PARAM); } catch (RuntimeException noParameterFound) { } try { JsonObject kurentoFilterJson = (JsonObject) RpcHandler.getParam(request, ProtocolElements.PUBLISHVIDEO_KURENTOFILTER_PARAM); if (kurentoFilterJson != null) { try { kurentoFilter = new KurentoFilter(kurentoFilterJson.get("type").getAsString(), kurentoFilterJson.get("options").getAsJsonObject()); } catch (Exception e) { throw new OpenViduException(Code.FILTER_NOT_APPLIED_ERROR_CODE, "'filter' parameter wrong:" + e.getMessage()); } } } catch (OpenViduException e) { throw e; } catch (RuntimeException noParameterFound) { } boolean doLoopback = RpcHandler.getBooleanParam(request, ProtocolElements.PUBLISHVIDEO_DOLOOPBACK_PARAM); return new KurentoMediaOptions(true, sdpOffer, null, null, hasAudio, hasVideo, audioActive, videoActive, typeOfVideo, frameRate, videoDimensions, kurentoFilter, doLoopback); } @Override public boolean unpublishStream(Session session, String streamId, Participant moderator, Integer transactionId, EndReason reason) { String participantPrivateId = ((KurentoSession) session).getParticipantPrivateIdFromStreamId(streamId); if (participantPrivateId != null) { Participant participant = this.getParticipant(participantPrivateId); if (participant != null) { this.unpublishVideo(participant, moderator, transactionId, reason); return true; } else { return false; } } else { return false; } } @Override public void applyFilter(Session session, String streamId, String filterType, JsonObject filterOptions, Participant moderator, Integer transactionId, String filterReason) { String participantPrivateId = ((KurentoSession) session).getParticipantPrivateIdFromStreamId(streamId); if (participantPrivateId != null) { Participant publisher = this.getParticipant(participantPrivateId); moderator = (moderator != null && publisher.getParticipantPublicId().equals(moderator.getParticipantPublicId())) ? null : moderator; log.debug("Request [APPLY_FILTER] over stream [{}] for reason [{}]", streamId, filterReason); KurentoParticipant kParticipantPublisher = (KurentoParticipant) publisher; if (!publisher.isStreaming()) { log.warn( "PARTICIPANT {}: Requesting to applyFilter to user {} " + "in session {} but user is not streaming media", moderator != null ? moderator.getParticipantPublicId() : publisher.getParticipantPublicId(), publisher.getParticipantPublicId(), session.getSessionId()); throw new OpenViduException(Code.USER_NOT_STREAMING_ERROR_CODE, "User '" + publisher.getParticipantPublicId() + " not streaming media in session '" + session.getSessionId() + "'"); } else if (kParticipantPublisher.getPublisher().getFilter() != null) { log.warn( "PARTICIPANT {}: Requesting to applyFilter to user {} " + "in session {} but user already has a filter", moderator != null ? moderator.getParticipantPublicId() : publisher.getParticipantPublicId(), publisher.getParticipantPublicId(), session.getSessionId()); throw new OpenViduException(Code.EXISTING_FILTER_ALREADY_APPLIED_ERROR_CODE, "User '" + publisher.getParticipantPublicId() + " already has a filter applied in session '" + session.getSessionId() + "'"); } else { try { KurentoFilter filter = new KurentoFilter(filterType, filterOptions); this.applyFilterInPublisher(kParticipantPublisher, filter); Set<Participant> participants = kParticipantPublisher.getSession().getParticipants(); sessionEventsHandler.onFilterChanged(publisher, moderator, transactionId, participants, streamId, filter, null, filterReason); } catch (OpenViduException e) { log.warn("PARTICIPANT {}: Error applying filter", publisher.getParticipantPublicId(), e); sessionEventsHandler.onFilterChanged(publisher, moderator, transactionId, new HashSet<>(), streamId, null, e, ""); } } log.info("State of filter for participant {}: {}", publisher.getParticipantPublicId(), ((KurentoParticipant) publisher).getPublisher().filterCollectionsToString()); } else { log.warn("PARTICIPANT {}: Requesting to applyFilter to stream {} " + "in session {} but the owner cannot be found", streamId, session.getSessionId()); throw new OpenViduException(Code.USER_NOT_FOUND_ERROR_CODE, "Owner of stream '" + streamId + "' not found in session '" + session.getSessionId() + "'"); } } @Override public void removeFilter(Session session, String streamId, Participant moderator, Integer transactionId, String filterReason) { String participantPrivateId = ((KurentoSession) session).getParticipantPrivateIdFromStreamId(streamId); if (participantPrivateId != null) { Participant participant = this.getParticipant(participantPrivateId); log.debug("Request [REMOVE_FILTER] over stream [{}] for reason [{}]", streamId, filterReason); KurentoParticipant kParticipant = (KurentoParticipant) participant; if (!participant.isStreaming()) { log.warn( "PARTICIPANT {}: Requesting to removeFilter to user {} " + "in session {} but user is not streaming media", moderator != null ? moderator.getParticipantPublicId() : participant.getParticipantPublicId(), participant.getParticipantPublicId(), session.getSessionId()); throw new OpenViduException(Code.USER_NOT_STREAMING_ERROR_CODE, "User '" + participant.getParticipantPublicId() + " not streaming media in session '" + session.getSessionId() + "'"); } else if (kParticipant.getPublisher().getFilter() == null) { log.warn( "PARTICIPANT {}: Requesting to removeFilter to user {} " + "in session {} but user does NOT have a filter", moderator != null ? moderator.getParticipantPublicId() : participant.getParticipantPublicId(), participant.getParticipantPublicId(), session.getSessionId()); throw new OpenViduException(Code.FILTER_NOT_APPLIED_ERROR_CODE, "User '" + participant.getParticipantPublicId() + " has no filter applied in session '" + session.getSessionId() + "'"); } else { this.removeFilterInPublisher(kParticipant); Set<Participant> participants = kParticipant.getSession().getParticipants(); sessionEventsHandler.onFilterChanged(participant, moderator, transactionId, participants, streamId, null, null, filterReason); } log.info("State of filter for participant {}: {}", kParticipant.getParticipantPublicId(), kParticipant.getPublisher().filterCollectionsToString()); } else { log.warn("PARTICIPANT {}: Requesting to removeFilter to stream {} " + "in session {} but the owner cannot be found", streamId, session.getSessionId()); throw new OpenViduException(Code.USER_NOT_FOUND_ERROR_CODE, "Owner of stream '" + streamId + "' not found in session '" + session.getSessionId() + "'"); } } @Override public void execFilterMethod(Session session, String streamId, String filterMethod, JsonObject filterParams, Participant moderator, Integer transactionId, String filterReason) { String participantPrivateId = ((KurentoSession) session).getParticipantPrivateIdFromStreamId(streamId); if (participantPrivateId != null) { Participant participant = this.getParticipant(participantPrivateId); log.debug("Request [EXEC_FILTER_MTEHOD] over stream [{}] for reason [{}]", streamId, filterReason); KurentoParticipant kParticipant = (KurentoParticipant) participant; if (!participant.isStreaming()) { log.warn( "PARTICIPANT {}: Requesting to execFilterMethod to user {} " + "in session {} but user is not streaming media", moderator != null ? moderator.getParticipantPublicId() : participant.getParticipantPublicId(), participant.getParticipantPublicId(), session.getSessionId()); throw new OpenViduException(Code.USER_NOT_STREAMING_ERROR_CODE, "User '" + participant.getParticipantPublicId() + " not streaming media in session '" + session.getSessionId() + "'"); } else if (kParticipant.getPublisher().getFilter() == null) { log.warn( "PARTICIPANT {}: Requesting to execFilterMethod to user {} " + "in session {} but user does NOT have a filter", moderator != null ? moderator.getParticipantPublicId() : participant.getParticipantPublicId(), participant.getParticipantPublicId(), session.getSessionId()); throw new OpenViduException(Code.FILTER_NOT_APPLIED_ERROR_CODE, "User '" + participant.getParticipantPublicId() + " has no filter applied in session '" + session.getSessionId() + "'"); } else { KurentoFilter updatedFilter = this.execFilterMethodInPublisher(kParticipant, filterMethod, filterParams); Set<Participant> participants = kParticipant.getSession().getParticipants(); sessionEventsHandler.onFilterChanged(participant, moderator, transactionId, participants, streamId, updatedFilter, null, filterReason); } } else { log.warn("PARTICIPANT {}: Requesting to removeFilter to stream {} " + "in session {} but the owner cannot be found", streamId, session.getSessionId()); throw new OpenViduException(Code.USER_NOT_FOUND_ERROR_CODE, "Owner of stream '" + streamId + "' not found in session '" + session.getSessionId() + "'"); } } @Override public void addFilterEventListener(Session session, Participant userSubscribing, String streamId, String eventType) throws OpenViduException { String publisherPrivateId = ((KurentoSession) session).getParticipantPrivateIdFromStreamId(streamId); if (publisherPrivateId != null) { log.debug("Request [ADD_FILTER_LISTENER] over stream [{}]", streamId); KurentoParticipant kParticipantPublishing = (KurentoParticipant) this .getParticipant(publisherPrivateId); KurentoParticipant kParticipantSubscribing = (KurentoParticipant) userSubscribing; if (!kParticipantPublishing.isStreaming()) { log.warn( "PARTICIPANT {}: Requesting to addFilterEventListener to stream {} " + "in session {} but the publisher is not streaming media", userSubscribing.getParticipantPublicId(), streamId, session.getSessionId()); throw new OpenViduException(Code.USER_NOT_STREAMING_ERROR_CODE, "User '" + kParticipantPublishing.getParticipantPublicId() + " not streaming media in session '" + session.getSessionId() + "'"); } else if (kParticipantPublishing.getPublisher().getFilter() == null) { log.warn( "PARTICIPANT {}: Requesting to addFilterEventListener to user {} " + "in session {} but user does NOT have a filter", kParticipantSubscribing.getParticipantPublicId(), kParticipantPublishing.getParticipantPublicId(), session.getSessionId()); throw new OpenViduException(Code.FILTER_NOT_APPLIED_ERROR_CODE, "User '" + kParticipantPublishing.getParticipantPublicId() + " has no filter applied in session '" + session.getSessionId() + "'"); } else { try { this.addFilterEventListenerInPublisher(kParticipantPublishing, eventType); kParticipantPublishing.getPublisher().addParticipantAsListenerOfFilterEvent(eventType, userSubscribing.getParticipantPublicId()); } catch (OpenViduException e) { throw e; } } log.info("State of filter for participant {}: {}", kParticipantPublishing.getParticipantPublicId(), kParticipantPublishing.getPublisher().filterCollectionsToString()); } else { throw new OpenViduException(Code.USER_NOT_FOUND_ERROR_CODE, "Not user found for streamId '" + streamId + "' in session '" + session.getSessionId() + "'"); } } @Override public void removeFilterEventListener(Session session, Participant subscriber, String streamId, String eventType) throws OpenViduException { String participantPrivateId = ((KurentoSession) session).getParticipantPrivateIdFromStreamId(streamId); if (participantPrivateId != null) { log.debug("Request [REMOVE_FILTER_LISTENER] over stream [{}]", streamId); Participant participantPublishing = this.getParticipant(participantPrivateId); KurentoParticipant kParticipantPublishing = (KurentoParticipant) participantPublishing; if (!participantPublishing.isStreaming()) { log.warn( "PARTICIPANT {}: Requesting to removeFilterEventListener to stream {} " + "in session {} but user is not streaming media", subscriber.getParticipantPublicId(), streamId, session.getSessionId()); throw new OpenViduException(Code.USER_NOT_STREAMING_ERROR_CODE, "User '" + participantPublishing.getParticipantPublicId() + " not streaming media in session '" + session.getSessionId() + "'"); } else if (kParticipantPublishing.getPublisher().getFilter() == null) { log.warn( "PARTICIPANT {}: Requesting to removeFilterEventListener to user {} " + "in session {} but user does NOT have a filter", subscriber.getParticipantPublicId(), participantPublishing.getParticipantPublicId(), session.getSessionId()); throw new OpenViduException(Code.FILTER_NOT_APPLIED_ERROR_CODE, "User '" + participantPublishing.getParticipantPublicId() + " has no filter applied in session '" + session.getSessionId() + "'"); } else { try { PublisherEndpoint pub = kParticipantPublishing.getPublisher(); if (pub.removeParticipantAsListenerOfFilterEvent(eventType, subscriber.getParticipantPublicId())) { // If there are no more participants listening to the event remove the event // from the GenericMediaElement this.removeFilterEventListenerInPublisher(kParticipantPublishing, eventType); } } catch (OpenViduException e) { throw e; } } log.info("State of filter for participant {}: {}", kParticipantPublishing.getParticipantPublicId(), kParticipantPublishing.getPublisher().filterCollectionsToString()); } } @Override public String getParticipantPrivateIdFromStreamId(String sessionId, String streamId) { Session session = this.getSession(sessionId); return ((KurentoSession) session).getParticipantPrivateIdFromStreamId(streamId); } public KmsManager getKmsManager() { return this.kmsManager; } private void applyFilterInPublisher(KurentoParticipant kParticipant, KurentoFilter filter) throws OpenViduException { GenericMediaElement.Builder builder = new GenericMediaElement.Builder(kParticipant.getPipeline(), filter.getType()); Props props = new JsonUtils().fromJsonObjectToProps(filter.getOptions()); props.forEach(prop -> { builder.withConstructorParam(prop.getName(), prop.getValue()); }); kParticipant.getPublisher().apply(builder.build()); kParticipant.getPublisher().getMediaOptions().setFilter(filter); } private void removeFilterInPublisher(KurentoParticipant kParticipant) { kParticipant.getPublisher().cleanAllFilterListeners(); kParticipant.getPublisher().revert(kParticipant.getPublisher().getFilter()); kParticipant.getPublisher().getMediaOptions().setFilter(null); } private KurentoFilter execFilterMethodInPublisher(KurentoParticipant kParticipant, String method, JsonObject params) { kParticipant.getPublisher().execMethod(method, params); KurentoFilter filter = kParticipant.getPublisher().getMediaOptions().getFilter(); KurentoFilter updatedFilter = new KurentoFilter(filter.getType(), filter.getOptions(), method, params); kParticipant.getPublisher().getMediaOptions().setFilter(updatedFilter); return updatedFilter; } private void addFilterEventListenerInPublisher(KurentoParticipant kParticipant, String eventType) throws OpenViduException { PublisherEndpoint pub = kParticipant.getPublisher(); if (!pub.isListenerAddedToFilterEvent(eventType)) { final String connectionId = kParticipant.getParticipantPublicId(); final String streamId = kParticipant.getPublisherStreamId(); final String filterType = kParticipant.getPublisherMediaOptions().getFilter().getType(); try { ListenerSubscription listener = pub.getFilter().addEventListener(eventType, event -> { sessionEventsHandler.onFilterEventDispatched(connectionId, streamId, filterType, event.getType(), event.getData(), kParticipant.getSession().getParticipants(), kParticipant.getPublisher().getPartipantsListentingToFilterEvent(eventType)); }); pub.storeListener(eventType, listener); } catch (Exception e) { log.error("Request to addFilterEventListener to stream {} gone wrong. Error: {}", streamId, e.getMessage()); throw new OpenViduException(Code.FILTER_EVENT_LISTENER_NOT_FOUND, "Request to addFilterEventListener to stream " + streamId + " gone wrong: " + e.getMessage()); } } } private void removeFilterEventListenerInPublisher(KurentoParticipant kParticipant, String eventType) { PublisherEndpoint pub = kParticipant.getPublisher(); if (pub.isListenerAddedToFilterEvent(eventType)) { GenericMediaElement filter = kParticipant.getPublisher().getFilter(); filter.removeEventListener(pub.removeListener(eventType)); } } }