Package org.red5.app.sip

Source Code of org.red5.app.sip.SipUserAgent

package org.red5.app.sip;

import org.zoolu.sip.call.*;
import org.zoolu.sip.address.*;
import org.zoolu.sip.provider.SipProvider;
import org.zoolu.sip.header.StatusLine;
import org.zoolu.sip.message.*;
import org.zoolu.sdp.*;
import org.red5.app.sip.codecs.Codec;
import org.red5.app.sip.codecs.CodecUtils;
import org.slf4j.Logger;
import org.red5.logging.Red5LoggerFactory;
import org.red5.server.api.scope.IScope;
import org.red5.server.api.stream.IBroadcastStream;
import org.zoolu.tools.Parser;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Set;
import java.util.Vector;

public class SipUserAgent extends CallListenerAdapter {
    private static Logger log = Red5LoggerFactory.getLogger(SipUserAgent.class, "sip");
   
    private SipUserAgentProfile userProfile;
    private SipProvider sipProvider;
    private ExtendedCall call;
    private ExtendedCall callTransfer;
    private CallStream callStream;   
    private String localSession = null;
    private Codec sipCodec = null;   
    private final ScopeProvider scopeProvider;
    private Set<SipUserAgentListener> listeners = new HashSet<SipUserAgentListener>();
   
    private enum CallState {
      UA_IDLE(0), UA_INCOMING_CALL(1), UA_OUTGOING_CALL(2), UA_ONCALL(3);     
      private final int state;
      CallState(int state) {this.state = state;}
      private int getState() {return state;}
    }

    private CallState callState;

    public SipUserAgent(SipProvider sipProvider, SipUserAgentProfile userProfile, ScopeProvider scopeProvider) {
        this.scopeProvider = scopeProvider;
        this.sipProvider = sipProvider;
        this.userProfile = userProfile;
       
        // If no contact_url and/or from_url has been set, create it now.
        userProfile.initContactAddress(sipProvider);       
        // Set local sdp.
        initSessionDescriptor();
    }
   
    public void addListener(SipUserAgentListener listener) {
    listeners.add(listener);
  }
   
    public void removeListener(SipUserAgentListener listener) {
    listeners.remove(listener);
  }
   
    private void changeStatus(CallState state) {
      callState = state;
    }
   
    public boolean isIdle() {
      return callState == CallState.UA_IDLE;
    }
   
    public void queueSipDtmfDigits(String digits) {
      callStream.queueSipDtmfDigits(digits);
    }
   
    public void initialize() {
      waitForIncomingCalls();
    }
   
    public void initSessionDescriptor() {       
        log.debug("initSessionDescriptor");       
        SessionDescriptor newSdp = SdpUtils.createInitialSdp(userProfile.username,
            sipProvider.getViaAddress(), userProfile.audioPort,
            userProfile.videoPort, userProfile.audioCodecsPrecedence );       
        localSession = newSdp.toString();       
        log.debug("localSession Descriptor = " + localSession );
    }

    public void call(String targetUrl) {     
      log.debug( "call", "Init..." )
      changeStatus(CallState.UA_OUTGOING_CALL);
       
        call = new ExtendedCall(sipProvider, userProfile.fromUrl,
                userProfile.contactUrl, userProfile.username,
                userProfile.realm, userProfile.passwd, this)
       
        // In case of incomplete url (e.g. only 'user' is present), try to
        // complete it.      
        targetUrl = sipProvider.completeNameAddress(targetUrl).toString();

        if (userProfile.noOffer) {
            call.call(targetUrl);
        } else {
            call.call(targetUrl, localSession);
        }
    }

    /** Call Transfer test by Lior */
    public void transfer( String transferTo ){
     log.debug("REFER/TRANSFER", "Init..." );
     if (call != null && call.isOnCall()) {
         call.transfer(transferTo);
     }
    }
    /** end of transfer test code */


