]> git.basschouten.com Git - openhab-addons.git/commitdiff
[ipcamera] Improvements and fix 503 errors go to offline with Hik (#11419)
authorMatthew Skinner <matt@pcmus.com>
Sun, 24 Oct 2021 09:36:20 +0000 (20:36 +1100)
committerGitHub <noreply@github.com>
Sun, 24 Oct 2021 09:36:20 +0000 (11:36 +0200)
* Stop hik logging 401 with digest.

Signed-off-by: Matthew Skinner <matt@pcmus.com>
* Improve and fix generic cams

Signed-off-by: Matthew Skinner <matt@pcmus.com>
* Stop dahua IntelliFrame logging

Signed-off-by: Matthew Skinner <matt@pcmus.com>
* Catch IllegalStateException

Signed-off-by: Matthew Skinner <matt@pcmus.com>
* Trial reusing channels.

Signed-off-by: Matthew Skinner <matt@pcmus.com>
* Tidy up

Signed-off-by: Matthew Skinner <matt@pcmus.com>
* cleanup 2

Signed-off-by: Matthew Skinner <matt@pcmus.com>
* Cleanup 3

Signed-off-by: Matthew Skinner <matt@pcmus.com>
* Disable checking connection with event stream.

Signed-off-by: Matthew Skinner <matt@pcmus.com>
* Bug fix

Signed-off-by: Matthew Skinner <matt@pcmus.com>
* more cleanup

Signed-off-by: Matthew Skinner <matt@pcmus.com>
* more cleanup

Signed-off-by: Matthew Skinner <matt@pcmus.com>
* Reduce logging to only whats needed.

Signed-off-by: Matthew Skinner <matt@pcmus.com>
* fix offline detection.

Signed-off-by: Matthew Skinner <matt@pcmus.com>
* fixes to ipcamera.mjpeg

Signed-off-by: Matthew Skinner <matt@pcmus.com>
* reverse some connection checks

Signed-off-by: Matthew Skinner <matt@pcmus.com>
bundles/org.openhab.binding.ipcamera/src/main/java/org/openhab/binding/ipcamera/internal/ChannelTracking.java
bundles/org.openhab.binding.ipcamera/src/main/java/org/openhab/binding/ipcamera/internal/DahuaHandler.java
bundles/org.openhab.binding.ipcamera/src/main/java/org/openhab/binding/ipcamera/internal/Ffmpeg.java
bundles/org.openhab.binding.ipcamera/src/main/java/org/openhab/binding/ipcamera/internal/HikvisionHandler.java
bundles/org.openhab.binding.ipcamera/src/main/java/org/openhab/binding/ipcamera/internal/MyNettyAuthHandler.java
bundles/org.openhab.binding.ipcamera/src/main/java/org/openhab/binding/ipcamera/internal/handler/IpCameraHandler.java
bundles/org.openhab.binding.ipcamera/src/main/java/org/openhab/binding/ipcamera/internal/onvif/OnvifConnection.java
bundles/org.openhab.binding.ipcamera/src/main/java/org/openhab/binding/ipcamera/internal/servlet/CameraServlet.java
bundles/org.openhab.binding.ipcamera/src/main/java/org/openhab/binding/ipcamera/internal/servlet/StreamOutput.java
bundles/org.openhab.binding.ipcamera/src/main/resources/OH-INF/thing/thing-types.xml

index fd42f29c91186f3b7c9fbd6eaeb6562de4c21be3..52a429bb853eb3c5c271ad50eb215e0388673fa7 100644 (file)
@@ -15,6 +15,7 @@ package org.openhab.binding.ipcamera.internal;
 import org.eclipse.jdt.annotation.NonNullByDefault;
 
 import io.netty.channel.Channel;
+import io.netty.channel.ChannelFuture;
 
 /**
  * The {@link ChannelTracking} Can be used to find the handle for a HTTP channel if you know the URL. The reply can
@@ -43,6 +44,15 @@ public class ChannelTracking {
         return channel;
     }
 
+    /**
+     * Closes the channel, but keeps the HTTP reply stored in the tracker.
+     *
+     * @return ChannelFuture
+     */
+    public ChannelFuture closeChannel() {
+        return channel.close();
+    }
+
     public String getReply() {
         return storedReply;
     }
index 05a6fd7c2a32a01bd4461d4b2036a70c627b6b68..816233f2619a20ef3087c3e2400d8af145964e52 100644 (file)
@@ -49,7 +49,7 @@ public class DahuaHandler extends ChannelDuplexHandler {
     }
 
     private void processEvent(String content) {
-        int startIndex = content.indexOf("Code=", 12) + 5;// skip --myboundary
+        int startIndex = content.indexOf("Code=", 12) + 5;// skip --myboundary and Code=
         int endIndex = content.indexOf(";", startIndex + 1);
         if (startIndex == -1 || endIndex == -1) {
             ipCameraHandler.logger.debug("Code= not found in Dahua event. Content was:{}", content);
@@ -177,7 +177,9 @@ public class DahuaHandler extends ChannelDuplexHandler {
             case "LensMaskClose":
                 ipCameraHandler.setChannelState(CHANNEL_ENABLE_PRIVACY_MODE, OnOffType.OFF);
                 break;
+            // Skip these so they are not logged.
             case "TimeChange":
+            case "IntelliFrame":
             case "NTPAdjustTime":
             case "StorageChange":
             case "Reboot":
index 9ca8a20711cd90efacdf75babf8166a7ab7bdf8d..b940e76e8b4d7f93c211777fdb82f0d80bedc0c3 100644 (file)
@@ -96,7 +96,7 @@ public class Ffmpeg {
     }
 
     private class IpCameraFfmpegThread extends Thread {
-        private ScheduledExecutorService threadPool = Executors.newScheduledThreadPool(2);
+        private ScheduledExecutorService threadPool = Executors.newScheduledThreadPool(1);
         public int countOfMotions;
 
         IpCameraFfmpegThread() {
@@ -220,6 +220,7 @@ public class Ffmpeg {
             Process localProcess = process;
             if (localProcess != null) {
                 localProcess.destroyForcibly();
+                process = null;
             }
             if (format.equals(FFmpegFormat.HLS)) {
                 ipCameraHandler.setChannelState(CHANNEL_START_STREAM, OnOffType.OFF);
index a2e86da0677c20a416f90be304a26d02b7046b88..51ae91b6e16faf23bb45175400c04d5cc51614e1 100644 (file)
@@ -196,9 +196,6 @@ public class HikvisionHandler extends ChannelDuplexHandler {
                             }
                         }
                         break;
-                    default:
-                        logger.debug("Unhandled reply-{}.", content);
-                        break;
                 }
             }
         } finally {
index 63df45cd5f6f1f2bd843bc91393700212a9c834d..f5089acb518865ca253a13292c59e6601699edb3 100644 (file)
@@ -89,7 +89,9 @@ public class MyNettyAuthHandler extends ChannelDuplexHandler {
         /////// Fresh Digest Authenticate method follows as Basic is already handled and returned ////////
         realm = Helper.searchString(authenticate, "realm=\"");
         if (realm.isEmpty()) {
-            logger.warn("Could not find a valid WWW-Authenticate response in :{}", authenticate);
+            logger.warn(
+                    "No valid WWW-Authenticate in response. Has the camera activated the illegal login lock? Details:{}",
+                    authenticate);
             return;
         }
         nonce = Helper.searchString(authenticate, "nonce=\"");
@@ -142,23 +144,17 @@ public class MyNettyAuthHandler extends ChannelDuplexHandler {
         if (msg == null || ctx == null) {
             return;
         }
-        boolean closeConnection = true;
-        String authenticate = "";
         if (msg instanceof HttpResponse) {
             HttpResponse response = (HttpResponse) msg;
             if (response.status().code() == 401) {
+                ctx.close();
                 if (!response.headers().isEmpty()) {
+                    String authenticate = "";
                     for (CharSequence name : response.headers().names()) {
                         for (CharSequence value : response.headers().getAll(name)) {
                             if (name.toString().equalsIgnoreCase("WWW-Authenticate")) {
                                 authenticate = value.toString();
                             }
-                            if (name.toString().equalsIgnoreCase("Connection")
-                                    && value.toString().contains("keep-alive")) {
-                                // closeConnection = false;
-                                // trial this for a while to see if it solves too many bytes with digest turned on.
-                                closeConnection = true;
-                            }
                         }
                     }
                     if (!authenticate.isEmpty()) {
@@ -167,24 +163,22 @@ public class MyNettyAuthHandler extends ChannelDuplexHandler {
                         ipCameraHandler.cameraConfigError(
                                 "Camera gave no WWW-Authenticate: Your login details must be wrong.");
                     }
-                    if (closeConnection) {
-                        ctx.close();// needs to be here
-                    }
                 }
             } else if (response.status().code() != 200) {
-                logger.debug("Camera at IP:{} gave a reply with a response code of :{}",
-                        ipCameraHandler.cameraConfig.getIp(), response.status().code());
+                ctx.close();
+                switch (response.status().code()) {
+                    case 403:
+                        logger.warn(
+                                "403 Forbidden: Check camera setup or has the camera activated the illegal login lock?");
+                        break;
+                    default:
+                        logger.debug("Camera at IP:{} gave a reply with a response code of :{}",
+                                ipCameraHandler.cameraConfig.getIp(), response.status().code());
+                        break;
+                }
             }
         }
         // Pass the Message back to the pipeline for the next handler to process//
         super.channelRead(ctx, msg);
     }
-
-    @Override
-    public void handlerAdded(@Nullable ChannelHandlerContext ctx) {
-    }
-
-    @Override
-    public void handlerRemoved(@Nullable ChannelHandlerContext ctx) {
-    }
 }
index 92c2c462ccc7c2defa27f74e76f4030e3fbc940e..b96ff172083e37e96b5bde5e58cfc797c56f2167 100644 (file)
@@ -23,6 +23,7 @@ import java.math.BigDecimal;
 import java.net.InetSocketAddress;
 import java.net.MalformedURLException;
 import java.net.URL;
+import java.time.Duration;
 import java.time.Instant;
 import java.util.ArrayList;
 import java.util.Collection;
@@ -123,7 +124,7 @@ import io.netty.util.concurrent.GlobalEventExecutor;
 public class IpCameraHandler extends BaseThingHandler {
     public final Logger logger = LoggerFactory.getLogger(getClass());
     public final IpCameraDynamicStateDescriptionProvider stateDescriptionProvider;
-    private ScheduledExecutorService threadPool = Executors.newScheduledThreadPool(4);
+    private ScheduledExecutorService threadPool = Executors.newScheduledThreadPool(2);
     private GroupTracker groupTracker;
     public CameraConfig cameraConfig = new CameraConfig();
 
@@ -140,6 +141,7 @@ public class IpCameraHandler extends BaseThingHandler {
     public @Nullable Ffmpeg ffmpegSnapshot = null;
     public boolean streamingAutoFps = false;
     public boolean motionDetected = false;
+    public Instant lastSnapshotRequest = Instant.now();
     public Instant currentSnapshotTime = Instant.now();
     private @Nullable ScheduledFuture<?> cameraConnectionJob = null;
     private @Nullable ScheduledFuture<?> pollCameraJob = null;
@@ -197,7 +199,6 @@ public class IpCameraHandler extends BaseThingHandler {
         private String boundary = "";
         private Object reply = new Object();
         private String requestUrl = "";
-        private boolean closeConnection = true;
         private boolean isChunked = false;
 
         public void setURL(String url) {
@@ -223,11 +224,6 @@ public class IpCameraHandler extends BaseThingHandler {
                                     case "content-length":
                                         bytesToRecieve = Integer.parseInt(response.headers().getAsString(name));
                                         break;
-                                    case "connection":
-                                        if (response.headers().getAsString(name).contains("keep-alive")) {
-                                            closeConnection = false;
-                                        }
-                                        break;
                                     case "transfer-encoding":
                                         if (response.headers().getAsString(name).contains("chunked")) {
                                             isChunked = true;
@@ -236,7 +232,6 @@ public class IpCameraHandler extends BaseThingHandler {
                                 }
                             }
                             if (contentType.contains("multipart")) {
-                                closeConnection = false;
                                 if (mjpegUri.equals(requestUrl)) {
                                     if (msg instanceof HttpMessage) {
                                         // very start of stream only
@@ -278,14 +273,7 @@ public class IpCameraHandler extends BaseThingHandler {
                             }
                             if (content instanceof LastHttpContent) {
                                 processSnapshot(incomingJpeg);
-                                // testing next line and if works need to do a full cleanup of this function.
-                                closeConnection = true;
-                                if (closeConnection) {
-                                    ctx.close();
-                                } else {
-                                    bytesToRecieve = 0;
-                                    bytesAlreadyRecieved = 0;
-                                }
+                                ctx.close();
                             }
                         } else { // incomingMessage that is not an IMAGE
                             if (incomingMessage.isEmpty()) {
@@ -353,18 +341,6 @@ public class IpCameraHandler extends BaseThingHandler {
             }
         }
 
-        @Override
-        public void channelReadComplete(@Nullable ChannelHandlerContext ctx) {
-        }
-
-        @Override
-        public void handlerAdded(@Nullable ChannelHandlerContext ctx) {
-        }
-
-        @Override
-        public void handlerRemoved(@Nullable ChannelHandlerContext ctx) {
-        }
-
         @Override
         public void exceptionCaught(@Nullable ChannelHandlerContext ctx, @Nullable Throwable cause) {
             if (cause == null || ctx == null) {
@@ -394,9 +370,6 @@ public class IpCameraHandler extends BaseThingHandler {
                         case DAHUA_THING:
                             urlToKeepOpen = "/cgi-bin/eventManager.cgi?action=attach&codes=[All]";
                             break;
-                        case HIKVISION_THING:
-                            urlToKeepOpen = "/ISAPI/Event/notification/alertStream";
-                            break;
                         case DOORBIRD_THING:
                             urlToKeepOpen = "/bha-api/monitor.cgi?ring=doorbell,motionsensor";
                             break;
@@ -407,6 +380,7 @@ public class IpCameraHandler extends BaseThingHandler {
                             return; // don't auto close this as it is for the alarms.
                         }
                     }
+                    logger.debug("Closing an idle channel for camera:{}", cameraConfig.getIp());
                     ctx.close();
                 }
             }
@@ -515,6 +489,10 @@ public class IpCameraHandler extends BaseThingHandler {
     }
 
     private void checkCameraConnection() {
+        if (snapshotUri.isEmpty() || snapshotPolling) {
+            // Already polling or camera has RTSP only and no HTTP server
+            return;
+        }
         Bootstrap localBootstrap = mainBootstrap;
         if (localBootstrap != null) {
             ChannelFuture chFuture = localBootstrap
@@ -688,7 +666,8 @@ public class IpCameraHandler extends BaseThingHandler {
     }
 
     public void openCamerasStream() {
-        threadPool.schedule(this::openMjpegStream, 500, TimeUnit.MILLISECONDS);
+        closeChannel(getTinyUrl(mjpegUri));
+        mainEventLoopGroup.schedule(this::openMjpegStream, 0, TimeUnit.MILLISECONDS);
     }
 
     private void openMjpegStream() {
@@ -719,7 +698,7 @@ public class IpCameraHandler extends BaseThingHandler {
      * open large amounts of channels. This may help to keep it under control and WARN the user every 8 seconds this is
      * still occurring.
      */
-    void cleanChannels() {
+    private void cleanChannels() {
         for (Channel channel : openChannels) {
             boolean oldChannel = true;
             for (ChannelTracking channelTracking : channelTrackingMap.values()) {
@@ -727,7 +706,7 @@ public class IpCameraHandler extends BaseThingHandler {
                     channelTrackingMap.remove(channelTracking.getRequestUrl());
                 }
                 if (channelTracking.getChannel() == channel) {
-                    logger.trace("Open channel to camera is used for URL:{}", channelTracking.getRequestUrl());
+                    logger.debug("Open channel to camera is used for URL:{}", channelTracking.getRequestUrl());
                     oldChannel = false;
                 }
             }
@@ -977,7 +956,7 @@ public class IpCameraHandler extends BaseThingHandler {
         if (cameraConfig.getUpdateImageWhen().contains("2")) {
             if (!firstMotionAlarm) {
                 if (!snapshotUri.isEmpty()) {
-                    sendHttpGET(snapshotUri);
+                    updateSnapshot();
                 }
                 firstMotionAlarm = true;// reset back to false when the jpg arrives.
             }
@@ -995,7 +974,7 @@ public class IpCameraHandler extends BaseThingHandler {
         if (cameraConfig.getUpdateImageWhen().contains("3")) {
             if (!firstAudioAlarm) {
                 if (!snapshotUri.isEmpty()) {
-                    sendHttpGET(snapshotUri);
+                    updateSnapshot();
                 }
                 firstAudioAlarm = true;// reset back to false when the jpg arrives.
             }
@@ -1161,7 +1140,7 @@ public class IpCameraHandler extends BaseThingHandler {
                             updateImageChannel = false;
                         } else {
                             updateImageChannel = true;
-                            sendHttpGET(snapshotUri);// Allows this to change Image FPS on demand
+                            updateSnapshot();// Allows this to change Image FPS on demand
                         }
                     } else {
                         Ffmpeg localSnaps = ffmpegSnapshot;
@@ -1194,7 +1173,7 @@ public class IpCameraHandler extends BaseThingHandler {
                             return;
                         }
                         onvifCamera.setAbsolutePan(Float.valueOf(command.toString()));
-                        threadPool.schedule(this::sendPTZRequest, 500, TimeUnit.MILLISECONDS);
+                        mainEventLoopGroup.schedule(this::sendPTZRequest, 500, TimeUnit.MILLISECONDS);
                     }
                     return;
                 case CHANNEL_TILT:
@@ -1219,7 +1198,7 @@ public class IpCameraHandler extends BaseThingHandler {
                             return;
                         }
                         onvifCamera.setAbsoluteTilt(Float.valueOf(command.toString()));
-                        threadPool.schedule(this::sendPTZRequest, 500, TimeUnit.MILLISECONDS);
+                        mainEventLoopGroup.schedule(this::sendPTZRequest, 500, TimeUnit.MILLISECONDS);
                     }
                     return;
                 case CHANNEL_ZOOM:
@@ -1244,7 +1223,7 @@ public class IpCameraHandler extends BaseThingHandler {
                             return;
                         }
                         onvifCamera.setAbsoluteZoom(Float.valueOf(command.toString()));
-                        threadPool.schedule(this::sendPTZRequest, 500, TimeUnit.MILLISECONDS);
+                        mainEventLoopGroup.schedule(this::sendPTZRequest, 500, TimeUnit.MILLISECONDS);
                     }
                     return;
             }
@@ -1316,11 +1295,14 @@ public class IpCameraHandler extends BaseThingHandler {
         Future<?> localFuture = cameraConnectionJob;
         if (localFuture != null) {
             localFuture.cancel(false);
+            cameraConnectionJob = null;
         }
-        if (cameraConfig.getGifPreroll() > 0 || cameraConfig.getUpdateImageWhen().contains("1")) {
-            snapshotPolling = true;
-            snapshotJob = threadPool.scheduleWithFixedDelay(this::snapshotRunnable, 1000, cameraConfig.getPollTime(),
-                    TimeUnit.MILLISECONDS);
+        if (!snapshotUri.isEmpty()) {
+            if (cameraConfig.getGifPreroll() > 0 || cameraConfig.getUpdateImageWhen().contains("1")) {
+                snapshotPolling = true;
+                snapshotJob = threadPool.scheduleWithFixedDelay(this::snapshotRunnable, 1000,
+                        cameraConfig.getPollTime(), TimeUnit.MILLISECONDS);
+            }
         }
 
         pollCameraJob = threadPool.scheduleWithFixedDelay(this::pollCameraRunnable, 1000, 8000, TimeUnit.MILLISECONDS);
@@ -1341,10 +1323,10 @@ public class IpCameraHandler extends BaseThingHandler {
     }
 
     void snapshotIsFfmpeg() {
-        bringCameraOnline();
         snapshotUri = "";// ffmpeg is a valid option. Simplify further checks.
         logger.debug(
                 "Binding has no snapshot url. Will use your CPU and FFmpeg to create snapshots from the cameras RTSP.");
+        bringCameraOnline();
         if (!rtspUri.isEmpty()) {
             updateImageChannel = false;
             ffmpegSnapshotGeneration = true;
@@ -1363,7 +1345,7 @@ public class IpCameraHandler extends BaseThingHandler {
             if (snapshotUri.isEmpty() || "ffmpeg".equals(snapshotUri)) {
                 snapshotIsFfmpeg();
             } else {
-                sendHttpRequest("GET", snapshotUri, null);
+                updateSnapshot();
             }
             return;
         }
@@ -1375,7 +1357,7 @@ public class IpCameraHandler extends BaseThingHandler {
         if ("ffmpeg".equals(snapshotUri)) {
             snapshotIsFfmpeg();
         } else if (!snapshotUri.isEmpty()) {
-            sendHttpRequest("GET", snapshotUri, null);
+            updateSnapshot();
         } else if (!rtspUri.isEmpty()) {
             snapshotIsFfmpeg();
         } else {
@@ -1410,7 +1392,7 @@ public class IpCameraHandler extends BaseThingHandler {
 
     void snapshotRunnable() {
         // Snapshot should be first to keep consistent time between shots
-        sendHttpGET(snapshotUri);
+        updateSnapshot();
         if (snapCount > 0) {
             if (--snapCount == 0) {
                 setupFfmpegFormat(FFmpegFormat.GIF);
@@ -1418,9 +1400,18 @@ public class IpCameraHandler extends BaseThingHandler {
         }
     }
 
+    private void takeSnapshot() {
+        sendHttpGET(snapshotUri);
+    }
+
+    private void updateSnapshot() {
+        lastSnapshotRequest = Instant.now();
+        mainEventLoopGroup.schedule(this::takeSnapshot, 0, TimeUnit.MILLISECONDS);
+    }
+
     public byte[] getSnapshot() {
         if (!isOnline) {
-            // Keep streams open when the camera goes offline so they dont stop.
+            // Single gray pixel JPG to keep streams open when the camera goes offline so they dont stop.
             return new byte[] { (byte) 0xff, (byte) 0xd8, (byte) 0xff, (byte) 0xe0, 0x00, 0x10, 0x4a, 0x46, 0x49, 0x46,
                     0x00, 0x01, 0x01, 0x01, 0x00, 0x48, 0x00, 0x48, 0x00, 0x00, (byte) 0xff, (byte) 0xdb, 0x00, 0x43,
                     0x00, 0x03, 0x02, 0x02, 0x02, 0x02, 0x02, 0x03, 0x02, 0x02, 0x02, 0x03, 0x03, 0x03, 0x03, 0x04,
@@ -1431,8 +1422,10 @@ public class IpCameraHandler extends BaseThingHandler {
                     (byte) 0xff, (byte) 0xcc, 0x00, 0x06, 0x00, 0x10, 0x10, 0x05, (byte) 0xff, (byte) 0xda, 0x00, 0x08,
                     0x01, 0x01, 0x00, 0x00, 0x3f, 0x00, (byte) 0xd2, (byte) 0xcf, 0x20, (byte) 0xff, (byte) 0xd9 };
         }
-        if (!snapshotPolling && !ffmpegSnapshotGeneration) {
-            sendHttpGET(snapshotUri);
+        // Most cameras will return a 503 busy error if snapshot is faster than 1 second
+        long lastUpdatedMs = Duration.between(lastSnapshotRequest, Instant.now()).toMillis();
+        if (!snapshotPolling && !ffmpegSnapshotGeneration && lastUpdatedMs >= cameraConfig.getPollTime()) {
+            updateSnapshot();
         }
         lockCurrentSnapshot.lock();
         try {
@@ -1464,26 +1457,19 @@ public class IpCameraHandler extends BaseThingHandler {
         if (snapshotPolling || ffmpegSnapshotGeneration) {
             return; // Already polling or creating with FFmpeg from RTSP
         }
-        if (streamingSnapshotMjpeg || streamingAutoFps) {
+        if (streamingSnapshotMjpeg || streamingAutoFps || cameraConfig.getUpdateImageWhen().contains("4")) {
             snapshotPolling = true;
-            snapshotJob = threadPool.scheduleWithFixedDelay(this::snapshotRunnable, 200, cameraConfig.getPollTime(),
-                    TimeUnit.MILLISECONDS);
-        } else if (cameraConfig.getUpdateImageWhen().contains("4")) { // During Motion Alarms
-            snapshotPolling = true;
-            snapshotJob = threadPool.scheduleWithFixedDelay(this::snapshotRunnable, 200, cameraConfig.getPollTime(),
+            snapshotJob = threadPool.scheduleWithFixedDelay(this::snapshotRunnable, 0, cameraConfig.getPollTime(),
                     TimeUnit.MILLISECONDS);
         }
     }
 
     /**
-     * {@link pollCameraRunnable} Polls every 8 seconds, to check camera is still ONLINE and keep mjpeg and alarm
+     * {@link pollCameraRunnable} Polls every 8 seconds, to check camera is still ONLINE and keep alarm
      * streams open and more.
      *
      */
     void pollCameraRunnable() {
-        if (!snapshotUri.isEmpty() && !snapshotPolling) {// we need to check camera is still online.
-            checkCameraConnection();
-        }
         // NOTE: Use lowPriorityRequests if get request is not needed every poll.
         if (!lowPriorityRequests.isEmpty()) {
             if (lowPriorityCounter >= lowPriorityRequests.size()) {
@@ -1494,13 +1480,29 @@ public class IpCameraHandler extends BaseThingHandler {
         // what needs to be done every poll//
         switch (thing.getThingTypeUID().getId()) {
             case GENERIC_THING:
+                if (!snapshotUri.isEmpty() && !snapshotPolling) {
+                    checkCameraConnection();
+                }
+                // RTSP stream has stopped and we need it for snapshots
+                if (ffmpegSnapshotGeneration) {
+                    Ffmpeg localSnapshot = ffmpegSnapshot;
+                    if (localSnapshot != null && !localSnapshot.getIsAlive()) {
+                        localSnapshot.startConverting();
+                    }
+                }
                 break;
             case ONVIF_THING:
+                if (!snapshotPolling) {
+                    checkCameraConnection();
+                }
                 if (!onvifCamera.isConnected()) {
                     onvifCamera.connect(true);
                 }
                 break;
             case INSTAR_THING:
+                if (!snapshotPolling) {
+                    checkCameraConnection();
+                }
                 noMotionDetected(CHANNEL_MOTION_ALARM);
                 noMotionDetected(CHANNEL_PIR_ALARM);
                 noAudioDetected();
@@ -1517,6 +1519,9 @@ public class IpCameraHandler extends BaseThingHandler {
                 sendHttpGET("/cgi-bin/eventManager.cgi?action=getEventIndexes&code=AudioMutation");
                 break;
             case DAHUA_THING:
+                if (!snapshotPolling) {
+                    checkCameraConnection();
+                }
                 // Check for alarms, channel for NVRs appears not to work at filtering.
                 if (streamIsStopped("/cgi-bin/eventManager.cgi?action=attach&codes=[All]")) {
                     logger.info("The alarm stream was not running for camera {}, re-starting it now",
@@ -1525,6 +1530,9 @@ public class IpCameraHandler extends BaseThingHandler {
                 }
                 break;
             case DOORBIRD_THING:
+                if (!snapshotPolling) {
+                    checkCameraConnection();
+                }
                 // Check for alarms, channel for NVRs appears not to work at filtering.
                 if (streamIsStopped("/bha-api/monitor.cgi?ring=doorbell,motionsensor")) {
                     logger.info("The alarm stream was not running for camera {}, re-starting it now",
@@ -1541,7 +1549,7 @@ public class IpCameraHandler extends BaseThingHandler {
         if (localHLS != null) {
             localHLS.checkKeepAlive();
         }
-        if (openChannels.size() > 18) {
+        if (openChannels.size() > 10) {
             logger.debug("There are {} open Channels being tracked.", openChannels.size());
             cleanChannels();
         }
@@ -1550,7 +1558,7 @@ public class IpCameraHandler extends BaseThingHandler {
     @Override
     public void initialize() {
         cameraConfig = getConfigAs(CameraConfig.class);
-        threadPool = Executors.newScheduledThreadPool(4);
+        threadPool = Executors.newScheduledThreadPool(2);
         mainEventLoopGroup = new NioEventLoopGroup(3);
         snapshotUri = getCorrectUrlFormat(cameraConfig.getSnapshotUrl());
         mjpegUri = getCorrectUrlFormat(cameraConfig.getMjpegUrl());
index 81fdc50c5e6997d095417103f1dff7ba71cf1ecc..95a0e109f2198a090d2666e3dd37bd134d4f5eb2 100644 (file)
@@ -333,8 +333,6 @@ public class OnvifConnection {
             }
         } else if (message.contains("GetEventPropertiesResponse")) {
             sendOnvifRequest(requestBuilder(RequestType.CreatePullPointSubscription, eventXAddr));
-        } else if (message.contains("SubscribeResponse")) {
-            logger.info("Onvif Subscribe appears to be working for Alarms/Events.");
         } else if (message.contains("CreatePullPointSubscriptionResponse")) {
             subscriptionXAddr = removeIPfromUrl(Helper.fetchXML(message, "SubscriptionReference>", "Address>"));
             logger.debug("subscriptionXAddr={}", subscriptionXAddr);
@@ -863,7 +861,7 @@ public class OnvifConnection {
                 sendOnvifRequest(requestBuilder(RequestType.Unsubscribe, subscriptionXAddr));
             }
             // give time for the Unsubscribe request to be sent to the camera.
-            threadPool.schedule(this::cleanup, 100, TimeUnit.MILLISECONDS);
+            threadPool.schedule(this::cleanup, 50, TimeUnit.MILLISECONDS);
         } else {
             cleanup();
         }
index d8f439151c553e1eed824720e10bc2290aca267e..f66e2f80b64edc161621fe7c1f4bf25fba482891 100644 (file)
@@ -160,10 +160,12 @@ public class CameraServlet extends IpCameraServlet {
                 do {
                     try {
                         output.sendSnapshotBasedFrame(handler.getSnapshot());
-                        Thread.sleep(1005);
+                        Thread.sleep(handler.cameraConfig.getPollTime());
                     } catch (InterruptedException | IOException e) {
                         // Never stop streaming until IOException. Occurs when browser stops the stream.
                         openSnapshotStreams.removeStream(output);
+                        logger.debug("Now there are {} snapshots.mjpeg streams open.",
+                                openSnapshotStreams.getNumberOfStreams());
                         if (openSnapshotStreams.isEmpty()) {
                             handler.streamingSnapshotMjpeg = false;
                             handler.stopSnapshotPolling();
@@ -187,8 +189,9 @@ public class CameraServlet extends IpCameraServlet {
                 } else {
                     ChannelTracking tracker = handler.channelTrackingMap.get(handler.mjpegUri);
                     if (tracker == null || !tracker.getChannel().isOpen()) {
-                        logger.warn("Not the first stream requested but the stream from camera was closed");
+                        logger.debug("Not the first stream requested but the stream from camera was closed");
                         handler.openCamerasStream();
+                        openStreams.closeAllStreams();
                     }
                     output = new StreamOutput(resp, handler.mjpegContentType);
                     openStreams.addStream(output);
@@ -199,6 +202,7 @@ public class CameraServlet extends IpCameraServlet {
                     } catch (InterruptedException | IOException e) {
                         // Never stop streaming until IOException. Occurs when browser stops the stream.
                         openStreams.removeStream(output);
+                        logger.debug("Now there are {} ipcamera.mjpeg streams open.", openStreams.getNumberOfStreams());
                         if (openStreams.isEmpty()) {
                             if (output.isSnapshotBased) {
                                 Ffmpeg localMjpeg = handler.ffmpegMjpeg;
@@ -231,6 +235,8 @@ public class CameraServlet extends IpCameraServlet {
                     } catch (InterruptedException | IOException e) {
                         // Never stop streaming until IOException. Occurs when browser stops the stream.
                         openAutoFpsStreams.removeStream(output);
+                        logger.debug("Now there are {} autofps.mjpeg streams open.",
+                                openAutoFpsStreams.getNumberOfStreams());
                         if (openAutoFpsStreams.isEmpty()) {
                             handler.streamingAutoFps = false;
                             logger.debug("All autofps.mjpeg streams have stopped.");
index c2a30bd9865e6231b1dae4a0ddcca72f5e5e6f0a..03d7a82387af7f2601d9316400c4457388fb0527 100644 (file)
@@ -73,7 +73,12 @@ public class StreamOutput {
     }
 
     public void queueFrame(byte[] frame) {
-        fifo.add(frame);
+        try {
+            fifo.add(frame);
+        } catch (IllegalStateException e) {
+            fifo.remove();
+            fifo.add(frame);
+        }
     }
 
     public void updateContentType(String contentType) {
index 81a64fedaefe749356bf831e6455a89294a59607..c5b4dd7f40be39ac22e6212a07e45a56dcd4eaf1 100644 (file)
                                        Default is "1000" which is 1 second.
                                </description>
                                <default>1000</default>
+                               <advanced>true</advanced>
                        </parameter>
 
                </config-description>
                                        Default is "1000" which is 1 second.
                                </description>
                                <default>1000</default>
+                               <advanced>true</advanced>
                        </parameter>
 
                        <parameter name="ptzContinuous" type="boolean" groupName="Settings">
                                        Default is "1000" which is 1 second.
                                </description>
                                <default>1000</default>
+                               <advanced>true</advanced>
                        </parameter>
 
                </config-description>
                                        Default is "1000" which is 1 second.
                                </description>
                                <default>1000</default>
+                               <advanced>true</advanced>
                        </parameter>
 
                </config-description>
                                        Default is "1000" which is 1 second.
                                </description>
                                <default>1000</default>
+                               <advanced>true</advanced>
                        </parameter>
 
                </config-description>
                                        Default is "1000" which is 1 second.
                                </description>
                                <default>1000</default>
+                               <advanced>true</advanced>
                        </parameter>
 
                </config-description>
                                        Default is "1000" which is 1 second.
                                </description>
                                <default>1000</default>
+                               <advanced>true</advanced>
                        </parameter>
 
                </config-description>
                                        Default is "1000" which is 1 second.
                                </description>
                                <default>1000</default>
+                               <advanced>true</advanced>
                        </parameter>
                </config-description>
        </thing-type>