/* * Jitsi, the OpenSource Java VoIP and Instant Messaging client. * * Copyright @ 2015 Atlassian Pty Ltd * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package net.java.sip.communicator.impl.gui.main.call; import java.awt.*; import java.beans.*; import java.util.*; import net.java.sip.communicator.service.protocol.*; import net.java.sip.communicator.service.protocol.event.*; import org.jitsi.util.event.*; /** * Facilitates the handling of the various video-related events occurring in a * <tt>CallConference</tt> in the UI as employed by <tt>OneToOneCallPanel</tt> * and <tt>VideoConferenceCallPanel</tt>. The intention at the time of this * writing is to have <tt>UIVideoHandler2</tt> initialized by <tt>CallPanel</tt> * and shared by it with <tt>OneToOneCallPanel</tt> and * <tt>VideoConferenceCallPanel</tt> as <tt>CallPanel</tt> switches between them * as needed. * * @author Lyubomir Marinov */ public class UIVideoHandler2 extends Observable { /** * The name of the <tt>UIVideoHandler2</tt> property which indicates whether * the visual <tt>Component</tt> displaying the video of the local peer/user * streaming to the remote peer(s) is to be made visible in the user * interface. */ public static final String LOCAL_VIDEO_VISIBLE_PROPERTY_NAME = "localVideoVisible"; /** * The <tt>CallConference</tt> in which the handling of the various * video-related events is to be facilitated by this * <tt>UIVideoHandler2</tt>. */ private final CallConference callConference; /** * The listener implementations which get notified by * {@link #callConference}, the <tt>Call</tt>s participating in it, the * <tt>CallPeer</tt>s associated with them, and the * <tt>ConferenceMember</tt>s participating in their telephony conferences * about events related to the handling of video which this instance * facilitates. */ private final CallConferenceListener callConferenceListener; /** * The indicator which determines whether the visual <tt>Component</tt> * depicting the video of the local peer/user streaming to the remote * peer(s) is to be made visible in the user interface. */ private boolean localVideoVisible = true; /** * Initializes a new <tt>UIVideoHandler2</tt> instance which is to * facilitate the handling of the various video-related events occurring in * a specific <tt>CallConference</tt>. * * @param callConference the <tt>CallConference</tt> in which the handling * of the various video-related events is to be facilitated by the new * instance */ public UIVideoHandler2(CallConference callConference) { this.callConference = callConference; callConferenceListener = new CallConferenceListener(); } /** * Notifies this instance about a change in the value of the <tt>calls</tt> * property of {@link #callConference} i.e. a <tt>Call</tt> was added to or * removed from the list of <tt>Call</tt>s participating in * <tt>callConference</tt>. Adding or removing <tt>Call</tt>s modifies the * list of <tt>CallPeer</tt>s associated with <tt>callConference</tt> which * in turn may result in the adding or removing of visual * <tt>Component</tt>s depicting video. * * @param ev a <tt>PropertyChangeEvent</tt> which specifies the * <tt>Call</tt> which was added to or removed from the list of * <tt>Call</tt>s participating in <tt>callConference</tt> */ protected void callConferenceCallsPropertyChange(PropertyChangeEvent ev) { notifyObservers(ev); } /** * Notifies this instance about a change in the value of a video-related * property of a <tt>ConferenceMember</tt>. Changing such a value means that * a visual <tt>Component</tt> displaying video may be associated or * dissociated with the <tt>ConferenceMember</tt>. * * @param ev a <tt>PropertyChangeEvent</tt> which specifies the * <tt>ConferenceMember</tt> whose video-related property value changed, the * name of the property whose value changed, and the old and new values of * the property in question */ protected void conferenceMemberVideoPropertyChange(PropertyChangeEvent ev) { notifyObservers(ev); } /** * Releases the resources (which require explicit disposal such as listeners * added to notifiers) acquired by this instance throughout its lifetime and * prepares it for garbage collection. */ void dispose() { callConferenceListener.dispose(); } /** * Determines whether a specific <tt>Container</tt> is an ancestor of a * specific <tt>Component</tt> (in the UI hierarchy). * * @param container the <tt>Container</tt> which is to be tested as an * ancestor of <tt>component</tt> * @param component the <tt>Component</tt> which is to be tested as having * <tt>container</tt> as its ancestor * @return <tt>true</tt> if <tt>container</tt> is an ancestor of * <tt>component</tt> (in the UI hierarchy); otherwise, <tt>false</tt> */ public static boolean isAncestor(Container container, Component component) { do { Container parent = component.getParent(); if (parent == null) return false; else if (parent.equals(container)) return true; else component = parent; } while (true); } /** * Gets the indicator which determines whether the visual <tt>Component</tt> * depicting the video of the local peer/user streaming to the remote * peer(s) is to be made visible in the user interface. The indicator does * not determine whether the local peer/user is actually streaming video to * the remote peer(s). * * @return <tt>true</tt> to have the visual <tt>Component</tt> depicting the * video of the local peer/user streaming to the remote peer(s) visible in * the user interface; otherwise, <tt>false</tt> */ public boolean isLocalVideoVisible() { return localVideoVisible; } /** * Notifies this instance that the value of the property which indicates * whether the local peer is streaming video to the remote peer(s) changed. * It is not very clear who is the source of the * <tt>PropertyChangeEvent</tt> because a <tt>PropertyChangeListener</tt> is * added through <tt>OperationSetVideoTelephony</tt> by specifying a * <tt>Call</tt>. But it is likely that a change in the value of the * property in question is related to the video and, consequently, this * instance. * * @param ev a <tt>PropertyChangeEvent</tt> which specifies the source * notifying about the change and the old and new values of the property. */ protected void localVideoStreamingPropertyChange(PropertyChangeEvent ev) { notifyObservers(ev); } /** * {@inheritDoc} * * Overrides {@link Observable#notifyObservers(Object)} to force the super * to notify the added <tt>Observer</tt>s regardless of the <tt>changed</tt> * state of this <tt>Observable</tt> which <tt>UIVideoHandler2</tt> does not * use at the time of this writing. */ @Override public void notifyObservers(Object arg) { setChanged(); super.notifyObservers(arg); } /** * Notifies this instance about a specific <tt>CallPeerConferenceEvent</tt> * fired by a <tt>CallPeer</tt> associated with a <tt>Call</tt> * participating in {@link #callConference}. Adding or removing a * <tt>ConferenceMember</tt> may cause a visual <tt>Component</tt> * displaying video to be associated or dissociated with the * <tt>ConferenceMember</tt>. * * @param ev the <tt>CallPeerConferenceEvent</tt> this instance is to be * notified about */ protected void onCallPeerConferenceEvent(CallPeerConferenceEvent ev) { notifyObservers(ev); } /** * Notifies this instance about a specific <tt>CallPeerEvent</tt> fired by a * <tt>Call</tt> participating in {@link #callConference}. Adding or * removing a <tt>CallPeer</tt> may modify the list of visual * <tt>Component</tt>s displaying video. * * @param ev the <tt>CallPeerEvent</tt> this instance is to be notified * about */ protected void onCallPeerEvent(CallPeerEvent ev) { notifyObservers(ev); } /** * Notifies this instance about a specific <tt>VideoEvent</tt> fired by a * <tt>CallPeer</tt> associated with a <tt>Call</tt> participating in * {@link #callConference}. * * @param ev the <tt>VideoEvent</tt> this instance is to be notified about */ protected void onVideoEvent(VideoEvent ev) { notifyObservers(ev); } /** * Sets the indicator which determines whether the visual <tt>Component</tt> * depicting the video of the local peer/user streaming to the remote * peer(s) is to be made visible in the user interface. The indicator does * not determine whether the local peer/user is actually streaming video to * the remote peer(s). * * @param localVideoVisible <tt>true</tt> to have the visual * <tt>Component</tt> depicting the video of the local peer/user streaming * to the remote peer(s) visible in the user interface; otherwise, * <tt>false</tt> */ public void setLocalVideoVisible(boolean localVideoVisible) { if (this.localVideoVisible != localVideoVisible) { boolean oldValue = this.localVideoVisible; this.localVideoVisible = localVideoVisible; notifyObservers( new PropertyChangeEvent( this, LOCAL_VIDEO_VISIBLE_PROPERTY_NAME, oldValue, this.localVideoVisible)); } } /** * Implements the listeners which get notified by * {@link UIVideoHandler2#callConference}, the <tt>Call</tt>s participating * in it, the <tt>CallPeer</tt>s associated with them, and the * <tt>ConferenceMember</tt>s participating in their telephony conferences * about events related to the handling of video which this * <tt>UIVideoHandler2</tt> facilitates. */ private class CallConferenceListener extends CallPeerConferenceAdapter implements CallChangeListener, PropertyChangeListener, VideoListener { /** * Initializes a new <tt>CallConferenceListener</tt> instance which is * to get notified by {@link UIVideoHandler2#callConference} about * events related to the handling of video. */ CallConferenceListener() { callConference.addCallChangeListener(this); callConference.addCallPeerConferenceListener(this); callConference.addPropertyChangeListener(this); for (Call call : callConference.getCalls()) addListeners(call); } /** * Adds this as a listener to the <tt>CallPeer</tt>s associated with a * specific <tt>Call</tt> and to the <tt>ConferenceMember</tt>s * participating in their telephony conferences. * * @param call the <tt>Call</tt> to whose associated <tt>CallPeer</tt>s * and <tt>ConferenceMember</tt>s this is to add itself as a listener */ private void addListeners(Call call) { OperationSetVideoTelephony videoTelephony = call.getProtocolProvider().getOperationSet( OperationSetVideoTelephony.class); if (videoTelephony != null) videoTelephony.addPropertyChangeListener(call, this); Iterator<? extends CallPeer> callPeerIter = call.getCallPeers(); while (callPeerIter.hasNext()) addListeners(callPeerIter.next()); } /** * Adds this as a listener to a specific <tt>CallPeer</tt> and to the * <tt>ConferenceMember</tt>s participating in its telephony conference. * * @param callPeer the <tt>CallPeer</tt> to which and to whose * participating <tt>ConferenceMember</tt>s this is to add itself as a * listener */ private void addListeners(CallPeer callPeer) { OperationSetVideoTelephony videoTelephony = callPeer.getProtocolProvider().getOperationSet( OperationSetVideoTelephony.class); if (videoTelephony != null) videoTelephony.addVideoListener(callPeer, this); for (ConferenceMember conferenceMember : callPeer.getConferenceMembers()) { addListeners(conferenceMember); } } /** * Adds this as a listener to a specific <tt>ConferenceMember</tt>. * * @param conferenceMember the <tt>ConferenceMember</tt> to which this * is to add itself as a listener */ private void addListeners(ConferenceMember conferenceMember) { conferenceMember.addPropertyChangeListener(this); } /** * {@inheritDoc} * * Delegates to {@link #onCallPeerEvent(CallPeerEvent)} because the * specifics can be determined from the <tt>CallPeerEvent</tt>. */ public void callPeerAdded(CallPeerEvent ev) { onCallPeerEvent(ev); } /** * {@inheritDoc} * * Delegates to {@link #onCallPeerEvent(CallPeerEvent)} because the * specifics can be determined from the <tt>CallPeerEvent</tt>. */ public void callPeerRemoved(CallPeerEvent ev) { onCallPeerEvent(ev); } /** * {@inheritDoc} * * <tt>CallConferenceListener</tt> does nothing because changes in the * state of a <tt>Call</tt> are not directly related to video or are * expressed with other events which are directly related to video. */ public void callStateChanged(CallChangeEvent ev) { } /** * Releases the resources (which require explicit disposal such as * listeners added to notifiers) acquired by this instance throughout * its lifetime and prepares it for garbage collection. */ void dispose() { callConference.removeCallChangeListener(this); callConference.removeCallPeerConferenceListener(this); callConference.removePropertyChangeListener(this); for (Call call : callConference.getCalls()) removeListeners(call); } /** * {@inheritDoc} */ @Override protected void onCallPeerConferenceEvent(CallPeerConferenceEvent ev) { switch (ev.getEventID()) { case CallPeerConferenceEvent.CONFERENCE_MEMBER_ADDED: addListeners(ev.getConferenceMember()); break; case CallPeerConferenceEvent.CONFERENCE_MEMBER_REMOVED: removeListeners(ev.getConferenceMember()); break; } UIVideoHandler2.this.onCallPeerConferenceEvent(ev); } /** * Notifies this instance about a specific <tt>CallPeerEvent</tt> which * was fired by a <tt>Call</tt> participating in * {@link UIVideoHandler2#callConference}. * * @param ev the <tt>CallPeerEvent</tt> which this instance is to be * notified about and which was fired by a <tt>Call</tt> participating * in <tt>UIVideoHandler2.callConference</tt> */ private void onCallPeerEvent(CallPeerEvent ev) { switch (ev.getEventID()) { case CallPeerEvent.CALL_PEER_ADDED: addListeners(ev.getSourceCallPeer()); break; case CallPeerEvent.CALL_PEER_REMOVED: removeListeners(ev.getSourceCallPeer()); break; } UIVideoHandler2.this.onCallPeerEvent(ev); } /** * Notifies this instance about a specific <tt>VideoEvent</tt> which was * fired by a <tt>CallPeer</tt> associated with a <tt>Call</tt> * participating in {@link UIVideoHandler2#callConference}. * * @param ev the <tt>VideoEvent</tt> which this instance is to be * notified about and which was fired by a <tt>CallPeer</tt> associated * with a <tt>Call</tt> participating in * <tt>UIVideoHandler2.callConference</tt> */ private void onVideoEvent(VideoEvent ev) { UIVideoHandler2.this.onVideoEvent(ev); } /** * {@inheritDoc} * * For example, notifies this <tt>UIVideoHandler2</tt> that a * <tt>Call</tt> was added/removed to/from the <tt>callConference</tt>. */ public void propertyChange(PropertyChangeEvent ev) { String propertyName = ev.getPropertyName(); if (CallConference.CALLS.equals(propertyName)) { if (ev.getSource() instanceof CallConference) { Object oldValue = ev.getOldValue(); if (oldValue instanceof Call) removeListeners((Call) oldValue); Object newValue = ev.getNewValue(); if (newValue instanceof Call) addListeners((Call) newValue); callConferenceCallsPropertyChange(ev); } } else if (ConferenceMember.VIDEO_SSRC_PROPERTY_NAME.equals( propertyName) || ConferenceMember.VIDEO_STATUS_PROPERTY_NAME.equals( propertyName)) { if (ev.getSource() instanceof ConferenceMember) conferenceMemberVideoPropertyChange(ev); } else if (OperationSetVideoTelephony.LOCAL_VIDEO_STREAMING.equals( propertyName)) { localVideoStreamingPropertyChange(ev); } } /** * Removes this as a listener from the <tt>CallPeer</tt>s associated * with a specific <tt>Call</tt> and from the <tt>ConferenceMember</tt>s * participating in their telephony conferences. * * @param call the <tt>Call</tt> from whose associated * <tt>CallPeer</tt>s and <tt>ConferenceMember</tt>s this is to remove * itself as a listener */ private void removeListeners(Call call) { OperationSetVideoTelephony videoTelephony = call.getProtocolProvider().getOperationSet( OperationSetVideoTelephony.class); if (videoTelephony != null) videoTelephony.addPropertyChangeListener(call, this); Iterator<? extends CallPeer> callPeerIter = call.getCallPeers(); while (callPeerIter.hasNext()) removeListeners(callPeerIter.next()); } /** * Removes this as a listener from a specific <tt>CallPeer</tt> and from * the <tt>ConferenceMember</tt>s participating in its telephony * conference. * * @param callPeer the <tt>CallPeer<tt> from which and from whose * participating <tt>ConferenceMember</tt>s this is to remove itself as * a listener */ private void removeListeners(CallPeer callPeer) { OperationSetVideoTelephony videoTelephony = callPeer.getProtocolProvider().getOperationSet( OperationSetVideoTelephony.class); if (videoTelephony != null) videoTelephony.removeVideoListener(callPeer, this); for (ConferenceMember conferenceMember : callPeer.getConferenceMembers()) { removeListeners(conferenceMember); } } /** * Removes this as a listener from a specific <tt>ConferenceMember</tt>. * * @param conferenceMember the <tt>ConferenceMember</tt> from which this * is to remove itself as a listener */ private void removeListeners(ConferenceMember conferenceMember) { conferenceMember.removePropertyChangeListener(this); } /** * {@inheritDoc} * * Implements {@link VideoListener#videoAdded(VideoEvent)}. Delegates to * {@link #onVideoEvent(VideoEvent) because the specifics can be * determined from the <tt>VideoEvent</tt>. */ public void videoAdded(VideoEvent ev) { onVideoEvent(ev); } /** * {@inheritDoc} * * Implements {@link VideoListener#videoRemoved(VideoEvent)}. Delegates * to {@link #onVideoEvent(VideoEvent) because the specifics can be * determined from the <tt>VideoEvent</tt>. */ public void videoRemoved(VideoEvent ev) { onVideoEvent(ev); } /** * {@inheritDoc} * * Implements {@link VideoListener#videoUpdate(VideoEvent)}. Delegates * to {@link #onVideoEvent(VideoEvent) because the specifics can be * determined from the <tt>VideoEvent</tt>. */ public void videoUpdate(VideoEvent ev) { onVideoEvent(ev); } } }