001 package com.github.sarxos.webcam;
002
003 import java.awt.Dimension;
004 import java.awt.image.BufferedImage;
005 import java.nio.ByteBuffer;
006 import java.util.ArrayList;
007 import java.util.Arrays;
008 import java.util.Collections;
009 import java.util.List;
010 import java.util.concurrent.TimeUnit;
011 import java.util.concurrent.TimeoutException;
012 import java.util.concurrent.atomic.AtomicBoolean;
013
014 import org.slf4j.Logger;
015 import org.slf4j.LoggerFactory;
016
017 import com.github.sarxos.webcam.WebcamDevice.BufferAccess;
018 import com.github.sarxos.webcam.ds.buildin.WebcamDefaultDevice;
019 import com.github.sarxos.webcam.ds.buildin.WebcamDefaultDriver;
020 import com.github.sarxos.webcam.ds.cgt.WebcamCloseTask;
021 import com.github.sarxos.webcam.ds.cgt.WebcamDisposeTask;
022 import com.github.sarxos.webcam.ds.cgt.WebcamOpenTask;
023 import com.github.sarxos.webcam.ds.cgt.WebcamReadBufferTask;
024 import com.github.sarxos.webcam.ds.cgt.WebcamReadImageTask;
025 import com.github.sarxos.webcam.util.ImageUtils;
026
027
028 /**
029 * Webcam class. It wraps webcam device obtained from webcam driver.
030 *
031 * @author Bartosz Firyn (bfiryn)
032 */
033 public class Webcam {
034
035 /**
036 * Logger instance.
037 */
038 private static final Logger LOG = LoggerFactory.getLogger(Webcam.class);
039
040 /**
041 * List of driver classes names to search for.
042 */
043 private static final List<String> DRIVERS_LIST = new ArrayList<String>();
044
045 /**
046 * List of driver classes to search for.
047 */
048 private static final List<Class<?>> DRIVERS_CLASS_LIST = new ArrayList<Class<?>>();
049
050 /**
051 * Discovery listeners.
052 */
053 private static final List<WebcamDiscoveryListener> DISCOVERY_LISTENERS = Collections.synchronizedList(new ArrayList<WebcamDiscoveryListener>());
054
055 /**
056 * Webcam driver (LtiCivil, JMF, FMJ, JQT, OpenCV, VLCj, etc).
057 */
058 private static WebcamDriver driver = null;
059
060 /**
061 * Webcam discovery service.
062 */
063 private static volatile WebcamDiscoveryService discovery = null;
064
065 /**
066 * Is automated deallocation on TERM signal enabled.
067 */
068 private static boolean deallocOnTermSignal = false;
069
070 /**
071 * Is auto-open feature enabled?
072 */
073 private static boolean autoOpen = false;
074
075 /**
076 * Webcam listeners.
077 */
078 private List<WebcamListener> listeners = Collections.synchronizedList(new ArrayList<WebcamListener>());
079
080 /**
081 * List of custom resolution sizes supported by webcam instance.
082 */
083 private List<Dimension> customSizes = new ArrayList<Dimension>();
084
085 /**
086 * Shutdown hook.
087 */
088 private WebcamShutdownHook hook = null;
089
090 /**
091 * Underlying webcam device.
092 */
093 private WebcamDevice device = null;
094
095 /**
096 * Is webcam open?
097 */
098 private AtomicBoolean open = new AtomicBoolean(false);
099
100 /**
101 * Is webcam already disposed?
102 */
103 private AtomicBoolean disposed = new AtomicBoolean(false);
104
105 /**
106 * Is non-blocking (asynchronous) access enabled?
107 */
108 private volatile boolean asynchronous = false;
109
110 /**
111 * Current FPS.
112 */
113 private volatile double fps = 0;
114
115 /**
116 * Webcam image updater.
117 */
118 private WebcamUpdater updater = new WebcamUpdater(this);
119
120 /**
121 * Webcam class.
122 *
123 * @param device - device to be used as webcam
124 * @throws IllegalArgumentException when device argument is null
125 */
126 protected Webcam(WebcamDevice device) {
127 if (device == null) {
128 throw new IllegalArgumentException("Webcam device cannot be null");
129 }
130 this.device = device;
131 }
132
133 /**
134 * Open the webcam in blocking (synchronous) mode.
135 *
136 * @see #open(boolean)
137 */
138 public boolean open() {
139 return open(false);
140 }
141
142 /**
143 * Open the webcam in either blocking (synchronous) or non-blocking
144 * (asynchronous) mode.The difference between those two modes lies in the
145 * image acquisition mechanism.<br>
146 * <br>
147 * In blocking mode, when user calls {@link #getImage()} method, device is
148 * being queried for new image buffer and user have to wait for it to be
149 * available.<br>
150 * <br>
151 * In non-blocking mode, there is a special thread running in the background
152 * which constantly fetch new images and cache them internally for further
153 * use. This cached instance is returned every time when user request new
154 * image. Because of that it can be used when timeing is very important,
155 * because all users calls for new image do not have to wait on device
156 * response. By using this mode user should be aware of the fact that in
157 * some cases, when two consecutive calls to get new image are executed more
158 * often than webcam device can serve them, the same image instance will be
159 * returned. User should use {@link #isImageNew()} method to distinguish if
160 * returned image is not the same as the previous one.
161 *
162 * @param asynchronous true for non-blocking mode, false for blocking
163 */
164 public boolean open(boolean async) {
165
166 if (open.compareAndSet(false, true)) {
167
168 WebcamOpenTask task = new WebcamOpenTask(driver, device);
169 try {
170 task.open();
171 } catch (InterruptedException e) {
172 open.set(false);
173 LOG.error("Processor has been interrupted before webcam was open!", e);
174 return false;
175 } catch (WebcamException e) {
176 open.set(false);
177 throw e;
178 }
179
180 LOG.debug("Webcam is now open {}", getName());
181
182 // setup non-blocking configuration
183
184 asynchronous = async;
185
186 if (async) {
187 updater.start();
188 }
189
190 // install shutdown hook
191
192 Runtime.getRuntime().addShutdownHook(hook = new WebcamShutdownHook(this));
193
194 // notify listeners
195
196 WebcamEvent we = new WebcamEvent(WebcamEventType.OPEN, this);
197 for (WebcamListener l : getWebcamListeners()) {
198 try {
199 l.webcamOpen(we);
200 } catch (Exception e) {
201 LOG.error(String.format("Notify webcam open, exception when calling listener %s", l.getClass()), e);
202 }
203 }
204
205 } else {
206 LOG.debug("Webcam is already open {}", getName());
207 }
208
209 return true;
210 }
211
212 /**
213 * Close the webcam.
214 */
215 public boolean close() {
216
217 if (open.compareAndSet(true, false)) {
218
219 // close webcam
220
221 WebcamCloseTask task = new WebcamCloseTask(driver, device);
222 try {
223 task.close();
224 } catch (InterruptedException e) {
225 open.set(true);
226 LOG.error("Processor has been interrupted before webcam was closed!", e);
227 return false;
228 } catch (WebcamException e) {
229 open.set(false);
230 throw e;
231 }
232
233 // stop updater
234
235 if (asynchronous) {
236 updater.stop();
237 }
238
239 // remove shutdown hook (it's not more necessary)
240
241 Runtime.getRuntime().removeShutdownHook(hook);
242
243 // notify listeners
244
245 WebcamEvent we = new WebcamEvent(WebcamEventType.CLOSED, this);
246 for (WebcamListener l : getWebcamListeners()) {
247 try {
248 l.webcamClosed(we);
249 } catch (Exception e) {
250 LOG.error(String.format("Notify webcam closed, exception when calling %s listener", l.getClass()), e);
251 }
252 }
253
254 } else {
255 LOG.debug("Webcam is already closed {}", getName());
256 }
257
258 return true;
259 }
260
261 /**
262 * Is webcam open?
263 *
264 * @return true if open, false otherwise
265 */
266 public boolean isOpen() {
267 return open.get();
268 }
269
270 /**
271 * Get current webcam resolution in pixels.
272 *
273 * @return Webcam resolution (picture size) in pixels.
274 */
275 public Dimension getViewSize() {
276 return device.getResolution();
277 }
278
279 /**
280 * Return list of supported view sizes. It can differ between vary webcam
281 * data sources.
282 *
283 * @return
284 */
285 public Dimension[] getViewSizes() {
286 return device.getResolutions();
287 }
288
289 /**
290 * Set custom resolution. If you are using this method you have to make sure
291 * that your webcam device can support this specific resolution.
292 *
293 * @param sizes the array of custom resolutions to be supported by webcam
294 */
295 public void setCustomViewSizes(Dimension[] sizes) {
296 if (sizes == null) {
297 customSizes.clear();
298 return;
299 }
300 customSizes = Arrays.asList(sizes);
301 }
302
303 public Dimension[] getCustomViewSizes() {
304 return customSizes.toArray(new Dimension[customSizes.size()]);
305 }
306
307 /**
308 * Set new view size. New size has to exactly the same as one of the default
309 * sized or exactly the same as one of the custom ones.
310 *
311 * @param size the new view size to be set
312 * @see Webcam#setCustomViewSizes(Dimension[])
313 * @see Webcam#getViewSizes()
314 */
315 public void setViewSize(Dimension size) {
316
317 if (size == null) {
318 throw new IllegalArgumentException("Resolution cannot be null!");
319 }
320
321 if (open.get()) {
322 throw new IllegalStateException("Cannot change resolution when webcam is open, please close it first");
323 }
324
325 // check if new resolution is the same as current one
326
327 Dimension current = getViewSize();
328 if (current != null && current.width == size.width && current.height == size.height) {
329 return;
330 }
331
332 // check if new resolution is valid
333
334 Dimension[] predefined = getViewSizes();
335 Dimension[] custom = getCustomViewSizes();
336
337 boolean ok = false;
338 for (Dimension d : predefined) {
339 if (d.width == size.width && d.height == size.height) {
340 ok = true;
341 break;
342 }
343 }
344 if (!ok) {
345 for (Dimension d : custom) {
346 if (d.width == size.width && d.height == size.height) {
347 ok = true;
348 break;
349 }
350 }
351 }
352
353 if (!ok) {
354 StringBuilder sb = new StringBuilder("Incorrect dimension [");
355 sb.append(size.width).append("x").append(size.height).append("] ");
356 sb.append("possible ones are ");
357 for (Dimension d : predefined) {
358 sb.append("[").append(d.width).append("x").append(d.height).append("] ");
359 }
360 for (Dimension d : custom) {
361 sb.append("[").append(d.width).append("x").append(d.height).append("] ");
362 }
363 throw new IllegalArgumentException(sb.toString());
364 }
365
366 LOG.debug("Setting new resolution {}x{}", size.width, size.height);
367
368 device.setResolution(size);
369 }
370
371 /**
372 * Capture image from webcam and return it. Will return image object or null
373 * if webcam is closed or has been already disposed by JVM.<br>
374 * <br>
375 * <b>IMPORTANT NOTE!!!</b><br>
376 * <br>
377 * There are two possible behaviors of what webcam should do when you try to
378 * get image and webcam is actually closed. Normally it will return null,
379 * but there is a special flag which can be statically set to switch all
380 * webcams to auto open mode. In this mode, webcam will be automatically
381 * open, when you try to get image from closed webcam. Please be aware of
382 * some side effects! In case of multi-threaded applications, there is no
383 * guarantee that one thread will not try to open webcam even if it was
384 * manually closed in different thread.
385 *
386 * @return Captured image or null if webcam is closed or disposed by JVM
387 */
388 public BufferedImage getImage() {
389
390 if (!isReady()) {
391 return null;
392 }
393
394 long t1 = 0;
395 long t2 = 0;
396
397 if (asynchronous) {
398 return updater.getImage();
399 } else {
400
401 // get image
402
403 t1 = System.currentTimeMillis();
404 BufferedImage image = new WebcamReadImageTask(driver, device).getImage();
405 t2 = System.currentTimeMillis();
406
407 if (image == null) {
408 return null;
409 }
410
411 // calculate FPS
412
413 // +1 to avoid division by zero
414 fps = (4 * fps + 1000 / (t2 - t1 + 1)) / 5;
415
416 // notify webcam listeners about new image available
417
418 updater.notifyWebcamImageObtained(this, image);
419
420 return image;
421 }
422 }
423
424 protected double getFPS() {
425 if (asynchronous) {
426 return updater.getFPS();
427 } else {
428 return fps;
429 }
430 }
431
432 /**
433 * Get RAW image ByteBuffer. It will always return buffer with 3 x 1 bytes
434 * per each pixel, where RGB components are on (0, 1, 2) and color space is
435 * sRGB.<br>
436 * <br>
437 *
438 * <b>IMPORTANT!</b><br>
439 * Some drivers can return direct ByteBuffer, so there is no guarantee that
440 * underlying bytes will not be released in next read image operation.
441 * Therefore, to avoid potential bugs you should convert this ByteBuffer to
442 * bytes array before you fetch next image.
443 *
444 * @return Byte buffer
445 */
446 public ByteBuffer getImageBytes() {
447
448 if (!isReady()) {
449 return null;
450 }
451
452 // some devices can support direct image buffers, and for those call
453 // processor task, and for those which does not support direct image
454 // buffers, just convert image to RGB byte array
455
456 if (device instanceof BufferAccess) {
457 return new WebcamReadBufferTask(driver, device).getBuffer();
458 } else {
459 return ByteBuffer.wrap(ImageUtils.toRawByteArray(getImage()));
460 }
461 }
462
463 /**
464 * Is webcam ready to be read.
465 *
466 * @return True if ready, false otherwise
467 */
468 private boolean isReady() {
469
470 if (disposed.get()) {
471 LOG.warn("Cannot get image, webcam has been already disposed");
472 return false;
473 }
474
475 if (!open.get()) {
476 if (autoOpen) {
477 open();
478 } else {
479 return false;
480 }
481 }
482
483 return true;
484 }
485
486 /**
487 * Get list of webcams to use. This method will wait predefined time
488 * interval for webcam devices to be discovered. By default this time is set
489 * to 1 minute.
490 *
491 * @return List of webcams existing in the ssytem
492 * @throws WebcamException when something is wrong
493 * @see Webcam#getWebcams(long, TimeUnit)
494 */
495 public static List<Webcam> getWebcams() throws WebcamException {
496
497 // timeout exception below will never be caught since user would have to
498 // wait around three hundreds billion years for it to occur
499
500 try {
501 return getWebcams(Long.MAX_VALUE);
502 } catch (TimeoutException e) {
503 throw new RuntimeException(e);
504 }
505 }
506
507 /**
508 * Get list of webcams to use. This method will wait given time interval for
509 * webcam devices to be discovered. Time argument is given in milliseconds.
510 *
511 * @param timeout the time to wait for webcam devices to be discovered
512 * @return List of webcams existing in the ssytem
513 * @throws WebcamException when something is wrong
514 * @see Webcam#getWebcams(long, TimeUnit)
515 */
516 public static List<Webcam> getWebcams(long timeout) throws TimeoutException, WebcamException {
517 return getWebcams(timeout, TimeUnit.MILLISECONDS);
518 }
519
520 /**
521 * Get list of webcams to use. This method will wait given time interval for
522 * webcam devices to be discovered.
523 *
524 * @param timeout the devices discovery timeout
525 * @param tunit the time unit
526 * @return List of webcams
527 * @throws TimeoutException when timeout has been exceeded
528 * @throws WebcamException when something is wrong
529 */
530 public static synchronized List<Webcam> getWebcams(long timeout, TimeUnit tunit) throws TimeoutException, WebcamException {
531
532 WebcamDiscoveryService discovery = getDiscoveryService();
533 List<Webcam> webcams = discovery.getWebcams(timeout, tunit);
534
535 if (!discovery.isRunning()) {
536 discovery.start();
537 }
538
539 return webcams;
540 }
541
542 /**
543 * Will discover and return first webcam available in the system.
544 *
545 * @return Default webcam (first from the list)
546 * @throws WebcamException if something is really wrong
547 * @see Webcam#getWebcams()
548 */
549 public static Webcam getDefault() throws WebcamException {
550
551 try {
552 return getDefault(Long.MAX_VALUE);
553 } catch (TimeoutException e) {
554 // this should never happen since user would have to wait 300000000
555 // years for it to occur
556 throw new RuntimeException(e);
557 }
558 }
559
560 /**
561 * Will discover and return first webcam available in the system.
562 *
563 * @param timeout the webcam discovery timeout (1 minute by default)
564 * @return Default webcam (first from the list)
565 * @throws TimeoutException when discovery timeout has been exceeded
566 * @throws WebcamException if something is really wrong
567 * @see Webcam#getWebcams(long)
568 */
569 public static Webcam getDefault(long timeout) throws TimeoutException, WebcamException {
570 return getDefault(timeout, TimeUnit.MILLISECONDS);
571 }
572
573 /**
574 * Will discover and return first webcam available in the system.
575 *
576 * @param timeout the webcam discovery timeout (1 minute by default)
577 * @param tunit the time unit
578 * @return Default webcam (first from the list)
579 * @throws TimeoutException when discovery timeout has been exceeded
580 * @throws WebcamException if something is really wrong
581 * @see Webcam#getWebcams(long, TimeUnit)
582 */
583 public static Webcam getDefault(long timeout, TimeUnit tunit) throws TimeoutException, WebcamException {
584
585 if (timeout < 0) {
586 throw new IllegalArgumentException("Timeout cannot be negative");
587 }
588 if (tunit == null) {
589 throw new IllegalArgumentException("Time unit cannot be null!");
590 }
591
592 List<Webcam> webcams = getWebcams(timeout, tunit);
593
594 if (!webcams.isEmpty()) {
595 return webcams.get(0);
596 }
597
598 LOG.warn("No webcam has been detected!");
599
600 return null;
601 }
602
603 /**
604 * Get webcam name (device name). The name of device depends on the value
605 * returned by the underlying data source, so in some cases it can be
606 * human-readable value and sometimes it can be some strange number.
607 *
608 * @return Name
609 */
610 public String getName() {
611 return device.getName();
612 }
613
614 @Override
615 public String toString() {
616 return String.format("Webcam %s", getName());
617 }
618
619 /**
620 * Add webcam listener.
621 *
622 * @param l the listener to be added
623 * @throws IllegalArgumentException when argument is null
624 */
625 public boolean addWebcamListener(WebcamListener l) {
626 if (l == null) {
627 throw new IllegalArgumentException("Webcam listener cannot be null!");
628 }
629 return listeners.add(l);
630 }
631
632 /**
633 * @return All webcam listeners
634 */
635 public WebcamListener[] getWebcamListeners() {
636 return listeners.toArray(new WebcamListener[listeners.size()]);
637 }
638
639 /**
640 * @return Number of webcam listeners
641 */
642 public int getWebcamListenersCount() {
643 return listeners.size();
644 }
645
646 /**
647 * Removes webcam listener.
648 *
649 * @param l the listener to be removed
650 * @return True if listener has been removed, false otherwise
651 */
652 public boolean removeWebcamListener(WebcamListener l) {
653 return listeners.remove(l);
654 }
655
656 /**
657 * Return webcam driver. Perform search if necessary.<br>
658 * <br>
659 * <b>This method is not thread-safe!</b>
660 *
661 * @return Webcam driver
662 */
663 public static synchronized WebcamDriver getDriver() {
664
665 if (driver == null) {
666 driver = WebcamDriverUtils.findDriver(DRIVERS_LIST, DRIVERS_CLASS_LIST);
667 }
668
669 if (driver == null) {
670 driver = new WebcamDefaultDriver();
671 }
672
673 LOG.info("{} capture driver will be used", driver.getClass().getSimpleName());
674
675 return driver;
676 }
677
678 /**
679 * Set new video driver to be used by webcam.<br>
680 * <br>
681 * <b>This method is not thread-safe!</b>
682 *
683 * @param driver new webcam driver to be used (e.g. LtiCivil, JFM, FMJ, QTJ)
684 * @throws IllegalArgumentException when argument is null
685 */
686 public static synchronized void setDriver(WebcamDriver driver) {
687
688 if (driver == null) {
689 throw new IllegalArgumentException("Webcam driver cannot be null!");
690 }
691
692 resetDriver();
693
694 Webcam.driver = driver;
695 }
696
697 /**
698 * Set new video driver class to be used by webcam. Class given in the
699 * argument shall extend {@link WebcamDriver} interface and should have
700 * public default constructor, so instance can be created by reflection.<br>
701 * <br>
702 * <b>This method is not thread-safe!</b>
703 *
704 * @param driver new video driver class to use
705 * @throws IllegalArgumentException when argument is null
706 */
707 public static synchronized void setDriver(Class<? extends WebcamDriver> driverClass) {
708
709 resetDriver();
710
711 if (driverClass == null) {
712 throw new IllegalArgumentException("Webcam driver class cannot be null!");
713 }
714
715 try {
716 driver = driverClass.newInstance();
717 } catch (InstantiationException e) {
718 throw new WebcamException(e);
719 } catch (IllegalAccessException e) {
720 throw new WebcamException(e);
721 }
722 }
723
724 /**
725 * Reset webcam driver.<br>
726 * <br>
727 * <b>This method is not thread-safe!</b>
728 */
729 public static synchronized void resetDriver() {
730
731 DRIVERS_LIST.clear();
732
733 if (discovery != null) {
734 discovery.shutdown();
735 discovery = null;
736 }
737
738 driver = null;
739 }
740
741 /**
742 * Register new webcam video driver.
743 *
744 * @param clazz webcam video driver class
745 * @throws IllegalArgumentException when argument is null
746 */
747 public static void registerDriver(Class<? extends WebcamDriver> clazz) {
748 if (clazz == null) {
749 throw new IllegalArgumentException("Webcam driver class to register cannot be null!");
750 }
751 DRIVERS_CLASS_LIST.add(clazz);
752 registerDriver(clazz.getCanonicalName());
753 }
754
755 /**
756 * Register new webcam video driver.
757 *
758 * @param clazzName webcam video driver class name
759 * @throws IllegalArgumentException when argument is null
760 */
761 public static void registerDriver(String clazzName) {
762 if (clazzName == null) {
763 throw new IllegalArgumentException("Webcam driver class name to register cannot be null!");
764 }
765 DRIVERS_LIST.add(clazzName);
766 }
767
768 /**
769 * Return underlying webcam device. Depending on the driver used to discover
770 * devices, this method can return instances of different class. By default
771 * {@link WebcamDefaultDevice} is returned when no external driver is used.
772 *
773 * @return Underlying webcam device instance
774 */
775 public WebcamDevice getDevice() {
776 return device;
777 }
778
779 /**
780 * Completely dispose capture device. After this operation webcam cannot be
781 * used any more and full reinstantiation is required.
782 */
783 protected void dispose() {
784
785 if (!disposed.compareAndSet(false, true)) {
786 return;
787 }
788
789 open.set(false);
790
791 LOG.info("Disposing webcam {}", getName());
792
793 WebcamDisposeTask task = new WebcamDisposeTask(driver, device);
794 try {
795 task.dispose();
796 } catch (InterruptedException e) {
797 LOG.error("Processor has been interrupted before webcam was disposed!", e);
798 return;
799 }
800
801 WebcamEvent we = new WebcamEvent(WebcamEventType.DISPOSED, this);
802 for (WebcamListener l : listeners) {
803 try {
804 l.webcamClosed(we);
805 l.webcamDisposed(we);
806 } catch (Exception e) {
807 LOG.error(String.format("Notify webcam disposed, exception when calling %s listener", l.getClass()), e);
808 }
809 }
810
811 // hook can be null because there is a possibility that webcam has never
812 // been open and therefore hook was not created
813 if (hook != null) {
814 try {
815 Runtime.getRuntime().removeShutdownHook(hook);
816 } catch (IllegalStateException e) {
817 LOG.trace("Shutdown in progress, cannot remove hook");
818 }
819 }
820
821 LOG.debug("Webcam disposed {}", getName());
822 }
823
824 /**
825 * <b>CAUTION!!!</b><br>
826 * <br>
827 * This is experimental feature to be used mostly in in development phase.
828 * After you set handle term signal to true, and fetch capture devices,
829 * Webcam Capture API will listen for TERM signal and try to close all
830 * devices after it has been received. <b>This feature can be unstable on
831 * some systems!</b>
832 *
833 * @param on signal handling will be enabled if true, disabled otherwise
834 */
835 public static void setHandleTermSignal(boolean on) {
836 if (on) {
837 LOG.warn("Automated deallocation on TERM signal is now enabled! Make sure to not use it in production!");
838 }
839 deallocOnTermSignal = on;
840 }
841
842 /**
843 * Is TERM signal handler enabled.
844 *
845 * @return True if enabled, false otherwise
846 */
847 public static boolean isHandleTermSignal() {
848 return deallocOnTermSignal;
849 }
850
851 /**
852 * Switch all webcams to auto open mode. In this mode, each webcam will be
853 * automatically open whenever user will try to get image from instance
854 * which has not yet been open. Please be aware of some side effects! In
855 * case of multi-threaded applications, there is no guarantee that one
856 * thread will not try to open webcam even if it was manually closed in
857 * different thread.
858 *
859 * @param on true to enable, false to disable
860 */
861 public static void setAutoOpenMode(boolean on) {
862 autoOpen = on;
863 }
864
865 /**
866 * Is auto open mode enabled. Auto open mode will will automatically open
867 * webcam whenever user will try to get image from instance which has not
868 * yet been open. Please be aware of some side effects! In case of
869 * multi-threaded applications, there is no guarantee that one thread will
870 * not try to open webcam even if it was manually closed in different
871 * thread.
872 *
873 * @return True if mode is enabled, false otherwise
874 */
875 public static boolean isAutoOpenMode() {
876 return autoOpen;
877 }
878
879 /**
880 * Add new webcam discovery listener.
881 *
882 * @param l the listener to be added
883 * @return True, if listeners list size has been changed, false otherwise
884 * @throws IllegalArgumentException when argument is null
885 */
886 public static boolean addDiscoveryListener(WebcamDiscoveryListener l) {
887 if (l == null) {
888 throw new IllegalArgumentException("Webcam discovery listener cannot be null!");
889 }
890 return DISCOVERY_LISTENERS.add(l);
891 }
892
893 public static WebcamDiscoveryListener[] getDiscoveryListeners() {
894 return DISCOVERY_LISTENERS.toArray(new WebcamDiscoveryListener[DISCOVERY_LISTENERS.size()]);
895 }
896
897 /**
898 * Remove discovery listener
899 *
900 * @param l the listener to be removed
901 * @return True if listeners list contained the specified element
902 */
903 public static boolean removeDiscoveryListener(WebcamDiscoveryListener l) {
904 return DISCOVERY_LISTENERS.remove(l);
905 }
906
907 /**
908 * Return discovery service.
909 *
910 * @return Discovery service
911 */
912 public static synchronized WebcamDiscoveryService getDiscoveryService() {
913 if (discovery == null) {
914 discovery = new WebcamDiscoveryService(getDriver());
915 }
916 return discovery;
917 }
918 }