Merge branch 'master' of github.com:ZoneMinder/zoneminder

This commit is contained in:
Isaac Connor 2021-11-11 13:58:56 -05:00
commit 6d30f5431b
41 changed files with 398 additions and 282 deletions

View File

@ -4,7 +4,7 @@
web/api/lib web/api/lib
web/includes/csrf/ web/includes/csrf/
web/js/videojs.zoomrotate.js web/js/videojs.zoomrotate.js
web/skins/classic/js/bootstrap.js web/skins/classic/js/bootstrap-4.5.0.js
web/skins/classic/js/chosen web/skins/classic/js/chosen
web/skins/classic/js/dateTimePicker web/skins/classic/js/dateTimePicker
web/skins/classic/js/jquery-*.js web/skins/classic/js/jquery-*.js

View File

@ -255,7 +255,7 @@ void zmDbQueue::process() {
Logger *log = Logger::fetch(); Logger *log = Logger::fetch();
Logger::Level db_level = log->databaseLevel(); Logger::Level db_level = log->databaseLevel();
log->databaseLevel(Logger::NOLOG); log->databaseLevel(Logger::NOLOG);
Warning("db queue size has grown larger than 10 entries"); Warning("db queue size has grown larger %zu than 10 entries", mQueue.size());
log->databaseLevel(db_level); log->databaseLevel(db_level);
} }
std::string sql = mQueue.front(); std::string sql = mQueue.front();

View File

