var vid = null; var spf = Math.round((eventData.Length / eventData.Frames)*1000000 )/1000000;//Seconds per frame for videojs frame by frame. var intervalRewind; var revSpeed = .5; // Function called when video.js hits the end of the video function vjsReplay() { switch ( replayMode.value ) { case 'none': break; case 'single': vid.play(); break; case 'all': if ( nextEventId == 0 ) { var overLaid = $j("#videoobj"); overLaid.append('

No more events

'); } else { var endTime = (Date.parse(eventData.EndTime)).getTime(); var nextStartTime = nextEventStartTime.getTime(); //nextEventStartTime.getTime() is a mootools workaround, highjacks Date.parse if ( nextStartTime <= endTime ) { streamNext(true); return; } var overLaid = $j("#videoobj"); vid.pause(); overLaid.append('

'); var gapDuration = (new Date().getTime()) + (nextStartTime - endTime); var messageP = $j('.vjsMessage'); var x = setInterval(function() { var now = new Date().getTime(); var 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; } } // end function vjsReplay $j.ajaxSetup({timeout: AJAX_TIMEOUT}); //sets timeout for all getJSON. var cueFrames = null; //make cueFrames available 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 && cueFrames.length ) ) { console.log('No cue frames for event'); return; } // This uses the Delta of the last frame to get the length of the event. I can't help but wonder though // if we shouldn't just use the event length endtime-starttime 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; var num_cueFrames = cueFrames.length; for ( var i = 0; i < num_cueFrames; 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; var 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 += ''; 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 < num_cueFrames) { //alarm is too short and there is more event continue; } 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 += ''; 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 += ''; } } return alarmHtml; } function changeCodec() { location.replace(thisUrl + '?view=event&eid=' + eventData.Id + filterQuery + sortQuery+'&codec='+$j('#codec').val()); } function changeScale() { var scale = $j('#scale').val(); var newWidth; var newHeight; var autoScale; var eventViewer; var alarmCue = $j('div.alarmCue'); var bottomEl = streamMode == 'stills' ? $j('#eventImageNav') : $j('#replayStatus'); if ( streamMode == 'stills' ) { eventViewer = $j('#eventThumbs'); } else { eventViewer = $j(vid ? '#videoobj' : '#evtStream'); } if ( scale == '0' || scale == 'auto' ) { var 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 == '0' ? 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 == '0' ) { Cookie.write('zmEventScaleAuto', 'auto', {duration: 10*365}); } else { Cookie.write('zmEventScale'+eventData.MonitorId, scale, {duration: 10*365}); Cookie.dispose('zmEventScaleAuto'); } } function changeReplayMode() { var replayMode = $('replayMode').get('value'); Cookie.write('replayMode', replayMode, {duration: 10*365}); refreshWindow(); } function changeRate() { var rate = parseInt($j('select[name="rate"]').val()); if ( ! rate ) { pauseClicked(); } else if ( rate < 0 ) { if ( vid ) { //There is no reverse play with mp4. Set the speed to 0 and manually set the time back. revSpeed = rates[rates.indexOf(-1*rate)-1]/100; clearInterval(intervalRewind); 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 } // end if vid } else { // Forward rate if ( vid ) { vid.playbackRate(rate/100); } } Cookie.write('zmEventRate', rate, {duration: 10*365}); } // end function changeRate var streamParms = "view=request&request=stream&connkey="+connKey; if ( auth_hash ) { streamParms += '&auth='+auth_hash; } 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) ) { console.log('Got an error from getCmdResponse'); console.log(respObj); console.log(respText); 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 ( lastEventId ) { if ( eventId != lastEventId ) { //Doesn't run on first load, prevents a double hit on event and nearEvents ajax eventQuery(eventId); initialAlarmCues(eventId); //zms uses this instead of a page reload, must call ajax+render lastEventId = eventId; } } else { lastEventId = eventId; //Only fires on first load. } if ( streamStatus.paused == true ) { streamPause( ); } else { console.log('streamStatus.rate: ' + streamStatus.rate); $j('select[name="rate"]').val(streamStatus.rate*100); Cookie.write('zmEventRate', streamStatus.rate*100, {duration: 10*365}); streamPlay( ); } $j('#progressValue').html(secsToTime(parseInt(streamStatus.progress))); $j('#zoomValue').html(streamStatus.zoom); if ( streamStatus.zoom == "1.0" ) { setButtonState( $('zoomOutBtn'), 'unavail' ); } else { setButtonState( $('zoomOutBtn'), 'inactive' ); } updateProgressBar(); if ( streamStatus.auth ) { // Try to reload the image stream. var streamImg = $j('#evtStream'); if ( streamImg ) { streamImg.src = streamImg.src.replace( /auth=\w+/i, 'auth='+streamStatus.auth ); } } // end if haev a new auth hash streamCmdTimer = streamQuery.delay( streamTimeout ); //Timeout is refresh rate for progressBox and time display } var streamReq = new Request.JSON( { url: monitorUrl, method: 'get', timeout: AJAX_TIMEOUT, link: 'chain', onSuccess: getCmdResponse } ); function pauseClicked() { if ( vid ) { if ( intervalRewind ) { stopFastRev(); } vid.pause(); } else { streamReq.send(streamParms+"&command="+CMD_PAUSE); } streamPause(); } function streamPause( ) { $j('#modeValue').html('Paused'); setButtonState( $('pauseBtn'), 'active' ); setButtonState( $('playBtn'), 'inactive' ); setButtonState( $('fastFwdBtn'), 'unavail' ); setButtonState( $('slowFwdBtn'), 'inactive' ); setButtonState( $('slowRevBtn'), 'inactive' ); setButtonState( $('fastRevBtn'), 'unavail' ); } function playClicked( ) { var rate_select = $j('select[name="rate"]'); if ( ! rate_select.val() ) { $j('select[name="rate"]').val(100); } 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 if ( intervalRewind ) { stopFastRev(); } $j('select[name="rate"]').val(vid.playbackRate()*100); Cookie.write('zmEventRate', vid.playbackRate()*100, {duration: 10*365}); streamPlay(); } function streamPlay( ) { setButtonState( $('pauseBtn'), 'inactive' ); setButtonState( $('playBtn'), 'active' ); setButtonState( $('fastFwdBtn'), 'inactive' ); setButtonState( $('slowFwdBtn'), 'unavail' ); setButtonState( $('slowRevBtn'), 'unavail' ); setButtonState( $('fastRevBtn'), 'inactive' ); } function streamFastFwd( action ) { setButtonState( $('pauseBtn'), 'inactive' ); setButtonState( $('playBtn'), 'inactive' ); setButtonState( $('fastFwdBtn'), 'active' ); setButtonState( $('slowFwdBtn'), 'unavail' ); setButtonState( $('slowRevBtn'), 'unavail' ); setButtonState( $('fastRevBtn'), 'inactive' ); 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('select[name="rate"]').val(vid.playbackRate()*100); Cookie.write('zmEventRate', vid.playbackRate()*100, {duration: 10*365}); } else { streamReq.send(streamParms+"&command="+CMD_FASTFWD); } } function streamSlowFwd( action ) { if ( vid ) { vid.currentTime(vid.currentTime() + spf); } else { streamReq.send(streamParms+"&command="+CMD_SLOWFWD); } } function streamSlowRev( action ) { if ( vid ) { vid.currentTime(vid.currentTime() - spf); } else { streamReq.send(streamParms+"&command="+CMD_SLOWREV); } } function stopFastRev() { clearInterval(intervalRewind); vid.playbackRate(1); $j('select[name="rate"]').val(vid.playbackRate()*100); Cookie.write('zmEventRate', vid.playbackRate()*100, {duration: 10*365}); revSpeed = .5; } function streamFastRev( action ) { setButtonState( $('pauseBtn'), 'inactive' ); setButtonState( $('playBtn'), 'inactive' ); setButtonState( $('fastFwdBtn'), 'inactive' ); setButtonState( $('slowFwdBtn'), 'unavail' ); setButtonState( $('slowRevBtn'), 'unavail' ); setButtonState( $('fastRevBtn'), 'active' ); if ( vid ) { //There is no reverse play with mp4. Set the speed to 0 and manually set the time back. revSpeed = rates[rates.indexOf(revSpeed*100)-1]/100; if ( rates.indexOf(revSpeed*100) == 0 ) { setButtonState( $('fastRevBtn'), 'unavail' ); } clearInterval(intervalRewind); $j('select[name="rate"]').val(-revSpeed*100); Cookie.write('zmEventRate', vid.playbackRate()*100, {duration: 10*365}); 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 ) { $j(".vjsMessage").remove(); location.replace(thisUrl + '?view=event&eid=' + prevEventId + filterQuery + sortQuery + '&popup='+popup); return; 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 + '&popup='+popup); } else { streamReq.send(streamParms+"&command="+CMD_PREV); streamPlay(); } } } function streamNext(action) { if ( action ) { $j(".vjsMessage").remove();//This shouldn't happen if ( nextEventId == 0 ) { //handles deleting last event. pauseClicked(); var hideContainer = $j('#eventVideo'); var hideStream = $j(vid ? "#videoobj" : "#evtStream").height() + (vid ? 0 :$j("#progressBar").height()); hideContainer.prepend('

