initial
This commit is contained in:
3
.vscode/settings.json
vendored
Normal file
3
.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"java.configuration.updateBuildConfiguration": "interactive"
|
||||||
|
}
|
||||||
65
README.md
Normal file
65
README.md
Normal 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
83
pom.xml
Normal 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>
|
||||||
BIN
snapshot_20250527_161519.jpg
Normal file
BIN
snapshot_20250527_161519.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 30 KiB |
142
src/main/java/com/jsca/CameraManager.java
Normal file
142
src/main/java/com/jsca/CameraManager.java
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
99
src/main/java/com/jsca/CameraPanel.java
Normal file
99
src/main/java/com/jsca/CameraPanel.java
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
240
src/main/java/com/jsca/CameraViewer.java
Normal file
240
src/main/java/com/jsca/CameraViewer.java
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
12
src/main/java/com/jsca/Main.java
Normal file
12
src/main/java/com/jsca/Main.java
Normal 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);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
109
src/main/java/com/jsca/NetworkStreamReader.java
Normal file
109
src/main/java/com/jsca/NetworkStreamReader.java
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
5
src/main/java/com/jsca/StreamReader.java
Normal file
5
src/main/java/com/jsca/StreamReader.java
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
package com.jsca;
|
||||||
|
|
||||||
|
public interface StreamReader extends Runnable {
|
||||||
|
void stop();
|
||||||
|
}
|
||||||
53
src/main/java/com/jsca/WebcamStreamReader.java
Normal file
53
src/main/java/com/jsca/WebcamStreamReader.java
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
target/classes/com/jsca/CameraManager$1.class
Normal file
BIN
target/classes/com/jsca/CameraManager$1.class
Normal file
Binary file not shown.
BIN
target/classes/com/jsca/CameraManager$CameraConfig.class
Normal file
BIN
target/classes/com/jsca/CameraManager$CameraConfig.class
Normal file
Binary file not shown.
BIN
target/classes/com/jsca/CameraManager.class
Normal file
BIN
target/classes/com/jsca/CameraManager.class
Normal file
Binary file not shown.
BIN
target/classes/com/jsca/CameraPanel.class
Normal file
BIN
target/classes/com/jsca/CameraPanel.class
Normal file
Binary file not shown.
BIN
target/classes/com/jsca/CameraViewer$1.class
Normal file
BIN
target/classes/com/jsca/CameraViewer$1.class
Normal file
Binary file not shown.
BIN
target/classes/com/jsca/CameraViewer.class
Normal file
BIN
target/classes/com/jsca/CameraViewer.class
Normal file
Binary file not shown.
BIN
target/classes/com/jsca/Main.class
Normal file
BIN
target/classes/com/jsca/Main.class
Normal file
Binary file not shown.
BIN
target/classes/com/jsca/NetworkStreamReader.class
Normal file
BIN
target/classes/com/jsca/NetworkStreamReader.class
Normal file
Binary file not shown.
BIN
target/classes/com/jsca/StreamReader.class
Normal file
BIN
target/classes/com/jsca/StreamReader.class
Normal file
Binary file not shown.
BIN
target/classes/com/jsca/WebcamStreamReader.class
Normal file
BIN
target/classes/com/jsca/WebcamStreamReader.class
Normal file
Binary file not shown.
Reference in New Issue
Block a user