|
|
|
|
@ -6,18 +6,27 @@
|
|
|
|
|
*/
|
|
|
|
|
package net.java.sip.communicator.service.neomedia.event;
|
|
|
|
|
|
|
|
|
|
import java.awt.Component;
|
|
|
|
|
import java.awt.*;
|
|
|
|
|
import java.util.*;
|
|
|
|
|
import java.util.List; // disambiguation
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Represents a mechanism to easily add to a specific <tt>Object</tt> by means
|
|
|
|
|
* of composition support for firing <tt>VideoEvent</tt>s to
|
|
|
|
|
* <tt>VideoListener</tt>s.
|
|
|
|
|
*
|
|
|
|
|
* @author Lubomir Marinov
|
|
|
|
|
* @author Lyubomir Marinov
|
|
|
|
|
*/
|
|
|
|
|
public class VideoNotifierSupport
|
|
|
|
|
{
|
|
|
|
|
private static final long THREAD_TIMEOUT = 5000;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* The list of <tt>VideoEvent</tt>s which are to be delivered to the
|
|
|
|
|
* {@link #listeners} registered with this instance when
|
|
|
|
|
* {@link #synchronous} is equal to <tt>false</tt>.
|
|
|
|
|
*/
|
|
|
|
|
private final List<VideoEvent> events;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* The list of <tt>VideoListener</tt>s interested in changes in the
|
|
|
|
|
@ -32,6 +41,18 @@ public class VideoNotifierSupport
|
|
|
|
|
*/
|
|
|
|
|
private final Object source;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* The indicator which determines whether this instance delivers the
|
|
|
|
|
* <tt>VideoEvent</tt>s to the {@link #listeners} synchronously.
|
|
|
|
|
*/
|
|
|
|
|
private final boolean synchronous;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* The <tt>Thread</tt> in which {@link #events} are delivered to the
|
|
|
|
|
* {@link #listeners} when {@link #synchronous} is equal to <tt>false</tt>.
|
|
|
|
|
*/
|
|
|
|
|
private Thread thread;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Initializes a new <tt>VideoNotifierSupport</tt> instance which is to
|
|
|
|
|
* facilitate the management of <tt>VideoListener</tt>s and firing
|
|
|
|
|
@ -41,8 +62,26 @@ public class VideoNotifierSupport
|
|
|
|
|
* of the <tt>VideoEvent</tt>s fired by the new instance
|
|
|
|
|
*/
|
|
|
|
|
public VideoNotifierSupport(Object source)
|
|
|
|
|
{
|
|
|
|
|
this(source, true);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Initializes a new <tt>VideoNotifierSupport</tt> instance which is to
|
|
|
|
|
* facilitate the management of <tt>VideoListener</tt>s and firing
|
|
|
|
|
* <tt>VideoEvent</tt>s to them for a specific <tt>Object</tt>.
|
|
|
|
|
*
|
|
|
|
|
* @param source the <tt>Object</tt> which is to be reported as the source
|
|
|
|
|
* of the <tt>VideoEvent</tt>s fired by the new instance
|
|
|
|
|
* @param synchronous <tt>true</tt> if the new instance is to deliver the
|
|
|
|
|
* <tt>VideoEvent</tt>s synchronously; otherwise, <tt>false</tt>
|
|
|
|
|
*/
|
|
|
|
|
public VideoNotifierSupport(Object source, boolean synchronous)
|
|
|
|
|
{
|
|
|
|
|
this.source = source;
|
|
|
|
|
this.synchronous = synchronous;
|
|
|
|
|
|
|
|
|
|
events = this.synchronous ? null : new LinkedList<VideoEvent>();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
@ -71,6 +110,32 @@ public void addVideoListener(VideoListener listener)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
protected void doFireVideoEvent(VideoEvent event)
|
|
|
|
|
{
|
|
|
|
|
VideoListener[] listeners;
|
|
|
|
|
|
|
|
|
|
synchronized (this.listeners)
|
|
|
|
|
{
|
|
|
|
|
listeners
|
|
|
|
|
= this.listeners.toArray(
|
|
|
|
|
new VideoListener[this.listeners.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;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Notifies the <tt>VideoListener</tt>s registered with this
|
|
|
|
|
* <tt>VideoMediaStream</tt> about a specific type of change in the
|
|
|
|
|
@ -83,6 +148,9 @@ public void addVideoListener(VideoListener listener)
|
|
|
|
|
* @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)
|
|
|
|
|
* @param wait <tt>true</tt> if the call is to wait till the specified
|
|
|
|
|
* <tt>VideoEvent</tt> has been delivered to the <tt>VideoListener</tt>s;
|
|
|
|
|
* otherwise, <tt>false</tt>
|
|
|
|
|
* @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
|
|
|
|
|
@ -90,44 +158,14 @@ public void addVideoListener(VideoListener listener)
|
|
|
|
|
* otherwise, <tt>false</tt>
|
|
|
|
|
*/
|
|
|
|
|
public boolean fireVideoEvent(
|
|
|
|
|
int type,
|
|
|
|
|
Component visualComponent,
|
|
|
|
|
int origin)
|
|
|
|
|
int type, Component visualComponent, int origin,
|
|
|
|
|
boolean wait)
|
|
|
|
|
{
|
|
|
|
|
VideoListener[] listeners;
|
|
|
|
|
|
|
|
|
|
synchronized (this.listeners)
|
|
|
|
|
{
|
|
|
|
|
listeners
|
|
|
|
|
= this.listeners
|
|
|
|
|
.toArray(new VideoListener[this.listeners.size()]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
boolean consumed;
|
|
|
|
|
|
|
|
|
|
if (listeners.length > 0)
|
|
|
|
|
{
|
|
|
|
|
VideoEvent event
|
|
|
|
|
= new VideoEvent(source, 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");
|
|
|
|
|
}
|
|
|
|
|
VideoEvent event
|
|
|
|
|
= new VideoEvent(source, type, visualComponent, origin);
|
|
|
|
|
|
|
|
|
|
consumed = event.isConsumed();
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
consumed = false;
|
|
|
|
|
return consumed;
|
|
|
|
|
fireVideoEvent(event, wait);
|
|
|
|
|
return event.isConsumed();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
@ -136,31 +174,45 @@ public boolean fireVideoEvent(
|
|
|
|
|
*
|
|
|
|
|
* @param event the <tt>VideoEvent</tt> to be fired to the
|
|
|
|
|
* <tt>VideoListener</tt>s registered with this instance
|
|
|
|
|
* @param wait <tt>true</tt> if the call is to wait till the specified
|
|
|
|
|
* <tt>VideoEvent</tt> has been delivered to the <tt>VideoListener</tt>s;
|
|
|
|
|
* otherwise, <tt>false</tt>
|
|
|
|
|
*/
|
|
|
|
|
public void fireVideoEvent(VideoEvent event)
|
|
|
|
|
public void fireVideoEvent(VideoEvent event, boolean wait)
|
|
|
|
|
{
|
|
|
|
|
VideoListener[] listeners;
|
|
|
|
|
|
|
|
|
|
synchronized (this.listeners)
|
|
|
|
|
if (synchronous)
|
|
|
|
|
doFireVideoEvent(event);
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
listeners
|
|
|
|
|
= this.listeners
|
|
|
|
|
.toArray(new VideoListener[this.listeners.size()]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (VideoListener listener : listeners)
|
|
|
|
|
switch (event.getType())
|
|
|
|
|
synchronized (events)
|
|
|
|
|
{
|
|
|
|
|
case VideoEvent.VIDEO_ADDED:
|
|
|
|
|
listener.videoAdded(event);
|
|
|
|
|
break;
|
|
|
|
|
case VideoEvent.VIDEO_REMOVED:
|
|
|
|
|
listener.videoRemoved(event);
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
listener.videoUpdate(event);
|
|
|
|
|
break;
|
|
|
|
|
events.add(event);
|
|
|
|
|
|
|
|
|
|
if (thread == null)
|
|
|
|
|
startThread();
|
|
|
|
|
else
|
|
|
|
|
events.notify();
|
|
|
|
|
|
|
|
|
|
if (wait)
|
|
|
|
|
{
|
|
|
|
|
boolean interrupted = false;
|
|
|
|
|
|
|
|
|
|
while (events.contains(event) && (thread != null))
|
|
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
events.wait();
|
|
|
|
|
}
|
|
|
|
|
catch (InterruptedException ie)
|
|
|
|
|
{
|
|
|
|
|
interrupted = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (interrupted)
|
|
|
|
|
Thread.currentThread().interrupt();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
@ -179,4 +231,97 @@ public void removeVideoListener(VideoListener listener)
|
|
|
|
|
listeners.remove(listener);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void runInThread()
|
|
|
|
|
{
|
|
|
|
|
while (true)
|
|
|
|
|
{
|
|
|
|
|
VideoEvent event = null;
|
|
|
|
|
|
|
|
|
|
synchronized (events)
|
|
|
|
|
{
|
|
|
|
|
long emptyTime = -1;
|
|
|
|
|
boolean interrupted = false;
|
|
|
|
|
|
|
|
|
|
while (events.isEmpty())
|
|
|
|
|
{
|
|
|
|
|
if (emptyTime == -1)
|
|
|
|
|
emptyTime = System.currentTimeMillis();
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
long newEmptyTime = System.currentTimeMillis();
|
|
|
|
|
|
|
|
|
|
if ((newEmptyTime - emptyTime) >= THREAD_TIMEOUT)
|
|
|
|
|
{
|
|
|
|
|
events.notify();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
events.wait(THREAD_TIMEOUT);
|
|
|
|
|
}
|
|
|
|
|
catch (InterruptedException ie)
|
|
|
|
|
{
|
|
|
|
|
interrupted = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (interrupted)
|
|
|
|
|
Thread.currentThread().interrupt();
|
|
|
|
|
|
|
|
|
|
event = events.remove(0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (event != null)
|
|
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
doFireVideoEvent(event);
|
|
|
|
|
}
|
|
|
|
|
catch (Throwable t)
|
|
|
|
|
{
|
|
|
|
|
if (t instanceof ThreadDeath)
|
|
|
|
|
throw (ThreadDeath) t;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
synchronized (events)
|
|
|
|
|
{
|
|
|
|
|
events.notify();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void startThread()
|
|
|
|
|
{
|
|
|
|
|
thread
|
|
|
|
|
= new Thread("VideoNotifierSupportThread")
|
|
|
|
|
{
|
|
|
|
|
@Override
|
|
|
|
|
public void run()
|
|
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
runInThread();
|
|
|
|
|
}
|
|
|
|
|
finally
|
|
|
|
|
{
|
|
|
|
|
synchronized (events)
|
|
|
|
|
{
|
|
|
|
|
if (Thread.currentThread().equals(thread))
|
|
|
|
|
{
|
|
|
|
|
thread = null;
|
|
|
|
|
if (events.isEmpty())
|
|
|
|
|
events.notify();
|
|
|
|
|
else
|
|
|
|
|
startThread();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
thread.setDaemon(true);
|
|
|
|
|
thread.start();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|