This commit is contained in:
rattatwinko
2025-05-27 16:34:09 +02:00
commit cceb0d44e8
21 changed files with 811 additions and 0 deletions

3
.vscode/settings.json vendored Normal file
View File

@@ -0,0 +1,3 @@
{
"java.configuration.updateBuildConfiguration": "interactive"
}

65
README.md Normal file
View File

@@ -0,0 +1,65 @@
# Java Security Camera App
A Java desktop application for viewing live streams from both USB webcams and network MJPEG cameras.
## Features
- View live streams from USB webcams
- Connect to network MJPEG cameras (e.g., DroidCamX)
- Take snapshots of the current view
- Simple and intuitive user interface
- Support for multiple camera sources
## Requirements
- Java 11 or higher
- Maven
- USB webcam (for local camera support)
- Network camera with MJPEG stream support (for network camera support)
## Building the Application
1. Clone the repository
2. Navigate to the project directory
3. Build with Maven:
```bash
mvn clean package
```
## Running the Application
After building, run the application using:
```bash
java -jar target/security-camera-app-1.0-SNAPSHOT-jar-with-dependencies.jar
```
## Usage
1. Launch the application
2. Select the camera source from the dropdown:
- USB Camera: Uses your computer's webcam
- Network Camera: Connects to an MJPEG stream URL
3. Click "Start" to begin streaming
4. Use the "Snapshot" button to capture the current frame
5. Click "Stop" to end the stream
### Using with Network Cameras
For network cameras, you'll need to provide the MJPEG stream URL. Common formats include:
- DroidCamX: `http://[IP_ADDRESS]:4747/video`
- Generic IP Camera: `http://[IP_ADDRESS]/video` or `http://[IP_ADDRESS]/mjpeg`
## Troubleshooting
1. No webcam detected:
- Ensure your webcam is properly connected
- Check if other applications are using the webcam
2. Network camera not connecting:
- Verify the camera URL is correct
- Ensure the camera is on the same network
- Check if the camera supports MJPEG streaming
## License
This project is open source and available under the MIT License.

83
pom.xml Normal file
View File

@@ -0,0 +1,83 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.jsca</groupId>
<artifactId>security-camera-app</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<!-- Webcam Capture for USB cameras -->
<dependency>
<groupId>com.github.sarxos</groupId>
<artifactId>webcam-capture</artifactId>
<version>0.3.12</version>
</dependency>
<!-- SLF4J API -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.32</version>
</dependency>
<!-- SLF4J Simple binding -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>1.7.32</version>
</dependency>
<!-- Gson for JSON handling -->
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.10.1</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>11</source>
<target>11</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<version>3.3.0</version>
<configuration>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
<archive>
<manifest>
<mainClass>com.jsca.Main</mainClass>
</manifest>
</archive>
</configuration>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

View File

