diff --git a/webcam-capture/src/example/java/WebcamViewerExample.java b/webcam-capture/src/example/java/WebcamViewerExample.java
index ae362ed7..b61d7922 100644
--- a/webcam-capture/src/example/java/WebcamViewerExample.java
+++ b/webcam-capture/src/example/java/WebcamViewerExample.java
@@ -9,6 +9,8 @@
import javax.swing.SwingUtilities;
import com.github.sarxos.webcam.Webcam;
+import com.github.sarxos.webcam.WebcamDiscoveryEvent;
+import com.github.sarxos.webcam.WebcamDiscoveryListener;
import com.github.sarxos.webcam.WebcamEvent;
import com.github.sarxos.webcam.WebcamListener;
import com.github.sarxos.webcam.WebcamPanel;
@@ -21,7 +23,7 @@
*
* @author Bartosz Firyn (SarXos)
*/
-public class WebcamViewerExample extends JFrame implements Runnable, WebcamListener, WindowListener, UncaughtExceptionHandler, ItemListener {
+public class WebcamViewerExample extends JFrame implements Runnable, WebcamListener, WindowListener, UncaughtExceptionHandler, ItemListener, WebcamDiscoveryListener {
private static final long serialVersionUID = 1L;
@@ -32,6 +34,8 @@ public class WebcamViewerExample extends JFrame implements Runnable, WebcamListe
@Override
public void run() {
+ Webcam.addDiscoveryListener(this);
+
setTitle("Java Webcam Capture POC");
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setLayout(new BorderLayout());
@@ -155,6 +159,7 @@ public void itemStateChanged(ItemEvent e) {
System.out.println("selected " + webcam.getName());
panel = new WebcamPanel(webcam, false);
+ panel.setFPSDisplayed(true);
add(panel, BorderLayout.CENTER);
pack();
@@ -173,4 +178,18 @@ public void run() {
}
}
}
+
+ @Override
+ public void webcamFound(WebcamDiscoveryEvent event) {
+ if (picker != null) {
+ picker.addItem(event.getWebcam());
+ }
+ }
+
+ @Override
+ public void webcamGone(WebcamDiscoveryEvent event) {
+ if (picker != null) {
+ picker.removeItem(event.getWebcam());
+ }
+ }
}
diff --git a/webcam-capture/src/example/resources/logback.xml b/webcam-capture/src/example/resources/logback.xml
index baeded4b..2bb01a64 100644
--- a/webcam-capture/src/example/resources/logback.xml
+++ b/webcam-capture/src/example/resources/logback.xml
@@ -4,7 +4,7 @@
%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n
-
+
diff --git a/webcam-capture/src/main/java/com/github/sarxos/webcam/Webcam.java b/webcam-capture/src/main/java/com/github/sarxos/webcam/Webcam.java
index 31f6baa1..bc777598 100644
--- a/webcam-capture/src/main/java/com/github/sarxos/webcam/Webcam.java
+++ b/webcam-capture/src/main/java/com/github/sarxos/webcam/Webcam.java
@@ -9,6 +9,9 @@
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicBoolean;
@@ -34,6 +37,61 @@
*/
public class Webcam {
+ /**
+ * Class used to asynchronously notify all webcam listeners about new image
+ * available.
+ *
+ * @author Bartosz Firyn (sarxos)
+ */
+ private static final class ImageNotification implements Runnable {
+
+ /**
+ * Camera.
+ */
+ private final Webcam webcam;
+
+ /**
+ * Acquired image.
+ */
+ private final BufferedImage image;
+
+ /**
+ * Create new notification.
+ *
+ * @param webcam the webcam from which image has been acquired
+ * @param image the acquired image
+ */
+ public ImageNotification(Webcam webcam, BufferedImage image) {
+ this.webcam = webcam;
+ this.image = image;
+ }
+
+ @Override
+ public void run() {
+ if (image != null) {
+ WebcamEvent we = new WebcamEvent(WebcamEventType.NEW_IMAGE, webcam, image);
+ for (WebcamListener l : webcam.getWebcamListeners()) {
+ try {
+ l.webcamImageObtained(we);
+ } catch (Exception e) {
+ LOG.error(String.format("Notify image acquired, exception when calling listener %s", l.getClass()), e);
+ }
+ }
+ }
+ }
+ }
+
+ private final class NotificationThreadFactory implements ThreadFactory {
+
+ @Override
+ public Thread newThread(Runnable r) {
+ Thread t = new Thread(r, String.format("notificator-[%s]", getName()));
+ t.setUncaughtExceptionHandler(WebcamExceptionHandler.getInstance());
+ t.setDaemon(true);
+ return t;
+ }
+ }
+
/**
* Logger instance.
*/
@@ -117,15 +175,24 @@ public class Webcam {
/**
* Webcam image updater.
*/
- private WebcamUpdater updater = new WebcamUpdater(this);
+ private volatile WebcamUpdater updater = null;
/**
- * IMage transformer.
+ * Image transformer.
*/
private volatile WebcamImageTransformer transformer = null;
+ /**
+ * Lock which denies access to the given webcam when it's already in use by
+ * other webcam capture API process or thread.
+ */
private WebcamLock lock = null;
+ /**
+ * Executor service for image notifications.
+ */
+ private ExecutorService notificator = null;
+
/**
* Webcam class.
*
@@ -140,6 +207,21 @@ protected Webcam(WebcamDevice device) {
this.lock = new WebcamLock(this);
}
+ /**
+ * Asynchronously start new thread which will notify all webcam listeners
+ * about the new image available.
+ */
+ protected void notifyWebcamImageAcquired(BufferedImage image) {
+
+ // notify webcam listeners of new image available, do that only if there
+ // are any webcam listeners available because there is no sense to start
+ // additional threads for no purpose
+
+ if (getWebcamListenersCount() > 0) {
+ notificator.execute(new ImageNotification(this, image));
+ }
+ }
+
/**
* Open the webcam in blocking (synchronous) mode.
*
@@ -175,9 +257,10 @@ public boolean open(boolean async) {
if (open.compareAndSet(false, true)) {
- assert updater != null;
assert lock != null;
+ notificator = Executors.newSingleThreadExecutor(new NotificationThreadFactory());
+
// lock webcam for other Java (only) processes
lock.lock();
@@ -195,6 +278,7 @@ public boolean open(boolean async) {
} catch (WebcamException e) {
lock.unlock();
open.set(false);
+ LOG.debug("Webcam exception when opening", e);
throw e;
}
@@ -202,9 +286,10 @@ public boolean open(boolean async) {
// setup non-blocking configuration
- asynchronous = async;
-
- if (async) {
+ if (asynchronous = async) {
+ if (updater == null) {
+ updater = new WebcamUpdater(this);
+ }
updater.start();
}
@@ -243,7 +328,6 @@ public boolean close() {
LOG.debug("Closing webcam {}", getName());
- assert updater != null;
assert lock != null;
// close webcam
@@ -261,7 +345,9 @@ public boolean close() {
}
// stop updater
- updater.stop();
+ if (asynchronous) {
+ updater.stop();
+ }
// remove shutdown hook (it's not more necessary)
removeShutdownHook();
@@ -284,6 +370,15 @@ public boolean close() {
}
}
+ notificator.shutdown();
+ while (!notificator.isTerminated()) {
+ try {
+ notificator.awaitTermination(100, TimeUnit.MILLISECONDS);
+ } catch (InterruptedException e) {
+ return false;
+ }
+ }
+
LOG.debug("Webcam {} has been closed", getName());
} else {
@@ -552,7 +647,7 @@ public BufferedImage getImage() {
// notify webcam listeners about new image available
- updater.notifyWebcamImageObtained(this, image);
+ notifyWebcamImageAcquired(image);
return image;
}
diff --git a/webcam-capture/src/main/java/com/github/sarxos/webcam/WebcamPanel.java b/webcam-capture/src/main/java/com/github/sarxos/webcam/WebcamPanel.java
index 5f6ed058..0448b75e 100644
--- a/webcam-capture/src/main/java/com/github/sarxos/webcam/WebcamPanel.java
+++ b/webcam-capture/src/main/java/com/github/sarxos/webcam/WebcamPanel.java
@@ -480,7 +480,7 @@ public void run() {
// completely started (it was in "starting" timeframe)
LOG.warn("Executor rejected paint update");
- LOG.debug("Executor rejected paint update because of", e);
+ LOG.trace("Executor rejected paint update because of", e);
return;
}
diff --git a/webcam-capture/src/main/java/com/github/sarxos/webcam/WebcamUpdater.java b/webcam-capture/src/main/java/com/github/sarxos/webcam/WebcamUpdater.java
index e7ee618d..093a619d 100644
--- a/webcam-capture/src/main/java/com/github/sarxos/webcam/WebcamUpdater.java
+++ b/webcam-capture/src/main/java/com/github/sarxos/webcam/WebcamUpdater.java
@@ -1,7 +1,6 @@
package com.github.sarxos.webcam;
import java.awt.image.BufferedImage;
-import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.ScheduledExecutorService;
@@ -45,50 +44,6 @@ public Thread newThread(Runnable r) {
}
- /**
- * Class used to asynchronously notify all webcam listeners about new image
- * available.
- *
- * @author Bartosz Firyn (sarxos)
- */
- private static final class ImageNotification implements Runnable {
-
- /**
- * Camera.
- */
- private final Webcam webcam;
-
- /**
- * Acquired image.
- */
- private final BufferedImage image;
-
- /**
- * Create new notification.
- *
- * @param webcam the webcam from which image has been acquired
- * @param image the acquired image
- */
- public ImageNotification(Webcam webcam, BufferedImage image) {
- this.webcam = webcam;
- this.image = image;
- }
-
- @Override
- public void run() {
- if (image != null) {
- WebcamEvent we = new WebcamEvent(WebcamEventType.NEW_IMAGE, webcam, image);
- for (WebcamListener l : webcam.getWebcamListeners()) {
- try {
- l.webcamImageObtained(we);
- } catch (Exception e) {
- LOG.error(String.format("Notify image acquired, exception when calling listener %s", l.getClass()), e);
- }
- }
- }
- }
- }
-
/**
* Logger.
*/
@@ -106,11 +61,6 @@ public void run() {
*/
private ScheduledExecutorService executor = null;
- /**
- * Executor service for image notifications.
- */
- private final ExecutorService notificator = Executors.newSingleThreadExecutor(THREAD_FACTORY);
-
/**
* Cached image.
*/
@@ -146,6 +96,7 @@ protected WebcamUpdater(Webcam webcam) {
* Start updater.
*/
public void start() {
+
if (running.compareAndSet(false, true)) {
image.set(new WebcamReadImageTask(Webcam.getDriver(), webcam.getDevice()).getImage());
@@ -166,12 +117,10 @@ public void stop() {
if (running.compareAndSet(true, false)) {
executor.shutdown();
-
while (!executor.isTerminated()) {
try {
executor.awaitTermination(100, TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
- LOG.trace(e.getMessage(), e);
return;
}
}
@@ -199,6 +148,10 @@ public void run() {
private void tick() {
+ if (!webcam.isOpen()) {
+ return;
+ }
+
long t1 = 0;
long t2 = 0;
@@ -245,22 +198,7 @@ private void tick() {
// notify webcam listeners about the new image available
- notifyWebcamImageObtained(webcam, image.get());
- }
-
- /**
- * Asynchronously start new thread which will notify all webcam listeners
- * about the new image available.
- */
- protected void notifyWebcamImageObtained(Webcam webcam, BufferedImage image) {
-
- // notify webcam listeners of new image available, do that only if there
- // are any webcam listeners available because there is no sense to start
- // additional threads for no purpose
-
- if (webcam.getWebcamListenersCount() > 0) {
- notificator.execute(new ImageNotification(webcam, image));
- }
+ webcam.notifyWebcamImageAcquired(image.get());
}
/**
diff --git a/webcam-capture/src/main/java/com/github/sarxos/webcam/ds/buildin/WebcamDefaultDevice.java b/webcam-capture/src/main/java/com/github/sarxos/webcam/ds/buildin/WebcamDefaultDevice.java
index e54a28b2..a96b1260 100644
--- a/webcam-capture/src/main/java/com/github/sarxos/webcam/ds/buildin/WebcamDefaultDevice.java
+++ b/webcam-capture/src/main/java/com/github/sarxos/webcam/ds/buildin/WebcamDefaultDevice.java
@@ -307,7 +307,7 @@ public void open() {
open.set(true);
- refresher = new Thread(this, String.format("frames-refresher:%s", id));
+ refresher = new Thread(this, String.format("frames-refresher-[%s]", id));
refresher.setUncaughtExceptionHandler(WebcamExceptionHandler.getInstance());
refresher.setDaemon(true);
refresher.start();