[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:
Matthew Skinner 2021-10-24 20:36:20 +11:00 committed by GitHub
parent fb2263622b
commit 04ba8d3e5e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 125 additions and 96 deletions

View 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;
}

View 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":

View 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);

View File

@ -196,9 +196,6 @@ public class HikvisionHandler extends ChannelDuplexHandler {
}
}
break;
default:
logger.debug("Unhandled reply-{}.", content);
break;
}
}
} finally {

View 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) {
}
}

View 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());

View 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();
}

View 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.");

View 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) {

View File

@ -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>