Add new features to AppRTCDemo from private repo.
- Add HUD fragment with HUD related controls and more HUD statistics. - Create and set all peer connection constraints in PeerConnectionClient class. - Handle registration request in web socket class internally once web socket connection is opened. R=wzh@webrtc.org Review URL: https://webrtc-codereview.appspot.com/44669004 Cr-Commit-Position: refs/heads/master@{#8762} git-svn-id: http://webrtc.googlecode.com/svn/trunk@8762 4adac7df-926f-26a2-2b94-8c16560cd09d
This commit is contained in:
parent
779c3d16b9
commit
2161234cf6
@ -4,8 +4,7 @@
|
||||
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:orientation="vertical">
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<android.opengl.GLSurfaceView
|
||||
android:id="@+id/glview_call"
|
||||
@ -16,5 +15,9 @@
|
||||
android:id="@+id/call_fragment_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" />
|
||||
<FrameLayout
|
||||
android:id="@+id/hud_fragment_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" />
|
||||
|
||||
</RelativeLayout>
|
||||
|
@ -4,18 +4,7 @@
|
||||
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:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/encoder_stat_call"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:textStyle="bold"
|
||||
android:textColor="#C000FF00"
|
||||
android:textSize="12dp"
|
||||
android:layout_margin="8dp"/>
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/contact_name_call"
|
||||
@ -26,23 +15,6 @@
|
||||
android:textSize="24sp"
|
||||
android:layout_margin="8dp"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/hud_stat_call"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:alpha="0.4"
|
||||
android:background="@android:color/white"
|
||||
android:textColor="@android:color/black" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/button_toggle_debug"
|
||||
android:background="@android:drawable/ic_menu_info_details"
|
||||
android:contentDescription="@string/toggle_debug"
|
||||
android:layout_alignParentBottom="true"
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"/>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/buttons_call_container"
|
||||
android:orientation="horizontal"
|
||||
|
75
talk/examples/android/res/layout/fragment_hud.xml
Normal file
75
talk/examples/android/res/layout/fragment_hud.xml
Normal file
@ -0,0 +1,75 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<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">
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/button_toggle_debug"
|
||||
android:background="@android:drawable/ic_menu_info_details"
|
||||
android:contentDescription="@string/toggle_debug"
|
||||
android:layout_alignParentBottom="true"
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/encoder_stat_call"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:textStyle="bold"
|
||||
android:textColor="#C000FF00"
|
||||
android:textSize="12dp"
|
||||
android:layout_margin="8dp"/>
|
||||
|
||||
<TableLayout
|
||||
android:id="@+id/hudview_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<TableRow>
|
||||
<TextView
|
||||
android:id="@+id/hud_stat_bwe"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:alpha="0.4"
|
||||
android:padding="2dip"
|
||||
android:background="@android:color/white"
|
||||
android:textColor="@android:color/black" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/hud_stat_connection"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:alpha="0.4"
|
||||
android:padding="2dip"
|
||||
android:background="@android:color/white"
|
||||
android:textColor="@android:color/black" />
|
||||
|
||||
</TableRow>
|
||||
|
||||
<TableRow>
|
||||
<TextView
|
||||
android:id="@+id/hud_stat_video_send"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:alpha="0.4"
|
||||
android:padding="2dip"
|
||||
android:background="@android:color/white"
|
||||
android:textColor="@android:color/black" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/hud_stat_video_recv"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="2dip"
|
||||
android:alpha="0.4"
|
||||
android:background="@android:color/white"
|
||||
android:textColor="@android:color/black" />
|
||||
</TableRow>
|
||||
</TableLayout>
|
||||
|
||||
</RelativeLayout>
|
@ -43,7 +43,7 @@ import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.view.Window;
|
||||
import android.view.WindowManager;
|
||||
import android.view.WindowManager.LayoutParams;
|
||||
import android.widget.Toast;
|
||||
|
||||
import org.webrtc.IceCandidate;
|
||||
@ -132,6 +132,7 @@ public class CallActivity extends Activity
|
||||
// Controls
|
||||
private GLSurfaceView videoView;
|
||||
CallFragment callFragment;
|
||||
HudFragment hudFragment;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
@ -142,8 +143,12 @@ public class CallActivity extends Activity
|
||||
// Set window styles for fullscreen-window size. Needs to be done before
|
||||
// adding content.
|
||||
requestWindowFeature(Window.FEATURE_NO_TITLE);
|
||||
getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
|
||||
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
|
||||
getWindow().addFlags(
|
||||
LayoutParams.FLAG_FULLSCREEN
|
||||
| LayoutParams.FLAG_KEEP_SCREEN_ON
|
||||
| LayoutParams.FLAG_DISMISS_KEYGUARD
|
||||
| LayoutParams.FLAG_SHOW_WHEN_LOCKED
|
||||
| LayoutParams.FLAG_TURN_SCREEN_ON);
|
||||
getWindow().getDecorView().setSystemUiVisibility(
|
||||
View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
|
||||
| View.SYSTEM_UI_FLAG_FULLSCREEN
|
||||
@ -157,6 +162,7 @@ public class CallActivity extends Activity
|
||||
// Create UI controls.
|
||||
videoView = (GLSurfaceView) findViewById(R.id.glview_call);
|
||||
callFragment = new CallFragment();
|
||||
hudFragment = new HudFragment();
|
||||
|
||||
// Create video renderers.
|
||||
VideoRendererGui.setView(videoView, new Runnable() {
|
||||
@ -219,11 +225,14 @@ public class CallActivity extends Activity
|
||||
roomConnectionParameters = new RoomConnectionParameters(
|
||||
roomUri.toString(), roomId, loopback);
|
||||
|
||||
// Send intent arguments to fragment.
|
||||
// Send intent arguments to fragments.
|
||||
callFragment.setArguments(intent.getExtras());
|
||||
// Activate call fragment and start the call.
|
||||
getFragmentManager().beginTransaction()
|
||||
.add(R.id.call_fragment_container, callFragment).commit();
|
||||
hudFragment.setArguments(intent.getExtras());
|
||||
// Activate call and HUD fragments and start the call.
|
||||
FragmentTransaction ft = getFragmentManager().beginTransaction();
|
||||
ft.add(R.id.call_fragment_container, callFragment);
|
||||
ft.add(R.id.hud_fragment_container, hudFragment);
|
||||
ft.commit();
|
||||
startCall();
|
||||
|
||||
// For command line execution run connection for <runTimeMs> and exit.
|
||||
@ -296,8 +305,10 @@ public class CallActivity extends Activity
|
||||
FragmentTransaction ft = getFragmentManager().beginTransaction();
|
||||
if (callControlFragmentVisible) {
|
||||
ft.show(callFragment);
|
||||
ft.show(hudFragment);
|
||||
} else {
|
||||
ft.hide(callFragment);
|
||||
ft.hide(hudFragment);
|
||||
}
|
||||
ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
|
||||
ft.commit();
|
||||
@ -387,6 +398,7 @@ public class CallActivity extends Activity
|
||||
|
||||
// Disconnect from remote resources, dispose of local resources, and exit.
|
||||
private void disconnect() {
|
||||
activityRunning = false;
|
||||
if (appRtcClient != null) {
|
||||
appRtcClient.disconnectFromRoom();
|
||||
appRtcClient = null;
|
||||
@ -436,6 +448,18 @@ public class CallActivity extends Activity
|
||||
logToast.show();
|
||||
}
|
||||
|
||||
private void reportError(final String description) {
|
||||
runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (!isError) {
|
||||
isError = true;
|
||||
disconnectWithErrorMessage(description);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// -----Implementation of AppRTCClient.AppRTCSignalingEvents ---------------
|
||||
// All callbacks are invoked from websocket signaling looper thread and
|
||||
// are routed to UI thread.
|
||||
@ -533,15 +557,7 @@ public class CallActivity extends Activity
|
||||
|
||||
@Override
|
||||
public void onChannelError(final String description) {
|
||||
runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (!isError) {
|
||||
isError = true;
|
||||
disconnectWithErrorMessage(description);
|
||||
}
|
||||
}
|
||||
});
|
||||
reportError(description);
|
||||
}
|
||||
|
||||
// -----Implementation of PeerConnectionClient.PeerConnectionEvents.---------
|
||||
@ -613,7 +629,7 @@ public class CallActivity extends Activity
|
||||
@Override
|
||||
public void run() {
|
||||
if (!isError && iceConnected) {
|
||||
callFragment.updateEncoderStatistics(reports);
|
||||
hudFragment.updateEncoderStatistics(reports);
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -621,14 +637,6 @@ public class CallActivity extends Activity
|
||||
|
||||
@Override
|
||||
public void onPeerConnectionError(final String description) {
|
||||
runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (!isError) {
|
||||
isError = true;
|
||||
disconnectWithErrorMessage(description);
|
||||
}
|
||||
}
|
||||
});
|
||||
reportError(description);
|
||||
}
|
||||
}
|
||||
|
@ -30,36 +30,26 @@ package org.appspot.apprtc;
|
||||
import android.app.Activity;
|
||||
import android.app.Fragment;
|
||||
import android.os.Bundle;
|
||||
import android.util.TypedValue;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.webrtc.StatsReport;
|
||||
import org.webrtc.VideoRendererGui.ScalingType;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Fragment for call control.
|
||||
*/
|
||||
public class CallFragment extends Fragment {
|
||||
private View controlView;
|
||||
private TextView encoderStatView;
|
||||
private TextView roomIdView;
|
||||
private TextView contactView;
|
||||
private ImageButton disconnectButton;
|
||||
private ImageButton cameraSwitchButton;
|
||||
private ImageButton videoScalingButton;
|
||||
private ImageButton toggleDebugButton;
|
||||
private OnCallEvents callEvents;
|
||||
private ScalingType scalingType;
|
||||
private boolean displayHud;
|
||||
private volatile boolean isRunning;
|
||||
private TextView hudView;
|
||||
private final CpuMonitor cpuMonitor = new CpuMonitor();
|
||||
private boolean videoCallEnabled = true;
|
||||
|
||||
/**
|
||||
* Call control interface for container activity.
|
||||
@ -77,20 +67,14 @@ public class CallFragment extends Fragment {
|
||||
inflater.inflate(R.layout.fragment_call, container, false);
|
||||
|
||||
// Create UI controls.
|
||||
encoderStatView =
|
||||
(TextView) controlView.findViewById(R.id.encoder_stat_call);
|
||||
roomIdView =
|
||||
contactView =
|
||||
(TextView) controlView.findViewById(R.id.contact_name_call);
|
||||
hudView =
|
||||
(TextView) controlView.findViewById(R.id.hud_stat_call);
|
||||
disconnectButton =
|
||||
(ImageButton) controlView.findViewById(R.id.button_call_disconnect);
|
||||
cameraSwitchButton =
|
||||
(ImageButton) controlView.findViewById(R.id.button_call_switch_camera);
|
||||
videoScalingButton =
|
||||
(ImageButton) controlView.findViewById(R.id.button_call_scaling_mode);
|
||||
toggleDebugButton =
|
||||
(ImageButton) controlView.findViewById(R.id.button_toggle_debug);
|
||||
|
||||
// Add buttons click events.
|
||||
disconnectButton.setOnClickListener(new View.OnClickListener() {
|
||||
@ -124,17 +108,6 @@ public class CallFragment extends Fragment {
|
||||
});
|
||||
scalingType = ScalingType.SCALE_ASPECT_FILL;
|
||||
|
||||
toggleDebugButton.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
if (displayHud) {
|
||||
int visibility = (hudView.getVisibility() == View.VISIBLE)
|
||||
? View.INVISIBLE : View.VISIBLE;
|
||||
hudView.setVisibility(visibility);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return controlView;
|
||||
}
|
||||
|
||||
@ -144,22 +117,13 @@ public class CallFragment extends Fragment {
|
||||
|
||||
Bundle args = getArguments();
|
||||
if (args != null) {
|
||||
String roomId = args.getString(CallActivity.EXTRA_ROOMID);
|
||||
roomIdView.setText(roomId);
|
||||
displayHud = args.getBoolean(CallActivity.EXTRA_DISPLAY_HUD, false);
|
||||
String contactName = args.getString(CallActivity.EXTRA_ROOMID);
|
||||
contactView.setText(contactName);
|
||||
videoCallEnabled = args.getBoolean(CallActivity.EXTRA_VIDEO_CALL, true);
|
||||
}
|
||||
if (!videoCallEnabled) {
|
||||
cameraSwitchButton.setVisibility(View.INVISIBLE);
|
||||
}
|
||||
int visibility = displayHud ? View.VISIBLE : View.INVISIBLE;
|
||||
encoderStatView.setVisibility(visibility);
|
||||
toggleDebugButton.setVisibility(visibility);
|
||||
hudView.setVisibility(View.INVISIBLE);
|
||||
hudView.setTextSize(TypedValue.COMPLEX_UNIT_PT, 5);
|
||||
isRunning = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStop() {
|
||||
isRunning = false;
|
||||
super.onStop();
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -168,73 +132,4 @@ public class CallFragment extends Fragment {
|
||||
callEvents = (OnCallEvents) activity;
|
||||
}
|
||||
|
||||
private Map<String, String> getReportMap(StatsReport report) {
|
||||
Map<String, String> reportMap = new HashMap<String, String>();
|
||||
for (StatsReport.Value value : report.values) {
|
||||
reportMap.put(value.name, value.value);
|
||||
}
|
||||
return reportMap;
|
||||
}
|
||||
|
||||
public void updateEncoderStatistics(final StatsReport[] reports) {
|
||||
if (!isRunning || !displayHud) {
|
||||
return;
|
||||
}
|
||||
String fps = null;
|
||||
String targetBitrate = null;
|
||||
String actualBitrate = null;
|
||||
StringBuilder bweBuilder = new StringBuilder();
|
||||
for (StatsReport report : reports) {
|
||||
if (report.type.equals("ssrc") && report.id.contains("ssrc")
|
||||
&& report.id.contains("send")) {
|
||||
Map<String, String> reportMap = getReportMap(report);
|
||||
String trackId = reportMap.get("googTrackId");
|
||||
if (trackId != null
|
||||
&& trackId.contains(PeerConnectionClient.VIDEO_TRACK_ID)) {
|
||||
fps = reportMap.get("googFrameRateSent");
|
||||
}
|
||||
} else if (report.id.equals("bweforvideo")) {
|
||||
Map<String, String> reportMap = getReportMap(report);
|
||||
targetBitrate = reportMap.get("googTargetEncBitrate");
|
||||
actualBitrate = reportMap.get("googActualEncBitrate");
|
||||
|
||||
for (StatsReport.Value value : report.values) {
|
||||
String name = value.name.replace("goog", "")
|
||||
.replace("Available", "").replace("Bandwidth", "")
|
||||
.replace("Bitrate", "").replace("Enc", "");
|
||||
bweBuilder.append(name).append("=").append(value.value)
|
||||
.append(" ");
|
||||
}
|
||||
bweBuilder.append("\n");
|
||||
}
|
||||
}
|
||||
|
||||
StringBuilder stat = new StringBuilder(128);
|
||||
if (fps != null) {
|
||||
stat.append("Fps: ")
|
||||
.append(fps)
|
||||
.append("\n");
|
||||
}
|
||||
if (targetBitrate != null) {
|
||||
stat.append("Target BR: ")
|
||||
.append(targetBitrate)
|
||||
.append("\n");
|
||||
}
|
||||
if (actualBitrate != null) {
|
||||
stat.append("Actual BR: ")
|
||||
.append(actualBitrate)
|
||||
.append("\n");
|
||||
}
|
||||
|
||||
if (cpuMonitor.sampleCpuUtilization()) {
|
||||
stat.append("CPU%: ")
|
||||
.append(cpuMonitor.getCpuCurrent())
|
||||
.append("/")
|
||||
.append(cpuMonitor.getCpuAvg3())
|
||||
.append("/")
|
||||
.append(cpuMonitor.getCpuAvgAll());
|
||||
}
|
||||
encoderStatView.setText(stat.toString());
|
||||
hudView.setText(bweBuilder.toString() + hudView.getText());
|
||||
}
|
||||
}
|
||||
|
217
talk/examples/android/src/org/appspot/apprtc/HudFragment.java
Normal file
217
talk/examples/android/src/org/appspot/apprtc/HudFragment.java
Normal file
@ -0,0 +1,217 @@
|
||||
/*
|
||||
* libjingle
|
||||
* Copyright 2015 Google Inc.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
* 3. The name of the author may not be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
|
||||
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
|
||||
* EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
|
||||
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
||||
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
|
||||
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
|
||||
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package org.appspot.apprtc;
|
||||
|
||||
import android.app.Fragment;
|
||||
import android.os.Bundle;
|
||||
import android.util.TypedValue;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.webrtc.StatsReport;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Fragment for HUD statistics display.
|
||||
*/
|
||||
public class HudFragment extends Fragment {
|
||||
private View controlView;
|
||||
private TextView encoderStatView;
|
||||
private TextView hudViewBwe;
|
||||
private TextView hudViewConnection;
|
||||
private TextView hudViewVideoSend;
|
||||
private TextView hudViewVideoRecv;
|
||||
private ImageButton toggleDebugButton;
|
||||
private boolean videoCallEnabled;
|
||||
private boolean displayHud;
|
||||
private volatile boolean isRunning;
|
||||
private final CpuMonitor cpuMonitor = new CpuMonitor();
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState) {
|
||||
controlView = inflater.inflate(R.layout.fragment_hud, container, false);
|
||||
|
||||
// Create UI controls.
|
||||
encoderStatView = (TextView) controlView.findViewById(R.id.encoder_stat_call);
|
||||
hudViewBwe = (TextView) controlView.findViewById(R.id.hud_stat_bwe);
|
||||
hudViewConnection = (TextView) controlView.findViewById(R.id.hud_stat_connection);
|
||||
hudViewVideoSend = (TextView) controlView.findViewById(R.id.hud_stat_video_send);
|
||||
hudViewVideoRecv = (TextView) controlView.findViewById(R.id.hud_stat_video_recv);
|
||||
toggleDebugButton = (ImageButton) controlView.findViewById(R.id.button_toggle_debug);
|
||||
|
||||
toggleDebugButton.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
if (displayHud) {
|
||||
int visibility = (hudViewBwe.getVisibility() == View.VISIBLE)
|
||||
? View.INVISIBLE : View.VISIBLE;
|
||||
hudViewsSetProperties(visibility);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return controlView;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStart() {
|
||||
super.onStart();
|
||||
|
||||
Bundle args = getArguments();
|
||||
if (args != null) {
|
||||
videoCallEnabled = args.getBoolean(CallActivity.EXTRA_VIDEO_CALL, true);
|
||||
displayHud = args.getBoolean(CallActivity.EXTRA_DISPLAY_HUD, false);
|
||||
}
|
||||
int visibility = displayHud ? View.VISIBLE : View.INVISIBLE;
|
||||
encoderStatView.setVisibility(visibility);
|
||||
toggleDebugButton.setVisibility(visibility);
|
||||
hudViewsSetProperties(View.INVISIBLE);
|
||||
isRunning = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStop() {
|
||||
isRunning = false;
|
||||
super.onStop();
|
||||
}
|
||||
|
||||
private void hudViewsSetProperties(int visibility) {
|
||||
hudViewBwe.setVisibility(visibility);
|
||||
hudViewConnection.setVisibility(visibility);
|
||||
hudViewVideoSend.setVisibility(visibility);
|
||||
hudViewVideoRecv.setVisibility(visibility);
|
||||
hudViewBwe.setTextSize(TypedValue.COMPLEX_UNIT_PT, 5);
|
||||
hudViewConnection.setTextSize(TypedValue.COMPLEX_UNIT_PT, 5);
|
||||
hudViewVideoSend.setTextSize(TypedValue.COMPLEX_UNIT_PT, 5);
|
||||
hudViewVideoRecv.setTextSize(TypedValue.COMPLEX_UNIT_PT, 5);
|
||||
}
|
||||
|
||||
private Map<String, String> getReportMap(StatsReport report) {
|
||||
Map<String, String> reportMap = new HashMap<String, String>();
|
||||
for (StatsReport.Value value : report.values) {
|
||||
reportMap.put(value.name, value.value);
|
||||
}
|
||||
return reportMap;
|
||||
}
|
||||
|
||||
public void updateEncoderStatistics(final StatsReport[] reports) {
|
||||
if (!isRunning || !displayHud) {
|
||||
return;
|
||||
}
|
||||
StringBuilder encoderStat = new StringBuilder(128);
|
||||
StringBuilder bweStat = new StringBuilder();
|
||||
StringBuilder connectionStat = new StringBuilder();
|
||||
StringBuilder videoSendStat = new StringBuilder();
|
||||
StringBuilder videoRecvStat = new StringBuilder();
|
||||
String fps = null;
|
||||
String targetBitrate = null;
|
||||
String actualBitrate = null;
|
||||
|
||||
for (StatsReport report : reports) {
|
||||
if (report.type.equals("ssrc") && report.id.contains("ssrc")
|
||||
&& report.id.contains("send")) {
|
||||
// Send video statistics.
|
||||
Map<String, String> reportMap = getReportMap(report);
|
||||
String trackId = reportMap.get("googTrackId");
|
||||
if (trackId != null && trackId.contains(PeerConnectionClient.VIDEO_TRACK_ID)) {
|
||||
fps = reportMap.get("googFrameRateSent");
|
||||
videoSendStat.append(report.id).append("\n");
|
||||
for (StatsReport.Value value : report.values) {
|
||||
String name = value.name.replace("goog", "");
|
||||
videoSendStat.append(name).append("=").append(value.value).append("\n");
|
||||
}
|
||||
}
|
||||
} else if (report.type.equals("ssrc") && report.id.contains("ssrc")
|
||||
&& report.id.contains("recv")) {
|
||||
// Receive video statistics.
|
||||
Map<String, String> reportMap = getReportMap(report);
|
||||
// Check if this stat is for video track.
|
||||
String frameWidth = reportMap.get("googFrameWidthReceived");
|
||||
if (frameWidth != null) {
|
||||
videoRecvStat.append(report.id).append("\n");
|
||||
for (StatsReport.Value value : report.values) {
|
||||
String name = value.name.replace("goog", "");
|
||||
videoRecvStat.append(name).append("=").append(value.value).append("\n");
|
||||
}
|
||||
}
|
||||
} else if (report.id.equals("bweforvideo")) {
|
||||
// BWE statistics.
|
||||
Map<String, String> reportMap = getReportMap(report);
|
||||
targetBitrate = reportMap.get("googTargetEncBitrate");
|
||||
actualBitrate = reportMap.get("googActualEncBitrate");
|
||||
|
||||
bweStat.append(report.id).append("\n");
|
||||
for (StatsReport.Value value : report.values) {
|
||||
String name = value.name.replace("goog", "").replace("Available", "");
|
||||
bweStat.append(name).append("=").append(value.value).append("\n");
|
||||
}
|
||||
} else if (report.type.equals("googCandidatePair")) {
|
||||
// Connection statistics.
|
||||
Map<String, String> reportMap = getReportMap(report);
|
||||
String activeConnection = reportMap.get("googActiveConnection");
|
||||
if (activeConnection != null && activeConnection.equals("true")) {
|
||||
connectionStat.append(report.id).append("\n");
|
||||
for (StatsReport.Value value : report.values) {
|
||||
String name = value.name.replace("goog", "");
|
||||
connectionStat.append(name).append("=").append(value.value).append("\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
hudViewBwe.setText(bweStat.toString());
|
||||
hudViewConnection.setText(connectionStat.toString());
|
||||
hudViewVideoSend.setText(videoSendStat.toString());
|
||||
hudViewVideoRecv.setText(videoRecvStat.toString());
|
||||
|
||||
if (videoCallEnabled) {
|
||||
if (fps != null) {
|
||||
encoderStat.append("Fps: ").append(fps).append("\n");
|
||||
}
|
||||
if (targetBitrate != null) {
|
||||
encoderStat.append("Target BR: ").append(targetBitrate).append("\n");
|
||||
}
|
||||
if (actualBitrate != null) {
|
||||
encoderStat.append("Actual BR: ").append(actualBitrate).append("\n");
|
||||
}
|
||||
}
|
||||
|
||||
if (cpuMonitor.sampleCpuUtilization()) {
|
||||
encoderStat.append("CPU%: ")
|
||||
.append(cpuMonitor.getCpuCurrent()).append("/")
|
||||
.append(cpuMonitor.getCpuAvg3()).append("/")
|
||||
.append(cpuMonitor.getCpuAvgAll());
|
||||
}
|
||||
encoderStatView.setText(encoderStat.toString());
|
||||
}
|
||||
}
|
@ -82,6 +82,7 @@ public class PeerConnectionClient {
|
||||
private static final String MIN_VIDEO_HEIGHT_CONSTRAINT = "minHeight";
|
||||
private static final String MAX_VIDEO_FPS_CONSTRAINT = "maxFrameRate";
|
||||
private static final String MIN_VIDEO_FPS_CONSTRAINT = "minFrameRate";
|
||||
private static final String DTLS_SRTP_KEY_AGREEMENT_CONSTRAINT = "DtlsSrtpKeyAgreement";
|
||||
private static final int HD_VIDEO_WIDTH = 1280;
|
||||
private static final int HD_VIDEO_HEIGHT = 720;
|
||||
private static final int MAX_VIDEO_WIDTH = 1280;
|
||||
@ -103,13 +104,15 @@ public class PeerConnectionClient {
|
||||
private VideoRenderer.Callbacks localRender;
|
||||
private VideoRenderer.Callbacks remoteRender;
|
||||
private SignalingParameters signalingParameters;
|
||||
private MediaConstraints pcConstraints;
|
||||
private MediaConstraints videoConstraints;
|
||||
private MediaConstraints audioConstraints;
|
||||
private MediaConstraints sdpMediaConstraints;
|
||||
private PeerConnectionParameters peerConnectionParameters;
|
||||
// Queued remote ICE candidates are consumed only after both local and
|
||||
// remote descriptions are set. Similarly local ICE candidates are sent to
|
||||
// remote peer after both local and remote description are set.
|
||||
private LinkedList<IceCandidate> queuedRemoteCandidates = null;
|
||||
private MediaConstraints sdpMediaConstraints;
|
||||
private PeerConnectionEvents events;
|
||||
private boolean isInitiator;
|
||||
private SessionDescription localSdp = null; // either offer or answer SDP
|
||||
@ -210,6 +213,7 @@ public class PeerConnectionClient {
|
||||
final PeerConnectionEvents events) {
|
||||
this.peerConnectionParameters = peerConnectionParameters;
|
||||
this.events = events;
|
||||
videoCallEnabled = peerConnectionParameters.videoCallEnabled;
|
||||
executor.requestStart();
|
||||
executor.execute(new Runnable() {
|
||||
@Override
|
||||
@ -230,59 +234,10 @@ public class PeerConnectionClient {
|
||||
this.localRender = localRender;
|
||||
this.remoteRender = remoteRender;
|
||||
this.signalingParameters = signalingParameters;
|
||||
// Merge video constraints from signaling parameters and peer connection
|
||||
// parameters.
|
||||
videoConstraints = signalingParameters.videoConstraints;
|
||||
if (signalingParameters.videoConstraints == null) {
|
||||
videoCallEnabled = false;
|
||||
}
|
||||
// Check if there is a camera on device and disable video call if not.
|
||||
numberOfCameras = VideoCapturerAndroid.getDeviceCount();
|
||||
if (numberOfCameras == 0) {
|
||||
Log.w(TAG, "No camera on device. Switch to audio only call.");
|
||||
videoCallEnabled = false;
|
||||
}
|
||||
if (videoCallEnabled) {
|
||||
int videoWidth = peerConnectionParameters.videoWidth;
|
||||
int videoHeight = peerConnectionParameters.videoHeight;
|
||||
|
||||
// If HW video encoder is supported and video resolution is not
|
||||
// specified force it to HD.
|
||||
if ((videoWidth == 0 || videoHeight == 0)
|
||||
&& peerConnectionParameters.videoCodecHwAcceleration
|
||||
&& MediaCodecVideoEncoder.isVp8HwSupported()) {
|
||||
videoWidth = HD_VIDEO_WIDTH;
|
||||
videoHeight = HD_VIDEO_HEIGHT;
|
||||
}
|
||||
|
||||
// Add video resolution constraints.
|
||||
if (videoWidth > 0 && videoHeight > 0) {
|
||||
videoWidth = Math.min(videoWidth, MAX_VIDEO_WIDTH);
|
||||
videoHeight = Math.min(videoHeight, MAX_VIDEO_HEIGHT);
|
||||
videoConstraints.mandatory.add(new KeyValuePair(
|
||||
MIN_VIDEO_WIDTH_CONSTRAINT, Integer.toString(videoWidth)));
|
||||
videoConstraints.mandatory.add(new KeyValuePair(
|
||||
MAX_VIDEO_WIDTH_CONSTRAINT, Integer.toString(videoWidth)));
|
||||
videoConstraints.mandatory.add(new KeyValuePair(
|
||||
MIN_VIDEO_HEIGHT_CONSTRAINT, Integer.toString(videoHeight)));
|
||||
videoConstraints.mandatory.add(new KeyValuePair(
|
||||
MAX_VIDEO_HEIGHT_CONSTRAINT, Integer.toString(videoHeight)));
|
||||
}
|
||||
|
||||
// Add fps constraints.
|
||||
int videoFps = peerConnectionParameters.videoFps;
|
||||
if (videoFps > 0) {
|
||||
videoFps = Math.min(videoFps, MAX_VIDEO_FPS);
|
||||
videoConstraints.mandatory.add(new KeyValuePair(
|
||||
MIN_VIDEO_FPS_CONSTRAINT, Integer.toString(videoFps)));
|
||||
videoConstraints.mandatory.add(new KeyValuePair(
|
||||
MAX_VIDEO_FPS_CONSTRAINT, Integer.toString(videoFps)));
|
||||
}
|
||||
}
|
||||
|
||||
executor.execute(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
createMediaConstraintsInternal();
|
||||
createPeerConnectionInternal();
|
||||
}
|
||||
});
|
||||
@ -303,7 +258,6 @@ public class PeerConnectionClient {
|
||||
Log.d(TAG, "Create peer connection factory with EGLContext "
|
||||
+ renderEGLContext + ". Use video: "
|
||||
+ peerConnectionParameters.videoCallEnabled);
|
||||
videoCallEnabled = peerConnectionParameters.videoCallEnabled;
|
||||
isError = false;
|
||||
// Check if VP9 is used by default.
|
||||
if (videoCallEnabled && peerConnectionParameters.videoCodec != null
|
||||
@ -340,18 +294,68 @@ public class PeerConnectionClient {
|
||||
protected void configureFactory(PeerConnectionFactory factory) {
|
||||
}
|
||||
|
||||
private void createPeerConnectionInternal() {
|
||||
if (factory == null || isError) {
|
||||
Log.e(TAG, "Peerconnection factory is not created");
|
||||
return;
|
||||
private void createMediaConstraintsInternal() {
|
||||
// Create peer connection constraints.
|
||||
pcConstraints = new MediaConstraints();
|
||||
// Enable DTLS for normal calls and disable for loopback calls.
|
||||
if (peerConnectionParameters.loopback) {
|
||||
pcConstraints.optional.add(
|
||||
new MediaConstraints.KeyValuePair(DTLS_SRTP_KEY_AGREEMENT_CONSTRAINT, "false"));
|
||||
} else {
|
||||
pcConstraints.optional.add(
|
||||
new MediaConstraints.KeyValuePair(DTLS_SRTP_KEY_AGREEMENT_CONSTRAINT, "true"));
|
||||
}
|
||||
Log.d(TAG, "Create peer connection");
|
||||
if (videoConstraints != null) {
|
||||
Log.d(TAG, "VideoConstraints: " + videoConstraints.toString());
|
||||
}
|
||||
isInitiator = signalingParameters.initiator;
|
||||
queuedRemoteCandidates = new LinkedList<IceCandidate>();
|
||||
|
||||
// Check if there is a camera on device and disable video call if not.
|
||||
numberOfCameras = VideoCapturerAndroid.getDeviceCount();
|
||||
if (numberOfCameras == 0) {
|
||||
Log.w(TAG, "No camera on device. Switch to audio only call.");
|
||||
videoCallEnabled = false;
|
||||
}
|
||||
// Create video constraints if video call is enabled.
|
||||
if (videoCallEnabled) {
|
||||
videoConstraints = new MediaConstraints();
|
||||
int videoWidth = peerConnectionParameters.videoWidth;
|
||||
int videoHeight = peerConnectionParameters.videoHeight;
|
||||
|
||||
// If VP8 HW video encoder is supported and video resolution is not
|
||||
// specified force it to HD.
|
||||
if ((videoWidth == 0 || videoHeight == 0)
|
||||
&& peerConnectionParameters.videoCodecHwAcceleration
|
||||
&& MediaCodecVideoEncoder.isVp8HwSupported()) {
|
||||
videoWidth = HD_VIDEO_WIDTH;
|
||||
videoHeight = HD_VIDEO_HEIGHT;
|
||||
}
|
||||
|
||||
// Add video resolution constraints.
|
||||
if (videoWidth > 0 && videoHeight > 0) {
|
||||
videoWidth = Math.min(videoWidth, MAX_VIDEO_WIDTH);
|
||||
videoHeight = Math.min(videoHeight, MAX_VIDEO_HEIGHT);
|
||||
videoConstraints.mandatory.add(new KeyValuePair(
|
||||
MIN_VIDEO_WIDTH_CONSTRAINT, Integer.toString(videoWidth)));
|
||||
videoConstraints.mandatory.add(new KeyValuePair(
|
||||
MAX_VIDEO_WIDTH_CONSTRAINT, Integer.toString(videoWidth)));
|
||||
videoConstraints.mandatory.add(new KeyValuePair(
|
||||
MIN_VIDEO_HEIGHT_CONSTRAINT, Integer.toString(videoHeight)));
|
||||
videoConstraints.mandatory.add(new KeyValuePair(
|
||||
MAX_VIDEO_HEIGHT_CONSTRAINT, Integer.toString(videoHeight)));
|
||||
}
|
||||
|
||||
// Add fps constraints.
|
||||
int videoFps = peerConnectionParameters.videoFps;
|
||||
if (videoFps > 0) {
|
||||
videoFps = Math.min(videoFps, MAX_VIDEO_FPS);
|
||||
videoConstraints.mandatory.add(new KeyValuePair(
|
||||
MIN_VIDEO_FPS_CONSTRAINT, Integer.toString(videoFps)));
|
||||
videoConstraints.mandatory.add(new KeyValuePair(
|
||||
MAX_VIDEO_FPS_CONSTRAINT, Integer.toString(videoFps)));
|
||||
}
|
||||
}
|
||||
|
||||
// Create audio constraints.
|
||||
audioConstraints = new MediaConstraints();
|
||||
|
||||
// Create SDP constraints.
|
||||
sdpMediaConstraints = new MediaConstraints();
|
||||
sdpMediaConstraints.mandatory.add(new MediaConstraints.KeyValuePair(
|
||||
"OfferToReceiveAudio", "true"));
|
||||
@ -362,10 +366,20 @@ public class PeerConnectionClient {
|
||||
sdpMediaConstraints.mandatory.add(new MediaConstraints.KeyValuePair(
|
||||
"OfferToReceiveVideo", "false"));
|
||||
}
|
||||
}
|
||||
|
||||
private void createPeerConnectionInternal() {
|
||||
if (factory == null || isError) {
|
||||
Log.e(TAG, "Peerconnection factory is not created");
|
||||
return;
|
||||
}
|
||||
Log.d(TAG, "Create peer connection");
|
||||
Log.d(TAG, "PCConstraints: " + pcConstraints.toString());
|
||||
if (videoConstraints != null) {
|
||||
Log.d(TAG, "VideoConstraints: " + videoConstraints.toString());
|
||||
}
|
||||
queuedRemoteCandidates = new LinkedList<IceCandidate>();
|
||||
|
||||
MediaConstraints pcConstraints = signalingParameters.pcConstraints;
|
||||
pcConstraints.optional.add(
|
||||
new MediaConstraints.KeyValuePair("RtpDataChannels", "true"));
|
||||
peerConnection = factory.createPeerConnection(
|
||||
signalingParameters.iceServers, pcConstraints, pcObserver);
|
||||
isInitiator = false;
|
||||
@ -390,11 +404,9 @@ public class PeerConnectionClient {
|
||||
mediaStream.addTrack(createVideoTrack(videoCapturer));
|
||||
}
|
||||
|
||||
if (signalingParameters.audioConstraints != null) {
|
||||
mediaStream.addTrack(factory.createAudioTrack(
|
||||
AUDIO_TRACK_ID,
|
||||
factory.createAudioSource(signalingParameters.audioConstraints)));
|
||||
}
|
||||
mediaStream.addTrack(factory.createAudioTrack(
|
||||
AUDIO_TRACK_ID,
|
||||
factory.createAudioSource(audioConstraints)));
|
||||
peerConnection.addStream(mediaStream);
|
||||
|
||||
Log.d(TAG, "Peer connection created.");
|
||||
@ -611,11 +623,9 @@ public class PeerConnectionClient {
|
||||
}
|
||||
|
||||
private VideoTrack createVideoTrack(VideoCapturerAndroid capturer) {
|
||||
videoSource = factory.createVideoSource(
|
||||
capturer, signalingParameters.videoConstraints);
|
||||
videoSource = factory.createVideoSource(capturer, videoConstraints);
|
||||
|
||||
localVideoTrack =
|
||||
factory.createVideoTrack(VIDEO_TRACK_ID, videoSource);
|
||||
localVideoTrack = factory.createVideoTrack(VIDEO_TRACK_ID, videoSource);
|
||||
localVideoTrack.setEnabled(renderVideo);
|
||||
localVideoTrack.addRenderer(new VideoRenderer(localRender));
|
||||
return localVideoTrack;
|
||||
@ -700,8 +710,8 @@ public class PeerConnectionClient {
|
||||
if (isAudio) {
|
||||
mediaDescription = "m=audio ";
|
||||
}
|
||||
for (int i = 0; (i < lines.length) &&
|
||||
(mLineIndex == -1 || codecRtpMap == null); i++) {
|
||||
for (int i = 0; (i < lines.length)
|
||||
&& (mLineIndex == -1 || codecRtpMap == null); i++) {
|
||||
if (lines[i].startsWith(mediaDescription)) {
|
||||
mLineIndex = i;
|
||||
continue;
|
||||
@ -720,23 +730,27 @@ public class PeerConnectionClient {
|
||||
Log.w(TAG, "No rtpmap for " + codec);
|
||||
return sdpDescription;
|
||||
}
|
||||
Log.d(TAG, "Found " + codec + " rtpmap " + codecRtpMap + ", prefer at " +
|
||||
lines[mLineIndex]);
|
||||
Log.d(TAG, "Found " + codec + " rtpmap " + codecRtpMap + ", prefer at "
|
||||
+ lines[mLineIndex]);
|
||||
String[] origMLineParts = lines[mLineIndex].split(" ");
|
||||
StringBuilder newMLine = new StringBuilder();
|
||||
int origPartIndex = 0;
|
||||
// Format is: m=<media> <port> <proto> <fmt> ...
|
||||
newMLine.append(origMLineParts[origPartIndex++]).append(" ");
|
||||
newMLine.append(origMLineParts[origPartIndex++]).append(" ");
|
||||
newMLine.append(origMLineParts[origPartIndex++]).append(" ");
|
||||
newMLine.append(codecRtpMap);
|
||||
for (; origPartIndex < origMLineParts.length; origPartIndex++) {
|
||||
if (!origMLineParts[origPartIndex].equals(codecRtpMap)) {
|
||||
newMLine.append(" ").append(origMLineParts[origPartIndex]);
|
||||
if (origMLineParts.length > 3) {
|
||||
StringBuilder newMLine = new StringBuilder();
|
||||
int origPartIndex = 0;
|
||||
// Format is: m=<media> <port> <proto> <fmt> ...
|
||||
newMLine.append(origMLineParts[origPartIndex++]).append(" ");
|
||||
newMLine.append(origMLineParts[origPartIndex++]).append(" ");
|
||||
newMLine.append(origMLineParts[origPartIndex++]).append(" ");
|
||||
newMLine.append(codecRtpMap);
|
||||
for (; origPartIndex < origMLineParts.length; origPartIndex++) {
|
||||
if (!origMLineParts[origPartIndex].equals(codecRtpMap)) {
|
||||
newMLine.append(" ").append(origMLineParts[origPartIndex]);
|
||||
}
|
||||
}
|
||||
lines[mLineIndex] = newMLine.toString();
|
||||
Log.d(TAG, "Change media description: " + lines[mLineIndex]);
|
||||
} else {
|
||||
Log.e(TAG, "Wrong SDP media description format: " + lines[mLineIndex]);
|
||||
}
|
||||
lines[mLineIndex] = newMLine.toString();
|
||||
Log.d(TAG, "Change media description: " + lines[mLineIndex]);
|
||||
StringBuilder newSdpDescription = new StringBuilder();
|
||||
for (String line : lines) {
|
||||
newSdpDescription.append(line).append("\r\n");
|
||||
|
@ -43,8 +43,8 @@ import org.webrtc.SessionDescription;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
import java.net.URLConnection;
|
||||
import java.util.LinkedList;
|
||||
import java.util.Scanner;
|
||||
|
||||
@ -54,10 +54,10 @@ import java.util.Scanner;
|
||||
*/
|
||||
public class RoomParametersFetcher {
|
||||
private static final String TAG = "RoomRTCClient";
|
||||
private static final int TURN_HTTP_TIMEOUT_MS = 5000;
|
||||
private final RoomParametersFetcherEvents events;
|
||||
private final boolean loopback;
|
||||
private final String registerUrl;
|
||||
private final String registerMessage;
|
||||
private final String roomUrl;
|
||||
private final String roomMessage;
|
||||
private AsyncHttpURLConnection httpConnection;
|
||||
|
||||
/**
|
||||
@ -76,18 +76,17 @@ public class RoomParametersFetcher {
|
||||
public void onSignalingParametersError(final String description);
|
||||
}
|
||||
|
||||
public RoomParametersFetcher(boolean loopback, String registerUrl,
|
||||
String registerMessage, final RoomParametersFetcherEvents events) {
|
||||
this.loopback = loopback;
|
||||
this.registerUrl = registerUrl;
|
||||
this.registerMessage = registerMessage;
|
||||
public RoomParametersFetcher(String roomUrl, String roomMessage,
|
||||
final RoomParametersFetcherEvents events) {
|
||||
this.roomUrl = roomUrl;
|
||||
this.roomMessage = roomMessage;
|
||||
this.events = events;
|
||||
}
|
||||
|
||||
public void makeRequest() {
|
||||
Log.d(TAG, "Connecting to room: " + registerUrl);
|
||||
Log.d(TAG, "Connecting to room: " + roomUrl);
|
||||
httpConnection = new AsyncHttpURLConnection(
|
||||
"POST", registerUrl, registerMessage,
|
||||
"POST", roomUrl, roomMessage,
|
||||
new AsyncHttpEvents() {
|
||||
@Override
|
||||
public void onHttpError(String errorMessage) {
|
||||
@ -161,6 +160,7 @@ public class RoomParametersFetcher {
|
||||
break;
|
||||
}
|
||||
}
|
||||
// Request TURN servers.
|
||||
if (!isTurnPresent) {
|
||||
LinkedList<PeerConnection.IceServer> turnServers =
|
||||
requestTurnServers(roomJson.getString("turn_url"));
|
||||
@ -170,17 +170,13 @@ public class RoomParametersFetcher {
|
||||
}
|
||||
}
|
||||
|
||||
MediaConstraints pcConstraints = constraintsFromJSON(
|
||||
roomJson.getString("pc_constraints"));
|
||||
addDTLSConstraintIfMissing(pcConstraints, loopback);
|
||||
MediaConstraints pcConstraints = constraintsFromJSON(roomJson.getString("pc_constraints"));
|
||||
Log.d(TAG, "pcConstraints: " + pcConstraints);
|
||||
MediaConstraints videoConstraints = constraintsFromJSON(
|
||||
getAVConstraints("video",
|
||||
roomJson.getString("media_constraints")));
|
||||
getAVConstraints("video", roomJson.getString("media_constraints")));
|
||||
Log.d(TAG, "videoConstraints: " + videoConstraints);
|
||||
MediaConstraints audioConstraints = constraintsFromJSON(
|
||||
getAVConstraints("audio",
|
||||
roomJson.getString("media_constraints")));
|
||||
getAVConstraints("audio", roomJson.getString("media_constraints")));
|
||||
Log.d(TAG, "audioConstraints: " + audioConstraints);
|
||||
|
||||
SignalingParameters params = new SignalingParameters(
|
||||
@ -197,35 +193,10 @@ public class RoomParametersFetcher {
|
||||
}
|
||||
}
|
||||
|
||||
// Mimic Chrome and set DtlsSrtpKeyAgreement to true if not set to false by
|
||||
// the web-app.
|
||||
private void addDTLSConstraintIfMissing(
|
||||
MediaConstraints pcConstraints, boolean loopback) {
|
||||
for (MediaConstraints.KeyValuePair pair : pcConstraints.mandatory) {
|
||||
if (pair.getKey().equals("DtlsSrtpKeyAgreement")) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
for (MediaConstraints.KeyValuePair pair : pcConstraints.optional) {
|
||||
if (pair.getKey().equals("DtlsSrtpKeyAgreement")) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
// DTLS isn't being specified (e.g. for debug=loopback calls), so enable
|
||||
// it for normal calls and disable for loopback calls.
|
||||
if (loopback) {
|
||||
pcConstraints.optional.add(
|
||||
new MediaConstraints.KeyValuePair("DtlsSrtpKeyAgreement", "false"));
|
||||
} else {
|
||||
pcConstraints.optional.add(
|
||||
new MediaConstraints.KeyValuePair("DtlsSrtpKeyAgreement", "true"));
|
||||
}
|
||||
}
|
||||
|
||||
// Return the constraints specified for |type| of "audio" or "video" in
|
||||
// |mediaConstraintsString|.
|
||||
private String getAVConstraints (
|
||||
String type, String mediaConstraintsString) throws JSONException {
|
||||
String type, String mediaConstraintsString) throws JSONException {
|
||||
JSONObject json = new JSONObject(mediaConstraintsString);
|
||||
// Tricky handling of values that are allowed to be (boolean or
|
||||
// MediaTrackConstraints) by the getUserMedia() spec. There are three
|
||||
@ -282,10 +253,17 @@ public class RoomParametersFetcher {
|
||||
LinkedList<PeerConnection.IceServer> turnServers =
|
||||
new LinkedList<PeerConnection.IceServer>();
|
||||
Log.d(TAG, "Request TURN from: " + url);
|
||||
URLConnection connection = (new URL(url)).openConnection();
|
||||
connection.addRequestProperty("user-agent", "Mozilla/5.0");
|
||||
connection.addRequestProperty("origin", "https://apprtc.appspot.com");
|
||||
String response = drainStream(connection.getInputStream());
|
||||
HttpURLConnection connection = (HttpURLConnection) new URL(url).openConnection();
|
||||
connection.setConnectTimeout(TURN_HTTP_TIMEOUT_MS);
|
||||
connection.setReadTimeout(TURN_HTTP_TIMEOUT_MS);
|
||||
int responseCode = connection.getResponseCode();
|
||||
if (responseCode != 200) {
|
||||
throw new IOException("Non-200 response when requesting TURN server from "
|
||||
+ url + " : " + connection.getHeaderField(null));
|
||||
}
|
||||
InputStream responseStream = connection.getInputStream();
|
||||
String response = drainStream(responseStream);
|
||||
connection.disconnect();
|
||||
Log.d(TAG, "TURN response: " + response);
|
||||
JSONObject responseJSON = new JSONObject(response);
|
||||
String username = responseJSON.getString("username");
|
||||
@ -317,7 +295,7 @@ public class RoomParametersFetcher {
|
||||
}
|
||||
|
||||
// Return the contents of an InputStream as a String.
|
||||
private String drainStream(InputStream in) {
|
||||
private static String drainStream(InputStream in) {
|
||||
Scanner s = new Scanner(in).useDelimiter("\\A");
|
||||
return s.hasNext() ? s.next() : "";
|
||||
}
|
||||
|
@ -82,14 +82,12 @@ public class WebSocketChannelClient {
|
||||
* All events are dispatched from a looper executor thread.
|
||||
*/
|
||||
public interface WebSocketChannelEvents {
|
||||
public void onWebSocketOpen();
|
||||
public void onWebSocketMessage(final String message);
|
||||
public void onWebSocketClose();
|
||||
public void onWebSocketError(final String description);
|
||||
}
|
||||
|
||||
public WebSocketChannelClient(LooperExecutor executor,
|
||||
WebSocketChannelEvents events) {
|
||||
public WebSocketChannelClient(LooperExecutor executor, WebSocketChannelEvents events) {
|
||||
this.executor = executor;
|
||||
this.events = events;
|
||||
roomID = null;
|
||||
@ -102,8 +100,7 @@ public class WebSocketChannelClient {
|
||||
return state;
|
||||
}
|
||||
|
||||
public void connect(final String wsUrl, final String postUrl,
|
||||
final String roomID, final String clientID) {
|
||||
public void connect(final String wsUrl, final String postUrl) {
|
||||
checkIfCalledOnValidThread();
|
||||
if (state != WebSocketConnectionState.NEW) {
|
||||
Log.e(TAG, "WebSocket is already connected.");
|
||||
@ -111,8 +108,6 @@ public class WebSocketChannelClient {
|
||||
}
|
||||
wsServerUrl = wsUrl;
|
||||
postServerUrl = postUrl;
|
||||
this.roomID = roomID;
|
||||
this.clientID = clientID;
|
||||
closeEvent = false;
|
||||
|
||||
Log.d(TAG, "Connecting WebSocket to: " + wsUrl + ". Post URL: " + postUrl);
|
||||
@ -127,12 +122,15 @@ public class WebSocketChannelClient {
|
||||
}
|
||||
}
|
||||
|
||||
public void register() {
|
||||
public void register(final String roomID, final String clientID) {
|
||||
checkIfCalledOnValidThread();
|
||||
this.roomID = roomID;
|
||||
this.clientID = clientID;
|
||||
if (state != WebSocketConnectionState.CONNECTED) {
|
||||
Log.w(TAG, "WebSocket register() in state " + state);
|
||||
Log.d(TAG, "WebSocket register() in state " + state);
|
||||
return;
|
||||
}
|
||||
Log.d(TAG, "Registering WebSocket for room " + roomID + ". CLientID: " + clientID);
|
||||
JSONObject json = new JSONObject();
|
||||
try {
|
||||
json.put("cmd", "register");
|
||||
@ -271,7 +269,10 @@ public class WebSocketChannelClient {
|
||||
@Override
|
||||
public void run() {
|
||||
state = WebSocketConnectionState.CONNECTED;
|
||||
events.onWebSocketOpen();
|
||||
// Check if we have pending register request.
|
||||
if (roomID != null && clientID != null) {
|
||||
register(roomID, clientID);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -77,6 +77,7 @@ public class WebSocketRTCClient implements AppRTCClient,
|
||||
this.events = events;
|
||||
this.executor = executor;
|
||||
roomState = ConnectionState.NEW;
|
||||
executor.requestStart();
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
@ -86,7 +87,6 @@ public class WebSocketRTCClient implements AppRTCClient,
|
||||
@Override
|
||||
public void connectToRoom(RoomConnectionParameters connectionParameters) {
|
||||
this.connectionParameters = connectionParameters;
|
||||
executor.requestStart();
|
||||
executor.execute(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
@ -131,8 +131,7 @@ public class WebSocketRTCClient implements AppRTCClient,
|
||||
}
|
||||
};
|
||||
|
||||
new RoomParametersFetcher(connectionParameters.loopback, connectionUrl,
|
||||
null, callbacks).makeRequest();
|
||||
new RoomParametersFetcher(connectionUrl, null, callbacks).makeRequest();
|
||||
}
|
||||
|
||||
// Disconnect from room and send bye messages - runs on a local looper thread.
|
||||
@ -193,9 +192,9 @@ public class WebSocketRTCClient implements AppRTCClient,
|
||||
// Fire connection and signaling parameters events.
|
||||
events.onConnectedToRoom(signalingParameters);
|
||||
|
||||
// Connect to WebSocket server.
|
||||
wsClient.connect(signalingParameters.wssUrl, signalingParameters.wssPostUrl,
|
||||
connectionParameters.roomId, signalingParameters.clientId);
|
||||
// Connect and register WebSocket client.
|
||||
wsClient.connect(signalingParameters.wssUrl, signalingParameters.wssPostUrl);
|
||||
wsClient.register(connectionParameters.roomId, signalingParameters.clientId);
|
||||
}
|
||||
|
||||
// Send local offer SDP to the other participant.
|
||||
@ -274,12 +273,6 @@ public class WebSocketRTCClient implements AppRTCClient,
|
||||
// WebSocketChannelEvents interface implementation.
|
||||
// All events are called by WebSocketChannelClient on a local looper thread
|
||||
// (passed to WebSocket client constructor).
|
||||
@Override
|
||||
public void onWebSocketOpen() {
|
||||
Log.d(TAG, "Websocket connection completed. Registering...");
|
||||
wsClient.register();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onWebSocketMessage(final String msg) {
|
||||
if (wsClient.getState() != WebSocketConnectionState.REGISTERED) {
|
||||
|
@ -40,6 +40,7 @@ import java.util.Scanner;
|
||||
*/
|
||||
public class AsyncHttpURLConnection {
|
||||
private static final int HTTP_TIMEOUT_MS = 8000;
|
||||
private static final String HTTP_ORIGIN = "https://apprtc.appspot.com";
|
||||
private final String method;
|
||||
private final String url;
|
||||
private final String message;
|
||||
@ -83,6 +84,8 @@ public class AsyncHttpURLConnection {
|
||||
connection.setDoInput(true);
|
||||
connection.setConnectTimeout(HTTP_TIMEOUT_MS);
|
||||
connection.setReadTimeout(HTTP_TIMEOUT_MS);
|
||||
// TODO(glaznev) - query request origin from pref_room_server_url_key preferences.
|
||||
connection.addRequestProperty("origin", HTTP_ORIGIN);
|
||||
boolean doOutput = false;
|
||||
if (method.equals("POST")) {
|
||||
doOutput = true;
|
||||
@ -102,6 +105,7 @@ public class AsyncHttpURLConnection {
|
||||
// Get response.
|
||||
int responseCode = connection.getResponseCode();
|
||||
if (responseCode != 200) {
|
||||
connection.disconnect();
|
||||
events.onHttpError("Non-200 response to " + method + " to URL: "
|
||||
+ url + " : " + connection.getHeaderField(null));
|
||||
return;
|
||||
@ -109,6 +113,7 @@ public class AsyncHttpURLConnection {
|
||||
InputStream responseStream = connection.getInputStream();
|
||||
String response = drainStream(responseStream);
|
||||
responseStream.close();
|
||||
connection.disconnect();
|
||||
events.onHttpComplete(response);
|
||||
} catch (SocketTimeoutException e) {
|
||||
events.onHttpError("HTTP " + method + " to " + url + " timeout");
|
||||
|
@ -85,7 +85,7 @@ public class LooperExecutor extends Thread implements Executor {
|
||||
handler.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
Looper.myLooper().quitSafely();
|
||||
Looper.myLooper().quit();
|
||||
Log.d(TAG, "Looper thread finished.");
|
||||
}
|
||||
});
|
||||
|
Loading…
x
Reference in New Issue
Block a user