Simple android app
authorMatthew Waters <matthew@centricular.com>
Wed, 7 Nov 2018 13:32:31 +0000 (00:32 +1100)
committerMatthew Waters <ystreet00@gmail.com>
Mon, 16 Sep 2019 14:55:58 +0000 (14:55 +0000)
20 files changed:
webrtc/android/app/.gitignore [new file with mode: 0644]
webrtc/android/app/build.gradle [new file with mode: 0644]
webrtc/android/app/gradle.properties [new file with mode: 0644]
webrtc/android/app/gradlew [new file with mode: 0644]
webrtc/android/app/gradlew.bat [new file with mode: 0644]
webrtc/android/app/proguard-rules.pro [new file with mode: 0644]
webrtc/android/app/src/main/AndroidManifest.xml [new file with mode: 0644]
webrtc/android/app/src/main/java/org/freedesktop/gstreamer/WebRTC.java [new file with mode: 0644]
webrtc/android/app/src/main/java/org/freedesktop/gstreamer/webrtc/GStreamerSurfaceView.java [new file with mode: 0644]
webrtc/android/app/src/main/java/org/freedesktop/gstreamer/webrtc/WebRTC.java [new file with mode: 0644]
webrtc/android/app/src/main/jni/Android.mk [new file with mode: 0644]
webrtc/android/app/src/main/jni/Application.mk [new file with mode: 0644]
webrtc/android/app/src/main/jni/webrtc.c [new file with mode: 0644]
webrtc/android/app/src/main/res/layout/main.xml [new file with mode: 0644]
webrtc/android/app/src/main/res/values/strings.xml [new file with mode: 0644]
webrtc/android/build.gradle [new file with mode: 0644]
webrtc/android/gradle.properties [new file with mode: 0644]
webrtc/android/gradlew [new file with mode: 0755]
webrtc/android/gradlew.bat [new file with mode: 0644]
webrtc/android/settings.gradle [new file with mode: 0644]

