From cceb0d44e8b918530d6b06e1ff5ccd178e422231 Mon Sep 17 00:00:00 2001 From: rattatwinko Date: Tue, 27 May 2025 16:34:09 +0200 Subject: [PATCH] initial --- .vscode/settings.json | 3 + README.md | 65 +++++ pom.xml | 83 ++++++ snapshot_20250527_161519.jpg | Bin 0 -> 31023 bytes src/main/java/com/jsca/CameraManager.java | 142 +++++++++++ src/main/java/com/jsca/CameraPanel.java | 99 ++++++++ src/main/java/com/jsca/CameraViewer.java | 240 ++++++++++++++++++ src/main/java/com/jsca/Main.java | 12 + .../java/com/jsca/NetworkStreamReader.java | 109 ++++++++ src/main/java/com/jsca/StreamReader.java | 5 + .../java/com/jsca/WebcamStreamReader.java | 53 ++++ target/classes/com/jsca/CameraManager$1.class | Bin 0 -> 702 bytes .../com/jsca/CameraManager$CameraConfig.class | Bin 0 -> 549 bytes target/classes/com/jsca/CameraManager.class | Bin 0 -> 6131 bytes target/classes/com/jsca/CameraPanel.class | Bin 0 -> 5242 bytes target/classes/com/jsca/CameraViewer$1.class | Bin 0 -> 761 bytes target/classes/com/jsca/CameraViewer.class | Bin 0 -> 10580 bytes target/classes/com/jsca/Main.class | Bin 0 -> 1145 bytes .../com/jsca/NetworkStreamReader.class | Bin 0 -> 4443 bytes target/classes/com/jsca/StreamReader.class | Bin 0 -> 161 bytes .../classes/com/jsca/WebcamStreamReader.class | Bin 0 -> 3152 bytes 21 files changed, 811 insertions(+) create mode 100644 .vscode/settings.json create mode 100644 README.md create mode 100644 pom.xml create mode 100644 snapshot_20250527_161519.jpg create mode 100644 src/main/java/com/jsca/CameraManager.java create mode 100644 src/main/java/com/jsca/CameraPanel.java create mode 100644 src/main/java/com/jsca/CameraViewer.java create mode 100644 src/main/java/com/jsca/Main.java create mode 100644 src/main/java/com/jsca/NetworkStreamReader.java create mode 100644 src/main/java/com/jsca/StreamReader.java create mode 100644 src/main/java/com/jsca/WebcamStreamReader.java create mode 100644 target/classes/com/jsca/CameraManager$1.class create mode 100644 target/classes/com/jsca/CameraManager$CameraConfig.class create mode 100644 target/classes/com/jsca/CameraManager.class create mode 100644 target/classes/com/jsca/CameraPanel.class create mode 100644 target/classes/com/jsca/CameraViewer$1.class create mode 100644 target/classes/com/jsca/CameraViewer.class create mode 100644 target/classes/com/jsca/Main.class create mode 100644 target/classes/com/jsca/NetworkStreamReader.class create mode 100644 target/classes/com/jsca/StreamReader.class create mode 100644 target/classes/com/jsca/WebcamStreamReader.class diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..c5f3f6b --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "java.configuration.updateBuildConfiguration": "interactive" +} \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..9a812a8 --- /dev/null +++ b/README.md @@ -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. \ No newline at end of file diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..2ed84c0 --- /dev/null +++ b/pom.xml @@ -0,0 +1,83 @@ + + + 4.0.0 + + com.jsca + security-camera-app + 1.0-SNAPSHOT + + + 11 + 11 + UTF-8 + + + + + + com.github.sarxos + webcam-capture + 0.3.12 + + + + + org.slf4j + slf4j-api + 1.7.32 + + + + + org.slf4j + slf4j-simple + 1.7.32 + + + + + com.google.code.gson + gson + 2.10.1 + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.8.1 + + 11 + 11 + + + + org.apache.maven.plugins + maven-assembly-plugin + 3.3.0 + + + jar-with-dependencies + + + + com.jsca.Main + + + + + + package + + single + + + + + + + \ No newline at end of file diff --git a/snapshot_20250527_161519.jpg b/snapshot_20250527_161519.jpg new file mode 100644 index 0000000000000000000000000000000000000000..71869d76119aec02a9bda5b26c6206de58385003 GIT binary patch literal 31023 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 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 configs = new ArrayList<>(); + for (Map.Entry 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 configs = gson.fromJson(reader, + new TypeToken>(){}.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 + } +} \ No newline at end of file diff --git a/src/main/java/com/jsca/CameraPanel.java b/src/main/java/com/jsca/CameraPanel.java new file mode 100644 index 0000000..bde55a9 --- /dev/null +++ b/src/main/java/com/jsca/CameraPanel.java @@ -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; + } +} \ No newline at end of file diff --git a/src/main/java/com/jsca/CameraViewer.java b/src/main/java/com/jsca/CameraViewer.java new file mode 100644 index 0000000..0be9ecd --- /dev/null +++ b/src/main/java/com/jsca/CameraViewer.java @@ -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 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 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); + } +} \ No newline at end of file diff --git a/src/main/java/com/jsca/Main.java b/src/main/java/com/jsca/Main.java new file mode 100644 index 0000000..284aac2 --- /dev/null +++ b/src/main/java/com/jsca/Main.java @@ -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); + }); + } +} \ No newline at end of file diff --git a/src/main/java/com/jsca/NetworkStreamReader.java b/src/main/java/com/jsca/NetworkStreamReader.java new file mode 100644 index 0000000..6324b6a --- /dev/null +++ b/src/main/java/com/jsca/NetworkStreamReader.java @@ -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(); + } + } +} \ No newline at end of file diff --git a/src/main/java/com/jsca/StreamReader.java b/src/main/java/com/jsca/StreamReader.java new file mode 100644 index 0000000..8e5c0ac --- /dev/null +++ b/src/main/java/com/jsca/StreamReader.java @@ -0,0 +1,5 @@ +package com.jsca; + +public interface StreamReader extends Runnable { + void stop(); +} \ No newline at end of file diff --git a/src/main/java/com/jsca/WebcamStreamReader.java b/src/main/java/com/jsca/WebcamStreamReader.java new file mode 100644 index 0000000..f840e2e --- /dev/null +++ b/src/main/java/com/jsca/WebcamStreamReader.java @@ -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; + } +} \ No newline at end of file diff --git a/target/classes/com/jsca/CameraManager$1.class b/target/classes/com/jsca/CameraManager$1.class new file mode 100644 index 0000000000000000000000000000000000000000..5d1025a498a0814b9111bbfd57cf4b30d6f7958e GIT binary patch literal 702 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 literal 0 HcmV?d00001 diff --git a/target/classes/com/jsca/CameraViewer$1.class b/target/classes/com/jsca/CameraViewer$1.class new file mode 100644 index 0000000000000000000000000000000000000000..f086b80f6631b45f23f9d264395bdc5f73c819af GIT binary patch literal 761 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 Y->S&bgMa)ff5!jBf%Gr@kgT)(Kk>cE@Bjb+ literal 0 HcmV?d00001 diff --git a/target/classes/com/jsca/Main.class b/target/classes/com/jsca/Main.class new file mode 100644 index 0000000000000000000000000000000000000000..c32667ea3e8f1687511e7d01a8c5f056006f57c0 GIT binary patch literal 1145 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 literal 0 HcmV?d00001 diff --git a/target/classes/com/jsca/StreamReader.class b/target/classes/com/jsca/StreamReader.class new file mode 100644 index 0000000000000000000000000000000000000000..8c514d0ac228d76f505c3c481b9e07f89f062ec7 GIT binary patch literal 161 zcmX^0Z`VEs1_pBmZgvJHMh4O3{9OI4;^ajA;F6-$#N43N#FW$`b_Nzk27#=^vPAuy z#JqHU|D>$c^glJG{US48SPAVenUM4iJlh6G$>KZ~*`$lqLKC literal 0 HcmV?d00001 diff --git a/target/classes/com/jsca/WebcamStreamReader.class b/target/classes/com/jsca/WebcamStreamReader.class new file mode 100644 index 0000000000000000000000000000000000000000..b577b495b6f0a8df1230550cb9ca9f8078787bf2 GIT binary patch literal 3152 zcmcImTXPf175>_?J+>HQBW!cW0u0zPu^^B@FbRoeSWINRK_Cantu3|f!P1PF8DSu| zO~^f2*v%D4QnhcpwQp1{mx8L~At_Ro%5$FcJ5s5TuV=>6I5@yd>{3t9>2tn5=Q|ha zum67X2LOlhdjl;3TS`tfd)X_=?2MW%$!fuOm8_msvaDPKtpYulBha6>{Dt~# z)|2iv$IFIikzX`zKW5pM{~3WzL#v|?7X?~#PFW?9z&aCMNC_l{hKrpL=rz%aq~7(L zFwh~;owsatre2*@?m0PIQ39#FQ<9aUbS?cJ30wUI%M;kvyz@n-=v{(=C9w?y2DS?f z=j)^pO2qfo~KcVJiz2Xb(8XA5j3)=r(Cf2|-uv=hrGdXALwr^F{iEAZQ^DW2j zz+MJ)#z}`XrpwA_@>DsAK@4kdGR)#oz6roYGB0DpMFab2863zZKRZEG+aqEUBN)|| zS>}0Ya&ow+<2_*F<2Wd=##4ULQcDHv2ASLtA+G=+wzl|$iBIA%Q{~i@9VF8+(}-XrLZSkJ>MZI9Ti!fEz80E)Qic0|Fc4jnH&mr=f_7i7HsAx>=``=LMzNyIh(9$x-j5(1MzY zE6@c|m5VCp*d^)DaIA6X`O@||N%q7qTkg6Tb&;q8oC#)F$>|fW>u?@lC9Y;}xiC*p z2feO`YZ7%_GjSa^1lD;A&Qj#+ge5Bu$L+BgYdNP{b8Kb%W3g+)=^mFvj)dtW7C@!6|!5d>h{}@Rq>NW=Vyelg$SyrJ|gZCEszc zoA@qnGELQ{NZ@aAx+2RE{FJoI73B@)9cQs#TgCHObSn(cU9YLQ`s*KtSH9df-e(bV zPF5-f%U5IF?f6y?Qc1S*4^7OX)NSAg6hyM0nXqt~vY!Tmckpuq?+OgQKwX%cu)Iz# zlXnHb5V-UIrz@(9OEG$*;(0~rw>$7_fvrI?r}gCHSWVBlPBr?HI+_+N`AOSWZmuFd zPk9FJ3haI%{MGVr;I{(XU#`ko8Wsz7FarsG+PJ3l?*Y!}as3N{&ldgt%4ZwTyRepP zH{YfyZ{f2$^9Vhe)(2Rh8F_%d`+>m*t|_!~?c$mw&|3a?VqE6oVI1dmdrvk6Q2)BNx`SytPjJ;}Jgg5c{?4 zAr8&FkAZM|BtP;DU73+5I0ih%@m`#Gj5m0;{w@lk%GAB*e}5k*^IGZ6KuOD=VJbSl zi(I%X5cWtQeTKfq;hN>ceSt@-9^DV(En}Xu6L4{hYXX{~i{t|6W&C|)r=RR>!alNc z2wVAsbQ_LiJHJ~8Fvau~C@W(Z2lpT`MsSmww~)bY?%(771B~KB?w@l1Cu9Sr)4Ycq zMob^)O<%+XGQ7ZyT*M{rzQprUbZ*@KFZ3Ds@;}j5Fi@N`@YUzEN*Gk(9(c};&~L;Q zkjr_Zr!88D_Dq5W(9C@@i1{G1cqs7147EZ}dRla+Q=-f0AT@Ovb&|=xk%uTZLD~bO zffzt%1At>80yxV8(*V5@z~U1)EIu#s7)wp8&1k32ZtCt~N$%y