From c28ff1f2b44aa2cecd14f210b7a8acdebb828d67 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Thu, 8 Oct 2020 09:06:56 -0400 Subject: [PATCH] Include loading EndTime when loading Events data so that we can determine if an event is in-progress Keep track of last_id which is the last frame id that we have info for. Use it instead of Frame Count to prevent accessing non-existent frame records close ffmpeg_input on event change include duration in status messages because it may change when playing an in-progress event. when we hit the end of our frame data if it is an in-progress event, reload the event data instead of moving to the next event. Include X-Timestamp in http headers to match live stream behaviour Only send time to next event when mode=ALL Fix scale behaviour on event view --- src/zm_eventstream.cpp | 96 ++++++++++++++++++----------- src/zm_eventstream.h | 6 +- web/ajax/stream.php | 4 +- web/skins/classic/views/event.php | 2 +- web/skins/classic/views/js/event.js | 3 + 5 files changed, 69 insertions(+), 42 deletions(-) diff --git a/src/zm_eventstream.cpp b/src/zm_eventstream.cpp index 875dff0cc..48b2fb59a 100644 --- a/src/zm_eventstream.cpp +++ b/src/zm_eventstream.cpp @@ -117,6 +117,7 @@ bool EventStream::loadEventData(uint64_t event_id) { snprintf(sql, sizeof(sql), "SELECT `MonitorId`, `StorageId`, `Frames`, unix_timestamp( `StartTime` ) AS StartTimestamp, " + "unix_timestamp( `EndTime` ) AS EndTimestamp, " "(SELECT max(`Delta`)-min(`Delta`) FROM `Frames` WHERE `EventId`=`Events`.`Id`) AS Duration, " "`DefaultVideo`, `Scheme`, `SaveJPEGs`, `Orientation`+0 FROM `Events` WHERE `Id` = %" PRIu64, event_id); @@ -150,9 +151,10 @@ bool EventStream::loadEventData(uint64_t event_id) { event_data->storage_id = dbrow[1] ? atoi(dbrow[1]) : 0; event_data->frame_count = dbrow[2] == NULL ? 0 : atoi(dbrow[2]); event_data->start_time = atoi(dbrow[3]); - event_data->duration = dbrow[4] ? atof(dbrow[4]) : 0.0; - strncpy(event_data->video_file, dbrow[5], sizeof(event_data->video_file)-1); - std::string scheme_str = std::string(dbrow[6]); + event_data->end_time = dbrow[4] ? atoi(dbrow[4]) : 0; + event_data->duration = dbrow[5] ? atof(dbrow[5]) : 0.0; + strncpy(event_data->video_file, dbrow[6], sizeof(event_data->video_file)-1); + std::string scheme_str = std::string(dbrow[7]); if ( scheme_str == "Deep" ) { event_data->scheme = Storage::DEEP; } else if ( scheme_str == "Medium" ) { @@ -160,8 +162,8 @@ bool EventStream::loadEventData(uint64_t event_id) { } else { event_data->scheme = Storage::SHALLOW; } - event_data->SaveJPEGs = dbrow[7] == NULL ? 0 : atoi(dbrow[7]); - event_data->Orientation = (Monitor::Orientation)(dbrow[8] == NULL ? 0 : atoi(dbrow[8])); + event_data->SaveJPEGs = dbrow[8] == NULL ? 0 : atoi(dbrow[8]); + event_data->Orientation = (Monitor::Orientation)(dbrow[9] == NULL ? 0 : atoi(dbrow[9])); mysql_free_result(result); if ( !monitor ) { @@ -223,8 +225,6 @@ bool EventStream::loadEventData(uint64_t event_id) { } updateFrameRate((double)event_data->frame_count/event_data->duration); - Debug(3, "fps set by frame_count(%d)/duration(%f)", - event_data->frame_count, event_data->duration); snprintf(sql, sizeof(sql), "SELECT `FrameId`, unix_timestamp(`TimeStamp`), `Delta` " "FROM `Frames` WHERE `EventId` = %" PRIu64 " ORDER BY `FrameId` ASC", event_id); @@ -276,7 +276,7 @@ bool EventStream::loadEventData(uint64_t event_id) { last_id = id; last_delta = delta; last_timestamp = event_data->frames[id-1].timestamp; - Debug(4, "Frame %d timestamp:(%f), offset(%f) delta(%f), in_db(%d)", + Debug(3, "Frame %d timestamp:(%f), offset(%f) delta(%f), in_db(%d)", id, event_data->frames[id-1].timestamp, event_data->frames[id-1].offset, @@ -284,11 +284,13 @@ bool EventStream::loadEventData(uint64_t event_id) { event_data->frames[id-1].in_db ); } + // Incomplete events might not have any frame data + event_data->last_frame_id = last_id; + if ( mysql_errno(&dbconn) ) { Error("Can't fetch row: %s", mysql_error(&dbconn)); exit(mysql_errno(&dbconn)); } - mysql_free_result(result); if ( event_data->video_file[0] || (monitor->GetOptVideoWriter() > 0) ) { @@ -296,9 +298,10 @@ bool EventStream::loadEventData(uint64_t event_id) { snprintf(event_data->video_file, sizeof(event_data->video_file), "%" PRIu64 "-%s", event_data->event_id, "video.mp4"); } std::string filepath = std::string(event_data->path) + "/" + std::string(event_data->video_file); - //char filepath[PATH_MAX]; - //snprintf(filepath, sizeof(filepath), "%s/%s", event_data->path, event_data->video_file); Debug(1, "Loading video file from %s", filepath.c_str()); + if ( ffmpeg_input ) + delete ffmpeg_input; + ffmpeg_input = new FFmpeg_Input(); if ( 0 > ffmpeg_input->Open(filepath.c_str()) ) { Warning("Unable to open ffmpeg_input %s", filepath.c_str()); @@ -307,14 +310,15 @@ bool EventStream::loadEventData(uint64_t event_id) { } } + // Not sure about this if ( forceEventChange || mode == MODE_ALL_GAPLESS ) { if ( replay_rate > 0 ) curr_stream_time = event_data->frames[0].timestamp; else - curr_stream_time = event_data->frames[event_data->frame_count-1].timestamp; + curr_stream_time = event_data->frames[event_data->last_frame_id-1].timestamp; } - Debug(2, "Event:%" PRIu64 ", Frames:%ld, Duration: %.2f", - event_data->event_id, event_data->frame_count, event_data->duration); + Debug(2, "Event:%" PRIu64 ", Frames:%ld, Last Frame ID(%ld, Duration: %.2f", + event_data->event_id, event_data->frame_count, event_data->last_frame_id, event_data->duration); return true; } // bool EventStream::loadEventData( int event_id ) @@ -341,12 +345,12 @@ void EventStream::processCommand(const CmdMsg *msg) { if ( (mode == MODE_SINGLE || mode == MODE_NONE) && - ((unsigned int)curr_frame_id == event_data->frame_count) + ((unsigned int)curr_frame_id == event_data->last_frame_id) ) { Debug(1, "Was in single or no replay mode, and at last frame, so jumping to 1st frame"); curr_frame_id = 1; } else { - Debug(1, "mode is %s, current frame is %d, frame count is %d", + Debug(1, "mode is %s, current frame is %ld, frame count is %ld, last frame id is %ld", (mode == MODE_SINGLE ? "single" : "not single"), curr_frame_id, event_data->frame_count ); } @@ -395,7 +399,7 @@ void EventStream::processCommand(const CmdMsg *msg) { paused = true; replay_rate = ZM_RATE_BASE; step = 1; - if ( (unsigned int)curr_frame_id < event_data->frame_count ) + if ( (unsigned int)curr_frame_id < event_data->last_frame_id ) curr_frame_id += 1; break; case CMD_SLOWREV : @@ -489,14 +493,14 @@ void EventStream::processCommand(const CmdMsg *msg) { if ( replay_rate >= 0 ) curr_frame_id = 0; else - curr_frame_id = event_data->frame_count+1; + curr_frame_id = event_data->last_frame_id+1; paused = false; forceEventChange = true; break; case CMD_NEXT : Debug(1, "Got NEXT command"); if ( replay_rate >= 0 ) - curr_frame_id = event_data->frame_count+1; + curr_frame_id = event_data->last_frame_id+1; else curr_frame_id = 0; paused = false; @@ -533,6 +537,7 @@ void EventStream::processCommand(const CmdMsg *msg) { } struct { uint64_t event_id; + double duration; double progress; int rate; int zoom; @@ -540,12 +545,14 @@ void EventStream::processCommand(const CmdMsg *msg) { } status_data; status_data.event_id = event_data->event_id; + status_data.duration = event_data->duration; status_data.progress = event_data->frames[curr_frame_id-1].offset; status_data.rate = replay_rate; status_data.zoom = zoom; status_data.paused = paused; - Debug(2, "Event:%" PRIu64 ", Paused:%d, progress:%f Rate:%d, Zoom:%d", + Debug(2, "Event:%" PRIu64 ", Duration %f, Paused:%d, progress:%f Rate:%d, Zoom:%d", status_data.event_id, + status_data.duration, status_data.paused, status_data.progress, status_data.rate, @@ -577,7 +584,14 @@ bool EventStream::checkEventLoaded() { snprintf(sql, sizeof(sql), "SELECT `Id` FROM `Events` WHERE `MonitorId` = %ld AND `Id` < %" PRIu64 " ORDER BY `Id` DESC LIMIT 1", event_data->monitor_id, event_data->event_id); - } else if ( (unsigned int)curr_frame_id > event_data->frame_count ) { + } else if ( (unsigned int)curr_frame_id > event_data->last_frame_id ) { + if ( !event_data->end_time ) { + // We are viewing an in-process event, so just reload it. + loadEventData(event_data->event_id); + if ( (unsigned int)curr_frame_id > event_data->last_frame_id ) + curr_frame_id = event_data->last_frame_id; + return false; + } snprintf(sql, sizeof(sql), "SELECT `Id` FROM `Events` WHERE `MonitorId` = %ld AND `Id` > %" PRIu64 " ORDER BY `Id` ASC LIMIT 1", event_data->monitor_id, event_data->event_id); @@ -590,6 +604,7 @@ bool EventStream::checkEventLoaded() { // Event change required. if ( forceEventChange || ( (mode != MODE_SINGLE) && (mode != MODE_NONE) ) ) { + Debug(1, "Checking for next event %s", sql); if ( mysql_query(&dbconn, sql) ) { Error("Can't run query: %s", mysql_error(&dbconn)); exit(mysql_errno(&dbconn)); @@ -600,6 +615,9 @@ bool EventStream::checkEventLoaded() { Error("Can't use query result: %s", mysql_error(&dbconn)); exit(mysql_errno(&dbconn)); } + if ( mysql_num_rows(result) != 1 ) { + Debug(1, "No rows returned for %s", sql); + } MYSQL_ROW dbrow = mysql_fetch_row(result); if ( mysql_errno(&dbconn)) { @@ -614,7 +632,7 @@ bool EventStream::checkEventLoaded() { loadEventData(event_id); if ( replay_rate < 0 ) // rewind - curr_frame_id = event_data->frame_count; + curr_frame_id = event_data->last_frame_id; else curr_frame_id = 1; Debug(2, "New frame id = %d", curr_frame_id); @@ -635,7 +653,7 @@ bool EventStream::checkEventLoaded() { if ( curr_frame_id <= 0 ) curr_frame_id = 1; else - curr_frame_id = event_data->frame_count; + curr_frame_id = event_data->last_frame_id; paused = true; } return false; @@ -846,15 +864,17 @@ Debug(1, "Loading image"); } else { Debug(3, "Content length: %d", img_buffer_size); if ( - (0 > fprintf(stdout, "Content-Length: %d\r\n\r\n", img_buffer_size) ) + ( 0 > fprintf(stdout, "Content-Length: %d\r\nX-Timestamp: %.06f\r\n\r\n", + img_buffer_size, event_data->frames[curr_frame_id-1].timestamp) ) || - ( fwrite(img_buffer, img_buffer_size, 1, stdout) != 1 ) ) { - Error("Unable to send stream frame: %s", strerror(errno)); - return false; + ( fwrite(img_buffer, img_buffer_size, 1, stdout) != 1 ) + ) { + //Error("Unable to send stream frame: %s", strerror(errno)); + //return false; } } // end if send_raw or not - fputs("\r\n\r\n", stdout); + fputs("\r\n", stdout); fflush(stdout); } // end if stream MPEG or other last_frame_sent = TV_2_FLOAT(now); @@ -874,7 +894,6 @@ void EventStream::runStream() { exit(0); } - Debug(3, "frame rate is: (%f)", (double)event_data->frame_count/event_data->duration); updateFrameRate((double)event_data->frame_count/event_data->duration); gettimeofday(&start, NULL); uint64_t start_usec = start.tv_sec * 1000000 + start.tv_usec; @@ -916,25 +935,28 @@ void EventStream::runStream() { time_to_event = event_data->frames[0].timestamp - curr_stream_time; if ( time_to_event > 0 ) in_event = false; + Debug(1, "replay rate(%d) in_event(%d) time_to_event(%f)=frames[0]->timestamp(%f)-curr_stream_time(%f)", + replay_rate, in_event, time_to_event, + event_data->frames[0].timestamp, + curr_stream_time + ); } else if ( replay_rate < 0 ) { time_to_event = curr_stream_time - event_data->frames[event_data->frame_count-1].timestamp; if ( time_to_event > 0 ) in_event = false; + Debug(1, "replay rate(%d) in_event(%d) time_to_event(%f)=curr_stream_time(%f)-frame timestamp:%f", + replay_rate, in_event, time_to_event, curr_stream_time, event_data->frames[event_data->frame_count-1].timestamp); } - Debug(1, "replay rate(%d) in_event(%d) time_to_event(%f)=curr_stream_time(%f)-frame timestamp:%f", - replay_rate, in_event, time_to_event, curr_stream_time, event_data->frames[event_data->frame_count-1].timestamp); - if ( !in_event ) { - double actual_delta_time = TV_2_FLOAT(now) - last_frame_sent; - Debug(1, "Ctual delta time = %f = %f - %f", actual_delta_time , TV_2_FLOAT(now) , last_frame_sent); + if ( !in_event and (mode == MODE_ALL) ) { + double time_since_last_send = TV_2_FLOAT(now) - last_frame_sent; + Debug(1, "Actual delta time = %f = %f - %f", time_since_last_send, TV_2_FLOAT(now), last_frame_sent); // > 1 second - if ( actual_delta_time > 1 ) { + if ( time_since_last_send > 1 ) { Debug(1, "Sending time to next event frame"); static char frame_text[64]; snprintf(frame_text, sizeof(frame_text), "Time to next event = %d seconds", (int)time_to_event); if ( !sendTextFrame(frame_text) ) zm_terminate = true; - } else { - Debug(1, "Not Sending time to next event frame because actual delta time is %f", actual_delta_time); } //else //{ diff --git a/src/zm_eventstream.h b/src/zm_eventstream.h index a482483b4..ce3a0dd1f 100644 --- a/src/zm_eventstream.h +++ b/src/zm_eventstream.h @@ -54,11 +54,13 @@ class EventStream : public StreamBase { uint64_t event_id; unsigned long monitor_id; unsigned long storage_id; - unsigned long frame_count; + unsigned long frame_count; // Value of Frames column in Event + unsigned long last_frame_id; // Highest frame id known about. Can be < frame_count in incomplete events time_t start_time; + time_t end_time; double duration; char path[PATH_MAX]; - int n_frames; + int n_frames; // # of frame rows returned from database FrameData *frames; char video_file[PATH_MAX]; Storage::Schemes scheme; diff --git a/web/ajax/stream.php b/web/ajax/stream.php index dacb41d78..ef29951ee 100644 --- a/web/ajax/stream.php +++ b/web/ajax/stream.php @@ -136,10 +136,10 @@ if ( sem_acquire($semaphore,1) !== false ) { case MSG_DATA_EVENT : if ( version_compare( phpversion(), '5.6.0', '<') ) { ZM\Logger::Debug('Using old unpack methods to handle 64bit event id'); - $data = unpack('ltype/ieventlow/ieventhigh/dprogress/irate/izoom/Cpaused', $msg); + $data = unpack('ltype/ieventlow/ieventhigh/dduration/dprogress/irate/izoom/Cpaused', $msg); $data['event'] = $data['eventhigh'] << 32 | $data['eventlow']; } else { - $data = unpack('ltype/Qevent/dprogress/irate/izoom/Cpaused', $msg); + $data = unpack('ltype/Qevent/dduration/dprogress/irate/izoom/Cpaused', $msg); } $data['rate'] /= RATE_BASE; $data['zoom'] = round($data['zoom']/SCALE_BASE, 1); diff --git a/web/skins/classic/views/event.php b/web/skins/classic/views/event.php index 464c68b14..32aef8557 100644 --- a/web/skins/classic/views/event.php +++ b/web/skins/classic/views/event.php @@ -52,7 +52,7 @@ if ( isset($_REQUEST['scale']) ) { } else if ( isset($_COOKIE['zmEventScale'.$Event->MonitorId()]) ) { $scale = $_COOKIE['zmEventScale'.$Event->MonitorId()]; } else { - $scale = reScale(SCALE_BASE, $Monitor->DefaultScale(), ZM_WEB_DEFAULT_SCALE); + $scale = $Monitor->DefaultScale(); } $codec = 'auto'; diff --git a/web/skins/classic/views/js/event.js b/web/skins/classic/views/js/event.js index 80b1311ca..9b9c475a5 100644 --- a/web/skins/classic/views/js/event.js +++ b/web/skins/classic/views/js/event.js @@ -243,6 +243,9 @@ function getCmdResponse( respObj, respText ) { } streamStatus = respObj.status; + if ( streamStatus.duration && ( streamStatus.duration != parseFloat(eventData.Length) ) ) { + eventData.Length = streamStatus.duration; + } if ( streamStatus.progress > parseFloat(eventData.Length) ) { console.log("Limiting progress to " + streamStatus.progress + ' >= ' + parseFloat(eventData.Length) ); streamStatus.progress = parseFloat(eventData.Length);