Adds offer processing for session update and re-enables hangup.

cusax-fix
Emil Ivov 17 years ago
parent 0a174c8bb3
commit 6d0973cba7

@ -80,7 +80,7 @@ public class CallPeerMediaHandler
*/
private static int nextMediaPortToTry = minMediaPort;
private boolean onHold = false;
private boolean locallyOnHold = false;
/**
* The RTP/RTCP socket couple that this media handler should use to send
@ -197,25 +197,26 @@ public boolean isMute()
}
/**
* Specifies that this handler's <tt>MediaStream</tt>s should be on hold
* so that this would be taken into account and the streams put in
* sendonly or inactive mode when the next update offer is generated.
* Note that the stream has no immediate effect and would only affect
* the flows after an update description is regenerated.
* Puts all <tt>MediaStream</tt>s in this handler locally on or off hold
* (according to the value of <tt>locallyOnHold</tt>). This would also be
* taken into account when the next update offer is generated.
*
* @param onHold <tt>true</tt> if we are to make our audio stream start
* transmitting silence and <tt>false</tt> if we are to end the transmission
* of silence and use our stream's <tt>MediaDevice</tt> again.
* @param locallyOnHold <tt>true</tt> if we are to make our audio stream
* stop transmitting and <tt>false</tt> if we are to start transmitting
* again.
*/
public void setOnHold(boolean onHold)
public void setLocallyOnHold(boolean locallyOnHold)
{
this.onHold = onHold;
this.locallyOnHold = locallyOnHold;
if(onHold)
if(locallyOnHold)
{
if(audioStream != null)
{
audioStream.setDirection(audioStream.getDirection()
.and(MediaDirection.SENDONLY));
audioStream.setMute(true);
}
if(videoStream != null)
videoStream.setDirection(videoStream.getDirection()
.and(MediaDirection.SENDONLY));
@ -223,25 +224,67 @@ public void setOnHold(boolean onHold)
else
{
if(audioStream != null)
{
audioStream.setDirection(audioStream.getDirection()
.or(MediaDirection.SENDONLY));
audioStream.setMute(false);
}
if(videoStream != null)
{
videoStream.setDirection(videoStream.getDirection()
.or(MediaDirection.SENDONLY));
//videoStream.setMute(true);
}
}
}
/**
* Closes and null-ifies all streams and connectors and readies this media
* handler for garbage collection (or reuse).
*/
public void close()
{
if (this.audioStream != null)
{
audioStream.close();
audioStream = null;
}
if (this.videoStream != null)
{
videoStream.close();
videoStream = null;
}
if (this.audioStreamConnector != null)
{
audioStreamConnector.getDataSocket().close();
audioStreamConnector.getControlSocket().close();
audioStreamConnector = null;
}
if (this.videoStreamConnector != null)
{
videoStreamConnector.getDataSocket().close();
videoStreamConnector.getControlSocket().close();
videoStreamConnector = null;
}
locallyOnHold = false;
}
/**
* Determines whether this handler's streams should be placed on hold during
* the next session update.
* Determines whether this handler's streams have been placed on hold.
*
* @return <tt>true</tt> if this handler's streams should be placed on hold
* next time we update this session and false otherwise.
* @return <tt>true</tt> if this handler's streams have been placed on hold
* and <tt>false</tt> otherwise.
*/
public boolean isOnHold()
public boolean isLocallyOnHold()
{
return onHold;
return locallyOnHold;
//no need to actually check stream directions because we only update
//them through the setLocallyOnHold() method so if the value of the
//locallyOnHold field has changed, so have stream directions.
}
/**
@ -312,7 +355,7 @@ private Vector<MediaDescription> createMediaDescriptions()
MediaDirection audioDirection
= aDev.getDirection().and(audioDirectionUserPreference);
if(onHold)
if(locallyOnHold)
audioDirection = audioDirection.and(MediaDirection.SENDONLY);
if(audioDirection != MediaDirection.INACTIVE);
@ -331,7 +374,7 @@ private Vector<MediaDescription> createMediaDescriptions()
MediaDirection videoDirection
= vDev.getDirection().and(videoDirectionUserPreference);
if(onHold)
if(locallyOnHold)
videoDirection = videoDirection.and(MediaDirection.SENDONLY);
if(videoDirection != MediaDirection.INACTIVE);
@ -456,26 +499,72 @@ private void registerDynamicPTsWithStream(MediaStream stream)
}
}
public String processOffer(String offerString)
throws OperationFailedException
{
SessionDescription offer = SdpUtils.parseSdpString(offerString);
if (localSess == null)
return processFirstOffer(offer).toString();
else
return processUpdateOffer(offer, localSess).toString();
}
public SessionDescription processFirstOffer(SessionDescription offer)
throws OperationFailedException,
IllegalArgumentException
{
this.remoteSess = offer;
Vector<MediaDescription> remoteDescriptions
= SdpUtils.extractMediaDescriptions(offer);
Vector<MediaDescription> answerDescriptions
= createMediaDescriptionsForAnswer(offer);
//wrap everything up in a session description
SessionDescription answer = SdpUtils.createSessionDescription(
getLastUsedLocalHost(), getUserName(), answerDescriptions);
this.localSess = answer;
return localSess;
}
public SessionDescription processUpdateOffer(
SessionDescription newOffer,
SessionDescription previousAnswer)
throws OperationFailedException,
IllegalArgumentException
{
this.remoteSess = newOffer;
Vector<MediaDescription> answerDescriptions
= createMediaDescriptionsForAnswer(newOffer);
// wrap everything up in a session description
SessionDescription newAnswer = SdpUtils.createSessionUpdateDescription(
previousAnswer, getLastUsedLocalHost(), answerDescriptions);
this.localSess = newAnswer;
return localSess;
}
private Vector<MediaDescription> createMediaDescriptionsForAnswer(
SessionDescription offer)
throws OperationFailedException
{
Vector<MediaDescription> remoteDescriptions = SdpUtils
.extractMediaDescriptions(offer);
MediaService mediaService = SipActivator.getMediaService();
//prepare to generate answers to all the incoming descriptions
// prepare to generate answers to all the incoming descriptions
Vector<MediaDescription> answerDescriptions
= new Vector<MediaDescription>(remoteDescriptions.size());
= new Vector<MediaDescription>( remoteDescriptions.size() );
this.setCallInfoURL(SdpUtils.getCallInfoURL(offer));
boolean atLeastOneValidDescription = false;
for ( MediaDescription mediaDescription : remoteDescriptions)
for (MediaDescription mediaDescription : remoteDescriptions)
{
MediaType mediaType = SdpUtils.getMediaType(mediaDescription);
@ -485,52 +574,46 @@ public SessionDescription processFirstOffer(SessionDescription offer)
MediaDevice dev = mediaService.getDefaultDevice(mediaType);
MediaDirection devDirection = dev.getDirection();
//stream target
MediaStreamTarget target
= SdpUtils.extractDefaultTarget(mediaDescription, offer);
// stream target
MediaStreamTarget target = SdpUtils.extractDefaultTarget(
mediaDescription, offer);
if (supportedFormats == null || supportedFormats.size() == 0
|| dev == null || devDirection == MediaDirection.INACTIVE
|| target.getDataAddress().getPort() == 0)
{
//mark stream as dead and go on bravely
answerDescriptions.add(
SdpUtils.createDisablingAnswer(mediaDescription));
// mark stream as dead and go on bravely
answerDescriptions.add(SdpUtils
.createDisablingAnswer(mediaDescription));
continue;
}
StreamConnector connector = getStreamConnector(mediaType);
//determine the direction that we need to announce.
MediaDirection remoteDirection
= SdpUtils.getDirection(mediaDescription);
// determine the direction that we need to announce.
MediaDirection remoteDirection = SdpUtils
.getDirection(mediaDescription);
MediaDirection direction
= devDirection.getDirectionForAnswer(remoteDirection);
MediaDirection direction = devDirection
.getDirectionForAnswer(remoteDirection);
//create the corresponding stream
initStream( connector, dev, supportedFormats.get(0),
target, direction);
// create the corresponding stream
initStream(connector, dev, supportedFormats.get(0), target,
direction);
//create the answer description
answerDescriptions.add( createMediaDescription(
supportedFormats, connector, direction));
// create the answer description
answerDescriptions.add(createMediaDescription(supportedFormats,
connector, direction));
atLeastOneValidDescription = true;
}
if(!atLeastOneValidDescription)
if (!atLeastOneValidDescription)
throw new OperationFailedException("Offer contained no valid "
+ "media descriptions.",
OperationFailedException.ILLEGAL_ARGUMENT);
//wrap everything up in a session description
SessionDescription answer = SdpUtils.createSessionDescription(
getLastUsedLocalHost(), getUserName(), answerDescriptions);
this.localSess = answer;
+ "media descriptions.",
OperationFailedException.ILLEGAL_ARGUMENT);
return localSess;
return answerDescriptions;
}
public void processAnswer(SessionDescription answer)
@ -778,6 +861,23 @@ private void initializePortNumbers()
}
}
/**
* Determines whether the remote party has placed all our streams on hold.
*
* @return <tt>true</tt> if all our streams have been placed on hold (i.e.
* if none of them is currently sending and <tt>false</tt> otherwise.
*/
public boolean isRemotelyOnHold()
{
if(audioStream != null && audioStream.getDirection().allowsSending())
return false;
if(videoStream != null && videoStream.getDirection().allowsSending())
return false;
return true;
}
/**
* Returns a <tt>URL</tt> pointing ta a location with call control
* information for this peer or <tt>null</tt> if no such <tt>URL</tt> is

@ -508,11 +508,16 @@ public void processReInvite(ServerTransaction serverTransaction)
try
{
response = messageFactory.createResponse(Response.OK, invite);
attachSdpAnswer(response);
String sdpAnswer = getMediaHandler()
.processOffer( getSdpDescription() );
logger.trace("will send an OK response: ");
response.setContent( sdpAnswer, getProtocolProvider()
.getHeaderFactory().createContentTypeHeader(
"application", "sdp"));
logger.trace("will send an OK response: " + response);
serverTransaction.sendResponse(response);
logger.debug("sent a an OK response: "+ response);
logger.debug("OK response sent");
}
catch (Exception ex)//no need to distinguish among exceptions.
{
@ -524,125 +529,67 @@ public void processReInvite(ServerTransaction serverTransaction)
return;
}
try
{
updateMediaFlags();
}
catch (OperationFailedException ex)
{
logger.error("Error after sending response " + response, ex);
}
reevalRemoteHoldStatus();
}
/**
* Creates an SDP description that could be sent to <tt>peer</tt> and adds
* it to <tt>response</tt>. Provides a hook for this instance to take last
* configuration steps on a specific <tt>Response</tt> before it is sent to
* a specific <tt>CallPeer</tt> as part of the execution of.
*
* @param response the <tt>Response</tt> to be sent to the <tt>peer</tt>
*
* @throws OperationFailedException if we fail parsing call peer's media.
* @throws ParseException if we try to attach invalid SDP to response.
* Updates the state of this <tt>CallPeer</tt> to match the remotely-on-hold
* status of our media handler.
*/
private void attachSdpAnswer(Response response)
throws OperationFailedException, ParseException
private void reevalRemoteHoldStatus()
{
/*
* At the time of this writing, we're only getting called because a
* response to a call-hold invite is to be sent.
*/
/**
* @todo update to neomedia.
CallSession callSession = getMediaCallSession();
boolean remotelyOnHold = getMediaHandler().isRemotelyOnHold();
String sdpAnswer = null;
try
CallPeerState state = getState();
if (CallPeerState.ON_HOLD_LOCALLY.equals(state))
{
sdpAnswer = callSession.processSdpOffer(this, getSdpDescription());
if (remotelyOnHold)
setState(CallPeerState.ON_HOLD_MUTUALLY);
}
catch (MediaException ex)
else if (CallPeerState.ON_HOLD_MUTUALLY.equals(state))
{
ProtocolProviderServiceSipImpl.throwOperationFailedException(
"Failed to create SDP answer to put-on/off-hold request.",
OperationFailedException.INTERNAL_ERROR, ex, logger);
if (!remotelyOnHold)
setState(CallPeerState.ON_HOLD_LOCALLY);
}
response.setContent(
sdpAnswer,
getProtocolProvider().getHeaderFactory()
.createContentTypeHeader("application", "sdp"));
*/
}
/**
* Updates the media flags for this peer according to the value of the SDP
* field.
*
* @throws OperationFailedException if we fail parsing callPeer's media.
*/
private void updateMediaFlags()
throws OperationFailedException
{
/*
* At the time of this writing, we're only getting called because a
* response to a call-hold invite is to be sent.
*/
/**
* @todo update to neomedia.
CallSession callSession = getMediaCallSession();
int mediaFlags = 0;
try
else if (CallPeerState.ON_HOLD_REMOTELY.equals(state))
{
mediaFlags = callSession.getSdpOfferMediaFlags(getSdpDescription());
if (!remotelyOnHold)
setState(CallPeerState.CONNECTED);
}
catch (MediaException ex)
else if (remotelyOnHold)
{
ProtocolProviderServiceSipImpl.throwOperationFailedException(
"Failed to create SDP answer to put-on/off-hold request.",
OperationFailedException.INTERNAL_ERROR, ex, logger);
setState(CallPeerState.ON_HOLD_REMOTELY);
}
*/
/*
* Comply with the request of the SDP offer with respect to putting on
* hold.
*/
/**
* @todo update to neomedia.
boolean on = ((mediaFlags & CallSession.ON_HOLD_REMOTELY) != 0);
}
callSession.putOnHold(on, false);
/**
* Updates the state of this <tt>CallPeer</tt> to match the locally-on-hold
* status of our media handler.
*/
private void reevalLocalHoldStatus()
{
boolean locallyOnHold = getMediaHandler().isLocallyOnHold();
CallPeerState state = getState();
if (CallPeerState.ON_HOLD_LOCALLY.equals(state))
{
if (on)
setState(CallPeerState.ON_HOLD_MUTUALLY);
if (!locallyOnHold)
setState(CallPeerState.CONNECTED);
}
else if (CallPeerState.ON_HOLD_MUTUALLY.equals(state))
{
if (!on)
setState(CallPeerState.ON_HOLD_LOCALLY);
if (!locallyOnHold)
setState(CallPeerState.ON_HOLD_REMOTELY);
}
else if (CallPeerState.ON_HOLD_REMOTELY.equals(state))
{
if (!on)
setState(CallPeerState.CONNECTED);
if (locallyOnHold)
setState(CallPeerState.ON_HOLD_MUTUALLY);
}
else if (on)
else if (locallyOnHold)
{
setState(CallPeerState.ON_HOLD_REMOTELY);
setState(CallPeerState.ON_HOLD_LOCALLY);
}
*/
/*
* Reflect the request of the SDP offer with respect to the modification
* of the availability of media.
*/
/**
* @todo update to neomedia.
callSession.setReceiveStreaming(mediaFlags);
*/
}
/**
@ -1289,7 +1236,7 @@ public synchronized void answer()
/**
* Puts the <tt>CallPeer</tt> represented by this instance on or off hold.
*
* @param on <tt>true</tt> to have the <tt>CallPeer</tt> put on hold;
* @param onHold <tt>true</tt> to have the <tt>CallPeer</tt> put on hold;
* <tt>false</tt>, otherwise
*
* @throws OperationFailedException if we fail to construct or send the
@ -1300,7 +1247,7 @@ public void putOnHold(boolean onHold)
{
CallPeerMediaHandler mediaHandler = getMediaHandler();
mediaHandler.setOnHold(onHold);
mediaHandler.setLocallyOnHold(onHold);
try
{
@ -1313,36 +1260,7 @@ public void putOnHold(boolean onHold)
OperationFailedException.INTERNAL_ERROR, ex, logger);
}
/*
* Putting on hold isn't a negotiation (i.e. the issuing side takes the
* decision and executes it) so we're muting now regardless of the
* desire of the peer to accept the offer.
*/
/**
* @todo update to neomedia.
callSession.putOnHold(on, true);
*/
CallPeerState state = getState();
if (CallPeerState.ON_HOLD_LOCALLY.equals(state))
{
if (!onHold)
setState(CallPeerState.CONNECTED);
}
else if (CallPeerState.ON_HOLD_MUTUALLY.equals(state))
{
if (!onHold)
setState(CallPeerState.ON_HOLD_REMOTELY);
}
else if (CallPeerState.ON_HOLD_REMOTELY.equals(state))
{
if (onHold)
setState(CallPeerState.ON_HOLD_MUTUALLY);
}
else if (onHold)
{
setState(CallPeerState.ON_HOLD_LOCALLY);
}
reevalLocalHoldStatus();
}
/**
@ -1395,23 +1313,19 @@ public void invite()
try
{
inviteTran = (ClientTransaction)getLatestInviteTransaction();
}
catch(ClassCastException exc)
{
throw new OperationFailedException(
"Can't invite someone that is actually inviting us",
OperationFailedException.INTERNAL_ERROR, exc);
}
attachSdpOffer(inviteTran.getRequest());
ContentTypeHeader contentTypeHeader = getProtocolProvider()
.getHeaderFactory().createContentTypeHeader(
"application", "sdp");
inviteTran.getRequest().setContent(getMediaHandler().createOffer(),
contentTypeHeader);
try
{
inviteTran.sendRequest();
if (logger.isDebugEnabled())
logger.debug("sent request:\n" + inviteTran.getRequest());
}
catch (SipException ex)
catch (Exception ex)
{
ProtocolProviderServiceSipImpl.throwOperationFailedException(
"An error occurred while sending invite request",
@ -1419,42 +1333,6 @@ public void invite()
}
}
/**
* Creates an SDP offer destined to <tt>callPeer</tt> and attaches it to
* the <tt>invite</tt> request.
*
* @param invite the invite <tt>Request</tt> that we'd like to attach an
* SDP offer to.
*
* @throws OperationFailedException if we fail constructing the session
* description.
*/
private void attachSdpOffer(Request invite)
throws OperationFailedException
{
try
{
ContentTypeHeader contentTypeHeader = getProtocolProvider()
.getHeaderFactory().createContentTypeHeader(
"application", "sdp");
invite.setContent(getMediaHandler().createOffer(),
contentTypeHeader);
}
catch (IllegalArgumentException ex)
{
ProtocolProviderServiceSipImpl.throwOperationFailedException(
"Failed to obtain an InetAddress for " + ex.getMessage(),
OperationFailedException.NETWORK_FAILURE, ex, logger);
}
catch (ParseException ex)
{
ProtocolProviderServiceSipImpl.throwOperationFailedException(
"Failed to parse sdp data while creating invite request!",
OperationFailedException.INTERNAL_ERROR, ex, logger);
}
}
/**
* Modifies the local media setup to reflect the requested setting for the
* streaming of the local video and then re-invites the peer represented by
@ -1593,4 +1471,13 @@ private CallPeerMediaHandler getMediaHandler()
{
return mediaHandler;
}
/**
* Overrides the
*/
public void setState(CallPeerState newState, String reason)
{
super.setState(newState, reason);
this.getMediaHandler().close();
}
}

