# default settings
-video.device=/dev/video2
video.width=720
video.height=576
video.bpp=2
}
public class VideoConfiguration {
- public String device;
public int width;
public int height;
public int bpp;
public CropConfiguration crop;
private VideoConfiguration(Properties prop) {
- device = get(prop, "video.device", "/dev/video0");
width = Integer.parseInt(get(prop, "video.width", "720"));
height = Integer.parseInt(get(prop, "video.height", "576"));
bpp = Integer.parseInt(get(prop, "video.bpp", "2"));
private FrameGrabber() {
}
- public static FrameGrabber from(Configuration config) {
+ public static FrameGrabber from(File videoDevice, Configuration config) {
FrameGrabber fg = new FrameGrabber();
fg.config = config;
- fg.file = new File(config.video.device);
+ fg.file = videoDevice;
fg.bytesPerFrame = config.video.width * config.video.height * config.video.bpp;
fg.prepare();
return fg;
--- /dev/null
+package kaka.cakelight;
+
+import java.io.File;
+import java.util.Optional;
+import java.util.function.Consumer;
+
+import static kaka.cakelight.Main.log;
+
+public class VideoDeviceListener {
+ private Thread thread;
+ private Consumer<Optional<File>> changeConsumer;
+ private File lastDevice = null;
+
+ public void startListening() {
+ thread = new Thread() {
+ public void run() {
+ try {
+ while (!isInterrupted()) {
+ Optional<File> device = findVideoDevice();
+ if (!device.equals(Optional.ofNullable(lastDevice))) {
+ log("Video device change: %s", device.map(File::getAbsolutePath).orElse("none"));
+ changeConsumer.accept(device);
+ lastDevice = device.orElseGet(() -> null);
+ }
+ Thread.sleep(1000);
+ }
+ } catch (InterruptedException e) {
+ }
+ }
+ };
+ thread.start();
+ }
+
+ public void stopListening() {
+ thread.interrupt();
+ }
+
+ private Optional<File> findVideoDevice() {
+ File[] files = new File("/dev").listFiles((dir, name) -> name.matches("video[0-9]+"));
+ if (files == null || files.length == 0) {
+ return Optional.empty();
+ } else {
+ return Optional.of(files[0]);
+ }
+ }
+
+ public void onVideoDeviceChange(Consumer<Optional<File>> consumer) {
+ changeConsumer = consumer;
+ }
+}
package kaka.cakelight;
+import java.io.File;
import java.io.IOException;
import java.util.Optional;
import java.util.function.Consumer;
-import static kaka.cakelight.Main.log;
-import static kaka.cakelight.Main.timeIt;
-
-public class VideoMode implements Mode {
+public class VideoMode implements Mode, Consumer<Optional<File>> {
private Configuration config;
private Thread thread;
private Consumer<Frame> frameConsumer;
+ private VideoDeviceListener deviceListener;
+
+ public VideoMode() {
+ deviceListener = new VideoDeviceListener();
+ deviceListener.onVideoDeviceChange(this);
+ }
@Override
public void enter(Configuration config) {
this.config = config;
- startGrabberThread();
+ deviceListener.startListening();
}
@Override
public void exit() {
thread.interrupt();
+ deviceListener.stopListening();
}
- private void startGrabberThread() {
+ private void startGrabberThread(File videoDevice) {
assert frameConsumer != null;
thread = new Thread() {
public void run() {
- try (FrameGrabber grabber = FrameGrabber.from(config)) {
+ try (FrameGrabber grabber = FrameGrabber.from(videoDevice, config)) {
while (!isInterrupted()) {
// Optional<Frame> frame = grabber.grabFrame();
grabber.grabFrame().ifPresent(frameConsumer);
public void onFrame(Consumer<Frame> consumer) {
frameConsumer = consumer;
}
+
+ @Override
+ public void accept(Optional<File> videoDevice) {
+ // Should only happen when this mode is active!
+ if (thread != null) {
+ thread.interrupt();
+ }
+ videoDevice.ifPresent(this::startGrabberThread);
+ }
}