Merge pull request #3408 from jp-bennett/janus

Adds Janus h264 streaming
This commit is contained in:
Isaac Connor 2022-01-14 16:34:19 -05:00 committed by GitHub
commit 1ae61afd47
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 4227 additions and 57 deletions

View File

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

View File

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

18
db/zm_update-1.37.8.sql Normal file
View File

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

50
misc/janus.jcfg Normal file
View File

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

View File

@ -0,0 +1,4 @@
general: {
admin_key = "supersecret"
rtp_port_range = "20000-40000"
}

View File

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

View File

@ -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<PollThread>(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;
}

View File

@ -34,6 +34,7 @@
#include <memory>
#include <sys/time.h>
#include <vector>
#include <curl/curl.h>
#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;

View File

@ -69,6 +69,16 @@ if ( !canEdit('Monitors') ) return;
}
?>
</div>
<div class="form-group" id="FunctionJanusEnabled">
<label for="newJanusEnabled"><?php echo translate('Janus Enabled') ?></label>
<input type="checkbox" name="newJanusEnabled" id="newJanusEnabled" value="1"/>
<?php
if ( isset($OLANG['FUNCTION_JANUS_ENABLED']) ) {
echo '<div class="form-text">'.$OLANG['FUNCTION_JANUS_ENABLED']['Help'].'</div>';
}
?>
</div>
</div>
<div class="modal-footer">

View File

@ -55,6 +55,7 @@ class Monitor extends ZM_Object {
'Function' => 'Mocord',
'Enabled' => array('type'=>'boolean','default'=>1),
'DecodingEnabled' => array('type'=>'boolean','default'=>1),
'JanusEnabled' => array('type'=>'boolean','default'=>0),
'LinkedMonitors' => array('type'=>'set', 'default'=>null),
'Triggers' => array('type'=>'set','default'=>''),
'EventStartCommand' => '',

View File

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

1
web/js/adapter.min.js vendored Normal file

File diff suppressed because one or more lines are too long

3649
web/js/janus.js Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1174,6 +1174,11 @@ $OLANG = array(
optionally choose to not decode the H264/H265 packets. This will drastically reduce cpu use
but will make live view unavailable for this monitor.'
),
'FUNCTION_JANUS_ENABLED' => array(
'Help' => '
Attempt to use Janus streaming server for h264/h265 live view. Experimental, but allows
for significantly better performance.'
),
'ImageBufferCount' => array(
'Help' => '
Number of raw images available in /dev/shm. Currently should be set in the 3-5 range. Used for live viewing.'

View File

@ -192,4 +192,6 @@ xhtmlHeaders(__FILE__, translate('CycleWatch'));
</div>
</div>
</div>
<script src="<?php echo cache_bust('js/adapter.min.js') ?>"></script>
<script src="<?php echo cache_bust('js/janus.js') ?>"></script>
<?php xhtmlFooter() ?>

View File

@ -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() {

View File

@ -20,7 +20,8 @@ monitorData[monitorData.length] = {
'url': '<?php echo $monitor->UrlToIndex() ?>',
'onclick': function(){window.location.assign( '?view=watch&mid=<?php echo $monitor->Id() ?>' );},
'type': '<?php echo $monitor->Type() ?>',
'refresh': '<?php echo $monitor->Refresh() ?>'
'refresh': '<?php echo $monitor->Refresh() ?>',
'janusEnabled': <?php echo $monitor->JanusEnabled() ?>
};
<?php
} // end foreach monitor

View File

@ -1,3 +1,9 @@
var server;
var janus = null;
var opaqueId;
var globalCount = 0;
var streamingList = [];
var janusMonitors = [];
/**
* called when the layoutControl select element is changed, or the page
* is rendered
@ -300,20 +306,43 @@ function initPage() {
$j("#flipMontageHeader").slideToggle("fast");
$j("#hdrbutton").toggleClass('glyphicon-menu-down').toggleClass('glyphicon-menu-up');
}
var initJanus = false;
var streamingMonitors = [];
for ( var i = 0, length = monitorData.length; i < length; i++ ) {
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);
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);

View File

@ -24,6 +24,7 @@ monitorData[monitorData.length] = {
'connKey': <?php echo $monitor->connKey() ?>,
'width': <?php echo $monitor->ViewWidth() ?>,
'height':<?php echo $monitor->ViewHeight() ?>,
'janusEnabled':<?php echo $monitor->JanusEnabled() ?>,
'url': '<?php echo $monitor->UrlToIndex( ZM_MIN_STREAMING_PORT ? ($monitor->Id() + ZM_MIN_STREAMING_PORT) : '') ?>',
'onclick': function(){window.location.assign( '?view=watch&mid=<?php echo $monitor->Id() ?>' );},
'type': '<?php echo $monitor->Type() ?>',

View File

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

View File

@ -66,6 +66,7 @@ monitorData[monitorData.length] = {
'id': <?php echo $m->Id() ?>,
'width': <?php echo $m->ViewWidth() ?>,
'height':<?php echo $m->ViewHeight() ?>,
'janusEnabled':<?php echo $m->JanusEnabled() ?>,
'url': '<?php echo $m->UrlToIndex() ?>',
'onclick': function(){window.location.assign( '?view=watch&mid=<?php echo $m->Id() ?>' );},
'type': '<?php echo $m->Type() ?>',

View File

@ -574,6 +574,16 @@ if (count($available_monitor_ids)) {
if ( isset($OLANG['FUNCTION_DECODING_ENABLED']) ) {
echo '<div class="form-text">'.$OLANG['FUNCTION_DECODING_ENABLED']['Help'].'</div>';
}
?>
</td>
</tr>
<tr id="FunctionJanusEnabled">
<td class="text-right pr-3"><?php echo translate('Janus Live Stream') ?></td>
<td><input type="checkbox" name="newMonitor[JanusEnabled]" value="1"<?php echo $monitor->JanusEnabled() ? ' checked="checked"' : '' ?>/>
<?php
if ( isset($OLANG['FUNCTION_JANUS_ENABLED']) ) {
echo '<div class="form-text">'.$OLANG['FUNCTION_JANUS_ENABLED']['Help'].'</div>';
}
?>
</td>
</tr>

View File

@ -325,5 +325,7 @@ foreach (array_reverse($zones) as $zone) {
</div>
</div>
</div>
<script src="<?php echo cache_bust('js/adapter.min.js') ?>"></script>
<script src="<?php echo cache_bust('js/janus.js') ?>"></script>
<script src="<?php echo cache_bust('js/MonitorStream.js') ?>"></script>
<?php xhtmlFooter() ?>

View File

@ -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 ) {
?>
</div>
</div>
<script src="<?php echo cache_bust('js/adapter.min.js') ?>"></script>
<script src="<?php echo cache_bust('js/janus.js') ?>"></script>
<?php xhtmlFooter() ?>