Package com.kurento.kmf.test.client

Source Code of com.kurento.kmf.test.client.BrowserClient$Builder

/*
* (C) Copyright 2014 Kurento (http://kurento.org/)
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the GNU Lesser General Public License
* (LGPL) version 2.1 which accompanies this distribution, and is available at
* http://www.gnu.org/licenses/lgpl-2.1.html
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
*/
package com.kurento.kmf.test.client;

import static com.kurento.kmf.common.PropertiesManager.getProperty;

import java.awt.Color;
import java.io.Closeable;
import java.io.File;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;

import org.apache.commons.codec.binary.Base64;
import org.apache.commons.lang3.SystemUtils;
import org.openqa.selenium.By;
import org.openqa.selenium.JavascriptExecutor;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.chrome.ChromeOptions;
import org.openqa.selenium.firefox.FirefoxDriver;
import org.openqa.selenium.firefox.FirefoxProfile;
import org.openqa.selenium.remote.DesiredCapabilities;
import org.openqa.selenium.remote.RemoteWebDriver;
import org.openqa.selenium.support.ui.ExpectedCondition;
import org.openqa.selenium.support.ui.WebDriverWait;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.kurento.kmf.media.WebRtcEndpoint;
import com.kurento.kmf.media.factory.KmfMediaApiProperties;
import com.kurento.kmf.test.base.GridBrowserMediaApiTest;
import com.kurento.kmf.test.services.AudioChannel;
import com.kurento.kmf.test.services.KurentoServicesTestHelper;
import com.kurento.kmf.test.services.Node;
import com.kurento.kmf.test.services.Recorder;

/**
* Class that models the video tag (HTML5) in a web browser; it uses Selenium to
* launch the real browser.
*
* @author Micael Gallego (micael.gallego@gmail.com)
* @author Boni Garcia (bgarcia@gsyc.es)
* @since 4.2.3
* @see <a href="http://www.seleniumhq.org/">Selenium</a>
*/
public class BrowserClient implements Closeable {

  public Logger log = LoggerFactory.getLogger(BrowserClient.class);
  private List<Thread> callbackThreads = new ArrayList<>();
  private Map<String, CountDownLatch> countDownLatchEvents;

  private WebDriver driver;
  private String videoUrl;
  private int timeout; // seconds
  private double maxDistance;

  private String video;
  private String audio;
  private int serverPort;
  private Client client;
  private Browser browser;
  private boolean usePhysicalCam;
  private Node remoteNode;
  private int recordAudio;
  private int audioSampleRate;
  private AudioChannel audioChannel;

  private BrowserClient(Builder builder) {
    this.video = builder.video;
    this.audio = builder.audio;
    this.serverPort = builder.serverPort;
    this.client = builder.client;
    this.browser = builder.browser;
    this.usePhysicalCam = builder.usePhysicalCam;
    this.remoteNode = builder.remoteNode;
    this.recordAudio = builder.recordAudio;
    this.audioSampleRate = builder.audioSampleRate;
    this.audioChannel = builder.audioChannel;

    countDownLatchEvents = new HashMap<>();
    timeout = 60; // default (60 seconds)
    maxDistance = 60.0; // default distance (for color comparison)

    String hostAddress = KmfMediaApiProperties.getThriftKmfAddress()
        .getHost();

    // Setup Selenium
    initDriver(hostAddress);

    // Launch Browser
    driver.manage().timeouts();
    driver.get("http://" + hostAddress + ":" + serverPort
        + client.toString());
  }

