[ipcamera] Improvements and fix 503 errors go to offline with Hik (#11419)
* 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>
This commit is contained in:
parent
fb2263622b
commit
04ba8d3e5e
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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":
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -196,9 +196,6 @@ public class HikvisionHandler extends ChannelDuplexHandler {
|
|||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
logger.debug("Unhandled reply-{}.", content);
|
||||
break;
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
|
|
|
@ -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) {
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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.");
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -313,6 +313,7 @@
|
|||
Default is "1000" which is 1 second.
|
||||
</description>
|
||||
<default>1000</default>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
|
||||
</config-description>
|
||||
|
@ -559,6 +560,7 @@
|
|||
Default is "1000" which is 1 second.
|
||||
</description>
|
||||
<default>1000</default>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
|
||||
<parameter name="ptzContinuous" type="boolean" groupName="Settings">
|
||||
|
@ -859,6 +861,7 @@
|
|||
Default is "1000" which is 1 second.
|
||||
</description>
|
||||
<default>1000</default>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
|
||||
</config-description>
|
||||
|
@ -1149,6 +1152,7 @@
|
|||
Default is "1000" which is 1 second.
|
||||
</description>
|
||||
<default>1000</default>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
|
||||
</config-description>
|
||||
|
@ -1408,6 +1412,7 @@
|
|||
Default is "1000" which is 1 second.
|
||||
</description>
|
||||
<default>1000</default>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
|
||||
</config-description>
|
||||
|
@ -1685,6 +1690,7 @@
|
|||
Default is "1000" which is 1 second.
|
||||
</description>
|
||||
<default>1000</default>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
|
||||
</config-description>
|
||||
|
@ -1969,6 +1975,7 @@
|
|||
Default is "1000" which is 1 second.
|
||||
</description>
|
||||
<default>1000</default>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
|
||||
</config-description>
|
||||
|
@ -2237,6 +2244,7 @@
|
|||
Default is "1000" which is 1 second.
|
||||
</description>
|
||||
<default>1000</default>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
</config-description>
|
||||
</thing-type>
|
||||
|
|
Loading…
Reference in New Issue