Merge branch 'master' into multistream
This commit is contained in:
commit
23bfbcd6ce
|
@ -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
|
||||
|
|
|
@ -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 '',
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
@ -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;
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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',
|
||||
},
|
||||
{
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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];
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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];
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 ) {
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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', '');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
?>
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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) ) {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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() {
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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);
|
|
@ -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.'
|
||||
|
|
|
@ -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 );
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
?>
|
||||
|
|
|
@ -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']);
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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');
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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');
|
||||
|
|
|
@ -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() ?>,
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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>
|
||||
-
|
||||
<span id="fpsValue<?php echo $monitor->Id() ?>"></span> fps
|
||||
</div>
|
||||
<?php
|
||||
echo $monitor->getMonitorStateHTML();
|
||||
}
|
||||
?>
|
||||
</div>
|
||||
|
|
|
@ -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() ?>
|
||||
|
|
|
@ -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') ?>: <span id="stateValue<?php echo $monitor->Id() ?>"></span> - <span id="fpsValue<?php echo $monitor->Id() ?>"></span> 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>
|
||||
|
|
|
@ -91,9 +91,7 @@ xhtmlHeaders(__FILE__, translate('Zones'));
|
|||
?>
|
||||
Sorry, your browser does not support inline SVG
|
||||
</svg>
|
||||
<div id="monitorState">
|
||||
<?php echo translate('State') ?>: <span id="stateValue<?php echo $monitor->Id() ?>"></span> - <span id="fpsValue<?php echo $monitor->Id() ?>"></span> fps
|
||||
</div>
|
||||
<?php echo $monitor->getMonitorStateHTML(); ?>
|
||||
</div>
|
||||
<div class="zones">
|
||||
<table id="zonesTable" class="major">
|
||||
|
|
Loading…
Reference in New Issue