Add h264 event view functionality and new feature alarmCues (#2012)

* Fix nearEventsQuery

Removed dbEscape from getNearEvents previous event because it only returns 0.  Now matches next.  Changed getEventDefaultVideoPath function to return a web path rather than the absolute path based on ic0ns branch.  Also added start times to allow for videoJS replaymode.

* Unescape filters

Filters need to be unescaped

* Add initial values to page load

* Add replay modes to videojs

* rough in figuring out a frame in between bulk frames

* Add alarmCues

Add a graphical indication of where alarm frames happened in an event.  Similar to what zmNinja shows.

* Add remaining buttons to videojs

Functionality for all buttons on videojs streams.  FF/RW buttons show as active when they are active.

* Whitespace and fix Bulkframe calcs

* Fix zms events trying to generate with mp4 code

ZMS events would attempt to generate frames as though they were an mp4/passthrough type because the full eventpath wasn't passed

* ZMS scrub bar

Move zms scrub bar to bottom of image feed.  Make it simpler and more like videojs style.

* Wrap event feeds properly

* Fix dvrControls on watch view

* Add scaleToFit

Add a scaleToFit option to event view

* Add navigation for videoJS streams

Disables nav buttons at beginning and end of events.  Handles switching from zms to videojs.  If zms crashes changes next event function to reload page instead of ajax.

* Add scaleToFit to watch and frame view

Adds scaleToFit to watch view.  Since frame view uses the watch cookie this required changes to frame view

* Add transition to zoom

* Change stills view to match stream

Move stills slider bar to match scrub bar on streams.  Allow it to resize, make it larger.  Add alarmcues.

* Add Stills for every event

Add stills for every event.  Match size to stream size

* Progressbox transitions
This commit is contained in:
digital-gnome 2017-12-04 21:26:59 -05:00 committed by Isaac Connor
parent fb952179cd
commit f8d3c07586
22 changed files with 912 additions and 499 deletions

View File

@ -136,6 +136,17 @@ $statusData = array(
//"Path" => array( "postFunc" => "getEventPath" ),
),
),
"frames" => array(
"permission" => "Events",
"table" => "Frames",
"selector" => "EventId",
"elements" => array(
"EventId" => true,
"FrameId" => true,
"Type" => true,
"Delta" => true,
),
),
"frame" => array(
"permission" => "Events",
"table" => "Frames",
@ -367,7 +378,7 @@ function getNearEvents() {
else
$midSql = '';
$sql = "select E.Id as Id from Events as E inner join Monitors as M on E.MonitorId = M.Id where ".dbEscape($sortColumn)." ".($sortOrder=='asc'?'<=':'>=')." '".$event[$_REQUEST['sort_field']]."'".$_REQUEST['filter']['sql'].$midSql." order by $sortColumn ".($sortOrder=='asc'?'desc':'asc');
$sql = "SELECT E.Id AS Id, E.StartTime AS StartTime FROM Events AS E INNER JOIN Monitors AS M ON E.MonitorId = M.Id WHERE $sortColumn ".($sortOrder=='asc'?'<=':'>=')." '".$event[$_REQUEST['sort_field']]."'".$_REQUEST['filter']['sql'].$midSql." ORDER BY $sortColumn ".($sortOrder=='asc'?'desc':'asc') . ' LIMIT 2';
$result = dbQuery( $sql );
while ( $id = dbFetchNext( $result, 'Id' ) ) {
if ( $id == $eventId ) {
@ -376,7 +387,7 @@ function getNearEvents() {
}
}
$sql = "select E.Id as Id from Events as E inner join Monitors as M on E.MonitorId = M.Id where $sortColumn ".($sortOrder=='asc'?'>=':'<=')." '".$event[$_REQUEST['sort_field']]."'".$_REQUEST['filter']['sql'].$midSql." order by $sortColumn $sortOrder";
$sql = "SELECT E.Id AS Id, E.StartTime AS StartTime FROM Events AS E INNER JOIN Monitors AS M ON E.MonitorId = M.Id WHERE $sortColumn ".($sortOrder=='asc'?'>=':'<=')." '".$event[$_REQUEST['sort_field']]."'".$_REQUEST['filter']['sql'].$midSql." ORDER BY $sortColumn $sortOrder LIMIT 2";
$result = dbQuery( $sql );
while ( $id = dbFetchNext( $result, 'Id' ) ) {
if ( $id == $eventId ) {
@ -388,8 +399,10 @@ function getNearEvents() {
$result = array( 'EventId'=>$eventId );
$result['PrevEventId'] = empty($prevEvent)?0:$prevEvent['Id'];
$result['NextEventId'] = empty($nextEvent)?0:$nextEvent['Id'];
$result['PrevEventDefVideoPath'] = empty($prevEvent)?0:(getEventDefaultVideoPath($prevEvent));
$result['NextEventDefVideoPath'] = empty($nextEvent)?0:(getEventDefaultVideoPath($nextEvent));
$result['PrevEventStartTime'] = empty($prevEvent)?0:$prevEvent['StartTime'];
$result['NextEventStartTime'] = empty($nextEvent)?0:$nextEvent['StartTime'];
$result['PrevEventDefVideoPath'] = empty($prevEvent)?0:(getEventDefaultVideoPath($prevEvent['Id']));
$result['NextEventDefVideoPath'] = empty($nextEvent)?0:(getEventDefaultVideoPath($nextEvent['Id']));
return( $result );
}

View File

@ -452,7 +452,8 @@ function getEventPath( $event ) {
}
function getEventDefaultVideoPath( $event ) {
return ZM_DIR_EVENTS . '/' . getEventPath($event) . '/' . $event['DefaultVideo'];
$Event = new Event( $event );
return $Event->getStreamSrc( array( "mode"=>"mpeg", "format"=>"h264" ) );
}
function deletePath( $path ) {
@ -992,7 +993,7 @@ function zmaCheck( $monitor ) {
}
function getImageSrc( $event, $frame, $scale=SCALE_BASE, $captureOnly=false, $overwrite=false ) {
$eventPath = getEventPath( $event );
$eventPath = ZM_DIR_EVENTS . '/' . getEventPath( $event );
if ( !is_array($frame) )
$frame = array( 'FrameId'=>$frame, 'Type'=>'' );
@ -2305,6 +2306,7 @@ function getStreamMode( ) {
$streamMode = 'single';
Info( 'The system has fallen back to single jpeg mode for streaming. Consider enabling Cambozola or upgrading the client browser.' );
}
return $streamMode;
} // end function getStreamMode
function folder_size($dir) {

View File

@ -1,3 +1,34 @@
#content .vjsMessage {
width: 100%;
position: absolute;
left: 0;
z-index: 10;
margin: 0;
font-size: 200%;
color: white;
background-color: black;
display: inline-block;
}
.alarmCue {
background-color: #222222;
height: 1.25em;
text-align: left;
margin: 0 auto 0 auto;
border-radius: 0 0 .3em .3em;
}
.alarmCue span {
background-color:red;
height: 100%;
display: inline-block;
border-radius: 0;
}
span.noneCue {
background: none;
}
#dataBar {
width: 100%;
margin: 2px auto;
@ -6,6 +37,7 @@
#dataBar #dataTable {
width: 100%;
table-layout: fixed;
}
#dataBar #dataTable td {
@ -13,6 +45,11 @@
padding: 2px;
}
#eventVideo {
display: inline-block;
position: relative;
}
#menuBar1 {
width: 100%;
padding: 3px 0;
@ -83,6 +120,8 @@
}
#imageFeed {
display: inline-block;
position: relative;
text-align: center;
}
@ -147,39 +186,29 @@
#progressBar {
position: relative;
border: 1px solid #666666;
height: 15px;
margin: 0 auto;
top: -1.25em;
height: 1.25em;
margin: 0 auto -1.25em auto;
}
#progressBar .progressBox {
position: absolute;
top: 0px;
left: 0px;
height: 15px;
background: #eeeeee;
border-left: 1px solid #999999;
}
#progressBar .complete {
background: #aaaaaa;
transition: width .1s;
height: 100%;
background: rgba(170, 170, 170, .7);
border-radius: 0 0 .3em .3em;
}
#eventStills {
width: 100%;
position: relative;
}
#eventThumbsPanel {
position: relative;
width: 100%;
margin: 4px auto;
z-index: 1;
}
#eventThumbs {
margin: 0 auto;
width: 100%;
overflow: hidden;
height: 300px;
}
@ -198,14 +227,19 @@
}
#eventImagePanel {
display: none;
position: absolute;
top: 0;
left: 0;
z-index: 10;
width: 100%;
}
#eventImageFrame {
border: 2px solid gray;
background-color: white;
padding: 4px;
display: inline-block;
}
#eventImage {
@ -215,6 +249,14 @@
margin-top: 2px;
}
#eventImageBar::after {
visibility: hidden;
display: block;
content: "";
clear: both;
height: 0;
}
#eventImageStats {
float: left;
}
@ -230,6 +272,7 @@
#eventImageNav {
position: relative;
margin: 0 0 4px 0;
}
#eventImageNav input {
@ -238,20 +281,20 @@
}
#thumbsSliderPanel {
width: 400px;
margin: 4px auto 0;
background: #888888;
padding: 1px;
width: 80%;
margin: 0px auto 4px auto;
}
#thumbsSlider {
width: 400px;
height: 10px;
background: #dddddd;
width: 100%;
height: 1.25em;
position: relative;
top: -1.25em;
margin: 0 0 -1.25em 0;
}
#thumbsKnob {
width: 8px;
height: 10px;
background-color: #444444;
width: 1em;
height: 100%;
background-color: #999999;
}

View File

@ -1,33 +1,16 @@
#scaleControl {
float: right;
}
#controls {
width: 80%;
text-align: center;
margin: 0 auto;
display: flex;
justify-content: space-between;
}
#controls a {
width: 40px;
margin-left: -20px;
}
#firstLink {
position: absolute;
left: 13%;
}
#prevLink {
position: absolute;
left: 37%;
}
#nextLink {
position: absolute;
left: 63%;
}
#lastLink {
position: absolute;
left: 87%;
}

View File

