/*
* 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.service.protocol.event.DTMFListener;
import org.jitsi.service.neomedia.*;
import org.jitsi.service.neomedia.device.*;
import org.jitsi.service.neomedia.event.*;
import org.jitsi.service.neomedia.recording.*;
/**
* 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 <T> the peer extension class like, for example,
* <tt>CallPeerSipImpl</tt> or <tt>CallPeerJabberImpl</tt>
* @param <U> the provider extension class like, for example,
* <tt>OperationSetBasicTelephonySipImpl</tt> or
* <tt>OperationSetBasicTelephonySipImpl</tt>
* @param <V> the provider extension class like, for example,
* <tt>ProtocolProviderServiceSipImpl</tt> or
* <tt>ProtocolProviderServiceJabberImpl</tt>
*
* @author Emil Ivov
* @author Lyubomir Marinov
*/
public abstract class MediaAwareCall<
T extends MediaAwareCallPeer<?, ?, V>,
U extends OperationSetBasicTelephony<V>,
V extends ProtocolProviderService>
extends AbstractCall<T, V>
implements CallPeerListener,
PropertyChangeListener,
DTMFListener
{
/**
* The name of the property of <tt>MediaAwareCall</tt> the value of which
* corresponds to the value returned by
* {@link #getDefaultDevice(MediaType)}. The <tt>oldValue</tt>
* and the <tt>newValue</tt> of the fired <tt>PropertyChangeEvent</tt> are
* not to be relied on and instead a call to <tt>getDefaultDevice</tt> is to
* be performed to retrieve the new value.
*/
public static final String DEFAULT_DEVICE = "defaultDevice";
/**
* Our video streaming policy.
*/
protected boolean localVideoAllowed = false;
/**
* The <tt>OperationSetBasicTelephony</tt> implementation which created us.
*/
protected final U parentOpSet;
/**
* The list of <tt>SoundLevelListener</tt>s interested in level changes of
* local audio.
* <p>
* It is implemented as a copy-on-write storage because the number of
* additions and removals of <tt>SoundLevelListener</tt>s is expected to be
* far smaller than the number of audio level changes. The access to it is
* to be synchronized using {@link #localUserAudioLevelListenersSyncRoot}.
* </p>
*/
private List<SoundLevelListener> localUserAudioLevelListeners;
/**
* The <tt>Object</tt> to synchronize the access to
* {@link #localUserAudioLevelListeners}.
*/
private final Object localUserAudioLevelListenersSyncRoot = new Object();
/**
* The indicator which determines whether this <tt>Call</tt> is set
* to transmit "silence" instead of the actual media.
*/
private boolean mute = false;
/**
* Device used in call will be chosen according to <tt>MediaUseCase</tt>.
*/
protected MediaUseCase mediaUseCase = MediaUseCase.ANY;
/**
* The listener that would actually subscribe for level events from the
* media handler if there's at least one listener in
* <tt>localUserAudioLevelListeners</tt>.
*/
private final SimpleAudioLevelListener localAudioLevelDelegator
= new SimpleAudioLevelListener()
{
public void audioLevelChanged(int level)
{
fireLocalUserAudioLevelChangeEvent(level);
}
};
/**
* Crates a <tt>Call</tt> instance belonging to <tt>parentOpSet</tt>.
*
* @param parentOpSet a reference to the operation set that's creating us
* and that we would be able to use for even dispatching.
*/
protected MediaAwareCall(U parentOpSet)
{
super(parentOpSet.getProtocolProvider());
this.parentOpSet = parentOpSet;
}
/**
* Adds <tt>callPeer</tt> to the list of peers in this call.
* If the call peer is already included in the call, the method has
* no effect.
*
* @param callPeer the new <tt>CallPeer</tt>
*/
protected void addCallPeer(T callPeer)
{
if (!doAddCallPeer(callPeer))
return;
callPeer.addCallPeerListener(this);
synchronized(localUserAudioLevelListenersSyncRoot)
{
/*
* If there's someone listening for audio level events, then they'd
* also like to know about the new peer. Make sure the first element
* is always the one to listen for local audio events.
*/
List<T> callPeers = getCallPeerList();
if ((callPeers.size() == 1) && callPeers.get(0).equals(callPeer))
{
callPeer.getMediaHandler().setLocalUserAudioLevelListener(
localAudioLevelDelegator);
}
}
fireCallPeerEvent(callPeer, CallPeerEvent.CALL_PEER_ADDED);
}
/**
* Removes <tt>callPeer</tt> from the list of peers in this call. The method
* has no effect if there was no such peer in the call.
*
* @param evt the event containing the <tt>CallPeer</tt> leaving the call and
* the reason (if any) for the <tt>CallPeerChangeEvent</tt>. Use the event
* as the cause for the call state change event.
*/
private void removeCallPeer(CallPeerChangeEvent evt)
{
@SuppressWarnings("unchecked")
T callPeer = (T) evt.getSourceCallPeer();
if (!doRemoveCallPeer(callPeer))
return;
callPeer.removeCallPeerListener(this);
synchronized (localUserAudioLevelListenersSyncRoot)
{
// remove sound level listeners from the peer
callPeer.getMediaHandler().setLocalUserAudioLevelListener(null);
// if there are more peers and the peer was the first, the one
// that listens for local levels, now lets make sure
// the new first will listen for local level events
List<T> callPeers = getCallPeerList();
if(!callPeers.isEmpty())
{
callPeers
.get(0)
.getMediaHandler()
.setLocalUserAudioLevelListener(
localAudioLevelDelegator);
}
}
try
{
fireCallPeerEvent(callPeer, CallPeerEvent.CALL_PEER_REMOVED,
(evt.getReasonString() != null));
}
finally
{
/*
* The peer should lose its state once it has finished firing the
* events in order to allow the listeners to undo.
*/
callPeer.setCall(null);
}
if (getCallPeerCount() == 0)
setCallState(CallState.CALL_ENDED, evt);
}
/**
* Dummy implementation of a method (inherited from CallPeerListener)
* that we don't need.
*
* @param evt unused.
*/
public void peerImageChanged(CallPeerChangeEvent evt)
{
//does not concern us
}
/**
* Dummy implementation of a method (inherited from CallPeerListener)
* that we don't need.
*
* @param evt unused.
*/
public void peerAddressChanged(CallPeerChangeEvent evt)
{
//does not concern us
}
/**
* Dummy implementation of a method (inherited from CallPeerListener)
* that we don't need.
*
* @param evt unused.
*/
public void peerTransportAddressChanged(
CallPeerChangeEvent evt)
{
//does not concern us
}
/**
* Dummy implementation of a method (inherited from CallPeerListener)
* that we don't need.
*
* @param evt unused.
*/
public void peerDisplayNameChanged(CallPeerChangeEvent evt)
{
//does not concern us
}
/**
* Verifies whether the call peer has entered a state.
*
* @param evt The <tt>CallPeerChangeEvent</tt> instance containing
* the source event as well as its previous and its new status.
*/
public void peerStateChanged(CallPeerChangeEvent evt)
{
Object newState = evt.getNewValue();
if (CallPeerState.DISCONNECTED.equals(newState)
|| CallPeerState.FAILED.equals(newState))
{
removeCallPeer(evt);
}
else if (CallPeerState.CONNECTED.equals(newState)
|| CallPeerState.CONNECTING_WITH_EARLY_MEDIA.equals(newState))
{
setCallState(CallState.CALL_IN_PROGRESS);
}
else if (CallPeerState.REFERRED.equals(newState))
{
setCallState(CallState.CALL_REFERRED);
}
}
/**
* Returns a reference to the <tt>OperationSetBasicTelephony</tt>
* implementation instance that created this call.
*
* @return a reference to the <tt>OperationSetBasicTelephony</tt>
* instance that created this call.
*/
public U getParentOperationSet()
{
return parentOpSet;
}
/**
* Gets the <tt>RTPTranslator</tt> which forwards RTP and RTCP traffic
* between the <tt>CallPeer</tt>s of this <tt>Call</tt> when the local
* peer represented by this <tt>Call</tt> is acting as a conference focus.
*
* @param mediaType the <tt>MediaType</tt> of the <tt>MediaStream</tt> which
* RTP and RTCP traffic is to be forwarded between
* @return the <tt>RTPTranslator</tt> which forwards RTP and RTCP traffic
* between the <tt>CallPeer</tt>s of this <tt>Call</tt> when the local
* peer represented by this <tt>Call</tt> is acting as a conference focus
*/
public RTPTranslator getRTPTranslator(MediaType mediaType)
{
return getConference().getRTPTranslator(mediaType);
}
/**
* Gets the indicator which determines whether the local peer represented by
* this <tt>Call</tt> is acting as a conference focus and thus may need to
* send the corresponding parameters in its outgoing signaling.
*
* @return <tt>true</tt> if the local peer represented by this <tt>Call</tt>
* is acting as a conference focus; otherwise, <tt>false</tt>
*/
@Override
public boolean isConferenceFocus()
{
return getConference().isConferenceFocus();
}
/**
* Gets a <tt>MediaDevice</tt> which is capable of capture and/or playback
* of media of the specified <tt>MediaType</tt>, is the default choice of
* the user for a <tt>MediaDevice</tt> with the specified <tt>MediaType</tt>
* and is appropriate for the current state of this <tt>Call</tt>.
* <p>
* For example, when the local peer represented by this <tt>Call</tt>
* instance is acting as a conference focus, the audio device must be a
* mixer.
* </p>
*
* @param mediaType the <tt>MediaType</tt> in which the retrieved
* <tt>MediaDevice</tt> is to capture and/or play back media
* @return a <tt>MediaDevice</tt> which is capable of capture and/or
* playback of media of the specified <tt>mediaType</tt>, is the default
* choice of the user for a <tt>MediaDevice</tt> with the specified
* <tt>mediaType</tt> and is appropriate for the current state of this
* <tt>Call</tt>
*/
public MediaDevice getDefaultDevice(MediaType mediaType)
{
return getConference().getDefaultDevice(mediaType, mediaUseCase);
}
/**
* Adds a specific <tt>SoundLevelListener</tt> to the list of
* listeners interested in and notified about changes in local sound level
* related information. When the first listener is being registered the
* method also registers its single listener with the call peer media
* handlers so that it would receive level change events and delegate them
* to the listeners that have registered with us.
*
* @param l the <tt>SoundLevelListener</tt> to add
*/
@Override
public void addLocalUserSoundLevelListener(SoundLevelListener l)
{
synchronized (localUserAudioLevelListenersSyncRoot)
{
if ((localUserAudioLevelListeners == null)
|| localUserAudioLevelListeners.isEmpty())
{
/*
* If this is the first listener that's being registered, we
* also need to register ourselves as an audio level listener
* with the MediaHandler. We do this so that audio level would
* only be calculated if anyone is interested in receiving them.
*/
Iterator<T> callPeerIter = getCallPeers();
while (callPeerIter.hasNext())
{
callPeerIter
.next()
.getMediaHandler()
.setLocalUserAudioLevelListener(
localAudioLevelDelegator);
}
}
/*
* Implement localUserAudioLevelListeners as a copy-on-write storage
* so that iterators over it can iterate without
* ConcurrentModificationExceptions.
*/
localUserAudioLevelListeners
= (localUserAudioLevelListeners == null)
? new ArrayList<SoundLevelListener>()
: new ArrayList<SoundLevelListener>(
localUserAudioLevelListeners);
localUserAudioLevelListeners.add(l);
}
}
/**
* Removes a specific <tt>SoundLevelListener</tt> from the list of listeners
* interested in and notified about changes in local sound level related
* information. If <tt>l</tt> is the last listener that we had here we are
* also going to unregister our own level event delegate in order to stop
* level calculations.
*
* @param l the <tt>SoundLevelListener</tt> to remove
*/
@Override
public void removeLocalUserSoundLevelListener(SoundLevelListener l)
{
synchronized (localUserAudioLevelListenersSyncRoot)
{
/*
* Implement localUserAudioLevelListeners as a copy-on-write storage
* so that iterators over it can iterate over it without
* ConcurrentModificationExceptions.
*/
if (localUserAudioLevelListeners != null)
{
localUserAudioLevelListeners
= new ArrayList<SoundLevelListener>(
localUserAudioLevelListeners);
if (localUserAudioLevelListeners.remove(l)
&& localUserAudioLevelListeners.isEmpty())
localUserAudioLevelListeners = null;
}
if ((localUserAudioLevelListeners == null)
|| localUserAudioLevelListeners.isEmpty())
{
//if this was the last listener that was registered with us then
//no long need to have a delegator registered with the call
//peer media handlers. We therefore remove it so that audio
//level calculations would be ceased.
Iterator<T> callPeerIter = getCallPeers();
while (callPeerIter.hasNext())
{
callPeerIter
.next()
.getMediaHandler()
.setLocalUserAudioLevelListener(null);
}
}
}
}
/**
* Notified by its very majesty the media service about changes in the
* audio level of the local user, this listener generates the corresponding
* events and delivers them to the listeners that have registered here.
*
* @param newLevel the new audio level of the local user.
*/
private void fireLocalUserAudioLevelChangeEvent(int newLevel)
{
List<SoundLevelListener> localUserAudioLevelListeners;
synchronized (localUserAudioLevelListenersSyncRoot)
{
/*
* Since the localUserAudioLevelListeners field of this
* MediaAwareCall 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.
*/
localUserAudioLevelListeners = this.localUserAudioLevelListeners;
}
if (localUserAudioLevelListeners != null)
{
/*
* Iterate over localUserAudioLevelListeners 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 localUserAudioLevelListenerCount
= localUserAudioLevelListeners.size();
for(int i = 0; i < localUserAudioLevelListenerCount; i++)
localUserAudioLevelListeners.get(i).soundLevelChanged(
this,
newLevel);
}
}
/**
* Determines whether this call is mute.
*
* @return <tt>true</tt> if an audio streams being sent to the call
* peers are currently muted; <tt>false</tt>, otherwise
*/
public boolean isMute()
{
return this.mute;
}
/**
* Sets the mute property for this call.
*
* @param mute the new value of the mute property for this call
*/
public void setMute(boolean mute)
{
if (this.mute != mute)
{
this.mute = mute;
Iterator<T> peers = getCallPeers();
while (peers.hasNext())
peers.next().setMute(this.mute);
}
}
/**
* Modifies the local media setup of all peers in the call to reflect the
* requested setting for the streaming of the local video and then passes
* the setting to the participating <tt>MediaAwareCallPeer</tt> instances.
*
* @param allowed <tt>true</tt> if local video transmission is allowed and
* <tt>false</tt> otherwise.
* @param useCase the use case of the video (i.e video call or desktop
* streaming/sharing session)
*
* @throws OperationFailedException if video initialization fails.
*/
public void setLocalVideoAllowed(boolean allowed, MediaUseCase useCase)
throws OperationFailedException
{
/*
* If the use case changes and we don't change the device, calls to
* getDefaultDevice() will return the device for the old use case (
* because it cached in MediaAwareCallConference)
*/
if (mediaUseCase != useCase)
setVideoDevice(null, useCase);
localVideoAllowed = allowed;
mediaUseCase = useCase;
// Record the setting locally and notify all peers.
Iterator<T> peers = getCallPeers();
while (peers.hasNext())
peers.next().setLocalVideoAllowed(allowed);
}
/**
* Get the media use case.
*
* @return media use case
*/
public MediaUseCase getMediaUseCase()
{
return mediaUseCase;
}
/**
* Determines whether the streaming of local video in this <tt>Call</tt>
* is currently allowed. The setting does not reflect the availability of
* actual video capture devices, it just expresses the local policy (or
* desire of the user) to have the local video streamed in the case the
* system is actually able to do so.
*
* @param useCase the use case of the video (i.e video call or desktop
* streaming/sharing session)
* @return <tt>true</tt> if the streaming of local video for this
* <tt>Call</tt> is allowed; otherwise, <tt>false</tt>
*/
public boolean isLocalVideoAllowed(MediaUseCase useCase)
{
return mediaUseCase.equals(useCase) && localVideoAllowed;
}
/**
* Determines whether we are currently streaming video toward at least one
* of the peers in this call.
*
* @return <tt>true</tt> if we are currently streaming video toward at least
* one of the peers in this call and <tt>false</tt> otherwise.
*/
public boolean isLocalVideoStreaming()
{
Iterator<T> peers = getCallPeers();
while (peers.hasNext())
{
if (peers.next().isLocalVideoStreaming())
return true;
}
return false;
}
/**
* Registers a <tt>listener</tt> with all <tt>CallPeer</tt> currently
* participating with the call so that it would be notified of changes in
* video related properties (e.g. <tt>LOCAL_VIDEO_STREAMING</tt>).
*
* @param listener the <tt>PropertyChangeListener</tt> to be notified
* when the properties associated with member <tt>CallPeer</tt>s change
* their values.
*/
public void addVideoPropertyChangeListener(
PropertyChangeListener listener)
{
Iterator<T> peers = getCallPeers();
while (peers.hasNext())
peers.next().addVideoPropertyChangeListener(listener);
}
/**
* Removes <tt>listener</tt> from all <tt>CallPeer</tt>s currently
* participating with the call so that it won't receive further notifications
* on changes in video related properties (e.g.
* <tt>LOCAL_VIDEO_STREAMING</tt>).
*
* @param listener the <tt>PropertyChangeListener</tt> to unregister from
* member <tt>CallPeer</tt>s change their values.
*/
public void removeVideoPropertyChangeListener(
PropertyChangeListener listener)
{
Iterator<T> peers = getCallPeers();
while (peers.hasNext())
peers.next().removeVideoPropertyChangeListener(listener);
}
/**
* Creates a new <tt>Recorder</tt> which is to record this <tt>Call</tt>
* (into a file which is to be specified when starting the returned
* <tt>Recorder</tt>).
*
* @return a new <tt>Recorder</tt> which is to record this <tt>Call</tt>
* (into a file which is to be specified when starting the returned
* <tt>Recorder</tt>)
* @throws OperationFailedException if anything goes wrong while creating
* the new <tt>Recorder</tt> for this <tt>Call</tt>
*/
public Recorder createRecorder()
throws OperationFailedException
{
final Recorder recorder
= ProtocolMediaActivator.getMediaService().createRecorder(
getDefaultDevice(MediaType.AUDIO));
if (recorder != null)
{
// listens for mute event to update recorder
final PropertyChangeListener muteListener
= new PropertyChangeListener()
{
public void propertyChange(PropertyChangeEvent evt)
{
if(evt.getPropertyName()
.equals(CallPeer.MUTE_PROPERTY_NAME))
{
updateRecorderMuteState(recorder);
}
}
};
// Make sure the recorder is stopped when this call ends.
final CallChangeListener callChangeListener
= new CallChangeListener()
{
/**
* When call ends we stop recording.
* @param evt the <tt>CallChangeEvent</tt> instance
* containing the source
*/
public void callStateChanged(CallChangeEvent evt)
{
if (!CallChangeEvent.CALL_STATE_CHANGE
.equals(evt.getPropertyName()))
return;
if (CallState.CALL_ENDED.equals(evt.getNewValue()))
recorder.stop();
}
/**
* We listen for mute on newly added call peers.
* @param evt the <tt>CallPeerEvent</tt> containing
* the source call
*/
public void callPeerAdded(CallPeerEvent evt)
{
updateRecorderMuteState(recorder);
evt.getSourceCallPeer()
.addPropertyChangeListener(muteListener);
}
/**
* We stop listen for mute on removed call peers.
* @param evt the <tt>CallPeerEvent</tt> containing
* the source call
*/
public void callPeerRemoved(CallPeerEvent evt)
{
updateRecorderMuteState(recorder);
evt.getSourceCallPeer()
.removePropertyChangeListener(muteListener);
}
};
addCallChangeListener(callChangeListener);
Iterator<Recorder.Listener> iterListeners =
ProtocolMediaActivator.getMediaService().getRecorderListeners();
while(iterListeners.hasNext())
recorder.addListener(iterListeners.next());
/*
* If the recorder gets stopped earlier than this call ends, don't
* wait for the end of the call because callChangeListener will keep
* a reference to the stopped recorder.
*/
recorder.addListener(
new Recorder.Listener()
{
public void recorderStopped(Recorder recorder)
{
removeCallChangeListener(callChangeListener);
Iterator<Recorder.Listener> iter
= ProtocolMediaActivator.getMediaService()
.getRecorderListeners();
while(iter.hasNext())
recorder.removeListener(iter.next());
}
});
// add listener for mute event to all current peers
Iterator<T> iter = getCallPeers();
while(iter.hasNext())
iter.next().addPropertyChangeListener(muteListener);
updateRecorderMuteState(recorder);
}
return recorder;
}
/**
* Updates the recorder mute state by looking at the peers state.
* If one of the peers is not muted and the recorder is not.
* If all the peers are muted so must be and the recorder.
* @param recorder the recorder we are operating on.
*/
private void updateRecorderMuteState(Recorder recorder)
{
Iterator<T> iter = getCallPeers();
while(iter.hasNext())
{
if(!iter.next().isMute())
{
// one peer is not muted so we unmute.
recorder.setMute(false);
return;
}
}
// all peers are muted, so we mute the recorder
recorder.setMute(true);
}
/**
* Sets the <tt>MediaDevice</tt> to be used by this <tt>Call</tt> for audio
* capture and/or playback.
*
* @param audioDevice the <tt>MediaDevice</tt> to be used by this
* <tt>Call</tt> for audio capture and/or playback
*/
public void setAudioDevice(MediaDevice audioDevice)
{
getConference().setDevice(MediaType.AUDIO, audioDevice);
}
/**
* Sets the <tt>MediaDevice</tt> to be used by this <tt>Call</tt> for video
* capture and/or playback and the use case of the video streaming in this
* <tt>MediaAwareCall</tt>.
*
* @param videoDevice the <tt>MediaDevice</tt> to be used by this
* <tt>Call</tt> for video capture and/or playback
* @param useCase the use case of the video streaming to be set on this
* <tt>MediaAwareCall</tt> such as webcam capture or desktop
* sharing/streaming
*/
public void setVideoDevice(MediaDevice videoDevice, MediaUseCase useCase)
{
/*
* XXX If the mediaUseCase is changing, it is important to realize the
* change prior to setting the specified videoDevice on the associated
* conference because the method MediaAwareCallConference.setDevice will
* indirectly utilize the mediaUseCase.
*/
mediaUseCase = useCase;
getConference().setDevice(MediaType.VIDEO, videoDevice);
}
/**
* Sets the state of this <tt>Call</tt> and fires a new
* <tt>CallChangeEvent</tt> notifying the registered
* <tt>CallChangeListener</tt>s about the change of the state.
*
* @param newState the <tt>CallState</tt> into which this <tt>Call</tt> is
* to enter
* @param cause the <tt>CallPeerChangeEvent</tt> which is the cause for the
* request to have this <tt>Call</tt> enter the specified <tt>CallState</tt>
* @see Call#setCallState(CallState, CallPeerChangeEvent)
*/
@Override
protected void setCallState(CallState newState, CallPeerChangeEvent cause)
{
try
{
super.setCallState(newState, cause);
}
finally
{
if (CallState.CALL_ENDED.equals(getCallState()))
ProtocolMediaActivator
.getMediaService()
.removePropertyChangeListener(this);
}
}
/**
* Notifies this instance about a change of the value of a specific property
* from a specific old value to a specific new value.
*
* @param ev a <tt>PropertyChangeEvent</tt> which specifies the name of the
* property which has its value changed and the old and new values
*/
public void propertyChange(PropertyChangeEvent ev)
{
/*
* Forward PropertyChangeEvents notifying about changes in the values of
* MediaAwareCall properties which are delegated to
* MediaAwareCallConference.
*/
if (ev.getSource() instanceof CallConference)
{
String propertyName = ev.getPropertyName();
if (CONFERENCE_FOCUS.equals(propertyName))
{
conferenceFocusChanged(
(Boolean) ev.getOldValue(),
(Boolean) ev.getNewValue());
}
else if (DEFAULT_DEVICE.equals(propertyName))
{
firePropertyChange(
DEFAULT_DEVICE,
ev.getOldValue(),
ev.getNewValue());
}
}
}
/**
* Notifies this instance that the value of its property
* {@link Call#CONFERENCE_FOCUS} has changed from a specific old value to a
* specific new value. Fires a <tt>PropertyChangeEvent</tt> to the
* registered <tt>PropertyChangeListener</tt>s. Protocol implementations
* which extend <tt>MediaAwareCall</tt> will likely want to override in
* order to add notifying the associated <tt>CallPeer</tt>s about the change
* of the property value (e.g. SIP will want to include the
* "isfocus" parameter in the Contact header while the local peer
* is acting as a conference focus.)
*
* @param oldValue the value of the property <tt>CONFERENCE_FOCUS</tt>
* before the change
* @param newValue the value of the property <tt>CONFERENCE_FOCUS</tt> after
* the change
*/
protected void conferenceFocusChanged(boolean oldValue, boolean newValue)
{
firePropertyChange(CONFERENCE_FOCUS, oldValue, newValue);
}
/**
* {@inheritDoc}
*
* Creates a new <tt>MediaAwareCallConference</tt> to represent the
* media-specific information associated with the telephony
* conference-related state of this <tt>MediaAwareCall</tt>.
*/
@Override
protected CallConference createConference()
{
return new MediaAwareCallConference(false, this.useTranslator);
}
/**
* {@inheritDoc}
*
* Makes sure that the telephony conference-related state of this
* <tt>MediaAwareCall</tt> is represented by a
* <tt>MediaAwareCallConference</tt> instance.
*/
@Override
public MediaAwareCallConference getConference()
{
return (MediaAwareCallConference) super.getConference();
}
/**
* {@inheritDoc}
*
* Listens to the changes in the values of the properties of this
* <tt>Call</tt>.
*/
@Override
protected void firePropertyChange(
String property,
Object oldValue, Object newValue)
{
if (oldValue != newValue)
{
/*
* Listen to the changes in the values of the properties of the
* telephony conference-related state of this Call. For example,
* MediaAwareCall delegates some of its properties (e.g.
* DEFAULT_DEVICE) to its associated MediaAwareCallConference so
* changes to the values of the properties of the latter should
* result in PropertyChangeEvents fired by the former (as well).
*/
if (CONFERENCE.equals(property))
{
if (oldValue != null)
{
((CallConference) oldValue).removePropertyChangeListener(
this);
}
if (newValue != null)
{
((CallConference) newValue).addPropertyChangeListener(
this);
}
}
}
super.firePropertyChange(property, oldValue, newValue);
}
/**
* {@inheritDoc}
*
* Makes sure that the telephony conference-related state of this
* <tt>MediaAwareCall</tt> is represented by a
* <tt>MediaAwareCallConference</tt> instance.
*/
@Override
public void setConference(CallConference conference)
{
super.setConference(conference);
}
/**
* {@inheritDoc}
*
* Implements
* {@link net.java.sip.communicator.service.protocol.event.DTMFListener#toneReceived(net.java.sip.communicator.service.protocol.event.DTMFReceivedEvent)}
*/
@Override
public void toneReceived(DTMFReceivedEvent evt)
{
// Stub
}
}