Package net.java.sip.communicator.service.protocol.media

Source Code of net.java.sip.communicator.service.protocol.media.CallPeerMediaHandler

/*
* SIP Communicator, the OpenSource Java VoIP and Instant Messaging client.
*
* Distributable under LGPL license.
* See terms of license at gnu.org.
*/
package net.java.sip.communicator.service.protocol.media;

import java.awt.*;
import java.beans.*;
import java.util.*;
import java.util.List;

import net.java.sip.communicator.service.neomedia.*;
import net.java.sip.communicator.service.neomedia.control.*;
import net.java.sip.communicator.service.neomedia.device.*;
import net.java.sip.communicator.service.neomedia.event.*;
import net.java.sip.communicator.service.neomedia.format.*;
import net.java.sip.communicator.service.protocol.*;
import net.java.sip.communicator.service.protocol.event.SizeChangeVideoEvent;
import net.java.sip.communicator.service.protocol.event.VideoEvent;
import net.java.sip.communicator.service.protocol.event.VideoListener;
import net.java.sip.communicator.util.*;

/**
* 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>
*
* @author Emil Ivov
*/
public abstract class CallPeerMediaHandler<
                                        T extends MediaAwareCallPeer<?, ?, ?>>
    extends PropertyChangeNotifier
{
    /**
     * The <tt>Logger</tt> used by the <tt>CallPeerMediaHandler</tt>
     * class and its instances for logging output.
     */
    private static final Logger logger
        = Logger.getLogger(CallPeerMediaHandler.class);

    /**
     * The name of the <tt>CallPeerMediaHandler</tt> property which specifies
     * the local SSRC of its audio <tt>MediaStream</tt>.
     */
    public static final String AUDIO_LOCAL_SSRC = "AUDIO_LOCAL_SSRC";

    /**
     * The name of the <tt>CallPeerMediaHandler</tt> property which specifies
     * the remote SSRC of its audio <tt>MediaStream</tt>.
     */
    public static final String AUDIO_REMOTE_SSRC = "AUDIO_REMOTE_SSRC";

    /**
     * The constant which signals that a SSRC value is unknown.
     */
    public static final long SSRC_UNKNOWN = -1;

    /**
     * The name of the <tt>CallPeerMediaHandler</tt> property which specifies
     * the local SSRC of its video <tt>MediaStream</tt>.
     */
    public static final String VIDEO_LOCAL_SSRC = "VIDEO_LOCAL_SSRC";

    /**
     * The name of the <tt>CallPeerMediaHandler</tt> property which specifies
     * the remote SSRC of its video <tt>MediaStream</tt>.
     */
    public static final String VIDEO_REMOTE_SSRC = "VIDEO_REMOTE_SSRC";

    /**
     * Determines whether or not streaming local video is currently enabled.
     * Default is RECVONLY. We tried to have INACTIVE at one point but it was
     * breaking incoming reINVITEs for video calls..
     */
    private MediaDirection videoDirectionUserPreference
        = MediaDirection.RECVONLY;

    /**
     * Determines whether or not streaming local audio is currently enabled.
     */
    private MediaDirection audioDirectionUserPreference
        = MediaDirection.SENDRECV;

    /**
     * A reference to the CallPeer instance that this handler is managing media
     * streams for.
     */
    private final T peer;

    /**
     * A reference to the object that would be responsible for ZRTP control
     * and which most often would be the peer itself.
     */
    public final ZrtpListener zrtpController;

    /**
     * The RTP stream that this media handler uses to send audio.
     */
    private AudioMediaStream audioStream = null;

    /**
     * The last-known local SSRC of {@link #audioStream}.
     */
    private long audioLocalSSRC = SSRC_UNKNOWN;

    /**
     * The last-known remote SSRC of {@link #audioStream}.
     */
    private long audioRemoteSSRC = SSRC_UNKNOWN;

    /**
     * The RTP stream that this media handler uses to send video.
     */
    private VideoMediaStream videoStream = null;

    /**
     * The last-known local SSRC of {@link #videoStream}.
     */
    private long videoLocalSSRC = SSRC_UNKNOWN;

    /**
     * The last-known remote SSRC of {@link #videoStream}.
     */
    private long videoRemoteSSRC = SSRC_UNKNOWN;

    /**
     * The listener that the <tt>CallPeer</tt> registered for local user audio
     * level events.
     */
    private SimpleAudioLevelListener localAudioLevelListener = null;

    /**
     * The object that we are using to sync operations on
     * <tt>localAudioLevelListener</tt>.
     */
    private final Object localAudioLevelListenerLock = new Object();

    /**
     * The listener that our <tt>CallPeer</tt> registered for stream audio
     * level events.
     */
    private SimpleAudioLevelListener streamAudioLevelListener = null;

    /**
     * The object that we are using to sync operations on
     * <tt>streamAudioLevelListener</tt>.
     */
    private final Object streamAudioLevelListenerLock = new Object();

    /**
     * The listener that our <tt>CallPeer</tt> registers for CSRC audio level
     * events.
     */
    private CsrcAudioLevelListener csrcAudioLevelListener = null;

    /**
     * The object that we are using to sync operations on
     * <tt>csrcAudioLevelListener</tt>.
     */
    private final Object csrcAudioLevelListenerLock = new Object();

    /**
     * Determines whether we have placed the call on hold locally.
     */
    private boolean locallyOnHold = false;

    /**
     * Indicates whether this handler has already started at least one of its
     * streams, at least once.
     */
    private boolean started = false;

    /**
     * Contains all dynamic payload type mappings that have been made for this
     * call.
     */
    private final DynamicPayloadTypeRegistry dynamicPayloadTypes
        = new DynamicPayloadTypeRegistry();

    /**
     * Contains all RTP extension mappings (those made through the extmap
     * attribute) that have been bound during this call.
     */
    private final DynamicRTPExtensionsRegistry rtpExtensionsRegistry
        = new DynamicRTPExtensionsRegistry();

    /**
     * Holds the ZRTP controls used for the current call.
     */
    private Map<MediaType, ZrtpControl> zrtpControls =
        new Hashtable<MediaType, ZrtpControl>();

    /**
     * The <tt>KeyFrameControl</tt> currently known to this
     * <tt>CallPeerMediaHandlerSipImpl</tt> and made available by
     * {@link #videoStream}.
     */
    private KeyFrameControl keyFrameControl;

    /**
     * The <tt>KeyFrameRequester</tt> implemented by this
     * <tt>CallPeerMediaHandlerSipImpl</tt> and provided to
     * {@link #keyFrameControl}.
     */
    private final KeyFrameControl.KeyFrameRequester keyFrameRequester
        = new KeyFrameControl.KeyFrameRequester()
        {
            public boolean requestKeyFrame()
            {
                return CallPeerMediaHandler.this.requestKeyFrame();
            }
        };

    /**
     * The <tt>List</tt> of <tt>VideoListener</tt>s interested in
     * <tt>VideoEvent</tt>s fired by this instance or rather its
     * <tt>VideoMediaStream</tt>.
     */
    private final List<VideoListener> videoListeners
        = new LinkedList<VideoListener>();

    /**
     * The <tt>PropertyChangeListener</tt> which listens to changes in the
     * values of the properties of {@link #audioStream} and
     * {@link #videoStream}.
     */
    private final PropertyChangeListener streamPropertyChangeListener
        = new PropertyChangeListener()
    {

        /**
         * Notifies this <tt>PropertyChangeListener</tt> that the value of
         * a specific property of the notifier it is registered with has
         * changed.
         *
         * @param evt a <tt>PropertyChangeEvent</tt> which describes the
         * source of the event, the name of the property which has changed
         * its value and the old and new values of the property
         * @see PropertyChangeListener#propertyChange(PropertyChangeEvent)
         */
        public void propertyChange(PropertyChangeEvent evt)
        {
            String propertyName = evt.getPropertyName();

            if (MediaStream.PNAME_LOCAL_SSRC.equals(propertyName))
            {
                Object source = evt.getSource();

                if (source == audioStream)
                    setAudioLocalSSRC(audioStream.getLocalSourceID());
                else if (source == videoStream)
                    setVideoLocalSSRC(videoStream.getLocalSourceID());
            }
            else if (MediaStream.PNAME_REMOTE_SSRC.equals(propertyName))
            {
                Object source = evt.getSource();

                if (source == audioStream)
                    setAudioRemoteSSRC(audioStream.getRemoteSourceID());
                else if (source == videoStream)
                    setVideoRemoteSSRC(videoStream.getRemoteSourceID());
            }
        }
    };

    /**
     * The neomedia <tt>VideoListener</tt> which listens to {@link #videoStream}
     * for changes in the availability of visual <tt>Component</tt>s displaying
     * remote video and re-fires them as
     * <tt>net.java.sip.communicator.service.protocol.event.VideoEvent</tt>s
     * originating from this instance.
     */
    private final net.java.sip.communicator.service.neomedia.event.VideoListener
        videoStreamVideoListener = new net.java.sip.communicator.service
            .neomedia.event.VideoListener()
    {
        /**
         * Notifies this neomedia <tt>VideoListener</tt> that a new visual
         * <tt>Component</tt> displaying remote video has been added in
         * {@link CallPeerMediaHandler#videoStream}.
         *
         * @param event the neomedia <tt>VideoEvent</tt> which specifies the
         * newly-added visual <tt>Component</tt> displaying remote video
         */
        public void videoAdded(
                net.java.sip.communicator.service.neomedia.event.VideoEvent
                    event)
        {
            if (fireVideoEvent(
                    event.getType(),
                    event.getVisualComponent(),
                    event.getOrigin()))
                event.consume();
        }

        /**
         * Notifies this neomedia <tt>VideoListener</tt> that a visual
         * <tt>Component</tt> displaying remote video has been removed from
         * {@link CallPeerMediaHandler#videoStream}.
         *
         * @param event the neomedia <tt>VideoEvent</tt> which specifies the
         * removed visual <tt>Component</tt> displaying remote video
         */
        public void videoRemoved(
                net.java.sip.communicator.service.neomedia.event.VideoEvent
                    event)
        {
            // VIDEO_REMOVED is forwarded the same way as VIDEO_ADDED is.
            videoAdded(event);
        }

        public void videoUpdate(
                net.java.sip.communicator.service.neomedia.event.VideoEvent
                    event)
        {
            fireVideoEvent(
                neomedia2protocol(event, CallPeerMediaHandler.this));
        }
    };

    /**
     * Creates a new handler that will be managing media streams for
     * <tt>peer</tt>.
     *
     * @param peer that <tt>CallPeer</tt> instance that we will be managing
     * media for.
     * @param zrtpController the object that would be responsible for
     * controlling zrtp, and which most often would be the peer itself.
     */
    public CallPeerMediaHandler(T            peer,
                                ZrtpListener zrtpController)
    {
        this.peer = peer;
        this.zrtpController = zrtpController;
    }

    /**
     * Puts all <tt>MediaStream</tt>s in this handler locally on or off hold
     * (according to the value of <tt>locallyOnHold</tt>). This would also be
     * taken into account when the next update offer is generated.
     *
     * @param locallyOnHold <tt>true</tt> if we are to make our audio stream
     * stop transmitting and <tt>false</tt> if we are to start transmitting
     * again.
     */
    public void setLocallyOnHold(boolean locallyOnHold)
    {
        this.locallyOnHold = locallyOnHold;

        if(locallyOnHold)
        {
            if(audioStream != null)
            {
                audioStream.setDirection(audioStream.getDirection()
                            .and(MediaDirection.SENDONLY));
                audioStream.setMute(locallyOnHold);
            }
            if(videoStream != null)
            {
                videoStream.setDirection(videoStream.getDirection()
                            .and(MediaDirection.SENDONLY));
                videoStream.setMute(locallyOnHold);
            }
        }
        else
        {
            //off hold - make sure that we re-enable sending, only
            // if other party is not on hold
            if (CallPeerState.ON_HOLD_MUTUALLY.equals(
                    getPeer().getState()))
            {
                return;
            }

            if(audioStream != null)
            {
                audioStream.setDirection(audioStream.getDirection()
                            .or(MediaDirection.SENDONLY));
                audioStream.setMute(locallyOnHold);
            }
            if(videoStream != null
                && videoStream.getDirection() != MediaDirection.INACTIVE)
            {
                videoStream.setDirection(videoStream.getDirection()
                            .or(MediaDirection.SENDONLY));
                videoStream.setMute(locallyOnHold);
            }
        }
    }

    /**
     * Closes and null-ifies all streams and connectors and readies this media
     * handler for garbage collection (or reuse). Synchronized if any other
     * stream operations are in process we won't interrupt them.
     */
    public synchronized void close()
    {
        closeStream(MediaType.AUDIO);
        closeStream(MediaType.VIDEO);

        locallyOnHold = false;
    }

    /**
     * Closes the <tt>MediaStream</tt> that this <tt>MediaHandler</tt> uses for
     * specified media <tt>type</tt> and prepares it for garbage collection.
     *
     * @param type the <tt>MediaType</tt> that we'd like to stop a stream for.
     */
    protected void closeStream(MediaType type)
    {
        if( type == MediaType.AUDIO)
            setAudioStream(null);
        else
            setVideoStream(null);

        getTransportManager().closeStreamConnector(type);

        // Clear the ZRTP controls used for the associated Call.
        ZrtpControl zrtpCtrl = zrtpControls.get(type);

        if (zrtpCtrl != null)
        {
            zrtpCtrl.cleanup();
            zrtpControls.remove(type);
        }
    }

    /**
     * Determines whether this handler's streams have been placed on hold.
     *
     * @return <tt>true</tt> if this handler's streams have been placed on hold
     * and <tt>false</tt> otherwise.
     */
    public boolean isLocallyOnHold()
    {
        return locallyOnHold;
        //no need to actually check stream directions because we only update
        //them through the setLocallyOnHold() method so if the value of the
        //locallyOnHold field has changed, so have stream directions.
    }

    /**
     * Causes this handler's <tt>AudioMediaStream</tt> to stop transmitting the
     * audio being fed from this stream's <tt>MediaDevice</tt> and transmit
     * silence instead.
     *
     * @param mute <tt>true</tt> if we are to make our audio stream start
     * transmitting silence and <tt>false</tt> if we are to end the transmission
     * of silence and use our stream's <tt>MediaDevice</tt> again.
     */
    public void setMute(boolean mute)
    {
        if (audioStream != null)
            audioStream.setMute(mute);
    }

    /**
     * Creates a new
     * <tt>net.java.sip.communicator.service.protocol.event.VideoEvent</tt>
     * instance which represents the same notification/information as a specific
     * <tt>net.java.sip.communicator.service.neomedia.event.VideoEvent</tt>.
     *
     * @param neomediaEvent the
     * <tt>net.java.sip.communicator.service.neomedia.event.VideoEvent</tt> to
     * represent as a
     * <tt>net.java.sip.communicator.service.protocol.event.VideoEvent</tt>
     * @param sender the <tt>Object</tt> to be reported as the source of the
     * new <tt>VideoEvent</tt>
     * @return a new
     * <tt>net.java.sip.communicator.service.protocol.event.VideoEvent</tt>
     * which represents the same notification/information as the specified
     * <tt>neomediaEvent</tt>
     */
    private static VideoEvent neomedia2protocol(
            net.java.sip.communicator.service.neomedia.event.VideoEvent
                neomediaEvent,
            Object sender)
    {
        if (neomediaEvent instanceof net.java.sip.communicator.service
                                          .neomedia.event.SizeChangeVideoEvent)
        {
            net.java.sip.communicator.service.neomedia.event.SizeChangeVideoEvent
                neomediaSizeChangeEvent
                    = (net.java.sip.communicator.service.neomedia.event
                                    .SizeChangeVideoEvent)neomediaEvent;

            return
                new SizeChangeVideoEvent(
                        sender,
                        neomediaEvent.getVisualComponent(),
                        neomediaEvent.getOrigin(),
                        neomediaSizeChangeEvent.getWidth(),
                        neomediaSizeChangeEvent.getHeight());
        }
        else
            throw new IllegalArgumentException("neomediaEvent");
    }

    /**
     * Determines whether the audio stream of this media handler is currently
     * on mute.
     *
     * @return <tt>true</tt> if local audio transmission is currently on mute
     * and <tt>false</tt> otherwise.
     */
    public boolean isMute()
    {
        return (audioStream != null) && audioStream.isMute();
    }

    /**
     * Gets the <tt>MediaDirection</tt> value which represents the preference of
     * the user with respect to streaming media of the specified
     * <tt>MediaType</tt>.
     *
     * @param mediaType the <tt>MediaType</tt> to retrieve the user preference
     * for
     * @return a <tt>MediaDirection</tt> value which represents the preference
     * of the user with respect to streaming media of the specified
     * <tt>mediaType</tt>
     */
    protected MediaDirection getDirectionUserPreference(MediaType mediaType)
    {
        switch (mediaType)
        {
        case AUDIO:
            return audioDirectionUserPreference;
        case VIDEO:
            return videoDirectionUserPreference;
        default:
            throw new IllegalArgumentException("mediaType");
        }
    }

    /**
     * Specifies whether this media handler should be allowed to transmit
     * local video.
     *
     * @param enabled  <tt>true</tt> if the media handler should transmit local
     * video and <tt>false</tt> otherwise.
     */
    public void setLocalVideoTransmissionEnabled(boolean enabled)
    {
        MediaDirection oldValue = videoDirectionUserPreference;
        MediaDirection newValue = null;

        videoDirectionUserPreference
            = enabled ? MediaDirection.SENDRECV : MediaDirection.RECVONLY;

        newValue = videoDirectionUserPreference;

        /* we do not send an event here if video is enabled because we have to
         * wait video stream starts to have correct MediaDevice set in
         * VideoMediaDeviceSession
         */
        if(!enabled)
        {
            firePropertyChange(OperationSetVideoTelephony.LOCAL_VIDEO_STREAMING,
                    oldValue, newValue);
        }
    }

    /**
     * Determines whether this media handler is currently set to transmit local
     * video.
     *
     * @return <tt>true</tt> if the media handler is set to transmit local video
     * and false otherwise.
     */
    public boolean isLocalVideoTransmissionEnabled()
    {
        return videoDirectionUserPreference.allowsSending();
    }

    /**
     * Sets the <tt>KeyFrameControl</tt> currently known to this
     * <tt>CallPeerMediaHandlerSipImpl</tt> made available by a specific
     * <tt>VideoMediaStream</tt>.
     *
     * @param videoStream the <tt>VideoMediaStream</tt> the
     * <tt>KeyFrameControl</tt> of which is to be set as the currently known to
     * this <tt>CallPeerMediaHandlerSipImpl</tt>
     */
    private void setKeyFrameControlFromVideoStream(VideoMediaStream videoStream)
    {
        KeyFrameControl keyFrameControl
            = (videoStream == null) ? null : videoStream.getKeyFrameControl();

        if (this.keyFrameControl != keyFrameControl)
        {
            if (this.keyFrameControl != null)
                this.keyFrameControl.removeKeyFrameRequester(keyFrameRequester);

            this.keyFrameControl = keyFrameControl;

            if (this.keyFrameControl != null)
                this.keyFrameControl.addKeyFrameRequester(-1, keyFrameRequester);
        }
    }

    /**
     * Specifies whether this media handler should be allowed to transmit
     * local audio.
     *
     * @param enabled  <tt>true</tt> if the media handler should transmit local
     * audio and <tt>false</tt> otherwise.
     */
    public void setLocalAudioTransmissionEnabled(boolean enabled)
    {
        audioDirectionUserPreference
            = enabled ? MediaDirection.SENDRECV : MediaDirection.RECVONLY;
    }

    /**
     * Determines whether this media handler is currently set to transmit local
     * audio.
     *
     * @return <tt>true</tt> if the media handler is set to transmit local audio
     * and <tt>false</tt> otherwise.
     */
    public boolean isLocalAudioTransmissionEnabled()
    {
        return audioDirectionUserPreference.allowsSending();
    }

    /**
     * Sets the last-known local SSRC of {@link #audioStream}.
     *
     * @param audioLocalSSRC the last-known local SSRC of {@link #audioStream}
     */
    private void setAudioLocalSSRC(long audioLocalSSRC)
    {
        if (this.audioLocalSSRC != audioLocalSSRC)
        {
            long oldValue = this.audioLocalSSRC;

            this.audioLocalSSRC = audioLocalSSRC;

            firePropertyChange(AUDIO_LOCAL_SSRC, oldValue, this.audioLocalSSRC);
        }
    }

    /**
     * Sets the last-known remote SSRC of {@link #audioStream}.
     *
     * @param audioRemoteSSRC the last-known remote SSRC of {@link #audioStream}
     */
    private void setAudioRemoteSSRC(long audioRemoteSSRC)
    {
        if (this.audioRemoteSSRC != audioRemoteSSRC)
        {
            long oldValue = this.audioRemoteSSRC;

            this.audioRemoteSSRC = audioRemoteSSRC;

            firePropertyChange(
                AUDIO_REMOTE_SSRC,
                oldValue,
                this.audioRemoteSSRC);
        }
    }

    /**
     * Sets the SAS verifications state of the call.
     *
     * @param isVerified the new SAS verification status
     */
    public void setSasVerified(boolean isVerified )
    {
        if(audioStream != null)
            audioStream.getZrtpControl().setSASVerification(isVerified);

        if(videoStream != null)
            videoStream.getZrtpControl().setSASVerification(isVerified);
    }

    /**
     * Returns the secure state of the call. If both audio and video is secured.
     *
     * @return the call secure state
     */
    public boolean isSecure()
    {
        /*
         * If a stream for a specific MediaType does not exist, it's said to be
         * secure.
         */
        boolean isAudioSecured
            = (audioStream == null)
                || audioStream.getZrtpControl().getSecureCommunicationStatus();

        if (!isAudioSecured)
            return false;

        boolean isVideoSecured
            = (videoStream == null)
                || videoStream.getZrtpControl().getSecureCommunicationStatus();

        if (!isVideoSecured)
            return false;

        return true;
    }

    /**
     * Passes <tt>multiStreamData</tt> to the video stream that we are using
     * in this media handler (if any) so that the underlying ZRTP lib could
     * properly handle stream security.
     *
     * @param multiStreamData the data that we are supposed to pass to our
     * video stream.
     */
    public void startZrtpMultistream(byte[] multiStreamData)
    {
        if(videoStream != null)
            videoStream.getZrtpControl().setMultistream(multiStreamData);
    }

    /**
     * Gets the last-known remote SSRC of the audio <tt>MediaStream</tt> of this
     * instance.
     *
     * @return the last-known remote SSRC of the audio <tt>MediaStream</tt> of
     * this instance
     */
    public long getAudioRemoteSSRC()
    {
        return audioRemoteSSRC;
    }

    /**
     * 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 states of the associated
     * <tt>CallPeer</tt> and <tt>Call</tt>.
     * <p>
     * For example, when the local peer is acting as a conference focus in the
     * <tt>Call</tt> of the associated <tt>CallPeer</tt>, 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 states of the
     * associated <tt>CallPeer</tt> and <tt>Call</tt>
     */
    protected MediaDevice getDefaultDevice(MediaType mediaType)
    {
        return peer.getCall().getDefaultDevice(mediaType);
    }

    /**
     * Registers a specific <tt>VideoListener</tt> with this instance so that it
     * starts receiving notifications from it about changes in the availability
     * of visual <tt>Component</tt>s displaying video.
     *
     * @param listener the <tt>VideoListener</tt> to be registered with this
     * instance and to start receiving notifications from it about changes in
     * the availability of visual <tt>Component</tt>s displaying video
     */
    public void addVideoListener(VideoListener listener)
    {
        if (listener == null)
            throw new NullPointerException("listener");

        synchronized (videoListeners)
        {
            if (!videoListeners.contains(listener))
                videoListeners.add(listener);
        }
    }

    /**
     * Sets the RTP media stream that this instance uses to stream audio to a
     * specific <tt>AudioMediaStream</tt>.
     *
     * @param audioStream the <tt>AudioMediaStream</tt> to be set as the RTP
     * media stream that this instance uses to stream audio
     */
    protected void setAudioStream(AudioMediaStream audioStream)
    {
        if (this.audioStream != audioStream)
        {
            if (this.audioStream != null)
            {
                this.audioStream
                        .removePropertyChangeListener(
                            streamPropertyChangeListener);

                this.audioStream.close();
            }

            this.audioStream = audioStream;

            long audioLocalSSRC;
            long audioRemoteSSRC;

            if (this.audioStream != null)
            {
                this.audioStream
                        .addPropertyChangeListener(
                            streamPropertyChangeListener);
                audioLocalSSRC = this.audioStream.getLocalSourceID();
                audioRemoteSSRC = this.audioStream.getRemoteSourceID();
            }
            else
                audioLocalSSRC = audioRemoteSSRC = SSRC_UNKNOWN;

            setAudioLocalSSRC(audioLocalSSRC);
            setAudioRemoteSSRC(audioRemoteSSRC);
        }
    }

    /**
     * Sets the last-known local SSRC of {@link #videoStream}.
     *
     * @param videoLocalSSRC the last-known local SSRC of {@link #videoStream}
     */
    private void setVideoLocalSSRC(long videoLocalSSRC)
    {
        if (this.videoLocalSSRC != videoLocalSSRC)
        {
            long oldValue = this.videoLocalSSRC;

            this.videoLocalSSRC = videoLocalSSRC;

            firePropertyChange(VIDEO_LOCAL_SSRC, oldValue, this.videoLocalSSRC);
        }
    }

    /**
     * Sets the last-known remote SSRC of {@link #videoStream}.
     *
     * @param videoRemoteSSRC the last-known remote SSRC of {@link #videoStream}
     */
    private void setVideoRemoteSSRC(long videoRemoteSSRC)
    {
        if (this.videoRemoteSSRC != videoRemoteSSRC)
        {
            long oldValue = this.videoRemoteSSRC;

            this.videoRemoteSSRC = videoRemoteSSRC;

            firePropertyChange(
                VIDEO_REMOTE_SSRC,
                oldValue,
                this.videoRemoteSSRC);
        }
    }

    /**
     * Sets the RTP media stream that this instance uses to stream video to a
     * specific <tt>VideoMediaStream</tt>.
     *
     * @param videoStream the <tt>VideoMediaStream</tt> to be set as the RTP
     * media stream that this instance uses to stream video
     */
    private void setVideoStream(VideoMediaStream videoStream)
    {
        if (this.videoStream != videoStream)
        {
            /*
             * Make sure we will no longer notify the registered VideoListeners
             * about changes in the availability of video in the old
             * videoStream.
             */
            Component oldVisualComponent = null;

            if (this.videoStream != null)
            {
                this.videoStream.removePropertyChangeListener(
                        streamPropertyChangeListener);

                this.videoStream.removeVideoListener(videoStreamVideoListener);
                oldVisualComponent = this.videoStream.getVisualComponent();

                /*
                 * The current videoStream is going away so this
                 * CallPeerMediaHandlerSipImpl should no longer use its
                 * KeyFrameControl.
                 */
                setKeyFrameControlFromVideoStream(null);

                this.videoStream.close();
            }

            this.videoStream = videoStream;

            /*
             * The videoStream has just changed so this
             * CallPeerMediaHandlerSipImpl should use its KeyFrameControl.
             */
            setKeyFrameControlFromVideoStream(this.videoStream);

            long videoLocalSSRC;
            long videoRemoteSSRC;
            /*
             * Make sure we will notify the registered VideoListeners about
             * changes in the availability of video in the new videoStream.
             */
            Component newVisualComponent = null;

            if (this.videoStream != null)
            {
                this.videoStream.addPropertyChangeListener(
                        streamPropertyChangeListener);
                videoLocalSSRC = this.videoStream.getLocalSourceID();
                videoRemoteSSRC = this.videoStream.getRemoteSourceID();

                this.videoStream.addVideoListener(videoStreamVideoListener);
                newVisualComponent = this.videoStream.getVisualComponent();
            }
            else
                videoLocalSSRC = videoRemoteSSRC = SSRC_UNKNOWN;

            setVideoLocalSSRC(videoLocalSSRC);
            setVideoRemoteSSRC(videoRemoteSSRC);

            /*
             * Notify the VideoListeners in case there was a change in the
             * availability of the visual Components displaying remote video.
             */
            if (oldVisualComponent != null)
                fireVideoEvent(
                    VideoEvent.VIDEO_REMOVED,
                    oldVisualComponent,
                    VideoEvent.REMOTE);
            if (newVisualComponent != null)
                fireVideoEvent(
                    VideoEvent.VIDEO_ADDED,
                    newVisualComponent,
                    VideoEvent.REMOTE);
        }
    }

    /**
     * Notifies the <tt>VideoListener</tt>s registered with this
     * <tt>CallPeerMediaHandler</tt> about a specific type of change in the
     * availability of a specific visual <tt>Component</tt> depicting video.
     *
     * @param type the type of change as defined by <tt>VideoEvent</tt> in the
     * availability of the specified visual <tt>Component</tt> depicting video
     * @param visualComponent the visual <tt>Component</tt> depicting video
     * which has been added or removed in this <tt>CallPeerMediaHandler</tt>
     * @param origin {@link VideoEvent#LOCAL} if the origin of the video is
     * local (e.g. it is being locally captured); {@link VideoEvent#REMOTE} if
     * the origin of the video is remote (e.g. a remote peer is streaming it)
     * @return <tt>true</tt> if this event and, more specifically, the visual
     * <tt>Component</tt> it describes have been consumed and should be
     * considered owned, referenced (which is important because
     * <tt>Component</tt>s belong to a single <tt>Container</tt> at a time);
     * otherwise, <tt>false</tt>
     */
    protected boolean fireVideoEvent(
            int type,
            Component visualComponent,
            int origin)
    {
        VideoListener[] listeners;

        synchronized (videoListeners)
        {
            listeners
                = videoListeners
                    .toArray(new VideoListener[videoListeners.size()]);
        }

        boolean consumed;

        if (listeners.length > 0)
        {
            VideoEvent event
                = new VideoEvent(this, type, visualComponent, origin);

            for (VideoListener listener : listeners)
                switch (type)
                {
                    case VideoEvent.VIDEO_ADDED:
                        listener.videoAdded(event);
                        break;
                    case VideoEvent.VIDEO_REMOVED:
                        listener.videoRemoved(event);
                        break;
                    default:
                        throw new IllegalArgumentException("type");
                }

            consumed = event.isConsumed();
        }
        else
            consumed = false;
        return consumed;
    }

    /**
     * Notifies the <tt>VideoListener</tt>s registered with this
     * <tt>CallPeerMediaHandler</tt> about a specific <tt>VideoEvent</tt>.
     *
     * @param event the <tt>VideoEvent</tt> to fire to the
     * <tt>VideoListener</tt>s registered with this
     * <tt>CallPeerMediaHandler</tt>
     */
    public void fireVideoEvent(VideoEvent event)
    {
        VideoListener[] listeners;

        synchronized (videoListeners)
        {
            listeners
                = videoListeners
                    .toArray(new VideoListener[videoListeners.size()]);
        }

        for (VideoListener listener : listeners)
            switch (event.getType())
            {
                case VideoEvent.VIDEO_ADDED:
                    listener.videoAdded(event);
                    break;
                case VideoEvent.VIDEO_REMOVED:
                    listener.videoRemoved(event);
                    break;
                default:
                    listener.videoUpdate(event);
                    break;
            }
    }

    /**
     * Gets local visual <tt>Component</tt> of the local peer.
     *
     * @return visual <tt>Component</tt>
     */
    public Component createLocalVisualComponent()
    {
        return
            ((videoStream == null) || !isLocalVideoTransmissionEnabled())
                ? null
                : videoStream.createLocalVisualComponent();
    }

    /**
     * Disposes of a specific local visual <tt>Component</tt> of the local peer.
     *
     * @param component the local visual <tt>Component</tt> of the local peer to
     * dispose of
     */
    public void disposeLocalVisualComponent(Component component)
    {
        if (videoStream != null)
            videoStream.disposeLocalVisualComponent(component);
    }

    /**
     * Gets the visual <tt>Component</tt> in which video from the remote peer is
     * currently being rendered or <tt>null</tt> if there is currently no video
     * streaming from the remote peer.
     *
     * @return the visual <tt>Component</tt> in which video from the remote peer
     * is currently being rendered or <tt>null</tt> if there is currently no
     * video streaming from the remote peer
     */
    public Component getVisualComponent()
    {
        return (videoStream == null) ? null : videoStream.getVisualComponent();
    }

    /**
     * Unregisters a specific <tt>VideoListener</tt> from this instance so that
     * it stops receiving notifications from it about changes in the
     * availability of visual <tt>Component</tt>s displaying video.
     *
     * @param listener the <tt>VideoListener</tt> to be unregistered from this
     * instance and to stop receiving notifications from it about changes in the
     * availability of visual <tt>Component</tt>s displaying video
     */
    public void removeVideoListener(VideoListener listener)
    {
        if (listener != null)
            synchronized (videoListeners)
            {
                videoListeners.remove(listener);
            }
    }

    /**
     * If the local <tt>AudioMediaStream</tt> has already been created, sets
     * <tt>listener</tt> as the <tt>SimpleAudioLevelListener</tt> that it should
     * notify for local user level events. Otherwise stores a reference to
     * <tt>listener</tt> so that we could add it once we create the stream.
     *
     * @param listener the <tt>SimpleAudioLevelListener</tt> to add or
     * <tt>null</tt> if we are trying to remove it.
     */
    public void setLocalUserAudioLevelListener(
                                            SimpleAudioLevelListener listener)
    {
        synchronized(localAudioLevelListenerLock)
        {
            this.localAudioLevelListener = listener;

            if(audioStream != null)
                audioStream.setLocalUserAudioLevelListener(listener);
        }
    }

    /**
     * If the local <tt>AudioMediaStream</tt> has already been created, sets
     * <tt>listener</tt> as the <tt>SimpleAudioLevelListener</tt> that it should
     * notify for stream user level events. Otherwise stores a reference to
     * <tt>listener</tt> so that we could add it once we create the stream.
     *
     * @param listener the <tt>SimpleAudioLevelListener</tt> to add or
     * <tt>null</tt> if we are trying to remove it.
     *
     */
    public void setStreamAudioLevelListener(SimpleAudioLevelListener listener)
    {
        synchronized(streamAudioLevelListenerLock)
        {
            this.streamAudioLevelListener = listener;

            if(audioStream != null)
                audioStream.setStreamAudioLevelListener(listener);
        }
    }

    /**
     * Sets <tt>csrcAudioLevelListener</tt> as the listener that will be
     * receiving notifications for changes in the audio levels of the remote
     * participants that our peer is mixing.
     *
     * @param csrcAudioLevelListener the <tt>CsrcAudioLevelListener</tt> to set
     * to our audio streams.
     */
    public void setCsrcAudioLevelListener(
                                CsrcAudioLevelListener csrcAudioLevelListener)
    {
        synchronized(csrcAudioLevelListenerLock)
        {
            this.csrcAudioLevelListener = csrcAudioLevelListener;

            if(audioStream != null)
                audioStream.setCsrcAudioLevelListener(csrcAudioLevelListener);
        }
    }

    /**
     * Returns the currently valid <tt>ZrtpControls</tt> map.
     *
     * @return the currently valid <tt>ZrtpControls</tt> map.
     */
    protected Map<MediaType, ZrtpControl> getZrtpControls()
    {
        return this.zrtpControls;
    }

    /**
     * Creates if necessary, and configures the stream that this
     * <tt>MediaHandler</tt> is using for the <tt>MediaType</tt> matching the
     * one of the <tt>MediaDevice</tt>.
     *
     * @param connector the <tt>MediaConnector</tt> that we'd like to bind the
     * newly created stream to.
     * @param device the <tt>MediaDevice</tt> that we'd like to attach the newly
     * created <tt>MediaStream</tt> to.
     * @param format the <tt>MediaFormat</tt> that we'd like the new
     * <tt>MediaStream</tt> to be set to transmit in.
     * @param target the <tt>MediaStreamTarget</tt> containing the RTP and RTCP
     * address:port couples that the new stream would be sending packets to.
     * @param direction the <tt>MediaDirection</tt> that we'd like the new
     * stream to use (i.e. sendonly, sendrecv, recvonly, or inactive).
     * @param rtpExtensions the list of <tt>RTPExtension</tt>s that should be
     * enabled for this stream.
     *
     * @return the newly created <tt>MediaStream</tt>.
     *
     * @throws OperationFailedException if creating the stream fails for any
     * reason (like for example accessing the device or setting the format).
     */
    protected MediaStream initStream(StreamConnector      connector,
                                     MediaDevice          device,
                                     MediaFormat          format,
                                     MediaStreamTarget    target,
                                     MediaDirection       direction,
                                     List<RTPExtension>   rtpExtensions)
        throws OperationFailedException
    {
        MediaType mediaType = device.getMediaType();
        MediaStream stream = getStream(mediaType);

        if (stream == null)
        {
            if (logger.isTraceEnabled() && (mediaType != format.getMediaType()))
                logger.trace("The media types of device and format differ.");

            // check whether a control already exists
            ZrtpControl control = zrtpControls.get(mediaType);
            MediaService mediaService
                = ProtocolMediaActivator.getMediaService();

            if(control == null)
                stream = mediaService.createMediaStream(connector, device);
            else
            {
                stream
                    = mediaService.createMediaStream(
                            connector, device, control);
            }
        }
        else
        {
            //this is a reinit
        }

        return
            configureStream(
                    device, format, target, direction, rtpExtensions, stream);
    }

    /**
     * Configures <tt>stream</tt> to use the specified <tt>format</tt>,
     * <tt>target</tt>, <tt>target</tt>, and <tt>direction</tt>.
     *
     * @param device the <tt>MediaDevice</tt> to be used by <tt>stream</tt>
     * for capture and playback
     * @param format the <tt>MediaFormat</tt> that we'd like the new stream
     * to transmit in.
     * @param target the <tt>MediaStreamTarget</tt> containing the RTP and
     * RTCP address:port couples that the new stream would be sending
     * packets to.
     * @param direction the <tt>MediaDirection</tt> that we'd like the new
     * stream to use (i.e. sendonly, sendrecv, recvonly, or inactive).
     * @param rtpExtensions the list of <tt>RTPExtension</tt>s that should be
     * enabled for this stream.
     * @param stream the <tt>MediaStream</tt> that we'd like to configure.
     *
     * @return the <tt>MediaStream</tt> that we received as a parameter (for
     * convenience reasons).
     *
     * @throws OperationFailedException if setting the <tt>MediaFormat</tt>
     * or connecting to the specified <tt>MediaDevice</tt> fails for some
     * reason.
     */
    protected MediaStream configureStream( MediaDevice          device,
                                           MediaFormat          format,
                                           MediaStreamTarget    target,
                                           MediaDirection       direction,
                                           List<RTPExtension>   rtpExtensions,
                                           MediaStream          stream)
           throws OperationFailedException
    {
        registerDynamicPTsWithStream(stream);
        registerRTPExtensionsWithStream(rtpExtensions, stream);

        stream.setDevice(device);
        stream.setTarget(target);
        stream.setDirection(direction);
        stream.setFormat(format);

        if( stream instanceof AudioMediaStream)
        {
            setAudioStream((AudioMediaStream) stream);

            registerAudioLevelListeners(audioStream);
        }
        else
            setVideoStream((VideoMediaStream) stream);

        if(peer.getCall().isDefaultEncrypted())
        {
            // we use the audio stream for master stream
            // when using ZRTP multistreams.
            ZrtpControl zrtpControl = stream.getZrtpControl();

            zrtpControl.setZrtpListener(zrtpController);
            zrtpControl.start(stream instanceof AudioMediaStream);
        }

        return stream;
    }

    /**
     * Requests a key frame from the remote peer of the associated
     * <tt>VideoMediaStream</tt> of this <tt>CallPeerMediaHandler</tt>. The
     * default implementation provided by <tt>CallPeerMediaHandler</tt> always
     * returns <tt>false</tt>.
     *
     * @return <tt>true</tt> if this <tt>CallPeerMediaHandler</tt> has indeed
     * requested a key frame from the remote peer of its associated
     * <tt>VideoMediaStream</tt> in response to the call; otherwise,
     * <tt>false</tt>
     */
    protected boolean requestKeyFrame()
    {
        return false;
    }

    /**
     * Sends empty UDP packets to target destination data/control ports in order
     * to open port on NAT or RTP proxy if any. In order to be really efficient,
     * this method should be called after we send our offer or answer.
     *
     * @param target <tt>MediaStreamTarget</tt>
     */
    protected void sendHolePunchPacket(MediaStreamTarget target)
    {
        getTransportManager().sendHolePunchPacket(target, MediaType.VIDEO);
    }

    /**
     * Processes a request for a (video) key frame from the remote peer to the
     * local peer.
     *
     * @return <tt>true</tt> if the request for a (video) key frame has been
     * honored by the local peer; otherwise, <tt>false</tt>
     */
    public boolean processKeyFrameRequest()
    {
        KeyFrameControl keyFrameControl = this.keyFrameControl;

        return
            (keyFrameControl == null)
                ? null
                : keyFrameControl.keyFrameRequest();
    }

    /**
     * Registers all audio level listeners currently known to this media handler
     * with the specified <tt>audioStream</tt>.
     *
     * @param audioStream the <tt>AudioMediaStream</tt> that we'd like to
     * register our audio level listeners with.
     */
    private void registerAudioLevelListeners(AudioMediaStream audioStream)
    {
        // if we already have a local level listener - register it now.
        synchronized (localAudioLevelListenerLock)
        {
            if (localAudioLevelListener != null)
                audioStream
                    .setLocalUserAudioLevelListener(localAudioLevelListener);
        }

        // if we already have a stream level listener - register it now.
        synchronized (streamAudioLevelListenerLock)
        {
            if (streamAudioLevelListener != null)
                audioStream
                    .setStreamAudioLevelListener(streamAudioLevelListener);
        }

        // if we already have a csrc level listener - register it now.
        synchronized (csrcAudioLevelListenerLock)
        {
            if (csrcAudioLevelListener != null)
                audioStream.setCsrcAudioLevelListener(csrcAudioLevelListener);
        }
    }

    /**
     * Registers all dynamic payload mappings known to this
     * <tt>MediaHandler</tt> with the specified <tt>MediaStream</tt>.
     *
     * @param stream the <tt>MediaStream</tt> that we'd like to register our
     * dynamic payload mappings with.
     */
    private void registerDynamicPTsWithStream(MediaStream stream)
    {
        for (Map.Entry<MediaFormat, Byte> mapEntry
                : dynamicPayloadTypes.getMappings().entrySet())
        {
            byte pt = mapEntry.getValue();
            MediaFormat fmt = mapEntry.getKey();

            stream.addDynamicRTPPayloadType(pt, fmt);
        }
    }

    /**
     * Registers with the specified <tt>MediaStream</tt> all RTP extensions
     * negotiated by this <tt>MediaHandler</tt>.
     *
     * @param stream the <tt>MediaStream</tt> that we'd like to register our
     * <tt>RTPExtension</tt>s with.
     * @param rtpExtensions the list of <tt>RTPExtension</tt>s that should be
     * enabled for <tt>stream</tt>.
     */
    private void registerRTPExtensionsWithStream(
                                          List<RTPExtension> rtpExtensions,
                                          MediaStream        stream)
    {
        for ( RTPExtension rtpExtension : rtpExtensions)
        {
            byte extensionID
                = rtpExtensionsRegistry.getExtensionMapping(rtpExtension);

            stream.addRTPExtension(extensionID, rtpExtension);
        }
    }

    /**
     * Gets the <tt>MediaStream</tt> of this <tt>CallPeerMediaHandler</tt> which
     * is of a specific <tt>MediaType</tt>. If this instance doesn't have such a
     * <tt>MediaStream</tt>, returns <tt>null</tt>
     *
     * @param mediaType the <tt>MediaType</tt> of the <tt>MediaStream</tt> to
     * retrieve
     * @return the <tt>MediaStream</tt> of this <tt>CallPeerMediaHandler</tt>
     * which is of the specified <tt>mediaType</tt> if this instance has such a
     * <tt>MediaStream</tt>; otherwise, <tt>null</tt>
     */
    public MediaStream getStream(MediaType mediaType)
    {
        switch (mediaType)
        {
        case AUDIO:
            return audioStream;
        case VIDEO:
            return videoStream;
        default:
            throw new IllegalArgumentException("mediaType");
        }
    }

    /**
     * Determines whether the remote party has placed all our streams on hold.
     *
     * @return <tt>true</tt> if all our streams have been placed on hold (i.e.
     * if none of them is currently sending and <tt>false</tt> otherwise.
     */
    public boolean isRemotelyOnHold()
    {
        if(audioStream != null && audioStream.getDirection().allowsSending())
            return false;

        if(videoStream != null && videoStream.getDirection().allowsSending())
            return false;

        return true;
    }

    /**
     * Compares a list of <tt>MediaFormat</tt>s offered by a remote party
     * to the list of locally supported <tt>RTPExtension</tt>s as returned
     * by one of our local <tt>MediaDevice</tt>s and returns a third
     * <tt>List</tt> that contains their intersection.
     *
     * @param remoteFormats remote <tt>MediaFormat</tt> found in the
     * SDP message
     * @param localFormats local supported <tt>MediaFormat</tt> of our device
     * @return intersection between our local and remote <tt>MediaFormat</tt>
     */
    protected List<MediaFormat> intersectFormats(
                                            List<MediaFormat> remoteFormats,
                                            List<MediaFormat> localFormats)
    {
        List<MediaFormat> ret = new ArrayList<MediaFormat>();

        for(MediaFormat remoteFormat : remoteFormats)
        {
            MediaFormat localFormat
                = findMediaFormat(localFormats, remoteFormat);

            if(localFormat != null)
                ret.add(localFormat);
        }
        return ret;
    }

    /**
     * Finds a <tt>MediaFormat</tt> in a specific list of <tt>MediaFormat</tt>s
     * which matches a specific <tt>MediaFormat</tt>.
     *
     * @param formats the list of <tt>MediaFormat</tt>s to find the specified
     * matching <tt>MediaFormat</tt> into
     * @param format encoding of the <tt>MediaFormat</tt> to find
     * @return the <tt>MediaFormat</tt> from <tt>formats</tt> which matches
     * <tt>format</tt> if such a match exists in <tt>formats</tt>; otherwise,
     * <tt>null</tt>
     */
    protected MediaFormat findMediaFormat(
            List<MediaFormat> formats, MediaFormat format)
    {
        MediaType mediaType = format.getMediaType();
        String encoding = format.getEncoding();
        double clockRate = format.getClockRate();
        int channels
            = MediaType.AUDIO.equals(mediaType)
                ? ((AudioMediaFormat) format).getChannels()
                : MediaFormatFactory.CHANNELS_NOT_SPECIFIED;
        Map<String, String> formatParameters = format.getFormatParameters();

        for(MediaFormat match : formats)
        {
            if (AbstractMediaStream.matches(
                        match,
                        mediaType,
                        encoding, clockRate, channels, formatParameters))
                return match;
        }
        return null;
    }

    /**
     * Compares a list of <tt>RTPExtension</tt>s offered by a remote party
     * to the list of locally supported <tt>RTPExtension</tt>s as returned
     * by one of our local <tt>MediaDevice</tt>s and returns a third
     * <tt>List</tt> that contains their intersection. The returned
     * <tt>List</tt> contains extensions supported by both the remote party and
     * the local device that we are dealing with. Direction attributes of both
     * lists are also intersected and the returned <tt>RTPExtension</tt>s have
     * directions valid from a local perspective. In other words, if
     * <tt>remoteExtensions</tt> contains an extension that the remote party
     * supports in a <tt>SENDONLY</tt> mode, and we support that extension in a
     * <tt>SENDRECV</tt> mode, the corresponding entry in the returned list will
     * have a <tt>RECVONLY</tt> direction.
     *
     * @param remoteExtensions the <tt>List</tt> of <tt>RTPExtension</tt>s as
     * advertised by the remote party.
     * @param supportedExtensions the <tt>List</tt> of <tt>RTPExtension</tt>s
     * that a local <tt>MediaDevice</tt> returned as supported.
     *
     * @return the (possibly empty) intersection of both of the extensions lists
     * in a form that can be used for generating an SDP media description or
     * for configuring a stream.
     */
    protected List<RTPExtension> intersectRTPExtensions(
                                    List<RTPExtension> remoteExtensions,
                                    List<RTPExtension> supportedExtensions)
    {
        if(remoteExtensions == null || supportedExtensions == null)
            return new ArrayList<RTPExtension>();

        List<RTPExtension> intersection = new ArrayList<RTPExtension>(
                Math.min(remoteExtensions.size(), supportedExtensions.size()));

        //loop through the list that the remote party sent
        for(RTPExtension remoteExtension : remoteExtensions)
        {
            RTPExtension localExtension = findExtension(
                    supportedExtensions, remoteExtension.getURI().toString());

            if(localExtension == null)
                continue;

            MediaDirection localDir  = localExtension.getDirection();
            MediaDirection remoteDir = remoteExtension.getDirection();

            RTPExtension intersected = new RTPExtension(
                            localExtension.getURI(),
                            localDir.getDirectionForAnswer(remoteDir),
                            remoteExtension.getExtensionAttributes());

            intersection.add(intersected);
        }

        return intersection;
    }

    /**
     * Returns the first <tt>RTPExtension</tt> in <tt>extList</tt> that uses
     * the specified <tt>extensionURN</tt> or <tt>null</tt> if <tt>extList</tt>
     * did not contain such an extension.
     *
     * @param extList the <tt>List</tt> that we will be looking through.
     * @param extensionURN the URN of the <tt>RTPExtension</tt> that we are
     * looking for.
     *
     * @return the first <tt>RTPExtension</tt> in <tt>extList</tt> that uses
     * the specified <tt>extensionURN</tt> or <tt>null</tt> if <tt>extList</tt>
     * did not contain such an extension.
     */
    private RTPExtension findExtension(List<RTPExtension> extList,
                                       String extensionURN)
    {
        for(RTPExtension rtpExt : extList)
            if (rtpExt.getURI().toASCIIString().equals(extensionURN))
                return rtpExt;
        return null;
    }

    /**
     * Returns a (possibly empty) <tt>List</tt> of <tt>RTPExtension</tt>s
     * supported by the device that this media handler uses to handle media of
     * the specified <tt>type</tt>.
     *
     * @param type the <tt>MediaType</tt> of the device whose
     * <tt>RTPExtension</tt>s we are interested in.
     *
     * @return a (possibly empty) <tt>List</tt> of <tt>RTPExtension</tt>s
     * supported by the device that this media handler uses to handle media of
     * the specified <tt>type</tt>.
     */
    protected List<RTPExtension> getExtensionsForType(MediaType type)
    {
        MediaDevice dev = getDefaultDevice(type);

        return dev.getSupportedExtensions();
    }

    /**
     * Returns the {@link DynamicPayloadTypeRegistry} instance we are currently
     * using.
     *
     * @return the {@link DynamicPayloadTypeRegistry} instance we are currently
     * using.
     */
    protected DynamicPayloadTypeRegistry getDynamicPayloadTypes()
    {
        return this.dynamicPayloadTypes;
    }

    /**
     * Returns the {@link DynamicRTPExtensionsRegistry} instance we are
     * currently using.
     *
     * @return the {@link DynamicRTPExtensionsRegistry} instance we are
     * currently using.
     */
    protected DynamicRTPExtensionsRegistry getRtpExtensionsRegistry()
    {
        return this.rtpExtensionsRegistry;
    }

    /**
     * Returns <tt>true</tt> if this handler has already started at least one
     * of its streams, at least once, and <tt>false</tt> otherwise.
     *
     * @return <tt>true</tt> if this handler has already started at least one
     * of its streams, at least once, and <tt>false</tt> otherwise.
     */
    public boolean isStarted()
    {
        return started;
    }

    /**
     * Starts this <tt>CallPeerMediaHandler</tt>. If it has already been
     * started, does nothing.
     *
     * @throws IllegalStateException if this method is called without this
     * handler having first seen a media description or having generated an
     * offer.
     */
    public void start()
        throws IllegalStateException
    {
        if(isStarted())
            return;

        MediaStream stream = getStream(MediaType.AUDIO);
        if ((stream != null)
                && !stream.isStarted()
                && isLocalAudioTransmissionEnabled())
        {
            stream.start();
        }

        stream = getStream(MediaType.VIDEO);
        if ((stream != null))
        {
            /* Inform listener of LOCAL_VIDEO_STREAMING only once the video
             * starts, so that VideoMediaDeviceSession has correct MediaDevice
             * set (switch from desktop streaming to webcam video or vice-versa
             * issue)
             */
            firePropertyChange(OperationSetVideoTelephony.LOCAL_VIDEO_STREAMING,
                    null, this.videoDirectionUserPreference);

            if(!stream.isStarted())
            {
                stream.start();

                // send empty packet to deblock some kind of RTP proxy to let
                // just one user sends its video
                if ((stream instanceof VideoMediaStream)
                        && !isLocalVideoTransmissionEnabled())
                {
                    sendHolePunchPacket(stream.getTarget());
                }
            }
        }
    }

    /**
     * Returns the peer that is this media handler's "raison d'etre".
     *
     * @return the {@link MediaAwareCallPeer} that this handler is servicing.
     */
    public T getPeer()
    {
        return peer;
    }

    /**
     * Lets the underlying implementation take note of this error and only
     * then throws it to the using bundles.
     *
     * @param message the message to be logged and then wrapped in a new
     * <tt>OperationFailedException</tt>
     * @param errorCode the error code to be assigned to the new
     * <tt>OperationFailedException</tt>
     * @param cause the <tt>Throwable</tt> that has caused the necessity to log
     * an error and have a new <tt>OperationFailedException</tt> thrown
     *
     * @throws OperationFailedException the exception that we wanted this method
     * to throw.
     */
    protected abstract void throwOperationFailedException( String    message,
                                                           int       errorCode,
                                                           Throwable cause)
        throws OperationFailedException;

    /**
     * Gets the <tt>TransportManager</tt> implementation handling our address
     * management.
     *
     * @return the <tt>TransportManager</tt> implementation handling our address
     * management
     */
    protected abstract TransportManager<T> getTransportManager();
}
TOP

Related Classes of net.java.sip.communicator.service.protocol.media.CallPeerMediaHandler

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.