No more events

'); if ( vid == null ) zmsBroke = true; return; } // We used to try to dynamically update all the bits in the page, which is really complex // How about we just reload the page? // location.replace(thisUrl + '?view=event&eid=' + nextEventId + filterQuery + sortQuery + '&popup='+popup); 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 + '&popup='+popup); } else { streamReq.send(streamParms+"&command="+CMD_NEXT); streamPlay(); } } } function vjsPanZoom(action, x, y) { //Pan and zoom with centering where the click occurs var outer = $j('#videoobj'); var video = outer.children().first(); var zoom = parseFloat($j('#zoomValue').html()); var zoomRate = .5; var matrix = video.css('transform').split(','); var currentPanX = parseFloat(matrix[4]); var currentPanY = parseFloat(matrix[5]); var xDist = outer.width()/2 - x; //Click distance from center of view var 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; } var limitX = ((zoom*outer.width()) - outer.width())/2; //Calculate outer bounds of video var 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 ) { if (vid) { vjsPanZoom('zoom', x, y); } else { streamReq.send( streamParms+"&command="+CMD_ZOOMIN+"&x="+x+"&y="+y ); } } function streamZoomOut() { if (vid) { vjsPanZoom('zoomOut'); } else { streamReq.send( streamParms+"&command="+CMD_ZOOMOUT ); } } function streamScale( scale ) { streamReq.send( streamParms+"&command="+CMD_SCALE+"&scale="+scale ); } function streamPan( x, y ) { if (vid) { vjsPanZoom('pan', x, y); } else { streamReq.send( streamParms+"&command="+CMD_PAN+"&x="+x+"&y="+y ); } } function streamSeek( offset ) { streamReq.send( streamParms+"&command="+CMD_SEEK+"&offset="+offset ); } function streamQuery() { streamReq.send( streamParms+"&command="+CMD_QUERY ); } var slider = null; var scroll = null; var currEventId = null; var CurEventDefVideoPath = null; function getEventResponse(respObj, respText) { if ( checkStreamForErrors('getEventResponse', respObj) ) { console.log('getEventResponse: errors'); return; } eventData = respObj.event; var eventStills = $('eventStills'); if ( eventStills && !$('eventStills').hasClass( 'hidden' ) && currEventId != eventData.Id ) { resetEventStills(); } currEventId = eventData.Id; $('dataId').set( 'text', eventData.Id ); if ( eventData.Notes ) { $('dataCause').setProperty( 'title', eventData.Notes ); } else { $('dataCause').setProperty( 'title', causeString ); } $('dataCause').set( 'text', eventData.Cause ); $('dataTime').set( 'text', eventData.StartTime ); $('dataDuration').set( 'text', eventData.Length ); $('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' ); $('unarchiveEvent').removeClass( 'hidden' ); } else { $('archiveEvent').removeClass( 'hidden' ); $('unarchiveEvent').addClass( 'hidden' ); } } // Technically, events can be different sizes, so may need to update the size of the image, but it might be better to have it stay scaled... //var eventImg = $('eventImage'); //eventImg.setStyles( { 'width': eventData.width, 'height': eventData.height } ); if ( vid && CurEventDefVideoPath ) { vid.src({type: 'video/mp4', src: CurEventDefVideoPath}); //Currently mp4 is all we use console.log('getEventResponse'); 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('#rate').val('100'); vjsPanZoom('zoomOut'); } else { drawProgressBar(); } nearEventsQuery( eventData.Id ); } // end function getEventResponse var eventReq = new Request.JSON( {url: thisUrl, method: 'get', timeout: AJAX_TIMEOUT, link: 'cancel', onSuccess: getEventResponse} ); function eventQuery( eventId ) { var eventParms = "view=request&request=status&entity=event&id="+eventId; if ( auth_hash ) { eventParms += '&auth='+auth_hash; } eventReq.send( eventParms ); } var prevEventId = 0; var nextEventId = 0; var prevEventStartTime = 0; var nextEventStartTime = 0; var PrevEventDefVideoPath = ""; var NextEventDefVideoPath = ""; function getNearEventsResponse( respObj, respText ) { if ( checkStreamForErrors( "getNearEventsResponse", respObj ) ) { 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; var prevEventBtn = $('prevEventBtn'); 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+filterQuery+sortQuery; nearEventsReq.send( parms ); } var frameBatch = 40; function loadEventThumb( event, frame, loadImage ) { var thumbImg = $('eventThumb'+frame.FrameId); if ( !thumbImg ) { console.error('No holder found for frame '+frame.FrameId); return; } var img = new Asset.image( imagePrefix+frame.EventId+"&fid="+frame.FrameId, { 'onload': ( function( loadImage ) { thumbImg.setProperty( 'src', img.getProperty( 'src' ) ); thumbImg.removeClass( 'placeholder' ); thumbImg.setProperty( 'class', frame.Type=='Alarm'?'alarm':'normal' ); thumbImg.setProperty( 'title', frame.FrameId+' / '+((frame.Type=='Alarm')?frame.Score:0) ); thumbImg.removeEvents( 'click' ); thumbImg.addEvent( 'click', function() { locateImage( frame.FrameId, true ); } ); if ( loadImage ) { loadEventImage( event, frame ); } } ).pass( loadImage ) } ); } function loadEventImage(event, frame) { console.debug('Loading '+event.Id+'/'+frame.FrameId); var eventImg = $('eventImage'); var thumbImg = $('eventThumb'+frame.FrameId); if ( eventImg.getProperty('src') != thumbImg.getProperty('src') ) { var eventImagePanel = $('eventImagePanel'); if ( eventImagePanel.getStyle('display') != 'none' ) { var lastThumbImg = $('eventThumb'+eventImg.getProperty('alt')); lastThumbImg.removeClass('selected'); lastThumbImg.setOpacity(1.0); } $('eventImageBar').setStyle('width', event.Width); if ( frame.Type == 'Alarm' ) { $('eventImageStats').removeClass('hidden'); } else { $('eventImageStats').addClass('hidden'); } thumbImg.addClass('selected'); thumbImg.setOpacity(0.5); if ( eventImagePanel.getStyle('display') == 'none' ) { eventImagePanel.setOpacity(0); 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); } } function hideEventImageComplete() { var thumbImg = $('eventThumb'+$('eventImage').getProperty('alt')); if ( thumbImg ) { thumbImg.removeClass('selected'); thumbImg.setOpacity(1.0); } else { console.log('Unable to find eventThumb at eventThumb'+$('eventImage').getProperty('alt')); } $('prevImageBtn').disabled = true; $('nextImageBtn').disabled = true; $('eventImagePanel').setStyle('display', 'none'); $('eventImageStats').addClass('hidden'); } function hideEventImage() { if ( $('eventImagePanel').getStyle('display') != 'none' ) { new Fx.Tween( $('eventImagePanel'), {duration: 500, transition: Fx.Transitions.Sine, onComplete: hideEventImageComplete} ).start('opacity', 1, 0); } } function resetEventStills() { hideEventImage(); $('eventThumbs').empty(); if ( true || !slider ) { slider = new Slider( $('thumbsSlider'), $('thumbsKnob'), { /*steps: eventData.Frames,*/ onChange: function( step ) { if ( !step ) { step = 0; } var fid = parseInt((step * eventData.Frames)/this.options.steps); if ( fid < 1 ) { fid = 1; } else if ( fid > eventData.Frames ) { fid = eventData.Frames; } checkFrames( eventData.Id, fid, ($j('#eventImagePanel').css('display')=='none'?'':'true')); scroll.toElement( 'eventThumb'+fid ); } } ).set( 0 ); } } function getFrameResponse(respObj, respText) { if ( checkStreamForErrors('getFrameResponse', respObj) ) { return; } var frame = respObj.frameimage; if ( !eventData ) { console.error('No event '+frame.EventId+' found'); return; } if ( !eventData['frames'] ) { eventData['frames'] = {}; } eventData['frames'][frame.FrameId] = frame; loadEventThumb(eventData, frame, respObj.loopback=="true"); } var frameReq = new Request.JSON( {url: thisUrl, method: 'get', timeout: AJAX_TIMEOUT, link: 'chain', onSuccess: getFrameResponse} ); function frameQuery( eventId, frameId, loadImage ) { var parms = "view=request&request=status&entity=frameimage&id[0]="+eventId+"&id[1]="+frameId+"&loopback="+loadImage; frameReq.send(parms); } var currFrameId = null; function checkFrames( eventId, frameId, loadImage ) { if ( !eventData ) { console.error("No event "+eventId+" found"); return; } if ( !eventData['frames'] ) { eventData['frames'] = {}; } currFrameId = frameId; var loFid = frameId - frameBatch/2; if ( loFid < 1 ) { loFid = 1; } var hiFid = loFid + (frameBatch-1); if ( hiFid > eventData.Frames ) { hiFid = eventData.Frames; } for ( var fid = loFid; fid <= hiFid; fid++ ) { if ( !$('eventThumb'+fid) ) { var img = new Element('img', {'id': 'eventThumb'+fid, 'src': 'graphics/transparent.png', 'alt': fid, 'class': 'placeholder'}); img.addEvent('click', function() { eventData['frames'][fid] = null; checkFrames(eventId, fid); }); frameQuery(eventId, fid, loadImage && (fid == frameId)); var imgs = $('eventThumbs').getElements('img'); var injected = false; if ( fid < imgs.length ) { img.inject(imgs[fid-1], 'before'); injected = true; } else { injected = imgs.some( function( thumbImg, index ) { if ( parseInt(img.getProperty('alt')) < parseInt(thumbImg.getProperty('alt')) ) { img.inject(thumbImg, 'before'); return true; } return false; } ); } if ( !injected ) { img.inject($('eventThumbs')); } var scale = parseInt(img.getStyle('height')); img.setStyles( { 'width': parseInt((eventData.Width*scale)/100), 'height': parseInt((eventData.Height*scale)/100) } ); } else if ( eventData['frames'][fid] ) { if ( loadImage && (fid == frameId) ) { loadEventImage( eventData, eventData['frames'][fid], loadImage ); } } } $('prevThumbsBtn').disabled = (frameId==1); $('nextThumbsBtn').disabled = (frameId==eventData.Frames); } function locateImage( frameId, loadImage ) { if ( slider ) { slider.fireEvent( 'tick', slider.toPosition( parseInt((frameId-1)*slider.options.steps/eventData.Frames) )); } checkFrames( eventData.Id, frameId, loadImage ); scroll.toElement( 'eventThumb'+frameId ); } function prevImage() { if ( currFrameId > 1 ) { locateImage( parseInt(currFrameId)-1, true ); } } function nextImage() { if ( currFrameId < eventData.Frames ) { locateImage( parseInt(currFrameId)+1, true ); } } function prevThumbs() { if ( currFrameId > 1 ) { locateImage( parseInt(currFrameId)>10?(parseInt(currFrameId)-10):1, $('eventImagePanel').getStyle('display')!="none" ); } } function nextThumbs() { if ( currFrameId < eventData.Frames ) { locateImage( parseInt(currFrameId)<(eventData.Frames-10)?(parseInt(currFrameId)+10):eventData.Frames, $('eventImagePanel').getStyle('display')!="none" ); } } function prevEvent() { if ( prevEventId ) { eventQuery( prevEventId ); streamPrev( true ); } } function nextEvent() { if ( nextEventId ) { eventQuery( nextEventId ); streamNext( true ); } } function getActResponse( respObj, respText ) { if ( checkStreamForErrors( "getActResponse", respObj ) ) { return; } if ( respObj.refreshEvent ) { eventQuery( eventData.Id ); } } var actReq = new Request.JSON( {url: thisUrl, method: 'get', timeout: AJAX_TIMEOUT, link: 'cancel', onSuccess: getActResponse} ); function actQuery(action, parms) { var actParms = "view=request&request=event&id="+eventData.Id+"&action="+action; if ( auth_hash ) { actParms += '&auth='+auth_hash; } if ( parms != null ) { actParms += "&"+Object.toQueryString(parms); } actReq.send(actParms); } function deleteEvent() { pauseClicked(); //Provides visual feedback that your click happened. var deleteReq = new Request.JSON({ url: thisUrl, method: 'post', timeout: AJAX_TIMEOUT, onSuccess: function onDeleteSuccess(respObj, respText) { getActResponse(respObj, respText); // We must wait for the deletion to happen before navigating to the next // event or this request will be cancelled. streamNext(true); }, }); deleteReq.send("view=request&request=event&id="+eventData.Id+"&action=delete"); } function renameEvent() { var newName = $('eventName').get('value'); actQuery('rename', {eventName: newName}); } function editEvent() { createPopup('?view=eventdetail&eid='+eventData.Id, 'zmEventDetail', 'eventdetail'); } function exportEvent() { createPopup('?view=export&eid='+eventData.Id, 'zmExport', 'export'); } function archiveEvent() { actQuery('archive'); } function unarchiveEvent() { actQuery('unarchive'); } function showEventFrames() { createPopup('?view=frames&eid='+eventData.Id, 'zmFrames', 'frames', WEB_LIST_THUMB_WIDTH, WEB_LIST_THUMB_HEIGHT); } function showStream() { $('eventStills').addClass('hidden'); $('eventVideo').removeClass('hidden'); $('stillsEvent').removeClass('hidden'); $('streamEvent').addClass('hidden'); streamMode = 'video'; if (scale == 'auto') changeScale(); } function showStills() { $('eventStills').removeClass('hidden'); $('eventVideo').addClass('hidden'); if (vid && ( vid.paused != true ) ) { // Pause the video vid.pause(); // Update the button text to 'Play' //if ( playButton ) //playButton.innerHTML = "Play"; } $('stillsEvent').addClass('hidden'); $('streamEvent').removeClass('hidden'); streamMode = 'stills'; pauseClicked(); if ( !scroll ) { scroll = new Fx.Scroll('eventThumbs', { wait: false, duration: 500, offset: {'x': 0, 'y': 0}, transition: Fx.Transitions.Quad.easeInOut } ); } resetEventStills(); if (scale == 'auto') changeScale(); } function showFrameStats() { var fid = $('eventImageNo').get('text'); createPopup('?view=stats&eid='+eventData.Id+'&fid='+fid, 'zmStats', 'stats', eventData.Width, eventData.Height); } 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 = $j('#evtStream').width(); $j('#progressBar').css('width', barWidth); } // Shows current stream progress. function updateProgressBar() { 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.pageX - $j(this).offset().left; var seekTime = (x / $j('#progressBar').width()) * parseFloat(eventData.Length); streamSeek(seekTime); }); } function handleClick( event ) { var target = event.target; 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 || 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 ( $j('#videoobj').length ) { vid = videojs('videoobj'); addVideoTimingTrack(vid, LabelFormat, eventData.MonitorName, eventData.Length, eventData.StartTime); $j('.vjs-progress-control').append('
');//add a place for videojs only on first load vid.on('ended', vjsReplay); vid.on('play', vjsPlay); vid.on('pause', pauseClicked); vid.on('click', function(event) { handleClick(event); }); vid.on('timeupdate', function() { $j('#progressValue').html(secsToTime(Math.floor(vid.currentTime()))); }); // rate is in % so 100 would be 1x if ( rate > 0 ) { // rate should be 100 = 1x, etc. vid.playbackRate(rate/100); } } else { progressBarNav(); streamCmdTimer = streamQuery.delay(250); if ( canStreamNative ) { var imageFeed = $('imageFeed'); if ( !imageFeed ) { console.log('No element with id tag imageFeed found.'); } else { var streamImg = imageFeed.getElement('img'); if ( !streamImg ) { streamImg = imageFeed.getElement('object'); } $(streamImg).addEvent('click', function(event) { handleClick(event); }); } } } nearEventsQuery(eventData.Id); initialAlarmCues(eventData.Id); //call ajax+renderAlarmCues if ( scale == '0' || scale == 'auto' ) changeScale(); document.querySelectorAll('select[name="rate"]').forEach(function(el) { el.onchange = window['changeRate']; }); } // Kick everything off window.addEventListener('DOMContentLoaded', initPage);