All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.kurento.test.browser.WebRtcTestPage Maven / Gradle / Ivy

Go to download

This project contains test cases for testing Kurento Java Client and Kurento Media Server.

There is a newer version: 6.18.0
Show newest version
/*
 * (C) Copyright 2015 Kurento (http://kurento.org/)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */

package org.kurento.test.browser;

import java.awt.Color;
import java.io.IOException;
import java.util.ArrayList;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;

import org.apache.commons.codec.binary.Base64;
import org.kurento.client.EventListener;
import org.kurento.client.IceCandidate;
import org.kurento.client.IceCandidateFoundEvent;
import org.kurento.client.MediaStateChangedEvent;
import org.kurento.client.WebRtcEndpoint;
import org.kurento.commons.exception.KurentoException;
import org.kurento.jsonrpc.JsonUtils;
import org.kurento.test.base.KurentoTest;
import org.kurento.test.latency.VideoTagType;
import org.kurento.test.monitor.PeerConnectionStats;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriverException;

import com.google.gson.JsonObject;
import com.google.gson.JsonParser;

/**
 * Specific client for tests within kurento-test project. This logic is linked to client page logic
 * (e.g. webrtc.html).
 *
 * @author Boni Garcia ([email protected])
 * @since 5.1.0
 */
public class WebRtcTestPage extends WebPage {

  public interface WebRtcConfigurer {
    public void addIceCandidate(IceCandidate candidate);

    public String processOffer(String sdpOffer);
  }

  protected final String FAKE_IPV4 = "10.2.3.4";
  protected final String FAKE_IPV6 = "2000:2001:2002:2003:2004:2005:2006";

  protected static final String LOCAL_VIDEO = "local";
  protected static final String REMOTE_VIDEO = "video";

  public WebRtcTestPage() {
  }

  @Override
  public void setBrowser(Browser browserClient) {
    super.setBrowser(browserClient);

    // By default all tests are going to track color in both video tags
    checkColor(LOCAL_VIDEO, REMOTE_VIDEO);

    VideoTagType.setLocalId(LOCAL_VIDEO);
    VideoTagType.setRemoteId(REMOTE_VIDEO);
  }

  /*
   * setColorCoordinates
   */
  @Override
  public void setColorCoordinates(int x, int y) {
    browser.getWebDriver().findElement(By.id("x")).clear();
    browser.getWebDriver().findElement(By.id("y")).clear();
    browser.getWebDriver().findElement(By.id("x")).sendKeys(String.valueOf(x));
    browser.getWebDriver().findElement(By.id("y")).sendKeys(String.valueOf(y));
    super.setColorCoordinates(x, y);
  }

  /*
   * similarColor
   */
  public boolean similarColor(Color expectedColor) {
    return similarColor(REMOTE_VIDEO, expectedColor);

  }

  /*
   * similarColorAt
   */
  public boolean similarColorAt(Color expectedColor, int x, int y) {
    return similarColorAt(REMOTE_VIDEO, expectedColor, x, y);
  }

  /*
   * subscribeEvents
   */
  public void subscribeEvents(String eventType) {
    subscribeEventsToVideoTag(REMOTE_VIDEO, eventType);
  }

  /*
   * subscribeLocalEvents
   */
  public void subscribeLocalEvents(String eventType) {
    subscribeEventsToVideoTag(LOCAL_VIDEO, eventType);
  }

  /*
   * start
   */
  public void start(String videoUrl) {
    browser.executeScript("play('" + videoUrl + "', false);");
  }

  /*
   * stop
   */
  public void stopPlay() {
    browser.executeScript("terminate();");
  }

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

  /*
   * readConsole
   */
  public String readConsole() {
    return browser.getWebDriver().findElement(By.id("console")).getText();
  }

  public void sendDataByDataChannel(String message) {
    browser.executeScript("sendDataByChannel('" + message + "')");
  }

  public boolean checkAudioDetection() {
    boolean checkAudio = (boolean) browser.executeScript("return checkAudioDetection()");
    log.debug("Checking Audio: {}", checkAudio);
    return checkAudio;
  }

  public void activateAudioDetection() {
    browser.executeScript("activateAudioDetection()");
  }

  public void stopAudioDetection() {
    browser.executeScript("stopAudioDetection()");
  }

  public void initAudioDetection() {
    browser.executeScript("initAudioDetection()");
  }

  public int getPeerConnAudioPacketsRecv(PeerConnectionStats stats) {
    String val = (String) stats.getStats().get("audio_peerconnection_inbound_packetsReceived");
    return Integer.parseInt(val);
  }

