From d87bf5a15663b7f1181ca74d103799cb0bed420f Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Fri, 20 Dec 2019 11:02:12 -0500 Subject: [PATCH 1/3] replace the rate display with a dropdown --- web/skins/classic/views/event.php | 7 ++++++- web/skins/classic/views/js/event.js | 31 ++++++++++++++++++++++------- 2 files changed, 30 insertions(+), 8 deletions(-) diff --git a/web/skins/classic/views/event.php b/web/skins/classic/views/event.php index 947bd3972..a17e48b65 100644 --- a/web/skins/classic/views/event.php +++ b/web/skins/classic/views/event.php @@ -257,7 +257,12 @@ if ( ZM_WEB_STREAM_METHOD == 'mpeg' && ZM_MPEG_LIVE_FORMAT ) {

: Replay - : x + : +translate('Stopped'), 100 => '1x', 200=>'2x', 400=>'4x', 800=>'8x' ); +echo htmlSelect('rate', $rates, $rate, array('id'=>'rateValue')); +?> + : 0s : 1x
diff --git a/web/skins/classic/views/js/event.js b/web/skins/classic/views/js/event.js index 76fc47c79..96f56dbd4 100644 --- a/web/skins/classic/views/js/event.js +++ b/web/skins/classic/views/js/event.js @@ -200,6 +200,19 @@ function changeReplayMode() { refreshWindow(); } +function changeRate() { + var rate = $j('select[name="rate"]').val(); + if ( ! rate ) { + pauseClicked(); + } else { + if ( vid ) { + vid.playbackRate(rate/100); + Cookie.write('zmEventRate', rate, {duration: 10*365}); + } + } +} + + var streamParms = "view=request&request=stream&connkey="+connKey; if ( auth_hash ) { streamParms += '&auth='+auth_hash; @@ -245,7 +258,7 @@ function getCmdResponse( respObj, respText ) { if ( streamStatus.paused == true ) { streamPause( ); } else { - $j('#rateValue').html(streamStatus.rate); + $j('select[name="rate"]').val(streamStatus.rate*100); Cookie.write('zmEventRate', streamStatus.rate*100, {duration: 10*365}); streamPlay( ); } @@ -296,7 +309,7 @@ function vjsPause() { function streamPause( ) { $j('#modeValue').html('Paused'); - $j('#rateValue').html('0'); + $j('select[name="rate"]').val('0'); setButtonState( $('pauseBtn'), 'active' ); setButtonState( $('playBtn'), 'inactive' ); setButtonState( $('fastFwdBtn'), 'unavail' ); @@ -322,7 +335,7 @@ function vjsPlay() { //catches if we change mode programatically if ( intervalRewind ) { stopFastRev(); } - $j('#rateValue').html(vid.playbackRate()); + $j('select[name="rate"]').val(vid.playbackRate()); Cookie.write('zmEventRate', vid.playbackRate()*100, {duration: 10*365}); streamPlay(); } @@ -350,7 +363,7 @@ function streamFastFwd( action ) { if ( rates.indexOf(vid.playbackRate()*100)-1 == -1 ) { setButtonState($('fastFwdBtn'), 'unavail'); } - $j('#rateValue').html(vid.playbackRate()); + $j('#rate').val(vid.playbackRate()); Cookie.write('zmEventRate', vid.playbackRate()*100, {duration: 10*365}); } else { streamReq.send(streamParms+"&command="+CMD_FASTFWD); @@ -397,7 +410,7 @@ function streamFastRev( action ) { setButtonState( $('fastRevBtn'), 'unavail' ); } clearInterval(intervalRewind); - $j('#rateValue').html(-revSpeed); + $j('#rate').val(-revSpeed); Cookie.write('zmEventRate', vid.playbackRate()*100, {duration: 10*365}); intervalRewind = setInterval(function() { if (vid.currentTime() <= 0) { @@ -588,7 +601,7 @@ function getEventResponse( respObj, respText ) { CurEventDefVideoPath = null; $j('#modeValue').html('Replay'); $j('#zoomValue').html('1'); - $j('#rateValue').html('1'); + $j('#rate').val('100'); vjsPanZoom('zoomOut'); } else { drawProgressBar(); @@ -1066,7 +1079,8 @@ function initPage() { $j('#progressValue').html(secsToTime(Math.floor(vid.currentTime()))); }); - if ( rate > 1 ) { + // rate is in % so 100 would be 1x + if ( rate > 0 ) { // rate should be 100 = 1x, etc. vid.playbackRate(rate/100); } @@ -1091,6 +1105,9 @@ function initPage() { nearEventsQuery(eventData.Id); initialAlarmCues(eventData.Id); //call ajax+renderAlarmCues if (scale == "auto") changeScale(); + document.querySelectorAll('select[name="rate"]').forEach(function(el) { + el.onchange = window['changeRate']; + }); } // Kick everything off From 1bdabefb2d06ef343782cb9c723fb9b506a8bb3e Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Tue, 7 Jan 2020 17:07:35 -0500 Subject: [PATCH 2/3] fix single stepping in reverse but actually modifying curr_frame_id --- src/zm_eventstream.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/zm_eventstream.cpp b/src/zm_eventstream.cpp index e2f1862ab..2ed2dc167 100644 --- a/src/zm_eventstream.cpp +++ b/src/zm_eventstream.cpp @@ -383,6 +383,8 @@ void EventStream::processCommand(const CmdMsg *msg) { paused = true; replay_rate = ZM_RATE_BASE; step = -1; + curr_frame_id -= 1; + if ( curr_frame_id < 1 ) curr_frame_id = 1; break; case CMD_FASTREV : Debug(1, "Got FAST REV command"); @@ -904,7 +906,7 @@ void EventStream::runStream() { send_frame = true; } } else if ( step != 0 ) { - Debug(2, "Paused with step"); + Debug(2, "Paused with step %d", step); // We are paused and are just stepping forward or backward one frame step = 0; send_frame = true; From 59bd658a96671b5fdda4b1a250544fd38d0fccbe Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Tue, 10 Mar 2020 18:46:20 -0400 Subject: [PATCH 3/3] replace vjsPause with pauseCLicked. Fix behaviour with rate dropdown and reverse/pause etc. --- web/skins/classic/views/js/event.js | 72 +++++++++++++++++------------ 1 file changed, 43 insertions(+), 29 deletions(-) diff --git a/web/skins/classic/views/js/event.js b/web/skins/classic/views/js/event.js index 30cc5057c..c569e08e2 100644 --- a/web/skins/classic/views/js/event.js +++ b/web/skins/classic/views/js/event.js @@ -1,4 +1,7 @@ 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() { @@ -39,7 +42,7 @@ function vjsReplay() { streamNext(true); break; } -} +} // end function vjsReplay $j.ajaxSetup({timeout: AJAX_TIMEOUT}); //sets timeout for all getJSON. @@ -67,14 +70,14 @@ function renderAlarmCues(containerEl) { var spanTimeStart = 0; var spanTimeEnd = 0; var alarmed = 0; - var alarmHtml = ""; + 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. + 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; @@ -88,24 +91,24 @@ function renderAlarmCues(containerEl) { } alarmHtml += ''; spanTimeStart = spanTimeEnd; - } else if (frame.Type !== "Alarm" && alarmed == 1) { //from alarm to nothing. End alarm and start nothing. + } 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. + 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") { + if ( cueFrames[indexPlus].Type == 'Alarm' ) { i = --indexPlus; skip = 1; break; } indexPlus++; } - if (skip == 1) continue; //javascript doesn't support continue 2; + if ( skip == 1 ) continue; //javascript doesn't support continue 2; spanTimeEnd = frame.Delta *100; spanTime = spanTimeEnd - spanTimeStart; alarmed = 0; @@ -118,7 +121,7 @@ function renderAlarmCues(containerEl) { } alarmHtml += ''; spanTimeStart = spanTimeEnd; - } else if (frame.Type == "Alarm" && alarmed == 1 && i + 1 >= cueFrames.length) { //event ends on an alarm + } 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; @@ -155,12 +158,12 @@ function changeScale() { var eventViewer; var alarmCue = $j('div.alarmCue'); var bottomEl = streamMode == 'stills' ? $j('#eventImageNav') : $j('#replayStatus'); - if (streamMode == 'stills') { + if ( streamMode == 'stills' ) { eventViewer = $j('#eventThumbs'); } else { eventViewer = $j(vid ? '#videoobj' : '#evtStream'); } - if ( scale == "auto" ) { + if ( scale == 'auto' ) { var newSize = scaleToFit(eventData.Width, eventData.Height, eventViewer, bottomEl); newWidth = newSize.width; newHeight = newSize.height; @@ -201,17 +204,31 @@ function changeReplayMode() { } function changeRate() { - var rate = $j('select[name="rate"]').val(); + var rate = parseInt($j('select[name="rate"]').val()); if ( ! rate ) { pauseClicked(); - } else { + } 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}); } } -} - + Cookie.write('zmEventRate', rate, {duration: 10*365}); +} // end function changeRate var streamParms = "view=request&request=stream&connkey="+connKey; if ( auth_hash ) { @@ -294,21 +311,18 @@ var streamReq = new Request.JSON( { function pauseClicked() { if ( vid ) { + if ( intervalRewind ) { + stopFastRev(); + } vid.pause(); } else { streamReq.send(streamParms+"&command="+CMD_PAUSE); - streamPause(); - } -} - -function vjsPause() { - if ( intervalRewind ) { - stopFastRev(); } streamPause(); } function streamPause( ) { + $j('#modeValue').html('Paused'); setButtonState( $('pauseBtn'), 'active' ); setButtonState( $('playBtn'), 'inactive' ); @@ -319,6 +333,10 @@ function streamPause( ) { } 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(); @@ -327,8 +345,8 @@ function playClicked( ) { } } else { streamReq.send(streamParms+"&command="+CMD_PLAY); - streamPlay(); } + streamPlay(); } function vjsPlay() { //catches if we change mode programatically @@ -341,7 +359,6 @@ function vjsPlay() { //catches if we change mode programatically } function streamPlay( ) { - $j('#modeValue').html('Replay'); setButtonState( $('pauseBtn'), 'inactive' ); setButtonState( $('playBtn'), 'active' ); setButtonState( $('fastFwdBtn'), 'inactive' ); @@ -370,9 +387,6 @@ function streamFastFwd( action ) { } } -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 ) { if ( vid ) { @@ -1073,7 +1087,7 @@ function initPage() { $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', vjsPause); + vid.on('pause', pauseClicked); vid.on('click', function(event) { handleClick(event); });