From 3a9c16aeeaef051d35cd3862f1ddf5e0fd3047eb Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Mon, 10 Jan 2022 23:22:37 -0600 Subject: [PATCH 1/6] 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 2/6] 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 3/6] 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 4/6] 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 5/6] 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 6/6] 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() ?> };