Modifies ColibriBuilder to be able to send RTP, SSRC and transport updates in a single request

sip-call-params 5504
paweldomas 11 years ago
parent 8cbe6c805b
commit cd6b77bb0e

@ -37,9 +37,9 @@
* Add one or multiple requests of the same type by calling
* {@link #addAllocateChannelsReq(boolean, String, boolean, java.util.List)}}
* or {@link #addExpireChannelsReq(ColibriConferenceIQ)}
* or {@link #addTransportUpdateReq(boolean, java.util.Map, ColibriConferenceIQ)}
* or {@link #addBundleTransportUpdateReq(
* boolean, IceUdpTransportPacketExtension, ColibriConferenceIQ)}.
* or {@link #addRtpDescription(Map, ColibriConferenceIQ)}
* and {@link #addSSSRCGroupsInfo(Map, ColibriConferenceIQ)}
* and {@link #addSSSRCInfo(Map, ColibriConferenceIQ)}.
* </li>
* <li>
* Compile the request by calling {@link #getRequest(String)}. Then send it to
@ -161,18 +161,26 @@ public void reset()
* @param contents the list of {@link ContentPacketExtension} describing
* channels media.
*
* @return this instance fo calls chaining purpose.
* @return <tt>true</tt> if the request yields any changes in Colibri
* channels state on the bridge or <tt>false</tt> otherwise.
* In general when <tt>false</tt> is returned for all
* combined requests it makes no sense to send it.
*/
public ColibriBuilder addAllocateChannelsReq(
boolean useBundle,
String endpointName,
boolean peerIsInitiator,
public boolean addAllocateChannelsReq(
boolean useBundle,
String endpointName,
boolean peerIsInitiator,
List<ContentPacketExtension> contents)
{
Objects.requireNonNull(endpointName, "endpointName");
Objects.requireNonNull(contents, "contents");
assertRequestType(RequestType.ALLOCATE_CHANNELS);
request.setType(IQ.Type.GET);
boolean hasAnyChanges = false;
for (ContentPacketExtension cpe : contents)
{
MediaType mediaType = JingleUtils.getMediaType(cpe);
@ -193,7 +201,7 @@ public ColibriBuilder addAllocateChannelsReq(
remoteChannelRequest.setChannelBundleId(endpointName);
}
if (mediaType != MediaType.DATA)
if (remoteChannelRequest instanceof ColibriConferenceIQ.Channel)
{
RtpDescriptionPacketExtension rdpe
= cpe.getFirstChildOfType(
@ -221,13 +229,17 @@ public ColibriBuilder addAllocateChannelsReq(
copyTransportOnChannel(cpe, remoteChannelRequest);
}
if (mediaType != MediaType.DATA)
if (remoteChannelRequest instanceof ColibriConferenceIQ.Channel)
{
hasAnyChanges = true;
contentRequest.addChannel(
(ColibriConferenceIQ.Channel) remoteChannelRequest);
}
else
{
hasAnyChanges = true;
contentRequest.addSctpConnection(
(ColibriConferenceIQ.SctpConnection) remoteChannelRequest);
}
@ -246,6 +258,8 @@ public ColibriBuilder addAllocateChannelsReq(
IceUdpTransportPacketExtension.class);
if (transport != null)
{
hasAnyChanges = true;
bundle.setTransport(
IceUdpTransportPacketExtension
.cloneTransportAndCandidates(transport, true));
@ -254,106 +268,38 @@ public ColibriBuilder addAllocateChannelsReq(
request.addChannelBundle(bundle);
}
return this;
return hasAnyChanges;
}
/**
* Adds next ICE transport update request to
* {@link RequestType#TRANSPORT_UPDATE} query currently being built.
*
* @param initiator the value that will be set in 'initiator'
* attribute({@link ColibriConferenceIQ.Channel#initiator}).
* @param map the map of content name to transport extensions. Maps
* transport to media types.
* @param localChannelsInfo {@link ColibriConferenceIQ} holding info about
* Colibri channels to be updated.
*
* @return this instance fo calls chaining purpose.
*/
public ColibriBuilder addTransportUpdateReq(
boolean initiator,
Map<String, IceUdpTransportPacketExtension> map,
ColibriConferenceIQ localChannelsInfo)
{
if (conferenceState == null
|| StringUtils.isNullOrEmpty(conferenceState.getID()))
{
// We are not initialized yet
return null;
}
assertRequestType(RequestType.TRANSPORT_UPDATE);
request.setType(IQ.Type.SET);
for (Map.Entry<String,IceUdpTransportPacketExtension> e
: map.entrySet())
{
String contentName = e.getKey();
ColibriConferenceIQ.ChannelCommon channel
= getColibriChannel(localChannelsInfo, contentName);
if (channel != null)
{
IceUdpTransportPacketExtension transport
= IceUdpTransportPacketExtension
.cloneTransportAndCandidates(e.getValue(), true);
ColibriConferenceIQ.ChannelCommon channelRequest
= channel instanceof ColibriConferenceIQ.Channel
? new ColibriConferenceIQ.Channel()
: new ColibriConferenceIQ.SctpConnection();
channelRequest.setID(channel.getID());
channelRequest.setEndpoint(channel.getEndpoint());
channelRequest.setInitiator(initiator);
channelRequest.setTransport(transport);
if (channelRequest instanceof ColibriConferenceIQ.Channel)
{
request.getOrCreateContent(contentName)
.addChannel(
(ColibriConferenceIQ.Channel) channelRequest);
}
else
{
request.getOrCreateContent(contentName)
.addSctpConnection(
(ColibriConferenceIQ.SctpConnection) channelRequest);
}
}
}
return this;
}
/**
* Adds next request to {@link RequestType#BUNDLE_TRANSPORT_UPDATE} query.
* @param initiator the value that will be set in 'initiator'
* attribute({@link ColibriConferenceIQ.Channel#initiator}).
* Adds next request to {@link RequestType#CHANNEL_INFO_UPDATE} query.
* @param localChannelsInfo the {@link ColibriConferenceIQ} instance that
* describes the channel for which bundle transport will be updated.
* It should contain the description of only one "channel bundle".
* If it contains more than one then the first one will be used.
* @return this instance for calls chaining purpose.
* @return <tt>true</tt> if the request yields any changes in Colibri
* channels state on the bridge or <tt>false</tt> otherwise.
* In general when <tt>false</tt> is returned for all
* combined requests it makes no sense to send it.
* @throws IllegalArgumentException if <tt>localChannelsInfo</tt> does not
* describe any channel bundles.
*/
public ColibriBuilder addBundleTransportUpdateReq(
boolean initiator,
IceUdpTransportPacketExtension transport,
ColibriConferenceIQ localChannelsInfo)
public boolean addBundleTransportUpdateReq(
IceUdpTransportPacketExtension transport,
ColibriConferenceIQ localChannelsInfo)
throws IllegalArgumentException
{
// FIXME:'initiator' not used on bundle transport update ?
Objects.requireNonNull(transport, "transport");
Objects.requireNonNull(localChannelsInfo, "localChannelsInfo");
if (conferenceState == null
|| StringUtils.isNullOrEmpty(conferenceState.getID()))
{
// We are not initialized yet
return null;
return false;
}
assertRequestType(RequestType.BUNDLE_TRANSPORT_UPDATE);
assertRequestType(RequestType.CHANNEL_INFO_UPDATE);
request.setType(IQ.Type.SET);
@ -371,7 +317,7 @@ public ColibriBuilder addBundleTransportUpdateReq(
else
{
throw new IllegalArgumentException(
"Expected ChannelBundle as not found");
"Expected ChannelBundle as not found");
}
ColibriConferenceIQ.ChannelBundle bundleUpdate
@ -387,7 +333,7 @@ public ColibriBuilder addBundleTransportUpdateReq(
request.addChannelBundle(bundleUpdate);
return this;
return true;
}
/**
@ -395,15 +341,20 @@ public ColibriBuilder addBundleTransportUpdateReq(
* {@link RequestType#EXPIRE_CHANNELS} query currently being built.
* @param channelInfo the {@link ColibriConferenceIQ} instance that contains
* info about the channels to be expired.
* @return this instance for the purpose of calls chaining.
* @return <tt>true</tt> if the request yields any changes in Colibri
* channels state on the bridge or <tt>false</tt> otherwise.
* In general when <tt>false</tt> is returned for all
* combined requests it makes no sense to send it.
*/
public ColibriBuilder addExpireChannelsReq(ColibriConferenceIQ channelInfo)
public boolean addExpireChannelsReq(ColibriConferenceIQ channelInfo)
{
Objects.requireNonNull(channelInfo, "channelInfo");
// Formulate the ColibriConferenceIQ request which is to be sent.
if (conferenceState == null
|| StringUtils.isNullOrEmpty(conferenceState.getID()))
{
return null;
return false;
}
assertRequestType(RequestType.EXPIRE_CHANNELS);
@ -411,7 +362,7 @@ public ColibriBuilder addExpireChannelsReq(ColibriConferenceIQ channelInfo)
request.setType(IQ.Type.SET);
for (ColibriConferenceIQ.Content expiredContent
: channelInfo.getContents())
: channelInfo.getContents())
{
ColibriConferenceIQ.Content stateContent
= conferenceState.getContent(expiredContent.getName());
@ -420,7 +371,7 @@ public ColibriBuilder addExpireChannelsReq(ColibriConferenceIQ channelInfo)
{
ColibriConferenceIQ.Content requestContent
= request.getOrCreateContent(
stateContent.getName());
stateContent.getName());
for (ColibriConferenceIQ.Channel expiredChannel
: expiredContent.getChannels())
@ -490,7 +441,7 @@ public ColibriBuilder addExpireChannelsReq(ColibriConferenceIQ channelInfo)
*/
/*if (stateContent.getChannelCount() == 1)
{
stateChannel = stateContent.getChannel(0);
stateChannel = stateContent.getRtpChannel(0);
ColibriConferenceIQ.Channel channelRequest
= new ColibriConferenceIQ.Channel();
@ -537,7 +488,293 @@ public ColibriBuilder addExpireChannelsReq(ColibriConferenceIQ channelInfo)
}
}
return this;
return hasAnyChannelsToExpire;
}
/**
* Adds next payload type information update request to
* {@link RequestType#CHANNEL_INFO_UPDATE} query currently being built.
*
* @param map the map of content name to RTP description packet extension.
* @param localChannelsInfo {@link ColibriConferenceIQ} holding info about
* Colibri channels to be updated.
*
* @return <tt>true</tt> if the request yields any changes in Colibri
* channels state on the bridge or <tt>false</tt> otherwise.
* In general when <tt>false</tt> is returned for all
* combined requests it makes no sense to send it.
*/
public boolean addRtpDescription(
Map<String, RtpDescriptionPacketExtension> map,
ColibriConferenceIQ localChannelsInfo)
{
Objects.requireNonNull(map, "map");
Objects.requireNonNull(localChannelsInfo, "localChannelsInfo");
if (conferenceState == null
|| StringUtils.isNullOrEmpty(conferenceState.getID()))
{
// We are not initialized yet
return false;
}
assertRequestType(RequestType.CHANNEL_INFO_UPDATE);
request.setType(IQ.Type.SET);
boolean anyUpdates = false;
for (Map.Entry<String, RtpDescriptionPacketExtension> e
: map.entrySet())
{
String contentName = e.getKey();
ColibriConferenceIQ.ChannelCommon channel
= getColibriChannel(localChannelsInfo, contentName);
if (channel != null
&& channel instanceof ColibriConferenceIQ.Channel)
{
RtpDescriptionPacketExtension rtpPE = e.getValue();
if (rtpPE == null)
{
continue;
}
List<PayloadTypePacketExtension> pts = rtpPE.getPayloadTypes();
if (pts == null || pts.isEmpty())
{
continue;
}
anyUpdates = true;
ColibriConferenceIQ.Channel channelRequest
= request.getOrCreateContent(contentName)
.getChannel(channel.getID());
if (channelRequest == null)
{
channelRequest = new ColibriConferenceIQ.Channel();
channelRequest.setID(channel.getID());
}
for (PayloadTypePacketExtension ptPE : pts)
{
channelRequest.addPayloadType(ptPE);
}
}
}
return anyUpdates;
}
/**
* Adds next SSRC information update request to
* {@link RequestType#CHANNEL_INFO_UPDATE} query currently being built.
*
* @param ssrcMap the map of content name to the list of
* <tt>SourcePacketExtension</tt>.
* @param localChannelsInfo {@link ColibriConferenceIQ} holding info about
* Colibri channels to be updated.
*
* @return <tt>true</tt> if the request yields any changes in Colibri
* channels state on the bridge or <tt>false</tt> otherwise.
* In general when <tt>false</tt> is returned for all
* combined requests it makes no sense to send it.
*/
public boolean addSSSRCInfo(
Map<String, List<SourcePacketExtension>> ssrcMap,
ColibriConferenceIQ localChannelsInfo)
{
Objects.requireNonNull(ssrcMap, "ssrcMap");
Objects.requireNonNull(localChannelsInfo, "localChannelsInfo");
if (conferenceState == null
|| StringUtils.isNullOrEmpty(conferenceState.getID()))
{
// We are not initialized yet
return false;
}
assertRequestType(RequestType.CHANNEL_INFO_UPDATE);
request.setType(IQ.Type.SET);
boolean anyUpdates = false;
// Go over SSRCs
for (String contentName : ssrcMap.keySet())
{
// Get channel from local channel info
ColibriConferenceIQ.ChannelCommon rtpChanel
= getRtpChannel(localChannelsInfo, contentName);
if (rtpChanel == null)
{
// There's no channel for this content name in localChannelsInfo
continue;
}
anyUpdates = true;
// Ok we have channel for this content, let's add SSRCs
ColibriConferenceIQ.Channel reqChannel
= (ColibriConferenceIQ.Channel) getRequestChannel(
request.getOrCreateContent(contentName), rtpChanel);
for (SourcePacketExtension ssrc : ssrcMap.get(contentName))
{
reqChannel.addSource(ssrc.copy());
}
if (reqChannel.getSources() == null
|| reqChannel.getSources().isEmpty())
{
// Put an empty source to remove all sources
SourcePacketExtension emptySource = new SourcePacketExtension();
emptySource.setSSRC(-1L);
reqChannel.addSource(emptySource);
}
}
return anyUpdates;
}
/**
* Adds next SSRC group information update request to
* {@link RequestType#CHANNEL_INFO_UPDATE} query currently being built.
*
* @param ssrcGroupMap the map of content name to the list of
* <tt>SourceGroupPacketExtension</tt>.
* @param localChannelsInfo {@link ColibriConferenceIQ} holding info about
* Colibri channels to be updated.
*
* @return <tt>true</tt> if the request yields any changes in Colibri
* channels state on the bridge or <tt>false</tt> otherwise.
* In general when <tt>false</tt> is returned for all
* combined requests it makes no sense to send it.
*/
public boolean addSSSRCGroupsInfo(
Map<String, List<SourceGroupPacketExtension>> ssrcGroupMap,
ColibriConferenceIQ localChannelsInfo)
{
Objects.requireNonNull(ssrcGroupMap, "ssrcGroupMap");
Objects.requireNonNull(localChannelsInfo, "localChannelsInfo");
if (conferenceState == null
|| StringUtils.isNullOrEmpty(conferenceState.getID()))
{
// We are not initialized yet
return false;
}
assertRequestType(RequestType.CHANNEL_INFO_UPDATE);
request.setType(IQ.Type.SET);
boolean anyUpdates = false;
// Go over SSRC groups
for (String contentName : ssrcGroupMap.keySet())
{
// Get channel from local channel info
ColibriConferenceIQ.Channel rtpChannel
= getRtpChannel(localChannelsInfo, contentName);
if (rtpChannel == null)
{
// There's no channel for this content name in localChannelsInfo
continue;
}
List<SourceGroupPacketExtension> groups
= ssrcGroupMap.get(contentName);
// Ok we have channel for this content, let's add SSRCs
ColibriConferenceIQ.Channel reqChannel
= (ColibriConferenceIQ.Channel) getRequestChannel(
request.getOrCreateContent(contentName), rtpChannel);
if (groups.isEmpty() && "video".equalsIgnoreCase(contentName))
{
anyUpdates = true;
// Put empty source group to turn off simulcast layers
reqChannel.addSourceGroup(
SourceGroupPacketExtension.createSimulcastGroup());
}
for (SourceGroupPacketExtension group : groups)
{
anyUpdates = true;
reqChannel.addSourceGroup(group);
}
}
return anyUpdates;
}
/**
* Adds next ICE transport update request to
* {@link RequestType#CHANNEL_INFO_UPDATE} query currently being built.
*
* @param map the map of content name to transport extensions. Maps
* transport to media types.
* @param localChannelsInfo {@link ColibriConferenceIQ} holding info about
* Colibri channels to be updated.
*
* @return <tt>true</tt> if the request yields any changes in Colibri
* channels state on the bridge or <tt>false</tt> otherwise.
* In general when <tt>false</tt> is returned for all
* combined requests it makes no sense to send it.
*/
public boolean addTransportUpdateReq(
Map<String, IceUdpTransportPacketExtension> map,
ColibriConferenceIQ localChannelsInfo)
{
Objects.requireNonNull(map, "map");
Objects.requireNonNull(localChannelsInfo, "localChannelsInfo");
if (conferenceState == null
|| StringUtils.isNullOrEmpty(conferenceState.getID()))
{
// We are not initialized yet
return false;
}
boolean hasAnyChanges = false;
assertRequestType(RequestType.CHANNEL_INFO_UPDATE);
request.setType(IQ.Type.SET);
for (Map.Entry<String,IceUdpTransportPacketExtension> e
: map.entrySet())
{
String contentName = e.getKey();
ColibriConferenceIQ.ChannelCommon channel
= getColibriChannel(localChannelsInfo, contentName);
if (channel != null)
{
IceUdpTransportPacketExtension transport
= IceUdpTransportPacketExtension
.cloneTransportAndCandidates(e.getValue(), true);
ColibriConferenceIQ.ChannelCommon channelRequest
= channel instanceof ColibriConferenceIQ.Channel
? new ColibriConferenceIQ.Channel()
: new ColibriConferenceIQ.SctpConnection();
channelRequest.setID(channel.getID());
channelRequest.setEndpoint(channel.getEndpoint());
channelRequest.setTransport(transport);
request.getOrCreateContent(contentName)
.addChannelCommon(channelRequest);
hasAnyChanges = true;
}
}
return hasAnyChanges;
}
/**
@ -583,10 +820,12 @@ public ColibriConferenceIQ getRequest(String videobridge)
request.setTo(videobridge);
if (requestType == RequestType.EXPIRE_CHANNELS
&& !hasAnyChannelsToExpire)
if (requestType == RequestType.EXPIRE_CHANNELS)
{
return null;
if (!hasAnyChannelsToExpire)
return null;
hasAnyChannelsToExpire = false;
}
return request;
@ -732,69 +971,64 @@ public void setSimulcastMode(SimulcastMode simulcastMode)
}
/**
* Adds next payload type information update request to
* {@link RequestType#RTP_DESCRIPTION_UPDATE} query currently being built.
* Creates a new instance of <tt>localChannelInfo</tt> and initializes only
* the fields required to identify particular Colibri channel on the bridge.
* This instance is meant to be used in Colibri
* {@link RequestType#CHANNEL_INFO_UPDATE} requests. This instance is also
* added to given <tt>requestContent</tt> which used to construct current
* request.
*
* @param map the map of content name to RTP description packet extension.
* @param localChannelsInfo {@link ColibriConferenceIQ} holding info about
* Colibri channels to be updated.
* @param requestContent <tt>Content</tt> of Colibri update request to which
* new instance wil be automatically added after has been created.
* @param localChannelInfo the original channel for which "update request"
* equivalent is to be created with this call.
*
* @return this instance for calls chaining purpose.
* @return new instance of <tt>localChannelInfo</tt> and initialized with
* only those fields required to identify particular Colibri channel on
* the bridge.
*/
public ColibriBuilder addRtpDescription(
Map<String, RtpDescriptionPacketExtension> map,
ColibriConferenceIQ localChannelsInfo) {
if (conferenceState == null
|| StringUtils.isNullOrEmpty(conferenceState.getID()))
{
// We are not initialized yet
return null;
}
assertRequestType(RequestType.RTP_DESCRIPTION_UPDATE);
request.setType(IQ.Type.SET);
for (Map.Entry<String, RtpDescriptionPacketExtension> e
: map.entrySet())
private ColibriConferenceIQ.ChannelCommon getRequestChannel(
ColibriConferenceIQ.Content requestContent,
ColibriConferenceIQ.ChannelCommon localChannelInfo)
{
ColibriConferenceIQ.ChannelCommon reqChannel
= requestContent.getChannel(localChannelInfo.getID());
if (reqChannel == null)
{
String contentName = e.getKey();
ColibriConferenceIQ.ChannelCommon channel
= getColibriChannel(localChannelsInfo, contentName);
if (channel != null
&& channel instanceof ColibriConferenceIQ.Channel)
if (localChannelInfo instanceof ColibriConferenceIQ.Channel)
{
RtpDescriptionPacketExtension rtpPE = e.getValue();
if (rtpPE == null)
{
continue;
}
List<PayloadTypePacketExtension> pts = rtpPE.getPayloadTypes();
if (pts == null || pts.isEmpty())
{
continue;
}
ColibriConferenceIQ.Channel channelRequest
= new ColibriConferenceIQ.Channel();
reqChannel = new ColibriConferenceIQ.Channel();
}
else if (
localChannelInfo instanceof ColibriConferenceIQ.SctpConnection)
{
reqChannel = new ColibriConferenceIQ.SctpConnection();
}
else
{
throw new RuntimeException(
"Unsupported ChannelCommon class: "
+ localChannelInfo.getClass());
}
channelRequest.setID(channel.getID());
reqChannel.setID(localChannelInfo.getID());
for (PayloadTypePacketExtension ptPE : rtpPE.getPayloadTypes())
{
channelRequest.addPayloadType(ptPE);
}
requestContent.addChannelCommon(reqChannel);
}
return reqChannel;
}
request.getOrCreateContent(contentName)
.addChannel(channelRequest);
}
private ColibriConferenceIQ.Channel getRtpChannel(
ColibriConferenceIQ localChannelsInfo,
String contentName)
{
ColibriConferenceIQ.Content content
= localChannelsInfo.getContent(contentName);
}
if (content == null)
return null;
return this;
return content.getChannelCount() > 0 ? content.getChannel(0) : null;
}
/**
@ -808,19 +1042,10 @@ public enum RequestType
ALLOCATE_CHANNELS,
/**
* Updates transport information for channels that use RTP bundle.
*/
BUNDLE_TRANSPORT_UPDATE,
/**
* Updates channel transport information(ICE transport candidates).
*/
TRANSPORT_UPDATE,
/**
* Updates the RTP description of a channel (payload types).
* An update request which is meant to modify some values of existing
* Colibri channels on the bridge.
*/
RTP_DESCRIPTION_UPDATE,
CHANNEL_INFO_UPDATE,
/**
* Expires specified Colibri channels.

@ -1924,6 +1924,25 @@ public boolean addChannel(Channel channel)
return channels.contains(channel) ? false : channels.add(channel);
}
/**
* Adds <tt>ChannelCommon</tt> to this <tt>Content</tt>.
* @param channelCommon {@link ChannelCommon} instance to be added to
* this content.
* @return <tt>true</tt> if given <tt>channelCommon</tt> has been
* actually added to this <tt>Content</tt> instance.
*/
public boolean addChannelCommon(ChannelCommon channelCommon)
{
if (channelCommon instanceof Channel)
{
return addChannel((Channel) channelCommon);
}
else
{
return addSctpConnection((SctpConnection) channelCommon);
}
}
/**
* Adds a specific <tt>SctpConnection</tt> to the list of
* <tt>SctpConnection</tt>s included into this <tt>Content</tt>.

@ -190,6 +190,27 @@ public boolean removeCandidate(CandidatePacketExtension candidate)
}
}
/**
* Removes given <tt>PacketExtension</tt> from the list of child packet
* extensions. <tt>CandidatePacketExtension</tt> are not taken into account
* in this method and {@link #removeCandidate(CandidatePacketExtension)}
* should be used instead.
*
* @param childExtension <tt>PacketExtension</tt> instance to be removed
* from child packet extensions list.
*
* @return <tt>true</tt> if given <tt>childExtension</tt> has been in the
* list and was removed or <tt>false</tt> otherwise.
*/
public boolean removeChildExtension(PacketExtension childExtension)
{
List<? extends PacketExtension> childExtensions
= super.getChildExtensions();
return childExtensions != null
&& childExtensions.remove(childExtension);
}
/**
* Returns the list of {@link CandidatePacketExtension}s currently
* registered with this transport.

Loading…
Cancel
Save