/
WebcamDiscoveryService.java
408 lines (310 loc) · 9.45 KB
/
WebcamDiscoveryService.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
package com.github.sarxos.webcam;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicBoolean;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class WebcamDiscoveryService implements Runnable {
private static final Logger LOG = LoggerFactory.getLogger(WebcamDiscoveryService.class);
private static final class WebcamsDiscovery implements Callable<List<Webcam>>, ThreadFactory {
private final WebcamDriver driver;
public WebcamsDiscovery(WebcamDriver driver) {
this.driver = driver;
}
@Override
public List<Webcam> call() throws Exception {
return toWebcams(driver.getDevices());
}
@Override
public Thread newThread(Runnable r) {
Thread t = new Thread(r, "webcam-discovery-service");
t.setDaemon(true);
t.setUncaughtExceptionHandler(WebcamExceptionHandler.getInstance());
return t;
}
}
private final WebcamDriver driver;
private final WebcamDiscoverySupport support;
private volatile List<Webcam> webcams = null;
private AtomicBoolean running = new AtomicBoolean(false);
private AtomicBoolean enabled = new AtomicBoolean(true);
private Thread runner = null;
protected WebcamDiscoveryService(WebcamDriver driver) {
if (driver == null) {
throw new IllegalArgumentException("Driver cannot be null!");
}
this.driver = driver;
this.support = (WebcamDiscoverySupport) (driver instanceof WebcamDiscoverySupport ? driver : null);
}
private static List<Webcam> toWebcams(List<WebcamDevice> devices) {
List<Webcam> webcams = new ArrayList<Webcam>();
for (WebcamDevice device : devices) {
webcams.add(new Webcam(device));
}
return webcams;
}
/**
* Get list of devices used by webcams.
*
* @return List of webcam devices
*/
private static List<WebcamDevice> getDevices(List<Webcam> webcams) {
List<WebcamDevice> devices = new ArrayList<WebcamDevice>();
for (Webcam webcam : webcams) {
devices.add(webcam.getDevice());
}
return devices;
}
public List<Webcam> getWebcams(long timeout, TimeUnit tunit) throws TimeoutException {
if (timeout < 0) {
throw new IllegalArgumentException("Timeout cannot be negative");
}
if (tunit == null) {
throw new IllegalArgumentException("Time unit cannot be null!");
}
List<Webcam> tmp = null;
synchronized (Webcam.class) {
if (webcams == null) {
WebcamsDiscovery discovery = new WebcamsDiscovery(driver);
ExecutorService executor = Executors.newSingleThreadExecutor(discovery);
Future<List<Webcam>> future = executor.submit(discovery);
executor.shutdown();
try {
executor.awaitTermination(timeout, tunit);
if (future.isDone()) {
webcams = future.get();
} else {
future.cancel(true);
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
} catch (ExecutionException e) {
throw new WebcamException(e);
}
if (webcams == null) {
throw new TimeoutException(String.format("Webcams discovery timeout (%d ms) has been exceeded", timeout));
}
tmp = new ArrayList<Webcam>(webcams);
if (Webcam.isHandleTermSignal()) {
WebcamDeallocator.store(webcams.toArray(new Webcam[webcams.size()]));
}
}
}
if (tmp != null) {
WebcamDiscoveryListener[] listeners = Webcam.getDiscoveryListeners();
for (Webcam webcam : tmp) {
notifyWebcamFound(webcam, listeners);
}
}
return Collections.unmodifiableList(webcams);
}
/**
* Scan for newly added or already removed webcams.
*/
public void scan() {
WebcamDiscoveryListener[] listeners = Webcam.getDiscoveryListeners();
List<WebcamDevice> tmpnew = driver.getDevices();
List<WebcamDevice> tmpold = null;
try {
tmpold = getDevices(getWebcams(Long.MAX_VALUE, TimeUnit.MILLISECONDS));
} catch (TimeoutException e) {
throw new WebcamException(e);
}
// convert to linked list due to O(1) on remove operation on
// iterator versus O(n) for the same operation in array list
List<WebcamDevice> oldones = new LinkedList<WebcamDevice>(tmpold);
List<WebcamDevice> newones = new LinkedList<WebcamDevice>(tmpnew);
Iterator<WebcamDevice> oi = oldones.iterator();
Iterator<WebcamDevice> ni = null;
WebcamDevice od = null; // old device
WebcamDevice nd = null; // new device
// reduce lists
while (oi.hasNext()) {
od = oi.next();
ni = newones.iterator();
while (ni.hasNext()) {
nd = ni.next();
// remove both elements, if device name is the same, which
// actually means that device is exactly the same
if (nd.getName().equals(od.getName())) {
ni.remove();
oi.remove();
break;
}
}
}
// if any left in old ones it means that devices has been removed
if (oldones.size() > 0) {
List<Webcam> notified = new ArrayList<Webcam>();
for (WebcamDevice device : oldones) {
for (Webcam webcam : webcams) {
if (webcam.getDevice().getName().equals(device.getName())) {
notified.add(webcam);
break;
}
}
}
setCurrentWebcams(tmpnew);
for (Webcam webcam : notified) {
notifyWebcamGone(webcam, listeners);
webcam.dispose();
}
}
// if any left in new ones it means that devices has been added
if (newones.size() > 0) {
setCurrentWebcams(tmpnew);
for (WebcamDevice device : newones) {
for (Webcam webcam : webcams) {
if (webcam.getDevice().getName().equals(device.getName())) {
notifyWebcamFound(webcam, listeners);
break;
}
}
}
}
}
@Override
public void run() {
// do not run if driver does not support discovery
if (support == null) {
return;
}
if (!support.isScanPossible()) {
return;
}
// wait initial time interval since devices has been initially
// discovered
Object monitor = new Object();
do {
synchronized (monitor) {
try {
monitor.wait(support.getScanInterval());
} catch (InterruptedException e) {
break;
} catch (Exception e) {
throw new RuntimeException("Problem waiting on monitor", e);
}
}
scan();
} while (running.get());
LOG.debug("Webcam discovery service loop has been stopped");
}
private void setCurrentWebcams(List<WebcamDevice> devices) {
webcams = toWebcams(devices);
if (Webcam.isHandleTermSignal()) {
WebcamDeallocator.unstore();
WebcamDeallocator.store(webcams.toArray(new Webcam[webcams.size()]));
}
}
private static void notifyWebcamGone(Webcam webcam, WebcamDiscoveryListener[] listeners) {
WebcamDiscoveryEvent event = new WebcamDiscoveryEvent(webcam, WebcamDiscoveryEvent.REMOVED);
for (WebcamDiscoveryListener l : listeners) {
try {
l.webcamGone(event);
} catch (Exception e) {
LOG.error(String.format("Webcam gone, exception when calling listener %s", l.getClass()), e);
}
}
}
private static void notifyWebcamFound(Webcam webcam, WebcamDiscoveryListener[] listeners) {
WebcamDiscoveryEvent event = new WebcamDiscoveryEvent(webcam, WebcamDiscoveryEvent.ADDED);
for (WebcamDiscoveryListener l : listeners) {
try {
l.webcamFound(event);
} catch (Exception e) {
LOG.error(String.format("Webcam found, exception when calling listener %s", l.getClass()), e);
}
}
}
/**
* Stop discovery service.
*/
public void stop() {
// return if not running
if (!running.compareAndSet(true, false)) {
return;
}
try {
runner.join();
} catch (InterruptedException e) {
throw new WebcamException("Joint interrupted");
}
LOG.debug("Discovery service has been stopped");
runner = null;
}
/**
* Start discovery service.
*/
public void start() {
// if configured to not start, then simply return
if (!enabled.get()) {
LOG.info("Discovery service has been disabled and thus it will not be started");
return;
}
// capture driver does not support discovery - nothing to do
if (support == null) {
LOG.info("Discovery will not run - driver {} does not support this feature", driver.getClass().getSimpleName());
return;
}
// return if already running
if (!running.compareAndSet(false, true)) {
return;
}
// start discovery service runner
runner = new Thread(this, "webcam-discovery-service");
runner.setUncaughtExceptionHandler(WebcamExceptionHandler.getInstance());
runner.setDaemon(true);
runner.start();
}
/**
* Is discovery service running?
*
* @return True or false
*/
public boolean isRunning() {
return running.get();
}
/**
* Webcam discovery service will be automatically started if it's enabled,
* otherwise, when set to disabled, it will never start, even when user try
* to run it.
*
* @param enabled the parameter controlling if discovery shall be started
*/
public void setEnabled(boolean enabled) {
this.enabled.set(enabled);
}
/**
* Cleanup.
*/
protected void shutdown() {
stop();
if (webcams == null)
return;
// dispose all webcams
Iterator<Webcam> wi = webcams.iterator();
while (wi.hasNext()) {
Webcam webcam = wi.next();
webcam.dispose();
}
synchronized (Webcam.class) {
// clear webcams list
webcams.clear();
// unassign webcams from deallocator
if (Webcam.isHandleTermSignal()) {
WebcamDeallocator.unstore();
}
}
}
}