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/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/src/zm_monitor.cpp b/src/zm_monitor.cpp index 18f72f6b3..e638ed8c8 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, " @@ -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++; @@ -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) { - 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 + + //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); - if (proxyEvent.PullMessages(response.SubscriptionReference.Address, NULL, &tev__PullMessages, tev__PullMessagesResponse) != SOAP_OK) { - Warning("Couldn't do initial event pull! %s", response.SubscriptionReference.Address); + Debug(1, "ONVIF Endpoint: %s", proxyEvent.soap_endpoint); + if (proxyEvent.CreatePullPointSubscription(&request, response) != SOAP_OK) { + Warning("Couldn't create subscription!"); } else { - Debug(1, "Good Initial ONVIF Pull"); - ONVIF_Healthy = TRUE; + //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"); + } + //End ONVIF Setup +#endif + +#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!"); } } - } else { - Debug(1, "Not Starting ONVIF"); - } - //End ONVIF Setup #endif + } else if (!shared_data->valid) { Error("Shared data not initialised by capture daemon for monitor %s", name.c_str()); return false; @@ -3141,7 +3154,9 @@ 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 && ONVIF_Healthy) { + if (!Poller) { Poller = zm::make_unique(this); } else { @@ -3203,6 +3218,11 @@ int Monitor::Close() { soap = nullptr; } //End ONVIF #endif +#if HAVE_LIBCURL //Janus Teardown + if (janus_enabled && (purpose == CAPTURE)) { + remove_from_janus(); + } +#endif packetqueue.clear(); if (audio_fifo) { @@ -3339,3 +3359,141 @@ 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::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); + + //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()); + res = curl_easy_perform(curl); + if (res != CURLE_OK) return -1; + + pos = response.find("\"id\": "); + 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\"}"; + 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()); + 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); //TODO: This is an assumption that the string is always 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()); + res = curl_easy_perform(curl); + if (res != CURLE_OK) return -1; + Debug(1,"Added stream to Janus: %s", response.c_str()); + curl_easy_cleanup(curl); + return 0; +} +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; + CURLcode res; + + curl = curl_easy_init(); + if(!curl) return -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()); + 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; + 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()); + 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; + + //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()); + res = curl_easy_perform(curl); + if (res != CURLE_OK) return -1; + + Debug(1, "Removed stream from Janus: %s", response.c_str()); + curl_easy_cleanup(curl); + return 0; +} diff --git a/src/zm_monitor.h b/src/zm_monitor.h index 4be731c5b..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. @@ -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/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() ?> }; 0 ) { - setInterval(reloadWebSite, interval*1000, i); + if (monitorData[i].janusEnabled) { + initJanus = true; + janusMonitors.push(monitorData[i]); + } + } + if (initJanus) { + 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() { + for ( var i = 0, length = janusMonitors.length; i < length; i++ ) { + attachVideo(janus, i); + } + } + }); + }}); + } + for ( var i = 0, length = monitorData.length; i < length; i++ ) { + if (!monitorData[i].janusEnabled) { + monitors[i] = new MonitorStream(monitorData[i]); + + // Start the fps and status updates. give a random delay so that we don't assault the server + var delay = Math.round( (Math.random()+0.5)*statusRefreshTimeout ); + console.log("delay: " + delay); + monitors[i].start(delay); + + var interval = monitors[i].refresh; + if ( monitors[i].type == 'WebSite' && interval > 0 ) { + setInterval(reloadWebSite, interval*1000, i); + } + monitors[i].setup_onclick(); } - monitors[i].setup_onclick(); } selectLayout('#zmMontageLayout'); } @@ -322,5 +351,59 @@ function watchFullscreen() { const content = document.getElementById('content'); openFullscreen(content); } + +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() + } + });// attach + +} // Kick everything off $j(document).ready(initPage); diff --git a/web/skins/classic/views/js/montage.js.php b/web/skins/classic/views/js/montage.js.php index 1c1c4130e..fac9e4d84 100644 --- a/web/skins/classic/views/js/montage.js.php +++ b/web/skins/classic/views/js/montage.js.php @@ -24,6 +24,7 @@ monitorData[monitorData.length] = { 'connKey': connKey() ?>, 'width': ViewWidth() ?>, 'height':ViewHeight() ?>, + 'janusEnabled':JanusEnabled() ?>, 'url': 'UrlToIndex( ZM_MIN_STREAMING_PORT ? ($monitor->Id() + ZM_MIN_STREAMING_PORT) : '') ?>', 'onclick': function(){window.location.assign( '?view=watch&mid=Id() ?>' );}, 'type': 'Type() ?>', diff --git a/web/skins/classic/views/js/watch.js b/web/skins/classic/views/js/watch.js index 89f774ed1..f3ea6385f 100644 --- a/web/skins/classic/views/js/watch.js +++ b/web/skins/classic/views/js/watch.js @@ -9,6 +9,10 @@ var forceAlmBtn = $j('#forceAlmBtn'); var table = $j('#eventList'); var filterQuery = '&filter[Query][terms][0][attr]=MonitorId&filter[Query][terms][0][op]=%3d&filter[Query][terms][0][val]='+monitorId; +var server; +var janus = null; +var opaqueId; +var streaming2; /* This is the format of the json object sent by bootstrap-table @@ -897,15 +901,76 @@ function initPage() { } if (monitorType != 'WebSite') { - if (streamMode == 'single') { - statusCmdTimer = setTimeout(statusCmdQuery, 200); - setInterval(watchdogCheck, statusRefreshTimeout*2, 'status'); - } else { - streamCmdTimer = setTimeout(streamCmdQuery, 200); - setInterval(watchdogCheck, statusRefreshTimeout*2, 'stream'); + if (streamMode != 'janus') { + if (streamMode == 'single') { + statusCmdTimer = setTimeout(statusCmdQuery, 200); + setInterval(watchdogCheck, statusRefreshTimeout*2, 'status'); + } else { + streamCmdTimer = setTimeout(streamCmdQuery, 200); + setInterval(watchdogCheck, statusRefreshTimeout*2, 'stream'); + } } - - if (canStreamNative || (streamMode == 'single')) { + 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); + } + }); + } + }, //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 + } else if (canStreamNative || (streamMode == 'single')) { var streamImg = $j('#imageFeed img'); if (!streamImg) streamImg = $j('#imageFeed object'); if (!streamImg) { diff --git a/web/skins/classic/views/js/watch.js.php b/web/skins/classic/views/js/watch.js.php index be8065d12..2f383ab24 100644 --- a/web/skins/classic/views/js/watch.js.php +++ b/web/skins/classic/views/js/watch.js.php @@ -66,6 +66,7 @@ monitorData[monitorData.length] = { 'id': Id() ?>, 'width': ViewWidth() ?>, 'height':ViewHeight() ?>, + 'janusEnabled':JanusEnabled() ?>, 'url': 'UrlToIndex() ?>', 'onclick': function(){window.location.assign( '?view=watch&mid=Id() ?>' );}, 'type': 'Type() ?>', diff --git a/web/skins/classic/views/monitor.php b/web/skins/classic/views/monitor.php index 4c9ce3810..f129315bc 100644 --- a/web/skins/classic/views/monitor.php +++ b/web/skins/classic/views/monitor.php @@ -574,6 +574,16 @@ if (count($available_monitor_ids)) { if ( isset($OLANG['FUNCTION_DECODING_ENABLED']) ) { echo '
'.$OLANG['FUNCTION_DECODING_ENABLED']['Help'].'
'; } +?> + + + + + JanusEnabled() ? ' checked="checked"' : '' ?>/> +'.$OLANG['FUNCTION_JANUS_ENABLED']['Help'].''; + } ?> diff --git a/web/skins/classic/views/montage.php b/web/skins/classic/views/montage.php index deaddcaf3..ed1869fab 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) { + + 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 ) { ?> + +