  public long getPeerConnAudioInboundTimestamp(PeerConnectionStats stats) {
    Long val = (Long) stats.getStats().get("audio_peerconnection_inbound_timestamp");
    return val;
  }

  /*
   * compare
   */
  public boolean compare(double i, double j) {
    return Math.abs(j - i) <= browser.getThresholdTime();
  }

  protected void addIceCandidate(JsonObject candidate) {
    browser.executeScript("addIceCandidate('" + candidate + "');");
  }

  /*
   * Decide if one candidate has to be added or not according with two parameters
   */
  protected Boolean filterCandidate(String candidate, WebRtcIpvMode webRtcIpvMode,
      WebRtcCandidateType webRtcCandidateType) {

    Boolean filtered = true;
    Boolean hasCandidateIpv6 = false;
    if (candidate.split("candidate:")[1].contains(":")) {
      hasCandidateIpv6 = true;
    }

    switch (webRtcIpvMode) {
      case IPV4:
        if (!hasCandidateIpv6) {
          filtered = false;
        }
        break;
      case IPV6:
        if (hasCandidateIpv6) {
          filtered = false;
        }
        break;
      case BOTH:
      default:
        filtered = false;
        break;
    }
    return filtered;
  }

  protected String manglingCandidate(String candidate, WebRtcIpvMode webRtcIpvMode,
      WebRtcCandidateType webRtcCandidateType) {

    String internalAddress;
    String publicAddress;
    String internalAddresses[];
    String publicAddresses[];
    String newInternalAddress = "";
    String newPublicAddress = "";

    String candidateType = candidate.split("typ")[1].split(" ")[1];

    if (WebRtcCandidateType.HOST.toString().equals(candidateType)) {
      internalAddress = candidate.split(" ")[4];
      switch (webRtcIpvMode) {
        case IPV4:
          if (!webRtcCandidateType.toString().equals(candidateType)) {
            internalAddresses = internalAddress.split("\\.");
            for (int i = 0; i < internalAddresses.length - 1; i++) {
              newInternalAddress = newInternalAddress.concat(internalAddresses[i] + ".");
            }
            newInternalAddress = newInternalAddress.concat("254");
          } else {
            newInternalAddress = internalAddress;
          }
          return candidate.replace(internalAddress, newInternalAddress);
        case IPV6:
          if (!webRtcCandidateType.toString().equals(candidateType)) {
            internalAddresses = internalAddress.split(":");
            for (int i = 0; i < internalAddresses.length - 1; i++) {
              newInternalAddress = newInternalAddress.concat(internalAddresses[i] + ":");
            }
            newInternalAddress = newInternalAddress.concat("2000");
          } else {
            newInternalAddress = internalAddress;
          }
          return candidate.replace(internalAddress, newInternalAddress);
        default:
          break;
      }

    } else if (WebRtcCandidateType.SRFLX.toString().equals(candidateType)
        || WebRtcCandidateType.RELAY.toString().equals(candidateType)) {
      publicAddress = candidate.split(" ")[4];
      internalAddress = candidate.split(" ")[9];
      switch (webRtcIpvMode) {
        case IPV4:
          internalAddresses = internalAddress.split("\\.");

          for (int i = 0; i < internalAddresses.length - 1; i++) {
            newInternalAddress = newInternalAddress.concat(internalAddresses[i] + ".");
          }
          newInternalAddress = newInternalAddress.concat("254");

          if (!webRtcCandidateType.toString().equals(candidateType)) {
            publicAddresses = publicAddress.split("\\.");
            for (int i = 0; i < publicAddresses.length - 1; i++) {
              newPublicAddress = newPublicAddress.concat(publicAddresses[i] + ".");
            }
            newPublicAddress = newPublicAddress.concat("254");
          } else {
            newPublicAddress = publicAddress;
          }
          return candidate.replace(internalAddress, newInternalAddress).replace(publicAddress,
              newPublicAddress);
        case IPV6:
          internalAddresses = internalAddress.split(":");
          for (int i = 0; i < internalAddresses.length - 1; i++) {
            newInternalAddress = newInternalAddress.concat(internalAddresses[i] + ":");
          }
          newInternalAddress = newInternalAddress.concat("2000");

          if (!webRtcCandidateType.toString().equals(candidateType)) {
            publicAddresses = publicAddress.split(":");
            for (int i = 0; i < publicAddresses.length - 1; i++) {
              newPublicAddress = newPublicAddress.concat(publicAddresses[i] + ":");
            }
            newPublicAddress = newPublicAddress.concat("2000");
          } else {
            newPublicAddress = publicAddress;
          }
          return candidate.replace(internalAddress, newInternalAddress).replace(publicAddress,
              newPublicAddress);
        default:
          break;
      }
    }

    return candidate;
  }

