<commit_before>app.photo = {
  // Dimensions of photos
  width: 300,
  height: 225,
  quality: 0.7,

  // The maximum size photo parts can be stored in
  partSize: 8192,

  // Provides a helper for building URLs from streams
  buildURL: window.webkitURL || window.URL,

  init: function() {
    // Kick off the initial refresh
    this.refresh();
  },

  /**
   * Clears out old photo keys
   */
  cleanup: function() {
    var keys = app.data.keys();
    for (var i = 0; i < keys.length; i++) {
      var key = keys[i];
      var participantId = app.participants.idFromKey(key);

      if (participantId == app.participant.id && key.indexOf('/photos') > 0) {
        app.data.clear(key);
      }
    }
  },

  /**
   * The default photo url to use
   */
  defaultUrl: function() {
    var canvas = $('<canvas />').attr({width: this.width, height: this.height})[0];
    var context = canvas.getContext('2d');
    this.filters.silhouette(canvas);
    return canvas.toDataURL('image/jpeg', this.quality);
  },

  /**
   * Whether photos are supported in this browser
   */
  isSupported: function() {
    return navigator.getMedia != undefined;
  },

  /**
   * Waits until the photo is able to be refresh -- at which point the
   * given callback is called.
   */
  waitUntilCanRefresh: function(callback) {
    clearInterval(this.waiter);

    this.waiter = setInterval($.proxy(function() {
      if (this.canRefresh()) {
        clearInterval(this.waiter);
        callback();
      }
    }, this), 1000);
  },

  /**
   * Gets the interval, in ms, to refresh photos
   */
  refreshInterval: function() {
    return parseFloat(app.settings.get('photoInterval')) * 60 * 1000;
  },

  /**
   * Refreshes the image representing the local participant
   */
  refresh: function() {
    if (app.settings.get('photoEnabled') == 'true') {
      if (this.canRefresh()) {
        var timeRemaining = this.lastRefresh ? this.lastRefresh + this.refreshInterval() - $.now() : 0;

        if (timeRemaining <= 0) {
          this.lastRefresh = $.now();
          timeRemaining = this.refreshInterval();

          // Constrain the video source based on existing settings
          var constraints;
          var photoSource = app.settings.get('photoSource');
          if (photoSource) {
            constraints = {optional: [{sourceId: photoSource}]}
          } else {
            constraints = true;
          }

          // Show a notification to the user to make sure they see to authorize
          // access to media
          if (app.settings.get('initialPhoto') == 'true') {
            app.settings.set('initialPhoto', 'false');
            app.notification.show("Please select Allow above to enable presence detection");
          }

          navigator.getMedia(
            {video: constraints},
            $.proxy(this.refreshWithStream, this),
            $.proxy(this.onError, this, null)
          );
        }

        // Restart timer
        clearTimeout(this.refresher);
        this.refresher = setTimeout($.proxy(this.refresh, this), timeRemaining);
      } else {
        this.waitUntilCanRefresh($.proxy(this.refresh, this));
      }
    }
  },

  /**
   * Determines whether the photo is capable of being refreshed
   */
  canRefresh: function() {
    return gapi.hangout.av.getCameraMute() == true &&
      gapi.hangout.av.getMicrophoneMute() == true &&
      !app.participant.isHanging() &&
      app.participants.all().length;
  },

  /**
   * Callback when the system is ready to refresh
   */
  refreshWithStream: function(stream) {
    var url = window.URL.createObjectURL(stream);
    var video = this.buildVideo(url);
    $(video).on('canplay', $.proxy(function() {
      this.captureImage(video, stream, $.proxy(this.update, this), $.proxy(this.onError, this, stream));
    }, this));
  },

  /**
   * Callback when an error is encountered updating the photo
   */
  onError: function(stream, error) {
    if (stream) {
      try {
        stream.stop();
      } catch(ex) {
        console.log('Error stopping stream: ', ex);
      }
    }

    app.notification.show('Unable to take a picture. Please check your webcam settings.');
    console.log('Error updating photo: ', error);
  },

  /**
   * Gets the available video sources and invokes the callback with those that
   * are found.
   */
  sources: function(callback) {
    if (MediaStreamTrack.getSources) {
      MediaStreamTrack.getSources(function(sources) {
        var videoSources = [];
        for (var i = 0; i < sources.length; i++) {
          var source = sources[i];

          if (source.kind == 'video') {
            videoSources.push(source);
          }
        }

        callback(videoSources);
      });
    } else {
      callback([]);
    }
  },

  /**
   * Loads a video feed for the given url, capturing the first image available
   */
  buildVideo: function(url) {
    var video = $('<video />').attr({width: this.width, height: this.height})[0];
    video.src = url;
    video.play();
    return video;
  },

  /**
   * Captures the image 
   */
  captureImage: function(video, stream, success, error) {
    try {
      // Draw the video onto our canvas
      var canvas = $('<canvas />').attr({width: this.width, height: this.height})[0];
      var context = canvas.getContext('2d');
      context.drawImage(video, 0, 0, this.width, this.height);

      // Save the image
      this.filter(canvas);
      var url = canvas.toDataURL('image/jpeg', this.quality);

      // Stop the stream
      video.pause();
      stream.stop();

      success(url);
    } catch(e) {
      if (e.name == 'NS_ERROR_NOT_AVAILABLE') {
        setTimeout($.proxy(this.captureImage, this, video, stream, success, error), 1000);
      } else {
        error(e);
      }
    }
  },

  /**
   * Runs image processing filters on the given canvas
   */
  filter: function(canvas) {
    this.filters.desaturate(canvas);

    switch(app.settings.get('photoPrivacy')) {
      case 'blur':
        this.filters.blur(canvas, 3);
        break;
      case 'pixelate':<commit_msg>Fix distorted images for cameras with certain aspect ratios<commit_after>app.photo = {
  // Dimensions of photos
  width: 300,
  height: 225,
  quality: 0.7,

  // The maximum size photo parts can be stored in
  partSize: 8192,

  // Provides a helper for building URLs from streams
  buildURL: window.webkitURL || window.URL,

  init: function() {
    // Kick off the initial refresh
    this.refresh();
  },

  /**
   * Clears out old photo keys
   */
  cleanup: function() {
    var keys = app.data.keys();
    for (var i = 0; i < keys.length; i++) {
      var key = keys[i];
      var participantId = app.participants.idFromKey(key);

      if (participantId == app.participant.id && key.indexOf('/photos') > 0) {
        app.data.clear(key);
      }
    }
  },

  /**
   * The default photo url to use
   */
  defaultUrl: function() {
    var canvas = $('<canvas />').attr({width: this.width, height: this.height})[0];
    var context = canvas.getContext('2d');
    this.filters.silhouette(canvas);
    return canvas.toDataURL('image/jpeg', this.quality);
  },

  /**
   * Whether photos are supported in this browser
   */
  isSupported: function() {
    return navigator.getMedia != undefined;
  },

  /**
   * Waits until the photo is able to be refresh -- at which point the
   * given callback is called.
   */
  waitUntilCanRefresh: function(callback) {
    clearInterval(this.waiter);

    this.waiter = setInterval($.proxy(function() {
      if (this.canRefresh()) {
        clearInterval(this.waiter);
        callback();
      }
    }, this), 1000);
  },

  /**
   * Gets the interval, in ms, to refresh photos
   */
  refreshInterval: function() {
    return parseFloat(app.settings.get('photoInterval')) * 60 * 1000;
  },

  /**
   * Refreshes the image representing the local participant
   */
  refresh: function() {
    if (app.settings.get('photoEnabled') == 'true') {
      if (this.canRefresh()) {
        var timeRemaining = this.lastRefresh ? this.lastRefresh + this.refreshInterval() - $.now() : 0;

        if (timeRemaining <= 0) {
          this.lastRefresh = $.now();
          timeRemaining = this.refreshInterval();

          // Constrain the video source based on existing settings
          var constraints;
          var photoSource = app.settings.get('photoSource');
          if (photoSource) {
            constraints = {optional: [{sourceId: photoSource}]}
          } else {
            constraints = true;
          }

          // Show a notification to the user to make sure they see to authorize
          // access to media
          if (app.settings.get('initialPhoto') == 'true') {
            app.settings.set('initialPhoto', 'false');
            app.notification.show("Please select Allow above to enable presence detection");
          }

          navigator.getMedia(
            {video: constraints},
            $.proxy(this.refreshWithStream, this),
            $.proxy(this.onError, this, null)
          );
        }

        // Restart timer
        clearTimeout(this.refresher);
        this.refresher = setTimeout($.proxy(this.refresh, this), timeRemaining);
      } else {
        this.waitUntilCanRefresh($.proxy(this.refresh, this));
      }
    }
  },

  /**
   * Determines whether the photo is capable of being refreshed
   */
  canRefresh: function() {
    return gapi.hangout.av.getCameraMute() == true &&
      gapi.hangout.av.getMicrophoneMute() == true &&
      !app.participant.isHanging() &&
      app.participants.all().length;
  },

  /**
   * Callback when the system is ready to refresh
   */
  refreshWithStream: function(stream) {
    var url = window.URL.createObjectURL(stream);
    var video = this.buildVideo(url);
    $(video).on('canplay', $.proxy(function() {
      this.captureImage(video, stream, $.proxy(this.update, this), $.proxy(this.onError, this, stream));
    }, this));
  },

  /**
   * Callback when an error is encountered updating the photo
   */
  onError: function(stream, error) {
    if (stream) {
      try {
        stream.stop();
      } catch(ex) {
        console.log('Error stopping stream: ', ex);
      }
    }

    app.notification.show('Unable to take a picture. Please check your webcam settings.');
    console.log('Error updating photo: ', error);
  },

  /**
   * Gets the available video sources and invokes the callback with those that
   * are found.
   */
  sources: function(callback) {
    if (MediaStreamTrack.getSources) {
      MediaStreamTrack.getSources(function(sources) {
        var videoSources = [];
        for (var i = 0; i < sources.length; i++) {
          var source = sources[i];

          if (source.kind == 'video') {
            videoSources.push(source);
          }
        }

        callback(videoSources);
      });
    } else {
      callback([]);
    }
  },

  /**
   * Loads a video feed for the given url, capturing the first image available
   */
  buildVideo: function(url) {
    var video = $('<video />').attr({width: this.width, height: this.height})[0];
    video.src = url;
    video.play();
    return video;
  },

  /**
   * Captures the image 
   */
  captureImage: function(video, stream, success, error) {
    try {
      // Calculate width / height of image based on video aspect ratio
      var videoRatio = video.videoWidth / video.videoHeight;
      var photoRatio = this.width / this.height;
      var width = this.width;
      var height = this.height;
      if (photoRatio < videoRatio) {
        height = parseInt(width / videoRatio);
      } else if (photoRatio > videoRatio) {
        width = parseInt(height * videoRatio);
      }

      // Draw the video onto our canvas
      var canvas = $('<canvas />').attr({width: this.width, height: this.height})[0];
      var context = canvas.getContext('2d');
      context.fillStyle = '#000000';
      context.fillRect(0, 0, canvas.width, canvas.height);
      context.drawImage(video, (canvas.width - width) / 2, (canvas.height - height) / 2, width, height);

      // Save the image
      this.filter(canvas);
      var url = canvas.toDataURL('image/jpeg', this.quality);

      // Stop the stream
      video.pause();
      stream.stop();

      success(url);
    } catch(e) {
      if (e.name == 'NS_ERROR_NOT_AVAILABLE') {
        setTimeout($.proxy(this.captureImage, this, video, stream, success, error), 1000);
      } else {
        error(e);
      }
    }
  },

  /**
   * Runs image processing filters on the given canvas
   */
  filter: function(canvas) {
    this.filters.desaturate(canvas);

    switch(app.settings.get('photoPrivacy')) {
      case 'blur':
        this.filters.blur(canvas, 3);
        break;
      case 'pixelate':
{'fraction_non_alphanumeric': 0.1386465456260316, 'fraction_numerical': 0.006759412088343944, 'mean_word_length': 2.3396325459317584, 'pattern_counts': {'":': 0, '<': 16, '<?xml version=': 0, '>': 12, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1312608', 'n_tokens_mistral': 3855, 'n_tokens_neox': 3698, 'n_words': 978}