org.kurento.test.browser.WebRtcTestPage.java Source code

Java tutorial

Introduction

Here is the source code for org.kurento.test.browser.WebRtcTestPage.java

Source

/*
 * (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 (bgarcia@gsyc.es)
 * @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<IceCandidateFoundEvent>() {

            @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<MediaStateChangedEvent>() {
            @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<Object> iceCandidates = (ArrayList<Object>) 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());
        }
    }

}