Merge pull request #3413 from jp-bennett/master

Adds janus updates
This commit is contained in:
Isaac Connor 2022-01-23 12:35:42 -05:00 committed by GitHub
commit f10d0fd3f5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 210 additions and 83 deletions

View File

@ -371,6 +371,28 @@ our @options = (
type => $types{boolean}, type => $types{boolean},
category => 'system', category => 'system',
}, },
{
name => 'ZM_JANUS_SECRET',
default => '',
description => 'Password for Janus streaming administration.',
help => q`This value should be set to a secure password,
and match the admin_key value in janus.plugin.streaming.config.
`,
type => $types{string},
category => 'system',
},
{
name => 'ZM_JANUS_PATH',
default => '',
description => 'URL for Janus HTTP/S port',
help => q`Janus requires HTTP/S communication to administer
and initiate h.264 streams. If left blank, this will default to
the ZM hostname, port 8088/janus. This setting is particularly
useful for putting janus behind a reverse proxy.
`,
type => $types{string},
category => 'system',
},
{ {
name => 'ZM_ENABLE_CSRF_MAGIC', name => 'ZM_ENABLE_CSRF_MAGIC',
default => 'yes', default => 'yes',

View File

@ -52,6 +52,7 @@
#include <algorithm> #include <algorithm>
#include <sys/types.h> #include <sys/types.h>
#include <sys/stat.h> #include <sys/stat.h>
#include <chrono>
#if ZM_MEM_MAPPED #if ZM_MEM_MAPPED
#include <sys/mman.h> #include <sys/mman.h>
@ -1122,10 +1123,9 @@ bool Monitor::connect() {
#if HAVE_LIBCURL //janus setup. Depends on libcurl. #if HAVE_LIBCURL //janus setup. Depends on libcurl.
if (janus_enabled && (path.find("rtsp://") != std::string::npos)) { if (janus_enabled && (path.find("rtsp://") != std::string::npos)) {
get_janus_session();
if (add_to_janus() != 0) { if (add_to_janus() != 0) {
if (add_to_janus() != 0) { //The initial attempt may fail. This is a temporary workaround. Warning("Failed to add monitor stream to Janus!"); //The first attempt may fail. Will be reattempted in the Poller thread
Warning("Failed to add monitor stream to Janus!");
}
} }
} }
#else #else
@ -1797,6 +1797,8 @@ void Monitor::UpdateFPS() {
//Thread where ONVIF polling, and other similar status polling can happen. //Thread where ONVIF polling, and other similar status polling can happen.
//Since these can be blocking, run here to avoid intefering with other processing //Since these can be blocking, run here to avoid intefering with other processing
bool Monitor::Poll() { bool Monitor::Poll() {
//We want to trigger every 5 seconds or so. so grab the time at the beginning of the loop, and sleep at the end.
std::chrono::system_clock::time_point loop_start_time = std::chrono::system_clock::now();
#ifdef WITH_GSOAP #ifdef WITH_GSOAP
if (ONVIF_Healthy) { if (ONVIF_Healthy) {
@ -1838,10 +1840,17 @@ bool Monitor::Poll() {
} }
} }
} }
} else {
std::this_thread::sleep_for (std::chrono::seconds(1)); //thread sleep to avoid the busy loop.
} }
#endif #endif
if (janus_enabled) {
if (janus_session.empty()) {
get_janus_session();
}
if (check_janus() == 0) {
add_to_janus();
}
}
std::this_thread::sleep_until(loop_start_time + std::chrono::seconds(5));
return TRUE; return TRUE;
} //end Poll } //end Poll
@ -3166,10 +3175,8 @@ int Monitor::PrimeCapture() {
} }
} // end if rtsp_server } // end if rtsp_server
#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. //Poller Thread
//ONVIF Thread if (onvif_event_listener || janus_enabled) {
if (onvif_event_listener && ONVIF_Healthy) {
if (!Poller) { if (!Poller) {
Poller = zm::make_unique<PollThread>(this); Poller = zm::make_unique<PollThread>(this);
@ -3177,7 +3184,7 @@ int Monitor::PrimeCapture() {
Poller->Start(); Poller->Start();
} }
} }
#endif
if (decoding_enabled) { if (decoding_enabled) {
if (!decoder_it) decoder_it = packetqueue.get_video_it(false); if (!decoder_it) decoder_it = packetqueue.get_video_it(false);
if (!decoder) { if (!decoder) {
@ -3381,14 +3388,17 @@ size_t Monitor::WriteCallback(void *contents, size_t size, size_t nmemb, void *u
} }
int Monitor::add_to_janus() { int Monitor::add_to_janus() {
//TODO clean this up, add error checking, etc
std::string response; std::string response;
std::string endpoint = "127.0.0.1:8088/janus/"; std::string endpoint;
if ((config.janus_path != nullptr) && (config.janus_path[0] != '\0')) {
endpoint = config.janus_path;
} else {
endpoint = "127.0.0.1:8088/janus/";
}
std::string postData = "{\"janus\" : \"create\", \"transaction\" : \"randomString\"}"; std::string postData = "{\"janus\" : \"create\", \"transaction\" : \"randomString\"}";
std::string rtsp_username; std::string rtsp_username;
std::string rtsp_password; std::string rtsp_password;
std::string rtsp_path = "rtsp://"; std::string rtsp_path = "rtsp://";
std::string janus_id;
std::size_t pos; std::size_t pos;
std::size_t pos2; std::size_t pos2;
CURLcode res; CURLcode res;
@ -3409,44 +3419,15 @@ int Monitor::add_to_janus() {
rtsp_password = path.substr(pos+1, pos2 - pos - 1); rtsp_password = path.substr(pos+1, pos2 - pos - 1);
rtsp_path += path.substr(pos2 + 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) {
Error("Failed to curl_easy_perform getting session/handle id");
curl_easy_cleanup(curl);
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) {
Error("Failed to curl_easy_perform attaching");
curl_easy_cleanup(curl);
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 += "/";
endpoint += handle_id; endpoint += janus_session;
//Assemble our actual request //Assemble our actual request
postData = "{\"janus\" : \"message\", \"transaction\" : \"randomString\", \"body\" : {"; postData = "{\"janus\" : \"message\", \"transaction\" : \"randomString\", \"body\" : {";
postData += "\"request\" : \"create\", \"admin_key\" : \"supersecret\", \"type\" : \"rtsp\", "; postData += "\"request\" : \"create\", \"admin_key\" : \"";
postData += "\"url\" : \""; postData += config.janus_secret;
postData += "\", \"type\" : \"rtsp\", ";
postData += "\"url\" : \"";
postData += rtsp_path; postData += rtsp_path;
postData += "\", \"rtsp_user\" : \""; postData += "\", \"rtsp_user\" : \"";
postData += rtsp_username; postData += rtsp_username;
@ -3467,52 +3448,39 @@ int Monitor::add_to_janus() {
curl_easy_cleanup(curl); curl_easy_cleanup(curl);
return -1; return -1;
} }
if ((response.find("error") != std::string::npos) && ((response.find("No such session") != std::string::npos) || (response.find("No such handle") != std::string::npos))) {
janus_session = "";
curl_easy_cleanup(curl);
return -2;
}
//scan for missing session or handle id "No such session" "no such handle"
Debug(1,"Added stream to Janus: %s", response.c_str()); Debug(1,"Added stream to Janus: %s", response.c_str());
curl_easy_cleanup(curl); curl_easy_cleanup(curl);
return 0; return 0;
} }
int Monitor::remove_from_janus() {
//TODO clean this up, add error checking, etc int Monitor::check_janus() {
std::string response; std::string response;
std::string endpoint = "127.0.0.1:8088/janus/"; std::string endpoint;
std::string postData = "{\"janus\" : \"create\", \"transaction\" : \"randomString\"}"; if ((config.janus_path != nullptr) && (config.janus_path[0] != '\0')) {
std::size_t pos; endpoint = config.janus_path;
} else {
endpoint = "127.0.0.1:8088/janus/";
}
std::string postData;
//std::size_t pos;
CURLcode res; CURLcode res;
curl = curl_easy_init(); curl = curl_easy_init();
if(!curl) return -1; 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 += "/";
endpoint += handle_id; endpoint += janus_session;
//Assemble our actual request //Assemble our actual request
postData = "{\"janus\" : \"message\", \"transaction\" : \"randomString\", \"body\" : {"; postData = "{\"janus\" : \"message\", \"transaction\" : \"randomString\", \"body\" : {";
postData += "\"request\" : \"destroy\", \"admin_key\" : \"supersecret\", \"id\" : "; postData += "\"request\" : \"info\", \"id\" : ";
postData += std::to_string(id); postData += std::to_string(id);
postData += "}}"; postData += "}}";
@ -3521,9 +3489,128 @@ int Monitor::remove_from_janus() {
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response); curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, postData.c_str()); curl_easy_setopt(curl, CURLOPT_POSTFIELDS, postData.c_str());
res = curl_easy_perform(curl); res = curl_easy_perform(curl);
if (res != CURLE_OK) return -1; if (res != CURLE_OK) { //may mean an error code thrown by Janus, because of a bad session
Warning("Attempted %s got %s", endpoint.c_str(), curl_easy_strerror(res));
curl_easy_cleanup(curl);
janus_session = "";
return -1;
}
curl_easy_cleanup(curl);
Debug(1, "Queried for stream status: %s", response.c_str());
if ((response.find("error") != std::string::npos) && ((response.find("No such session") != std::string::npos) || (response.find("No such handle") != std::string::npos))) {
Warning("Janus Session timed out");
janus_session = "";
return -2;
} else if (response.find("No such mountpoint") != std::string::npos) {
Warning("Mountpoint Missing");
return 0;
} else {
return 1;
}
}
int Monitor::remove_from_janus() {
std::string response;
std::string endpoint;
if ((config.janus_path != nullptr) && (config.janus_path[0] != '\0')) {
endpoint = config.janus_path;
} else {
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;
endpoint += "/";
endpoint += janus_session;
//Assemble our actual request
postData = "{\"janus\" : \"message\", \"transaction\" : \"randomString\", \"body\" : {";
postData += "\"request\" : \"destroy\", \"admin_key\" : \"";
postData += config.janus_secret;
postData += "\", \"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) {
Warning("Libcurl attempted %s got %s", endpoint.c_str(), curl_easy_strerror(res));
curl_easy_cleanup(curl);
return -1;
}
Debug(1, "Removed stream from Janus: %s", response.c_str()); Debug(1, "Removed stream from Janus: %s", response.c_str());
curl_easy_cleanup(curl); curl_easy_cleanup(curl);
return 0; return 0;
} }
int Monitor::get_janus_session() {
std::string response;
std::string endpoint;
if ((config.janus_path != nullptr) && (config.janus_path[0] != '\0')) {
endpoint = config.janus_path;
} else {
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) {
Warning("Libcurl attempted %s got %s", endpoint.c_str(), curl_easy_strerror(res));
curl_easy_cleanup(curl);
return -1;
}
pos = response.find("\"id\": ");
if (pos == std::string::npos)
{
curl_easy_cleanup(curl);
return -1;
}
janus_session = response.substr(pos + 6, 16);
response = "";
endpoint += "/";
endpoint += janus_session;
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)
{
Warning("Libcurl attempted %s got %s", endpoint.c_str(), curl_easy_strerror(res));
curl_easy_cleanup(curl);
return -1;
}
pos = response.find("\"id\": ");
if (pos == std::string::npos)
{
curl_easy_cleanup(curl);
return -1;
}
janus_session += "/";
janus_session += response.substr(pos + 6, 16);
curl_easy_cleanup(curl);
return 1;
} //get_janus_session

View File

@ -455,6 +455,8 @@ protected:
static size_t WriteCallback(void *contents, size_t size, size_t nmemb, void *userp); static size_t WriteCallback(void *contents, size_t size, size_t nmemb, void *userp);
int add_to_janus(); int add_to_janus();
int remove_from_janus(); int remove_from_janus();
int get_janus_session();
std::string janus_session;
// Used in check signal // Used in check signal
uint8_t red_val; uint8_t red_val;
@ -521,6 +523,7 @@ public:
bool OnvifEnabled() { bool OnvifEnabled() {
return onvif_event_listener; return onvif_event_listener;
} }
int check_janus(); //returns 1 for healthy, 0 for success but missing stream, negative for error.
#ifdef WITH_GSOAP #ifdef WITH_GSOAP
bool OnvifHealthy() { bool OnvifHealthy() {
return ONVIF_Healthy; return ONVIF_Healthy;

View File

@ -2095,7 +2095,7 @@ function getStreamHTML($monitor, $options = array()) {
) ); ) );
return getVideoStreamHTML( 'liveStream'.$monitor->Id(), $streamSrc, $options['width'], $options['height'], ZM_MPEG_LIVE_FORMAT, $monitor->Name() ); return getVideoStreamHTML( 'liveStream'.$monitor->Id(), $streamSrc, $options['width'], $options['height'], ZM_MPEG_LIVE_FORMAT, $monitor->Name() );
} else if ( $monitor->JanusEnabled() ) { } else if ( $monitor->JanusEnabled() ) {
return '<video id="liveStream'.$monitor->Id().'" width="'.$options['width'].'"autoplay muted playsinline=""></video>'; return '<video id="liveStream'.$monitor->Id().'" width="'.$options['width'].'"autoplay muted controls playsinline="" ></video>';
} else if ( $options['mode'] == 'stream' and canStream() ) { } else if ( $options['mode'] == 'stream' and canStream() ) {
$options['mode'] = 'jpeg'; $options['mode'] = 'jpeg';
$streamSrc = $monitor->getStreamSrc($options); $streamSrc = $monitor->getStreamSrc($options);

View File

@ -92,7 +92,9 @@ function MonitorStream(monitorData) {
if (this.janusEnabled) { if (this.janusEnabled) {
var id = parseInt(this.id); var id = parseInt(this.id);
var server; var server;
if (window.location.protocol=='https:') { if (ZM_JANUS_PATH) {
server = ZM_JANUS_PATH;
} else if (window.location.protocol=='https:') {
// Assume reverse proxy setup for now // Assume reverse proxy setup for now
server = "https://" + window.location.hostname + "/janus"; server = "https://" + window.location.hostname + "/janus";
} else { } else {
@ -519,6 +521,9 @@ async function attachVideo(id) {
if (jsep !== undefined && jsep !== null) { if (jsep !== undefined && jsep !== null) {
Janus.debug("Handling SDP as well..."); Janus.debug("Handling SDP as well...");
Janus.debug(jsep); Janus.debug(jsep);
if ((navigator.userAgent.toLowerCase().indexOf('firefox') > -1) && (jsep["sdp"].includes("420029"))) { //because firefox devs are stubborn
jsep["sdp"] = jsep["sdp"].replace("420029", "42e01f");
}
// Offer from the plugin, let's answer // Offer from the plugin, let's answer
streaming[id].createAnswer({ streaming[id].createAnswer({
jsep: jsep, jsep: jsep,

View File

@ -51,7 +51,14 @@ function initCycle() {
if ( scale == '0' || scale == 'auto' ) changeScale(); if ( scale == '0' || scale == 'auto' ) changeScale();
if (monitorData[monIdx].janusEnabled) { if (monitorData[monIdx].janusEnabled) {
server = "http://" + window.location.hostname + ":8088/janus"; if (ZM_JANUS_PATH) {
server = ZM_JANUS_PATH;
} else if (window.location.protocol=='https:') {
// Assume reverse proxy setup for now
server = "https://" + window.location.hostname + "/janus";
} else {
server = "http://" + window.location.hostname + ":8088/janus";
}
opaqueId = "streamingtest-"+Janus.randomString(12); opaqueId = "streamingtest-"+Janus.randomString(12);
Janus.init({debug: "all", callback: function() { Janus.init({debug: "all", callback: function() {
janus = new Janus({ janus = new Janus({
@ -84,6 +91,9 @@ function initCycle() {
if (jsep !== undefined && jsep !== null) { if (jsep !== undefined && jsep !== null) {
Janus.debug("Handling SDP as well..."); Janus.debug("Handling SDP as well...");
Janus.debug(jsep); Janus.debug(jsep);
if ((navigator.userAgent.toLowerCase().indexOf('firefox') > -1) && (jsep["sdp"].includes("420029"))) { //because firefox devs are stubborn
jsep["sdp"] = jsep["sdp"].replace("420029", "42e01f");
}
// Offer from the plugin, let's answer // Offer from the plugin, let's answer
streaming2.createAnswer({ streaming2.createAnswer({
jsep: jsep, jsep: jsep,