@ -60,8 +60,8 @@ Event::Event(
//snapshit_file(), //snapshit_file(),
//alarm_file(""), //alarm_file(""),
videoStore(nullptr), videoStore(nullptr),
//video_name(""),
//video_file(""), //video_file(""),
//video_path(""),
last_db_frame(0), last_db_frame(0),
have_video_keyframe(false), have_video_keyframe(false),
//scheme //scheme
@ -104,6 +104,13 @@ Event::Event(
// Copy it in case opening the mp4 doesn't work we can set it to another value // Copy it in case opening the mp4 doesn't work we can set it to another value
save_jpegs = monitor->GetOptSaveJPEGs(); save_jpegs = monitor->GetOptSaveJPEGs();
Storage *storage = monitor->getStorage(); Storage *storage = monitor->getStorage();
if (monitor->GetOptVideoWriter() != 0) {
container = monitor->OutputContainer();
if ( container == "auto" || container == "" ) {
container = "mp4";
}
video_incomplete_file = "incomplete."+container;
}
std::string sql = stringtf( std::string sql = stringtf(
"INSERT INTO `Events` " "INSERT INTO `Events` "
@ -120,7 +127,7 @@ Event::Event(
state_id, state_id,
monitor->getOrientation(), monitor->getOrientation(),
0, 0,
"", video_incomplete_file.c_str(),
save_jpegs, save_jpegs,
storage->SchemeString().c_str() storage->SchemeString().c_str()
); );
@ -134,7 +141,7 @@ Event::Event(
if (monitor->ServerId()) if (monitor->ServerId())
sql += stringtf(" AND ServerId=%u", monitor->ServerId()); sql += stringtf(" AND ServerId=%u", monitor->ServerId());
Debug(1, "%s", sql.c_str()); delete storage;
storage = nullptr; storage = nullptr;
MYSQL_RES *result = zmDbFetch(sql); MYSQL_RES *result = zmDbFetch(sql);
@ -178,24 +185,16 @@ Event::Event(
} // end if ! setPath(Storage) } // end if ! setPath(Storage)
Debug(1, "Using storage area at %s", path.c_str()); Debug(1, "Using storage area at %s", path.c_str());
video_name = "";
snapshot_file = path + "/snapshot.jpg"; snapshot_file = path + "/snapshot.jpg";
alarm_file = path + "/alarm.jpg"; alarm_file = path + "/alarm.jpg";
/* Save as video */ video_incomplete_path = path + "/" + video_incomplete_file;
if (monitor->GetOptVideoWriter() != 0) { if (monitor->GetOptVideoWriter() != 0) {
std::string container = monitor->OutputContainer(); /* Save as video */
if ( container == "auto" || container == "" ) {
container = "mp4";
}
video_name = stringtf("%" PRIu64 "-%s.%s", id, "video", container.c_str());
video_file = path + "/" + video_name;
Debug(1, "Writing video file to %s", video_file.c_str());
videoStore = new VideoStore( videoStore = new VideoStore(
video_file.c_str(), video_incomplete_path.c_str(),
container.c_str(), container.c_str(),
monitor->GetVideoStream(), monitor->GetVideoStream(),
monitor->GetVideoCodecContext(), monitor->GetVideoCodecContext(),
@ -213,10 +212,13 @@ Event::Event(
zmDbDo(sql); zmDbDo(sql);
} }
} else { } else {
sql = stringtf("UPDATE Events SET Videoed=1, DefaultVideo = '%s' WHERE Id=%" PRIu64, video_name.c_str(), id); std::string codec = videoStore->get_codec();
zmDbDo(sql); video_file = stringtf("%" PRIu64 "-%s.%s.%s", id, "video", codec.c_str(), container.c_str());
video_path = path + "/" + video_file;
Debug(1, "Video file is %s", video_file.c_str());
} }
} // end if GetOptVideoWriter } // end if GetOptVideoWriter
delete storage;
} }
Event::~Event() { Event::~Event() {
@ -227,6 +229,14 @@ Event::~Event() {
Debug(4, "Deleting video store"); Debug(4, "Deleting video store");
delete videoStore; delete videoStore;
videoStore = nullptr; videoStore = nullptr;
int result = rename(video_incomplete_path.c_str(), video_path.c_str());
if (result == 0) {
Debug(1, "File successfully renamed");
} else {
Error("Failed renaming %s to %s", video_incomplete_path.c_str(), video_path.c_str());
// So that we don't update the event record
video_file = video_incomplete_file;
}
} }
// endtime is set in AddFrame, so SHOULD be set to the value of the last frame timestamp. // endtime is set in AddFrame, so SHOULD be set to the value of the last frame timestamp.
@ -245,21 +255,23 @@ Event::~Event() {
} }
std::string sql = stringtf( std::string sql = stringtf(
"UPDATE Events SET Name='%s%" PRIu64 "', EndDateTime = from_unixtime(%ld), Length = %.2f, Frames = %d, AlarmFrames = %d, TotScore = %d, AvgScore = %d, MaxScore = %d WHERE Id = %" PRIu64 " AND Name='New Event'", "UPDATE Events SET Name='%s%" PRIu64 "', EndDateTime = from_unixtime(%ld), Length = %.2f, Frames = %d, AlarmFrames = %d, TotScore = %d, AvgScore = %d, MaxScore = %d, DefaultVideo='%s' WHERE Id = %" PRIu64 " AND Name='New Event'",
monitor->EventPrefix(), id, std::chrono::system_clock::to_time_t(end_time), monitor->EventPrefix(), id, std::chrono::system_clock::to_time_t(end_time),
delta_time.count(), delta_time.count(),
frames, alarm_frames, frames, alarm_frames,
tot_score, static_cast<uint32>(alarm_frames ? (tot_score / alarm_frames) : 0), max_score, tot_score, static_cast<uint32>(alarm_frames ? (tot_score / alarm_frames) : 0), max_score,
video_file.c_str(), // defaults to ""
id); id);
if (!zmDbDoUpdate(sql)) { if (!zmDbDoUpdate(sql)) {
// Name might have been changed during recording, so just do the update without changing the name. // Name might have been changed during recording, so just do the update without changing the name.
sql = stringtf( sql = stringtf(
"UPDATE Events SET EndDateTime = from_unixtime(%ld), Length = %.2f, Frames = %d, AlarmFrames = %d, TotScore = %d, AvgScore = %d, MaxScore = %d WHERE Id = %" PRIu64, "UPDATE Events SET EndDateTime = from_unixtime(%ld), Length = %.2f, Frames = %d, AlarmFrames = %d, TotScore = %d, AvgScore = %d, MaxScore = %d, DefaultVideo='%s' WHERE Id = %" PRIu64,
std::chrono::system_clock::to_time_t(end_time), std::chrono::system_clock::to_time_t(end_time),
delta_time.count(), delta_time.count(),
frames, alarm_frames, frames, alarm_frames,
tot_score, static_cast<uint32>(alarm_frames ? (tot_score / alarm_frames) : 0), max_score, tot_score, static_cast<uint32>(alarm_frames ? (tot_score / alarm_frames) : 0), max_score,
video_file.c_str(), // defaults to ""
id); id);
zmDbDoUpdate(sql); zmDbDoUpdate(sql);
} // end if no changed rows due to Name change during recording } // end if no changed rows due to Name change during recording
@ -326,12 +338,12 @@ void Event::updateNotes(const StringSetMap &newNoteSetMap) {
if (newNoteSet.size() > 0) { if (newNoteSet.size() > 0) {
StringSetMap::iterator noteSetMapIter = noteSetMap.find(newNoteGroup); StringSetMap::iterator noteSetMapIter = noteSetMap.find(newNoteGroup);
if (noteSetMapIter == noteSetMap.end()) { if (noteSetMapIter == noteSetMap.end()) {
//Info( "Can't find note group %s, copying %d strings", newNoteGroup.c_str(), newNoteSet.size() ); //Debug(3, "Can't find note group %s, copying %d strings", newNoteGroup.c_str(), newNoteSet.size());
noteSetMap.insert(StringSetMap::value_type(newNoteGroup, newNoteSet)); noteSetMap.insert(StringSetMap::value_type(newNoteGroup, newNoteSet));
update = true; update = true;
} else { } else {
StringSet &noteSet = noteSetMapIter->second; StringSet &noteSet = noteSetMapIter->second;
//Info( "Found note group %s, got %d strings", newNoteGroup.c_str(), newNoteSet.size() ); //Debug(3, "Found note group %s, got %d strings", newNoteGroup.c_str(), newNoteSet.size());
for (StringSet::const_iterator newNoteSetIter = newNoteSet.begin(); for (StringSet::const_iterator newNoteSetIter = newNoteSet.begin();
newNoteSetIter != newNoteSet.end(); newNoteSetIter != newNoteSet.end();
++newNoteSetIter) { ++newNoteSetIter) {
@ -479,7 +491,7 @@ void Event::AddFrame(Image *image,
Debug(1, "Writing snapshot"); Debug(1, "Writing snapshot");
WriteFrameImage(image, timestamp, snapshot_file.c_str()); WriteFrameImage(image, timestamp, snapshot_file.c_str());
} else { } else {
Debug(1, "Not Writing snapshot"); Debug(1, "Not Writing snapshot because score %d > max %d", score, max_score);
} }
// We are writing an Alarm frame // We are writing an Alarm frame
@ -491,7 +503,7 @@ void Event::AddFrame(Image *image,
Debug(1, "Writing alarm image"); Debug(1, "Writing alarm image");
WriteFrameImage(image, timestamp, alarm_file.c_str()); WriteFrameImage(image, timestamp, alarm_file.c_str());
} else { } else {
Debug(1, "Not Writing alarm image"); Debug(3, "Not Writing alarm image because alarm frame already written");
} }
if (alarm_image and (save_jpegs & 2)) { if (alarm_image and (save_jpegs & 2)) {

View File

@ -84,8 +84,13 @@ class Event {
std::string alarm_file; std::string alarm_file;
VideoStore *videoStore; VideoStore *videoStore;
std::string video_name; std::string container;
std::string codec;
std::string video_file; std::string video_file;
std::string video_path;
std::string video_incomplete_file;
std::string video_incomplete_path;
int last_db_frame; int last_db_frame;
bool have_video_keyframe; // a flag to tell us if we have had a video keyframe when writing an mp4. The first frame SHOULD be a video keyframe. bool have_video_keyframe; // a flag to tell us if we have had a video keyframe when writing an mp4. The first frame SHOULD be a video keyframe.
Storage::Schemes scheme; Storage::Schemes scheme;

View File

@ -141,7 +141,7 @@ bool EventStream::loadEventData(uint64_t event_id) {
event_data->storage_id = dbrow[1] ? atoi(dbrow[1]) : 0; event_data->storage_id = dbrow[1] ? atoi(dbrow[1]) : 0;
event_data->frame_count = dbrow[2] == nullptr ? 0 : atoi(dbrow[2]); event_data->frame_count = dbrow[2] == nullptr ? 0 : atoi(dbrow[2]);
event_data->start_time = SystemTimePoint(Seconds(atoi(dbrow[3]))); event_data->start_time = SystemTimePoint(Seconds(atoi(dbrow[3])));
event_data->end_time = dbrow[4] ? SystemTimePoint(Seconds(atoi(dbrow[4]))) : SystemTimePoint(); event_data->end_time = dbrow[4] ? SystemTimePoint(Seconds(atoi(dbrow[4]))) : std::chrono::system_clock::now();
event_data->duration = std::chrono::duration_cast<Microseconds>(event_data->end_time - event_data->start_time); event_data->duration = std::chrono::duration_cast<Microseconds>(event_data->end_time - event_data->start_time);
event_data->frames_duration = event_data->frames_duration =
std::chrono::duration_cast<Microseconds>(dbrow[5] ? FPSeconds(atof(dbrow[5])) : FPSeconds(0.0)); std::chrono::duration_cast<Microseconds>(dbrow[5] ? FPSeconds(atof(dbrow[5])) : FPSeconds(0.0));

View File

@ -143,8 +143,8 @@ bool Fifo::writePacket(std::string filename, const ZMPacket &packet) {
bool Fifo::write(uint8_t *data, size_t bytes, int64_t pts) { bool Fifo::write(uint8_t *data, size_t bytes, int64_t pts) {
if (!(outfile or open())) return false; if (!(outfile or open())) return false;
// Going to write a brief header // Going to write a brief header
Debug(1, "Writing header ZM %lu %" PRId64, bytes, pts); Debug(1, "Writing header ZM %zu %" PRId64, bytes, pts);
if ( fprintf(outfile, "ZM %lu %" PRId64 "\n", bytes, pts) < 0 ) { if (fprintf(outfile, "ZM %zu %" PRId64 "\n", bytes, pts) < 0) {
if (errno != EAGAIN) { if (errno != EAGAIN) {
Error("Problem during writing: %s", strerror(errno)); Error("Problem during writing: %s", strerror(errno));
} else { } else {

View File

@ -270,7 +270,6 @@ int Image::PopulateFrame(AVFrame *frame) {
frame->width = width; frame->width = width;
frame->height = height; frame->height = height;
frame->format = imagePixFormat; frame->format = imagePixFormat;
Debug(1, "PopulateFrame: width %d height %d linesize %d colours %d imagesize %d", width, height, linesize, colours, size);
zm_dump_video_frame(frame, "Image.Populate(frame)"); zm_dump_video_frame(frame, "Image.Populate(frame)");
return 1; return 1;
} // int Image::PopulateFrame(AVFrame *frame) } // int Image::PopulateFrame(AVFrame *frame)

View File

@ -155,7 +155,7 @@ bool Monitor::MonitorLink::connect() {
mem_size = sizeof(SharedData) + sizeof(TriggerData); mem_size = sizeof(SharedData) + sizeof(TriggerData);
Debug(1, "link.mem.size=%jd", mem_size); Debug(1, "link.mem.size=%jd", static_cast<intmax_t>(mem_size));
#if ZM_MEM_MAPPED #if ZM_MEM_MAPPED
map_fd = open(mem_file.c_str(), O_RDWR, (mode_t)0600); map_fd = open(mem_file.c_str(), O_RDWR, (mode_t)0600);
if (map_fd < 0) { if (map_fd < 0) {
@ -182,14 +182,14 @@ bool Monitor::MonitorLink::connect() {
disconnect(); disconnect();
return false; return false;
} else if (map_stat.st_size < mem_size) { } else if (map_stat.st_size < mem_size) {
Error("Got unexpected memory map file size %ld, expected %jd", map_stat.st_size, mem_size); Error("Got unexpected memory map file size %ld, expected %jd", map_stat.st_size, static_cast<intmax_t>(mem_size));
disconnect(); disconnect();
return false; return false;
} }
mem_ptr = (unsigned char *)mmap(nullptr, mem_size, PROT_READ|PROT_WRITE, MAP_SHARED, map_fd, 0); mem_ptr = (unsigned char *)mmap(nullptr, mem_size, PROT_READ|PROT_WRITE, MAP_SHARED, map_fd, 0);
if (mem_ptr == MAP_FAILED) { if (mem_ptr == MAP_FAILED) {
Error("Can't map file %s (%jd bytes) to memory: %s", mem_file.c_str(), mem_size, strerror(errno)); Error("Can't map file %s (%jd bytes) to memory: %s", mem_file.c_str(), static_cast<intmax_t>(mem_size), strerror(errno));
disconnect(); disconnect();
return false; return false;
} }
@ -947,7 +947,7 @@ bool Monitor::connect() {
map_fd = -1; map_fd = -1;
return false; return false;
} else { } else {
Error("Got unexpected memory map file size %ld, expected %jd", map_stat.st_size, mem_size); Error("Got unexpected memory map file size %ld, expected %jd", map_stat.st_size, static_cast<intmax_t>(mem_size));
close(map_fd); close(map_fd);
map_fd = -1; map_fd = -1;
return false; return false;
@ -959,18 +959,18 @@ bool Monitor::connect() {
mem_ptr = (unsigned char *)mmap(nullptr, mem_size, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_LOCKED, map_fd, 0); mem_ptr = (unsigned char *)mmap(nullptr, mem_size, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_LOCKED, map_fd, 0);
if (mem_ptr == MAP_FAILED) { if (mem_ptr == MAP_FAILED) {
if (errno == EAGAIN) { if (errno == EAGAIN) {
Debug(1, "Unable to map file %s (%jd bytes) to locked memory, trying unlocked", mem_file.c_str(), mem_size); Debug(1, "Unable to map file %s (%jd bytes) to locked memory, trying unlocked", mem_file.c_str(), static_cast<intmax_t>(mem_size));
#endif #endif
mem_ptr = (unsigned char *)mmap(nullptr, mem_size, PROT_READ|PROT_WRITE, MAP_SHARED, map_fd, 0); mem_ptr = (unsigned char *)mmap(nullptr, mem_size, PROT_READ|PROT_WRITE, MAP_SHARED, map_fd, 0);
Debug(1, "Mapped file %s (%jd bytes) to unlocked memory", mem_file.c_str(), mem_size); Debug(1, "Mapped file %s (%jd bytes) to unlocked memory", mem_file.c_str(), static_cast<intmax_t>(mem_size));
#ifdef MAP_LOCKED #ifdef MAP_LOCKED
} else { } else {
Error("Unable to map file %s (%jd bytes) to locked memory (%s)", mem_file.c_str(), mem_size, strerror(errno)); Error("Unable to map file %s (%jd bytes) to locked memory (%s)", mem_file.c_str(), static_cast<intmax_t>(mem_size), strerror(errno));
} }
} }
#endif #endif
if ((mem_ptr == MAP_FAILED) or (mem_ptr == nullptr)) { if ((mem_ptr == MAP_FAILED) or (mem_ptr == nullptr)) {
Error("Can't map file %s (%jd bytes) to memory: %s(%d)", mem_file.c_str(), mem_size, strerror(errno), errno); Error("Can't map file %s (%jd bytes) to memory: %s(%d)", mem_file.c_str(), static_cast<intmax_t>(mem_size), strerror(errno), errno);
close(map_fd); close(map_fd);
map_fd = -1; map_fd = -1;
mem_ptr = nullptr; mem_ptr = nullptr;
@ -1656,7 +1656,7 @@ void Monitor::CheckAction() {
} }
} }
void Monitor::UpdateCaptureFPS() { void Monitor::UpdateFPS() {
if ( fps_report_interval and if ( fps_report_interval and
( (
!(image_count%fps_report_interval) !(image_count%fps_report_interval)
@ -1675,82 +1675,35 @@ void Monitor::UpdateCaptureFPS() {
uint32 new_camera_bytes = camera->Bytes(); uint32 new_camera_bytes = camera->Bytes();
uint32 new_capture_bandwidth = uint32 new_capture_bandwidth =
static_cast<uint32>((new_camera_bytes - last_camera_bytes) / elapsed.count()); static_cast<uint32>((new_camera_bytes - last_camera_bytes) / elapsed.count());
last_camera_bytes = new_camera_bytes; double new_analysis_fps = (motion_frame_count - last_motion_frame_count) / elapsed.count();
Debug(4, "%s: %d - last %d = %d now:%lf, last %lf, elapsed %lf = %lffps", Debug(4, "FPS: capture count %d - last capture count %d = %d now:%lf, last %lf, elapsed %lf = capture: %lf fps analysis: %lf fps",
"Capturing",
image_count, image_count,
last_capture_image_count, last_capture_image_count,
image_count - last_capture_image_count, image_count - last_capture_image_count,
FPSeconds(now.time_since_epoch()).count(), FPSeconds(now.time_since_epoch()).count(),
FPSeconds(last_analysis_fps_time.time_since_epoch()).count(), FPSeconds(last_fps_time.time_since_epoch()).count(),
elapsed.count(), elapsed.count(),
new_capture_fps); new_capture_fps,
new_analysis_fps);
Info("%s: %d - Capturing at %.2lf fps, capturing bandwidth %ubytes/sec", Info("%s: %d - Capturing at %.2lf fps, capturing bandwidth %ubytes/sec Analysing at %.2lf fps",
name.c_str(), image_count, new_capture_fps, new_capture_bandwidth); name.c_str(), image_count, new_capture_fps, new_capture_bandwidth, new_analysis_fps);
shared_data->capture_fps = new_capture_fps; shared_data->capture_fps = new_capture_fps;
last_fps_time = now; last_fps_time = now;
last_capture_image_count = image_count; last_capture_image_count = image_count;
shared_data->analysis_fps = new_analysis_fps;
last_motion_frame_count = motion_frame_count;
last_camera_bytes = new_camera_bytes;
std::string sql = stringtf( std::string sql = stringtf(
"UPDATE LOW_PRIORITY Monitor_Status SET CaptureFPS = %.2lf, CaptureBandwidth=%u WHERE MonitorId=%u", "UPDATE LOW_PRIORITY Monitor_Status SET CaptureFPS = %.2lf, CaptureBandwidth=%u, AnalysisFPS = %.2lf WHERE MonitorId=%u",
new_capture_fps, new_capture_bandwidth, id); new_capture_fps, new_capture_bandwidth, new_analysis_fps, id);
dbQueue.push(std::move(sql)); dbQueue.push(std::move(sql));
} // now != last_fps_time } // now != last_fps_time
} // end if report fps } // end if report fps
} // void Monitor::UpdateCaptureFPS() } // void Monitor::UpdateFPS()
void Monitor::UpdateAnalysisFPS() {
Debug(1, "analysis_image_count(%d) motion_count(%d) fps_report_interval(%d) mod%d",
analysis_image_count, motion_frame_count, fps_report_interval,
((analysis_image_count && fps_report_interval) ? !(analysis_image_count%fps_report_interval) : -1 ) );
if (
( analysis_image_count and fps_report_interval and !(analysis_image_count%fps_report_interval) )
or
// In startup do faster updates
( (analysis_image_count < fps_report_interval) and !(analysis_image_count%10) )
) {
SystemTimePoint now = std::chrono::system_clock::now();
FPSeconds elapsed = now - last_analysis_fps_time;
Debug(4, "%s: %d - now: %.2f, last %lf, diff %lf",
name.c_str(),
analysis_image_count,
FPSeconds(now.time_since_epoch()).count(),
FPSeconds(last_analysis_fps_time.time_since_epoch()).count(),
elapsed.count());
if (elapsed > Seconds(1)) {
double new_analysis_fps = (motion_frame_count - last_motion_frame_count) / elapsed.count();
Info("%s: %d - Analysing at %.2lf fps from %d - %d=%d / %lf - %lf = %lf",
name.c_str(),
analysis_image_count,
new_analysis_fps,
motion_frame_count,
last_motion_frame_count,
(motion_frame_count - last_motion_frame_count),
FPSeconds(now.time_since_epoch()).count(),
FPSeconds(last_analysis_fps_time.time_since_epoch()).count(),
elapsed.count());
if (new_analysis_fps != shared_data->analysis_fps) {
shared_data->analysis_fps = new_analysis_fps;
std::string sql = stringtf("UPDATE LOW_PRIORITY Monitor_Status SET AnalysisFPS = %.2lf WHERE MonitorId=%u",
new_analysis_fps, id);
dbQueue.push(std::move(sql));
last_analysis_fps_time = now;
last_motion_frame_count = motion_frame_count;
} else {
Debug(4, "No change in fps");
} // end if change in fps
} // end if at least 1 second has passed since last update
} // end if time to do an update
} // end void Monitor::UpdateAnalysisFPS
// Would be nice if this JUST did analysis // Would be nice if this JUST did analysis
// This idea is that we should be analysing as close to the capture frame as possible. // This idea is that we should be analysing as close to the capture frame as possible.
@ -1909,8 +1862,6 @@ bool Monitor::Analyse() {
Debug(3, "signal and active and modect"); Debug(3, "signal and active and modect");
Event::StringSet zoneSet; Event::StringSet zoneSet;
int motion_score = last_motion_score;
if (analysis_fps_limit) { if (analysis_fps_limit) {
double capture_fps = get_capture_fps(); double capture_fps = get_capture_fps();
motion_frame_skip = capture_fps / analysis_fps_limit; motion_frame_skip = capture_fps / analysis_fps_limit;
@ -1927,7 +1878,7 @@ bool Monitor::Analyse() {
} else { } else {
Debug(1, "Detecting motion on image %d, image %p", snap->image_index, snap->image); Debug(1, "Detecting motion on image %d, image %p", snap->image_index, snap->image);
// Get new score. // Get new score.
motion_score = DetectMotion(*(snap->image), zoneSet); int motion_score = DetectMotion(*(snap->image), zoneSet);
snap->zone_stats.reserve(zones.size()); snap->zone_stats.reserve(zones.size());
for (const Zone &zone : zones) { for (const Zone &zone : zones) {
@ -1939,21 +1890,20 @@ bool Monitor::Analyse() {
Debug(3, "After motion detection, score:%d last_motion_score(%d), new motion score(%d)", Debug(3, "After motion detection, score:%d last_motion_score(%d), new motion score(%d)",
score, last_motion_score, motion_score); score, last_motion_score, motion_score);
motion_frame_count += 1; motion_frame_count += 1;
// Why are we updating the last_motion_score too?
last_motion_score = motion_score; last_motion_score = motion_score;
if (motion_score) {
if (cause.length()) cause += ", ";
cause += MOTION_CAUSE;
noteSetMap[MOTION_CAUSE] = zoneSet;
} // end if motion_score
} }
} else { } else {
Debug(1, "no image so skipping motion detection"); Debug(1, "no image so skipping motion detection");
} // end if has image } // end if has image
} else { } else {
Debug(1, "Skipped motion detection last motion score was %d", motion_score); Debug(1, "Skipped motion detection last motion score was %d", last_motion_score);
} }
if (motion_score) { score += last_motion_score;
score += motion_score;
if (cause.length()) cause += ", ";
cause += MOTION_CAUSE;
noteSetMap[MOTION_CAUSE] = zoneSet;
} // end if motion_score
} else { } else {
Debug(1, "Not Active(%d) enabled %d active %d doing motion detection: %d", Debug(1, "Not Active(%d) enabled %d active %d doing motion detection: %d",
Active(), enabled, shared_data->active, Active(), enabled, shared_data->active,
@ -1966,7 +1916,7 @@ bool Monitor::Analyse() {
if (event) { if (event) {
Debug(2, "Have event %" PRIu64 " in record", event->Id()); Debug(2, "Have event %" PRIu64 " in record", event->Id());
if (section_length != Seconds(0) && (timestamp - GetVideoWriterStartTime() >= section_length) if (section_length != Seconds(0) && (timestamp - event->StartTime() >= section_length)
&& ((function == MOCORD && event_close_mode != CLOSE_TIME) && ((function == MOCORD && event_close_mode != CLOSE_TIME)
|| (function == RECORD && event_close_mode == CLOSE_TIME) || (function == RECORD && event_close_mode == CLOSE_TIME)
|| std::chrono::duration_cast<Seconds>(timestamp.time_since_epoch()) % section_length == Seconds(0))) { || std::chrono::duration_cast<Seconds>(timestamp.time_since_epoch()) % section_length == Seconds(0))) {
@ -1975,8 +1925,8 @@ bool Monitor::Analyse() {
image_count, image_count,
event->Id(), event->Id(),
static_cast<int64>(std::chrono::duration_cast<Seconds>(timestamp.time_since_epoch()).count()), static_cast<int64>(std::chrono::duration_cast<Seconds>(timestamp.time_since_epoch()).count()),
static_cast<int64>(std::chrono::duration_cast<Seconds>(GetVideoWriterStartTime().time_since_epoch()).count()), static_cast<int64>(std::chrono::duration_cast<Seconds>(event->StartTime().time_since_epoch()).count()),
static_cast<int64>(std::chrono::duration_cast<Seconds>(timestamp - GetVideoWriterStartTime()).count()), static_cast<int64>(std::chrono::duration_cast<Seconds>(timestamp - event->StartTime()).count()),
static_cast<int64>(Seconds(section_length).count())); static_cast<int64>(Seconds(section_length).count()));
closeEvent(); closeEvent();
} // end if section_length } // end if section_length
@ -2054,26 +2004,27 @@ bool Monitor::Analyse() {
} // end if ! event } // end if ! event
} // end if RECORDING } // end if RECORDING
if (score and (function == MODECT or function == NODECT)) { if (score and (function != MONITOR)) {
if ((state == IDLE) || (state == TAPE) || (state == PREALARM)) { if ((state == IDLE) || (state == TAPE) || (state == PREALARM)) {
// If we should end then previous continuous event and start a new non-continuous event // If we should end then previous continuous event and start a new non-continuous event
if (event && event->Frames() if (event && event->Frames()
&& !event->AlarmFrames() && !event->AlarmFrames()
&& event_close_mode == CLOSE_ALARM && (event_close_mode == CLOSE_ALARM)
&& timestamp - GetVideoWriterStartTime() >= min_section_length && ((timestamp - event->StartTime()) >= min_section_length)
&& (!pre_event_count || Event::PreAlarmCount() >= alarm_frame_count - 1)) { && ((!pre_event_count) || (Event::PreAlarmCount() >= alarm_frame_count - 1))) {
Info("%s: %03d - Closing event %" PRIu64 ", continuous end, alarm begins", Info("%s: %03d - Closing event %" PRIu64 ", continuous end, alarm begins",
name.c_str(), image_count, event->Id()); name.c_str(), image_count, event->Id());
closeEvent(); closeEvent();
} else if (event) { } 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 // 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
Debug(3, Debug(3,
"pre_alarm_count in event %d, event frames %d, alarm frames %d event length %" PRIi64 " >=? %" PRIi64 " min", "pre_alarm_count in event %d of %d, event frames %d, alarm frames %d event length %" PRIi64 " >=? %" PRIi64 " min close mode is ALARM? %d",
Event::PreAlarmCount(), Event::PreAlarmCount(), pre_event_count,
event->Frames(), event->Frames(),
event->AlarmFrames(), event->AlarmFrames(),
static_cast<int64>(std::chrono::duration_cast<Seconds>(timestamp - GetVideoWriterStartTime()).count()), static_cast<int64>(std::chrono::duration_cast<Seconds>(timestamp - event->StartTime()).count()),
static_cast<int64>(Seconds(min_section_length).count())); static_cast<int64>(Seconds(min_section_length).count()),
(event_close_mode == CLOSE_ALARM));
} }
if ((!pre_event_count) || (Event::PreAlarmCount() >= alarm_frame_count-1)) { if ((!pre_event_count) || (Event::PreAlarmCount() >= alarm_frame_count-1)) {
// lets construct alarm cause. It will contain cause + names of zones alarmed // lets construct alarm cause. It will contain cause + names of zones alarmed
@ -2170,8 +2121,10 @@ bool Monitor::Analyse() {
Info("%s: %03d - Gone into alert state", name.c_str(), analysis_image_count); Info("%s: %03d - Gone into alert state", name.c_str(), analysis_image_count);
shared_data->state = state = ALERT; shared_data->state = state = ALERT;
} else if (state == ALERT) { } else if (state == ALERT) {
if (analysis_image_count - last_alarm_count > post_event_count if (
&& timestamp - GetVideoWriterStartTime() >= min_section_length) { ((analysis_image_count - last_alarm_count) > post_event_count)
&&
((timestamp - event->StartTime()) >= min_section_length)) {
Info("%s: %03d - Left alarm state (%" PRIu64 ") - %d(%d) images", Info("%s: %03d - Left alarm state (%" PRIu64 ") - %d(%d) images",
name.c_str(), analysis_image_count, event->Id(), event->Frames(), event->AlarmFrames()); name.c_str(), analysis_image_count, event->Id(), event->Frames(), event->AlarmFrames());
//if ( function != MOCORD || event_close_mode == CLOSE_ALARM || event->Cause() == SIGNAL_CAUSE ) //if ( function != MOCORD || event_close_mode == CLOSE_ALARM || event->Cause() == SIGNAL_CAUSE )
@ -2189,7 +2142,8 @@ bool Monitor::Analyse() {
shared_data->state = state = ((function != MOCORD) ? IDLE : TAPE); shared_data->state = state = ((function != MOCORD) ? IDLE : TAPE);
} else { } else {
Debug(1, Debug(1,
"State %s because image_count(%d)-last_alarm_count(%d) > post_event_count(%d) and timestamp.tv_sec(%" PRIi64 ") - recording.tv_src(%" PRIi64 ") >= min_section_length(%" PRIi64 ")", "State %d %s because analysis_image_count(%d)-last_alarm_count(%d) > post_event_count(%d) and timestamp.tv_sec(%" PRIi64 ") - recording.tv_src(%" PRIi64 ") >= min_section_length(%" PRIi64 ")",
state,
State_Strings[state].c_str(), State_Strings[state].c_str(),
analysis_image_count, analysis_image_count,
last_alarm_count, last_alarm_count,
@ -2208,18 +2162,15 @@ bool Monitor::Analyse() {
// Generate analysis images if necessary // Generate analysis images if necessary
if ((savejpegs > 1) and snap->image) { if ((savejpegs > 1) and snap->image) {
for (const Zone &zone : zones) { for (const Zone &zone : zones) {
if (zone.Alarmed()) { if (zone.Alarmed() and zone.AlarmImage()) {
if (zone.AlarmImage()) {
if (!snap->analysis_image) if (!snap->analysis_image)
snap->analysis_image = new Image(*(snap->image)); snap->analysis_image = new Image(*(snap->image));
snap->analysis_image->Overlay(*(zone.AlarmImage())); snap->analysis_image->Overlay(*(zone.AlarmImage()));
}
} // end if zone is alarmed } // end if zone is alarmed
} // end foreach zone } // end foreach zone
} // end if savejpegs } // end if savejpegs
// incremement pre alarm image count // incremement pre alarm image count
//have_pre_alarmed_frames ++;
Event::AddPreAlarmFrame(snap->image, timestamp, score, nullptr); Event::AddPreAlarmFrame(snap->image, timestamp, score, nullptr);
} else if (state == ALARM) { } else if (state == ALARM) {
for (const Zone &zone : zones) { for (const Zone &zone : zones) {
@ -2234,7 +2185,7 @@ bool Monitor::Analyse() {
if (event) { if (event) {
if (noteSetMap.size() > 0) if (noteSetMap.size() > 0)
event->updateNotes(noteSetMap); event->updateNotes(noteSetMap);
if (section_length != Seconds(0) && (timestamp - GetVideoWriterStartTime() >= section_length)) { if (section_length != Seconds(0) && (timestamp - event->StartTime() >= section_length)) {
Warning("%s: %03d - event %" PRIu64 ", has exceeded desired section length. %" PRIi64 " - %" PRIi64 " = %" PRIi64 " >= %" PRIi64, Warning("%s: %03d - event %" PRIu64 ", has exceeded desired section length. %" PRIi64 " - %" PRIi64 " = %" PRIi64 " >= %" PRIi64,
name.c_str(), analysis_image_count, event->Id(), name.c_str(), analysis_image_count, event->Id(),
static_cast<int64>(std::chrono::duration_cast<Seconds>(timestamp.time_since_epoch()).count()), static_cast<int64>(std::chrono::duration_cast<Seconds>(timestamp.time_since_epoch()).count()),
@ -2299,8 +2250,6 @@ bool Monitor::Analyse() {
// Only do these if it's a video packet. // Only do these if it's a video packet.
shared_data->last_read_index = snap->image_index; shared_data->last_read_index = snap->image_index;
analysis_image_count++; analysis_image_count++;
if (function == MODECT or function == MOCORD)
UpdateAnalysisFPS();
} }
packetqueue.increment_it(analysis_it); packetqueue.increment_it(analysis_it);
packetqueue.unlock(packet_lock); packetqueue.unlock(packet_lock);
@ -2363,7 +2312,7 @@ void Monitor::ReloadLinkedMonitors(const char *p_linked_monitors) {
while ( 1 ) { while ( 1 ) {
dest_ptr = link_id_str; dest_ptr = link_id_str;
while ( *src_ptr >= '0' && *src_ptr <= '9' ) { while ( *src_ptr >= '0' && *src_ptr <= '9' ) {
if ( (dest_ptr-link_id_str) < (unsigned int)(sizeof(link_id_str)-1) ) { if ( (unsigned int)(dest_ptr-link_id_str) < (unsigned int)(sizeof(link_id_str)-1) ) {
*dest_ptr++ = *src_ptr++; *dest_ptr++ = *src_ptr++;
} else { } else {
break; break;
@ -2585,7 +2534,6 @@ int Monitor::Capture() {
// Will only be queued if there are iterators allocated in the queue. // Will only be queued if there are iterators allocated in the queue.
packetqueue.queuePacket(packet); packetqueue.queuePacket(packet);
UpdateCaptureFPS();
} else { // result == 0 } else { // result == 0
// Question is, do we update last_write_index etc? // Question is, do we update last_write_index etc?
return 0; return 0;
@ -2625,7 +2573,7 @@ bool Monitor::Decode() {
// //
//capture_image = packet->image = new Image(width, height, camera->Colours(), camera->SubpixelOrder()); //capture_image = packet->image = new Image(width, height, camera->Colours(), camera->SubpixelOrder());
int ret = packet->decode(camera->getVideoCodecContext()); int ret = packet->decode(camera->getVideoCodecContext());
if (ret > 0) { if (ret > 0 and !zm_terminate) {
if (packet->in_frame and !packet->image) { if (packet->in_frame and !packet->image) {
packet->image = new Image(camera_width, camera_height, camera->Colours(), camera->SubpixelOrder()); packet->image = new Image(camera_width, camera_height, camera->Colours(), camera->SubpixelOrder());
AVFrame *input_frame = packet->in_frame; AVFrame *input_frame = packet->in_frame;
@ -2795,7 +2743,7 @@ void Monitor::TimestampImage(Image *ts_image, SystemTimePoint ts_time) const {
const char *s_ptr = label_time_text; const char *s_ptr = label_time_text;
char *d_ptr = label_text; char *d_ptr = label_text;
while (*s_ptr && ((d_ptr - label_text) < (unsigned int) sizeof(label_text))) { while (*s_ptr && ((unsigned int)(d_ptr - label_text) < (unsigned int) sizeof(label_text))) {
if ( *s_ptr == config.timestamp_code_char[0] ) { if ( *s_ptr == config.timestamp_code_char[0] ) {
bool found_macro = false; bool found_macro = false;
switch ( *(s_ptr+1) ) { switch ( *(s_ptr+1) ) {
@ -2811,7 +2759,7 @@ void Monitor::TimestampImage(Image *ts_image, SystemTimePoint ts_time) const {
typedef std::chrono::duration<int64, std::centi> Centiseconds; typedef std::chrono::duration<int64, std::centi> Centiseconds;
Centiseconds centi_sec = std::chrono::duration_cast<Centiseconds>( Centiseconds centi_sec = std::chrono::duration_cast<Centiseconds>(
ts_time.time_since_epoch() - std::chrono::duration_cast<Seconds>(ts_time.time_since_epoch())); ts_time.time_since_epoch() - std::chrono::duration_cast<Seconds>(ts_time.time_since_epoch()));
d_ptr += snprintf(d_ptr, sizeof(label_text) - (d_ptr - label_text), "%02ld", centi_sec.count()); d_ptr += snprintf(d_ptr, sizeof(label_text) - (d_ptr - label_text), "%02lld", static_cast<long long int>(centi_sec.count()));
found_macro = true; found_macro = true;
break; break;
} }

View File

@ -100,7 +100,7 @@ public:
} Deinterlace; } Deinterlace;
typedef enum { typedef enum {
UNKNOWN=-1, UNKNOWN,
IDLE, IDLE,
PREALARM, PREALARM,
ALARM, ALARM,
@ -546,8 +546,7 @@ public:
unsigned int GetLastWriteIndex() const; unsigned int GetLastWriteIndex() const;
uint64_t GetLastEventId() const; uint64_t GetLastEventId() const;
double GetFPS() const; double GetFPS() const;
void UpdateAnalysisFPS(); void UpdateFPS();
void UpdateCaptureFPS();
void ForceAlarmOn( int force_score, const char *force_case, const char *force_text="" ); void ForceAlarmOn( int force_score, const char *force_case, const char *force_text="" );
void ForceAlarmOff(); void ForceAlarmOff();
void CancelForced(); void CancelForced();

View File

@ -229,6 +229,7 @@ void MonitorStream::processCommand(const CmdMsg *msg) {
break; break;
case CMD_QUIT : case CMD_QUIT :
Info("User initiated exit - CMD_QUIT"); Info("User initiated exit - CMD_QUIT");
zm_terminate = true;
break; break;
case CMD_QUERY : case CMD_QUERY :
Debug(1, "Got QUERY command, sending STATUS"); Debug(1, "Got QUERY command, sending STATUS");
@ -315,16 +316,6 @@ void MonitorStream::processCommand(const CmdMsg *msg) {
} }
} }
Debug(2, "Number of bytes sent to (%s): (%d)", rem_addr.sun_path, nbytes); Debug(2, "Number of bytes sent to (%s): (%d)", rem_addr.sun_path, nbytes);
// quit after sending a status, if this was a quit request
if ( (MsgCommand)msg->msg_data[0] == CMD_QUIT ) {
zm_terminate = true;
Debug(2, "Quitting");
return;
}
//Debug(2,"Updating framerate");
//updateFrameRate(monitor->GetFPS());
} // end void MonitorStream::processCommand(const CmdMsg *msg) } // end void MonitorStream::processCommand(const CmdMsg *msg)
bool MonitorStream::sendFrame(const std::string &filepath, SystemTimePoint timestamp) { bool MonitorStream::sendFrame(const std::string &filepath, SystemTimePoint timestamp) {

View File

@ -209,7 +209,7 @@ void PacketQueue::clearPackets(const std::shared_ptr<ZMPacket> &add_packet) {
--it; --it;
} }
} }
Debug(1, "Tail count is %d, queue size is %lu", tail_count, pktQueue.size()); Debug(1, "Tail count is %d, queue size is %zu", tail_count, pktQueue.size());
if (!keep_keyframes) { if (!keep_keyframes) {
// If not doing passthrough, we don't care about starting with a keyframe so logic is simpler // If not doing passthrough, we don't care about starting with a keyframe so logic is simpler

View File

@ -46,6 +46,7 @@ RETSIGTYPE zm_die_handler(int signal, siginfo_t * info, void *context)
RETSIGTYPE zm_die_handler(int signal) RETSIGTYPE zm_die_handler(int signal)
#endif #endif
{ {
zm_terminate = true;
Error("Got signal %d (%s), crashing", signal, strsignal(signal)); Error("Got signal %d (%s), crashing", signal, strsignal(signal));
#if (defined(__i386__) || defined(__x86_64__)) #if (defined(__i386__) || defined(__x86_64__))
// Get more information if available // Get more information if available

View File

@ -619,7 +619,8 @@ VideoStore::~VideoStore() {
Debug(1, "Writing trailer"); Debug(1, "Writing trailer");
/* Write the trailer before close */ /* Write the trailer before close */
if (int rc = av_write_trailer(oc)) { int rc;
if ((rc = av_write_trailer(oc)) < 0) {
Error("Error writing trailer %s", av_err2str(rc)); Error("Error writing trailer %s", av_err2str(rc));
} else { } else {
Debug(3, "Success Writing trailer"); Debug(3, "Success Writing trailer");
@ -629,7 +630,7 @@ VideoStore::~VideoStore() {
if (!(out_format->flags & AVFMT_NOFILE)) { if (!(out_format->flags & AVFMT_NOFILE)) {
/* Close the out file. */ /* Close the out file. */
Debug(4, "Closing"); Debug(4, "Closing");
if (int rc = avio_close(oc->pb)) { if ((rc = avio_close(oc->pb)) < 0) {
Error("Error closing avio %s", av_err2str(rc)); Error("Error closing avio %s", av_err2str(rc));
} }
} else { } else {

View File

@ -111,6 +111,13 @@ class VideoStore {
int writePacket(const std::shared_ptr<ZMPacket> &pkt); int writePacket(const std::shared_ptr<ZMPacket> &pkt);
int write_packets(PacketQueue &queue); int write_packets(PacketQueue &queue);
void flush_codecs(); void flush_codecs();
const char *get_codec() {
if (chosen_codec_data)
return chosen_codec_data->codec_codec;
if (video_out_stream)
return avcodec_get_name(video_out_stream->codecpar->codec_id);
return "";
}
}; };
#endif // ZM_VIDEOSTORE_H #endif // ZM_VIDEOSTORE_H

View File

@ -308,6 +308,7 @@ int main(int argc, char *argv[]) {
result = -1; result = -1;
break; break;
} }
monitors[i]->UpdateFPS();
// capture_delay is the amount of time we should sleep in useconds to achieve the desired framerate. // capture_delay is the amount of time we should sleep in useconds to achieve the desired framerate.
Microseconds delay = (monitors[i]->GetState() == Monitor::ALARM) ? monitors[i]->GetAlarmCaptureDelay() Microseconds delay = (monitors[i]->GetState() == Monitor::ALARM) ? monitors[i]->GetAlarmCaptureDelay()

View File

@ -232,8 +232,7 @@ cd ../
if [ ! -e "$DIRECTORY.orig.tar.gz" ]; then if [ ! -e "$DIRECTORY.orig.tar.gz" ]; then
read -p "$DIRECTORY.orig.tar.gz does not exist, create it? [Y/n]" read -p "$DIRECTORY.orig.tar.gz does not exist, create it? [Y/n]"
if [[ $REPLY == [yY] ]]; then if [[ "$REPLY" == "" || "$REPLY" == [yY] ]]; then
tar zcf $DIRECTORY.orig.tar.gz $DIRECTORY.orig tar zcf $DIRECTORY.orig.tar.gz $DIRECTORY.orig
fi; fi;
fi; fi;

View File

@ -93,7 +93,7 @@ if ( canView('Events') or canView('Snapshots') ) {
$exportFormat, $exportFormat,
$exportCompress, $exportCompress,
$exportStructure, $exportStructure,
(!empty($_REQUEST['exportFile'])?$_REQUEST['exportFile']:'zmExport'), (!empty($_REQUEST['exportFile'])?$_REQUEST['exportFile']:'zmExport')
)) { )) {
ajaxResponse(array('exportFile'=>$exportFile)); ajaxResponse(array('exportFile'=>$exportFile));
} else { } else {

View File

@ -75,7 +75,6 @@ if ( isset($_REQUEST['offset']) ) {
} }
} }
// Limit specifies the number of rows to return // Limit specifies the number of rows to return
// Set the default to 0 for events view, to prevent an issue with ALL pagination // Set the default to 0 for events view, to prevent an issue with ALL pagination
$limit = 0; $limit = 0;
@ -108,7 +107,6 @@ switch ( $task ) {
ajaxError('Insufficient permissions for user '.$user['Username']); ajaxError('Insufficient permissions for user '.$user['Username']);
return; return;
} }
foreach ($eids as $eid) $data[] = deleteRequest($eid); foreach ($eids as $eid) $data[] = deleteRequest($eid);
break; break;
case 'query' : case 'query' :
@ -139,6 +137,8 @@ function deleteRequest($eid) {
$message[] = array($eid=>'Event not found.'); $message[] = array($eid=>'Event not found.');
} else if ( $event->Archived() ) { } else if ( $event->Archived() ) {
$message[] = array($eid=>'Event is archived, cannot delete it.'); $message[] = array($eid=>'Event is archived, cannot delete it.');
} else if (!$event->canEdit()) {
$message[] = array($eid=>'You do not have permission to delete event '.$event->Id());
} else { } else {
$event->delete(); $event->delete();
} }
@ -147,7 +147,6 @@ function deleteRequest($eid) {
} }
function queryRequest($filter, $search, $advsearch, $sort, $offset, $order, $limit) { function queryRequest($filter, $search, $advsearch, $sort, $offset, $order, $limit) {
$data = array( $data = array(
'total' => 0, 'total' => 0,
'totalNotFiltered' => 0, 'totalNotFiltered' => 0,
@ -195,7 +194,10 @@ function queryRequest($filter, $search, $advsearch, $sort, $offset, $order, $lim
ZM\Debug('Calling the following sql query: ' .$sql); ZM\Debug('Calling the following sql query: ' .$sql);
$query = dbQuery($sql, $values); $query = dbQuery($sql, $values);
if ( $query ) { if (!$query) {
ajaxError(dbError($sql));
return;
}
while ($row = dbFetchNext($query)) { while ($row = dbFetchNext($query)) {
$event = new ZM\Event($row); $event = new ZM\Event($row);
$event->remove_from_cache(); $event->remove_from_cache();
@ -205,7 +207,6 @@ function queryRequest($filter, $search, $advsearch, $sort, $offset, $order, $lim
$event_ids[] = $event->Id(); $event_ids[] = $event->Id();
$unfiltered_rows[] = $row; $unfiltered_rows[] = $row;
} # end foreach row } # end foreach row
}
ZM\Debug('Have ' . count($unfiltered_rows) . ' events matching base filter.'); ZM\Debug('Have ' . count($unfiltered_rows) . ' events matching base filter.');
@ -239,15 +240,17 @@ function queryRequest($filter, $search, $advsearch, $sort, $offset, $order, $lim
} # end if search } # end if search
$sql = 'SELECT ' .$col_str. ' FROM `Events` AS E INNER JOIN Monitors AS M ON E.MonitorId = M.Id WHERE '.$search_filter->sql().' ORDER BY ' .$sort. ' ' .$order; $sql = 'SELECT ' .$col_str. ' FROM `Events` AS E INNER JOIN Monitors AS M ON E.MonitorId = M.Id WHERE '.$search_filter->sql().' ORDER BY ' .$sort. ' ' .$order;
ZM\Debug('Calling the following sql query: ' .$sql);
$filtered_rows = dbFetchAll($sql); $filtered_rows = dbFetchAll($sql);
ZM\Debug('Have ' . count($filtered_rows) . ' events matching search filter.'); ZM\Debug('Have ' . count($filtered_rows) . ' events matching search filter: '.$sql);
} else { } else {
$filtered_rows = $unfiltered_rows; $filtered_rows = $unfiltered_rows;
} # end if search_filter->terms() > 1 } # end if search_filter->terms() > 1
if ($limit)
$filtered_rows = array_slice($filtered_rows, $offset, $limit);
$returned_rows = array(); $returned_rows = array();
foreach ( array_slice($filtered_rows, $offset, $limit) as $row ) { foreach ($filtered_rows as $row) {
$event = new ZM\Event($row); $event = new ZM\Event($row);
$scale = intval(5*100*ZM_WEB_LIST_THUMB_WIDTH / $event->Width()); $scale = intval(5*100*ZM_WEB_LIST_THUMB_WIDTH / $event->Width());

View File

@ -38,7 +38,7 @@ if ($zmuOutput) {
$ctls = shell_exec('v4l2-ctl -d '.$monitor->Device().' --list-ctrls'); $ctls = shell_exec('v4l2-ctl -d '.$monitor->Device().' --list-ctrls');
if (!$ctls) { if (!$ctls) {
ZM\Warning("Guessing v4l ctrls. We need v4l2-ctl please install it"); ZM\Warning('Guessing v4l ctrls. We need v4l2-ctl please install it');
$ctls = ' $ctls = '
brightness 0x00980900 (int) : min=-10 max=10 step=1 default=0 value=8 brightness 0x00980900 (int) : min=-10 max=10 step=1 default=0 value=8
contrast 0x00980901 (int) : min=0 max=20 step=1 default=10 value=12 contrast 0x00980901 (int) : min=0 max=20 step=1 default=10 value=12
@ -83,10 +83,15 @@ foreach ($ctls as $line) {
} }
} }
$label = translate($setting_uc);
if ($label == $setting_uc) {
$label = ucwords(str_replace('_', ' ', $label));
}
if ($setting == 'brightness' or $setting == 'colour' or $setting == 'contrast' or $setting == 'hue') { if ($setting == 'brightness' or $setting == 'colour' or $setting == 'contrast' or $setting == 'hue') {
echo ' echo '
<tr> <tr>
<th scope="row">'.translate($setting_uc).'</th> <th scope="row">'.$label.'</th>
<td>'.$min.'</td><td><input type="range" title="'.$value.'" min="'.$min.'" max="'.$max.'" step="'.$step.'" default="'.$default.'" value="'.$value.'" id="new'.$setting_uc.'" name="new'.$setting_uc.'" '.(canEdit('Control') ? '' : 'disabled="disabled"') .'/></td><td>'.$max.'</td> <td>'.$min.'</td><td><input type="range" title="'.$value.'" min="'.$min.'" max="'.$max.'" step="'.$step.'" default="'.$default.'" value="'.$value.'" id="new'.$setting_uc.'" name="new'.$setting_uc.'" '.(canEdit('Control') ? '' : 'disabled="disabled"') .'/></td><td>'.$max.'</td>
</tr> </tr>
'; ';
@ -94,7 +99,7 @@ foreach ($ctls as $line) {
if ($type == '(bool)') { if ($type == '(bool)') {
echo ' echo '
<tr> <tr>
<th scope="row">'.translate($setting_uc).'</th> <th scope="row">'.$label.'</th>
<td></td><td>'.html_radio('new'.$setting_uc, array('0'=>translate('True'), '1', translate('False')), $value, array('disabled'=>'disabled')).' <td></td><td>'.html_radio('new'.$setting_uc, array('0'=>translate('True'), '1', translate('False')), $value, array('disabled'=>'disabled')).'
</td><td></td> </td><td></td>
</tr> </tr>
@ -102,14 +107,14 @@ foreach ($ctls as $line) {
} else if ($type == '(int)') { } else if ($type == '(int)') {
echo ' echo '
<tr> <tr>
<th scope="row">'.translate($setting_uc).'</th> <th scope="row">'.$label.'</th>
<td></td><td><input type="range" '.$ctl[1].' disabled="disabled"/></td><td></td> <td></td><td><input type="range" '.$ctl[1].' disabled="disabled"/></td><td></td>
</tr> </tr>
'; ';
} else { } else {
echo ' echo '
<tr> <tr>
<th scope="row">'.translate($setting_uc).'</th> <th scope="row">'.$label.'</th>
<td></td><td>'.$value.'</td><td></td> <td></td><td>'.$value.'</td><td></td>
</tr> </tr>
'; ';

View File

@ -495,6 +495,10 @@ class Monitor extends ZM_Object {
return $this->Server()->UrlToIndex($port); return $this->Server()->UrlToIndex($port);
} }
public function UrlToZMS($port=null) {
return $this->Server()->UrlToZMS($port).'?mid='.$this->Id();
}
public function sendControlCommand($command) { public function sendControlCommand($command) {
// command is generally a command option list like --command=blah but might be just the word quit // command is generally a command option list like --command=blah but might be just the word quit

View File

@ -3,8 +3,10 @@ function MonitorStream(monitorData) {
this.id = monitorData.id; this.id = monitorData.id;
this.connKey = monitorData.connKey; this.connKey = monitorData.connKey;
this.url = monitorData.url; this.url = monitorData.url;
this.url_to_zms = monitorData.url_to_zms;
this.width = monitorData.width; this.width = monitorData.width;
this.height = monitorData.height; this.height = monitorData.height;
this.scale = 100;
this.status = null; this.status = null;
this.alarmState = STATE_IDLE; this.alarmState = STATE_IDLE;
this.lastAlarmState = STATE_IDLE; this.lastAlarmState = STATE_IDLE;
@ -15,18 +17,67 @@ function MonitorStream(monitorData) {
}; };
this.type = monitorData.type; this.type = monitorData.type;
this.refresh = monitorData.refresh; this.refresh = monitorData.refresh;
this.element = null;
this.getElement = function() {
if (this.element) return this.element;
this.element = document.getElementById('liveStream'+this.id);
if (!this.element) {
console.error("No img for #liveStream"+this.id);
}
return this.element;
};
/* if the img element didn't have a src, this would fill it in, causing it to show. */
this.show = function() {
const stream = this.getElement();
if (!stream.src) {
stream.src = this.url_to_zms+"&mode=single&scale=100&connkey="+this.connKey;
}
};
this.setScale = function(newscale) {
const img = this.getElement();
if (!img) return;
this.scale = newscale;
const oldSrc = img.getAttribute('src');
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');
}
var newSize = scaleToFit(this.width, this.height, $j(img), $j(bottomElement));
console.log(newSize);
newWidth = newSize.width;
newHeight = newSize.height;
autoScale = parseInt(newSize.autoScale);
// This is so that we don't waste bandwidth and let the browser do all the scaling.
if (autoScale > 100) autoScale = 100;
if (autoScale) {
newSrc = oldSrc.replace(/scale=\d+/i, 'scale='+autoScale);
}
} else {
newWidth = this.width * newscale / SCALE_BASE;
newHeight = this.height * newscale / SCALE_BASE;
img.width(newWidth);
img.height(newHeight);
if (newscale > 100) newscale = 100;
newSrc = oldSrc.replace(/scale=\d+/i, 'scale='+newscale);
}
img.setAttribute('src', newSrc);
};
this.start = function(delay) { this.start = function(delay) {
// Step 1 make sure we are streaming instead of a static image // Step 1 make sure we are streaming instead of a static image
var stream = $j('#liveStream'+this.id); const stream = this.getElement();
if (!stream.length) { if (!stream) return;
console.log('No live stream');
return;
}
stream = stream[0];
if ( !stream ) {
console.log('No live stream');
return;
}
if (!stream.src) { if (!stream.src) {
// Website Monitors won't have an img tag // Website Monitors won't have an img tag
console.log('No src for #liveStream'+this.id); console.log('No src for #liveStream'+this.id);
@ -38,7 +89,7 @@ function MonitorStream(monitorData) {
src += '&connkey='+this.connKey; src += '&connkey='+this.connKey;
} }
if ( stream.src != src ) { if ( stream.src != src ) {
console.log("Setting to streaming"); console.log("Setting to streaming: " + src);
stream.src = ''; stream.src = '';
stream.src = src; stream.src = src;
} }

View File

@ -916,7 +916,7 @@ function xhtmlFooter() {
?> ?>
<script src="<?php echo cache_bust('skins/'.$skin.'/js/jquery.min.js'); ?>"></script> <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="skins/<?php echo $skin; ?>/js/jquery-ui-1.12.1/jquery-ui.min.js"></script>
<script src="<?php echo cache_bust('skins/'.$skin.'/js/bootstrap.min.js'); ?>"></script> <script src="skins/<?php echo $skin; ?>/js/bootstrap-4.5.0.min.js"></script>
<?php echo output_script_if_exists(array( <?php echo output_script_if_exists(array(
'js/tableExport.min.js', 'js/tableExport.min.js',
'js/bootstrap-table.min.js', 'js/bootstrap-table.min.js',

View File

@ -584,10 +584,21 @@ function scaleToFit(baseWidth, baseHeight, scaleEl, bottomEl) {
$j(window).on('resize', endOfResize); //set delayed scaling when Scale to Fit is selected $j(window).on('resize', endOfResize); //set delayed scaling when Scale to Fit is selected
var ratio = baseWidth / baseHeight; var ratio = baseWidth / baseHeight;
var container = $j('#content'); var container = $j('#content');
if (!container) {
console.error("No container found");
return;
}
if (!bottomEl || !bottomEl.length) {
bottomEl = $j(container[0].lastElementChild);
}
//console.log(bottomEl);
var viewPort = $j(window); var viewPort = $j(window);
// jquery does not provide a bottom offet, and offset dows not include margins. outerHeight true minus false gives total vertical margins. // jquery does not provide a bottom offset, and offset does not include margins. outerHeight true minus false gives total vertical margins.
var bottomLoc = bottomEl.offset().top + (bottomEl.outerHeight(true) - bottomEl.outerHeight()) + bottomEl.outerHeight(true); var bottomLoc = bottomEl.offset().top + (bottomEl.outerHeight(true) - bottomEl.outerHeight()) + bottomEl.outerHeight(true);
//console.log("bottomLoc: " + bottomEl.offset().top + " + (" + bottomEl.outerHeight(true) + ' - ' + bottomEl.outerHeight() +') + '+bottomEl.outerHeight(true));
var newHeight = viewPort.height() - (bottomLoc - scaleEl.outerHeight(true)); var newHeight = viewPort.height() - (bottomLoc - scaleEl.outerHeight(true));
//console.log("newHeight = " + viewPort.height() +" - " + bottomLoc + ' - ' + scaleEl.outerHeight(true));
var newWidth = ratio * newHeight; var newWidth = ratio * newHeight;
if (newWidth > container.innerWidth()) { if (newWidth > container.innerWidth()) {
newWidth = container.innerWidth(); newWidth = container.innerWidth();
@ -598,13 +609,15 @@ function scaleToFit(baseWidth, baseHeight, scaleEl, bottomEl) {
return parseInt($j(this).val()); return parseInt($j(this).val());
}).get(); }).get();
scales.shift(); scales.shift();
var closest; var closest = null;
$j(scales).each(function() { //Set zms scale to nearest regular scale. Zoom does not like arbitrary scale values. $j(scales).each(function() { //Set zms scale to nearest regular scale. Zoom does not like arbitrary scale values.
if (closest == null || Math.abs(this - autoScale) < Math.abs(closest - autoScale)) { if (closest == null || Math.abs(this - autoScale) < Math.abs(closest - autoScale)) {
closest = this.valueOf(); closest = this.valueOf();
} }
}); });
if (closest) {
autoScale = closest; autoScale = closest;
}
return {width: Math.floor(newWidth), height: Math.floor(newHeight), autoScale: autoScale}; return {width: Math.floor(newWidth), height: Math.floor(newHeight), autoScale: autoScale};
} }
@ -947,3 +960,29 @@ function initThumbAnimation() {
}); });
} }
} }
/* View in fullscreen */
function openFullscreen(elem) {
if (elem.requestFullscreen) {
elem.requestFullscreen();
} else if (elem.webkitRequestFullscreen) {
/* Safari */
elem.webkitRequestFullscreen();
} else if (elem.msRequestFullscreen) {
/* IE11 */
elem.msRequestFullscreen();
}
}
/* Close fullscreen */
function closeFullscreen() {
if (document.exitFullscreen) {
document.exitFullscreen();
} else if (document.webkitExitFullscreen) {
/* Safari */
document.webkitExitFullscreen();
} else if (document.msExitFullscreen) {
/* IE11 */
document.msExitFullscreen();
}
}

View File

@ -54,6 +54,7 @@ foreach ( $perms as $perm ) {
?> ?>
var ANIMATE_THUMBS = <?php echo ZM_WEB_ANIMATE_THUMBS?'true':'false' ?>; var ANIMATE_THUMBS = <?php echo ZM_WEB_ANIMATE_THUMBS?'true':'false' ?>;
var SCALE_BASE = <?php echo SCALE_BASE ?>;
var refreshParent = <?php var refreshParent = <?php
if ( ! empty($refreshParent) ) { if ( ! empty($refreshParent) ) {

View File

@ -300,7 +300,7 @@ function getCmdResponse(respObj, respText) {
if (streamStatus.auth) { if (streamStatus.auth) {
// Try to reload the image stream. // Try to reload the image stream.
var streamImg = $j('#evtStream'); var streamImg = document.getElementById('evtStream');
if (streamImg) { if (streamImg) {
streamImg.src = streamImg.src.replace(/auth=\w+/i, 'auth='+streamStatus.auth); streamImg.src = streamImg.src.replace(/auth=\w+/i, 'auth='+streamStatus.auth);
} }

View File

@ -41,6 +41,10 @@ function ajaxRequest(params) {
} }
$j.getJSON(thisUrl + '?view=request&request=events&task=query'+filterQuery, params.data) $j.getJSON(thisUrl + '?view=request&request=events&task=query'+filterQuery, params.data)
.done(function(data) { .done(function(data) {
if (data.result == 'Error') {
alert(data.message);
return;
}
var rows = processRows(data.rows); var rows = processRows(data.rows);
// rearrange the result into what bootstrap-table expects // rearrange the result into what bootstrap-table expects
params.success({total: data.total, totalNotFiltered: data.totalNotFiltered, rows: rows}); params.success({total: data.total, totalNotFiltered: data.totalNotFiltered, rows: rows});

View File

@ -317,5 +317,10 @@ function initPage() {
} }
selectLayout('#zmMontageLayout'); selectLayout('#zmMontageLayout');
} }
function watchFullscreen() {
const content = document.getElementById('content');
openFullscreen(content);
}
// Kick everything off // Kick everything off
$j(document).ready(initPage); $j(document).ready(initPage);

View File

@ -16,6 +16,9 @@ function generateVideoResponse( data, responseText ) {
} }
function generateVideo() { function generateVideo() {
$j.ajaxSetup({
timeout: 0
});
var form = $j('#videoForm').serialize(); var form = $j('#videoForm').serialize();
$j.getJSON(thisUrl + '?view=request&request=event&action=video', form) $j.getJSON(thisUrl + '?view=request&request=event&action=video', form)
.done(generateVideoResponse) .done(generateVideoResponse)

View File

@ -970,5 +970,20 @@ function initPage() {
}); });
} // initPage } // initPage
function watchFullscreen() {
const btn = document.getElementById('fullscreenBtn');
console.log(btn);
if (btn.firstElementChild.innerHTML=='fullscreen') {
const content = document.getElementById('content');
openFullscreen(content);
btn.firstElementChild.innerHTML='fullscreen_exit';
btn.setAttribute('title', translate["Exit Fullscreen"]);
} else {
closeFullscreen();
btn.firstElementChild.innerHTML='fullscreen';
btn.setAttribute('title', translate["Fullscreen"]);
}
}
// Kick everything off // Kick everything off
$j(document).ready(initPage); $j(document).ready(initPage);

View File

@ -103,3 +103,7 @@ labels[<?php echo validInt($index) ?>] = '<?php echo validJsStr($label) ?>';
<?php <?php
} }
?> ?>
var translate = {
"Fullscreen": "<?php echo translate('Fullscreen') ?>",
"Exit Fullscreen": "<?php echo translate('Exit Fullscreen') ?>",
};

View File

@ -661,6 +661,7 @@ function initPage() {
// Start the fps and status updates. give a random delay so that we don't assault the server // Start the fps and status updates. give a random delay so that we don't assault the server
var delay = Math.round( (Math.random()+0.5)*statusRefreshTimeout ); var delay = Math.round( (Math.random()+0.5)*statusRefreshTimeout );
monitors[i].setScale('auto');
monitors[i].start(delay); monitors[i].start(delay);
} }

View File

@ -66,6 +66,7 @@ monitorData[monitorData.length] = {
'width': <?php echo $monitor->ViewWidth() ?>, 'width': <?php echo $monitor->ViewWidth() ?>,
'height':<?php echo $monitor->ViewHeight() ?>, 'height':<?php echo $monitor->ViewHeight() ?>,
'url': '<?php echo $monitor->UrlToIndex( ZM_MIN_STREAMING_PORT ? ($monitor->Id() + ZM_MIN_STREAMING_PORT) : '') ?>', 'url': '<?php echo $monitor->UrlToIndex( ZM_MIN_STREAMING_PORT ? ($monitor->Id() + ZM_MIN_STREAMING_PORT) : '') ?>',
'url_to_zms': '<?php echo $monitor->UrlToZMS( ZM_MIN_STREAMING_PORT ? ($monitor->Id() + ZM_MIN_STREAMING_PORT) : '') ?>',
'type': '<?php echo $monitor->Type() ?>', 'type': '<?php echo $monitor->Type() ?>',
'refresh': '<?php echo $monitor->Refresh() ?>' 'refresh': '<?php echo $monitor->Refresh() ?>'
}; };

View File

@ -12,6 +12,7 @@ function initPage() {
// Start the fps and status updates. give a random delay so that we don't assault the server // Start the fps and status updates. give a random delay so that we don't assault the server
var delay = Math.round( (Math.random()+0.5)*statusRefreshTimeout ); var delay = Math.round( (Math.random()+0.5)*statusRefreshTimeout );
monitors[i].setScale('auto');
monitors[i].start(delay); monitors[i].start(delay);
} }
@ -31,5 +32,12 @@ function initPage() {
}); });
} }
function streamCmdQuit() {
for ( var i = 0, length = monitorData.length; i < length; i++ ) {
monitors[i] = new MonitorStream(monitorData[i]);
monitors[i].stop();
}
}
window.addEventListener('DOMContentLoaded', initPage); window.addEventListener('DOMContentLoaded', initPage);

View File

@ -9,6 +9,7 @@ monitorData[monitorData.length] = {
'width': <?php echo $monitor->ViewWidth() ?>, 'width': <?php echo $monitor->ViewWidth() ?>,
'height':<?php echo $monitor->ViewHeight() ?>, 'height':<?php echo $monitor->ViewHeight() ?>,
'url': '<?php echo $monitor->UrlToIndex( ZM_MIN_STREAMING_PORT ? ($monitor->Id() + ZM_MIN_STREAMING_PORT) : '') ?>', 'url': '<?php echo $monitor->UrlToIndex( ZM_MIN_STREAMING_PORT ? ($monitor->Id() + ZM_MIN_STREAMING_PORT) : '') ?>',
'url_to_zms': '<?php echo $monitor->UrlToZMS( ZM_MIN_STREAMING_PORT ? ($monitor->Id() + ZM_MIN_STREAMING_PORT) : '') ?>',
'type': '<?php echo $monitor->Type() ?>', 'type': '<?php echo $monitor->Type() ?>',
'refresh': '<?php echo $monitor->Refresh() ?>' 'refresh': '<?php echo $monitor->Refresh() ?>'
}; };
@ -24,7 +25,7 @@ var STATE_TAPE = <?php echo STATE_TAPE ?>;
var stateStrings = new Array(); var stateStrings = new Array();
stateStrings[STATE_IDLE] = "<?php echo translate('Idle') ?>"; stateStrings[STATE_IDLE] = "<?php echo translate('Idle') ?>";
stateStrings[STATE_PREALARM] = "<?php echo translate('Idle') ?>"; stateStrings[STATE_PREALARM] = "<?php echo translate('PreAlarm') ?>";
stateStrings[STATE_ALARM] = "<?php echo translate('Alarm') ?>"; stateStrings[STATE_ALARM] = "<?php echo translate('Alarm') ?>";
stateStrings[STATE_ALERT] = "<?php echo translate('Alert') ?>"; stateStrings[STATE_ALERT] = "<?php echo translate('Alert') ?>";
stateStrings[STATE_TAPE] = "<?php echo translate('Record') ?>"; stateStrings[STATE_TAPE] = "<?php echo translate('Record') ?>";

View File

@ -461,8 +461,11 @@ switch ( $name ) {
<tr class="Id"> <tr class="Id">
<td class="text-right pr-3"><?php echo translate('Id') ?></td> <td class="text-right pr-3"><?php echo translate('Id') ?></td>
<td><input type="number" step="1" min="1" name="newMonitor[Id]" placeholder="leave blank for auto"/><br/> <td><input type="number" step="1" min="1" name="newMonitor[Id]" placeholder="leave blank for auto"/><br/>
10 Available Ids: <?php
<?php echo implode(', ', array_slice($available_monitor_ids, 0, 10)); ?> if (count($available_monitor_ids)) {
echo 'Some available ids: '.implode(', ', array_slice($available_monitor_ids, 0, 10));
}
?>
</td> </td>
</tr> </tr>
<?php <?php

View File

@ -206,6 +206,9 @@ if ( canView('System') ) {
&nbsp;<?php echo translate('Snapshot') ?> &nbsp;<?php echo translate('Snapshot') ?>
</button> </button>
<?php } ?> <?php } ?>
<button type="button" id="fullscreenBtn" title="<?php echo translate('Fullscreen') ?>" class="avail" data-on-click="watchFullscreen">
<i class="material-icons md-18">fullscreen</i>
</button>
</form> </form>
</div> </div>
</div> </div>

View File

@ -142,6 +142,9 @@ if ( $streamMode == 'jpeg' ) {
<button type="button" id="zoomOutBtn" title="<?php echo translate('ZoomOut') ?>" class="avail" data-on-click="streamCmdZoomOut"> <button type="button" id="zoomOutBtn" title="<?php echo translate('ZoomOut') ?>" class="avail" data-on-click="streamCmdZoomOut">
<i class="material-icons md-18">zoom_out</i> <i class="material-icons md-18">zoom_out</i>
</button> </button>
<button type="button" id="fullscreenBtn" title="<?php echo translate('Fullscreen') ?>" class="avail" data-on-click="watchFullscreen">
<i class="material-icons md-18">fullscreen</i>
</button>
<?php <?php
} // end if streamMode==jpeg } // end if streamMode==jpeg
?> ?>

View File

@ -84,7 +84,6 @@ xhtmlHeaders(__FILE__, translate('Zones'));
?> ?>
<polygon points="<?php echo $zone['AreaCoords'] ?>" <polygon points="<?php echo $zone['AreaCoords'] ?>"
class="zmlink <?php echo $zone['Type']?>" class="zmlink <?php echo $zone['Type']?>"
data-on-click-true="streamCmdQuit"
data-url="?view=zone&amp;mid=<?php echo $mid ?>&amp;zid=<?php echo $zone['Id'] ?>" data-url="?view=zone&amp;mid=<?php echo $mid ?>&amp;zid=<?php echo $zone['Id'] ?>"
/> />
<?php <?php