From d9b5ba9037840dabd553c742c2c8a2ad0e84ea22 Mon Sep 17 00:00:00 2001 From: Boris Grozev Date: Thu, 9 Apr 2015 16:29:21 +0200 Subject: [PATCH] Implements OperationSetIncomingDTMF for XMPP, wired to receive events via RTP/RFC4733. --- .../impl/protocol/jabber/CallJabberImpl.java | 28 ++++++ .../OperationSetIncomingDTMFJabberImpl.java | 61 +++++++++++++ .../ProtocolProviderServiceJabberImpl.java | 8 +- .../protocol/sip/OperationSetDTMFSipImpl.java | 4 +- .../sip/OperationSetIncomingDTMFSipImpl.java | 4 +- .../impl/protocol/sip/dtmf/DTMFInfo.java | 2 +- .../protocol/event/DTMFReceivedEvent.java | 74 +++++++++++++-- .../protocol/media/CallPeerMediaHandler.java | 8 +- .../protocol/media/MediaAwareCall.java | 16 +++- .../service/protocol/media/MediaHandler.java | 91 ++++++++++++++++++- 10 files changed, 278 insertions(+), 18 deletions(-) create mode 100644 src/net/java/sip/communicator/impl/protocol/jabber/OperationSetIncomingDTMFJabberImpl.java diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/CallJabberImpl.java b/src/net/java/sip/communicator/impl/protocol/jabber/CallJabberImpl.java index 65597171c..fe4f4f7b4 100644 --- a/src/net/java/sip/communicator/impl/protocol/jabber/CallJabberImpl.java +++ b/src/net/java/sip/communicator/impl/protocol/jabber/CallJabberImpl.java @@ -1401,4 +1401,32 @@ && getConference().isJitsiVideobridge()) } return this.jitsiVideobridge; } + + /** + * {@inheritDoc} + * + * Implements + * {@link net.java.sip.communicator.service.protocol.event.DTMFListener#toneReceived(net.java.sip.communicator.service.protocol.event.DTMFReceivedEvent)} + * + * Forwards DTMF events to the IncomingDTMF operation set, setting + * this Call as the source. + */ + @Override + public void toneReceived(DTMFReceivedEvent evt) + { + OperationSetIncomingDTMF opSet + = getProtocolProvider() + .getOperationSet(OperationSetIncomingDTMF.class); + + if (opSet != null && opSet instanceof OperationSetIncomingDTMFJabberImpl) + { + // Re-fire the event using this Call as the source. + ((OperationSetIncomingDTMFJabberImpl) opSet).toneReceived( + new DTMFReceivedEvent( + this, + evt.getValue(), + evt.getDuration(), + evt.getStart())); + } + } } diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/OperationSetIncomingDTMFJabberImpl.java b/src/net/java/sip/communicator/impl/protocol/jabber/OperationSetIncomingDTMFJabberImpl.java new file mode 100644 index 000000000..ba2fff020 --- /dev/null +++ b/src/net/java/sip/communicator/impl/protocol/jabber/OperationSetIncomingDTMFJabberImpl.java @@ -0,0 +1,61 @@ +/* + * Jitsi, the OpenSource Java VoIP and Instant Messaging client. + * + * Distributable under LGPL license. + * See terms of license at gnu.org. + */ +package net.java.sip.communicator.impl.protocol.jabber; + +import net.java.sip.communicator.service.protocol.*; +import net.java.sip.communicator.service.protocol.event.*; + +import java.util.*; + +/** + * Implements OperationSetIncomingDTMF for the jabber protocol. + * + * @author Boris Grozev + */ +public class OperationSetIncomingDTMFJabberImpl + implements OperationSetIncomingDTMF, + DTMFListener +{ + private final Set listeners = new HashSet(); + + /** + * {@inheritDoc} + * + * Implements + * {@link net.java.sip.communicator.service.protocol.OperationSetIncomingDTMF#addDTMFListener(net.java.sip.communicator.service.protocol.event.DTMFListener)} + */ + @Override + public void addDTMFListener(DTMFListener listener) + { + listeners.add(listener); + } + + /** + * {@inheritDoc} + * + * Implements + * {@link net.java.sip.communicator.service.protocol.OperationSetIncomingDTMF#removeDTMFListener(net.java.sip.communicator.service.protocol.event.DTMFListener)} + */ + @Override + public void removeDTMFListener(DTMFListener listener) + { + listeners.remove(listener); + } + + /** + * {@inheritDoc} + * + * Implements + * {@link net.java.sip.communicator.service.protocol.event.DTMFListener#toneReceived(net.java.sip.communicator.service.protocol.event.DTMFReceivedEvent)} + */ + @Override + public void toneReceived(DTMFReceivedEvent evt) + { + for (DTMFListener listener : listeners) + listener.toneReceived(evt); + } +} diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/ProtocolProviderServiceJabberImpl.java b/src/net/java/sip/communicator/impl/protocol/jabber/ProtocolProviderServiceJabberImpl.java index 3d6587095..589929b51 100644 --- a/src/net/java/sip/communicator/impl/protocol/jabber/ProtocolProviderServiceJabberImpl.java +++ b/src/net/java/sip/communicator/impl/protocol/jabber/ProtocolProviderServiceJabberImpl.java @@ -1831,10 +1831,14 @@ protected void initialize(String screenname, } // init DTMF - OperationSetDTMFJabberImpl operationSetDTMFSip + OperationSetDTMFJabberImpl operationSetDTMF = new OperationSetDTMFJabberImpl(this); addSupportedOperationSet( - OperationSetDTMF.class, operationSetDTMFSip); + OperationSetDTMF.class, operationSetDTMF); + + addSupportedOperationSet( + OperationSetIncomingDTMF.class, + new OperationSetIncomingDTMFJabberImpl()); addJingleFeatures(); diff --git a/src/net/java/sip/communicator/impl/protocol/sip/OperationSetDTMFSipImpl.java b/src/net/java/sip/communicator/impl/protocol/sip/OperationSetDTMFSipImpl.java index b04b4c848..07887daa1 100644 --- a/src/net/java/sip/communicator/impl/protocol/sip/OperationSetDTMFSipImpl.java +++ b/src/net/java/sip/communicator/impl/protocol/sip/OperationSetDTMFSipImpl.java @@ -101,11 +101,11 @@ public synchronized void startSendingDTMF(CallPeer callPeer, DTMFTone tone) } // If the account is configured to use RTP DTMF method and the call - // does not manage telephone events. Then, we log it for futur + // does not manage telephone events. Then, we log it for future // debugging. if(this.dtmfMethod == DTMFMethod.RTP_DTMF && !isRFC4733Active(cp)) { - logger.debug("RTP DTMF used without telephon-event capacities"); + logger.debug("RTP DTMF used without telephone-event capacity"); } ((AudioMediaStream)cp.getMediaHandler().getStream(MediaType.AUDIO)) diff --git a/src/net/java/sip/communicator/impl/protocol/sip/OperationSetIncomingDTMFSipImpl.java b/src/net/java/sip/communicator/impl/protocol/sip/OperationSetIncomingDTMFSipImpl.java index 4cb651b1b..af16cfb2a 100644 --- a/src/net/java/sip/communicator/impl/protocol/sip/OperationSetIncomingDTMFSipImpl.java +++ b/src/net/java/sip/communicator/impl/protocol/sip/OperationSetIncomingDTMFSipImpl.java @@ -10,8 +10,8 @@ import net.java.sip.communicator.service.protocol.event.*; /** - * An OperationSet that allows us to receive DMF tones through - * this protocol provider. + * An OperationSet that allows us to receive DTMF tones through + * this protocol provider. Only supports SIP INFO. * * @author Damian Minkov */ diff --git a/src/net/java/sip/communicator/impl/protocol/sip/dtmf/DTMFInfo.java b/src/net/java/sip/communicator/impl/protocol/sip/dtmf/DTMFInfo.java index bb08186b7..f695d8574 100644 --- a/src/net/java/sip/communicator/impl/protocol/sip/dtmf/DTMFInfo.java +++ b/src/net/java/sip/communicator/impl/protocol/sip/dtmf/DTMFInfo.java @@ -289,7 +289,7 @@ public boolean processResponse(ResponseEvent responseEvent) return processed; } - /* + /** * Receives dtmf info requests. */ @Override diff --git a/src/net/java/sip/communicator/service/protocol/event/DTMFReceivedEvent.java b/src/net/java/sip/communicator/service/protocol/event/DTMFReceivedEvent.java index 84cac5eaa..c07febec4 100644 --- a/src/net/java/sip/communicator/service/protocol/event/DTMFReceivedEvent.java +++ b/src/net/java/sip/communicator/service/protocol/event/DTMFReceivedEvent.java @@ -8,14 +8,13 @@ import java.util.*; -import net.java.sip.communicator.service.protocol.*; - import org.jitsi.service.protocol.*; /** * DTMFReceivedEvents indicate reception of a DTMF tone. * * @author Damian Minkov + * @author Boris Grozev */ public class DTMFReceivedEvent extends EventObject @@ -28,30 +27,77 @@ public class DTMFReceivedEvent /** * The tone. */ - private DTMFTone value = null; + private final DTMFTone value; /** * The duration. */ - private long duration; + private final long duration; + + /** + * Whether this DTMFReceivedEvent represents the start of reception + * of a tone (if true), the end of reception of a tone (if + * false), or the reception of a tone with a given duration (if + * null). + */ + private final Boolean start; /** * Creates a MessageReceivedEvent representing reception of the * source message received from the specified from * contact. * - * @param source the Message whose reception this event represents. - * @param value dmtf tone value - * @param duration duration of the DTMF tone + * @param source the source of the event. + * @param value dmtf tone value. + * @param start whether this event represents the start of reception (if + * true), the end of reception (if false) or the reception + * of a tone with a given direction (if null). */ - public DTMFReceivedEvent(ProtocolProviderService source, + public DTMFReceivedEvent(Object source, + DTMFTone value, + boolean start) + { + this(source, value, -1, start); + } + + /** + * Creates a MessageReceivedEvent representing reception of the + * source message received from the specified from + * contact. + * + * @param source the source of the event. + * @param value dmtf tone value. + * @param duration duration of the DTMF tone. + */ + public DTMFReceivedEvent(Object source, DTMFTone value, long duration) + { + this(source, value, duration, null); + } + + /** + * Creates a MessageReceivedEvent representing reception of the + * source message received from the specified from + * contact. + * + * @param source the source of the event. + * @param value dmtf tone value. + * @param duration duration of the DTMF tone. + * @param start whether this event represents the start of reception (if + * true), the end of reception (if false) or the reception + * of a tone with a given direction (if null). + */ + public DTMFReceivedEvent(Object source, + DTMFTone value, + long duration, + Boolean start) { super(source); this.value = value; this.duration = duration; + this.start = start; } /** @@ -71,4 +117,16 @@ public long getDuration() { return duration; } + + /** + * Returns the value of the start attribute of this + * DTMFReceivedEvent, which indicates whether this + * DTMFReceivedEvent represents the start of reception of a tone + * (if true), the end of reception of a tone (if false), + * or the reception of a tone with a given duration (if null). + */ + public Boolean getStart() + { + return start; + } } diff --git a/src/net/java/sip/communicator/service/protocol/media/CallPeerMediaHandler.java b/src/net/java/sip/communicator/service/protocol/media/CallPeerMediaHandler.java index d1995718e..c55703ee9 100644 --- a/src/net/java/sip/communicator/service/protocol/media/CallPeerMediaHandler.java +++ b/src/net/java/sip/communicator/service/protocol/media/CallPeerMediaHandler.java @@ -27,7 +27,7 @@ * A utility class implementing media control code shared between current * telephony implementations. This class is only meant for use by protocol * implementations and should not be accessed by bundles that are simply using - * the telephony functionalities. + * the telephony functionality. * * @param the peer extension class like for example CallPeerSipImpl * or CallPeerJabberImpl @@ -1806,6 +1806,11 @@ public void setMediaHandler(MediaHandler mediaHandler) if (srtpListener != null) this.mediaHandler.removeSrtpListener(srtpListener); this.mediaHandler.removeVideoListener(videoStreamVideoListener); + + // We intentionally do not remove our Call from the list of + // DTMF listeners. It should stay there as long as the + // MediaHandler is used by at least one CallPeer/CPMH. + //this.mediaHandler.removeDtmfListener(getPeer().getCall()); } this.mediaHandler = mediaHandler; @@ -1843,6 +1848,7 @@ public void setMediaHandler(MediaHandler mediaHandler) if (srtpListener != null) this.mediaHandler.addSrtpListener(srtpListener); this.mediaHandler.addVideoListener(videoStreamVideoListener); + this.mediaHandler.addDtmfListener(getPeer().getCall()); } } } diff --git a/src/net/java/sip/communicator/service/protocol/media/MediaAwareCall.java b/src/net/java/sip/communicator/service/protocol/media/MediaAwareCall.java index ba32c597a..34af55d3f 100644 --- a/src/net/java/sip/communicator/service/protocol/media/MediaAwareCall.java +++ b/src/net/java/sip/communicator/service/protocol/media/MediaAwareCall.java @@ -12,6 +12,7 @@ import net.java.sip.communicator.service.protocol.*; import net.java.sip.communicator.service.protocol.event.*; +import net.java.sip.communicator.service.protocol.event.DTMFListener; import org.jitsi.service.neomedia.*; import org.jitsi.service.neomedia.device.*; import org.jitsi.service.neomedia.event.*; @@ -41,7 +42,8 @@ public abstract class MediaAwareCall< V extends ProtocolProviderService> extends AbstractCall implements CallPeerListener, - PropertyChangeListener + PropertyChangeListener, + DTMFListener { /** * The name of the property of MediaAwareCall the value of which @@ -960,4 +962,16 @@ public void setConference(CallConference conference) { super.setConference(conference); } + + /** + * {@inheritDoc} + * + * Implements + * {@link net.java.sip.communicator.service.protocol.event.DTMFListener#toneReceived(net.java.sip.communicator.service.protocol.event.DTMFReceivedEvent)} + */ + @Override + public void toneReceived(DTMFReceivedEvent evt) + { + // Stub + } } diff --git a/src/net/java/sip/communicator/service/protocol/media/MediaHandler.java b/src/net/java/sip/communicator/service/protocol/media/MediaHandler.java index e4de1dfb6..42e5db975 100644 --- a/src/net/java/sip/communicator/service/protocol/media/MediaHandler.java +++ b/src/net/java/sip/communicator/service/protocol/media/MediaHandler.java @@ -11,7 +11,9 @@ import java.util.*; import java.util.List; -import net.java.sip.communicator.service.protocol.*; +import net.java.sip.communicator.service.protocol.OperationFailedException; +import net.java.sip.communicator.service.protocol.event.*; +import net.java.sip.communicator.service.protocol.event.DTMFListener; import net.java.sip.communicator.util.*; import org.jitsi.service.neomedia.*; @@ -19,6 +21,7 @@ import org.jitsi.service.neomedia.device.*; import org.jitsi.service.neomedia.event.*; import org.jitsi.service.neomedia.format.*; +import org.jitsi.service.protocol.*; import org.jitsi.util.event.*; /** @@ -184,6 +187,18 @@ public void securityTurnedOn( private final List srtpListeners = new LinkedList(); + /** + * The set of listeners in the application (Jitsi) which are to + * be notified of DTMF events. + */ + private final Set dtmfListeners + = new HashSet(); + + /** + * The listener registered to receive DTMF events from {@link #audioStream}. + */ + private final MyDTMFListener dtmfListener = new MyDTMFListener(); + /** * The SimpleAudioLeveListener that this instance sets on its * {@link #audioStream} if {@link #streamAudioLevelListeners} is not empty @@ -444,6 +459,27 @@ void addSrtpListener(SrtpListener listener) } } + /** + * Adds a DTMFListener which will be notified when DTMF events + * are received from the MediaHandler's audio stream. + * @param listener the listener to add. + */ + void addDtmfListener(DTMFListener listener) + { + if (listener != null) + dtmfListeners.add(listener); + } + + /** + * Removes a DTMFListener from the set of listeners to be notified + * for DTMF events from this MediaHandler's audio steam. + * @param listener the listener to remove. + */ + void removeDtmfListener(DTMFListener listener) + { + dtmfListeners.remove(listener); + } + /** * Adds a specific SimpleAudioLevelListener to the list of * SimpleAudioLevelListeners to be notified about changes in the @@ -1201,6 +1237,8 @@ private void setAudioStream(AudioMediaStream audioStream) this.audioStream.removePropertyChangeListener( streamPropertyChangeListener); + this.audioStream.removeDTMFListener(dtmfListener); + this.audioStream.close(); } @@ -1240,6 +1278,8 @@ private void setAudioStream(AudioMediaStream audioStream) streamAudioLevelListener); } } + + this.audioStream.addDTMFListener(dtmfListener); } else { @@ -1495,4 +1535,53 @@ private void setVideoStream(VideoMediaStream videoStream) } } } + + /** + * Implements a libjitsi DTMFListener, which receives + * events from an AudioMediaStream, translate them into + * Jitsi events (DTMFReceivedEvents) and forward them to + * any registered listeners. + */ + private class MyDTMFListener + implements org.jitsi.service.neomedia.event.DTMFListener + { + /** + * {@inheritDoc} + */ + @Override + public void dtmfToneReceptionStarted(DTMFToneEvent dtmfToneEvent) + { + fireEvent( + new DTMFReceivedEvent( + this, + DTMFTone.getDTMFTone(dtmfToneEvent.getDtmfTone().getValue()), + true)); + } + + /** + * {@inheritDoc} + */ + @Override + public void dtmfToneReceptionEnded(DTMFToneEvent dtmfToneEvent) + { + fireEvent( + new DTMFReceivedEvent( + this, + DTMFTone.getDTMFTone(dtmfToneEvent.getDtmfTone().getValue()), + false)); + } + + /** + * Sends an DTMFReceivedEvent to all listeners. + * @param event the event to send. + */ + private void fireEvent(DTMFReceivedEvent event) + { + for (net.java.sip.communicator.service.protocol.event.DTMFListener + listener : dtmfListeners) + { + listener.toneReceived(event); + } + } + } }