/*
* 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 net.java.sip.communicator.service.neomedia.*;
import net.java.sip.communicator.service.protocol.*;
import net.java.sip.communicator.service.protocol.event.*;
/**
* Represents a default implementation of OperationSetVideoTelephony in
* order to make it easier for implementers to provide complete solutions while
* focusing on implementation-specific details.
*
* @param the implementation specific telephony operation set class like for
* example OperationSetBasicTelephonySipImpl.
* @param the implementation specific provider class like for example
* ProtocolProviderServiceSipImpl.
* @param the MediaAwareCall implementation like
* CallSipImpl or CallJabberImpl.
* @param the MediaAwarePeerCall implementation like
* CallPeerSipImpl or CallPeerJabberImpl.
* @author Emil Ivov
* @author Sebastien Vincent
*/
public abstract class AbstractOperationSetVideoTelephony<
T extends OperationSetBasicTelephony,
U extends ProtocolProviderService,
V extends MediaAwareCall,
W extends MediaAwareCallPeer >
implements OperationSetVideoTelephony
{
/**
* The SIP ProtocolProviderService implementation which created
* this instance and for which telephony conferencing services are being
* provided by this instance.
*/
protected final U parentProvider;
/**
* The telephony-related functionality this extension builds upon.
*/
protected final T basicTelephony;
/**
* Initializes a new AbstractOperationSetVideoTelephony instance
* which builds upon the telephony-related functionality of a specific
* OperationSetBasicTelephony implementation.
*
* @param basicTelephony the OperationSetBasicTelephony
* the new extension should build upon
*/
public AbstractOperationSetVideoTelephony(T basicTelephony)
{
this.basicTelephony = basicTelephony;
this.parentProvider = basicTelephony.getProtocolProvider();
}
/**
* Delegates to the CallPeerMediaHandler of the specified
* CallPeer because the video is provided by it. Because other
* OperationSetVideoTelephony implementations may not provide their
* video through the CallPeerMediaHandler, this implementation
* promotes itself as the provider of the video by replacing the
* CallPeerMediaHandler in the VideoEvents it fires.
*
* @param peer the CallPeer that we will be registering
* listener with.
* @param listener the VideoListener that we'd like to register.
*/
@SuppressWarnings("unchecked") // work with MediaAware* in media package
public void addVideoListener(CallPeer peer, VideoListener listener)
{
if (listener == null)
throw new NullPointerException("listener");
((W)peer)
.getMediaHandler()
.addVideoListener(
new InternalVideoListener(this, peer, listener));
}
/**
* Implements
* {@link OperationSetVideoTelephony#createLocalVisualComponent(CallPeer,
* VideoListener)}.
*
* @param peer the CallPeer that we are sending our local video to.
* @param listener the VideoListener where we'd like to retrieve
* the Component containing the local video.
* @return the Component containing the local video.
* @throws OperationFailedException if we fail extracting the local video.
*/
@SuppressWarnings("unchecked") // work with MediaAware* in media package
public Component createLocalVisualComponent(
CallPeer peer,
VideoListener listener)
throws OperationFailedException
{
return ((W)peer).getMediaHandler().createLocalVisualComponent();
}
/**
* Implements
* {@link OperationSetVideoTelephony#disposeLocalVisualComponent(CallPeer,
* Component)}.
*
* @param peer the CallPeer whose local video component we'd like
* to dispose of.
* @param component the Component that we'll be disposing of.
*/
@SuppressWarnings("unchecked") // work with MediaAware* in media package
public void disposeLocalVisualComponent(CallPeer peer, Component component)
{
((W)peer).getMediaHandler().disposeLocalVisualComponent();
}
/**
* Gets the visual/video Component available in this telephony for
* a specific CallPeer.
*
* @param peer the CallPeer whose video is to be retrieved
* @return the visual/video Component available in this telephony
* for the specified peer if any; otherwise, null
*/
@SuppressWarnings("unchecked") // work with MediaAware* in media package
public Component getVisualComponent(CallPeer peer)
{
return ((W)peer).getMediaHandler().getVisualComponent();
}
/**
* Delegates to the CallPeerMediaHandler of the specified
* CallPeer because the video is provided by it.
*
* @param peer the CallPeer that we'd like to unregister our
* VideoListener from.
* @param listener the VideoListener that we'd like to unregister.
*/
@SuppressWarnings("unchecked") // work with MediaAware* in media package
public void removeVideoListener(CallPeer peer, VideoListener listener)
{
if (listener != null)
((W)peer)
.getMediaHandler()
.removeVideoListener(
new InternalVideoListener(this, peer, listener));
}
/**
* Implements OperationSetVideoTelephony#setLocalVideoAllowed(Call,
* boolean). Modifies the local media setup to reflect the requested setting
* for the streaming of the local video and then re-invites all
* CallPeers to re-negotiate the modified media setup.
*
* @param call the call where we'd like to allow sending local video.
* @param allowed true if local video transmission is allowed and
* false otherwise.
*
* @throws OperationFailedException if video initialization fails.
*/
@SuppressWarnings("unchecked") // work with MediaAware* in media package
public void setLocalVideoAllowed(Call call, boolean allowed)
throws OperationFailedException
{
((V)call).setLocalVideoAllowed(allowed, MediaUseCase.CALL);
}
/**
* Determines whether the streaming of local video in a specific
* Call is currently allowed. The setting does not reflect
* the availability of actual video capture devices, it just expresses the
* desire of the user to have the local video streamed in the case the
* system is actually able to do so.
*
* @param call the Call whose video transmission properties we are
* interested in.
*
* @return true if the streaming of local video for the specified
* Call is allowed; otherwise, false
*/
@SuppressWarnings("unchecked") // work with MediaAware* in media package
public boolean isLocalVideoAllowed(Call call)
{
return ((V)call).isLocalVideoAllowed(MediaUseCase.CALL);
}
/**
* Determines whether a specific Call is currently streaming the
* local video (to a remote destination).
*
* @param call the Call whose video transmission we are interested
* in.
*
* @return true if the specified Call is currently
* streaming the local video (to a remote destination); otherwise,
* false
*/
@SuppressWarnings("unchecked") // work with MediaAware* in media package
public boolean isLocalVideoStreaming(Call call)
{
return ((V)call).isLocalVideoStreaming();
}
/**
* Adds a specific PropertyChangeListener to the list of
* listeners which get notified when the properties (e.g.
* {@link #LOCAL_VIDEO_STREAMING}) associated with a specific
* Call change their values.
*
* @param call the Call to start listening to the changes of
* the property values of
* @param listener the PropertyChangeListener to be notified
* when the properties associated with the specified Call change
* their values
*/
@SuppressWarnings("unchecked") // work with MediaAware* in media package
public void addPropertyChangeListener(
Call call,
PropertyChangeListener listener)
{
((V)call).addVideoPropertyChangeListener(listener);
}
/**
* Removes a specific PropertyChangeListener from the list of
* listeners which get notified when the properties (e.g.
* {@link #LOCAL_VIDEO_STREAMING}) associated with a specific
* Call change their values.
*
* @param call the Call to stop listening to the changes of the
* property values of
* @param listener the PropertyChangeListener to no longer be
* notified when the properties associated with the specified Call
* change their values
*/
@SuppressWarnings("unchecked") // work with MediaAware* in media package
public void removePropertyChangeListener(
Call call,
PropertyChangeListener listener)
{
((V)call).removeVideoPropertyChangeListener(listener);
}
/**
* Get the MediaUseCase of a video telephony operation set.
*
* @return MediaUseCase.CALL
*/
public MediaUseCase getMediaUseCase()
{
return MediaUseCase.CALL;
}
/**
* Represents a VideoListener which forwards notifications to a
* specific delegate VideoListener and hides the original
* VideoEvent sender from it by pretending the sender is a
* specific OperationSetVideoTelephony. It's necessary in order
* to hide from the VideoListeners the fact that the video of
* the SIP protocol implementation is managed by CallSession.
*/
private static class InternalVideoListener
implements VideoListener
{
/**
* The VideoListener this implementation hides the original
* VideoEvent source from.
*/
private final VideoListener delegate;
/**
* The CallPeer whose videos {@link #delegate} is
* interested in.
*/
private final CallPeer peer;
/**
* The OperationSetVideoTelephony which is to be presented
* as the source of the VideoEvents forwarded to
* {@link #delegate}.
*/
private final OperationSetVideoTelephony telephony;
/**
* Initializes a new InternalVideoListener which is to
* impersonate the sources of VideoEvents with a specific
* OperationSetVideoTelephony for a specific
* VideoListener interested in the videos of a specific
* CallPeer.
*
* @param telephony the OperationSetVideoTelephony which is
* to be stated as the source of the VideoEvent sent to the
* specified delegate VideoListener
* @param peer the CallPeer whose videos the specified delegate
* VideoListener is interested in
* @param delegate the VideoListener which shouldn't know
* that the videos in the SIP protocol implementation is managed by the
* CallSession and not by the specified telephony
*/
public InternalVideoListener(
OperationSetVideoTelephony telephony,
CallPeer peer,
VideoListener delegate)
{
if (peer == null)
throw new NullPointerException("peer cannot be null");
this.telephony = telephony;
this.peer = peer;
this.delegate = delegate;
}
/**
* Compares two InternalVideoListeners and determines they are equal
* if they impersonate the sources of VideoEvents with equal
* OperationSetVideoTelephonies for equal delegate VideoListeners added
* to equal CallPeer-s.
*
* @param other the object that we'd be compared to.
*
* @return true if the underlying peer, telephony operation set and
* delegate are equal to those of the other instance.
*/
@Override
public boolean equals(Object other)
{
if (other == this)
return true;
if ((other == null) || !other.getClass().equals(getClass()))
return false;
InternalVideoListener otherListener = (InternalVideoListener) other;
return otherListener.telephony.equals(telephony)
&& otherListener.peer.equals(peer)
&& otherListener.delegate.equals(delegate);
}
/**
* Returns a hashcode based on the hash codes of the wrapped telephony
* operation set and VideoListener delegate.
*
* @return a hashcode based on the hash codes of the wrapped telephony
* operation set and VideoListener delegate.
*/
@Override
public int hashCode()
{
return (telephony.hashCode() << 16) + (delegate.hashCode() >> 16);
}
/**
* Upon receiving a VideoEvent, sends to delegate a new VideoEvent of
* the same type and with the same visual Component but with the source
* of the event being set to #telephony. Thus the fact that the
* CallSession is the original source is hidden from the clients of
* OperationSetVideoTelephony.
*
* @param event the VideoEvent containing the visual component
*/
public void videoAdded(VideoEvent event)
{
VideoEvent delegateEvent
= new VideoEvent(
this,
event.getType(),
event.getVisualComponent(),
event.getOrigin());
delegate.videoAdded(delegateEvent);
if (delegateEvent.isConsumed())
event.consume();
}
/**
* Upon receiving a VideoEvent, sends to delegate a new VideoEvent of
* the same type and with the same visual Component but with the source
* of the event being set to #telephony. Thus the fact that the
* CallSession is the original source is hidden from the clients of
* OperationSetVideoTelephony.
*
* @param event the VideoEvent containing video details and
* the event component.
*/
public void videoRemoved(VideoEvent event)
{
VideoEvent delegateEvent
= new VideoEvent(
this,
event.getType(),
event.getVisualComponent(),
event.getOrigin());
delegate.videoRemoved(delegateEvent);
if (delegateEvent.isConsumed())
event.consume();
}
/**
* Forwards a specific VideoEvent to the associated delegate.
*
* @param event the VideoEvent to be forwarded to the
* associated delegate
*/
public void videoUpdate(VideoEvent event)
{
/*
* TODO Since the specified event is directly fired, the sender is
* the original one and not this.
*/
delegate.videoUpdate(event);
}
}
}