Source: player.js


/** @file This file the information about the video player. */

/** Global reference to the video player 
 * @global 
 
 */
var oVP;

/** 
 * Creates a video player pointing to files specified by the url
 * @constructor
*/
function JSVideo() {

  var fname_folder = main_media.GetFileInfo().GetImagePath()+"/";
  fname_folder =  fname_folder;

  // *******************************************
  // Private variables:
  // *******************************************

  // Struct with video data:
  var oVideoData = null;

  // HTML elements with video data:
  var aFrameImages = [];
  
  // Display width/height for video:
  var imageWidth;
  var imageHeight;
  
  // Current displayed video frame:
  var iFrameCtr = 0;

  // Last "time" for frame rate calculation:
  var iLastTime = -1;

  // Indicates whether the video has started playing:
  var bPlaying = false;

  // Indicates if the video is paused:
  var bPaused = false;

  // Indicates whether it was the user who paused the video
  var uPaused = false;

  // Indicates if the scroll button is clicked:
  var scroll_button_clicked = false;

  // *******************************************
  // Public methods:
  // *******************************************

  // Starting point of video player.  Load a video file from URL.
  this.getnumFrames = function (){
    if (oVideoData == null) return 0;
    else return oVideoData.frames;
  }
  this.getcurrentFrame = function (){
    return iFrameCtr;
  }

/** This function checks that the loaded frames are correct, stores them in the video structure
   * It prepares the polygon for scaling.
   * @param {int} frame - the position corresponding to the first frame in the set
   * @param {bool} first_time - boolean indicating whether it was the first frame load of the video
   * @param {json} response - json with the set of jpg frames being loaded
*/
this.loadFile = function(frame, first_time, isbackground, response) {


    if (first_time || frame == iFrameCtr) $('#oLoading').css('display',"block");
    
    var fncLoad = function() {
      try {
        console.timeEnd('Load video');
        oVideoData = eval("(" + response + ")");
        if (first_time){ 
          oVP.imageWidth = oVideoData.width;
          oVP.imageHeight = oVideoData.height;
          oVP.CreateVideoCanvas();
        }
        if (first_time || frame >= iFrameCtr) $('#oLoading').css('display',"none");
        oVP.GenerateFrames();
        if (first_time) ovP.GoToFrame(frame);
        ovP.seekChunkToDownload(frame);
        if (first_time || frame == iFrameCtr ) oVP.Play();
      }
      catch(e) {
         console.log("Error parsing video data ", e);
      }
    }
    var fncError = function() {
      console.log("Error loading video file");
    }
    if (response) fncLoad();
    else fncError();
  }

  /** This function creates the html elements (display, scroll bar and buttons) for the video player */
  // Create video canvas elements.
  this.CreateVideoCanvas = function() {
    // Create canvas:
    var oCanvas = '<svg id="canvas" width="' + this.imageWidth + '" height="' + this.imageHeight + '" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" style="position:absolute;left:8px;top:8px;z-index:-5;"></svg><div id="oCanvas" style="background-color:black;width:' + this.imageWidth + 'px;height:' + this.imageHeight + 'px;position:relative;z-index:-5;" />';
    $('#videoplayer').append(oCanvas);
  
    // Create "loading" message:
    var oLoading = '<div id="oLoading" style="width:100%;height:30px;position:absolute;bottom:0px;padding-top:10px;color:white;text-align:center;display:none;font-family:verdana;font-size:12px;z-index:4" >Loading video file...</div>';
    $('#oCanvas').append(oLoading);
    
    // Create scroll bar:
    var oScroll = '<div id="oScroll" style="width:'+this.imageWidth+'px;"></div>'
    var oLoad = '<div id="oLoadBar" style="width:'+this.imageWidth+'px; z-index:0"></div>'

          
    $('#videoplayer').append(oScroll);
    $('#oScroll').slider({
            range : "min",
            min   : 1,
            max   : this.imageWidth,
            value : 1,
            slide : function (event, ui) {
                pos = ui.value/ovP.imageWidth;
                iFrameCtr = Math.max(Math.floor(pos*(oVideoData.frames-1)),1);
                ovP.DisplayFrame(iFrameCtr);
            }
            
        });

    // Create loaded bar
    
    $('#oScroll').append(oLoad);
    $('#oLoadBar').slider({
            range : "min",
            min   : 1,
            max   : this.imageWidth,
            value : 1,
            
            
        });
    $("#oScroll .ui-slider-range").css("background", "red");
    $("#oLoadBar .ui-slider-range").css("background", "gray");
    $("#oLoadBar .ui-slider-handle").css("display", "none");
    $('#oLoadBar.ui-slider, #oLoadBar ui-slider-handler').off();
    //$('#oScroll.ui-slider, #oScroll ui-slider-handler').off();
    // Create controls:
    var oControls = '<div id="oControls" style="width:' + this.imageWidth + 'px;height:30px;bottom:0px;text-align:center;padding-top:10px;z-index:10;" />';
    $('#videoplayer').append(oControls);
    
    // Step beginning button:
    var oBtnStepBeginning = '<img id="stepbeginningbutton" src="./annotationTools/video/icons/video_beginning.png" style="border-width:px;padding:10px;" onclick="oVP.StepBeginning();" title="Go to beginning" />';
    $('#oControls').append(oBtnStepBeginning);
    
    // Step backward button:
    var oBtnStepBackward = '<img id="stepbackwardbutton" src="./annotationTools/video/icons/video_stepback.png" style="border-width:px;padding:10px;" onclick="oVP.StepBackward();" title="Step backward" />';
    $('#oControls').append(oBtnStepBackward);
    
    // Pause button:
    var oBtnPause = '<img id="playpausebutton" src="./annotationTools/video/icons/video_pause.png" style="border-width:px;padding:10px;" onclick="oVP.Pause(true);" title="Pause" />';
    $('#oControls').append(oBtnPause);
    
    // Step forward button:
    var oBtnStepForward = '<img id="stepforwardbutton" src="./annotationTools/video/icons/video_stepforward.png" style="border-width:px;padding:10px;" onclick="oVP.StepForward();" title="Step forward" />';
    $('#oControls').append(oBtnStepForward);

    // Step end button:
    var oBtnStepEnd = '<img id="stependbutton" src="./annotationTools/video/icons/video_end.png" style="border-width:px;padding:10px;" onclick="oVP.StepEnd();" title="Go to end" />';
    $('#oControls').append(oBtnStepEnd);
    
    var frameNumber = '<div>Frame number: <p style="display: inline-block" id="framenum">0</p></div>';
    $('#oControls').append(frameNumber);

    var goToFrameForm = '<form action="">Go to frame:<input type="text" id="frameinput" placeholder="Frame Number"></input><input type="button" onclick="oVP.GoToFrameButtonClicked()" value="Submit"></input></form>';
     $('#oControls').append(goToFrameForm);         

  };

  this.poly_id = Array();
  this.X = Array();
  this.Y = Array();
  this.display_polygon = Array();
  /** This function is called when the user clicks the Go to frame button. It displays the frame indicated by the text field */
  this.GoToFrameButtonClicked = function(){
    var framevalue = Math.max(Math.min(this.getnumFrames()-1,$('#frameinput').val()),0);
    this.GoToFrame(parseInt(framevalue));
  }

  /** This function generates the html <img> elements that contain the loaded frames */
  this.GenerateFrames = function() {
    var shift = oVideoData.firstframe;
    for(var i = 0; i < oVideoData.data.video.length; i++) {
      var oImage = '<img src="' + oVideoData.data.video[i] + '" id="im" style="display:block;position:absolute;padding:0;border-width:0;width:' + this.imageWidth + 'px;height:' + this.imageHeight + 'px;z-index:-3;" />';
      aFrameImages[i+shift] = oImage;
    }

  }
  /** This function displays the frame at position i, as well as the annotations corresponding to such frame. 
   * If such frame is not available, it pauses the player, loads a chunk of frames starting from i and replays.
   * @param {int} i - the index of the frame to be displayed
  */
  this.DisplayFrame = function(i) {
    // Show frame:
    $('#framenum').html(i);

    if (aFrameImages[i] == null){
      this.Pause();
      ovP.loadChunk(i, 2, false, false);
      //return;
    }
    if($('#im').length) {
      $('#im').replaceWith(aFrameImages[i]);
    }
    else {
      $('#oCanvas').append(aFrameImages[i]);
    }
    $('#myCanvas_bg').empty();

    var attr = 'fill="none" stroke="' + HashObjectColor(name) + '" stroke-width="4"';
    var scale = 1;
    
    // Plot polygons:
    var xml = LM_xml
    var N = $(xml).children("annotation").children("object").length;
    for(var it = 0; it < N; it++) {
      var obj = $(xml).children("annotation").children("object").eq(it);
        // Get object name:

        // Get points:
        var anno_id = obj.children("id").text();
          var X = Array();
          var Y = Array();
          var framestamps = (obj.children("polygon").children("t").text()).split(',')
          for(var ti=0; ti<framestamps.length; ti++) { framestamps[ti] = parseInt(framestamps[ti], 10); } 
          var objectind = framestamps.indexOf(i);
          
          if (objectind >= 0){
           var pointsx = (obj.children("polygon").children("x").text()).split(';')[objectind]
           X = pointsx.split(',')
           for(var ti=0; ti<X.length; ti++) { X[ti] = parseInt(X[ti], 10); } 
           var pointsy = (obj.children("polygon").children("y").text()).split(';')[objectind]
           Y = pointsy.split(',')
           for(var ti=0; ti<Y.length; ti++) { Y[ti] = parseInt(Y[ti], 10); } 
            var obj_name = "foo";
           if (obj.children("name")) obj_name = obj.children("name").text();
           if (select_anno == null || (select_anno && select_anno.anno_id != anno_id)){
              polid = DrawPolygon('myCanvas_bg',X,Y,obj_name,attr,scale);
              $('#'+polid).attr('onmousedown','StartEditVideoEvent("'+polid+'",' + it + ',evt); return false;');
              //$('#'+polid).attr('onmousemove','main_handler.CanvasMouseMove(evt,'+ it +'); return false;');
              $('#'+polid).attr('oncontextmenu','return false');
              $('#'+polid).css('cursor','pointer');
            }
            else {

              $('#'+select_anno.polygon_id).parent().remove();
              $('#'+select_anno.polygon_id).remove();
              adjust_event.x  = X;
              adjust_event.y = Y;
              adjust_event.polygon_id = adjust_event.DrawPolygon(adjust_event.dom_attach,X,Y,obj_name,scale);
              select_anno.polygon_id = adjust_event.polygon_id;
              
              adjust_event.RemoveControlPoints();
              adjust_event.RemoveCenterOfMass();

              adjust_event.ShowControlPoints();
              adjust_event.ShowCenterOfMass();
            }
          }
        }
  }

  /** This function sets the player to frame 'frame'. 
   * @param {int} frame - the index of the frame of interest
  */  
  this.GoToFrame = function(frame){
    iFrameCtr = frame;
    this.DisplayFrame(iFrameCtr);
    this.UpdateScrollbar(iFrameCtr/oVideoData.frames);
  }


  
  this.ShowFirstFrame = function() {
    // $('#oCanvas').append(aFrameImages[0]);
    this.DisplayFrame(0);
    this.UpdateScrollbar(0);
  }
  
  /** This function updates the position of the scroll bar. 
    * @param {float} pos - float from 0 to 1 indicating the position of the scroll bar 
  */
  this.UpdateScrollbar = function(pos) {
    $('#oScroll').slider('value',  pos*this.imageWidth);
    //$('#scrollbutton').css('left',pos*this.imageWidth);
    //$('#oProgress').css('width',pos*this.imageWidth);
  }

  /** This function updates the position of the load bar. 
    * @param {float} pos - float from 0 to 1 indicating the position of the load bar 
  */
  this.UpdateLoadbar = function(pos){
    //$('#oLoadBar').css('width',pos*this.imageWidth);
    $('#oLoadBar').slider('value',  pos*this.imageWidth);
  }

  /** This function sets the player to play. It can be called when a chunk of needed frames is loaded or when the user hits the play button. 
    * @param {bool} buttonClicked - boolean indicating whether the user clicked the play button or the video was played because the chunks got loaded
  */
  this.Play = function(buttonClicked) {
    //if (active_canvas != REST_CANVAS) return;
    if (buttonClicked) uPaused = false;
    else if (uPaused == true) return;
    if (aFrameImages[iFrameCtr] == null) return;
    if (bPlaying) {
      if (bPaused) bPaused = false;
      
      // Replace with pause button:
      $('#playpausebutton').attr('src','annotationTools/video/icons/video_pause.png');
      $('#playpausebutton').attr('title','Pause');
      $('#playpausebutton').attr('onclick','oVP.Pause(true);');

      if(iFrameCtr == (oVideoData.frames-1)) iFrameCtr = 0;
      
      return;
    }
    bPlaying = true;
    this.NextFrame();
  }
  
  /** Displays the next frame in a video file according to the video rate. 
  */
  this.NextFrame = function() {
    if (!bPlaying) return;
    
    var iFrameRate = Math.round(1000 / oVideoData.rate);
    var iNow = new Date().getTime();
    var iLag = 0;
    if (!bPaused) {
      iFrameCtr++;
      if (iLastTime > -1) {
	var iDeltaTime = iNow - iLastTime;
	iLag = iDeltaTime - iFrameRate;
	while (iLag > iFrameRate) {
	  iFrameCtr++;
	  iLag -= iFrameRate;
	}
      }
      if (iLag < 0) iLag = 0;
      if (iFrameCtr >= oVideoData.frames) {
	iFrameCtr = oVideoData.frames-1;
	this.Pause();
      }
      
      this.DisplayFrame(iFrameCtr);
      this.UpdateScrollbar(iFrameCtr/oVideoData.frames);
    }
    iLastTime = iNow;
    setTimeout(function() {
	oVP.NextFrame();
      }, iFrameRate - iLag);
  }
  
  // Pause playback:
  this.Pause = function(buttonClicked) {
    if (buttonClicked) uPaused = true;
    bPaused = true;
    
    // Replace with play button:
    $('#playpausebutton').attr('src','./annotationTools/video/icons/video_play.png');
    $('#playpausebutton').attr('title','Play');
    $('#playpausebutton').attr('onclick','oVP.Play(true);');
  }
  
  /** This function forces to go to the next frame, regardless of frame rate. 
  */
  this.StepForward = function() {
    if(!bPaused) this.Pause();
    else {
      iFrameCtr++;
      if (iFrameCtr >= oVideoData.frames) {
	iFrameCtr = oVideoData.frames-1;
      }
      
      // Render next frame:
      this.DisplayFrame(iFrameCtr);
      this.UpdateScrollbar(iFrameCtr/oVideoData.frames);
    }
  }
  
  /** This function goes to the previous frame. 
  */
  this.StepBackward = function() {
    if(!bPaused) this.Pause();
    else {
      iFrameCtr--;
      if (iFrameCtr <= 0) {
	iFrameCtr = 1;
      }
      
      // Render next frame:s
      this.DisplayFrame(iFrameCtr);
      this.UpdateScrollbar(iFrameCtr/oVideoData.frames);
    }
  }

  /** This function goes to the start of the video. 
  */
  this.StepBeginning = function() {
    this.Pause();
    iFrameCtr = 1;
    
    // Render next frame:
    this.DisplayFrame(iFrameCtr);
    this.UpdateScrollbar(iFrameCtr/oVideoData.frames);
  }

  /** This function goes to the end of the video. 
  */
  this.StepEnd = function() {
    this.Pause();
    iFrameCtr = oVideoData.frames-1;
    
    // Render next frame:
    this.DisplayFrame(iFrameCtr);
    this.UpdateScrollbar(iFrameCtr/oVideoData.frames);
  }
  /** This function looks for future frames that haven't been loaded yet and loads them into the player. 
    * @param {int} frame - the frame index from which to start seeking
  */
  this.seekChunkToDownload = function (frame){
    while (aFrameImages[frame] != null) frame++;
    if (frame < oVideoData.frames) this.loadChunk(frame, 1, false, true);
  }
  /** This function loads a set of frames to the player. 
    * @param {int} frame - the first frame to load
    * @param {int} duration - the duration of the chunk to be loaded
    * @param {bool} first_time - boolean indicating whether the function is called for the first time
  */
  this.loadChunk = function(frame, duration, first_time, isbackground){
    ovP = this;
    $.ajax({
           async: true,
           type: "POST", 
           url: "./annotationTools/php/encode.php",
           data: {width: "640", height: "480", rate:"15", input: fname_folder,frame: frame.toString(), duration: duration},
           success: function(response){
            last_frame = Math.min(frame + duration*15, ovP.getnumFrames());
            ovP.loadFile(frame, first_time, isbackground, response)
            if (ovP.getnumFrames() != 0 && ovP.getcurrentFrame() <= last_frame) ovP.UpdateLoadbar(last_frame/ovP.getnumFrames());

          }
    });
  }
}