/* * Jitsi, the OpenSource Java VoIP and Instant Messaging client. * * Copyright @ 2015 Atlassian Pty Ltd * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package net.java.sip.communicator.impl.protocol.jabber; import java.net.*; import java.util.*; import net.java.sip.communicator.impl.protocol.jabber.extensions.colibri.*; import net.java.sip.communicator.impl.protocol.jabber.extensions.jingle.*; import net.java.sip.communicator.impl.protocol.jabber.jinglesdp.*; import net.java.sip.communicator.service.protocol.*; import net.java.sip.communicator.service.protocol.media.*; import net.java.sip.communicator.util.*; import org.jitsi.service.neomedia.*; import org.jivesoftware.smack.packet.*; /** * <tt>TransportManager</tt>s gather local candidates for incoming and outgoing * calls. Their work starts by calling a start method which, using the remote * peer's session description, would start the harvest. Calling a second wrapup * method would deliver the candidate harvest, possibly after blocking if it has * not yet completed. * * @author Emil Ivov * @author Lyubomir Marinov */ public abstract class TransportManagerJabberImpl extends TransportManager<CallPeerJabberImpl> { /** * The <tt>Logger</tt> used by the <tt>TransportManagerJabberImpl</tt> class * and its instances to print debug messages. */ private static final Logger logger = Logger.getLogger(TransportManagerJabberImpl.class); /** * The ID that we will be assigning to our next candidate. We use * <tt>int</tt>s for interoperability reasons (Emil: I believe that GTalk * uses <tt>int</tt>s. If that turns out not to be the case we can stop * using <tt>int</tt>s here if that's an issue). */ private static int nextID = 1; /** * The information pertaining to the Jisti Videobridge conference which the * local peer represented by this instance is a focus of. It gives a view of * the whole Jitsi Videobridge conference managed by the associated * <tt>CallJabberImpl</tt> which provides information specific to this * <tt>TransportManager</tt> only. */ private ColibriConferenceIQ colibri; /** * The generation of the candidates we are currently generating */ private int currentGeneration = 0; /** * The indicator which determines whether this <tt>TransportManager</tt> * instance is responsible to establish the connectivity with the associated * Jitsi Videobridge (in case it is being employed at all). */ boolean isEstablishingConnectivityWithJitsiVideobridge = false; /** * The indicator which determines whether this <tt>TransportManager</tt> * instance is yet to start establishing the connectivity with the * associated Jitsi Videobridge (in case it is being employed at all). */ boolean startConnectivityEstablishmentWithJitsiVideobridge = false; /** * Creates a new instance of this transport manager, binding it to the * specified peer. * * @param callPeer the {@link CallPeer} whose traffic we will be taking * care of. */ protected TransportManagerJabberImpl(CallPeerJabberImpl callPeer) { super(callPeer); } /** * Returns the <tt>InetAddress</tt> that is most likely to be to be used * as a next hop when contacting the specified <tt>destination</tt>. This is * an utility method that is used whenever we have to choose one of our * local addresses to put in the Via, Contact or (in the case of no * registrar accounts) From headers. * * @param peer the CallPeer that we would contact. * * @return the <tt>InetAddress</tt> that is most likely to be to be used * as a next hop when contacting the specified <tt>destination</tt>. * * @throws IllegalArgumentException if <tt>destination</tt> is not a valid * host/IP/FQDN */ @Override protected InetAddress getIntendedDestination(CallPeerJabberImpl peer) { return peer.getProtocolProvider().getNextHop(); } /** * Returns the ID that we will be assigning to the next candidate we create. * * @return the next ID to use with a candidate. */ protected String getNextID() { int nextID; synchronized (TransportManagerJabberImpl.class) { nextID = TransportManagerJabberImpl.nextID++; } return Integer.toString(nextID); } /** * Gets the <tt>MediaStreamTarget</tt> to be used as the <tt>target</tt> of * the <tt>MediaStream</tt> with a specific <tt>MediaType</tt>. * * @param mediaType the <tt>MediaType</tt> of the <tt>MediaStream</tt> which * is to have its <tt>target</tt> set to the returned * <tt>MediaStreamTarget</tt> * @return the <tt>MediaStreamTarget</tt> to be used as the <tt>target</tt> * of the <tt>MediaStream</tt> with the specified <tt>MediaType</tt> */ public abstract MediaStreamTarget getStreamTarget(MediaType mediaType); /** * Gets the XML namespace of the Jingle transport implemented by this * <tt>TransportManagerJabberImpl</tt>. * * @return the XML namespace of the Jingle transport implemented by this * <tt>TransportManagerJabberImpl</tt> */ public abstract String getXmlNamespace(); /** * Returns the generation that our current candidates belong to. * * @return the generation that we should assign to candidates that we are * currently advertising. */ protected int getCurrentGeneration() { return currentGeneration; } /** * Increments the generation that we are assigning candidates. */ protected void incrementGeneration() { currentGeneration++; } /** * Sends transport-related information received from the remote peer to the * associated Jiitsi Videobridge in order to update the (remote) * <tt>ColibriConferenceIQ.Channel</tt> associated with this * <tt>TransportManager</tt> instance. * * @param map a <tt>Map</tt> of media-IceUdpTransportPacketExtension pairs * which represents the transport-related information which has been * received from the remote peer and which is to be sent to the associated * Jitsi Videobridge */ protected void sendTransportInfoToJitsiVideobridge( Map<String,IceUdpTransportPacketExtension> map) { CallPeerJabberImpl peer = getCallPeer(); boolean initiator = !peer.isInitiator(); ColibriConferenceIQ conferenceRequest = null; for (Map.Entry<String,IceUdpTransportPacketExtension> e : map.entrySet()) { String media = e.getKey(); MediaType mediaType = MediaType.parseString(media); ColibriConferenceIQ.Channel channel = getColibriChannel(mediaType, false /* remote */); if (channel != null) { IceUdpTransportPacketExtension transport; try { transport = cloneTransportAndCandidates(e.getValue()); } catch (OperationFailedException ofe) { transport = null; } if (transport == null) continue; ColibriConferenceIQ.Channel channelRequest = new ColibriConferenceIQ.Channel(); channelRequest.setID(channel.getID()); channelRequest.setInitiator(initiator); channelRequest.setTransport(transport); if (conferenceRequest == null) { if (colibri == null) break; else { String id = colibri.getID(); if ((id == null) || (id.length() == 0)) break; else { conferenceRequest = new ColibriConferenceIQ(); conferenceRequest.setID(id); conferenceRequest.setTo(colibri.getFrom()); conferenceRequest.setType(IQ.Type.SET); } } } conferenceRequest.getOrCreateContent(media).addChannel( channelRequest); } } if (conferenceRequest != null) { peer.getProtocolProvider().getConnection().sendPacket( conferenceRequest); } } /** * Starts transport candidate harvest for a specific * <tt>ContentPacketExtension</tt> that we are going to offer or answer * with. * * @param theirContent the <tt>ContentPacketExtension</tt> offered by the * remote peer to which we are going to answer with <tt>ourContent</tt> or * <tt>null</tt> if <tt>ourContent</tt> will be an offer to the remote peer * @param ourContent the <tt>ContentPacketExtension</tt> for which transport * candidate harvest is to be started * @param transportInfoSender a <tt>TransportInfoSender</tt> if the * harvested transport candidates are to be sent in a * <tt>transport-info</tt> rather than in <tt>ourContent</tt>; otherwise, * <tt>null</tt> * @param media the media of the <tt>RtpDescriptionPacketExtension</tt> * child of <tt>ourContent</tt> * @return a <tt>PacketExtension</tt> to be added as a child to * <tt>ourContent</tt>; otherwise, <tt>null</tt> * @throws OperationFailedException if anything goes wrong while starting * transport candidate harvest for the specified <tt>ourContent</tt> */ protected abstract PacketExtension startCandidateHarvest( ContentPacketExtension theirContent, ContentPacketExtension ourContent, TransportInfoSender transportInfoSender, String media) throws OperationFailedException; /** * Starts transport candidate harvest. This method should complete rapidly * and, in case of lengthy procedures like STUN/TURN/UPnP candidate harvests * are necessary, they should be executed in a separate thread. Candidate * harvest would then need to be concluded in the * {@link #wrapupCandidateHarvest()} method which would be called once we * absolutely need the candidates. * * @param theirOffer a media description offer that we've received from the * remote party and that we should use in case we need to know what * transports our peer is using. * @param ourAnswer the content descriptions that we should be adding our * transport lists to (although not necessarily in this very instance). * @param transportInfoSender the <tt>TransportInfoSender</tt> to be used by * this <tt>TransportManagerJabberImpl</tt> to send <tt>transport-info</tt> * <tt>JingleIQ</tt>s from the local peer to the remote peer if this * <tt>TransportManagerJabberImpl</tt> wishes to utilize * <tt>transport-info</tt>. Local candidate addresses sent by this * <tt>TransportManagerJabberImpl</tt> in <tt>transport-info</tt> are * expected to not be included in the result of * {@link #wrapupCandidateHarvest()}. * * @throws OperationFailedException if we fail to allocate a port number. */ public void startCandidateHarvest( List<ContentPacketExtension> theirOffer, List<ContentPacketExtension> ourAnswer, TransportInfoSender transportInfoSender) throws OperationFailedException { CallPeerJabberImpl peer = getCallPeer(); CallJabberImpl call = peer.getCall(); boolean isJitsiVideobridge = call.getConference().isJitsiVideobridge(); List<ContentPacketExtension> cpes = (theirOffer == null) ? ourAnswer : theirOffer; /* * If Jitsi Videobridge is to be used, determine which channels are to * be allocated and attempt to allocate them now. */ if (isJitsiVideobridge) { Map<ContentPacketExtension,ContentPacketExtension> contentMap = new LinkedHashMap <ContentPacketExtension,ContentPacketExtension>(); for (ContentPacketExtension cpe : cpes) { MediaType mediaType = JingleUtils.getMediaType(cpe); /* * The existence of a content for the mediaType and regardless * of the existence of channels in it signals that a channel * allocation request has already been sent for that mediaType. */ if ((colibri == null) || (colibri.getContent(mediaType.toString()) == null)) { ContentPacketExtension local, remote; if (cpes == ourAnswer) { local = cpe; remote = (theirOffer == null) ? null : findContentByName(theirOffer, cpe.getName()); } else { local = findContentByName(ourAnswer, cpe.getName()); remote = cpe; } contentMap.put(local, remote); } } if (!contentMap.isEmpty()) { /* * We are about to request the channel allocations for the media * types found in contentMap. Regardless of the response, we do * not want to repeat these requests. */ if (colibri == null) colibri = new ColibriConferenceIQ(); for (Map.Entry<ContentPacketExtension,ContentPacketExtension> e : contentMap.entrySet()) { ContentPacketExtension cpe = e.getValue(); if (cpe == null) cpe = e.getKey(); colibri.getOrCreateContent( JingleUtils.getMediaType(cpe).toString()); } ColibriConferenceIQ conferenceResult = call.createColibriChannels(peer, contentMap); if (conferenceResult != null) { String videobridgeID = colibri.getID(); String conferenceResultID = conferenceResult.getID(); if (videobridgeID == null) colibri.setID(conferenceResultID); else if (!videobridgeID.equals(conferenceResultID)) throw new IllegalStateException("conference.id"); String videobridgeFrom = conferenceResult.getFrom(); if ((videobridgeFrom != null) && (videobridgeFrom.length() != 0)) { colibri.setFrom(videobridgeFrom); } for (ColibriConferenceIQ.Content contentResult : conferenceResult.getContents()) { ColibriConferenceIQ.Content content = colibri.getOrCreateContent( contentResult.getName()); for (ColibriConferenceIQ.Channel channelResult : contentResult.getChannels()) { if (content.getChannel(channelResult.getID()) == null) { content.addChannel(channelResult); } } } } else { /* * The call fails if the createColibriChannels method fails * which may happen if the conference packet times out or it * can't be built. */ ProtocolProviderServiceJabberImpl .throwOperationFailedException( "Failed to allocate colibri channel.", OperationFailedException.GENERAL_ERROR, null, logger); } } } for (ContentPacketExtension cpe : cpes) { String contentName = cpe.getName(); ContentPacketExtension ourContent = findContentByName(ourAnswer, contentName); //it might be that we decided not to reply to this content if (ourContent != null) { ContentPacketExtension theirContent = (theirOffer == null) ? null : findContentByName(theirOffer, contentName); RtpDescriptionPacketExtension rtpDesc = ourContent.getFirstChildOfType( RtpDescriptionPacketExtension.class); String media = rtpDesc.getMedia(); PacketExtension pe = startCandidateHarvest( theirContent, ourContent, transportInfoSender, media); if (pe != null) ourContent.addChildExtension(pe); } } } /** * Starts transport candidate harvest. This method should complete rapidly * and, in case of lengthy procedures like STUN/TURN/UPnP candidate harvests * are necessary, they should be executed in a separate thread. Candidate * harvest would then need to be concluded in the * {@link #wrapupCandidateHarvest()} method which would be called once we * absolutely need the candidates. * * @param ourOffer the content descriptions that we should be adding our * transport lists to (although not necessarily in this very instance). * @param transportInfoSender the <tt>TransportInfoSender</tt> to be used by * this <tt>TransportManagerJabberImpl</tt> to send <tt>transport-info</tt> * <tt>JingleIQ</tt>s from the local peer to the remote peer if this * <tt>TransportManagerJabberImpl</tt> wishes to utilize * <tt>transport-info</tt>. Local candidate addresses sent by this * <tt>TransportManagerJabberImpl</tt> in <tt>transport-info</tt> are * expected to not be included in the result of * {@link #wrapupCandidateHarvest()}. * @throws OperationFailedException if we fail to allocate a port number. */ public void startCandidateHarvest( List<ContentPacketExtension> ourOffer, TransportInfoSender transportInfoSender) throws OperationFailedException { startCandidateHarvest( /* theirOffer */ null, ourOffer, transportInfoSender); } /** * Notifies the transport manager that it should conclude candidate * harvesting as soon as possible and return the lists of candidates * gathered so far. * * @return the content list that we received earlier (possibly cloned into * a new instance) and that we have updated with transport lists. */ public abstract List<ContentPacketExtension> wrapupCandidateHarvest(); /** * Looks through the <tt>cpExtList</tt> and returns the {@link * ContentPacketExtension} with the specified name. * * @param cpExtList the list that we will be searching for a specific * content. * @param name the name of the content element we are looking for. * @return the {@link ContentPacketExtension} with the specified name or * <tt>null</tt> if no such content element exists. */ public static ContentPacketExtension findContentByName( Iterable<ContentPacketExtension> cpExtList, String name) { for(ContentPacketExtension cpExt : cpExtList) { if(cpExt.getName().equals(name)) return cpExt; } return null; } /** * Starts the connectivity establishment of this * <tt>TransportManagerJabberImpl</tt> i.e. checks the connectivity between * the local and the remote peers given the remote counterpart of the * negotiation between them. * * @param remote the collection of <tt>ContentPacketExtension</tt>s which * represents the remote counterpart of the negotiation between the local * and the remote peer * @return <tt>true</tt> if connectivity establishment has been started in * response to the call; otherwise, <tt>false</tt>. * <tt>TransportManagerJabberImpl</tt> implementations which do not perform * connectivity checks (e.g. raw UDP) should return <tt>true</tt>. The * default implementation does not perform connectivity checks and always * returns <tt>true</tt>. */ public boolean startConnectivityEstablishment( Iterable<ContentPacketExtension> remote) { return true; } /** * Starts the connectivity establishment of this * <tt>TransportManagerJabberImpl</tt> i.e. checks the connectivity between * the local and the remote peers given the remote counterpart of the * negotiation between them. * * @param remote a <tt>Map</tt> of * media-<tt>IceUdpTransportPacketExtension</tt> pairs which represents the * remote counterpart of the negotiation between the local and the remote * peers * @return <tt>true</tt> if connectivity establishment has been started in * response to the call; otherwise, <tt>false</tt>. * <tt>TransportManagerJabberImpl</tt> implementations which do not perform * connectivity checks (e.g. raw UDP) should return <tt>true</tt>. The * default implementation does not perform connectivity checks and always * returns <tt>true</tt>. */ protected boolean startConnectivityEstablishment( Map<String,IceUdpTransportPacketExtension> remote) { return true; } /** * Notifies this <tt>TransportManagerJabberImpl</tt> that it should conclude * any started connectivity establishment. * * @throws OperationFailedException if anything goes wrong with connectivity * establishment (i.e. ICE failed, ...) */ public void wrapupConnectivityEstablishment() throws OperationFailedException { } /** * Removes a content with a specific name from the transport-related part of * the session represented by this <tt>TransportManagerJabberImpl</tt> which * may have been reported through previous calls to the * <tt>startCandidateHarvest</tt> and * <tt>startConnectivityEstablishment</tt> methods. * <p> * <b>Note</b>: Because <tt>TransportManager</tt> deals with * <tt>MediaType</tt>s, not content names and * <tt>TransportManagerJabberImpl</tt> does not implement translating from * content name to <tt>MediaType</tt>, implementers are expected to call * {@link TransportManager#closeStreamConnector(MediaType)}. * </p> * * @param name the name of the content to be removed from the * transport-related part of the session represented by this * <tt>TransportManagerJabberImpl</tt> */ public abstract void removeContent(String name); /** * Removes a content with a specific name from a specific collection of * contents and closes any associated <tt>StreamConnector</tt>. * * @param contents the collection of contents to remove the content with the * specified name from * @param name the name of the content to remove * @return the removed <tt>ContentPacketExtension</tt> if any; otherwise, * <tt>null</tt> */ protected ContentPacketExtension removeContent( Iterable<ContentPacketExtension> contents, String name) { for (Iterator<ContentPacketExtension> contentIter = contents.iterator(); contentIter.hasNext();) { ContentPacketExtension content = contentIter.next(); if (name.equals(content.getName())) { contentIter.remove(); // closeStreamConnector MediaType mediaType = JingleUtils.getMediaType(content); if (mediaType != null) { closeStreamConnector(mediaType); } return content; } } return null; } /** * Clones a specific <tt>IceUdpTransportPacketExtension</tt> and its * candidates. * * @param src the <tt>IceUdpTransportPacketExtension</tt> to be cloned * @return a new <tt>IceUdpTransportPacketExtension</tt> instance which has * the same run-time type, attributes, namespace, text and candidates as the * specified <tt>src</tt> * @throws OperationFailedException if an error occurs during the cloing of * the specified <tt>src</tt> and its candidates */ static IceUdpTransportPacketExtension cloneTransportAndCandidates( IceUdpTransportPacketExtension src) throws OperationFailedException { try { return IceUdpTransportPacketExtension .cloneTransportAndCandidates(src); } catch (Exception e) { ProtocolProviderServiceJabberImpl .throwOperationFailedException( "Failed to close transport and candidates.", OperationFailedException.GENERAL_ERROR, e, logger); } return null; } /** * Releases the resources acquired by this <tt>TransportManager</tt> and * prepares it for garbage collection. */ public void close() { for (MediaType mediaType : MediaType.values()) closeStreamConnector(mediaType); } /** * Closes a specific <tt>StreamConnector</tt> associated with a specific * <tt>MediaType</tt>. If this <tt>TransportManager</tt> has a reference to * the specified <tt>streamConnector</tt>, it remains. * Also expires the <tt>ColibriConferenceIQ.Channel</tt> associated with * the closed <tt>StreamConnector</tt>. * * @param mediaType the <tt>MediaType</tt> associated with the specified * <tt>streamConnector</tt> * @param streamConnector the <tt>StreamConnector</tt> to be closed */ @Override protected void closeStreamConnector( MediaType mediaType, StreamConnector streamConnector) { try { boolean superCloseStreamConnector = true; if (streamConnector instanceof ColibriStreamConnector) { CallPeerJabberImpl peer = getCallPeer(); if (peer != null) { CallJabberImpl call = peer.getCall(); if (call != null) { superCloseStreamConnector = false; call.closeColibriStreamConnector( peer, mediaType, (ColibriStreamConnector) streamConnector); } } } if (superCloseStreamConnector) super.closeStreamConnector(mediaType, streamConnector); } finally { /* * Expire the ColibriConferenceIQ.Channel associated with the closed * StreamConnector. */ if (colibri != null) { ColibriConferenceIQ.Content content = colibri.getContent(mediaType.toString()); if (content != null) { List<ColibriConferenceIQ.Channel> channels = content.getChannels(); if (channels.size() == 2) { ColibriConferenceIQ requestConferenceIQ = new ColibriConferenceIQ(); requestConferenceIQ.setID(colibri.getID()); ColibriConferenceIQ.Content requestContent = requestConferenceIQ.getOrCreateContent( content.getName()); requestContent.addChannel(channels.get(1 /* remote */)); /* * Regardless of whether the request to expire the * Channel associated with mediaType succeeds, consider * the Channel in question expired. Since * RawUdpTransportManager allocates a single channel per * MediaType, consider the whole Content expired. */ colibri.removeContent(content); CallPeerJabberImpl peer = getCallPeer(); if (peer != null) { CallJabberImpl call = peer.getCall(); if (call != null) { call.expireColibriChannels( peer, requestConferenceIQ); } } } } } } } /** * {@inheritDoc} * * Adds support for telephony conferences utilizing the Jitsi Videobridge * server-side technology. * * @see #doCreateStreamConnector(MediaType) */ @Override protected StreamConnector createStreamConnector(final MediaType mediaType) throws OperationFailedException { ColibriConferenceIQ.Channel channel = getColibriChannel(mediaType, true /* local */); if (channel != null) { CallPeerJabberImpl peer = getCallPeer(); CallJabberImpl call = peer.getCall(); StreamConnector streamConnector = call.createColibriStreamConnector( peer, mediaType, channel, new StreamConnectorFactory() { public StreamConnector createStreamConnector() { try { return doCreateStreamConnector(mediaType); } catch (OperationFailedException ofe) { return null; } } }); if (streamConnector != null) return streamConnector; } return doCreateStreamConnector(mediaType); } protected abstract PacketExtension createTransport(String media) throws OperationFailedException; protected PacketExtension createTransportForStartCandidateHarvest( String media) throws OperationFailedException { PacketExtension pe = null; if (getCallPeer().isJitsiVideobridge()) { MediaType mediaType = MediaType.parseString(media); ColibriConferenceIQ.Channel channel = getColibriChannel(mediaType, false /* remote */); if (channel != null) pe = cloneTransportAndCandidates(channel.getTransport()); } else pe = createTransport(media); return pe; } /** * Initializes a new <tt>PacketExtension</tt> instance appropriate to the * type of Jingle transport represented by this <tt>TransportManager</tt>. * The new instance is not initialized with any attributes or child * extensions. * * @return a new <tt>PacketExtension</tt> instance appropriate to the type * of Jingle transport represented by this <tt>TransportManager</tt> */ protected abstract PacketExtension createTransportPacketExtension(); /** * Creates a media <tt>StreamConnector</tt> for a stream of a specific * <tt>MediaType</tt>. The minimum and maximum of the media port boundaries * are taken into account. * * @param mediaType the <tt>MediaType</tt> of the stream for which a * <tt>StreamConnector</tt> is to be created * @return a <tt>StreamConnector</tt> for the stream of the specified * <tt>mediaType</tt> * @throws OperationFailedException if the binding of the sockets fails */ protected StreamConnector doCreateStreamConnector(MediaType mediaType) throws OperationFailedException { return super.createStreamConnector(mediaType); } /** * Finds a <tt>TransportManagerJabberImpl</tt> participating in a telephony * conference utilizing the Jitsi Videobridge server-side technology that * this instance is participating in which is establishing the connectivity * with the Jitsi Videobridge server (as opposed to a <tt>CallPeer</tt>). * * @return a <tt>TransportManagerJabberImpl</tt> which is participating in * a telephony conference utilizing the Jitsi Videobridge server-side * technology that this instance is participating in which is establishing * the connectivity with the Jitsi Videobridge server (as opposed to a * <tt>CallPeer</tt>). */ TransportManagerJabberImpl findTransportManagerEstablishingConnectivityWithJitsiVideobridge() { Call call = getCallPeer().getCall(); TransportManagerJabberImpl transportManager = null; if (call != null) { CallConference conference = call.getConference(); if ((conference != null) && conference.isJitsiVideobridge()) { for (Call aCall : conference.getCalls()) { Iterator<? extends CallPeer> callPeerIter = aCall.getCallPeers(); while (callPeerIter.hasNext()) { CallPeer aCallPeer = callPeerIter.next(); if (aCallPeer instanceof CallPeerJabberImpl) { TransportManagerJabberImpl aTransportManager = ((CallPeerJabberImpl) aCallPeer) .getMediaHandler() .getTransportManager(); if (aTransportManager .isEstablishingConnectivityWithJitsiVideobridge) { transportManager = aTransportManager; break; } } } } } } return transportManager; } /** * Gets the {@link ColibriConferenceIQ.Channel} which belongs to a content * associated with a specific <tt>MediaType</tt> and is to be either locally * or remotely used. * <p> * <b>Note</b>: Modifications to the <tt>ColibriConferenceIQ.Channel</tt> * instance returned by the method propagate to (the state of) this * instance. * </p> * * @param mediaType the <tt>MediaType</tt> associated with the content which * contains the <tt>ColibriConferenceIQ.Channel</tt> to get * @param local <tt>true</tt> if the <tt>ColibriConferenceIQ.Channel</tt> * which is to be used locally is to be returned or <tt>false</tt> for the * one which is to be used remotely * @return the <tt>ColibriConferenceIQ.Channel</tt> which belongs to a * content associated with the specified <tt>mediaType</tt> and which is to * be used in accord with the specified <tt>local</tt> indicator if such a * channel exists; otherwise, <tt>null</tt> */ ColibriConferenceIQ.Channel getColibriChannel( MediaType mediaType, boolean local) { ColibriConferenceIQ.Channel channel = null; if (colibri != null) { ColibriConferenceIQ.Content content = colibri.getContent(mediaType.toString()); if (content != null) { List<ColibriConferenceIQ.Channel> channels = content.getChannels(); if (channels.size() == 2) channel = channels.get(local ? 0 : 1); } } return channel; } }