All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.github.sarxos.webcam.WebcamLock Maven / Gradle / Ivy

Go to download

This library allows you to use your PC webcam, IP or network cameras directly from Java. It's compatible with most operating systems (Windows, Linux, MacOS).

There is a newer version: 0.3.9
Show newest version
package com.github.sarxos.webcam;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.EOFException;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.concurrent.atomic.AtomicBoolean;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;


/**
 * This class is used as a global (system) lock preventing other processes from using the same
 * camera while it's open. Whenever webcam is open there is a thread running in background which
 * updates the lock once per 2 seconds. Lock is being released whenever webcam is either closed or
 * completely disposed. Lock will remain for at least 2 seconds in case when JVM has not been
 * gracefully terminated (due to SIGSEGV, SIGTERM, etc).
 *
 * @author Bartosz Firyn (sarxos)
 */
public class WebcamLock {

	/**
	 * Logger.
	 */
	private static final Logger LOG = LoggerFactory.getLogger(WebcamLock.class);

	/**
	 * Update interval (ms).
	 */
	public static final long INTERVAL = 2000;

	/**
	 * Used to update lock state.
	 *
	 * @author sarxos
	 */
	private class LockUpdater extends Thread {

		public LockUpdater() {
			super();
			setName(String.format("webcam-lock-[%s]", webcam.getName()));
			setDaemon(true);
			setUncaughtExceptionHandler(WebcamExceptionHandler.getInstance());
		}

		@Override
		public void run() {
			do {
				if (disabled.get()) {
					return;
				}
				update();
				try {
					Thread.sleep(INTERVAL);
				} catch (InterruptedException e) {
					LOG.debug("Lock updater has been interrupted");
					return;
				}
			} while (locked.get());
		}

	}

	/**
	 * And the Webcam we will be locking.
	 */
	private final Webcam webcam;

	/**
	 * Updater thread. It will update the lock value in fixed interval.
	 */
	private Thread updater = null;

	/**
	 * Is webcam locked (local, not cross-VM variable).
	 */
	private final AtomicBoolean locked = new AtomicBoolean(false);

	/**
	 * Is lock completely disabled.
	 */
	private final AtomicBoolean disabled = new AtomicBoolean(false);

	/**
	 * Lock file.
	 */
	private final File lock;

	/**
	 * Creates global webcam lock.
	 *
	 * @param webcam the webcam instance to be locked
	 */
	protected WebcamLock(Webcam webcam) {
		super();
		this.webcam = webcam;
		this.lock = new File(System.getProperty("java.io.tmpdir"), getLockName());
		this.lock.deleteOnExit();
	}

	private String getLockName() {
		return String.format(".webcam-lock-%d", Math.abs(webcam.getName().hashCode()));
	}

	private void write(long value) {

		if (disabled.get()) {
			return;
		}

		String name = getLockName();

		File tmp = null;
		DataOutputStream dos = null;

		try {

			tmp = File.createTempFile(String.format("%s-tmp", name), "");
			tmp.deleteOnExit();

			dos = new DataOutputStream(new FileOutputStream(tmp));
			dos.writeLong(value);
			dos.flush();

		} catch (IOException e) {
			throw new RuntimeException(e);
		} finally {
			if (dos != null) {
				try {
					dos.close();
				} catch (IOException e) {
					throw new RuntimeException(e);
				}
			}
		}

		if (!locked.get()) {
			return;
		}

		if (tmp.renameTo(lock)) {

			// atomic rename operation can fail (mostly on Windows), so we
			// simply jump out the method if it succeed, or try to rewrite
			// content using streams if it fail

			return;
		} else {

			// create lock file if not exist

			if (!lock.exists()) {
				try {
					if (lock.createNewFile()) {
						LOG.info("Lock file {} for {} has been created", lock, webcam);
					} else {
						throw new RuntimeException("Not able to create file " + lock);
					}
				} catch (IOException e) {
					throw new RuntimeException(e);
				}
			}

			FileOutputStream fos = null;
			FileInputStream fis = null;

			int k = 0;
			int n = -1;
			byte[] buffer = new byte[8];
			boolean rewritten = false;

			// rewrite temporary file content to lock, try max 5 times

			synchronized (webcam) {
				do {
					try {

						fos = new FileOutputStream(lock);
						fis = new FileInputStream(tmp);
						while ((n = fis.read(buffer)) != -1) {
							fos.write(buffer, 0, n);
						}
						rewritten = true;

					} catch (IOException e) {
						LOG.debug("Not able to rewrite lock file", e);
					} finally {
						if (fos != null) {
							try {
								fos.close();
							} catch (IOException e) {
								throw new RuntimeException(e);
							}
						}
						if (fis != null) {
							try {
								fis.close();
							} catch (IOException e) {
								throw new RuntimeException(e);
							}
						}
					}
					if (rewritten) {
						break;
					}
				} while (k++ < 5);
			}

			if (!rewritten) {
				throw new WebcamException("Not able to write lock file");
			}

			// remove temporary file

			if (!tmp.delete()) {
				tmp.deleteOnExit();
			}
		}

	}

