ここんところ、WebRTCのアプリを作ろうとして、四苦八苦していました。
やっと、データチャンネルで送受信するアプリができたので、ここに公開します。
開発環境ですが
Android Studio 1.2.1.1 build 141.19032520
を使って開発しています。
普通にプロジェクトを作成します。
まず、WebRTCのライブラリをインポートする為の設定を行います。
app配下にあるbuild.gradleのdependenciesに下記の一行を入れるだけ
compile 'io.pristine:libjingle:9357@aar'
これでなんと、ネイティブのsoライブラリまで持ってきてくれます(なんと便利な!)
次に、AndroidManifest.xmlにパーミッションを追加します
<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" />
これで、準備OK、ここからは、MainActivity.javaのソースをそのまま掲載しちゃいます。
package jp.eguchi.android.datachannelsample;
// WebRTC Data Channel Sample Program
// Programed by Kazuyuki Eguchi
import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import org.json.JSONObject;
import org.webrtc.DataChannel;
import org.webrtc.IceCandidate;
import org.webrtc.MediaConstraints;
import org.webrtc.MediaStream;
import org.webrtc.PeerConnection;
import org.webrtc.PeerConnectionFactory;
import org.webrtc.SdpObserver;
import org.webrtc.SessionDescription;
import java.nio.ByteBuffer;
import java.util.LinkedList;
import java.util.List;
public class MainActivity extends Activity {
final private String TAG = "sample";
PeerConnectionFactory factory = null;
PeerConnection pc1 = null;
PeerConnection pc2 = null;
DataChannel dc1 = null;
DataChannel dc2 = null;
// pc1用のObserver
PeerConnection.Observer pcob1 = new PeerConnection.Observer() {
@Override
public void onSignalingChange(PeerConnection.SignalingState signalingState) {
Log.d(TAG, "pcob1 onSignalingChange() " + signalingState.name());
}
@Override
public void onIceConnectionChange(PeerConnection.IceConnectionState iceConnectionState) {
Log.d(TAG, "pcob1 onIceConnectionChange() " + iceConnectionState.name());
}
@Override
public void onIceGatheringChange(PeerConnection.IceGatheringState iceGatheringState) {
Log.d(TAG, "pcob1 onIceGatheringChange() " + iceGatheringState.name());
}
@Override
public void onIceCandidate(IceCandidate iceCandidate) {
Log.d(TAG, "pcob1 onIceCandidate()");
JSONObject json = new JSONObject();
String mes = null;
try {
json.put("type", "candidate");
json.put("sdpMLineIndex", iceCandidate.sdpMLineIndex);
json.put("sdpMid", iceCandidate.sdpMid);
json.put("candidate", iceCandidate.sdp);
mes = json.toString();
Log.d(TAG, mes);
// ここで、 WebSocket等で相手側に mesを送る
// 受信側では
JSONObject json2 = new JSONObject(mes);
IceCandidate candidate = new IceCandidate(json2.getString("sdpMid"), json2.getInt("sdpMLineIndex"), json2.getString("candidate"));
pc2.addIceCandidate(candidate);
} catch (org.json.JSONException ex) {
Log.d(TAG, ex.toString());
}
}
@Override
public void onAddStream(MediaStream mediaStream) {
Log.d(TAG, "pcob1 onAddStream()");
}
@Override
public void onRemoveStream(MediaStream mediaStream) {
Log.d(TAG, "pcob1 onRemoveStream");
}
@Override
public void onDataChannel(DataChannel dataChannel) {
Log.d(TAG, "pcob1 onDataChannel()");
}
@Override
public void onRenegotiationNeeded() {
Log.d(TAG, "pcob1 onRenegotiationNeeded()");
}
};
// pc2用のObserver
PeerConnection.Observer pcob2 = new PeerConnection.Observer() {
@Override
public void onSignalingChange(PeerConnection.SignalingState signalingState) {
Log.d(TAG, "pcob2 onSignalingChange() " + signalingState.name());
}
@Override
public void onIceConnectionChange(PeerConnection.IceConnectionState iceConnectionState) {
Log.d(TAG, "pcob2 onIceConnectionChange() " + iceConnectionState.name());
}
@Override
public void onIceGatheringChange(PeerConnection.IceGatheringState iceGatheringState) {
Log.d(TAG, "pcob2 onIceGatheringChange() " + iceGatheringState.name());
}
@Override
public void onIceCandidate(IceCandidate iceCandidate) {
Log.d(TAG, "pcob2 onIceCandidate()");
JSONObject json = new JSONObject();
String mes = null;
try {
json.put("type", "candidate");
json.put("sdpMLineIndex", iceCandidate.sdpMLineIndex);
json.put("sdpMid", iceCandidate.sdpMid);
json.put("candidate", iceCandidate.sdp);
mes = json.toString();
Log.d(TAG, mes);
// ここで、 WebSocket等で相手側に mesを送る
// 受信側では
JSONObject json2 = new JSONObject(mes);
IceCandidate candidate = new IceCandidate(json2.getString("sdpMid"), json2.getInt("sdpMLineIndex"), json2.getString("candidate"));
pc1.addIceCandidate(candidate);
} catch (org.json.JSONException ex) {
Log.d(TAG, ex.toString());
}
}
@Override
public void onAddStream(MediaStream mediaStream) {
Log.d(TAG, "pcob2 onAddStream()");
}
@Override
public void onRemoveStream(MediaStream mediaStream) {
Log.d(TAG, "pcob2 onRemoveStream");
}
@Override
public void onDataChannel(DataChannel dataChannel) {
Log.d(TAG, "pcob2 onDataChannel()");
dc2 = dataChannel;
dc2.registerObserver(dc2o);
}
@Override
public void onRenegotiationNeeded() {
Log.d(TAG, "pcob2 onRenegotiationNeeded()");
}
};
SdpObserver so1 = new SdpObserver() {
@Override
public void onCreateSuccess(SessionDescription sessionDescription) {
Log.d(TAG, "so1 onCreateSuccess()");
pc1.setLocalDescription(so1, sessionDescription);
JSONObject json = new JSONObject();
String mes = null;
try {
json.put("type", sessionDescription.type.toString().toLowerCase());
json.put("sdp", sessionDescription.description);
mes = json.toString();
Log.d(TAG, mes);
// ここで、 WebSocket等で相手側に mesを送る
// 受信側では
JSONObject json2 = new JSONObject(mes);
String type = json2.getString("type");
String sdp = json2.getString("sdp");
SessionDescription sdp2 = new SessionDescription(SessionDescription.Type.fromCanonicalForm(type), sdp);
pc2.setRemoteDescription(so2, sdp2);
MediaConstraints constraints = new MediaConstraints();
pc2.createAnswer(so2, constraints);
} catch (org.json.JSONException ex) {
Log.d(TAG, ex.toString());
}
}
@Override
public void onSetSuccess() {
Log.d(TAG, "so1 onCreateSuccess()");
}
@Override
public void onCreateFailure(String s) {
Log.d(TAG, "so1 onCreateFailure() " + s);
}
@Override
public void onSetFailure(String s) {
Log.d(TAG, "so1 onSetFailure() " + s);
}
};
SdpObserver so2 = new SdpObserver() {
@Override
public void onCreateSuccess(SessionDescription sessionDescription) {
Log.d(TAG, "so2 onCreateSuccess()");
pc2.setLocalDescription(so2, sessionDescription);
JSONObject json = new JSONObject();
String mes = null;
try {
json.put("type", sessionDescription.type.toString().toLowerCase());
json.put("sdp", sessionDescription.description);
mes = json.toString();
Log.d(TAG, mes);
// ここで、 WebSocket等で相手側に mesを送る
// 受信側では
JSONObject json2 = new JSONObject(mes);
String type = json2.getString("type");
String sdp = json2.getString("sdp");
SessionDescription sdp2 = new SessionDescription(SessionDescription.Type.fromCanonicalForm(type), sdp);
pc1.setRemoteDescription(so1, sdp2);
} catch (org.json.JSONException ex) {
Log.d(TAG, ex.toString());
}
}
@Override
public void onSetSuccess() {
Log.d(TAG, "so2 onCreateSuccess()");
}
@Override
public void onCreateFailure(String s) {
Log.d(TAG, "so2 onCreateFailure() " + s);
}
@Override
public void onSetFailure(String s) {
Log.d(TAG, "so2 onSetFailure() " + s);
}
};
DataChannel.Observer dc1o = new DataChannel.Observer() {
@Override
public void onStateChange() {
Log.d(TAG, "da1o onStateChange() " + dc1.state().name());
if (dc1.state() == DataChannel.State.OPEN) {
String data = "from dc1";
ByteBuffer buffer = ByteBuffer.wrap(data.getBytes());
dc1.send(new DataChannel.Buffer(buffer, false));
}
}
@Override
public void onMessage(DataChannel.Buffer buffer) {
Log.d(TAG, "da1o onMessage()");
if (buffer.binary == false) {
int limit = buffer.data.limit();
byte[] datas = new byte[limit];
buffer.data.get(datas);
String tmp = new String(datas);
Log.d(TAG, tmp);
}
}
};
DataChannel.Observer dc2o = new DataChannel.Observer() {
@Override
public void onStateChange() {
Log.d(TAG, "da2o onStateChange() " + dc2.state().name());
if (dc2.state() == DataChannel.State.OPEN) {
String data = "from dc2";
ByteBuffer buffer = ByteBuffer.wrap(data.getBytes());
dc2.send(new DataChannel.Buffer(buffer, false));
}
}
@Override
public void onMessage(DataChannel.Buffer buffer) {
Log.d(TAG, "da2o onMessage()");
if (buffer.binary == false) {
int limit = buffer.data.limit();
byte[] datas = new byte[limit];
buffer.data.get(datas);
String tmp = new String(datas);
Log.d(TAG, tmp);
}
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Log.d(TAG, "onCreate()");
PeerConnectionFactory.initializeAndroidGlobals(getApplicationContext(), true, false, false, null);
factory = new PeerConnectionFactory();
}
@Override
protected void onResume() {
super.onResume();
Log.d(TAG, "onResume()");
// STRNサーバは Google様のを使わせてもらう。
List<PeerConnection.IceServer> iceServers = new LinkedList<PeerConnection.IceServer>();
iceServers.add(new PeerConnection.IceServer("stun:stun.l.google.com:19302"));
MediaConstraints constraints = new MediaConstraints();
pc1 = factory.createPeerConnection(iceServers, constraints, pcob1);
pc2 = factory.createPeerConnection(iceServers, constraints, pcob2);
// offerする前にデータチャネルを作成する
dc1 = pc1.createDataChannel("RTCDataChannel", new DataChannel.Init());
dc1.registerObserver(dc1o);
// Offerする
pc1.createOffer(so1, constraints);
}
@Override
protected void onPause() {
super.onPause();
Log.d(TAG, "onPause()");
if (dc1 != null) {
dc1.close();
dc1.unregisterObserver();
}
if (dc2 != null) {
dc2.close();
dc2.unregisterObserver();
}
if (pc1 != null) {
pc1.close();
}
if (pc2 != null) {
pc2.close();
}
}
}
実行した際のLogcatのログ
06-10 17:44:58.719 4149-4149/jp.eguchi.android.datachannelsample D/sample﹕ onCreate()
06-10 17:44:58.723 4149-11577/jp.eguchi.android.datachannelsample D/JVM﹕ AttachCurrentThreadIfNeeded::ctor@[tid=11577]
06-10 17:44:58.723 4149-11577/jp.eguchi.android.datachannelsample D/JVM﹕ Attaching thread to JVM
06-10 17:44:58.724 4149-11577/jp.eguchi.android.datachannelsample D/JVM﹕ JVM::environment@[tid=11577]
06-10 17:44:58.724 4149-11577/jp.eguchi.android.datachannelsample D/JVM﹕ JNIEnvironment::ctor@[tid=11577]
06-10 17:44:58.724 4149-11577/jp.eguchi.android.datachannelsample D/AudioManager﹕ ctor@[tid=11577]
06-10 17:44:58.724 4149-11577/jp.eguchi.android.datachannelsample D/JVM﹕ JNIEnvironment::RegisterNatives(org/webrtc/voiceengine/WebRtcAudioManager)
06-10 17:44:58.724 4149-11577/jp.eguchi.android.datachannelsample D/JVM﹕ NativeRegistration::ctor@[tid=11577]
06-10 17:44:58.724 4149-11577/jp.eguchi.android.datachannelsample D/JVM﹕ NativeRegistration::NewObject@[tid=11577]
06-10 17:44:58.724 4149-11577/jp.eguchi.android.datachannelsample D/WebRtcAudioManager﹕ ctor@[name=Thread-1038, id=1038]
06-10 17:44:58.724 4149-11577/jp.eguchi.android.datachannelsample D/WebRtcAudioManager﹕ Nexus 5 is blacklisted for HW AEC usage!
06-10 17:44:58.726 4149-11577/jp.eguchi.android.datachannelsample D/AudioManager﹕ OnCacheAudioParameters@[tid=11577]
06-10 17:44:58.726 4149-11577/jp.eguchi.android.datachannelsample D/AudioManager﹕ hardware_aec: 0
06-10 17:44:58.726 4149-11577/jp.eguchi.android.datachannelsample D/AudioManager﹕ low_latency_output: 1
06-10 17:44:58.726 4149-11577/jp.eguchi.android.datachannelsample D/AudioManager﹕ sample_rate: 48000
06-10 17:44:58.726 4149-11577/jp.eguchi.android.datachannelsample D/AudioManager﹕ channels: 1
06-10 17:44:58.726 4149-11577/jp.eguchi.android.datachannelsample D/AudioManager﹕ output_buffer_size: 240
06-10 17:44:58.726 4149-11577/jp.eguchi.android.datachannelsample D/AudioManager﹕ input_buffer_size: 1920
06-10 17:44:58.726 4149-11577/jp.eguchi.android.datachannelsample D/JVM﹕ GlobalRef::ctor@[tid=11577]
06-10 17:44:58.726 4149-11577/jp.eguchi.android.datachannelsample D/AudioManager﹕ JavaAudioManager::ctor@[tid=11577]
06-10 17:44:58.726 4149-11577/jp.eguchi.android.datachannelsample D/AudioManager﹕ IsLowLatencyPlayoutSupported()
06-10 17:44:58.726 4149-11577/jp.eguchi.android.datachannelsample W/AudioManager﹕ NOTE: OpenSL ES output is currently disabled!
06-10 17:44:58.726 4149-11577/jp.eguchi.android.datachannelsample D/JVM﹕ AttachCurrentThreadIfNeeded::ctor@[tid=11577]
06-10 17:44:58.726 4149-11577/jp.eguchi.android.datachannelsample D/JVM﹕ JVM::environment@[tid=11577]
06-10 17:44:58.726 4149-11577/jp.eguchi.android.datachannelsample D/JVM﹕ JNIEnvironment::ctor@[tid=11577]
06-10 17:44:58.726 4149-11577/jp.eguchi.android.datachannelsample D/AudioTrackJni﹕ ctor@[tid=11577]
06-10 17:44:58.726 4149-11577/jp.eguchi.android.datachannelsample D/JVM﹕ JNIEnvironment::RegisterNatives(org/webrtc/voiceengine/WebRtcAudioTrack)
06-10 17:44:58.726 4149-11577/jp.eguchi.android.datachannelsample D/JVM﹕ NativeRegistration::ctor@[tid=11577]
06-10 17:44:58.726 4149-11577/jp.eguchi.android.datachannelsample D/JVM﹕ NativeRegistration::NewObject@[tid=11577]
06-10 17:44:58.726 4149-11577/jp.eguchi.android.datachannelsample D/WebRtcAudioTrack﹕ ctor@[name=Thread-1038, id=1038]
06-10 17:44:58.726 4149-11577/jp.eguchi.android.datachannelsample D/WebRtcAudioTrack﹕ Android SDK: 22, Release: M, Brand: google, Device: hammerhead, Id: MPZ44Q, Hardware: hammerhead, Manufacturer: LGE, Model: Nexus 5, Product: hammerhead
06-10 17:44:58.726 4149-11577/jp.eguchi.android.datachannelsample D/JVM﹕ GlobalRef::ctor@[tid=11577]
06-10 17:44:58.726 4149-11577/jp.eguchi.android.datachannelsample D/JVM﹕ AttachCurrentThreadIfNeeded::ctor@[tid=11577]
06-10 17:44:58.726 4149-11577/jp.eguchi.android.datachannelsample D/JVM﹕ JVM::environment@[tid=11577]
06-10 17:44:58.726 4149-11577/jp.eguchi.android.datachannelsample D/JVM﹕ JNIEnvironment::ctor@[tid=11577]
06-10 17:44:58.726 4149-11577/jp.eguchi.android.datachannelsample D/AudioRecordJni﹕ ctor@[tid=11577]
06-10 17:44:58.726 4149-11577/jp.eguchi.android.datachannelsample D/JVM﹕ JNIEnvironment::RegisterNatives(org/webrtc/voiceengine/WebRtcAudioRecord)
06-10 17:44:58.726 4149-11577/jp.eguchi.android.datachannelsample D/JVM﹕ NativeRegistration::ctor@[tid=11577]
06-10 17:44:58.726 4149-11577/jp.eguchi.android.datachannelsample D/JVM﹕ NativeRegistration::NewObject@[tid=11577]
06-10 17:44:58.726 4149-11577/jp.eguchi.android.datachannelsample D/WebRtcAudioRecord﹕ ctor@[name=Thread-1038, id=1038]
06-10 17:44:58.726 4149-11577/jp.eguchi.android.datachannelsample D/JVM﹕ GlobalRef::ctor@[tid=11577]
06-10 17:44:58.727 4149-11577/jp.eguchi.android.datachannelsample D/AudioManager﹕ SetActiveAudioLayer(5)@[tid=11577]
06-10 17:44:58.727 4149-11577/jp.eguchi.android.datachannelsample D/AudioManager﹕ delay_estimate_in_milliseconds: 150
06-10 17:44:58.727 4149-11577/jp.eguchi.android.datachannelsample D/AudioTrackJni﹕ AttachAudioBuffer@[tid=11577]
06-10 17:44:58.727 4149-11577/jp.eguchi.android.datachannelsample D/AudioTrackJni﹕ SetPlayoutSampleRate(48000)
06-10 17:44:58.727 4149-11577/jp.eguchi.android.datachannelsample D/AudioTrackJni﹕ SetPlayoutChannels(1)
06-10 17:44:58.727 4149-11577/jp.eguchi.android.datachannelsample D/AudioRecordJni﹕ AttachAudioBuffer
06-10 17:44:58.727 4149-11577/jp.eguchi.android.datachannelsample D/AudioRecordJni﹕ SetRecordingSampleRate(48000)
06-10 17:44:58.727 4149-11577/jp.eguchi.android.datachannelsample D/AudioRecordJni﹕ SetRecordingChannels(1)
06-10 17:44:58.727 4149-11577/jp.eguchi.android.datachannelsample D/AudioRecordJni﹕ total_delay_in_milliseconds: 150
06-10 17:44:58.727 4149-11577/jp.eguchi.android.datachannelsample D/AudioManager﹕ Init@[tid=11577]
06-10 17:44:58.727 4149-11577/jp.eguchi.android.datachannelsample D/WebRtcAudioManager﹕ init@[name=Thread-1038, id=1038]
06-10 17:44:58.731 4149-4149/jp.eguchi.android.datachannelsample D/sample﹕ onResume()
06-10 17:44:59.157 4149-11578/jp.eguchi.android.datachannelsample D/sample﹕ pcob1 onRenegotiationNeeded()
06-10 17:44:59.158 4149-11578/jp.eguchi.android.datachannelsample D/sample﹕ so1 onCreateSuccess()
06-10 17:44:59.395 4149-11578/jp.eguchi.android.datachannelsample D/sample﹕ pcob1 onSignalingChange() HAVE_LOCAL_OFFER
06-10 17:44:59.396 4149-11578/jp.eguchi.android.datachannelsample D/sample﹕ {"type":"offer","sdp":"v=0\r\no=- 113918549713234061 2 IN IP4 127.0.0.1\r\ns=-\r\nt=0 0\r\na=msid-semantic: WMS\r\nm=application 9 DTLS\/SCTP 5000\r\nc=IN IP4 0.0.0.0\r\na=ice-ufrag:lw3\/LpT+Giz61KGg\r\na=ice-pwd:QxRiyQgLEFnfWEsJ0p5+6G3s\r\na=fingerprint:sha-256 B8:AB:2D:09:BB:01:23:04:44:64:81:1E:F6:65:24:25:74:A5:6C:F0:62:8B:96:3E:86:83:E2:ED:A9:33:E6:FA\r\na=setup:actpass\r\na=mid:data\r\na=sctpmap:5000 webrtc-datachannel 1024\r\n"}
06-10 17:44:59.398 4149-11578/jp.eguchi.android.datachannelsample D/sample﹕ pcob2 onSignalingChange() HAVE_REMOTE_OFFER
06-10 17:44:59.398 4149-11578/jp.eguchi.android.datachannelsample D/sample﹕ pcob1 onIceGatheringChange() GATHERING
06-10 17:44:59.398 4149-11578/jp.eguchi.android.datachannelsample D/sample﹕ so1 onCreateSuccess()
06-10 17:44:59.398 4149-11578/jp.eguchi.android.datachannelsample D/sample﹕ so2 onCreateSuccess()
06-10 17:44:59.399 4149-11578/jp.eguchi.android.datachannelsample D/sample﹕ so2 onCreateSuccess()
06-10 17:44:59.404 4149-11578/jp.eguchi.android.datachannelsample D/sample﹕ pcob2 onSignalingChange() STABLE
06-10 17:44:59.406 4149-11578/jp.eguchi.android.datachannelsample D/sample﹕ {"type":"answer","sdp":"v=0\r\no=- 3088308566766388678 2 IN IP4 127.0.0.1\r\ns=-\r\nt=0 0\r\na=msid-semantic: WMS\r\nm=application 9 DTLS\/SCTP 5000\r\nc=IN IP4 0.0.0.0\r\nb=AS:30\r\na=ice-ufrag:4gco7NuR3wheyF6s\r\na=ice-pwd:Fv95\/TF5FvTbGMCcISNSKNf3\r\na=fingerprint:sha-256 31:EE:E9:19:85:5B:02:5F:CC:80:A3:FC:F7:64:EC:71:B6:01:4E:D0:35:1B:74:8E:D1:30:05:43:49:0D:52:55\r\na=setup:active\r\na=mid:data\r\na=sctpmap:5000 webrtc-datachannel 1024\r\n"}
06-10 17:44:59.410 4149-11578/jp.eguchi.android.datachannelsample D/sample﹕ pcob1 onSignalingChange() STABLE
06-10 17:44:59.412 4149-11578/jp.eguchi.android.datachannelsample D/sample﹕ pcob1 onIceConnectionChange() CHECKING
06-10 17:44:59.412 4149-11578/jp.eguchi.android.datachannelsample D/sample﹕ pcob1 onIceCandidate()
06-10 17:44:59.412 4149-11578/jp.eguchi.android.datachannelsample D/sample﹕ {"type":"candidate","sdpMLineIndex":0,"sdpMid":"data","candidate":"candidate:3209757399 1 udp 2122260223 10.49.120.146 39965 typ host generation 0"}
06-10 17:44:59.415 4149-11578/jp.eguchi.android.datachannelsample D/sample﹕ pcob2 onIceConnectionChange() CHECKING
06-10 17:44:59.415 4149-11578/jp.eguchi.android.datachannelsample D/sample﹕ pcob2 onIceGatheringChange() GATHERING
06-10 17:44:59.415 4149-11578/jp.eguchi.android.datachannelsample D/sample﹕ so2 onCreateSuccess()
06-10 17:44:59.415 4149-11578/jp.eguchi.android.datachannelsample D/sample﹕ so1 onCreateSuccess()
06-10 17:44:59.416 4149-11578/jp.eguchi.android.datachannelsample D/sample﹕ pcob2 onIceCandidate()
06-10 17:44:59.416 4149-11578/jp.eguchi.android.datachannelsample D/sample﹕ {"type":"candidate","sdpMLineIndex":0,"sdpMid":"data","candidate":"candidate:3209757399 1 udp 2122260223 10.49.120.146 41101 typ host generation 0"}
06-10 17:44:59.444 4149-11578/jp.eguchi.android.datachannelsample D/sample﹕ pcob1 onIceGatheringChange() COMPLETE
06-10 17:44:59.453 4149-11578/jp.eguchi.android.datachannelsample D/sample﹕ pcob2 onIceGatheringChange() COMPLETE
06-10 17:44:59.486 4149-11578/jp.eguchi.android.datachannelsample D/sample﹕ pcob1 onIceConnectionChange() CONNECTED
06-10 17:44:59.487 4149-11578/jp.eguchi.android.datachannelsample D/sample﹕ pcob1 onIceConnectionChange() COMPLETED
06-10 17:44:59.488 4149-11578/jp.eguchi.android.datachannelsample D/sample﹕ da1o onStateChange() OPEN
06-10 17:44:59.489 4149-11578/jp.eguchi.android.datachannelsample D/sample﹕ pcob2 onIceConnectionChange() CONNECTED
06-10 17:44:59.493 4149-11578/jp.eguchi.android.datachannelsample D/sample﹕ pcob2 onDataChannel()
06-10 17:44:59.494 4149-11578/jp.eguchi.android.datachannelsample D/sample﹕ da2o onStateChange() OPEN
06-10 17:44:59.495 4149-11578/jp.eguchi.android.datachannelsample D/sample﹕ da2o onMessage()
06-10 17:44:59.495 4149-11578/jp.eguchi.android.datachannelsample D/sample﹕ from dc1
06-10 17:44:59.496 4149-11578/jp.eguchi.android.datachannelsample D/sample﹕ da1o onMessage()
06-10 17:44:59.496 4149-11578/jp.eguchi.android.datachannelsample D/sample﹕ from dc2
06-10 17:45:03.231 4149-4149/jp.eguchi.android.datachannelsample D/sample﹕ onPause()
06-10 17:45:03.231 4149-11578/jp.eguchi.android.datachannelsample D/sample﹕ da1o onStateChange() CLOSING
06-10 17:45:03.233 4149-11578/jp.eguchi.android.datachannelsample D/sample﹕ da1o onStateChange() CLOSED
06-10 17:45:03.233 4149-11578/jp.eguchi.android.datachannelsample D/sample﹕ da2o onStateChange() CLOSING
06-10 17:45:03.234 4149-11578/jp.eguchi.android.datachannelsample D/sample﹕ da2o onStateChange() CLOSED
06-10 17:45:03.236 4149-11578/jp.eguchi.android.datachannelsample D/sample﹕ pcob1 onIceConnectionChange() CLOSED
06-10 17:45:03.236 4149-11578/jp.eguchi.android.datachannelsample D/sample﹕ pcob1 onSignalingChange() CLOSED
06-10 17:45:03.239 4149-11578/jp.eguchi.android.datachannelsample D/sample﹕ pcob2 onIceConnectionChange() CLOSED
06-10 17:45:03.240 4149-11578/jp.eguchi.android.datachannelsample D/sample﹕ pcob2 onSignalingChange() CLOSED
以上、ご参考までに
これで、WebRTCのアプリが増えるといいなあ〜