@@ -0,0 +1,142 @@
package com.jsca;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.reflect.TypeToken;
import javax.swing.*;
import java.io.*;
import java.util.*;
public class CameraManager {
private final Map<Integer, CameraPanel> cameras = new HashMap<>();
private final JPanel gridPanel;
private static final int MAX_CAMERAS = 4;
private static final String CONFIG_FILE = "camera_config.json";
private final Gson gson;
public CameraManager(JPanel gridPanel) {
this.gridPanel = gridPanel;
this.gson = new GsonBuilder().setPrettyPrinting().create();
// Initialize empty panels
for (int i = 0; i < MAX_CAMERAS; i++) {
CameraPanel emptyPanel = new CameraPanel("Empty " + (i + 1));
cameras.put(i, emptyPanel);
gridPanel.add(emptyPanel);
}
}
public boolean addCamera(String name, StreamReader streamReader, int position) {
if (position < 0 || position >= MAX_CAMERAS) {
return false;
}
CameraPanel oldPanel = cameras.get(position);
if (oldPanel != null) {
oldPanel.stopStream();
}
CameraPanel newPanel = new CameraPanel(name);
newPanel.startStream(streamReader);
cameras.put(position, newPanel);
gridPanel.remove(position);
gridPanel.add(newPanel, position);
gridPanel.revalidate();
gridPanel.repaint();
return true;
}
public void removeCamera(int position) {
if (position >= 0 && position < MAX_CAMERAS) {
CameraPanel panel = cameras.get(position);
if (panel != null) {
panel.stopStream();
CameraPanel emptyPanel = new CameraPanel("Empty " + (position + 1));
cameras.put(position, emptyPanel);
gridPanel.remove(position);
gridPanel.add(emptyPanel, position);
gridPanel.revalidate();
gridPanel.repaint();
}
}
}
public void stopAllCameras() {
for (CameraPanel panel : cameras.values()) {
if (panel != null) {
panel.stopStream();
}
}
}
public CameraPanel getCameraPanel(int position) {
return cameras.get(position);
}
public void restartCamera(int position) {
CameraPanel panel = cameras.get(position);
if (panel != null && panel.isStreaming()) {
StreamReader currentStream = panel.getCurrentStream();
if (currentStream != null) {
panel.stopStream();
panel.startStream(currentStream);
}
}
}
public void saveConfiguration() {
List<CameraConfig> configs = new ArrayList<>();
for (Map.Entry<Integer, CameraPanel> entry : cameras.entrySet()) {
CameraPanel panel = entry.getValue();
if (panel.isStreaming()) {
CameraConfig config = new CameraConfig();
config.position = entry.getKey();
config.name = panel.getCameraName();
// Add more configuration details as needed
configs.add(config);
}
}
try (Writer writer = new FileWriter(CONFIG_FILE)) {
gson.toJson(configs, writer);
} catch (IOException e) {
JOptionPane.showMessageDialog(null,
"Error saving configuration: " + e.getMessage(),
"Save Error",
JOptionPane.ERROR_MESSAGE);
}
}
public void loadConfiguration() {
try (Reader reader = new FileReader(CONFIG_FILE)) {
List<CameraConfig> configs = gson.fromJson(reader,
new TypeToken<List<CameraConfig>>(){}.getType());
if (configs != null) {
stopAllCameras();
for (CameraConfig config : configs) {
// Implement camera restoration based on config
// This is a placeholder for the actual implementation
// You'll need to create appropriate StreamReader instances
}
}
} catch (IOException e) {
JOptionPane.showMessageDialog(null,
"Error loading configuration: " + e.getMessage(),
"Load Error",
JOptionPane.ERROR_MESSAGE);
}
}
private static class CameraConfig {
int position;
String name;
String type; // "network" or "local"
String url; // for network cameras
String username;
String password;
int deviceIndex; // for local cameras
}
}

View File

@@ -0,0 +1,99 @@
package com.jsca;
import javax.swing.*;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.File;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import javax.imageio.ImageIO;
public class CameraPanel extends JPanel {
private final JLabel videoLabel;
private final String cameraName;
private volatile boolean isStreaming = false;
private StreamReader currentStream;
public CameraPanel(String cameraName) {
this.cameraName = cameraName;
setLayout(new BorderLayout());
// Create video display panel
videoLabel = new JLabel();
videoLabel.setPreferredSize(new Dimension(640, 480));
videoLabel.setBorder(BorderFactory.createLineBorder(Color.BLACK));
add(videoLabel, BorderLayout.CENTER);
// Create control panel
JPanel controlPanel = new JPanel();
controlPanel.setLayout(new FlowLayout());
JButton snapshotButton = new JButton("Snapshot");
snapshotButton.addActionListener(e -> takeSnapshot());
controlPanel.add(snapshotButton);
add(controlPanel, BorderLayout.SOUTH);
}
public void startStream(StreamReader streamReader) {
stopStream(); // Stop any existing stream
currentStream = streamReader;
new Thread(currentStream).start();
isStreaming = true;
}
public void stopStream() {
if (currentStream != null) {
currentStream.stop();
currentStream = null;
}
isStreaming = false;
videoLabel.setIcon(null);
}
private void takeSnapshot() {
if (videoLabel.getIcon() == null) return;
try {
BufferedImage image = new BufferedImage(
videoLabel.getWidth(),
videoLabel.getHeight(),
BufferedImage.TYPE_INT_RGB
);
Graphics g = image.getGraphics();
videoLabel.paint(g);
g.dispose();
String timestamp = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMdd_HHmmss"));
File outputFile = new File(String.format("snapshot_%s_%s.jpg", cameraName.replaceAll("[^a-zA-Z0-9]", "_"), timestamp));
ImageIO.write(image, "jpg", outputFile);
JOptionPane.showMessageDialog(this,
"Snapshot saved: " + outputFile.getName(),
"Snapshot Saved",
JOptionPane.INFORMATION_MESSAGE);
} catch (Exception e) {
JOptionPane.showMessageDialog(this,
"Error saving snapshot: " + e.getMessage(),
"Error",
JOptionPane.ERROR_MESSAGE);
}
}
public void updateFrame(Image frame) {
if (frame != null) {
videoLabel.setIcon(new ImageIcon(frame));
}
}
public String getCameraName() {
return cameraName;
}
public boolean isStreaming() {
return isStreaming;
}
public StreamReader getCurrentStream() {
return currentStream;
}
}