    /** Waits for an incoming call (acting as UAS). */
    private void waitForIncomingCalls() {
      log.debug("waitForIncomingCalls..." );   
      changeStatus(CallState.UA_IDLE);
       
        call = new ExtendedCall(sipProvider, userProfile.fromUrl,
                userProfile.contactUrl, userProfile.username,
                userProfile.realm, userProfile.passwd, this);       
        call.listen();
    }


    /** Closes an ongoing, incoming, or pending call */
    public void hangup() {
      log.debug("hangup");
     
      if (isIdle()) return;
     
      closeMediaApplication();       
      if (call != null) call.hangup();   
      changeStatus(CallState.UA_IDLE);
      waitForIncomingCalls();  
    }


    /** Closes an ongoing, incoming, or pending call */
    public void accept() {       
      log.debug("accept", "Init...");

        if (call != null) {
            call.accept(localSession);
        }
    }

    public void redirect(String redirection) {     
      log.debug( "redirect", "Init..." );
     
        if (call != null) {
            call.redirect(redirection);
        }
    }

    protected void launchMediaApplication() {
        // Exit if the Media Application is already running.
        if (callStream != null) {           
          log.debug("launchMediaApplication", "Media application is already running.");
            return;
        }
       
        SessionDescriptor localSdp = new SessionDescriptor( call.getLocalSessionDescriptor() );       
        SessionDescriptor remoteSdp = new SessionDescriptor( call.getRemoteSessionDescriptor() );
        String remoteMediaAddress = (new Parser(remoteSdp.getConnection().toString())).skipString().skipString().getString();
        int remoteAudioPort = getRemoteAudioPort(remoteSdp);
        int localAudioPort = getLocalAudioPort(localSdp);
       
        log.debug("[localAudioPort=" + localAudioPort + ",remoteAudioPort=" + remoteAudioPort + "]");

        if (userProfile.audio && localAudioPort != 0 && remoteAudioPort != 0) {
            if ((callStream == null) && (sipCodec != null)) {  
              SipConnectInfo connInfo = new SipConnectInfo(localAudioPort, remoteMediaAddress, remoteAudioPort);
             
              try {
          callStream = new CallStream(sipCodec, connInfo, scopeProvider);
          notifyListenersOnCallConnected(callStream.getTalkStreamName(), callStream.getListenStreamName());
        } catch (Exception e) {
          log.error("Failed to create Call Stream.");
        }               
            }
        }
    }

    private void notifyListenersOnCallConnected(String talkStream, String listenStream) {
      for (SipUserAgentListener listener : listeners) {
        listener.onCallConnected(talkStream, listenStream);
      }    
    }
     
    private int getLocalAudioPort(SessionDescriptor localSdp) {
        int localAudioPort = 0;
        //int localVideoPort = 0;

        // parse local sdp
        for ( Enumeration e = localSdp.getMediaDescriptors().elements(); e.hasMoreElements(); ) {
            MediaField media = ( (MediaDescriptor) e.nextElement() ).getMedia();
            if ( media.getMedia().equals( "audio" ) ) {
                localAudioPort = media.getPort();
            }
            //if ( media.getMedia().equals( "video" ) ) {
            //    localVideoPort = media.getPort();
            //}
        }
       
        return localAudioPort;
    }
   
    private int getRemoteAudioPort(SessionDescriptor remoteSdp) {
      int remoteAudioPort = 0;
        //int remoteVideoPort = 0;

        for (Enumeration e = remoteSdp.getMediaDescriptors().elements(); e.hasMoreElements();) {
            MediaDescriptor descriptor = (MediaDescriptor) e.nextElement();
            MediaField media = descriptor.getMedia();

            if ( media.getMedia().equals( "audio" ) ) {
                remoteAudioPort = media.getPort();
            }

           // if ( media.getMedia().equals( "video" ) ) {
           //     remoteVideoPort = media.getPort();
           // }
        }
       
        return remoteAudioPort;
    }
   
    public void startTalkStream(IBroadcastStream broadcastStream, IScope scope) {
      callStream.startTalkStream(broadcastStream, scope);    
    }
   
