diff options
Diffstat (limited to 'src/net/java/sip/communicator/service/protocol/media/MediaAwareCallConference.java')
-rw-r--r-- | src/net/java/sip/communicator/service/protocol/media/MediaAwareCallConference.java | 1286 |
1 files changed, 665 insertions, 621 deletions
diff --git a/src/net/java/sip/communicator/service/protocol/media/MediaAwareCallConference.java b/src/net/java/sip/communicator/service/protocol/media/MediaAwareCallConference.java index 7a58e4a..81e917c 100644 --- a/src/net/java/sip/communicator/service/protocol/media/MediaAwareCallConference.java +++ b/src/net/java/sip/communicator/service/protocol/media/MediaAwareCallConference.java @@ -1,4 +1,4 @@ -/*
+/* * Jitsi, the OpenSource Java VoIP and Instant Messaging client. * * Copyright @ 2015 Atlassian Pty Ltd @@ -15,623 +15,667 @@ * 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.lang.ref.*;
-import java.util.*;
-
-import org.jitsi.service.neomedia.*;
-import org.jitsi.service.neomedia.device.*;
-import org.jitsi.util.*;
-import org.jitsi.util.event.*;
-
-import net.java.sip.communicator.service.protocol.*;
-
-/**
- * Extends <tt>CallConference</tt> to represent the media-specific information
- * associated with the telephony conference-related state of a
- * <tt>MediaAwareCall</tt>.
- *
- * @author Lyubomir Marinov
- */
-public class MediaAwareCallConference
- extends CallConference
-{
- /**
- * The <tt>PropertyChangeListener</tt> which will listen to the
- * <tt>MediaService</tt> about <tt>PropertyChangeEvent</tt>s.
- */
- private static WeakPropertyChangeListener
- mediaServicePropertyChangeListener;
-
- /**
- * The <tt>MediaDevice</tt>s indexed by <tt>MediaType</tt> ordinal which are
- * to be used by this telephony conference for media capture and/or
- * playback. If the <tt>MediaDevice</tt> for a specific <tt>MediaType</tt>
- * is <tt>null</tt>,
- * {@link MediaService#getDefaultDevice(MediaType, MediaUseCase)} is called.
- */
- private final MediaDevice[] devices;
-
- /**
- * The <tt>MediaDevice</tt>s which implement media mixing on the respective
- * <tt>MediaDevice</tt> in {@link #devices} for the purposes of this
- * telephony conference.
- */
- private final MediaDevice[] mixers;
-
- /**
- * The <tt>VolumeControl</tt> implementation which is to control the volume
- * (level) of the audio played back the telephony conference represented by
- * this instance.
- */
- private final VolumeControl outputVolumeControl
- = new BasicVolumeControl(
- VolumeControl.PLAYBACK_VOLUME_LEVEL_PROPERTY_NAME);
-
- /**
- * The <tt>PropertyChangeListener</tt> which listens to sources of
- * <tt>PropertyChangeEvent</tt>s on behalf of this instance.
- */
- private final PropertyChangeListener propertyChangeListener
- = new PropertyChangeListener()
- {
- @Override
- public void propertyChange(PropertyChangeEvent ev)
- {
- MediaAwareCallConference.this.propertyChange(ev);
- }
- };
-
- /**
- * The <tt>RTPTranslator</tt> which forwards video RTP and RTCP traffic
- * between the <tt>CallPeer</tt>s of the <tt>Call</tt>s participating in
- * this telephony conference when the local peer is acting as a conference
- * focus.
- */
- private RTPTranslator videoRTPTranslator;
-
- /**
- * Initializes a new <tt>MediaAwareCallConference</tt> instance.
- */
- public MediaAwareCallConference()
- {
- this(false);
- }
-
- /**
- * Initializes a new <tt>MediaAwareCallConference</tt> instance which is to
- * optionally utilize the Jitsi Videobridge server-side telephony
- * conferencing technology.
- *
- * @param jitsiVideobridge <tt>true</tt> if the telephony conference
- * represented by the new instance is to utilize the Jitsi Videobridge
- * server-side telephony conferencing technology; otherwise, <tt>false</tt>
- */
- public MediaAwareCallConference(boolean jitsiVideobridge)
- {
- super(jitsiVideobridge);
-
- int mediaTypeCount = MediaType.values().length;
-
- devices = new MediaDevice[mediaTypeCount];
- mixers = new MediaDevice[mediaTypeCount];
-
- /*
- * Listen to the MediaService in order to reflect changes in the user's
- * selection with respect to the default media device.
- */
- addMediaServicePropertyChangeListener(propertyChangeListener);
- }
-
- /**
- * Adds a specific <tt>PropertyChangeListener</tt> to be notified about
- * <tt>PropertyChangeEvent</tt>s fired by the current <tt>MediaService</tt>
- * implementation. The implementation adds a <tt>WeakReference</tt> to the
- * specified <tt>listener</tt> because <tt>MediaAwareCallConference</tt>
- * is unable to determine when the <tt>PropertyChangeListener</tt> is to be
- * removed.
- *
- * @param listener the <tt>PropertyChangeListener</tt> to add
- */
- private static synchronized void addMediaServicePropertyChangeListener(
- PropertyChangeListener listener)
- {
- if (mediaServicePropertyChangeListener == null)
- {
- final MediaService mediaService
- = ProtocolMediaActivator.getMediaService();
-
- if (mediaService != null)
- {
- mediaServicePropertyChangeListener
- = new WeakPropertyChangeListener()
- {
- @Override
- protected void addThisToNotifier()
- {
- mediaService.addPropertyChangeListener(this);
- }
-
- @Override
- protected void removeThisFromNotifier()
- {
- mediaService.removePropertyChangeListener(this);
- }
- };
- }
- }
- if (mediaServicePropertyChangeListener != null)
- {
- mediaServicePropertyChangeListener.addPropertyChangeListener(
- listener);
- }
- }
-
- /**
- * {@inheritDoc}
- *
- * If this telephony conference switches from being a conference focus to
- * not being such, disposes of the mixers used by this instance when it was
- * a conference focus
- */
- @Override
- protected void conferenceFocusChanged(boolean oldValue, boolean newValue)
- {
- /*
- * If this telephony conference switches from being a conference
- * focus to not being one, dispose of the mixers used when it was a
- * conference focus.
- */
- if (oldValue && !newValue)
- {
- Arrays.fill(mixers, null);
-
- /* Disposing the video translator is not needed when the conference
- changes as we have video and we will want to continue with
- the video
- Removed when chasing a bug where video call becomes conference
- call and then back again video call and the video from the
- conference focus side is not transmitted.
- if (videoRTPTranslator != null)
- {
- videoRTPTranslator.dispose();
- videoRTPTranslator = null;
- }
- */
- }
-
- super.conferenceFocusChanged(oldValue, newValue);
- }
-
- /**
- * {@inheritDoc}
- *
- * Disposes of <tt>this.videoRTPTranslator</tt> if the removed <tt>Call</tt>
- * was the last <tt>Call</tt> in this <tt>CallConference</tt>.
- *
- * @param call the <tt>Call</tt> which has been removed from the list of
- * <tt>Call</tt>s participating in this telephony conference.
- */
- @Override
- protected void callRemoved(Call call)
- {
- super.callRemoved(call);
-
- if (getCallCount() == 0 && (videoRTPTranslator != null))
- {
- videoRTPTranslator.dispose();
- videoRTPTranslator = null;
- }
- }
-
- /**
- * Gets a <tt>MediaDevice</tt> which is capable of capture and/or playback
- * of media of the specified <tt>MediaType</tt> and is the default choice of
- * the user with respect to such a <tt>MediaDevice</tt>.
- *
- * @param mediaType the <tt>MediaType</tt> in which the retrieved
- * <tt>MediaDevice</tt> is to capture and/or play back media
- * @param useCase the <tt>MediaUseCase</tt> associated with the intended
- * utilization of the <tt>MediaDevice</tt> to be retrieved
- * @return a <tt>MediaDevice</tt> which is capable of capture and/or
- * playback of media of the specified <tt>mediaType</tt> and is the default
- * choice of the user with respect to such a <tt>MediaDevice</tt>
- */
- public MediaDevice getDefaultDevice(
- MediaType mediaType,
- MediaUseCase useCase)
- {
- int mediaTypeIndex = mediaType.ordinal();
- MediaDevice device = devices[mediaTypeIndex];
- MediaService mediaService = ProtocolMediaActivator.getMediaService();
-
- if (device == null)
- device = mediaService.getDefaultDevice(mediaType, useCase);
-
- /*
- * Make sure that the device is capable of mixing in order to support
- * conferencing and call recording.
- */
- if (device != null)
- {
- MediaDevice mixer = mixers[mediaTypeIndex];
-
- if (mixer == null)
- {
- switch (mediaType)
- {
- case AUDIO:
- /*
- * TODO AudioMixer leads to very poor audio quality on
- * Android so do not use it unless it is really really
- * necessary.
- */
- if ((!OSUtils.IS_ANDROID || isConferenceFocus())
- /*
- * We can use the AudioMixer only if the device is
- * able to capture (because the AudioMixer will push
- * when the capture device pushes).
- */
- && device.getDirection().allowsSending())
- {
- mixer = mediaService.createMixer(device);
- }
- break;
-
- case VIDEO:
- if (isConferenceFocus())
- mixer = mediaService.createMixer(device);
- break;
- }
-
- mixers[mediaTypeIndex] = mixer;
- }
-
- if (mixer != null)
- device = mixer;
- }
-
- return device;
- }
-
- /**
- * Gets the <tt>VolumeControl</tt> which controls the volume (level) of the
- * audio played back in the telephony conference represented by this
- * instance.
- *
- * @return the <tt>VolumeControl</tt> which controls the volume (level) of
- * the audio played back in the telephony conference represented by this
- * instance
- */
- public VolumeControl getOutputVolumeControl()
- {
- return outputVolumeControl;
- }
-
- /**
- * Gets the <tt>RTPTranslator</tt> which forwards RTP and RTCP traffic
- * between the <tt>CallPeer</tt>s of the <tt>Call</tt>s participating in
- * this telephony conference when the local peer 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 the <tt>Call</tt>s participating in
- * this telephony conference when the local peer is acting as a conference
- * focus
- */
- public RTPTranslator getRTPTranslator(MediaType mediaType)
- {
- RTPTranslator rtpTranslator = null;
-
- /*
- * XXX A mixer is created for audio even when the local peer is not a
- * conference focus in order to enable additional functionality.
- * Similarly, the videoRTPTranslator is created even when the local peer
- * is not a conference focus in order to enable the local peer to turn
- * into a conference focus at a later time. More specifically,
- * MediaStreamImpl is unable to accommodate an RTPTranslator after it
- * has created its RTPManager. Yet again like the audio mixer, we'd
- * better not try to use it on Android at this time because of
- * performance issues that might arise.
- */
- if (MediaType.VIDEO.equals(mediaType)
- && (!OSUtils.IS_ANDROID || isConferenceFocus()))
- {
- if (videoRTPTranslator == null)
- {
- videoRTPTranslator
- = ProtocolMediaActivator
- .getMediaService()
- .createRTPTranslator();
- }
- rtpTranslator = videoRTPTranslator;
- }
- return rtpTranslator;
- }
-
- /**
- * Notifies this <tt>MediaAwareCallConference</tt> about changes in the
- * values of the properties of sources of <tt>PropertyChangeEvent</tt>s. For
- * example, this instance listens to changes of the value of
- * {@link MediaService#DEFAULT_DEVICE} which represents the user's choice
- * with respect to the default audio device.
- *
- * @param ev a <tt>PropertyChangeEvent</tt> which specifies the name of the
- * property which had its value changed and the old and new values of that
- * property
- */
- private void propertyChange(PropertyChangeEvent ev)
- {
- String propertyName = ev.getPropertyName();
-
- if (MediaService.DEFAULT_DEVICE.equals(propertyName))
- {
- Object source = ev.getSource();
-
- if (source instanceof MediaService)
- {
- /*
- * XXX We only support changing the default audio device at the
- * time of this writing.
- */
- int mediaTypeIndex = MediaType.AUDIO.ordinal();
- MediaDevice mixer = mixers[mediaTypeIndex];
- MediaDevice oldValue
- = (mixer instanceof MediaDeviceWrapper)
- ? ((MediaDeviceWrapper) mixer).getWrappedDevice()
- : null;
- MediaDevice newValue = devices[mediaTypeIndex];
-
- if (newValue == null)
- {
- newValue
- = ProtocolMediaActivator
- .getMediaService()
- .getDefaultDevice(
- MediaType.AUDIO,
- MediaUseCase.ANY);
- }
-
- /*
- * XXX If MediaService#getDefaultDevice(MediaType, MediaUseCase)
- * above returns null and its earlier return value was not null,
- * we will not notify of an actual change in the value of the
- * user's choice with respect to the default audio device.
- */
- if (oldValue != newValue)
- {
- mixers[mediaTypeIndex] = null;
- firePropertyChange(
- MediaAwareCall.DEFAULT_DEVICE,
- oldValue, newValue);
- }
- }
- }
- }
-
- /**
- * Sets the <tt>MediaDevice</tt> to be used by this telephony conference for
- * capture and/or playback of media of a specific <tt>MediaType</tt>.
- *
- * @param mediaType the <tt>MediaType</tt> of the media which is to be
- * captured and/or played back by the specified <tt>device</tt>
- * @param device the <tt>MediaDevice</tt> to be used by this telephony
- * conference for capture and/or playback of media of the specified
- * <tt>mediaType</tt>
- */
- void setDevice(MediaType mediaType, MediaDevice device)
- {
- int mediaTypeIndex = mediaType.ordinal();
- MediaDevice oldValue = devices[mediaTypeIndex];
-
- /*
- * XXX While we know the old and the new master/wrapped devices, we
- * are not sure whether the mixer has been used. Anyway, we have to
- * report different values in order to have PropertyChangeSupport
- * really fire an event.
- */
- MediaDevice mixer = mixers[mediaTypeIndex];
-
- if (mixer instanceof MediaDeviceWrapper)
- oldValue = ((MediaDeviceWrapper) mixer).getWrappedDevice();
-
- MediaDevice newValue = devices[mediaTypeIndex] = device;
-
- if (oldValue != newValue)
- {
- mixers[mediaTypeIndex] = null;
- firePropertyChange(
- MediaAwareCall.DEFAULT_DEVICE,
- oldValue, newValue);
- }
- }
-
- /**
- * Implements a <tt>PropertyChangeListener</tt> which weakly references and
- * delegates to specific <tt>PropertyChangeListener</tt>s and automatically
- * adds itself to and removes itself from a specific
- * <tt>PropertyChangeNotifier</tt> depending on whether there are
- * <tt>PropertyChangeListener</tt>s to delegate to. Thus enables listening
- * to a <tt>PropertyChangeNotifier</tt> by invoking
- * {@link PropertyChangeNotifier#addPropertyChangeListener(
- * PropertyChangeListener)} without
- * {@link PropertyChangeNotifier#removePropertyChangeListener(
- * PropertyChangeListener)}.
- */
- private static class WeakPropertyChangeListener
- implements PropertyChangeListener
- {
- /**
- * The indicator which determines whether this
- * <tt>PropertyChangeListener</tt> has been added to {@link #notifier}.
- */
- private boolean added = false;
-
- /**
- * The list of <tt>PropertyChangeListener</tt>s which are to be notified
- * about <tt>PropertyChangeEvent</tt>s fired by {@link #notifier}.
- */
- private final List<WeakReference<PropertyChangeListener>> listeners
- = new LinkedList<WeakReference<PropertyChangeListener>>();
-
- /**
- * The <tt>PropertyChangeNotifier</tt> this instance is to listen to
- * about <tt>PropertyChangeEvent</tt>s which are to be forwarded to
- * {@link #listeners}.
- */
- private final PropertyChangeNotifier notifier;
-
- /**
- * Initializes a new <tt>WeakPropertyChangeListener</tt> instance.
- */
- protected WeakPropertyChangeListener()
- {
- this(null);
- }
-
- /**
- * Initializes a new <tt>WeakPropertyChangeListener</tt> instance which
- * is to listen to a specific <tt>PropertyChangeNotifier</tt>.
- *
- * @param notifier the <tt>PropertyChangeNotifier</tt> the new instance
- * is to listen to
- */
- public WeakPropertyChangeListener(PropertyChangeNotifier notifier)
- {
- this.notifier = notifier;
- }
-
- /**
- * Adds a specific <tt>PropertyChangeListener</tt> to the list of
- * <tt>PropertyChangeListener</tt>s to be notified about
- * <tt>PropertyChangeEvent</tt>s fired by the
- * <tt>PropertyChangeNotifier</tt> associated with this instance.
- *
- * @param listener the <tt>PropertyChangeListener</tt> to add
- */
- public synchronized void addPropertyChangeListener(
- PropertyChangeListener listener)
- {
- Iterator<WeakReference<PropertyChangeListener>> i
- = listeners.iterator();
- boolean add = true;
-
- while (i.hasNext())
- {
- PropertyChangeListener l = i.next().get();
-
- if (l == null)
- i.remove();
- else if (l.equals(listener))
- add = false;
- }
- if (add
- && listeners.add(
- new WeakReference<PropertyChangeListener>(listener))
- && !this.added)
- {
- addThisToNotifier();
- this.added = true;
- }
- }
-
- /**
- * Adds this as a <tt>PropertyChangeListener</tt> to {@link #notifier}.
- */
- protected void addThisToNotifier()
- {
- if (notifier != null)
- notifier.addPropertyChangeListener(this);
- }
-
- /**
- * {@inheritDoc}
- *
- * Notifies this instance about a <tt>PropertyChangeEvent</tt> fired by
- * {@link #notifier}.
- */
- @Override
- public void propertyChange(PropertyChangeEvent ev)
- {
- PropertyChangeListener[] ls;
- int n;
-
- synchronized (this)
- {
- Iterator<WeakReference<PropertyChangeListener>> i
- = listeners.iterator();
-
- ls = new PropertyChangeListener[listeners.size()];
- n = 0;
- while (i.hasNext())
- {
- PropertyChangeListener l = i.next().get();
-
- if (l == null)
- i.remove();
- else
- ls[n++] = l;
- }
- if ((n == 0) && this.added)
- {
- removeThisFromNotifier();
- this.added = false;
- }
- }
-
- if (n != 0)
- {
- for (PropertyChangeListener l : ls)
- {
- if (l == null)
- break;
- else
- l.propertyChange(ev);
- }
- }
- }
-
- /**
- * Removes a specific <tt>PropertyChangeListener</tt> from the list of
- * <tt>PropertyChangeListener</tt>s to be notified about
- * <tt>PropertyChangeEvent</tt>s fired by the
- * <tt>PropertyChangeNotifier</tt> associated with this instance.
- *
- * @param listener the <tt>PropertyChangeListener</tt> to remove
- */
- @SuppressWarnings("unused")
- public synchronized void removePropertyChangeListener(
- PropertyChangeListener listener)
- {
- Iterator<WeakReference<PropertyChangeListener>> i
- = listeners.iterator();
-
- while (i.hasNext())
- {
- PropertyChangeListener l = i.next().get();
-
- if ((l == null) || l.equals(listener))
- i.remove();
- }
- if (this.added && (listeners.size() == 0))
- {
- removeThisFromNotifier();
- this.added = false;
- }
- }
-
- /**
- * Removes this as a <tt>PropertyChangeListener</tt> from
- * {@link #notifier}.
- */
- protected void removeThisFromNotifier()
- {
- if (notifier != null)
- notifier.removePropertyChangeListener(this);
- }
- }
-}
+package net.java.sip.communicator.service.protocol.media; + +import java.beans.*; +import java.lang.ref.*; +import java.util.*; + +import org.jitsi.service.neomedia.*; +import org.jitsi.service.neomedia.device.*; +import org.jitsi.util.*; +import org.jitsi.util.event.*; + +import net.java.sip.communicator.service.protocol.*; + +/** + * Extends <tt>CallConference</tt> to represent the media-specific information + * associated with the telephony conference-related state of a + * <tt>MediaAwareCall</tt>. + * + * @author Lyubomir Marinov + */ +public class MediaAwareCallConference + extends CallConference +{ + /** + * The <tt>PropertyChangeListener</tt> which will listen to the + * <tt>MediaService</tt> about <tt>PropertyChangeEvent</tt>s. + */ + private static WeakPropertyChangeListener + mediaServicePropertyChangeListener; + + /** + * The <tt>MediaDevice</tt>s indexed by <tt>MediaType</tt> ordinal which are + * to be used by this telephony conference for media capture and/or + * playback. If the <tt>MediaDevice</tt> for a specific <tt>MediaType</tt> + * is <tt>null</tt>, + * {@link MediaService#getDefaultDevice(MediaType, MediaUseCase)} is called. + */ + private final MediaDevice[] devices; + + /** + * The <tt>MediaDevice</tt>s which implement media mixing on the respective + * <tt>MediaDevice</tt> in {@link #devices} for the purposes of this + * telephony conference. + */ + private final MediaDevice[] mixers; + + /** + * The <tt>VolumeControl</tt> implementation which is to control the volume + * (level) of the audio played back the telephony conference represented by + * this instance. + */ + private final VolumeControl outputVolumeControl + = new BasicVolumeControl( + VolumeControl.PLAYBACK_VOLUME_LEVEL_PROPERTY_NAME); + + /** + * The <tt>PropertyChangeListener</tt> which listens to sources of + * <tt>PropertyChangeEvent</tt>s on behalf of this instance. + */ + private final PropertyChangeListener propertyChangeListener + = new PropertyChangeListener() + { + @Override + public void propertyChange(PropertyChangeEvent ev) + { + MediaAwareCallConference.this.propertyChange(ev); + } + }; + + /** + * The <tt>RTPTranslator</tt> which forwards video RTP and RTCP traffic + * between the <tt>CallPeer</tt>s of the <tt>Call</tt>s participating in + * this telephony conference when the local peer is acting as a conference + * focus. + */ + private RTPTranslator videoRTPTranslator; + + /** + * The <tt>RTPTranslator</tt> which forwards autio RTP and RTCP traffic + * between the <tt>CallPeer</tt>s of the <tt>Call</tt>s participating in + * this telephony conference when the local peer is acting as a conference + * focus. + */ + private RTPTranslator audioRTPTranslator; + + /** + * The indicator which determines whether the telephony conference + * represented by this instance is mixing or relaying. + * By default what can be mixed is mixed (audio) and rest is relayed. + */ + private boolean translator = false; + + /** + * Initializes a new <tt>MediaAwareCallConference</tt> instance. + */ + public MediaAwareCallConference() + { + this(false); + } + + /** + * Initializes a new <tt>MediaAwareCallConference</tt> instance which is to + * optionally utilize the Jitsi Videobridge server-side telephony + * conferencing technology. + * + * @param jitsiVideobridge <tt>true</tt> if the telephony conference + * represented by the new instance is to utilize the Jitsi Videobridge + * server-side telephony conferencing technology; otherwise, <tt>false</tt> + */ + public MediaAwareCallConference(boolean jitsiVideobridge) + { + this(jitsiVideobridge, false); + } + + /** + * Initializes a new <tt>MediaAwareCallConference</tt> instance which is to + * optionally utilize the Jitsi Videobridge server-side telephony + * conferencing technology. + * + * @param jitsiVideobridge <tt>true</tt> if the telephony conference + * represented by the new instance is to utilize the Jitsi Videobridge + * server-side telephony conferencing technology; otherwise, <tt>false</tt> + */ + public MediaAwareCallConference(boolean jitsiVideobridge, + boolean translator) + { + super(jitsiVideobridge); + + this.translator = translator; + + int mediaTypeCount = MediaType.values().length; + + devices = new MediaDevice[mediaTypeCount]; + mixers = new MediaDevice[mediaTypeCount]; + + /* + * Listen to the MediaService in order to reflect changes in the user's + * selection with respect to the default media device. + */ + addMediaServicePropertyChangeListener(propertyChangeListener); + } + + /** + * Adds a specific <tt>PropertyChangeListener</tt> to be notified about + * <tt>PropertyChangeEvent</tt>s fired by the current <tt>MediaService</tt> + * implementation. The implementation adds a <tt>WeakReference</tt> to the + * specified <tt>listener</tt> because <tt>MediaAwareCallConference</tt> + * is unable to determine when the <tt>PropertyChangeListener</tt> is to be + * removed. + * + * @param listener the <tt>PropertyChangeListener</tt> to add + */ + private static synchronized void addMediaServicePropertyChangeListener( + PropertyChangeListener listener) + { + if (mediaServicePropertyChangeListener == null) + { + final MediaService mediaService + = ProtocolMediaActivator.getMediaService(); + + if (mediaService != null) + { + mediaServicePropertyChangeListener + = new WeakPropertyChangeListener() + { + @Override + protected void addThisToNotifier() + { + mediaService.addPropertyChangeListener(this); + } + + @Override + protected void removeThisFromNotifier() + { + mediaService.removePropertyChangeListener(this); + } + }; + } + } + if (mediaServicePropertyChangeListener != null) + { + mediaServicePropertyChangeListener.addPropertyChangeListener( + listener); + } + } + + /** + * {@inheritDoc} + * + * If this telephony conference switches from being a conference focus to + * not being such, disposes of the mixers used by this instance when it was + * a conference focus + */ + @Override + protected void conferenceFocusChanged(boolean oldValue, boolean newValue) + { + /* + * If this telephony conference switches from being a conference + * focus to not being one, dispose of the mixers used when it was a + * conference focus. + */ + if (oldValue && !newValue) + { + Arrays.fill(mixers, null); + + /* Disposing the video translator is not needed when the conference + changes as we have video and we will want to continue with + the video + Removed when chasing a bug where video call becomes conference + call and then back again video call and the video from the + conference focus side is not transmitted. + if (videoRTPTranslator != null) + { + videoRTPTranslator.dispose(); + videoRTPTranslator = null; + } + */ + } + + super.conferenceFocusChanged(oldValue, newValue); + } + + /** + * {@inheritDoc} + * + * Disposes of <tt>this.videoRTPTranslator</tt> if the removed <tt>Call</tt> + * was the last <tt>Call</tt> in this <tt>CallConference</tt>. + * + * @param call the <tt>Call</tt> which has been removed from the list of + * <tt>Call</tt>s participating in this telephony conference. + */ + @Override + protected void callRemoved(Call call) + { + super.callRemoved(call); + + if (getCallCount() == 0 && (videoRTPTranslator != null)) + { + videoRTPTranslator.dispose(); + videoRTPTranslator = null; + } + } + + /** + * Gets a <tt>MediaDevice</tt> which is capable of capture and/or playback + * of media of the specified <tt>MediaType</tt> and is the default choice of + * the user with respect to such a <tt>MediaDevice</tt>. + * + * @param mediaType the <tt>MediaType</tt> in which the retrieved + * <tt>MediaDevice</tt> is to capture and/or play back media + * @param useCase the <tt>MediaUseCase</tt> associated with the intended + * utilization of the <tt>MediaDevice</tt> to be retrieved + * @return a <tt>MediaDevice</tt> which is capable of capture and/or + * playback of media of the specified <tt>mediaType</tt> and is the default + * choice of the user with respect to such a <tt>MediaDevice</tt> + */ + public MediaDevice getDefaultDevice( + MediaType mediaType, + MediaUseCase useCase) + { + int mediaTypeIndex = mediaType.ordinal(); + MediaDevice device = devices[mediaTypeIndex]; + MediaService mediaService = ProtocolMediaActivator.getMediaService(); + + if (device == null) + device = mediaService.getDefaultDevice(mediaType, useCase); + + /* + * Make sure that the device is capable of mixing in order to support + * conferencing and call recording. + */ + if (device != null) + { + MediaDevice mixer = mixers[mediaTypeIndex]; + + if (mixer == null) + { + switch (mediaType) + { + case AUDIO: + /* + * TODO AudioMixer leads to very poor audio quality on + * Android so do not use it unless it is really really + * necessary. + */ + if ((!OSUtils.IS_ANDROID || isConferenceFocus()) + && !this.translator + /* + * We can use the AudioMixer only if the device is + * able to capture (because the AudioMixer will push + * when the capture device pushes). + */ + && device.getDirection().allowsSending()) + { + mixer = mediaService.createMixer(device); + } + break; + + case VIDEO: + if (isConferenceFocus()) + mixer = mediaService.createMixer(device); + break; + } + + mixers[mediaTypeIndex] = mixer; + } + + if (mixer != null) + device = mixer; + } + + return device; + } + + /** + * Gets the <tt>VolumeControl</tt> which controls the volume (level) of the + * audio played back in the telephony conference represented by this + * instance. + * + * @return the <tt>VolumeControl</tt> which controls the volume (level) of + * the audio played back in the telephony conference represented by this + * instance + */ + public VolumeControl getOutputVolumeControl() + { + return outputVolumeControl; + } + + /** + * Gets the <tt>RTPTranslator</tt> which forwards RTP and RTCP traffic + * between the <tt>CallPeer</tt>s of the <tt>Call</tt>s participating in + * this telephony conference when the local peer 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 the <tt>Call</tt>s participating in + * this telephony conference when the local peer is acting as a conference + * focus + */ + public RTPTranslator getRTPTranslator(MediaType mediaType) + { + /* + * XXX A mixer is created for audio even when the local peer is not a + * conference focus in order to enable additional functionality. + * Similarly, the videoRTPTranslator is created even when the local peer + * is not a conference focus in order to enable the local peer to turn + * into a conference focus at a later time. More specifically, + * MediaStreamImpl is unable to accommodate an RTPTranslator after it + * has created its RTPManager. Yet again like the audio mixer, we'd + * better not try to use it on Android at this time because of + * performance issues that might arise. + */ + if (MediaType.VIDEO.equals(mediaType) + && (!OSUtils.IS_ANDROID || isConferenceFocus())) + { + if (videoRTPTranslator == null) + { + videoRTPTranslator + = ProtocolMediaActivator + .getMediaService() + .createRTPTranslator(); + } + return videoRTPTranslator; + } + + if (this.translator) + { + if(audioRTPTranslator == null) + { + audioRTPTranslator + = ProtocolMediaActivator + .getMediaService() + .createRTPTranslator(); + } + return audioRTPTranslator; + } + + return null; + } + + /** + * Notifies this <tt>MediaAwareCallConference</tt> about changes in the + * values of the properties of sources of <tt>PropertyChangeEvent</tt>s. For + * example, this instance listens to changes of the value of + * {@link MediaService#DEFAULT_DEVICE} which represents the user's choice + * with respect to the default audio device. + * + * @param ev a <tt>PropertyChangeEvent</tt> which specifies the name of the + * property which had its value changed and the old and new values of that + * property + */ + private void propertyChange(PropertyChangeEvent ev) + { + String propertyName = ev.getPropertyName(); + + if (MediaService.DEFAULT_DEVICE.equals(propertyName)) + { + Object source = ev.getSource(); + + if (source instanceof MediaService) + { + /* + * XXX We only support changing the default audio device at the + * time of this writing. + */ + int mediaTypeIndex = MediaType.AUDIO.ordinal(); + MediaDevice mixer = mixers[mediaTypeIndex]; + MediaDevice oldValue + = (mixer instanceof MediaDeviceWrapper) + ? ((MediaDeviceWrapper) mixer).getWrappedDevice() + : null; + MediaDevice newValue = devices[mediaTypeIndex]; + + if (newValue == null) + { + newValue + = ProtocolMediaActivator + .getMediaService() + .getDefaultDevice( + MediaType.AUDIO, + MediaUseCase.ANY); + } + + /* + * XXX If MediaService#getDefaultDevice(MediaType, MediaUseCase) + * above returns null and its earlier return value was not null, + * we will not notify of an actual change in the value of the + * user's choice with respect to the default audio device. + */ + if (oldValue != newValue) + { + mixers[mediaTypeIndex] = null; + firePropertyChange( + MediaAwareCall.DEFAULT_DEVICE, + oldValue, newValue); + } + } + } + } + + /** + * Sets the <tt>MediaDevice</tt> to be used by this telephony conference for + * capture and/or playback of media of a specific <tt>MediaType</tt>. + * + * @param mediaType the <tt>MediaType</tt> of the media which is to be + * captured and/or played back by the specified <tt>device</tt> + * @param device the <tt>MediaDevice</tt> to be used by this telephony + * conference for capture and/or playback of media of the specified + * <tt>mediaType</tt> + */ + void setDevice(MediaType mediaType, MediaDevice device) + { + int mediaTypeIndex = mediaType.ordinal(); + MediaDevice oldValue = devices[mediaTypeIndex]; + + /* + * XXX While we know the old and the new master/wrapped devices, we + * are not sure whether the mixer has been used. Anyway, we have to + * report different values in order to have PropertyChangeSupport + * really fire an event. + */ + MediaDevice mixer = mixers[mediaTypeIndex]; + + if (mixer instanceof MediaDeviceWrapper) + oldValue = ((MediaDeviceWrapper) mixer).getWrappedDevice(); + + MediaDevice newValue = devices[mediaTypeIndex] = device; + + if (oldValue != newValue) + { + mixers[mediaTypeIndex] = null; + firePropertyChange( + MediaAwareCall.DEFAULT_DEVICE, + oldValue, newValue); + } + } + + /** + * Implements a <tt>PropertyChangeListener</tt> which weakly references and + * delegates to specific <tt>PropertyChangeListener</tt>s and automatically + * adds itself to and removes itself from a specific + * <tt>PropertyChangeNotifier</tt> depending on whether there are + * <tt>PropertyChangeListener</tt>s to delegate to. Thus enables listening + * to a <tt>PropertyChangeNotifier</tt> by invoking + * {@link PropertyChangeNotifier#addPropertyChangeListener( + * PropertyChangeListener)} without + * {@link PropertyChangeNotifier#removePropertyChangeListener( + * PropertyChangeListener)}. + */ + private static class WeakPropertyChangeListener + implements PropertyChangeListener + { + /** + * The indicator which determines whether this + * <tt>PropertyChangeListener</tt> has been added to {@link #notifier}. + */ + private boolean added = false; + + /** + * The list of <tt>PropertyChangeListener</tt>s which are to be notified + * about <tt>PropertyChangeEvent</tt>s fired by {@link #notifier}. + */ + private final List<WeakReference<PropertyChangeListener>> listeners + = new LinkedList<WeakReference<PropertyChangeListener>>(); + + /** + * The <tt>PropertyChangeNotifier</tt> this instance is to listen to + * about <tt>PropertyChangeEvent</tt>s which are to be forwarded to + * {@link #listeners}. + */ + private final PropertyChangeNotifier notifier; + + /** + * Initializes a new <tt>WeakPropertyChangeListener</tt> instance. + */ + protected WeakPropertyChangeListener() + { + this(null); + } + + /** + * Initializes a new <tt>WeakPropertyChangeListener</tt> instance which + * is to listen to a specific <tt>PropertyChangeNotifier</tt>. + * + * @param notifier the <tt>PropertyChangeNotifier</tt> the new instance + * is to listen to + */ + public WeakPropertyChangeListener(PropertyChangeNotifier notifier) + { + this.notifier = notifier; + } + + /** + * Adds a specific <tt>PropertyChangeListener</tt> to the list of + * <tt>PropertyChangeListener</tt>s to be notified about + * <tt>PropertyChangeEvent</tt>s fired by the + * <tt>PropertyChangeNotifier</tt> associated with this instance. + * + * @param listener the <tt>PropertyChangeListener</tt> to add + */ + public synchronized void addPropertyChangeListener( + PropertyChangeListener listener) + { + Iterator<WeakReference<PropertyChangeListener>> i + = listeners.iterator(); + boolean add = true; + + while (i.hasNext()) + { + PropertyChangeListener l = i.next().get(); + + if (l == null) + i.remove(); + else if (l.equals(listener)) + add = false; + } + if (add + && listeners.add( + new WeakReference<PropertyChangeListener>(listener)) + && !this.added) + { + addThisToNotifier(); + this.added = true; + } + } + + /** + * Adds this as a <tt>PropertyChangeListener</tt> to {@link #notifier}. + */ + protected void addThisToNotifier() + { + if (notifier != null) + notifier.addPropertyChangeListener(this); + } + + /** + * {@inheritDoc} + * + * Notifies this instance about a <tt>PropertyChangeEvent</tt> fired by + * {@link #notifier}. + */ + @Override + public void propertyChange(PropertyChangeEvent ev) + { + PropertyChangeListener[] ls; + int n; + + synchronized (this) + { + Iterator<WeakReference<PropertyChangeListener>> i + = listeners.iterator(); + + ls = new PropertyChangeListener[listeners.size()]; + n = 0; + while (i.hasNext()) + { + PropertyChangeListener l = i.next().get(); + + if (l == null) + i.remove(); + else + ls[n++] = l; + } + if ((n == 0) && this.added) + { + removeThisFromNotifier(); + this.added = false; + } + } + + if (n != 0) + { + for (PropertyChangeListener l : ls) + { + if (l == null) + break; + else + l.propertyChange(ev); + } + } + } + + /** + * Removes a specific <tt>PropertyChangeListener</tt> from the list of + * <tt>PropertyChangeListener</tt>s to be notified about + * <tt>PropertyChangeEvent</tt>s fired by the + * <tt>PropertyChangeNotifier</tt> associated with this instance. + * + * @param listener the <tt>PropertyChangeListener</tt> to remove + */ + @SuppressWarnings("unused") + public synchronized void removePropertyChangeListener( + PropertyChangeListener listener) + { + Iterator<WeakReference<PropertyChangeListener>> i + = listeners.iterator(); + + while (i.hasNext()) + { + PropertyChangeListener l = i.next().get(); + + if ((l == null) || l.equals(listener)) + i.remove(); + } + if (this.added && (listeners.size() == 0)) + { + removeThisFromNotifier(); + this.added = false; + } + } + + /** + * Removes this as a <tt>PropertyChangeListener</tt> from + * {@link #notifier}. + */ + protected void removeThisFromNotifier() + { + if (notifier != null) + notifier.removePropertyChangeListener(this); + } + } +} |