View File

@@ -0,0 +1,240 @@
package com.jsca;
import com.github.sarxos.webcam.Webcam;
import javax.swing.*;
import java.awt.*;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
public class CameraViewer extends JFrame {
private final JPanel gridPanel;
private final CameraManager cameraManager;
private final JLabel statusLabel;
public CameraViewer() {
setTitle("Security Camera Viewer");
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setPreferredSize(new Dimension(1280, 960));
// Create grid panel for cameras
gridPanel = new JPanel(new GridLayout(2, 2, 5, 5));
gridPanel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
// Create status bar
statusLabel = new JLabel("Ready");
statusLabel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
// Initialize camera manager
cameraManager = new CameraManager(gridPanel);
// Create menu bar
setJMenuBar(createMenuBar());
// Add components to frame
add(gridPanel, BorderLayout.CENTER);
add(statusLabel, BorderLayout.SOUTH);
// Add window listener for cleanup
addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent e) {
cameraManager.stopAllCameras();
}
});
pack();
setLocationRelativeTo(null);
}
private JMenuBar createMenuBar() {
JMenuBar menuBar = new JMenuBar();
// File Menu
JMenu fileMenu = new JMenu("File");
JMenuItem loadSetup = new JMenuItem("Load Camera Setup");
JMenuItem saveSetup = new JMenuItem("Save Camera Setup");
JMenuItem exit = new JMenuItem("Exit");
loadSetup.addActionListener(e -> cameraManager.loadConfiguration());
saveSetup.addActionListener(e -> cameraManager.saveConfiguration());
exit.addActionListener(e -> {
cameraManager.stopAllCameras();
dispose();
});
fileMenu.add(loadSetup);
fileMenu.add(saveSetup);
fileMenu.addSeparator();
fileMenu.add(exit);
// Camera Menu
JMenu cameraMenu = new JMenu("Camera");
JMenuItem addNetwork = new JMenuItem("Add Network Camera");
JMenuItem addLocal = new JMenuItem("Add Local Camera");
JMenuItem removeCamera = new JMenuItem("Remove Camera");
JMenuItem restartCamera = new JMenuItem("Restart Camera");
addNetwork.addActionListener(e -> addNetworkCamera());
addLocal.addActionListener(e -> addLocalCamera());
removeCamera.addActionListener(e -> removeSelectedCamera());
restartCamera.addActionListener(e -> restartSelectedCamera());
cameraMenu.add(addNetwork);
cameraMenu.add(addLocal);
cameraMenu.addSeparator();
cameraMenu.add(removeCamera);
cameraMenu.add(restartCamera);
// Help Menu
JMenu helpMenu = new JMenu("Help");
JMenuItem about = new JMenuItem("About");
JMenuItem instructions = new JMenuItem("Instructions");
about.addActionListener(e -> showAboutDialog());
instructions.addActionListener(e -> showInstructions());
helpMenu.add(about);
helpMenu.add(instructions);
menuBar.add(fileMenu);
menuBar.add(cameraMenu);
menuBar.add(helpMenu);
return menuBar;
}
private void addNetworkCamera() {
JPanel panel = new JPanel(new GridLayout(0, 2, 5, 5));
JTextField urlField = new JTextField();
JTextField usernameField = new JTextField();
JPasswordField passwordField = new JPasswordField();
SpinnerNumberModel positionModel = new SpinnerNumberModel(0, 0, 3, 1);
JSpinner positionSpinner = new JSpinner(positionModel);
panel.add(new JLabel("URL:"));
panel.add(urlField);
panel.add(new JLabel("Username:"));
panel.add(usernameField);
panel.add(new JLabel("Password:"));
panel.add(passwordField);
panel.add(new JLabel("Position (0-3):"));
panel.add(positionSpinner);
int result = JOptionPane.showConfirmDialog(this, panel,
"Add Network Camera", JOptionPane.OK_CANCEL_OPTION);
if (result == JOptionPane.OK_OPTION) {
String url = urlField.getText();
int position = (Integer) positionSpinner.getValue();
if (!url.trim().isEmpty()) {
String name = "Network Camera " + (position + 1);
NetworkStreamReader reader = new NetworkStreamReader(
cameraManager.getCameraPanel(position),
url
);
cameraManager.addCamera(name, reader, position);
updateStatus("Added network camera at position " + position);
}
}
}
private void addLocalCamera() {
java.util.List<Webcam> webcams = Webcam.getWebcams();
if (webcams.isEmpty()) {
JOptionPane.showMessageDialog(this,
"No webcams detected",
"Error",
JOptionPane.ERROR_MESSAGE);
return;
}
JPanel panel = new JPanel(new GridLayout(0, 2, 5, 5));
String[] options = webcams.stream()
.map(Webcam::getName)
.toArray(String[]::new);
JComboBox<String> webcamBox = new JComboBox<>(options);
SpinnerNumberModel positionModel = new SpinnerNumberModel(0, 0, 3, 1);
JSpinner positionSpinner = new JSpinner(positionModel);
panel.add(new JLabel("Select Webcam:"));
panel.add(webcamBox);
panel.add(new JLabel("Position (0-3):"));
panel.add(positionSpinner);
int result = JOptionPane.showConfirmDialog(this, panel,
"Add Local Camera", JOptionPane.OK_CANCEL_OPTION);
if (result == JOptionPane.OK_OPTION) {
String selected = (String) webcamBox.getSelectedItem();
int position = (Integer) positionSpinner.getValue();
if (selected != null) {
String name = "Local Camera " + (position + 1) + " (" + selected + ")";
WebcamStreamReader reader = new WebcamStreamReader(
cameraManager.getCameraPanel(position)
);
cameraManager.addCamera(name, reader, position);
updateStatus("Added local camera at position " + position);
}
}
}
private void removeSelectedCamera() {
SpinnerNumberModel positionModel = new SpinnerNumberModel(0, 0, 3, 1);
JSpinner positionSpinner = new JSpinner(positionModel);
int result = JOptionPane.showConfirmDialog(this,
positionSpinner,
"Select Camera Position to Remove (0-3)",
JOptionPane.OK_CANCEL_OPTION);
if (result == JOptionPane.OK_OPTION) {
int position = (Integer) positionSpinner.getValue();
cameraManager.removeCamera(position);
updateStatus("Removed camera at position " + position);
}
}
private void restartSelectedCamera() {
SpinnerNumberModel positionModel = new SpinnerNumberModel(0, 0, 3, 1);
JSpinner positionSpinner = new JSpinner(positionModel);
int result = JOptionPane.showConfirmDialog(this,
positionSpinner,
"Select Camera Position to Restart (0-3)",
JOptionPane.OK_CANCEL_OPTION);
if (result == JOptionPane.OK_OPTION) {
int position = (Integer) positionSpinner.getValue();
cameraManager.restartCamera(position);
updateStatus("Restarted camera at position " + position);
}
}
private void showAboutDialog() {
JOptionPane.showMessageDialog(this,
"Security Camera Viewer\nVersion 1.0\n\n" +
"A multi-camera security monitoring application\n" +
"Supports both network and local USB cameras",
"About",
JOptionPane.INFORMATION_MESSAGE);
}
private void showInstructions() {
JOptionPane.showMessageDialog(this,
"Instructions:\n\n" +
"1. Use the Camera menu to add network or local cameras\n" +
"2. Select a position (0-3) for each camera\n" +
"3. Use the File menu to save/load your camera setup\n" +
"4. Each camera panel has its own snapshot button\n\n" +
"Network Camera URLs should be in MJPEG format\n" +
"Example: http://camera-ip:port/video",
"Instructions",
JOptionPane.INFORMATION_MESSAGE);
}
private void updateStatus(String message) {
statusLabel.setText(message);
}
}