    public void stopTalkStream(IBroadcastStream broadcastStream, IScope scope) {
      if (callStream != null) {
        callStream.stopTalkStream(broadcastStream, scope);    
      }
    }
   
    private void closeMediaApplication() {       
      log.debug("closeMediaApplication" );
       
        if (callStream != null) {
          callStream.stopMedia();
          callStream = null;
        }
    }


    // ********************** Call callback functions **********************

    private void createAudioCodec(SessionDescriptor newSdp) {
      sipCodec = SdpUtils.getNegotiatedAudioCodec(newSdp);
    }
   
    /**
     * Callback function called when arriving a new INVITE method (incoming call)
     */
    public void onCallIncoming(Call call, NameAddress callee, NameAddress caller, String sdp, Message invite) {       
      log.debug("onCallIncoming");
       
      if (!isCurrentCall(call)) return;
       
        log.debug("IncomingCallIncoming()");
       
        changeStatus(CallState.UA_INCOMING_CALL);
        call.ring();

        if (sdp != null) {
          setupSdpAndCodec(sdp);
        }

        notifyListenersOfNewIncomingCall(callee, caller);
    }
   
    private void setupSdpAndCodec(String sdp) {
      SessionDescriptor remoteSdp = new SessionDescriptor(sdp);
        SessionDescriptor localSdp = new SessionDescriptor(localSession);
       
        log.debug("localSdp = " + localSdp.toString() + ".");
        log.debug("remoteSdp = " + remoteSdp.toString() + ".");
       
        // First we need to make payloads negotiation so the related attributes can be then matched.
        SessionDescriptor newSdp = SdpUtils.makeMediaPayloadsNegotiation(localSdp, remoteSdp);
       
        createAudioCodec(newSdp);
       
        // Now we complete the SDP negotiation informing the selected
        // codec, so it can be internally updated during the process.
        SdpUtils.completeSdpNegotiation(newSdp, localSdp, remoteSdp);

        localSession = newSdp.toString();
       
        log.debug("newSdp = " + localSession + "." );
       
        // Finally, we use the "newSdp" and "remoteSdp" to initialize the lasting codec informations.
        CodecUtils.initSipAudioCodec(sipCodec, userProfile.audioDefaultPacketization,
                userProfile.audioDefaultPacketization, newSdp, remoteSdp);
    }

    private void notifyListenersOfNewIncomingCall(NameAddress callee, NameAddress caller) {
      String source = caller.getAddress().toString();
        String sourceName = caller.hasDisplayName() ? caller.getDisplayName() : "";
        String destination = callee.getAddress().toString();
        String destinationName = callee.hasDisplayName() ? callee.getDisplayName() : "";
       
      for (SipUserAgentListener listener : listeners) {
        listener.onNewIncomingCall(source, sourceName, destination, destinationName);
      }
    }

    /**
     * Callback function called when arriving a new Re-INVITE method (re-inviting/call modify)
     */
    public void onCallModifying(Call call, String sdp, Message invite) {
      log.debug("onCallModifying");
       
      if (!isCurrentCall(call)) return;
       
        log.debug("RE-INVITE/MODIFY.");
       
        // to be implemented.
        // currently it simply accepts the session changes (see method
        // onCallModifying() in CallListenerAdapter)
        super.onCallModifying(call, sdp, invite);
    }


    /**
     * Callback function that may be overloaded (extended). Called when arriving a 180 Ringing
     */
    public void onCallRinging(Call call, Message resp) {       
      log.debug("onCallRinging");
       
      if (!isCurrentCallOrCallTransfer(call)) return;
       
        log.debug("RINGING." );
       
        notifyListenersOfOnOutgoingCallRemoteRinging();
    }
   
    private void notifyListenersOfOnOutgoingCallRemoteRinging() {
      for (SipUserAgentListener listener : listeners) {
        listener.onOutgoingCallRemoteRinging();
      }
    }


