UNPKG

cntk-video-tagging-tool

Version:

An electron app for building end to end Object Detection Models with CNTK from Sample Videos.

828 lines (811 loc) 36.5 kB
<!-- * video-tagging control for video tagging * Main file of the video-tagging module. --> <link rel="import" href="../polymer/polymer.html"> <link rel="import" href="video-taggingstyles.html"> <link rel="import" href="optional-tags.html"> <link rel="import" href="playback-control.html"> <dom-module id="video-tagging"> <template> <style include="video-taggingstyles"></style> <link rel="stylesheet" href="css/sliders.css" /> <link rel="stylesheet" href="../jquery-ui/themes/base/resizable.css"> <link rel="stylesheet" href="css/imgareaselect-animated.css" /> <link rel="stylesheet" href="../bootstrap/dist/css/bootstrap.min.css" /> <link rel="stylesheet" href="assets/icons/style.css"> <div id="controlWrapper" class="controlWrapper"> <playback-control id="playSpeedControl" class="playSpeedControl"></playback-control> <div id='videoWrapper' class="relativeDiv"> <div id = "regionLabelDiv" style="width:40px;height:20px;" class="regionLabel" > <span id="regionLabelSpan" class="regionLabelSpan"></span> <span id="closeRegionImage" on-click="deleteRegion" class="closeRegion clickableControl">X</span> </div> <canvas id="overlay" on-click='videoClicked' class="overlaystyle" > Your browser does not support the HTML5 canvas location. </canvas> <video id='vid' class="videoStyle"> Your browser does not support the video location. </video> </div> <div id="videoControls" class="videoControls"> <input id="seekBar" class="seek clickableControl" type="range" min='0' value="0" step="any" onkeydown="return false;" required /> <table id="videoControlsTable" class="videoControlsTable"> <tr> <td class="videoControlCell simpleControl clickableControl"> <span id="stepbwd" title="prev" class="icon-backward2 taggingControls" on-click='stepBwdClicked'></span> </td> <td class="videoControlCell simpleControl clickableControl" style="display: none"> <span id="play-pause" title="play/pause" class="icon-play3 taggingControls" on-click='playPauseClicked' ></span> </td> <td class="videoControlCell simpleControl clickableControl"> <span id="stepfwd" title="next" class="icon-forward3 taggingControls" on-click='stepFwdClicked'></span> </td> <td class="videoControlCell simpleControl clickableControl"> <span id="nextuntagged" title="first untagged frame" class="icon-next2 taggingControls" on-click='nextUntaggedClicked'></span> </td> <td class="videoControlCell simpleControl clickableControl"> <span id="clearRegions" title="clear tags" class="glyphicon glyphicon-ban-circle taggingControls" on-click='clearRegions'></span> </td> <td class="videoControlCell frameNumber"> <span id='frameText' class="textElements" title="frame#"></span> </td> <td id="playSpeedCell" title="play speed" class="videoControlCell longControl clickableControl" style="display: none"> <span id="playbackSpan" class="textElements" on-click="playbackSpeedClicked">x 1.0</span> </td> <td nowrap="nowrap" class="videoControlCell simpleControl"> <span id="timeSpan" class="textElements"></span> </td> <td class ="videoControlCell simpleControl clickableControl" style="display: none"> <span id="mute" title="Mute" class="icon-volume-medium taggingControls" on-click='muteClicked'></span> </td> <td class="volumeControlCell longControl" style="display: none"> <input id="volumeSlider" class="volume clickableControl" type="range" min=0 max = 1 value=.5 step= .1 required /> </td> </tr> </table> </div> <div id="videoTagControls" class="videoTagControls"> <div class="optionalTags"> <div class="optionalTagsWrapper"> <optional-tags id="optionalTags"></optional-tags> </div> </div> <div class="labelControls"> <span id="emptyFrame" title="empty frame" class="icon-share taggingControls controlOff" on-click='emptyFrameClicked' ></span> <span id="lockTag" title="lock tags" class="icon-pushpin taggingControls lockTag controlOff" on-click='lockTagsClicked' ></span> </div> <div style="clear: both"> </div> </div> </div> </template> <script> Polymer({ is: 'video-tagging', properties: { framerate: Number, videoduration: Number, videowidth: Number, videoheight: Number, regiontype: String, multiregions: Number, regionsize: Number, inputtagsarray:Object, inputframes: Object, src: { type: String, value: '', observer: 'videoSrcChanged' } }, // Element Lifecycle ready: function() { this.frames = {}; // Holds the data of the tagged frames, their regions and tags this.seeking = false;//Flag for enabling control over the seek bar while the video is playing, see playingCallback function this.selectedRegionId = 0;//Holds the current selected region number this.lockTagsEnabled = false; this.selectedTags = []; this.uniqueTagId = 0; this.videoStartTime = 0; //sometimes videos don't load at absoultue zero this.canMove = true; //Divs and spans this.controlWrapper= this.$$('#controlWrapper'); this.videoWrapper= this.$$('#videoWrapper'); this.overlay = this.$$('#overlay'); this.video= this.$$('#vid'); this.optionalTags= this.$$('#optionalTags'); this.regionLabelDiv = this.$$('#regionLabelDiv'); this.regionLabelSpan = this.$$('#regionLabelSpan'); this.timeSpan = this.$$('#timeSpan'); this.frameText = this.$$("#frameText"); this.playbackSpan = this.$$("#playbackSpan"); // Buttons this.playButton = this.$$('#play-pause'); this.stepfwd = this.$$("#stepfwd"); this.stepbwd = this.$$("#stepbwd"); this.lockTag = this.$$("#lockTag"); this.playSpeedControl = this.$$("#playSpeedControl"); this.playSpeedCell = this.$$("#playSpeedCell"); this.mute = this.$$('#mute'); this.emptyFrame = this.$$("#emptyFrame"); this.nextuntagged = this.$$("#nextuntagged"); // Sliders this.seekBar = this.$$("#seekBar"); this.volumeSlider = this.$$("#volumeSlider"); //dynamic styles for sliders this.volumeStyle = document.createElement('style'); Polymer.dom(this.root).appendChild(this.volumeStyle); this.seekStyle = document.createElement('style'); Polymer.dom(this.root).appendChild(this.seekStyle); this.playing = null; this.ctx = this.overlay.getContext("2d"); this.aspect = 0; this.snapWidth = 0; }, /** * Events registration and handling * * @method attached * Events registration and handling */ attached: function() { //Reset all variables to new src this.video.addEventListener( "loadedmetadata", init); var self = this; function init() { self.controlWrapper.style.display = "block"; //Init variables and controls self.frames = self.inputframes? self.inputframes:{}; self.frameTime = 1/self.framerate; self.enableAreaSelect(self); self.optionalTags.createTagControls(self.inputtagsarray); //Take the raw video aspect ratio self.aspect = self.video.offsetWidth / self.video.offsetHeight; self.snapToAspectRatio(); //Init sliders self.volumeSlider.value = 0.5; self.seekBar.max = self.video.duration; self.playingCallback(); //Size canvas as video self.overlay.width = self.video.offsetWidth; self.overlay.height = self.video.offsetHeight; //fix resize bug $(window).resize( function(){ if (self.video.offsetWidth !== undefined){ self.snapToAspectRatio(); //get transform ratio var transformWidth = self.video.offsetWidth/self.overlay.width; var transformHeight = self.video.offsetHeight/self.overlay.height; //resize the overlay self.overlay.width = self.video.offsetWidth; self.overlay.height = self.video.offsetHeight; //resize the region boxes $('.regionCanvas').each(function() { this.style.left = (parseFloat(this.style.left) * transformWidth) + "px"; this.style.top = (parseFloat(this.style.top) * transformHeight) + "px"; this.style.width = (parseFloat(this.style.width) * transformWidth) + "px"; this.style.height = (parseFloat(this.style.height) * transformHeight) + "px"; }); //reposition selectedRegion Label var selectedDiv = $('.regionCanvasSelected')[0]; self.positionRegionNameLabel(selectedDiv); } }); //bind keys (note this currently overrides any listener on the parent controls needs a more elegant way to work only when control is in focus) window.addEventListener("keydown", (function(canMove) { return function(e) { if (!canMove) return false; canMove = false; setTimeout(function() { canMove = true; }, 100); switch (e.keyCode) { case 37: // left self.stepBwdClicked(); break; case 39: // right self.stepFwdClicked(); break; case 46: //delete case 8: //backspace if($('.regionCanvasSelected')[0]){ self.deleteRegion(); } break; default: return; // exit this handler for other keys } e.preventDefault(); // prevent the default action (scroll / move caret) }; })(true), false); } this.video.onended = function(){ self.pauseState(); }; this.seekBar.addEventListener("mousedown", function() { self.seeking = true; }); this.seekBar.addEventListener("mouseup", function() { self.seeking = false; }); this.seekBar.addEventListener("change", function() { self.seeking = false; self.video.currentTime = Math.floor(self.seekBar.value/self.frameTime) * self.frameTime;//keep the frame in sync self.playingCallback(); }); this.volumeSlider.addEventListener("change", function() { self.video.volume = self.volumeSlider.value; var perc = 100 * self.volumeSlider.value / self.volumeSlider.max; self.volumeStyle.textContent = '.volume::-webkit-slider-runnable-track{background-size:'+ perc +'% 100%} '; self.volumeStyle.textContent += '.volume::-moz-range-track{background-size:'+ perc +'% 100%} '; }); this.addEventListener("playSpeedSelected", function(e) { self.playback(e.detail.playbackValue, e.detail.playbackText); }); this.addEventListener("ontagsubmitted", function(e) { var arr = []; arr.push(e.detail.tagid); this.addTagsToRegion(arr); this.emitRegionToHost();//Persist }); this.addEventListener("ontagdeleted", function(e) { var regionId = this.selectedRegionId -1;//Revert to zero-based array var region = this.frames[this.getCurrentFrame()][regionId]; for(var index = 0;index < region.tags.length;index++){ if(region.tags[index] === e.detail.tagid){ region.tags.splice(index, 1); break; } } this.emitRegionToHost(); }); }, snapToAspectRatio: function() { // -5 accounts for rounding errors during rendering to ensure // we never redraw larger than the parent container. // We need to offset for the non-scaling parts of the video controls this.snapWidth = ($('#video-tagging').parent().height() - $('#videoControls').innerHeight() - $('#videoTagControls').innerHeight() - 5) * this.aspect; if(this.snapWidth > $('#video-tagging').parent().width()) $('#video-tagging').width($('#video-tagging').parent().width()); else $('#video-tagging').width(this.snapWidth); }, /** * JQuery Area selector * Based on JQuery plugin imgareaselect * http://odyniec.net/projects/imgareaselect/usage.html * * Allows selection of a rectangle */ clearArea: function() { $('canvas#overlay').imgAreaSelect({ show:false, hide:true }); }, enableAreaSelect: function(self) { $('canvas#overlay').imgAreaSelect({ disable: true, show:false, hide:true }); if((this.regiontype.toLowerCase() === "square") || (this.regiontype.toLowerCase() === "rectangle")) { $('canvas#overlay').imgAreaSelect({ disable: false, //enable/disable handles: true, //grab handles when selecting the area aspectRatio: '1:1', maxWidth: self.overlay.offsetWidth, maxHeight: self.overlay.offsetHeight, minWidth:10, fadeSpeed: 200, square: (this.regiontype.toLowerCase() === "square"), onSelectEnd: function(img, selection){ if (selection.width !== 0 && selection.height !== 0) { self.areaSelected = true; self.createRegion(selection.x1, selection.y1, selection.x2, selection.y2); self.clearArea(); } }, onSelectStart: function(img, selection){ self.cleanSelectedElements(); }, }); } }, /** * All functions related to regions management * * @method positionRegionNameLabel * Positions the div with the region number next to the region div. * @param {div} The region div */ //Regions management updateRegionZIndices: function(){ $('.regionCanvas').sort(function (e1, e2) { return ($(e1).width() * $(e1).height() <= $(e2).width() * $(e2).height()); }).each(function(idx,e) { e.style.zIndex =(idx+200).toString(); }); }, positionRegionNameLabel: function(div) { if (div){ this.regionLabelDiv.style.left = (div.offsetLeft + parseFloat(div.style.width, 10) - parseFloat(this.regionLabelDiv.style.width, 10)) + "px"; this.regionLabelDiv.style.top = (div.offsetTop - parseFloat(this.regionLabelDiv.style.height, 10)) + "px" ; //Out of bounds top - move it below div if(parseFloat(this.regionLabelDiv.style.top) < 0) { this.regionLabelDiv.style.top = (div.offsetTop + parseFloat(div.style.height, 10)) + "px" ; } this.regionLabelSpan.innerHTML = div.id; this.regionLabelDiv.style.display = "block"; } }, createRegion: function(x1, y1, x2, y2) { var region = this.addRegion(x1, y1, x2, y2);//Add to in-memory collection this.addDivToRegion(x1, y1, x2 , y2, region.name);//add frame this.updateRegionZIndices();//update region z indecies this.regionSelected(region.name);//Select it by default //if there is only one tag enable it by default if ($(`#${this.optionalTags.id}`).find('.tagButtons').size() == 1){ $(`#${this.optionalTags.id}`).find('.tagButtons')[0].click(); } if(this.lockTagsEnabled) { //Get all selected tags and add them to current region automatically //this.selectedTags was populated in this.lockTagsClicked var arr = []; for (var i=0; i<this.selectedTags.length;i++) { this.optionalTags.setSelected(this.selectedTags[i]); arr.push(this.selectedTags[i].id); } this.addTagsToRegion(arr); var self = this; //Auto step functionality - Goes to next frame automatically if(this.multiregions === "0") { setTimeout(function(){ self.stepFwdClicked(); }, 500); } } this.emitRegionToHost();//Persist }, addTagsToRegion: function(selectedTagsArray) { var regionId = this.selectedRegionId -1;//Revert to zero-based array var region = this.frames[this.getCurrentFrame()][regionId]; for (var i = 0; i < selectedTagsArray.length; i++) { region.tags.push(selectedTagsArray[i]); } }, nextUntaggedClicked :function() { var frameIndex = this.getCurrentFrame(); var frames = this.video.duration * this.framerate; var framesForward = 1; for (var i=frameIndex;i<frames;i++) { //The next frame is a first occurence of a frame with no regions if(!this.frames[i + 1]) { if(!this.video.paused) { this.playPauseClicked();//Will pause the video } this.video.currentTime += (this.frameTime * framesForward); this.playingCallback(); break; } framesForward++; } }, lockTagsClicked: function() { var selTags = this.optionalTags.getSelectedTags(); //There has to be selected label/s if (!this.lockTagsEnabled && selTags.length > 0) { this.lockTagsEnabled = true; this.selectedTags = selTags; if(this.multiregions === "0"){this.stepFwdClicked();} } else { this.lockTagsEnabled = false; this.selectedTags = []; this.optionalTags.resetSelected(); } this.lockTag.classList.toggle("controlOn", this.lockTagsEnabled); this.lockTag.classList.toggle("controlOff", !this.lockTagsEnabled); }, regionSelected: function(divId) { this.cleanSelectedElements(); var div = document.getElementById(divId); div.classList.add("regionCanvasSelected"); this.selectedRegionId = div.id; this.positionRegionNameLabel(div); //Tags - display the tags of the region and enable editing this.optionalTags.toggleEnableButtons(true); this.optionalTags.resetSelected(); var regions = this.frames[this.getCurrentFrame()]; if(regions) { var tags = this.frames[this.getCurrentFrame()][this.selectedRegionId - 1].tags; this.optionalTags.displaySelectedTags(tags); } function updateRegionFromDiv(div){ var region = regions[(divId-1).toString()]; region.width = $('#vid').width(); region.height = $('#vid').height(); region.x1 = parseInt(div.style.left); region.y1 = parseInt(div.style.top); region.x2 = region.x1 + parseInt(div.style.width); region.y2 = region.y1 + parseInt(div.style.height); } //make region draggable $('#'+divId).draggable({ stop: function(event, ui) { updateRegionFromDiv(this); }, containment: $("#overlay")[0] }); //make region resizable $('#'+divId).resizable({ stop: function(event, ui) { updateRegionFromDiv(this); $('.regionCanvas').sort(function (e1, e2) { return ($(e1).width() * $(e1).height() <= $(e2).width() * $(e2).height()); }).each(function(idx,e) { e.style.zIndex =(idx+200).toString(); }); }, containment: $("#overlay")[0], handles: 'all' }); //raise regionSelectedEvent $('#video-tagging').trigger('canvasRegionSelected',[divId]); }, deleteRegion: function(e) { var regions = this.frames[this.getCurrentFrame()]; var deletedRegion = regions[this.selectedRegionId - 1]; regions.splice(this.selectedRegionId - 1, 1); //Shift array left to cover removed item - Rename all regions which are higher than the deleted one, for (var i=0;i<regions.length;i++) { var id = Number(regions[i].name); if(id > this.selectedRegionId){ regions[i].name = id - 1; } } this.showAllRegions(); this.emitRegionToHost(); }, clearRegions: function(e) { this.frames[this.getCurrentFrame()]=[]; this.showAllRegions(); this.emitRegionToHost(); }, /** * @method emitRegionToHost * Fires an event to send the array of regions. * The host html page can listen to this event. */ emitRegionToHost: function() { var frameIndex = this.getCurrentFrame(); this.fire('onregionchanged', {frame: {frameIndex:frameIndex, regions:this.frames[frameIndex]}}); }, /** * @method addDivToRegion * Adds a transparent div to the region, the size of the region. * Used for click events, hover, etc.. * @params {x1, y1, x2, y2} region coordinates. * @param {regionId} The region id, which is the region index + 1. */ addDivToRegion: function(x1, y1, x2, y2, regionId) { var div = document.createElement("div"); div.id = regionId; div.classList.add("regionCanvas"); if(this.regiontype.toLowerCase() === "point") { div.style.width = this.regionsize - 1 + "px";//Compensate for 1px border div.style.height = this.regionsize - 1 + "px"; div.style.top = y1 - this.regionsize/2 + "px"; div.style.left = x1 - this.regionsize/2 + "px"; div.classList.add("regionPoint"); } if((this.regiontype.toLowerCase() === "square") || (this.regiontype.toLowerCase() === "rectangle")) { div.style.width = x2 - x1 + "px"; div.style.height = y2 - y1 + "px"; div.style.top = y1 + "px"; div.style.left = x1 + "px"; } var self = this; $( div ) .click(function(e) { self.regionSelected(div.id); }); $( div ) .mouseenter(function(e) { self.positionRegionNameLabel(this); }) .mouseleave(function() { self.regionLabelDiv.style.display = "none"; if(self.selectedRegionId === div.id) { self.regionLabelDiv.style.display = "block"; } }); Polymer.dom(this.$.videoWrapper).appendChild(div); }, /** * @method cleanSelectedElements * Removes all borders of regions and disables label buttons */ cleanSelectedElements: function() { //Remove selected style var regionCanvases = Polymer.dom(this.root).querySelectorAll('.regionCanvas'); for (var i=0;i<regionCanvases.length;i++) { regionCanvases[i].classList.remove('regionCanvasSelected'); } //reset this.selectedRegionId = 0; this.optionalTags.toggleEnableButtons(false); this.optionalTags.resetSelected(); }, showAllRegions: function() { this.clearFrameElements();//Clear canvas and tags this.cleanSelectedElements();//Clear selected regions var frameIndex = this.getCurrentFrame(); var regions = this.frames[frameIndex]; if(regions && regions.length > 0) { //Draw all regions for this frame for (var i=0; i<regions.length;i++) { var region = regions[i]; //Frame was tagged as empty? if(Object.keys(region).length === 0) { this.indicateEmptyFrame(true); continue; } //Calculate x, y relative to current width and height var widthRatio = $('#vid').width() / region.width; var heightRatio = $('#vid').height() / region.height ; var x1 = (region.x1 * widthRatio); var y1 = (region.y1 * heightRatio); var x2 = (region.x2 * widthRatio); var y2 = (region.y2 * heightRatio); x1 = (region.x1 * widthRatio); y1 = (region.y1 * heightRatio); x2 = (region.x2 * widthRatio); y2 = (region.y2 * heightRatio); this.addDivToRegion( x1, y1, x2, y2, region.name); //Only 1 region - select it and show tags if(regions.length === 1) { this.regionSelected(region.name); } this.updateRegionZIndices(); } } }, /** * @method addRegion * Adds a region to the array of regions per current frame. * @params {x1, y1, x2, y2} region coordinates. */ addRegion: function(x1, y1, x2, y2) { this.resetEmptyFrame();//Clear empty frame logic var region = {}; region.x1 = x1; region.y1 = y1; region.x2 = x2; region.y2 = y2; region.id = this.uniqueTagId++; region.width = $('#vid').width(); region.height = $('#vid').height(); region.type = this.regiontype; region.tags = []; var frameIndex = this.getCurrentFrame(); var regions = this.frames[frameIndex]; //The array is populated and can contain multiple regions if(this.multiregions == 1 && regions) { region.name = regions.length + 1; this.frames[frameIndex].push(region); } else {//Only one region allowed this.clearFrameElements(); region.name = 1; this.frames[frameIndex] = []; this.frames[frameIndex].push(region); } return region; }, /** * All functions related to frames management * * @method clearFrameElements * Clears all drawings and divs from the video area and resets tag controls. */ clearFrameElements: function() { //Clear divs $(this.videoWrapper).children(".regionCanvas").remove(); //reset tag buttons to not selected this.optionalTags.resetSelected(); //hide region number self.regionLabelDiv.style.display = "none"; //Clears the empty frame icon this.indicateEmptyFrame(false); }, getCurrentFrame: function() { //if (this.videoStartTime == 0) return Math.ceil((this.video.currentTime - this.videoStartTime) * this.framerate) + 1 ; return this.video.currentTime === 0 ? 1 : Math.ceil((this.video.currentTime - this.videoStartTime) * this.framerate) + 1 ; }, indicateEmptyFrame : function(selected) { if(selected) { this.emptyFrame.classList.remove("controlOff"); this.emptyFrame.classList.add("controlOn"); } else { this.emptyFrame.classList.remove("controlOn"); this.emptyFrame.classList.add("controlOff"); } }, /** * @method emptyFrameClicked * Creates an empty region array for the current frame, meaning that the frame is not tagged, but has been reviewed. */ emptyFrameClicked: function() { var frameIndex = this.getCurrentFrame(); var regions = this.frames[frameIndex]; if(!regions || regions.length === 0) { this.frames[frameIndex] = [{}]; this.indicateEmptyFrame(true); this.emitRegionToHost(); if(this.lockTagsEnabled){ this.stepFwdClicked(); } } }, /** * @method resetEmptyFrame * If the frame has been marked as empty - reset that. */ resetEmptyFrame: function() { var regions = this.frames[this.getCurrentFrame()]; //If there is an empty region if(regions && regions.length === 1 && Object.keys(regions[0]).length === 0) { //reset all for this frame this.frames[this.getCurrentFrame()] = []; this.clearFrameElements(); } }, /** * Video management * */ muteClicked: function() { this.mute.classList.toggle("icon-volume-mute2", !this.video.muted); this.mute.classList.toggle("icon-volume-medium", this.video.muted); this.video.muted = !this.video.muted; }, /** * Handler for setting the video play speed */ playback: function(val, text) { if(val !== null){ this.video.playbackRate = val; this.playbackSpan.innerHTML = text; } this.playSpeedControl.style.display = "none"; }, /** * Shows the play speed control */ playbackSpeedClicked: function() { var offset = $('#playSpeedCell').offset(); var top = offset.top - $('#playSpeedControl').height(); var left = offset.left + $('#playSpeedCell').width() / 4; $('#playSpeedControl').css({'left': left, 'top': top}); this.playSpeedControl.style.display = this.playSpeedControl.style.display === "block"?"none":"block"; }, playPauseClicked: function() { this.video.paused ? this.playState():this.pauseState(); }, getUnlabeledRegionTags: function(regionId){ var regions = this.frames[regionId]; var unlabledTags = []; if (regions !== undefined){ unlabledTags = regions.map(function(region,index){ if (!region.tags.length > 0) return index + 1; }).filter(Number.isInteger); } return unlabledTags; }, checkRegionLabels: function(){ var unlabledTags = this.getUnlabeledRegionTags(this.getCurrentFrame()); if (unlabledTags.length > 0){ alert(`Cannot move to the next frame until all tags are labeled. Please label the following tags [${unlabledTags}] on the displayed frame.`); return false; } return true; }, stepFwdClicked: function(fireEvents = true) { if (!this.canMove) return; if(!this.video.paused) this.pauseState(); if ((this.video.currentTime + this.frameTime) > this.video.duration ){ this.video.currentTime = this.video.duration; return; } if (this.checkRegionLabels()){ //raise before next frame if(fireEvents)$('#video-tagging').trigger('stepFwdClicked-BeforeStep'); //if there are no unlabled tags move to next frame this.video.currentTime += this.frameTime; this.playingCallback(); //raise after next frame if(fireEvents)$('#video-tagging').trigger('stepFwdClicked-AfterStep'); } }, stepBwdClicked: function() { if (!this.canMove) return; if (this.video.currentTime > 0) { if(!this.video.paused) { this.pauseState(); } if (this.checkRegionLabels()){ this.video.currentTime -= this.frameTime; this.playingCallback(); } } }, videoSrcChanged: function(newValue, oldValue) { if(this.video){ this.video.src = newValue; } }, videoClicked: function(e) { this.cleanSelectedElements(); if(!((this.regiontype.toLowerCase() === "square") || (this.regiontype.toLowerCase() === "rectangle"))) { var rect = this.overlay.getBoundingClientRect(); var x1 = (e.clientX-rect.left)/(rect.right-rect.left)*this.overlay.width; var y1 = (e.clientY-rect.top)/(rect.bottom-rect.top)*this.overlay.height; this.createRegion(x1, y1, null, null); } }, playState: function() { this.video.play(); this.playButton.classList.toggle("icon-pause2", !this.video.paused); //Reset lock tags to off this.lockTagsEnabled = true; this.lockTagsClicked(); this.optionalTags.toggleEnableButtons(false); $('canvas#overlay').imgAreaSelect({disable: true});//disable canvas var self = this; this.playing = setInterval(function() { self.playingCallback(); }, 10); }, pauseState: function() { this.video.pause(); clearInterval(this.playing); this.video.currentTime = Math.floor(this.video.currentTime/this.frameTime) * this.frameTime;//keep the frame in sync this.playingCallback(); this.playButton.classList.toggle("icon-pause2", !this.video.paused); this.optionalTags.toggleEnableButtons(false); $('canvas#overlay').imgAreaSelect({disable: false});//enable canvas }, playingCallback: function() { this.frameText.innerHTML = this.getCurrentFrame(); this.displayVideoTime(); if(!this.seeking){ this.updateSeekBar(); } this.showAllRegions(); }, displayVideoTime: function() { var currentTime = Math.round(this.video.currentTime); var remainingtTime = Math.round(this.video.duration - this.video.currentTime); //Format using moment.js currentTime = moment().startOf('day').seconds(currentTime).format('HH:mm:ss'); remainingtTime = moment().startOf('day').seconds(remainingtTime).format('HH:mm:ss'); this.timeSpan.innerHTML = currentTime + " / " + remainingtTime; }, updateSeekBar: function() { this.seekBar.value = this.video.currentTime; var perc = 100 * this.seekBar.value / this.seekBar.max; this.seekStyle.textContent = '.seek::-webkit-slider-runnable-track{background-size:'+perc+'% 100%}'; this.seekStyle.textContent += '.seek::-moz-range-track{background-size:'+perc+'% 100%}'; }, }); </script> <script src="../jquery/dist/jquery.min.js"></script> <script src="../moment/moment.js"></script> <script src="js/jquery.imgareaselect.js"></script> <script src="../jquery-ui/jquery-ui.min.js"></script> </dom-module>