ここんところ、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のアプリが増えるといいなあ〜