View File

@@ -0,0 +1,12 @@
package com.jsca;
import javax.swing.SwingUtilities;
public class Main {
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
CameraViewer viewer = new CameraViewer();
viewer.setVisible(true);
});
}
}

View File

@@ -0,0 +1,109 @@
package com.jsca;
import javax.imageio.ImageIO;
import javax.swing.SwingUtilities;
import java.awt.Image;
import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Arrays;
public class NetworkStreamReader implements StreamReader {
private final CameraPanel panel;
private final String streamUrl;
private volatile boolean running = true;
private HttpURLConnection connection;
public NetworkStreamReader(CameraPanel panel, String streamUrl) {
this.panel = panel;
this.streamUrl = streamUrl;
}
@Override
public void run() {
try {
URL url = new URL(streamUrl);
connection = (HttpURLConnection) url.openConnection();
connection.setConnectTimeout(5000);
connection.setReadTimeout(5000);
try (BufferedInputStream in = new BufferedInputStream(connection.getInputStream())) {
byte[] buffer = new byte[8192];
int bytesRead;
byte[] imageBuffer = new byte[1024 * 1024]; // 1MB buffer for JPEG
int imagePos = 0;
boolean foundHeader = false;
while (running) {
bytesRead = in.read(buffer);
if (bytesRead == -1) break;
for (int i = 0; i < bytesRead; i++) {
// Look for JPEG header (0xFF, 0xD8)
if (!foundHeader && i < bytesRead - 1) {
if (buffer[i] == (byte) 0xFF && buffer[i + 1] == (byte) 0xD8) {
imagePos = 0;
foundHeader = true;
}
}
if (foundHeader) {
imageBuffer[imagePos++] = buffer[i];
// Look for JPEG footer (0xFF, 0xD9)
if (imagePos > 1 &&
imageBuffer[imagePos - 2] == (byte) 0xFF &&
imageBuffer[imagePos - 1] == (byte) 0xD9) {
// We have a complete JPEG image
byte[] imageData = Arrays.copyOf(imageBuffer, imagePos);
processImage(imageData);
foundHeader = false;
}
if (imagePos >= imageBuffer.length) {
// Buffer overflow, reset
foundHeader = false;
}
}
}
}
}
} catch (Exception e) {
if (running) {
SwingUtilities.invokeLater(() -> {
javax.swing.JOptionPane.showMessageDialog(panel,
"Error reading from network camera: " + e.getMessage(),
"Network Camera Error",
javax.swing.JOptionPane.ERROR_MESSAGE);
});
}
} finally {
if (connection != null) {
connection.disconnect();
}
}
}
private void processImage(byte[] imageData) {
try {
Image image = ImageIO.read(new ByteArrayInputStream(imageData));
if (image != null) {
SwingUtilities.invokeLater(() -> panel.updateFrame(image));
}
Thread.sleep(33); // ~30 FPS
} catch (IOException | InterruptedException e) {
// Ignore individual frame errors
}
}
@Override
public void stop() {
running = false;
if (connection != null) {
connection.disconnect();
}
}
}

