From 3a9c16aeeaef051d35cd3862f1ddf5e0fd3047eb Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Mon, 10 Jan 2022 23:22:37 -0600 Subject: [PATCH 01/18] First working Janus build --- CMakeLists.txt | 2 +- db/zm_create.sql.in | 1 + db/zm_update-1.37.8.sql | 18 + src/zm_monitor.cpp | 210 +- src/zm_monitor.h | 12 +- web/ajax/modals/function.php | 10 + web/includes/Monitor.php | 1 + web/includes/functions.php | 2 + web/js/MonitorStream.js | 66 + web/js/janus.js | 3649 +++++++++++++++++++++ web/lang/en_gb.php | 5 + web/skins/classic/views/js/montage.js | 109 +- web/skins/classic/views/js/montage.js.php | 1 + web/skins/classic/views/js/watch.js.php | 1 + web/skins/classic/views/monitor.php | 10 + web/skins/classic/views/montage.php | 2 + 16 files changed, 4047 insertions(+), 52 deletions(-) create mode 100644 db/zm_update-1.37.8.sql create mode 100644 web/js/janus.js diff --git a/CMakeLists.txt b/CMakeLists.txt index 0479ce1fd..7df6aaa70 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -321,7 +321,7 @@ if(NOT ZM_NO_CURL) find_package(CURL) if(CURL_FOUND) set(HAVE_LIBCURL 1) - #list(APPEND ZM_BIN_LIBS ${CURL_LIBRARIES}) + list(APPEND ZM_BIN_LIBS ${CURL_LIBRARIES}) include_directories(${CURL_INCLUDE_DIRS}) set(CMAKE_REQUIRED_INCLUDES ${CURL_INCLUDE_DIRS}) check_include_file("curl/curl.h" HAVE_CURL_CURL_H) diff --git a/db/zm_create.sql.in b/db/zm_create.sql.in index 110365125..06d2ab385 100644 --- a/db/zm_create.sql.in +++ b/db/zm_create.sql.in @@ -455,6 +455,7 @@ CREATE TABLE `Monitors` ( `Function` enum('None','Monitor','Modect','Record','Mocord','Nodect') NOT NULL default 'Monitor', `Enabled` tinyint(3) unsigned NOT NULL default '1', `DecodingEnabled` tinyint(3) unsigned NOT NULL default '1', + `JanusEnabled` BOOLEAN NOT NULL default false, `LinkedMonitors` varchar(255), `Triggers` set('X10') NOT NULL default '', `EventStartCommand` VARCHAR(255) NOT NULL DEFAULT '', diff --git a/db/zm_update-1.37.8.sql b/db/zm_update-1.37.8.sql new file mode 100644 index 000000000..650489e7b --- /dev/null +++ b/db/zm_update-1.37.8.sql @@ -0,0 +1,18 @@ +-- +-- Update Monitors table to have JanusEnabled +-- + +SELECT 'Checking for JanusEnabled in Monitors'; +SET @s = (SELECT IF( + (SELECT COUNT(*) + FROM INFORMATION_SCHEMA.COLUMNS + WHERE table_name = 'Monitors' + AND table_schema = DATABASE() + AND column_name = 'JanusEnabled' + ) > 0, +"SELECT 'Column JanusEnabled already exists in Monitors'", +"ALTER TABLE `Monitors` ADD COLUMN `JanusEnabled` BOOLEAN NOT NULL default false AFTER `DecodingEnabled`" +)); + +PREPARE stmt FROM @s; +EXECUTE stmt; diff --git a/src/zm_monitor.cpp b/src/zm_monitor.cpp index e07b872fc..80770a964 100644 --- a/src/zm_monitor.cpp +++ b/src/zm_monitor.cpp @@ -77,7 +77,7 @@ struct Namespace namespaces[] = // This is the official SQL (and ordering of the fields) to load a Monitor. // It will be used whereever a Monitor dbrow is needed. WHERE conditions can be appended std::string load_monitor_sql = -"SELECT `Id`, `Name`, `ServerId`, `StorageId`, `Type`, `Function`+0, `Enabled`, `DecodingEnabled`, " +"SELECT `Id`, `Name`, `ServerId`, `StorageId`, `Type`, `Function`+0, `Enabled`, `DecodingEnabled`, `JanusEnabled`," "`LinkedMonitors`, `EventStartCommand`, `EventEndCommand`, `AnalysisFPSLimit`, `AnalysisUpdateDelay`, `MaxFPS`, `AlarmMaxFPS`," "`Device`, `Channel`, `Format`, `V4LMultiBuffer`, `V4LCapturesPerFrame`, " // V4L Settings "`Protocol`, `Method`, `Options`, `User`, `Pass`, `Host`, `Port`, `Path`, `SecondPath`, `Width`, `Height`, `Colours`, `Palette`, `Orientation`+0, `Deinterlacing`, " @@ -306,6 +306,7 @@ Monitor::Monitor() function(NONE), enabled(false), decoding_enabled(false), + janus_enabled(false), //protocol //method //options @@ -446,7 +447,7 @@ Monitor::Monitor() /* std::string load_monitor_sql = - "SELECT Id, Name, ServerId, StorageId, Type, Function+0, Enabled, DecodingEnabled, LinkedMonitors, `EventStartCommand`, `EventEndCommand`, " + "SELECT Id, Name, ServerId, StorageId, Type, Function+0, Enabled, DecodingEnabled, JanusEnabled, LinkedMonitors, `EventStartCommand`, `EventEndCommand`, " "AnalysisFPSLimit, AnalysisUpdateDelay, MaxFPS, AlarmMaxFPS," "Device, Channel, Format, V4LMultiBuffer, V4LCapturesPerFrame, " // V4L Settings "Protocol, Method, Options, User, Pass, Host, Port, Path, SecondPath, Width, Height, Colours, Palette, Orientation+0, Deinterlacing, RTSPDescribe, " @@ -487,7 +488,7 @@ void Monitor::Load(MYSQL_ROW dbrow, bool load_zones=true, Purpose p = QUERY) { } else if ( ! strcmp(dbrow[col], "Libvlc") ) { type = LIBVLC; } else if ( ! strcmp(dbrow[col], "cURL") ) { - type = CURL; + type = LIBCURL; } else if ( ! strcmp(dbrow[col], "VNC") ) { type = VNC; } else { @@ -499,6 +500,7 @@ void Monitor::Load(MYSQL_ROW dbrow, bool load_zones=true, Purpose p = QUERY) { enabled = dbrow[col] ? atoi(dbrow[col]) : false; col++; decoding_enabled = dbrow[col] ? atoi(dbrow[col]) : false; col++; // See below after save_jpegs for a recalculation of decoding_enabled + janus_enabled = dbrow[col] ? atoi(dbrow[col]) : false; col++; ReloadLinkedMonitors(dbrow[col]); col++; event_start_command = dbrow[col] ? dbrow[col] : ""; col++; @@ -859,7 +861,7 @@ void Monitor::LoadCamera() { #endif // HAVE_LIBVLC break; } - case CURL: { + case LIBCURL: { #if HAVE_LIBCURL camera = zm::make_unique(this, path.c_str(), @@ -1074,44 +1076,55 @@ bool Monitor::connect() { usedsubpixorder = camera->SubpixelOrder(); // Used in CheckSignal shared_data->valid = true; - //ONVIF Setup #ifdef WITH_GSOAP - ONVIF_Trigger_State = FALSE; - if (onvif_event_listener) { //Temporarily using this option to enable the feature - Debug(1, "Starting ONVIF"); - ONVIF_Healthy = FALSE; - if (onvif_options.find("closes_event") != std::string::npos) { //Option to indicate that ONVIF will send a close event message - ONVIF_Closes_Event = TRUE; - } - tev__PullMessages.Timeout = "PT600S"; - tev__PullMessages.MessageLimit = 100; - soap = soap_new(); - soap->connect_timeout = 5; - soap->recv_timeout = 5; - soap->send_timeout = 5; - soap_register_plugin(soap, soap_wsse); - proxyEvent = PullPointSubscriptionBindingProxy(soap); - std::string full_url = onvif_url + "/Events"; - proxyEvent.soap_endpoint = full_url.c_str(); - set_credentials(soap); - Debug(1, "ONVIF Endpoint: %s", proxyEvent.soap_endpoint); - if (proxyEvent.CreatePullPointSubscription(&request, response) != SOAP_OK) { - Warning("Couldn't create subscription!"); - } else { - //Empty the stored messages - set_credentials(soap); - if (proxyEvent.PullMessages(response.SubscriptionReference.Address, NULL, &tev__PullMessages, tev__PullMessagesResponse) != SOAP_OK) { - Warning("Couldn't do initial event pull! %s", response.SubscriptionReference.Address); - } else { - Debug(1, "Good Initial ONVIF Pull"); - ONVIF_Healthy = TRUE; + //ONVIF Setup + ONVIF_Trigger_State = FALSE; + if (onvif_event_listener) { //Temporarily using this option to enable the feature + Debug(1, "Starting ONVIF"); + ONVIF_Healthy = FALSE; + if (onvif_options.find("closes_event") != std::string::npos) { //Option to indicate that ONVIF will send a close event message + ONVIF_Closes_Event = TRUE; } + tev__PullMessages.Timeout = "PT600S"; + tev__PullMessages.MessageLimit = 100; + soap = soap_new(); + soap->connect_timeout = 5; + soap->recv_timeout = 5; + soap->send_timeout = 5; + soap_register_plugin(soap, soap_wsse); + proxyEvent = PullPointSubscriptionBindingProxy(soap); + std::string full_url = onvif_url + "/Events"; + proxyEvent.soap_endpoint = full_url.c_str(); + set_credentials(soap); + Debug(1, "ONVIF Endpoint: %s", proxyEvent.soap_endpoint); + if (proxyEvent.CreatePullPointSubscription(&request, response) != SOAP_OK) { + Warning("Couldn't create subscription!"); + } else { + //Empty the stored messages + set_credentials(soap); + if (proxyEvent.PullMessages(response.SubscriptionReference.Address, NULL, &tev__PullMessages, tev__PullMessagesResponse) != SOAP_OK) { + Warning("Couldn't do initial event pull! %s", response.SubscriptionReference.Address); + } else { + Debug(1, "Good Initial ONVIF Pull"); + ONVIF_Healthy = TRUE; + } + } + } else { + Debug(1, "Not Starting ONVIF"); } - } else { - Debug(1, "Not Starting ONVIF"); - } - //End ONVIF Setup + //End ONVIF Setup #endif + +//janus setup. + if (janus_enabled) { + add_to_janus(); + } +//ifdef janus, and if url contains rtsp +//look for username and password +//make the initial call, scrape id, then connect to plugin +//add stream using the same id + + } else if (!shared_data->valid) { Error("Shared data not initialised by capture daemon for monitor %s", name.c_str()); return false; @@ -3189,6 +3202,10 @@ int Monitor::Close() { soap = nullptr; } //End ONVIF #endif + //Janus Teardown + if (janus_enabled && (purpose == CAPTURE)) { + remove_from_janus(); + } packetqueue.clear(); if (audio_fifo) { @@ -3325,3 +3342,120 @@ int SOAP_ENV__Fault(struct soap *soap, char *faultcode, char *faultstring, char return soap_send_empty_response(soap, SOAP_OK); } #endif + +size_t Monitor::WriteCallback(void *contents, size_t size, size_t nmemb, void *userp) +{ + ((std::string*)userp)->append((char*)contents, size * nmemb); + return size * nmemb; +} + +int Monitor::add_to_janus() { + //TODO clean this up, add error checking, etc + std::string response; + std::string endpoint = "127.0.0.1:8088/janus/"; + std::string postData = "{\"janus\" : \"create\", \"transaction\" : \"randomString\"}"; + std::string rtsp_username; + std::string rtsp_password; + std::string rtsp_path = "rtsp://"; + std::size_t pos; + std::size_t pos2; + + curl = curl_easy_init(); + + //parse username and password + pos = path.find(":", 7); + rtsp_username = path.substr(7, pos-7); + + pos2 = path.find("@", pos); + rtsp_password = path.substr(pos+1, pos2 - pos - 1); + rtsp_path += path.substr(pos2 + 1); + + //Start Janus API init. Need to get a session_id and handle_id + curl_easy_setopt(curl, CURLOPT_URL, endpoint.c_str()); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response); + curl_easy_setopt(curl, CURLOPT_POSTFIELDS, postData.c_str()); + curl_easy_perform(curl); + pos = response.find("\"id\": "); + std::string janus_id = response.substr(pos + 6, 16); + response = ""; + endpoint += janus_id; + postData = "{\"janus\" : \"attach\", \"plugin\" : \"janus.plugin.streaming\", \"transaction\" : \"randomString\"}"; + curl_easy_setopt(curl, CURLOPT_URL,endpoint.c_str()); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response); + curl_easy_setopt(curl, CURLOPT_POSTFIELDS, postData.c_str()); + curl_easy_perform(curl); + pos = response.find("\"id\": "); + std::string handle_id = response.substr(pos + 6, 16); + endpoint += "/"; + endpoint += handle_id; + + //Assemble our actual request + postData = "{\"janus\" : \"message\", \"transaction\" : \"randomString\", \"body\" : {"; + postData += "\"request\" : \"create\", \"admin_key\" : \"supersecret\", \"type\" : \"rtsp\", "; + postData += "\"url\" : \""; + postData += rtsp_path; + postData += "\", \"rtsp_user\" : \""; + postData += rtsp_username; + postData += "\", \"rtsp_pwd\" : \""; + postData += rtsp_password; + postData += "\", \"id\" : "; + postData += std::to_string(id); + postData += ", \"video\" : true}}"; + + curl_easy_setopt(curl, CURLOPT_URL,endpoint.c_str()); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response); + curl_easy_setopt(curl, CURLOPT_POSTFIELDS, postData.c_str()); + curl_easy_perform(curl); + Warning(response.c_str()); + curl_easy_cleanup(curl); + +} +int Monitor::remove_from_janus() { + //TODO clean this up, add error checking, etc + std::string response; + std::string endpoint = "127.0.0.1:8088/janus/"; + std::string postData = "{\"janus\" : \"create\", \"transaction\" : \"randomString\"}"; + std::size_t pos; + + curl = curl_easy_init(); + + + //Start Janus API init. Need to get a session_id and handle_id + curl_easy_setopt(curl, CURLOPT_URL, endpoint.c_str()); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response); + curl_easy_setopt(curl, CURLOPT_POSTFIELDS, postData.c_str()); + curl_easy_perform(curl); + pos = response.find("\"id\": "); + std::string janus_id = response.substr(pos + 6, 16); + response = ""; + endpoint += janus_id; + postData = "{\"janus\" : \"attach\", \"plugin\" : \"janus.plugin.streaming\", \"transaction\" : \"randomString\"}"; + curl_easy_setopt(curl, CURLOPT_URL,endpoint.c_str()); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response); + curl_easy_setopt(curl, CURLOPT_POSTFIELDS, postData.c_str()); + curl_easy_perform(curl); + pos = response.find("\"id\": "); + std::string handle_id = response.substr(pos + 6, 16); + endpoint += "/"; + endpoint += handle_id; + + //Assemble our actual request + postData = "{\"janus\" : \"message\", \"transaction\" : \"randomString\", \"body\" : {"; + postData += "\"request\" : \"destroy\", \"admin_key\" : \"supersecret\", \"id\" : "; + postData += std::to_string(id); + postData += "}}"; + + curl_easy_setopt(curl, CURLOPT_URL,endpoint.c_str()); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response); + curl_easy_setopt(curl, CURLOPT_POSTFIELDS, postData.c_str()); + curl_easy_perform(curl); + Warning(response.c_str()); + curl_easy_cleanup(curl); + +} diff --git a/src/zm_monitor.h b/src/zm_monitor.h index 0396253ca..0d63816e9 100644 --- a/src/zm_monitor.h +++ b/src/zm_monitor.h @@ -34,6 +34,7 @@ #include #include #include +#include #ifdef WITH_GSOAP #include "soapPullPointSubscriptionBindingProxy.h" @@ -47,6 +48,7 @@ class Group; #define MOTION_CAUSE "Motion" #define LINKED_CAUSE "Linked" + // // This is the main class for monitors. Each monitor is associated // with a camera and is effectively a collector for events. @@ -76,7 +78,7 @@ public: FILE, FFMPEG, LIBVLC, - CURL, + LIBCURL, NVSOCKET, VNC, } CameraType; @@ -266,6 +268,7 @@ protected: Function function; // What the monitor is doing bool enabled; // Whether the monitor is enabled or asleep bool decoding_enabled; // Whether the monitor will decode h264/h265 packets + bool janus_enabled; // Whether we set the h264/h265 stream up on janus std::string protocol; std::string method; @@ -445,6 +448,13 @@ protected: void set_credentials(struct soap *soap); #endif + //curl stuff for Janus + CURL *curl; + //helper class for CURL + static size_t WriteCallback(void *contents, size_t size, size_t nmemb, void *userp); + int add_to_janus(); + int remove_from_janus(); + // Used in check signal uint8_t red_val; uint8_t green_val; diff --git a/web/ajax/modals/function.php b/web/ajax/modals/function.php index 08b470840..b23bbb649 100644 --- a/web/ajax/modals/function.php +++ b/web/ajax/modals/function.php @@ -69,6 +69,16 @@ if ( !canEdit('Monitors') ) return; } ?> + +
+ + +'.$OLANG['FUNCTION_JANUS_ENABLED']['Help'].'
'; + } +?> + '; + } ?> diff --git a/web/skins/classic/views/montage.php b/web/skins/classic/views/montage.php index deaddcaf3..e0285cbd4 100644 --- a/web/skins/classic/views/montage.php +++ b/web/skins/classic/views/montage.php @@ -325,5 +325,7 @@ foreach (array_reverse($zones) as $zone) { + + From 50c824f3bbc818acca2b28a27469fc986e40c047 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Tue, 11 Jan 2022 21:19:58 -0600 Subject: [PATCH 02/18] Janus cleanup, adds support to the "watch" view --- misc/janus.jcfg | 50 ++++++++++++++++ misc/janus.plugin.streaming.jcfg | 4 ++ misc/janus.transport.http.jcfg | 25 ++++++++ web/js/MonitorStream.js | 66 --------------------- web/js/adapter.min.js | 1 + web/skins/classic/views/js/montage.js | 2 +- web/skins/classic/views/js/watch.js | 83 ++++++++++++++++++++++++--- web/skins/classic/views/montage.php | 2 +- web/skins/classic/views/watch.php | 8 ++- 9 files changed, 164 insertions(+), 77 deletions(-) create mode 100644 misc/janus.jcfg create mode 100644 misc/janus.plugin.streaming.jcfg create mode 100644 misc/janus.transport.http.jcfg create mode 100644 web/js/adapter.min.js diff --git a/misc/janus.jcfg b/misc/janus.jcfg new file mode 100644 index 000000000..a0adffb88 --- /dev/null +++ b/misc/janus.jcfg @@ -0,0 +1,50 @@ +general: { + configs_folder = "/usr/local/etc/janus" # Configuration files folder + plugins_folder = "/usr/local/lib/janus/plugins" # Plugins folder + transports_folder = "/usr/local/lib/janus/transports" # Transports folder + events_folder = "/usr/local/lib/janus/events" # Event handlers folder + loggers_folder = "/usr/local/lib/janus/loggers" # External loggers folder + debug_level = 4 # Debug/logging level, valid values are 0-7 + admin_secret = "janusoverlord" # String that all Janus requests must contain + protected_folders = [ + "/bin", + "/boot", + "/dev", + "/etc", + "/initrd", + "/lib", + "/lib32", + "/lib64", + "/proc", + "/sbin", + "/sys", + "/usr", + "/var", + "/opt/janus/bin", + "/opt/janus/etc", + "/opt/janus/include", + "/opt/janus/lib", + "/opt/janus/lib32", + "/opt/janus/lib64", + "/opt/janus/sbin" + ] +} +media: { + #ipv6 = true + #ipv6_linklocal = true + rtp_port_range = "20000-40000" +} +nat: { + nice_debug = false + ignore_mdns = true + keep_private_host = true + ice_ignore_list = "vmnet" +} + +plugins: { + disable = "libjanus_audiobridge.so,libjanus_echotest.so,libjanus_recordplay.so,libjanus_sip.so,libjanus_textroom.so,libjanus_videocall.so,libjanus_videoroom.so,libjanus_voicemail.so, + libjanus_nosip.so" +} +transports: { + disable = "libjanus_rabbitmq.so, libjanus_pfunix.so,libjanus_websockets.so" +} diff --git a/misc/janus.plugin.streaming.jcfg b/misc/janus.plugin.streaming.jcfg new file mode 100644 index 000000000..f93b15a3e --- /dev/null +++ b/misc/janus.plugin.streaming.jcfg @@ -0,0 +1,4 @@ +general: { + admin_key = "supersecret" + rtp_port_range = "20000-40000" +} diff --git a/misc/janus.transport.http.jcfg b/misc/janus.transport.http.jcfg new file mode 100644 index 000000000..8ae9171ad --- /dev/null +++ b/misc/janus.transport.http.jcfg @@ -0,0 +1,25 @@ +general: { + json = "indented" # Whether the JSON messages should be indented (default), + base_path = "/janus" # Base path to bind to in the web server (plain HTTP only) + http = true # Whether to enable the plain HTTP interface + port = 8088 # Web server HTTP port + https = false # Whether to enable HTTPS (default=false) +} + +# Janus can also expose an admin/monitor endpoint, to allow you to check +# which sessions are up, which handles they're managing, their current +# status and so on. This provides a useful aid when debugging potential +# issues in Janus. The configuration is pretty much the same as the one +# already presented above for the webserver stuff, as the API is very +# similar: choose the base bath for the admin/monitor endpoint (/admin +# by default), ports, etc. Besides, you can specify +# a secret that must be provided in all requests as a crude form of +# authorization mechanism, and partial or full source IPs if you want to +# limit access basing on IP addresses. For security reasons, this +# endpoint is disabled by default, enable it by setting admin_http=true. +admin: { + admin_base_path = "/admin" # Base path to bind to in the admin/monitor web server (plain HTTP only) + admin_http = false # Whether to enable the plain HTTP interface + admin_port = 7088 # Admin/monitor web server HTTP port + admin_https = false # Whether to enable HTTPS (default=false) +} diff --git a/web/js/MonitorStream.js b/web/js/MonitorStream.js index 1bc96d62a..ef4d6db41 100644 --- a/web/js/MonitorStream.js +++ b/web/js/MonitorStream.js @@ -5,7 +5,6 @@ function MonitorStream(monitorData) { this.auth_relay = auth_relay; this.auth_hash = auth_hash; this.url = monitorData.url; - this.janusEnabled = monitorData.janusEnabled; this.url_to_zms = monitorData.url_to_zms; this.width = monitorData.width; this.height = monitorData.height; @@ -87,71 +86,6 @@ function MonitorStream(monitorData) { console.log(stream); return; } - if (this.janusEnabled) { - var id = parseInt(this.id); - var opaqueId = "streamingtest-"+Janus.randomString(12); - var server = "http://zm-dev:8088/janus"; - Janus.init({debug: "all", callback: function() { - janus = new Janus({ - server: server, - success: function() { - janus.attach({ - plugin: "janus.plugin.streaming", - opaqueId: opaqueId, - success: function(pluginHandle) { - streaming = pluginHandle; - var body = { "request": "watch", "id": id }; - streaming.send({"message": body}); - }, - error: function(error) { - Janus.error(" -- Error attaching plugin... ", error); - }, - onmessage: function(msg, jsep) { - Janus.debug(" ::: Got a message :::"); - Janus.debug(msg); - var result = msg["result"]; - if(result !== null && result !== undefined) { - if(result["status"] !== undefined && result["status"] !== null) { - var status = result["status"]; - } - } else if(msg["error"] !== undefined && msg["error"] !== null) { - alert(msg["error"]); - stopStream(); - return; - } - if(jsep !== undefined && jsep !== null) { - Janus.debug("Handling SDP as well..."); - Janus.debug(jsep); - // Offer from the plugin, let's answer - streaming.createAnswer({ - jsep: jsep, - // We want recvonly audio/video and, if negotiated, datachannels - media: { audioSend: false, videoSend: false, data: true }, - success: function(jsep) { - Janus.debug("Got SDP!"); - Janus.debug(jsep); - var body = { "request": "start"}; - streaming.send({"message": body, "jsep": jsep}); - }, - error: function(error) { - Janus.error("WebRTC error:", error); - alert("WebRTC error... " + JSON.stringify(error)); - } - }); - } - }, //onmessage function - onremotestream: function(ourstream) { - Janus.debug(" ::: Got a remote track :::"); - Janus.debug(ourstream); - Janus.attachMediaStream(stream, ourstream); - stream.play() - } - });// attach - } //Success functio - }); //new Janus -}}); //janus.init callback - return; - } src = stream.src.replace(/mode=single/i, 'mode=jpeg'); if ( -1 == src.search('connkey') ) { src += '&connkey='+this.connKey; diff --git a/web/js/adapter.min.js b/web/js/adapter.min.js new file mode 100644 index 000000000..bcbefc98d --- /dev/null +++ b/web/js/adapter.min.js @@ -0,0 +1 @@ +!function(e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):("undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:this).adapter=e()}(function(){return function n(i,o,a){function s(t,e){if(!o[t]){if(!i[t]){var r="function"==typeof require&&require;if(!e&&r)return r(t,!0);if(c)return c(t,!0);throw(r=new Error("Cannot find module '"+t+"'")).code="MODULE_NOT_FOUND",r}r=o[t]={exports:{}},i[t][0].call(r.exports,function(e){return s(i[t][1][e]||e)},r,r.exports,n,i,o,a)}return o[t].exports}for(var c="function"==typeof require&&require,e=0;er.sctp.maxMessageSize)throw new TypeError("Message too large (can send a maximum of "+r.sctp.maxMessageSize+" bytes)");return n.apply(t,arguments)}}e.RTCPeerConnection&&"createDataChannel"in e.RTCPeerConnection.prototype&&(t=e.RTCPeerConnection.prototype.createDataChannel,e.RTCPeerConnection.prototype.createDataChannel=function(){var e=t.apply(this,arguments);return r(e,this),e},s.wrapPeerConnectionEvent(e,"datachannel",function(e){return r(e.channel,e.target),e}))},r.shimConnectionState=function(e){var r;!e.RTCPeerConnection||"connectionState"in e.RTCPeerConnection.prototype||(r=e.RTCPeerConnection.prototype,Object.defineProperty(r,"connectionState",{get:function(){return{completed:"connected",checking:"connecting"}[this.iceConnectionState]||this.iceConnectionState},enumerable:!0,configurable:!0}),Object.defineProperty(r,"onconnectionstatechange",{get:function(){return this._onconnectionstatechange||null},set:function(e){this._onconnectionstatechange&&(this.removeEventListener("connectionstatechange",this._onconnectionstatechange),delete this._onconnectionstatechange),e&&this.addEventListener("connectionstatechange",this._onconnectionstatechange=e)},enumerable:!0,configurable:!0}),["setLocalDescription","setRemoteDescription"].forEach(function(e){var t=r[e];r[e]=function(){return this._connectionstatechangepoly||(this._connectionstatechangepoly=function(e){var t,r=e.target;return r._lastConnectionState!==r.connectionState&&(r._lastConnectionState=r.connectionState,t=new Event("connectionstatechange",e),r.dispatchEvent(t)),e},this.addEventListener("iceconnectionstatechange",this._connectionstatechangepoly)),t.apply(this,arguments)}}))},r.removeExtmapAllowMixed=function(r,e){var n;r.RTCPeerConnection&&("chrome"===e.browser&&71<=e.version||"safari"===e.browser&&605<=e.version||(n=r.RTCPeerConnection.prototype.setRemoteDescription,r.RTCPeerConnection.prototype.setRemoteDescription=function(e){var t;return e&&e.sdp&&-1!==e.sdp.indexOf("\na=extmap-allow-mixed")&&(t=e.sdp.split("\n").filter(function(e){return"a=extmap-allow-mixed"!==e.trim()}).join("\n"),r.RTCSessionDescription&&e instanceof r.RTCSessionDescription?arguments[0]=new r.RTCSessionDescription({type:e.type,sdp:t}):e.sdp=t),n.apply(this,arguments)}))},r.shimAddIceCandidateNullOrEmpty=function(e,t){var r;e.RTCPeerConnection&&e.RTCPeerConnection.prototype&&((r=e.RTCPeerConnection.prototype.addIceCandidate)&&0!==r.length&&(e.RTCPeerConnection.prototype.addIceCandidate=function(){return arguments[0]?("chrome"===t.browser&&t.version<78||"firefox"===t.browser&&t.version<68||"safari"===t.browser)&&arguments[0]&&""===arguments[0].candidate?Promise.resolve():r.apply(this,arguments):(arguments[1]&&arguments[1].apply(null),Promise.resolve())}))},r.shimParameterlessSetLocalDescription=function(e,t){var r;e.RTCPeerConnection&&e.RTCPeerConnection.prototype&&((r=e.RTCPeerConnection.prototype.setLocalDescription)&&0!==r.length&&(e.RTCPeerConnection.prototype.setLocalDescription=function(){var t=this,e=arguments[0]||{};if("object"!==(void 0===e?"undefined":o(e))||e.type&&e.sdp)return r.apply(this,arguments);if(!(e={type:e.type,sdp:e.sdp}).type)switch(this.signalingState){case"stable":case"have-local-offer":case"have-remote-pranswer":e.type="offer";break;default:e.type="answer"}return e.sdp||"offer"!==e.type&&"answer"!==e.type?r.apply(this,[e]):("offer"===e.type?this.createOffer:this.createAnswer).apply(this).then(function(e){return r.apply(t,[e])})}))};var n,i=e("sdp"),a=(n=i)&&n.__esModule?n:{default:n},s=function(e){{if(e&&e.__esModule)return e;var t={};if(null!=e)for(var r in e)Object.prototype.hasOwnProperty.call(e,r)&&(t[r]=e[r]);return t.default=e,t}}(e("./utils"))},{"./utils":11,sdp:12}],7:[function(e,t,r){"use strict";Object.defineProperty(r,"__esModule",{value:!0}),r.shimGetDisplayMedia=r.shimGetUserMedia=void 0;var a="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},n=e("./getusermedia");Object.defineProperty(r,"shimGetUserMedia",{enumerable:!0,get:function(){return n.shimGetUserMedia}});var i=e("./getdisplaymedia");Object.defineProperty(r,"shimGetDisplayMedia",{enumerable:!0,get:function(){return i.shimGetDisplayMedia}}),r.shimOnTrack=function(e){"object"===(void 0===e?"undefined":a(e))&&e.RTCTrackEvent&&"receiver"in e.RTCTrackEvent.prototype&&!("transceiver"in e.RTCTrackEvent.prototype)&&Object.defineProperty(e.RTCTrackEvent.prototype,"transceiver",{get:function(){return{receiver:this.receiver}}})},r.shimPeerConnection=function(n,i){var o,r;"object"===(void 0===n?"undefined":a(n))&&(n.RTCPeerConnection||n.mozRTCPeerConnection)&&(!n.RTCPeerConnection&&n.mozRTCPeerConnection&&(n.RTCPeerConnection=n.mozRTCPeerConnection),i.version<53&&["setLocalDescription","setRemoteDescription","addIceCandidate"].forEach(function(e){var t=n.RTCPeerConnection.prototype[e],r=function(e,t,r){t in e?Object.defineProperty(e,t,{value:r,enumerable:!0,configurable:!0,writable:!0}):e[t]=r;return e}({},e,function(){return arguments[0]=new("addIceCandidate"===e?n.RTCIceCandidate:n.RTCSessionDescription)(arguments[0]),t.apply(this,arguments)});n.RTCPeerConnection.prototype[e]=r[e]}),o={inboundrtp:"inbound-rtp",outboundrtp:"outbound-rtp",candidatepair:"candidate-pair",localcandidate:"local-candidate",remotecandidate:"remote-candidate"},r=n.RTCPeerConnection.prototype.getStats,n.RTCPeerConnection.prototype.getStats=function(){var e=Array.prototype.slice.call(arguments),t=e[0],n=e[1],e=e[2];return r.apply(this,[t||null]).then(function(r){if(i.version<53&&!n)try{r.forEach(function(e){e.type=o[e.type]||e.type})}catch(e){if("TypeError"!==e.name)throw e;r.forEach(function(e,t){r.set(t,Object.assign({},e,{type:o[e.type]||e.type}))})}return r}).then(n,e)})},r.shimSenderGetStats=function(e){var r,t;"object"===(void 0===e?"undefined":a(e))&&e.RTCPeerConnection&&e.RTCRtpSender&&(e.RTCRtpSender&&"getStats"in e.RTCRtpSender.prototype||((r=e.RTCPeerConnection.prototype.getSenders)&&(e.RTCPeerConnection.prototype.getSenders=function(){var t=this,e=r.apply(this,[]);return e.forEach(function(e){return e._pc=t}),e}),(t=e.RTCPeerConnection.prototype.addTrack)&&(e.RTCPeerConnection.prototype.addTrack=function(){var e=t.apply(this,arguments);return e._pc=this,e}),e.RTCRtpSender.prototype.getStats=function(){return this.track?this._pc.getStats(this.track):Promise.resolve(new Map)}))},r.shimReceiverGetStats=function(e){var r;"object"===(void 0===e?"undefined":a(e))&&e.RTCPeerConnection&&e.RTCRtpSender&&(e.RTCRtpSender&&"getStats"in e.RTCRtpReceiver.prototype||((r=e.RTCPeerConnection.prototype.getReceivers)&&(e.RTCPeerConnection.prototype.getReceivers=function(){var t=this,e=r.apply(this,[]);return e.forEach(function(e){return e._pc=t}),e}),o.wrapPeerConnectionEvent(e,"track",function(e){return e.receiver._pc=e.srcElement,e}),e.RTCRtpReceiver.prototype.getStats=function(){return this._pc.getStats(this.track)}))},r.shimRemoveStream=function(e){!e.RTCPeerConnection||"removeStream"in e.RTCPeerConnection.prototype||(e.RTCPeerConnection.prototype.removeStream=function(t){var r=this;o.deprecated("removeStream","removeTrack"),this.getSenders().forEach(function(e){e.track&&t.getTracks().includes(e.track)&&r.removeTrack(e)})})},r.shimRTCDataChannel=function(e){e.DataChannel&&!e.RTCDataChannel&&(e.RTCDataChannel=e.DataChannel)},r.shimAddTransceiver=function(e){var i;"object"!==(void 0===e?"undefined":a(e))||!e.RTCPeerConnection||(i=e.RTCPeerConnection.prototype.addTransceiver)&&(e.RTCPeerConnection.prototype.addTransceiver=function(){this.setParametersPromises=[];var e=arguments[1],t=e&&"sendEncodings"in e;t&&e.sendEncodings.forEach(function(e){if("rid"in e)if(!/^[a-z0-9]{0,16}$/i.test(e.rid))throw new TypeError("Invalid RID value provided.");if("scaleResolutionDownBy"in e&&!(1<=parseFloat(e.scaleResolutionDownBy)))throw new RangeError("scale_resolution_down_by must be >= 1.0");if("maxFramerate"in e&&!(0<=parseFloat(e.maxFramerate)))throw new RangeError("max_framerate must be >= 0.0")});var r,n=i.apply(this,arguments);return t&&("encodings"in(t=(r=n.sender).getParameters())&&(1!==t.encodings.length||0!==Object.keys(t.encodings[0]).length)||(t.encodings=e.sendEncodings,r.sendEncodings=e.sendEncodings,this.setParametersPromises.push(r.setParameters(t).then(function(){delete r.sendEncodings}).catch(function(){delete r.sendEncodings})))),n})},r.shimGetParameters=function(e){var t;"object"!==(void 0===e?"undefined":a(e))||!e.RTCRtpSender||(t=e.RTCRtpSender.prototype.getParameters)&&(e.RTCRtpSender.prototype.getParameters=function(){var e=t.apply(this,arguments);return"encodings"in e||(e.encodings=[].concat(this.sendEncodings||[{}])),e})},r.shimCreateOffer=function(e){var r;"object"===(void 0===e?"undefined":a(e))&&e.RTCPeerConnection&&(r=e.RTCPeerConnection.prototype.createOffer,e.RTCPeerConnection.prototype.createOffer=function(){var e=this,t=arguments;return this.setParametersPromises&&this.setParametersPromises.length?Promise.all(this.setParametersPromises).then(function(){return r.apply(e,t)}).finally(function(){e.setParametersPromises=[]}):r.apply(this,arguments)})},r.shimCreateAnswer=function(e){var r;"object"===(void 0===e?"undefined":a(e))&&e.RTCPeerConnection&&(r=e.RTCPeerConnection.prototype.createAnswer,e.RTCPeerConnection.prototype.createAnswer=function(){var e=this,t=arguments;return this.setParametersPromises&&this.setParametersPromises.length?Promise.all(this.setParametersPromises).then(function(){return r.apply(e,t)}).finally(function(){e.setParametersPromises=[]}):r.apply(this,arguments)})};var o=function(e){{if(e&&e.__esModule)return e;var t={};if(null!=e)for(var r in e)Object.prototype.hasOwnProperty.call(e,r)&&(t[r]=e[r]);return t.default=e,t}}(e("../utils"))},{"../utils":11,"./getdisplaymedia":8,"./getusermedia":9}],8:[function(e,t,r){"use strict";Object.defineProperty(r,"__esModule",{value:!0}),r.shimGetDisplayMedia=function(t,r){t.navigator.mediaDevices&&"getDisplayMedia"in t.navigator.mediaDevices||t.navigator.mediaDevices&&(t.navigator.mediaDevices.getDisplayMedia=function(e){if(e&&e.video)return!0===e.video?e.video={mediaSource:r}:e.video.mediaSource=r,t.navigator.mediaDevices.getUserMedia(e);e=new DOMException("getDisplayMedia without video constraints is undefined");return e.name="NotFoundError",e.code=8,Promise.reject(e)})}},{}],9:[function(e,t,r){"use strict";Object.defineProperty(r,"__esModule",{value:!0});var s="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e};r.shimGetUserMedia=function(e,t){var n=e&&e.navigator,e=e&&e.MediaStreamTrack;{var r,i,o,a;n.getUserMedia=function(e,t,r){c.deprecated("navigator.getUserMedia","navigator.mediaDevices.getUserMedia"),n.mediaDevices.getUserMedia(e).then(t,r)},55=r&&parseInt(t[r],10)}function c(e){return"[object Object]"===Object.prototype.toString.call(e)}function p(t,r,n){r&&!n.has(r.id)&&(n.set(r.id,r),Object.keys(r).forEach(function(e){e.endsWith("Id")?p(t,t.get(r[e]),n):e.endsWith("Ids")&&r[e].forEach(function(e){p(t,t.get(e),n)})}))}},{}],12:[function(e,t,r){"use strict";var n="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},p={generateIdentifier:function(){return Math.random().toString(36).substr(2,10)}};p.localCName=p.generateIdentifier(),p.splitLines=function(e){return e.trim().split("\n").map(function(e){return e.trim()})},p.splitSections=function(e){return e.split("\nm=").map(function(e,t){return(0n&&(n=e.maxptime)}),0 - + diff --git a/web/skins/classic/views/watch.php b/web/skins/classic/views/watch.php index 6acce1b5c..3128f7767 100644 --- a/web/skins/classic/views/watch.php +++ b/web/skins/classic/views/watch.php @@ -135,7 +135,11 @@ if (isset($_REQUEST['height'])) { } $connkey = generateConnKey(); -$streamMode = getStreamMode(); +if ( $monitor->JanusEnabled() ) { + $streamMode = 'janus'; +} else { + $streamMode = getStreamMode(); +} noCacheHeaders(); xhtmlHeaders(__FILE__, $monitor->Name().' - '.translate('Feed')); @@ -407,4 +411,6 @@ if ( ZM_WEB_SOUND_ON_ALARM ) { ?> + + From cb46b94ea1c81e1cd62ccc89b5a902d9e8914254 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Wed, 12 Jan 2022 00:19:54 -0600 Subject: [PATCH 03/18] Add error handling to libcurl calls to Janus --- src/zm_monitor.cpp | 60 ++++++++++++++++++++++++++++++---------------- 1 file changed, 39 insertions(+), 21 deletions(-) diff --git a/src/zm_monitor.cpp b/src/zm_monitor.cpp index f38c7b446..774884454 100644 --- a/src/zm_monitor.cpp +++ b/src/zm_monitor.cpp @@ -1115,15 +1115,12 @@ bool Monitor::connect() { //End ONVIF Setup #endif -//janus setup. - if (janus_enabled) { - add_to_janus(); + //janus setup. + if (janus_enabled && (path.find("rtsp://") != std::string::npos)) { + if (add_to_janus() != 0) { + Warning("Failed to add monitor stream to Janus!"); + } } -//ifdef janus, and if url contains rtsp -//look for username and password -//make the initial call, scrape id, then connect to plugin -//add stream using the same id - } else if (!shared_data->valid) { Error("Shared data not initialised by capture daemon for monitor %s", name.c_str()); @@ -3360,16 +3357,21 @@ int Monitor::add_to_janus() { std::string rtsp_username; std::string rtsp_password; std::string rtsp_path = "rtsp://"; + std::string janus_id; std::size_t pos; std::size_t pos2; + CURLcode res; curl = curl_easy_init(); - + if(!curl) return -1; //parse username and password pos = path.find(":", 7); + if (pos == std::string::npos) return -1; rtsp_username = path.substr(7, pos-7); pos2 = path.find("@", pos); + if (pos2 == std::string::npos) return -1; + rtsp_password = path.substr(pos+1, pos2 - pos - 1); rtsp_path += path.substr(pos2 + 1); @@ -3378,9 +3380,12 @@ int Monitor::add_to_janus() { curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback); curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response); curl_easy_setopt(curl, CURLOPT_POSTFIELDS, postData.c_str()); - curl_easy_perform(curl); + res = curl_easy_perform(curl); + if (res != CURLE_OK) return -1; + pos = response.find("\"id\": "); - std::string janus_id = response.substr(pos + 6, 16); + if (pos == std::string::npos) return -1; + janus_id = response.substr(pos + 6, 16); response = ""; endpoint += janus_id; postData = "{\"janus\" : \"attach\", \"plugin\" : \"janus.plugin.streaming\", \"transaction\" : \"randomString\"}"; @@ -3388,9 +3393,11 @@ int Monitor::add_to_janus() { curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback); curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response); curl_easy_setopt(curl, CURLOPT_POSTFIELDS, postData.c_str()); - curl_easy_perform(curl); + res = curl_easy_perform(curl); + if (res != CURLE_OK) return -1; pos = response.find("\"id\": "); - std::string handle_id = response.substr(pos + 6, 16); + if (pos == std::string::npos) return -1; + std::string handle_id = response.substr(pos + 6, 16); //TODO: This is an assumption that the string is always 16 endpoint += "/"; endpoint += handle_id; @@ -3411,10 +3418,11 @@ int Monitor::add_to_janus() { curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback); curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response); curl_easy_setopt(curl, CURLOPT_POSTFIELDS, postData.c_str()); - curl_easy_perform(curl); - Warning(response.c_str()); + res = curl_easy_perform(curl); + if (res != CURLE_OK) return -1; + Debug(1,response.c_str()); curl_easy_cleanup(curl); - + return 0; } int Monitor::remove_from_janus() { //TODO clean this up, add error checking, etc @@ -3422,8 +3430,10 @@ int Monitor::remove_from_janus() { std::string endpoint = "127.0.0.1:8088/janus/"; std::string postData = "{\"janus\" : \"create\", \"transaction\" : \"randomString\"}"; std::size_t pos; + CURLcode res; curl = curl_easy_init(); + if(!curl) return -1; //Start Janus API init. Need to get a session_id and handle_id @@ -3431,8 +3441,11 @@ int Monitor::remove_from_janus() { curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback); curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response); curl_easy_setopt(curl, CURLOPT_POSTFIELDS, postData.c_str()); - curl_easy_perform(curl); + res = curl_easy_perform(curl); + if (res != CURLE_OK) return -1; + pos = response.find("\"id\": "); + if (pos == std::string::npos) return -1; std::string janus_id = response.substr(pos + 6, 16); response = ""; endpoint += janus_id; @@ -3441,8 +3454,11 @@ int Monitor::remove_from_janus() { curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback); curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response); curl_easy_setopt(curl, CURLOPT_POSTFIELDS, postData.c_str()); - curl_easy_perform(curl); + res = curl_easy_perform(curl); + if (res != CURLE_OK) return -1; + pos = response.find("\"id\": "); + if (pos == std::string::npos) return -1; std::string handle_id = response.substr(pos + 6, 16); endpoint += "/"; endpoint += handle_id; @@ -3457,8 +3473,10 @@ int Monitor::remove_from_janus() { curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback); curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response); curl_easy_setopt(curl, CURLOPT_POSTFIELDS, postData.c_str()); - curl_easy_perform(curl); - Warning(response.c_str()); - curl_easy_cleanup(curl); + res = curl_easy_perform(curl); + if (res != CURLE_OK) return -1; + Debug(1, response.c_str()); + curl_easy_cleanup(curl); + return 0; } From 7a5a05fe9428cd95664fc57d2a27bf981e2dbe10 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Wed, 12 Jan 2022 01:29:32 -0600 Subject: [PATCH 04/18] Clean up warnings --- src/zm_monitor.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/zm_monitor.cpp b/src/zm_monitor.cpp index 774884454..3b8c47a25 100644 --- a/src/zm_monitor.cpp +++ b/src/zm_monitor.cpp @@ -3420,7 +3420,7 @@ int Monitor::add_to_janus() { curl_easy_setopt(curl, CURLOPT_POSTFIELDS, postData.c_str()); res = curl_easy_perform(curl); if (res != CURLE_OK) return -1; - Debug(1,response.c_str()); + Debug(1,"Added stream to Janus: %s", response.c_str()); curl_easy_cleanup(curl); return 0; } @@ -3476,7 +3476,7 @@ int Monitor::remove_from_janus() { res = curl_easy_perform(curl); if (res != CURLE_OK) return -1; - Debug(1, response.c_str()); + Debug(1, "Removed stream from Janus: %s", response.c_str()); curl_easy_cleanup(curl); return 0; } From 84a73bb0078dd7c650a13c81a166452140865b98 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Wed, 12 Jan 2022 09:37:48 -0600 Subject: [PATCH 05/18] Add check to prevent tight busy loop if ONVIF setup fails --- src/zm_monitor.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/zm_monitor.cpp b/src/zm_monitor.cpp index 3b8c47a25..59dc90ead 100644 --- a/src/zm_monitor.cpp +++ b/src/zm_monitor.cpp @@ -3140,7 +3140,7 @@ int Monitor::PrimeCapture() { #ifdef WITH_GSOAP //For now, just don't run the thread if no ONVIF support. This may change if we add other long polling options. //ONVIF Thread - if (onvif_event_listener) { + if (onvif_event_listener && ONVIF_Healthy) { if (!Poller) { Poller = zm::make_unique(this); } else { From 2beffeb1b433aa159ca2c20203d55400ab4d46ef Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Wed, 12 Jan 2022 22:46:26 -0600 Subject: [PATCH 06/18] Add Janus to Cycle view. Remove debug Alert() messages --- src/zm_monitor.cpp | 6 ++- web/skins/classic/views/cycle.php | 2 + web/skins/classic/views/js/cycle.js | 65 +++++++++++++++++++++++++ web/skins/classic/views/js/cycle.js.php | 3 +- web/skins/classic/views/js/montage.js | 4 +- web/skins/classic/views/js/watch.js | 4 +- 6 files changed, 75 insertions(+), 9 deletions(-) diff --git a/src/zm_monitor.cpp b/src/zm_monitor.cpp index 4b136e1aa..a612e84c3 100644 --- a/src/zm_monitor.cpp +++ b/src/zm_monitor.cpp @@ -1117,12 +1117,13 @@ bool Monitor::connect() { //End ONVIF Setup #endif - //janus setup. +#if HAVE_LIBCURL //janus setup. Depends on libcurl. if (janus_enabled && (path.find("rtsp://") != std::string::npos)) { if (add_to_janus() != 0) { Warning("Failed to add monitor stream to Janus!"); } } +#endif } else if (!shared_data->valid) { Error("Shared data not initialised by capture daemon for monitor %s", name.c_str()); @@ -3206,10 +3207,11 @@ int Monitor::Close() { soap = nullptr; } //End ONVIF #endif - //Janus Teardown +#if HAVE_LIBCURL //Janus Teardown if (janus_enabled && (purpose == CAPTURE)) { remove_from_janus(); } +#endif packetqueue.clear(); if (audio_fifo) { diff --git a/web/skins/classic/views/cycle.php b/web/skins/classic/views/cycle.php index b9f779c50..279d9bba5 100644 --- a/web/skins/classic/views/cycle.php +++ b/web/skins/classic/views/cycle.php @@ -192,4 +192,6 @@ xhtmlHeaders(__FILE__, translate('CycleWatch')); + + diff --git a/web/skins/classic/views/js/cycle.js b/web/skins/classic/views/js/cycle.js index 767fad475..401e36760 100644 --- a/web/skins/classic/views/js/cycle.js +++ b/web/skins/classic/views/js/cycle.js @@ -1,3 +1,6 @@ +var server; +var janus = null; +var streaming2; var intervalId; var pauseBtn = $j('#pauseBtn'); var playBtn = $j('#playBtn'); @@ -46,6 +49,68 @@ function initCycle() { intervalId = setInterval(nextCycleView, cycleRefreshTimeout); var scale = $j('#scale').val(); if ( scale == '0' || scale == 'auto' ) changeScale(); + + if (monitorData[monIdx].janusEnabled) { + server = "http://" + window.location.hostname + ":8088/janus"; + opaqueId = "streamingtest-"+Janus.randomString(12); + Janus.init({debug: "all", callback: function() { + janus = new Janus({ + server: server, + success: function() { + janus.attach({ + plugin: "janus.plugin.streaming", + opaqueId: opaqueId, + success: function(pluginHandle) { + streaming2 = pluginHandle; + var body = { "request": "watch", "id":monitorData[monIdx].id }; + streaming2.send({"message": body}); + }, + error: function(error) { + Janus.error(" -- Error attaching plugin... ", error); + }, + onmessage: function(msg, jsep) { + Janus.debug(" ::: Got a message :::"); + Janus.debug(msg); + var result = msg["result"]; + if(result !== null && result !== undefined) { + if(result["status"] !== undefined && result["status"] !== null) { + var status = result["status"]; + } + } else if(msg["error"] !== undefined && msg["error"] !== null) { + Janus.debug(msg["error"]); + return; + } + if(jsep !== undefined && jsep !== null) { + Janus.debug("Handling SDP as well..."); + Janus.debug(jsep); + // Offer from the plugin, let's answer + streaming2.createAnswer({ + jsep: jsep, + // We want recvonly audio/video and, if negotiated, datachannels + media: { audioSend: false, videoSend: false, data: true }, + success: function(jsep) { + Janus.debug("Got SDP!"); + Janus.debug(jsep); + var body = { "request": "start"}; + streaming2.send({"message": body, "jsep": jsep}); + }, + error: function(error) { + Janus.error("WebRTC error:", error); + } + }); + } + }, //onmessage function + onremotestream: function(stream) { + Janus.debug(" ::: Got a remote track :::"); + Janus.debug(stream); + Janus.attachMediaStream(document.getElementById("liveStream" + monitorData[monIdx].id), stream); + document.getElementById("liveStream" + monitorData[monIdx].id).play(); + } + });// attach + } //Success functio + }); //new Janus + }}); //janus.init callback + } //if janus } function changeSize() { diff --git a/web/skins/classic/views/js/cycle.js.php b/web/skins/classic/views/js/cycle.js.php index 7bcd4497c..201deea76 100644 --- a/web/skins/classic/views/js/cycle.js.php +++ b/web/skins/classic/views/js/cycle.js.php @@ -20,7 +20,8 @@ monitorData[monitorData.length] = { 'url': 'UrlToIndex() ?>', 'onclick': function(){window.location.assign( '?view=watch&mid=Id() ?>' );}, 'type': 'Type() ?>', - 'refresh': 'Refresh() ?>' + 'refresh': 'Refresh() ?>', + 'janusEnabled': JanusEnabled() ?> }; Date: Thu, 13 Jan 2022 22:14:14 -0500 Subject: [PATCH 07/18] Preface Debug with ZM --- web/includes/csrf/csrf-magic.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/includes/csrf/csrf-magic.php b/web/includes/csrf/csrf-magic.php index b102c565a..10d200240 100644 --- a/web/includes/csrf/csrf-magic.php +++ b/web/includes/csrf/csrf-magic.php @@ -348,7 +348,7 @@ return false; return $value === csrf_hash($_COOKIE[$n], $time); case 'key': if (!$GLOBALS['csrf']['key']) { - Debug("Checking key: no key set" ); + ZM\Debug("Checking key: no key set" ); return false; } #Debug("Checking sid: $value === " . csrf_hash($GLOBALS['csrf']['key'], $time) ); From 9240a93ef501658fb1869433437bdfb4730629fb Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Fri, 14 Jan 2022 14:09:18 -0500 Subject: [PATCH 08/18] Revert my idea of having ONVIF and TRIGGER code start/end events. Move code to tst for Ready() up above ONVIF and TRIGGER code. When waiting for decode, junk the idea of locking in the packetqueue. Since waiting releases the lock, we should be ok waiting and notifying in the packet. (I know there was a time when I felt this was necessary and I think it had to do with the deinterlacing case, but I've simplified that code too). --- src/zm_monitor.cpp | 128 ++++++++++++++++++++++++--------------------- 1 file changed, 67 insertions(+), 61 deletions(-) diff --git a/src/zm_monitor.cpp b/src/zm_monitor.cpp index d5865a842..bc7ee0484 100644 --- a/src/zm_monitor.cpp +++ b/src/zm_monitor.cpp @@ -1839,6 +1839,12 @@ bool Monitor::Analyse() { packetqueue.increment_it(analysis_it); return false; } + // Ready means that we have captured the warmup # of frames + if (!Ready()) { + Debug(3, "Not ready?"); + delete packet_lock; + return false; + } // signal is set by capture bool signal = shared_data->signal; @@ -1853,54 +1859,36 @@ bool Monitor::Analyse() { // if we have been told to be OFF, then we are off and don't do any processing. if (trigger_data->trigger_state != TriggerState::TRIGGER_OFF) { Debug(4, "Trigger not OFF state is (%d)", int(trigger_data->trigger_state)); - // Ready means that we have captured the warmup # of frames - if (!Ready()) { - Debug(3, "Not ready?"); - delete packet_lock; - return false; - } int score = 0; std::string cause; Event::StringSetMap noteSetMap; #ifdef WITH_GSOAP - if (ONVIF_Trigger_State) { - score += 9; - Debug(1, "Triggered on ONVIF"); - if (!event) { + if (onvif_event_listener && ONVIF_Healthy) { + if (ONVIF_Trigger_State) { + score += 9; + Debug(1, "Triggered on ONVIF"); Event::StringSet noteSet; noteSet.insert("ONVIF2"); noteSetMap[MOTION_CAUSE] = noteSet; - - event = openEvent(snap, "ONVIF", noteSetMap); cause += "ONVIF"; - } else { - event->addNote(MOTION_CAUSE, "ONVIF2"); -// Add to cause - } - // Regardless of previous state, we go to ALARM - shared_data->state = state = ALARM; - //If the camera isn't going to send an event close, we need to close it here, but only after it has actually triggered an alarm. - if (!ONVIF_Closes_Event && state == ALARM) - ONVIF_Trigger_State = FALSE; - } // end ONVIF_Trigger + //If the camera isn't going to send an event close, we need to close it here, but only after it has actually triggered an alarm. + if (!ONVIF_Closes_Event && state == ALARM) + ONVIF_Trigger_State = FALSE; + } // end ONVIF_Trigger + } // end if (onvif_event_listener && ONVIF_Healthy) #endif - // Specifically told to be on. Setting the score here will trigger the alarm. + // Specifically told to be on. Setting the score here is not enough to trigger the alarm. Must jump directly to ALARM if (trigger_data->trigger_state == TriggerState::TRIGGER_ON) { score += trigger_data->trigger_score; Debug(1, "Triggered on score += %d => %d", trigger_data->trigger_score, score); - if (!event) { - Event::StringSet noteSet; - noteSet.insert(trigger_data->trigger_text); - noteSetMap[trigger_data->trigger_cause] = noteSet; - event = openEvent(snap, trigger_data->trigger_cause, noteSetMap); - Info("%s: %03d - Opening new event %" PRIu64 ", alarm start", name.c_str(), analysis_image_count, event->Id()); - } else { - event->addNote(trigger_data->trigger_cause, trigger_data->trigger_text); - // Need to know if we should end the previous and start a new one, or just add the data - } + if (!cause.empty()) cause += ", "; + cause += trigger_data->trigger_cause; + Event::StringSet noteSet; + noteSet.insert(trigger_data->trigger_text); + noteSetMap[trigger_data->trigger_cause] = noteSet; shared_data->state = state = ALARM; } // end if trigger_on @@ -1968,14 +1956,21 @@ bool Monitor::Analyse() { /* try to stay behind the decoder. */ if (decoding_enabled) { - if (!snap->decoded and !zm_terminate and !analysis_thread->Stopped()) { + while (!snap->decoded and !zm_terminate and !analysis_thread->Stopped()) { // Need to wait for the decoder thread. + // decoder thread might be waiting on the lock for this packet. + // So we need to relinquish the lock and wait. Waiting automatically relinquishes the lock + // So... Debug(1, "Waiting for decode"); - packetqueue.unlock(packet_lock); // This will delete packet_lock and notify_all - packetqueue.wait(); - // Everything may have changed, just return and start again. This needs to be more RAII - return true; + packet_lock->wait(); + //packetqueue.unlock(packet_lock); // This will delete packet_lock and notify_all + //packetqueue.wait(); + ////packet_lock->lock(); } // end while ! decoded + if (zm_terminate or analysis_thread->Stopped()) { + delete packet_lock; + return false; + } } // end if decoding enabled if (Active() and (function == MODECT or function == MOCORD)) { @@ -1990,7 +1985,6 @@ bool Monitor::Analyse() { } if (snap->image) { - // decoder may not have been able to provide an image if (!ref_image.Buffer()) { Debug(1, "Assigning instead of Detecting"); @@ -2024,8 +2018,9 @@ bool Monitor::Analyse() { if (snap->score) { if (cause.length()) cause += ", "; - cause += MOTION_CAUSE+std::string(":")+snap->alarm_cause; + cause += MOTION_CAUSE + std::string(":") + snap->alarm_cause; noteSetMap[MOTION_CAUSE] = zoneSet; + score += snap->score; } // end if motion_score } else { Debug(1, "Skipped motion detection last motion score was %d", last_motion_score); @@ -2069,13 +2064,15 @@ bool Monitor::Analyse() { Info("%s: %03d - Opened new event %" PRIu64 ", continuous section start", name.c_str(), analysis_image_count, event->Id()); /* To prevent cancelling out an existing alert\prealarm\alarm state */ + // This ignores current score status. This should all come after the state machine calculations if (state == IDLE) { shared_data->state = state = TAPE; } } // end if ! event } // end if RECORDING - if ((snap->score > 0) and (function != MONITOR)) { + // If motion detecting, score will be > 0 on motion, but if skipping frames, might not be. So also test snap->score + if ((score > 0) or ((snap->score > 0) and (function != MONITOR))) { if ((state == IDLE) || (state == TAPE) || (state == PREALARM)) { // If we should end then previous continuous event and start a new non-continuous event if (event && event->Frames() @@ -2133,8 +2130,8 @@ bool Monitor::Analyse() { if (state == ALARM) { last_alarm_count = analysis_image_count; } // This is needed so post_event_count counts after last alarmed frames while in ALARM not single alarmed frames while ALERT - } else if (!score and snap->score == 0) { // snap->score means -1 which means didn't do motion detection so don't do state transition - Debug(1, "!score"); + } else if (!score and (snap->score == 0)) { // snap->score means -1 which means didn't do motion detection so don't do state transition + Debug(1, "!score %s", State_Strings[state].c_str()); alert_to_alarm_frame_count = alarm_frame_count; // load same value configured for alarm_frame_count if (state == ALARM) { @@ -2238,17 +2235,17 @@ bool Monitor::Analyse() { // In the case where people have pre-alarm frames, the web ui will generate the frame images // from the mp4. So no one will notice anyways. if (snap->image and (videowriter == PASSTHROUGH)) { - if (!savejpegs) { - Debug(1, "Deleting image data for %d", snap->image_index); - // Don't need raw images anymore - delete snap->image; - snap->image = nullptr; - } - if (snap->analysis_image and !(savejpegs & 2)) { - Debug(1, "Deleting analysis image data for %d", snap->image_index); - delete snap->analysis_image; - snap->analysis_image = nullptr; - } + if (!savejpegs) { + Debug(1, "Deleting image data for %d", snap->image_index); + // Don't need raw images anymore + delete snap->image; + snap->image = nullptr; + } + if (snap->analysis_image and !(savejpegs & 2)) { + Debug(1, "Deleting analysis image data for %d", snap->image_index); + delete snap->analysis_image; + snap->analysis_image = nullptr; + } } packetqueue.clearPackets(snap); @@ -2571,7 +2568,8 @@ bool Monitor::Decode() { std::shared_ptr packet = packet_lock->packet_; if (packet->codec_type != AVMEDIA_TYPE_VIDEO) { Debug(4, "Not video"); - packetqueue.unlock(packet_lock); + //packetqueue.unlock(packet_lock); + delete packet_lock; return true; // Don't need decode } @@ -2677,24 +2675,31 @@ bool Monitor::Decode() { capture_image->Deinterlace_Blend(); } else if (deinterlacing_value == 4) { while (!zm_terminate) { + // ICON FIXME SHould we not clone decoder_it? ZMLockedPacket *deinterlace_packet_lock = packetqueue.get_packet(decoder_it); if (!deinterlace_packet_lock) { - packetqueue.unlock(packet_lock); + delete packet_lock; + //packetqueue.unlock(packet_lock); return false; } if (deinterlace_packet_lock->packet_->codec_type == packet->codec_type) { capture_image->Deinterlace_4Field(deinterlace_packet_lock->packet_->image, (deinterlacing>>8)&0xff); - packetqueue.unlock(deinterlace_packet_lock); + delete deinterlace_packet_lock; + //packetqueue.unlock(deinterlace_packet_lock); break; } - packetqueue.unlock(deinterlace_packet_lock); + delete deinterlace_packet_lock; + //packetqueue.unlock(deinterlace_packet_lock); packetqueue.increment_it(decoder_it); } - if (zm_terminate) return false; + if (zm_terminate) { + delete packet_lock; + return false; + } } else if (deinterlacing_value == 5) { capture_image->Deinterlace_Blend_CustomRatio((deinterlacing>>8)&0xff); } - } + } // end if deinterlacing_value if (orientation != ROTATE_0) { Debug(3, "Doing rotation"); @@ -2731,7 +2736,8 @@ bool Monitor::Decode() { shared_data->signal = (capture_image and signal_check_points) ? CheckSignal(capture_image) : true; shared_data->last_write_index = index; shared_data->last_write_time = std::chrono::system_clock::to_time_t(packet->timestamp); - packetqueue.unlock(packet_lock); + delete packet_lock; + //packetqueue.unlock(packet_lock); return true; } // end bool Monitor::Decode() From 8b99b15b41b56e56d66ad37f48fc8616d64d3c77 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Fri, 14 Jan 2022 14:17:07 -0500 Subject: [PATCH 09/18] Use a scope to shorten the lock on event_lock --- src/zm_monitor.cpp | 702 +++++++++++++++++++++++---------------------- 1 file changed, 352 insertions(+), 350 deletions(-) diff --git a/src/zm_monitor.cpp b/src/zm_monitor.cpp index bc7ee0484..e8b1bb1c0 100644 --- a/src/zm_monitor.cpp +++ b/src/zm_monitor.cpp @@ -1853,384 +1853,386 @@ bool Monitor::Analyse() { Debug(3, "Motion detection is enabled signal(%d) signal_change(%d) trigger state(%s) image index %d", signal, signal_change, TriggerState_Strings[trigger_data->trigger_state].c_str(), snap->image_index); - // Need to guard around event creation/deletion from Reload() - std::lock_guard lck(event_mutex); + { // scope for event lock + // Need to guard around event creation/deletion from Reload() + std::lock_guard lck(event_mutex); - // if we have been told to be OFF, then we are off and don't do any processing. - if (trigger_data->trigger_state != TriggerState::TRIGGER_OFF) { - Debug(4, "Trigger not OFF state is (%d)", int(trigger_data->trigger_state)); + // if we have been told to be OFF, then we are off and don't do any processing. + if (trigger_data->trigger_state != TriggerState::TRIGGER_OFF) { + Debug(4, "Trigger not OFF state is (%d)", int(trigger_data->trigger_state)); - int score = 0; - std::string cause; - Event::StringSetMap noteSetMap; + int score = 0; + std::string cause; + Event::StringSetMap noteSetMap; #ifdef WITH_GSOAP - if (onvif_event_listener && ONVIF_Healthy) { - if (ONVIF_Trigger_State) { - score += 9; - Debug(1, "Triggered on ONVIF"); - Event::StringSet noteSet; - noteSet.insert("ONVIF2"); - noteSetMap[MOTION_CAUSE] = noteSet; - cause += "ONVIF"; - //If the camera isn't going to send an event close, we need to close it here, but only after it has actually triggered an alarm. - if (!ONVIF_Closes_Event && state == ALARM) - ONVIF_Trigger_State = FALSE; - } // end ONVIF_Trigger - } // end if (onvif_event_listener && ONVIF_Healthy) + if (onvif_event_listener && ONVIF_Healthy) { + if (ONVIF_Trigger_State) { + score += 9; + Debug(1, "Triggered on ONVIF"); + Event::StringSet noteSet; + noteSet.insert("ONVIF2"); + noteSetMap[MOTION_CAUSE] = noteSet; + cause += "ONVIF"; + //If the camera isn't going to send an event close, we need to close it here, but only after it has actually triggered an alarm. + if (!ONVIF_Closes_Event && state == ALARM) + ONVIF_Trigger_State = FALSE; + } // end ONVIF_Trigger + } // end if (onvif_event_listener && ONVIF_Healthy) #endif - // Specifically told to be on. Setting the score here is not enough to trigger the alarm. Must jump directly to ALARM - if (trigger_data->trigger_state == TriggerState::TRIGGER_ON) { - score += trigger_data->trigger_score; - Debug(1, "Triggered on score += %d => %d", trigger_data->trigger_score, score); - if (!cause.empty()) cause += ", "; - cause += trigger_data->trigger_cause; - Event::StringSet noteSet; - noteSet.insert(trigger_data->trigger_text); - noteSetMap[trigger_data->trigger_cause] = noteSet; - shared_data->state = state = ALARM; - } // end if trigger_on + // Specifically told to be on. Setting the score here is not enough to trigger the alarm. Must jump directly to ALARM + if (trigger_data->trigger_state == TriggerState::TRIGGER_ON) { + score += trigger_data->trigger_score; + Debug(1, "Triggered on score += %d => %d", trigger_data->trigger_score, score); + if (!cause.empty()) cause += ", "; + cause += trigger_data->trigger_cause; + Event::StringSet noteSet; + noteSet.insert(trigger_data->trigger_text); + noteSetMap[trigger_data->trigger_cause] = noteSet; + shared_data->state = state = ALARM; + } // end if trigger_on - // FIXME this snap might not be the one that caused the signal change. Need to store that in the packet. - if (signal_change) { - Debug(2, "Signal change, new signal is %d", signal); - if (!signal) { - if (event) { - event->addNote(SIGNAL_CAUSE, "Lost"); - Info("%s: %03d - Closing event %" PRIu64 ", signal loss", name.c_str(), analysis_image_count, event->Id()); - closeEvent(); + // FIXME this snap might not be the one that caused the signal change. Need to store that in the packet. + if (signal_change) { + Debug(2, "Signal change, new signal is %d", signal); + if (!signal) { + if (event) { + event->addNote(SIGNAL_CAUSE, "Lost"); + Info("%s: %03d - Closing event %" PRIu64 ", signal loss", name.c_str(), analysis_image_count, event->Id()); + closeEvent(); + } + } else if (function == MOCORD or function == RECORD) { + if (!event) { + if (!cause.empty()) cause += ", "; + cause += SIGNAL_CAUSE + std::string(": Reacquired"); + } else { + event->addNote(SIGNAL_CAUSE, "Reacquired"); + } + if (snap->image) + ref_image.Assign(*(snap->image)); } - } else if (function == MOCORD or function == RECORD) { - if (!event) { - if (!cause.empty()) cause += ", "; - cause += SIGNAL_CAUSE + std::string(": Reacquired"); - } else { - event->addNote(SIGNAL_CAUSE, "Reacquired"); - } - if (snap->image) - ref_image.Assign(*(snap->image)); - } - shared_data->state = state = IDLE; - shared_data->active = signal; - } // end if signal change + shared_data->state = state = IDLE; + shared_data->active = signal; + } // end if signal change - if (signal) { - if (snap->codec_type == AVMEDIA_TYPE_VIDEO) { - // Check to see if linked monitors are triggering. - if (n_linked_monitors > 0) { - Debug(1, "Checking linked monitors"); - // FIXME improve logic here - bool first_link = true; - Event::StringSet noteSet; - for (int i = 0; i < n_linked_monitors; i++) { - // TODO: Shouldn't we try to connect? - if (linked_monitors[i]->isConnected()) { - Debug(1, "Linked monitor %d %s is connected", - linked_monitors[i]->Id(), linked_monitors[i]->Name()); - if (linked_monitors[i]->hasAlarmed()) { - Debug(1, "Linked monitor %d %s is alarmed", + if (signal) { + if (snap->codec_type == AVMEDIA_TYPE_VIDEO) { + // Check to see if linked monitors are triggering. + if (n_linked_monitors > 0) { + Debug(1, "Checking linked monitors"); + // FIXME improve logic here + bool first_link = true; + Event::StringSet noteSet; + for (int i = 0; i < n_linked_monitors; i++) { + // TODO: Shouldn't we try to connect? + if (linked_monitors[i]->isConnected()) { + Debug(1, "Linked monitor %d %s is connected", linked_monitors[i]->Id(), linked_monitors[i]->Name()); - if (!event) { - if (first_link) { - if (cause.length()) - cause += ", "; - cause += LINKED_CAUSE; - first_link = false; + if (linked_monitors[i]->hasAlarmed()) { + Debug(1, "Linked monitor %d %s is alarmed", + linked_monitors[i]->Id(), linked_monitors[i]->Name()); + if (!event) { + if (first_link) { + if (cause.length()) + cause += ", "; + cause += LINKED_CAUSE; + first_link = false; + } + } + noteSet.insert(linked_monitors[i]->Name()); + score += linked_monitors[i]->lastFrameScore(); // 50; + } else { + Debug(1, "Linked monitor %d %s is not alarmed", + linked_monitors[i]->Id(), linked_monitors[i]->Name()); + } + } else { + Debug(1, "Linked monitor %d %d is not connected. Connecting.", i, linked_monitors[i]->Id()); + linked_monitors[i]->connect(); + } + } // end foreach linked_monitor + if (noteSet.size() > 0) + noteSetMap[LINKED_CAUSE] = noteSet; + } // end if linked_monitors + + /* try to stay behind the decoder. */ + if (decoding_enabled) { + while (!snap->decoded and !zm_terminate and !analysis_thread->Stopped()) { + // Need to wait for the decoder thread. + // decoder thread might be waiting on the lock for this packet. + // So we need to relinquish the lock and wait. Waiting automatically relinquishes the lock + // So... + Debug(1, "Waiting for decode"); + packet_lock->wait(); + //packetqueue.unlock(packet_lock); // This will delete packet_lock and notify_all + //packetqueue.wait(); + ////packet_lock->lock(); + } // end while ! decoded + if (zm_terminate or analysis_thread->Stopped()) { + delete packet_lock; + return false; + } + } // end if decoding enabled + + if (Active() and (function == MODECT or function == MOCORD)) { + Debug(3, "signal and active and modect"); + Event::StringSet zoneSet; + + if (analysis_fps_limit) { + double capture_fps = get_capture_fps(); + motion_frame_skip = capture_fps / analysis_fps_limit; + Debug(1, "Recalculating motion_frame_skip (%d) = capture_fps(%f) / analysis_fps(%f)", + motion_frame_skip, capture_fps, analysis_fps_limit); + } + + if (snap->image) { + // decoder may not have been able to provide an image + if (!ref_image.Buffer()) { + Debug(1, "Assigning instead of Detecting"); + ref_image.Assign(*(snap->image)); + alarm_image.Assign(*(snap->image)); + } else if (!(analysis_image_count % (motion_frame_skip+1))) { + Debug(1, "Detecting motion on image %d, image %p", snap->image_index, snap->image); + // Get new score. + snap->score = DetectMotion(*(snap->image), zoneSet); + + if (!snap->analysis_image) + snap->analysis_image = new Image(*(snap->image)); + // lets construct alarm cause. It will contain cause + names of zones alarmed + snap->zone_stats.reserve(zones.size()); + for (const Zone &zone : zones) { + const ZoneStats &stats = zone.GetStats(); + stats.DumpToLog("After detect motion"); + snap->zone_stats.push_back(stats); + if (zone.Alarmed()) { + if (!snap->alarm_cause.empty()) snap->alarm_cause += ","; + snap->alarm_cause += std::string(zone.Label()); + if (zone.AlarmImage()) + snap->analysis_image->Overlay(*(zone.AlarmImage())); } } - noteSet.insert(linked_monitors[i]->Name()); - score += linked_monitors[i]->lastFrameScore(); // 50; + alarm_image.Assign(*(snap->analysis_image)); + Debug(3, "After motion detection, score:%d last_motion_score(%d), new motion score(%d)", + score, last_motion_score, snap->score); + motion_frame_count += 1; + last_motion_score = snap->score; + + if (snap->score) { + if (cause.length()) cause += ", "; + cause += MOTION_CAUSE + std::string(":") + snap->alarm_cause; + noteSetMap[MOTION_CAUSE] = zoneSet; + score += snap->score; + } // end if motion_score } else { - Debug(1, "Linked monitor %d %s is not alarmed", - linked_monitors[i]->Id(), linked_monitors[i]->Name()); + Debug(1, "Skipped motion detection last motion score was %d", last_motion_score); + alarm_image.Assign(*(snap->image)); } } else { - Debug(1, "Linked monitor %d %d is not connected. Connecting.", i, linked_monitors[i]->Id()); - linked_monitors[i]->connect(); - } - } // end foreach linked_monitor - if (noteSet.size() > 0) - noteSetMap[LINKED_CAUSE] = noteSet; - } // end if linked_monitors - - /* try to stay behind the decoder. */ - if (decoding_enabled) { - while (!snap->decoded and !zm_terminate and !analysis_thread->Stopped()) { - // Need to wait for the decoder thread. - // decoder thread might be waiting on the lock for this packet. - // So we need to relinquish the lock and wait. Waiting automatically relinquishes the lock - // So... - Debug(1, "Waiting for decode"); - packet_lock->wait(); - //packetqueue.unlock(packet_lock); // This will delete packet_lock and notify_all - //packetqueue.wait(); - ////packet_lock->lock(); - } // end while ! decoded - if (zm_terminate or analysis_thread->Stopped()) { - delete packet_lock; - return false; - } - } // end if decoding enabled - - if (Active() and (function == MODECT or function == MOCORD)) { - Debug(3, "signal and active and modect"); - Event::StringSet zoneSet; - - if (analysis_fps_limit) { - double capture_fps = get_capture_fps(); - motion_frame_skip = capture_fps / analysis_fps_limit; - Debug(1, "Recalculating motion_frame_skip (%d) = capture_fps(%f) / analysis_fps(%f)", - motion_frame_skip, capture_fps, analysis_fps_limit); - } - - if (snap->image) { - // decoder may not have been able to provide an image - if (!ref_image.Buffer()) { - Debug(1, "Assigning instead of Detecting"); - ref_image.Assign(*(snap->image)); - alarm_image.Assign(*(snap->image)); - } else if (!(analysis_image_count % (motion_frame_skip+1))) { - Debug(1, "Detecting motion on image %d, image %p", snap->image_index, snap->image); - // Get new score. - snap->score = DetectMotion(*(snap->image), zoneSet); - - if (!snap->analysis_image) - snap->analysis_image = new Image(*(snap->image)); - // lets construct alarm cause. It will contain cause + names of zones alarmed - snap->zone_stats.reserve(zones.size()); - for (const Zone &zone : zones) { - const ZoneStats &stats = zone.GetStats(); - stats.DumpToLog("After detect motion"); - snap->zone_stats.push_back(stats); - if (zone.Alarmed()) { - if (!snap->alarm_cause.empty()) snap->alarm_cause += ","; - snap->alarm_cause += std::string(zone.Label()); - if (zone.AlarmImage()) - snap->analysis_image->Overlay(*(zone.AlarmImage())); - } - } - alarm_image.Assign(*(snap->analysis_image)); - Debug(3, "After motion detection, score:%d last_motion_score(%d), new motion score(%d)", - score, last_motion_score, snap->score); - motion_frame_count += 1; - last_motion_score = snap->score; - - if (snap->score) { - if (cause.length()) cause += ", "; - cause += MOTION_CAUSE + std::string(":") + snap->alarm_cause; - noteSetMap[MOTION_CAUSE] = zoneSet; - score += snap->score; - } // end if motion_score - } else { - Debug(1, "Skipped motion detection last motion score was %d", last_motion_score); - alarm_image.Assign(*(snap->image)); - } + Debug(1, "no image so skipping motion detection"); + } // end if has image + //score += last_motion_score; } else { - Debug(1, "no image so skipping motion detection"); - } // end if has image -//score += last_motion_score; - } else { - Debug(1, "Not Active(%d) enabled %d shared->active %d doing motion detection: %d", - Active(), enabled, shared_data->active, - (function == MODECT or function == MOCORD) - ); - } // end if active and doing motion detection + Debug(1, "Not Active(%d) enabled %d shared->active %d doing motion detection: %d", + Active(), enabled, shared_data->active, + (function == MODECT or function == MOCORD) + ); + } // end if active and doing motion detection - if (function == RECORD or function == MOCORD) { - // If doing record, check to see if we need to close the event or not. - if (event) { - Debug(2, "Have event %" PRIu64 " in record", event->Id()); + if (function == RECORD or function == MOCORD) { + // If doing record, check to see if we need to close the event or not. + if (event) { + Debug(2, "Have event %" PRIu64 " in record", event->Id()); - if (section_length != Seconds(0) && (snap->timestamp - event->StartTime() >= section_length) - && ((function == MOCORD && event_close_mode != CLOSE_TIME) - || (function == RECORD && event_close_mode == CLOSE_TIME) - || std::chrono::duration_cast(snap->timestamp.time_since_epoch()) % section_length == Seconds(0))) { - Info("%s: %03d - Closing event %" PRIu64 ", section end forced %" PRIi64 " - %" PRIi64 " = %" PRIi64 " >= %" PRIi64 , - name.c_str(), - image_count, - event->Id(), - static_cast(std::chrono::duration_cast(snap->timestamp.time_since_epoch()).count()), - static_cast(std::chrono::duration_cast(event->StartTime().time_since_epoch()).count()), - static_cast(std::chrono::duration_cast(snap->timestamp - event->StartTime()).count()), - static_cast(Seconds(section_length).count())); - closeEvent(); - } // end if section_length - } // end if event - - if (!event) { - event = openEvent(snap, cause.empty() ? "Continuous" : cause, noteSetMap); - - Info("%s: %03d - Opened new event %" PRIu64 ", continuous section start", - name.c_str(), analysis_image_count, event->Id()); - /* To prevent cancelling out an existing alert\prealarm\alarm state */ - // This ignores current score status. This should all come after the state machine calculations - if (state == IDLE) { - shared_data->state = state = TAPE; - } - } // end if ! event - } // end if RECORDING - - // If motion detecting, score will be > 0 on motion, but if skipping frames, might not be. So also test snap->score - if ((score > 0) or ((snap->score > 0) and (function != MONITOR))) { - if ((state == IDLE) || (state == TAPE) || (state == PREALARM)) { - // If we should end then previous continuous event and start a new non-continuous event - if (event && event->Frames() - && !event->AlarmFrames() - && (event_close_mode == CLOSE_ALARM) - // FIXME since we won't be including this snap in the event if we close it, we should be looking at event->duration() instead - && ((snap->timestamp - event->StartTime()) >= min_section_length) - && ((!pre_event_count) || (Event::PreAlarmCount() >= alarm_frame_count - 1))) { - Info("%s: %03d - Closing event %" PRIu64 ", continuous end, alarm begins", - name.c_str(), image_count, event->Id()); - closeEvent(); - } else if (event) { - // This is so if we need more than 1 alarm frame before going into alarm, so it is basically if we have enough alarm frames - Debug(3, - "pre_alarm_count in event %d of %d, event frames %d, alarm frames %d event length %" PRIi64 " >=? %" PRIi64 " min close mode is ALARM? %d", - Event::PreAlarmCount(), pre_event_count, - event->Frames(), - event->AlarmFrames(), - static_cast(std::chrono::duration_cast(snap->timestamp - event->StartTime()).count()), - static_cast(Seconds(min_section_length).count()), - (event_close_mode == CLOSE_ALARM)); - } - if ((!pre_event_count) || (Event::PreAlarmCount() >= alarm_frame_count-1)) { - Info("%s: %03d - Gone into alarm state PreAlarmCount: %u > AlarmFrameCount:%u Cause:%s", - name.c_str(), image_count, Event::PreAlarmCount(), alarm_frame_count, cause.c_str()); - - if (!event) { - event = openEvent(snap, cause, noteSetMap); - Info("%s: %03d - Opening new event %" PRIu64 ", alarm start", name.c_str(), analysis_image_count, event->Id()); - } // end if no event, so start it - shared_data->state = state = ALARM; - if (alarm_frame_count) { - Debug(1, "alarm frame count so SavePreAlarmFrames"); - event->SavePreAlarmFrames(); - } - } else if (state != PREALARM) { - Info("%s: %03d - Gone into prealarm state", name.c_str(), analysis_image_count); - shared_data->state = state = PREALARM; - } - } else if (state == ALERT) { - alert_to_alarm_frame_count--; - Info("%s: %03d - Alarmed frame while in alert state. Consecutive alarmed frames left to return to alarm state: %03d", - name.c_str(), analysis_image_count, alert_to_alarm_frame_count); - if (alert_to_alarm_frame_count == 0) { - Info("%s: %03d - Gone back into alarm state", name.c_str(), analysis_image_count); - shared_data->state = state = ALARM; - } - } else if (state == TAPE) { - // Already recording, but IDLE so switch to ALARM - shared_data->state = state = ALARM; - Debug(1, "Was in TAPE, going into ALARM"); - } else { - Debug(1, "Staying in %s", State_Strings[state].c_str()); - } - if (state == ALARM) { - last_alarm_count = analysis_image_count; - } // This is needed so post_event_count counts after last alarmed frames while in ALARM not single alarmed frames while ALERT - } else if (!score and (snap->score == 0)) { // snap->score means -1 which means didn't do motion detection so don't do state transition - Debug(1, "!score %s", State_Strings[state].c_str()); - alert_to_alarm_frame_count = alarm_frame_count; // load same value configured for alarm_frame_count - - if (state == ALARM) { - Info("%s: %03d - Gone into alert state", name.c_str(), analysis_image_count); - shared_data->state = state = ALERT; - } else if (state == ALERT) { - if ( - ((analysis_image_count - last_alarm_count) > post_event_count) - && - ((snap->timestamp - event->StartTime()) >= min_section_length)) { - Info("%s: %03d - Left alarm state (%" PRIu64 ") - %d(%d) images", - name.c_str(), analysis_image_count, event->Id(), event->Frames(), event->AlarmFrames()); - if ( - (function != RECORD && function != MOCORD) - || - (event_close_mode == CLOSE_ALARM || event_close_mode==CLOSE_IDLE) - ) { - shared_data->state = state = IDLE; - Info("%s: %03d - Closing event %" PRIu64 ", alarm end%s", - name.c_str(), analysis_image_count, event->Id(), (function==MOCORD)?", section truncated":"" ); + if (section_length != Seconds(0) && (snap->timestamp - event->StartTime() >= section_length) + && ((function == MOCORD && event_close_mode != CLOSE_TIME) + || (function == RECORD && event_close_mode == CLOSE_TIME) + || std::chrono::duration_cast(snap->timestamp.time_since_epoch()) % section_length == Seconds(0))) { + Info("%s: %03d - Closing event %" PRIu64 ", section end forced %" PRIi64 " - %" PRIi64 " = %" PRIi64 " >= %" PRIi64 , + name.c_str(), + image_count, + event->Id(), + static_cast(std::chrono::duration_cast(snap->timestamp.time_since_epoch()).count()), + static_cast(std::chrono::duration_cast(event->StartTime().time_since_epoch()).count()), + static_cast(std::chrono::duration_cast(snap->timestamp - event->StartTime()).count()), + static_cast(Seconds(section_length).count())); closeEvent(); - } else { + } // end if section_length + } // end if event + + if (!event) { + event = openEvent(snap, cause.empty() ? "Continuous" : cause, noteSetMap); + + Info("%s: %03d - Opened new event %" PRIu64 ", continuous section start", + name.c_str(), analysis_image_count, event->Id()); + /* To prevent cancelling out an existing alert\prealarm\alarm state */ + // This ignores current score status. This should all come after the state machine calculations + if (state == IDLE) { shared_data->state = state = TAPE; } + } // end if ! event + } // end if RECORDING + + // If motion detecting, score will be > 0 on motion, but if skipping frames, might not be. So also test snap->score + if ((score > 0) or ((snap->score > 0) and (function != MONITOR))) { + if ((state == IDLE) || (state == TAPE) || (state == PREALARM)) { + // If we should end then previous continuous event and start a new non-continuous event + if (event && event->Frames() + && !event->AlarmFrames() + && (event_close_mode == CLOSE_ALARM) + // FIXME since we won't be including this snap in the event if we close it, we should be looking at event->duration() instead + && ((snap->timestamp - event->StartTime()) >= min_section_length) + && ((!pre_event_count) || (Event::PreAlarmCount() >= alarm_frame_count - 1))) { + Info("%s: %03d - Closing event %" PRIu64 ", continuous end, alarm begins", + name.c_str(), image_count, event->Id()); + closeEvent(); + } else if (event) { + // This is so if we need more than 1 alarm frame before going into alarm, so it is basically if we have enough alarm frames + Debug(3, + "pre_alarm_count in event %d of %d, event frames %d, alarm frames %d event length %" PRIi64 " >=? %" PRIi64 " min close mode is ALARM? %d", + Event::PreAlarmCount(), pre_event_count, + event->Frames(), + event->AlarmFrames(), + static_cast(std::chrono::duration_cast(snap->timestamp - event->StartTime()).count()), + static_cast(Seconds(min_section_length).count()), + (event_close_mode == CLOSE_ALARM)); + } + if ((!pre_event_count) || (Event::PreAlarmCount() >= alarm_frame_count-1)) { + Info("%s: %03d - Gone into alarm state PreAlarmCount: %u > AlarmFrameCount:%u Cause:%s", + name.c_str(), image_count, Event::PreAlarmCount(), alarm_frame_count, cause.c_str()); + + if (!event) { + event = openEvent(snap, cause, noteSetMap); + Info("%s: %03d - Opening new event %" PRIu64 ", alarm start", name.c_str(), analysis_image_count, event->Id()); + } // end if no event, so start it + shared_data->state = state = ALARM; + if (alarm_frame_count) { + Debug(1, "alarm frame count so SavePreAlarmFrames"); + event->SavePreAlarmFrames(); + } + } else if (state != PREALARM) { + Info("%s: %03d - Gone into prealarm state", name.c_str(), analysis_image_count); + shared_data->state = state = PREALARM; + } + } else if (state == ALERT) { + alert_to_alarm_frame_count--; + Info("%s: %03d - Alarmed frame while in alert state. Consecutive alarmed frames left to return to alarm state: %03d", + name.c_str(), analysis_image_count, alert_to_alarm_frame_count); + if (alert_to_alarm_frame_count == 0) { + Info("%s: %03d - Gone back into alarm state", name.c_str(), analysis_image_count); + shared_data->state = state = ALARM; + } + } else if (state == TAPE) { + // Already recording, but IDLE so switch to ALARM + shared_data->state = state = ALARM; + Debug(1, "Was in TAPE, going into ALARM"); + } else { + Debug(1, "Staying in %s", State_Strings[state].c_str()); } - } else if (state == PREALARM) { - // Back to IDLE - shared_data->state = state = ((function != MOCORD) ? IDLE : TAPE); - } else { - Debug(1, - "State %d %s because analysis_image_count(%d)-last_alarm_count(%d) > post_event_count(%d) and timestamp.tv_sec(%" PRIi64 ") - recording.tv_src(%" PRIi64 ") >= min_section_length(%" PRIi64 ")", - state, - State_Strings[state].c_str(), - analysis_image_count, - last_alarm_count, - post_event_count, - static_cast(std::chrono::duration_cast(snap->timestamp.time_since_epoch()).count()), - static_cast(std::chrono::duration_cast(GetVideoWriterStartTime().time_since_epoch()).count()), - static_cast(Seconds(min_section_length).count())); - } - if (Event::PreAlarmCount()) - Event::EmptyPreAlarmFrames(); - } // end if score or not + if (state == ALARM) { + last_alarm_count = analysis_image_count; + } // This is needed so post_event_count counts after last alarmed frames while in ALARM not single alarmed frames while ALERT + } else if (!score and (snap->score == 0)) { // snap->score means -1 which means didn't do motion detection so don't do state transition + Debug(1, "!score %s", State_Strings[state].c_str()); + alert_to_alarm_frame_count = alarm_frame_count; // load same value configured for alarm_frame_count - if (score > snap->score) - snap->score = score; + if (state == ALARM) { + Info("%s: %03d - Gone into alert state", name.c_str(), analysis_image_count); + shared_data->state = state = ALERT; + } else if (state == ALERT) { + if ( + ((analysis_image_count - last_alarm_count) > post_event_count) + && + ((snap->timestamp - event->StartTime()) >= min_section_length)) { + Info("%s: %03d - Left alarm state (%" PRIu64 ") - %d(%d) images", + name.c_str(), analysis_image_count, event->Id(), event->Frames(), event->AlarmFrames()); + if ( + (function != RECORD && function != MOCORD) + || + (event_close_mode == CLOSE_ALARM || event_close_mode==CLOSE_IDLE) + ) { + shared_data->state = state = IDLE; + Info("%s: %03d - Closing event %" PRIu64 ", alarm end%s", + name.c_str(), analysis_image_count, event->Id(), (function==MOCORD)?", section truncated":"" ); + closeEvent(); + } else { + shared_data->state = state = TAPE; + } + } + } else if (state == PREALARM) { + // Back to IDLE + shared_data->state = state = ((function != MOCORD) ? IDLE : TAPE); + } else { + Debug(1, + "State %d %s because analysis_image_count(%d)-last_alarm_count(%d) > post_event_count(%d) and timestamp.tv_sec(%" PRIi64 ") - recording.tv_src(%" PRIi64 ") >= min_section_length(%" PRIi64 ")", + state, + State_Strings[state].c_str(), + analysis_image_count, + last_alarm_count, + post_event_count, + static_cast(std::chrono::duration_cast(snap->timestamp.time_since_epoch()).count()), + static_cast(std::chrono::duration_cast(GetVideoWriterStartTime().time_since_epoch()).count()), + static_cast(Seconds(min_section_length).count())); + } + if (Event::PreAlarmCount()) + Event::EmptyPreAlarmFrames(); + } // end if score or not - if (state == PREALARM) { - // incremement pre alarm image count - Event::AddPreAlarmFrame(snap->image, snap->timestamp, score, nullptr); - } else if (state == ALARM) { - if (event) { - if (noteSetMap.size() > 0) + if (score > snap->score) + snap->score = score; + + if (state == PREALARM) { + // incremement pre alarm image count + Event::AddPreAlarmFrame(snap->image, snap->timestamp, score, nullptr); + } else if (state == ALARM) { + if (event) { + if (noteSetMap.size() > 0) + event->updateNotes(noteSetMap); + if (section_length != Seconds(0) && (snap->timestamp - event->StartTime() >= section_length)) { + Warning("%s: %03d - event %" PRIu64 ", has exceeded desired section length. %" PRIi64 " - %" PRIi64 " = %" PRIi64 " >= %" PRIi64, + name.c_str(), analysis_image_count, event->Id(), + static_cast(std::chrono::duration_cast(snap->timestamp.time_since_epoch()).count()), + static_cast(std::chrono::duration_cast(GetVideoWriterStartTime().time_since_epoch()).count()), + static_cast(std::chrono::duration_cast(snap->timestamp - GetVideoWriterStartTime()).count()), + static_cast(Seconds(section_length).count())); + closeEvent(); + event = openEvent(snap, cause, noteSetMap); + } + } else { + Error("ALARM but no event"); + } + } else if (state == ALERT) { + // Alert means this frame has no motion, but we were alarmed and are still recording. + if ((noteSetMap.size() > 0) and event) event->updateNotes(noteSetMap); - if (section_length != Seconds(0) && (snap->timestamp - event->StartTime() >= section_length)) { - Warning("%s: %03d - event %" PRIu64 ", has exceeded desired section length. %" PRIi64 " - %" PRIi64 " = %" PRIi64 " >= %" PRIi64, - name.c_str(), analysis_image_count, event->Id(), - static_cast(std::chrono::duration_cast(snap->timestamp.time_since_epoch()).count()), - static_cast(std::chrono::duration_cast(GetVideoWriterStartTime().time_since_epoch()).count()), - static_cast(std::chrono::duration_cast(snap->timestamp - GetVideoWriterStartTime()).count()), - static_cast(Seconds(section_length).count())); - closeEvent(); - event = openEvent(snap, cause, noteSetMap); + } else if (state == TAPE) { + // bulk frame code moved to event. + } // end if state machine + + if ((function == MODECT or function == MOCORD) and snap->image) { + if (!ref_image.Buffer()) { + Debug(1, "Assigning"); + ref_image.Assign(*(snap->image)); + } else { + Debug(1, "Blending"); + ref_image.Blend(*(snap->image), ( state==ALARM ? alarm_ref_blend_perc : ref_blend_perc )); + Debug(1, "Done Blending"); } - } else { - Error("ALARM but no event"); } - } else if (state == ALERT) { - // Alert means this frame has no motion, but we were alarmed and are still recording. - if ((noteSetMap.size() > 0) and event) - event->updateNotes(noteSetMap); - } else if (state == TAPE) { - // bulk frame code moved to event. - } // end if state machine + last_signal = signal; + } // end if videostream + } // end if signal + shared_data->last_frame_score = score; + } else { + Debug(3, "trigger == off"); + if (event) { + Info("%s: %03d - Closing event %" PRIu64 ", trigger off", name.c_str(), analysis_image_count, event->Id()); + closeEvent(); + } + shared_data->state = state = IDLE; + } // end if ( trigger_data->trigger_state != TRIGGER_OFF ) - if ((function == MODECT or function == MOCORD) and snap->image) { - if (!ref_image.Buffer()) { - Debug(1, "Assigning"); - ref_image.Assign(*(snap->image)); - } else { - Debug(1, "Blending"); - ref_image.Blend(*(snap->image), ( state==ALARM ? alarm_ref_blend_perc : ref_blend_perc )); - Debug(1, "Done Blending"); - } - } - last_signal = signal; - } // end if videostream - } // end if signal - shared_data->last_frame_score = score; - } else { - Debug(3, "trigger == off"); - if (event) { - Info("%s: %03d - Closing event %" PRIu64 ", trigger off", name.c_str(), analysis_image_count, event->Id()); - closeEvent(); - } - shared_data->state = state = IDLE; - } // end if ( trigger_data->trigger_state != TRIGGER_OFF ) - - if (event) event->AddPacket(snap); + if (event) event->AddPacket(snap); + } // end scope for event_lock // In the case where people have pre-alarm frames, the web ui will generate the frame images // from the mp4. So no one will notice anyways. From eb59b4de7f920236520724598afd26e8eb503415 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Fri, 14 Jan 2022 14:20:17 -0500 Subject: [PATCH 10/18] Clear out dead code --- src/zm_event.h | 30 ------------------------------ 1 file changed, 30 deletions(-) diff --git a/src/zm_event.h b/src/zm_event.h index be6d922de..74955d23f 100644 --- a/src/zm_event.h +++ b/src/zm_event.h @@ -178,18 +178,6 @@ class Event { return pre_alarm_count; } static void EmptyPreAlarmFrames() { -#if 0 - while ( pre_alarm_count > 0 ) { - int i = pre_alarm_count - 1; - delete pre_alarm_data[i].image; - pre_alarm_data[i].image = nullptr; - if ( pre_alarm_data[i].alarm_frame ) { - delete pre_alarm_data[i].alarm_frame; - pre_alarm_data[i].alarm_frame = nullptr; - } - pre_alarm_count--; - } -#endif pre_alarm_count = 0; } static void AddPreAlarmFrame( @@ -198,28 +186,10 @@ class Event { int score=0, Image *alarm_frame=nullptr ) { -#if 0 - pre_alarm_data[pre_alarm_count].image = new Image(*image); - pre_alarm_data[pre_alarm_count].timestamp = timestamp; - pre_alarm_data[pre_alarm_count].score = score; - if ( alarm_frame ) { - pre_alarm_data[pre_alarm_count].alarm_frame = new Image(*alarm_frame); - } -#endif pre_alarm_count++; } void SavePreAlarmFrames() { -#if 0 - for ( int i = 0; i < pre_alarm_count; i++ ) { - AddFrame( - pre_alarm_data[i].image, - pre_alarm_data[i].timestamp, - pre_alarm_data[i].score, - pre_alarm_data[i].alarm_frame); - } -#endif EmptyPreAlarmFrames(); } }; - #endif // ZM_EVENT_H From 1fe4bbedc0e2e810ec5d036e73d00e9766975636 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Fri, 14 Jan 2022 14:35:16 -0500 Subject: [PATCH 11/18] use min_section_length instead of 0 when testing minimumm section length. Use event->Duration instead of snap-event->StartTime because this snap will not be included in the event. This results in events of 29.59 seconds instead of 30 seconds. --- src/zm_monitor.cpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/zm_monitor.cpp b/src/zm_monitor.cpp index e8b1bb1c0..4a707889f 100644 --- a/src/zm_monitor.cpp +++ b/src/zm_monitor.cpp @@ -2043,7 +2043,7 @@ bool Monitor::Analyse() { if (event) { Debug(2, "Have event %" PRIu64 " in record", event->Id()); - if (section_length != Seconds(0) && (snap->timestamp - event->StartTime() >= section_length) + if (section_length != Seconds(min_section_length) && (event->Duration() >= section_length) && ((function == MOCORD && event_close_mode != CLOSE_TIME) || (function == RECORD && event_close_mode == CLOSE_TIME) || std::chrono::duration_cast(snap->timestamp.time_since_epoch()) % section_length == Seconds(0))) { @@ -2080,7 +2080,7 @@ bool Monitor::Analyse() { && !event->AlarmFrames() && (event_close_mode == CLOSE_ALARM) // FIXME since we won't be including this snap in the event if we close it, we should be looking at event->duration() instead - && ((snap->timestamp - event->StartTime()) >= min_section_length) + && (event->Duration() >= min_section_length) && ((!pre_event_count) || (Event::PreAlarmCount() >= alarm_frame_count - 1))) { Info("%s: %03d - Closing event %" PRIu64 ", continuous end, alarm begins", name.c_str(), image_count, event->Id()); @@ -2092,7 +2092,7 @@ bool Monitor::Analyse() { Event::PreAlarmCount(), pre_event_count, event->Frames(), event->AlarmFrames(), - static_cast(std::chrono::duration_cast(snap->timestamp - event->StartTime()).count()), + static_cast(std::chrono::duration_cast(event->Duration()).count()), static_cast(Seconds(min_section_length).count()), (event_close_mode == CLOSE_ALARM)); } @@ -2142,7 +2142,7 @@ bool Monitor::Analyse() { if ( ((analysis_image_count - last_alarm_count) > post_event_count) && - ((snap->timestamp - event->StartTime()) >= min_section_length)) { + (event->Duration() >= min_section_length)) { Info("%s: %03d - Left alarm state (%" PRIu64 ") - %d(%d) images", name.c_str(), analysis_image_count, event->Id(), event->Frames(), event->AlarmFrames()); if ( @@ -2187,12 +2187,12 @@ bool Monitor::Analyse() { if (event) { if (noteSetMap.size() > 0) event->updateNotes(noteSetMap); - if (section_length != Seconds(0) && (snap->timestamp - event->StartTime() >= section_length)) { + if (section_length != Seconds(min_section_length) && (event->Duration() >= section_length)) { Warning("%s: %03d - event %" PRIu64 ", has exceeded desired section length. %" PRIi64 " - %" PRIi64 " = %" PRIi64 " >= %" PRIi64, name.c_str(), analysis_image_count, event->Id(), static_cast(std::chrono::duration_cast(snap->timestamp.time_since_epoch()).count()), - static_cast(std::chrono::duration_cast(GetVideoWriterStartTime().time_since_epoch()).count()), - static_cast(std::chrono::duration_cast(snap->timestamp - GetVideoWriterStartTime()).count()), + static_cast(std::chrono::duration_cast(event->StartTime().time_since_epoch()).count()), + static_cast(std::chrono::duration_cast(event->Duration()).count()), static_cast(Seconds(section_length).count())); closeEvent(); event = openEvent(snap, cause, noteSetMap); From a4b0aa442a655337c808bde8d0dcabcc0b82851f Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Fri, 14 Jan 2022 14:35:26 -0500 Subject: [PATCH 12/18] Introduce event->Duration() --- src/zm_event.h | 1 + 1 file changed, 1 insertion(+) diff --git a/src/zm_event.h b/src/zm_event.h index 74955d23f..31a23fad4 100644 --- a/src/zm_event.h +++ b/src/zm_event.h @@ -132,6 +132,7 @@ class Event { SystemTimePoint StartTime() const { return start_time; } SystemTimePoint EndTime() const { return end_time; } + TimePoint::duration Duration() const { return end_time - start_time; }; void AddPacket(const std::shared_ptr &p); void AddPacket_(const std::shared_ptr &p); From 3296e142644e6d6aae0dde02b18a6576088d6622 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Fri, 14 Jan 2022 15:19:28 -0500 Subject: [PATCH 13/18] Move RECORD/MOCORD length close code after the state machine. If the state machine has taken action, this code likely won't trigger, but the way it was before it could ignore current state. Plus I think it logically keeps relevant logic closer together. Must use packetqueue.unlock after decoding as analysis might be waiting in the packetqueue. Doing this will do both notifies. Disable de-interlacing for decoding style monitors. --- src/zm_monitor.cpp | 76 ++++++++++++++++++++++++---------------------- 1 file changed, 40 insertions(+), 36 deletions(-) diff --git a/src/zm_monitor.cpp b/src/zm_monitor.cpp index 4a707889f..96d082f71 100644 --- a/src/zm_monitor.cpp +++ b/src/zm_monitor.cpp @@ -2038,39 +2038,6 @@ bool Monitor::Analyse() { ); } // end if active and doing motion detection - if (function == RECORD or function == MOCORD) { - // If doing record, check to see if we need to close the event or not. - if (event) { - Debug(2, "Have event %" PRIu64 " in record", event->Id()); - - if (section_length != Seconds(min_section_length) && (event->Duration() >= section_length) - && ((function == MOCORD && event_close_mode != CLOSE_TIME) - || (function == RECORD && event_close_mode == CLOSE_TIME) - || std::chrono::duration_cast(snap->timestamp.time_since_epoch()) % section_length == Seconds(0))) { - Info("%s: %03d - Closing event %" PRIu64 ", section end forced %" PRIi64 " - %" PRIi64 " = %" PRIi64 " >= %" PRIi64 , - name.c_str(), - image_count, - event->Id(), - static_cast(std::chrono::duration_cast(snap->timestamp.time_since_epoch()).count()), - static_cast(std::chrono::duration_cast(event->StartTime().time_since_epoch()).count()), - static_cast(std::chrono::duration_cast(snap->timestamp - event->StartTime()).count()), - static_cast(Seconds(section_length).count())); - closeEvent(); - } // end if section_length - } // end if event - - if (!event) { - event = openEvent(snap, cause.empty() ? "Continuous" : cause, noteSetMap); - - Info("%s: %03d - Opened new event %" PRIu64 ", continuous section start", - name.c_str(), analysis_image_count, event->Id()); - /* To prevent cancelling out an existing alert\prealarm\alarm state */ - // This ignores current score status. This should all come after the state machine calculations - if (state == IDLE) { - shared_data->state = state = TAPE; - } - } // end if ! event - } // end if RECORDING // If motion detecting, score will be > 0 on motion, but if skipping frames, might not be. So also test snap->score if ((score > 0) or ((snap->score > 0) and (function != MONITOR))) { @@ -2208,6 +2175,40 @@ bool Monitor::Analyse() { // bulk frame code moved to event. } // end if state machine + if (function == RECORD or function == MOCORD) { + // If doing record, check to see if we need to close the event or not. + if (event) { + Debug(2, "Have event %" PRIu64 " in record", event->Id()); + + if (section_length != Seconds(min_section_length) && (event->Duration() >= section_length) + && ((function == MOCORD && event_close_mode != CLOSE_TIME) + || (function == RECORD && event_close_mode == CLOSE_TIME) + || std::chrono::duration_cast(snap->timestamp.time_since_epoch()) % section_length == Seconds(0))) { + Info("%s: %03d - Closing event %" PRIu64 ", section end forced %" PRIi64 " - %" PRIi64 " = %" PRIi64 " >= %" PRIi64 , + name.c_str(), + image_count, + event->Id(), + static_cast(std::chrono::duration_cast(snap->timestamp.time_since_epoch()).count()), + static_cast(std::chrono::duration_cast(event->StartTime().time_since_epoch()).count()), + static_cast(std::chrono::duration_cast(snap->timestamp - event->StartTime()).count()), + static_cast(Seconds(section_length).count())); + closeEvent(); + } // end if section_length + } // end if event + + if (!event) { + event = openEvent(snap, cause.empty() ? "Continuous" : cause, noteSetMap); + + Info("%s: %03d - Opened new event %" PRIu64 ", continuous section start", + name.c_str(), analysis_image_count, event->Id()); + /* To prevent cancelling out an existing alert\prealarm\alarm state */ + // This ignores current score status. This should all come after the state machine calculations + if (state == IDLE) { + shared_data->state = state = TAPE; + } + } // end if ! event + } // end if RECORDING + if ((function == MODECT or function == MOCORD) and snap->image) { if (!ref_image.Buffer()) { Debug(1, "Assigning"); @@ -2685,7 +2686,11 @@ bool Monitor::Decode() { return false; } if (deinterlace_packet_lock->packet_->codec_type == packet->codec_type) { - capture_image->Deinterlace_4Field(deinterlace_packet_lock->packet_->image, (deinterlacing>>8)&0xff); + if (!deinterlace_packet_lock->packet_->image) { + Error("Can't de-interlace when we have to decode. De-Interlacing should only be useful on old low res local cams"); + } else { + capture_image->Deinterlace_4Field(deinterlace_packet_lock->packet_->image, (deinterlacing>>8)&0xff); + } delete deinterlace_packet_lock; //packetqueue.unlock(deinterlace_packet_lock); break; @@ -2738,8 +2743,7 @@ bool Monitor::Decode() { shared_data->signal = (capture_image and signal_check_points) ? CheckSignal(capture_image) : true; shared_data->last_write_index = index; shared_data->last_write_time = std::chrono::system_clock::to_time_t(packet->timestamp); - delete packet_lock; - //packetqueue.unlock(packet_lock); + packetqueue.unlock(packet_lock); return true; } // end bool Monitor::Decode() From aba387f20255cbc8766056d793899c3cafb9a5fa Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Fri, 14 Jan 2022 16:31:52 -0500 Subject: [PATCH 14/18] Fix !== which should be >= --- src/zm_monitor.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/zm_monitor.cpp b/src/zm_monitor.cpp index 96d082f71..18f72f6b3 100644 --- a/src/zm_monitor.cpp +++ b/src/zm_monitor.cpp @@ -2154,7 +2154,7 @@ bool Monitor::Analyse() { if (event) { if (noteSetMap.size() > 0) event->updateNotes(noteSetMap); - if (section_length != Seconds(min_section_length) && (event->Duration() >= section_length)) { + if (section_length >= Seconds(min_section_length) && (event->Duration() >= section_length)) { Warning("%s: %03d - event %" PRIu64 ", has exceeded desired section length. %" PRIi64 " - %" PRIi64 " = %" PRIi64 " >= %" PRIi64, name.c_str(), analysis_image_count, event->Id(), static_cast(std::chrono::duration_cast(snap->timestamp.time_since_epoch()).count()), @@ -2180,7 +2180,7 @@ bool Monitor::Analyse() { if (event) { Debug(2, "Have event %" PRIu64 " in record", event->Id()); - if (section_length != Seconds(min_section_length) && (event->Duration() >= section_length) + if (section_length >= Seconds(min_section_length) && (event->Duration() >= section_length) && ((function == MOCORD && event_close_mode != CLOSE_TIME) || (function == RECORD && event_close_mode == CLOSE_TIME) || std::chrono::duration_cast(snap->timestamp.time_since_epoch()) % section_length == Seconds(0))) { From 8d757d37a6a85f6b8eeef4b5fa47d02bdc927f0e Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Fri, 14 Jan 2022 16:43:59 -0500 Subject: [PATCH 15/18] fix eslint --- web/skins/classic/views/js/cycle.js | 17 ++-- web/skins/classic/views/js/montage.js | 102 +++++++++++------------ web/skins/classic/views/js/watch.js | 115 +++++++++++++------------- 3 files changed, 118 insertions(+), 116 deletions(-) diff --git a/web/skins/classic/views/js/cycle.js b/web/skins/classic/views/js/cycle.js index 401e36760..670671faa 100644 --- a/web/skins/classic/views/js/cycle.js +++ b/web/skins/classic/views/js/cycle.js @@ -62,7 +62,7 @@ function initCycle() { opaqueId: opaqueId, success: function(pluginHandle) { streaming2 = pluginHandle; - var body = { "request": "watch", "id":monitorData[monIdx].id }; + var body = {"request": "watch", "id": monitorData[monIdx].id}; streaming2.send({"message": body}); }, error: function(error) { @@ -72,26 +72,27 @@ function initCycle() { Janus.debug(" ::: Got a message :::"); Janus.debug(msg); var result = msg["result"]; - if(result !== null && result !== undefined) { - if(result["status"] !== undefined && result["status"] !== null) { - var status = result["status"]; + if (result !== null && result !== undefined) { + if (result["status"] !== undefined && result["status"] !== null) { + const status = result["status"]; + console.log(status); } - } else if(msg["error"] !== undefined && msg["error"] !== null) { + } else if (msg["error"] !== undefined && msg["error"] !== null) { Janus.debug(msg["error"]); return; } - if(jsep !== undefined && jsep !== null) { + if (jsep !== undefined && jsep !== null) { Janus.debug("Handling SDP as well..."); Janus.debug(jsep); // Offer from the plugin, let's answer streaming2.createAnswer({ jsep: jsep, // We want recvonly audio/video and, if negotiated, datachannels - media: { audioSend: false, videoSend: false, data: true }, + media: {audioSend: false, videoSend: false, data: true}, success: function(jsep) { Janus.debug("Got SDP!"); Janus.debug(jsep); - var body = { "request": "start"}; + var body = {"request": "start"}; streaming2.send({"message": body, "jsep": jsep}); }, error: function(error) { diff --git a/web/skins/classic/views/js/montage.js b/web/skins/classic/views/js/montage.js index d02fb63f8..369f23e8f 100644 --- a/web/skins/classic/views/js/montage.js +++ b/web/skins/classic/views/js/montage.js @@ -307,7 +307,7 @@ function initPage() { $j("#hdrbutton").toggleClass('glyphicon-menu-down').toggleClass('glyphicon-menu-up'); } var initJanus = false; - var streamingMonitors = []; + //var streamingMonitors = []; for ( var i = 0, length = monitorData.length; i < length; i++ ) { if (monitorData[i].janusEnabled) { initJanus = true; @@ -353,57 +353,57 @@ function watchFullscreen() { } function attachVideo(janus, i) { - janus.attach({ - plugin: "janus.plugin.streaming", - opaqueId:"streamingtest-"+Janus.randomString(12), - success: function(pluginHandle) { - janusMonitors[i].streaming = pluginHandle; - var body = { "request": "watch", "id":parseInt(janusMonitors[i].id) }; - janusMonitors[i].streaming.send({"message": body}); - }, - error: function(error) { - Janus.error(" -- Error attaching plugin... ", error); - }, - onmessage: function(msg, jsep) { - Janus.debug(" ::: Got a message :::"); - Janus.debug(msg); - var result = msg["result"]; - if(result !== null && result !== undefined) { - if(result["status"] !== undefined && result["status"] !== null) { - var status = result["status"]; - } - } else if(msg["error"] !== undefined && msg["error"] !== null) { - Janus.error(msg["error"]); - return; - } - if(jsep !== undefined && jsep !== null) { - Janus.debug("Handling SDP as well..."); - Janus.debug(jsep); - // Offer from the plugin, let's answer - janusMonitors[i].streaming.createAnswer({ - jsep: jsep, - // We want recvonly audio/video and, if negotiated, datachannels - media: { audioSend: false, videoSend: false, data: true }, - success: function(jsep) { - Janus.debug("Got SDP!"); - Janus.debug(jsep); - var body = { "request": "start"}; - janusMonitors[i].streaming.send({"message": body, "jsep": jsep}); - }, - error: function(error) { - Janus.error("WebRTC error:", error); - } - }); - } - }, //onmessage function - onremotestream: function(ourstream) { - Janus.debug(" ::: Got a remote track :::"); - Janus.debug(ourstream); - Janus.attachMediaStream(document.getElementById("liveStream" + janusMonitors[i].id), ourstream); - document.getElementById("liveStream" + janusMonitors[i].id).play() + janus.attach({ + plugin: "janus.plugin.streaming", + opaqueId: "streamingtest-"+Janus.randomString(12), + success: function(pluginHandle) { + janusMonitors[i].streaming = pluginHandle; + var body = {"request": "watch", "id": parseInt(janusMonitors[i].id)}; + janusMonitors[i].streaming.send({"message": body}); + }, + error: function(error) { + Janus.error(" -- Error attaching plugin... ", error); + }, + onmessage: function(msg, jsep) { + Janus.debug(" ::: Got a message :::"); + Janus.debug(msg); + var result = msg["result"]; + if (result !== null && result !== undefined) { + if (result["status"] !== undefined && result["status"] !== null) { + const status = result["status"]; + console.log(status); } - });// attach - + } else if (msg["error"] !== undefined && msg["error"] !== null) { + Janus.error(msg["error"]); + return; + } + if (jsep !== undefined && jsep !== null) { + Janus.debug("Handling SDP as well..."); + Janus.debug(jsep); + // Offer from the plugin, let's answer + janusMonitors[i].streaming.createAnswer({ + jsep: jsep, + // We want recvonly audio/video and, if negotiated, datachannels + media: {audioSend: false, videoSend: false, data: true}, + success: function(jsep) { + Janus.debug("Got SDP!"); + Janus.debug(jsep); + var body = {"request": "start"}; + janusMonitors[i].streaming.send({"message": body, "jsep": jsep}); + }, + error: function(error) { + Janus.error("WebRTC error:", error); + } + }); + } + }, //onmessage function + onremotestream: function(ourstream) { + Janus.debug(" ::: Got a remote track :::"); + Janus.debug(ourstream); + Janus.attachMediaStream(document.getElementById("liveStream" + janusMonitors[i].id), ourstream); + document.getElementById("liveStream" + janusMonitors[i].id).play(); + } + });// attach } // Kick everything off $j(document).ready(initPage); diff --git a/web/skins/classic/views/js/watch.js b/web/skins/classic/views/js/watch.js index f3ea6385f..13d54928a 100644 --- a/web/skins/classic/views/js/watch.js +++ b/web/skins/classic/views/js/watch.js @@ -911,65 +911,66 @@ function initPage() { } } if (streamMode == 'janus') { - server = "http://" + window.location.hostname + ":8088/janus"; - opaqueId = "streamingtest-"+Janus.randomString(12); - Janus.init({debug: "all", callback: function() { - janus = new Janus({ - server: server, - success: function() { - janus.attach({ - plugin: "janus.plugin.streaming", - opaqueId: opaqueId, - success: function(pluginHandle) { - streaming2 = pluginHandle; - var body = { "request": "watch", "id":monitorId }; - streaming2.send({"message": body}); - }, - error: function(error) { - Janus.error(" -- Error attaching plugin... ", error); - }, - onmessage: function(msg, jsep) { - Janus.debug(" ::: Got a message :::"); - Janus.debug(msg); - var result = msg["result"]; - if(result !== null && result !== undefined) { - if(result["status"] !== undefined && result["status"] !== null) { - var status = result["status"]; - } - } else if(msg["error"] !== undefined && msg["error"] !== null) { - Janus.error(msg["error"]); - return; - } - if(jsep !== undefined && jsep !== null) { - Janus.debug("Handling SDP as well..."); - Janus.debug(jsep); - // Offer from the plugin, let's answer - streaming2.createAnswer({ - jsep: jsep, - // We want recvonly audio/video and, if negotiated, datachannels - media: { audioSend: false, videoSend: false, data: true }, - success: function(jsep) { - Janus.debug("Got SDP!"); - Janus.debug(jsep); - var body = { "request": "start"}; - streaming2.send({"message": body, "jsep": jsep}); - }, - error: function(error) { - Janus.error("WebRTC error:", error); + server = "http://" + window.location.hostname + ":8088/janus"; + opaqueId = "streamingtest-"+Janus.randomString(12); + Janus.init({debug: "all", callback: function() { + janus = new Janus({ + server: server, + success: function() { + janus.attach({ + plugin: "janus.plugin.streaming", + opaqueId: opaqueId, + success: function(pluginHandle) { + streaming2 = pluginHandle; + var body = {"request": "watch", "id": monitorId}; + streaming2.send({"message": body}); + }, + error: function(error) { + Janus.error(" -- Error attaching plugin... ", error); + }, + onmessage: function(msg, jsep) { + Janus.debug(" ::: Got a message :::"); + Janus.debug(msg); + var result = msg["result"]; + if (result !== null && result !== undefined) { + if (result["status"] !== undefined && result["status"] !== null) { + var status = result["status"]; + console.log(status); } - }); + } else if (msg["error"] !== undefined && msg["error"] !== null) { + Janus.error(msg["error"]); + return; + } + if (jsep !== undefined && jsep !== null) { + Janus.debug("Handling SDP as well..."); + Janus.debug(jsep); + // Offer from the plugin, let's answer + streaming2.createAnswer({ + jsep: jsep, + // We want recvonly audio/video and, if negotiated, datachannels + media: {audioSend: false, videoSend: false, data: true}, + success: function(jsep) { + Janus.debug("Got SDP!"); + Janus.debug(jsep); + var body = {"request": "start"}; + streaming2.send({"message": body, "jsep": jsep}); + }, + error: function(error) { + Janus.error("WebRTC error:", error); + } + }); + } + }, //onmessage function + onremotestream: function(stream) { + Janus.debug(" ::: Got a remote track :::"); + Janus.debug(stream); + Janus.attachMediaStream(document.getElementById("liveStream" + monitorId), stream); + document.getElementById("liveStream" + monitorId).play(); } - }, //onmessage function - onremotestream: function(stream) { - Janus.debug(" ::: Got a remote track :::"); - Janus.debug(stream); - Janus.attachMediaStream(document.getElementById("liveStream" + monitorId), stream); - document.getElementById("liveStream" + monitorId).play(); - } - });// attach - } //Success functio - }); //new Janus - }}); //janus.init callback + }); // attach + } //Success functio + }); //new Janus + }}); //janus.init callback } else if (canStreamNative || (streamMode == 'single')) { var streamImg = $j('#imageFeed img'); if (!streamImg) streamImg = $j('#imageFeed object'); From 4cd085e0852d29a5606bda6215665b5ca6df8f26 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Sat, 15 Jan 2022 17:18:55 -0500 Subject: [PATCH 16/18] Bump eslint to ECMA2017 --- .eslintrc.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.eslintrc.js b/.eslintrc.js index 0bb050ad1..16cbb37ba 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -3,7 +3,7 @@ module.exports = { "env": { "browser": true, - "es6": true, + "es2017": true, }, "extends": ["google"], "overrides": [{ From 1891002c65e6e19a59604e06b11c7573901a8672 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Sat, 15 Jan 2022 17:19:10 -0500 Subject: [PATCH 17/18] Remove Freenode from the badges. --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index 2249f1752..da96b3e60 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,6 @@ ZoneMinder [![Build Status](https://travis-ci.org/ZoneMinder/zoneminder.png)](https://travis-ci.org/ZoneMinder/zoneminder) [![Bounty Source](https://api.bountysource.com/badge/team?team_id=204&style=bounties_received)](https://www.bountysource.com/teams/zoneminder/issues?utm_source=ZoneMinder&utm_medium=shield&utm_campaign=bounties_received) [![Join Slack](https://github.com/ozonesecurity/ozonebase/blob/master/img/slacksm.png?raw=true)](https://join.slack.com/t/zoneminder-chat/shared_invite/enQtNTU0NDkxMDM5NDQwLTdhZmQ5Y2M2NWQyN2JkYTBiN2ZkMzIzZGQ0MDliMTRmM2FjZWRlYzUwYTQ2MjMwMTVjMzQ1NjYxOTdmMjE2MTE) -[![IRC Network](https://img.shields.io/badge/irc-%23zoneminder-blue.svg "IRC Freenode")](https://webchat.freenode.net/?channels=zoneminder) All documentation for ZoneMinder is now online at https://zoneminder.readthedocs.org From 5f17cb6e9a8c65611eb1b3589a1d9b43f51be26e Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Sat, 15 Jan 2022 17:19:30 -0500 Subject: [PATCH 18/18] Debug the size returned as compared to the file size --- src/zm_eventstream.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/zm_eventstream.cpp b/src/zm_eventstream.cpp index 927c3b3aa..54f6b60d8 100644 --- a/src/zm_eventstream.cpp +++ b/src/zm_eventstream.cpp @@ -1126,7 +1126,8 @@ bool EventStream::send_file(const std::string &filepath) { } else { Debug(1, "Failed to sendfile?"); } - Warning("Unable to send raw frame %ld: %s rc %d", curr_frame_id, strerror(errno), rc); + Warning("Unable to send raw frame %ld: %s rc %d != %d", + curr_frame_id, strerror(errno), rc, (int)filestat.st_size); #endif static unsigned char temp_img_buffer[ZM_MAX_IMAGE_SIZE];