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 }