package org.domain.mediaendpointsdemo.session; import java.io.IOException; import javax.servlet.sip.SipServletRequest; import javax.servlet.sip.SipServletResponse; import javax.servlet.sip.SipSession; import org.jboss.seam.ScopeType; import org.jboss.seam.annotations.In; import org.jboss.seam.annotations.Logger; import org.jboss.seam.annotations.Name; import org.jboss.seam.annotations.Observer; import org.jboss.seam.annotations.Out; import org.jboss.seam.annotations.Scope; import org.jboss.seam.log.Log; import org.mobicents.mscontrol.MsConnection; import org.mobicents.mscontrol.MsConnectionEvent; import org.mobicents.mscontrol.MsLinkEvent; import org.mobicents.mscontrol.MsLinkMode; import org.mobicents.servlet.sip.seam.entrypoint.media.MediaController; import org.mobicents.servlet.sip.seam.media.framework.IVRHelper; import org.mobicents.servlet.sip.seam.media.framework.MediaSessionStore; @Name("serviceMessageHandler") @Scope(ScopeType.STATELESS) public class ServiceMessageHandler { @In MediaController mediaController; @In MediaSessionStore mediaSessionStore; @In IVRHelper ivrHelper; @In SipSession sipSession; /* * Represents the current state of the call. I.e. - are we conneted to IVR * or a conference/1 or conference/$. Basically we store here the last pressed * DTMF button as it identifies the mode uniquely for this application. */ @In(required=false,scope=ScopeType.SESSION) @Out(required=false,scope=ScopeType.SESSION) String mode; @Logger private static Log log; /* [0] * We receive an incoming call. INVITE has been sent from some SIP client * to our applications. */ @Observer("INVITE") public void doInvite(SipServletRequest request) throws Exception { /* [1] * Let's store the invite in the session. This is equivalent to * what we do with the "mode" variable, which is session-scoped, * but declared in the Seam-way. */ sipSession.setAttribute("inviteRequest", request); /* [2] * Let's tell the caller that we received the INVITE and we are * ringing now. */ request.createResponse(180).send(); /* [3] * Take the remote SDP sent by the caller. */ String sdp = new String((byte[]) request.getContent()); /* [4] * Now create a Media connection between the caller and any available * PacketRelay endpoint in our media server. Note that we pass the SDP * with the modify method. The remote SDP contains the RTP port where the caller * want to receive the RTP stream. By the following line of code we are telling our * Media Server to create a local UDP socket and start preparing to stream * to the remote socket using the parameters contained in this SDP. * Other than the RTP port of the caller, the SDP contains information about * the supported codecs and media capabilities. We usually dont care about * it as it is responsibility of the media server to process the data. SDP is * not very human-readable anyway. */ mediaController.createConnection("media/trunk/PacketRelay/$").modify("$", sdp); } /* [5] * Once the RTP connection between fully negotiated from step [4], the Media * Server notified us with this callback and the RTP is ready for streaming * immediately after we send our SDP to the caller. So far we have built the * following topology: * * Caller <----MsConnection----> PacketRelay */ @Observer("connectionOpen") public void connectionOpenRequest(MsConnectionEvent event) throws Exception { /* [6] * Let's get the MsConnection object that connects the caller to the * PacketRelay Endpoint. We can take it from mediaSessionStore, where STF * collects objects related to the Media Topology of the current call(session). */ MsConnection connection = mediaSessionStore.getMsConnection(); /* [7] * Let's take the local SDP, where the information about our own RTP port and * supported codecs is contained. We must send this info to the caller so they * will know where to begin streaming they media and what codecs they can use. */ String sdp = event.getConnection().getLocalDescriptor(); /* [8] * Recall the INVITE we stored in step [1] */ SipServletRequest inviteRequest = (SipServletRequest) sipSession.getAttribute("inviteRequest"); /* [9] * Prepare the response we are about to send to the caller. */ SipServletResponse sipServletResponse = inviteRequest .createResponse(SipServletResponse.SC_OK); /* [10] * Put our local SDP in the response and send the response. */ sipServletResponse.setContent(sdp, "application/sdp"); sipServletResponse.send(); /* [11] * Set the initial mode of the application */ mode = "1"; /* [12] * And connect the PacketRelay endpoint to an IVR endpoint. The IVR endpoint * supports a variety of operations like playback, recording, DTMF. Basically, * when this line is executed we have built the following topology: * Caller <----MsConnection----> PacketRelay <----MsLink----> IVR */ mediaController.createLink(MsLinkMode.FULL_DUPLEX) .join("media/trunk/IVR/$", connection.getEndpoint().getLocalName()); } /* [13] * The Media Server notifies us that the link we created in step [12] * was connected successfully. This method is also called from steps [20], * [21], [22] where we perform a similar link connection operation. This * method can handle all cases as long as the mode is set. */ @Observer("linkConnected") public void doLinkConnected(MsLinkEvent event) { if("1".equals(mode)) { /* [14] * If we are in IVR mode just play some announcement and detect DTMF. */ ivrHelper.playAnnouncementWithDtmf( "http://mobicents.googlecode.com/svn/tags/servers/media/examples/" + "mobicents-media-server-examples-1.0.0.GA/mms-demo/web/src/main/" + "webapp/audio/welcome.wav"); } else { /* [15] * Otherwise just detect DTMF so we can keep reacting on DTMF events */ ivrHelper.detectDtmf(); } } /* [16] * This callback is called when the caller has pressed a button on her phone. * Here, we switch between different endpoint to perform different functions. * Initially, the caller is connected to IVR (through the PacketRelay), but * we can connect the caller to a Conference endpoint for example as shown below. */ @Observer("DTMF") public void doDtmf(String digit) throws Exception{ log.info("DTMF = " + digit + ", previous mode = " + mode); /* [17] * Change the mode to the new digit pressed */ mode = digit; /* [18] * Some cleanup to release the link that we don;t need anymore (as * we create a new one). This operations is done automatically in * newer versions of STF. */ mediaSessionStore.getMsLink().release(); /* [19] * Branch the logic depending on which button is pressed */ if("1".equals(digit)) { /* [20] * If "1" is pressed build the following topology: * Caller <----MsConnection----> PacketRelay <----MsLink----> IVR */ mediaController.createLink(MsLinkMode.FULL_DUPLEX) .join("media/trunk/IVR/$", mediaSessionStore.getMsConnection().getEndpoint().getLocalName()); } else if("2".equals(digit)) { /* [21] * If "2" is pressed build the following topology: * Caller <----MsConnection----> PacketRelay <----MsLink----> Conference */ mediaController.createLink(MsLinkMode.FULL_DUPLEX) .join("media/trunk/Conference/$", mediaSessionStore.getMsConnection().getEndpoint().getLocalName()); } else if("3".equals(digit)) { /* [22] * If "3" is pressed build the following topology: * Caller <----MsConnection----> PacketRelay <----MsLink----> Conference * * The difference from the previous option is only that here we strictly * connect to the Conference Endpoint 1, while in the previous option we * connect to the first available (unused) Conference endpoint. */ mediaController.createLink(MsLinkMode.FULL_DUPLEX) .join("media/trunk/Conference/1", mediaSessionStore.getMsConnection().getEndpoint().getLocalName()); } else if("0".equals(digit)) { /* [23] * Here we just end the call if the user presses "0" */ sipSession.createRequest("BYE").send(); } } @Observer("BYE") public void doBye(SipServletRequest request) throws Exception { request.createResponse(200).send(); } @Observer("INFO") public void doInfo(SipServletRequest request) throws Exception { request.createResponse(200).send(); } @Observer("REGISTER") public void doRegister(SipServletRequest request) throws Exception { request.createResponse(200).send(); } }