  /*
   * initWebRtc with IPVMode
   */
  public void initWebRtc(final WebRtcEndpoint webRtcEndpoint, final WebRtcChannel channel,
      final WebRtcMode mode, final WebRtcIpvMode webRtcIpvMode,
      final WebRtcCandidateType webRtcCandidateType, boolean useDataChannels)
      throws InterruptedException {

    webRtcEndpoint.addIceCandidateFoundListener(new EventListener() {

      @Override
      public void onEvent(IceCandidateFoundEvent event) {
        JsonObject candidate = JsonUtils.toJsonObject(event.getCandidate());

        if (!filterCandidate(candidate.get("candidate").getAsString(), webRtcIpvMode,
            webRtcCandidateType)) {
          log.debug("OnIceCandadite -> Adding candidate: {} IpvMode: {} CandidateType: {}",
              candidate.get("candidate").getAsString(), webRtcIpvMode, webRtcCandidateType);
          addIceCandidate(candidate);
        }
      }
    });

    webRtcEndpoint.addMediaStateChangedListener(new EventListener() {
      @Override
      public void onEvent(MediaStateChangedEvent event) {
        log.debug("MediaStateChangedEvent from {} to {} on {} at {}", event.getOldState(),
            event.getNewState(), webRtcEndpoint.getId(), event.getTimestamp());
      }
    });

    WebRtcConfigurer webRtcConfigurer = new WebRtcConfigurer() {
      @Override
      public void addIceCandidate(IceCandidate candidate) {

        if (!filterCandidate(candidate.getCandidate(), webRtcIpvMode, webRtcCandidateType)) {
          log.debug("webRtcConfigurer -> Adding candidate: {} IpvMode: {} CandidateType: {}",
              candidate.getCandidate(), webRtcIpvMode, webRtcCandidateType);
          webRtcEndpoint.addIceCandidate(candidate);
        }
      }

      @Override
      public String processOffer(String sdpOffer) {
        String sdpAnswer = webRtcEndpoint.processOffer(sdpOffer);
        webRtcEndpoint.gatherCandidates();
        return sdpAnswer;
      }
    };

    initWebRtc(webRtcConfigurer, channel, mode, webRtcCandidateType, useDataChannels);
  }

  /**
   *
   * initWebRtc with IPVMode and without useDataChannels
   */
  public void initWebRtc(final WebRtcEndpoint webRtcEndpoint, final WebRtcChannel channel,
      final WebRtcMode mode, final WebRtcIpvMode webRtcIpvMode,
      final WebRtcCandidateType webRtcCandidateType) throws InterruptedException {
    initWebRtc(webRtcEndpoint, channel, mode, webRtcIpvMode, webRtcCandidateType, false);
  }

  /*
   * initWebRtc with IPVMode
   */
  public void initWebRtc(final WebRtcEndpoint webRtcEndpoint, final WebRtcChannel channel,
      final WebRtcMode mode, final WebRtcIpvMode webRtcIpvMode) throws InterruptedException {
    initWebRtc(webRtcEndpoint, channel, mode, webRtcIpvMode, WebRtcCandidateType.ALL, false);
  }

  /*
   * initWebRtc with useDataChannels
   */
  public void initWebRtc(final WebRtcEndpoint webRtcEndpoint, final WebRtcChannel channel,
      final WebRtcMode mode, final Boolean useDataChannels) throws InterruptedException {
    initWebRtc(webRtcEndpoint, channel, mode, WebRtcIpvMode.BOTH, WebRtcCandidateType.ALL,
        useDataChannels);
  }

  /*
   * initWebRtc without IPVMode
   */
  public void initWebRtc(final WebRtcEndpoint webRtcEndpoint, final WebRtcChannel channel,
      final WebRtcMode mode) throws InterruptedException {
    initWebRtc(webRtcEndpoint, channel, mode, WebRtcIpvMode.BOTH, WebRtcCandidateType.ALL, false);
  }