diff --git a/webrtc/android/app/.gitignore b/webrtc/android/app/.gitignore
new file mode 100644 (file)
index 0000000..a683103
--- /dev/null
@@ -0,0 +1,5 @@
+.externalNativeBuild/
+assets/
+gst-build-*/
+src/main/java/org/freedesktop/gstreamer/GStreamer.java
+src/main/java/org/freedesktop/gstreamer/androidmedia/
diff --git a/webrtc/android/app/build.gradle b/webrtc/android/app/build.gradle
new file mode 100644 (file)
index 0000000..944e8e7
--- /dev/null
@@ -0,0 +1,61 @@
+apply plugin: 'com.android.application'
+
+android {
+    compileSdkVersion 23
+    buildToolsVersion "26.0.2"
+
+    defaultConfig {
+        applicationId "org.freedesktop.gstreamer.webrtc"
+        minSdkVersion 15
+        targetSdkVersion 15
+        versionCode 1
+        versionName "1.0"
+
+
+        externalNativeBuild {
+            ndkBuild {
+                def gstRoot
+
+                if (project.hasProperty('gstAndroidRoot'))
+                    gstRoot = project.gstAndroidRoot
+                else
+                    gstRoot = System.env.GSTREAMER_ROOT_ANDROID
+
+                if (gstRoot == null)
+                    throw new GradleException('GSTREAMER_ROOT_ANDROID must be set, or "gstAndroidRoot" must be defined in your gradle.properties in the top level directory of the unpacked universal GStreamer Android binaries')
+
+                arguments "NDK_APPLICATION_MK=src/main/jni/Application.mk", "GSTREAMER_JAVA_SRC_DIR=src/main/java", "GSTREAMER_ROOT_ANDROID=$gstRoot", "GSTREAMER_ASSETS_DIR=src/main/assets", "V=1"
+
+                targets "gstwebrtc"
+
+                // All archs except MIPS and MIPS64 are supported
+                abiFilters 'armeabi-v7a', 'arm64-v8a' //, 'x86', 'x86_64', 'armeabi'
+            }
+        }
+    }
+
+    buildTypes {
+        release {
+            minifyEnabled false
+            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+        }
+    }
+
+    externalNativeBuild {
+        ndkBuild {
+            path 'src/main/jni/Android.mk'
+        }
+    }
+}
+
+afterEvaluate {
+    compileDebugJavaWithJavac.dependsOn 'externalNativeBuildDebug'
+    compileReleaseJavaWithJavac.dependsOn 'externalNativeBuildRelease'
+}
+
+dependencies {
+    compile fileTree(dir: 'libs', include: ['*.jar'])
+    testCompile 'junit:junit:4.12'
+    compile 'com.android.support:appcompat-v7:23.1.1'
+    implementation 'com.android.support.constraint:constraint-layout:1.0.2'
+}
diff --git a/webrtc/android/app/gradle.properties b/webrtc/android/app/gradle.properties
new file mode 100644 (file)
index 0000000..cdcfa9d
--- /dev/null
@@ -0,0 +1,19 @@
+# Project-wide Gradle settings.
+
+# IDE (e.g. Android Studio) users:
+# Gradle settings configured through the IDE *will override*
+# any settings specified in this file.
+
+# For more details on how to configure your build environment visit
+# http://www.gradle.org/docs/current/userguide/build_environment.html
+
+# Specifies the JVM arguments used for the daemon process.
+# The setting is particularly useful for tweaking memory settings.
+#org.gradle.jvmargs=-Xmx1536m
+
+# When configured, Gradle will run in incubating parallel mode.
+# This option should only be used with decoupled projects. More details, visit
+# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
+# org.gradle.parallel=true
+#gstAndroidRoot=/home/matt/Projects/cerbero/build/dist/android_universal
+
diff --git a/webrtc/android/app/gradlew b/webrtc/android/app/gradlew
new file mode 100644 (file)
index 0000000..9d82f78
--- /dev/null
@@ -0,0 +1,160 @@
+#!/usr/bin/env bash
+
+##############################################################################
+##
+##  Gradle start up script for UN*X
+##
+##############################################################################
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn ( ) {
+    echo "$*"
+}
+
+die ( ) {
+    echo
+    echo "$*"
+    echo
+    exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+case "`uname`" in
+  CYGWIN* )
+    cygwin=true
+    ;;
+  Darwin* )
+    darwin=true
+    ;;
+  MINGW* )
+    msys=true
+    ;;
+esac
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+    ls=`ls -ld "$PRG"`
+    link=`expr "$ls" : '.*-> \(.*\)$'`
+    if expr "$link" : '/.*' > /dev/null; then
+        PRG="$link"
+    else
+        PRG=`dirname "$PRG"`"/$link"
+    fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+    if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+        # IBM's JDK on AIX uses strange locations for the executables
+        JAVACMD="$JAVA_HOME/jre/sh/java"
+    else
+        JAVACMD="$JAVA_HOME/bin/java"
+    fi
+    if [ ! -x "$JAVACMD" ] ; then
+        die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+    fi
+else
+    JAVACMD="java"
+    which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
+    MAX_FD_LIMIT=`ulimit -H -n`
+    if [ $? -eq 0 ] ; then
+        if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+            MAX_FD="$MAX_FD_LIMIT"
+        fi
+        ulimit -n $MAX_FD
+        if [ $? -ne 0 ] ; then
+            warn "Could not set maximum file descriptor limit: $MAX_FD"
+        fi
+    else
+        warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+    fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+    GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
+    APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+    CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+    JAVACMD=`cygpath --unix "$JAVACMD"`
+
+    # We build the pattern for arguments to be converted via cygpath
+    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+    SEP=""
+    for dir in $ROOTDIRSRAW ; do
+        ROOTDIRS="$ROOTDIRS$SEP$dir"
+        SEP="|"
+    done
+    OURCYGPATTERN="(^($ROOTDIRS))"
+    # Add a user-defined pattern to the cygpath arguments
+    if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+        OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+    fi
+    # Now convert the arguments - kludge to limit ourselves to /bin/sh
+    i=0
+    for arg in "$@" ; do
+        CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+        CHECK2=`echo "$arg"|egrep -c "^-"`                                 ### Determine if an option
+
+        if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition
+            eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+        else
+            eval `echo args$i`="\"$arg\""
+        fi
+        i=$((i+1))
+    done
+    case $i in
+        (0) set -- ;;
+        (1) set -- "$args0" ;;
+        (2) set -- "$args0" "$args1" ;;
+        (3) set -- "$args0" "$args1" "$args2" ;;
+        (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+        (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+        (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+        (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+        (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+        (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+    esac
+fi
+
+# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
+function splitJvmOpts() {
+    JVM_OPTS=("$@")
+}
+eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
+JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
+
+exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
diff --git a/webrtc/android/app/gradlew.bat b/webrtc/android/app/gradlew.bat
new file mode 100644 (file)
index 0000000..aec9973
--- /dev/null
@@ -0,0 +1,90 @@
+@if "%DEBUG%" == "" @echo off\r
+@rem ##########################################################################\r
+@rem\r
+@rem  Gradle startup script for Windows\r
+@rem\r
+@rem ##########################################################################\r
+\r
+@rem Set local scope for the variables with windows NT shell\r
+if "%OS%"=="Windows_NT" setlocal\r
+\r
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\r
+set DEFAULT_JVM_OPTS=\r
+\r
+set DIRNAME=%~dp0\r
+if "%DIRNAME%" == "" set DIRNAME=.\r
+set APP_BASE_NAME=%~n0\r
+set APP_HOME=%DIRNAME%\r
+\r
+@rem Find java.exe\r
+if defined JAVA_HOME goto findJavaFromJavaHome\r
+\r
+set JAVA_EXE=java.exe\r
+%JAVA_EXE% -version >NUL 2>&1\r
+if "%ERRORLEVEL%" == "0" goto init\r
+\r
+echo.\r
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\r
+echo.\r
+echo Please set the JAVA_HOME variable in your environment to match the\r
+echo location of your Java installation.\r
+\r
+goto fail\r
+\r
+:findJavaFromJavaHome\r
+set JAVA_HOME=%JAVA_HOME:"=%\r
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe\r
+\r
+if exist "%JAVA_EXE%" goto init\r
+\r
+echo.\r
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%\r
+echo.\r
+echo Please set the JAVA_HOME variable in your environment to match the\r
+echo location of your Java installation.\r
+\r
+goto fail\r
+\r
+:init\r
+@rem Get command-line arguments, handling Windowz variants\r
+\r
+if not "%OS%" == "Windows_NT" goto win9xME_args\r
+if "%@eval[2+2]" == "4" goto 4NT_args\r
+\r
+:win9xME_args\r
+@rem Slurp the command line arguments.\r
+set CMD_LINE_ARGS=\r
+set _SKIP=2\r
+\r
+:win9xME_args_slurp\r
+if "x%~1" == "x" goto execute\r
+\r
+set CMD_LINE_ARGS=%*\r
+goto execute\r
+\r
+:4NT_args\r
+@rem Get arguments from the 4NT Shell from JP Software\r
+set CMD_LINE_ARGS=%$\r
+\r
+:execute\r
+@rem Setup the command line\r
+\r
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar\r
+\r
+@rem Execute Gradle\r
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%\r
+\r
+:end\r
+@rem End local scope for the variables with windows NT shell\r
+if "%ERRORLEVEL%"=="0" goto mainEnd\r
+\r
+:fail\r
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of\r
+rem the _cmd.exe /c_ return code!\r
+if  not "" == "%GRADLE_EXIT_CONSOLE%" exit 1\r
+exit /b 1\r
+\r
+:mainEnd\r
+if "%OS%"=="Windows_NT" endlocal\r
+\r
+:omega\r
diff --git a/webrtc/android/app/proguard-rules.pro b/webrtc/android/app/proguard-rules.pro
new file mode 100644 (file)
index 0000000..d5d45de
--- /dev/null
@@ -0,0 +1,17 @@
+# Add project specific ProGuard rules here.
+# By default, the flags in this file are appended to flags specified
+# in /home/arun/code/android/sdk/tools/proguard/proguard-android.txt
+# You can edit the include path and order by changing the proguardFiles
+# directive in build.gradle.
+#
+# For more details, see
+#   http://developer.android.com/guide/developing/tools/proguard.html
+
+# Add any project specific keep options here:
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+#   public *;
+#}
diff --git a/webrtc/android/app/src/main/AndroidManifest.xml b/webrtc/android/app/src/main/AndroidManifest.xml
new file mode 100644 (file)
index 0000000..e4baa4f
--- /dev/null
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+      package="org.freedesktop.gstreamer.webrtc">
+
+    <uses-permission android:name="android.permission.INTERNET"/>
+    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
+    <uses-permission android:name="android.permission.WAKE_LOCK"/>
+    <uses-permission android:name="android.permission.RECORD_AUDIO" />
+    <uses-feature android:glEsVersion="0x00020000"/>
+
+    <application android:label="@string/app_name">
+        <activity android:name=".WebRTC"
+                  android:label="@string/app_name">
+            <!-- Files whose MIME type is known to Android -->
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+
+                <category android:name="android.intent.category.LAUNCHER" />
+                <category android:name="android.intent.category.DEFAULT" />
+                <category android:name="android.intent.category.BROWSABLE" />
+            </intent-filter>
+
+        </activity>
+    </application>
+</manifest> 
diff --git a/webrtc/android/app/src/main/java/org/freedesktop/gstreamer/WebRTC.java b/webrtc/android/app/src/main/java/org/freedesktop/gstreamer/WebRTC.java
new file mode 100644 (file)
index 0000000..f9c0a6a
--- /dev/null
@@ -0,0 +1,92 @@
+/* GStreamer
+ *
+ * Copyright (C) 2014-2015 Matthew Waters <matthew@centricular.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * 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
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+package org.freedesktop.gstreamer;
+
+import java.io.Closeable;
+import android.view.Surface;
+import android.content.Context;
+import org.freedesktop.gstreamer.GStreamer;
+
+public class WebRTC implements Closeable {
+    private static native void nativeClassInit();
+    public static void init(Context context) throws Exception {
+        System.loadLibrary("gstreamer_android");
+        GStreamer.init(context);
+
+        System.loadLibrary("gstwebrtc");
+        nativeClassInit();
+    }
+
+    private long native_webrtc;
+    private native void nativeNew();
+    public WebRTC() {
+        nativeNew();
+    }
+
+    private native void nativeFree();
+    @Override
+    public void close() {
+        nativeFree();
+    }
+
+    private Surface surface;
+    private native void nativeSetSurface(Surface surface);
+    public void setSurface(Surface surface) {
+        this.surface = surface;
+        nativeSetSurface(surface);
+    }
+
+    public Surface getSurface() {
+        return surface;
+    }
+
+    private String signallingServer;
+    private native void nativeSetSignallingServer(String server);
+    public void setSignallingServer(String server) {
+        this.signallingServer = server;
+        nativeSetSignallingServer(server);
+    }
+
+    public String getSignallingServer() {
+        return this.signallingServer;
+    }
+
+    private String callID;
+    private native void nativeSetCallID(String ID);
+    public void setCallID(String ID) {
+        this.callID = ID;
+        nativeSetCallID(ID);
+    }
+
+    public String getCallID() {
+        return this.callID;
+    }
+
+    private native void nativeCallOtherParty();
+    public void callOtherParty() {
+        nativeCallOtherParty();
+    }
+
+    private native void nativeEndCall();
+    public void endCall() {
+        nativeEndCall();
+    }
+}
diff --git a/webrtc/android/app/src/main/java/org/freedesktop/gstreamer/webrtc/GStreamerSurfaceView.java b/webrtc/android/app/src/main/java/org/freedesktop/gstreamer/webrtc/GStreamerSurfaceView.java
new file mode 100644 (file)
index 0000000..25a3e6b
--- /dev/null
@@ -0,0 +1,110 @@
+/* GStreamer
+ *
+ * Copyright (C) 2014 Sebastian Dröge <sebastian@centricular.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * 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
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+package org.freedesktop.gstreamer.webrtc;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.SurfaceView;
+import android.view.View;
+
+// A simple SurfaceView whose width and height can be set from the outside
+public class GStreamerSurfaceView extends SurfaceView {
+    public int media_width = 320;
+    public int media_height = 240;
+
+    // Mandatory constructors, they do not do much
+    public GStreamerSurfaceView(Context context, AttributeSet attrs,
+            int defStyle) {
+        super(context, attrs, defStyle);
+    }
+
+    public GStreamerSurfaceView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    public GStreamerSurfaceView (Context context) {
+        super(context);
+    }
+
+    // Called by the layout manager to find out our size and give us some rules.
+    // We will try to maximize our size, and preserve the media's aspect ratio if
+    // we are given the freedom to do so.
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        if (media_width == 0 || media_height == 0) {
+            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+            return;
+        }
+
+        int width = 0, height = 0;
+        int wmode = View.MeasureSpec.getMode(widthMeasureSpec);
+        int hmode = View.MeasureSpec.getMode(heightMeasureSpec);
+        int wsize = View.MeasureSpec.getSize(widthMeasureSpec);
+        int hsize = View.MeasureSpec.getSize(heightMeasureSpec);
+
+        Log.i ("GStreamer", "onMeasure called with " + media_width + "x" + media_height);
+        // Obey width rules
+        switch (wmode) {
+        case View.MeasureSpec.AT_MOST:
+            if (hmode == View.MeasureSpec.EXACTLY) {
+                width = Math.min(hsize * media_width / media_height, wsize);
+                break;
+            }
+        case View.MeasureSpec.EXACTLY:
+            width = wsize;
+            break;
+        case View.MeasureSpec.UNSPECIFIED:
+            width = media_width;
+        }
+
+        // Obey height rules
+        switch (hmode) {
+        case View.MeasureSpec.AT_MOST:
+            if (wmode == View.MeasureSpec.EXACTLY) {
+                height = Math.min(wsize * media_height / media_width, hsize);
+                break;
+            }
+        case View.MeasureSpec.EXACTLY:
+            height = hsize;
+            break;
+        case View.MeasureSpec.UNSPECIFIED:
+            height = media_height;
+        }
+
+        // Finally, calculate best size when both axis are free
+        if (hmode == View.MeasureSpec.AT_MOST && wmode == View.MeasureSpec.AT_MOST) {
+            int correct_height = width * media_height / media_width;
+            int correct_width = height * media_width / media_height;
+
+            if (correct_height < height)
+                height = correct_height;
+            else
+                width = correct_width;
+        }
+
+        // Obey minimum size
+        width = Math.max (getSuggestedMinimumWidth(), width);
+        height = Math.max (getSuggestedMinimumHeight(), height);
+        setMeasuredDimension(width, height);
+    }
+
+}
diff --git a/webrtc/android/app/src/main/java/org/freedesktop/gstreamer/webrtc/WebRTC.java b/webrtc/android/app/src/main/java/org/freedesktop/gstreamer/webrtc/WebRTC.java
new file mode 100644 (file)
index 0000000..8c5af6e
--- /dev/null
@@ -0,0 +1,125 @@
+/* GStreamer
+ *
+ * Copyright (C) 2014 Sebastian Dröge <sebastian@centricular.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * 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
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+package org.freedesktop.gstreamer.webrtc;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.PowerManager;
+import android.util.Log;
+import android.view.SurfaceHolder;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.widget.ImageButton;
+import android.widget.TextView;
+import android.widget.Toast;
+
+public class WebRTC extends Activity implements SurfaceHolder.Callback {
+    private PowerManager.WakeLock wake_lock;
+    private org.freedesktop.gstreamer.WebRTC webRTC;
+
+    @Override
+    public void onCreate(Bundle savedInstanceState)
+    {
+        super.onCreate(savedInstanceState);
+
+        try {
+            org.freedesktop.gstreamer.WebRTC.init(this);
+        } catch (Exception e) {
+            Toast.makeText(this, e.getMessage(), Toast.LENGTH_LONG).show();
+            finish();
+            return;
+        }
+
+        setContentView(R.layout.main);
+
+        webRTC = new org.freedesktop.gstreamer.WebRTC();
+
+        PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
+        wake_lock = pm.newWakeLock(PowerManager.FULL_WAKE_LOCK, "GStreamer WebRTC");
+        wake_lock.setReferenceCounted(false);
+
+        final TextView URLText = (TextView) this.findViewById(R.id.URLText);
+        final TextView IDText = (TextView) this.findViewById(R.id.IDText);
+        final GStreamerSurfaceView gsv = (GStreamerSurfaceView) this.findViewById(R.id.surface_video);
+
+        ImageButton play = (ImageButton) this.findViewById(R.id.button_play);
+        play.setOnClickListener(new OnClickListener() {
+            public void onClick(View v) {
+                webRTC.setSignallingServer(URLText.getText().toString());
+                webRTC.setCallID(IDText.getText().toString());
+                webRTC.setSurface(gsv.getHolder().getSurface());
+                webRTC.callOtherParty();
+                wake_lock.acquire();
+            }
+        });
+
+        ImageButton pause= (ImageButton) this.findViewById(R.id.button_pause);
+        pause.setOnClickListener(new OnClickListener() {
+            public void onClick(View v) {
+                webRTC.endCall();
+                wake_lock.release();
+            }
+        });
+
+/*        webRTC.setVideoDimensionsChangedListener(new org.freedesktop.gstreamer.WebRTC.VideoDimensionsChangedListener() {
+            public void videoDimensionsChanged(org.freedesktop.gstreamer.WebRTC webRTC, final int width, final int height) {
+                runOnUiThread (new Runnable() {
+                    public void run() {
+                        Log.i ("GStreamer", "Media size changed to " + width + "x" + height);
+                        gsv.media_width = width;
+                        gsv.media_height = height;
+                        runOnUiThread(new Runnable() {
+                            public void run() {
+                                gsv.requestLayout();
+                            }
+                        });
+                    }
+                });
+            }
+        });*/
+
+        SurfaceHolder sh = gsv.getHolder();
+        sh.addCallback(this);
+    }
+
+    protected void onDestroy() {
+        webRTC.close();
+        super.onDestroy();
+    }
+
+    public void surfaceChanged(SurfaceHolder holder, int format, int width,
+            int height) {
+        Log.d("GStreamer", "Surface changed to format " + format + " width "
+                + width + " height " + height);
+        webRTC.setSurface(holder.getSurface());
+    }
+
+    public void surfaceCreated(SurfaceHolder holder) {
+        Log.d("GStreamer", "Surface created: " + holder.getSurface());
+    }
+
+    public void surfaceDestroyed(SurfaceHolder holder) {
+        Log.d("GStreamer", "Surface destroyed");
+        webRTC.setSurface(null);
+    }
+}
diff --git a/webrtc/android/app/src/main/jni/Android.mk b/webrtc/android/app/src/main/jni/Android.mk
new file mode 100644 (file)
index 0000000..a12b19e
--- /dev/null
@@ -0,0 +1,51 @@
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE    := gstwebrtc
+LOCAL_SRC_FILES := webrtc.c
+
+LOCAL_SHARED_LIBRARIES := gstreamer_android
+LOCAL_LDLIBS := -llog -landroid
+include $(BUILD_SHARED_LIBRARY)
+
+ifndef GSTREAMER_ROOT_ANDROID
+$(error GSTREAMER_ROOT_ANDROID is not defined!)
+endif
+
+ifeq ($(TARGET_ARCH_ABI),armeabi)
+GSTREAMER_ROOT        := $(GSTREAMER_ROOT_ANDROID)/arm
+else ifeq ($(TARGET_ARCH_ABI),armeabi-v7a)
+GSTREAMER_ROOT        := $(GSTREAMER_ROOT_ANDROID)/armv7
+else ifeq ($(TARGET_ARCH_ABI),arm64-v8a)
+GSTREAMER_ROOT        := $(GSTREAMER_ROOT_ANDROID)/arm64
+else ifeq ($(TARGET_ARCH_ABI),x86)
+GSTREAMER_ROOT        := $(GSTREAMER_ROOT_ANDROID)/x86
+else ifeq ($(TARGET_ARCH_ABI),x86_64)
+GSTREAMER_ROOT        := $(GSTREAMER_ROOT_ANDROID)/x86_64
+else
+$(error Target arch ABI not supported: $(TARGET_ARCH_ABI))
+endif
+
+GSTREAMER_NDK_BUILD_PATH  := $(GSTREAMER_ROOT)/share/gst-android/ndk-build/
+
+include $(GSTREAMER_NDK_BUILD_PATH)/plugins.mk
+GSTREAMER_PLUGINS         := $(GSTREAMER_PLUGINS_CORE)      \
+                             $(GSTREAMER_PLUGINS_PLAYBACK)  \
+                             $(GSTREAMER_PLUGINS_NET)       \
+                             $(GSTREAMER_PLUGINS_SYS)       \
+                             $(GSTREAMER_PLUGINS_CODECS_RESTRICTED) \
+                             $(GSTREAMER_CODECS_GPL)        \
+                             $(GSTREAMER_PLUGINS_ENCODING)  \
+                             $(GSTREAMER_PLUGINS_VIS)       \
+                             $(GSTREAMER_PLUGINS_EFFECTS)   \
+                             $(GSTREAMER_PLUGINS_NET_RESTRICTED) \
+                             subparse ogg theora vorbis opus ivorbisdec alaw apetag audioparsers auparse avi dv flac flv flxdec icydemux id3demux isomp4 jpeg lame matroska mpg123 mulaw multipart png speex taglib vpx wavenc wavpack wavparse y4menc adpcmdec adpcmenc dashdemux dvbsuboverlay dvdspu hls id3tag kate midi mxf openh264 opusparse pcapparse pnm rfbsrc siren smoothstreaming subenc videoparsersbad y4mdec jpegformat gdp rsvg openjpeg spandsp sbc \
+                             nice androidmedia
+
+#                             $(GSTREAMER_PLUGINS_CODECS)
+GSTREAMER_EXTRA_DEPS      := gstreamer-webrtc-1.0 gstreamer-video-1.0 libsoup-2.4 json-glib-1.0 glib-2.0
+
+G_IO_MODULES = gnutls
+
+include $(GSTREAMER_NDK_BUILD_PATH)/gstreamer-1.0.mk
diff --git a/webrtc/android/app/src/main/jni/Application.mk b/webrtc/android/app/src/main/jni/Application.mk
new file mode 100644 (file)
index 0000000..6f8c817
--- /dev/null
@@ -0,0 +1,3 @@
+APP_PLATFORM = 15
+APP_ABI = armeabi armeabi-v7a arm64-v8a x86 x86_64
+#APP_ABI = armeabi-v7a arm64-v8a x86_64
diff --git a/webrtc/android/app/src/main/jni/webrtc.c b/webrtc/android/app/src/main/jni/webrtc.c
new file mode 100644 (file)
index 0000000..1e85c76
--- /dev/null
@@ -0,0 +1,900 @@
+/* GStreamer
+ *
+ * Copyright (C) 2014 Sebastian Dröge <sebastian@centricular.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * 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
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#include <string.h>
+#include <stdint.h>
+#include <jni.h>
+#include <android/log.h>
+#include <android/native_window.h>
+#include <android/native_window_jni.h>
+
+#include <gst/video/videooverlay.h>
+
+/* helper library for webrtc things */
+#define GST_USE_UNSTABLE_API
+#include <gst/webrtc/webrtc.h>
+
+/* For signalling */
+#include <libsoup/soup.h>
+#include <json-glib/json-glib.h>
+
+GST_DEBUG_CATEGORY_STATIC (debug_category);
+#define GST_CAT_DEFAULT debug_category
+
+#define DEFAULT_SIGNALLING_SERVER "wss://webrtc.nirbheek.in:8443"
+
+#define GET_CUSTOM_DATA(env, thiz, fieldID) (WebRTC *)(gintptr)(*env)->GetLongField (env, thiz, fieldID)
+#define SET_CUSTOM_DATA(env, thiz, fieldID, data) (*env)->SetLongField (env, thiz, fieldID, (jlong)(gintptr)data)
+
+enum AppState {
+    APP_STATE_UNKNOWN = 0,
+    APP_STATE_ERROR = 1, /* generic error */
+    SERVER_CONNECTING = 1000,
+    SERVER_CONNECTION_ERROR,
+    SERVER_CONNECTED, /* Ready to register */
+    SERVER_REGISTERING = 2000,
+    SERVER_REGISTRATION_ERROR,
+    SERVER_REGISTERED, /* Ready to call a peer */
+    SERVER_CLOSED, /* server connection closed by us or the server */
+    PEER_CONNECTING = 3000,
+    PEER_CONNECTION_ERROR,
+    PEER_CONNECTED,
+    PEER_CALL_NEGOTIATING = 4000,
+    PEER_CALL_STARTED,
+    PEER_CALL_STOPPING,
+    PEER_CALL_STOPPED,
+    PEER_CALL_ERROR,
+};
+
+typedef struct _WebRTC
+{
+  jobject java_webrtc;
+  GstElement *pipe;
+  GThread *thread;
+  GMainLoop *loop;
+  GMutex lock;
+  GCond cond;
+  ANativeWindow *native_window;
+  SoupWebsocketConnection *ws_conn;
+  gchar *signalling_server;
+  gchar *peer_id;
+  enum AppState app_state;
+  GstElement *webrtcbin, *video_sink;
+} WebRTC;
+
+static pthread_key_t current_jni_env;
+static JavaVM *java_vm;
+static jfieldID native_webrtc_field_id;
+
+static gboolean
+cleanup_and_quit_loop (WebRTC * webrtc, const gchar * msg, enum AppState state)
+{
+  if (msg)
+    g_printerr ("%s\n", msg);
+  if (state > 0)
+    webrtc->app_state = state;
+
+  if (webrtc->ws_conn) {
+    if (soup_websocket_connection_get_state (webrtc->ws_conn) ==
+        SOUP_WEBSOCKET_STATE_OPEN)
+      /* This will call us again */
+      soup_websocket_connection_close (webrtc->ws_conn, 1000, "");
+    else
+      g_object_unref (webrtc->ws_conn);
+  }
+
+  if (webrtc->loop) {
+    g_main_loop_quit (webrtc->loop);
+    webrtc->loop = NULL;
+  }
+
+  if (webrtc->pipe) {
+    gst_element_set_state (webrtc->pipe, GST_STATE_NULL);
+    gst_object_unref (webrtc->pipe);
+    webrtc->pipe = NULL;
+  }
+
+  /* To allow usage as a GSourceFunc */
+  return G_SOURCE_REMOVE;
+}
+
+static gchar*
+get_string_from_json_object (JsonObject * object)
+{
+  JsonNode *root;
+  JsonGenerator *generator;
+  gchar *text;
+
+  /* Make it the root node */
+  root = json_node_init_object (json_node_alloc (), object);
+  generator = json_generator_new ();
+  json_generator_set_root (generator, root);
+  text = json_generator_to_data (generator, NULL);
+
+  /* Release everything */
+  g_object_unref (generator);
+  json_node_free (root);
+  return text;
+}
+
+static GstElement *
+handle_media_stream (GstPad * pad, GstElement * pipe, const char * convert_name,
+                     const char * sink_name)
+{
+  GstPad *qpad;
+  GstElement *q, *conv, *sink;
+  GstPadLinkReturn ret;
+
+  q = gst_element_factory_make ("queue", NULL);
+  g_assert (q);
+  conv = gst_element_factory_make (convert_name, NULL);
+  g_assert (conv);
+  sink = gst_element_factory_make (sink_name, NULL);
+  g_assert (sink);
+  if (g_strcmp0 (convert_name, "audioconvert") == 0) {
+    GstElement *resample = gst_element_factory_make ("audioresample", NULL);
+    g_assert_nonnull (resample);
+    gst_bin_add_many (GST_BIN (pipe), q, conv, resample, sink, NULL);
+    gst_element_sync_state_with_parent (q);
+    gst_element_sync_state_with_parent (conv);
+    gst_element_sync_state_with_parent (resample);
+    gst_element_sync_state_with_parent (sink);
+    gst_element_link_many (q, conv, resample, sink, NULL);
+  } else {
+    gst_bin_add_many (GST_BIN (pipe), q, conv, sink, NULL);
+    gst_element_sync_state_with_parent (q);
+    gst_element_sync_state_with_parent (conv);
+    gst_element_sync_state_with_parent (sink);
+    gst_element_link_many (q, conv, sink, NULL);
+  }
+
+  qpad = gst_element_get_static_pad (q, "sink");
+
+  ret = gst_pad_link (pad, qpad);
+  g_assert (ret == GST_PAD_LINK_OK);
+  gst_object_unref (qpad);
+
+  return sink;
+}
+
+static void
+on_incoming_decodebin_stream (GstElement * decodebin, GstPad * pad,
+                              WebRTC * webrtc)
+{
+  GstCaps *caps;
+  const gchar *name;
+
+  if (!gst_pad_has_current_caps (pad)) {
+    g_printerr ("Pad '%s' has no caps, can't do anything, ignoring\n",
+                GST_PAD_NAME (pad));
+    return;
+  }
+
+  caps = gst_pad_get_current_caps (pad);
+  name = gst_structure_get_name (gst_caps_get_structure (caps, 0));
+
+  if (g_str_has_prefix (name, "video")) {
+    GstElement *sink = handle_media_stream (pad, webrtc->pipe, "videoconvert", "glimagesink");
+    if (webrtc->video_sink == NULL) {
+      webrtc->video_sink = sink;
+      if (webrtc->native_window)
+        gst_video_overlay_set_window_handle (GST_VIDEO_OVERLAY (sink), (gpointer) webrtc->native_window);
+    }
+  } else if (g_str_has_prefix (name, "audio")) {
+    handle_media_stream (pad, webrtc->pipe, "audioconvert", "autoaudiosink");
+  } else {
+    g_printerr ("Unknown pad %s, ignoring", GST_PAD_NAME (pad));
+  }
+
+  gst_caps_unref (caps);
+}
+
+static void
+on_incoming_stream (GstElement * webrtcbin, GstPad * pad, WebRTC * webrtc)
+{
+  GstElement *decodebin;
+
+  if (GST_PAD_DIRECTION (pad) != GST_PAD_SRC)
+    return;
+
+  decodebin = gst_element_factory_make ("decodebin", NULL);
+  g_signal_connect (decodebin, "pad-added",
+                    G_CALLBACK (on_incoming_decodebin_stream), webrtc);
+  gst_bin_add (GST_BIN (webrtc->pipe), decodebin);
+  gst_element_sync_state_with_parent (decodebin);
+  gst_element_link (webrtcbin, decodebin);
+}
+
+static void
+send_ice_candidate_message (GstElement * webrtcbin G_GNUC_UNUSED, guint mlineindex,
+                            gchar * candidate, WebRTC * webrtc)
+{
+  gchar *text;
+  JsonObject *ice, *msg;
+
+  if (webrtc->app_state < PEER_CALL_NEGOTIATING) {
+    cleanup_and_quit_loop (webrtc, "Can't send ICE, not in call", APP_STATE_ERROR);
+    return;
+  }
+
+  ice = json_object_new ();
+  json_object_set_string_member (ice, "candidate", candidate);
+  json_object_set_int_member (ice, "sdpMLineIndex", mlineindex);
+  msg = json_object_new ();
+  json_object_set_object_member (msg, "ice", ice);
+  text = get_string_from_json_object (msg);
+  json_object_unref (msg);
+
+  soup_websocket_connection_send_text (webrtc->ws_conn, text);
+  g_free (text);
+}
+
+static void
+send_sdp_offer (WebRTC * webrtc, GstWebRTCSessionDescription * offer)
+{
+  gchar *text;
+  JsonObject *msg, *sdp;
+
+  if (webrtc->app_state < PEER_CALL_NEGOTIATING) {
+    cleanup_and_quit_loop (webrtc, "Can't send offer, not in call", APP_STATE_ERROR);
+    return;
+  }
+
+  text = gst_sdp_message_as_text (offer->sdp);
+  g_print ("Sending offer:\n%s\n", text);
+
+  sdp = json_object_new ();
+  json_object_set_string_member (sdp, "type", "offer");
+  json_object_set_string_member (sdp, "sdp", text);
+  g_free (text);
+
+  msg = json_object_new ();
+  json_object_set_object_member (msg, "sdp", sdp);
+  text = get_string_from_json_object (msg);
+  json_object_unref (msg);
+
+  soup_websocket_connection_send_text (webrtc->ws_conn, text);
+  g_free (text);
+}
+
+/* Offer created by our pipeline, to be sent to the peer */
+static void
+on_offer_created (GstPromise * promise, WebRTC * webrtc)
+{
+  GstWebRTCSessionDescription *offer = NULL;
+  const GstStructure *reply;
+  gchar *desc;
+
+  g_assert (webrtc->app_state == PEER_CALL_NEGOTIATING);
+
+  g_assert (gst_promise_wait(promise) == GST_PROMISE_RESULT_REPLIED);
+  reply = gst_promise_get_reply (promise);
+  gst_structure_get (reply, "offer",
+                     GST_TYPE_WEBRTC_SESSION_DESCRIPTION, &offer, NULL);
+  gst_promise_unref (promise);
+
+  promise = gst_promise_new ();
+  g_signal_emit_by_name (webrtc->webrtcbin, "set-local-description", offer, promise);
+  gst_promise_interrupt (promise);
+  gst_promise_unref (promise);
+
+  /* Send offer to peer */
+  send_sdp_offer (webrtc, offer);
+  gst_webrtc_session_description_free (offer);
+}
+
+static void
+on_negotiation_needed (GstElement * element, WebRTC * webrtc)
+{
+  GstPromise *promise;
+
+  webrtc->app_state = PEER_CALL_NEGOTIATING;
+  promise = gst_promise_new_with_change_func (on_offer_created, webrtc, NULL);;
+  g_signal_emit_by_name (webrtc->webrtcbin, "create-offer", NULL, promise);
+}
+
+#define RTP_CAPS_OPUS "application/x-rtp,media=audio,encoding-name=OPUS,payload=100"
+#define RTP_CAPS_VP8 "application/x-rtp,media=video,encoding-name=VP8,payload=101"
+
+static gboolean
+start_pipeline (WebRTC * webrtc)
+{
+  GstStateChangeReturn ret;
+  GError *error = NULL;
+  GstPad *pad;
+
+  webrtc->pipe =
+          gst_parse_launch ("webrtcbin name=sendrecv "
+                            "videotestsrc pattern=ball ! queue ! vp8enc deadline=1 error-resilient=default ! rtpvp8pay picture-id-mode=15-bit ! "
+                            "queue ! " RTP_CAPS_VP8 " ! sendrecv.sink_0 "
+                            "openslessrc ! queue ! audioconvert ! audioresample ! audiorate ! queue ! opusenc ! rtpopuspay ! "
+                            "queue ! " RTP_CAPS_OPUS " ! sendrecv.sink_1 ",
+                            &error);
+
+  if (error) {
+    g_printerr ("Failed to parse launch: %s\n", error->message);
+    g_error_free (error);
+    goto err;
+  }
+
+  webrtc->webrtcbin = gst_bin_get_by_name (GST_BIN (webrtc->pipe), "sendrecv");
+  g_assert (webrtc->webrtcbin != NULL);
+
+  pad = gst_element_get_static_pad (webrtc->webrtcbin, "sink_0");
+  gst_util_set_object_arg (G_OBJECT (pad), "fec-type", "ulp-red");
+  g_object_set (pad, "do-nack", FALSE);
+  gst_object_unref (pad);
+
+  /* This is the gstwebrtc entry point where we create the offer and so on. It
+   * will be called when the pipeline goes to PLAYING. */
+  g_signal_connect (webrtc->webrtcbin, "on-negotiation-needed",
+                    G_CALLBACK (on_negotiation_needed), webrtc);
+  /* We need to transmit this ICE candidate to the browser via the websockets
+   * signalling server. Incoming ice candidates from the browser need to be
+   * added by us too, see on_server_message() */
+  g_signal_connect (webrtc->webrtcbin, "on-ice-candidate",
+                    G_CALLBACK (send_ice_candidate_message), webrtc);
+  /* Incoming streams will be exposed via this signal */
+  g_signal_connect (webrtc->webrtcbin, "pad-added", G_CALLBACK (on_incoming_stream),
+                    webrtc);
+  /* Lifetime is the same as the pipeline itself */
+  gst_object_unref (webrtc->webrtcbin);
+
+  g_print ("Starting pipeline\n");
+  ret = gst_element_set_state (GST_ELEMENT (webrtc->pipe), GST_STATE_PLAYING);
+  if (ret == GST_STATE_CHANGE_FAILURE)
+    goto err;
+
+  return TRUE;
+
+err:
+  if (webrtc->pipe)
+    g_clear_object (&webrtc->pipe);
+  if (webrtc->webrtcbin)
+    webrtc->webrtcbin = NULL;
+  return FALSE;
+}
+
+static gboolean
+setup_call (WebRTC * webrtc)
+{
+  gchar *msg;
+
+  if (soup_websocket_connection_get_state (webrtc->ws_conn) !=
+      SOUP_WEBSOCKET_STATE_OPEN)
+    return FALSE;
+
+  if (!webrtc->peer_id)
+    return FALSE;
+
+  g_print ("Setting up signalling server call with %s\n", webrtc->peer_id);
+  webrtc->app_state = PEER_CONNECTING;
+  msg = g_strdup_printf ("SESSION %s", webrtc->peer_id);
+  soup_websocket_connection_send_text (webrtc->ws_conn, msg);
+  g_free (msg);
+  return TRUE;
+}
+
+static gboolean
+register_with_server (WebRTC * webrtc)
+{
+  gchar *hello;
+  gint32 our_id;
+
+  if (soup_websocket_connection_get_state (webrtc->ws_conn) !=
+      SOUP_WEBSOCKET_STATE_OPEN)
+    return FALSE;
+
+  our_id = g_random_int_range (10, 10000);
+  g_print ("Registering id %i with server\n", our_id);
+  webrtc->app_state = SERVER_REGISTERING;
+
+  /* Register with the server with a random integer id. Reply will be received
+   * by on_server_message() */
+  hello = g_strdup_printf ("HELLO %i", our_id);
+  soup_websocket_connection_send_text (webrtc->ws_conn, hello);
+  g_free (hello);
+
+  return TRUE;
+}
+
+static void
+on_server_closed (SoupWebsocketConnection * conn G_GNUC_UNUSED,
+                  WebRTC * webrtc)
+{
+  webrtc->app_state = SERVER_CLOSED;
+  cleanup_and_quit_loop (webrtc, "Server connection closed", 0);
+}
+
+/* One mega message handler for our asynchronous calling mechanism */
+static void
+on_server_message (SoupWebsocketConnection * conn, SoupWebsocketDataType type,
+                   GBytes * message, WebRTC * webrtc)
+{
+  gsize size;
+  gchar *text, *data;
+
+  switch (type) {
+    case SOUP_WEBSOCKET_DATA_BINARY:
+      g_printerr ("Received unknown binary message, ignoring\n");
+          g_bytes_unref (message);
+          return;
+    case SOUP_WEBSOCKET_DATA_TEXT:
+      data = g_bytes_unref_to_data (message, &size);
+          /* Convert to NULL-terminated string */
+          text = g_strndup (data, size);
+          g_free (data);
+          break;
+    default:
+      g_assert_not_reached ();
+  }
+
+  /* Server has accepted our registration, we are ready to send commands */
+  if (g_strcmp0 (text, "HELLO") == 0) {
+    if (webrtc->app_state != SERVER_REGISTERING) {
+      cleanup_and_quit_loop (webrtc, "ERROR: Received HELLO when not registering",
+                             APP_STATE_ERROR);
+      goto out;
+    }
+    webrtc->app_state = SERVER_REGISTERED;
+    g_print ("Registered with server\n");
+    /* Ask signalling server to connect us with a specific peer */
+    if (!setup_call (webrtc)) {
+      cleanup_and_quit_loop (webrtc, "ERROR: Failed to setup call", PEER_CALL_ERROR);
+      goto out;
+    }
+    /* Call has been setup by the server, now we can start negotiation */
+  } else if (g_strcmp0 (text, "SESSION_OK") == 0) {
+    if (webrtc->app_state != PEER_CONNECTING) {
+      cleanup_and_quit_loop (webrtc, "ERROR: Received SESSION_OK when not calling",
+                             PEER_CONNECTION_ERROR);
+      goto out;
+    }
+
+    webrtc->app_state = PEER_CONNECTED;
+    /* Start negotiation (exchange SDP and ICE candidates) */
+    if (!start_pipeline (webrtc))
+      cleanup_and_quit_loop (webrtc, "ERROR: failed to start pipeline",
+                             PEER_CALL_ERROR);
+    /* Handle errors */
+  } else if (g_str_has_prefix (text, "ERROR")) {
+    switch (webrtc->app_state) {
+      case SERVER_CONNECTING:
+        webrtc->app_state = SERVER_CONNECTION_ERROR;
+            break;
+      case SERVER_REGISTERING:
+        webrtc->app_state = SERVER_REGISTRATION_ERROR;
+            break;
+      case PEER_CONNECTING:
+        webrtc->app_state = PEER_CONNECTION_ERROR;
+            break;
+      case PEER_CONNECTED:
+      case PEER_CALL_NEGOTIATING:
+        webrtc->app_state = PEER_CALL_ERROR;
+      default:
+        webrtc->app_state = APP_STATE_ERROR;
+    }
+    cleanup_and_quit_loop (webrtc, text, 0);
+    /* Look for JSON messages containing SDP and ICE candidates */
+  } else {
+    JsonNode *root;
+    JsonObject *object;
+    JsonParser *parser = json_parser_new ();
+    if (!json_parser_load_from_data (parser, text, -1, NULL)) {
+      g_printerr ("Unknown message '%s', ignoring", text);
+      g_object_unref (parser);
+      goto out;
+    }
+
+    root = json_parser_get_root (parser);
+    if (!JSON_NODE_HOLDS_OBJECT (root)) {
+      g_printerr ("Unknown json message '%s', ignoring", text);
+      g_object_unref (parser);
+      goto out;
+    }
+
+    object = json_node_get_object (root);
+    /* Check type of JSON message */
+    if (json_object_has_member (object, "sdp")) {
+      int ret;
+      const gchar *text;
+      GstSDPMessage *sdp;
+      GstWebRTCSessionDescription *answer;
+
+      g_assert (webrtc->app_state == PEER_CALL_NEGOTIATING);
+
+      g_assert (json_object_has_member (object, "type"));
+      /* In this example, we always create the offer and receive one answer.
+       * See tests/examples/webrtcbidirectional.c in gst-plugins-bad for how to
+       * handle offers from peers and reply with answers using webrtcbin. */
+      g_assert_cmpstr (json_object_get_string_member (object, "type"), ==,
+                       "answer");
+
+      text = json_object_get_string_member (object, "sdp");
+
+      g_print ("Received answer:\n%s\n", text);
+
+      ret = gst_sdp_message_new (&sdp);
+      g_assert (ret == GST_SDP_OK);
+
+      ret = gst_sdp_message_parse_buffer (text, strlen (text), sdp);
+      g_assert (ret == GST_SDP_OK);
+
+      answer = gst_webrtc_session_description_new (GST_WEBRTC_SDP_TYPE_ANSWER,
+                                                   sdp);
+      g_assert (answer);
+
+      /* Set remote description on our pipeline */
+      {
+        GstPromise *promise = gst_promise_new ();
+        g_signal_emit_by_name (webrtc->webrtcbin, "set-remote-description", answer,
+                               promise);
+        gst_promise_interrupt (promise);
+        gst_promise_unref (promise);
+      }
+
+      webrtc->app_state = PEER_CALL_STARTED;
+    } else if (json_object_has_member (object, "ice")) {
+      JsonObject *ice;
+      const gchar *candidate;
+      gint sdpmlineindex;
+
+      ice = json_object_get_object_member (object, "ice");
+      candidate = json_object_get_string_member (ice, "candidate");
+      sdpmlineindex = json_object_get_int_member (ice, "sdpMLineIndex");
+
+      /* Add ice candidate sent by remote peer */
+      g_signal_emit_by_name (webrtc->webrtcbin, "add-ice-candidate", sdpmlineindex,
+                             candidate);
+    } else {
+      g_printerr ("Ignoring unknown JSON message:\n%s\n", text);
+    }
+    g_object_unref (parser);
+  }
+
+    out:
+  g_free (text);
+}
+
+static void
+on_server_connected (SoupSession * session, GAsyncResult * res,
+                     WebRTC * webrtc)
+{
+  GError *error = NULL;
+
+  webrtc->ws_conn = soup_session_websocket_connect_finish (session, res, &error);
+  if (error) {
+    cleanup_and_quit_loop (webrtc, error->message, SERVER_CONNECTION_ERROR);
+    g_error_free (error);
+    return;
+  }
+
+  g_assert (webrtc->ws_conn != NULL);
+
+  webrtc->app_state = SERVER_CONNECTED;
+  g_print ("Connected to signalling server\n");
+
+  g_signal_connect (webrtc->ws_conn, "closed", G_CALLBACK (on_server_closed), webrtc);
+  g_signal_connect (webrtc->ws_conn, "message", G_CALLBACK (on_server_message), webrtc);
+
+  /* Register with the server so it knows about us and can accept commands */
+  register_with_server (webrtc);
+}
+
+/*
+ * Connect to the signalling server. This is the entrypoint for everything else.
+ */
+static gboolean
+connect_to_websocket_server_async (WebRTC * webrtc)
+{
+  SoupLogger *logger;
+  SoupMessage *message;
+  SoupSession *session;
+  const char *https_aliases[] = {"wss", NULL};
+  const gchar *ca_certs;
+
+  ca_certs = g_getenv("CA_CERTIFICATES");
+  g_assert (ca_certs != NULL);
+  g_print ("ca-certificates %s", ca_certs);
+  session = soup_session_new_with_options (SOUP_SESSION_SSL_STRICT, FALSE,
+          //                                 SOUP_SESSION_SSL_USE_SYSTEM_CA_FILE, TRUE,
+                                           SOUP_SESSION_SSL_CA_FILE, ca_certs,
+                                           SOUP_SESSION_HTTPS_ALIASES, https_aliases, NULL);
+
+  logger = soup_logger_new (SOUP_LOGGER_LOG_BODY, -1);
+  soup_session_add_feature (session, SOUP_SESSION_FEATURE (logger));
+  g_object_unref (logger);
+
+  message = soup_message_new (SOUP_METHOD_GET, webrtc->signalling_server);
+
+  g_print ("Connecting to server...\n");
+
+  /* Once connected, we will register */
+  soup_session_websocket_connect_async (session, message, NULL, NULL, NULL,
+                                        (GAsyncReadyCallback) on_server_connected, webrtc);
+  webrtc->app_state = SERVER_CONNECTING;
+
+  return G_SOURCE_REMOVE;
+}
+
+/* Register this thread with the VM */
+static JNIEnv *
+attach_current_thread (void)
+{
+  JNIEnv *env;
+  JavaVMAttachArgs args;
+
+  GST_DEBUG ("Attaching thread %p", g_thread_self ());
+  args.version = JNI_VERSION_1_4;
+  args.name = NULL;
+  args.group = NULL;
+
+  if ((*java_vm)->AttachCurrentThread (java_vm, &env, &args) < 0) {
+    GST_ERROR ("Failed to attach current thread");
+    return NULL;
+  }
+
+  return env;
+}
+
+/* Unregister this thread from the VM */
+static void
+detach_current_thread (void *env)
+{
+  GST_DEBUG ("Detaching thread %p", g_thread_self ());
+  (*java_vm)->DetachCurrentThread (java_vm);
+}
+
+/* Retrieve the JNI environment for this thread */
+static JNIEnv *
+get_jni_env (void)
+{
+  JNIEnv *env;
+
+  if ((env = pthread_getspecific (current_jni_env)) == NULL) {
+    env = attach_current_thread ();
+    pthread_setspecific (current_jni_env, env);
+  }
+
+  return env;
+}
+
+/*
+ * Java Bindings
+ */
+
+static void
+native_end_call (JNIEnv * env, jobject thiz)
+{
+  WebRTC *webrtc = GET_CUSTOM_DATA (env, thiz, native_webrtc_field_id);
+
+  if (!webrtc)
+    return;
+
+  g_mutex_lock (&webrtc->lock);
+  if (webrtc->loop) {
+    GThread *thread = webrtc->thread;
+
+    GST_INFO("Ending current call");
+    cleanup_and_quit_loop (webrtc, NULL, 0);
+    webrtc->thread = NULL;
+    g_mutex_unlock (&webrtc->lock);
+    g_thread_join (thread);
+  } else {
+    g_mutex_unlock (&webrtc->lock);
+  }
+}
+
+static gboolean
+_unlock_mutex (GMutex * m)
+{
+  g_mutex_unlock (m);
+  return G_SOURCE_REMOVE;
+}
+
+static gpointer
+_call_thread (WebRTC * webrtc)
+{
+  GMainContext *context = NULL;
+  JNIEnv *env = attach_current_thread();
+
+  g_mutex_lock (&webrtc->lock);
+
+  context = g_main_context_new ();
+  webrtc->loop = g_main_loop_new (context, FALSE);
+  g_main_context_invoke (context, (GSourceFunc) _unlock_mutex, &webrtc->lock);
+  g_main_context_invoke (context, (GSourceFunc) connect_to_websocket_server_async, webrtc);
+  g_main_context_push_thread_default (context);
+  g_cond_broadcast (&webrtc->cond);
+  g_main_loop_run (webrtc->loop);
+  g_main_context_pop_thread_default (context);
+
+  detach_current_thread (env);
+}
+
+static void
+native_call_other_party(JNIEnv * env, jobject thiz)
+{
+  WebRTC *webrtc = GET_CUSTOM_DATA (env, thiz, native_webrtc_field_id);
+
+  if (!webrtc)
+    return;
+
+  if (webrtc->thread)
+    native_end_call (env, thiz);
+
+  GST_INFO("calling other party");
+
+  webrtc->thread = g_thread_new("webrtc", (GThreadFunc) _call_thread, webrtc);
+  g_mutex_lock (&webrtc->lock);
+  while (!webrtc->loop)
+    g_cond_wait (&webrtc->cond, &webrtc->lock);
+  g_mutex_unlock (&webrtc->lock);
+}
+
+static void
+native_new (JNIEnv * env, jobject thiz)
+{
+  WebRTC *webrtc = g_new0 (WebRTC, 1);
+
+  SET_CUSTOM_DATA (env, thiz, native_webrtc_field_id, webrtc);
+  webrtc->java_webrtc = (*env)->NewGlobalRef (env, thiz);
+
+  webrtc->signalling_server = g_strdup (DEFAULT_SIGNALLING_SERVER);
+
+  g_mutex_init (&webrtc->lock);
+  g_cond_init (&webrtc->cond);
+}
+
+static void
+native_free (JNIEnv * env, jobject thiz)
+{
+  WebRTC *webrtc = GET_CUSTOM_DATA (env, thiz, native_webrtc_field_id);
+
+  if (!webrtc)
+    return;
+
+  (*env)->DeleteGlobalRef (env, webrtc->java_webrtc);
+
+  native_end_call (env, thiz);
+
+  g_cond_clear (&webrtc->cond);
+  g_mutex_clear (&webrtc->lock);
+  g_free (webrtc->peer_id);
+  g_free (webrtc->signalling_server);
+  g_free (webrtc);
+  SET_CUSTOM_DATA (env, thiz, native_webrtc_field_id, NULL);
+}
+
+static void
+native_class_init (JNIEnv * env, jclass klass)
+{
+  native_webrtc_field_id =
+      (*env)->GetFieldID (env, klass, "native_webrtc", "J");
+
+  if (!native_webrtc_field_id) {
+    static const gchar *message =
+        "The calling class does not implement all necessary interface methods";
+    jclass exception_class = (*env)->FindClass (env, "java/lang/Exception");
+    __android_log_print (ANDROID_LOG_ERROR, "GstPlayer", "%s", message);
+    (*env)->ThrowNew (env, exception_class, message);
+  }
+
+  gst_debug_set_threshold_from_string ("gl*:7", FALSE);
+}
+
+static void
+native_set_surface (JNIEnv * env, jobject thiz, jobject surface)
+{
+  WebRTC *webrtc= GET_CUSTOM_DATA (env, thiz, native_webrtc_field_id);
+  ANativeWindow *new_native_window;
+
+  if (!webrtc)
+    return;
+
+  new_native_window = surface ? ANativeWindow_fromSurface (env, surface) : NULL;
+  GST_DEBUG ("Received surface %p (native window %p)", surface,
+             new_native_window);
+
+  if (webrtc->native_window) {
+    ANativeWindow_release (webrtc->native_window);
+  }
+
+  webrtc->native_window = new_native_window;
+  if (webrtc->video_sink)
+    gst_video_overlay_set_window_handle (GST_VIDEO_OVERLAY (webrtc->video_sink), (guintptr) new_native_window);
+}
+
+static void
+native_set_signalling_server (JNIEnv * env, jobject thiz, jstring server) {
+  WebRTC *webrtc= GET_CUSTOM_DATA (env, thiz, native_webrtc_field_id);
+  const gchar *s;
+
+  if (!webrtc)
+    return;
+
+  s = (*env)->GetStringUTFChars(env, server, NULL);
+  if (webrtc->signalling_server)
+    g_free (webrtc->signalling_server);
+  webrtc->signalling_server = g_strdup (s);
+  (*env)->ReleaseStringUTFChars(env, server, s);
+}
+
+static void
+native_set_call_id(JNIEnv * env, jobject thiz, jstring peer_id) {
+  WebRTC *webrtc = GET_CUSTOM_DATA (env, thiz, native_webrtc_field_id);
+  const gchar *s;
+
+  if (!webrtc)
+    return;
+
+  s = (*env)->GetStringUTFChars(env, peer_id, NULL);
+  g_free (webrtc->peer_id);
+  webrtc->peer_id = g_strdup (s);
+  (*env)->ReleaseStringUTFChars(env, peer_id, s);
+}
+
+/* List of implemented native methods */
+static JNINativeMethod native_methods[] = {
+  {"nativeClassInit", "()V", (void *) native_class_init},
+  {"nativeNew", "()V", (void *) native_new},
+  {"nativeFree", "()V", (void *) native_free},
+  {"nativeSetSurface", "(Landroid/view/Surface;)V",
+      (void *) native_set_surface},
+  {"nativeSetSignallingServer", "(Ljava/lang/String;)V",
+      (void *) native_set_signalling_server},
+  {"nativeSetCallID", "(Ljava/lang/String;)V",
+      (void *) native_set_call_id},
+  {"nativeCallOtherParty", "()V",
+      (void *) native_call_other_party},
+  {"nativeEndCall", "()V",
+      (void *) native_end_call}
+};
+
+/* Library initializer */
+jint
+JNI_OnLoad (JavaVM * vm, void *reserved)
+{
+  JNIEnv *env = NULL;
+
+  java_vm = vm;
+
+  if ((*vm)->GetEnv (vm, (void **) &env, JNI_VERSION_1_4) != JNI_OK) {
+    __android_log_print (ANDROID_LOG_ERROR, "GstPlayer",
+        "Could not retrieve JNIEnv");
+    return 0;
+  }
+  jclass klass = (*env)->FindClass (env, "org/freedesktop/gstreamer/WebRTC");
+  if (!klass) {
+    __android_log_print (ANDROID_LOG_ERROR, "GstWebRTC",
+        "Could not retrieve class org.freedesktop.gstreamer.WebRTC");
+    return 0;
+  }
+  if ((*env)->RegisterNatives (env, klass, native_methods,
+          G_N_ELEMENTS (native_methods))) {
+    __android_log_print (ANDROID_LOG_ERROR, "GstWebRTC",
+        "Could not register native methods for org.freedesktop.gstreamer.WebRTC");
+    return 0;
+  }
+
+  pthread_key_create (&current_jni_env, detach_current_thread);
+
+  return JNI_VERSION_1_4;
+}
diff --git a/webrtc/android/app/src/main/res/layout/main.xml b/webrtc/android/app/src/main/res/layout/main.xml
new file mode 100644 (file)
index 0000000..77c7dad
--- /dev/null
@@ -0,0 +1,124 @@
+<?xml version="1.0" encoding="utf-8"?>\r
+<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"\r
+    xmlns:app="http://schemas.android.com/apk/res-auto"\r
+    xmlns:tools="http://schemas.android.com/tools"\r
+    android:id="@+id/layout"\r
+    android:layout_width="match_parent"\r
+    android:layout_height="match_parent"\r
+    android:gravity="center_vertical">\r
+\r
+\r
+    <TableLayout\r
+        android:id="@+id/input"\r
+        android:layout_width="0dp"\r
+        android:layout_height="0dp"\r
+        android:stretchColumns="1"\r
+        app:layout_constraintBottom_toTopOf="@+id/controls"\r
+        app:layout_constraintEnd_toEndOf="parent"\r
+        app:layout_constraintStart_toStartOf="parent"\r
+        app:layout_constraintTop_toTopOf="parent"\r
+        app:layout_constraintVertical_bias="1.0"\r
+        app:layout_constraintVertical_chainStyle="spread_inside">\r
+\r
+        <TableRow\r
+            android:layout_width="match_parent"\r
+            android:layout_height="match_parent">\r
+\r
+            <TextView\r
+                android:id="@+id/URL"\r
+                android:layout_width="wrap_content"\r
+                android:layout_height="wrap_content"\r
+                android:layout_gravity="right|center_vertical"\r
+                android:padding="8dp"\r
+                android:text="URL" />\r
+\r
+            <EditText\r
+                android:id="@+id/URLText"\r
+                android:layout_width="wrap_content"\r
+                android:layout_height="wrap_content"\r
+                android:layout_margin="8dp"\r
+                android:inputType="textUri"\r
+                android:text="wss://10.6.5.229:8443" />\r
+        </TableRow>\r
+\r
+        <TableRow\r
+            android:layout_width="match_parent"\r
+            android:layout_height="match_parent">\r
+\r
+            <TextView\r
+                android:id="@+id/ID"\r
+                android:layout_width="wrap_content"\r
+                android:layout_height="wrap_content"\r
+                android:layout_gravity="right|center_vertical"\r
+                android:padding="8dp"\r
+                android:text="ID" />\r
+\r
+            <EditText\r
+                android:id="@+id/IDText"\r
+                android:layout_width="match_parent"\r
+                android:layout_height="wrap_content"\r
+                android:layout_margin="8dp"\r
+                android:inputType="number"\r
+                android:text="ID" />\r
+        </TableRow>\r
+\r
+    </TableLayout>\r
+\r
+\r
+    <android.support.constraint.ConstraintLayout\r
+        android:id="@+id/controls"\r
+        android:layout_width="0dp"\r
+        android:layout_height="wrap_content"\r
+        app:layout_constraintBottom_toTopOf="@id/surface_video"\r
+        app:layout_constraintEnd_toEndOf="parent"\r
+        app:layout_constraintHorizontal_bias="0.0"\r
+        app:layout_constraintStart_toStartOf="parent"\r
+        app:layout_constraintTop_toBottomOf="@id/input">\r
+\r
+        <ImageButton\r
+            android:id="@+id/button_play"\r
+            android:layout_width="wrap_content"\r
+            android:layout_height="wrap_content"\r
+            android:layout_marginStart="8dp"\r
+            android:contentDescription="@string/button_play"\r
+            android:src="@android:drawable/ic_media_play"\r
+            android:text="@string/button_play"\r
+            app:layout_constraintBottom_toBottomOf="parent"\r
+            app:layout_constraintEnd_toStartOf="@+id/button_pause"\r
+            app:layout_constraintHorizontal_chainStyle="packed"\r
+            app:layout_constraintStart_toStartOf="parent" />\r
+\r
+        <ImageButton\r
+            android:id="@+id/button_pause"\r
+            android:layout_width="wrap_content"\r
+            android:layout_height="wrap_content"\r
+            android:layout_marginEnd="2dp"\r
+            android:contentDescription="@string/button_pause"\r
+            android:src="@android:drawable/ic_media_pause"\r
+            android:text="@string/button_pause"\r
+            app:layout_constraintEnd_toEndOf="parent"\r
+            app:layout_constraintStart_toEndOf="@+id/button_play"\r
+            app:layout_constraintTop_toTopOf="@+id/button_play" />\r
+\r
+    </android.support.constraint.ConstraintLayout>\r
+\r
+\r
+    <org.freedesktop.gstreamer.webrtc.GStreamerSurfaceView\r
+        android:id="@+id/surface_video"\r
+        android:layout_width="wrap_content"\r
+        android:layout_height="wrap_content"\r
+        android:layout_marginTop="8dp"\r
+        app:layout_constraintBottom_toBottomOf="parent"\r
+        app:layout_constraintEnd_toStartOf="parent"\r
+        app:layout_constraintStart_toStartOf="parent"\r
+        app:layout_constraintTop_toBottomOf="@id/controls"\r
+        app:layout_constraintVertical_bias="1.0" />\r
+\r
+    <android.support.constraint.Guideline\r
+        android:id="@+id/guideline"\r
+        android:layout_width="wrap_content"\r
+        android:layout_height="wrap_content"\r
+        android:orientation="vertical"\r
+        app:layout_constraintGuide_begin="50dp" />\r
+\r
+</android.support.constraint.ConstraintLayout>\r
diff --git a/webrtc/android/app/src/main/res/values/strings.xml b/webrtc/android/app/src/main/res/values/strings.xml
new file mode 100644 (file)
index 0000000..859dca1
--- /dev/null
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <string name="app_name">GStreamer WebRTC</string>
+    <string name="button_play">Play</string>
+    <string name="button_pause">Pause</string>
+</resources>
diff --git a/webrtc/android/build.gradle b/webrtc/android/build.gradle
new file mode 100644 (file)
index 0000000..7e01a0a
--- /dev/null
@@ -0,0 +1,25 @@
+// Top-level build file where you can add configuration options common to all sub-projects/modules.
+
+buildscript {
+    repositories {
+        jcenter()
+        google()
+    }
+    dependencies {
+        classpath 'com.android.tools.build:gradle:3.0.1'
+
+        // NOTE: Do not place your application dependencies here; they belong
+        // in the individual module build.gradle files
+    }
+}
+
+allprojects {
+    repositories {
+        jcenter()
+        google()
+    }
+}
+
+task clean(type: Delete) {
+    delete rootProject.buildDir
+}
diff --git a/webrtc/android/gradle.properties b/webrtc/android/gradle.properties
new file mode 100644 (file)
index 0000000..0e82e65
--- /dev/null
@@ -0,0 +1,23 @@
+# Project-wide Gradle settings.
+
+# IDE (e.g. Android Studio) users:
+# Gradle settings configured through the IDE *will override*
+# any settings specified in this file.
+
+# For more details on how to configure your build environment visit
+# http://www.gradle.org/docs/current/userguide/build_environment.html
+
+# Specifies the JVM arguments used for the daemon process.
+# The setting is particularly useful for tweaking memory settings.
+# Default value: -Xmx10248m -XX:MaxPermSize=256m
+# org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
+
+# When configured, Gradle will run in incubating parallel mode.
+# This option should only be used with decoupled projects. More details, visit
+# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
+# org.gradle.parallel=true
+
+# gstAndroidRoot can be set to point to the unpacked GStreamer android top-level directory
+# containing each architecture in subdirectories, or else set the GSTREAMER_ROOT_ANDROID
+# environment variable to that location
+# gstAndroidRoot=/home/matt/Projects/cerbero/build/dist/android_universal
diff --git a/webrtc/android/gradlew b/webrtc/android/gradlew
new file mode 100755 (executable)
index 0000000..9d82f78
--- /dev/null
@@ -0,0 +1,160 @@
+#!/usr/bin/env bash
+
+##############################################################################
+##
+##  Gradle start up script for UN*X
+##
+##############################################################################
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn ( ) {
+    echo "$*"
+}
+
+die ( ) {
+    echo
+    echo "$*"
+    echo
+    exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+case "`uname`" in
+  CYGWIN* )
+    cygwin=true
+    ;;
+  Darwin* )
+    darwin=true
+    ;;
+  MINGW* )
+    msys=true
+    ;;
+esac
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+    ls=`ls -ld "$PRG"`
+    link=`expr "$ls" : '.*-> \(.*\)$'`
+    if expr "$link" : '/.*' > /dev/null; then
+        PRG="$link"
+    else
+        PRG=`dirname "$PRG"`"/$link"
+    fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+    if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+        # IBM's JDK on AIX uses strange locations for the executables
+        JAVACMD="$JAVA_HOME/jre/sh/java"
+    else
+        JAVACMD="$JAVA_HOME/bin/java"
+    fi
+    if [ ! -x "$JAVACMD" ] ; then
+        die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+    fi
+else
+    JAVACMD="java"
+    which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
+    MAX_FD_LIMIT=`ulimit -H -n`
+    if [ $? -eq 0 ] ; then
+        if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+            MAX_FD="$MAX_FD_LIMIT"
+        fi
+        ulimit -n $MAX_FD
+        if [ $? -ne 0 ] ; then
+            warn "Could not set maximum file descriptor limit: $MAX_FD"
+        fi
+    else
+        warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+    fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+    GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
+    APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+    CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+    JAVACMD=`cygpath --unix "$JAVACMD"`
+
+    # We build the pattern for arguments to be converted via cygpath
+    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+    SEP=""
+    for dir in $ROOTDIRSRAW ; do
+        ROOTDIRS="$ROOTDIRS$SEP$dir"
+        SEP="|"
+    done
+    OURCYGPATTERN="(^($ROOTDIRS))"
+    # Add a user-defined pattern to the cygpath arguments
+    if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+        OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+    fi
+    # Now convert the arguments - kludge to limit ourselves to /bin/sh
+    i=0
+    for arg in "$@" ; do
+        CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+        CHECK2=`echo "$arg"|egrep -c "^-"`                                 ### Determine if an option
+
+        if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition
+            eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+        else
+            eval `echo args$i`="\"$arg\""
+        fi
+        i=$((i+1))
+    done
+    case $i in
+        (0) set -- ;;
+        (1) set -- "$args0" ;;
+        (2) set -- "$args0" "$args1" ;;
+        (3) set -- "$args0" "$args1" "$args2" ;;
+        (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+        (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+        (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+        (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+        (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+        (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+    esac
+fi
+
+# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
+function splitJvmOpts() {
+    JVM_OPTS=("$@")
+}
+eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
+JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
+
+exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
diff --git a/webrtc/android/gradlew.bat b/webrtc/android/gradlew.bat
new file mode 100644 (file)
index 0000000..aec9973
--- /dev/null
@@ -0,0 +1,90 @@
+@if "%DEBUG%" == "" @echo off\r
+@rem ##########################################################################\r
+@rem\r
+@rem  Gradle startup script for Windows\r
+@rem\r
+@rem ##########################################################################\r
+\r
+@rem Set local scope for the variables with windows NT shell\r
+if "%OS%"=="Windows_NT" setlocal\r
+\r
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\r
+set DEFAULT_JVM_OPTS=\r
+\r
+set DIRNAME=%~dp0\r
+if "%DIRNAME%" == "" set DIRNAME=.\r
+set APP_BASE_NAME=%~n0\r
+set APP_HOME=%DIRNAME%\r
+\r
+@rem Find java.exe\r
+if defined JAVA_HOME goto findJavaFromJavaHome\r
+\r
+set JAVA_EXE=java.exe\r
+%JAVA_EXE% -version >NUL 2>&1\r
+if "%ERRORLEVEL%" == "0" goto init\r
+\r
+echo.\r
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\r
+echo.\r
+echo Please set the JAVA_HOME variable in your environment to match the\r
+echo location of your Java installation.\r
+\r
+goto fail\r
+\r
+:findJavaFromJavaHome\r
+set JAVA_HOME=%JAVA_HOME:"=%\r
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe\r
+\r
+if exist "%JAVA_EXE%" goto init\r
+\r
+echo.\r
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%\r
+echo.\r
+echo Please set the JAVA_HOME variable in your environment to match the\r
+echo location of your Java installation.\r
+\r
+goto fail\r
+\r
+:init\r
+@rem Get command-line arguments, handling Windowz variants\r
+\r
+if not "%OS%" == "Windows_NT" goto win9xME_args\r
+if "%@eval[2+2]" == "4" goto 4NT_args\r
+\r
+:win9xME_args\r
+@rem Slurp the command line arguments.\r
+set CMD_LINE_ARGS=\r
+set _SKIP=2\r
+\r
+:win9xME_args_slurp\r
+if "x%~1" == "x" goto execute\r
+\r
+set CMD_LINE_ARGS=%*\r
+goto execute\r
+\r
+:4NT_args\r
+@rem Get arguments from the 4NT Shell from JP Software\r
+set CMD_LINE_ARGS=%$\r
+\r
+:execute\r
+@rem Setup the command line\r
+\r
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar\r
+\r
+@rem Execute Gradle\r
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%\r
+\r
+:end\r
+@rem End local scope for the variables with windows NT shell\r
+if "%ERRORLEVEL%"=="0" goto mainEnd\r
+\r
+:fail\r
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of\r
+rem the _cmd.exe /c_ return code!\r
+if  not "" == "%GRADLE_EXIT_CONSOLE%" exit 1\r
+exit /b 1\r
+\r
+:mainEnd\r
+if "%OS%"=="Windows_NT" endlocal\r
+\r
+:omega\r
diff --git a/webrtc/android/settings.gradle b/webrtc/android/settings.gradle
new file mode 100644 (file)
index 0000000..e7b4def
--- /dev/null
@@ -0,0 +1 @@
+include ':app'