/* * 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.service.protocol.media; import java.beans.*; import java.util.*; import net.java.sip.communicator.service.protocol.*; import net.java.sip.communicator.service.protocol.event.*; import net.java.sip.communicator.util.*; import org.jitsi.service.neomedia.*; import org.jitsi.service.neomedia.event.*; /** * A utility class implementing media control code shared between current * telephony implementations. This class is only meant for use by protocol * implementations and should/could not be accessed by bundles that are simply * using the telephony functionalities. * * @param the peer extension class like for example CallSipImpl * or CallJabberImpl * @param the media handler extension class like for example * CallPeerMediaHandlerSipImpl or * CallPeerMediaHandlerJabberImpl * @param the provider extension class like for example * ProtocolProviderServiceSipImpl or * ProtocolProviderServiceJabberImpl * * @author Emil Ivov * @author Lyubomir Marinov * @author Boris Grozev */ public abstract class MediaAwareCallPeer , U extends CallPeerMediaHandler, V extends ProtocolProviderService> extends AbstractCallPeer implements SrtpListener, CallPeerConferenceListener, CsrcAudioLevelListener, SimpleAudioLevelListener { /** * The Logger used by the MediaAwareCallPeer class and its * instances for logging output. */ private static final Logger logger = Logger.getLogger(MediaAwareCallPeer.class); /** * The call this peer belongs to. */ private T call; /** * The listeners registered for level changes in the audio of participants * that this peer might be mixing and that we are not directly communicating * with. */ private final List conferenceMembersSoundLevelListeners = new ArrayList(); /** * A byte array containing the image/photo representing the call peer. */ private byte[] image; /** * The media handler class handles all media management for a single * CallPeer. This includes initializing and configuring streams, * generating SDP, handling ICE, etc. One instance of CallPeeralways * corresponds to exactly one instance of CallPeerMediaHandler and * both classes are only separated for reasons of readability. */ private U mediaHandler; /** * The PropertyChangeListener which listens to * {@link CallPeerMediaHandler} for changes in the values of its properties. */ private PropertyChangeListener mediaHandlerPropertyChangeListener; /** * A string uniquely identifying the peer. */ private String peerID; /** * The protocol provider that this peer belongs to. */ private final V protocolProvider; /** * The list of SoundLevelListeners interested in level changes in * the audio we are getting from the remote peer. *

* It is implemented as a copy-on-write storage because the number of * additions and removals of SoundLevelListeners is expected to be * far smaller than the number of audio level changes. The access to it is * to be synchronized using {@link #streamSoundLevelListenersSyncRoot}. *

*/ private List streamSoundLevelListeners; /** * The Object to synchronize the access to * {@link #streamSoundLevelListeners}. */ private final Object streamSoundLevelListenersSyncRoot = new Object(); /** * The List of PropertyChangeListeners listening to this * CallPeer for changes in the values of its properties related to * video. */ private final List videoPropertyChangeListeners = new LinkedList(); /** * Represents the last Conference Information (RFC4575) document sent to * this CallPeer. This is always a document with state "full", even * if the last document actually sent was a "partial" */ private ConferenceInfoDocument lastConferenceInfoSent = null; /** * The time (as obtained by System.currentTimeMillis()) at which * a Conference Information (RFC4575) document was last sent to this * CallPeer. */ private long lastConferenceInfoSentTimestamp = -1; /** * The last Conference Information (RFC4575) document sent to us by this * CallPeer. This is always a document with state "full", which is * only gets updated by "partial" or "deleted" documents. */ private ConferenceInfoDocument lastConferenceInfoReceived = null; /** * Whether a conference-info document has been scheduled to be sent to this * CallPeer */ private boolean confInfoScheduled = false; /** * Synchronization object for confInfoScheduled */ private final Object confInfoScheduledSyncRoot = new Object(); /** * Creates a new call peer with address peerAddress. * * @param owningCall the call that contains this call peer. */ public MediaAwareCallPeer(T owningCall) { this.call = owningCall; this.protocolProvider = owningCall.getProtocolProvider(); // create the uid this.peerID = String.valueOf(System.currentTimeMillis()) + String.valueOf(hashCode()); // we listen for events when the call will become focus or not // of a conference so we will add or remove our sound level listeners super.addCallPeerConferenceListener(this); } /** * Adds a specific ConferenceMembersSoundLevelListener to the list * of listeners interested in and notified about changes in conference * members sound level. * * @param listener the ConferenceMembersSoundLevelListener to add * @throws NullPointerException if listener is null */ public void addConferenceMembersSoundLevelListener( ConferenceMembersSoundLevelListener listener) { /* * XXX The uses of the method at the time of this writing rely on being * able to add a null listener so make it a no-op here. */ if (listener == null) return; synchronized (conferenceMembersSoundLevelListeners) { if (conferenceMembersSoundLevelListeners.size() == 0) { // if this is the first listener that's being registered with // us, we also need to register ourselves as a CSRC audio level // listener with the media handler. getMediaHandler().setCsrcAudioLevelListener(this); } conferenceMembersSoundLevelListeners.add(listener); } } /** * Adds a specific SoundLevelListener to the list of listeners * interested in and notified about changes in the sound level of the audio * sent by the remote party. When the first listener is being registered * the method also registers its single listener with the media handler so * that it would receive level change events and delegate them to the * listeners that have registered with us. * * @param listener the SoundLevelListener to add */ public void addStreamSoundLevelListener(SoundLevelListener listener) { synchronized (streamSoundLevelListenersSyncRoot) { if ((streamSoundLevelListeners == null) || streamSoundLevelListeners.isEmpty()) { CallPeerMediaHandler mediaHandler = getMediaHandler(); if (isJitsiVideobridge()) { /* * When the local user/peer has organized a telephony * conference utilizing the Jitsi Videobridge server-side * technology, the server will calculate the audio levels * and not the client. */ mediaHandler.setCsrcAudioLevelListener(this); } else { /* * If this is the first listener that's being registered * with us, we also need to register ourselves as an audio * level listener with the media handler. We do this so that * audio levels would only be calculated if anyone is * interested in receiving them. */ mediaHandler.setStreamAudioLevelListener(this); } } /* * Implement streamAudioLevelListeners as a copy-on-write storage so * that iterators over it can iterate without * ConcurrentModificationExceptions. */ streamSoundLevelListeners = (streamSoundLevelListeners == null) ? new ArrayList() : new ArrayList( streamSoundLevelListeners); streamSoundLevelListeners.add(listener); } } /** * Adds a specific PropertyChangeListener to the list of * listeners which get notified when the properties (e.g. * LOCAL_VIDEO_STREAMING) associated with this CallPeer change * their values. * * @param listener the PropertyChangeListener to be notified * when the properties associated with the specified Call change * their values */ public void addVideoPropertyChangeListener(PropertyChangeListener listener) { if (listener == null) throw new NullPointerException("listener"); synchronized (videoPropertyChangeListeners) { /* * The video is part of the media-related functionality and thus it * is the responsibility of mediaHandler. So listen to mediaHandler * for video-related property changes and re-fire them as * originating from this instance. */ if (!videoPropertyChangeListeners.contains(listener) && videoPropertyChangeListeners.add(listener) && (mediaHandlerPropertyChangeListener == null)) { mediaHandlerPropertyChangeListener = new PropertyChangeListener() { public void propertyChange(PropertyChangeEvent event) { Iterable listeners; synchronized (videoPropertyChangeListeners) { listeners = new LinkedList( videoPropertyChangeListeners); } PropertyChangeEvent thisEvent = new PropertyChangeEvent( this, event.getPropertyName(), event.getOldValue(), event.getNewValue()); for (PropertyChangeListener listener : listeners) listener.propertyChange(thisEvent); } }; getMediaHandler() .addPropertyChangeListener( mediaHandlerPropertyChangeListener); } } } /** * Notified by its very majesty the media service about changes in the audio * level of the stream coming from this peer, the method generates the * corresponding events and delivers them to the listeners that have * registered here. * * @param newLevel the new audio level of the audio stream received from the * remote peer */ public void audioLevelChanged(int newLevel) { /* * If we're in a conference in which this CallPeer is the focus and * we're the only member in it besides the focus, we will not receive * audio levels in the RTP and our media will instead measure the audio * levels of the received media. In order to make the UI oblivious of * the difference, we have to translate the event to the appropriate * type of listener. * * We may end up in a conference call with 0 members if the server * for some reason doesn't support sip conference (our subscribes * doesn't go to the focus of the conference) and so we must * pass the sound levels measured on the stream so we can see * the stream activity of the call. */ int conferenceMemberCount = getConferenceMemberCount(); if ((conferenceMemberCount > 0) && (conferenceMemberCount < 3)) { long audioRemoteSSRC = getMediaHandler().getRemoteSSRC(MediaType.AUDIO); if (audioRemoteSSRC != CallPeerMediaHandler.SSRC_UNKNOWN) { audioLevelsReceived(new long[] { audioRemoteSSRC, newLevel }); return; } } fireStreamSoundLevelChanged(newLevel); } /** * Implements {@link CsrcAudioLevelListener#audioLevelsReceived(long[])}. * Delivers the received audio levels to the * {@link ConferenceMembersSoundLevelListener}s registered with this * MediaAwareCallPeer.. * * @param audioLevels the levels that we need to dispatch to all registered * ConferenceMemberSoundLevelListeners. */ public void audioLevelsReceived(long[] audioLevels) { /* * When the local user/peer has organized a telephony conference * utilizing the Jitsi Videobridge server-side technology, the server * will calculate the audio levels and not the client. */ if (isJitsiVideobridge()) { long audioRemoteSSRC = getMediaHandler().getRemoteSSRC(MediaType.AUDIO); if (audioRemoteSSRC != CallPeerMediaHandler.SSRC_UNKNOWN) { for (int i = 0; i < audioLevels.length; i += 2) { if (audioLevels[i] == audioRemoteSSRC) { fireStreamSoundLevelChanged((int) audioLevels[i + 1]); break; } } } } if (getConferenceMemberCount() == 0) return; Map levelsMap = new HashMap(); for (int i = 0; i < audioLevels.length; i += 2) { ConferenceMember mmbr = findConferenceMember(audioLevels[i]); if (mmbr != null) levelsMap.put(mmbr, (int) audioLevels[i + 1]); } synchronized (conferenceMembersSoundLevelListeners) { int conferenceMemberSoundLevelListenerCount = conferenceMembersSoundLevelListeners.size(); if (conferenceMemberSoundLevelListenerCount > 0) { ConferenceMembersSoundLevelEvent ev = new ConferenceMembersSoundLevelEvent(this, levelsMap); for (int i = 0; i < conferenceMemberSoundLevelListenerCount; i++) { conferenceMembersSoundLevelListeners .get(i) .soundLevelChanged(ev); } } } } /** * Does nothing. * @param evt the event. */ public void callPeerAdded(CallPeerEvent evt) {} /** * Does nothing. * @param evt the event. */ public void callPeerRemoved(CallPeerEvent evt) {} /** * Dummy implementation of {@link CallPeerConferenceListener * #conferenceFocusChanged(CallPeerConferenceEvent)}. * * @param evt ignored */ public void conferenceFocusChanged(CallPeerConferenceEvent evt) { } /** * Called when this peer becomes a mixer. The method add removes this class * as the stream audio level listener for the media coming from this peer * because the levels it delivers no longer represent the level of a * particular member. The method also adds this class as a member (CSRC) * audio level listener. * * @param conferenceEvent the event containing information (that we don't * really use) on the newly add member. */ public void conferenceMemberAdded(CallPeerConferenceEvent conferenceEvent) { if (getConferenceMemberCount() > 2) { /* * This peer is now a conference focus with more than three * participants. It means that this peer is mixing and sending us * audio for at least two separate participants. We therefore need * to switch from stream to CSRC level listening. */ CallPeerMediaHandler mediaHandler = getMediaHandler(); mediaHandler.setStreamAudioLevelListener(null); mediaHandler.setCsrcAudioLevelListener(this); } } /** * Dummy implementation of {@link CallPeerConferenceListener * #conferenceMemberErrorReceived(CallPeerConferenceEvent)}. * * @param ev the event */ public void conferenceMemberErrorReceived(CallPeerConferenceEvent ev) {}; /** * Called when this peer stops being a mixer. The method add removes this * class as the stream audio level listener for the media coming from this * peer because the levels it delivers no longer represent the level of a * particular member. The method also adds this class as a member (CSRC) * audio level listener. * * @param conferenceEvent the event containing information (that we don't * really use) on the freshly removed member. */ public void conferenceMemberRemoved(CallPeerConferenceEvent conferenceEvent) { if (getConferenceMemberCount() < 3) { /* * This call peer is no longer mixing audio from multiple sources * since there's only us and her in the call. We therefore need to * switch from CSRC to stream level listening. */ CallPeerMediaHandler mediaHandler = getMediaHandler(); mediaHandler.setStreamAudioLevelListener(this); mediaHandler.setCsrcAudioLevelListener(null); } } /** * Invokes {@link SoundLevelListener#soundLevelChanged(Object, int) on * the SoundLevelListeners interested in the changes of the audio * stream received from the remote peer i.e. in * {@link #streamSoundLevelListeners}. * * @param newLevel the new value of the sound level to notify * streamSoundLevelListeners about */ private void fireStreamSoundLevelChanged(int newLevel) { List streamSoundLevelListeners; synchronized (streamSoundLevelListenersSyncRoot) { /* * Since the streamAudioLevelListeners field of this * MediaAwareCallPeer is implemented as a copy-on-write storage, * just get a reference to it and it should be safe to iterate over it * without ConcurrentModificationExceptions. */ streamSoundLevelListeners = this.streamSoundLevelListeners; } if (streamSoundLevelListeners != null) { /* * Iterate over streamAudioLevelListeners using an index rather than * an Iterator in order to try to reduce the number of allocations * (as the number of audio level changes is expected to be very * large). */ int streamSoundLevelListenerCount = streamSoundLevelListeners.size(); for(int i = 0; i < streamSoundLevelListenerCount; i++) { streamSoundLevelListeners.get(i).soundLevelChanged( this, newLevel); } } } /** * Returns a reference to the call that this peer belongs to. Calls * are created by underlying telephony protocol implementations. * * @return a reference to the call containing this peer. */ @Override public T getCall() { return call; } /** * The method returns an image representation of the call peer if one is * available. * * @return byte[] a byte array containing the image or null if no image is * available. */ public byte[] getImage() { return image; } /** * Returns a reference to the CallPeerMediaHandler used by this * peer. The media handler class handles all media management for a single * CallPeer. This includes initializing and configuring streams, * generating SDP, handling ICE, etc. One instance of CallPeer * always corresponds to exactly one instance of * CallPeerMediaHandler and both classes are only separated for * reasons of readability. * * @return a reference to the CallPeerMediaHandler instance that * this peer uses for media related tips and tricks. */ public U getMediaHandler() { return mediaHandler; } /** * Returns a unique identifier representing this peer. * * @return an identifier representing this call peer. */ public String getPeerID() { return peerID; } /** * Returns the protocol provider that this peer belongs to. * * @return a reference to the ProtocolProviderService that this * peer belongs to. */ @Override public V getProtocolProvider() { return protocolProvider; } /** * Determines whether this CallPeer is participating in a telephony * conference organized by the local user/peer utilizing the Jitsi * Videobridge server-side technology. * * @return true if this CallPeer is participating in a * telephony conference organized by the local user/peer utilizing the Jitsi * Videobridge server-side technology; otherwise, false */ public final boolean isJitsiVideobridge() { Call call = getCall(); if (call != null) { CallConference conference = call.getConference(); if (conference != null) return conference.isJitsiVideobridge(); } return false; } /** * Determines whether we are currently streaming video toward whoever this * MediaAwareCallPeer represents. * * @return true if we are currently streaming video toward this * CallPeer and false otherwise. */ public boolean isLocalVideoStreaming() { return getMediaHandler().isLocalVideoTransmissionEnabled(); } /** * Determines whether the audio stream (if any) being sent to this * peer is mute. * * @return true if an audio stream is being sent to this * peer and it is currently mute; false, otherwise */ @Override public boolean isMute() { return getMediaHandler().isMute(); } /** * Logs message and cause and sets this peer's * state to CallPeerState.FAILED * * @param message a message to log and display to the user. * @param throwable the exception that cause the error we are logging */ public void logAndFail(String message, Throwable throwable) { logger.error(message, throwable); setState(CallPeerState.FAILED, message); } /** * Updates the state of this CallPeer to match the locally-on-hold * status of our media handler. */ public void reevalLocalHoldStatus() { CallPeerState state = getState(); boolean locallyOnHold = getMediaHandler().isLocallyOnHold(); if (CallPeerState.ON_HOLD_LOCALLY.equals(state)) { if (!locallyOnHold) setState(CallPeerState.CONNECTED); } else if (CallPeerState.ON_HOLD_MUTUALLY.equals(state)) { if (!locallyOnHold) setState(CallPeerState.ON_HOLD_REMOTELY); } else if (CallPeerState.ON_HOLD_REMOTELY.equals(state)) { if (locallyOnHold) setState(CallPeerState.ON_HOLD_MUTUALLY); } else if (locallyOnHold) { setState(CallPeerState.ON_HOLD_LOCALLY); } } /** * Updates the state of this CallPeer to match the remotely-on-hold * status of our media handler. */ public void reevalRemoteHoldStatus() { boolean remotelyOnHold = getMediaHandler().isRemotelyOnHold(); CallPeerState state = getState(); if (CallPeerState.ON_HOLD_LOCALLY.equals(state)) { if (remotelyOnHold) setState(CallPeerState.ON_HOLD_MUTUALLY); } else if (CallPeerState.ON_HOLD_MUTUALLY.equals(state)) { if (!remotelyOnHold) setState(CallPeerState.ON_HOLD_LOCALLY); } else if (CallPeerState.ON_HOLD_REMOTELY.equals(state)) { if (!remotelyOnHold) setState(CallPeerState.CONNECTED); } else if (remotelyOnHold) { setState(CallPeerState.ON_HOLD_REMOTELY); } } /** * Removes a specific ConferenceMembersSoundLevelListener of the * list of listeners interested in and notified about changes in conference * members sound level. * * @param listener the ConferenceMembersSoundLevelListener to * remove */ public void removeConferenceMembersSoundLevelListener( ConferenceMembersSoundLevelListener listener) { synchronized (conferenceMembersSoundLevelListeners) { if (conferenceMembersSoundLevelListeners.remove(listener) && (conferenceMembersSoundLevelListeners.size() == 0)) { // if this was the last listener then we also remove ourselves // as a CSRC audio level listener from the handler so that we // don't have to create new events and maps for something no one // is interested in. getMediaHandler().setCsrcAudioLevelListener(null); } } } /** * Removes a specific SoundLevelListener of the list of * listeners interested in and notified about changes in stream sound level * related information. * * @param listener the SoundLevelListener to remove */ public void removeStreamSoundLevelListener(SoundLevelListener listener) { synchronized (streamSoundLevelListenersSyncRoot) { /* * Implement streamAudioLevelListeners as a copy-on-write storage so * that iterators over it can iterate over it without * ConcurrentModificationExceptions. */ if (streamSoundLevelListeners != null) { streamSoundLevelListeners = new ArrayList( streamSoundLevelListeners); if (streamSoundLevelListeners.remove(listener) && streamSoundLevelListeners.isEmpty()) streamSoundLevelListeners = null; } if ((streamSoundLevelListeners == null) || streamSoundLevelListeners.isEmpty()) { // if this was the last listener then we also need to remove // ourselves as an audio level so that audio levels would only // be calculated if anyone is interested in receiving them. getMediaHandler().setStreamAudioLevelListener(null); } } } /** * Removes a specific PropertyChangeListener from the list of * listeners which get notified when the properties (e.g. * LOCAL_VIDEO_STREAMING) associated with this CallPeer change * their values. * * @param listener the PropertyChangeListener to no longer be * notified when the properties associated with the specified Call * change their values */ public void removeVideoPropertyChangeListener( PropertyChangeListener listener) { if (listener != null) synchronized (videoPropertyChangeListeners) { /* * The video is part of the media-related functionality and thus * it is the responsibility of mediaHandler. So we're listening * to mediaHandler for video-related property changes and w're * re-firing them as originating from this instance. Make sure * that we're not listening to mediaHandler if noone is * interested in video-related property changes originating from * this instance. */ if (videoPropertyChangeListeners.remove(listener) && videoPropertyChangeListeners.isEmpty() && (mediaHandlerPropertyChangeListener != null)) { // getMediaHandler() // .removePropertyChangeListener( // mediaHandlerPropertyChangeListener); mediaHandlerPropertyChangeListener = null; } } } /** * Sets the security message associated with a failure/warning or * information coming from the encryption protocol. * * @param messageType the type of the message. * @param i18nMessage the message * @param severity severity level */ public void securityMessageReceived( String messageType, String i18nMessage, int severity) { fireCallPeerSecurityMessageEvent(messageType, i18nMessage, severity); } /** * Indicates that the other party has timeouted replying to our * offer to secure the connection. * * @param mediaType the MediaType of the call session * @param sender the security controller that caused the event */ public void securityNegotiationStarted( MediaType mediaType, SrtpControl sender) { fireCallPeerSecurityNegotiationStartedEvent( new CallPeerSecurityNegotiationStartedEvent( this, toSessionType(mediaType), sender)); } /** * Indicates that the other party has timeouted replying to our * offer to secure the connection. * * @param mediaType the MediaType of the call session */ public void securityTimeout(MediaType mediaType) { fireCallPeerSecurityTimeoutEvent( new CallPeerSecurityTimeoutEvent( this, toSessionType(mediaType))); } /** * Sets the security status to OFF for this call peer. * * @param mediaType the MediaType of the call session */ public void securityTurnedOff(MediaType mediaType) { // If this event has been triggered because of a call end event and the // call is already ended we don't need to alert the user for // security off. if((call != null) && !call.getCallState().equals(CallState.CALL_ENDED)) { fireCallPeerSecurityOffEvent( new CallPeerSecurityOffEvent( this, toSessionType(mediaType))); } } /** * Sets the security status to ON for this call peer. * * @param mediaType the MediaType of the call session * @param cipher the cipher * @param sender the security controller that caused the event */ public void securityTurnedOn( MediaType mediaType, String cipher, SrtpControl sender) { getMediaHandler().startSrtpMultistream(sender); fireCallPeerSecurityOnEvent( new CallPeerSecurityOnEvent( this, toSessionType(mediaType), cipher, sender)); } /** * Sets the call containing this peer. * * @param call the call that this call peer is participating in. */ public void setCall(T call) { this.call = call; } /** * Sets the byte array containing an image representation (photo or picture) * of the call peer. * * @param image a byte array containing the image */ public void setImage(byte[] image) { byte[] oldImage = getImage(); this.image = image; //Fire the Event fireCallPeerChangeEvent( CallPeerChangeEvent.CALL_PEER_IMAGE_CHANGE, oldImage, image); } /** * 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 * this class using a corresponding SDP description.. * * @param allowed true if local video transmission is allowed and * false otherwise. * * @throws OperationFailedException if video initialization fails. */ public void setLocalVideoAllowed(boolean allowed) throws OperationFailedException { CallPeerMediaHandler mediaHandler = getMediaHandler(); if(mediaHandler.isLocalVideoTransmissionEnabled() != allowed) { // Modify the local media setup to reflect the requested setting for // the streaming of the local video. mediaHandler.setLocalVideoTransmissionEnabled(allowed); } } /** * Sets a reference to the CallPeerMediaHandler used by this * peer. The media handler class handles all media management for a single * CallPeer. This includes initializing and configuring streams, * generating SDP, handling ICE, etc. One instance of CallPeer * always corresponds to exactly one instance of * CallPeerMediaHandler and both classes are only separated for * reasons of readability. * * @param mediaHandler a reference to the CallPeerMediaHandler * instance that this peer uses for media related tips and tricks. */ protected void setMediaHandler(U mediaHandler) { this.mediaHandler = mediaHandler; } /** * Sets the mute property for this call peer. * * @param newMuteValue the new value of the mute property for this call peer */ @Override public void setMute(boolean newMuteValue) { getMediaHandler().setMute(newMuteValue); super.setMute(newMuteValue); } /** * Sets the String that serves as a unique identifier of this * CallPeer. * @param peerID the ID of this call peer. */ public void setPeerID(String peerID) { this.peerID = peerID; } /** * Overrides the parent set state method in order to make sure that we * close our media handler whenever we enter a disconnected state. * * @param newState the CallPeerState that we are about to enter and * that we pass to our predecessor. * @param reason a reason phrase explaining the state (e.g. if newState * indicates a failure) and that we pass to our predecessor. * @param reasonCode the code for the reason of the state change. */ @Override public void setState(CallPeerState newState, String reason, int reasonCode) { // synchronized to mediaHandler if there are currently jobs of // initializing, configuring and starting streams (method processAnswer // of CallPeerMediaHandler) we won't set and fire the current state // to Disconnected. Before closing the mediaHandler is setting the state // in order to deliver states as quick as possible. CallPeerMediaHandler mediaHandler = getMediaHandler(); synchronized(mediaHandler) { try { super.setState(newState, reason, reasonCode); } finally { // make sure whatever happens to close the media if (CallPeerState.DISCONNECTED.equals(newState) || CallPeerState.FAILED.equals(newState)) mediaHandler.close(); } } } /** * Returns the last ConferenceInfoDocument sent by us to this * CallPeer. It is a document with state full * @return the last ConferenceInfoDocument sent by us to this * CallPeer. It is a document with state full */ public ConferenceInfoDocument getLastConferenceInfoSent() { return lastConferenceInfoSent; } /** * Sets the last ConferenceInfoDocument sent by us to this * CallPeer. * @param confInfo the document to set. */ public void setLastConferenceInfoSent(ConferenceInfoDocument confInfo) { lastConferenceInfoSent = confInfo; } /** * Gets the time (as obtained by System.currentTimeMillis()) * at which we last sent a ConferenceInfoDocument to this * CallPeer. * @return the time (as obtained by System.currentTimeMillis()) * at which we last sent a ConferenceInfoDocument to this * CallPeer. */ public long getLastConferenceInfoSentTimestamp() { return lastConferenceInfoSentTimestamp; } /** * Sets the time (as obtained by System.currentTimeMillis()) * at which we last sent a ConferenceInfoDocument to this * CallPeer. * @param newTimestamp the time to set */ public void setLastConferenceInfoSentTimestamp(long newTimestamp) { lastConferenceInfoSentTimestamp = newTimestamp; } /** * Gets the last ConferenceInfoDocument sent to us by this * CallPeer. * @return the last ConferenceInfoDocument sent to us by this * CallPeer. */ public ConferenceInfoDocument getLastConferenceInfoReceived() { return lastConferenceInfoReceived; } /** * Gets the last ConferenceInfoDocument sent to us by this * CallPeer. * @return the last ConferenceInfoDocument sent to us by this * CallPeer. */ public void setLastConferenceInfoReceived(ConferenceInfoDocument confInfo) { lastConferenceInfoReceived = confInfo; } /** * Gets the version of the last ConferenceInfoDocument * sent to us by this CallPeer, or -1 if we haven't (yet) received * a ConferenceInformationDocument from this CallPeer. * @return */ public int getLastConferenceInfoReceivedVersion() { return (lastConferenceInfoReceived == null) ? -1 : lastConferenceInfoReceived.getVersion(); } /** * Gets the String to be used for this CallPeer when * we describe it in a ConferenceInfoDocument (e.g. the * entity key attribute which to use for the user * element corresponding to this CallPeer) * * @return the String to be used for this CallPeer when * we describe it in a ConferenceInfoDocument (e.g. the * entity key attribute which to use for the user * element corresponding to this CallPeer) */ public abstract String getEntity(); /** * Check whether a conference-info document is scheduled to be sent to * this CallPeer (i.e. there is a thread which will eventually * (after sleeping a certain amount of time) trigger a document to be sent) * @return true if there is a conference-info document scheduled * to be sent to this CallPeer and false otherwise. */ public boolean isConfInfoScheduled() { synchronized (confInfoScheduledSyncRoot) { return confInfoScheduled; } } /** * Sets the property which indicates whether a conference-info document * is scheduled to be sent to this CallPeer. * @param confInfoScheduled */ public void setConfInfoScheduled(boolean confInfoScheduled) { synchronized (confInfoScheduledSyncRoot) { this.confInfoScheduled = confInfoScheduled; } } /** * Returns the direction of the session for media of type mediaType * that we have with this CallPeer. This is the direction of the * session negotiated in the signaling protocol, and it may or may not * coincide with the direction of the media stream. * For example, if we are the focus of a videobridge conference and another * peer is sending video to us, we have a RECVONLY video stream, * but SENDONLY or SENDRECV (Jingle) sessions with the * rest of the conference members. * Should always return non-null. * * @param mediaType the MediaType to use * @return Returns the direction of the session for media of type * mediaType that we have with this CallPeer. */ public abstract MediaDirection getDirection(MediaType mediaType); /** * {@inheritDoc} * * When a ConferenceMember is removed from a conference with a * Jitsi-videobridge, an RTCP BYE packet is not always sent. Therefore, * if the ConferenceMember had an associated video SSRC, the stream * isn't be removed until it times out, leaving a blank video container in * the interface for a few seconds. * TODO: This works around the problem by removing the * ConferenceMember's ReceiveStream when the * ConferenceMember is removed. The proper solution is to ensure * that RTCP BYEs are sent whenever necessary, and when it is deployed this * code should be removed. * * @param conferenceMember a ConferenceMember to be removed from * the list of ConferenceMember reported by this peer. If the * specified ConferenceMember is no contained in the list, no event */ @Override public void removeConferenceMember(ConferenceMember conferenceMember) { MediaStream videoStream = getMediaHandler().getStream(MediaType.VIDEO); if (videoStream != null) videoStream.removeReceiveStreamForSsrc( conferenceMember.getVideoSsrc()); super.removeConferenceMember(conferenceMember); } /** * Converts a specific MediaType into a sessionType value * in the terms of the CallPeerSecurityStatusEvent class. * * @param mediaType the MediaType to be converted * @return the sessionType value in the terms of the * CallPeerSecurityStatusEvent class that is equivalent to the * specified mediaType */ private static int toSessionType(MediaType mediaType) { switch (mediaType) { case AUDIO: return CallPeerSecurityStatusEvent.AUDIO_SESSION; case VIDEO: return CallPeerSecurityStatusEvent.VIDEO_SESSION; default: throw new IllegalArgumentException("mediaType"); } } }