  @SuppressWarnings({ "unchecked", "deprecation" })
  protected void initWebRtc(final WebRtcConfigurer webRtcConfigurer, final WebRtcChannel channel,
      final WebRtcMode mode, final WebRtcCandidateType candidateType, boolean useDataChannels)
      throws InterruptedException {
    // ICE candidates
    Thread t1 = new Thread() {
      @Override
      public void run() {
        JsonParser parser = new JsonParser();
        int numCandidate = 0;
        while (true) {
          try {
            ArrayList iceCandidates =
                (ArrayList) browser.executeScript("return iceCandidates;");

            for (int i = numCandidate; i < iceCandidates.size(); i++) {
              JsonObject jsonCandidate = (JsonObject) parser.parse(iceCandidates.get(i).toString());
              IceCandidate candidate =
                  new IceCandidate(jsonCandidate.get("candidate").getAsString(),
                      jsonCandidate.get("sdpMid").getAsString(),
                      jsonCandidate.get("sdpMLineIndex").getAsInt());
              // log.debug("Adding candidate {}: {}", i, jsonCandidate);
              webRtcConfigurer.addIceCandidate(candidate);
              numCandidate++;
            }

            // Poll 300 ms
            Thread.sleep(300);

          } catch (Throwable e) {
            log.debug("Exiting gather candidates thread");
            break;
          }
        }
      }
    };
    t1.start();

    // Append WebRTC mode (send/receive and audio/video) to identify test
    addTestName(KurentoTest.getTestClassName() + "." + KurentoTest.getTestMethodName());
    appendStringToTitle(mode.toString());
    appendStringToTitle(channel.toString());

    // Setting custom audio stream (if necessary)
    String audio = browser.getAudio();
    if (audio != null) {
      browser.executeScript("setCustomAudio('" + audio + "');");
    }

    // Create peerConnection for using dataChannels (if necessary)
    if (useDataChannels) {
      browser.executeScript("useDataChannels()");
    }

    // Setting IceServer (if necessary)
    String iceServerJsFunction = candidateType.getJsFunction();
    log.debug("Setting IceServer: {}", iceServerJsFunction);
    if (iceServerJsFunction != null) {
      browser.executeScript(iceServerJsFunction);
    }

    // Setting MediaConstraints (if necessary)
    String channelJsFunction = channel.getJsFunction();
    if (channelJsFunction != null) {
      browser.executeScript(channelJsFunction);
    }

    // Execute JavaScript kurentoUtils.WebRtcPeer
    browser.executeScript(mode.getJsFunction());

    // SDP offer/answer
    final CountDownLatch latch = new CountDownLatch(1);
    Thread t2 = new Thread() {
      @Override
      public void run() {
        // Wait to valid sdpOffer
        String sdpOffer = (String) browser.executeScriptAndWaitOutput("return sdpOffer;");

        log.debug("SDP offer: {}", sdpOffer);
        String sdpAnswer = webRtcConfigurer.processOffer(sdpOffer);
        log.debug("SDP answer: {}", sdpAnswer);

        // Encoding in Base64 to avoid parsing errors in JavaScript
        sdpAnswer = new String(Base64.encodeBase64(sdpAnswer.getBytes()));

        // Process sdpAnswer
        browser.executeScript("processSdpAnswer('" + sdpAnswer + "');");

        latch.countDown();
      }
    };
    t2.start();

    if (!latch.await(browser.getTimeout(), TimeUnit.SECONDS)) {
      t1.interrupt();
      t1.stop();
      t2.interrupt();
      t2.stop();
      throw new KurentoException(
          "ICE negotiation not finished in " + browser.getTimeout() + " seconds");
    }
  }

  protected void initWebRtc(final WebRtcConfigurer webRtcConfigurer, final WebRtcChannel channel,
      final WebRtcMode mode) throws InterruptedException {
    initWebRtc(webRtcConfigurer, channel, mode, WebRtcCandidateType.ALL, false);
  }

  /*
   * reload
   */
  public void reload() throws IOException {
    browser.reload();
    browser.injectKurentoTestJs();
    browser.executeScriptAndWaitOutput("return kurentoTest;");
    setBrowser(browser);
  }

  /*
   * addTestName
   */
  public void addTestName(String testName) {
    try {
      browser.executeScript("addTestName('" + testName + "');");
    } catch (WebDriverException we) {
      log.warn(we.getMessage());
    }
  }

  /*
   * appendStringToTitle
   */
  public void appendStringToTitle(String webRtcMode) {
    try {
      browser.executeScript("appendStringToTitle('" + webRtcMode + "');");
    } catch (WebDriverException we) {
      log.warn(we.getMessage());
    }
  }

}