    /** Callback function called when arriving a 2xx (call accepted) */
    public void onCallAccepted(Call call, String sdp, Message resp) {       
      log.debug( "onCallAccepted");
       
      if (!isCurrentCallOrCallTransfer(call)) return;
       
        log.debug("ACCEPTED/CALL.");
        changeStatus(CallState.UA_ONCALL);

        setupSdpAndCodec(sdp);

        if (userProfile.noOffer) {
            // Answer with the local sdp.
            call.ackWithAnswer(localSession);
        }

        launchMediaApplication();

        if (call == callTransfer) {
            StatusLine statusLine = resp.getStatusLine();
            int code = statusLine.getCode();
            String reason = statusLine.getReason();
            this.call.notify(code, reason);
        }

        notifyListenersOfOnOutgoingCallAccepted();
    }

    public void notifyListenersOfOnOutgoingCallAccepted() {
      for (SipUserAgentListener listener : listeners) {
        listener.onOutgoingCallAccepted();
      }
    }

    /** Callback function called when arriving an ACK method (call confirmed) */
    public void onCallConfirmed(Call call, String sdp, Message ack) {
      log.debug("onCallConfirmed");
       
      if (!isCurrentCall(call)) return;
       
        log.debug("CONFIRMED/CALL.");
        changeStatus(CallState.UA_ONCALL);

        launchMediaApplication();
    }


    /** Callback function called when arriving a 2xx (re-invite/modify accepted) */
    public void onCallReInviteAccepted(Call call, String sdp, Message resp) {
      log.debug( "onCallReInviteAccepted");
       
      if (!isCurrentCall(call)) return;
       
        log.debug("RE-INVITE-ACCEPTED/CALL." );
    }


    /** Callback function called when arriving a 4xx (re-invite/modify failure) */
    public void onCallReInviteRefused(Call call, String reason, Message resp) {
      log.debug("onCallReInviteRefused");
       
      if (!isCurrentCall(call)) return;
       
        log.debug("RE-INVITE-REFUSED (" + reason + ")/CALL.");
       
        notifyListenersOnOutgoingCallFailed();
        waitForIncomingCalls();
    }

    /** Callback function called when arriving a 4xx (call failure) */
    public void onCallRefused(Call call, String reason, Message resp) {       
      log.debug("onCallRefused");
       
      if (!isCurrentCall(call)) return;
     
        log.debug("REFUSED (" + reason + ").");
        changeStatus(CallState.UA_IDLE);

        if (call == callTransfer) {
            StatusLine status_line = resp.getStatusLine();
            int code = status_line.getCode();
            // String reason=status_line.getReason();
            this.call.notify(code, reason);
            callTransfer = null;
        }

        notifyListenersOnOutgoingCallFailed();
        waitForIncomingCalls();
    }
   
    private void notifyListenersOnOutgoingCallFailed() {
      for (SipUserAgentListener listener : listeners) {
        listener.onOutgoingCallFailed();
      }
    }


    /** Callback function called when arriving a 3xx (call redirection) */
    public void onCallRedirection(Call call, String reason, Vector contact_list, Message resp) {       
      log.debug("onCallRedirection");
       
      if (!isCurrentCall(call)) return;
        log.debug("REDIRECTION (" + reason + ")." );
       
        call.call(((String) contact_list.elementAt(0)));
    }


    /**
     * Callback function that may be overloaded (extended). Called when arriving a CANCEL request
     */
    public void onCallCanceling(Call call, Message cancel) {
      log.debug("onCallCanceling");
       
      if (!isCurrentCall(call)) return;
       
        log.debug("CANCEL.");
        changeStatus(CallState.UA_IDLE);
        notifyListenersOfOnIncomingCallCancelled();
        waitForIncomingCalls();
    }
   
    private void notifyListenersOfOnIncomingCallCancelled() {
      for (SipUserAgentListener listener : listeners) {
        listener.onIncomingCallCancelled();
      }
    }


