/*
* Copyright (C) 2009 Risto Känsäkoski - Sesca ISW Ltd
* Copyright (C) 2005 Luca Veltri - University of Parma - Italy
*
* This file is part of SIP-Applet (www.sesca.com, www.purplescout.com)
* This file is modified from MjSip (http://www.mjsip.org)
*
* MjSip is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* MjSip is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with MjSip; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
package com.sesca.voip.ua;
import local.media.AudioClipPlayer;
import local.ua.MediaLauncher;
import local.ua.UserAgentListener;
import org.zoolu.sip.call.*;
import org.zoolu.sip.address.*;
import org.zoolu.sip.provider.SipStack;
import org.zoolu.sip.provider.SipProvider;
import org.zoolu.sip.provider.TransactionIdentifier;
import org.zoolu.sip.header.StatusLine;
import org.zoolu.sip.message.*;
import org.zoolu.sdp.*;
import org.zoolu.tools.Log;
import org.zoolu.tools.LogLevel;
import org.zoolu.tools.Parser;
import org.zoolu.tools.Archive;
import com.sesca.audio.AudioCodecConfiguration;
import com.sesca.misc.Config;
import com.sesca.voip.ua.modules.debugjs;
import com.sesca.voip.media.JAudioLauncher;
import com.sesca.voip.media.TunneledAudioLauncher;
//import java.util.Iterator;
import java.util.Enumeration;
import java.util.Vector;
import java.io.*;
import java.net.InetSocketAddress;
import java.net.URL;
import java.net.UnknownHostException;
import java.nio.channels.SocketChannel;
// Copied form local.ua.UserAgent
/** Simple SIP user agent (UA).
* It includes audio/video applications.
* <p>
* It can use external audio/video tools as media applications.
* Currently only RAT (Robust Audio Tool) and VIC are supported as external applications.
*/
public class UserAgent extends CallListenerAdapter
{
// Socket audiosocket = null;
SocketChannel audiochannel = null;
Config conf = new Config();
public boolean tunnel = false;
/** Event debugjs. */
Log log;
public int lastResponseCode = 0;
URL codeBase = null;
/** UserAgentProfile */
public UserAgentProfile user_profile;
/** SipProvider */
protected SipProvider sip_provider;
/** Call */
//Call call;
protected ExtendedCall call;
/** Call transfer */
protected ExtendedCall call_transfer;
/** Audio application */
protected MediaLauncher audio_app = null;
/** Video application */
protected MediaLauncher video_app = null;
/** Local sdp */
protected String local_session = null;
/** UserAgent listener */
protected UserAgentListener listener = null;
/** On wav file */
String AUDIO_PATH="/resources/audio/";
final String CLIP_ON = "ringing_8k.wav";
final String CLIP_RING = "ringing_8k.wav";
final String CLIP_OFF = "busy_8k.wav";
InputStream is_clip_on;
InputStream is_clip_off;
InputStream is_clip_ring;
/** Ring sound */
public AudioClipPlayer clip_ring;
/** On sound */
public AudioClipPlayer clip_on;
/** Off sound */
public AudioClipPlayer clip_off;
public boolean callInProgress = false;
// *********************** Startup Configuration ***********************
/** UA_IDLE=0 */
static final String UA_IDLE = "IDLE";
/** UA_INCOMING_CALL=1 */
static final String UA_INCOMING_CALL = "INCOMING_CALL";
/** UA_OUTGOING_CALL=2 */
static final String UA_OUTGOING_CALL = "OUTGOING_CALL";
/** UA_ONCALL=3 */
static final String UA_ONCALL = "ONCALL";
/** Call state
* <P>UA_IDLE=0, <BR>UA_INCOMING_CALL=1, <BR>UA_OUTGOING_CALL=2, <BR>UA_ONCALL=3 */
String call_state = UA_IDLE;
// *************************** Basic methods ***************************
/** Changes the call state */
protected void changeStatus(String state)
{
call_state = state;
//printLog("state: "+call_state,LogLevel.MEDIUM);
}
/** Checks the call state */
protected boolean statusIs(String state)
{
return call_state.equals(state);
}
/** Gets the call state */
public String getStatus()
{
return call_state;
}
/** Sets the automatic answer time (default is -1 that means no auto accept mode) */
public void setAcceptTime(int accept_time)
{
user_profile.accept_time = accept_time;
}
/** Sets the automatic hangup time (default is 0, that corresponds to manual hangup mode) */
public void setHangupTime(int time)
{
user_profile.hangup_time = time;
}
/** Sets the redirection url (default is null, that is no redircetion) */
public void setRedirection(String url)
{
user_profile.redirect_to = url;
}
/** Sets the no offer mode for the invite (default is false) */
public void setNoOfferMode(boolean nooffer)
{
user_profile.no_offer = nooffer;
}
/** Enables audio */
public void setAudio(boolean enable)
{
user_profile.audio = enable;
}
/** Enables video */
public void setVideo(boolean enable)
{
user_profile.video = enable;
}
/** Sets the receive only mode */
public void setReceiveOnlyMode(boolean r_only)
{
user_profile.recv_only = r_only;
}
/** Sets the send only mode */
public void setSendOnlyMode(boolean s_only)
{
user_profile.send_only = s_only;
}
/** Sets the send tone mode */
public void setSendToneMode(boolean s_tone)
{
user_profile.send_tone = s_tone;
}
/** Sets the send file */
public void setSendFile(String file_name)
{
user_profile.send_file = file_name;
}
/** Sets the recv file */
public void setRecvFile(String file_name)
{
user_profile.recv_file = file_name;
}
/** Gets the local SDP */
public String getSessionDescriptor()
{
debugjs.paranoia("UserAgent.getSessioDescriptor(): local_session="+local_session);
return local_session;
}
/** Sets the local SDP */
/*
public void setSessionDescriptor(String sdp)
{
local_session = sdp;
}
*/
/** Inits the local SDP (no media spec) */
public void initSessionDescriptor()
{
SessionDescriptor sdp = new SessionDescriptor(user_profile.from_url, sip_provider.getViaAddress());
local_session = sdp.toString();
debugjs.paranoia("UserAgent.initSessioDescriptor(): local_session="+local_session);
}
/** Adds a media to the SDP */
// public void addMediaDescriptor(String media, int port, int avp, String codec, int rate)
// {
// if(local_session == null)
// initSessionDescriptor();
// SessionDescriptor sdp = new SessionDescriptor(local_session);
// String attr_param = String.valueOf(avp);
// if(codec != null)
// attr_param += " " + codec + "/" + rate;
// sdp.addMedia(new MediaField(media, port, 0, "RTP/AVP", String.valueOf(avp)), new AttributeField("rtpmap", attr_param));
// local_session = sdp.toString();
// debugjs.paranoia("UserAgent.addMediaDescriptor(): local_session="+local_session);
// }
// *************************** Public Methods **************************
/** Costructs a UA with a default media port */
public UserAgent(SipProvider sip_provider, UserAgentProfile user_profile, UserAgentListener listener, URL codeBase)
{
this.sip_provider = sip_provider;
log = sip_provider.getLog();
this.listener = listener;
this.user_profile = user_profile;
// if no contact_url and/or from_url has been set, create it now
user_profile.initContactAddress(sip_provider);
// load sounds
// ################# patch to make audio working with javax.sound.. #################
// currently AudioSender must be started before any AudioClipPlayer is initialized,
// since there is a problem with the definition of the audio format
if(!user_profile.use_rat && !user_profile.use_jmf)
{
//if(user_profile.audio && !user_profile.recv_only && user_profile.send_file == null && !user_profile.send_tone)
//local.media.AudioInput.initAudioLine();
//if(user_profile.audio && !user_profile.send_only && user_profile.recv_file == null)
//local.media.AudioOutput.initAudioLine();
}
// ################# patch to make rat working.. #################
// in case of rat, do not load and play audio clips
if(!user_profile.use_rat)
{
try
{
//String jar_file=user_profile.ua_jar;
//if(listener instanceof Applet)
if(true) //patched to work with modular structures, where listener is not an applet
{
/*
if (CLIP_ON!=null)clip_on = new AudioClipPlayer(Archive.getAudioInputStream(new URL(codeBase, CLIP_ON)), null);
debugjs.debug("clip_on="+clip_on);
if (CLIP_OFF!=null)clip_off = new AudioClipPlayer(Archive.getAudioInputStream(new URL(codeBase, CLIP_OFF)), null);
debugjs.debug("clip_off="+clip_off);
if (CLIP_RING!=null)clip_ring = new AudioClipPlayer(Archive.getAudioInputStream(new URL(codeBase, CLIP_RING)), null);
debugjs.debug("clAip_ring="+clip_ring);
*/
is_clip_on = getClass().getResourceAsStream(AUDIO_PATH + CLIP_ON);
is_clip_off = getClass().getResourceAsStream(AUDIO_PATH + CLIP_OFF);
is_clip_ring = getClass().getResourceAsStream(AUDIO_PATH + CLIP_RING);
if(CLIP_ON != null)clip_on = new AudioClipPlayer(is_clip_on, null);
debugjs.debug("clip_on="+clip_on);
if (CLIP_OFF!=null)clip_off = new AudioClipPlayer(is_clip_off, null);
debugjs.debug("clip_off="+clip_off);
if (CLIP_RING!=null)clip_ring = new AudioClipPlayer(is_clip_ring, null);
debugjs.debug("clAip_ring="+clip_ring);
}
}
catch (Exception e)
{
debugjs.error("Error occured while loading audio files");
//printException(e, LogLevel.HIGH);
e.printStackTrace();
}
}
// set local sdp
initSessionDescriptor();
if(user_profile.audio || !user_profile.video)
{
//addMediaDescriptor("audio", user_profile.audio_port, user_profile.audio_avp, user_profile.audio_codec, user_profile.audio_sample_rate);
//addMediaDescriptor("audio", 21000, 8, "PCMA", 8000);
debugjs.paranoia("UserAgent.UserAgent(): local_session="+local_session);
SessionDescriptor sdp = new SessionDescriptor(local_session);
AudioCodecConfiguration acc = new AudioCodecConfiguration();
String audioSdp = acc.createSdpAudioAttributes();
if (audioSdp!=null) local_session=sdp.toString()+audioSdp;
else local_session=sdp.toString();
}
//if(user_profile.video)
//addMediaDescriptor("video", user_profile.video_port, user_profile.video_avp, null, 0);
// // video is not supported
}
/** Creates a new session descriptor */
/*private void newSession(int media_port)
{ SessionDescriptor local_sdp=new SessionDescriptor(user_profile.from_url,sip_provider.getAddress());
int audio_port=media_port;
int video_port=media_port+2;
//PATCH [040902] if (audio || !video) local_sdp.addMedia(new MediaField("audio",audio_port,0,"RTP/AVP","0"),new AttributeField("rtpmap","0 PCMU/8000"));
//PATCH [040902] if (video || !(audio || video)) local_sdp.addMedia(new MediaField("video",video_port,0,"RTP/AVP","7"),new AttributeField("rtpmap","17"));
local_sdp.addMedia(new MediaField("audio",audio_port,0,"RTP/AVP","0"),new AttributeField("rtpmap","0 PCMU/8000"));
local_session=local_sdp.toString();
}*/
/** Makes a new call (acting as UAC). */
public void call(String target_url)
{
changeStatus(UA_OUTGOING_CALL);
initSessionDescriptor();
initMedia();
call = new ExtendedCall(sip_provider, user_profile.from_url, user_profile.contact_url, user_profile.username, user_profile.realm, user_profile.passwd, this);
// in case of incomplete url (e.g. only 'user' is present), try to complete it
target_url = sip_provider.completeNameAddress(target_url).toString();
//if (clip_on!=null) clip_on.loop();
if(user_profile.no_offer)
{
debugjs.debug("user_profile.no_offer");
if(tunnel)
preOpenSocketForAudioTunneling();
call.call(target_url);
}
else
{
debugjs.debug("!user_profile.no_offer");
if(tunnel)
preOpenSocketForAudioTunneling();
call.call(target_url, local_session);
}
}
/** Waits for an incoming call (acting as UAS). */
public void listen()
{
sip_provider.removeSipProviderListener(new TransactionIdentifier(SipMethods.INVITE)); // try to remove old INVITE listener if any.
debugjs.debug("UA is listening");
changeStatus(UA_IDLE);
call = new ExtendedCall(sip_provider, user_profile.from_url, user_profile.contact_url, user_profile.username, user_profile.realm, user_profile.passwd, this);
debugjs.paranoia("Listen|call="+call);
call.listen();
}
/** Closes an ongoing, incoming, or pending call */
public void hangup()
{
debugjs.warning("hangup() fct called ");
if(clip_off != null)
if (!call_state.equals(UA_IDLE) && !call_state.equals(UA_INCOMING_CALL)) clip_off.loop(); // was clip_off.stop();
else debugjs.warning("clip_off==null");
if(clip_ring != null)
{
clip_ring.stop();
}
//audio_app:lle pitää tehdä jotain.
closeAudioSocket();
if(call != null)
{
if(clip_on != null)
clip_on.stop();
else debugjs.warning("clip_on==null");
call.hangup();
}
changeStatus(UA_IDLE);
}
/** Closes an ongoing, incoming, or pending call */
public void accept()
{
if(clip_ring != null)
clip_ring.stop();
if(call != null)
call.accept(local_session);
}
/** Redirects an incoming call */
public void redirect(String redirection)
{
if(clip_ring != null)
clip_ring.stop();
if(call != null)
call.redirect(redirection);
}
/** Launches the Media Application (currently, the RAT audio tool) */
protected void launchMediaApplication(boolean incoming)
{
debugjs.debug("UserAgent.launchMediaApplication");
// exit if the Media Application is already running
if(audio_app != null || video_app != null)
{
debugjs.warning("DEBUG: media application is already running");
debugjs.debug("Stopping media application...");
if (audio_app!=null)
{
audio_app.stopMedia();
audio_app=null;
}
if (video_app!=null)
{
video_app.stopMedia();
video_app=null;
}
}
debugjs.debug("audio_app="+audio_app);
debugjs.paranoia("call="+call);
String localSdp=call.getLocalSessionDescriptor();
debugjs.paranoia("localSdp="+localSdp);
SessionDescriptor local_sdp = new SessionDescriptor(localSdp);
debugjs.paranoia("local_sdp="+local_sdp);
String local_media_address = (new Parser(local_sdp.getConnection().toString())).skipString().skipString().getString();
int local_audio_port = 0;
int local_video_port = 0;
int payloadType=-1;
// parse local sdp
debugjs.paranoia("UserAgent.launcMediaApplication: local sdp="+local_sdp.toString());
for (Enumeration e = local_sdp.getMediaDescriptors().elements(); e.hasMoreElements();)
{
MediaField media = ((MediaDescriptor) e.nextElement()).getMedia();
if(media.getMedia().equals("audio"))
if (incoming) payloadType=Integer.parseInt(media.getFormatList().elementAt(0).toString());
local_audio_port = media.getPort();
if(media.getMedia().equals("video"))
local_video_port = media.getPort();
}
// parse remote sdp
SessionDescriptor remote_sdp = new SessionDescriptor(call.getRemoteSessionDescriptor());
String remote_media_address = (new Parser(remote_sdp.getConnection().toString())).skipString().skipString().getString();
int remote_audio_port = 0;
int remote_video_port = 0;
debugjs.hysteria("UserAgent.launcMediaApplication: remote sdp="+remote_sdp.toString());
for (Enumeration e = remote_sdp.getMediaDescriptors().elements(); e.hasMoreElements();)
{
MediaField media = ((MediaDescriptor) e.nextElement()).getMedia();
if(media.getMedia().equals("audio"))
{
remote_audio_port = media.getPort();
if (!incoming) payloadType=Integer.parseInt(media.getFormatList().elementAt(0).toString());
debugjs.debug("parsed media format="+payloadType);
}
if(media.getMedia().equals("video"))
remote_video_port = media.getPort();
}
// select the media direction (send_only, recv_ony, fullduplex)
int dir = 0;
if(user_profile.recv_only)
dir = -1;
else if(user_profile.send_only)
dir = 1;
if(user_profile.audio && local_audio_port != 0 && remote_audio_port != 0)
{ // create an audio_app and start it
/* if (user_profile.use_rat)
{ audio_app=new RATLauncher(user_profile.bin_rat,local_audio_port,remote_media_address,remote_audio_port,log);
}
else
if (user_profile.use_jmf)
{ // try to use JMF audio app
try
{ Class myclass=Class.forName("local.ua.JMFAudioLauncher");
Class[] parameter_types={ java.lang.Integer.TYPE, Class.forName("java.lang.String"), java.lang.Integer.TYPE, java.lang.Integer.TYPE, Class.forName("org.zoolu.tools.Log") };
Object[] parameters={ new Integer(local_audio_port), remote_media_address, new Integer(remote_audio_port), new Integer(dir), log };
java.lang.reflect.Constructor constructor=myclass.getConstructor(parameter_types);
audio_app=(MediaLauncher)constructor.newInstance(parameters);
}
catch (Exception e)
{ printException(e,LogLevel.HIGH);
printLog("Error trying to create the JMFAudioLauncher",LogLevel.HIGH);
}
}
// else
*/
if(audio_app == null)
{ // for testing..
String audio_in = null;
if(user_profile.send_tone)
audio_in = JAudioLauncher.TONE;
else if(user_profile.send_file != null)
audio_in = user_profile.send_file;
String audio_out = null;
if(user_profile.recv_file != null) {
audio_out = user_profile.recv_file;
//audio_app=new JAudioLauncher(local_audio_port,remote_media_address,remote_audio_port,dir,log);
debugjs.debug("Audio_out=" + audio_out);
}
else debugjs.debug("Audio_out=Speakers");
if(tunnel)
{
debugjs.info("New tunneledaudiolauncher");
//audio_app = new TunneledAudioLauncher(local_audio_port, remote_media_address, remote_audio_port, dir, audio_in, audio_out, user_profile.audio_sample_rate, user_profile.audio_sample_size, user_profile.audio_frame_size, log, audiochannel, this);
audio_app = new TunneledAudioLauncher(local_audio_port, remote_media_address, remote_audio_port, dir, audio_in, audio_out, payloadType, 8000, 1, 160, log, audiochannel, this);
}
else
//audio_app = new JAudioLauncher(local_audio_port, remote_media_address, remote_audio_port, dir, audio_in, audio_out, user_profile.audio_sample_rate, user_profile.audio_sample_size, user_profile.audio_frame_size, log);
debugjs.info("New Audiolauncher");
audio_app = new JAudioLauncher(local_audio_port, remote_media_address, remote_audio_port, dir, audio_in, audio_out, payloadType, 8000, 1, 160, log);
}
audio_app.startMedia();
}
/* if (user_profile.video && local_video_port!=0 && remote_video_port!=0)
{ // create a video_app and start it
if (user_profile.use_vic)
{ video_app=new VICLauncher(user_profile.bin_vic,local_video_port,remote_media_address,remote_video_port,log);
}
else
if (user_profile.use_jmf)
{ // try to use JMF video app
try
{ Class myclass=Class.forName("local.ua.JMFVideoLauncher");
Class[] parameter_types={ java.lang.Integer.TYPE, Class.forName("java.lang.String"), java.lang.Integer.TYPE, java.lang.Integer.TYPE, Class.forName("org.zoolu.tools.Log") };
Object[] parameters={ new Integer(local_video_port), remote_media_address, new Integer(remote_video_port), new Integer(dir), log };
java.lang.reflect.Constructor constructor=myclass.getConstructor(parameter_types);
video_app=(MediaLauncher)constructor.newInstance(parameters);
}
catch (Exception e)
{ printException(e,LogLevel.HIGH);
printLog("Error trying to create the JMFVideoLauncher",LogLevel.HIGH);
}
}
// else
if (video_app==null)
{ printLog("No external video application nor JMF has been provided: Video not started",LogLevel.HIGH);
return;
}
video_app.startMedia();
}
*/
}
/** Close the Media Application */
protected void closeMediaApplication()
{
if(audio_app != null)
{
audio_app.stopMedia();
audio_app = null;
}
if(video_app != null)
{
video_app.stopMedia();
video_app = null;
}
}
// ********************** Call callback functions **********************
/** Callback function called when arriving a new INVITE method (incoming call) */
public void onCallIncoming(Call call, NameAddress callee, NameAddress caller, String sdp, Message invite)
{
debugjs.paranoia("UserAgent.onCallIncoming");
if(call != this.call)
{
printLog("NOT the current call", LogLevel.LOW);
debugjs.paranoia("NOT the current call");
debugjs.paranoia(" incoming="+call);
debugjs.paranoia(" current="+this.call);
return;
}
printLog("INCOMING", LogLevel.HIGH);
changeStatus(UA_INCOMING_CALL);
debugjs.paranoia(" invoking call.ring()");
call.ring();
if(sdp != null)
{ // Create the new SDP
SessionDescriptor remote_sdp = new SessionDescriptor(sdp);
SessionDescriptor local_sdp = new SessionDescriptor(local_session);
SessionDescriptor new_sdp = new SessionDescriptor(remote_sdp.getOrigin(), remote_sdp.getSessionName(), local_sdp.getConnection(), local_sdp.getTime());
// System.out.println("UserAgent.onIncomingCall");
/*
new_sdp.addMediaDescriptors(local_sdp.getMediaDescriptors());
new_sdp = SdpTools.sdpMediaProduct(new_sdp, remote_sdp.getMediaDescriptors());
new_sdp = SdpTools.sdpAttirbuteSelection(new_sdp, "rtpmap");
local_session = new_sdp.toString();
*/
local_session=new_sdp.toString()+new AudioCodecConfiguration().createSdpAudioAttributes(remote_sdp);
debugjs.paranoia("UserAgent.onCallIncoming(): local_session="+local_session);
}
// play "ring" sound
if(clip_ring != null)
clip_ring.loop();
if(listener != null)
{
listener.onUaCallIncoming(this, callee, caller);
debugjs.paranoia(" listener="+listener);
}
else debugjs.paranoia(" listener=NULL!");
}
/** Callback function called when arriving a new Re-INVITE method (re-inviting/call modify) */
public void onCallModifying(Call call, String sdp, Message invite)
{
debugjs.paranoia("UserAgent.onCallModifying");
if(call != this.call)
{
printLog("NOT the current call", LogLevel.LOW);
return;
}
printLog("RE-INVITE/MODIFY", LogLevel.HIGH);
// 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)
{
debugjs.paranoia("UserAgent.onCallRinging");
if(call != this.call && call != call_transfer)
{
printLog("NOT the current call", LogLevel.LOW);
return;
}
//printLog("RINGING",LogLevel.HIGH);
// play "on" sound
//if (clip_on!=null) clip_on.replay();
//if (clip_on!=null) clip_on.loop();
if(listener != null)
listener.onUaCallRinging(this);
}
/** Callback function called when arriving a 2xx (call accepted) */
public void onCallAccepted(Call call, String sdp, Message resp)
{
debugjs.paranoia("UserAgent.onCallAccepted: sdp="+sdp);
printLog("onCallAccepted()", LogLevel.LOW);
if(call != this.call && call != call_transfer)
{
printLog("NOT the current call", LogLevel.LOW);
return;
}
//printLog("ACCEPTED/CALL",LogLevel.HIGH);
changeStatus(UA_ONCALL);
if(user_profile.no_offer)
{ // Create the new SDP
debugjs.debug("UserAgent.onCallAccepted: user:profile.no_offer");
SessionDescriptor remote_sdp = new SessionDescriptor(sdp);
SessionDescriptor local_sdp = new SessionDescriptor(local_session);
SessionDescriptor new_sdp = new SessionDescriptor(remote_sdp.getOrigin(), remote_sdp.getSessionName(), local_sdp.getConnection(), local_sdp.getTime());
new_sdp.addMediaDescriptors(local_sdp.getMediaDescriptors());
new_sdp = SdpTools.sdpMediaProduct(new_sdp, remote_sdp.getMediaDescriptors());
new_sdp = SdpTools.sdpAttirbuteSelection(new_sdp, "rtpmap");
// update the local SDP
local_session = new_sdp.toString();
// answer with the local sdp
call.ackWithAnswer(local_session);
}
// play "on" sound
if(clip_on != null)
clip_on.stop();
else debugjs.warning("clip_on==null");
if(listener != null)
listener.onUaCallAccepted(this);
launchMediaApplication(false);
if(call == call_transfer)
{
StatusLine status_line = resp.getStatusLine();
int code = status_line.getCode();
String reason = status_line.getReason();
this.call.notify(code, reason);
}
}
/** Callback function called when arriving an ACK method (call confirmed) */
public void onCallConfirmed(Call call, String sdp, Message ack)
{
debugjs.paranoia("onCallConfirmed()");
debugjs.paranoia("sdp="+sdp);
debugjs.paranoia("local_session="+local_session);
if(call != this.call)
{
printLog("NOT the current call", LogLevel.LOW);
return;
}
printLog("CONFIRMED/CALL", LogLevel.HIGH);
changeStatus(UA_ONCALL);
// don't play "on" sound
/*
if(clip_on != null)
{
debugjs.debug("Starting clip_on");
clip_on.replay();
}
else debugjs.warning("clip_on==null");
*/
launchMediaApplication(true);
if(user_profile.hangup_time > 0)
this.automaticHangup(user_profile.hangup_time);
}
/** Callback function called when arriving a 2xx (re-invite/modify accepted) */
public void onCallReInviteAccepted(Call call, String sdp, Message resp)
{
debugjs.paranoia("UserAgent.onCallReInviteAccepted");
if(call != this.call)
{
printLog("NOT the current call", LogLevel.LOW);
return;
}
printLog("RE-INVITE-ACCEPTED/CALL", LogLevel.HIGH);
}
/** Callback function called when arriving a 4xx (re-invite/modify failure) */
public void onCallReInviteRefused(Call call, String reason, Message resp)
{
debugjs.paranoia("UserAgent.onCallReInviteRefused");
if(call != this.call)
{
printLog("NOT the current call", LogLevel.LOW);
return;
}
printLog("RE-INVITE-REFUSED (" + reason + ")/CALL", LogLevel.HIGH);
if(listener != null)
listener.onUaCallFailed(this);
}
/** Callback function called when arriving a 4xx (call failure) */
public void onCallRefused(Call call, String reason, Message resp)
{
debugjs.paranoia("UserAgent.onCallRefused");
lastResponseCode = resp.getStatusLine().getCode();
if(call != this.call)
{
printLog("NOT the current call", LogLevel.LOW);
return;
}
printLog("REFUSED (" + reason + ")", LogLevel.HIGH);
changeStatus(UA_IDLE);
if(call == call_transfer)
{
StatusLine status_line = resp.getStatusLine();
int code = status_line.getCode();
//String reason=status_line.getReason();
this.call.notify(code, reason);
call_transfer = null;
}
if(clip_on != null)
clip_on.stop();
else debugjs.warning("clip_on==null");
// play "off" sound
if(clip_off != null)
{
if(lastResponseCode == 486 || lastResponseCode == 487 && callInProgress)
clip_off.loop();
}
else debugjs.warning("clip_off==null");
if(listener != null)
listener.onUaCallFailed(this);
}
/** Callback function called when arriving a 3xx (call redirection) */
public void onCallRedirection(Call call, String reason, Vector contact_list, Message resp)
{
debugjs.paranoia("UserAgent.onCallRedirection");
if(call != this.call)
{
printLog("NOT the current call", LogLevel.LOW);
return;
}
printLog("REDIRECTION (" + reason + ")", LogLevel.HIGH);
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)
{
debugjs.paranoia("UserAgent.onCallCanceling");
if(call != this.call)
{
printLog("NOT the current call", LogLevel.LOW);
return;
}
printLog("CANCEL", LogLevel.HIGH);
changeStatus(UA_IDLE);
// stop ringing
if(clip_ring != null)
clip_ring.stop();
else debugjs.warning("clip_ring==null");
// play "off" sound
if(clip_off != null)
clip_off.loop();
else debugjs.warning("clip_off==null");
if(listener != null)
listener.onUaCallCancelled(this);
}
/** Callback function called when arriving a BYE request */
public void onCallClosing(Call call, Message bye)
{
debugjs.paranoia("UserAgent.onCallClosing");
if(call != this.call && call != call_transfer)
{
printLog("NOT the current call", LogLevel.LOW);
return;
}
if(call != call_transfer && call_transfer != null)
{
printLog("CLOSE PREVIOUS CALL", LogLevel.HIGH);
this.call = call_transfer;
call_transfer = null;
return;
}
// else
printLog("CLOSE", LogLevel.HIGH);
closeMediaApplication();
// play "off" sound
if(clip_off != null)
{
if (!callInProgress) debugjs.error("!! Call is not in progress !!");
clip_off.loop();
}
else debugjs.warning("clip_off==null");
if(listener != null)
listener.onUaCallClosed(this);
changeStatus(UA_IDLE);
}
/** Callback function called when arriving a response after a BYE request (call closed) */
public void onCallClosed(Call call, Message resp)
{
debugjs.paranoia("UserAgent.onCallClosed");
if(call != this.call)
{
printLog("NOT the current call", LogLevel.LOW);
return;
}
printLog("CLOSE/OK", LogLevel.HIGH);
if(listener != null)
listener.onUaCallClosed(this);
changeStatus(UA_IDLE);
}
/** Callback function called when the invite expires */
public void onCallTimeout(Call call)
{
debugjs.paranoia("UserAgent.onCallTimeout");
lastResponseCode = 408;
if(call != this.call)
{
printLog("NOT the current call", LogLevel.LOW);
return;
}
printLog("NOT FOUND/TIMEOUT", LogLevel.HIGH);
changeStatus(UA_IDLE);
if(call == call_transfer)
{
int code = 408;
String reason = "Request Timeout";
this.call.notify(code, reason);
call_transfer = null;
}
if(clip_on != null)
clip_on.stop();
else debugjs.warning("clip_on==null");
// play "off" sound
if(clip_off != null)
clip_off.replay();
else debugjs.warning("clip_off==null");
if(listener != null)
listener.onUaCallFailed(this);
}
// ****************** 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)
{
debugjs.paranoia("UserAgent.onCallTransfer: local_session="+local_session);
if(call != this.call)
{
printLog("NOT the current call", LogLevel.LOW);
return;
}
// reset sdp
initSessionDescriptor();
initMedia();
printLog("Transfer to " + refer_to.toString(), LogLevel.HIGH);
call.acceptTransfer();
call_transfer = new ExtendedCall(sip_provider, user_profile.from_url, user_profile.contact_url, this);
call_transfer.call(refer_to.toString(), local_session);
}
protected void initMedia() {
if(user_profile.audio || !user_profile.video)
{
debugjs.paranoia("UserAgent.initMedia(): local_session="+local_session);
SessionDescriptor sdp = new SessionDescriptor(local_session);
AudioCodecConfiguration acc = new AudioCodecConfiguration();
String audioSdp = acc.createSdpAudioAttributes();
if (audioSdp!=null) local_session=sdp.toString()+audioSdp;
else local_session=sdp.toString();
}
}
/** Callback function called when a call transfer is accepted. */
public void onCallTransferAccepted(ExtendedCall call, Message resp)
{
debugjs.paranoia("UserAgent.onCallTransferAccepted");
if(call != this.call)
{
printLog("NOT the current call", LogLevel.LOW);
return;
}
printLog("Transfer accepted", LogLevel.HIGH);
}
/** Callback function called when a call transfer is refused. */
public void onCallTransferRefused(ExtendedCall call, String reason, Message resp)
{
debugjs.paranoia("UserAgent.onCallTransferRefused");
if(call != this.call)
{
printLog("NOT the current call", LogLevel.LOW);
return;
}
printLog("Transfer refused", LogLevel.HIGH);
}
/** Callback function called when a call transfer is successfully completed */
public void onCallTransferSuccess(ExtendedCall call, Message notify)
{
debugjs.paranoia("UserAgent.onCallTransferSuccess");
if(call != this.call)
{
printLog("NOT the current call", LogLevel.LOW);
return;
}
printLog("Transfer successed", LogLevel.HIGH);
call.hangup();
if(listener != null)
listener.onUaCallTrasferred(this);
}
/** Callback function called when a call transfer is NOT sucessfully completed */
public void onCallTransferFailure(ExtendedCall call, String reason, Message notify)
{
debugjs.paranoia("UserAgent.onCallTransferFailure");
if(call != this.call)
{
printLog("NOT the current call", LogLevel.LOW);
return;
}
printLog("Transfer failed", LogLevel.HIGH);
}
// ************************* Schedule events ***********************
/** Schedules a re-inviting event after <i>delay_time</i> secs. */
void reInvite(final String contact_url, final int delay_time)
{
SessionDescriptor sdp = new SessionDescriptor(local_session);
final SessionDescriptor new_sdp = new SessionDescriptor(sdp.getOrigin(), sdp.getSessionName(), new ConnectionField("IP4", "0.0.0.0"), new TimeField());
new_sdp.addMediaDescriptors(sdp.getMediaDescriptors());
(new Thread()
{
public void run()
{
runReInvite(contact_url, new_sdp.toString(), delay_time);
}
}).start();
}
/** Re-invite. */
private void runReInvite(String contact, String body, int delay_time)
{
try
{
if(delay_time > 0)
Thread.sleep(delay_time * 1000);
printLog("RE-INVITING/MODIFING");
if(call != null && call.isOnCall())
{
printLog("REFER/TRANSFER");
call.modify(contact, body);
}
}
catch (Exception e)
{
e.printStackTrace();
}
}
/** Schedules a call-transfer event after <i>delay_time</i> secs. */
void callTransfer(final String transfer_to, final int delay_time)
{
(new Thread()
{
public void run()
{
runCallTransfer(transfer_to, delay_time);
}
}).start();
}
/** Call-transfer. */
private void runCallTransfer(String transfer_to, int delay_time)
{
try
{
if(delay_time > 0)
Thread.sleep(delay_time * 1000);
if(call != null && call.isOnCall())
{
printLog("REFER/TRANSFER");
call.transfer(transfer_to);
}
}
catch (Exception e)
{
e.printStackTrace();
}
}
/** Schedules an automatic answer event after <i>delay_time</i> secs. */
void automaticAccept(final int delay_time)
{
(new Thread()
{
public void run()
{
runAutomaticAccept(delay_time);
}
}).start();
}
/** Automatic answer. */
private void runAutomaticAccept(int delay_time)
{
try
{
if(delay_time > 0)
Thread.sleep(delay_time * 1000);
if(call != null)
{
printLog("AUTOMATIC-ANSWER");
accept();
}
}
catch (Exception e)
{
e.printStackTrace();
}
}
/** Schedules an automatic hangup event after <i>delay_time</i> secs. */
void automaticHangup(final int delay_time)
{
(new Thread()
{
public void run()
{
runAutomaticHangup(delay_time);
}
}).start();
}
/** Automatic hangup. */
private void runAutomaticHangup(int delay_time)
{
try
{
if(delay_time > 0)
Thread.sleep(delay_time * 1000);
if(call != null && call.isOnCall())
{
printLog("AUTOMATIC-HANGUP");
hangup();
listen();
}
}
catch (Exception e)
{
e.printStackTrace();
}
}
// ****************************** Logs *****************************
/** Adds a new string to the default Log */
void printLog(String str)
{
printLog(str, LogLevel.HIGH);
}
/** Adds a new string to the default Log */
void printLog(String str, int level)
{
if(log != null)
log.println("UA: " + str, level + SipStack.LOG_LEVEL_UA);
}
/** Adds the Exception message to the default Log */
void printException(Exception e, int level)
{
if(log != null)
log.printException(e, level + SipStack.LOG_LEVEL_UA);
}
/*
public void setCodeBase(URL u){
codeBase=u;
}
*/
public void onCallProvisionalResponse(Call call, int code)
{
debugjs.paranoia("UserAgent.onCallProvisionalResponse");
listener.onUaCallProvisionalResponse(code, this);
// TODO Auto-generated method stub
}
public void preOpenSocketForAudioTunneling()
{
debugjs.paranoia("UserAgent.onCallOpenSocketForAudioTunneling");
try
{
debugjs.info("Preparing connection to http tunnel");
//Config c=new Config();
//audiosocket = new Socket(InetAddress.getByName(user_profile.tunnelServer), user_profile.tunnelPort);
audiochannel = SocketChannel.open();
//audiochannel = SocketChannel.open(new InetSocketAddress(user_profile.tunnelServer, user_profile.tunnelPort));
audiochannel.configureBlocking(false);
audiochannel.connect(new InetSocketAddress(user_profile.tunnelServer, user_profile.tunnelPort));
while (!audiochannel.finishConnect()) {
// Do nothing
}
//audiosocket.setSendBufferSize(1);
// audiosocket.setTcpNoDelay(false);
//audiosocket.setPerformancePreferences(0, 3, 2);
// debugjs.debug("socket(audiosocket) tcpnodelay="+audiosocket.getTcpNoDelay());
}
catch (UnknownHostException e1)
{
// TODO Auto-generated catch block
e1.printStackTrace();
}
catch (IOException e1)
{
// TODO Auto-generated catch block
e1.printStackTrace();
}
}
public void onTunnelTerminated()
{
return;
// hangup();
// debugjs.warning(clip_off.toString());
// if(clip_off != null)
// {
// clip_off.loop();
// }
// else debugjs.warning("clip_off==null");
// //closeMediaApplication();
// listener.onUaCallClosed(this);
}
public void closeAudioSocket()
{
debugjs.paranoia("closeAudioSocket() called");
if(audiochannel != null)
{
try
{
debugjs.info("Trying to close audiosocket");
audiochannel.close();
}
catch (IOException e)
{
e.printStackTrace();
}
}
else
debugjs.info("Audiosocket was already closed");
closeMediaApplication();
}
void generateTone(int i, int duration)
{
// System.out.println("UserAgent.generateTone("+i+","+duration+")");
if (audio_app instanceof JAudioLauncher)
{
// System.out.println("JAudioLauncher=audio_app");
JAudioLauncher jau = (JAudioLauncher)audio_app;
jau.walker(i,duration);
}
else if (audio_app instanceof TunneledAudioLauncher)
{
// System.out.println("TunneledAudioLauncher=audio_app");
TunneledAudioLauncher tau = (TunneledAudioLauncher)audio_app;
tau.walker(i,duration);
}
else {
//System.out.println("audio_app ei tue dtmf:ää");
debugjs.info("Audio_app is not an instance of JaudioLauncher/TunneledAudioLauncher");
if (audio_app == null) {
debugjs.info("audio_app is NULL");
}
}
// System.out.println("generatetone loppuu");
}
}