@ -629,7 +629,7 @@ public void removeCallPeerConferenceListener(
* Adds a specific <tt>StreamSoundLevelListener</tt> to the list of
* listeners interested in and notified about changes in stream sound level
* related information.
*
*
* @param listener the <tt>StreamSoundLevelListener</tt> to add
*/
public void addStreamSoundLevelListener(StreamSoundLevelListener listener)
@ -645,7 +645,7 @@ public void addStreamSoundLevelListener(StreamSoundLevelListener listener)
* Removes a specific <tt>StreamSoundLevelListener</tt> of the list of
* listeners interested in and notified about changes in stream sound level
* related information.
*
*
* @param listener the <tt>StreamSoundLevelListener</tt> to remove
*/
public void removeStreamSoundLevelListener(
@ -662,7 +662,7 @@ public void removeStreamSoundLevelListener(
* Adds a specific <tt>ConferenceMembersSoundLevelListener</tt> to the list
* of listeners interested in and notified about changes in conference
* members sound level.
*
*
* @param listener the <tt>ConferenceMembersSoundLevelListener</tt> to add
*/
public void addConferenceMembersSoundLevelListener(
@ -679,7 +679,7 @@ public void addConferenceMembersSoundLevelListener(
* Removes a specific <tt>ConferenceMembersSoundLevelListener</tt> of the
* list of listeners interested in and notified about changes in conference
* members sound level.
*
*
* @param listener the <tt>ConferenceMembersSoundLevelListener</tt> to
* remove
*/

Loading…
Cancel
Save