Merge branch 'master' into multistream

This commit is contained in:
Isaac Connor 2022-01-26 10:13:20 -05:00
commit 23bfbcd6ce
48 changed files with 926 additions and 559 deletions

View File

@ -14,6 +14,7 @@ web/skins/classic/js/moment.js
web/skins/classic/js/video.js
web/tools/mootools
web/js/janus.js
web/js/ajaxQueue.js
# Cannot be parsed as JS
web/skins/classic/includes/export_functions.php

View File

@ -39,6 +39,7 @@ CREATE TABLE `Config` (
`Help` text,
`Category` varchar(32) NOT NULL default '',
`Readonly` tinyint(3) unsigned NOT NULL default '0',
`Private` BOOLEAN NOT NULL DEFAULT FALSE,
`Requires` text,
PRIMARY KEY (`Name`)
) ENGINE=@ZM_MYSQL_ENGINE@;
@ -460,6 +461,7 @@ CREATE TABLE `Monitors` (
`Enabled` tinyint(3) unsigned NOT NULL default '1',
`DecodingEnabled` tinyint(3) unsigned NOT NULL default '1',
`JanusEnabled` BOOLEAN NOT NULL default false,
`JanusAudioEnabled` BOOLEAN NOT NULL default false,
`LinkedMonitors` varchar(255),
`Triggers` set('X10') NOT NULL default '',
`EventStartCommand` VARCHAR(255) NOT NULL DEFAULT '',

20
db/zm_update-1.37.10.sql Normal file
View File

@ -0,0 +1,20 @@
--
-- Update Config table to have Private
--
SELECT 'Checking for Private in Config';
SET @s = (SELECT IF(
(SELECT COUNT(*)
FROM INFORMATION_SCHEMA.COLUMNS
WHERE table_name = 'Config'
AND table_schema = DATABASE()
AND column_name = 'Private'
) > 0,
"SELECT 'Column Private already exists in Config'",
"ALTER TABLE `Config` ADD COLUMN `Private` BOOLEAN NOT NULL DEFAULT FALSE AFTER `Readonly`"
));
PREPARE stmt FROM @s;
EXECUTE stmt;

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

@ -0,0 +1,18 @@
--
-- Update Monitors table to have JanusEnabled
--
SELECT 'Checking for JanusAudioEnabled in Monitors';
SET @s = (SELECT IF(
(SELECT COUNT(*)
FROM INFORMATION_SCHEMA.COLUMNS
WHERE table_name = 'Monitors'
AND table_schema = DATABASE()
AND column_name = 'JanusAudioEnabled'
) > 0,
"SELECT 'Column JanusAudioEnabled already exists in Monitors'",
"ALTER TABLE `Monitors` ADD COLUMN `JanusAudioEnabled` BOOLEAN NOT NULL default false AFTER `JanusEnabled`"
));
PREPARE stmt FROM @s;
EXECUTE stmt;

View File

@ -36,7 +36,7 @@
%global _hardened_build 1
Name: zoneminder
Version: 1.37.8
Version: 1.37.10
Release: 1%{?dist}
Summary: A camera monitoring and analysis tool
Group: System Environment/Daemons

View File

@ -156,7 +156,7 @@ sub loadConfigFromDB {
print("Error: unable to load options from database: $DBI::errstr\n");
return(0);
}
my $sql = "select * from Config";
my $sql = 'SELECT * FROM Config';
my $sth = $dbh->prepare_cached( $sql )
or croak( "Can't prepare '$sql': ".$dbh->errstr() );
my $res = $sth->execute()
@ -203,7 +203,7 @@ sub saveConfigToDB {
my $res = $dbh->do( $sql )
or croak( "Can't do '$sql': ".$dbh->errstr() );
$sql = "replace into Config set Id = ?, Name = ?, Value = ?, Type = ?, DefaultValue = ?, Hint = ?, Pattern = ?, Format = ?, Prompt = ?, Help = ?, Category = ?, Readonly = ?, Requires = ?";
$sql = "replace into Config set Id = ?, Name = ?, Value = ?, Type = ?, DefaultValue = ?, Hint = ?, Pattern = ?, Format = ?, Prompt = ?, Help = ?, Category = ?, Readonly = ?, Private = ?, Requires = ?";
my $sth = $dbh->prepare_cached( $sql )
or croak( "Can't prepare '$sql': ".$dbh->errstr() );
foreach my $option ( @options ) {
@ -240,6 +240,7 @@ sub saveConfigToDB {
$option->{help},
$option->{category},
$option->{readonly} ? 1 : 0,
$option->{private} ? 1 : 0,
$option->{db_requires}
) or croak("Can't execute when updating config entry $$option{name}: ".$sth->errstr() );
} # end foreach option

View File

@ -304,6 +304,7 @@ our @options = (
{ name=>'ZM_AUTH_RELAY', value=>'hashed' }
],
type => $types{string},
private => 1,
category => 'system',
},
{
@ -370,6 +371,28 @@ our @options = (
type => $types{boolean},
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',
default => 'yes',
@ -462,6 +485,7 @@ our @options = (
{name=>'ZM_OPT_USE_GOOG_RECAPTCHA', value=>'yes'}
],
type => $types{string},
private => 1,
category => 'system',
},
{
@ -477,6 +501,7 @@ our @options = (
{name=>'ZM_OPT_USE_GOOG_RECAPTCHA', value=>'yes'}
],
type => $types{string},
private => 1,
category => 'system',
},
{

View File

@ -444,11 +444,6 @@ if ( $version ) {
print( "\nUpgrading database to version ".ZM_VERSION."\n" );
# Update config first of all
migratePaths();
ZoneMinder::Config::loadConfigFromDB();
ZoneMinder::Config::saveConfigToDB();
my $cascade = undef;
if ( $cascade || $version eq "1.19.0" ) {
# Patch the database

View File

@ -245,6 +245,7 @@ class Socket : public CommsBase {
}
virtual ssize_t recv(std::string &msg) const {
msg.reserve(ZM_NETWORK_BUFSIZ);
std::vector<char> buffer(msg.capacity());
ssize_t nBytes;
if ((nBytes = ::recv(mSd, buffer.data(), buffer.size(), 0)) < 0) {

View File

@ -251,7 +251,7 @@ void zmDbQueue::process() {
mCondition.wait(lock);
}
while (!mQueue.empty()) {
if (mQueue.size() > 20) {
if (mQueue.size() > 30) {
Logger *log = Logger::fetch();
Logger::Level db_level = log->databaseLevel();
log->databaseLevel(Logger::NOLOG);

View File

@ -1096,35 +1096,33 @@ void EventStream::runStream() {
bool EventStream::send_file(const std::string &filepath) {
FILE *fdj = nullptr;
fdj = fopen(filepath.c_str(), "rb");
if ( !fdj ) {
if (!fdj) {
Error("Can't open %s: %s", filepath.c_str(), strerror(errno));
std::string error_message = stringtf("Can't open %s: %s", filepath.c_str(), strerror(errno));
return sendTextFrame(error_message.c_str());
}
#if HAVE_SENDFILE
static struct stat filestat;
if ( fstat(fileno(fdj), &filestat) < 0 ) {
if (fstat(fileno(fdj), &filestat) < 0) {
fclose(fdj); /* Close the file handle */
Error("Failed getting information about file %s: %s", filepath.c_str(), strerror(errno));
return false;
}
if ( !filestat.st_size ) {
if (!filestat.st_size) {
fclose(fdj); /* Close the file handle */
Info("File size is zero. Unable to send raw frame %ld: %s", curr_frame_id, strerror(errno));
return false;
}
if ( 0 > fprintf(stdout, "Content-Length: %d\r\n\r\n", (int)filestat.st_size) ) {
if (0 > fprintf(stdout, "Content-Length: %d\r\n\r\n", (int)filestat.st_size)) {
fclose(fdj); /* Close the file handle */
Info("Unable to send raw frame %ld: %s", curr_frame_id, strerror(errno));
return false;
}
int rc = zm_sendfile(fileno(stdout), fileno(fdj), 0, (int)filestat.st_size);
if ( rc == (int)filestat.st_size ) {
if (rc == (int)filestat.st_size) {
// Success
fclose(fdj); /* Close the file handle */
return true;
} else {
Debug(1, "Failed to sendfile?");
}
Warning("Unable to send raw frame %ld: %s rc %d != %d",
curr_frame_id, strerror(errno), rc, (int)filestat.st_size);

View File

@ -24,6 +24,7 @@
#include "zm_utils.h"
#include <algorithm>
#include <fcntl.h>
#include <mutex>
#include <sys/stat.h>
#include <unistd.h>
@ -80,6 +81,8 @@ imgbufcpy_fptr_t fptr_imgbufcpy;
/* Font */
static ZmFont font;
std::mutex jpeg_mutex;
void Image::update_function_pointers() {
/* Because many loops are unrolled and work on 16 colours/time or 4 pixels/time, we have to meet requirements */
if ( pixels % 16 || pixels % 12 ) {
@ -114,22 +117,23 @@ Image::Image() :
delta8_argb(&std_delta8_argb),
delta8_abgr(&std_delta8_abgr),
delta8_gray8(&std_delta8_gray8),
blend(&std_blend)
blend(&std_blend),
width(0),
linesize(0),
height(0),
pixels(0),
colours(0),
padding(0),
size(0),
subpixelorder(0),
allocation(0),
buffer(nullptr),
buffertype(ZM_BUFTYPE_DONTFREE),
holdbuffer(0)
{
if ( !initialised )
if (!initialised)
Initialise();
width = 0;
linesize = 0;
height = 0;
padding = 0;
pixels = 0;
colours = 0;
subpixelorder = 0;
size = 0;
allocation = 0;
buffer = 0;
buffertype = ZM_BUFTYPE_DONTFREE;
holdbuffer = 0;
// Update blend to fast function determined by Initialise, I'm sure this can be improve.
blend = fptr_blend;
}
@ -158,15 +162,15 @@ Image::Image(int p_width, int p_height, int p_colours, int p_subpixelorder, uint
colours(p_colours),
padding(p_padding),
subpixelorder(p_subpixelorder),
buffer(p_buffer) {
buffer(p_buffer),
holdbuffer(0)
{
if (!initialised)
Initialise();
pixels = width * height;
linesize = p_width * p_colours;
size = linesize * height + padding;
buffer = nullptr;
holdbuffer = 0;
if (p_buffer) {
allocation = size;
buffertype = ZM_BUFTYPE_DONTFREE;
@ -174,7 +178,7 @@ Image::Image(int p_width, int p_height, int p_colours, int p_subpixelorder, uint
} else {
AllocImgBuffer(size);
}
if (!subpixelorder and colours>1) {
if (!subpixelorder and (colours>1)) {
// Default to RGBA when no subpixelorder is specified.
subpixelorder = ZM_SUBPIX_ORDER_RGBA;
}
@ -1082,6 +1086,9 @@ bool Image::WriteJpeg(const std::string &filename,
const int &quality_override,
SystemTimePoint timestamp,
bool on_blocking_abort) const {
// jpeg libs are not thread safe
std::unique_lock<std::mutex> lck(jpeg_mutex);
if (config.colour_jpeg_files && (colours == ZM_COLOUR_GRAY8)) {
Image temp_image(*this);
temp_image.Colourise(ZM_COLOUR_RGB24, ZM_SUBPIX_ORDER_RGB);
@ -1365,6 +1372,8 @@ bool Image::EncodeJpeg(JOCTET *outbuffer, int *outbuffer_size, int quality_overr
return temp_image.EncodeJpeg(outbuffer, outbuffer_size, quality_override);
}
std::unique_lock<std::mutex> lck(jpeg_mutex);
int quality = quality_override ? quality_override : config.jpeg_stream_quality;
struct jpeg_compress_struct *cinfo = encodejpg_ccinfo[quality];

View File

@ -145,11 +145,13 @@ class Image {
explicit Image(const AVFrame *frame);
~Image();
static void Initialise();
static void Deinitialise();
inline void DumpImgBuffer() {
DumpBuffer(buffer, buffertype);
if (buffertype != ZM_BUFTYPE_DONTFREE)
DumpBuffer(buffer, buffertype);
buffertype = ZM_BUFTYPE_DONTFREE;
buffer = nullptr;
allocation = 0;

View File

@ -52,6 +52,7 @@
#include <algorithm>
#include <sys/types.h>
#include <sys/stat.h>
#include <chrono>
#if ZM_MEM_MAPPED
#include <sys/mman.h>
@ -78,7 +79,7 @@ struct Namespace namespaces[] =
// 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`, `Capturing`+0, `Analysing`+0, `AnalysisSource`, `Recording`+0, `RecordingSource`, `DecodingEnabled`, "
"`JanusEnabled`,"
"`JanusEnabled`, `JanusAudioEnabled`, "
"`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`, "
@ -319,6 +320,7 @@ Monitor::Monitor()
recording(RECORDING_ALWAYS),
decoding_enabled(false),
janus_enabled(false),
janus_audio_enabled(false),
//protocol
//method
//options
@ -458,9 +460,10 @@ Monitor::Monitor()
} // Monitor::Monitor
/*
<<<<<<< HEAD
std::string load_monitor_sql =
"SELECT `Id`, `Name`, `ServerId`, `StorageId`, `Type`, `Capturing`+0, `Analysing`+0, `AnalysisSource`, `Recording`+0, `RecordingSource`,
`DecodingEnabled`, JanusEnabled, "
`DecodingEnabled`, JanusEnabled, JanusAudioEnabled, "
"LinkedMonitors, `EventStartCommand`, `EventEndCommand`, "
"AnalysisFPSLimit, AnalysisUpdateDelay, MaxFPS, AlarmMaxFPS,"
"Device, Channel, Format, V4LMultiBuffer, V4LCapturesPerFrame, " // V4L Settings
@ -519,6 +522,7 @@ void Monitor::Load(MYSQL_ROW dbrow, bool load_zones=true, Purpose p = QUERY) {
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++;
janus_audio_enabled = dbrow[col] ? atoi(dbrow[col]) : false; col++;
ReloadLinkedMonitors(dbrow[col]); col++;
event_start_command = dbrow[col] ? dbrow[col] : ""; col++;
@ -1138,12 +1142,14 @@ bool Monitor::connect() {
#if HAVE_LIBCURL //janus setup. Depends on libcurl.
if (janus_enabled && (path.find("rtsp://") != std::string::npos)) {
get_janus_session();
if (add_to_janus() != 0) {
if (add_to_janus() != 0) {
Warning("Failed to add monitor stream to Janus!");
}
Warning("Failed to add monitor stream to Janus!"); //The first attempt may fail. Will be reattempted in the Poller thread
}
}
#else
if (janus_enabled)
Error("zmc not compiled with LIBCURL. Janus support not built in!");
#endif
} else if (!shared_data->valid) {
@ -1166,6 +1172,7 @@ bool Monitor::disconnect() {
}
if (purpose == CAPTURE) {
alarm_image.HoldBuffer(false); /* Allow to reset buffer */
if (unlink(mem_file.c_str()) < 0) {
Warning("Can't unlink '%s': %s", mem_file.c_str(), strerror(errno));
}
@ -1808,6 +1815,8 @@ void Monitor::UpdateFPS() {
//Thread where ONVIF polling, and other similar status polling can happen.
//Since these can be blocking, run here to avoid intefering with other processing
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
if (ONVIF_Healthy) {
@ -1849,10 +1858,17 @@ bool Monitor::Poll() {
}
}
}
} else {
std::this_thread::sleep_for (std::chrono::seconds(1)); //thread sleep to avoid the busy loop.
}
#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;
} //end Poll
@ -2084,7 +2100,7 @@ bool Monitor::Analyse() {
&& (event->Duration() >= min_section_length)
&& ((!pre_event_count) || (Event::PreAlarmCount() >= alarm_frame_count - 1))) {
Info("%s: %03d - Closing event %" PRIu64 ", continuous end, alarm begins",
name.c_str(), image_count, event->Id());
name.c_str(), snap->image_index, event->Id());
closeEvent();
} else if (event) {
// This is so if we need more than 1 alarm frame before going into alarm, so it is basically if we have enough alarm frames
@ -2099,7 +2115,7 @@ bool Monitor::Analyse() {
}
if ((!pre_event_count) || (Event::PreAlarmCount() >= alarm_frame_count-1)) {
Info("%s: %03d - Gone into alarm state PreAlarmCount: %u > AlarmFrameCount:%u Cause:%s",
name.c_str(), image_count, Event::PreAlarmCount(), alarm_frame_count, cause.c_str());
name.c_str(), snap->image_index, Event::PreAlarmCount(), alarm_frame_count, cause.c_str());
shared_data->state = state = ALARM;
} else if (state != PREALARM) {
@ -2222,7 +2238,7 @@ bool Monitor::Analyse() {
Info("%s: %03d - Closing event %" PRIu64 ", section end forced %" PRIi64 " - %" PRIi64 " = %" PRIi64 " >= %" PRIi64 ,
name.c_str(),
image_count,
snap->image_index,
event->Id(),
static_cast<int64>(std::chrono::duration_cast<Seconds>(snap->timestamp.time_since_epoch()).count()),
static_cast<int64>(std::chrono::duration_cast<Seconds>(event->StartTime().time_since_epoch()).count()),
@ -2562,8 +2578,6 @@ int Monitor::Capture() {
} else {
Debug(4, "Not Queueing audio packet");
}
// Don't update last_write_index because that is used for live streaming
//shared_data->last_write_time = image_buffer[index].timestamp->tv_sec;
return 1;
} else {
Debug(1, "Unknown codec type %d", packet->codec_type);
@ -2694,7 +2708,7 @@ bool Monitor::Decode() {
} // end if need_decoding
Image* capture_image = nullptr;
unsigned int index = image_count % image_buffer_count;
unsigned int index = packet->image_index % image_buffer_count;
if (packet->image) {
capture_image = packet->image;
@ -3167,10 +3181,8 @@ int Monitor::PrimeCapture() {
}
} // 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.
//ONVIF Thread
if (onvif_event_listener && ONVIF_Healthy) {
//Poller Thread
if (onvif_event_listener || janus_enabled) {
if (!Poller) {
Poller = zm::make_unique<PollThread>(this);
@ -3178,7 +3190,7 @@ int Monitor::PrimeCapture() {
Poller->Start();
}
}
#endif
if (decoding_enabled) {
if (!decoder_it) decoder_it = packetqueue.get_video_it(false);
if (!decoder) {
@ -3382,20 +3394,26 @@ size_t Monitor::WriteCallback(void *contents, size_t size, size_t nmemb, void *u
}
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 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 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;
if (!curl) {
Error("Failed to init curl");
return -1;
}
//parse username and password
pos = path.find(":", 7);
if (pos == std::string::npos) return -1;
@ -3407,36 +3425,15 @@ int Monitor::add_to_janus() {
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;
endpoint += janus_session;
//Assemble our actual request
postData = "{\"janus\" : \"message\", \"transaction\" : \"randomString\", \"body\" : {";
postData += "\"request\" : \"create\", \"admin_key\" : \"supersecret\", \"type\" : \"rtsp\", ";
postData += "\"url\" : \"";
postData += "\"request\" : \"create\", \"admin_key\" : \"";
postData += config.janus_secret;
postData += "\", \"type\" : \"rtsp\", ";
postData += "\"url\" : \"";
postData += rtsp_path;
postData += "\", \"rtsp_user\" : \"";
postData += rtsp_username;
@ -3444,6 +3441,7 @@ int Monitor::add_to_janus() {
postData += rtsp_password;
postData += "\", \"id\" : ";
postData += std::to_string(id);
if (janus_audio_enabled) postData += ", \"audio\" : true";
postData += ", \"video\" : true}}";
curl_easy_setopt(curl, CURLOPT_URL,endpoint.c_str());
@ -3451,53 +3449,44 @@ int Monitor::add_to_janus() {
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;
if (res != CURLE_OK) {
Error("Failed to curl_easy_perform adding rtsp stream");
curl_easy_cleanup(curl);
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());
curl_easy_cleanup(curl);
return 0;
}
int Monitor::remove_from_janus() {
//TODO clean this up, add error checking, etc
int Monitor::check_janus() {
std::string response;
std::string endpoint = "127.0.0.1:8088/janus/";
std::string postData = "{\"janus\" : \"create\", \"transaction\" : \"randomString\"}";
std::size_t pos;
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;
//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;
endpoint += janus_session;
//Assemble our actual request
postData = "{\"janus\" : \"message\", \"transaction\" : \"randomString\", \"body\" : {";
postData += "\"request\" : \"destroy\", \"admin_key\" : \"supersecret\", \"id\" : ";
postData += "\"request\" : \"info\", \"id\" : ";
postData += std::to_string(id);
postData += "}}";
@ -3506,9 +3495,128 @@ int Monitor::remove_from_janus() {
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;
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());
curl_easy_cleanup(curl);
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

@ -308,6 +308,7 @@ protected:
bool decoding_enabled; // Whether the monitor will decode h264/h265 packets
bool janus_enabled; // Whether we set the h264/h265 stream up on janus
bool janus_audio_enabled; // Whether we tell Janus to try to include audio.
std::string protocol;
std::string method;
@ -494,6 +495,8 @@ protected:
static size_t WriteCallback(void *contents, size_t size, size_t nmemb, void *userp);
int add_to_janus();
int remove_from_janus();
int get_janus_session();
std::string janus_session;
// Used in check signal
uint8_t red_val;
@ -554,6 +557,21 @@ public:
inline bool DecodingEnabled() const {
return decoding_enabled;
}
bool JanusEnabled() {
return janus_enabled;
}
bool JanusAudioEnabled() {
return janus_audio_enabled;
}
bool OnvifEnabled() {
return onvif_event_listener;
}
int check_janus(); //returns 1 for healthy, 0 for success but missing stream, negative for error.
#ifdef WITH_GSOAP
bool OnvifHealthy() {
return ONVIF_Healthy;
}
#endif
inline const char *EventPrefix() const { return event_prefix.c_str(); }
inline bool Ready() const {
if (image_count >= ready_count) {

View File

@ -734,12 +734,12 @@ void MonitorStream::runStream() {
(monitor->GetFunction() == Monitor::MOCORD || monitor->GetFunction() == Monitor::MODECT)) {
Debug(1, "Sending analysis image");
send_image = monitor->GetAlarmImage();
if ( !send_image ) {
if (!send_image) {
Debug(1, "Falling back");
send_image = monitor->image_buffer[index];
}
} else {
Debug(1, "Sending regular image");
Debug(1, "Sending regular image index %d", index);
send_image = monitor->image_buffer[index];
}

View File

@ -22,6 +22,7 @@
#include "zm_config.h"
#include "zm_monitor.h"
#include "zm_packet.h"
#include "zm_signal.h"
RemoteCameraRtsp::RemoteCameraRtsp(
const Monitor *monitor,
@ -126,8 +127,8 @@ int RemoteCameraRtsp::Disconnect() {
int RemoteCameraRtsp::PrimeCapture() {
Debug(2, "Waiting for sources");
for (int i = 0; i < 100 && !rtspThread->hasSources(); i++) {
std::this_thread::sleep_for(Microseconds(100));
for (int i = 100; i && !zm_terminate && !rtspThread->hasSources(); i--) {
std::this_thread::sleep_for(Microseconds(10000));
}
if (!rtspThread->hasSources()) {
@ -168,8 +169,10 @@ int RemoteCameraRtsp::PrimeCapture() {
}
} // end foreach stream
if ( mVideoStreamId == -1 )
Fatal("Unable to locate video stream");
if ( mVideoStreamId == -1 ) {
Error("Unable to locate video stream");
return -1;
}
if ( mAudioStreamId == -1 )
Debug(3, "Unable to locate audio stream");
@ -179,17 +182,22 @@ int RemoteCameraRtsp::PrimeCapture() {
// Find the decoder for the video stream
AVCodec *codec = avcodec_find_decoder(mVideoCodecContext->codec_id);
if ( codec == nullptr )
Panic("Unable to locate codec %d decoder", mVideoCodecContext->codec_id);
if ( codec == nullptr ) {
Error("Unable to locate codec %d decoder", mVideoCodecContext->codec_id);
return -1;
}
// Open codec
if ( avcodec_open2(mVideoCodecContext, codec, nullptr) < 0 )
Panic("Can't open codec");
if ( avcodec_open2(mVideoCodecContext, codec, nullptr) < 0 ) {
Error("Can't open codec");
return -1;
}
int pSize = av_image_get_buffer_size(imagePixFormat, width, height, 1);
if ( (unsigned int)pSize != imagesize ) {
Fatal("Image size mismatch. Required: %d Available: %llu", pSize, imagesize);
Error("Image size mismatch. Required: %d Available: %llu", pSize, imagesize);
return -1;
}
return 1;
@ -208,18 +216,13 @@ int RemoteCameraRtsp::PreCapture() {
int RemoteCameraRtsp::Capture(std::shared_ptr<ZMPacket> &zm_packet) {
int frameComplete = false;
AVPacket *packet = &zm_packet->packet;
if ( !zm_packet->image ) {
Debug(1, "Allocating image %dx%d %d colours %d", width, height, colours, subpixelorder);
zm_packet->image = new Image(width, height, colours, subpixelorder);
}
while (!frameComplete) {
buffer.clear();
if (!rtspThread || rtspThread->IsStopped())
if (!rtspThread || rtspThread->IsStopped() || zm_terminate)
return -1;
if ( rtspThread->getFrame(buffer) ) {
if (rtspThread->getFrame(buffer)) {
Debug(3, "Read frame %d bytes", buffer.size());
Hexdump(4, buffer.head(), 16);
@ -254,36 +257,20 @@ int RemoteCameraRtsp::Capture(std::shared_ptr<ZMPacket> &zm_packet) {
//while ( (!frameComplete) && (buffer.size() > 0) ) {
if ( buffer.size() > 0 ) {
packet->data = buffer.head();
packet->data = (uint8_t*)av_malloc(buffer.size());
memcpy(packet->data, buffer.head(), buffer.size());
//packet->data = buffer.head();
packet->size = buffer.size();
bytes += packet->size;
buffer -= packet->size;
struct timeval now;
gettimeofday(&now, NULL);
gettimeofday(&now, nullptr);
packet->pts = packet->dts = now.tv_sec*1000000+now.tv_usec;
int bytes_consumed = zm_packet->decode(mVideoCodecContext);
if ( bytes_consumed < 0 ) {
Error("Error while decoding frame %d", frameCount);
//Hexdump(Logger::ERROR, buffer.head(), buffer.size()>256?256:buffer.size());
}
buffer -= packet->size;
if ( bytes_consumed ) {
zm_dump_video_frame(zm_packet->in_frame, "remote_rtsp_decode");
if (!mVideoStream->codecpar->width) {
zm_dump_codec(mVideoCodecContext);
zm_dump_codecpar(mVideoStream->codecpar);
mVideoStream->codecpar->width = zm_packet->in_frame->width;
mVideoStream->codecpar->height = zm_packet->in_frame->height;
zm_dump_codecpar(mVideoStream->codecpar);
}
zm_packet->codec_type = mVideoCodecContext->codec_type;
zm_packet->stream = mVideoStream;
frameComplete = true;
Debug(2, "Frame: %d - %d/%d", frameCount, bytes_consumed, buffer.size());
packet->data = nullptr;
packet->size = 0;
}
zm_packet->codec_type = mVideoCodecContext->codec_type;
zm_packet->stream = mVideoStream;
frameComplete = true;
Debug(2, "Frame: %d - %d/%d", frameCount, packet->size, buffer.size());
}
} /* getFrame() */
} // end while true

View File

@ -277,7 +277,7 @@ void RtpCtrlThread::Run() {
TimePoint last_receive = std::chrono::steady_clock::now();
bool timeout = false; // used as a flag that we had a timeout, and then sent an RR to see if we wake back up. Real timeout will happen when this is true.
while (!mTerminate && select.wait() >= 0) {
while (!mTerminate && (select.wait() >= 0)) {
TimePoint now = std::chrono::steady_clock::now();
zm::Select::CommsList readable = select.getReadable();
if ( readable.size() == 0 ) {

View File

@ -45,8 +45,10 @@ RtpSource::RtpSource(
mFrame(65536),
mFrameCount(0),
mFrameGood(true),
prevM(false),
mFrameReady(false),
mFrameProcessed(false)
mFrameProcessed(false),
mTerminate(false)
{
char hostname[256] = "";
gethostname(hostname, sizeof(hostname));

View File

@ -91,8 +91,6 @@ private:
bool mFrameGood;
bool prevM;
bool mTerminate;
bool mFrameReady;
std::condition_variable mFrameReadyCv;
std::mutex mFrameReadyMutex;
@ -100,6 +98,7 @@ private:
bool mFrameProcessed;
std::condition_variable mFrameProcessedCv;
std::mutex mFrameProcessedMutex;
bool mTerminate;
private:
void init(uint16_t seq);

View File

@ -1 +1 @@
1.37.8
1.37.10

View File

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

View File

@ -25,7 +25,7 @@ class Group extends ZM_Object {
if ( isset($_COOKIE['zmGroup']) ) {
if ( $this->{'Id'} == $_COOKIE['zmGroup'] ) {
unset($_COOKIE['zmGroup']);
setcookie('zmGroup', '', time()-3600*24*2);
zm_setcookie('zmGroup', '');
}
}
}

View File

@ -117,6 +117,7 @@ class Monitor extends ZM_Object {
'Enabled' => array('type'=>'boolean','default'=>1),
'DecodingEnabled' => array('type'=>'boolean','default'=>1),
'JanusEnabled' => array('type'=>'boolean','default'=>0),
'JanusAudioEnabled' => array('type'=>'boolean','default'=>0),
'LinkedMonitors' => array('type'=>'set', 'default'=>null),
'Triggers' => array('type'=>'set','default'=>''),
'EventStartCommand' => '',
@ -780,5 +781,27 @@ class Monitor extends ZM_Object {
}
return $this->{'Manufacturer'};
}
function getMonitorStateHTML() {
$html = '
<div id="monitorStatus'.$this->Id().'" class="monitorStatus">
<div id="monitorState'.$this->Id().'" class="monitorState">
<span>'.translate('State').':<span id="stateValue'.$this->Id().'"></span></span>
<span id="viewingFPS'.$this->Id().'" title="'.translate('Viewing FPS').'"><span id="viewingFPSValue'.$this->Id().'"></span> fps</span>
<span id="captureFPS'.$this->Id().'" title="'.translate('Capturing FPS').'"><span id="captureFPSValue'.$this->Id().'"></span> fps</span>
';
if ( $this->Function() == 'Modect' or $this->Function() == 'Mocord' ) {
$html .= '<span id="analysisFPS'.$this->Id().'" title="'.translate('Analysis FPS').'"><span id="analysisFPSValue'.$this->Id().'"></span> fps</span>
';
}
$html .= '
<span id="rate'.$this->Id().'" class="hidden">'.translate('Rate').': <span id="rateValue'.$this->Id().'"></span>x</span>
<span id="delay'.$this->Id().'" class="hidden">'.translate('Delay').': <span id="delayValue'.$this->Id().'"></span>s</span>
<span id="level'.$this->Id().'" class="hidden">'.translate('Buffer').': <span id="levelValue'.$this->Id().'"></span>%</span>
<span id="zoom'.$this->Id().'">'. translate('Zoom').': <span id="zoomValue'.$this->Id().'"></span>x</span>
</div>
</div>
';
return $html;
}
} // end class Monitor
?>

View File

@ -21,7 +21,7 @@
if ( $action == 'bandwidth' && isset($_REQUEST['newBandwidth']) ) {
$_COOKIE['zmBandwidth'] = validStr($_REQUEST['newBandwidth']);
setcookie('zmBandwidth', validStr($_REQUEST['newBandwidth']), time()+3600*24*30*12*10);
zm_setcookie('zmBandwidth', validStr($_REQUEST['newBandwidth']));
$refreshParent = true;
$view = 'none';
$closePopup = true;

View File

@ -21,9 +21,9 @@
// Group view actions
if ( ($action == 'setgroup') && canView('Groups')) {
if ( !empty($_REQUEST['gid']) ) {
setcookie('zmGroup', validInt($_REQUEST['gid']), time()+3600*24*30*12*10);
zm_setcookie('zmGroup', validInt($_REQUEST['gid']));
} else {
setcookie('zmGroup', '', time()-3600*24*2);
zm_setcookie('zmGroup', '', time()-3600*24*2);
}
$refreshParent = true;
return;

View File

@ -89,6 +89,8 @@ if ($action == 'save') {
'ModectDuringPTZ' => 0,
'Enabled' => 0,
'DecodingEnabled' => 0,
'JanusEnabled' => 0,
'JanusAudioEnabled' => 0,
'Exif' => 0,
'RTSPDescribe' => 0,
'V4LMultiBuffer' => '',
@ -324,7 +326,8 @@ if ($action == 'save') {
// really should thump zmwatch and maybe zmtrigger too.
//daemonControl( 'restart', 'zmwatch.pl' );
} // end if restart
$redirect = '?view=console';
if (!$error_message)
$redirect = '?view=console';
} else {
ZM\Warning("Unknown action $action in Monitor");
} // end if action == Delete

View File

@ -38,7 +38,7 @@ if ( isset($_REQUEST['object']) ) {
$Layout->save();
zm_session_start();
$_SESSION['zmMontageLayout'] = $Layout->Id();
setcookie('zmMontageLayout', $Layout->Id(), 1);
zm_setcookie('zmMontageLayout', $Layout->Id());
session_write_close();
$redirect = '?view=montage';
} // end if save

View File

@ -165,7 +165,7 @@ function loadConfig( $defineConsts=true ) {
$config = array();
$result = $dbConn->query('SELECT Name,Value FROM Config');
$result = $dbConn->query('SELECT Name,Value,Private FROM Config');
if ( !$result )
echo mysql_error();
while( $row = dbFetchNext($result) ) {

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() );
} 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() ) {
$options['mode'] = 'jpeg';
$streamSrc = $monitor->getStreamSrc($options);

View File

@ -1,4 +1,21 @@
<?php
// Wrapper around setcookie that auto-sets samesite, and deals with older versions of php
function zm_setcookie($cookie, $value, $options=array()) {
if (!isset($options['expires'])) {
$options['expires'] = time()+3600*24*30*12*10; // 10 years?!
}
if (!isset($options['samesite'])) {
$options['samesite'] = 'Strict';
}
if (version_compare(phpversion(), '7.3.0', '>=')) {
setcookie($cookie, $value, $options);
} else {
setcookie($cookie, $value, $options['expires'], '/; samesite=strict');
}
}
// ZM session start function support timestamp management
function zm_session_start() {

View File

@ -139,11 +139,6 @@ $skinBase[] = $skin;
zm_session_start();
$cookie_options = array(
'expires'=>time()+3600*24*30*12*10,
'samesite' => 'Strict',
);
if (
!isset($_SESSION['skin']) ||
isset($_REQUEST['skin']) ||
@ -151,11 +146,7 @@ if (
($_COOKIE['zmSkin'] != $skin)
) {
$_SESSION['skin'] = $skin;
if (version_compare(phpversion(), '7.3.0', '>=')) {
setcookie('zmSkin', $skin, $cookie_options);
} else {
setcookie('zmSkin', $skin, $cookie_options['expires'], '/; samesite=strict');
}
zm_setcookie('zmSkin', $skin);
}
if (
@ -165,11 +156,7 @@ if (
($_COOKIE['zmCSS'] != $css)
) {
$_SESSION['css'] = $css;
if (version_compare(phpversion(), '7.3.0', '>=')) {
setcookie('zmCSS', $css, $cookie_options);
} else {
setcookie('zmCSS', $css, $cookie_options['expires'], '/; samesite=strict');
}
zm_setcookie('zmCSS', $css);
}
# Running is global but only do the daemonCheck if it is actually needed

View File

@ -13,15 +13,22 @@ function MonitorStream(monitorData) {
this.janusEnabled = monitorData.janusEnabled;
this.scale = 100;
this.status = null;
this.alarmState = STATE_IDLE;
this.lastAlarmState = STATE_IDLE;
this.streamCmdTimer = null;
this.streamCmdParms = {
view: 'request',
request: 'stream',
connkey: this.connKey
};
this.ajaxQueue = null;
this.type = monitorData.type;
this.refresh = monitorData.refresh;
this.buttons = {}; // index by name
this.setButton = function(name, element) {
this.buttons.name = element;
};
this.element = null;
this.getElement = function() {
if (this.element) return this.element;
@ -36,7 +43,7 @@ function MonitorStream(monitorData) {
this.show = function() {
const stream = this.getElement();
if (!stream.src) {
stream.src = this.url_to_zms+"&mode=single&scale=100&connkey="+this.connKey;
stream.src = this.url_to_zms+"&mode=single&scale=100&connkey="+this.connKey+this.auth_relay;
}
};
@ -47,19 +54,21 @@ function MonitorStream(monitorData) {
this.scale = newscale;
const oldSrc = img.getAttribute('src');
if (!oldSrc) {
console.log('No src on img?!');
console.log(img);
return;
}
let newSrc = '';
img.setAttribute('src', '');
console.log("Scaling to: " + newscale);
if (newscale == '0' || newscale == 'auto') {
let bottomElement = document.getElementById('replayStatus');
if (!bottomElement) {
bottomElement = document.getElementById('monitorState');
}
const bottomElement = document.getElementById('monitorState'+this.id);
var newSize = scaleToFit(this.width, this.height, $j(img), $j(bottomElement));
console.log(newSize);
//console.log(newSize);
newWidth = newSize.width;
newHeight = newSize.height;
autoScale = parseInt(newSize.autoScale);
@ -78,14 +87,20 @@ function MonitorStream(monitorData) {
}
img.setAttribute('src', newSrc);
};
this.start = function(delay) {
// Step 1 make sure we are streaming instead of a static image
const stream = this.getElement();
if (!stream) return;
this.start = function(delay) {
if (this.janusEnabled) {
var id = parseInt(this.id);
var server = "http://" + window.location.hostname + ":8088/janus";
var server;
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";
}
if (janus == null) {
Janus.init({debug: "all", callback: function() {
janus = new Janus({server: server}); //new Janus
@ -94,47 +109,46 @@ function MonitorStream(monitorData) {
attachVideo(id);
return;
}
const stream = this.getElement();
if (!stream) return;
if (!stream.src) {
// Website Monitors won't have an img tag
// Website Monitors won't have an img tag, neither will video
console.log('No src for #liveStream'+this.id);
console.log(stream);
return;
}
// Step 1 make sure we are streaming instead of a static image
src = stream.src.replace(/mode=single/i, 'mode=jpeg');
if ( -1 == src.search('connkey') ) {
if (-1 == src.search('connkey')) {
src += '&connkey='+this.connKey;
}
if ( stream.src != src ) {
if (stream.src != src) {
console.log("Setting to streaming: " + src);
stream.src = '';
stream.src = src;
}
setTimeout(this.streamCmdQuery.bind(this), delay);
setTimeout(this.statusQuery.bind(this), delay);
};
this.stop = function() {
if ( 0 ) {
var stream = $j('#liveStream'+this.id)[0];
if ( ! stream ) {
console.log('No live stream');
return;
}
const stream = this.getElement();
if (!stream) return;
src = stream.src.replace(/mode=jpeg/i, 'mode=single');
if ( stream.src != src ) {
if (stream.src != src) {
console.log("Setting to stopped");
stream.src = '';
stream.src = src;
}
}
this.streamCmdParms.command = CMD_STOP;
this.streamCmdReq(this.streamCmdParms);
this.streamCommand(CMD_STOP);
};
this.pause = function() {
this.streamCmdParms.command = CMD_PAUSE;
this.streamCmdReq(this.streamCmdParms);
this.streamCommand(CMD_PAUSE);
};
this.play = function() {
this.streamCmdParms.command = CMD_PLAY;
this.streamCmdReq(this.streamCmdParms);
this.streamCommand(CMD_PLAY);
};
this.eventHandler = function(event) {
@ -142,105 +156,251 @@ function MonitorStream(monitorData) {
};
this.onclick = function(evt) {
var el = evt.currentTarget;
var id = el.getAttribute("data-monitor-id");
var url = '?view=watch&mid='+id;
evt.preventDefault();
window.location.assign(url);
console.log('onclick');
};
this.setup_onclick = function() {
var el = document.getElementById('imageFeed'+this.id);
if ( el ) el.addEventListener('click', this.onclick, false);
this.setup_onclick = function(func) {
this.onclick = func;
const el = this.getElement();
if (!el) return;
el.addEventListener('click', this.onclick, false);
};
this.disable_onclick = function() {
document.getElementById('imageFeed'+this.id).removeEventListener('click', this.onclick );
const el = this.getElement();
if (!el) return;
el.removeEventListener('click', this.onclick);
};
this.onpause = function() {
console.log('onpause');
};
this.setup_onpause = function(func) {
this.onpause = func;
};
this.onplay = null;
this.setup_onplay = function(func) {
this.onplay = func;
};
this.setStateClass = function(jobj, stateClass) {
if ( !jobj ) {
if (!jobj) {
console.log("No obj in setStateClass");
return;
}
if ( !jobj.hasClass( stateClass ) ) {
if ( stateClass != 'alarm' ) jobj.removeClass('alarm');
if ( stateClass != 'alert' ) jobj.removeClass('alert');
if ( stateClass != 'idle' ) jobj.removeClass('idle');
if (!jobj.hasClass(stateClass)) {
if (stateClass != 'alarm') jobj.removeClass('alarm');
if (stateClass != 'alert') jobj.removeClass('alert');
if (stateClass != 'idle') jobj.removeClass('idle');
jobj.addClass(stateClass);
}
};
this.setAlarmState = function(alarmState) {
var stateClass = '';
if (alarmState == STATE_ALARM) {
stateClass = 'alarm';
} else if (alarmState == STATE_ALERT) {
stateClass = 'alert';
}
const stateValue = $j('#stateValue'+this.id);
if (stateValue.length) {
stateValue.text(stateStrings[alarmState]);
if (stateClass) {
stateValue.addClass(stateClass);
} else {
stateValue.removeClass();
}
} else {
console.log("No statevalue");
}
//const monitorState = $j('#monitorState'+this.id);
//if (monitorState.length) this.setStateClass(monitorState, stateClass);
const isAlarmed = ( alarmState == STATE_ALARM || alarmState == STATE_ALERT );
const wasAlarmed = ( this.lastAlarmState == STATE_ALARM || this.lastAlarmState == STATE_ALERT );
const newAlarm = ( isAlarmed && !wasAlarmed );
const oldAlarm = ( !isAlarmed && wasAlarmed );
if (newAlarm) {
if (SOUND_ON_ALARM) {
// Enable the alarm sound
if (!msieVer) {
$j('#alarmSound').removeClass('hidden');
} else {
$j('#MediaPlayer').trigger('play');
}
}
if (POPUP_ON_ALARM) {
window.focus();
}
if (this.onalarm) {
this.onalarm();
}
}
if (oldAlarm) { // done with an event do a refresh
if (SOUND_ON_ALARM) {
// Disable alarm sound
if (!msieVer) {
$j('#alarmSound').addClass('hidden');
} else {
$j('#MediaPlayer').trigger('pause');
}
}
if (this.onalarm) {
this.onalarm();
}
}
this.lastAlarmState = alarmState;
}; // end function setAlarmState( currentAlarmState )
this.onalarm = null;
this.setup_onalarm = function(func) {
this.onalarm = func;
};
this.onFailure = function(jqxhr, textStatus, error) {
// Assuming temporary problem, retry in a bit.
setTimeout(this.streamCmdQuery.bind(this), 1000*statusRefreshTimeout);
logAjaxFail(jqxhr, textStatus, error);
};
this.getStreamCmdResponse = function(respObj, respText) {
var stream = $j('#liveStream'+this.id)[0];
if ( ! stream ) {
console.log('No live stream');
var stream = this.getElement();
if (!stream) {
return;
}
if ( respObj.result == 'Ok' ) {
if ( respObj.status ) {
this.status = respObj.status;
this.alarmState = this.status.state;
//watchdogOk('stream');
if (this.streamCmdTimer) {
this.streamCmdTimer = clearTimeout(this.streamCmdTimer);
}
var stateClass = '';
if ( this.alarmState == STATE_ALARM ) {
stateClass = 'alarm';
} else if ( this.alarmState == STATE_ALERT ) {
stateClass = 'alert';
} else {
stateClass = 'idle';
}
if (respObj.result == 'Ok') {
if (respObj.status) {
const streamStatus = this.status = respObj.status;
if ( (
(typeof COMPACT_MONTAGE === 'undefined') ||
!COMPACT_MONTAGE) &&
(this.type != 'WebSite')
) {
var fpsValue = $j('#fpsValue'+this.id);
var stateValue = $j('#stateValue'+this.id);
var monitorState = $j('#monitorState'+this.id);
const viewingFPSValue = $j('#viewingFPSValue'+this.id);
const captureFPSValue = $j('#captureFPSValue'+this.id);
const analysisFPSValue = $j('#analysisFPSValue'+this.id);
if ( fpsValue.length ) fpsValue.text(this.status.fps);
if ( stateValue.length ) stateValue.text(stateStrings[this.alarmState]);
if ( monitorState.length ) this.setStateClass(monitorState, stateClass);
}
this.setStateClass($j('#monitor'+this.id), stateClass);
/*Stream could be an applet so can't use moo tools*/
//stream.parentNode().className = stateClass;
var isAlarmed = ( this.alarmState == STATE_ALARM || this.alarmState == STATE_ALERT );
var wasAlarmed = ( this.lastAlarmState == STATE_ALARM || this.lastAlarmState == STATE_ALERT );
var newAlarm = ( isAlarmed && !wasAlarmed );
var oldAlarm = ( !isAlarmed && wasAlarmed );
if (newAlarm) {
if (false && SOUND_ON_ALARM) {
// Enable the alarm sound
$j('#alarmSound').removeClass('hidden');
if (viewingFPSValue.length && (viewingFPSValue.text != this.status.fps)) {
viewingFPSValue.text(this.status.fps);
}
if ((typeof POPUP_ON_ALARM !== 'undefined') && POPUP_ON_ALARM) {
windowToFront();
if (analysisFPSValue.length && (analysisFPSValue.text != this.status.analysisfps)) {
analysisFPSValue.text(this.status.analysisfps);
}
}
if (false && SOUND_ON_ALARM) {
if ( oldAlarm ) {
// Disable alarm sound
$j('#alarmSound').addClass('hidden');
if (captureFPSValue.length && (captureFPSValue.text != this.status.capturefps)) {
captureFPSValue.text(this.status.capturefps);
}
}
const levelValue = $j('#levelValue');
if (levelValue.length) {
levelValue.text(this.status.level);
var newClass = 'ok';
if (this.status.level > 95) {
newClass = 'alarm';
} else if (this.status.level > 80) {
newClass = 'alert';
}
levelValue.removeClass();
levelValue.addClass(newClass);
}
const delayString = secsToTime(this.status.delay);
if (this.status.paused == true) {
$j('#modeValue'+this.id).text('Paused');
$j('#rate'+this.id).addClass('hidden');
$j('#delayValue'+this.id).text(delayString);
$j('#delay'+this.id).removeClass('hidden');
$j('#level'+this.id).removeClass('hidden');
this.onpause();
} else if (this.status.delayed == true) {
$j('#modeValue'+this.id).text('Replay');
$j('#rateValue'+this.id).text(this.status.rate);
$j('#rate'+this.id).removeClass('hidden');
$j('#delayValue'+this.id).text(delayString);
$j('#delay'+this.id).removeClass('hidden');
$j('#level'+this.id).removeClass('hidden');
if (this.status.rate == 1) {
if (this.onplay) this.onplay();
} else if (this.status.rate > 0) {
if (this.status.rate < 1) {
streamCmdSlowFwd(false);
} else {
streamCmdFastFwd(false);
}
} else {
if (this.status.rate > -1) {
streamCmdSlowRev(false);
} else {
streamCmdFastRev(false);
}
} // rate
} else {
$j('#modeValue'+this.id).text('Live');
$j('#rate'+this.id).addClass('hidden');
$j('#delay'+this.id).addClass('hidden');
$j('#level'+this.id).addClass('hidden');
if (this.onplay) this.onplay();
} // end if paused or delayed
$j('#zoomValue'+this.id).text(this.status.zoom);
if ('zoomOutBtn' in this.buttons) {
if (this.status.zoom == '1.0') {
setButtonState('zoomOutBtn', 'unavail');
} else {
setButtonState('zoomOutBtn', 'inactive');
}
}
} // end if compact montage
this.setAlarmState(this.status.state);
if (canEdit.Monitors) {
if (streamStatus.enabled) {
if ('enableAlarmButton' in this.buttons) {
this.buttons.enableAlarmButton.addClass('disabled');
this.buttons.enableAlarmButton.prop('title', disableAlarmsStr);
}
if ('forceAlarmButton' in this.buttons) {
if (streamStatus.forced) {
this.buttons.forceAlarmButton.addClass('disabled');
this.buttons.forceAlarmButton.prop('title', cancelForcedAlarmStr);
} else {
this.buttons.forceAlarmButton.removeClass('disabled');
this.buttons.forceAlarmButton.prop('title', forceAlarmStr);
}
this.buttons.forceAlarmButton.prop('disabled', false);
}
} else {
if ('enableAlarmButton' in this.buttons) {
this.buttons.enableAlarmButton.removeClass('disabled');
this.buttons.enableAlarmButton.prop('title', enableAlarmsStr);
}
if ('forceAlarmButton' in this.buttons) {
this.buttons.forceAlarmButton.prop('disabled', true);
}
}
if ('enableAlarmButton' in this.buttons) {
this.buttons.enableAlarmButton.prop('disabled', false);
}
} // end if canEdit.Monitors
if (this.status.auth) {
if (this.status.auth != auth_hash) {
if (this.status.auth != this.auth_hash) {
// Try to reload the image stream.
if (stream) {
if (stream && stream.src) {
const oldsrc = stream.src;
stream.src = '';
stream.src = oldsrc.replace(/auth=\w+/i, 'auth='+this.status.auth);
@ -254,23 +414,25 @@ function MonitorStream(monitorData) {
console.error(respObj.message);
// Try to reload the image stream.
if (stream) {
if ( stream.src ) {
if (stream.src) {
console.log('Reloading stream: ' + stream.src);
src = stream.src.replace(/rand=\d+/i, 'rand='+Math.floor((Math.random() * 1000000) ));
if ( src != stream.src ) {
// Maybe navbar updated auth FIXME
if (src != stream.src) {
stream.src = src;
} else {
console.log("Failed to update rand on stream src");
}
} else {
}
} else {
console.log('No stream to reload?');
}
} // end if Ok or not
};
this.lastAlarmState = this.alarmState;
setTimeout(this.streamCmdQuery.bind(this), statusRefreshTimeout);
this.statusQuery = function() {
this.streamCmdQuery(CMD_QUERY);
setTimeout(this.statusQuery.bind(this), statusRefreshTimeout);
};
this.streamCmdQuery = function(resent) {
@ -281,15 +443,44 @@ function MonitorStream(monitorData) {
}
};
if ( this.type != 'WebSite' ) {
this.streamCommand = function(command) {
if (typeof(command) == 'object') {
for (const key in command) this.streamCmdParms[key] = command[key];
} else {
this.streamCmdParms.command = command;
}
this.streamCmdReq(this.streamCmdParms);
};
this.alarmCommand = function(command) {
if (this.ajaxQueue) {
this.ajaxQueue.abort();
}
const alarmCmdParms = Object.assign({}, this.streamCmdParms);
alarmCmdParms.request = 'alarm';
alarmCmdParms.command = command;
alarmCmdParms.id = this.id;
this.ajaxQueue = jQuery.ajaxQueue({
url: this.url,
data: alarmCmdParms, dataType: "json"})
.done(this.getStreamCmdResponse.bind(this))
.fail(this.onFailure.bind(this));
};
if (this.type != 'WebSite') {
$j.ajaxSetup({timeout: AJAX_TIMEOUT});
if (auth_hash) {
this.streamCmdParms.auth = auth_hash;
} else if ( auth_relay ) {
this.streamCmdParms.auth_relay = '';
}
this.streamCmdReq = function(streamCmdParms) {
if ( auth_hash ) {
this.streamCmdParms.auth = auth_hash;
} else if ( auth_relay ) {
this.streamCmdParms.auth_relay = '';
if (this.ajaxQueue) {
this.ajaxQueue.abort();
}
$j.ajaxSetup({timeout: AJAX_TIMEOUT});
$j.getJSON(this.url, streamCmdParms)
this.ajaxQueue = jQuery.ajaxQueue({url: this.url, data: streamCmdParms, dataType: "json"})
.done(this.getStreamCmdResponse.bind(this))
.fail(this.onFailure.bind(this));
};
@ -297,7 +488,7 @@ function MonitorStream(monitorData) {
this.analyse_frames = true;
this.show_analyse_frames = function(toggle) {
this.analyse_frames = toggle;
this.streamCmdParms.command = this.analyse_frames?CMD_ANALYZE_ON:CMD_ANALYZE_OFF;
this.streamCmdParms.command = this.analyse_frames ? CMD_ANALYZE_ON : CMD_ANALYZE_OFF;
this.streamCmdReq(this.streamCmdParms);
};
} // end function MonitorStream
@ -330,6 +521,9 @@ async function attachVideo(id) {
if (jsep !== undefined && jsep !== null) {
Janus.debug("Handling SDP as well...");
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
streaming[id].createAnswer({
jsep: jsep,

48
web/js/ajaxQueue.js Normal file
View File

@ -0,0 +1,48 @@
// See https://github.com/gnarf/jquery-ajaxQueue for license and copyright
(function($) {
// jQuery on an empty object, we are going to use this as our Queue
var ajaxQueue = $({});
$.ajaxQueue = function( ajaxOpts ) {
var jqXHR,
dfd = $.Deferred(),
promise = dfd.promise();
// run the actual query
function doRequest( next ) {
jqXHR = $.ajax( ajaxOpts );
jqXHR.done( dfd.resolve )
.fail( dfd.reject )
.then( next, next );
}
// queue our ajax request
ajaxQueue.queue( doRequest );
// add the abort method
promise.abort = function( statusText ) {
// proxy abort to the jqXHR if it is active
if ( jqXHR ) {
return jqXHR.abort( statusText );
}
// if there wasn't already a jqXHR we need to remove from queue
var queue = ajaxQueue.queue(),
index = $.inArray( doRequest, queue );
if ( index > -1 ) {
queue.splice( index, 1 );
}
// and then reject the deferred
dfd.rejectWith( ajaxOpts.context || ajaxOpts, [ promise, statusText, "" ] );
return promise;
};
return promise;
};
})(jQuery);

View File

@ -1129,6 +1129,11 @@ Always: A zmc process will run and immediately connect and stay connected.~~~~
Attempt to use Janus streaming server for h264/h265 live view. Experimental, but allows
for significantly better performance.'
),
'FUNCTION_JANUS_AUDIO_ENABLED' => array(
'Help' => '
Attempt to enable audio in the Janus stream. Has no effect for cameras without audio support,
but can prevent a stream playing if your camera sends an audio format unsupported by the browser.'
),
'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

@ -916,6 +916,7 @@ function xhtmlFooter() {
?>
<script src="<?php echo cache_bust('skins/'.$skin.'/js/jquery.min.js'); ?>"></script>
<script src="skins/<?php echo $skin; ?>/js/jquery-ui-1.12.1/jquery-ui.min.js"></script>
<script src="<?php echo cache_bust('js/ajaxQueue.js') ?>"></script>
<script src="skins/<?php echo $skin; ?>/js/bootstrap-4.5.0.min.js"></script>
<?php echo output_script_if_exists(array(
'js/tableExport.min.js',
@ -944,7 +945,8 @@ function xhtmlFooter() {
<script src="skins/<?php echo $skin ?>/js/moment.min.js"></script>
<?php
?>
<script nonce="<?php echo $cspNonce; ?>">var $j = jQuery.noConflict();
<script nonce="<?php echo $cspNonce; ?>">
var $j = jQuery.noConflict();
<?php
if ( $skinJsPhpFile ) {
require_once( $skinJsPhpFile );

View File

@ -105,3 +105,11 @@ stateStrings[STATE_PREALARM] = "<?php echo translate('Prealarm') ?>";
stateStrings[STATE_ALARM] = "<?php echo translate('Alarm') ?>";
stateStrings[STATE_ALERT] = "<?php echo translate('Alert') ?>";
stateStrings[STATE_TAPE] = "<?php echo translate('Record') ?>";
<?php
global $config;
foreach ($config as $name=>$c) {
if (!$c['Private'])
echo 'const '. $name . ' = \''.$c['Value'].'\''.PHP_EOL;
}
?>

View File

@ -46,6 +46,10 @@ if (isset($_REQUEST['rate']) ) {
} else {
$rate = reScale(RATE_BASE, $monitor->DefaultRate(), ZM_WEB_DEFAULT_RATE);
}
if ($rate > 1600) {
$rate = 1600;
zm_setcookie('zmEventRate', $rate);
}
if (isset($_REQUEST['scale'])) {
$scale = validInt($_REQUEST['scale']);

View File

@ -51,7 +51,14 @@ function initCycle() {
if ( scale == '0' || scale == 'auto' ) changeScale();
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);
Janus.init({debug: "all", callback: function() {
janus = new Janus({
@ -84,6 +91,9 @@ function initCycle() {
if (jsep !== undefined && jsep !== null) {
Janus.debug("Handling SDP as well...");
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
streaming2.createAnswer({
jsep: jsep,

View File

@ -260,6 +260,21 @@ function initPage() {
window.location.assign('?view=console');
});
//manage the Janus audio check
if (document.getElementsByName("newMonitor[JanusEnabled]")[0].checked) {
document.getElementById("FunctionJanusAudioEnabled").hidden = false;
} else {
document.getElementById("FunctionJanusAudioEnabled").hidden = true;
}
document.getElementsByName("newMonitor[JanusEnabled]")[0].addEventListener('change', function() {
if (this.checked) {
document.getElementById("FunctionJanusAudioEnabled").hidden = false;
} else {
document.getElementById("FunctionJanusAudioEnabled").hidden = true;
}
});
if ( ZM_OPT_USE_GEOLOCATION ) {
if ( window.L ) {
var form = document.getElementById('contentForm');

View File

@ -117,7 +117,7 @@ function changeSize() {
monitor_frame.css('height', ( height ? height+'px' : 'auto'));
/*Stream could be an applet so can't use moo tools*/
var streamImg = document.getElementById('liveStream'+monitor.id);
var streamImg = monitor.getElement();
if ( streamImg ) {
if ( streamImg.nodeName == 'IMG' ) {
var src = streamImg.src;
@ -269,7 +269,7 @@ function cancel_layout(button) {
$j('#EditLayout').show();
for ( var i = 0, length = monitors.length; i < length; i++ ) {
var monitor = monitors[i];
monitor.setup_onclick();
monitor.setup_onclick(handleClick);
//monitor_feed = $j('#imageFeed'+monitor.id);
//monitor_feed.click(monitor.onclick);
@ -312,7 +312,7 @@ function initPage() {
if ( monitors[i].type == 'WebSite' && interval > 0 ) {
setInterval(reloadWebSite, interval*1000, i);
}
monitors[i].setup_onclick();
monitors[i].setup_onclick(handleClick);
}
selectLayout('#zmMontageLayout');
}
@ -322,5 +322,15 @@ function watchFullscreen() {
openFullscreen(content);
}
function handleClick(evt) {
console.log("handleClick");
var el = evt.currentTarget;
console.log(el);
var id = el.getAttribute("data-monitor-id");
var url = '?view=watch&mid='+id;
evt.preventDefault();
window.location.assign(url);
}
// Kick everything off
$j(document).ready(initPage);

View File

@ -182,73 +182,6 @@ function changeScale() {
}
} // end function changeScale
function setAlarmState(currentAlarmState) {
alarmState = currentAlarmState;
var stateClass = '';
if (alarmState == STATE_ALARM) {
stateClass = 'alarm';
} else if (alarmState == STATE_ALERT) {
stateClass = 'alert';
}
$j('#stateValue').text(stateStrings[alarmState]);
if (stateClass) {
$j('#stateValue').addClass(stateClass);
} else {
$j('#stateValue').removeClass();
}
var isAlarmed = ( alarmState == STATE_ALARM || alarmState == STATE_ALERT );
var wasAlarmed = ( lastAlarmState == STATE_ALARM || lastAlarmState == STATE_ALERT );
var newAlarm = ( isAlarmed && !wasAlarmed );
var oldAlarm = ( !isAlarmed && wasAlarmed );
if (newAlarm) {
table.bootstrapTable('refresh');
if (SOUND_ON_ALARM) {
// Enable the alarm sound
if (!msieVer) {
$j('#alarmSound').removeClass('hidden');
} else {
$j('#MediaPlayer').trigger('play');
}
}
if (POPUP_ON_ALARM) {
window.focus();
}
}
if (oldAlarm) { // done with an event do a refresh
table.bootstrapTable('refresh');
if (SOUND_ON_ALARM) {
// Disable alarm sound
if (!msieVer) {
$j('#alarmSound').addClass('hidden');
} else {
$j('#MediaPlayer').trigger('pause');
}
}
}
lastAlarmState = alarmState;
} // end function setAlarmState( currentAlarmState )
function streamCmdReq(data) {
if (auth_hash) data.auth = auth_hash;
$j.getJSON(monitorUrl + '?view=request&request=stream&connkey='+connKey, data)
.done(getStreamCmdResponse)
.fail(getStreamCmdError);
streamCmdTimer = null;
}
function getStreamCmdError(text, error) {
console.log(error);
// Error are normally due to failed auth. reload the page.
//window.location.reload();
}
function getStreamCmdResponse(respObj, respText) {
watchdogOk('stream');
if (streamCmdTimer) {
@ -379,7 +312,7 @@ function streamCmdQuery() {
streamCmdReq({command: CMD_QUERY});
}
function streamCmdPause(action) {
function onPause() {
setButtonState('pauseBtn', 'active');
setButtonState('playBtn', 'inactive');
setButtonState('stopBtn', 'inactive');
@ -389,18 +322,17 @@ function streamCmdPause(action) {
setButtonState('slowRevBtn', 'inactive');
setButtonState('fastRevBtn', 'inactive');
}
}
function streamCmdPause(action) {
onPause();
if (action) {
var data = {};
if (auth_hash) data.auth = auth_hash;
data.command = CMD_PAUSE;
streamCmdReq(data);
monitorStream.streamCommand(CMD_PAUSE);
}
}
function streamCmdPlay(action) {
function onPlay() {
setButtonState('pauseBtn', 'inactive');
setButtonState('playBtn', 'active');
if (streamStatus.delayed == true) {
if (monitorStream.status.delayed == true) {
setButtonState('stopBtn', 'inactive');
if (monitorStreamReplayBuffer) {
setButtonState('fastFwdBtn', 'inactive');
@ -417,11 +349,14 @@ function streamCmdPlay(action) {
setButtonState('fastRevBtn', 'unavail');
}
}
if (action) {
streamCmdReq({command: CMD_PLAY});
}
}
function streamCmdPlay(action) {
onPlay();
if (action) {
monitorStream.streamCommand(CMD_PLAY);
}
}
function streamCmdStop(action) {
setButtonState('pauseBtn', 'inactive');
@ -434,7 +369,7 @@ function streamCmdStop(action) {
setButtonState('fastRevBtn', 'unavail');
}
if (action) {
streamCmdReq({command: CMD_STOP});
monitorStream.streamCommand(CMD_STOP);
}
setButtonState('stopBtn', 'unavail');
setButtonState('playBtn', 'active');
@ -451,7 +386,7 @@ function streamCmdFastFwd(action) {
setButtonState('fastRevBtn', 'inactive');
}
if (action) {
streamCmdReq({command: CMD_FASTFWD});
monitorStream.streamCommand(CMD_FASTFWD);
}
}
@ -466,7 +401,7 @@ function streamCmdSlowFwd(action) {
setButtonState('fastRevBtn', 'inactive');
}
if (action) {
streamCmdReq({command: CMD_SLOWFWD});
monitorStream.command(CMD_SLOWFWD);
}
setButtonState('pauseBtn', 'active');
if (monitorStreamReplayBuffer) {
@ -485,7 +420,7 @@ function streamCmdSlowRev(action) {
setButtonState('fastRevBtn', 'inactive');
}
if (action) {
streamCmdReq({command: CMD_SLOWREV});
monitorStream.command(CMD_SLOWREV);
}
setButtonState('pauseBtn', 'active');
if (monitorStreamReplayBuffer) {
@ -504,35 +439,24 @@ function streamCmdFastRev(action) {
setButtonState('fastRevBtn', 'inactive');
}
if (action) {
streamCmdReq({command: CMD_FASTREV});
monitorStream.command(CMD_FASTREV);
}
}
function streamCmdZoomIn(x, y) {
var data = {};
data.x = x;
data.y = y;
data.command = CMD_ZOOMIN;
streamCmdReq(data);
monitorStream.streamCommand({x: x, y: y, command: CMD_ZOOMIN});
}
function streamCmdZoomOut() {
streamCmdReq({command: CMD_ZOOMOUT});
monitorStream.streamCommand(CMD_ZOOMOUT);
}
function streamCmdScale(scale) {
var data = {};
data.command = CMD_SCALE;
data.scale = scale;
streamCmdReq(data);
monitorStream.streamCommand({command: CMD_SCALE, scale: scale});
}
function streamCmdPan(x, y) {
var data = {};
data.x = x;
data.y = y;
data.command = CMD_PAN;
streamCmdReq(data);
monitorStream.streamCommand({x: x, y: y, command: CMD_PAN});
}
@ -565,33 +489,12 @@ function statusCmdQuery() {
statusCmdTimer = null;
}
function alarmCmdReq(data) {
if (auth_hash) data.auth = auth_hash;
$j.getJSON(monitorUrl + '?view=request&request=alarm&id='+monitorId, data)
.done(getAlarmCmdResponse)
.fail(function(jqxhr, textStatus, error) {
if (textStatus === 'timeout') {
streamCmdQuery();
} else {
logAjaxFail(jqxhr, textStatus, error);
}
});
}
function getAlarmCmdResponse(respObj, respText) {
checkStreamForErrors('getAlarmCmdResponse', respObj);
}
function cmdDisableAlarms() {
var data = {};
data.command = 'disableAlarms';
alarmCmdReq(data);
monitorStream.alarmCommand('disableAlarms');
}
function cmdEnableAlarms() {
var data = {};
data.command = 'enableAlarms';
alarmCmdReq(data);
monitorStream.alarmCommand('enableAlarms');
}
function cmdAlarm() {
@ -603,16 +506,13 @@ function cmdAlarm() {
}
function cmdForceAlarm() {
var data = {};
data.command = 'forceAlarm';
alarmCmdReq(data);
monitorStream.alarmCommand('forceAlarm');
if (window.event) window.event.preventDefault();
return false;
}
function cmdCancelForcedAlarm() {
var data = {};
data.command = 'cancelForcedAlarm';
alarmCmdReq(data);
monitorStream.alarmCommand('cancelForcedAlarm');
if (window.event) window.event.preventDefault();
return false;
}
@ -719,6 +619,7 @@ console.log("New src: " + newsrc);
function handleClick(event) {
// target should be the img tag
var target = $j(event.target);
console.log("click " + showMode);
var width = target.width();
var height = target.height();
@ -894,6 +795,10 @@ function msieVer() {
}
}
function refresh_events_table() {
table.bootstrapTable('refresh');
}
function initPage() {
if (canView.Control) {
// Load the PTZ Preset modal into the DOM
@ -901,79 +806,37 @@ function initPage() {
// Load the settings modal into the DOM
if (monitorType == 'Local') getSettingsModal();
}
// Only enable the settings button for local cameras
settingsBtn.prop('disabled', !(canView.Control && (monitorType == 'Local')));
if (monitorType != 'WebSite') {
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');
}
monitorStream = new MonitorStream(monitorData[monIdx]);
// Start the fps and status updates. give a random delay so that we don't assault the server
monitorStream.setScale('auto');
monitorStream.start(Math.round( (Math.random()+0.5)*statusRefreshTimeout ));
if (streamMode == 'single') {
monitorStream.setup_onclick(fetchImage);
} else {
monitorStream.setup_onclick(handleClick);
}
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"];
console.log(status);
}
} else if (msg["error"] !== undefined && msg["error"] !== null) {
Janus.error(msg["error"]);
return;
}
if (jsep !== undefined && jsep !== null) {
Janus.debug("Handling SDP as well...");
Janus.debug(jsep);
// Offer from the plugin, let's answer
streaming2.createAnswer({
jsep: jsep,
// We want recvonly audio/video and, if negotiated, datachannels
media: {audioSend: false, videoSend: false, data: true},
success: function(jsep) {
Janus.debug("Got SDP!");
Janus.debug(jsep);
var body = {"request": "start"};
streaming2.send({"message": body, "jsep": jsep});
},
error: function(error) {
Janus.error("WebRTC error:", error);
}
});
}
}, //onmessage function
onremotestream: function(stream) {
Janus.debug(" ::: Got a remote track :::");
Janus.debug(stream);
Janus.attachMediaStream(document.getElementById("liveStream" + monitorId), stream);
document.getElementById("liveStream" + monitorId).play();
}
}); // attach
} //Success functio
}); //new Janus
}}); //janus.init callback
} else if (canStreamNative || (streamMode == 'single')) {
monitorStream.setup_onpause(onPause);
monitorStream.setup_onplay(onPlay);
monitorStream.setup_onalarm(refresh_events_table);
monitorStream.setButton('enableAlarmButton', enableAlmBtn);
monitorStream.setButton('forceAlarmButton', forceAlmBtn);
monitorStream.setButton('zoomOutButton', $j('zoomOutBtn'));
/*
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')) {
var streamImg = $j('#imageFeed img');
if (!streamImg) streamImg = $j('#imageFeed object');
if (!streamImg) {
@ -998,6 +861,7 @@ function initPage() {
if (refreshApplet && appletRefreshTime) {
setTimeout(appletRefresh, appletRefreshTime*1000);
}
*/
if (window.history.length == 1) {
$j('#closeControl').html('');
}
@ -1008,6 +872,27 @@ function initPage() {
document.querySelectorAll('select[name="changeRate"]').forEach(function(el) {
el.onchange = window['changeRate'].bind(el, el);
});
// Init the bootstrap-table
table.bootstrapTable({icons: icons});
// Update table rows each time after new data is loaded
table.on('post-body.bs.table', function(data) {
$j('#eventList tr:contains("New Event")').addClass('recent');
});
// Take appropriate action when the user clicks on a cell
table.on('click-cell.bs.table', processClicks);
// Some toolbar events break the thumbnail animation, so re-init eventlistener
table.on('all.bs.table', initThumbAnimation);
// Update table links each time after new data is loaded
table.on('post-body.bs.table', function(data) {
var thumb_ndx = $j('#eventList tr th').filter(function() {
return $j(this).text().trim() == 'Thumbnail';
}).index();
table.find("tr td:nth-child(" + (thumb_ndx+1) + ")").addClass('colThumbnail');
});
} else if (monitorRefresh > 0) {
setInterval(reloadWebSite, monitorRefresh*1000);
}
@ -1044,31 +929,6 @@ function initPage() {
} else {
cyclePause();
}
// Only enable the settings button for local cameras
settingsBtn.prop('disabled', !(canView.Control && (monitorType == 'Local')));
// Init the bootstrap-table
if (monitorType != 'WebSite') table.bootstrapTable({icons: icons});
// Update table rows each time after new data is loaded
table.on('post-body.bs.table', function(data) {
$j('#eventList tr:contains("New Event")').addClass('recent');
});
// Take appropriate action when the user clicks on a cell
table.on('click-cell.bs.table', processClicks);
// Some toolbar events break the thumbnail animation, so re-init eventlistener
table.on('all.bs.table', initThumbAnimation);
// Update table links each time after new data is loaded
table.on('post-body.bs.table', function(data) {
var thumb_ndx = $j('#eventList tr th').filter(function() {
return $j(this).text().trim() == 'Thumbnail';
}).index();
table.find("tr td:nth-child(" + (thumb_ndx+1) + ")").addClass('colThumbnail');
});
} // initPage
function watchFullscreen() {
@ -1153,10 +1013,7 @@ function cycleToggle(e) {
function changeRate(e) {
const newvalue = $j(e).val();
if (1) {
var data = {};
data.command = CMD_MAXFPS;
data.maxfps = newvalue;
streamCmdReq(data);
monitorStream.streamCommand({command: CMD_MAXFPS, maxfps: newvalue});
} else {
streamImage = $j('#liveStream'+monitorData[monIdx].id);
const oldsrc = streamImage.attr('src');

View File

@ -64,6 +64,7 @@ foreach ($monitors as $m) {
?>
monitorData[monitorData.length] = {
'id': <?php echo $m->Id() ?>,
'connKey': <?php echo $m->connKey() ?>,
'width': <?php echo $m->ViewWidth() ?>,
'height':<?php echo $m->ViewHeight() ?>,
'janusEnabled':<?php echo $m->JanusEnabled() ?>,

View File

@ -585,9 +585,19 @@ if (count($available_monitor_ids)) {
</div>
</td>
</tr>
<tr class="LinkedMonitors">
<td class="text-right pr-3"><?php echo translate('LinkedMonitors'); echo makeHelpLink('OPTIONS_LINKED_MONITORS') ?></td>
<td>
<tr id="FunctionJanusAudioEnabled">
<td class="text-right pr-3"><?php echo translate('Janus Live Stream Audio') ?></td>
<td><input type="checkbox" name="newMonitor[JanusAudioEnabled]" value="1"<?php echo $monitor->JanusAudioEnabled() ? ' checked="checked"' : '' ?>/>
<?php
if ( isset($OLANG['FUNCTION_JANUS_AUDIO_ENABLED']) ) {
echo '<div class="form-text">'.$OLANG['FUNCTION_JANUS_AUDIO_ENABLED']['Help'].'</div>';
}
?>
</td>
</tr>
<tr class="LinkedMonitors">
<td class="text-right pr-3"><?php echo translate('LinkedMonitors'); echo makeHelpLink('OPTIONS_LINKED_MONITORS') ?></td>
<td>
<?php
$monitor_options = array();
foreach ($monitors as $linked_monitor) {

View File

@ -307,14 +307,7 @@ foreach (array_reverse($zones) as $zone) {
</div>
<?php
if ((!ZM_WEB_COMPACT_MONTAGE) && ($monitor->Type() != 'WebSite')) {
?>
<div id="monitorState<?php echo $monitor->Id() ?>" class="monitorState idle">
<?php echo translate('State') ?>:
<span id="stateValue<?php echo $monitor->Id() ?>"></span>
&nbsp;-&nbsp;
<span id="fpsValue<?php echo $monitor->Id() ?>"></span>&nbsp;fps
</div>
<?php
echo $monitor->getMonitorStateHTML();
}
?>
</div>

View File

@ -247,7 +247,7 @@ echo htmlSelect('cyclePeriod', $cyclePeriodOptions, $period, array('id'=>'cycleP
</ul>
</nav>
<div class="container-fluid col-sm-offset-2 h-100 pr-0">
<div id="imageFeed"
<div id="imageFeed<?php echo $monitor->Id() ?>"
<?php
if ($streamMode == 'jpeg') {
echo 'title="Click to zoom, shift click to pan, ctrl click to zoom out"';
@ -255,24 +255,9 @@ if ($streamMode == 'jpeg') {
?>
><?php echo getStreamHTML($monitor, $options); ?>
</div>
<?php if ($monitor->Type() != 'WebSite') { ?>
<div id="monitorStatus">
<div id="monitorState">
<span><?php echo translate('State') ?>:<span id="stateValue"></span></span>
<span id="viewingFPS" title="<?php echo translate('Viewing FPS')?>"><span id="viewingFPSValue"></span> fps</span>
<span id="captureFPS" title="<?php echo translate('Capturing FPS')?>"><span id="captureFPSValue"></span> fps</span>
<?php if ( $monitor->Function() == 'Modect' or $monitor->Function() == 'Mocord' ) { ?>
<span id="analysisFPS" title="<?php echo translate('Analysis FPS')?>"><span id="analysisFPSValue"></span> fps</span>
<?php } ?>
</div>
</div>
<div id="replayStatus"<?php echo $streamMode=="single" ? ' class="hidden"' : '' ?>>
<span id="mode"><?php echo translate('Mode') ?>: <span id="modeValue"></span></span>
<span id="rate"><?php echo translate('Rate') ?>: <span id="rateValue"></span>x</span>
<span id="delay"><?php echo translate('Delay') ?>: <span id="delayValue"></span>s</span>
<span id="level"><?php echo translate('Buffer') ?>: <span id="levelValue"></span>%</span>
<span id="zoom"><?php echo translate('Zoom') ?>: <span id="zoomValue"></span>x</span>
</div>
<?php if ($monitor->Type() != 'WebSite') {
echo $monitor->getMonitorStateHTML();
?>
<div class="buttons" id="dvrControls">
<?php
if ($streamMode == 'jpeg') {
@ -413,4 +398,5 @@ if ( ZM_WEB_SOUND_ON_ALARM ) {
</div>
<script src="<?php echo cache_bust('js/adapter.min.js') ?>"></script>
<script src="/javascript/janus/janus.js"></script>
<script src="<?php echo cache_bust('js/MonitorStream.js') ?>"></script>
<?php xhtmlFooter() ?>

View File

@ -164,10 +164,10 @@ if ( count($other_zones) ) {
<polygon id="zonePoly" points="<?php echo $zone['AreaCoords'] ?>" class="Editing <?php echo $zone['Type'] ?>"/>
Sorry, your browser does not support inline SVG
</svg>
</div><?php # imageFrame?>
<div id="monitorState">
<?php echo translate('State') ?>:&nbsp;<span id="stateValue<?php echo $monitor->Id() ?>"></span>&nbsp;-&nbsp;<span id="fpsValue<?php echo $monitor->Id() ?>"></span>&nbsp;fps
</div>
</div><?php # imageFrame
echo $monitor->getMonitorStateHTML();
?>
<div id="StreamControlButtons">
<button type="button" id="analyseBtn" class="btn btn-primary" title="<?php echo translate('Showing Analysis') ?>">
<i class="material-icons md-18">assessment</i>

View File

@ -91,9 +91,7 @@ xhtmlHeaders(__FILE__, translate('Zones'));
?>
Sorry, your browser does not support inline SVG
</svg>
<div id="monitorState">
<?php echo translate('State') ?>:&nbsp;<span id="stateValue<?php echo $monitor->Id() ?>"></span>&nbsp;-&nbsp;<span id="fpsValue<?php echo $monitor->Id() ?>"></span>&nbsp;fps
</div>
<?php echo $monitor->getMonitorStateHTML(); ?>
</div>
<div class="zones">
<table id="zonesTable" class="major">