diff --git a/vcamera/pom.xml b/vcamera/pom.xml
index b08235d..dec9477 100755
--- a/vcamera/pom.xml
+++ b/vcamera/pom.xml
@@ -4,7 +4,7 @@
org.vaadin.addons.parttio
vcamera
- 4.0.1-SNAPSHOT
+ 4.1.0-SNAPSHOT
Vcamera
Integration of app-media for Vaadin 14+
@@ -89,6 +89,12 @@
3.8.1
test
+
+ com.fasterxml.jackson.core
+ jackson-core
+ 2.18.2
+
+
diff --git a/vcamera/src/main/java/org/vaadin/vcamera/VCamera.java b/vcamera/src/main/java/org/vaadin/vcamera/VCamera.java
index f0d8ab9..55cb03f 100755
--- a/vcamera/src/main/java/org/vaadin/vcamera/VCamera.java
+++ b/vcamera/src/main/java/org/vaadin/vcamera/VCamera.java
@@ -1,23 +1,36 @@
package org.vaadin.vcamera;
import java.io.OutputStream;
+import java.util.List;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.ObjectMapper;
import com.vaadin.flow.component.Component;
import com.vaadin.flow.component.ComponentEventListener;
+import com.vaadin.flow.component.HasSize;
import com.vaadin.flow.component.Tag;
+import com.vaadin.flow.component.page.PendingJavaScriptResult;
import com.vaadin.flow.server.StreamReceiver;
import com.vaadin.flow.server.StreamVariable;
import com.vaadin.flow.shared.Registration;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.vaadin.vcamera.mediadevices.Device;
+import org.vaadin.vcamera.mediadevices.DevicesListedEvent;
/**
* A special video element that streams content from browser camera.
*
During streaming, users can record still or video clips of the stream, that browser send to the server. The data can be accessed using the DataReceiver interface, see {@link #setReceiver(DataReceiver)}.
*/
@Tag("video")
-public class VCamera extends Component {
+public class VCamera extends Component implements HasSize {
private boolean cameraOn;
private boolean recording;
+ private boolean flashOn;
+
+ private static final Logger LOG = LoggerFactory.getLogger(VCamera.class);
public VCamera() {
getElement().setProperty("volume", 0);
@@ -49,7 +62,7 @@ public void startRecording() {
}).then(response => console.log(response));
}
this.recorder.start();
- """);
+ """);
}
public void stopRecording() {
@@ -110,9 +123,99 @@ public void openCamera(String optionsJson) {
});
}
}
- """.formatted(optionsJson));
+ """.formatted(optionsJson));
+ }
+
+ public boolean isFlashOn() {
+ return flashOn;
+ }
+
+ public void toggleFlashlight() {
+ flashOn = !flashOn;
+ getElement().executeJs("""
+ if(this.stream != null) {
+ const track = this.stream.getVideoTracks()[0];
+
+ //Create image capture object and get camera capabilities
+ const imageCapture = new ImageCapture(track)
+ const photoCapabilities = imageCapture.getPhotoCapabilities().then(() => {
+ track.applyConstraints({
+ advanced: [{torch: %s}]
+ });
+ });
+ }
+ """.formatted(flashOn));
}
+ /**
+ * Opens a specific videoinput device.
+ * @param device device to open
+ * @throws IllegalArgumentException if device is not of kind "videoinput"
+ */
+ public void openCamera(Device device) {
+ String sourceId = device.getDeviceId();
+ if(device.getKind() != Device.DeviceKind.videoinput) {
+ throw new IllegalArgumentException("Device "+device+" is not a video device");
+ }
+ openCamera("""
+ { video: {
+ optional: [{sourceId: "%s"}]
+ }}
+ """.formatted(sourceId));
+ }
+
+ /**
+ * Reads available devices on the users browser
+ * @param listener is called with a list of devices after Javascript execution completes
+ */
+ public void listDevices(ComponentEventListener listener) {
+ PendingJavaScriptResult pendingJavaScriptResult = getElement().executeJs("""
+ if (!navigator.mediaDevices || !navigator.mediaDevices.enumerateDevices) {
+ console.log("enumerateDevices() not supported.");
+ return "";
+ }
+
+ // List cameras and microphones
+ return new Promise(function(myResolve, myReject){
+ navigator.mediaDevices.enumerateDevices()
+ .then(function(devices) {
+ devicesString = JSON.stringify(devices);
+ myResolve(devicesString); // when successful
+ myReject(""); // when error
+ } )
+ });
+ """);
+
+ pendingJavaScriptResult.then(String.class, (s) -> {
+ ObjectMapper objectMapper = new ObjectMapper();
+ try {
+ List devices = objectMapper.readValue(s, new TypeReference<>() {
+ });
+ DevicesListedEvent event = new DevicesListedEvent(this, false, devices);
+ listener.onComponentEvent(event);
+ } catch (JsonProcessingException e) {
+ LOG.error("Could not read devices");
+ listener.onComponentEvent(new DevicesListedEvent(this, false, List.of()));
+ }
+ });
+ }
+
+ /**
+ * Tries to open the "normal", back-facing camera on a mobile phone. While it is possible to set a constraint facingMode = environment,
+ * it is up to the device, which camera to use. E.g. there could be a fishEye camera which is then used, which is rarely wanted.
+ * A reddit post mentions, that the most reliable way
+ * was to just use the last enumerated device as returned by {@link #listDevices(ComponentEventListener)}
+ */
+ public void openNormalEnvironmentCamera() {
+ listDevices(e -> {
+ List videoDevices = e.getDevices().stream()
+ .filter(device -> device.getKind() == Device.DeviceKind.videoinput)
+ .toList();
+ openCamera(videoDevices.get(videoDevices.size() - 1));
+ });
+ }
+
+
public boolean isCameraOpen() {
return cameraOn;
}
diff --git a/vcamera/src/main/java/org/vaadin/vcamera/mediadevices/Device.java b/vcamera/src/main/java/org/vaadin/vcamera/mediadevices/Device.java
new file mode 100644
index 0000000..6ff31a1
--- /dev/null
+++ b/vcamera/src/main/java/org/vaadin/vcamera/mediadevices/Device.java
@@ -0,0 +1,56 @@
+package org.vaadin.vcamera.mediadevices;
+
+/**
+ * A device as returned by Javascripts MediaDevices: enumerateDevices
+ */
+public class Device {
+
+ public enum DeviceKind {
+ videoinput, audioinput, audiooutput
+ }
+
+ private String deviceId, label, groupId;
+ private DeviceKind kind;
+
+ public DeviceKind getKind() {
+ return kind;
+ }
+
+ public void setKind(DeviceKind kind) {
+ this.kind = kind;
+ }
+
+ public String getDeviceId() {
+ return deviceId;
+ }
+
+ public void setDeviceId(String deviceId) {
+ this.deviceId = deviceId;
+ }
+
+ public String getLabel() {
+ return label;
+ }
+
+ public void setLabel(String label) {
+ this.label = label;
+ }
+
+ public String getGroupId() {
+ return groupId;
+ }
+
+ public void setGroupId(String groupId) {
+ this.groupId = groupId;
+ }
+
+ @Override
+ public String toString() {
+ return "Device{" +
+ "deviceId='" + deviceId + '\'' +
+ ", label='" + label + '\'' +
+ ", groupId='" + groupId + '\'' +
+ ", kind=" + kind +
+ '}';
+ }
+}
diff --git a/vcamera/src/main/java/org/vaadin/vcamera/mediadevices/DevicesListedEvent.java b/vcamera/src/main/java/org/vaadin/vcamera/mediadevices/DevicesListedEvent.java
new file mode 100644
index 0000000..47ba79a
--- /dev/null
+++ b/vcamera/src/main/java/org/vaadin/vcamera/mediadevices/DevicesListedEvent.java
@@ -0,0 +1,20 @@
+package org.vaadin.vcamera.mediadevices;
+
+import com.vaadin.flow.component.ComponentEvent;
+import org.vaadin.vcamera.VCamera;
+
+import java.util.List;
+
+public class DevicesListedEvent extends ComponentEvent {
+
+ private final List devices;
+
+ public DevicesListedEvent(VCamera source, boolean fromClient, List devices) {
+ super(source, fromClient);
+ this.devices = devices;
+ }
+
+ public List getDevices() {
+ return devices;
+ }
+}