ZMS controls for videojs

Add ZMS style controls to videojs page.  Zoom, fast forward, fast reverse, frame skip, play/pause.  Some cleanup of old videojs code
This commit is contained in:
digital-gnome
2017-11-11 11:19:31 -05:00
parent 21949f5426
commit 91c3e3573c
4 changed files with 178 additions and 52 deletions

View File

@@ -1,3 +1,11 @@
.vjs-tech {
pointer-events: none;
}
.vjs-captions-button {
display: none;
}
.vjs-tt-cue {
margin-bottom: .75em;
}

View File

@@ -156,7 +156,7 @@ if ( $Event->DefaultVideo() ) {
?>
<div id="eventVideo" class="">
<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.

View File

@@ -124,7 +124,11 @@ function renderAlarmCues () {
function setButtonState( element, butClass ) {
if ( element ) {
element.className = butClass;
element.disabled = (butClass != 'inactive' && (element.id == "pauseBtn" || element.id == "playBtn"));
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");
}
@@ -218,17 +222,13 @@ function getCmdResponse( respObj, respText ) {
lastEventId = eventId;
}
if (lastEventId == 0) lastEventId = eventId; //Only fires on first load.
if ( streamStatus.paused == true ) {
$('modeValue').set( 'text', 'Paused' );
$('rate').addClass( 'hidden' );
} else {
if (!streamStatus.paused){
console.log('playing');
$('modeValue').set( 'text', "Replay" );
$('rateValue').set( 'text', streamStatus.rate );
$('rate').removeClass( 'hidden' );
$j('#modeValue').html("Replay");
$j('#rateValue').html(streamStatus.rate);
}
$('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
@@ -248,13 +248,23 @@ function getCmdResponse( respObj, respText ) {
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' );
@@ -264,11 +274,26 @@ function streamPause( ) {
}
function playClicked( ) {
streamReq.send( streamParms+"&command="+CMD_PLAY );
if (vid) {
if (vid.paused()) {
vid.play();
} else {
vjsPlay();
}
} 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' );
setButtonState( $('playBtn'), 'active' );
setButtonState( $('fastFwdBtn'), 'inactive' );
@@ -284,15 +309,40 @@ function streamFastFwd( action ) {
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 ) {
streamReq.send( streamParms+"&command="+CMD_SLOWFWD );
if (vid) {
vid.currentTime(vid.currentTime() + spf);
} else {
streamReq.send( streamParms+"&command="+CMD_SLOWFWD );
}
}
function streamSlowRev( action ) {
streamReq.send( streamParms+"&command="+CMD_SLOWREV );
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 ) {
@@ -301,8 +351,26 @@ function streamFastRev( action ) {
setButtonState( $('fastFwdBtn'), 'inactive' );
setButtonState( $('slowFwdBtn'), 'unavail' );
setButtonState( $('slowRevBtn'), 'unavail' );
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) {
@@ -324,7 +392,7 @@ function streamNext(action) {
if (action) {
$j(".vjsMessage").remove();//This shouldn't happen
if (nextEventId == 0) { //handles deleting last event.
vid ? vid.pause() : streamPause();
pauseClicked();
let hideContainer = $j( vid ? "#eventVideo" : "#imageFeed");
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>');
@@ -343,12 +411,61 @@ function streamNext(action) {
}
}
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 ) {
@@ -356,7 +473,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 ) {
@@ -414,6 +535,10 @@ function getEventResponse( respObj, respText ) {
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();
}
@@ -715,7 +840,7 @@ function actQuery( action, parms ) {
}
function deleteEvent() {
vid ? vid.pause() : streamPause(); //Provides visual feedback that your click happened.
pauseClicked(); //Provides visual feedback that your click happened.
actQuery( 'delete' );
streamNext( true );
}
@@ -781,7 +906,7 @@ function showStills() {
streamMode = 'stills';
streamPause( true );
pauseClicked();
if ( !scroll ) {
scroll = new Fx.Scroll( 'eventThumbs', {
wait: false,
@@ -829,11 +954,19 @@ function progressBarNav (){
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) {
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);
}
@@ -937,31 +1070,14 @@ function setupListener() {
function initPage() {
//FIXME prevent blocking...not sure what is happening or best way to unblock
if ($j('#videoobj').length) {
vid = videojs("videoobj");
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
nearEventsQuery(eventData.Id);
initialAlarmCues(eventData.Id); //call ajax+renderAlarmCues after videojs is initialized
vjsReplay();
}
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(); */
$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 );

View File

@@ -33,12 +33,14 @@ var eventData = {
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(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())?>";