diff --git a/src/main/java/org/jitsi/jigasi/AbstractGatewaySession.java b/src/main/java/org/jitsi/jigasi/AbstractGatewaySession.java index 89bc798e7..181f0e018 100644 --- a/src/main/java/org/jitsi/jigasi/AbstractGatewaySession.java +++ b/src/main/java/org/jitsi/jigasi/AbstractGatewaySession.java @@ -207,6 +207,12 @@ abstract void onJvbConferenceWillStop(JvbConference jvbConference, */ public void onJvbCallEnded() {} + /** + * Method called by JvbConference to notify JVB call has been + * established. + */ + public void onJvbCallEstablished() {} + /** * Cancels current session by leaving the muc room */ @@ -410,6 +416,11 @@ public String getFocusResourceAddr() return focusResourceAddr; } + /** + * If muting is supported will mute the participant. + */ + public abstract void mute(); + /** * Whether the gateway implementation supports call resuming. Where we can * keep the gateway session while the xmpp call is been disconnected or diff --git a/src/main/java/org/jitsi/jigasi/JigasiBundleActivator.java b/src/main/java/org/jitsi/jigasi/JigasiBundleActivator.java index ee097f6e3..354d06882 100644 --- a/src/main/java/org/jitsi/jigasi/JigasiBundleActivator.java +++ b/src/main/java/org/jitsi/jigasi/JigasiBundleActivator.java @@ -183,6 +183,15 @@ public void start(final BundleContext bundleContext) if(isSipEnabled()) { + MuteIqProvider.registerMuteIqProvider(); + + // recording status, to detect recording start/stop + ProviderManager.addExtensionProvider( + RecordingStatus.ELEMENT_NAME, + RecordingStatus.NAMESPACE, + new DefaultPacketExtensionProvider<>(RecordingStatus.class) + ); + logger.info("initialized SipGateway"); sipGateway = new SipGateway(bundleContext) { @@ -222,9 +231,14 @@ void notifyCallEnded(CallContext callContext) else { logger.info("skipped initialization of TranscriptionGateway"); - } + // Register Jitsi Meet media presence extension. + MediaPresenceExtension.registerExtensions(); + + // Register Rayo IQs + new RayoIqProvider().registerRayoIQs(); + bundleContext.addServiceListener(this); Collection> refs @@ -273,19 +287,6 @@ public void serviceChanged(ServiceEvent serviceEvent) return; } - // Register Jitsi Meet media presence extension. - MediaPresenceExtension.registerExtensions(); - - // Register Rayo IQs - new RayoIqProvider().registerRayoIQs(); - - // recording status, to detect recording start/stop - ProviderManager.addExtensionProvider( - RecordingStatus.ELEMENT_NAME, - RecordingStatus.NAMESPACE, - new DefaultPacketExtensionProvider<>(RecordingStatus.class) - ); - ProtocolProviderService pps = (ProtocolProviderService) service; if (sipGateway != null && sipGateway.getSipProvider() == null && ProtocolNames.SIP.equals(pps.getProtocolName())) diff --git a/src/main/java/org/jitsi/jigasi/JvbConference.java b/src/main/java/org/jitsi/jigasi/JvbConference.java index f9ff5617d..12411e853 100644 --- a/src/main/java/org/jitsi/jigasi/JvbConference.java +++ b/src/main/java/org/jitsi/jigasi/JvbConference.java @@ -36,6 +36,7 @@ import org.jitsi.xmpp.extensions.rayo.*; import org.jivesoftware.smack.*; import org.jivesoftware.smack.bosh.*; +import org.jivesoftware.smack.iqrequest.*; import org.jivesoftware.smack.packet.*; import org.jivesoftware.smackx.nick.packet.*; import org.jxmpp.jid.*; @@ -582,14 +583,12 @@ public synchronized void registrationStateChanged( // Join the MUC joinConferenceRoom(); + XMPPConnection connection = getConnection(); if (xmppProvider != null - && xmppProvider instanceof ProtocolProviderServiceJabberImpl - && ((ProtocolProviderServiceJabberImpl) xmppProvider) - .getConnection() instanceof XMPPBOSHConnection) + && connection != null + && connection instanceof XMPPBOSHConnection) { - Object sessionId = Util.getConnSessionId( - ((ProtocolProviderServiceJabberImpl) xmppProvider) - .getConnection()); + Object sessionId = Util.getConnSessionId(connection); if (sessionId != null) { logger.error(this.callContext + " Registered bosh sid: " @@ -643,6 +642,15 @@ public boolean isInTheRoom() return mucRoom != null && mucRoom.isJoined(); } + /** + * Indicates whether this conference has been started. + * @return true is this conference is started, false otherwise. + */ + public boolean isStarted() + { + return started; + } + private void joinConferenceRoom() { // Advertise gateway feature before joining @@ -747,6 +755,11 @@ private void joinConferenceRoom() "is not an instance of ChatRoomJabberImpl"); } + if (JigasiBundleActivator.isSipStartMutedEnabled()) + { + getConnection().registerIQRequestHandler(new MuteIqHandler()); + } + // we invite focus and wait for its response // to be sure that if it is not in the room, the focus will be the // first to join, mimic the web behaviour @@ -1158,6 +1171,7 @@ public synchronized void callStateChanged(CallChangeEvent evt) if (jvbCall.getCallState() == CallState.CALL_IN_PROGRESS) { logger.info(callContext + " JVB conference call IN_PROGRESS."); + gatewaySession.onJvbCallEstablished(); } else if(jvbCall.getCallState() == CallState.CALL_ENDED) { @@ -1407,8 +1421,7 @@ private void inviteFocus(final EntityBareJid roomIdentifier) StanzaCollector collector = null; try { - collector = ((ProtocolProviderServiceJabberImpl) xmppProvider) - .getConnection() + collector = getConnection() .createStanzaCollectorAndSend(focusInviteIQ); collector.nextResultOrThrow(); } @@ -1687,4 +1700,52 @@ private XMPPConnection getConnection() return null; } + + /** + * Handles mute requests received by jicofo if enabled. + */ + private class MuteIqHandler + extends AbstractIqRequestHandler + { + MuteIqHandler() + { + super( + MuteIq.ELEMENT_NAME, + MuteIq.NAMESPACE, + IQ.Type.set, + Mode.sync); + } + + @Override + public IQ handleIQRequest(IQ iqRequest) + { + return handleMuteIq((MuteIq) iqRequest); + } + + /** + * Handles the incoming mute request only if it is from the focus. + * @param muteIq the incoming iq. + * @return the result iq. + */ + private IQ handleMuteIq(MuteIq muteIq) + { + Boolean doMute = muteIq.getMute(); + Jid from = muteIq.getFrom(); + + if (doMute == null + || !from.getResourceOrEmpty().equals( + gatewaySession.getFocusResourceAddr())) + { + return IQ.createErrorResponse(muteIq, XMPPError.getBuilder( + XMPPError.Condition.item_not_found)); + } + + if (doMute) + { + gatewaySession.mute(); + } + + return IQ.createResultIQ(muteIq); + } + } } diff --git a/src/main/java/org/jitsi/jigasi/SipGatewaySession.java b/src/main/java/org/jitsi/jigasi/SipGatewaySession.java index d6dc01ada..35abfc072 100644 --- a/src/main/java/org/jitsi/jigasi/SipGatewaySession.java +++ b/src/main/java/org/jitsi/jigasi/SipGatewaySession.java @@ -22,7 +22,6 @@ import net.java.sip.communicator.service.protocol.media.*; import net.java.sip.communicator.util.Logger; import org.jitsi.impl.neomedia.*; -import org.jitsi.jigasi.JigasiBundleActivator; import org.jitsi.jigasi.stats.*; import org.jitsi.jigasi.util.*; import org.jitsi.service.neomedia.*; @@ -594,6 +593,8 @@ private void sipCallEnded() statsHandler = null; } + jitsiMeetTools.removeRequestListener(SipGatewaySession.this); + if (peerStateListener != null) peerStateListener.unregister(); @@ -644,7 +645,10 @@ public void onJoinJitsiMeetRequest( @Override public void onSessionStartMuted(boolean[] startMutedFlags) { - this.startAudioMuted = startMutedFlags[0]; + if (isMutingSupported()) + { + this.startAudioMuted = startMutedFlags[0]; + } } /** @@ -661,7 +665,6 @@ public void onJSONReceived(CallPeer callPeer, { try { - if (callPeer.getCall() != this.sipCall) { if (logger.isTraceEnabled()) @@ -671,13 +674,13 @@ public void onJSONReceived(CallPeer callPeer, return; } - if (jsonObject.containsKey("type") == false) + if (!jsonObject.containsKey("type")) { logger.error("Unknown json object type!"); return; } - if (jsonObject.containsKey("id") == false) + if (!jsonObject.containsKey("id")) { logger.error("Unknown json object id!"); return; @@ -686,16 +689,15 @@ public void onJSONReceived(CallPeer callPeer, String id = (String)jsonObject.get("id"); String type = (String)jsonObject.get("type"); - if (type.equalsIgnoreCase("muteResponse") == true) + if (type.equalsIgnoreCase("muteResponse")) { - if (jsonObject.containsKey("status") == false) + if (!jsonObject.containsKey("status")) { logger.error("muteResponse without status!"); return; } - if ( ((String)jsonObject.get("status")) - .equalsIgnoreCase("OK") == true) + if (((String) jsonObject.get("status")).equalsIgnoreCase("OK")) { JSONObject data = (JSONObject) jsonObject.get("data"); @@ -705,14 +707,14 @@ public void onJSONReceived(CallPeer callPeer, this.jvbConference.setChatRoomAudioMuted(bMute); } } - else if (type.equalsIgnoreCase("muteRequest") == true) + else if (type.equalsIgnoreCase("muteRequest")) { JSONObject data = (JSONObject) jsonObject.get("data"); boolean bAudioMute = (boolean)data.get("audio"); // Send request to jicofo - if (jvbConference.requestAudioMute(bAudioMute) == true) + if (jvbConference.requestAudioMute(bAudioMute)) { // Send response through sip respondRemoteAudioMute(bAudioMute, @@ -765,8 +767,8 @@ private JSONObject createSIPJSONAudioMuteRequest(boolean bMuted) { JSONObject muteSettingsJson = new JSONObject(); muteSettingsJson.put("audio", bMuted); - JSONObject muteRequestJson = createSIPJSON("muteRequest", muteSettingsJson, null); - return muteRequestJson; + + return createSIPJSON("muteRequest", muteSettingsJson, null); } /** @@ -783,8 +785,9 @@ private JSONObject createSIPJSONAudioMuteResponse(boolean bMuted, { JSONObject muteSettingsJson = new JSONObject(); muteSettingsJson.put("audio", bMuted); - JSONObject muteResponseJson = createSIPJSON("muteResponse", muteSettingsJson, id); - muteResponseJson.put("status", bSucceeded == true ? "OK" : "FAILED"); + JSONObject muteResponseJson + = createSIPJSON("muteResponse", muteSettingsJson, id); + muteResponseJson.put("status", bSucceeded ? "OK" : "FAILED"); return muteResponseJson; } @@ -795,9 +798,8 @@ private JSONObject createSIPJSONAudioMuteResponse(boolean bMuted, * @param callPeer CallPeer to send JSON to. * @throws OperationFailedException */ - private void requestRemoteAudioMute(boolean bMuted, - CallPeer callPeer) - throws OperationFailedException + private void requestRemoteAudioMute(boolean bMuted, CallPeer callPeer) + throws OperationFailedException { // Mute audio JSONObject muteRequestJson = createSIPJSONAudioMuteRequest(bMuted); @@ -822,7 +824,7 @@ private void respondRemoteAudioMute(boolean bMuted, boolean bSucceeded, CallPeer callPeer, String id) - throws OperationFailedException + throws OperationFailedException { JSONObject muteResponseJson = createSIPJSONAudioMuteResponse(bMuted, bSucceeded, id); @@ -830,9 +832,9 @@ private void respondRemoteAudioMute(boolean bMuted, jitsiMeetTools.sendJSON(callPeer, muteResponseJson, new HashMap() {{ - put("VIA", (Object)("SIP.INFO")); + put("VIA", "SIP.INFO"); }}); - } + } /** * Initializes the sip call listeners. @@ -851,6 +853,7 @@ private void initSipCall(String sipCallIdentifier) DEFAULT_STATS_REMOTE_ID + "-" + sipCallIdentifier); } sipCall.addCallChangeListener(statsHandler); + jitsiMeetTools.addRequestListener(this); if (mediaDroppedThresholdMs != -1) { @@ -931,8 +934,6 @@ private void waitForRoomName() waitThread = new WaitForJvbRoomNameThread(); - jitsiMeetTools.addRequestListener(this); - waitThread.start(); } @@ -1195,6 +1196,76 @@ public boolean hasCallResumeSupport() return true; } + /** + * When + */ + @Override + public void onJvbCallEstablished() + { + maybeProcessStartMuted(); + } + + /** + * Processes start muted in case: + * - we had received that flag + * - start muted is enabled through the flag + * - jvb call is in progress as we will be muting the channels + * - sip call is in progress we will be sending SIP Info messages + */ + private void maybeProcessStartMuted() + { + if (this.startAudioMuted + && isMutingSupported() + && jvbConferenceCall != null + && jvbConferenceCall.getCallState() == CallState.CALL_IN_PROGRESS + && sipCall != null + && sipCall.getCallState() == CallState.CALL_IN_PROGRESS) + { + if (jvbConference.requestAudioMute(startAudioMuted)) + { + mute(); + } + + // in case we reconnect start muted maybe no-longer set + this.startAudioMuted = false; + } + } + + /** + * Sends mute request to be remotely muted. + * This is a SIP Info message to the IVR so the user will be notified of it + * When we receive confirmation for the announcement we will update + * our presence status in the conference. + */ + public void mute() + { + if (!isMutingSupported()) + return; + + // Notify peer + CallPeer callPeer = sipCall.getCallPeers().next(); + + try + { + logger.info( + SipGatewaySession.this.callContext + " Sending mute request "); + requestRemoteAudioMute(true, callPeer); + } + catch (Exception ex) + { + logger.error(ex.getMessage()); + } + } + + /** + * Muting is supported when it is enabled by configuration. + * @return true if mute support is enabled. + */ + public boolean isMutingSupported() + { + return JigasiBundleActivator.isSipStartMutedEnabled(); + } + /** * PeriodicRunnable that will check incoming RTP and if needed to hangup. */ @@ -1312,6 +1383,8 @@ void handleCallState(Call call, CallPeerChangeEvent cause) logger.info(SipGatewaySession.this.callContext + " SIP call format used: " + Util.getFirstPeerMediaFormat(call)); + + maybeProcessStartMuted(); } else if(call.getCallState() == CallState.CALL_ENDED) { @@ -1386,39 +1459,6 @@ public void peerStateChanged(final CallPeerChangeEvent evt) if (jvbConference != null) jvbConference.setPresenceStatus(stateString); - if (JigasiBundleActivator.isSipStartMutedEnabled() == true) - { - if (CallPeerState.CONNECTED.equals(callPeerState) == true) - { - // After CallPeer is in CONNECTED state handle startmuted flags - jitsiMeetTools.addRequestListener(SipGatewaySession.this); - - if (SipGatewaySession.this.startAudioMuted == true) - { - // Send request to jicofo - if (jvbConference.requestAudioMute(startAudioMuted) == true) - { - // Notify peer - CallPeer callPeer = evt.getSourceCallPeer(); - - try - { - requestRemoteAudioMute(startAudioMuted, callPeer); - } - catch (Exception ex) - { - logger.error(ex.getMessage()); - } - } - } - } - - if (CallPeerState.DISCONNECTED.equals(callPeerState) == true) - { - jitsiMeetTools.removeRequestListener(SipGatewaySession.this); - } - } - soundNotificationManager.process(callPeerState); } @@ -1487,10 +1527,6 @@ public void run() { Thread.currentThread().interrupt(); } - finally - { - jitsiMeetTools.removeRequestListener(SipGatewaySession.this); - } } } diff --git a/src/main/java/org/jitsi/jigasi/SoundNotificationManager.java b/src/main/java/org/jitsi/jigasi/SoundNotificationManager.java index 21ebfa0da..6b04b4333 100644 --- a/src/main/java/org/jitsi/jigasi/SoundNotificationManager.java +++ b/src/main/java/org/jitsi/jigasi/SoundNotificationManager.java @@ -531,7 +531,12 @@ public void notifyChatRoomMemberJoined(ChatRoomMember member) */ public void notifyChatRoomMemberLeft(ChatRoomMember member) { - playParticipantLeftNotification(); + // if this is the sip hanging up (stopping) skip playing + if (gatewaySession.jvbConference.isStarted() + && gatewaySession.getSipCall() != null) + { + playParticipantLeftNotification(); + } } /** @@ -603,7 +608,7 @@ private void playParticipantJoinedNotification() if (getParticipantJoinedRateLimiter().on() == false) { Call sipCall = gatewaySession.getSipCall(); - + if (sipCall != null) { injectSoundFile(sipCall, PARTICIPANT_JOINED); @@ -704,7 +709,6 @@ private class SoundRateLimiter implements RateLimiter /** * SoundRateLimiter constructor. * - * @param timePoint Initial start timepoint. * @param maxTimeout Timeout in milliseconds to block notification. */ SoundRateLimiter(long maxTimeout) @@ -720,8 +724,7 @@ private class SoundRateLimiter implements RateLimiter */ public boolean on() { - if (this.startTimePoint - .compareAndSet(null, Instant.now()) == true) + if (this.startTimePoint.compareAndSet(null, Instant.now())) { return false; } diff --git a/src/main/java/org/jitsi/jigasi/TranscriptionGatewaySession.java b/src/main/java/org/jitsi/jigasi/TranscriptionGatewaySession.java index 085d76c13..c13a6c8f5 100644 --- a/src/main/java/org/jitsi/jigasi/TranscriptionGatewaySession.java +++ b/src/main/java/org/jitsi/jigasi/TranscriptionGatewaySession.java @@ -697,4 +697,9 @@ public boolean hasCallResumeSupport() { return false; } + + /** + * {@inheritDoc} + */ + public void mute(){} }