View File

@@ -0,0 +1,5 @@
package com.jsca;
public interface StreamReader extends Runnable {
void stop();
}

View File

@@ -0,0 +1,53 @@
package com.jsca;
import com.github.sarxos.webcam.Webcam;
import java.awt.Dimension;
import java.awt.Image;
import javax.swing.SwingUtilities;
public class WebcamStreamReader implements StreamReader {
private final CameraPanel panel;
private volatile boolean running = true;
private Webcam webcam;
public WebcamStreamReader(CameraPanel panel) {
this.panel = panel;
}
@Override
public void run() {
try {
webcam = Webcam.getDefault();
if (webcam == null) {
throw new RuntimeException("No webcam detected");
}
webcam.setViewSize(new Dimension(640, 480));
webcam.open();
while (running && webcam.isOpen()) {
final Image image = webcam.getImage();
if (image != null) {
SwingUtilities.invokeLater(() -> panel.updateFrame(image));
}
Thread.sleep(33); // ~30 FPS
}
} catch (Exception e) {
SwingUtilities.invokeLater(() -> {
javax.swing.JOptionPane.showMessageDialog(panel,
"Error reading from webcam: " + e.getMessage(),
"Webcam Error",
javax.swing.JOptionPane.ERROR_MESSAGE);
});
} finally {
if (webcam != null && webcam.isOpen()) {
webcam.close();
}
}
}
@Override
public void stop() {
running = false;
}
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.