Merge branch 'montagereview_rework' into storageareas

pull/2077/head
Isaac Connor 2017-07-05 15:34:02 -04:00
commit c3c6459052
16 changed files with 1171 additions and 1162 deletions

View File

@ -337,13 +337,21 @@ CREATE TABLE `Monitors` (
`Format` int(10) unsigned NOT NULL default '0',
`V4LMultiBuffer` tinyint(1) unsigned,
`V4LCapturesPerFrame` tinyint(3) unsigned,
<<<<<<< HEAD
`Protocol` varchar(16),
=======
`Protocol` varchar(16) default '',
>>>>>>> montagereview_rework
`Method` varchar(16) NOT NULL default '',
`Host` varchar(64),
`Port` varchar(8) NOT NULL default '',
`SubPath` varchar(64) NOT NULL default '',
`Path` varchar(255),
<<<<<<< HEAD
`Options` varchar(255),
=======
`Options` varchar(255) default '',
>>>>>>> montagereview_rework
`User` varchar(64),
`Pass` varchar(64),
`Width` smallint(5) unsigned NOT NULL default '0',

View File

@ -28,7 +28,7 @@ New installs
1. Unless you are already using MariaDB server, you need to ensure that the
server is configured to start during boot and properly secured by running:
sudo dnf install mariadb-server
sudo yum install mariadb-server
sudo systemctl enable mariadb
sudo systemctl start mariadb.service
mysql_secure_installation

View File

@ -18,7 +18,7 @@ Alias /zm "@ZM_WEBDIR@"
Allow from all
</Directory>
ScriptAlias /cgi-bin/zm "@ZM_CGIDIR@"
ScriptAlias /cgi-bin-zm "@ZM_CGIDIR@"
<Directory "@ZM_CGIDIR@">
SSLRequireSSL
AllowOverride All

View File

@ -4,3 +4,5 @@ var/cache/zoneminder/events
var/cache/zoneminder/images
var/cache/zoneminder/temp
usr/share/zoneminder/db
etc/zm
etc/zm/conf.d

View File

@ -4,51 +4,51 @@ set -e
if [ "$1" = "configure" ]; then
. /etc/zm/zm.conf
. /etc/zm/zm.conf
# The logs can contain passwords, etc... so by setting group root, only www-data can read them, not people in the www-data group
chown www-data:root /var/log/zm
chown www-data:www-data /var/lib/zm
if [ -z "$2" ]; then
chown www-data:www-data /var/cache/zoneminder /var/cache/zoneminder/*
fi
# The logs can contain passwords, etc... so by setting group root, only www-data can read them, not people in the www-data group
chown www-data:root /var/log/zm
chown www-data:www-data /var/lib/zm
if [ -z "$2" ]; then
chown www-data:www-data /var/cache/zoneminder /var/cache/zoneminder/*
fi
# Do this every time the package is installed or upgraded
# Do this every time the package is installed or upgraded
# Ensure zoneminder is stopped
invoke-rc.d zoneminder stop || true
if [ "$ZM_DB_HOST" = "localhost" ]; then
if [ -e "/etc/init.d/mysql" ]; then
#
# Get mysql started if it isn't
#
if ! $(/etc/init.d/mysql status >/dev/null 2>&1); then
invoke-rc.d mysql start
fi
if $(/etc/init.d/mysql status >/dev/null 2>&1); then
mysqladmin --defaults-file=/etc/mysql/debian.cnf -f reload
# test if database if already present...
if ! $(echo quit | mysql --defaults-file=/etc/mysql/debian.cnf zm > /dev/null 2> /dev/null) ; then
cat /usr/share/zoneminder/db/zm_create.sql | mysql --defaults-file=/etc/mysql/debian.cnf
# This creates the user.
echo "grant lock tables, alter,select,insert,update,delete,create,index on ${ZM_DB_NAME}.* to '${ZM_DB_USER}'@localhost identified by \"${ZM_DB_PASS}\";" | mysql --defaults-file=/etc/mysql/debian.cnf mysql
else
echo "grant lock tables, alter,select,insert,update,delete,create,index on ${ZM_DB_NAME}.* to '${ZM_DB_USER}'@localhost;" | mysql --defaults-file=/etc/mysql/debian.cnf mysql
fi
# Ensure zoneminder is stopped
invoke-rc.d zoneminder stop || true
zmupdate.pl --nointeractive
zmupdate.pl --nointeractive -f
echo "Done Updating, starting ZoneMinder"
invoke-rc.d zoneminder start || true
else
echo 'NOTE: mysql not running, please start mysql and run dpkg-reconfigure zoneminder when it is running.'
fi
else
echo 'mysql not found, assuming remote server.'
if [ "$ZM_DB_HOST" = "localhost" ]; then
if [ -e "/etc/init.d/mysql" ]; then
#
# Get mysql started if it isn't
#
if ! $(/etc/init.d/mysql status >/dev/null 2>&1); then
invoke-rc.d mysql start
fi
if $(/etc/init.d/mysql status >/dev/null 2>&1); then
mysqladmin --defaults-file=/etc/mysql/debian.cnf -f reload
# test if database if already present...
if ! $(echo quit | mysql --defaults-file=/etc/mysql/debian.cnf zm > /dev/null 2> /dev/null) ; then
cat /usr/share/zoneminder/db/zm_create.sql | mysql --defaults-file=/etc/mysql/debian.cnf
# This creates the user.
echo "grant lock tables, alter,select,insert,update,delete,create,index on ${ZM_DB_NAME}.* to '${ZM_DB_USER}'@localhost identified by \"${ZM_DB_PASS}\";" | mysql --defaults-file=/etc/mysql/debian.cnf mysql
else
echo "grant lock tables, alter,select,insert,update,delete,create,index on ${ZM_DB_NAME}.* to '${ZM_DB_USER}'@localhost;" | mysql --defaults-file=/etc/mysql/debian.cnf mysql
fi
zmupdate.pl --nointeractive
zmupdate.pl --nointeractive -f
else
echo 'NOTE: mysql not running, please start mysql and run dpkg-reconfigure zoneminder when it is running.'
fi
else
echo "Not doing database upgrade due to remote db server ($ZM_DB_HOST)"
echo 'mysql not found, assuming remote server.'
fi
else
echo "Not doing database upgrade due to remote db server ($ZM_DB_HOST)"
fi
echo "Done Updating, starting ZoneMinder"
invoke-rc.d zoneminder start || true
fi
#DEBHELPER#

View File

@ -21,6 +21,8 @@ if [ "$1" = "configure" ]; then
# Ensure zoneminder is stopped
deb-systemd-invoke stop zoneminder.service || exit $?
# Ensure zoneminder is stopped
deb-systemd-invoke stop zoneminder.service || exit $?
if [ "$ZM_DB_HOST" = "localhost" ]; then
if [ -e "/etc/init.d/mysql" ]; then
@ -46,10 +48,10 @@ if [ "$1" = "configure" ]; then
zmupdate.pl --nointeractive
zmupdate.pl --nointeractive -f
else
echo 'NOTE: mysql not running, please start mysql and run dpkg-reconfigure zoneminder when it is running.'
echo 'NOTE: mysql not running, please start mysql and run dpkg-reconfigure zoneminder when it is running.'
fi
else
echo 'mysql not found, assuming remote server.'
echo 'mysql not found, assuming remote server.'
fi
else

View File

@ -181,7 +181,7 @@ sub getUUID {
my $uuid= "";
# Verify the current UUID is valid and not nil
if (( $Config{ZM_TELEMETRY_UUID} =~ /([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})/i ) && ( $Config{ZM_TELEMETRY_UUID} ne "00000000-0000-0000-0000-000000000000" )) {
if (( $Config{ZM_TELEMETRY_UUID} =~ /([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})/i ) && ( $Config{ZM_TELEMETRY_UUID} ne '00000000-0000-0000-0000-000000000000' )) {
$uuid = $Config{ZM_TELEMETRY_UUID};
} else {
my $sql = 'SELECT uuid()';
@ -190,7 +190,7 @@ sub getUUID {
$uuid = $Config{ZM_TELEMETRY_UUID} = $sth->fetchrow_array();
$sth->finish();
$sql = "UPDATE Config set Value = ? WHERE Name = 'ZM_TELEMETRY_UUID'";
$sql = q`UPDATE Config set Value = ? WHERE Name = 'ZM_TELEMETRY_UUID'`;
$sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() );
$res = $sth->execute( "$uuid" ) or die( "Can't execute: ".$sth->errstr() );
$sth->finish();

View File

@ -195,7 +195,7 @@ void Logger::initialise( const std::string &id, const Options &options ) {
level( tempLevel );
mFlush = false;
if (envPtr = getenv( "LOG_FLUSH")) {
if ( (envPtr = getenv("LOG_FLUSH")) ) {
mFlush = atoi( envPtr );
} else if ( config.log_debug ) {
mFlush = true;

View File

@ -30,8 +30,7 @@ class Monitor;
#define TV_2_FLOAT( tv ) ( double((tv).tv_sec) + (double((tv).tv_usec) / 1000000.0) )
class StreamBase
{
class StreamBase {
public:
typedef enum { STREAM_JPEG, STREAM_RAW, STREAM_ZIP, STREAM_SINGLE, STREAM_MPEG } StreamType;
@ -110,8 +109,7 @@ protected:
virtual void processCommand( const CmdMsg *msg )=0;
public:
StreamBase()
{
StreamBase() {
monitor = 0;
type = DEFAULT_TYPE;
@ -145,32 +143,27 @@ public:
}
virtual ~StreamBase();
void setStreamType( StreamType p_type )
{
void setStreamType( StreamType p_type ) {
type = p_type;
}
void setStreamFormat( const char *p_format )
{
void setStreamFormat( const char *p_format ) {
format = p_format;
}
void setStreamScale( int p_scale )
{
void setStreamScale( int p_scale ) {
scale = p_scale;
if ( ! scale )
scale = DEFAULT_SCALE;
}
void setStreamReplayRate( int p_rate )
{
void setStreamReplayRate( int p_rate ) {
replay_rate = p_rate;
}
void setStreamMaxFPS( double p_maxfps )
{
void setStreamMaxFPS( double p_maxfps ) {
maxfps = p_maxfps;
}
void setStreamBitrate( int p_bitrate )
{
void setStreamBitrate( int p_bitrate ) {
bitrate = p_bitrate;
}
void setStreamQueue( int p_connkey )
{
void setStreamQueue( int p_connkey ) {
connkey = p_connkey;
}
virtual void openComms();

View File

@ -28,43 +28,41 @@
#include "zm_monitorstream.h"
bool ValidateAccess( User *user, int mon_id ) {
bool allowed = true;
bool allowed = true;
if ( mon_id > 0 ) {
if ( user->getStream() < User::PERM_VIEW )
allowed = false;
if ( !user->canAccess( mon_id ) )
allowed = false;
} else {
if ( user->getEvents() < User::PERM_VIEW )
allowed = false;
}
if ( !allowed ) {
Error( "Error, insufficient privileges for requested action" );
exit( -1 );
if ( mon_id > 0 ) {
if ( user->getStream() < User::PERM_VIEW )
allowed = false;
if ( !user->canAccess( mon_id ) )
allowed = false;
} else {
Debug( 1, "User allowed.");
}
return( allowed );
if ( user->getEvents() < User::PERM_VIEW )
allowed = false;
}
if ( !allowed ) {
Error( "Error, insufficient privileges for requested action" );
exit( -1 );
}
return( allowed );
}
int main( int argc, const char *argv[] ) {
self = argv[0];
self = argv[0];
srand( getpid() * time( 0 ) );
srand( getpid() * time( 0 ) );
enum { ZMS_MONITOR, ZMS_EVENT } source = ZMS_MONITOR;
enum { ZMS_JPEG, ZMS_MPEG, ZMS_RAW, ZMS_ZIP, ZMS_SINGLE } mode = ZMS_JPEG;
char format[32] = "";
int monitor_id = 0;
time_t event_time = 0;
int event_id = 0;
unsigned int frame_id = 1;
unsigned int scale = 100;
unsigned int rate = 100;
double maxfps = 10.0;
unsigned int bitrate = 100000;
unsigned int ttl = 0;
enum { ZMS_MONITOR, ZMS_EVENT } source = ZMS_MONITOR;
enum { ZMS_JPEG, ZMS_MPEG, ZMS_RAW, ZMS_ZIP, ZMS_SINGLE } mode = ZMS_JPEG;
char format[32] = "";
int monitor_id = 0;
time_t event_time = 0;
int event_id = 0;
unsigned int frame_id = 1;
unsigned int scale = 100;
unsigned int rate = 100;
double maxfps = 10.0;
unsigned int bitrate = 100000;
unsigned int ttl = 0;
EventStream::StreamMode replay = EventStream::MODE_SINGLE;
std::string username;
std::string password;
@ -72,163 +70,158 @@ int main( int argc, const char *argv[] ) {
unsigned int connkey = 0;
unsigned int playback_buffer = 0;
bool nph = false;
const char *basename = strrchr( argv[0], '/' );
if (basename) //if we found a / lets skip past it
basename++;
else //argv[0] will not always contain the full path, but rather just the script name
basename = argv[0];
const char *nph_prefix = "nph-";
if ( basename && !strncmp( basename, nph_prefix, strlen(nph_prefix) ) ) {
nph = true;
}
zmLoadConfig();
logInit( "zms" );
bool nph = false;
const char *basename = strrchr( argv[0], '/' );
if (basename) //if we found a / lets skip past it
basename++;
else //argv[0] will not always contain the full path, but rather just the script name
basename = argv[0];
const char *nph_prefix = "nph-";
if ( basename && !strncmp( basename, nph_prefix, strlen(nph_prefix) ) ) {
nph = true;
}
hwcaps_detect();
zmLoadConfig();
zmSetDefaultTermHandler();
zmSetDefaultDieHandler();
logInit( "zms" );
hwcaps_detect();
const char *query = getenv( "QUERY_STRING" );
if ( query ) {
Debug( 1, "Query: %s", query );
char temp_query[1024];
strncpy( temp_query, query, sizeof(temp_query) );
char *q_ptr = temp_query;
char *parms[16]; // Shouldn't be more than this
int parm_no = 0;
while( (parm_no < 16) && (parms[parm_no] = strtok( q_ptr, "&" )) ) {
parm_no++;
q_ptr = NULL;
}
for ( int p = 0; p < parm_no; p++ ) {
char *name = strtok( parms[p], "=" );
char *value = strtok( NULL, "=" );
zmSetDefaultTermHandler();
zmSetDefaultDieHandler();
const char *query = getenv( "QUERY_STRING" );
if ( query ) {
Debug( 1, "Query: %s", query );
char temp_query[1024];
strncpy( temp_query, query, sizeof(temp_query) );
char *q_ptr = temp_query;
char *parms[16]; // Shouldn't be more than this
int parm_no = 0;
while( (parm_no < 16) && (parms[parm_no] = strtok( q_ptr, "&" )) ) {
parm_no++;
q_ptr = NULL;
}
for ( int p = 0; p < parm_no; p++ ) {
char *name = strtok( parms[p], "=" );
char *value = strtok( NULL, "=" );
if ( !value )
value = (char *)"";
if ( !strcmp( name, "source" ) ) {
source = !strcmp( value, "event" )?ZMS_EVENT:ZMS_MONITOR;
} else if ( !strcmp( name, "mode" ) ) {
mode = !strcmp( value, "jpeg" )?ZMS_JPEG:ZMS_MPEG;
mode = !strcmp( value, "raw" )?ZMS_RAW:mode;
mode = !strcmp( value, "zip" )?ZMS_ZIP:mode;
mode = !strcmp( value, "single" )?ZMS_SINGLE:mode;
} else if ( !strcmp( name, "format" ) )
strncpy( format, value, sizeof(format) );
else if ( !strcmp( name, "monitor" ) )
monitor_id = atoi( value );
else if ( !strcmp( name, "time" ) )
event_time = atoi( value );
else if ( !strcmp( name, "event" ) )
event_id = strtoull( value, (char **)NULL, 10 );
else if ( !strcmp( name, "frame" ) )
frame_id = strtoull( value, (char **)NULL, 10 );
else if ( !strcmp( name, "scale" ) )
scale = atoi( value );
else if ( !strcmp( name, "rate" ) )
rate = atoi( value );
else if ( !strcmp( name, "maxfps" ) )
maxfps = atof( value );
else if ( !strcmp( name, "bitrate" ) )
bitrate = atoi( value );
else if ( !strcmp( name, "ttl" ) )
ttl = atoi(value);
else if ( !strcmp( name, "replay" ) ) {
replay = !strcmp( value, "gapless" )?EventStream::MODE_ALL_GAPLESS:EventStream::MODE_SINGLE;
replay = !strcmp( value, "all" )?EventStream::MODE_ALL:replay;
} else if ( !strcmp( name, "connkey" ) )
connkey = atoi(value);
else if ( !strcmp( name, "buffer" ) )
playback_buffer = atoi(value);
else if ( config.opt_use_auth ) {
if ( strcmp( config.auth_relay, "none" ) == 0 ) {
if ( !strcmp( name, "user" ) ) {
username = UriDecode( value );
}
} else {
//if ( strcmp( config.auth_relay, "hashed" ) == 0 )
{
if ( !strcmp( name, "auth" ) ) {
strncpy( auth, value, sizeof(auth) );
}
}
//else if ( strcmp( config.auth_relay, "plain" ) == 0 )
{
if ( !strcmp( name, "user" ) ) {
if ( !strcmp( name, "source" ) ) {
source = !strcmp( value, "event" )?ZMS_EVENT:ZMS_MONITOR;
} else if ( !strcmp( name, "mode" ) ) {
mode = !strcmp( value, "jpeg" )?ZMS_JPEG:ZMS_MPEG;
mode = !strcmp( value, "raw" )?ZMS_RAW:mode;
mode = !strcmp( value, "zip" )?ZMS_ZIP:mode;
mode = !strcmp( value, "single" )?ZMS_SINGLE:mode;
} else if ( !strcmp( name, "format" ) ) {
strncpy( format, value, sizeof(format) );
} else if ( !strcmp( name, "monitor" ) ) {
monitor_id = atoi( value );
} else if ( !strcmp( name, "time" ) ) {
event_time = atoi( value );
} else if ( !strcmp( name, "event" ) ) {
event_id = strtoull( value, (char **)NULL, 10 );
} else if ( !strcmp( name, "frame" ) ) {
frame_id = strtoull( value, (char **)NULL, 10 );
} else if ( !strcmp( name, "scale" ) ) {
scale = atoi( value );
} else if ( !strcmp( name, "rate" ) ) {
rate = atoi( value );
} else if ( !strcmp( name, "maxfps" ) ) {
maxfps = atof( value );
} else if ( !strcmp( name, "bitrate" ) ) {
bitrate = atoi( value );
} else if ( !strcmp( name, "ttl" ) ) {
ttl = atoi(value);
} else if ( !strcmp( name, "replay" ) ) {
replay = !strcmp( value, "gapless" )?EventStream::MODE_ALL_GAPLESS:EventStream::MODE_SINGLE;
replay = !strcmp( value, "all" )?EventStream::MODE_ALL:replay;
} else if ( !strcmp( name, "connkey" ) ) {
connkey = atoi(value);
} else if ( !strcmp( name, "buffer" ) ) {
playback_buffer = atoi(value);
} else if ( config.opt_use_auth ) {
if ( strcmp( config.auth_relay, "none" ) == 0 ) {
if ( !strcmp( name, "user" ) ) {
username = value;
}
} else {
//if ( strcmp( config.auth_relay, "hashed" ) == 0 )
{
if ( !strcmp( name, "auth" ) ) {
strncpy( auth, value, sizeof(auth) );
}
}
//else if ( strcmp( config.auth_relay, "plain" ) == 0 )
{
if ( !strcmp( name, "user" ) ) {
username = UriDecode( value );
Debug( 1, "Have %s for username", username.c_str() );
}
if ( !strcmp( name, "pass" ) ) {
}
if ( !strcmp( name, "pass" ) ) {
password = UriDecode( value );
Debug( 1, "Have %s for password", password.c_str() );
}
}
}
}
} // end foreach parm
} // end if query
}
}
}
}
} // end foreach parm
} // end if query
if ( config.opt_use_auth ) {
User *user = 0;
if ( config.opt_use_auth ) {
User *user = 0;
if ( strcmp( config.auth_relay, "none" ) == 0 ) {
if ( username.length() ) {
user = zmLoadUser( username.c_str() );
}
} else {
//if ( strcmp( config.auth_relay, "hashed" ) == 0 )
{
if ( *auth ) {
user = zmLoadAuthUser( auth, config.auth_hash_ips );
} else {
Debug( 1, "Need both username and password" );
}
}
//else if ( strcmp( config.auth_relay, "plain" ) == 0 )
{
if ( username.length() && password.length() ) {
user = zmLoadUser( username.c_str(), password.c_str() );
} else {
Debug( 1, "Need both username and password" );
}
}
} // auth is none or something else
if ( !user ) {
Error( "Unable to authenticate user" );
logTerm();
zmDbClose();
return( -1 );
}
ValidateAccess( user, monitor_id );
} // end if use_auth
if ( strcmp( config.auth_relay, "none" ) == 0 ) {
if ( username.length() ) {
user = zmLoadUser( username.c_str() );
}
} else {
//if ( strcmp( config.auth_relay, "hashed" ) == 0 )
{
if ( *auth ) {
user = zmLoadAuthUser( auth, config.auth_hash_ips );
}
}
//else if ( strcmp( config.auth_relay, "plain" ) == 0 )
{
if ( username.length() && password.length() ) {
user = zmLoadUser( username.c_str(), password.c_str() );
}
}
}
if ( !user ) {
Error( "Unable to authenticate user" );
logTerm();
zmDbClose();
return( -1 );
}
ValidateAccess( user, monitor_id );
}
setbuf( stdout, 0 );
if ( nph ) {
fprintf( stdout, "HTTP/1.0 200 OK\r\n" );
}
fprintf( stdout, "Server: ZoneMinder Video Server/%s\r\n", ZM_VERSION );
time_t now = time( 0 );
char date_string[64];
strftime( date_string, sizeof(date_string)-1, "%a, %d %b %Y %H:%M:%S GMT", gmtime( &now ) );
setbuf( stdout, 0 );
if ( nph ) {
fprintf( stdout, "HTTP/1.0 200 OK\r\n" );
}
fprintf( stdout, "Server: ZoneMinder Video Server/%s\r\n", ZM_VERSION );
time_t now = time( 0 );
char date_string[64];
strftime( date_string, sizeof(date_string)-1, "%a, %d %b %Y %H:%M:%S GMT", gmtime( &now ) );
fprintf( stdout, "Expires: Mon, 26 Jul 1997 05:00:00 GMT\r\n" );
fprintf( stdout, "Last-Modified: %s\r\n", date_string );
fprintf( stdout, "Cache-Control: no-store, no-cache, must-revalidate\r\n" );
fprintf( stdout, "Cache-Control: post-check=0, pre-check=0\r\n" );
fprintf( stdout, "Pragma: no-cache\r\n");
// Removed as causing more problems than it fixed.
//if ( !nph )
//{
//fprintf( stdout, "Content-Length: 0\r\n");
//}
fprintf( stdout, "Expires: Mon, 26 Jul 1997 05:00:00 GMT\r\n" );
fprintf( stdout, "Last-Modified: %s\r\n", date_string );
fprintf( stdout, "Cache-Control: no-store, no-cache, must-revalidate\r\n" );
fprintf( stdout, "Cache-Control: post-check=0, pre-check=0\r\n" );
fprintf( stdout, "Pragma: no-cache\r\n");
// Removed as causing more problems than it fixed.
//if ( !nph )
//{
//fprintf( stdout, "Content-Length: 0\r\n");
//}
if ( source == ZMS_MONITOR ) {
if ( source == ZMS_MONITOR ) {
MonitorStream stream;
stream.setStreamScale( scale );
stream.setStreamReplayRate( rate );
@ -248,9 +241,9 @@ int main( int argc, const char *argv[] ) {
stream.setStreamType( MonitorStream::STREAM_JPEG );
} else if ( mode == ZMS_RAW ) {
stream.setStreamType( MonitorStream::STREAM_RAW );
} else if ( mode == ZMS_ZIP ) {
} else if ( mode == ZMS_ZIP ) {
stream.setStreamType( MonitorStream::STREAM_ZIP );
} else if ( mode == ZMS_SINGLE ) {
} else if ( mode == ZMS_SINGLE ) {
stream.setStreamType( MonitorStream::STREAM_SINGLE );
} else {
#if HAVE_LIBAVCODEC

View File

@ -155,7 +155,7 @@ if ( $Event->DefaultVideo() ) {
?>
<div id="videoFeed">
<video id="videoobj" class="video-js vjs-default-skin" width="<?php echo reScale( $Event->Width(), $scale ) ?>" height="<?php echo reScale( $Event->Height(), $scale ) ?>" data-setup='{ "controls": true, "playbackRates": [0.5, 1, 1.5, 2, 4, 8, 16, 32, 64, 128, 256], "autoplay": true, "preload": "auto", "plugins": { "zoomrotate": { "zoom": "<?php echo $Zoom ?>"}}}'>
<source src="<?php echo $Event->getStreamSrc( array( "mode=mpeg&format=h264" ) ); ?>" type="video/mp4">
<source src="<?php echo $Event->getStreamSrc( array( 'mode'=>'mpeg','format'=>'h264' ) ); ?>" type="video/mp4">
Your browser does not support the video tag.
</video>
</div>

View File

@ -214,7 +214,7 @@ foreach ( $events as $event ) {
<td class="colThumbnail">
<?php
$imgSrc = '?view=image&amp;eid='.$event->Id().'&amp;fid='.$thumbData['FrameId'].'&amp;width='.$thumbData['Width'].'&amp;height='.$thumbData['Height'];
$streamSrc = getStreamSrc( array( "source=event", "mode=jpeg", "event=".$event->Id(), "scale=".$scale, "maxfps=".ZM_WEB_VIDEO_MAXFPS, "replay=single") );
$streamSrc = $Event->getStreamSrc( array( 'mode'=>'jpeg', 'scale'=>$scale, 'maxfps'=>ZM_WEB_VIDEO_MAXFPS, 'replay'=>'single') );
$imgHtml = '<img id="thumbnail'.$event->id().'" src="'.$imgSrc.'" alt="'. validHtmlStr('Event '.$event->Id()) .'" style="width:'. validInt($thumbData['Width']) .'px;height:'. validInt( $thumbData['Height'] ).'px;" onmouseover="this.src=\''.$streamSrc.'\';" onmouseout="this.src=\''.$imgSrc.'\';"/>';

View File

@ -0,0 +1,720 @@
function evaluateLoadTimes() {
// Only consider it a completed event if we load ALL monitors, then zero all and start again
var start=0;
var end=0;
if ( liveMode != 1 && currentSpeed == 0 ) return; // don't evaluate when we are not moving as we can do nothing really fast.
for ( var i = 0; i < monitorIndex.length; i++ ) {
if ( monitorName[i] > "" ) {
if ( monitorLoadEndTimems[i] ==0 ) return; // if we have a monitor with no time yet just wait
if ( start == 0 || start > monitorLoadStartTimems[i] ) start = monitorLoadStartTimems[i];
if ( end == 0 || end < monitorLoadEndTimems[i] ) end = monitorLoadEndTimems[i];
}
}
if ( start == 0 || end == 0 ) return; // we really should not get here
for ( var i=0; i < numMonitors; i++ ) {
var monId = monitorPtr[i];
monitorLoadStartTimems[monId] = 0;
monitorLoadEndTimems[monId] = 0;
}
freeTimeLastIntervals[imageLoadTimesEvaluated++] = 1 - ((end - start)/currentDisplayInterval);
if( imageLoadTimesEvaluated < imageLoadTimesNeeded ) return;
var avgFrac=0;
for ( var i=0; i < imageLoadTimesEvaluated; i++ )
avgFrac += freeTimeLastIntervals[i];
avgFrac = avgFrac / imageLoadTimesEvaluated;
// The larger this is(positive) the faster we can go
if (avgFrac >= 0.9) currentDisplayInterval = (currentDisplayInterval * 0.50).toFixed(1); // we can go much faster
else if (avgFrac >= 0.8) currentDisplayInterval = (currentDisplayInterval * 0.55).toFixed(1);
else if (avgFrac >= 0.7) currentDisplayInterval = (currentDisplayInterval * 0.60).toFixed(1);
else if (avgFrac >= 0.6) currentDisplayInterval = (currentDisplayInterval * 0.65).toFixed(1);
else if (avgFrac >= 0.5) currentDisplayInterval = (currentDisplayInterval * 0.70).toFixed(1);
else if (avgFrac >= 0.4) currentDisplayInterval = (currentDisplayInterval * 0.80).toFixed(1);
else if (avgFrac >= 0.35) currentDisplayInterval = (currentDisplayInterval * 0.90).toFixed(1);
else if (avgFrac >= 0.3) currentDisplayInterval = (currentDisplayInterval * 1.00).toFixed(1);
else if (avgFrac >= 0.25) currentDisplayInterval = (currentDisplayInterval * 1.20).toFixed(1);
else if (avgFrac >= 0.2) currentDisplayInterval = (currentDisplayInterval * 1.50).toFixed(1);
else if (avgFrac >= 0.1) currentDisplayInterval = (currentDisplayInterval * 2.00).toFixed(1);
else currentDisplayInterval = (currentDisplayInterval * 2.50).toFixed(1);
currentDisplayInterval=Math.min(Math.max(currentDisplayInterval, 30),10000); // limit this from about 30fps to .1 fps
imageLoadTimesEvaluated=0;
setSpeed(speedIndex);
$('fps').innerHTML="Display refresh rate is " + (1000 / currentDisplayInterval).toFixed(1) + " per second, avgFrac=" + avgFrac.toFixed(3) + ".";
}
function SetImageSource( monId, val ) {
if ( liveMode == 1 ) {
return monitorImageObject[monId].src.replace(/rand=\d+/i, 'rand='+Math.floor((Math.random() * 1000000) ));
} else {
for ( var i=0, eIdlength = eId.length; i < eIdlength; i++ ) {
// Search for a match
if ( eMonId[i] == monId && val >= eStartSecs[i] && val <= eEndSecs[i] ) {
var frame = parseInt((val - eStartSecs[i])/(eEndSecs[i]-eStartSecs[i])*eventFrames[i])+1;
return "index.php?view=image&eid=" + eId[i] + '&fid='+frame + "&width=" + monitorCanvasObj[monId].width + "&height=" + monitorCanvasObj[monId].height;
}
} // end for
return "no data";
}
}
// callback when loading an image. Will load itself to the canvas, or draw no data
function imagedone( obj, monId, success ) {
if ( success ) {
var canvasCtx = monitorCanvasCtx[monId];
var canvasObj = monitorCanvasObj[monId];
canvasCtx.drawImage( monitorImageObject[monId], 0, 0, canvasObj.width, canvasObj.height );
var iconSize=(Math.max(canvasObj.width, canvasObj.height) * 0.10);
canvasCtx.font = "600 " + iconSize.toString() + "px Arial";
canvasCtx.fillStyle = "white";
canvasCtx.globalCompositeOperation = "difference";
canvasCtx.fillText( "+", iconSize*0.2, iconSize*1.2 );
canvasCtx.fillText( "-", canvasObj.width - iconSize*1.2, iconSize*1.2 );
canvasCtx.globalCompositeOperation = "source-over";
monitorLoadEndTimems[monId] = new Date().getTime(); // elapsed time to load
evaluateLoadTimes();
}
monitorLoading[monId] = false;
if ( ! success ) {
// if we had a failrue queue up the no-data image
//loadImage2Monitor(monId,"no data"); // leave the staged URL if there is one, just ignore it here.
loadNoData( monId );
} else {
if ( monitorLoadingStageURL[monId] == "" ) {
console.log("Not showing image for " + monId );
// This means that there wasn't a loading image placeholder.
// So we weren't actually loading an image... which seems weird.
return;
}
//loadImage2Monitor(monId,monitorLoadingStageURL[monId] );
//monitorLoadingStageURL[monId]="";
}
return;
}
function loadNoData( monId ) {
if ( monId ) {
var canvasCtx = monitorCanvasCtx[monId];
var canvasObj = monitorCanvasObj[monId];
canvasCtx.fillStyle="white";
canvasCtx.fillRect(0, 0, canvasObj.width, canvasObj.height);
var textSize=canvasObj.width * 0.15;
var text="No Data";
canvasCtx.font = "600 " + textSize.toString() + "px Arial";
canvasCtx.fillStyle="black";
var textWidth = canvasCtx.measureText(text).width;
canvasCtx.fillText(text,canvasObj.width/2 - textWidth/2,canvasObj.height/2);
} else {
console.log("No monId in loadNoData");
}
}
// Either draws the
function loadImage2Monitor( monId, url ) {
if ( monitorLoading[monId] && monitorImageObject[monId].src != url ) {
// never queue the same image twice (if it's loading it has to be defined, right?
monitorLoadingStageURL[monId] = url; // we don't care if we are overriting, it means it didn't change fast enough
} else {
if ( monitorImageObject[monId].src == url ) return; // do nothing if it's the same
if ( url == 'no data' ) {
loadNoData( monId );
} else {
monitorLoading[monId] = true;
monitorLoadStartTimems[monId] = new Date().getTime();
monitorImageObject[monId].src = url; // starts a load but doesn't refresh yet, wait until ready
}
}
}
function timerFire() {
// See if we need to reschedule
if(currentDisplayInterval != timerInterval || currentSpeed == 0) {
// zero just turn off interrupts
clearInterval(timerObj);
timerInterval=currentDisplayInterval;
if(currentSpeed>0 || liveMode!=0) timerObj=setInterval(timerFire,timerInterval); // don't fire out of live mode if speed is zero
}
if (liveMode) outputUpdate(currentTimeSecs); // In live mode we basically do nothing but redisplay
else if (currentTimeSecs + playSecsperInterval >= maxTimeSecs) // beyond the end just stop
{
setSpeed(0);
outputUpdate(currentTimeSecs);
}
else outputUpdate(currentTimeSecs + playSecsperInterval);
return;
}
function drawSliderOnGraph(val) {
var sliderWidth=10;
var sliderLineWidth=1;
var sliderHeight=cHeight;
if(liveMode==1) {
val=Math.floor( Date.now() / 1000);
}
// Set some sizes
var labelpx = Math.max( 6, Math.min( 20, parseInt(cHeight * timeLabelsFractOfRow / (numMonitors+1)) ) );
var labbottom=parseInt(cHeight * 0.2 / (numMonitors+1)).toString() + "px"; // This is positioning same as row labels below, but from bottom so 1-position
var labfont=labelpx + "px Georgia"; // set this like below row labels
if(numMonitors>0) {
// if we have no data to display don't do the slider itself
var sliderX=parseInt( (val - minTimeSecs) / rangeTimeSecs * cWidth - sliderWidth/2); // position left side of slider
if(sliderX < 0) sliderX=0;
if(sliderX+sliderWidth > cWidth) sliderX=cWidth-sliderWidth-1;
// If we have data already saved first restore it from LAST time
if(typeof underSlider !== 'undefined')
{
ctx.putImageData(underSlider,underSliderX, 0, 0, 0, sliderWidth, sliderHeight);
underSlider=undefined;
}
if(liveMode==0) // we get rid of the slider if we switch to live (since it may not be in the "right" place)
{
// Now save where we are putting it THIS time
underSlider=ctx.getImageData(sliderX, 0, sliderWidth, sliderHeight);
// And add in the slider'
ctx.lineWidth=sliderLineWidth;
ctx.strokeStyle='black';
// looks like strokes are on the outside (or could be) so shrink it by the line width so we replace all the pixels
ctx.strokeRect(sliderX+sliderLineWidth,sliderLineWidth,sliderWidth - 2*sliderLineWidth, sliderHeight - 2*sliderLineWidth);
underSliderX=sliderX;
}
var o = $('scruboutput');
if(liveMode==1)
{
o.innerHTML="Live Feed @ " + (1000 / currentDisplayInterval).toFixed(1) + " fps";
o.style.color="red";
}
else
{
o.innerHTML=secs2dbstr(val);
o.style.color="blue";
}
o.style.position="absolute";
o.style.bottom=labbottom;
o.style.font=labfont;
// try to get length and then when we get too close to the right switch to the left
var len = o.offsetWidth;
var x;
if(sliderX > cWidth/2)
x=sliderX - len - 10;
else
x=sliderX + 10;
o.style.left=x.toString() + "px";
}
// This displays (or not) the left/right limits depending on how close the slider is.
// Because these change widths if the slider is too close, use the slider width as an estimate for the left/right label length (i.e. don't recalculate len from above)
// If this starts to collide increase some of the extra space
var o = $('scrubleft');
o.innerHTML=secs2dbstr(minTimeSecs);
o.style.position="absolute";
o.style.bottom=labbottom;
o.style.font=labfont;
o.style.left="5px";
if(numMonitors==0) // we need a len calculation if we skipped the slider
len = o.offsetWidth;
// If the slider will overlay part of this suppress (this is the left side)
if(len + 10 > sliderX || cWidth < len * 4 ) // that last check is for very narrow browsers
o.style.display="none";
else
{
o.style.display="inline";
o.style.display="inline-flex"; // safari won't take this but will just ignore
}
var o = $('scrubright');
o.innerHTML=secs2dbstr(maxTimeSecs);
o.style.position="absolute";
o.style.bottom=labbottom;
o.style.font=labfont;
// If the slider will overlay part of this suppress (this is the right side)
o.style.left=(cWidth - len - 15).toString() + "px";
if(sliderX > cWidth - len - 20 || cWidth < len * 4 )
o.style.display="none";
else
{
o.style.display="inline";
o.style.display="inline-flex";
}
}
function drawGraph()
{
var divWidth=$('timelinediv').clientWidth
canvas.width = cWidth = divWidth; // Let it float and determine width (it should be sized a bit smaller percentage of window)
canvas.height=cHeight = parseInt(window.innerHeight * 0.10);
if(eId.length==0)
{
ctx.font="40px Georgia";
ctx.fillStyle="Black";
ctx.globalAlpha=1;
var t="No data found in range - choose differently";
var l=ctx.measureText(t).width;
ctx.fillText(t,(cWidth - l)/2, cHeight-10);
underSlider=undefined;
return;
}
var rowHeight=parseInt(cHeight / (numMonitors + 1) ); // Leave room for a scale of some sort
// first fill in the bars for the events (not alarms)
for(var i=0; i<eId.length; i++) // Display all we loaded
{
var x1=parseInt( (eStartSecs[i] - minTimeSecs) / rangeTimeSecs * cWidth) ; // round low end down
var x2=parseInt( (eEndSecs[i] - minTimeSecs) / rangeTimeSecs * cWidth + 0.5 ) ; // round high end up to be sure consecutive ones connect
ctx.fillStyle=monitorColour[eMonId[i]];
ctx.globalAlpha = 0.2; // light color for background
ctx.clearRect(x1,monitorIndex[eMonId[i]]*rowHeight,x2-x1,rowHeight); // Erase any overlap so it doesn't look artificially darker
ctx.fillRect (x1,monitorIndex[eMonId[i]]*rowHeight,x2-x1,rowHeight);
}
for(var i=0; (i<fScore.length) && (maxScore>0); i++) // Now put in scored frames (if any)
{
var x1=parseInt( (fTimeFromSecs[i] - minTimeSecs) / rangeTimeSecs * cWidth) ; // round low end down
var x2=parseInt( (fTimeToSecs[i] - minTimeSecs) / rangeTimeSecs * cWidth + 0.5 ) ; // round up
if(x2-x1 < 2) x2=x1+2; // So it is visible make them all at least this number of seconds wide
ctx.fillStyle=monitorColour[fMonId[i]];
ctx.globalAlpha = 0.4 + 0.6 * (1 - fScore[i]/maxScore); // Background is scaled but even lowest is twice as dark as the background
ctx.fillRect(x1,monitorIndex[fMonId[i]]*rowHeight,x2-x1,rowHeight);
}
for(var i=0; i<numMonitors; i++) // Note that this may be a sparse array
{
ctx.font= parseInt(rowHeight * timeLabelsFractOfRow).toString() + "px Georgia";
ctx.fillStyle="Black";
ctx.globalAlpha=1;
ctx.fillText(monitorName[monitorPtr[i]], 0, (i + 1 - (1 - timeLabelsFractOfRow)/2 ) * rowHeight ); // This should roughly center font in row
}
underSlider=undefined; // flag we don't have a slider cached
drawSliderOnGraph(currentTimeSecs);
return;
}
function redrawScreen()
{
if(fitMode==0) // if we fit, then monitors were absolutely positioned already (or will be) otherwise release them to float
{
for(var i=0; i<numMonitors; i++)
monitorCanvasObj[monitorPtr[i]].style.position="";
$('monitors').setStyle('height',"auto");
}
if(liveMode==1) // if we are not in live view switch to history -- this has to come before fit in case we re-establish the timeline
{
$('SpeedDiv').style.display="none";
$('timelinediv').style.display="none";
$('live').innerHTML="History";
$('zoomin').style.display="none";
$('zoomout').style.display="none";
$('panleft').style.display="none";
$('panright').style.display="none";
}
else // switch out of liveview mode
{
$('SpeedDiv').style.display="inline";
$('SpeedDiv').style.display="inline-flex";
$('timelinediv').style.display=null;
$('live').innerHTML="Live";
$('zoomin').style.display="inline";
$('zoomin').style.display="inline-flex";
$('zoomout').style.display="inline";
$('zoomout').style.display="inline-flex";
$('panleft').style.display="inline";
$('panleft').style.display="inline-flex";
$('panright').style.display="inline";
$('panright').style.display="inline-flex";
}
if(fitMode==1)
{
$('ScaleDiv').style.display="none";
$('fit').innerHTML="Scale";
var vh=window.innerHeight;
var vw=window.innerWidth;
var pos=$('monitors').getPosition();
var mh=(vh - pos.y - $('fps').getSize().y);
$('monitors').setStyle('height',mh.toString() + "px"); // leave a small gap at bottom
if(maxfit2($('monitors').getSize().x,$('monitors').getSize().y) == 0) /// if we fail to fix we back out of fit mode -- ??? This may need some better handling
fitMode=1-fitMode;
}
else // switch out of fit mode
{
$('ScaleDiv').style.display="inline";
$('ScaleDiv').style.display="inline-flex";
$('fit').innerHTML="Fit";
setScale(currentScale);
}
drawGraph();
outputUpdate(currentTimeSecs);
timerFire(); // force a fire in case it's not timing
}
function outputUpdate(val)
{
drawSliderOnGraph(val);
for(var i=0; i<numMonitors; i++)
{
loadImage2Monitor(monitorPtr[i],SetImageSource(monitorPtr[i],val));
}
var currentTimeMS = new Date(val*1000);
currentTimeSecs=val;
}
/// Found this here: http://stackoverflow.com/questions/55677/how-do-i-get-the-coordinates-of-a-mouse-click-on-a-canvas-element
function relMouseCoords(event){
var totalOffsetX = 0;
var totalOffsetY = 0;
var canvasX = 0;
var canvasY = 0;
var currentElement = this;
do{
totalOffsetX += currentElement.offsetLeft - currentElement.scrollLeft;
totalOffsetY += currentElement.offsetTop - currentElement.scrollTop;
}
while(currentElement = currentElement.offsetParent)
canvasX = event.pageX - totalOffsetX;
canvasY = event.pageY - totalOffsetY;
return {x:canvasX, y:canvasY}
}
HTMLCanvasElement.prototype.relMouseCoords = relMouseCoords;
// These are the functions for mouse movement in the timeline. Note that touch is treated as a mouse move with mouse down
var mouseisdown=false;
function mdown(event) {mouseisdown=true; mmove(event);}
function mup(event) {mouseisdown=false;}
function mout(event) {mouseisdown=false;} // if we go outside treat it as release
function tmove(event) {mouseisdown=true; mmove(event);}
function mmove(event) {
if(mouseisdown) {
// only do anything if the mouse is depressed while on the sheet
var sec = minTimeSecs + rangeTimeSecs / event.target.width * event.target.relMouseCoords(event).x;
outputUpdate(sec);
}
}
function secs2dbstr (s)
{
var st = (new Date(s * 1000)).format("%Y-%m-%d %H:%M:%S");
return st;
}
function setFit(value)
{
fitMode=value;
redrawScreen();
}
function showScale(newscale) // updates slider only
{
$('scaleslideroutput').innerHTML = parseFloat(newscale).toFixed(2).toString() + " x";
return;
}
function setScale(newscale) // makes actual change
{
showScale(newscale);
for(var i=0; i<numMonitors; i++)
{
monitorCanvasObj[monitorPtr[i]].width=monitorWidth[monitorPtr[i]]*monitorNormalizeScale[monitorPtr[i]]*monitorZoomScale[monitorPtr[i]]*newscale;
monitorCanvasObj[monitorPtr[i]].height=monitorHeight[monitorPtr[i]]*monitorNormalizeScale[monitorPtr[i]]*monitorZoomScale[monitorPtr[i]]*newscale;
}
currentScale=newscale;
}
function showSpeed(val) // updates slider only
{
$('speedslideroutput').innerHTML = parseFloat(speeds[val]).toFixed(2).toString() + " x";
}
function setSpeed(val) // Note parameter is the index not the speed
{
var t;
if(liveMode==1) return; // we shouldn't actually get here but just in case
currentSpeed=parseFloat(speeds[val]);
speedIndex=val;
playSecsperInterval = currentSpeed * currentDisplayInterval / 1000;
showSpeed(val);
if( timerInterval != currentDisplayInterval || currentSpeed == 0 ) timerFire(); // if the timer isn't firing we need to trigger it to update
}
function setLive(value)
{
liveMode=value;
redrawScreen();
}
//vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
// The section below are to reload this program with new parameters
function clicknav(minSecs,maxSecs,arch,live) {// we use the current time if we can
var now = new Date() / 1000;
var minStr="";
var maxStr="";
var currentStr="";
if ( minSecs > 0 ) {
if(maxSecs > now)
maxSecs = parseInt(now);
maxStr="&maxTime=" + secs2dbstr(maxSecs);
}
if ( maxSecs > 0 )
minStr="&minTime=" + secs2dbstr(minSecs);
if ( maxSecs == 0 && minSecs == 0 ) {
minStr="&minTime=01/01/1950 12:00:00";
maxStr="&maxTime=12/31/2035 12:00:00";
}
var intervalStr="&displayinterval=" + currentDisplayInterval.toString();
if ( minSecs && maxSecs ) {
if ( currentTimeSecs > minSecs && currentTimeSecs < maxSecs ) // make sure time is in the new range
currentStr="&current=" + secs2dbstr(currentTimeSecs);
}
var liveStr="&live=0";
if ( live == 1 )
liveStr="&live=1";
var fitStr="&fit=0";
if ( fitMode == 1 )
fitStr="&fit=1";
var zoomStr="";
for ( var i=0; i < numMonitors; i++ )
if ( monitorZoomScale[monitorPtr[i]] < 0.99 || monitorZoomScale[monitorPtr[i]] > 1.01 ) // allow for some up/down changes and just treat as 1 of almost 1
zoomStr += "&z" + monitorPtr[i].toString() + "=" + monitorZoomScale[monitorPtr[i]].toFixed(2);
var uri = "?view=" + currentView + fitStr + groupStr + minStr + maxStr + currentStr + intervalStr + liveStr + zoomStr + "&scale=" + document.getElementById("scaleslider").value + "&speed=" + speeds[$j("#speedslider").value];
window.location = uri;
}
function lastHour() {
var now = new Date() / 1000;
clicknav(now - 3600 + 1, now,1,0);
}
function lastEight() {
var now = new Date() / 1000;
clicknav(now - 3600*8 + 1, now,1,0);
}
function zoomin() {
rangeTimeSecs = parseInt(rangeTimeSecs / 2);
minTimeSecs = parseInt(currentTimeSecs - rangeTimeSecs/2); // this is the slider current time, we center on that
maxTimeSecs = parseInt(currentTimeSecs + rangeTimeSecs/2);
clicknav(minTimeSecs,maxTimeSecs,1,0);
}
function zoomout() {
rangeTimeSecs = parseInt(rangeTimeSecs * 2);
minTimeSecs = parseInt(currentTimeSecs - rangeTimeSecs/2); // this is the slider current time, we center on that
maxTimeSecs = parseInt(currentTimeSecs + rangeTimeSecs/2);
clicknav(minTimeSecs,maxTimeSecs,1,0);
}
function panleft() {
minTimeSecs = parseInt(minTimeSecs - rangeTimeSecs/2);
maxTimeSecs = minTimeSecs + rangeTimeSecs - 1;
clicknav(minTimeSecs,maxTimeSecs,1,0);
}
function panright() {
minTimeSecs = parseInt(minTimeSecs + rangeTimeSecs/2);
maxTimeSecs = minTimeSecs + rangeTimeSecs - 1;
clicknav(minTimeSecs,maxTimeSecs,1,0);
}
function allof() {
clicknav(0,0,1,0);
}
function allnon() {
clicknav(0,0,0,0);
}
/// >>>>>>>>>>>>>>>>> handles packing different size/aspect monitors on screen <<<<<<<<<<<<<<<<<<<<<<<<
function compSize(a, b) { // sort array by some size parameter - height seems to work best. A semi-greedy algorithm
var a_value = monitorHeight[a] * monitorWidth[a] * monitorNormalizeScale[a] * monitorZoomScale[a] * monitorNormalizeScale[a] * monitorZoomScale[a];
var b_value = monitorHeight[b] * monitorWidth[b] * monitorNormalizeScale[b] * monitorZoomScale[b] * monitorNormalizeScale[b] * monitorZoomScale[b];
if ( a_value > b_value ) return -1;
else if ( a_value == b_value ) return 0;
else return 1;
}
function maxfit2(divW, divH) {
var bestFitX=[]; // how we arranged the so-far best match
var bestFitX2=[];
var bestFitY=[];
var bestFitY2=[];
var bestFitScale;
var minScale=0.05;
var maxScale=5.00;
var bestFitArea=0;
var borders=-1;
monitorPtr.sort(compSize);
while(1) {
if( maxScale - minScale < 0.01 ) break;
var thisScale = (maxScale + minScale) / 2;
var allFit=1;
var thisArea=0;
var thisX=[]; // top left
var thisY=[];
var thisX2=[]; // bottom right
var thisY2=[];
for ( var m = 0; m < numMonitors; m++ ) {
// this loop places each monitor (if it can)
var monId = monitorPtr[m];
function doesItFit(x,y,w,h,d) { // does block (w,h) fit at position (x,y) relative to edge and other nodes already done (0..d)
if(x+w>=divW) return 0;
if(y+h>=divH) return 0;
for(var i=0; i<=d; i++)
if( !( thisX[i]>x+w-1 || thisX2[i] < x || thisY[i] > y+h-1 || thisY2[i] < y ) ) return 0;
return 1; // it's OK
}
if ( borders <= 0 )
borders=$("Monitor"+monId).getStyle("border").toInt() * 2; // assume fixed size border, and added to both sides and top/bottom
// try fitting over first, then down. Each new one must land at either upper right or lower left corner of last (try in that order)
// Pick the one with the smallest Y, then smallest X if Y equal
var fitX = 999999999;
var fitY = 999999999;
for ( adjacent = 0; adjacent < m; adjacent ++ ) {
// try top right of adjacent
if ( doesItFit(thisX2[adjacent]+1, thisY[adjacent], monitorWidth[monId] * thisScale * monitorNormalizeScale[monId] * monitorZoomScale[monId] + borders, monitorHeight[monId] * thisScale * monitorNormalizeScale[monId] * monitorZoomScale[monId] + borders, m-1) == 1 ) {
if ( thisY[adjacent]<fitY || ( thisY[adjacent] == fitY && thisX2[adjacent]+1 < fitX ) ) {
fitX = thisX2[adjacent] + 1;
fitY = thisY[adjacent];
}
}
// try bottom left
if ( doesItFit(thisX[adjacent], thisY2[adjacent]+1, monitorWidth[monId] * thisScale * monitorNormalizeScale[monId] * monitorZoomScale[monId] + borders, monitorHeight[monId] * thisScale * monitorNormalizeScale[monId] * monitorZoomScale[monId] + borders, m-1) == 1 ) {
if ( thisY2[adjacent]+1 < fitY || ( thisY2[adjacent]+1 == fitY && thisX[adjacent] < fitX ) ) {
fitX = thisX[adjacent];
fitY = thisY2[adjacent] + 1;
}
}
}
if ( m == 0 ) { // note for the very first one there were no adjacents so the above loop didn't run
if ( doesItFit(0,0,monitorWidth[monId] * thisScale * monitorNormalizeScale[monId] * monitorZoomScale[monId] + borders, monitorHeight[monId] * thisScale * monitorNormalizeScale[monId] * monitorZoomScale[monId] + borders, -1) == 1 ) {
fitX = 0;
fitY = 0;
}
}
if ( fitX == 999999999 ) {
allFit = 0;
break; // break out of monitor loop flagging we didn't fit
}
thisX[m] =fitX;
thisX2[m]=fitX + monitorWidth[monitorPtr[m]] * thisScale * monitorNormalizeScale[monitorPtr[m]] * monitorZoomScale[monitorPtr[m]] + borders;
thisY[m] =fitY;
thisY2[m]=fitY + monitorHeight[monitorPtr[m]] * thisScale * monitorNormalizeScale[monitorPtr[m]] * monitorZoomScale[monitorPtr[m]] + borders;
thisArea += (thisX2[m] - thisX[m])*(thisY2[m] - thisY[m]);
}
if ( allFit == 1 ) {
minScale=thisScale;
if(bestFitArea<thisArea) {
bestFitArea=thisArea;
bestFitX=thisX;
bestFitY=thisY;
bestFitX2=thisX2;
bestFitY2=thisY2;
bestFitScale=thisScale;
}
} else {
// didn't fit
maxScale=thisScale;
}
}
if ( bestFitArea > 0 ) { // only rearrange if we could fit -- otherwise just do nothing, let them start coming out, whatever
for ( m = 0; m < numMonitors; m++ ) {
c = $("Monitor" + monitorPtr[m]);
c.style.position="absolute";
c.style.left=bestFitX[m].toString() + "px";
c.style.top=bestFitY[m].toString() + "px";
c.width = bestFitX2[m] - bestFitX[m] + 1 - borders;
c.height= bestFitY2[m] - bestFitY[m] + 1 - borders;
}
return 1;
} else {
return 0;
}
}
// >>>>>>>>>>>>>>>> Handles individual monitor clicks and navigation to the standard event/watch display
function showOneMonitor(monId) {
// link out to the normal view of one event's data
// We know the monitor, need to determine the event based on current time
var url;
if ( liveMode != 0 )
url="?view=watch&mid=" + monId.toString();
else
for ( var i=0, len=eId.length; i<len; i++ ) {
if ( eMonId[i] == monId && currentTimeSecs >= eStartSecs[i] && currentTimeSecs <= eEndSecs[i] )
url="?view=event&eid=" + eId[i] + '&fid=' + parseInt(Math.max(1, Math.min(eventFrames[i], eventFrames[i] * (currentTimeSecs - eStartSecs[i]) / (eEndSecs[i] - eStartSecs[i] + 1) ) ));
break;
}
createPopup(url, 'zmEvent', 'event', monitorWidth[eMonId[i]], monitorHeight[eMonId[i]]);
}
function zoom(monId,scale) {
var lastZoomMonPriorScale = monitorZoomScale[monId];
monitorZoomScale[monId] *= scale;
if ( redrawScreen() == 0 ) {// failure here is probably because we zoomed too far
monitorZoomScale[monId] = lastZoomMonPriorScale;
alert("You can't zoom that far -- rolling back");
redrawScreen(); // put things back and hope it works
}
}
function clickMonitor(event,monId) {
var monitor_element = $("Monitor"+monId.toString());
var pos_x = event.offsetX ? (event.offsetX) : event.pageX - monitor_element.offsetLeft;
var pos_y = event.offsetY ? (event.offsetY) : event.pageY - monitor_element.offsetTop;
if ( pos_x < monitor_element.width/4 && pos_y < monitor_element.height/4 )
zoom(monId,1.15);
else if ( pos_x > monitor_element.width * 3/4 && pos_y < monitor_element.height/4 )
zoom(monId,1/1.15);
else
showOneMonitor(monId);
return;
}
// >>>>>>>>> Initialization that runs on window load by being at the bottom
function initPage() {
canvas = $("timeline");
ctx = canvas.getContext('2d');
for ( var i = 0, len = monitorPtr.length; i < len; i += 1 ) {
var monId = monitorPtr[i];
if ( ! monId ) continue;
monitorCanvasObj[monId] = $('Monitor'+monId );
if ( ! monitorCanvasObj[monId] ) {
alert("Couldn't find DOM element for Monitor"+monId + "monitorPtr.length="+len);
} else {
monitorCanvasCtx[monId] = monitorCanvasObj[monId].getContext('2d');
var imageObject = monitorImageObject[monId] = new Image();
imageObject.monId = monId;
imageObject.onload = function() {imagedone(this, this.monId, true )};
imageObject.onerror = function() {imagedone(this, this.monId, false )};
loadImage2Monitor( monId, monitorImageURL[monId] );
}
}
drawGraph();
setSpeed(speedIndex);
setFit(fitMode); // will redraw
setLive(liveMode); // will redraw
}
window.addEventListener("resize",redrawScreen);
// Kick everything off
window.addEvent( 'domready', initPage );

View File

@ -0,0 +1,194 @@
var currentScale=<?php echo $defaultScale?>;
var liveMode=<?php echo $initialModeIsLive?>;
console.log("Live mode?"+liveMode);
var fitMode=<?php echo $fitMode?>;
var currentSpeed=<?php echo $speeds[$speedIndex]?>; // slider scale, which is only for replay and relative to real time
var speedIndex=<?php echo $speedIndex?>;
var currentDisplayInterval=<?php echo $initialDisplayInterval?>; // will be set based on performance, this is the display interval in milliseconds for history, and fps for live, and dynamically determined (in ms)
var playSecsperInterval=1; // How many seconds of recorded image we play per refresh determined by speed (replay rate) and display interval; (default=1 if coming from live)
var timerInterval; // milliseconds between interrupts
var timerObj; // object to hold timer interval;
var freeTimeLastIntervals=[]; // Percentage of current interval used in loading most recent image
var imageLoadTimesEvaluated=0; // running count
var imageLoadTimesNeeded=15; // and how many we need
var timeLabelsFractOfRow = 0.9;
var eMonId = [];
var eId = [];
var eStartSecs = [];
var eEndSecs = [];
var eventFrames = []; // this is going to presume all frames equal durationlength
var groupStr=<?php if($group=="") echo '""'; else echo "\"&group=$group\""; ?>;
<?php
// Because we might not have time as the criteria, figure out the min/max time when we run the query
$minTimeSecs = strtotime('2036-01-01 01:01:01');
$maxTimeSecs = strtotime('1950-01-01 01:01:01');
// This builds the list of events that are eligible from this range
$index=0;
$anyAlarms=false;
foreach( dbFetchAll( $eventsSql ) as $event ) {
if ( $minTimeSecs > $event['StartTimeSecs'] ) $minTimeSecs = $event['StartTimeSecs'];
if ( $maxTimeSecs < $event['CalcEndTimeSecs'] ) $maxTimeSecs = $event['CalcEndTimeSecs'];
echo "
eMonId[$index]=" . $event['MonitorId'] . ";
eId[$index]=" . $event['Id'] . ";
eStartSecs[$index]=" . $event['StartTimeSecs'] . ";
eEndSecs[$index]=" . $event['CalcEndTimeSecs'] . ";
eventFrames[$index]=" . $event['Frames'] . ";
";
$index = $index + 1;
if ( $event['MaxScore'] > 0 )
$anyAlarms = true;
}
// if there is no data set the min/max to the passed in values
if ( $index == 0 ) {
if ( isset($minTime) && isset($maxTime) ) {
$minTimeSecs = strtotime($minTime);
$maxTimeSecs = strtotime($maxTime);
} else {
// this is the case of no passed in times AND no data -- just set something arbitrary
$minTimeSecs = strtotime('1950-06-01 01:01:01'); // random time so there's something to display
$maxTimeSecs = time() + 86400;
}
}
// We only reset the calling time if there was no calling time
if ( !isset($minTime) || !isset($maxTime) ) {
$maxTime = strftime($maxTimeSecs);
$minTime = strftime($minTimeSecs);
} else {
$minTimeSecs = strtotime($minTime);
$maxTimeSecs = strtotime($maxTime);
}
// If we had any alarms in those events, this builds the list of all alarm frames, but consolidated down to (nearly) contiguous segments
// comparison in else governs how aggressively it consolidates
echo "var fMonId = [];\n";
echo "var fTimeFromSecs = [];\n";
echo "var fTimeToSecs = [];\n";
echo "var fScore = [];\n";
$maxScore=0;
$index=0;
$mId=-1;
$fromSecs=-1;
$toSecs=-1;
$maxScore=-1;
if ( $anyAlarms ) {
foreach( dbFetchAll ($frameSql) as $frame ) {
if ( $mId < 0 ) {
$mId = $frame['MonitorId'];
$fromSecs = $frame['TimeStampSecs'];
$toSecs = $frame['TimeStampSecs'];
$maxScore = $frame['Score'];
} else if ( $mId != $frame['MonitorId'] || $frame['TimeStampSecs'] - $toSecs > 10 ) {
// dump this one start a new
$index++;
echo "
fMonId[$index]= $mId;
fTimeFromSecs[$index]= $fromSecs;
fTimeToSecs[$index]= $toSecs;
fScore[$index]= $maxScore;
";
$mId = $frame['MonitorId'];
$fromSecs = $frame['TimeStampSecs'];
$toSecs = $frame['TimeStampSecs'];
$maxScore = $frame['Score'];
} else {
// just add this one on
$toSecs = $frame['TimeStampSecs'];
if ( $maxScore < $frame['Score'] ) $maxScore = $frame['Score'];
}
}
}
if ( $mId > 0 ) {
echo "
fMonId[$index]= $mId;
fTimeFromSecs[$index]= $fromSecs;
fTimeToSecs[$index]= $toSecs;
fScore[$index]= $maxScore;
";
}
echo "var maxScore=$maxScore;\n"; // used to skip frame load if we find no alarms.
echo "var monitorName = [];\n";
echo "var monitorLoading = [];\n";
echo "var monitorImageObject = [];\n";
echo "var monitorImageURL = [];\n";
echo "var monitorLoadingStageURL = [];\n";
echo "var monitorLoadStartTimems = [];\n";
echo "var monitorLoadEndTimems = [];\n";
echo "var monitorColour = [];\n";
echo "var monitorWidth = [];\n";
echo "var monitorHeight = [];\n";
echo "var monitorIndex = [];\n";
echo "var monitorNormalizeScale = [];\n";
echo "var monitorZoomScale = [];\n";
echo "var monitorCanvasObj = [];\n"; // stash location of these here so we don't have to search
echo "var monitorCanvasCtx = [];\n";
echo "var monitorPtr = []; // monitorName[monitorPtr[0]] is first monitor\n";
$numMonitors=0; // this array is indexed by the monitor ID for faster access later, so it may be sparse
$avgArea=floatval(0); // Calculations the normalizing scale
foreach ( $monitors as $m ) {
$avgArea = $avgArea + floatval($m->Width() * $m->Height());
$numMonitors++;
}
if ( $numMonitors > 0 ) $avgArea = $avgArea / $numMonitors;
$numMonitors = 0;
foreach ( $monitors as $m ) {
echo " monitorLoading[" . $m->Id() . "]=false;\n";
echo " monitorImageURL[" . $m->Id() . "]='".$m->getStreamSrc( array('mode'=>'single','scale'=>$defaultScale*100), '&' )."';\n";
echo " monitorLoadingStageURL[" . $m->Id() . "] = '';\n";
echo " monitorColour[" . $m->Id() . "]=\"" . $m->WebColour() . "\";\n";
echo " monitorWidth[" . $m->Id() . "]=" . $m->Width() . ";\n";
echo " monitorHeight[" . $m->Id() . "]=" . $m->Height() . ";\n";
echo " monitorIndex[" . $m->Id() . "]=" . $numMonitors . ";\n";
echo " monitorName[" . $m->Id() . "]=\"" . $m->Name() . "\";\n";
echo " monitorLoadStartTimems[" . $m->Id() . "]=0;\n";
echo " monitorLoadEndTimems[" . $m->Id() . "]=0;\n";
echo " monitorNormalizeScale[" . $m->Id() . "]=" . sqrt($avgArea / ($m->Width() * $m->Height() )) . ";\n";
$zoomScale=1.0;
if(isset($_REQUEST[ 'z' . $m->Id() ]) )
$zoomScale = floatval( validHtmlStr($_REQUEST[ 'z' . $m->Id() ]) );
echo " monitorZoomScale[" . $m->Id() . "]=" . $zoomScale . ";\n";
echo " monitorPtr[" . $numMonitors . "]=" . $m->Id() . ";\n";
$numMonitors += 1;
}
echo "var numMonitors = $numMonitors;\n";
echo "var minTimeSecs=" . $minTimeSecs . ";\n";
echo "var maxTimeSecs=" . $maxTimeSecs . ";\n";
echo "var rangeTimeSecs=" . ( $maxTimeSecs - $minTimeSecs + 1) . ";\n";
if(isset($defaultCurrentTime))
echo "var currentTimeSecs=" . strtotime($defaultCurrentTime) . ";\n";
else
echo "var currentTimeSecs=" . ($minTimeSecs + $maxTimeSecs)/2 . ";\n";
echo 'var speeds=[';
for ($i=0; $i<count($speeds); $i++)
echo (($i>0)?', ':'') . $speeds[$i];
echo "];\n";
?>
var scrubAsObject=$('scrub');
var cWidth; // save canvas width
var cHeight; // save canvas height
var canvas; // global canvas definition so we don't have to keep looking it up
var ctx;
var underSlider; // use this to hold what is hidden by the slider
var underSliderX; // Where the above was taken from (left side, Y is zero)

View File

@ -41,6 +41,7 @@ if ( isset( $_REQUEST['showZones'] ) ) {
}
}
$monitors = array();
<<<<<<< HEAD
$widths = array(
'' => 'auto',
160 => 160,
@ -63,8 +64,10 @@ if ( isset( $_REQUEST['scale'] ) ) {
} else if ( isset( $_COOKIE['zmMontageScale'] ) ) {
$scale = $_COOKIE['zmMontageScale'];
Logger::Debug("Setting scale from cookie to $scale");
} else {
Logger::Debug("scale is $scale");
}
if ( ! $scale )
$scale = 100;
}
foreach( dbFetchAll( $sql ) as $row ) {

View File

@ -95,20 +95,20 @@
//
if ( !canView( 'Events' ) ) {
$view = 'error';
return;
$view = 'error';
return;
}
require_once( 'includes/Monitor.php' );
# FIXME THere is no way to select group at this time.
if ( !empty($_REQUEST['group']) ) {
$group = $_REQUEST['group'];
$row = dbFetchOne( 'SELECT * FROM Groups WHERE Id = ?', NULL, array($_REQUEST['group']) );
$monitorsSql = "SELECT * FROM Monitors WHERE Function != 'None' AND find_in_set( Id, '".$row['MonitorIds']."' ) ";
$group = $_REQUEST['group'];
$row = dbFetchOne( 'SELECT * FROM Groups WHERE Id = ?', NULL, array($_REQUEST['group']) );
$monitorsSql = "SELECT * FROM Monitors WHERE Function != 'None' AND find_in_set( Id, '".$row['MonitorIds']."' ) ";
} else {
$monitorsSql = "SELECT * FROM Monitors WHERE Function != 'None'";
$group = '';
$monitorsSql = "SELECT * FROM Monitors WHERE Function != 'None'";
$group = '';
}
// Note that this finds incomplete events as well, and any frame records written, but still cannot "see" to the end frame
@ -222,7 +222,7 @@ $monitors = array();
$monitorsSql .= ' ORDER BY Sequence ASC';
$index=0;
foreach( dbFetchAll( $monitorsSql ) as $row ) {
$monitors[$index] = $row;
$monitors[$index] = new Monitor( $row );
$index = $index + 1;
}
@ -270,921 +270,15 @@ input[type=range]::-ms-tooltip {
<span id="scrubright"></span>
<span id="scruboutput"></span>
</div>
<div id="monitors">
<?php
// Monitor images - these had to be loaded after the monitors used were determined (after loading events)
echo '<div id="monitors">';
foreach ($monitors as $m) {
echo '<canvas width="' . $m['Width'] * $defaultScale . 'px" height="' . $m['Height'] * $defaultScale . 'px" id="Monitor' . $m['Id'] . '" style="border:3px solid ' . $m['WebColour'] . '" onclick="clickMonitor(event,' . $m['Id'] . ')">No Canvas Support!!</canvas>';
echo '<canvas width="' . $m->Width() * $defaultScale . 'px" height="' . $m->Height() * $defaultScale . 'px" id="Monitor' . $m->Id() . '" style="border:3px solid ' . $m->WebColour() . '" onclick="clickMonitor(event,' . $m->Id() . ')">No Canvas Support!!</canvas>';
}
echo "</div>\n";
echo "<p id=\"fps\">evaluating fps</p>\n";
echo "<script type=\"text/javascript\">\n";
?>
var currentScale=<?php echo $defaultScale?>;
var liveMode=<?php echo $initialModeIsLive?>;
var fitMode=<?php echo $fitMode?>;
var currentSpeed=<?php echo $speeds[$speedIndex]?>; // slider scale, which is only for replay and relative to real time
var speedIndex=<?php echo $speedIndex?>;
var currentDisplayInterval=<?php echo $initialDisplayInterval?>; // will be set based on performance, this is the display interval in milliseconds for history, and fps for live, and dynamically determined (in ms)
var playSecsperInterval=1; // How many seconds of recorded image we play per refresh determined by speed (replay rate) and display interval; (default=1 if coming from live)
var timerInterval; // milliseconds between interrupts
var timerObj; // object to hold timer interval;
var freeTimeLastIntervals=[]; // Percentage of current interval used in loading most recent image
var imageLoadTimesEvaluated=0; // running count
var imageLoadTimesNeeded=15; // and how many we need
var timeLabelsFractOfRow = 0.9;
var eMonId = [];
var eId = [];
var eStartSecs = [];
var eEndSecs = [];
var ePath = [];
var eventFrames = []; // this is going to presume all frames equal durationlength
<?php
// Because we might not have time as the criteria, figure out the min/max time when we run the query
$minTimeSecs = strtotime("2036-01-01 01:01:01");
$maxTimeSecs = strtotime("1950-01-01 01:01:01");
// This builds the list of events that are eligible from this range
$index=0;
$anyAlarms=false;
foreach( dbFetchAll( $eventsSql ) as $event ) {
if( $minTimeSecs > $event['StartTimeSecs']) $minTimeSecs = $event['StartTimeSecs'];
if( $maxTimeSecs < $event['CalcEndTimeSecs']) $maxTimeSecs = $event['CalcEndTimeSecs'];
echo "eMonId[$index]=" . $event['MonitorId'] . ";
eId[$index]=" . $event['Id'] . ";
eStartSecs[$index]=" . $event['StartTimeSecs'] . ";
eEndSecs[$index]=" . $event['CalcEndTimeSecs'] . ";
eventFrames[$index]=" . $event['Frames'] . "; ";
// NO GOOD, need to use view=image
if ( ZM_USE_DEEP_STORAGE )
echo "ePath[$index] = \"events/" . $event['MonitorId'] . "/" . strftime("%y/%m/%d/%H/%M/%S", $event['StartTimeSecs']) . "/\";" ;
else
echo "ePath[$index] = \"events/" . $event['MonitorId'] . "/" . $event['Id'] . "/\";" ;
$index = $index + 1;
if($event['MaxScore']>0)
$anyAlarms = true;
echo "\n";
}
// if there is no data set the min/max to the passed in values
if($index == 0) {
if(isset($minTime) && isset($maxTime)) {
$minTimeSecs = strtotime($minTime);
$maxTimeSecs = strtotime($maxTime);
} else {
// this is the case of no passed in times AND no data -- just set something arbitrary
$minTimeSecs=strtotime('1950-06-01 01:01:01'); // random time so there's something to display
$maxTimeSecs=strtotime('2020-06-02 02:02:02');
}
}
// We only reset the calling time if there was no calling time
if(!isset($minTime) || !isset($maxTime)) {
$maxTime = strftime($maxTimeSecs);
$minTime = strftime($minTimeSecs);
} else {
$minTimeSecs = strtotime($minTime);
$maxTimeSecs = strtotime($maxTime);
}
// If we had any alarms in those events, this builds the list of all alarm frames, but consolidated down to (nearly) contiguous segments
// comparison in else governs how aggressively it consolidates
echo "var fMonId = [];\n";
echo "var fTimeFromSecs = [];\n";
echo "var fTimeToSecs = [];\n";
echo "var fScore = [];\n";
$maxScore=0;
$index=0;
$mId=-1;
$fromSecs=-1;
$toSecs=-1;
$maxScore=-1;
if($anyAlarms) {
foreach( dbFetchAll ($frameSql) as $frame ) {
if($mId<0) {
$mId=$frame['MonitorId'];
$fromSecs=$frame['TimeStampSecs'];
$toSecs=$frame['TimeStampSecs'];
$maxScore=$frame['Score'];
} else if ($mId != $frame['MonitorId'] || $frame['TimeStampSecs'] - $toSecs > 10) {
// dump this one start a new
$index++;
echo " fMonId[$index]=" . $mId . ";";
echo " fTimeFromSecs[$index]=" . $fromSecs . ";";
echo " fTimeToSecs[$index]=" . $toSecs . ";";
echo " fScore[$index]=" . $maxScore . ";\n";
$mId=$frame['MonitorId'];
$fromSecs=$frame['TimeStampSecs'];
$toSecs=$frame['TimeStampSecs'];
$maxScore=$frame['Score'];
} else {
// just add this one on
$toSecs=$frame['TimeStampSecs'];
if($maxScore < $frame['Score']) $maxScore=$frame['Score'];
}
}
}
if($mId>0) {
echo " fMonId[$index]=" . $mId . ";";
echo " fTimeFromSecs[$index]=" . $fromSecs . ";";
echo " fTimeToSecs[$index]=" . $toSecs . ";";
echo " fScore[$index]=" . $maxScore . ";\n";
}
echo "var maxScore=$maxScore;\n"; // used to skip frame load if we find no alarms.
echo "var monitorName = [];\n";
echo "var monitorLoading = [];\n";
echo "var monitorImageObject = [];\n";
echo "var monitorLoadingStageURL = [];\n";
echo "var monitorLoadStartTimems = [];\n";
echo "var monitorLoadEndTimems = [];\n";
echo "var monitorColour = [];\n";
echo "var monitorWidth = [];\n";
echo "var monitorHeight = [];\n";
echo "var monitorIndex = [];\n";
echo "var monitorNormalizeScale = [];\n";
echo "var monitorZoomScale = [];\n";
echo "var monitorCanvasObj = [];\n"; // stash location of these here so we don't have to search
echo "var monitorCanvasCtx = [];\n";
echo "var monitorPtr = []; // monitorName[monitorPtr[0]] is first monitor\n";
$numMonitors=0; // this array is indexed by the monitor ID for faster access later, so it may be sparse
$avgArea=floatval(0); // Calculations the normalizing scale
foreach ($monitors as $m) {
$avgArea = $avgArea + floatval($m['Width'] * $m['Height']);
$numMonitors++;
}
if($numMonitors>0) $avgArea= $avgArea / $numMonitors;
$numMonitors=0;
foreach ($monitors as $m) {
echo " monitorLoading[" . $m['Id'] . "]=false; ";
echo " monitorImageObject[" . $m['Id'] . "]=null; ";
echo " monitorLoadingStageURL[" . $m['Id'] . "] = ''; ";
echo " monitorColour[" . $m['Id'] . "]=\"" . $m['WebColour'] . "\"; ";
echo " monitorWidth[" . $m['Id'] . "]=" . $m['Width'] . "; ";
echo " monitorHeight[" . $m['Id'] . "]=" . $m['Height'] . "; ";
echo " monitorIndex[" . $m['Id'] . "]=" . $numMonitors . "; ";
echo " monitorName[" . $m['Id'] . "]=\"" . $m['Name'] . "\"; ";
echo " monitorLoadStartTimems[" . $m['Id'] . "]=0; ";
echo " monitorLoadEndTimems[" . $m['Id'] . "]=0; ";
echo " monitorCanvasObj[" . $m['Id'] . "]=document.getElementById('Monitor" . $m['Id'] . "'); ";
echo " monitorCanvasCtx[" . $m['Id'] . "]=monitorCanvasObj[" . $m['Id'] . "].getContext('2d'); ";
echo " monitorNormalizeScale[" . $m['Id'] . "]=" . sqrt($avgArea / ($m['Width'] * $m['Height'] )) . "; ";
$zoomScale=1.0;
if(isset($_REQUEST[ 'z' . $m['Id'] ]) )
$zoomScale = floatval( validHtmlStr($_REQUEST[ 'z' . $m['Id'] ]) );
echo " monitorZoomScale[" . $m['Id'] . "]=" . $zoomScale . ";";
echo " monitorPtr[" . $numMonitors . "]=" . $m['Id'] . ";\n";
$numMonitors += 1;
}
echo "var numMonitors = $numMonitors;\n";
echo "var minTimeSecs=" . $minTimeSecs . ";\n";
echo "var maxTimeSecs=" . $maxTimeSecs . ";\n";
echo "var rangeTimeSecs=" . ( $maxTimeSecs - $minTimeSecs + 1) . ";\n";
if(isset($defaultCurrentTime))
echo "var currentTimeSecs=" . strtotime($defaultCurrentTime) . ";\n";
else
echo "var currentTimeSecs=" . ($minTimeSecs + $maxTimeSecs)/2 . ";\n";
echo "var speeds=[";
for ($i=0; $i<count($speeds); $i++)
echo (($i>0)?", ":"") . $speeds[$i];
echo "];\n";
?>
var scrubAsObject=document.getElementById('scrub');
var cWidth; // save canvas width
var cHeight; // save canvas height
var canvas=document.getElementById("timeline"); // global canvas definition so we don't have to keep looking it up
var ctx=canvas.getContext('2d');
var underSlider; // use this to hold what is hidden by the slider
var underSliderX; // Where the above was taken from (left side, Y is zero)
function evaluateLoadTimes() {
// Only consider it a completed event if we load ALL monitors, then zero all and start again
var start=0;
var end=0;
if(liveMode!=1 && currentSpeed==0) return; // don't evaluate when we are not moving as we can do nothing really fast.
for(var i=0; i<monitorIndex.length; i++) {
if( monitorName[i]>"") {
if( monitorLoadEndTimems[i]==0) return; // if we have a monitor with no time yet just wait
if( start == 0 || start > monitorLoadStartTimems[i] ) start = monitorLoadStartTimems[i];
if( end == 0 || end < monitorLoadEndTimems[i] ) end = monitorLoadEndTimems[i];
}
}
if(start==0 || end==0) return; // we really should not get here
for(var i=0; i<numMonitors; i++) {
monitorLoadStartTimems[monitorPtr[i]]=0;
monitorLoadEndTimems[monitorPtr[i]]=0;
}
freeTimeLastIntervals[imageLoadTimesEvaluated++] = 1 - ((end - start)/currentDisplayInterval);
if( imageLoadTimesEvaluated < imageLoadTimesNeeded ) return;
var avgFrac=0;
for(var i=0; i<imageLoadTimesEvaluated; i++)
avgFrac += freeTimeLastIntervals[i];
avgFrac = avgFrac / imageLoadTimesEvaluated;
// The larger this is(positive) the faster we can go
if (avgFrac >= 0.9) currentDisplayInterval = (currentDisplayInterval * 0.50).toFixed(1); // we can go much faster
else if (avgFrac >= 0.8) currentDisplayInterval = (currentDisplayInterval * 0.55).toFixed(1);
else if (avgFrac >= 0.7) currentDisplayInterval = (currentDisplayInterval * 0.60).toFixed(1);
else if (avgFrac >= 0.6) currentDisplayInterval = (currentDisplayInterval * 0.65).toFixed(1);
else if (avgFrac >= 0.5) currentDisplayInterval = (currentDisplayInterval * 0.70).toFixed(1);
else if (avgFrac >= 0.4) currentDisplayInterval = (currentDisplayInterval * 0.80).toFixed(1);
else if (avgFrac >= 0.35) currentDisplayInterval = (currentDisplayInterval * 0.90).toFixed(1);
else if (avgFrac >= 0.3) currentDisplayInterval = (currentDisplayInterval * 1.00).toFixed(1);
else if (avgFrac >= 0.25) currentDisplayInterval = (currentDisplayInterval * 1.20).toFixed(1);
else if (avgFrac >= 0.2) currentDisplayInterval = (currentDisplayInterval * 1.50).toFixed(1);
else if (avgFrac >= 0.1) currentDisplayInterval = (currentDisplayInterval * 2.00).toFixed(1);
else currentDisplayInterval = (currentDisplayInterval * 2.50).toFixed(1);
currentDisplayInterval=Math.min(Math.max(currentDisplayInterval, 30),10000); // limit this from about 30fps to .1 fps
imageLoadTimesEvaluated=0;
setSpeed(speedIndex);
$('fps').innerHTML="Display refresh rate is " + (1000 / currentDisplayInterval).toFixed(1) + " per second, avgFrac=" + avgFrac.toFixed(3) + ".";
}
function SetImageSource(monId,val) {
if(liveMode==1) {
// This uses the standard php routine to set up the url and authentication, but because it is called repeatedly the built in random number is not usable, so one is appended below for two total (yuck)
var effectiveScale = (100.0 * monitorCanvasObj[monId].width) / monitorWidth[monId];
var $x = "<?php echo getStreamSrc( array("mode=single"),"&" )?>" + "&monitor=" + monId.toString() + "&scale=" + effectiveScale + Math.random().toString() ;
return $x;
} else {
var zeropad = <?php echo sprintf("\"%0" . ZM_EVENT_IMAGE_DIGITS . "d\"",0); ?>;
for(var i=0, eIdlength = eId.length; i<eIdlength; i++) {
// Search for a match
if(eMonId[i]==monId && val >= eStartSecs[i] && val <= eEndSecs[i]) {
var frame=parseInt((val - eStartSecs[i])/(eEndSecs[i]-eStartSecs[i])*eventFrames[i])+1;
img = "index.php?view=image&eid=" + eId[i] + '&fid='+frame + "&width=" + monitorCanvasObj[monId].width + "&height=" + monitorCanvasObj[monId].height;
return img;
}
} // end for
return "no data";
}
}
function imagedone(obj, monId, success) {
if(success) {
monitorCanvasCtx[monId].drawImage( monitorImageObject[monId], 0, 0, monitorCanvasObj[monId].width, monitorCanvasObj[monId].height);
var iconSize=(Math.max(monitorCanvasObj[monId].width,monitorCanvasObj[monId].height) * 0.10);
monitorCanvasCtx[monId].font = "600 " + iconSize.toString() + "px Arial";
monitorCanvasCtx[monId].fillStyle="white";
monitorCanvasCtx[monId].globalCompositeOperation="difference";
monitorCanvasCtx[monId].fillText("+",iconSize*0.2, iconSize*1.2);
monitorCanvasCtx[monId].fillText("-",monitorCanvasObj[monId].width - iconSize*1.2, iconSize*1.2);
monitorCanvasCtx[monId].globalCompositeOperation="source-over";
monitorLoadEndTimems[monId] = new Date().getTime(); // elapsed time to load
evaluateLoadTimes();
}
monitorLoading[monId]=false;
if(!success) {
// if we had a failrue queue up the no-data image
loadImage2Monitor(monId,"no data"); // leave the staged URL if there is one, just ignore it here.
} else {
if(monitorLoadingStageURL[monId]=="") return;
loadImage2Monitor(monId,monitorLoadingStageURL[monId]);
monitorLoadingStageURL[monId]="";
}
return;
}
function loadImage2Monitor(monId,url) {
if(monitorLoading[monId] && monitorImageObject[monId].src != url ) {
// never queue the same image twice (if it's loading it has to be defined, right?
monitorLoadingStageURL[monId]=url; // we don't care if we are overriting, it means it didn't change fast enough
} else {
var skipthis=0;
if( typeof monitorImageObject[monId] !== "undefined" && monitorImageObject[monId] != null && monitorImageObject[monId].src == url ) return; // do nothing if it's the same
if( monitorImageObject[monId] == null ) {
monitorImageObject[monId]=new Image();
monitorImageObject[monId].onload = function() {imagedone(this, monId,true )};
monitorImageObject[monId].onerror = function() {imagedone(this, monId,false)};
}
if(url=='no data') {
monitorCanvasCtx[monId].fillStyle="white";
monitorCanvasCtx[monId].fillRect(0,0,monitorCanvasObj[monId].width,monitorCanvasObj[monId].height);
var textSize=monitorCanvasObj[monId].width * 0.15;
var text="No Data";
monitorCanvasCtx[monId].font = "600 " + textSize.toString() + "px Arial";
monitorCanvasCtx[monId].fillStyle="black";
var textWidth = monitorCanvasCtx[monId].measureText(text).width;
monitorCanvasCtx[monId].fillText(text,monitorCanvasObj[monId].width/2 - textWidth/2,monitorCanvasObj[monId].height/2);
} else {
monitorLoading[monId]=true;
monitorLoadStartTimems[monId]=new Date().getTime();
monitorImageObject[monId].src=url; // starts a load but doesn't refresh yet, wait until ready
}
}
}
function timerFire() {
// See if we need to reschedule
if(currentDisplayInterval != timerInterval || currentSpeed == 0) {
// zero just turn off interrupts
clearInterval(timerObj);
timerInterval=currentDisplayInterval;
if(currentSpeed>0 || liveMode!=0) timerObj=setInterval(timerFire,timerInterval); // don't fire out of live mode if speed is zero
}
if (liveMode) outputUpdate(currentTimeSecs); // In live mode we basically do nothing but redisplay
else if (currentTimeSecs + playSecsperInterval >= maxTimeSecs) // beyond the end just stop
{
setSpeed(0);
outputUpdate(currentTimeSecs);
}
else outputUpdate(currentTimeSecs + playSecsperInterval);
return;
}
function drawSliderOnGraph(val) {
var sliderWidth=10;
var sliderLineWidth=1;
var sliderHeight=cHeight;
if(liveMode==1) {
val=Math.floor( Date.now() / 1000);
}
// Set some sizes
var labelpx = Math.max( 6, Math.min( 20, parseInt(cHeight * timeLabelsFractOfRow / (numMonitors+1)) ) );
var labbottom=parseInt(cHeight * 0.2 / (numMonitors+1)).toString() + "px"; // This is positioning same as row labels below, but from bottom so 1-position
var labfont=labelpx + "px Georgia"; // set this like below row labels
if(numMonitors>0) {
// if we have no data to display don't do the slider itself
var sliderX=parseInt( (val - minTimeSecs) / rangeTimeSecs * cWidth - sliderWidth/2); // position left side of slider
if(sliderX < 0) sliderX=0;
if(sliderX+sliderWidth > cWidth) sliderX=cWidth-sliderWidth-1;
// If we have data already saved first restore it from LAST time
if(typeof underSlider !== 'undefined')
{
ctx.putImageData(underSlider,underSliderX, 0, 0, 0, sliderWidth, sliderHeight);
underSlider=undefined;
}
if(liveMode==0) // we get rid of the slider if we switch to live (since it may not be in the "right" place)
{
// Now save where we are putting it THIS time
underSlider=ctx.getImageData(sliderX, 0, sliderWidth, sliderHeight);
// And add in the slider'
ctx.lineWidth=sliderLineWidth;
ctx.strokeStyle='black';
// looks like strokes are on the outside (or could be) so shrink it by the line width so we replace all the pixels
ctx.strokeRect(sliderX+sliderLineWidth,sliderLineWidth,sliderWidth - 2*sliderLineWidth, sliderHeight - 2*sliderLineWidth);
underSliderX=sliderX;
}
var o = $('scruboutput');
if(liveMode==1)
{
o.innerHTML="Live Feed @ " + (1000 / currentDisplayInterval).toFixed(1) + " fps";
o.style.color="red";
}
else
{
o.innerHTML=secs2dbstr(val);
o.style.color="blue";
}
o.style.position="absolute";
o.style.bottom=labbottom;
o.style.font=labfont;
// try to get length and then when we get too close to the right switch to the left
var len = o.offsetWidth;
var x;
if(sliderX > cWidth/2)
x=sliderX - len - 10;
else
x=sliderX + 10;
o.style.left=x.toString() + "px";
}
// This displays (or not) the left/right limits depending on how close the slider is.
// Because these change widths if the slider is too close, use the slider width as an estimate for the left/right label length (i.e. don't recalculate len from above)
// If this starts to collide increase some of the extra space
var o = $('scrubleft');
o.innerHTML=secs2dbstr(minTimeSecs);
o.style.position="absolute";
o.style.bottom=labbottom;
o.style.font=labfont;
o.style.left="5px";
if(numMonitors==0) // we need a len calculation if we skipped the slider
len = o.offsetWidth;
// If the slider will overlay part of this suppress (this is the left side)
if(len + 10 > sliderX || cWidth < len * 4 ) // that last check is for very narrow browsers
o.style.display="none";
else
{
o.style.display="inline";
o.style.display="inline-flex"; // safari won't take this but will just ignore
}
var o = $('scrubright');
o.innerHTML=secs2dbstr(maxTimeSecs);
o.style.position="absolute";
o.style.bottom=labbottom;
o.style.font=labfont;
// If the slider will overlay part of this suppress (this is the right side)
o.style.left=(cWidth - len - 15).toString() + "px";
if(sliderX > cWidth - len - 20 || cWidth < len * 4 )
o.style.display="none";
else
{
o.style.display="inline";
o.style.display="inline-flex";
}
}
function drawGraph()
{
var divWidth=$('timelinediv').clientWidth
canvas.width = cWidth = divWidth; // Let it float and determine width (it should be sized a bit smaller percentage of window)
canvas.height=cHeight = parseInt(window.innerHeight * 0.10);
if(eId.length==0)
{
ctx.font="40px Georgia";
ctx.fillStyle="Black";
ctx.globalAlpha=1;
var t="No data found in range - choose differently";
var l=ctx.measureText(t).width;
ctx.fillText(t,(cWidth - l)/2, cHeight-10);
underSlider=undefined;
return;
}
var rowHeight=parseInt(cHeight / (numMonitors + 1) ); // Leave room for a scale of some sort
// first fill in the bars for the events (not alarms)
for(var i=0; i<eId.length; i++) // Display all we loaded
{
var x1=parseInt( (eStartSecs[i] - minTimeSecs) / rangeTimeSecs * cWidth) ; // round low end down
var x2=parseInt( (eEndSecs[i] - minTimeSecs) / rangeTimeSecs * cWidth + 0.5 ) ; // round high end up to be sure consecutive ones connect
ctx.fillStyle=monitorColour[eMonId[i]];
ctx.globalAlpha = 0.2; // light color for background
ctx.clearRect(x1,monitorIndex[eMonId[i]]*rowHeight,x2-x1,rowHeight); // Erase any overlap so it doesn't look artificially darker
ctx.fillRect (x1,monitorIndex[eMonId[i]]*rowHeight,x2-x1,rowHeight);
}
for(var i=0; (i<fScore.length) && (maxScore>0); i++) // Now put in scored frames (if any)
{
var x1=parseInt( (fTimeFromSecs[i] - minTimeSecs) / rangeTimeSecs * cWidth) ; // round low end down
var x2=parseInt( (fTimeToSecs[i] - minTimeSecs) / rangeTimeSecs * cWidth + 0.5 ) ; // round up
if(x2-x1 < 2) x2=x1+2; // So it is visible make them all at least this number of seconds wide
ctx.fillStyle=monitorColour[fMonId[i]];
ctx.globalAlpha = 0.4 + 0.6 * (1 - fScore[i]/maxScore); // Background is scaled but even lowest is twice as dark as the background
ctx.fillRect(x1,monitorIndex[fMonId[i]]*rowHeight,x2-x1,rowHeight);
}
for(var i=0; i<numMonitors; i++) // Note that this may be a sparse array
{
ctx.font= parseInt(rowHeight * timeLabelsFractOfRow).toString() + "px Georgia";
ctx.fillStyle="Black";
ctx.globalAlpha=1;
ctx.fillText(monitorName[monitorPtr[i]], 0, (i + 1 - (1 - timeLabelsFractOfRow)/2 ) * rowHeight ); // This should roughly center font in row
}
underSlider=undefined; // flag we don't have a slider cached
drawSliderOnGraph(currentTimeSecs);
return;
}
function redrawScreen()
{
if(fitMode==0) // if we fit, then monitors were absolutely positioned already (or will be) otherwise release them to float
{
for(var i=0; i<numMonitors; i++)
monitorCanvasObj[monitorPtr[i]].style.position="";
$('monitors').setStyle('height',"auto");
}
if(liveMode==1) // if we are not in live view switch to history -- this has to come before fit in case we re-establish the timeline
{
$('SpeedDiv').style.display="none";
$('timelinediv').style.display="none";
$('live').innerHTML="History";
$('zoomin').style.display="none";
$('zoomout').style.display="none";
$('panleft').style.display="none";
$('panright').style.display="none";
}
else // switch out of liveview mode
{
$('SpeedDiv').style.display="inline";
$('SpeedDiv').style.display="inline-flex";
$('timelinediv').style.display=null;
$('live').innerHTML="Live";
$('zoomin').style.display="inline";
$('zoomin').style.display="inline-flex";
$('zoomout').style.display="inline";
$('zoomout').style.display="inline-flex";
$('panleft').style.display="inline";
$('panleft').style.display="inline-flex";
$('panright').style.display="inline";
$('panright').style.display="inline-flex";
}
if(fitMode==1)
{
$('ScaleDiv').style.display="none";
$('fit').innerHTML="Scale";
var vh=window.innerHeight;
var vw=window.innerWidth;
var pos=$('monitors').getPosition();
var mh=(vh - pos.y - $('fps').getSize().y);
$('monitors').setStyle('height',mh.toString() + "px"); // leave a small gap at bottom
if(maxfit2($('monitors').getSize().x,$('monitors').getSize().y) == 0) /// if we fail to fix we back out of fit mode -- ??? This may need some better handling
fitMode=1-fitMode;
}
else // switch out of fit mode
{
$('ScaleDiv').style.display="inline";
$('ScaleDiv').style.display="inline-flex";
$('fit').innerHTML="Fit";
setScale(currentScale);
}
drawGraph();
outputUpdate(currentTimeSecs);
timerFire(); // force a fire in case it's not timing
}
function outputUpdate(val)
{
drawSliderOnGraph(val);
for(var i=0; i<numMonitors; i++)
{
loadImage2Monitor(monitorPtr[i],SetImageSource(monitorPtr[i],val));
}
var currentTimeMS = new Date(val*1000);
currentTimeSecs=val;
}
/// Found this here: http://stackoverflow.com/questions/55677/how-do-i-get-the-coordinates-of-a-mouse-click-on-a-canvas-element
function relMouseCoords(event){
var totalOffsetX = 0;
var totalOffsetY = 0;
var canvasX = 0;
var canvasY = 0;
var currentElement = this;
do{
totalOffsetX += currentElement.offsetLeft - currentElement.scrollLeft;
totalOffsetY += currentElement.offsetTop - currentElement.scrollTop;
}
while(currentElement = currentElement.offsetParent)
canvasX = event.pageX - totalOffsetX;
canvasY = event.pageY - totalOffsetY;
return {x:canvasX, y:canvasY}
}
HTMLCanvasElement.prototype.relMouseCoords = relMouseCoords;
// These are the functions for mouse movement in the timeline. Note that touch is treated as a mouse move with mouse down
var mouseisdown=false;
function mdown(event) {mouseisdown=true; mmove(event);}
function mup(event) {mouseisdown=false;}
function mout(event) {mouseisdown=false;} // if we go outside treat it as release
function tmove(event) {mouseisdown=true; mmove(event);}
function mmove(event) {
if(mouseisdown) {
// only do anything if the mouse is depressed while on the sheet
var sec = minTimeSecs + rangeTimeSecs / event.target.width * event.target.relMouseCoords(event).x;
outputUpdate(sec);
}
}
function secs2dbstr (s)
{
var st = (new Date(s * 1000)).format("%Y-%m-%d %H:%M:%S");
return st;
}
function setFit(value)
{
fitMode=value;
redrawScreen();
}
function showScale(newscale) // updates slider only
{
$('scaleslideroutput').innerHTML = parseFloat(newscale).toFixed(2).toString() + " x";
return;
}
function setScale(newscale) // makes actual change
{
showScale(newscale);
for(var i=0; i<numMonitors; i++)
{
monitorCanvasObj[monitorPtr[i]].width=monitorWidth[monitorPtr[i]]*monitorNormalizeScale[monitorPtr[i]]*monitorZoomScale[monitorPtr[i]]*newscale;
monitorCanvasObj[monitorPtr[i]].height=monitorHeight[monitorPtr[i]]*monitorNormalizeScale[monitorPtr[i]]*monitorZoomScale[monitorPtr[i]]*newscale;
}
currentScale=newscale;
}
function showSpeed(val) // updates slider only
{
$('speedslideroutput').innerHTML = parseFloat(speeds[val]).toFixed(2).toString() + " x";
}
function setSpeed(val) // Note parameter is the index not the speed
{
var t;
if(liveMode==1) return; // we shouldn't actually get here but just in case
currentSpeed=parseFloat(speeds[val]);
speedIndex=val;
playSecsperInterval = currentSpeed * currentDisplayInterval / 1000;
showSpeed(val);
if( timerInterval != currentDisplayInterval || currentSpeed == 0 ) timerFire(); // if the timer isn't firing we need to trigger it to update
}
function setLive(value)
{
liveMode=value;
redrawScreen();
}
//vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
// The section below are to reload this program with new parameters
function clicknav(minSecs,maxSecs,arch,live) // we use the current time if we can
{
var now = new Date() / 1000;
var minStr="";
var maxStr="";
var currentStr="";
if(minSecs>0)
{
if(maxSecs > now)
maxSecs = parseInt(now);
maxStr="&maxTime=" + secs2dbstr(maxSecs);
}
if(maxSecs>0)
minStr="&minTime=" + secs2dbstr(minSecs);
if(maxSecs==0 && minSecs==0)
{
minStr="&minTime=01/01/1950 12:00:00";
maxStr="&maxTime=12/31/2035 12:00:00";
}
var intervalStr="&displayinterval=" + currentDisplayInterval.toString();
if(minSecs && maxSecs)
{
if(currentTimeSecs > minSecs && currentTimeSecs < maxSecs) // make sure time is in the new range
currentStr="&current=" + secs2dbstr(currentTimeSecs);
}
var liveStr="&live=0";
if(live==1)
liveStr="&live=1";
var fitStr="&fit=0";
if(fitMode==1)
fitStr="&fit=1";
var zoomStr="";
for(var i=0; i<numMonitors; i++)
if(monitorZoomScale[monitorPtr[i]] < 0.99 || monitorZoomScale[monitorPtr[i]] > 1.01) // allow for some up/down changes and just treat as 1 of almost 1
zoomStr += "&z" + monitorPtr[i].toString() + "=" + monitorZoomScale[monitorPtr[i]].toFixed(2);
var groupStr=<?php if($group=="") echo '""'; else echo "\"&group=$group\""; ?>;
var uri = "?view=" + currentView + fitStr + groupStr + minStr + maxStr + currentStr + intervalStr + liveStr + zoomStr + "&scale=" + document.getElementById("scaleslider").value + "&speed=" + speeds[document.getElementById("speedslider").value];
window.location=uri;
}
function lastHour()
{
var now = new Date() / 1000;
clicknav(now - 3600 + 1, now,1,0);
}
function lastEight()
{
var now = new Date() / 1000;
clicknav(now - 3600*8 + 1, now,1,0);
}
function zoomin()
{
rangeTimeSecs = parseInt(rangeTimeSecs / 2);
minTimeSecs = parseInt(currentTimeSecs - rangeTimeSecs/2); // this is the slider current time, we center on that
maxTimeSecs = parseInt(currentTimeSecs + rangeTimeSecs/2);
clicknav(minTimeSecs,maxTimeSecs,1,0);
}
function zoomout()
{
rangeTimeSecs = parseInt(rangeTimeSecs * 2);
minTimeSecs = parseInt(currentTimeSecs - rangeTimeSecs/2); // this is the slider current time, we center on that
maxTimeSecs = parseInt(currentTimeSecs + rangeTimeSecs/2);
clicknav(minTimeSecs,maxTimeSecs,1,0);
}
function panleft()
{
minTimeSecs = parseInt(minTimeSecs - rangeTimeSecs/2);
maxTimeSecs = minTimeSecs + rangeTimeSecs - 1;
clicknav(minTimeSecs,maxTimeSecs,1,0);
}
function panright()
{
minTimeSecs = parseInt(minTimeSecs + rangeTimeSecs/2);
maxTimeSecs = minTimeSecs + rangeTimeSecs - 1;
clicknav(minTimeSecs,maxTimeSecs,1,0);
}
function allof()
{
clicknav(0,0,1,0);
}
function allnon()
{
clicknav(0,0,0,0);
}
/// >>>>>>>>>>>>>>>>> handles packing different size/aspect monitors on screen <<<<<<<<<<<<<<<<<<<<<<<<
function compSize(a, b) // sort array by some size parameter - height seems to work best. A semi-greedy algorithm
{
if ( monitorHeight[a] * monitorWidth[a] * monitorNormalizeScale[a] * monitorZoomScale[a] * monitorNormalizeScale[a] * monitorZoomScale[a] > monitorHeight[b] * monitorWidth[b] * monitorNormalizeScale[b] * monitorZoomScale[b] * monitorNormalizeScale[b] * monitorZoomScale[b]) return -1;
else if ( monitorHeight[a] * monitorWidth[a] * monitorNormalizeScale[a] * monitorZoomScale[a] * monitorNormalizeScale[a] * monitorZoomScale[a] == monitorHeight[b] * monitorWidth[b] * monitorNormalizeScale[b] * monitorZoomScale[b] * monitorNormalizeScale[b] * monitorZoomScale[b]) return 0;
else return 1;
}
function maxfit2(divW, divH)
{
var bestFitX=[]; // how we arranged the so-far best match
var bestFitX2=[];
var bestFitY=[];
var bestFitY2=[];
var bestFitScale;
var minScale=0.05;
var maxScale=5.00;
var bestFitArea=0;
var borders=-1;
monitorPtr.sort(compSize);
while(1)
{
if( maxScale - minScale < 0.01 ) break;
var thisScale = (maxScale + minScale) / 2;
var allFit=1;
var thisArea=0;
var thisX=[]; // top left
var thisY=[];
var thisX2=[]; // bottom right
var thisY2=[];
for(var m=0; m<numMonitors; m++)
{
// this loop places each monitor (if it can)
function doesItFit(x,y,w,h,d)
{ // does block (w,h) fit at position (x,y) relative to edge and other nodes already done (0..d)
if(x+w>=divW) return 0;
if(y+h>=divH) return 0;
for(var i=0; i<=d; i++)
if( !( thisX[i]>x+w-1 || thisX2[i] < x || thisY[i] > y+h-1 || thisY2[i] < y ) ) return 0;
return 1; // it's OK
}
if(borders<=0) borders=$("Monitor"+monitorPtr[m]).getStyle("border").toInt() * 2; // assume fixed size border, and added to both sides and top/bottom
// try fitting over first, then down. Each new one must land at either upper right or lower left corner of last (try in that order)
// Pick the one with the smallest Y, then smallest X if Y equal
var fitX = 999999999;
var fitY = 999999999;
for( adjacent=0; adjacent<m; adjacent++)
{
// try top right of adjacent
if( doesItFit(thisX2[adjacent]+1, thisY[adjacent], monitorWidth[monitorPtr[m]] * thisScale * monitorNormalizeScale[monitorPtr[m]] * monitorZoomScale[monitorPtr[m]] + borders, monitorHeight[monitorPtr[m]] * thisScale * monitorNormalizeScale[monitorPtr[m]] * monitorZoomScale[monitorPtr[m]] + borders, m-1) == 1 )
{
if(thisY[adjacent]<fitY || ( thisY[adjacent]==fitY && thisX2[adjacent]+1 < fitX ))
{
fitX=thisX2[adjacent]+1;
fitY=thisY[adjacent];
}
}
// try bottom left
if ( doesItFit(thisX[adjacent], thisY2[adjacent]+1, monitorWidth[monitorPtr[m]] * thisScale * monitorNormalizeScale[monitorPtr[m]] * monitorZoomScale[monitorPtr[m]] + borders, monitorHeight[monitorPtr[m]] * thisScale * monitorNormalizeScale[monitorPtr[m]] * monitorZoomScale[monitorPtr[m]] + borders, m-1) == 1 )
{
if(thisY2[adjacent]+1<fitY || ( thisY2[adjacent]+1 == fitY && thisX[adjacent]<fitX ))
{
fitX=thisX[adjacent];
fitY=thisY2[adjacent]+1;
}
}
}
if(m==0) // note for teh very first one there were no adjacents so the above loop didn't run
{
if( doesItFit(0,0,monitorWidth[monitorPtr[m]] * thisScale * monitorNormalizeScale[monitorPtr[m]] * monitorZoomScale[monitorPtr[m]] + borders, monitorHeight[monitorPtr[m]] * thisScale * monitorNormalizeScale[monitorPtr[m]] * monitorZoomScale[monitorPtr[m]] + borders, -1) == 1 )
{
fitX=0;
fitY=0;
}
}
if(fitX==999999999)
{
allFit=0;
break; // break out of monitor loop flagging we didn't fit
}
thisX[m] =fitX;
thisX2[m]=fitX + monitorWidth[monitorPtr[m]] * thisScale * monitorNormalizeScale[monitorPtr[m]] * monitorZoomScale[monitorPtr[m]] + borders;
thisY[m] =fitY;
thisY2[m]=fitY + monitorHeight[monitorPtr[m]] * thisScale * monitorNormalizeScale[monitorPtr[m]] * monitorZoomScale[monitorPtr[m]] + borders;
thisArea += (thisX2[m] - thisX[m])*(thisY2[m] - thisY[m]);
}
if(allFit==1)
{
minScale=thisScale;
if(bestFitArea<thisArea)
{
bestFitArea=thisArea;
bestFitX=thisX;
bestFitY=thisY;
bestFitX2=thisX2;
bestFitY2=thisY2;
bestFitScale=thisScale;
}
}
else // didn't fit
{
maxScale=thisScale;
}
}
if(bestFitArea>0) // only rearrange if we could fit -- otherwise just do nothing, let them start coming out, whatever
{
for(m=0; m<numMonitors; m++)
{
c = $("Monitor" + monitorPtr[m]);
c.style.position="absolute";
c.style.left=bestFitX[m].toString() + "px";
c.style.top=bestFitY[m].toString() + "px";
c.width = bestFitX2[m] - bestFitX[m] + 1 - borders;
c.height= bestFitY2[m] - bestFitY[m] + 1 - borders;
}
return 1;
}
else
return 0;
}
// >>>>>>>>>>>>>>>> Handles individual monitor clicks and navigation to the standard event/watch display
function showOneMonitor(monId) // link out to the normal view of one event's data
{
// We know the monitor, need to determine the event based on current time
var url;
if(liveMode!=0) url="?view=watch&mid=" + monId.toString();
else
for(var i=0; i<eId.length; i++)
if(eMonId[i]==monId && currentTimeSecs >= eStartSecs[i] && currentTimeSecs <= eEndSecs[i])
url="?view=event&eid=" + eId[i] + '&fid=' + parseInt(Math.max(1, Math.min(eventFrames[i], eventFrames[i] * (currentTimeSecs - eStartSecs[i]) / (eEndSecs[i] - eStartSecs[i] + 1) ) ));
createPopup(url, 'zmEvent', 'event', monitorWidth[eMonId[i]], monitorHeight[eMonId[i]]);
}
function zoom(monId,scale)
{
var lastZoomMonPriorScale=monitorZoomScale[monId];
monitorZoomScale[monId] *= scale;
if(redrawScreen()==0) // failure here is probably because we zoomed too far
{
monitorZoomScale[monId]=lastZoomMonPriorScale;
alert("You can't zoom that far -- rolling back");
redrawScreen(); // put things back and hope it works
}
}
function clickMonitor(event,monId)
{
var pos_x = event.offsetX ? (event.offsetX) : event.pageX - $("Monitor"+monId.toString()).offsetLeft;
var pos_y = event.offsetY ? (event.offsetY) : event.pageY - $("Monitor"+monId.toString()).offsetTop;
if(pos_x < $("Monitor"+monId.toString()).width/4 && pos_y < $("Monitor"+monId.toString()).height/4) zoom(monId,1.15);
else if(pos_x > $("Monitor"+monId.toString()).width * 3/4 && pos_y < $("Monitor"+monId.toString()).height/4) zoom(monId,1/1.15);
else showOneMonitor(monId);
return;
}
// >>>>>>>>> Initialization that runs on window load by being at the bottom
drawGraph();
setSpeed(speedIndex);
setFit(fitMode); // will redraw
setLive(liveMode); // will redraw
window.addEventListener("resize",redrawScreen);
</script>
</div>
<p id="fps">evaluating fps</p>
</div>
</body>