@ -1,3 +1,39 @@
#content .vjsMessage {
width: 100%;
position: absolute;
left: 0;
z-index: 10;
margin: 0;
font-size: 200%;
color: white;
background-color: black;
display: inline-block;
}
.alarmCue {
background-color: #222222;
height: 1.25em;
text-align: left;
margin: 0 auto 0 auto;
border-radius: 0 0 .3em .3em;
}
.alarmCue span {
background-color:red;
height: 100%;
display: inline-block;
border-radius: 0;
}
span.noneCue {
background: none;
}
#eventVideo {
display: inline-block;
position: relative;
}
#dataBar {
width: 100%;
margin: 2px auto;
@ -6,6 +42,7 @@
#dataBar #dataTable {
width: 100%;
table-layout: fixed;
}
#dataBar #dataTable td {
@ -66,6 +103,8 @@
}
#imageFeed {
display: inline-block;
position: relative;
text-align: center;
}
@ -130,39 +169,30 @@
#progressBar {
position: relative;
border: 1px solid #666666;
height: 15px;
margin: 0 auto;
top: -1.25em;
height: 1.25em;
margin: 0 auto -1.25em auto;
}
#progressBar .progressBox {
position: absolute;
top: 0px;
left: 0px;
height: 15px;
background: #eeeeee;
border-left: 1px solid #999999;
}
#progressBar .complete {
background: #aaaaaa;
transition: width .1s;
height: 100%;
background: rgba(170, 170, 170, .7);
border-radius: 0 0 .3em .3em;
}
#eventStills {
width: 100%;
position: relative;
}
#eventThumbsPanel {
position: relative;
width: 100%;
margin: 4px auto;
z-index: 1;
}
#eventThumbs {
margin: 0 auto;
width: 100%;
overflow: hidden;
height: 300px;
}
@ -181,14 +211,19 @@
}
#eventImagePanel {
display: none;
position: absolute;
top: 0;
left: 0;
z-index: 10;
width: 100%;
}
#eventImageFrame {
border: 2px solid gray;
background-color: white;
padding: 4px;
display: inline-block;
}
#eventImage {
@ -198,6 +233,14 @@
margin-top: 2px;
}
#eventImageBar::after {
visibility: hidden;
display: block;
content: "";
clear: both;
height: 0;
}
#eventImageStats {
float: left;
}
@ -213,6 +256,7 @@
#eventImageNav {
position: relative;
margin: 0 0 4px 0;
}
#eventImageNav input {
@ -222,19 +266,25 @@
#thumbsSliderPanel {
width: 400px;
margin: 4px auto 0;
background: #888888;
padding: 1px;
width: 80%;
margin: 0px auto 4px auto;
}
#thumbsSlider {
width: 400px;
height: 10px;
background: #dddddd;
width: 100%;
height: 1.25em;
position: relative;
top: -1.25em;
margin: 0 0 -1.25em 0;
}
#eventVideo {
display: inline-block;
position: relative;
}
#thumbsKnob {
width: 8px;
height: 10px;
background-color: #444444;
width: 1em;
height: 100%;
background-color: #999999;
}

View File

@ -1,33 +1,16 @@
#scaleControl {
float: right;
}
#controls {
width: 80%;
text-align: center;
margin: 0 auto;
display: flex;
justify-content: space-between;
}
#controls a {
width: 40px;
margin-left: -20px;
}
#firstLink {
position: absolute;
left: 13%;
}
#prevLink {
position: absolute;
left: 37%;
}
#nextLink {
position: absolute;
left: 63%;
}
#lastLink {
position: absolute;
left: 87%;
}

View File

@ -1,3 +1,34 @@
#content .vjsMessage {
width: 100%;
position: absolute;
left: 0;
z-index: 10;
margin: 0;
font-size: 200%;
color: white;
background-color: black;
display: inline-block;
}
.alarmCue {
background-color: #222222;
height: 1.25em;
text-align: left;
margin: 0 auto 0 auto;
border-radius: 0 0 .3em .3em;
}
.alarmCue span {
background-color:red;
height: 100%;
display: inline-block;
border-radius: 0;
}
span.noneCue {
background: none;
}
#dataBar {
width: 100%;
margin: 2px auto;
@ -6,6 +37,7 @@
#dataBar #dataTable {
width: 100%;
table-layout: fixed;
}
#dataBar #dataTable td {
@ -64,7 +96,10 @@
clear: both;
visibility: hidden;
}
#imageFeed {
display: inline-block;
position: relative;
text-align: center;
}
@ -135,39 +170,30 @@
#progressBar {
position: relative;
border: 1px solid #666666;
height: 15px;
margin: 0 auto;
top: -1.25em;
height: 1.25em;
margin: 0 auto -1.25em auto;
}
#progressBar .progressBox {
position: absolute;
top: 0px;
left: 0px;
height: 15px;
background: #eeeeee;
border-left: 1px solid #999999;
}
#progressBar .complete {
background: #aaaaaa;
transition: width .1s;
height: 100%;
background: rgba(170, 170, 170, .7);
border-radius: 0 0 .3em .3em;
}
#eventStills {
width: 100%;
position: relative;
}
#eventThumbsPanel {
position: relative;
width: 100%;
margin: 4px auto;
margin: 0;
z-index: 1;
}
#eventThumbs {
margin: 0 auto;
width: 100%;
overflow: hidden;
height: 300px;
}
@ -186,14 +212,19 @@
}
#eventImagePanel {
display: none;
position: absolute;
top: 0;
left: 0;
z-index: 10;
width: 100%;
}
#eventImageFrame {
border: 2px solid gray;
background-color: white;
padding: 4px;
display: inline-block;
}
#eventImage {
@ -216,8 +247,17 @@
float: right;
}
#eventImageBar::after {
visibility: hidden;
display: block;
content: "";
clear: both;
height: 0;
}
#eventImageNav {
position: relative;
margin: 0 0 4px 0;
}
#eventImageNav input {
@ -226,24 +266,25 @@
}
#thumbsSliderPanel {
width: 400px;
margin: 4px auto 0;
background: #888888;
padding: 1px;
width: 80%;
margin: 0px auto 4px auto;
}
#thumbsSlider {
width: 400px;
height: 10px;
background: #dddddd;
width: 100%;
height: 1.25em;
position: relative;
top: -1.25em;
margin: 0 0 -1.25em 0;
}
#thumbsKnob {
width: 8px;
height: 10px;
background-color: #444444;
width: 1em;
height: 100%;
background-color: #999999;
}
#eventVideo {
display: inline-block;
position: relative;
}
@ -274,10 +315,6 @@
);
}
#eventVideo:hover #video-controls {
opacity: .9;
}
button {
background: rgba(0,0,0,.5);
border: 0;

View File

@ -6,29 +6,11 @@
width: 80%;
text-align: center;
margin: 0 auto;
display: flex;
justify-content: space-between;
}
#controls a {
width: 40px;
margin-left: -20px;
}
#firstLink {
position: absolute;
left: 13%;
}
#prevLink {
position: absolute;
left: 37%;
}
#nextLink {
position: absolute;
left: 63%;
}
#lastLink {
position: absolute;
left: 87%;
}

View File

@ -31,6 +31,7 @@ $rates = array(
);
$scales = array(
"auto" => translate("Scale to Fit"),
"400" => "4x",
"300" => "3x",
"200" => "2x",
@ -43,6 +44,8 @@ $scales = array(
"12.5" => "1/8x",
);
if (isset($_REQUEST['view']) && ($_REQUEST['view'] == 'montage')) unset($scales['auto']); //Remove auto if we aren't using event view
$bwArray = array(
"high" => translate('High'),
"medium" => translate('Medium'),

View File

@ -92,6 +92,7 @@ var $j = jQuery.noConflict();
<?php } else if ( $title == 'Event' ) {
?>
<link href="skins/<?php echo $skin ?>/js/video-js.css" rel="stylesheet">
<link href="skins/<?php echo $skin ?>/js/video-js-skin.css" rel="stylesheet">
<script src="skins/<?php echo $skin ?>/js/video.js"></script>
<script src="./js/videojs.zoomrotate.js"></script>
<script src="skins/<?php echo $skin ?>/js/moment.min.js"></script>

View File

@ -262,6 +262,17 @@ function convertLabelFormat(LabelFormat, monitorName){
}
function addVideoTimingTrack(video, LabelFormat, monitorName, duration, startTime){
//This is a hacky way to handle changing the texttrack. If we ever upgrade vjs in a revamp replace this. Old method preserved because it's the right way.
let cues = vid.textTracks()[0].cues();
let labelFormat = convertLabelFormat(LabelFormat, monitorName);
startTime = moment(startTime);
for (let i = 0; i <= duration; i++) {
cues[i] = {id: i, index: i, startTime: i, Ca: i+1, text: startTime.format(labelFormat)};
startTime.add(1, 's');
}
}
/*
var labelFormat = convertLabelFormat(LabelFormat, monitorName);
var webvttformat = 'HH:mm:ss.SSS', webvttdata="WEBVTT\n\n";
@ -283,3 +294,37 @@ function addVideoTimingTrack(video, LabelFormat, monitorName, duration, startTim
track.src = 'data:plain/text;charset=utf-8,'+encodeURIComponent(webvttdata);
video.appendChild(track);
}
*/
var resizeTimer;
function endOfResize(e) {
clearTimeout(resizeTimer);
resizeTimer = setTimeout(changeScale, 250);
}
function scaleToFit (baseWidth, baseHeight, scaleEl, bottomEl) {
$j(window).on('resize', endOfResize) //set delayed scaling when Scale to Fit is selected
let ratio = baseWidth / baseHeight;
let container = $j('#content');
let viewPort = $j(window);
// jquery does not provide a bottom offet, and offset dows not include margins. outerHeight true minus false gives total vertical margins.
let bottomLoc = bottomEl.offset().top + (bottomEl.outerHeight(true) - bottomEl.outerHeight()) + bottomEl.outerHeight(true);
let newHeight = viewPort.height() - (bottomLoc - scaleEl.outerHeight(true))
let newWidth = ratio * newHeight;
if (newWidth > container.innerWidth()) {
newWidth = container.innerWidth();
newHeight = newWidth / ratio;
}
let autoScale = Math.round(newWidth / baseWidth * SCALE_BASE);
let scales = $j('#scale option').map(function() {return parseInt($j(this).val());}).get();
scales.shift();
let closest;
$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)) {
closest = this.valueOf();
}
});
autoScale = closest;
return {width: Math.floor(newWidth), height: Math.floor(newHeight), autoScale: autoScale};
}