    /** Callback function called when arriving a BYE request */
    public void onCallClosing(Call call, Message bye) {
      log.debug("onCallClosing");
       
      if (!isCurrentCallOrCallTransfer(call)) return;

        if (call != callTransfer && callTransfer != null) {
          log.debug("CLOSE PREVIOUS CALL.");
            this.call = callTransfer;
            callTransfer = null;
            return;
        }
       
        log.debug("CLOSE.");
       
        closeMediaApplication();

        notifyListenersOfOnCallClosed();
        changeStatus(CallState.UA_IDLE);

        // Reset local sdp for next call.
        initSessionDescriptor();
        waitForIncomingCalls();
    }

    private void notifyListenersOfOnCallClosed() {
      for (SipUserAgentListener listener : listeners) {
        listener.onCallClosed();
      }
    }

    /**
     * Callback function called when arriving a response after a BYE request
     * (call closed)
     */
    public void onCallClosed(Call call, Message resp) {
      log.debug("onCallClosed");
       
      if (!isCurrentCall(call)) return;
       
        log.debug("CLOSE/OK.");
       
        notifyListenersOfOnCallClosed();
        changeStatus(CallState.UA_IDLE);
        waitForIncomingCalls();
    }


    /** Callback function called when the invite expires */
    public void onCallTimeout(Call call) {       
      log.debug("onCallTimeout");
       
      if (!isCurrentCall(call)) return;
       
        log.debug("NOT FOUND/TIMEOUT.");
        changeStatus(CallState.UA_IDLE);

        if (call == callTransfer) {
            int code = 408;
            String reason = "Request Timeout";
            this.call.notify(code, reason);
            callTransfer = null;
        }

        notifyListenersOnOutgoingCallFailed();
        waitForIncomingCalls();
    }


    // ****************** ExtendedCall callback functions ******************
    /**
     * Callback function called when arriving a new REFER method (transfer request)
     */
    public void onCallTransfer(ExtendedCall call, NameAddress refer_to, NameAddress refered_by, Message refer) {
      log.debug("onCallTransfer");
       
      if (!isCurrentCall(call)) return;
       
        log.debug("Transfer to " + refer_to.toString() + ".");
       
        call.acceptTransfer();

        callTransfer = new ExtendedCall(sipProvider, userProfile.fromUrl, userProfile.contactUrl, this);
        callTransfer.call(refer_to.toString(), localSession);
    }


    /** Callback function called when a call transfer is accepted. */
    public void onCallTransferAccepted(ExtendedCall call, Message resp) {
      log.debug("onCallTransferAccepted");
       
      if (!isCurrentCall(call)) return;
       
        log.debug("Transfer accepted.");
    }


    /** Callback function called when a call transfer is refused. */
    public void onCallTransferRefused(ExtendedCall call, String reason, Message resp) {
      log.debug("onCallTransferRefused");       
      if (!isCurrentCall(call)) return;       
        log.debug("Transfer refused.");
    }


    /** Callback function called when a call transfer is successfully completed */
    public void onCallTransferSuccess(ExtendedCall call, Message notify) {
      log.debug("onCallTransferSuccess");       
      if (!isCurrentCall(call)) return;
       
        log.debug("Transfer succeeded.");
       
        call.hangup();

        notifyListenersOfOnCallTransferred();
    }

    private void notifyListenersOfOnCallTransferred() {
      for (SipUserAgentListener listener : listeners) {
        listener.onCallTrasferred();
      }
    }

    /**
     * Callback function called when a call transfer is NOT successfully completed
     */
    public void onCallTransferFailure(ExtendedCall call, String reason, Message notify) {
      log.debug("onCallTransferFailure");       
      if (!isCurrentCall(call)) return;
       
        log.info("Transfer failed.");
    }
   
    private boolean isCurrentCallOrCallTransfer(Call call) {
      return (call == this.call) || (call != callTransfer);
    }
   
    private boolean isCurrentCall(Call call) {
      return this.call == call;
    }
}
TOP

Related Classes of org.red5.app.sip.SipUserAgent

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.