  private void initDriver(String hostAddress) {
    Class<? extends WebDriver> driverClass = browser.getDriverClass();
    int hubPort = getProperty("test.hub.port",
        GridBrowserMediaApiTest.DEFAULT_HUB_PORT);

    try {
      if (driverClass.equals(FirefoxDriver.class)) {
        FirefoxProfile profile = new FirefoxProfile();
        // This flag avoids granting the access to the camera
        profile.setPreference("media.navigator.permission.disabled",
            true);
        if (remoteNode != null) {
          DesiredCapabilities capabilities = new DesiredCapabilities();
          capabilities.setCapability(FirefoxDriver.PROFILE, profile);
          capabilities.setBrowserName(DesiredCapabilities.firefox()
              .getBrowserName());

          driver = new RemoteWebDriver(new URL("http://"
              + hostAddress + ":" + hubPort + "/wd/hub"),
              capabilities);
        } else {
          driver = new FirefoxDriver(profile);
        }

        if (!usePhysicalCam && video != null) {
          launchFakeCam();
        }

      } else if (driverClass.equals(ChromeDriver.class)) {
        String chromedriver = null;
        if (SystemUtils.IS_OS_MAC || SystemUtils.IS_OS_LINUX) {
          chromedriver = "chromedriver";
        } else if (SystemUtils.IS_OS_WINDOWS) {
          chromedriver = "chromedriver.exe";
        }
        System.setProperty("webdriver.chrome.driver", new File(
            "target/webdriver/" + chromedriver).getAbsolutePath());
        ChromeOptions options = new ChromeOptions();

        // This flag avoids grant the camera
        options.addArguments("--use-fake-ui-for-media-stream");

        // This flag avoids warning in chrome. See:
        // https://code.google.com/p/chromedriver/issues/detail?id=799
        options.addArguments("--test-type");

        if (!usePhysicalCam) {
          // This flag makes using a synthetic video (green with
          // spinner) in webrtc. Or it is needed to combine with
          // use-file-for-fake-video-capture to use a file faking the
          // cam
          options.addArguments("--use-fake-device-for-media-stream");

          if (video != null) {
            options.addArguments("--use-file-for-fake-video-capture="
                + video);

            // Alternative: lauch fake cam also in Chrome
            // launchFakeCam();
          }
        }

        if (remoteNode != null) {
          DesiredCapabilities capabilities = new DesiredCapabilities();
          capabilities.setCapability(ChromeOptions.CAPABILITY,
              options);
          capabilities.setBrowserName(DesiredCapabilities.chrome()
              .getBrowserName());
          driver = new RemoteWebDriver(new URL("http://"
              + hostAddress + ":" + hubPort + "/wd/hub"),
              capabilities);

        } else {
          driver = new ChromeDriver(options);
        }

      }
      driver.manage().timeouts()
          .setScriptTimeout(timeout, TimeUnit.SECONDS);

    } catch (MalformedURLException e) {
      log.error("MalformedURLException in BrowserClient.initDriver", e);
    }
  }

  private void launchFakeCam() {
    FakeCam.getSingleton().launchCam(video);
  }

  public void setURL(String videoUrl) {
    this.videoUrl = videoUrl;
  }

  public void resetEvents() {
    driver.findElement(By.id("status")).clear();
  }

  public void setColorCoordinates(int x, int y) {
    driver.findElement(By.id("x")).clear();
    driver.findElement(By.id("y")).clear();
    driver.findElement(By.id("x")).sendKeys(String.valueOf(x));
    driver.findElement(By.id("y")).sendKeys(String.valueOf(y));
  }

  public void subscribeEvents(String... eventType) {
    for (final String e : eventType) {
      CountDownLatch latch = new CountDownLatch(1);
      countDownLatchEvents.put(e, latch);
      this.addEventListener(e, new EventListener() {
        @Override
        public void onEvent(String event) {
          log.info("Event: {}", event);
          countDownLatchEvents.get(e).countDown();
        }
      });
    }
  }

  public boolean waitForEvent(final String eventType)
      throws InterruptedException {
    if (!countDownLatchEvents.containsKey(eventType)) {
      // We cannot wait for an event without previous subscription
      return false;
    }

    boolean result = countDownLatchEvents.get(eventType).await(timeout,
        TimeUnit.SECONDS);

    // Record local audio when playing event reaches the browser
    if (eventType.equalsIgnoreCase("playing") && recordAudio > 0) {
      if (remoteNode != null) {
        Recorder.recordRemote(remoteNode, recordAudio, audioSampleRate,
            audioChannel);
      } else {
        Recorder.record(recordAudio, audioSampleRate, audioChannel);
      }
    }

    countDownLatchEvents.remove(eventType);
    return result;
  }

  public void addEventListener(final String eventType,
      final EventListener eventListener) {
    Thread t = new Thread() {
      public void run() {
        ((JavascriptExecutor) driver)
            .executeScript("video.addEventListener('" + eventType
                + "', videoEvent, false);");
        (new WebDriverWait(driver, timeout))
            .until(new ExpectedCondition<Boolean>() {
              public Boolean apply(WebDriver d) {
                return d.findElement(By.id("status"))
                    .getAttribute("value")
                    .equalsIgnoreCase(eventType);
              }
            });
        eventListener.onEvent(eventType);
      }
    };
    callbackThreads.add(t);
    t.setDaemon(true);
    t.start();
  }

  public void start() {
    if (driver instanceof JavascriptExecutor) {
      ((JavascriptExecutor) driver).executeScript("play('" + videoUrl
          + "', false);");
    }
  }

  public void showSpinners() {
    if (driver instanceof JavascriptExecutor) {
      ((JavascriptExecutor) driver)
          .executeScript("showSpinner('local');");
      ((JavascriptExecutor) driver)
          .executeScript("showSpinner('video');");
    }
  }

  public void stop() {
    if (driver instanceof JavascriptExecutor) {
      ((JavascriptExecutor) driver).executeScript("terminate();");
    }
  }

  public void startRcvOnly() {
    if (driver instanceof JavascriptExecutor) {
      ((JavascriptExecutor) driver).executeScript("play('" + videoUrl
          + "', true);");
    }
  }

  @SuppressWarnings("deprecation")
  public void close() {
    for (Thread t : callbackThreads) {
      t.stop();
    }
    driver.quit();
    driver = null;

  }

  public int getTimeout() {
    return timeout;
  }

