Source: visualization/MultiStreamViewer.js

/**
 * @author Russell Toris - rctoris@wpi.edu
 */

/**
 * A MultiStreamViewer can be used to stream multiple MJPEG topics into a canvas.
 *
 * Emits the following events:
 *   * 'warning' - emitted if the given topic is unavailable
 *   * 'change' - emitted with the topic name that the canvas was changed to
 *
 * @constructor
 * @param options - possible keys include:
 *   * divID - the ID of the HTML div to place the canvas in
 *   * width - the width of the canvas
 *   * height - the height of the canvas
 *   * host - the hostname of the MJPEG server
 *   * port (optional) - the port to connect to
 *   * quality (optional) - the quality of the stream (from 1-100)
 *   * topics - an array of topics to stream
 *   * labels (optional) - an array of labels associated with each topic
 *   * defaultStream (optional) - the index of the default stream to use
 */
MJPEGCANVAS.MultiStreamViewer = function(options) {
  var that = this;
  options = options || {};
  var divID = options.divID;
  var width = options.width;
  var height = options.height;
  var host = options.host;
  var port = options.port || 8080;
  var quality = options.quality;
  var topics = options.topics;
  var labels = options.labels;
  var defaultStream = options.defaultStream || 0;
  var currentTopic = topics[defaultStream];

  // create an overlay canvas for the button
  var canvas = document.createElement('canvas');
  canvas.width = width;
  canvas.height = height;
  var context = canvas.getContext('2d');

  // menu div
  var menu = document.createElement('div');
  menu.style.display = 'none';
  document.getElementsByTagName('body')[0].appendChild(menu);

  // button for the error
  var buttonHeight = height / 8;
  var buttonPadding = 10;
  var button = new MJPEGCANVAS.Button({
    text : 'Edit',
    height : buttonHeight
  });
  var buttonWidth = button.width;

  // use a regular viewer internally
  var viewer = new MJPEGCANVAS.Viewer({
    divID : divID,
    width : width,
    height : height,
    host : host,
    port : port,
    quality : quality,
    topic : currentTopic,
    overlay : canvas
  });

  // catch the events
  viewer.on('warning', function(warning) {
    that.emit('warning', warning);
  });
  viewer.on('change', function(topic) {
    currentTopic = topic;
    that.emit('change', topic);
  });

  /**
   * Clear the button from the overlay canvas.
   */
  function clearButton() {
    if (buttonTimer) {
      window.clearInterval(buttonTimer);
      // clear the button
      canvas.width = canvas.width;
      hasButton = false;
    }
  }

  /**
   * Fades the stream by putting an overlay on it.
   */
  function fadeImage() {
    canvas.width = canvas.width;
    // create the white box
    context.globalAlpha = 0.44;
    context.beginPath();
    context.rect(0, 0, width, height);
    context.fillStyle = '#fefefe';
    context.fill();
    context.globalAlpha = 1;
  }

  // add the event listener
  var buttonTimer = null;
  var menuOpen = false;
  var hasButton = false;
  viewer.canvas.addEventListener('mousemove', function(e) {
    clearButton();

    if (!menuOpen) {
      hasButton = true;
      // add the button
      button.redraw();
      context.drawImage(button.canvas, buttonPadding, height - (buttonHeight + buttonPadding));

      // display the button for 3 seconds
      buttonTimer = setInterval(function() {
        // clear the overlay canvas
        clearButton();
      }, 3000);
    } else {
      fadeImage();
    }
  }, false);

  // add the click listener
  viewer.canvas.addEventListener('click', function(e) {
    // check if the button is there
    if (hasButton) {
      // determine the click position
      var offsetLeft = 0;
      var offsetTop = 0;
      var element = viewer.canvas;
      while (element && !isNaN(element.offsetLeft) && !isNaN(element.offsetTop)) {
        offsetLeft += element.offsetLeft - element.scrollLeft;
        offsetTop += element.offsetTop - element.scrollTop;
        element = element.offsetParent;
      }

      var x = e.pageX - offsetLeft;
      var y = e.pageY - offsetTop;

      // check if we are in the 'edit' button
      if (x < (buttonWidth + buttonPadding) && x > buttonPadding
          && y > (height - (buttonHeight + buttonPadding)) && y < height - buttonPadding) {
        menuOpen = true;
        clearButton();

        // create the menu
        var heading = document.createElement('span');
        heading.innerHTML = '<h2>Camera Streams</h2><hr /><br />';
        menu.appendChild(heading);
        var form = document.createElement('form');
        var streamLabel = document.createElement('label');
        streamLabel.setAttribute('for', 'stream');
        streamLabel.innerHTML = 'Stream: ';
        form.appendChild(streamLabel);
        var streamMenu = document.createElement('select');
        streamMenu.setAttribute('name', 'stream');
        // add each option
        for ( var i = 0; i < topics.length; i++) {
          var option = document.createElement('option');
          // check if this is the selected option
          if (topics[i] === currentTopic) {
            option.setAttribute('selected', 'selected');
          }
          option.setAttribute('value', topics[i]);
          // check for a label
          if (labels) {
            option.innerHTML = labels[i];
          } else {
            option.innerHTML += topics[i];
          }
          streamMenu.appendChild(option);
        }
        form.appendChild(streamMenu);
        menu.appendChild(form);
        menu.appendChild(document.createElement('br'));
        var close = document.createElement('button');
        close.innerHTML = 'Close';
        menu.appendChild(close);

        // display the menu
        menu.style.position = 'absolute';
        menu.style.top = offsetTop + 'px';
        menu.style.left = offsetLeft + 'px';
        menu.style.width = width + 'px';
        menu.style.display = 'block';

        streamMenu.addEventListener('click', function() {
          var topic = streamMenu.options[streamMenu.selectedIndex].value;
          // make sure it is a new stream
          if (topic !== currentTopic) {
            viewer.changeStream(topic);
          }
        }, false);

        // handle the interactions
        close.addEventListener('click', function(e) {
          // close the menu
          menu.innerHTML = '';
          menu.style.display = 'none';
          menuOpen = false;
          canvas.width = canvas.width;
        }, false);
      }
    }
  }, false);
};
MJPEGCANVAS.MultiStreamViewer.prototype.__proto__ = EventEmitter2.prototype;