View File

@ -39,4 +39,4 @@ var refreshParent = <?php echo !empty($refreshParent)?'true':'false' ?>;
var focusWindow = <?php echo !empty($focusWindow)?'true':'false' ?>;
var imagePrefix = "<?php echo viewImagePath( "", '&' ) ?>";
var imagePrefix = "<?php echo "?view=image&eid=" ?>";

View File

@ -0,0 +1,65 @@
.vjs-tech {
pointer-events: none;
transition: transform .25s;
}
.vjs-captions-button {
display: none;
}
.vjs-tt-cue {
margin-bottom: .75em;
}
.vjs-menu {
z-index: 5;
}
.vjs-default-skin .vjs-playback-rate.vjs-menu-button .vjs-menu .vjs-menu-content {
width: 5em;
}
.vjs-default-skin.vjs-user-inactive:hover .vjs-progress-control {
font-size: .3em;
}
.vjs-default-skin.vjs-has-started.vjs-user-inactive.vjs-playing .vjs-control-bar {
visibility: visible;
opacity: 1;
bottom: -2em;
-webkit-transition: all .2s;
-moz-transition: all .2s;
-o-transition: all .2s;
transition: all .2s
}
.vjs-default-skin.vjs-has-started.vjs-user-inactive.vjs-playing .vjs-control,
.vjs-default-skin.vjs-has-started.vjs-user-inactive.vjs-playing .vjs-time-divider {
display: none;
}
.vjs-default-skin .vjs-progress-holder .vjs-play-progress, .vjs-default-skin .vjs-progress-holder .vjs-load-progress {
height: 1em;
}
.vjs-progress-holder.vjs-slider {
background-color: transparent;
z-index: 1;
}
.vjs-progress-control.vjs-control {
background-color: rgba(7, 20, 30, 0.8);
height: 1.3em;
top: -1.3em;
display: block !important;
}
.vjs-control div.alarmCue {
position: relative;
top: -1.3em;
background: none;
}
.vjs-control .alarmCue {
height: 1.3em;
}

View File