  public void setTimeout(int timeout) {
    this.timeout = timeout;
  }

  public double getCurrentTime() {
    log.debug("getCurrentTime() called");
    double currentTime = Double.parseDouble(driver.findElement(
        By.id("currentTime")).getAttribute("value"));
    log.debug("getCurrentTime() result: {}", currentTime);
    return currentTime;
  }

  public boolean color(Color expectedColor, final double seconds, int x, int y) {
    // Wait to be in the right time
    (new WebDriverWait(driver, timeout))
        .until(new ExpectedCondition<Boolean>() {
          public Boolean apply(WebDriver d) {
            double time = Double.parseDouble(d.findElement(
                By.id("currentTime")).getAttribute("value"));
            return time > seconds;
          }
        });

    setColorCoordinates(x, y);
    // Guard time to wait JavaScript function to detect the color (otherwise
    // race conditions could appear)
    try {
      Thread.sleep(200);
    } catch (InterruptedException e) {
      log.trace("InterruptedException in guard condition ({})",
          e.getMessage());
    }
    return colorSimilarTo(expectedColor);
  }

  public boolean colorSimilarTo(Color expectedColor) {
    String[] realColor = driver.findElement(By.id("color"))
        .getAttribute("value").split(",");
    int red = Integer.parseInt(realColor[0]);
    int green = Integer.parseInt(realColor[1]);
    int blue = Integer.parseInt(realColor[2]);

    double distance = Math.sqrt((red - expectedColor.getRed())
        * (red - expectedColor.getRed())
        + (green - expectedColor.getGreen())
        * (green - expectedColor.getGreen())
        + (blue - expectedColor.getBlue())
        * (blue - expectedColor.getBlue()));

    log.info("Color comparision: real {}, expected {}, distance {}",
        realColor, expectedColor, distance);

    return distance <= getMaxDistance();
  }

  public double getMaxDistance() {
    return maxDistance;
  }

  public void setMaxDistance(double maxDistance) {
    this.maxDistance = maxDistance;
  }

  public void connectToWebRtcEndpoint(WebRtcEndpoint webRtcEndpoint,
      WebRtcChannel channel) {
    if (driver instanceof JavascriptExecutor) {
      String getSdpOffer = "getSdpOffer(" + channel.getAudio() + ","
          + channel.getVideo();
      if (audio != null) {
        getSdpOffer += ",'" + audio + "');";
      } else {
        getSdpOffer += ");";
      }

      ((JavascriptExecutor) driver).executeScript(getSdpOffer);

      // Wait to valid sdpOffer
      (new WebDriverWait(driver, timeout))
          .until(new ExpectedCondition<Boolean>() {
            public Boolean apply(WebDriver d) {
              return ((JavascriptExecutor) driver)
                  .executeScript("return sdpOffer;") != null;
            }
          });
      String sdpOffer = (String) ((JavascriptExecutor) driver)
          .executeScript("return sdpOffer;");
      String sdpAnswer = webRtcEndpoint.processOffer(sdpOffer);

      // Encode to base64 to avoid parsing error in Javascript due to
      // break lines
      sdpAnswer = new String(Base64.encodeBase64(sdpAnswer.getBytes()));

      ((JavascriptExecutor) driver).executeScript("setSdpAnswer('"
          + sdpAnswer + "');");
    }
  }

  public static class Builder {
    private String video;
    private String audio;
    private int serverPort;
    private Client client;
    private Browser browser;
    private boolean usePhysicalCam;
    private Node remoteNode;
    private int recordAudio; // seconds
    private int audioSampleRate; // samples per seconds (e.g. 8000, 16000)
    private AudioChannel audioChannel; // stereo, mono

    public Builder() {
      this.serverPort = KurentoServicesTestHelper.getAppHttpPort();

      // By default physical camera will not be used; instead synthetic
      // videos will be used for testing
      this.usePhysicalCam = false;

      // By default is not a remote test
      this.remoteNode = null;

      // By default, not recording audio (0 seconds)
      this.recordAudio = 0;
    }

    public Builder(int serverPort) {
      this.serverPort = serverPort;
    }

    public Builder video(String video) {
      this.video = video;
      return this;
    }

    public Builder client(Client client) {
      this.client = client;
      return this;
    }

    public Builder browser(Browser browser) {
      this.browser = browser;
      return this;
    }

    public Builder usePhysicalCam() {
      this.usePhysicalCam = true;
      return this;
    }

    public Builder remoteNode(Node remoteNode) {
      this.remoteNode = remoteNode;
      return this;
    }

    public Builder audio(String audio, int recordAudio,
        int audioSampleRate, AudioChannel audioChannel) {
      this.audio = audio;
      this.recordAudio = recordAudio;
      this.audioSampleRate = audioSampleRate;
      this.audioChannel = audioChannel;
      return this;
    }

    public BrowserClient build() {
      return new BrowserClient(this);
    }
  }
}
TOP

Related Classes of com.kurento.kmf.test.client.BrowserClient$Builder

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.