diff --git a/src/main/java/com/telcotronics/hikvision/player/panel.form b/src/main/java/com/telcotronics/hikvision/player/panel.form
new file mode 100644
index 0000000..1353b59
--- /dev/null
+++ b/src/main/java/com/telcotronics/hikvision/player/panel.form
@@ -0,0 +1,446 @@
+
+
+
diff --git a/src/main/java/com/telcotronics/hikvision/player/panel.java b/src/main/java/com/telcotronics/hikvision/player/panel.java
new file mode 100644
index 0000000..b6ac782
--- /dev/null
+++ b/src/main/java/com/telcotronics/hikvision/player/panel.java
@@ -0,0 +1,741 @@
+/*
+ * Click nbfs://nbhost/SystemFileSystem/Templates/Licenses/license-default.txt to change this license
+ * Click nbfs://nbhost/SystemFileSystem/Templates/GUIForms/JFrame.java to edit this template
+ */
+package com.telcotronics.hikvision.player;
+
+import java.awt.BorderLayout;
+import java.awt.Canvas;
+import java.awt.Color;
+import java.awt.event.ComponentAdapter;
+import java.awt.event.ComponentEvent;
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+import com.sun.jna.Library;
+import com.sun.jna.Native;
+import com.sun.jna.NativeLong;
+import com.sun.jna.Pointer;
+import java.awt.Toolkit;
+import java.awt.datatransfer.Clipboard;
+import java.awt.datatransfer.StringSelection;
+import java.util.concurrent.ExecutionException;
+import javax.swing.JOptionPane;
+import javax.swing.SwingUtilities;
+
+/**
+ *
+ * @author pablinux
+ */
+public class panel extends javax.swing.JFrame {
+
+ // SDK de Hikvision
+ private HCNetSDK hcNetSDK;
+ private NativeLong userID;
+ private NativeLong realHandle;
+ private Canvas videoCanvas;
+ private ConfigManager config;
+
+ private static final java.util.logging.Logger logger = java.util.logging.Logger.getLogger(panel.class.getName());
+
+ public panel() {
+
+ initComponents();
+ setupLogging(); // Configure logging to file
+ config = new ConfigManager();
+ loadConfigToUI();
+
+ // Add action listener for the checkbox
+ ck_habilitaVerificacionCode.addActionListener(e -> {
+ txt_videoCrypt.setEnabled(ck_habilitaVerificacionCode.isSelected());
+ });
+ // Set initial state of txt_videoCrypt based on checkbox
+ txt_videoCrypt.setEnabled(ck_habilitaVerificacionCode.isSelected());
+
+ initSDK();
+
+ addWindowListener(new WindowAdapter() {
+ @Override
+ public void windowOpened(WindowEvent e) {
+ setupVideo();
+ }
+
+ @Override
+ public void windowClosing(WindowEvent e) {
+ cleanup();
+ }
+ });
+ }
+
+ /**
+ * This method is called from within the constructor to initialize the form.
+ * WARNING: Do NOT modify this code. The content of this method is always
+ * regenerated by the Form Editor.
+ */
+ @SuppressWarnings("unchecked")
+ // //GEN-BEGIN:initComponents
+ private void initComponents() {
+
+ jPopupMenu1 = new javax.swing.JPopupMenu();
+ menu_copiar = new javax.swing.JMenuItem();
+ jTabbedPane1 = new javax.swing.JTabbedPane();
+ jPanel1 = new javax.swing.JPanel();
+ panel_video = new javax.swing.JPanel();
+ jPanel2 = new javax.swing.JPanel();
+ bt_conectar = new javax.swing.JButton();
+ bt_reproducir = new javax.swing.JButton();
+ jPanel3 = new javax.swing.JPanel();
+ jPanel4 = new javax.swing.JPanel();
+ cb_channel = new javax.swing.JComboBox<>();
+ txt_puerto = new javax.swing.JTextField();
+ txt_ip = new javax.swing.JTextField();
+ jLabel3 = new javax.swing.JLabel();
+ jLabel4 = new javax.swing.JLabel();
+ jLabel5 = new javax.swing.JLabel();
+ bt_cargarConfig = new javax.swing.JButton();
+ jPanel5 = new javax.swing.JPanel();
+ txt_pasword = new javax.swing.JTextField();
+ txt_usr = new javax.swing.JTextField();
+ jLabel1 = new javax.swing.JLabel();
+ jLabel2 = new javax.swing.JLabel();
+ bt_guardarConfig = new javax.swing.JButton();
+ jPanel6 = new javax.swing.JPanel();
+ jLabel6 = new javax.swing.JLabel();
+ txt_videoCrypt = new javax.swing.JTextField();
+ ck_habilitaVerificacionCode = new javax.swing.JCheckBox();
+ jScrollPane1 = new javax.swing.JScrollPane();
+ txtPane_consola = new javax.swing.JTextPane();
+ bt_limpiarLogs = new javax.swing.JButton();
+
+ menu_copiar.setText("Copiar");
+ menu_copiar.addActionListener(this::menu_copiarActionPerformed);
+ jPopupMenu1.add(menu_copiar);
+
+ setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);
+
+ jTabbedPane1.addChangeListener(this::jTabbedPane1StateChanged);
+
+ panel_video.setBackground(new java.awt.Color(0, 0, 0));
+ panel_video.setLayout(new java.awt.BorderLayout());
+
+ jPanel2.setBorder(javax.swing.BorderFactory.createEtchedBorder());
+
+ bt_conectar.setText("Conectar");
+ bt_conectar.addActionListener(this::bt_conectarActionPerformed);
+
+ bt_reproducir.setText("play");
+ bt_reproducir.setEnabled(false);
+ bt_reproducir.addActionListener(this::bt_reproducirActionPerformed);
+
+ javax.swing.GroupLayout jPanel2Layout = new javax.swing.GroupLayout(jPanel2);
+ jPanel2.setLayout(jPanel2Layout);
+ jPanel2Layout.setHorizontalGroup(
+ jPanel2Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+ .addGroup(jPanel2Layout.createSequentialGroup()
+ .addContainerGap()
+ .addComponent(bt_conectar)
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
+ .addComponent(bt_reproducir)
+ .addContainerGap(544, Short.MAX_VALUE))
+ );
+ jPanel2Layout.setVerticalGroup(
+ jPanel2Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+ .addGroup(jPanel2Layout.createSequentialGroup()
+ .addContainerGap()
+ .addGroup(jPanel2Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
+ .addComponent(bt_conectar)
+ .addComponent(bt_reproducir))
+ .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
+ );
+
+ javax.swing.GroupLayout jPanel1Layout = new javax.swing.GroupLayout(jPanel1);
+ jPanel1.setLayout(jPanel1Layout);
+ jPanel1Layout.setHorizontalGroup(
+ jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+ .addGroup(jPanel1Layout.createSequentialGroup()
+ .addContainerGap()
+ .addComponent(panel_video, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
+ .addContainerGap())
+ .addComponent(jPanel2, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
+ );
+ jPanel1Layout.setVerticalGroup(
+ jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+ .addGroup(jPanel1Layout.createSequentialGroup()
+ .addContainerGap()
+ .addComponent(panel_video, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+ .addComponent(jPanel2, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
+ );
+
+ jTabbedPane1.addTab("Vision en directo", jPanel1);
+
+ jPanel4.setBackground(new java.awt.Color(153, 153, 153));
+
+ cb_channel.setModel(new javax.swing.DefaultComboBoxModel<>(new String[] { "1", "2", "3", "4" }));
+
+ jLabel3.setText("IP");
+
+ jLabel4.setText("PUERTO");
+
+ jLabel5.setText("CANAL");
+
+ javax.swing.GroupLayout jPanel4Layout = new javax.swing.GroupLayout(jPanel4);
+ jPanel4.setLayout(jPanel4Layout);
+ jPanel4Layout.setHorizontalGroup(
+ jPanel4Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+ .addGroup(jPanel4Layout.createSequentialGroup()
+ .addGap(14, 14, 14)
+ .addComponent(jLabel3)
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+ .addComponent(txt_ip, javax.swing.GroupLayout.PREFERRED_SIZE, 266, javax.swing.GroupLayout.PREFERRED_SIZE)
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+ .addComponent(jLabel4)
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
+ .addComponent(txt_puerto, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
+ .addComponent(jLabel5)
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+ .addComponent(cb_channel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
+ .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
+ );
+ jPanel4Layout.setVerticalGroup(
+ jPanel4Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+ .addGroup(jPanel4Layout.createSequentialGroup()
+ .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
+ .addGroup(jPanel4Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
+ .addComponent(txt_ip, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
+ .addComponent(txt_puerto, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
+ .addComponent(cb_channel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
+ .addComponent(jLabel3)
+ .addComponent(jLabel4)
+ .addComponent(jLabel5))
+ .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
+ );
+
+ bt_cargarConfig.setText("Cargar Configuracion");
+ bt_cargarConfig.addActionListener(this::bt_cargarConfigActionPerformed);
+
+ jPanel5.setBackground(new java.awt.Color(204, 204, 204));
+
+ jLabel1.setText("Usuario");
+
+ jLabel2.setText("Contraseña");
+
+ javax.swing.GroupLayout jPanel5Layout = new javax.swing.GroupLayout(jPanel5);
+ jPanel5.setLayout(jPanel5Layout);
+ jPanel5Layout.setHorizontalGroup(
+ jPanel5Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+ .addGroup(jPanel5Layout.createSequentialGroup()
+ .addGap(37, 37, 37)
+ .addComponent(jLabel1)
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+ .addComponent(txt_usr, javax.swing.GroupLayout.PREFERRED_SIZE, 260, javax.swing.GroupLayout.PREFERRED_SIZE)
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+ .addComponent(jLabel2)
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+ .addComponent(txt_pasword, javax.swing.GroupLayout.PREFERRED_SIZE, 241, javax.swing.GroupLayout.PREFERRED_SIZE)
+ .addContainerGap(34, Short.MAX_VALUE))
+ );
+ jPanel5Layout.setVerticalGroup(
+ jPanel5Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+ .addGroup(jPanel5Layout.createSequentialGroup()
+ .addContainerGap()
+ .addGroup(jPanel5Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
+ .addComponent(txt_pasword, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
+ .addComponent(txt_usr, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
+ .addComponent(jLabel1)
+ .addComponent(jLabel2))
+ .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
+ );
+
+ bt_guardarConfig.setText("Guardar Configuracion");
+ bt_guardarConfig.addActionListener(this::bt_guardarConfigActionPerformed);
+
+ jPanel6.setBackground(new java.awt.Color(204, 204, 204));
+
+ jLabel6.setText("Codigo de Verificacion");
+
+ ck_habilitaVerificacionCode.setText("Contraseña de Ecriptacion de flujo de video");
+
+ javax.swing.GroupLayout jPanel6Layout = new javax.swing.GroupLayout(jPanel6);
+ jPanel6.setLayout(jPanel6Layout);
+ jPanel6Layout.setHorizontalGroup(
+ jPanel6Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+ .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, jPanel6Layout.createSequentialGroup()
+ .addContainerGap()
+ .addComponent(ck_habilitaVerificacionCode)
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
+ .addComponent(jLabel6)
+ .addGap(18, 18, 18)
+ .addComponent(txt_videoCrypt, javax.swing.GroupLayout.PREFERRED_SIZE, 177, javax.swing.GroupLayout.PREFERRED_SIZE)
+ .addGap(43, 43, 43))
+ );
+ jPanel6Layout.setVerticalGroup(
+ jPanel6Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+ .addGroup(jPanel6Layout.createSequentialGroup()
+ .addContainerGap()
+ .addGroup(jPanel6Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
+ .addComponent(jLabel6)
+ .addComponent(txt_videoCrypt, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
+ .addComponent(ck_habilitaVerificacionCode))
+ .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
+ );
+
+ txtPane_consola.setComponentPopupMenu(jPopupMenu1);
+ jScrollPane1.setViewportView(txtPane_consola);
+
+ bt_limpiarLogs.setText("Limpiar Log");
+ bt_limpiarLogs.addActionListener(this::bt_limpiarLogsActionPerformed);
+
+ javax.swing.GroupLayout jPanel3Layout = new javax.swing.GroupLayout(jPanel3);
+ jPanel3.setLayout(jPanel3Layout);
+ jPanel3Layout.setHorizontalGroup(
+ jPanel3Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+ .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, jPanel3Layout.createSequentialGroup()
+ .addContainerGap()
+ .addGroup(jPanel3Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING)
+ .addComponent(jScrollPane1)
+ .addComponent(jPanel4, javax.swing.GroupLayout.Alignment.LEADING, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
+ .addComponent(jPanel5, javax.swing.GroupLayout.Alignment.LEADING, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
+ .addComponent(jPanel6, javax.swing.GroupLayout.Alignment.LEADING, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
+ .addGroup(javax.swing.GroupLayout.Alignment.LEADING, jPanel3Layout.createSequentialGroup()
+ .addComponent(bt_cargarConfig, javax.swing.GroupLayout.PREFERRED_SIZE, 216, javax.swing.GroupLayout.PREFERRED_SIZE)
+ .addGap(18, 18, 18)
+ .addComponent(bt_guardarConfig, javax.swing.GroupLayout.PREFERRED_SIZE, 216, javax.swing.GroupLayout.PREFERRED_SIZE)
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
+ .addComponent(bt_limpiarLogs, javax.swing.GroupLayout.PREFERRED_SIZE, 164, javax.swing.GroupLayout.PREFERRED_SIZE)))
+ .addContainerGap())
+ );
+ jPanel3Layout.setVerticalGroup(
+ jPanel3Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+ .addGroup(jPanel3Layout.createSequentialGroup()
+ .addContainerGap()
+ .addComponent(jPanel4, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+ .addComponent(jPanel5, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+ .addComponent(jPanel6, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
+ .addComponent(jScrollPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 340, Short.MAX_VALUE)
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
+ .addGroup(jPanel3Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
+ .addComponent(bt_cargarConfig, javax.swing.GroupLayout.PREFERRED_SIZE, 46, javax.swing.GroupLayout.PREFERRED_SIZE)
+ .addComponent(bt_guardarConfig, javax.swing.GroupLayout.PREFERRED_SIZE, 46, javax.swing.GroupLayout.PREFERRED_SIZE)
+ .addComponent(bt_limpiarLogs, javax.swing.GroupLayout.PREFERRED_SIZE, 46, javax.swing.GroupLayout.PREFERRED_SIZE))
+ .addContainerGap())
+ );
+
+ jTabbedPane1.addTab("Configuracion", jPanel3);
+
+ javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane());
+ getContentPane().setLayout(layout);
+ layout.setHorizontalGroup(
+ layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+ .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup()
+ .addGap(0, 0, Short.MAX_VALUE)
+ .addComponent(jTabbedPane1, javax.swing.GroupLayout.PREFERRED_SIZE, 728, javax.swing.GroupLayout.PREFERRED_SIZE))
+ );
+ layout.setVerticalGroup(
+ layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+ .addComponent(jTabbedPane1)
+ );
+
+ pack();
+ }// //GEN-END:initComponents
+
+ private void bt_conectarActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_bt_conectarActionPerformed
+ if (bt_conectar.getText().equals("Conectar")) {
+ connectToDVRAsync();
+ //bt_conectar.setText("Desconectar");
+ //bt_reproducir.setEnabled(true);
+ } else {
+ bt_reproducir.setEnabled(false);
+ bt_conectar.setText("Conectar");
+ stopPreview(); // ⚠️ AGREGA ESTO
+ disconnectFromDVR();
+ }
+
+ }//GEN-LAST:event_bt_conectarActionPerformed
+
+ private void bt_reproducirActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_bt_reproducirActionPerformed
+ if (bt_reproducir.getText().equals("play")) {
+ startPreviewAsync();
+ bt_reproducir.setText("Stop");
+ } else {
+ stopPreview();
+ bt_reproducir.setText("play");
+ }
+ }//GEN-LAST:event_bt_reproducirActionPerformed
+
+ private void bt_guardarConfigActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_bt_guardarConfigActionPerformed
+ saveConfigFromUI();
+ }//GEN-LAST:event_bt_guardarConfigActionPerformed
+
+ private void bt_cargarConfigActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_bt_cargarConfigActionPerformed
+ config = new ConfigManager(); // Recarga desde el archivo
+ loadConfigToUI();
+ JOptionPane.showMessageDialog(this,
+ "Configuración recargada desde config.properties",
+ "Éxito", JOptionPane.INFORMATION_MESSAGE);
+ }//GEN-LAST:event_bt_cargarConfigActionPerformed
+
+ private void jTabbedPane1StateChanged(javax.swing.event.ChangeEvent evt) {//GEN-FIRST:event_jTabbedPane1StateChanged
+ if(jTabbedPane1.getSelectedIndex()==1){
+ cargar_logs();
+ }
+ }//GEN-LAST:event_jTabbedPane1StateChanged
+
+ private void bt_limpiarLogsActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_bt_limpiarLogsActionPerformed
+ try {
+ // Close all handlers on the root logger to release the file lock
+ for (java.util.logging.Handler handler : java.util.logging.Logger.getLogger("").getHandlers()) {
+ handler.close();
+ }
+ // Delete the file
+ java.nio.file.Files.deleteIfExists(java.nio.file.Paths.get("hikvision-player.log"));
+ // Re-initialize logging to create a new file
+ setupLogging();
+ // Refresh the text pane
+ cargar_logs();
+ logger.info("Log file cleared.");
+ } catch (java.io.IOException e) {
+ logger.log(java.util.logging.Level.SEVERE, "Failed to clear log file", e);
+ JOptionPane.showMessageDialog(this, "Error al limpiar el log: " + e.getMessage(), "Error", JOptionPane.ERROR_MESSAGE);
+ }
+ }//GEN-LAST:event_bt_limpiarLogsActionPerformed
+
+ private void menu_copiarActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_menu_copiarActionPerformed
+ String textoACopiar = txtPane_consola.getText();
+ // 1. Obtener el portapapeles del sistema.
+ Clipboard portapapeles = Toolkit.getDefaultToolkit().getSystemClipboard();
+
+ // 2. Crear un objeto StringSelection con el texto.
+ StringSelection seleccion = new StringSelection(textoACopiar);
+
+ // 3. Colocar el StringSelection en el portapapeles.
+ portapapeles.setContents(seleccion, null);
+
+ System.out.println("El texto se ha copiado al portapapeles.");
+ }//GEN-LAST:event_menu_copiarActionPerformed
+
+ private void initSDK() {
+ try {
+ System.setProperty("jna.library.path", "./lib");
+ hcNetSDK = HCNetSDK.INSTANCE;
+
+ // >>> INICIO: CONFIGURAR CLAVE DE ENCRIPTACIÓN ANTES DE INICIALIZAR
+ if (config.getEnableVerificationCode()) {
+ String verificationCode = config.getVerificationCode();
+ if (verificationCode != null && !verificationCode.isEmpty()) {
+ logger.info("Intentando configurar código de verificación...");
+ HCNetSDK.NET_DVR_LOCAL_PROTECT_KEY_CFG keyCfg = new HCNetSDK.NET_DVR_LOCAL_PROTECT_KEY_CFG();
+ System.arraycopy(verificationCode.getBytes(), 0, keyCfg.byProtectKey, 0, verificationCode.length());
+
+ boolean success = hcNetSDK.NET_DVR_SetSDKLocalCfg(HCNetSDK.NET_SDK_LOCAL_CFG_TYPE_PROTECT_KEY, keyCfg.getPointer());
+ if (success) {
+ logger.info("Código de verificación configurado exitosamente.");
+ } else {
+ logger.severe("FALLO al configurar código de verificación. Error SDK: " + hcNetSDK.NET_DVR_GetLastError());
+ }
+ }
+ }
+ // <<< FIN: CONFIGURAR CLAVE DE ENCRIPTACIÓN
+
+ boolean init = hcNetSDK.NET_DVR_Init();
+ if (init) {
+ logger.info("SDK inicializado correctamente");
+ } else {
+ logger.severe("Error al inicializar SDK. Error SDK: " + hcNetSDK.NET_DVR_GetLastError());
+ }
+ } catch (Exception e) {
+ logger.log(java.util.logging.Level.SEVERE, "Error al cargar SDK", e);
+ JOptionPane.showMessageDialog(this,
+ "Error al cargar el SDK: " + e.getMessage(),
+ "Error", JOptionPane.ERROR_MESSAGE);
+ }
+ }
+
+ private void setupVideo() {
+ // Asegurarse de que panel_video use BorderLayout para que el Canvas ocupe todo el panel
+ panel_video.setLayout(new BorderLayout());
+
+ videoCanvas = new Canvas();
+ videoCanvas.setBackground(Color.BLACK);
+
+ // AGREGAR LISTENER PARA HABILITAR EL BOTÓN CUANDO EL CANVAS ESTÉ LISTO
+ videoCanvas.addComponentListener(new ComponentAdapter() {
+ @Override
+ public void componentResized(ComponentEvent e) {
+ // Una vez que el canvas tiene tamaño, habilitamos el botón de reproducir
+ // si ya estamos conectados.
+ if (userID != null && userID.longValue() > -1) {
+ bt_reproducir.setEnabled(true);
+ }
+ // Opcional: remover el listener para que no se ejecute más
+ videoCanvas.removeComponentListener(this);
+ }
+ });
+
+ // Hacer que el canvas sea visible y agregalo al panel
+ panel_video.removeAll();
+ panel_video.add(videoCanvas, BorderLayout.CENTER);
+ videoCanvas.setVisible(true);
+
+ panel_video.revalidate();
+ panel_video.repaint();
+ }
+
+ private void connectToDVRAsync() {
+ // Deshabilitar botón mientras conecta
+ bt_conectar.setEnabled(false);
+ bt_conectar.setText("Conectando...");
+
+ DVRConnectionWorker worker = new DVRConnectionWorker(
+ hcNetSDK,
+ config.getDvrIP(),
+ config.getDvrPort(),
+ config.getUsername(),
+ config.getPassword()
+ );
+
+ worker.execute();
+
+ // Manejar el resultado cuando termine
+ new Thread(() -> {
+ try {
+ ConnectionResult result = worker.get();
+
+ SwingUtilities.invokeLater(() -> {
+ if (result.success) {
+ userID = result.userID;
+ logger.info("Conectado. Canales: " + result.deviceInfo.byChanNum);
+
+ JOptionPane.showMessageDialog(this,
+ "Conectado exitosamente\n"
+ + "Canales analógicos: " + result.deviceInfo.byChanNum + "\n"
+ + "Canales IP: " + result.deviceInfo.byIPChanNum,
+ "Éxito", JOptionPane.INFORMATION_MESSAGE);
+
+ bt_conectar.setText("Desconectar");
+ // NO habilitar play aquí. El listener se encargará.
+ // Si el canvas ya tiene tamaño, el listener ya se disparó.
+ // Si no, se disparará pronto.
+ if (videoCanvas.getWidth() > 0 && videoCanvas.getHeight() > 0) {
+ bt_reproducir.setEnabled(true);
+ }
+
+ } else {
+ String errorMessage = String.format("Error al conectar. Código: %d. IP: %s, Puerto: %d",
+ result.errorCode, config.getDvrIP(), config.getDvrPort());
+ logger.severe(errorMessage);
+ JOptionPane.showMessageDialog(this,
+ errorMessage,
+ "Error", JOptionPane.ERROR_MESSAGE);
+
+ bt_conectar.setText("Conectar");
+ bt_reproducir.setEnabled(false);
+ }
+ bt_conectar.setEnabled(true);
+ });
+
+ } catch (InterruptedException | ExecutionException e) {
+ SwingUtilities.invokeLater(() -> {
+ logger.severe("Error en conexión: " + e.getMessage());
+ bt_conectar.setText("Conectar");
+ bt_conectar.setEnabled(true);
+ bt_reproducir.setEnabled(false);
+ });
+ }
+ }).start();
+ }
+
+ private void startPreviewAsync() {
+ if (userID == null || userID.intValue() < 0) {
+ JOptionPane.showMessageDialog(this,
+ "Primero conecte al DVR",
+ "Advertencia", JOptionPane.WARNING_MESSAGE);
+ return;
+ }
+
+ bt_reproducir.setEnabled(false);
+ bt_reproducir.setText("Iniciando...");
+
+ PreviewWorker worker = new PreviewWorker(
+ hcNetSDK,
+ userID,
+ config.getChannel(),
+ config.getStreamType(),
+ videoCanvas
+ );
+
+ worker.execute();
+
+ new Thread(() -> {
+ try {
+ PreviewResult result = worker.get();
+
+ SwingUtilities.invokeLater(() -> {
+ if (result.success) {
+ realHandle = result.realHandle;
+ logger.info("Preview iniciado en canal " + config.getChannel());
+ bt_reproducir.setText("Parar");
+ } else {
+ String errorMessage = String.format("Error al iniciar video. Código: %d. Canal: %d",
+ result.errorCode, config.getChannel());
+ logger.severe(errorMessage);
+ JOptionPane.showMessageDialog(this,
+ errorMessage,
+ "Error", JOptionPane.ERROR_MESSAGE);
+ bt_reproducir.setText("play");
+ }
+ bt_reproducir.setEnabled(true);
+ });
+
+ } catch (InterruptedException | ExecutionException e) {
+ SwingUtilities.invokeLater(() -> {
+ logger.severe("Error en preview: " + e.getMessage());
+ bt_reproducir.setText("play");
+ bt_reproducir.setEnabled(true);
+ });
+ }
+ }).start();
+ }
+
+ private void stopPreview() {
+ if (realHandle != null && realHandle.intValue() >= 0) {
+ hcNetSDK.NET_DVR_StopRealPlay(realHandle);
+ logger.info("Preview detenido");
+ realHandle = null;
+ }
+ }
+
+ private void disconnectFromDVR() {
+ stopPreview();
+ if (userID != null && userID.intValue() >= 0) {
+ hcNetSDK.NET_DVR_Logout(userID);
+ logger.info("Desconectado del DVR");
+ userID = null;
+ }
+ }
+
+ private void cleanup() {
+ disconnectFromDVR();
+ if (hcNetSDK != null) {
+ hcNetSDK.NET_DVR_Cleanup();
+ logger.info("SDK limpiado");
+ }
+ }
+
+ // Cargar configuración a los campos de la UI
+ private void loadConfigToUI() {
+ txt_ip.setText(config.getDvrIP());
+ txt_puerto.setText(String.valueOf(config.getDvrPort()));
+ txt_usr.setText(config.getUsername());
+ txt_pasword.setText(config.getPassword());
+ cb_channel.setSelectedItem(String.valueOf(config.getChannel()));
+ txt_videoCrypt.setText(config.getVerificationCode());
+ ck_habilitaVerificacionCode.setSelected(config.getEnableVerificationCode());
+ txt_videoCrypt.setEnabled(ck_habilitaVerificacionCode.isSelected()); // Set initial state
+ }
+
+// Guardar configuración desde los campos de la UI
+ private void saveConfigFromUI() {
+ try {
+ config.setDvrIP(txt_ip.getText().trim());
+ config.setDvrPort(Integer.parseInt(txt_puerto.getText().trim()));
+ config.setUsername(txt_usr.getText().trim());
+ config.setPassword(txt_pasword.getText().trim());
+ config.setChannel(Integer.parseInt((String) cb_channel.getSelectedItem()));
+ config.setVerificationCode(txt_videoCrypt.getText().trim());
+ config.setEnableVerificationCode(ck_habilitaVerificacionCode.isSelected());
+ config.saveConfig();
+
+ JOptionPane.showMessageDialog(this,
+ "Configuración guardada exitosamente",
+ "Éxito", JOptionPane.INFORMATION_MESSAGE);
+
+ } catch (NumberFormatException e) {
+ JOptionPane.showMessageDialog(this,
+ "Error: Puerto y Canal deben ser números",
+ "Error", JOptionPane.ERROR_MESSAGE);
+ }
+ }
+
+ private void setupLogging() {
+ try {
+ java.util.logging.FileHandler fileHandler = new java.util.logging.FileHandler("hikvision-player.log", true); // true for append
+ java.util.logging.SimpleFormatter formatter = new java.util.logging.SimpleFormatter();
+ fileHandler.setFormatter(formatter);
+ java.util.logging.Logger.getLogger("").addHandler(fileHandler);
+ logger.info("Logging to file configured.");
+ } catch (java.io.IOException | SecurityException e) {
+ logger.log(java.util.logging.Level.SEVERE, "Failed to set up logging to file", e);
+ }
+ }
+
+ private void cargar_logs(){
+ try {
+ String content = new String(java.nio.file.Files.readAllBytes(java.nio.file.Paths.get("hikvision-player.log")));
+ txtPane_consola.setText(content);
+ } catch (java.io.IOException e) {
+ txtPane_consola.setText("Error al cargar el archivo de log: " + e.getMessage());
+ }
+ }
+
+ /**
+ * @param args the command line arguments
+ */
+ public static void main(String args[]) {
+ /* Set the Nimbus look and feel */
+ //
+ /* If Nimbus (introduced in Java SE 6) is not available, stay with the default look and feel.
+ * For details see http://download.oracle.com/javase/tutorial/uiswing/lookandfeel/plaf.html
+ */
+ try {
+ for (javax.swing.UIManager.LookAndFeelInfo info : javax.swing.UIManager.getInstalledLookAndFeels()) {
+ if ("Nimbus".equals(info.getName())) {
+ javax.swing.UIManager.setLookAndFeel(info.getClassName());
+ break;
+ }
+ }
+ } catch (ReflectiveOperationException | javax.swing.UnsupportedLookAndFeelException ex) {
+ logger.log(java.util.logging.Level.SEVERE, null, ex);
+ }
+ //
+
+ /* Create and display the form */
+ java.awt.EventQueue.invokeLater(() -> new panel().setVisible(true));
+ }
+
+ // Variables declaration - do not modify//GEN-BEGIN:variables
+ private javax.swing.JButton bt_cargarConfig;
+ private javax.swing.JButton bt_conectar;
+ private javax.swing.JButton bt_guardarConfig;
+ private javax.swing.JButton bt_limpiarLogs;
+ private javax.swing.JButton bt_reproducir;
+ private javax.swing.JComboBox cb_channel;
+ private javax.swing.JCheckBox ck_habilitaVerificacionCode;
+ private javax.swing.JLabel jLabel1;
+ private javax.swing.JLabel jLabel2;
+ private javax.swing.JLabel jLabel3;
+ private javax.swing.JLabel jLabel4;
+ private javax.swing.JLabel jLabel5;
+ private javax.swing.JLabel jLabel6;
+ private javax.swing.JPanel jPanel1;
+ private javax.swing.JPanel jPanel2;
+ private javax.swing.JPanel jPanel3;
+ private javax.swing.JPanel jPanel4;
+ private javax.swing.JPanel jPanel5;
+ private javax.swing.JPanel jPanel6;
+ private javax.swing.JPopupMenu jPopupMenu1;
+ private javax.swing.JScrollPane jScrollPane1;
+ private javax.swing.JTabbedPane jTabbedPane1;
+ private javax.swing.JMenuItem menu_copiar;
+ private javax.swing.JPanel panel_video;
+ private javax.swing.JTextPane txtPane_consola;
+ private javax.swing.JTextField txt_ip;
+ private javax.swing.JTextField txt_pasword;
+ private javax.swing.JTextField txt_puerto;
+ private javax.swing.JTextField txt_usr;
+ private javax.swing.JTextField txt_videoCrypt;
+ // End of variables declaration//GEN-END:variables
+}