	private long read() {

		if (disabled.get()) {
			return -1;
		}

		DataInputStream dis = null;

		long value = -1;
		boolean broken = false;

		synchronized (webcam) {

			try {
				value = (dis = new DataInputStream(new FileInputStream(lock))).readLong();
			} catch (EOFException e) {
				LOG.debug("Webcam lock is broken - EOF when reading long variable from stream", e);
				broken = true;
			} catch (IOException e) {
				throw new RuntimeException(e);
			} finally {
				if (dis != null) {
					try {
						dis.close();
					} catch (IOException e) {
						throw new RuntimeException(e);
					}
				}
			}

			if (broken) {
				LOG.warn("Lock file {} for {} is broken - recreating it", lock, webcam);
				write(-1);
			}
		}

		return value;
	}

	private void update() {

		if (disabled.get()) {
			return;
		}

		write(System.currentTimeMillis());
	}

	/**
	 * Lock webcam.
	 */
	public void lock() {

		if (disabled.get()) {
			return;
		}

		if (isLocked()) {
			throw new WebcamLockException(String.format("Webcam %s has already been locked", webcam.getName()));
		}

		if (!locked.compareAndSet(false, true)) {
			return;
		}

		LOG.debug("Lock {}", webcam);

		update();

		updater = new LockUpdater();
		updater.start();
	}

	/**
	 * Completely disable locking mechanism. After this method is invoked, the lock will not have
	 * any effect on the webcam runtime.
	 */
	public void disable() {
		if (disabled.compareAndSet(false, true)) {
			LOG.info("Locking mechanism has been disabled in {}", webcam);
			if (updater != null) {
				updater.interrupt();
			}
		}
	}

	/**
	 * Unlock webcam.
	 */
	public void unlock() {

		// do nothing when lock disabled

		if (disabled.get()) {
			return;
		}

		if (!locked.compareAndSet(true, false)) {
			return;
		}

		LOG.debug("Unlock {}", webcam);

		updater.interrupt();

		write(-1);

		if (!lock.delete()) {
			lock.deleteOnExit();
		}
	}

	/**
	 * Check if webcam is locked.
	 *
	 * @return True if webcam is locked, false otherwise
	 */
	public boolean isLocked() {

		// always return false when lock is disabled

		if (disabled.get()) {
			return false;
		}

		// check if locked by current process

		if (locked.get()) {
			return true;
		}

		// check if locked by other process

		if (!lock.exists()) {
			return false;
		}

		long now = System.currentTimeMillis();
		long tsp = read();

		LOG.trace("Lock timestamp {} now {} for {}", tsp, now, webcam);

		if (tsp > now - INTERVAL * 2) {
			return true;
		}

		return false;
	}

	public File getLockFile() {
		return lock;
	}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy