Repository: GleasonK/AndroidRTC
Branch: master
Commit: 9756044380ec
Files: 61
Total size: 132.7 KB
Directory structure:
gitextract_4p2k_p1l/
├── .gitignore
├── LICENSE
├── README.md
├── app/
│ ├── .gitignore
│ ├── build.gradle
│ ├── proguard-rules.pro
│ └── src/
│ ├── androidTest/
│ │ └── java/
│ │ └── me/
│ │ └── kevingleason/
│ │ └── androidrtc/
│ │ └── ApplicationTest.java
│ └── main/
│ ├── AndroidManifest.xml
│ ├── java/
│ │ └── me/
│ │ └── kevingleason/
│ │ └── androidrtc/
│ │ ├── IncomingCallActivity.java
│ │ ├── LoginActivity.java
│ │ ├── MainActivity.java
│ │ ├── VideoChatActivity.java
│ │ ├── adapters/
│ │ │ ├── ChatAdapter.java
│ │ │ └── HistoryAdapter.java
│ │ ├── adt/
│ │ │ ├── ChatMessage.java
│ │ │ ├── ChatUser.java
│ │ │ └── HistoryItem.java
│ │ ├── servers/
│ │ │ └── XirSysRequest.java
│ │ └── util/
│ │ ├── Constants.java
│ │ └── LogRTCListener.java
│ └── res/
│ ├── drawable/
│ │ ├── light_fade_down.xml
│ │ ├── light_fade_up.xml
│ │ ├── online_circle.xml
│ │ ├── round_button.xml
│ │ └── round_button_send.xml
│ ├── layout/
│ │ ├── activity_incoming_call.xml
│ │ ├── activity_login.xml
│ │ ├── activity_main.xml
│ │ ├── activity_video_chat.xml
│ │ ├── chat_message_row_layout.xml
│ │ └── history_row_layout.xml
│ ├── menu/
│ │ ├── menu_incoming_call.xml
│ │ ├── menu_login.xml
│ │ ├── menu_main.xml
│ │ └── menu_video_chat.xml
│ ├── values/
│ │ ├── colors.xml
│ │ ├── dimens.xml
│ │ ├── strings.xml
│ │ └── styles.xml
│ ├── values-v21/
│ │ └── styles.xml
│ └── values-w820dp/
│ └── dimens.xml
├── build.gradle
├── gradle/
│ └── wrapper/
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradle.properties
├── gradlew
├── gradlew.bat
├── pnwebrtc/
│ ├── .gitignore
│ ├── build.gradle
│ ├── gradle.properties
│ ├── proguard-rules.pro
│ └── src/
│ ├── androidTest/
│ │ └── java/
│ │ └── me/
│ │ └── kevingleason/
│ │ └── pnwebrtc/
│ │ └── ApplicationTest.java
│ └── main/
│ ├── AndroidManifest.xml
│ ├── java/
│ │ └── me/
│ │ └── kevingleason/
│ │ └── pnwebrtc/
│ │ ├── PnPeer.java
│ │ ├── PnPeerConnectionClient.java
│ │ ├── PnRTCClient.java
│ │ ├── PnRTCListener.java
│ │ ├── PnRTCMessage.java
│ │ └── PnSignalingParams.java
│ └── res/
│ └── values/
│ └── strings.xml
└── settings.gradle
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitignore
================================================
.gradle
/local.properties
/.idea/workspace.xml
/.idea/libraries
.DS_Store
/build
================================================
FILE: LICENSE
================================================
The MIT License (MIT)
Copyright (c) 2015 Kevin Gleason
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: README.md
================================================
# AndroidRTC
An example of native WebRTC on Android using PubNub's Android SDK signaling.
### Big News: PubNub Android SDK for Signaling!