@ -43,6 +43,8 @@ else
if ( isset( $_REQUEST['scale'] ) ) {
$scale = validInt($_REQUEST['scale']);
} else if ( isset( $_COOKIE['zmEventScaleAuto'] ) ) { //If we're using scale to fit use it on all monitors
$scale = 'auto';
} else if ( isset( $_COOKIE['zmEventScale'.$Event->MonitorId()] ) ) {
$scale = $_COOKIE['zmEventScale'.$Event->MonitorId()];
} else {
@ -61,6 +63,7 @@ if ( isset( $_REQUEST['streamMode'] ) )
else
$streamMode = 'video';
$replayMode = '';
if ( isset( $_REQUEST['replayMode'] ) )
$replayMode = validHtmlStr($_REQUEST['replayMode']);
if ( isset( $_COOKIE['replayMode']) && preg_match('#^[a-z]+$#', $_COOKIE['replayMode']) )
@ -84,10 +87,6 @@ parseSort();
parseFilter( $_REQUEST['filter'] );
$filterQuery = $_REQUEST['filter']['query'];
$panelSections = 40;
$panelSectionWidth = (int)ceil(reScale($Event->Width(),$scale)/$panelSections);
$panelWidth = ($panelSections*$panelSectionWidth-1);
$connkey = generateConnKey();
$focusWindow = true;
@ -126,24 +125,25 @@ if ( canEdit( 'Events' ) ) {
?>
<div id="deleteEvent"><a href="#" onclick="deleteEvent()"><?php echo translate('Delete') ?></a></div>
<div id="editEvent"><a href="#" onclick="editEvent()"><?php echo translate('Edit') ?></a></div>
<div id="archiveEvent" class="hidden"><a href="#" onclick="archiveEvent()"><?php echo translate('Archive') ?></a></div>
<div id="unarchiveEvent" class="hidden"><a href="#" onclick="unarchiveEvent()"><?php echo translate('Unarchive') ?></a></div>
<?php
<div id="archiveEvent"<?php echo $Event->Archived == 1 ? ' class="hidden"' : '' ?>><a href="#" onclick="archiveEvent()"><?php echo translate('Archive') ?></a></div>
<div id="unarchiveEvent"<?php echo $Event->Archived == 0 ? ' class="hidden"' : '' ?>><a href="#" onclick="unarchiveEvent()"><?php echo translate('Unarchive') ?></a></div>
<?php
} // end if can edit Events
if ( $Event->DefaultVideo() ) { ?>
<div id="downloadEventFile"><a href="<?php echo $Event->getStreamSrc()?>">Download MP4</a></div>
?>
<div id="framesEvent"><a href="#" onclick="showEventFrames()"><?php echo translate('Frames') ?></a></div>
<div id="streamEvent" class="hidden"><a href="#" onclick="showStream()"><?php echo translate('Stream') ?></a></div>
<div id="stillsEvent"><a href="#" onclick="showStills()"><?php echo translate('Stills') ?></a></div>
<?php
if ( $Event->DefaultVideo() ) {
?>
<div id="downloadEventFile"><a href="<?php echo $Event->getStreamSrc(array('mode'=>'mp4'))?>" download>Download MP4</a></div>
<?php
} else {
?>
<div id="videoEvent"><a href="#" onclick="videoEvent();"><?php echo translate('Video') ?></a></div>
<?php
} // end if Event->DefaultVideo
?>
<div id="framesEvent"><a href="#" onclick="showEventFrames()"><?php echo translate('Frames') ?></a></div>
<?php
if ( $Event->SaveJPEGs() & 3 ) { // Analysis or Jpegs
?>
<div id="stillsEvent"<?php if ( $streamMode == 'still' ) { ?> class="hidden"<?php } ?>><a href="#" onclick="showStills()"><?php echo translate('Stills') ?></a></div>
<?php
} // has frames or analysis
?>
<div id="videoEvent"><a href="#" onclick="videoEvent();"><?php echo translate('Video') ?></a></div>
<div id="exportEvent"><a href="#" onclick="exportEvent();"><?php echo translate('Export') ?></a></div>
</div>
<div id="eventVideo" class="">
@ -151,23 +151,17 @@ if ( $Event->SaveJPEGs() & 3 ) { // Analysis or Jpegs
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 ?>"}}}'>
<video id="videoobj" class="video-js vjs-default-skin" style="transform: matrix(1, 0, 0, 1, 0, 0)" width="<?php echo reScale( $Event->Width(), $scale ) ?>" height="<?php echo reScale( $Event->Height(), $scale ) ?>" data-setup='{ "controls": true, "autoplay": true, "preload": "auto", "plugins": { "zoomrotate": { "zoom": "<?php echo $Zoom ?>"}}}'>
<source src="<?php echo $Event->getStreamSrc( array( 'mode'=>'mpeg','format'=>'h264' ) ); ?>" type="video/mp4">
<track id="monitorCaption" kind="captions" label="English" srclang="en" src='data:plain/text;charset=utf-8,"WEBVTT\n\n 00:00:00.000 --> 00:00:01.000 ZoneMinder"' default>
Your browser does not support the video tag.
</video>
</div>
<!--script>includeVideoJs();</script-->
<script>
var LabelFormat = "<?php echo validJsStr($Monitor->LabelFormat())?>";
var monitorName = "<?php echo validJsStr($Monitor->Name())?>";
var duration = <?php echo $Event->Length() ?>, startTime = '<?php echo $Event->StartTime() ?>';
addVideoTimingTrack(document.getElementById('videoobj'), LabelFormat, monitorName, duration, startTime);
</script>
</div><!--videoFeed-->
<?php
} // end if DefaultVideo
?>
<div id="imageFeed" <?php if ( $Event->DefaultVideo() ) { ?>class="hidden"<?php } ?> >
<?php if (!$Event->DefaultVideo()) { ?>
<div id="imageFeed">
<?php
if ( ZM_WEB_STREAM_METHOD == 'mpeg' && ZM_MPEG_LIVE_FORMAT ) {
$streamSrc = $Event->getStreamSrc( array( 'mode'=>'mpeg', 'scale'=>$scale, 'rate'=>$rate, 'bitrate'=>ZM_WEB_VIDEO_BITRATE, 'maxfps'=>ZM_WEB_VIDEO_MAXFPS, 'format'=>ZM_MPEG_REPLAY_FORMAT, 'replay'=>$replayMode ) );
@ -181,34 +175,30 @@ if ( ZM_WEB_STREAM_METHOD == 'mpeg' && ZM_MPEG_LIVE_FORMAT ) {
}
} // end if stream method
?>
</div>
<div id="alarmCue" class="alarmCue"></div>
<div id="progressBar" style="width: <?php echo reScale($Event->Width(), $scale);?>px;">
<div class="progressBox" id="progressBox" title="" style="width: 0%;"></div>
</div><!--progressBar-->
</div><!--imageFeed-->
<?php } /*end if !DefaultVideo*/ ?>
<p id="dvrControls">
<input type="button" value="&lt;+" id="prevBtn" title="<?php echo translate('Prev') ?>" class="inactive" onclick="streamPrev( true );"/>
<input type="button" value="&lt;&lt;" id="fastRevBtn" title="<?php echo translate('Rewind') ?>" class="inactive" disabled="disabled" onclick="streamFastRev( true );"/>
<input type="button" value="&lt;&lt;" id="fastRevBtn" title="<?php echo translate('Rewind') ?>" class="inactive" onclick="streamFastRev( true );"/>
<input type="button" value="&lt;" id="slowRevBtn" title="<?php echo translate('StepBack') ?>" class="unavail" disabled="disabled" onclick="streamSlowRev( true );"/>
<input type="button" value="||" id="pauseBtn" title="<?php echo translate('Pause') ?>" class="inactive" onclick="pauseClicked();"/>
<input type="button" value="|>" id="playBtn" title="<?php echo translate('Play') ?>" class="active" disabled="disabled" onclick="playClicked();"/>
<input type="button" value="&gt;" id="slowFwdBtn" title="<?php echo translate('StepForward') ?>" class="unavail" disabled="disabled" onclick="streamSlowFwd( true );"/>
<input type="button" value="&gt;&gt;" id="fastFwdBtn" title="<?php echo translate('FastForward') ?>" class="inactive" disabled="disabled" onclick="streamFastFwd( true );"/>
<input type="button" value="&ndash;" id="zoomOutBtn" title="<?php echo translate('ZoomOut') ?>" class="avail" onclick="streamZoomOut();"/>
<input type="button" value="&gt;&gt;" id="fastFwdBtn" title="<?php echo translate('FastForward') ?>" class="inactive" onclick="streamFastFwd( true );"/>
<input type="button" value="&ndash;" id="zoomOutBtn" title="<?php echo translate('ZoomOut') ?>" class="unavail" disabled="disabled" onclick="streamZoomOut();"/>
<input type="button" value="+&gt;" id="nextBtn" title="<?php echo translate('Next') ?>" class="inactive" onclick="streamNext( true );"/>
</p>
<div id="replayStatus">
<span id="mode"><?php echo translate('Mode') ?>: <span id="modeValue">&nbsp;</span></span>
<span id="rate"><?php echo translate('Rate') ?>: <span id="rateValue"></span>x</span>
<span id="progress"><?php echo translate('Progress') ?>: <span id="progressValue"></span>s</span>
<span id="zoom"><?php echo translate('Zoom') ?>: <span id="zoomValue"></span>x</span>
<span id="mode"><?php echo translate('Mode') ?>: <span id="modeValue">Replay</span></span>
<span id="rate"><?php echo translate('Rate') ?>: <span id="rateValue"><?php echo $rate/100 ?></span>x</span>
<span id="progress"><?php echo translate('Progress') ?>: <span id="progressValue">0</span>s</span>
<span id="zoom"><?php echo translate('Zoom') ?>: <span id="zoomValue">1</span>x</span>
</div>
<div id="progressBar" class="invisible">
<?php for ( $i = 0; $i < $panelSections; $i++ ) { ?>
<div class="progressBox" id="progressBox<?php echo $i ?>" title=""></div>
<?php } ?>
</div>
</div>
</div>
<?php
if ($Event->SaveJPEGs() & 3) { // frames or analysis
?>
</div><!--eventVideo-->
<div id="eventStills" class="hidden">
<div id="eventThumbsPanel">
<div id="eventThumbs">
@ -225,6 +215,13 @@ if ( ZM_WEB_STREAM_METHOD == 'mpeg' && ZM_MPEG_LIVE_FORMAT ) {
</div>
</div>
<div id="eventImageNav">
<div id="thumbsSliderPanel">
<div id="alarmCue" class="alarmCue"></div>
<div id="thumbsSlider">
<div id="thumbsKnob">
</div>
</div>
</div>
<div id="eventImageButtons">
<div id="prevButtonsPanel">
<input id="prevEventBtn" type="button" value="&lt;E" onclick="prevEvent()" disabled="disabled"/>
@ -235,18 +232,12 @@ if ( ZM_WEB_STREAM_METHOD == 'mpeg' && ZM_MPEG_LIVE_FORMAT ) {
<input id="nextEventBtn" type="button" value="E&gt;" onclick="nextEvent()" disabled="disabled"/>
</div>
</div>
<div id="thumbsSliderPanel">
<div id="thumbsSlider">
<div id="thumbsKnob">
</div>
</div>
</div>
</div>
</div>
<?php
} // end if SaveJPEGs() & 3 Analysis or Jpegs
} // end if Event exists
?>
</div><!--content-->
</div><!--page-->
</body>
</html>

View File

@ -51,7 +51,7 @@ $lastFid = $maxFid;
$alarmFrame = $Frame->Type()=='Alarm';
if ( isset( $_REQUEST['scale'] ) ) {
$scale = validInt($_REQUEST['scale']);
$scale = $_REQUEST['scale'];
} else if ( isset( $_COOKIE['zmWatchScale'.$Monitor->Id()] ) ) {
$scale = $_COOKIE['zmWatchScale'.$Monitor->Id()];
} else if ( isset( $_COOKIE['zmWatchScale'] ) ) {

View File

@ -1,31 +1,179 @@
var vid = null;
function vjsReplay() {
let endTime = (Date.parse(eventData.EndTime)).getTime();
switch(replayMode.value) {
case 'none':
break;
case 'single':
player.play();
break;
case 'all':
if (nextEventId == 0) {
let overLaid = $j("#videoobj");
overLaid.append('<p class="vjsMessage" style="height: '+overLaid.height()+'px; line-height: '+overLaid.height()+'px;">No more events</p>');
} else {
let nextStartTime = nextEventStartTime.getTime(); //nextEventStartTime.getTime() is a mootools workaround, highjacks Date.parse
if (nextStartTime <= endTime) {
streamNext( true );
return;
}
let overLaid = $j("#videoobj");
vid.pause();
overLaid.append('<p class="vjsMessage" style="height: '+overLaid.height()+'px; line-height: '+overLaid.height()+'px;"></p>');
let gapDuration = (new Date().getTime()) + (nextStartTime - endTime);
let messageP = $j(".vjsMessage");
let x = setInterval(function() {
let now = new Date().getTime();
let remainder = new Date(Math.round(gapDuration - now)).toISOString().substr(11,8);
messageP.html(remainder + ' to next event.');
if (remainder < 0) {
clearInterval(x);
streamNext( true );
}
}, 1000);
}
break;
case 'gapless':
streamNext( true );
break;
}
}
$j.ajaxSetup ({timeout: AJAX_TIMEOUT }); //sets timeout for all getJSON.
var cueFrames = null; //make cueFrames availaible even if we don't send another ajax query
function initialAlarmCues (eventId) {
$j.getJSON(thisUrl + '?view=request&request=status&entity=frames&id=' + eventId, setAlarmCues); //get frames data for alarmCues and inserts into html
}
function setAlarmCues (data) {
cueFrames = data.frames;
alarmSpans = renderAlarmCues(vid ? $j("#videoobj") : $j("#evtStream"));//use videojs width or zms width
$j(".alarmCue").html(alarmSpans);
}
function renderAlarmCues (containerEl) {
if (cueFrames) {
var cueRatio = containerEl.width() / (cueFrames[cueFrames.length - 1].Delta * 100);
var minAlarm = Math.ceil(1/cueRatio);
var spanTimeStart = 0;
var spanTimeEnd = 0;
var alarmed = 0;
var alarmHtml = "";
var pixSkew = 0;
var skip = 0;
for (let i = 0; i < cueFrames.length; i++) {
skip = 0;
frame = cueFrames[i];
if (frame.Type == "Alarm" && alarmed == 0) { //From nothing to alarm. End nothing and start alarm.
alarmed = 1;
if (frame.Delta == 0) continue; //If event starts with an alarm or too few for a nonespan
spanTimeEnd = frame.Delta * 100;
spanTime = spanTimeEnd - spanTimeStart;
let pix = cueRatio * spanTime;
pixSkew += pix - Math.round(pix);//average out the rounding errors.
pix = Math.round(pix);
if ((pixSkew > 1 || pixSkew < -1) && pix + Math.round(pixSkew) > 0) { //add skew if it's a pixel and won't zero out span.
pix += Math.round(pixSkew);
pixSkew = pixSkew - Math.round(pixSkew);
}
alarmHtml += '<span class="alarmCue noneCue" style="width: ' + pix + 'px;"></span>';
spanTimeStart = spanTimeEnd;
} else if (frame.Type !== "Alarm" && alarmed == 1) { //from alarm to nothing. End alarm and start nothing.
futNone = 0;
indexPlus = i+1;
if (((frame.Delta * 100) - spanTimeStart) < minAlarm && indexPlus < cueFrames.length) continue; //alarm is too short and there is more event
while (futNone < minAlarm) { //check ahead to see if there's enough for a nonespan
if (indexPlus >= cueFrames.length) break; //check if end of event.
futNone = (cueFrames[indexPlus].Delta *100) - (frame.Delta *100);
if (cueFrames[indexPlus].Type == "Alarm") {
i = --indexPlus;
skip = 1;
break;
}
indexPlus++;
}
if (skip == 1) continue; //javascript doesn't support continue 2;
spanTimeEnd = frame.Delta *100;
spanTime = spanTimeEnd - spanTimeStart;
alarmed = 0;
pix = cueRatio * spanTime;
pixSkew += pix - Math.round(pix);
pix = Math.round(pix);
if ((pixSkew > 1 || pixSkew < -1) && pix + Math.round(pixSkew) > 0) {
pix += Math.round(pixSkew);
pixSkew = pixSkew - Math.round(pixSkew);
}
alarmHtml += '<span class="alarmCue" style="width: ' + pix + 'px;"></span>';
spanTimeStart = spanTimeEnd;
} else if (frame.Type == "Alarm" && alarmed == 1 && i + 1 >= cueFrames.length) { //event ends on an alarm
spanTimeEnd = frame.Delta * 100;
spanTime = spanTimeEnd - spanTimeStart;
alarmed = 0;
pix = Math.round(cueRatio * spanTime);
if (pixSkew >= .5 || pixSkew <= -.5) pix += Math.round(pixSkew);
alarmHtml += '<span class="alarmCue" style="width: ' + pix + 'px;"></span>';
}
}
return alarmHtml;
}
}
function setButtonState( element, butClass ) {
if ( element ) {
element.className = butClass;
element.disabled = (butClass != 'inactive');
if (butClass == 'unavail' || (butClass == 'active' && (element.id == 'pauseBtn' || element.id == 'playBtn'))) {
element.disabled = true;
} else {
element.disabled = false;
}
} else {
console.log("Element was null in setButtonState");
}
}
function changeScale() {
var scale = $('scale').get('value');
var baseWidth = eventData.Width;
var baseHeight = eventData.Height;
var newWidth = ( baseWidth * scale ) / SCALE_BASE;
var newHeight = ( baseHeight * scale ) / SCALE_BASE;
if ( vid ) {
// Using video.js
vid.width = newWidth;
vid.height = newHeight;
} else {
streamScale( scale );
var streamImg = document.getElementById('evtStream');
streamImg.style.width = newWidth + "px";
streamImg.style.height = newHeight + "px";
Cookie.write( 'zmEventScale'+eventData.MonitorId, scale, { duration: 10*365 } );
let scale = $j('#scale').val();
let newWidth;
let newHeight;
let autoScale;
let eventViewer;
let alarmCue = $j('div.alarmCue');
let bottomEl = streamMode == 'stills' ? $j('#eventImageNav') : $j('#replayStatus');
if (streamMode == 'stills') {
eventViewer = $j('#eventThumbs');
} else {
eventViewer = $j(vid ? '#videoobj' : '#evtStream');
}
if (scale == "auto") {
let newSize = scaleToFit(eventData.Width, eventData.Height, eventViewer, bottomEl);
newWidth = newSize.width;
newHeight = newSize.height;
autoScale = newSize.autoScale;
} else {
$j(window).off('resize', endOfResize); //remove resize handler when Scale to Fit is not active
newWidth = eventData.Width * scale / SCALE_BASE;
newHeight = eventData.Height * scale / SCALE_BASE;
}
if (!(streamMode == 'stills')) eventViewer.width(newWidth); //stills handles its own width
eventViewer.height(newHeight);
if ( !vid ) { // zms needs extra sizing
streamScale(scale == "auto" ? autoScale : scale);
drawProgressBar();
}
if (streamMode == 'stills') {
slider.autosize();
alarmCue.html(renderAlarmCues($j('#thumbsSliderPanel')));
} else {
alarmCue.html(renderAlarmCues(eventViewer));//just re-render alarmCues. skip ajax call
}
if (scale == "auto") {
Cookie.write('zmEventScaleAuto', 'auto', {duration: 10*365});
}else{
Cookie.write('zmEventScale'+eventData.MonitorId, scale, {duration: 10*365});
Cookie.dispose('zmEventScaleAuto');
}
}
@ -42,33 +190,38 @@ var streamCmdTimer = null;
var streamStatus = null;
var lastEventId = 0;
var zmsBroke = false; //Use alternate navigation if zms has crashed
function getCmdResponse( respObj, respText ) {
if ( checkStreamForErrors( "getCmdResponse", respObj ) )
if ( checkStreamForErrors( "getCmdResponse", respObj ) ) {
console.log('Got an error from getCmdResponse');
zmsBroke = true;
return;
}
zmsBroke = false;
if ( streamCmdTimer )
streamCmdTimer = clearTimeout( streamCmdTimer );
streamStatus = respObj.status;
if (streamStatus.progress >= Math.round(parseFloat(eventData.Length))) streamStatus.progress = parseFloat(eventData.Length); //Limit progress to reality
var eventId = streamStatus.event;
if ( eventId != lastEventId ) {
if ( eventId != lastEventId && lastEventId != 0) { //Doesn't run on first load, prevents a double hit on event and nearEvents ajax
eventQuery( eventId );
initialAlarmCues(eventId);
lastEventId = eventId;
}
if (lastEventId == 0) lastEventId = eventId; //Only fires on first load.
if ( streamStatus.paused == true ) {
$('modeValue').set( 'text', 'Paused' );
$('rate').addClass( 'hidden' );
streamPause( );
} else {
$('modeValue').set( 'text', "Replay" );
$('rateValue').set( 'text', streamStatus.rate );
$('rate').removeClass( 'hidden' );
$j('#rateValue').html(streamStatus.rate);
streamPlay( );
}
$('progressValue').set( 'text', secsToTime( parseInt(streamStatus.progress) ) );
$('zoomValue').set( 'text', streamStatus.zoom );
$j('#progressValue').html(secsToTime(parseInt(streamStatus.progress)));
$j('#zoomValue').html(streamStatus.zoom);
if ( streamStatus.zoom == "1.0" )
setButtonState( $('zoomOutBtn'), 'unavail' );
else
@ -83,17 +236,29 @@ function getCmdResponse( respObj, respText ) {
streamImg.src = streamImg.src.replace( /auth=\w+/i, 'auth='+streamStatus.auth );
} // end if haev a new auth hash
streamCmdTimer = streamQuery.delay( streamTimeout );
streamCmdTimer = streamQuery.delay( streamTimeout ); //Timeout is refresh rate for progressBox and time display
}
var streamReq = new Request.JSON( { url: thisUrl, method: 'get', timeout: AJAX_TIMEOUT, link: 'chain', onSuccess: getCmdResponse } );
function pauseClicked( ) {
streamReq.send( streamParms+"&command="+CMD_PAUSE );
function pauseClicked() {
if (vid) {
vid.pause();
} else {
streamReq.send( streamParms+"&command="+CMD_PAUSE );
streamPause();
}
}
function vjsPause () {
stopFastRev();
streamPause();
}
// Called when stream becomes paused, just updates the button status
function streamPause( ) {
$j('#modeValue').html('Paused');
$j('#rateValue').html('0');
setButtonState( $('pauseBtn'), 'active' );
setButtonState( $('playBtn'), 'inactive' );
setButtonState( $('fastFwdBtn'), 'unavail' );
@ -103,13 +268,28 @@ function streamPause( ) {
}
function playClicked( ) {
streamReq.send( streamParms+"&command="+CMD_PLAY );
if (vid) {
if (vid.paused()) {
vid.play();
} else {
vjsPlay(); //handles fast forward and rewind
}
} else {
streamReq.send( streamParms+"&command="+CMD_PLAY );
streamPlay();
}
}
function vjsPlay () { //catches if we change mode programatically
stopFastRev();
$j('#rateValue').html(vid.playbackRate());
streamPlay();
}
function streamPlay( ) {
$j('#modeValue').html('Replay');
setButtonState( $('pauseBtn'), 'inactive' );
if (streamStatus)
setButtonState( $('playBtn'), streamStatus.rate==1?'active':'inactive' );
setButtonState( $('playBtn'), 'active' );
setButtonState( $('fastFwdBtn'), 'inactive' );
setButtonState( $('slowFwdBtn'), 'unavail' );
setButtonState( $('slowRevBtn'), 'unavail' );
@ -119,35 +299,44 @@ function streamPlay( ) {
function streamFastFwd( action ) {
setButtonState( $('pauseBtn'), 'inactive' );
setButtonState( $('playBtn'), 'inactive' );
setButtonState( $('fastFwdBtn'), 'inactive' );
setButtonState( $('fastFwdBtn'), 'active' );
setButtonState( $('slowFwdBtn'), 'unavail' );
setButtonState( $('slowRevBtn'), 'unavail' );
setButtonState( $('fastRevBtn'), 'inactive' );
streamReq.send( streamParms+"&command="+CMD_FASTFWD );
if (vid) {
if (revSpeed != .5) stopFastRev();
vid.playbackRate(rates[rates.indexOf(vid.playbackRate()*100)-1]/100);
if (rates.indexOf(vid.playbackRate()*100)-1 == -1) setButtonState($('fastFwdBtn'), 'unavail');
$j('#rateValue').html(vid.playbackRate());
} else {
streamReq.send( streamParms+"&command="+CMD_FASTFWD );
}
}
var spf = Math.round((eventData.Length / eventData.Frames)*1000000 )/1000000;//Seconds per frame for videojs frame by frame.
var intervalRewind;
var revSpeed = .5;
function streamSlowFwd( action ) {
setButtonState( $('pauseBtn'), 'inactive' );
setButtonState( $('playBtn'), 'inactive' );
setButtonState( $('fastFwdBtn'), 'unavail' );
setButtonState( $('slowFwdBtn'), 'active' );
setButtonState( $('slowRevBtn'), 'inactive' );
setButtonState( $('fastRevBtn'), 'unavail' );
streamReq.send( streamParms+"&command="+CMD_SLOWFWD );
setButtonState( $('pauseBtn'), 'inactive' );
setButtonState( $('slowFwdBtn'), 'inactive' );
if (vid) {
vid.currentTime(vid.currentTime() + spf);
} else {
streamReq.send( streamParms+"&command="+CMD_SLOWFWD );
}
}
function streamSlowRev( action ) {
setButtonState( $('pauseBtn'), 'inactive' );
setButtonState( $('playBtn'), 'inactive' );
setButtonState( $('fastFwdBtn'), 'unavail' );
setButtonState( $('slowFwdBtn'), 'inactive' );
setButtonState( $('slowRevBtn'), 'active' );
setButtonState( $('fastRevBtn'), 'unavail' );
streamReq.send( streamParms+"&command="+CMD_SLOWREV );
setButtonState( $('pauseBtn'), 'inactive' );
setButtonState( $('slowRevBtn'), 'inactive' );
if (vid) {
vid.currentTime(vid.currentTime() - spf);
} else {
streamReq.send( streamParms+"&command="+CMD_SLOWREV );
}
}
function stopFastRev () {
clearInterval(intervalRewind);
vid.playbackRate(1);
revSpeed = .5;
}
function streamFastRev( action ) {
@ -156,26 +345,121 @@ function streamFastRev( action ) {
setButtonState( $('fastFwdBtn'), 'inactive' );
setButtonState( $('slowFwdBtn'), 'unavail' );
setButtonState( $('slowRevBtn'), 'unavail' );
setButtonState( $('fastRevBtn'), 'inactive' );
streamReq.send( streamParms+"&command="+CMD_FASTREV );
setButtonState( $('fastRevBtn'), 'active' );
if (vid) { //There is no reverse play with mp4. Set the speed to 0 and manualy set the time back.
revSpeed = rates[rates.indexOf(revSpeed*100)-1]/100;
if (rates.indexOf(revSpeed*100) == 0) {
setButtonState( $('fastRevBtn'), 'unavail' );
}
clearInterval(intervalRewind);
$j('#rateValue').html(-revSpeed);
intervalRewind = setInterval(function() {
if (vid.currentTime() <= 0) {
clearInterval(intervalRewind);
vid.pause();
} else {
vid.playbackRate(0);
vid.currentTime(vid.currentTime() - (revSpeed/2)); //Half of reverse speed because our interval is 500ms.
}
}, 500); //500ms is a compromise between smooth reverse and realistic performance
} else {
streamReq.send( streamParms+"&command="+CMD_FASTREV );
}
}
function streamPrev( action ) {
if ( action )
streamReq.send( streamParms+"&command="+CMD_PREV );
function streamPrev(action) {
if (action) {
$j(".vjsMessage").remove();
if (vid && PrevEventDefVideoPath.indexOf("view_video") > 0) {
CurEventDefVideoPath = PrevEventDefVideoPath;
eventQuery(prevEventId);
} else if (zmsBroke || (vid && PrevEventDefVideoPath.indexOf("view_video") < 0) || $j("#vjsMessage").length || PrevEventDefVideoPath.indexOf("view_video") > 0) {//zms broke, leaving videojs, last event, moving to videojs
location.replace(thisUrl + '?view=event&eid=' + prevEventId + filterQuery + sortQuery);
} else {
streamReq.send(streamParms+"&command="+CMD_PREV);
streamPlay();
}
}
}
function streamNext( action ) {
if ( action )
streamReq.send( streamParms+"&command="+CMD_NEXT );
function streamNext(action) {
if (action) {
$j(".vjsMessage").remove();//This shouldn't happen
if (nextEventId == 0) { //handles deleting last event.
pauseClicked();
let hideContainer = $j('#eventVideo');
let hideStream = $j(vid ? "#videoobj" : "#evtStream").height() + (vid ? 0 :$j("#progressBar").height());
hideContainer.prepend('<p class="vjsMessage" style="height: ' + hideStream + 'px; line-height: ' + hideStream + 'px;">No more events</p>');
if (vid == null) zmsBroke = true;
return;
}
if (vid && NextEventDefVideoPath.indexOf("view_video") > 0) { //on and staying with videojs
CurEventDefVideoPath = NextEventDefVideoPath;
eventQuery(nextEventId);
} else if (zmsBroke || (vid && NextEventDefVideoPath.indexOf("view_video") < 0) || NextEventDefVideoPath.indexOf("view_video") > 0) {//reload zms, leaving vjs, moving to vjs
location.replace(thisUrl + '?view=event&eid=' + nextEventId + filterQuery + sortQuery);
} else {
streamReq.send(streamParms+"&command="+CMD_NEXT);
streamPlay();
}
}
}
function vjsPanZoom (action, x, y) { //Pan and zoom with centering where the click occurs
let outer = $j('#videoobj');
let video = outer.children().first();
let zoom = parseFloat($j('#zoomValue').html());
let zoomRate = .5;
let matrix = video.css('transform').split(',');
let currentPanX = parseFloat(matrix[4]);
let currentPanY = parseFloat(matrix[5]);
let xDist = outer.width()/2 - x //Click distance from center of view
let yDist = outer.height()/2 - y
if (action == 'zoomOut') {
zoom -= zoomRate;
if (x && y) {
x = (xDist + currentPanX)*((zoom-zoomRate)/zoom); // if ctrl-click Pan but use ratio of old zoom to new zoom for coords
y = (yDist + currentPanY)*((zoom-zoomRate)/zoom);
} else {
x = currentPanX*((zoom-zoomRate)/zoom); //Leave zoom centered where it was
y = currentPanY*((zoom-zoomRate)/zoom);
}
if (zoom <= 1) {
zoom = 1;
$j('#zoomOutBtn').attr('class', 'unavail').attr('disabled', 'disabled');
}
$j('#zoomValue').html(zoom);
} else if (action == 'zoom') {
zoom += zoomRate;
x = (xDist + currentPanX)*(zoom/(zoom-zoomRate)); //Pan but use ratio of new zoom to old zoom for coords. Center on mouse click.
y = (yDist + currentPanY)*(zoom/(zoom-zoomRate));
$j('#zoomOutBtn').attr('class', 'inactive').removeAttr('disabled');
$j('#zoomValue').html(zoom);
} else if (action == 'pan') {
x = xDist + currentPanX;
y = yDist + currentPanY;
}
let limitX = ((zoom*outer.width()) - outer.width())/2; //Calculate outer bounds of video
let limitY = ((zoom*outer.height()) - outer.height())/2;
x = Math.min(Math.max((x),-limitX),limitX); //Limit pan to outer bounds of video
y = Math.min(Math.max((y),-limitY),limitY);
video.css('transform', 'matrix('+zoom+', 0, 0, '+zoom+', '+x+', '+y+')');
}
function streamZoomIn( x, y ) {
streamReq.send( streamParms+"&command="+CMD_ZOOMIN+"&x="+x+"&y="+y );
if (vid) {
vjsPanZoom('zoom', x, y);
} else {
streamReq.send( streamParms+"&command="+CMD_ZOOMIN+"&x="+x+"&y="+y );
}
}
function streamZoomOut() {
streamReq.send( streamParms+"&command="+CMD_ZOOMOUT );
if (vid) {
vjsPanZoom('zoomOut');
} else {
streamReq.send( streamParms+"&command="+CMD_ZOOMOUT );
}
}
function streamScale( scale ) {
@ -183,7 +467,11 @@ function streamScale( scale ) {
}
function streamPan( x, y ) {
streamReq.send( streamParms+"&command="+CMD_PAN+"&x="+x+"&y="+y );
if (vid) {
vjsPanZoom('pan', x, y);
} else {
streamReq.send( streamParms+"&command="+CMD_PAN+"&x="+x+"&y="+y );
}
}
function streamSeek( offset ) {
@ -196,6 +484,8 @@ function streamQuery() {
var slider = null;
var scroll = null;
var currEventId = null;
var CurEventDefVideoPath = null;
function getEventResponse( respObj, respText ) {
if ( checkStreamForErrors( "getEventResponse", respObj ) )
@ -220,7 +510,7 @@ function getEventResponse( respObj, respText ) {
$('dataFrames').set( 'text', eventData.Frames+"/"+eventData.AlarmFrames );
$('dataScore').set( 'text', eventData.TotScore+"/"+eventData.AvgScore+"/"+eventData.MaxScore );
$('eventName').setProperty( 'value', eventData.Name );
history.replaceState(null, null, '?view=event&eid=' + eventData.Id + filterQuery + sortQuery);//if popup removed, check if this allows forward
if ( canEditEvents ) {
if ( parseInt(eventData.Archived) ) {
$('archiveEvent').addClass( 'hidden' );
@ -232,7 +522,18 @@ function getEventResponse( respObj, respText ) {
}
//var eventImg = $('eventImage');
//eventImg.setStyles( { 'width': eventData.width, 'height': eventData.height } );
drawProgressBar();
if (vid && CurEventDefVideoPath) {
vid.src({type: 'video/mp4', src: CurEventDefVideoPath}); //Currently mp4 is all we use
initialAlarmCues(eventData.Id);//ajax and render, new event
addVideoTimingTrack(vid, LabelFormat, eventData.MonitorName, eventData.Length, eventData.StartTime);
CurEventDefVideoPath = null;
$j('#modeValue').html('Replay');
$j('#zoomValue').html('1');
$j('#rateValue').html('1');
vjsPanZoom('zoomOut');
} else {
drawProgressBar();
}
nearEventsQuery( eventData.Id );
}
@ -245,6 +546,8 @@ function eventQuery( eventId ) {
var prevEventId = 0;
var nextEventId = 0;
var prevEventStartTime = 0;
var nextEventStartTime = 0;
var PrevEventDefVideoPath = "";
var NextEventDefVideoPath = "";
@ -253,6 +556,8 @@ function getNearEventsResponse( respObj, respText ) {
return;
prevEventId = respObj.nearevents.PrevEventId;
nextEventId = respObj.nearevents.NextEventId;
prevEventStartTime = Date.parse(respObj.nearevents.PrevEventStartTime);
nextEventStartTime = Date.parse(respObj.nearevents.NextEventStartTime);
PrevEventDefVideoPath = respObj.nearevents.PrevEventDefVideoPath;
NextEventDefVideoPath = respObj.nearevents.NextEventDefVideoPath;
@ -260,12 +565,14 @@ function getNearEventsResponse( respObj, respText ) {
if ( prevEventBtn ) prevEventBtn.disabled = !prevEventId;
var nextEventBtn = $('nextEventBtn');
if ( nextEventBtn ) nextEventBtn.disabled = !nextEventId;
$j('#prevBtn').prop('disabled', prevEventId == 0 ? true : false).attr('class', prevEventId == 0 ? 'unavail' : 'inactive');
$j('#nextBtn').prop('disabled', nextEventId == 0 ? true : false).attr('class', nextEventId == 0 ? 'unavail' : 'inactive');
}
var nearEventsReq = new Request.JSON( { url: thisUrl, method: 'get', timeout: AJAX_TIMEOUT, link: 'cancel', onSuccess: getNearEventsResponse } );
function nearEventsQuery( eventId ) {
var parms = "view=request&request=status&entity=nearevents&id="+eventId;
var parms = "view=request&request=status&entity=nearevents&id="+eventId+filterQuery+sortQuery;
nearEventsReq.send( parms );
}
@ -277,7 +584,7 @@ function loadEventThumb( event, frame, loadImage ) {
console.error( "No holder found for frame "+frame.FrameId );
return;
}
var img = new Asset.image( imagePrefix+frame.Image.imagePath,
var img = new Asset.image( imagePrefix+frame.EventId+"&fid="+frame.FrameId,
{
'onload': ( function( loadImage ) {
thumbImg.setProperty( 'src', img.getProperty( 'src' ) );
@ -293,29 +600,6 @@ function loadEventThumb( event, frame, loadImage ) {
);
}
function updateStillsSizes( noDelay ) {
var containerDim = $('eventThumbs').getSize();
var containerWidth = containerDim.x;
var containerHeight = containerDim.y;
var popupWidth = parseInt($('eventImage').getStyle( 'width' ));
var popupHeight = parseInt($('eventImage').getStyle( 'height' ));
var left = (containerWidth - popupWidth)/2;
if ( left < 0 ) left = 0;
var top = (containerHeight - popupHeight)/2;
if ( top < 0 ) top = 0;
if ( popupHeight == 0 && !noDelay ) {
// image not yet loaded lets give it another second
updateStillsSizes.pass( true ).delay( 50 );
return;
}
$('eventImagePanel').setStyles( {
'left': left,
'top': top
} );
}
function loadEventImage( event, frame ) {
console.debug( "Loading "+event.Id+"/"+frame.FrameId );
var eventImg = $('eventImage');
@ -329,14 +613,6 @@ function loadEventImage( event, frame ) {
lastThumbImg.setOpacity( 1.0 );
}
eventImg.setProperties( {
'class': frame.Type=='Alarm'?'alarm':'normal',
'src': thumbImg.getProperty( 'src' ),
'title': thumbImg.getProperty( 'title' ),
'alt': thumbImg.getProperty( 'alt' ),
'width': event.Width,
'height': event.Height
} );
$('eventImageBar').setStyle( 'width', event.Width );
if ( frame.Type=='Alarm' )
$('eventImageStats').removeClass( 'hidden' );
@ -347,11 +623,18 @@ function loadEventImage( event, frame ) {
if ( eventImagePanel.getStyle( 'display' ) == 'none' ) {
eventImagePanel.setOpacity( 0 );
updateStillsSizes();
eventImagePanel.setStyle( 'display', 'block' );
eventImagePanel.setStyle( 'display', 'inline-block' );
new Fx.Tween( eventImagePanel, { duration: 500, transition: Fx.Transitions.Sine } ).start( 'opacity', 0, 1 );
}
eventImg.setProperties( {
'class': frame.Type=='Alarm'?'alarm':'normal',
'src': thumbImg.getProperty( 'src' ),
'title': thumbImg.getProperty( 'title' ),
'alt': thumbImg.getProperty( 'alt' ),
'height': $j('#eventThumbs').height() - $j('#eventImageBar').outerHeight(true)-10
} );
$('eventImageNo').set( 'text', frame.FrameId );
$('prevImageBtn').disabled = (frame.FrameId==1);
$('nextImageBtn').disabled = (frame.FrameId==event.Frames);
@ -392,13 +675,11 @@ function resetEventStills() {
fid = 1;
else if ( fid > eventData.Frames )
fid = eventData.Frames;
checkFrames( eventData.Id, fid );
checkFrames( eventData.Id, fid, ($j('#eventImagePanel').css('display')=='none'?'':'true'));
scroll.toElement( 'eventThumb'+fid );
}
} ).set( 0 );
}
if ( $('eventThumbs').getStyle( 'height' ).match( /^\d+/ ) < (parseInt(eventData.Height)+80) )
$('eventThumbs').setStyle( 'height', (parseInt(eventData.Height)+80)+'px' );
}
function getFrameResponse( respObj, respText ) {
@ -548,6 +829,7 @@ function actQuery( action, parms ) {
}
function deleteEvent() {
pauseClicked(); //Provides visual feedback that your click happened.
actQuery( 'delete' );
streamNext( true );
}
@ -577,21 +859,20 @@ function showEventFrames() {
createPopup( '?view=frames&eid='+eventData.Id, 'zmFrames', 'frames' );
}
function showVideo() {
function showStream() {
$('eventStills').addClass( 'hidden' );
$('imageFeed').addClass('hidden');
$('eventVideo').removeClass( 'hidden' );
$('stillsEvent').removeClass( 'hidden' );
$('videoEvent').addClass( 'hidden' );
$('streamEvent').addClass( 'hidden' );
streamMode = 'video';
if (scale == 'auto') changeScale();
}
function showStills() {
$('eventStills').removeClass( 'hidden' );
$('imageFeed').removeClass('hidden');
$('eventVideo').addClass( 'hidden' );
$('eventVideo').addClass( 'hidden' );
if (vid && ( vid.paused != true ) ) {
// Pause the video
@ -603,11 +884,11 @@ function showStills() {
}
$('stillsEvent').addClass( 'hidden' );
$('videoEvent').removeClass( 'hidden' );
$('streamEvent').removeClass( 'hidden' );
streamMode = 'stills';
streamPause( true );
pauseClicked();
if ( !scroll ) {
scroll = new Fx.Scroll( 'eventThumbs', {
wait: false,
@ -618,7 +899,7 @@ function showStills() {
);
}
resetEventStills();
$(window).addEvent( 'resize', updateStillsSizes );
if (scale == 'auto') changeScale();
}
function showFrameStats() {
@ -630,184 +911,64 @@ function videoEvent() {
createPopup( '?view=video&eid='+eventData.Id, 'zmVideo', 'video', eventData.Width, eventData.Height );
}
// Called on each event load because each event can be a different width
function drawProgressBar() {
var barWidth = 0;
$('progressBar').addClass( 'invisible' );
var cells = $('progressBar').getElements( 'div' );
var cellWidth = parseInt( eventData.Width/$$(cells).length );
$$(cells).forEach(
function( cell, index ) {
if ( index == 0 )
$(cell).setStyles( { 'left': barWidth, 'width': cellWidth, 'borderLeft': 0 } );
else
$(cell).setStyles( { 'left': barWidth, 'width': cellWidth } );
var offset = parseInt((index*eventData.Length)/$$(cells).length);
$(cell).setProperty( 'title', '+'+secsToTime(offset)+'s' );
$(cell).removeEvent( 'click' );
$(cell).addEvent( 'click', function() { streamSeek( offset ); } );
barWidth += $(cell).getCoordinates().width;
}
);
$('progressBar').setStyle( 'width', barWidth );
$('progressBar').removeClass( 'invisible' );
let barWidth = $j('#evtStream').width();
$j('#progressBar').css( 'width', barWidth );
}
// Shows current stream progress.
function updateProgressBar() {
if ( eventData && streamStatus ) {
var cells = $('progressBar').getElements( 'div' );
var completeIndex = parseInt((($$(cells).length+1)*streamStatus.progress)/eventData.Length);
$$(cells).forEach(
function( cell, index ) {
if ( index < completeIndex ) {
if ( !$(cell).hasClass( 'complete' ) ) {
$(cell).addClass( 'complete' );
}
} else {
if ( $(cell).hasClass( 'complete' ) ) {
$(cell).removeClass( 'complete' );
}
} // end if
} // end function
);
//$('progressBar').setStyle( 'width', barWidth );
$('progressBar').removeClass( 'invisible' );
} // end if eventData && streamStatus
if ( ! ( eventData && streamStatus ) ) {
return;
} // end if ! eventData && streamStatus
var curWidth = (streamStatus.progress / parseFloat(eventData.Length)) * 100;
$j("#progressBox").css('width', curWidth + '%');
} // end function updateProgressBar()
// Handles seeking when clicking on the progress bar.
function progressBarNav (){
$j('#progressBar').click(function(e){
var x = e.offsetX;
var seekTime = (x / $j('#progressBar').width()) * parseFloat(eventData.Length);
streamSeek (seekTime);
});
}
function handleClick( event ) {
var target = event.target;
var x = event.page.x - $(target).getLeft();
var y = event.page.y - $(target).getTop();
if (vid) {
if (target.id != 'videoobj') return; //ignore clicks on control bar
var x = event.offsetX;
var y = event.offsetY;
} else {
var x = event.page.x - $(target).getLeft();
var y = event.page.y - $(target).getTop();
}
if ( event.shift )
streamPan( x, y );
else
streamZoomIn( x, y );
}
function setupListener() {
// Buttons
var playButton = document.getElementById("play-pause");
var muteButton = document.getElementById("mute");
var fullScreenButton = document.getElementById("full-screen");
// Sliders
var seekBar = document.getElementById("seekbar");
var volumeBar = document.getElementById("volume-bar");
// Event listener for the play/pause button
playButton.addEventListener( "click", function() {
if (vid.paused == true) {
// Play the video
vid.play();
// Update the button text to 'Pause'
playButton.innerHTML = "Pause";
} else {
// Pause the video
vid.pause();
// Update the button text to 'Play'
playButton.innerHTML = "Play";
}
});
// Event listener for the mute button
muteButton.addEventListener("click", function() {
if (vid.muted == false) {
// Mute the video
vid.muted = true;
// Update the button text
muteButton.innerHTML = "Unmute";
} else {
// Unmute the video
vid.muted = false;
// Update the button text
muteButton.innerHTML = "Mute";
}
});
// Event listener for the full-screen button
fullScreenButton.addEventListener("click", function() {
if (vid.requestFullscreen) {
vid.requestFullscreen();
} else if (vid.mozRequestFullScreen) {
vid.mozRequestFullScreen(); // Firefox
} else if (vid.webkitRequestFullscreen) {
vid.webkitRequestFullscreen(); // Chrome and Safari
}
});
// Event listener for the seek bar
seekBar.addEventListener("change", function() {
// Calculate the new time
var time = vid.duration * (seekBar.value / 100);
// Update the video time
vid.currentTime = time;
});
// Update the seek bar as the video plays
vid.addEventListener("timeupdate", function() {
// Calculate the slider value
var value = (100 / vid.duration) * vid.currentTime;
// Update the slider value
seekBar.value = value;
});
// Pause the video when the seek handle is being dragged
seekBar.addEventListener("mousedown", function() {
vid.pause();
});
// Play the video when the seek handle is dropped
seekBar.addEventListener("mouseup", function() {
vid.play();
});
// Event listener for the volume bar
volumeBar.addEventListener("change", function() {
// Update the video volume
vid.volume = volumeBar.value;
});
if (event.shift || event.shiftKey) {//handle both jquery and mootools
streamPan(x, y);
} else if (vid && event.ctrlKey) { //allow zoom out by control click. useful in fullscreen
vjsPanZoom('zoomOut', x, y);
} else {
streamZoomIn(x, y);
}
}
function initPage() {
//FIXME prevent blocking...not sure what is happening or best way to unblock
if ( $('videoobj') ) {
vid = videojs("videoobj");
}
if ( vid ) {
/*
setupListener();
vid.removeAttribute("controls");
/* window.videoobj.oncanplay=null;
window.videoobj.currentTime=window.videoobj.currentTime-1;
window.videoobj.currentTime=window.videoobj.currentTime+1;//may not be symetrical of course
vid.onstalled=function(){window.vid.currentTime=window.vid.currentTime-1;window.vid.currentTime=window.vid.currentTime+1;}
vid.onwaiting=function(){window.vid.currentTime=window.vid.currentTime-1;window.vid.currentTime=window.vid.currentTime+1;}
vid.onloadstart=function(){window.vid.currentTime=window.vid.currentTime-1;window.vid.currentTime=window.vid.currentTime+1;}
vid.onplay=function(){window.vid.currentTime=window.vid.currentTime-1;window.vid.currentTime=window.vid.currentTime+1;}
vid.onplaying=function(){window.vid.currentTime=window.vid.currentTime-1;window.vid.currentTime=window.vid.currentTime+1;}
//window.vid.hide();//does not help
var sources = window.videoobj.getElementsByTagName('source');
sources[0].src=null;
window.videoobj.load();
streamPlay(); */
if ($j('#videoobj').length) {
vid = videojs('videoobj');
addVideoTimingTrack(vid, LabelFormat, eventData.MonitorName, eventData.Length, eventData.StartTime);
$j('.vjs-progress-control').append('<div class="alarmCue"></div>');//add a place for videojs only on first load
vid.on('ended', vjsReplay);
vid.on('play', vjsPlay);
vid.on('pause', vjsPause);
vid.on('click', function(event){handleClick(event);});
vid.on('timeupdate', function (){$j('#progressValue').html(secsToTime(Math.floor(vid.currentTime())))});
} else {
progressBarNav ();
streamCmdTimer = streamQuery.delay( 250 );
eventQuery.pass( eventData.Id ).delay( 500 );
if ( canStreamNative ) {
var streamImg = $('imageFeed').getElement('img');
if ( !streamImg )
@ -815,6 +976,9 @@ function initPage() {
$(streamImg).addEvent( 'click', function( event ) { handleClick( event ); } );
}
}
nearEventsQuery(eventData.Id);
initialAlarmCues(eventData.Id); //call ajax+renderAlarmCues
if (scale == "auto") changeScale();
}
// Kick everything off

View File

@ -30,17 +30,25 @@ var eventData = {
MonitorId: '<?php echo $Event->MonitorId() ?>',
Width: '<?php echo $Event->Width() ?>',
Height: '<?php echo $Event->Height() ?>',
Length: '<?php echo $Event->Length() ?>'
Length: '<?php echo $Event->Length() ?>',
StartTime: '<?php echo $Event->StartTime() ?>',
EndTime: '<?php echo $Event->EndTime() ?>',
Frames: '<?php echo $Event->Frames() ?>',
MonitorName: '<?php echo $Monitor->Name() ?>'
};
var filterQuery = '<?php echo isset($filterQuery)?validJsStr($filterQuery):'' ?>';
var sortQuery = '<?php echo isset($sortQuery)?validJsStr($sortQuery):'' ?>';
var filterQuery = '<?php echo isset($filterQuery)?validJsStr(htmlspecialchars_decode($filterQuery)):'' ?>';
var sortQuery = '<?php echo isset($sortQuery)?validJsStr(htmlspecialchars_decode($sortQuery)):'' ?>';
var rates = <?php echo json_encode(array_keys($rates)) ?>;
var scale = "<?php echo $scale ?>";
var LabelFormat = "<?php echo validJsStr($Monitor->LabelFormat())?>";
var scale = <?php echo $scale ?>;
var canEditEvents = <?php echo canEdit( 'Events' )?'true':'false' ?>;
var streamTimeout = <?php echo 1000*ZM_WEB_REFRESH_STATUS ?>;
var canStreamNative = <?php echo canStreamNative()?'true':'false' ?>;
var streamMode = '<?php echo $streamMode ?>';
//
// Strings

View File

@ -1,15 +1,34 @@
function changeScale() {
var scale = $('scale').get('value');
var img = $('frameImg');
if ( img ) {
var baseWidth = $('base_width').value;
var baseHeight = $('base_height').value;
var newWidth = ( baseWidth * scale ) / SCALE_BASE;
var newHeight = ( baseHeight * scale ) / SCALE_BASE;
let scale = $j('#scale').val();
let img = $j('#frameImg');
let controlsLinks = {
next: $j('#nextLink'),
prev: $j('#prevLink'),
first: $j('#firstLink'),
last: $j('#lastLink')
}
img.style.width = newWidth + "px";
img.style.height = newHeight + "px";
if (img) {
let baseWidth = $j('#base_width').val();
let baseHeight = $j('#base_height').val();
if (scale == "auto") {
let newSize = scaleToFit(baseWidth, baseHeight, img, $j('#controls'));
newWidth = newSize.width;
newHeight = newSize.height;
autoScale = newSize.autoScale;
} else {
$j(window).off('resize', endOfResize); //remove resize handler when Scale to Fit is not active
newWidth = baseWidth * scale / SCALE_BASE;
newHeight = baseHeight * scale / SCALE_BASE;
}
img.css('width', newWidth + "px");
img.css('height', newHeight + "px");
}
Cookie.write( 'zmWatchScale', scale, { duration: 10*365 } );
$j.each(controlsLinks, function(k, anchor) { //Make frames respect scale choices
anchor.prop('href', anchor.prop('href').replace(/scale=.*&/, 'scale=' + scale + '&'));
});
}
if (scale == "auto") $j(document).ready(changeScale);

View File

@ -1,2 +1,3 @@
var scale = '<?php echo $scale ?>';
var SCALE_BASE = <?php echo SCALE_BASE ?>;

View File

@ -25,18 +25,29 @@ function showPtzControls() {
function changeScale() {
var scale = $('scale').get('value');
var newWidth = ( monitorWidth * scale ) / SCALE_BASE;
var newHeight = ( monitorHeight * scale ) / SCALE_BASE;
var newWidth;
var newHeight;
if (scale == "auto") {
let newSize = scaleToFit(monitorWidth, monitorHeight, $j('#liveStream'+monitorId), $j('#replayStatus'));
newWidth = newSize.width;
newHeight = newSize.height;
autoScale = newSize.autoScale;
} else {
$j(window).off('resize', endOfResize); //remove resize handler when Scale to Fit is not active
newWidth = monitorWidth * scale / SCALE_BASE;
newHeight = monitorHeight * scale / SCALE_BASE;
}
Cookie.write( 'zmWatchScale'+monitorId, scale, { duration: 10*365 } );
/*Stream could be an applet so can't use moo tools*/
var streamImg = document.getElementById('liveStream'+monitorId);
var streamImg = $('liveStream'+monitorId);
if ( streamImg ) {
streamImg.style.width = newWidth + "px";
streamImg.style.height = newHeight + "px";
streamImg.src = streamImg.src.replace(/scale=\d+/i, 'scale='+scale);
streamImg.src = streamImg.src.replace(/scale=\d+/i, 'scale='+(scale== 'auto' ? autoScale : scale));
} else {
console.error("No element found for liveStream.");
}
@ -622,6 +633,7 @@ function initPage() {
if ( refreshApplet && appletRefreshTime )
appletRefresh.delay( appletRefreshTime*1000 );
if (scale == "auto") changeScale();
}
// Kick everything off

View File

@ -50,7 +50,7 @@ var monitorWidth = <?php echo $monitor->Width() ?>;
var monitorHeight = <?php echo $monitor->Height() ?>;
var monitorUrl = '<?php echo ( $monitor->Server()->Url() ) ?>';
var scale = <?php echo $scale ?>;
var scale = '<?php echo $scale ?>';
var statusRefreshTimeout = <?php echo 1000*ZM_WEB_REFRESH_STATUS ?>;
var eventsRefreshTimeout = <?php echo 1000*ZM_WEB_REFRESH_EVENTS ?>;

View File

@ -61,13 +61,24 @@ $path = null;
if ( empty($_REQUEST['path']) ) {
if ( ! empty($_REQUEST['fid']) ) {
$show = empty($_REQUEST['show']) ? 'capture' : $_REQUEST['show'];
if ( ! empty($_REQUEST['eid'] ) ) {
$Event = new Event( $_REQUEST['eid'] );
$Frame = Frame::find_one( array( 'EventId' => $_REQUEST['eid'], 'FrameId' => $_REQUEST['fid'] ) );
if ( ! $Frame ) {
Fatal("No Frame found for event(".$_REQUEST['eid'].") and frame id(".$_REQUEST['fid'].")");
$previousBulkFrame = dbFetchOne( "SELECT * FROM Frames WHERE EventId=? AND FrameId < ? ORDER BY FrameID DESC LIMIT 1", NULL, array($_REQUEST['eid'], $_REQUEST['fid'] ) );
$nextBulkFrame = dbFetchOne( "SELECT * FROM Frames WHERE EventId=? AND FrameId > ? ORDER BY FrameID ASC LIMIT 1", NULL, array($_REQUEST['eid'], $_REQUEST['fid'] ) );
if ( $previousBulkFrame and $nextBulkFrame ) {
$Frame = new Frame( $previousBulkFrame );
$Frame->FrameId = ( $_REQUEST['fid'] );
$percentage = ($Frame->FrameId() - $previousBulkFrame['FrameId']) / ($nextBulkFrame['FrameId'] - $previousBulkFrame['FrameId']);
$Frame->Delta = ( $previousBulkFrame['Delta'] + floor( 100* ( $nextBulkFrame['Delta'] - $previousBulkFrame['Delta'] ) * $percentage )/100 );
Logger::Debug("Got virtual frame from Bulk Frames previous delta: " . $previousBulkFrame['Delta'] . " + nextdelta:" . $nextBulkFrame['Delta'] . ' - ' . $previousBulkFrame['Delta'] . ' * ' . $percentage );
} else {
Fatal("No Frame found for event(".$_REQUEST['eid'].") and frame id(".$_REQUEST['fid'].")");
}
}
// Frame can be non-existent. We have Bulk frames. So now we should try to load the bulk frame
} else {
# If we are only specifying fid, then the fid must be the primary key into the frames table. But when the event is specified, then it is the frame #
$Frame = new Frame( $_REQUEST['fid'] );