This means that you can now create video chatting applications natively on Android in a breeze. Best of all, it is fully compatible with the [PubNub Javascript SDK][JS SDK]! That's right, you are minutes away from creating your very own cross platform video-chatting application.
[__Get it now!__][PnWebRTC]
_NOTE:_ The following demo uses the PubNub Android SDK for signaling to transfer the metadata and establish the peer-to-peer connection. Once the connection is established, the video and voice runs on public Google STUN/TURN servers.
Keep in mind, PubNub can provide the signaling for WebRTC, and requires you to combine it with a hosted WebRTC solution. For more detail on what PubNub does, and what PubNub doesn’t do with WebRTC, check out this article: https://support.pubnub.com/support/solutions/articles/14000043715-does-pubnub-provide-webrtc-and-video-chat-
## The AndroidRTC Example App
This app shows how to accomplish signling on a standby channel to coordinate users, then hop into a video chat and use the PubNub JavaScript SDK for signaling. You can even make a call to the AndroidRTC App [from the web interface](http://kevingleason.me/AndroidRTC).
<img src="http://kevingleason.me/AndroidRTC/assets/Main.png" height=500 />
Users in this app layout must be subscribed to `username-stdby`, a standby channel. The Android side checks if a user is online by checking presence on the standby channel.
In this app, a call can be placed by sending a JSON packet to the user's standby channel:
{"call_user":"UserName","call_time":currentTimeMillis}
Upon accepting the call, the answerer creates the SDP Offer, and video chat begins.
<img src="http://kevingleason.me/AndroidRTC/assets/Kevin.png" height=500 />
### Incoming Calls
AndroidRTC provides a good example of how to handle incoming calls.
<img src="http://kevingleason.me/AndroidRTC/assets/Incoming.png" height=500 />
### User Messages
This app also shows how to send custom user messages. These messages could be chat, game scores, and much more.
<img src="http://kevingleason.me/AndroidRTC/assets/Kurt.png" height=500 />
[PnWebRTC]:https://github.com/GleasonK/pubnub-android-webrtc
[JavaDoc]:http://kevingleason.me/pubnub-android-webrtc/
[AndroidRTC]:https://github.com/GleasonK/AndroidRTC/
[JS SDK]:https://github.com/stephenlb/webrtc-sdk
================================================
FILE: app/.gitignore
================================================
/build
================================================
FILE: app/build.gradle
================================================
apply plugin: 'com.android.application'
android {
compileSdkVersion 21
buildToolsVersion "20.0.0"
defaultConfig {
applicationId "me.kevingleason.androidrtc"
minSdkVersion 17
targetSdkVersion 21
versionCode 1
versionName "1.0"
}
buildTypes {
release {
runProguard false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
repositories {
flatDir {
dirs 'libs'
}
}
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
compile 'com.pubnub:pubnub-android:3.7.4'
compile 'io.pristine:libjingle:9694@aar'
compile project(':pnwebrtc')
}
================================================
FILE: app/proguard-rules.pro
================================================
# Add project specific ProGuard rules here.
# By default, the flags in this file are appended to flags specified
# in /Users/GleasonK/algs4/AndroidSDK/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 *;
#}
================================================
FILE: app/src/androidTest/java/me/kevingleason/androidrtc/ApplicationTest.java
================================================
package me.kevingleason.androidrtc;
import android.app.Application;
import android.test.ApplicationTestCase;
/**
* <a href="http://d.android.com/tools/testing/testing_android.html">Testing Fundamentals</a>
*/
public class ApplicationTest extends ApplicationTestCase<Application> {
public ApplicationTest() {
super(Application.class);
}
}
================================================
FILE: app/src/main/AndroidManifest.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="me.kevingleason.androidrtc" >
<!-- WebRTC Dependencies -->
<uses-feature android:name="android.hardware.camera" />
<uses-feature android:name="android.hardware.camera.autofocus" />
<uses-feature
android:glEsVersion="0x00020000"
android:required="true" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
<!-- PubNub Dependencies -->
<!--<uses-permission android:name="android.permission.INTERNET" />-->
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" />
<permission android:name="your.package.name.permission.C2D_MESSAGE" android:protectionLevel="signature" />
<uses-permission android:name="your.package.name.permission.C2D_MESSAGE" />
<application
android:allowBackup="true"
android:icon="@drawable/ic_pubrtc"
android:label="@string/app_name"
android:theme="@style/AppTheme" >
<activity
android:name="me.kevingleason.androidrtc.MainActivity"
android:label="@string/app_name" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name="me.kevingleason.androidrtc.VideoChatActivity"
android:label="@string/title_activity_video_chat"
android:parentActivityName="me.kevingleason.androidrtc.MainActivity"
android:windowSoftInputMode="stateHidden"
android:theme="@android:style/Theme.NoTitleBar.Fullscreen" >
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="me.kevingleason.androidrtc.MainActivity" />
</activity>
<activity
android:name="me.kevingleason.androidrtc.IncomingCallActivity"
android:label="@string/title_activity_incoming_call">
</activity>
<activity
android:name="me.kevingleason.androidrtc.LoginActivity"
android:label="@string/title_activity_login" >
</activity>
</application>
</manifest>
================================================
FILE: app/src/main/java/me/kevingleason/androidrtc/IncomingCallActivity.java
================================================
package me.kevingleason.androidrtc;
import android.app.Activity;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.TextView;
import android.widget.Toast;
import com.pubnub.api.Callback;
import com.pubnub.api.Pubnub;
import org.json.JSONException;
import org.json.JSONObject;
import me.kevingleason.androidrtc.util.Constants;
import me.kevingleason.pnwebrtc.PnPeerConnectionClient;
public class IncomingCallActivity extends Activity {
private SharedPreferences mSharedPreferences;
private String username;
private String callUser;
private Pubnub mPubNub;
private TextView mCallerID;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_incoming_call);
this.mSharedPreferences = getSharedPreferences(Constants.SHARED_PREFS, MODE_PRIVATE);
if (!this.mSharedPreferences.contains(Constants.USER_NAME)){
Intent intent = new Intent(this, LoginActivity.class);
startActivity(intent);
finish();
return;
}
this.username = this.mSharedPreferences.getString(Constants.USER_NAME, "");
Bundle extras = getIntent().getExtras();
if (extras==null || !extras.containsKey(Constants.CALL_USER)){
Intent intent = new Intent(this, MainActivity.class);
startActivity(intent);
Toast.makeText(this, "Need to pass username to IncomingCallActivity in intent extras (Constants.CALL_USER).",
Toast.LENGTH_SHORT).show();
finish();
return;
}
this.callUser = extras.getString(Constants.CALL_USER, "");
this.mCallerID = (TextView) findViewById(R.id.caller_id);
this.mCallerID.setText(this.callUser);
this.mPubNub = new Pubnub(Constants.PUB_KEY, Constants.SUB_KEY);
this.mPubNub.setUUID(this.username);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.menu_incoming_call, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
int id = item.getItemId();
//noinspection SimplifiableIfStatement
if (id == R.id.action_settings) {
return true;
}
return super.onOptionsItemSelected(item);
}
public void acceptCall(View view){
Intent intent = new Intent(IncomingCallActivity.this, VideoChatActivity.class);
intent.putExtra(Constants.USER_NAME, this.username);
intent.putExtra(Constants.CALL_USER, this.callUser);
startActivity(intent);
}
/**
* Publish a hangup command if rejecting call.
* @param view
*/
public void rejectCall(View view){
JSONObject hangupMsg = PnPeerConnectionClient.generateHangupPacket(this.username);
this.mPubNub.publish(this.callUser,hangupMsg, new Callback() {
@Override
public void successCallback(String channel, Object message) {
Intent intent = new Intent(IncomingCallActivity.this, MainActivity.class);
startActivity(intent);
}
});
}
@Override
protected void onStop() {
super.onStop();
if(this.mPubNub!=null){
this.mPubNub.unsubscribeAll();
}
}
}
================================================
FILE: app/src/main/java/me/kevingleason/androidrtc/LoginActivity.java
================================================
package me.kevingleason.androidrtc;
import android.app.Activity;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.EditText;
import me.kevingleason.androidrtc.util.Constants;
/**
* Login Activity for the first time the app is opened, or when a user clicks the sign out button.
* Saves the username in SharedPreferences.
*/
public class LoginActivity extends Activity {
private EditText mUsername;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login);
mUsername = (EditText) findViewById(R.id.login_username);
Bundle extras = getIntent().getExtras();
if (extras != null){
String lastUsername = extras.getString("oldUsername", "");
mUsername.setText(lastUsername);
}
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.menu_login, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
int id = item.getItemId();
//noinspection SimplifiableIfStatement
if (id == R.id.action_settings) {
return true;
}
return super.onOptionsItemSelected(item);
}
/**
* Takes the username from the EditText, check its validity and saves it if valid.
* Then, redirects to the MainActivity.
* @param view Button clicked to trigger call to joinChat
*/
public void joinChat(View view){
String username = mUsername.getText().toString();
if (!validUsername(username))
return;
SharedPreferences sp = getSharedPreferences(Constants.SHARED_PREFS,MODE_PRIVATE);
SharedPreferences.Editor edit = sp.edit();
edit.putString(Constants.USER_NAME, username);
edit.apply();
Intent intent = new Intent(this, MainActivity.class);
startActivity(intent);
}
/**
* Optional function to specify what a username in your chat app can look like.
* @param username The name entered by a user.
* @return is username valid
*/
private boolean validUsername(String username) {
if (username.length() == 0) {
mUsername.setError("Username cannot be empty.");
return false;
}
if (username.length() > 16) {
mUsername.setError("Username too long.");
return false;
}
return true;
}
}
================================================
FILE: app/src/main/java/me/kevingleason/androidrtc/MainActivity.java
================================================
package me.kevingleason.androidrtc;
import android.app.ListActivity;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.EditText;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;
import com.pubnub.api.Callback;
import com.pubnub.api.Pubnub;
import com.pubnub.api.PubnubError;
import com.pubnub.api.PubnubException;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.ArrayList;
import me.kevingleason.androidrtc.adapters.HistoryAdapter;
import me.kevingleason.androidrtc.adt.HistoryItem;
import me.kevingleason.androidrtc.util.Constants;
public class MainActivity extends ListActivity {
private SharedPreferences mSharedPreferences;
private String username;
private String stdByChannel;
private Pubnub mPubNub;
private ListView mHistoryList;
private HistoryAdapter mHistoryAdapter;
private EditText mCallNumET;
private TextView mUsernameTV;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
this.mSharedPreferences = getSharedPreferences(Constants.SHARED_PREFS, MODE_PRIVATE);
if (!this.mSharedPreferences.contains(Constants.USER_NAME)){
Intent intent = new Intent(this, LoginActivity.class);
startActivity(intent);
finish();
return;
}
this.username = this.mSharedPreferences.getString(Constants.USER_NAME, "");
this.stdByChannel = this.username + Constants.STDBY_SUFFIX;
this.mHistoryList = getListView();
this.mCallNumET = (EditText) findViewById(R.id.call_num);
this.mUsernameTV = (TextView) findViewById(R.id.main_username);
this.mUsernameTV.setText(this.username);
initPubNub();
this.mHistoryAdapter = new HistoryAdapter(this, new ArrayList<HistoryItem>(), this.mPubNub);
this.mHistoryList.setAdapter(this.mHistoryAdapter);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.menu_main, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
int id = item.getItemId();
//noinspection SimplifiableIfStatement
switch(id){
case R.id.action_settings:
return true;
case R.id.action_sign_out:
signOut();
return true;
}
return super.onOptionsItemSelected(item);
}
@Override
protected void onStop() {
super.onStop();
if(this.mPubNub!=null){
this.mPubNub.unsubscribeAll();
}
}
@Override
protected void onRestart() {
super.onRestart();
if(this.mPubNub==null){
initPubNub();
} else {
subscribeStdBy();
}
}
/**
* Subscribe to standby channel so that it doesn't interfere with the WebRTC Signaling.
*/
public void initPubNub(){
this.mPubNub = new Pubnub(Constants.PUB_KEY, Constants.SUB_KEY);
this.mPubNub.setUUID(this.username);
subscribeStdBy();
}
/**
* Subscribe to standby channel
*/
private void subscribeStdBy(){
try {
this.mPubNub.subscribe(this.stdByChannel, new Callback() {
@Override
public void successCallback(String channel, Object message) {
Log.d("MA-iPN", "MESSAGE: " + message.toString());
if (!(message instanceof JSONObject)) return; // Ignore if not JSONObject
JSONObject jsonMsg = (JSONObject) message;
try {
if (!jsonMsg.has(Constants.JSON_CALL_USER)) return; //Ignore Signaling messages.
String user = jsonMsg.getString(Constants.JSON_CALL_USER);
dispatchIncomingCall(user);
} catch (JSONException e){
e.printStackTrace();
}
}
@Override
public void connectCallback(String channel, Object message) {
Log.d("MA-iPN", "CONNECTED: " + message.toString());
setUserStatus(Constants.STATUS_AVAILABLE);
}
@Override
public void errorCallback(String channel, PubnubError error) {
Log.d("MA-iPN","ERROR: " + error.toString());
}
});
} catch (PubnubException e){
Log.d("HERE","HEREEEE");
e.printStackTrace();
}
}
/**
* Take the user to a video screen. USER_NAME is a required field.
* @param view button that is clicked to trigger toVideo
*/
public void makeCall(View view){
String callNum = mCallNumET.getText().toString();
if (callNum.isEmpty() || callNum.equals(this.username)){
showToast("Enter a valid user ID to call.");
return;
}
dispatchCall(callNum);
}
/**TODO: Debate who calls who. Should one be on standby? Or use State API for busy/available
* Check that user is online. If they are, dispatch the call by publishing to their standby
* channel. If the publish was successful, then change activities over to the video chat.
* The called user will then have the option to accept of decline the call. If they accept,
* they will be brought to the video chat activity as well, to connect video/audio. If
* they decline, a hangup will be issued, and the VideoChat adapter's onHangup callback will
* be invoked.
* @param callNum Number to publish a call to.
*/
public void dispatchCall(final String callNum){
final String callNumStdBy = callNum + Constants.STDBY_SUFFIX;
this.mPubNub.hereNow(callNumStdBy, new Callback() {
@Override
public void successCallback(String channel, Object message) {
Log.d("MA-dC", "HERE_NOW: " +" CH - " + callNumStdBy + " " + message.toString());
try {
int occupancy = ((JSONObject) message).getInt(Constants.JSON_OCCUPANCY);
if (occupancy == 0) {
showToast("User is not online!");
return;
}
JSONObject jsonCall = new JSONObject();
jsonCall.put(Constants.JSON_CALL_USER, username);
jsonCall.put(Constants.JSON_CALL_TIME, System.currentTimeMillis());
mPubNub.publish(callNumStdBy, jsonCall, new Callback() {
@Override
public void successCallback(String channel, Object message) {
Log.d("MA-dC", "SUCCESS: " + message.toString());
Intent intent = new Intent(MainActivity.this, VideoChatActivity.class);
intent.putExtra(Constants.USER_NAME, username);
intent.putExtra(Constants.CALL_USER, callNum); // Only accept from this number?
startActivity(intent);
}
});
} catch (JSONException e) {
e.printStackTrace();
}
}
});
}
/**
* Handle incoming calls. TODO: Implement an accept/reject functionality.
* @param userId
*/
private void dispatchIncomingCall(String userId){
showToast("Call from: " + userId);
Intent intent = new Intent(MainActivity.this, IncomingCallActivity.class);
intent.putExtra(Constants.USER_NAME, username);
intent.putExtra(Constants.CALL_USER, userId);
startActivity(intent);
}
private void setUserStatus(String status){
try {
JSONObject state = new JSONObject();
state.put(Constants.JSON_STATUS, status);
this.mPubNub.setState(this.stdByChannel, this.username, state, new Callback() {
@Override
public void successCallback(String channel, Object message) {
Log.d("MA-sUS","State Set: " + message.toString());
}
});
} catch (JSONException e){
e.printStackTrace();
}
}
private void getUserStatus(String userId){
String stdByUser = userId + Constants.STDBY_SUFFIX;
this.mPubNub.getState(stdByUser, userId, new Callback() {
@Override
public void successCallback(String channel, Object message) {
Log.d("MA-gUS", "User Status: " + message.toString());
}
});
}
/**
* Ensures that toast is run on the UI thread.
* @param message
*/
private void showToast(final String message){
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(MainActivity.this, message, Toast.LENGTH_SHORT).show();
}
});
}
/**
* Log out, remove username from SharedPreferences, unsubscribe from PubNub, and send user back
* to the LoginActivity
*/
public void signOut(){
this.mPubNub.unsubscribeAll();
SharedPreferences.Editor edit = this.mSharedPreferences.edit();
edit.remove(Constants.USER_NAME);
edit.apply();
Intent intent = new Intent(this, LoginActivity.class);
intent.putExtra("oldUsername", this.username);
startActivity(intent);
}
}
================================================
FILE: app/src/main/java/me/kevingleason/androidrtc/VideoChatActivity.java
================================================
package me.kevingleason.androidrtc;
import android.app.Activity;
import android.app.ListActivity;
import android.content.Context;
import android.content.Intent;
import android.opengl.GLSurfaceView;
import android.os.Bundle;
import android.os.Handler;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.WindowManager;
import android.view.inputmethod.InputMethodManager;
import android.widget.EditText;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;
import org.json.JSONException;
import org.json.JSONObject;
import org.webrtc.AudioSource;
import org.webrtc.AudioTrack;
import org.webrtc.MediaConstraints;
import org.webrtc.MediaStream;
import org.webrtc.PeerConnection;
import org.webrtc.PeerConnectionFactory;
import org.webrtc.VideoCapturer;
import org.webrtc.VideoCapturerAndroid;
import org.webrtc.VideoRenderer;
import org.webrtc.VideoRendererGui;
import org.webrtc.VideoSource;
import org.webrtc.VideoTrack;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.ExecutionException;
import me.kevingleason.androidrtc.adapters.ChatAdapter;
import me.kevingleason.androidrtc.adt.ChatMessage;
import me.kevingleason.androidrtc.servers.XirSysRequest;
import me.kevingleason.androidrtc.util.Constants;
import me.kevingleason.androidrtc.util.LogRTCListener;
import me.kevingleason.pnwebrtc.PnPeer;
import me.kevingleason.pnwebrtc.PnRTCClient;
import me.kevingleason.pnwebrtc.PnSignalingParams;
/**
* This chat will begin/subscribe to a video chat.
* REQUIRED: The intent must contain a
*/
public class VideoChatActivity extends ListActivity {
public static final String VIDEO_TRACK_ID = "videoPN";
public static final String AUDIO_TRACK_ID = "audioPN";
public static final String LOCAL_MEDIA_STREAM_ID = "localStreamPN";
private PnRTCClient pnRTCClient;
private VideoSource localVideoSource;
private VideoRenderer.Callbacks localRender;
private VideoRenderer.Callbacks remoteRender;
private GLSurfaceView videoView;
private EditText mChatEditText;
private ListView mChatList;
private ChatAdapter mChatAdapter;
private TextView mCallStatus;
private String username;
private boolean backPressed = false;
private Thread backPressedThread = null;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_video_chat);
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
Bundle extras = getIntent().getExtras();
if (extras == null || !extras.containsKey(Constants.USER_NAME)) {
Intent intent = new Intent(this, MainActivity.class);
startActivity(intent);
Toast.makeText(this, "Need to pass username to VideoChatActivity in intent extras (Constants.USER_NAME).",
Toast.LENGTH_SHORT).show();
finish();
return;
}
this.username = extras.getString(Constants.USER_NAME, "");
this.mChatList = getListView();
this.mChatEditText = (EditText) findViewById(R.id.chat_input);
this.mCallStatus = (TextView) findViewById(R.id.call_status);
// Set up the List View for chatting
List<ChatMessage> ll = new LinkedList<ChatMessage>();
mChatAdapter = new ChatAdapter(this, ll);
mChatList.setAdapter(mChatAdapter);
// First, we initiate the PeerConnectionFactory with our application context and some options.
PeerConnectionFactory.initializeAndroidGlobals(
this, // Context
true, // Audio Enabled
true, // Video Enabled
true, // Hardware Acceleration Enabled
null); // Render EGL Context
PeerConnectionFactory pcFactory = new PeerConnectionFactory();
this.pnRTCClient = new PnRTCClient(Constants.PUB_KEY, Constants.SUB_KEY, this.username);
List<PeerConnection.IceServer> servers = getXirSysIceServers();
if (!servers.isEmpty()){
this.pnRTCClient.setSignalParams(new PnSignalingParams());
}
// Returns the number of cams & front/back face device name
int camNumber = VideoCapturerAndroid.getDeviceCount();
String frontFacingCam = VideoCapturerAndroid.getNameOfFrontFacingDevice();
String backFacingCam = VideoCapturerAndroid.getNameOfBackFacingDevice();
// Creates a VideoCapturerAndroid instance for the device name
VideoCapturer capturer = VideoCapturerAndroid.create(frontFacingCam);
// First create a Video Source, then we can make a Video Track
localVideoSource = pcFactory.createVideoSource(capturer, this.pnRTCClient.videoConstraints());
VideoTrack localVideoTrack = pcFactory.createVideoTrack(VIDEO_TRACK_ID, localVideoSource);
// First we create an AudioSource then we can create our AudioTrack
AudioSource audioSource = pcFactory.createAudioSource(this.pnRTCClient.audioConstraints());
AudioTrack localAudioTrack = pcFactory.createAudioTrack(AUDIO_TRACK_ID, audioSource);
// To create our VideoRenderer, we can use the included VideoRendererGui for simplicity
// First we need to set the GLSurfaceView that it should render to
this.videoView = (GLSurfaceView) findViewById(R.id.gl_surface);
// Then we set that view, and pass a Runnable to run once the surface is ready
VideoRendererGui.setView(videoView, null);
// Now that VideoRendererGui is ready, we can get our VideoRenderer.
// IN THIS ORDER. Effects which is on top or bottom
remoteRender = VideoRendererGui.create(0, 0, 100, 100, VideoRendererGui.ScalingType.SCALE_ASPECT_FILL, false);
localRender = VideoRendererGui.create(0, 0, 100, 100, VideoRendererGui.ScalingType.SCALE_ASPECT_FILL, true);
// We start out with an empty MediaStream object, created with help from our PeerConnectionFactory
// Note that LOCAL_MEDIA_STREAM_ID can be any string
MediaStream mediaStream = pcFactory.createLocalMediaStream(LOCAL_MEDIA_STREAM_ID);
// Now we can add our tracks.
mediaStream.addTrack(localVideoTrack);
mediaStream.addTrack(localAudioTrack);
// First attach the RTC Listener so that callback events will be triggered
this.pnRTCClient.attachRTCListener(new DemoRTCListener());
// Then attach your local media stream to the PnRTCClient.
// This will trigger the onLocalStream callback.
this.pnRTCClient.attachLocalMediaStream(mediaStream);
// Listen on a channel. This is your "phone number," also set the max chat users.
this.pnRTCClient.listenOn("Kevin");
this.pnRTCClient.setMaxConnections(1);
// If the intent contains a number to dial, call it now that you are connected.
// Else, remain listening for a call.
if (extras.containsKey(Constants.CALL_USER)) {
String callUser = extras.getString(Constants.CALL_USER, "");
connectToUser(callUser);
}
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.menu_video_chat, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
int id = item.getItemId();
//noinspection SimplifiableIfStatement
if (id == R.id.action_settings) {
return true;
}
return super.onOptionsItemSelected(item);
}
@Override
protected void onPause() {
super.onPause();
this.videoView.onPause();
this.localVideoSource.stop();
}
@Override
protected void onResume() {
super.onResume();
this.videoView.onResume();
this.localVideoSource.restart();
}
@Override
protected void onDestroy() {
super.onDestroy();
if (this.localVideoSource != null) {
this.localVideoSource.stop();
}
if (this.pnRTCClient != null) {
this.pnRTCClient.onDestroy();
}
}
@Override
public void onBackPressed() {
if (!this.backPressed){
this.backPressed = true;
Toast.makeText(this,"Press back again to end.",Toast.LENGTH_SHORT).show();
this.backPressedThread = new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(5000);
backPressed = false;
} catch (InterruptedException e){ Log.d("VCA-oBP","Successfully interrupted"); }
}
});
this.backPressedThread.start();
return;
}
if (this.backPressedThread != null)
this.backPressedThread.interrupt();
super.onBackPressed();
}
public List<PeerConnection.IceServer> getXirSysIceServers(){
List<PeerConnection.IceServer> servers = new ArrayList<PeerConnection.IceServer>();
try {
servers = new XirSysRequest().execute().get();
} catch (InterruptedException e){
e.printStackTrace();
}catch (ExecutionException e){
e.printStackTrace();
}
return servers;
}
public void connectToUser(String user) {
this.pnRTCClient.connect(user);
}
public void hangup(View view) {
this.pnRTCClient.closeAllConnections();
endCall();
}
private void endCall() {
startActivity(new Intent(VideoChatActivity.this, MainActivity.class));
finish();
}
public void sendMessage(View view) {
String message = mChatEditText.getText().toString();
if (message.equals("")) return; // Return if empty
ChatMessage chatMsg = new ChatMessage(this.username, message, System.currentTimeMillis());
mChatAdapter.addMessage(chatMsg);
JSONObject messageJSON = new JSONObject();
try {
messageJSON.put(Constants.JSON_MSG_UUID, chatMsg.getSender());
messageJSON.put(Constants.JSON_MSG, chatMsg.getMessage());
messageJSON.put(Constants.JSON_TIME, chatMsg.getTimeStamp());
this.pnRTCClient.transmitAll(messageJSON);
} catch (JSONException e) {
e.printStackTrace();
}
// Hide keyboard when you send a message.
View focusView = this.getCurrentFocus();
if (focusView != null) {
InputMethodManager inputManager = (InputMethodManager) this.getSystemService(Context.INPUT_METHOD_SERVICE);
inputManager.hideSoftInputFromWindow(view.getWindowToken(), InputMethodManager.HIDE_NOT_ALWAYS);
}
mChatEditText.setText("");
}
/**
* LogRTCListener is used for debugging purposes, it prints all RTC messages.
* DemoRTC is just a Log Listener with the added functionality to append screens.
*/
private class DemoRTCListener extends LogRTCListener {
@Override
public void onLocalStream(final MediaStream localStream) {
super.onLocalStream(localStream); // Will log values
VideoChatActivity.this.runOnUiThread(new Runnable() {
@Override
public void run() {
if(localStream.videoTracks.size()==0) return;
localStream.videoTracks.get(0).addRenderer(new VideoRenderer(localRender));
}
});
}
@Override
public void onAddRemoteStream(final MediaStream remoteStream, final PnPeer peer) {
super.onAddRemoteStream(remoteStream, peer); // Will log values
VideoChatActivity.this.runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(VideoChatActivity.this,"Connected to " + peer.getId(), Toast.LENGTH_SHORT).show();
try {
if(remoteStream.audioTracks.size()==0 || remoteStream.videoTracks.size()==0) return;
mCallStatus.setVisibility(View.GONE);
remoteStream.videoTracks.get(0).addRenderer(new VideoRenderer(remoteRender));
VideoRendererGui.update(remoteRender, 0, 0, 100, 100, VideoRendererGui.ScalingType.SCALE_ASPECT_FILL, false);
VideoRendererGui.update(localRender, 72, 65, 25, 25, VideoRendererGui.ScalingType.SCALE_ASPECT_FIT, true);
}
catch (Exception e){ e.printStackTrace(); }
}
});
}
@Override
public void onMessage(PnPeer peer, Object message) {
super.onMessage(peer, message); // Will log values
if (!(message instanceof JSONObject)) return; //Ignore if not JSONObject
JSONObject jsonMsg = (JSONObject) message;
try {
String uuid = jsonMsg.getString(Constants.JSON_MSG_UUID);
String msg = jsonMsg.getString(Constants.JSON_MSG);
long time = jsonMsg.getLong(Constants.JSON_TIME);
final ChatMessage chatMsg = new ChatMessage(uuid, msg, time);
VideoChatActivity.this.runOnUiThread(new Runnable() {
@Override
public void run() {
mChatAdapter.addMessage(chatMsg);
}
});
} catch (JSONException e){
e.printStackTrace();
}
}
@Override
public void onPeerConnectionClosed(PnPeer peer) {
super.onPeerConnectionClosed(peer);
VideoChatActivity.this.runOnUiThread(new Runnable() {
@Override
public void run() {
mCallStatus.setText("Call Ended...");
mCallStatus.setVisibility(View.VISIBLE);
}
});
try {Thread.sleep(1500);} catch (InterruptedException e){e.printStackTrace();}
Intent intent = new Intent(VideoChatActivity.this, MainActivity.class);
startActivity(intent);
finish();
}
}
}
================================================
FILE: app/src/main/java/me/kevingleason/androidrtc/adapters/ChatAdapter.java
================================================
package me.kevingleason.androidrtc.adapters;
import android.content.Context;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.AccelerateInterpolator;
import android.view.animation.AlphaAnimation;
import android.view.animation.Animation;
import android.view.animation.AnimationSet;
import android.widget.ArrayAdapter;
import android.widget.TextView;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.List;
import me.kevingleason.androidrtc.R;
import me.kevingleason.androidrtc.adt.ChatMessage;
/**
* Created by GleasonK on 6/25/15.
*/
public class ChatAdapter extends ArrayAdapter<ChatMessage> {
private static final long FADE_TIMEOUT = 3000;
private final Context context;
private LayoutInflater inflater;
private List<ChatMessage> values;
public ChatAdapter(Context context, List<ChatMessage> values) {
super(context, R.layout.chat_message_row_layout, android.R.id.text1, values);
this.context = context;
this.inflater = LayoutInflater.from(context);
this.values=values;
}
class ViewHolder {
TextView sender;
TextView message;
TextView timeStamp;
ChatMessage chatMsg;
}
@Override
public View getView(final int position, View convertView, ViewGroup parent) {
ChatMessage chatMsg;
if(position >= values.size()){ chatMsg = new ChatMessage("","",0); } // Catch Edge Case
else { chatMsg = this.values.get(position); }
ViewHolder holder;
if (convertView == null) {
holder = new ViewHolder();
convertView = inflater.inflate(R.layout.chat_message_row_layout, parent, false);
holder.sender = (TextView) convertView.findViewById(R.id.chat_user);
holder.message = (TextView) convertView.findViewById(R.id.chat_message);
holder.timeStamp = (TextView) convertView.findViewById(R.id.chat_timestamp);
convertView.setTag(holder);
Log.d("Adapter", "Recreating fadeout.");
} else {
holder = (ViewHolder) convertView.getTag();
}
holder.sender.setText(chatMsg.getSender() + ": ");
holder.message.setText(chatMsg.getMessage());
holder.timeStamp.setText(formatTimeStamp(chatMsg.getTimeStamp()));
holder.chatMsg=chatMsg;
setFadeOut3(convertView, chatMsg);
return convertView;
}
@Override
public int getCount() {
return this.values.size();
}
@Override
public boolean hasStableIds() {
return true;
}
@Override
public long getItemId(int position){
if (position >= values.size()){ return -1; }
return values.get(position).hashCode();
}
public void removeMsg(int loc){
this.values.remove(loc);
notifyDataSetChanged();
}
public void addMessage(ChatMessage chatMsg){
this.values.add(chatMsg);
notifyDataSetChanged();
}
private void setFadeOut2(final View view, final ChatMessage message){
Log.i("AdapterFade", "Caling Fade2");
view.animate().setDuration(1000).setStartDelay(2000).alpha(0)
.withEndAction(new Runnable() {
@Override
public void run() {
if (values.contains(message))
values.remove(message);
notifyDataSetChanged();
view.setAlpha(1);
}
});
}
private void setFadeOut3(final View view, final ChatMessage message){
Log.i("AdapterFade", "Caling Fade3");
long elapsed = System.currentTimeMillis() - message.getTimeStamp();
if (elapsed >= FADE_TIMEOUT){
if (values.contains(message))
values.remove(message);
notifyDataSetChanged();
return;
}
view.animate().setStartDelay(FADE_TIMEOUT - elapsed).setDuration(1500).alpha(0)
.withEndAction(new Runnable() {
@Override
public void run() {
if (values.contains(message)){
values.remove(message);
}
notifyDataSetChanged();
view.setAlpha(1);
}
});
}
private void setFadeOut(final View view, final ChatMessage message){
long elapsed = System.currentTimeMillis() - message.getTimeStamp();
if (elapsed >= FADE_TIMEOUT){
if (values.contains(message))
values.remove(message);
notifyDataSetChanged();
return;
}
view.setHasTransientState(true);
Animation fadeOut = new AlphaAnimation(1, 0);
fadeOut.setInterpolator(new AccelerateInterpolator()); //and this
fadeOut.setStartOffset(FADE_TIMEOUT - elapsed);
fadeOut.setDuration(1000);
AnimationSet animation = new AnimationSet(false);
animation.addAnimation(fadeOut);
animation.setRepeatCount(1);
view.setAnimation(animation);
animation.setAnimationListener(new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {}
@Override
public void onAnimationEnd(Animation animation) {
if (values.contains(message)){
values.remove(message);
}
notifyDataSetChanged();
view.setAlpha(1);
view.setHasTransientState(false);
}
@Override
public void onAnimationRepeat(Animation animation) {}
});
}
/**
* Format the long System.currentTimeMillis() to a better looking timestamp. Uses a calendar
* object to format with the user's current time zone.
* @param timeStamp
* @return
*/
public static String formatTimeStamp(long timeStamp){
// Create a DateFormatter object for displaying date in specified format.
SimpleDateFormat formatter = new SimpleDateFormat("h:mm.ss a");
// Create a calendar object that will convert the date and time value in milliseconds to date.
Calendar calendar = Calendar.getInstance();
calendar.setTimeInMillis(timeStamp);
return formatter.format(calendar.getTime());
}
}
================================================
FILE: app/src/main/java/me/kevingleason/androidrtc/adapters/HistoryAdapter.java
================================================
package me.kevingleason.androidrtc.adapters;
import android.app.Activity;
import android.content.Context;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.ImageButton;
import android.widget.TextView;
import com.pubnub.api.Callback;
import com.pubnub.api.Pubnub;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import me.kevingleason.androidrtc.MainActivity;
import me.kevingleason.androidrtc.R;
import me.kevingleason.androidrtc.adt.ChatUser;
import me.kevingleason.androidrtc.adt.HistoryItem;
import me.kevingleason.androidrtc.util.Constants;
/**
* Created by GleasonK on 7/31/15.
*/
public class HistoryAdapter extends ArrayAdapter<HistoryItem> {
private final Context context;
private Pubnub mPubNub;
private LayoutInflater inflater;
private List<HistoryItem> values;
private Map<String, ChatUser> users;
public HistoryAdapter(Context context, List<HistoryItem> values, Pubnub pubnub) {
super(context, R.layout.history_row_layout, android.R.id.text1, values);
this.context = context;
this.inflater = LayoutInflater.from(context);
this.mPubNub = pubnub;
this.values = values;
this.users = new HashMap<String, ChatUser>();
updateHistory();
}
class ViewHolder {
TextView user;
TextView status;
TextView time;
ImageButton callBtn;
HistoryItem histItem;
}
@Override
public View getView(final int position, View convertView, ViewGroup parent) {
final HistoryItem hItem = this.values.get(position);
ViewHolder holder;
if (convertView == null) {
holder = new ViewHolder();
convertView = inflater.inflate(R.layout.history_row_layout, parent, false);
holder.user = (TextView) convertView.findViewById(R.id.history_name);
holder.status = (TextView) convertView.findViewById(R.id.history_status);
holder.time = (TextView) convertView.findViewById(R.id.history_time);
holder.callBtn = (ImageButton) convertView.findViewById(R.id.history_call);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
holder.user.setText(hItem.getUser().getUserId());
holder.time.setText(formatTimeStamp(hItem.getTimeStamp()));
holder.status.setText(hItem.getUser().getStatus());
if (hItem.getUser().getStatus().equals(Constants.STATUS_OFFLINE))
getUserStatus(hItem.getUser(), holder.status);
holder.callBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
((MainActivity)context).dispatchCall(hItem.getUser().getUserId());
}
});
holder.histItem=hItem;
return convertView;
}
@Override
public int getCount() {
return this.values.size();
}
public void removeButton(int loc){
this.values.remove(loc);
notifyDataSetChanged();
}
private void getUserStatus(final ChatUser user, final TextView statusView){
String stdByUser = user.getUserId() + Constants.STDBY_SUFFIX;
this.mPubNub.getState(stdByUser, user.getUserId(), new Callback() {
@Override
public void successCallback(String channel, Object message) {
JSONObject jsonMsg = (JSONObject) message;
try {
if (!jsonMsg.has(Constants.JSON_STATUS)) return;
final String status = jsonMsg.getString(Constants.JSON_STATUS);
user.setStatus(status);
((Activity)getContext()).runOnUiThread(new Runnable() {
@Override
public void run() {
statusView.setText(status);
}
});
} catch (JSONException e){
e.printStackTrace();
}
}
});
}
public void updateHistory(){
final List<HistoryItem> rtcHistory = new LinkedList<HistoryItem>();
String usrStdBy = this.mPubNub.getUUID() + Constants.STDBY_SUFFIX;
this.mPubNub.history(usrStdBy, 25, new Callback() {
@Override
public void successCallback(String channel, Object message) {
Log.d("HA-uH","HISTORY: " + message.toString());
try {
JSONArray historyArray = ((JSONArray) message).getJSONArray(0);
for(int i=0; i< historyArray.length(); i++){
JSONObject historyJson = historyArray.getJSONObject(i);
String userName = historyJson.getString(Constants.JSON_CALL_USER);
long timeStamp = historyJson.getLong(Constants.JSON_CALL_TIME);
ChatUser cUser = new ChatUser(userName);
if (users.containsKey(userName)){
cUser = users.get(userName);
} else {
users.put(userName, cUser);
}
rtcHistory.add(0, new HistoryItem(cUser, timeStamp));
}
values = rtcHistory;
updateAdapter();
} catch (JSONException e){
// e.printStackTrace();
}
}
});
}
private void updateAdapter(){
((Activity)context).runOnUiThread(new Runnable() {
@Override
public void run() {
notifyDataSetChanged();
}
});
}
/**
* Format the long System.currentTimeMillis() to a better looking timestamp. Uses a calendar
* object to format with the user's current time zone.
* @param timeStamp
* @return
*/
public static String formatTimeStamp(long timeStamp){
// Create a DateFormatter object for displaying date in specified format.
SimpleDateFormat formatter = new SimpleDateFormat("MMM d, h:mm a");
// Create a calendar object that will convert the date and time value in milliseconds to date.
Calendar calendar = Calendar.getInstance();
calendar.setTimeInMillis(timeStamp);
return formatter.format(calendar.getTime());
}
}
================================================
FILE: app/src/main/java/me/kevingleason/androidrtc/adt/ChatMessage.java
================================================
package me.kevingleason.androidrtc.adt;
/**
* Created by GleasonK on 6/25/15.
*/
public class ChatMessage {
private String sender;
private String message;
private long timeStamp;
public ChatMessage(String sender, String message, long timeStamp){
this.sender = sender;
this.message = message;
this.timeStamp=timeStamp;
}
public String getSender() {
return sender;
}
public String getMessage() {
return message;
}
public long getTimeStamp() {
return timeStamp;
}
@Override
public int hashCode() {
return (this.sender + this.message + this.timeStamp).hashCode();
}
}
================================================
FILE: app/src/main/java/me/kevingleason/androidrtc/adt/ChatUser.java
================================================
package me.kevingleason.androidrtc.adt;
import me.kevingleason.androidrtc.util.Constants;
/**
* Created by GleasonK on 7/31/15.
*/
public class ChatUser {
private String userId;
private String status;
public ChatUser(String userId) {
this.userId = userId;
this.status = Constants.STATUS_OFFLINE;
}
public ChatUser(String userId, String status) {
this.userId = userId;
this.status = status;
}
public String getUserId() {
return userId;
}
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
@Override
public boolean equals(Object o) {
if (this==o) return true;
if (!(o instanceof ChatUser)) return false;
ChatUser cu = (ChatUser)o;
return this.userId.equals(((ChatUser) o).getUserId());
}
@Override
public int hashCode() {
return this.getUserId().hashCode();
}
}
================================================
FILE: app/src/main/java/me/kevingleason/androidrtc/adt/HistoryItem.java
================================================
package me.kevingleason.androidrtc.adt;
/**
* Created by GleasonK on 7/31/15.
*/
public class HistoryItem {
private ChatUser user;
private Long timeStamp;
public HistoryItem(ChatUser user, Long timeStamp){
this.user=user;
this.timeStamp=timeStamp;
}
public ChatUser getUser() {
return user;
}
public Long getTimeStamp() {
return timeStamp;
}
}
================================================
FILE: app/src/main/java/me/kevingleason/androidrtc/servers/XirSysRequest.java
================================================
package me.kevingleason.androidrtc.servers;
import android.os.AsyncTask;
import android.util.Log;
import org.apache.http.HttpRequest;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.message.BasicNameValuePair;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.json.JSONTokener;
import org.webrtc.PeerConnection;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.List;
/**
* Created by GleasonK on 11/12/15.
*/
public class XirSysRequest extends AsyncTask<Void,Void,List<PeerConnection.IceServer>> {
public List<PeerConnection.IceServer> doInBackground(Void... params){
List<PeerConnection.IceServer> servers = new ArrayList<PeerConnection.IceServer>();
HttpClient httpClient = new DefaultHttpClient();
HttpPost request = new HttpPost("https://service.xirsys.com/ice");
List<NameValuePair> data = new ArrayList<NameValuePair>();
data.add(new BasicNameValuePair("room", "default"));
data.add(new BasicNameValuePair("application", "default"));
data.add(new BasicNameValuePair("domain", "kevingleason.me"));
data.add(new BasicNameValuePair("ident", "gleasonk"));
data.add(new BasicNameValuePair("secret", "b9066b5e-1f75-11e5-866a-c400956a1e19"));
data.add(new BasicNameValuePair("secure", "1"));
//Encoding POST data
try {
request.setEntity(new UrlEncodedFormEntity(data));
HttpResponse response = httpClient.execute(request);
// write response to log
Log.d("Http Post Response:", response.toString());
BufferedReader reader = new BufferedReader(new InputStreamReader(response.getEntity().getContent(), "UTF-8"));
StringBuilder builder = new StringBuilder();
for (String line=null; (line = reader.readLine()) != null;) {
builder.append(line).append("\n");
}
JSONTokener tokener = new JSONTokener(builder.toString());
JSONObject json = new JSONObject(tokener);
if (json.isNull("e")){
JSONArray iceServers = json.getJSONObject("d").getJSONArray("iceServers");
for (int i = 0; i < iceServers.length(); i++) {
JSONObject srv = iceServers.getJSONObject(i);
PeerConnection.IceServer is;
if (srv.has("username"))
is = new PeerConnection.IceServer(srv.getString("url"),
srv.getString("username"),srv.getString("credential"));
else
is = new PeerConnection.IceServer(srv.getString("url"));
servers.add(is);
}
}
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
} catch (ClientProtocolException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (JSONException e){
e.printStackTrace();
}
Log.i("XIRSYS","Servers: " + servers.toString());
return servers;
}
}
/*
function get_xirsys_servers() {
var servers;
$.ajax({
type: 'POST',
url: 'https://service.xirsys.com/ice',
data: {
room: 'default',
application: 'default',
domain: 'kevingleason.me',
ident: 'gleasonk',
secret: 'b9066b5e-1f75-11e5-866a-c400956a1e19',
secure: 1,
},
success: function(res) {
console.log(res);
res = JSON.parse(res);
if (!res.e) servers = res.d.iceServers;
},
async: false
});
return servers;
}
*/
================================================
FILE: app/src/main/java/me/kevingleason/androidrtc/util/Constants.java
================================================
package me.kevingleason.androidrtc.util;
/**
* Created by GleasonK on 7/30/15.
*/
public class Constants {
public static final String SHARED_PREFS = "me.kg.androidrtc.SHARED_PREFS";
public static final String USER_NAME = "me.kg.androidrtc.SHARED_PREFS.USER_NAME";
public static final String CALL_USER = "me.kg.androidrtc.SHARED_PREFS.CALL_USER";
public static final String STDBY_SUFFIX = "-stdby";
public static final String PUB_KEY = "pub-c-9d0d75a5-38db-404f-ac2a-884e18b041d8"; // Your Pub Key
public static final String SUB_KEY = "sub-c-4e25fb64-37c7-11e5-a477-0619f8945a4f"; // Your Sub Key
// public static final String PUB_KEY = "demo"; // Your Pub Key
// public static final String SUB_KEY = "demo"; // Your Sub Key
public static final String JSON_CALL_USER = "call_user";
public static final String JSON_CALL_TIME = "call_time";
public static final String JSON_OCCUPANCY = "occupancy";
public static final String JSON_STATUS = "status";
// JSON for user messages
public static final String JSON_USER_MSG = "user_message";
public static final String JSON_MSG_UUID = "msg_uuid";
public static final String JSON_MSG = "msg_message";
public static final String JSON_TIME = "msg_timestamp";
public static final String STATUS_AVAILABLE = "Available";
public static final String STATUS_OFFLINE = "Offline";
public static final String STATUS_BUSY = "Busy";
}
================================================
FILE: app/src/main/java/me/kevingleason/androidrtc/util/LogRTCListener.java
================================================
package me.kevingleason.androidrtc.util;
import android.util.Log;
import org.webrtc.MediaStream;
import me.kevingleason.pnwebrtc.PnPeer;
import me.kevingleason.pnwebrtc.PnRTCListener;
import me.kevingleason.pnwebrtc.PnRTCMessage;
/**
* <p>Created by GleasonK on 7/23/15.</p>
*/
public class LogRTCListener extends PnRTCListener {
@Override
public void onCallReady(String callId) {
Log.i("RTCListener", "OnCallReady - " + callId);
}
@Override
public void onConnected(String userId) {
Log.i("RTCListener", "OnConnected - " + userId);
}
@Override
public void onPeerStatusChanged(PnPeer peer) {
Log.i("RTCListener", "OnPeerStatusChanged - " + peer.toString());
}
@Override
public void onPeerConnectionClosed(PnPeer peer) {
Log.i("RTCListener", "OnPeerConnectionClosed - " + peer.toString());
}
@Override
public void onLocalStream(MediaStream localStream) {
Log.i("RTCListener", "OnLocalStream - " + localStream.toString());
}
@Override
public void onAddRemoteStream(MediaStream remoteStream, PnPeer peer) {
Log.i("RTCListener", "OnAddRemoteStream - " + peer.toString());
}
@Override
public void onRemoveRemoteStream(MediaStream remoteStream, PnPeer peer) {
Log.i("RTCListener", "OnRemoveRemoteStream - " + peer.toString());
}
@Override
public void onMessage(PnPeer peer, Object message) {
Log.i("RTCListener", "OnMessage - " + message.toString());
}
@Override
public void onDebug(PnRTCMessage message) {
Log.i("RTCListener", "OnDebug - " + message.getMessage());
}
}
================================================
FILE: app/src/main/res/drawable/light_fade_down.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<shape>
<gradient
android:angle="90"
android:endColor="#c8747474"
android:startColor="#00000000"
android:type="linear" />
</shape>
</item>
</selector>
================================================
FILE: app/src/main/res/drawable/light_fade_up.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<shape>
<gradient
android:angle="90"
android:startColor="#c8747474"
android:endColor="#00000000"
android:type="linear" />
</shape>
</item>
</selector>
================================================
FILE: app/src/main/res/drawable/online_circle.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<shape
xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<solid
android:color="#ff00f306"/>
<size
android:width="10dp"
android:height="10dp"/>
</shape>
================================================
FILE: app/src/main/res/drawable/round_button.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="#b4ff0000" />
<padding android:top="5dp"
android:right="5dp"
android:left="5dp"
android:bottom="5dp"/>
<corners android:bottomRightRadius="45dip"
android:bottomLeftRadius="45dip"
android:topRightRadius="45dip"
android:topLeftRadius="45dip"/>
</shape>
================================================
FILE: app/src/main/res/drawable/round_button_send.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<gradient
android:angle="135"
android:endColor="@color/pn_blue"
android:startColor="@color/pn_blue_dark"
android:type="linear" />
<padding android:top="5dp"
android:right="5dp"
android:left="5dp"
android:bottom="5dp"/>
<corners android:bottomRightRadius="45dip"
android:bottomLeftRadius="45dip"
android:topRightRadius="45dip"
android:topLeftRadius="45dip"/>
</shape>
================================================
FILE: app/src/main/res/layout/activity_incoming_call.xml
================================================
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="me.kevingleason.pubrtc.IncomingCallActivity"
android:orientation="vertical"
android:background="@color/pn_blue">
<TextView
android:text="@string/incoming_call"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:textSize="26sp"
android:textColor="@color/white"
android:layout_margin="25dp"/>
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:src="@drawable/ic_pubrtc"/>
<TextView
android:id="@+id/caller_id"
android:text="@string/incoming_call_from"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:textSize="26sp"
android:textColor="@color/white"
android:layout_margin="25dp"
/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="60dp">
<ImageButton
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:background="@color/green"
android:onClick="acceptCall"
android:src="@drawable/ic_action_call"/>
<ImageButton
android:layout_width="0dp"
android:layout_height="match_parent"
android:background="@color/pn_red"
android:layout_weight="1"
android:onClick="rejectCall"
android:src="@drawable/ic_action_end_call"/>
</LinearLayout>
</LinearLayout>
================================================
FILE: app/src/main/res/layout/activity_login.xml
================================================
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
android:layout_height="match_parent" android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:paddingBottom="@dimen/activity_vertical_margin"
tools:context="me.kevingleason.pubnubchat.LoginActivity">
<TextView
android:text="@string/login"
android:textSize="35sp"
android:gravity="center"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="40dp"
android:textColor="@color/pn_blue"
android:id="@+id/textView"
android:layout_alignParentTop="true"
android:layout_centerHorizontal="true" />
<EditText
android:hint="@string/username"
android:inputType="text"
android:layout_width="match_parent"
android:layout_height="75dp"
android:textSize="30sp"
android:textColor="#333"
android:gravity="center"
android:id="@+id/login_username"
android:layout_below="@+id/textView"
android:layout_alignParentRight="true"
android:layout_alignParentEnd="true"
android:layout_marginTop="45dp" />
<Button
android:id="@+id/button"
android:layout_width="match_parent"
android:layout_height="100dp"
android:text="@string/login_submit"
android:textSize="25sp"
android:textColor="#FFF"
android:background="@color/pn_red"
android:layout_above="@+id/imageHolder"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_marginBottom="35dp"
android:onClick="joinChat"/>
<LinearLayout
android:id="@+id/imageHolder"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="10dp"
android:layout_alignParentBottom="true">
<ImageView
android:layout_width="0dp"
android:layout_height="70dp"
android:layout_weight="2"
android:src="@drawable/pow_by_pubnub"
android:layout_alignParentBottom="true"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_marginBottom="10dp" />
<ImageView
android:layout_width="0dp"
android:layout_height="70dp"
android:layout_weight="1"
android:src="@drawable/ic_pubrtc"
android:layout_alignParentBottom="true"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_marginBottom="10dp" />
</LinearLayout>
</RelativeLayout>
================================================
FILE: app/src/main/res/layout/activity_main.xml
================================================
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity"
android:orientation="vertical">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="50dp"
android:background="@color/pn_red"
>
<TextView
android:id="@+id/main_username"
android:text="Kevin"
android:layout_centerInParent="true"
android:textColor="@color/white"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="25sp"/>
<View
android:layout_alignParentBottom="true"
android:layout_width="match_parent"
android:layout_height="3dp"
android:background="@drawable/light_fade_up"/>
</RelativeLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingLeft ="@dimen/activity_horizontal_margin"
android:paddingRight ="@dimen/activity_horizontal_margin"
android:paddingTop ="10dp"
android:paddingBottom="10dp"
android:orientation="horizontal">
<EditText
android:id="@+id/call_num"
android:hint="Enter a number..."
android:inputType="textShortMessage"
android:maxLines="1"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="4"/>
<RelativeLayout
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1">
<ImageButton
android:background="@drawable/round_button_send"
android:src="@drawable/ic_action_call"
android:layout_centerInParent="true"
android:layout_height="50dp"
android:layout_width ="50dp"
android:scaleType="fitCenter"
android:padding="8dp"
android:onClick="makeCall"/>
</RelativeLayout>
</LinearLayout>
<View
android:layout_width="match_parent"
android:layout_height="3dp"
android:background="@drawable/light_fade_down"/>
<ListView
android:id="@android:id/list"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
<TextView
android:id="@android:id/empty"
android:background="@color/grey"
android:gravity="center"
android:textSize="20sp"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="@string/no_call_history"
/>
</LinearLayout>
================================================
FILE: app/src/main/res/layout/activity_video_chat.xml
================================================
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="me.kevingleason.androidrtc.VideoChatActivity">
<android.opengl.GLSurfaceView
android:id="@+id/gl_surface"
android:layout_height="match_parent"
android:layout_width="match_parent" />
<TextView
android:id="@+id/call_status"
android:text="Connecting..."
android:padding="10dp"
android:textSize="25sp"
android:layout_centerHorizontal="true"
android:layout_above="@+id/call_chat_box"
android:textColor="@color/white"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<LinearLayout
android:id="@+id/call_chat_box"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="10dp"
android:background="#64000000"
android:layout_alignParentBottom="true"
android:orientation="horizontal">
<EditText
android:id="@+id/chat_input"
android:textColor="#FFF"
android:backgroundTint="@color/pn_blue"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="4"
android:textColorHint="@color/white"
android:hint="Enter Message..."/>
<RelativeLayout
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="match_parent">
<ImageButton
android:layout_centerHorizontal="true"
android:contentDescription="Send"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/round_button_send"
android:src="@drawable/ic_action_send_now"
android:onClick="sendMessage" />
</RelativeLayout>
</LinearLayout>
<ListView
android:id="@android:id/list"
android:layout_height="match_parent"
android:layout_width="wrap_content"
android:minWidth="300dp"
android:layout_alignParentEnd="true"
android:layout_above="@id/call_chat_box"
android:stackFromBottom="true"
android:divider="@null"
android:dividerHeight="0dp"
/>
<ImageButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:src="@drawable/ic_action_end_call"
android:background="@drawable/round_button"
android:onClick="hangup"
/>
</RelativeLayout>
================================================
FILE: app/src/main/res/layout/chat_message_row_layout.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/pn_blue"
android:padding="10dp">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/chat_user"
android:layout_alignParentStart="true"
android:textColor="@color/white"
android:text="Kevin"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<TextView
android:id="@+id/chat_timestamp"
android:text="now"
android:textColor="@color/white"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"/>
</RelativeLayout>
<TextView
android:id="@+id/chat_message"
android:textColor="@color/white"
android:text="Hello World!"
android:textSize="20sp"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
================================================
FILE: app/src/main/res/layout/history_row_layout.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="10dp">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:orientation="vertical">
<TextView
android:id="@+id/history_name"
android:text="Kevin Gleason"
android:textSize="20sp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"/>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:id="@+id/history_time"
android:text="Aug 3, 7:30pm"
android:layout_marginEnd="5dp"
android:layout_marginRight="5dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="12sp"/>
<TextView
android:id="@+id/history_status"
android:text="Offline"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="12sp"/>
</LinearLayout>
</LinearLayout>
<ImageButton
android:id="@+id/history_call"
android:src="@drawable/ic_action_call_dark"
android:background="@null"
android:layout_width="50dp"
android:layout_height="50dp"
android:padding="10dp"
android:scaleType="fitCenter"
android:layout_centerVertical="true"
android:layout_alignParentEnd="true"
android:layout_alignParentRight="true"/>
</RelativeLayout>
</LinearLayout>
================================================
FILE: app/src/main/res/menu/menu_incoming_call.xml
================================================
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
tools:context="me.kevingleason.pubrtc.IncomingCallActivity">
<item android:id="@+id/action_settings" android:title="@string/action_settings"
android:orderInCategory="100" android:showAsAction="never" />
</menu>
================================================
FILE: app/src/main/res/menu/menu_login.xml
================================================
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
tools:context="me.kevingleason.androidrtc.LoginActivity">
<item android:id="@+id/action_settings" android:title="@string/action_settings"
android:orderInCategory="100" android:showAsAction="never" />
</menu>
================================================
FILE: app/src/main/res/menu/menu_main.xml
================================================
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" tools:context=".MainActivity">
<item android:id="@+id/action_sign_out" android:title="@string/action_sign_out"
android:orderInCategory="1" android:showAsAction="never" />
<item android:id="@+id/action_settings" android:title="@string/action_settings"
android:orderInCategory="100" android:showAsAction="never" />
</menu>
================================================
FILE: app/src/main/res/menu/menu_video_chat.xml
================================================
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
tools:context="me.kevingleason.androidrtc.VideoChatActivity">
<item android:id="@+id/action_settings" android:title="@string/action_settings"
android:orderInCategory="100" android:showAsAction="never" />
</menu>
================================================
FILE: app/src/main/res/values/colors.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="pn_red">#d02129</color>
<color name="pn_blue">#ff19a2c6</color>
<color name="pn_blue_dark">#ff187998</color>
<color name="white">#FFF</color>
<color name="green">#ff1ce322</color>
<color name="grey">#eee</color>
</resources>
================================================
FILE: app/src/main/res/values/dimens.xml
================================================
<resources>
<!-- Default screen margins, per the Android Design guidelines. -->
<dimen name="activity_horizontal_margin">16dp</dimen>
<dimen name="activity_vertical_margin">16dp</dimen>
</resources>
================================================
FILE: app/src/main/res/values/strings.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">AndroidRTC</string>
<string name="hello_world">Hello world!</string>
<string name="action_settings">Settings</string>
<string name="title_activity_video_chat">Video Call</string>
<string name="title_activity_incoming_call">Incoming Call</string>
<string name="title_activity_login">Login</string>
<string name="login">Choose a username:</string>
<string name="username">Username</string>
<string name="login_submit">Sign in!</string>
<string name="enter_new_channel">Enter new channel:</string>
<string name="action_sign_out">Sign Out</string>
<string name="here_now">Here Now:</string>
<string name="no_call_history">No call History\n\nRecent calls appear here.</string>
<!-- Incoming Call Activity -->
<string name="incoming_call">Incoming Call…</string>
<string name="incoming_call_from">Kevin Gleason</string>
</resources>
================================================
FILE: app/src/main/res/values/styles.xml
================================================
<resources>
<!-- Base application theme. -->
<style name="AppTheme" parent="android:Theme.Holo.Light.DarkActionBar">
</style>
</resources>
================================================
FILE: app/src/main/res/values-v21/styles.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="AppTheme" parent="android:Theme.Material.Light">
<item name="android:colorPrimary">@color/pn_blue</item>
</style>
</resources>
================================================
FILE: app/src/main/res/values-w820dp/dimens.xml
================================================
<resources>
<!-- Example customization of dimensions originally defined in res/values/dimens.xml
(such as screen margins) for screens with more than 820dp of available width. This
would include 7" and 10" devices in landscape (~960dp and ~1280dp respectively). -->
<dimen name="activity_horizontal_margin">64dp</dimen>
</resources>
================================================
FILE: build.gradle
================================================
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
repositories {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:0.13.2'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
allprojects {
repositories {
jcenter()
}
}
================================================
FILE: gradle/wrapper/gradle-wrapper.properties
================================================
#Wed Apr 10 15:27:10 PDT 2013
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-2.1-all.zip
================================================
FILE: gradle.properties
================================================
# 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
VERSION_NAME=1.0.6
VERSION_CODE=6
GROUP=me.kevingleason
POM_DESCRIPTION=Android WebRTC signaling library using PubNub
POM_URL=https://github.com/GleasonK/pubnub-android-webrtc
POM_SCM_URL=https://github.com/GleasonK/pubnub-android-webrtc
POM_SCM_CONNECTION=scm:git@github.com:GleasonK/pubnub-android-webrtc.git
POM_SCM_DEV_CONNECTION=scm:git@github.com:GleasonK/pubnub-android-webrtc.git
POM_LICENCE_NAME=The Apache Software License, Version 2.0
POM_LICENCE_URL=http://www.apache.org/licenses/LICENSE-2.0.txt
POM_LICENCE_DIST=repo
POM_DEVELOPER_ID=GleasonK
POM_DEVELOPER_NAME=Kevin Gleason
================================================
FILE: gradlew
================================================
#!/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
# For Cygwin, ensure paths are in UNIX format before anything is touched.
if $cygwin ; then
[ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
fi
# 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\"`/" >&-
APP_HOME="`pwd -P`"
cd "$SAVED" >&-
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"`
# 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 "$@"
================================================
FILE: gradlew.bat
================================================
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS=
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto init
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:init
@rem Get command-line arguments, handling Windowz variants
if not "%OS%" == "Windows_NT" goto win9xME_args
if "%@eval[2+2]" == "4" goto 4NT_args
:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2
:win9xME_args_slurp
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
goto execute
:4NT_args
@rem Get arguments from the 4NT Shell from JP Software
set CMD_LINE_ARGS=%$
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega
================================================
FILE: pnwebrtc/.gitignore
================================================
/build
================================================
FILE: pnwebrtc/build.gradle
================================================
apply plugin: 'com.android.library'
android {
compileSdkVersion 21
buildToolsVersion "20.0.0"
defaultConfig {
applicationId "me.kevingleason.pnwebrtc"
minSdkVersion 15
targetSdkVersion 21
versionName project.VERSION_NAME
versionCode Integer.parseInt(project.VERSION_CODE)
}
buildTypes {
release {
runProguard false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
compile 'io.pristine:libjingle:9694@aar'
compile 'com.pubnub:pubnub-android:3.7.4'
}
//apply from: '../maven_push.gradle'
================================================
FILE: pnwebrtc/gradle.properties
================================================
POM_NAME=PubNub WebRTC API
POM_ARTIFACT_ID=pnwebrtc
POM_PACKAGING=aar
================================================
FILE: pnwebrtc/proguard-rules.pro
================================================
# Add project specific ProGuard rules here.
# By default, the flags in this file are appended to flags specified
# in /Users/GleasonK/algs4/AndroidSDK/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 *;
#}
================================================
FILE: pnwebrtc/src/androidTest/java/me/kevingleason/pnwebrtc/ApplicationTest.java
================================================
package me.kevingleason.pnwebrtc;
import android.app.Application;
import android.test.ApplicationTestCase;
/**
* <a href="http://d.android.com/tools/testing/testing_android.html">Testing Fundamentals</a>
*/
public class ApplicationTest extends ApplicationTestCase<Application> {
public ApplicationTest() {
super(Application.class);
}
}
================================================
FILE: pnwebrtc/src/main/AndroidManifest.xml
================================================
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="me.kevingleason.pnwebrtc">
<!-- WebRTC Dependencies -->
<uses-feature android:name="android.hardware.camera" />
<uses-feature android:name="android.hardware.camera.autofocus" />
<uses-feature
android:glEsVersion="0x00020000"
android:required="true" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
<!-- PubNub Dependencies -->
<!--<uses-permission android:name="android.permission.INTERNET" />-->
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" />
<permission android:name="your.package.name.permission.C2D_MESSAGE" android:protectionLevel="signature" />
<uses-permission android:name="your.package.name.permission.C2D_MESSAGE" />
<application android:allowBackup="true" android:label="@string/app_name">
</application>
</manifest>
================================================
FILE: pnwebrtc/src/main/java/me/kevingleason/pnwebrtc/PnPeer.java
================================================
package me.kevingleason.pnwebrtc;
import android.util.Log;
import org.json.JSONException;
import org.json.JSONObject;
import org.webrtc.DataChannel;
import org.webrtc.IceCandidate;
import org.webrtc.MediaStream;
import org.webrtc.PeerConnection;
import org.webrtc.SdpObserver;
import org.webrtc.SessionDescription;
/**
* <h1>PubNub Peer object to hold information on {@link org.webrtc.PeerConnection}</h1>
* <pre>
* Author: Kevin Gleason - Boston College '16
* File: PnPeer.java
* Date: 7/22/15
* Use: Store information about various Peer Connections
* © 2009 - 2015 PubNub, Inc.
* </pre>
*/
public class PnPeer implements SdpObserver, PeerConnection.Observer {
public static final String TAG = "PnPeer";
public static final String STATUS_CONNECTING = "CONNECTING";
public static final String STATUS_CONNECTED = "CONNECTED"; // TODO: Where to change status to this?
public static final String STATUS_DISCONNECTED = "DISCONNECTED";
public static final String TYPE_NONE = "NONE";
public static final String TYPE_OFFER = "offer";
public static final String TYPE_ANSWER = "answer";
private PnPeerConnectionClient pcClient;
PeerConnection pc;
String id;
String type;
String status;
boolean dialed;
boolean received;
// Todo: Maybe attach MediaStream as private var?
public PnPeer(String id, PnPeerConnectionClient pcClient) {
Log.d(TAG, "new Peer: " + id);
this.id = id;
this.type = TYPE_NONE;
this.dialed = false;
this.received = false;
this.pcClient = pcClient;
this.pc = pcClient.pcFactory.createPeerConnection(pcClient.signalingParams.iceServers,
pcClient.signalingParams.pcConstraints, this);
setStatus(STATUS_CONNECTING);
pc.addStream(pcClient.getLocalMediaStream());
}
public synchronized void setStatus(String status){
this.status = status;
pcClient.mRtcListener.onPeerStatusChanged(this);
}
public String getStatus() {
return status;
}
public void setType(String type){this.type = type;}
public String getType() {
return type;
}
public boolean isDialed() {
return dialed;
}
public void setDialed(boolean dialed) {
this.dialed = dialed;
}
public boolean isReceived() {
return received;
}
public void setReceived(boolean received) {
this.received = received;
}
public PeerConnection getPc() {
return pc;
}
public String getId() {
return id;
}
public void hangup(){
if (this.status.equals(STATUS_DISCONNECTED)) return; // Already hung up on.
this.pcClient.removePeer(this.id);
setStatus(STATUS_DISCONNECTED);
}
@Override
public void onCreateSuccess(final SessionDescription sdp) {
// TODO: modify sdp to use pcParams prefered codecs
try {
JSONObject payload = new JSONObject();
payload.put("type", sdp.type.canonicalForm());
payload.put("sdp", sdp.description);
pcClient.transmitMessage(id, payload);
pc.setLocalDescription(PnPeer.this, sdp);
} catch (JSONException e) {
e.printStackTrace();
}
}
@Override
public void onSetSuccess() {
}
@Override
public void onCreateFailure(String s) {
}
@Override
public void onSetFailure(String s) {
}
@Override
public void onSignalingChange(PeerConnection.SignalingState signalingState) {
}
@Override
public void onIceConnectionChange(PeerConnection.IceConnectionState iceConnectionState) {
if (this.status.equals(STATUS_DISCONNECTED)) return; // Already hung up on.
if (iceConnectionState == PeerConnection.IceConnectionState.DISCONNECTED) {
pcClient.removePeer(id); // TODO: Ponder. Also, might want to Pub a disconnect.
setStatus(STATUS_DISCONNECTED);
}
}
// Todo: Look into what this should be used for
@Override
public void onIceConnectionReceivingChange(boolean iceConnectionReceivingChange){
}
@Override
public void onIceGatheringChange(PeerConnection.IceGatheringState iceGatheringState) {
}
@Override
public void onIceCandidate(final IceCandidate candidate) {
try {
JSONObject payload = new JSONObject();
payload.put("sdpMLineIndex", candidate.sdpMLineIndex);
payload.put("sdpMid", candidate.sdpMid);
payload.put("candidate", candidate.sdp);
pcClient.transmitMessage(id, payload);
} catch (JSONException e) {
e.printStackTrace();
}
}
@Override
public void onAddStream(MediaStream mediaStream) {
Log.d(TAG, "onAddStream " + mediaStream.label());
// remote streams are displayed from 1 to MAX_PEER (0 is localStream)
pcClient.mRtcListener.onAddRemoteStream(mediaStream, PnPeer.this);
}
@Override
public void onRemoveStream(MediaStream mediaStream) {
Log.d(TAG, "onRemoveStream " + mediaStream.label());
PnPeer peer = pcClient.removePeer(id);
pcClient.mRtcListener.onRemoveRemoteStream(mediaStream, peer);
}
@Override
public void onDataChannel(DataChannel dataChannel) {
}
@Override
public void onRenegotiationNeeded() {
}
/**
* Overriding toString for debugging purposes.
* @return String representation of a peer.
*/
@Override
public String toString(){
return this.id + " Status: " + this.status + " Dialed: " + this.dialed +
" Received: " + this.received + " Type: " + this.type;
}
}
================================================
FILE: pnwebrtc/src/main/java/me/kevingleason/pnwebrtc/PnPeerConnectionClient.java
================================================
package me.kevingleason.pnwebrtc;
import android.util.Log;
import com.pubnub.api.Callback;
import com.pubnub.api.Pubnub;
import com.pubnub.api.PubnubError;
import com.pubnub.api.PubnubException;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.webrtc.IceCandidate;
import org.webrtc.MediaStream;
import org.webrtc.PeerConnection;
import org.webrtc.PeerConnectionFactory;
import org.webrtc.SessionDescription;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
/**
* <h1>PeerConnection manager for {@link me.kevingleason.pnwebrtc.PnRTCClient}</h1>
* <pre>
* Author: Kevin Gleason - Boston College '16
* File: PnPeerConnectionClient.java
* Date: 7/20/15
* Use: WebRTC PeerConnection Manager
* © 2009 - 2015 PubNub, Inc.
* </pre>
*
* {@link PnPeerConnectionClient} is used to manage peer connections.
*/
public class PnPeerConnectionClient {
private SessionDescription localSdp = null; // either offer or answer SDP
private MediaStream localMediaStream = null;
PeerConnectionFactory pcFactory;
PnRTCListener mRtcListener;
PnSignalingParams signalingParams;
int MAX_CONNECTIONS = Integer.MAX_VALUE;
private Pubnub mPubNub;
private PnRTCReceiver mSubscribeReceiver;
private Map<String,PnAction> actionMap;
private Map<String,PnPeer> peers;
private String id;
public PnPeerConnectionClient(Pubnub pubnub, PnSignalingParams signalingParams, PnRTCListener rtcListener){
this.mPubNub = pubnub;
this.signalingParams = signalingParams;
this.mRtcListener = rtcListener;
this.pcFactory = new PeerConnectionFactory(); // TODO: Check it allowed, else extra param
this.peers = new HashMap<String, PnPeer>();
init();
}
private void init(){
this.actionMap = new HashMap<String, PnAction>();
this.actionMap.put(CreateOfferAction.TRIGGER, new CreateOfferAction());
this.actionMap.put(CreateAnswerAction.TRIGGER, new CreateAnswerAction());
this.actionMap.put(SetRemoteSDPAction.TRIGGER, new SetRemoteSDPAction());
this.actionMap.put(AddIceCandidateAction.TRIGGER, new AddIceCandidateAction());
this.actionMap.put(PnUserHangupAction.TRIGGER, new PnUserHangupAction());
this.actionMap.put(PnUserMessageAction.TRIGGER, new PnUserMessageAction());
mSubscribeReceiver = new PnRTCReceiver();
}
boolean listenOn(String myId){ // Todo: return success?
if (localMediaStream==null){ // Not true for streaming?
mRtcListener.onDebug(new PnRTCMessage("Need to add media stream before you can connect."));
return false;
}
if (this.id != null){ // Prevent listening on multiple channels.
mRtcListener.onDebug(new PnRTCMessage("Already listening on " + this.id + ". Cannot have multiple connections."));
return false;
}
this.id = myId;
subscribe(myId);
return true;
}
/**TODO: Add a max user threshold.
* Connect with another user by their ID.
* @param userId The user to establish a WebRTC connection with
* @return boolean value of success
*/
boolean connect(String userId) {
if (!peers.containsKey(userId)) { // Prevents duplicate dials.
if (peers.size() < MAX_CONNECTIONS) {
PnPeer peer = addPeer(userId);
peer.pc.addStream(this.localMediaStream);
try {
actionMap.get(CreateOfferAction.TRIGGER).execute(userId, new JSONObject());
} catch (JSONException e){
e.printStackTrace();
return false;
}
return true;
}
}
this.mRtcListener.onDebug(new PnRTCMessage("CONNECT FAILED. Duplicate dial or max peer " +
"connections exceeded. Max: " + MAX_CONNECTIONS + " Current: " + this.peers.size()));
return false;
}
public void setRTCListener(PnRTCListener listener){
this.mRtcListener = listener;
}
private void subscribe(String channel){
try {
mPubNub.subscribe(channel, this.mSubscribeReceiver);
} catch (PubnubException e){
e.printStackTrace();
}
}
public void setLocalMediaStream(MediaStream localStream){
this.localMediaStream = localStream;
mRtcListener.onLocalStream(localStream);
}
public MediaStream getLocalMediaStream(){
return this.localMediaStream;
}
private PnPeer addPeer(String id) {
PnPeer peer = new PnPeer(id, this);
peers.put(id, peer);
return peer;
}
PnPeer removePeer(String id) {
PnPeer peer = peers.get(id);
peer.pc.close();
return peers.remove(peer.id);
}
List<PnPeer> getPeers(){
return new ArrayList<PnPeer>(this.peers.values());
}
/**
* Close connection (hangup) no a certain peer.
* @param id PnPeer id to close connection with
*/
public void closeConnection(String id){
JSONObject packet = new JSONObject();
try {
if (!this.peers.containsKey(id)) return;
PnPeer peer = this.peers.get(id);
peer.hangup();
packet.put(PnRTCMessage.JSON_HANGUP, true);
transmitMessage(id, packet);
mRtcListener.onPeerConnectionClosed(peer);
} catch (JSONException e){
e.printStackTrace();
}
}
/**
* Close connections (hangup) on all open connections.
*/
public void closeAllConnections() {
Iterator<String> peerIds = this.peers.keySet().iterator();
while (peerIds.hasNext()) {
closeConnection(peerIds.next());
}
}
/**
* Send SDP Offers/Answers nd ICE candidates to peers.
* @param toID The id or "number" that you wish to transmit a message to.
* @param packet The JSON data to be transmitted
*/
void transmitMessage(String toID, JSONObject packet){
if (this.id==null){ // Not logged in. Put an error in the debug cb.
mRtcListener.onDebug(new PnRTCMessage("Cannot transmit before calling Client.connect"));
}
try {
JSONObject message = new JSONObject();
message.put(PnRTCMessage.JSON_PACKET, packet);
message.put(PnRTCMessage.JSON_ID, ""); //Todo: session id, unused in js SDK?
message.put(PnRTCMessage.JSON_NUMBER, this.id);
this.mPubNub.publish(toID, message, new Callback() { // Todo: reconsider callback.
@Override
public void successCallback(String channel, Object message, String timetoken) {
mRtcListener.onDebug(new PnRTCMessage((JSONObject)message));
}
@Override
public void errorCallback(String channel, PubnubError error) {
mRtcListener.onDebug(new PnRTCMessage(error.errorObject));
}
});
} catch (JSONException e){
e.printStackTrace();
}
}
private interface PnAction{
void execute(String peerId, JSONObject payload) throws JSONException;
}
private class CreateOfferAction implements PnAction{
public static final String TRIGGER = "init";
public void execute(String peerId, JSONObject payload) throws JSONException {
Log.d("COAction","CreateOfferAction");
PnPeer peer = peers.get(peerId);
peer.setDialed(true);
peer.setType(PnPeer.TYPE_ANSWER);
peer.pc.createOffer(peer, signalingParams.pcConstraints);
}
}
private class CreateAnswerAction implements PnAction{
public static final String TRIGGER = "offer";
public void execute(String peerId, JSONObject payload) throws JSONException {
Log.d("CAAction","CreateAnswerAction");
PnPeer peer = peers.get(peerId);
peer.setType(PnPeer.TYPE_OFFER);
peer.setStatus(PnPeer.STATUS_CONNECTED);
SessionDescription sdp = new SessionDescription(
SessionDescription.Type.fromCanonicalForm(payload.getString("type")),
payload.getString("sdp")
);
peer.pc.setRemoteDescription(peer, sdp);
peer.pc.createAnswer(peer, signalingParams.pcConstraints);
}
}
private class SetRemoteSDPAction implements PnAction{
public static final String TRIGGER = "answer";
public void execute(String peerId, JSONObject payload) throws JSONException {
Log.d("SRSAction","SetRemoteSDPAction");
PnPeer peer = peers.get(peerId);
SessionDescription sdp = new SessionDescription(
SessionDescription.Type.fromCanonicalForm(payload.getString("type")),
payload.getString("sdp")
);
peer.pc.setRemoteDescription(peer, sdp);
}
}
private class AddIceCandidateAction implements PnAction{
public static final String TRIGGER = "candidate";
public void execute(String peerId, JSONObject payload) throws JSONException {
Log.d("AICAction","AddIceCandidateAction");
PeerConnection pc = peers.get(peerId).pc;
if (pc.getRemoteDescription() != null) {
IceCandidate candidate = new IceCandidate(
payload.getString("sdpMid"),
payload.getInt("sdpMLineIndex"),
payload.getString("candidate")
);
pc.addIceCandidate(candidate);
}
}
}
private class PnUserHangupAction implements PnAction{
public static final String TRIGGER = PnRTCMessage.JSON_HANGUP;
public void execute(String peerId, JSONObject payload) throws JSONException {
Log.d("PnUserHangup","PnUserHangupAction");
PnPeer peer = peers.get(peerId);
peer.hangup();
mRtcListener.onPeerConnectionClosed(peer);
// Todo: Consider Callback?
}
}
private class PnUserMessageAction implements PnAction{
public static final String TRIGGER = PnRTCMessage.JSON_USERMSG;
public void execute(String peerId, JSONObject payload) throws JSONException {
Log.d("PnUserMessage","AddIceCandidateAction");
JSONObject msgJson = payload.getJSONObject(PnRTCMessage.JSON_USERMSG);
PnPeer peer = peers.get(peerId);
mRtcListener.onMessage(peer, msgJson);
}
}
/**
* @param userId Your id. Used to tag the message before publishing it to another user.
* @return
*/
public static JSONObject generateHangupPacket(String userId){
JSONObject json = new JSONObject();
try {
JSONObject packet = new JSONObject();
packet.put(PnRTCMessage.JSON_HANGUP, true);
json.put(PnRTCMessage.JSON_PACKET, packet);
json.put(PnRTCMessage.JSON_ID, ""); //Todo: session id, unused in js SDK?
json.put(PnRTCMessage.JSON_NUMBER, userId);
} catch (JSONException e){
e.printStackTrace();
}
return json;
}
/**
* Static method to generate the proper JSON for a user message. Use this when you don't have
* a {@link me.kevingleason.pnwebrtc.PnRTCClient} instantiated. Simply send a publish with the
* returned JSONObject to the ID that a user is subscribed to.
* @param userId Your UserID, needed to tag the message
* @param message The message you with to send some other user
* @return JSONObject properly formatted for the PubNub WebRTC API
*/
public static JSONObject generateUserMessage(String userId, JSONObject message){
JSONObject json = new JSONObject();
try {
JSONObject packet = new JSONObject();
packet.put(PnRTCMessage.JSON_USERMSG, message);
json.put(PnRTCMessage.JSON_PACKET, packet);
json.put(PnRTCMessage.JSON_ID, ""); //Todo: session id, unused in js SDK?
json.put(PnRTCMessage.JSON_NUMBER, userId);
} catch (JSONException e){
e.printStackTrace();
}
return json;
}
private class PnRTCReceiver extends Callback {
@Override
public void connectCallback(String channel, Object message) {
mRtcListener.onDebug(new PnRTCMessage(((JSONArray) message).toString()));
mRtcListener.onConnected(channel);
}
@Override
public void successCallback(String channel, Object message) {
if (!(message instanceof JSONObject)) return; // Ignore if not valid JSON.
JSONObject jsonMessage = (JSONObject) message;
mRtcListener.onDebug(new PnRTCMessage(jsonMessage));
try {
String peerId = jsonMessage.getString(PnRTCMessage.JSON_NUMBER);
JSONObject packet = jsonMessage.getJSONObject(PnRTCMessage.JSON_PACKET);
PnPeer peer;
if (!peers.containsKey(peerId)){
// Possibly threshold number of allowed users
peer = addPeer(peerId);
peer.pc.addStream(localMediaStream);
} else {
peer = peers.get(peerId);
}
if (peer.getStatus().equals(PnPeer.STATUS_DISCONNECTED)) return; // Do nothing if disconnected.
if (packet.has(PnRTCMessage.JSON_USERMSG)) {
actionMap.get(PnUserMessageAction.TRIGGER).execute(peerId,packet);
return;
}
if (packet.has(PnRTCMessage.JSON_HANGUP)){
actionMap.get(PnUserHangupAction.TRIGGER).execute(peerId,packet);
return;
}
if (packet.has(PnRTCMessage.JSON_THUMBNAIL)) {
return; // No handler for thumbnail or hangup yet, will be separate controller callback
}
if (packet.has(PnRTCMessage.JSON_SDP)) {
if(!peer.received) {
peer.setReceived(true);
mRtcListener.onDebug(new PnRTCMessage("SDP - " + peer.toString()));
// Todo: reveivercb(peer);
}
String type = packet.getString(PnRTCMessage.JSON_TYPE);
actionMap.get(type).execute(peerId, packet);
return;
}
if (packet.has(PnRTCMessage.JSON_ICE)){
actionMap.get(AddIceCandidateAction.TRIGGER).execute(peerId,packet);
return;
}
} catch (JSONException e){
e.printStackTrace();
}
}
@Override
public void errorCallback(String channel, PubnubError error) {
super.errorCallback(channel, error);
}
}
}
================================================
FILE: pnwebrtc/src/main/java/me/kevingleason/pnwebrtc/PnRTCClient.java
================================================
package me.kevingleason.pnwebrtc;
import com.pubnub.api.Pubnub;
import org.json.JSONException;
import org.json.JSONObject;
import org.webrtc.MediaConstraints;
import org.webrtc.MediaStream;
import java.util.List;
/**
* <h1>Main WebRTC Signaling class, holds all functions to set up {@link org.webrtc.PeerConnection}</h1>
* <pre>
* Author: Kevin Gleason - Boston College '16
* File: PnRTCClient.java
* Date: 7/20/15
* Use: PubNub WebRTC Signaling
* © 2009 - 2015 PubNub, Inc.
* </pre>
*/
public class PnRTCClient {
private PnSignalingParams pnSignalingParams;
private Pubnub mPubNub;
private PnPeerConnectionClient pcClient;
private String UUID;
/**
* Minimal constructor. Requires a valid Pub and Sub key. Get your Pub/Sub keys for free at
* https://admin.pubnub.com/#/register and find keys on developer portal.
* No UUID provided so a random phone number will be generated with this constructor (XXX-XXXX).
* @param pubKey PubNub Pub Key
* @param subKey PubNub Sub Key
*/
public PnRTCClient(String pubKey, String subKey) {
this.UUID = generateRandomNumber();
this.mPubNub = new Pubnub(pubKey, subKey);
this.mPubNub.setUUID(this.UUID);
this.pnSignalingParams = PnSignalingParams.defaultInstance();
this.pcClient = new PnPeerConnectionClient(this.mPubNub, this.pnSignalingParams, new PnRTCListener() {});
}
/**
* Slightly more verbose constructor. Requires a valid Pub and Sub key. Get your Pub/Sub keys for free at
* https://admin.pubnub.com/#/register and find keys on developer portal.
* @param pubKey PubNub Pub Key
* @param subKey PubNub Sub Key
* @param UUID Any UUID to be used as a username
*/
public PnRTCClient(String pubKey, String subKey, String UUID) {
this.UUID = UUID;
this.mPubNub = new Pubnub(pubKey, subKey);
this.mPubNub.setUUID(this.UUID);
this.pnSignalingParams = PnSignalingParams.defaultInstance();
this.pcClient = new PnPeerConnectionClient(this.mPubNub, this.pnSignalingParams, new PnRTCListener() {});
}
/**
* Return the {@link me.kevingleason.pnwebrtc.PnRTCClient} peer connection constraints.
* @return Peer Connection Constrains
*/
public MediaConstraints pcConstraints() {
return pnSignalingParams.pcConstraints;
}
/**
* Return the {@link me.kevingleason.pnwebrtc.PnRTCClient} video constraints.
* @return Video Constrains
*/
public MediaConstraints videoConstraints() {
return pnSignalingParams.videoConstraints;
}
/**
* Return the {@link me.kevingleason.pnwebrtc.PnRTCClient} audio constraints.
* @return Audio Constrains
*/
public MediaConstraints audioConstraints() {
return pnSignalingParams.audioConstraints;
}
/**
* Return the {@link me.kevingleason.pnwebrtc.PnRTCClient} Pubnub instance.
* @return The PnRTCClient's {@link com.pubnub.api.Pubnub} instance
*/
public Pubnub getPubNub(){
return this.mPubNub;
}
/**
* Return the UUID (username) of the {@link me.kevingleason.pnwebrtc.PnRTCClient}. If not
* provided by the constructor, a random phone number is generated and can be retrieived
* with this method
* @return The UUID username of the client
*/
public String getUUID() {
return UUID;
}
/**
* Set the signaling parameters. This includes {@link org.webrtc.MediaConstraints} for
* {@link org.webrtc.PeerConnection}, Video, and Audio, as well as a list of possible
* {@link org.webrtc.PeerConnection.IceServer} candidates.
* @param signalParams Parameters for WebRTC Signaling
*/
public void setSignalParams(PnSignalingParams signalParams){
this.pnSignalingParams = signalParams;
}
/**
* Need to attach mediaStream before you can connect.
* @param mediaStream Not null local media stream
*/
public void attachLocalMediaStream(MediaStream mediaStream){
this.pcClient.setLocalMediaStream(mediaStream);
}
/**
* Attach custom listener for callbacks!
* @param listener The listener which extends PnRTCListener to implement callbacks
*/
public void attachRTCListener(PnRTCListener listener){
this.pcClient.setRTCListener(listener);
}
/**
* Set the maximum simultaneous connections allowed
* @param max Max simultaneous connections
*/
public void setMaxConnections(int max){
this.pcClient.MAX_CONNECTIONS = max;
}
/**
* Subscribe to a channel using PubNub to listen for calls.
* @param channel The channel to listen on, your "phone number"
*/
public void listenOn(String channel){
this.pcClient.listenOn(channel);
}
/**
* Connect with another user by their ID.
* @param userId The user to establish a WebRTC connection with
*/
public void connect(String userId){
this.pcClient.connect(userId);
}
/**
* Close a single peer connection. Send a PubNub hangup signal as well
* @param userId User to close a connection with
*/
public void closeConnection(String userId){
this.pcClient.closeConnection(userId);
}
/**
* Close all peer connections. Send a PubNub hangup signal as well.
*/
public void closeAllConnections(){
this.pcClient.closeAllConnections();
}
/**
* Send a custom JSONObject user message to a single peer.
* @param userId user to send a message to
* @param message the JSON message to pass to a peer.
*/
public void transmit(String userId, JSONObject message){
JSONObject usrMsgJson = new JSONObject();
try {
usrMsgJson.put(PnRTCMessage.JSON_USERMSG, message);
this.pcClient.transmitMessage(userId, usrMsgJson);
} catch (JSONException e){
e.printStackTrace();
}
}
/**
* Send a custom JSONObject user message to all peers.
* @param message the JSON message to pass to all peers.
*/
public void transmitAll(JSONObject message){
List<PnPeer> peerList = this.pcClient.getPeers();
for(PnPeer p : peerList){
transmit(p.getId(), message);
}
}
private static String generateRandomNumber(){
String areaCode = String.valueOf((int)(Math.random()*1000));
String digits = String.valueOf((int)(Math.random()*10000));
while (areaCode.length() < 3) areaCode += "0";
while (digits.length() < 4) digits += "0";
return areaCode + "-" + digits;
}
/**
* Call this method in Activity.onDestroy() to clost all open connections and clean up
* instance for garbage collection.
*/
public void onDestroy() {
this.pcClient.closeAllConnections();
this.mPubNub.unsubscribeAll();
}
}
================================================
FILE: pnwebrtc/src/main/java/me/kevingleason/pnwebrtc/PnRTCListener.java
================================================
package me.kevingleason.pnwebrtc;
import org.webrtc.MediaStream;
/**
* <h1>Callback listener for various WebRTC and {@link org.webrtc.PeerConnection.Observer} events.</h1>
* <pre>
* Author: Kevin Gleason - Boston College '16
* File: PnRTCListener.java
* Date: 7/20/15
* Use: Callback listener for various WebRTC events
* © 2009 - 2015 PubNub, Inc.
* </pre>
* <h2>About this class:</h2>
* <p>
* Implement this interface to be notified of WebRTC events.
* It is an abstract class with default behaviors of doing nothing.
* Use a PnRTCListener to implement the various callbacks of your WebRTC application.
* </p>
*/
public abstract class PnRTCListener{
public void onCallReady(String callId){} // TODO: Maybe not needed?
/**
* Called in {@link com.pubnub.api.Pubnub} object's subscribe connected callback.
* Means that you are ready to receive calls.
* @param userId The channel you are subscribed to, the userId you may be called on.
*/
public void onConnected(String userId){}
/**
* Peer status changed. {@link PnPeer} status changed, can be
* CONNECTING, CONNECTED, or DISCONNECTED.
* @param peer The peer object, can use to check peer.getStatus()
*/
public void onPeerStatusChanged(PnPeer peer){}
/**TODO: Is this different than onPeerStatusChanged == DISCONNECTED?
* Called when a hangup occurs.
* @param peer The peer who was hung up on, or who hung up on you
*/
public void onPeerConnectionClosed(PnPeer peer){}
/**
* Called in {@link PnPeerConnectionClient} when setLocalStream
* is invoked.
* @param localStream The users local stream from Android's front or back camera.
*/
public void onLocalStream(MediaStream localStream){}
/**
* Called when a remote stream is added in the {@link org.webrtc.PeerConnection.Observer}
* in {@link PnPeer}.
* @param remoteStream The remote stream that was added
* @param peer The peer that added the remote stream
* Todo: Maybe not the right peer?
*/
public void onAddRemoteStream(MediaStream remoteStream, PnPeer peer){}
/**
* Called in the {@link org.webrtc.PeerConnection.Observer} implemented
* by {@link PnPeer}.
* @param remoteStream The stream that was removed by your peer
* @param peer The peer that removed the stream.
*/
public void onRemoveRemoteStream(MediaStream remoteStream, PnPeer peer){}
/**
* Called when a user message is send via {@link com.pubnub.api.Pubnub} object.
* @param peer The peer who sent the message
* @param message The {@link org.json.JSONObject} message sent by the user.
*/
public void onMessage(PnPeer peer, Object message){}
/**
* A helpful debugging callback for testing and developing your app.
* @param message The {@link PnRTCMessage} debug message.
*/
public void onDebug(PnRTCMessage message){}
}
================================================
FILE: pnwebrtc/src/main/java/me/kevingleason/pnwebrtc/PnRTCMessage.java
================================================
package me.kevingleason.pnwebrtc;
import org.json.JSONException;
import org.json.JSONObject;
/**
* <h1>Used for debug messages and static definitions of JSON keys</h1>
* <pre>
* Author: Kevin Gleason - Boston College '16
* File: PnRTCMessage.java
* Date: 7/20/15
* Use: Debug messages and JSON key definitions
* © 2009 - 2015 PubNub, Inc.
* </pre>
*/
public class PnRTCMessage extends JSONObject {
public static final String JSON_TYPE = "type";
public static final String JSON_PACKET = "packet";
public static final String JSON_ID = "id";
public static final String JSON_NUMBER = "number"; // Todo: Change to more accurate name.
public static final String JSON_MESSAGE = "message";
public static final String JSON_USERMSG = "usermsg";
public static final String JSON_HANGUP = "hangup";
public static final String JSON_THUMBNAIL = "thumbnail";
public static final String JSON_SDP = "sdp";
public static final String JSON_ICE = "candidate"; // Identify ICE
private String message;
private JSONObject json;
public PnRTCMessage(String message){
super();
try {
this.put(JSON_MESSAGE, message);
} catch (JSONException e){
throw new RuntimeException("Invalid JSON Payload");
}
this.message = message;
this.json = this;
}
public PnRTCMessage(JSONObject json){
super();
try {
if (json==null){
json = new JSONObject("{error:1}");
}
this.put(JSON_MESSAGE, json);
} catch (JSONException e){
throw new RuntimeException("Invalid JSON Payload");
}
this.message = json.toString();
this.json = this;
}
public String getMessage() {
return message;
}
public JSONObject getJSON(){
return this.json;
}
}
================================================
FILE: pnwebrtc/src/main/java/me/kevingleason/pnwebrtc/PnSignalingParams.java
================================================
package me.kevingleason.pnwebrtc;
import org.webrtc.MediaConstraints;
import org.webrtc.PeerConnection;
import java.util.ArrayList;
import java.util.List;
/**
* <h1>Define {@link org.webrtc.MediaConstraints} and {@link org.webrtc.PeerConnection.IceServer} for WebRTC PeerConnections</h1>
* <pre>
* Author: Kevin Gleason - Boston College '16
* File: PnSignalingParams.java
* Date: 7/20/15
* Use: Hold the signaling parameters of a WebRTC PeerConnection
* © 2009 - 2015 PubNub, Inc.
* </pre>
* <p>IceServers allow Trickling, so they are not final.</p>
*
*/
public class PnSignalingParams {
public List<PeerConnection.IceServer> iceServers;
public final MediaConstraints pcConstraints;
public final MediaConstraints videoConstraints;
public final MediaConstraints audioConstraints;
public PnSignalingParams(
List<PeerConnection.IceServer> iceServers,
MediaConstraints pcConstraints,
MediaConstraints videoConstraints,
MediaConstraints audioConstraints) {
this.iceServers = (iceServers==null) ? defaultIceServers() : iceServers;
this.pcConstraints = (pcConstraints==null) ? defaultPcConstraints() : pcConstraints;
this.videoConstraints = (videoConstraints==null) ? defaultVideoConstraints() : videoConstraints;
this.audioConstraints = (audioConstraints==null) ? defaultAudioConstraints() : audioConstraints;
}
/**
* Default Ice Servers, but specified parameters.
* @param pcConstraints
* @param videoConstraints
* @param audioConstraints
*/
public PnSignalingParams(
MediaConstraints pcConstraints,
MediaConstraints videoConstraints,
MediaConstraints audioConstraints) {
this.iceServers = PnSignalingParams.defaultIceServers();
this.pcConstraints = (pcConstraints==null) ? defaultPcConstraints() : pcConstraints;
this.videoConstraints = (videoConstraints==null) ? defaultVideoConstraints() : videoConstraints;
this.audioConstraints = (audioConstraints==null) ? defaultAudioConstraints() : audioConstraints;
}
/**
* Default media params, but specified Ice Servers
* @param iceServers
*/
public PnSignalingParams(List<PeerConnection.IceServer> iceServers) {
this.iceServers = iceServers; //defaultIceServers();
this.pcConstraints = defaultPcConstraints();
this.videoConstraints = defaultVideoConstraints();
this.audioConstraints = defaultAudioConstraints();
// addIceServers(iceServers);
}
/**
* Default media params and ICE servers.
*/
public PnSignalingParams() {
this.iceServers = defaultIceServers();
this.pcConstraints = defaultPcConstraints();
this.videoConstraints = defaultVideoConstraints();
this.audioConstraints = defaultAudioConstraints();
}
/**
* The default parameters for media constraints. Might have to tweak in future.
* @return default parameters
*/
public static PnSignalingParams defaultInstance() {
MediaConstraints pcConstraints = PnSignalingParams.defaultPcConstraints();
MediaConstraints videoConstraints = PnSignalingParams.defaultVideoConstraints();
MediaConstraints audioConstraints = PnSignalingParams.defaultAudioConstraints();
List<PeerConnection.IceServer> iceServers = PnSignalingParams.defaultIceServers();
return new PnSignalingParams(iceServers, pcConstraints, videoConstraints, audioConstraints);
}
private static MediaConstraints defaultPcConstraints(){
MediaConstraints pcConstraints = new MediaConstraints();
pcConstraints.optional.add(new MediaConstraints.KeyValuePair("DtlsSrtpKeyAgreement", "true"));
pcConstraints.mandatory.add(new MediaConstraints.KeyValuePair("OfferToReceiveAudio", "true"));
pcConstraints.mandatory.add(new MediaConstraints.KeyValuePair("OfferToReceiveVideo", "true"));
return pcConstraints;
}
private static MediaConstraints defaultVideoConstraints(){
MediaConstraints videoConstraints = new MediaConstraints();
videoConstraints.mandatory.add(new MediaConstraints.KeyValuePair("maxWidth","1280"));
videoConstraints.mandatory.add(new MediaConstraints.KeyValuePair("maxHeight","720"));
videoConstraints.mandatory.add(new MediaConstraints.KeyValuePair("minWidth", "640"));
videoConstraints.mandatory.add(new MediaConstraints.KeyValuePair("minHeight","480"));
return videoConstraints;
}
private static MediaConstraints defaultAudioConstraints(){
MediaConstraints audioConstraints = new MediaConstraints();
return audioConstraints;
}
public static List<PeerConnection.IceServer> defaultIceServers(){
List<PeerConnection.IceServer> iceServers = new ArrayList<PeerConnection.IceServer>(25);
iceServers.add(new PeerConnection.IceServer("stun:stun.l.google.com:19302"));
iceServers.add(new PeerConnection.IceServer("stun:stun.services.mozilla.com"));
iceServers.add(new PeerConnection.IceServer("turn:turn.bistri.com:80", "homeo", "homeo"));
iceServers.add(new PeerConnection.IceServer("turn:turn.anyfirewall.com:443?transport=tcp", "webrtc", "webrtc"));
// Extra Defaults - 19 STUN servers + 4 initial = 23 severs (+2 padding) = Array cap 25
iceServers.add(new PeerConnection.IceServer("stun:stun1.l.google.com:19302"));
iceServers.add(new PeerConnection.IceServer("stun:stun2.l.google.com:19302"));
iceServers.add(new PeerConnection.IceServer("stun:stun3.l.google.com:19302"));
iceServers.add(new PeerConnection.IceServer("stun:stun4.l.google.com:19302"));
iceServers.add(new PeerConnection.IceServer("stun:23.21.150.121"));
iceServers.add(new PeerConnection.IceServer("stun:stun01.sipphone.com"));
iceServers.add(new PeerConnection.IceServer("stun:stun.ekiga.net"));
iceServers.add(new PeerConnection.IceServer("stun:stun.fwdnet.net"));
iceServers.add(new PeerConnection.IceServer("stun:stun.ideasip.com"));
iceServers.add(new PeerConnection.IceServer("stun:stun.iptel.org"));
iceServers.add(new PeerConnection.IceServer("stun:stun.rixtelecom.se"));
iceServers.add(new PeerConnection.IceServer("stun:stun.schlund.de"));
iceServers.add(new PeerConnection.IceServer("stun:stunserver.org"));
iceServers.add(new PeerConnection.IceServer("stun:stun.softjoys.com"));
iceServers.add(new PeerConnection.IceServer("stun:stun.voiparound.com"));
iceServers.add(new PeerConnection.IceServer("stun:stun.voipbuster.com"));
iceServers.add(new PeerConnection.IceServer("stun:stun.voipstunt.com"));
iceServers.add(new PeerConnection.IceServer("stun:stun.voxgratia.org"));
iceServers.add(new PeerConnection.IceServer("stun:stun.xten.com"));
return iceServers;
}
/**
* Append default servers to the end of given list and set as iceServers instance variable
* @param iceServers List of iceServers
*/
public void addIceServers(List<PeerConnection.IceServer> iceServers){
if(this.iceServers!=null) {
iceServers.addAll(this.iceServers);
}
this.iceServers = iceServers;
}
/**
* Instantiate iceServers if they are not already, and add Ice Server to beginning of list.
* @param iceServers Ice Server to add
*/
public void addIceServers(PeerConnection.IceServer iceServers){
if (this.iceServers == null){
this.iceServers = new ArrayList<PeerConnection.IceServer>();
}
this.iceServers.add(0, iceServers);
}
}
================================================
FILE: pnwebrtc/src/main/res/values/strings.xml
================================================
<resources>
<string name="app_name">PnWebRTC</string>
</resources>
================================================
FILE: settings.gradle
================================================
include ':app', ':pnwebrtc'
gitextract_4p2k_p1l/ ├── .gitignore ├── LICENSE ├── README.md ├── app/ │ ├── .gitignore │ ├── build.gradle │ ├── proguard-rules.pro │ └── src/ │ ├── androidTest/ │ │ └── java/ │ │ └── me/ │ │ └── kevingleason/ │ │ └── androidrtc/ │ │ └── ApplicationTest.java │ └── main/ │ ├── AndroidManifest.xml │ ├── java/ │ │ └── me/ │ │ └── kevingleason/ │ │ └── androidrtc/ │ │ ├── IncomingCallActivity.java │ │ ├── LoginActivity.java │ │ ├── MainActivity.java │ │ ├── VideoChatActivity.java │ │ ├── adapters/ │ │ │ ├── ChatAdapter.java │ │ │ └── HistoryAdapter.java │ │ ├── adt/ │ │ │ ├── ChatMessage.java │ │ │ ├── ChatUser.java │ │ │ └── HistoryItem.java │ │ ├── servers/ │ │ │ └── XirSysRequest.java │ │ └── util/ │ │ ├── Constants.java │ │ └── LogRTCListener.java │ └── res/ │ ├── drawable/ │ │ ├── light_fade_down.xml │ │ ├── light_fade_up.xml │ │ ├── online_circle.xml │ │ ├── round_button.xml │ │ └── round_button_send.xml │ ├── layout/ │ │ ├── activity_incoming_call.xml │ │ ├── activity_login.xml │ │ ├── activity_main.xml │ │ ├── activity_video_chat.xml │ │ ├── chat_message_row_layout.xml │ │ └── history_row_layout.xml │ ├── menu/ │ │ ├── menu_incoming_call.xml │ │ ├── menu_login.xml │ │ ├── menu_main.xml │ │ └── menu_video_chat.xml │ ├── values/ │ │ ├── colors.xml │ │ ├── dimens.xml │ │ ├── strings.xml │ │ └── styles.xml │ ├── values-v21/ │ │ └── styles.xml │ └── values-w820dp/ │ └── dimens.xml ├── build.gradle ├── gradle/ │ └── wrapper/ │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradle.properties ├── gradlew ├── gradlew.bat ├── pnwebrtc/ │ ├── .gitignore │ ├── build.gradle │ ├── gradle.properties │ ├── proguard-rules.pro │ └── src/ │ ├── androidTest/ │ │ └── java/ │ │ └── me/ │ │ └── kevingleason/ │ │ └── pnwebrtc/ │ │ └── ApplicationTest.java │ └── main/ │ ├── AndroidManifest.xml │ ├── java/ │ │ └── me/ │ │ └── kevingleason/ │ │ └── pnwebrtc/ │ │ ├── PnPeer.java │ │ ├── PnPeerConnectionClient.java │ │ ├── PnRTCClient.java │ │ ├── PnRTCListener.java │ │ ├── PnRTCMessage.java │ │ └── PnSignalingParams.java │ └── res/ │ └── values/ │ └── strings.xml └── settings.gradle
SYMBOL INDEX (213 symbols across 20 files)
FILE: app/src/androidTest/java/me/kevingleason/androidrtc/ApplicationTest.java
class ApplicationTest (line 9) | public class ApplicationTest extends ApplicationTestCase<Application> {
method ApplicationTest (line 10) | public ApplicationTest() {
FILE: app/src/main/java/me/kevingleason/androidrtc/IncomingCallActivity.java
class IncomingCallActivity (line 23) | public class IncomingCallActivity extends Activity {
method onCreate (line 31) | @Override
method onCreateOptionsMenu (line 63) | @Override
method onOptionsItemSelected (line 70) | @Override
method acceptCall (line 85) | public void acceptCall(View view){
method rejectCall (line 96) | public void rejectCall(View view){
method onStop (line 107) | @Override
FILE: app/src/main/java/me/kevingleason/androidrtc/LoginActivity.java
class LoginActivity (line 18) | public class LoginActivity extends Activity {
method onCreate (line 22) | @Override
method onCreateOptionsMenu (line 37) | @Override
method onOptionsItemSelected (line 44) | @Override
method joinChat (line 64) | public void joinChat(View view){
method validUsername (line 83) | private boolean validUsername(String username) {
FILE: app/src/main/java/me/kevingleason/androidrtc/MainActivity.java
class MainActivity (line 31) | public class MainActivity extends ListActivity {
method onCreate (line 42) | @Override
method onCreateOptionsMenu (line 69) | @Override
method onOptionsItemSelected (line 76) | @Override
method onStop (line 94) | @Override
method onRestart (line 102) | @Override
method initPubNub (line 115) | public void initPubNub(){
method subscribeStdBy (line 124) | private void subscribeStdBy(){
method makeCall (line 162) | public void makeCall(View view){
method dispatchCall (line 180) | public void dispatchCall(final String callNum){
method dispatchIncomingCall (line 216) | private void dispatchIncomingCall(String userId){
method setUserStatus (line 224) | private void setUserStatus(String status){
method getUserStatus (line 239) | private void getUserStatus(String userId){
method showToast (line 253) | private void showToast(final String message){
method signOut (line 266) | public void signOut(){
FILE: app/src/main/java/me/kevingleason/androidrtc/VideoChatActivity.java
class VideoChatActivity (line 54) | public class VideoChatActivity extends ListActivity {
method onCreate (line 73) | @Override
method onCreateOptionsMenu (line 168) | @Override
method onOptionsItemSelected (line 175) | @Override
method onPause (line 190) | @Override
method onResume (line 197) | @Override
method onDestroy (line 204) | @Override
method onBackPressed (line 215) | @Override
method getXirSysIceServers (line 237) | public List<PeerConnection.IceServer> getXirSysIceServers(){
method connectToUser (line 249) | public void connectToUser(String user) {
method hangup (line 253) | public void hangup(View view) {
method endCall (line 258) | private void endCall() {
method sendMessage (line 264) | public void sendMessage(View view) {
class DemoRTCListener (line 291) | private class DemoRTCListener extends LogRTCListener {
method onLocalStream (line 292) | @Override
method onAddRemoteStream (line 304) | @Override
method onMessage (line 323) | @Override
method onPeerConnectionClosed (line 344) | @Override
FILE: app/src/main/java/me/kevingleason/androidrtc/adapters/ChatAdapter.java
class ChatAdapter (line 26) | public class ChatAdapter extends ArrayAdapter<ChatMessage> {
method ChatAdapter (line 33) | public ChatAdapter(Context context, List<ChatMessage> values) {
class ViewHolder (line 40) | class ViewHolder {
method getView (line 47) | @Override
method getCount (line 72) | @Override
method hasStableIds (line 77) | @Override
method getItemId (line 82) | @Override
method removeMsg (line 88) | public void removeMsg(int loc){
method addMessage (line 93) | public void addMessage(ChatMessage chatMsg){
method setFadeOut2 (line 98) | private void setFadeOut2(final View view, final ChatMessage message){
method setFadeOut3 (line 112) | private void setFadeOut3(final View view, final ChatMessage message){
method setFadeOut (line 135) | private void setFadeOut(final View view, final ChatMessage message){
method formatTimeStamp (line 182) | public static String formatTimeStamp(long timeStamp){
FILE: app/src/main/java/me/kevingleason/androidrtc/adapters/HistoryAdapter.java
class HistoryAdapter (line 37) | public class HistoryAdapter extends ArrayAdapter<HistoryItem> {
method HistoryAdapter (line 45) | public HistoryAdapter(Context context, List<HistoryItem> values, Pubnu...
class ViewHolder (line 55) | class ViewHolder {
method getView (line 63) | @Override
method getCount (line 93) | @Override
method removeButton (line 98) | public void removeButton(int loc){
method getUserStatus (line 103) | private void getUserStatus(final ChatUser user, final TextView statusV...
method updateHistory (line 126) | public void updateHistory(){
method updateAdapter (line 156) | private void updateAdapter(){
method formatTimeStamp (line 171) | public static String formatTimeStamp(long timeStamp){
FILE: app/src/main/java/me/kevingleason/androidrtc/adt/ChatMessage.java
class ChatMessage (line 6) | public class ChatMessage {
method ChatMessage (line 11) | public ChatMessage(String sender, String message, long timeStamp){
method getSender (line 17) | public String getSender() {
method getMessage (line 21) | public String getMessage() {
method getTimeStamp (line 25) | public long getTimeStamp() {
method hashCode (line 29) | @Override
FILE: app/src/main/java/me/kevingleason/androidrtc/adt/ChatUser.java
class ChatUser (line 8) | public class ChatUser {
method ChatUser (line 12) | public ChatUser(String userId) {
method ChatUser (line 17) | public ChatUser(String userId, String status) {
method getUserId (line 22) | public String getUserId() {
method getStatus (line 26) | public String getStatus() {
method setStatus (line 30) | public void setStatus(String status) {
method equals (line 34) | @Override
method hashCode (line 42) | @Override
FILE: app/src/main/java/me/kevingleason/androidrtc/adt/HistoryItem.java
class HistoryItem (line 6) | public class HistoryItem {
method HistoryItem (line 10) | public HistoryItem(ChatUser user, Long timeStamp){
method getUser (line 15) | public ChatUser getUser() {
method getTimeStamp (line 19) | public Long getTimeStamp() {
FILE: app/src/main/java/me/kevingleason/androidrtc/servers/XirSysRequest.java
class XirSysRequest (line 31) | public class XirSysRequest extends AsyncTask<Void,Void,List<PeerConnecti...
method doInBackground (line 33) | public List<PeerConnection.IceServer> doInBackground(Void... params){
FILE: app/src/main/java/me/kevingleason/androidrtc/util/Constants.java
class Constants (line 6) | public class Constants {
FILE: app/src/main/java/me/kevingleason/androidrtc/util/LogRTCListener.java
class LogRTCListener (line 14) | public class LogRTCListener extends PnRTCListener {
method onCallReady (line 15) | @Override
method onConnected (line 20) | @Override
method onPeerStatusChanged (line 25) | @Override
method onPeerConnectionClosed (line 30) | @Override
method onLocalStream (line 35) | @Override
method onAddRemoteStream (line 40) | @Override
method onRemoveRemoteStream (line 45) | @Override
method onMessage (line 50) | @Override
method onDebug (line 55) | @Override
FILE: pnwebrtc/src/androidTest/java/me/kevingleason/pnwebrtc/ApplicationTest.java
class ApplicationTest (line 9) | public class ApplicationTest extends ApplicationTestCase<Application> {
method ApplicationTest (line 10) | public ApplicationTest() {
FILE: pnwebrtc/src/main/java/me/kevingleason/pnwebrtc/PnPeer.java
class PnPeer (line 24) | public class PnPeer implements SdpObserver, PeerConnection.Observer {
method PnPeer (line 42) | public PnPeer(String id, PnPeerConnectionClient pcClient) {
method setStatus (line 55) | public synchronized void setStatus(String status){
method getStatus (line 60) | public String getStatus() {
method setType (line 64) | public void setType(String type){this.type = type;}
method getType (line 66) | public String getType() {
method isDialed (line 70) | public boolean isDialed() {
method setDialed (line 74) | public void setDialed(boolean dialed) {
method isReceived (line 78) | public boolean isReceived() {
method setReceived (line 82) | public void setReceived(boolean received) {
method getPc (line 86) | public PeerConnection getPc() {
method getId (line 90) | public String getId() {
method hangup (line 94) | public void hangup(){
method onCreateSuccess (line 100) | @Override
method onSetSuccess (line 114) | @Override
method onCreateFailure (line 118) | @Override
method onSetFailure (line 122) | @Override
method onSignalingChange (line 126) | @Override
method onIceConnectionChange (line 130) | @Override
method onIceConnectionReceivingChange (line 140) | @Override
method onIceGatheringChange (line 145) | @Override
method onIceCandidate (line 149) | @Override
method onAddStream (line 162) | @Override
method onRemoveStream (line 169) | @Override
method onDataChannel (line 176) | @Override
method onRenegotiationNeeded (line 180) | @Override
method toString (line 189) | @Override
FILE: pnwebrtc/src/main/java/me/kevingleason/pnwebrtc/PnPeerConnectionClient.java
class PnPeerConnectionClient (line 37) | public class PnPeerConnectionClient {
method PnPeerConnectionClient (line 51) | public PnPeerConnectionClient(Pubnub pubnub, PnSignalingParams signali...
method init (line 60) | private void init(){
method listenOn (line 71) | boolean listenOn(String myId){ // Todo: return success?
method connect (line 90) | boolean connect(String userId) {
method setRTCListener (line 109) | public void setRTCListener(PnRTCListener listener){
method subscribe (line 113) | private void subscribe(String channel){
method setLocalMediaStream (line 121) | public void setLocalMediaStream(MediaStream localStream){
method getLocalMediaStream (line 126) | public MediaStream getLocalMediaStream(){
method addPeer (line 130) | private PnPeer addPeer(String id) {
method removePeer (line 136) | PnPeer removePeer(String id) {
method getPeers (line 142) | List<PnPeer> getPeers(){
method closeConnection (line 150) | public void closeConnection(String id){
method closeAllConnections (line 167) | public void closeAllConnections() {
method transmitMessage (line 179) | void transmitMessage(String toID, JSONObject packet){
type PnAction (line 204) | private interface PnAction{
method execute (line 205) | void execute(String peerId, JSONObject payload) throws JSONException;
class CreateOfferAction (line 208) | private class CreateOfferAction implements PnAction{
method execute (line 210) | public void execute(String peerId, JSONObject payload) throws JSONEx...
class CreateAnswerAction (line 219) | private class CreateAnswerAction implements PnAction{
method execute (line 221) | public void execute(String peerId, JSONObject payload) throws JSONEx...
class SetRemoteSDPAction (line 235) | private class SetRemoteSDPAction implements PnAction{
method execute (line 237) | public void execute(String peerId, JSONObject payload) throws JSONEx...
class AddIceCandidateAction (line 248) | private class AddIceCandidateAction implements PnAction{
method execute (line 250) | public void execute(String peerId, JSONObject payload) throws JSONEx...
class PnUserHangupAction (line 264) | private class PnUserHangupAction implements PnAction{
method execute (line 266) | public void execute(String peerId, JSONObject payload) throws JSONEx...
class PnUserMessageAction (line 275) | private class PnUserMessageAction implements PnAction{
method execute (line 277) | public void execute(String peerId, JSONObject payload) throws JSONEx...
method generateHangupPacket (line 290) | public static JSONObject generateHangupPacket(String userId){
method generateUserMessage (line 312) | public static JSONObject generateUserMessage(String userId, JSONObject...
class PnRTCReceiver (line 326) | private class PnRTCReceiver extends Callback {
method connectCallback (line 328) | @Override
method successCallback (line 334) | @Override
method errorCallback (line 381) | @Override
FILE: pnwebrtc/src/main/java/me/kevingleason/pnwebrtc/PnRTCClient.java
class PnRTCClient (line 24) | public class PnRTCClient {
method PnRTCClient (line 37) | public PnRTCClient(String pubKey, String subKey) {
method PnRTCClient (line 52) | public PnRTCClient(String pubKey, String subKey, String UUID) {
method pcConstraints (line 64) | public MediaConstraints pcConstraints() {
method videoConstraints (line 72) | public MediaConstraints videoConstraints() {
method audioConstraints (line 80) | public MediaConstraints audioConstraints() {
method getPubNub (line 88) | public Pubnub getPubNub(){
method getUUID (line 98) | public String getUUID() {
method setSignalParams (line 108) | public void setSignalParams(PnSignalingParams signalParams){
method attachLocalMediaStream (line 116) | public void attachLocalMediaStream(MediaStream mediaStream){
method attachRTCListener (line 124) | public void attachRTCListener(PnRTCListener listener){
method setMaxConnections (line 132) | public void setMaxConnections(int max){
method listenOn (line 140) | public void listenOn(String channel){
method connect (line 148) | public void connect(String userId){
method closeConnection (line 156) | public void closeConnection(String userId){
method closeAllConnections (line 163) | public void closeAllConnections(){
method transmit (line 172) | public void transmit(String userId, JSONObject message){
method transmitAll (line 186) | public void transmitAll(JSONObject message){
method generateRandomNumber (line 193) | private static String generateRandomNumber(){
method onDestroy (line 205) | public void onDestroy() {
FILE: pnwebrtc/src/main/java/me/kevingleason/pnwebrtc/PnRTCListener.java
class PnRTCListener (line 21) | public abstract class PnRTCListener{
method onCallReady (line 22) | public void onCallReady(String callId){}
method onConnected (line 29) | public void onConnected(String userId){}
method onPeerStatusChanged (line 36) | public void onPeerStatusChanged(PnPeer peer){}
method onPeerConnectionClosed (line 42) | public void onPeerConnectionClosed(PnPeer peer){}
method onLocalStream (line 49) | public void onLocalStream(MediaStream localStream){}
method onAddRemoteStream (line 58) | public void onAddRemoteStream(MediaStream remoteStream, PnPeer peer){}
method onRemoveRemoteStream (line 66) | public void onRemoveRemoteStream(MediaStream remoteStream, PnPeer peer){}
method onMessage (line 73) | public void onMessage(PnPeer peer, Object message){}
method onDebug (line 79) | public void onDebug(PnRTCMessage message){}
FILE: pnwebrtc/src/main/java/me/kevingleason/pnwebrtc/PnRTCMessage.java
class PnRTCMessage (line 16) | public class PnRTCMessage extends JSONObject {
method PnRTCMessage (line 31) | public PnRTCMessage(String message){
method PnRTCMessage (line 42) | public PnRTCMessage(JSONObject json){
method getMessage (line 56) | public String getMessage() {
method getJSON (line 60) | public JSONObject getJSON(){
FILE: pnwebrtc/src/main/java/me/kevingleason/pnwebrtc/PnSignalingParams.java
class PnSignalingParams (line 21) | public class PnSignalingParams {
method PnSignalingParams (line 27) | public PnSignalingParams(
method PnSignalingParams (line 44) | public PnSignalingParams(
method PnSignalingParams (line 58) | public PnSignalingParams(List<PeerConnection.IceServer> iceServers) {
method PnSignalingParams (line 69) | public PnSignalingParams() {
method defaultInstance (line 80) | public static PnSignalingParams defaultInstance() {
method defaultPcConstraints (line 88) | private static MediaConstraints defaultPcConstraints(){
method defaultVideoConstraints (line 96) | private static MediaConstraints defaultVideoConstraints(){
method defaultAudioConstraints (line 105) | private static MediaConstraints defaultAudioConstraints(){
method defaultIceServers (line 110) | public static List<PeerConnection.IceServer> defaultIceServers(){
method addIceServers (line 145) | public void addIceServers(List<PeerConnection.IceServer> iceServers){
method addIceServers (line 156) | public void addIceServers(PeerConnection.IceServer iceServers){
Condensed preview — 61 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (146K chars).
[
{
"path": ".gitignore",
"chars": 81,
"preview": ".gradle\n/local.properties\n/.idea/workspace.xml\n/.idea/libraries\n.DS_Store\n/build\n"
},
{
"path": "LICENSE",
"chars": 1081,
"preview": "The MIT License (MIT)\n\nCopyright (c) 2015 Kevin Gleason\n\nPermission is hereby granted, free of charge, to any person obt"
},
{
"path": "README.md",
"chars": 2547,
"preview": "# AndroidRTC\nAn example of native WebRTC on Android using PubNub's Android SDK signaling.\n\n### Big News: PubNub Android "
},
{
"path": "app/.gitignore",
"chars": 7,
"preview": "/build\n"
},
{
"path": "app/build.gradle",
"chars": 718,
"preview": "apply plugin: 'com.android.application'\n\nandroid {\n compileSdkVersion 21\n buildToolsVersion \"20.0.0\"\n\n defaultC"
},
{
"path": "app/proguard-rules.pro",
"chars": 667,
"preview": "# Add project specific ProGuard rules here.\n# By default, the flags in this file are appended to flags specified\n# in /U"
},
{
"path": "app/src/androidTest/java/me/kevingleason/androidrtc/ApplicationTest.java",
"chars": 357,
"preview": "package me.kevingleason.androidrtc;\n\nimport android.app.Application;\nimport android.test.ApplicationTestCase;\n\n/**\n * <a"
},
{
"path": "app/src/main/AndroidManifest.xml",
"chars": 2672,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n package="
},
{
"path": "app/src/main/java/me/kevingleason/androidrtc/IncomingCallActivity.java",
"chars": 3821,
"preview": "package me.kevingleason.androidrtc;\n\nimport android.app.Activity;\nimport android.content.Intent;\nimport android.content."
},
{
"path": "app/src/main/java/me/kevingleason/androidrtc/LoginActivity.java",
"chars": 2949,
"preview": "package me.kevingleason.androidrtc;\n\nimport android.app.Activity;\nimport android.content.Intent;\nimport android.content."
},
{
"path": "app/src/main/java/me/kevingleason/androidrtc/MainActivity.java",
"chars": 10150,
"preview": "package me.kevingleason.androidrtc;\n\nimport android.app.ListActivity;\nimport android.content.Intent;\nimport android.cont"
},
{
"path": "app/src/main/java/me/kevingleason/androidrtc/VideoChatActivity.java",
"chars": 14673,
"preview": "package me.kevingleason.androidrtc;\n\nimport android.app.Activity;\nimport android.app.ListActivity;\nimport android.conten"
},
{
"path": "app/src/main/java/me/kevingleason/androidrtc/adapters/ChatAdapter.java",
"chars": 6464,
"preview": "package me.kevingleason.androidrtc.adapters;\n\nimport android.content.Context;\nimport android.util.Log;\nimport android.vi"
},
{
"path": "app/src/main/java/me/kevingleason/androidrtc/adapters/HistoryAdapter.java",
"chars": 6765,
"preview": "package me.kevingleason.androidrtc.adapters;\n\nimport android.app.Activity;\nimport android.content.Context;\nimport androi"
},
{
"path": "app/src/main/java/me/kevingleason/androidrtc/adt/ChatMessage.java",
"chars": 684,
"preview": "package me.kevingleason.androidrtc.adt;\n\n/**\n * Created by GleasonK on 6/25/15.\n */\npublic class ChatMessage {\n priva"
},
{
"path": "app/src/main/java/me/kevingleason/androidrtc/adt/ChatUser.java",
"chars": 996,
"preview": "package me.kevingleason.androidrtc.adt;\n\nimport me.kevingleason.androidrtc.util.Constants;\n\n/**\n * Created by GleasonK o"
},
{
"path": "app/src/main/java/me/kevingleason/androidrtc/adt/HistoryItem.java",
"chars": 413,
"preview": "package me.kevingleason.androidrtc.adt;\n\n/**\n * Created by GleasonK on 7/31/15.\n */\npublic class HistoryItem {\n priva"
},
{
"path": "app/src/main/java/me/kevingleason/androidrtc/servers/XirSysRequest.java",
"chars": 4178,
"preview": "package me.kevingleason.androidrtc.servers;\n\nimport android.os.AsyncTask;\nimport android.util.Log;\n\nimport org.apache.ht"
},
{
"path": "app/src/main/java/me/kevingleason/androidrtc/util/Constants.java",
"chars": 1476,
"preview": "package me.kevingleason.androidrtc.util;\n\n/**\n * Created by GleasonK on 7/30/15.\n */\npublic class Constants {\n public"
},
{
"path": "app/src/main/java/me/kevingleason/androidrtc/util/LogRTCListener.java",
"chars": 1657,
"preview": "package me.kevingleason.androidrtc.util;\n\nimport android.util.Log;\n\nimport org.webrtc.MediaStream;\n\nimport me.kevingleas"
},
{
"path": "app/src/main/res/drawable/light_fade_down.xml",
"chars": 366,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<selector xmlns:android=\"http://schemas.android.com/apk/res/android\">\n <item>\n"
},
{
"path": "app/src/main/res/drawable/light_fade_up.xml",
"chars": 366,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<selector xmlns:android=\"http://schemas.android.com/apk/res/android\">\n <item>\n"
},
{
"path": "app/src/main/res/drawable/online_circle.xml",
"chars": 263,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape\n xmlns:android=\"http://schemas.android.com/apk/res/android\"\n android"
},
{
"path": "app/src/main/res/drawable/round_button.xml",
"chars": 493,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape xmlns:android=\"http://schemas.android.com/apk/res/android\"\n android:sha"
},
{
"path": "app/src/main/res/drawable/round_button_send.xml",
"chars": 619,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape xmlns:android=\"http://schemas.android.com/apk/res/android\"\n android:sha"
},
{
"path": "app/src/main/res/layout/activity_incoming_call.xml",
"chars": 1949,
"preview": "<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n xmlns:tools=\"http://schemas.android.com/too"
},
{
"path": "app/src/main/res/layout/activity_login.xml",
"chars": 2963,
"preview": "<RelativeLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n xmlns:tools=\"http://schemas.android.com/t"
},
{
"path": "app/src/main/res/layout/activity_main.xml",
"chars": 2896,
"preview": "<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n xmlns:tools=\"http://schemas.android.com/too"
},
{
"path": "app/src/main/res/layout/activity_video_chat.xml",
"chars": 2800,
"preview": "<RelativeLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n xmlns:tools=\"http://schemas.android.com/t"
},
{
"path": "app/src/main/res/layout/chat_message_row_layout.xml",
"chars": 1282,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout\n xmlns:android=\"http://schemas.android.com/apk/res/android\"\n "
},
{
"path": "app/src/main/res/layout/history_row_layout.xml",
"chars": 2318,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n andr"
},
{
"path": "app/src/main/res/menu/menu_incoming_call.xml",
"chars": 343,
"preview": "<menu xmlns:android=\"http://schemas.android.com/apk/res/android\"\n xmlns:tools=\"http://schemas.android.com/tools\"\n "
},
{
"path": "app/src/main/res/menu/menu_login.xml",
"chars": 340,
"preview": "<menu xmlns:android=\"http://schemas.android.com/apk/res/android\"\n xmlns:tools=\"http://schemas.android.com/tools\"\n "
},
{
"path": "app/src/main/res/menu/menu_main.xml",
"chars": 457,
"preview": "<menu xmlns:android=\"http://schemas.android.com/apk/res/android\"\n xmlns:tools=\"http://schemas.android.com/tools\" tool"
},
{
"path": "app/src/main/res/menu/menu_video_chat.xml",
"chars": 344,
"preview": "<menu xmlns:android=\"http://schemas.android.com/apk/res/android\"\n xmlns:tools=\"http://schemas.android.com/tools\"\n "
},
{
"path": "app/src/main/res/values/colors.xml",
"chars": 312,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n <color name=\"pn_red\">#d02129</color>\n <color name=\"pn_blue\">#f"
},
{
"path": "app/src/main/res/values/dimens.xml",
"chars": 211,
"preview": "<resources>\n <!-- Default screen margins, per the Android Design guidelines. -->\n <dimen name=\"activity_horizontal"
},
{
"path": "app/src/main/res/values/strings.xml",
"chars": 977,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n\n <string name=\"app_name\">AndroidRTC</string>\n <string name=\"he"
},
{
"path": "app/src/main/res/values/styles.xml",
"chars": 153,
"preview": "<resources>\n\n <!-- Base application theme. -->\n <style name=\"AppTheme\" parent=\"android:Theme.Holo.Light.DarkAction"
},
{
"path": "app/src/main/res/values-v21/styles.xml",
"chars": 207,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n <style name=\"AppTheme\" parent=\"android:Theme.Material.Light\">\n "
},
{
"path": "app/src/main/res/values-w820dp/dimens.xml",
"chars": 358,
"preview": "<resources>\n <!-- Example customization of dimensions originally defined in res/values/dimens.xml\n (such as s"
},
{
"path": "build.gradle",
"chars": 437,
"preview": "// Top-level build file where you can add configuration options common to all sub-projects/modules.\n\nbuildscript {\n r"
},
{
"path": "gradle/wrapper/gradle-wrapper.properties",
"chars": 230,
"preview": "#Wed Apr 10 15:27:10 PDT 2013\ndistributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\nzipStoreBase=GRADLE_USER_"
},
{
"path": "gradle.properties",
"chars": 1447,
"preview": "# Project-wide Gradle settings.\n\n# IDE (e.g. Android Studio) users:\n# Gradle settings configured through the IDE *will o"
},
{
"path": "gradlew",
"chars": 5080,
"preview": "#!/usr/bin/env bash\n\n##############################################################################\n##\n## Gradle start "
},
{
"path": "gradlew.bat",
"chars": 2404,
"preview": "@if \"%DEBUG%\" == \"\" @echo off\r\n@rem ##########################################################################\r\n@rem\r\n@r"
},
{
"path": "pnwebrtc/.gitignore",
"chars": 7,
"preview": "/build\n"
},
{
"path": "pnwebrtc/build.gradle",
"chars": 711,
"preview": "apply plugin: 'com.android.library'\n\nandroid {\n compileSdkVersion 21\n buildToolsVersion \"20.0.0\"\n\n defaultConfi"
},
{
"path": "pnwebrtc/gradle.properties",
"chars": 70,
"preview": "POM_NAME=PubNub WebRTC API\nPOM_ARTIFACT_ID=pnwebrtc\nPOM_PACKAGING=aar\n"
},
{
"path": "pnwebrtc/proguard-rules.pro",
"chars": 667,
"preview": "# Add project specific ProGuard rules here.\n# By default, the flags in this file are appended to flags specified\n# in /U"
},
{
"path": "pnwebrtc/src/androidTest/java/me/kevingleason/pnwebrtc/ApplicationTest.java",
"chars": 355,
"preview": "package me.kevingleason.pnwebrtc;\n\nimport android.app.Application;\nimport android.test.ApplicationTestCase;\n\n/**\n * <a h"
},
{
"path": "pnwebrtc/src/main/AndroidManifest.xml",
"chars": 1290,
"preview": "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n package=\"me.kevingleason.pnwebrtc\">\n\n <!-- W"
},
{
"path": "pnwebrtc/src/main/java/me/kevingleason/pnwebrtc/PnPeer.java",
"chars": 5776,
"preview": "package me.kevingleason.pnwebrtc;\n\nimport android.util.Log;\n\nimport org.json.JSONException;\nimport org.json.JSONObject;\n"
},
{
"path": "pnwebrtc/src/main/java/me/kevingleason/pnwebrtc/PnPeerConnectionClient.java",
"chars": 15213,
"preview": "package me.kevingleason.pnwebrtc;\n\nimport android.util.Log;\n\nimport com.pubnub.api.Callback;\nimport com.pubnub.api.Pubnu"
},
{
"path": "pnwebrtc/src/main/java/me/kevingleason/pnwebrtc/PnRTCClient.java",
"chars": 6973,
"preview": "package me.kevingleason.pnwebrtc;\n\n\nimport com.pubnub.api.Pubnub;\n\nimport org.json.JSONException;\nimport org.json.JSONOb"
},
{
"path": "pnwebrtc/src/main/java/me/kevingleason/pnwebrtc/PnRTCListener.java",
"chars": 2981,
"preview": "package me.kevingleason.pnwebrtc;\n\nimport org.webrtc.MediaStream;\n\n/**\n * <h1>Callback listener for various WebRTC and {"
},
{
"path": "pnwebrtc/src/main/java/me/kevingleason/pnwebrtc/PnRTCMessage.java",
"chars": 1942,
"preview": "package me.kevingleason.pnwebrtc;\n\nimport org.json.JSONException;\nimport org.json.JSONObject;\n\n/**\n * <h1>Used for debug"
},
{
"path": "pnwebrtc/src/main/java/me/kevingleason/pnwebrtc/PnSignalingParams.java",
"chars": 7807,
"preview": "package me.kevingleason.pnwebrtc;\n\nimport org.webrtc.MediaConstraints;\nimport org.webrtc.PeerConnection;\n\nimport java.ut"
},
{
"path": "pnwebrtc/src/main/res/values/strings.xml",
"chars": 71,
"preview": "<resources>\n <string name=\"app_name\">PnWebRTC</string>\n</resources>\n"
},
{
"path": "settings.gradle",
"chars": 28,
"preview": "include ':app', ':pnwebrtc'\n"
}
]
// ... and 1 more files (download for full content)
About this extraction
This page contains the full source code of the GleasonK/AndroidRTC GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 61 files (132.7 KB), approximately 32.4k tokens, and a symbol index with 213 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.