1package eu.siacs.conversations.xmpp.jingle;
2
3import android.content.Context;
4import android.util.Log;
5
6import com.google.common.collect.ImmutableList;
7import com.google.common.util.concurrent.Futures;
8import com.google.common.util.concurrent.ListenableFuture;
9import com.google.common.util.concurrent.MoreExecutors;
10import com.google.common.util.concurrent.SettableFuture;
11
12import org.webrtc.AudioSource;
13import org.webrtc.AudioTrack;
14import org.webrtc.DataChannel;
15import org.webrtc.IceCandidate;
16import org.webrtc.MediaConstraints;
17import org.webrtc.MediaStream;
18import org.webrtc.PeerConnection;
19import org.webrtc.PeerConnectionFactory;
20import org.webrtc.RtpReceiver;
21import org.webrtc.SdpObserver;
22import org.webrtc.SessionDescription;
23
24import java.util.List;
25
26import javax.annotation.Nonnull;
27import javax.annotation.Nullable;
28
29import eu.siacs.conversations.Config;
30
31public class WebRTCWrapper {
32
33 private final EventCallback eventCallback;
34
35 private final PeerConnection.Observer peerConnectionObserver = new PeerConnection.Observer() {
36 @Override
37 public void onSignalingChange(PeerConnection.SignalingState signalingState) {
38 Log.d(Config.LOGTAG, "onSignalingChange(" + signalingState + ")");
39
40 }
41
42 @Override
43 public void onConnectionChange(PeerConnection.PeerConnectionState newState) {
44 Log.d(Config.LOGTAG, "onConnectionChange(" + newState + ")");
45 }
46
47 @Override
48 public void onIceConnectionChange(PeerConnection.IceConnectionState iceConnectionState) {
49
50 }
51
52 @Override
53 public void onIceConnectionReceivingChange(boolean b) {
54
55 }
56
57 @Override
58 public void onIceGatheringChange(PeerConnection.IceGatheringState iceGatheringState) {
59
60 }
61
62 @Override
63 public void onIceCandidate(IceCandidate iceCandidate) {
64 eventCallback.onIceCandidate(iceCandidate);
65 }
66
67 @Override
68 public void onIceCandidatesRemoved(IceCandidate[] iceCandidates) {
69
70 }
71
72 @Override
73 public void onAddStream(MediaStream mediaStream) {
74 Log.d(Config.LOGTAG, "onAddStream");
75 for(AudioTrack audioTrack : mediaStream.audioTracks) {
76 Log.d(Config.LOGTAG,"remote? - audioTrack enabled:"+audioTrack.enabled()+" state="+audioTrack.state());
77 }
78 }
79
80 @Override
81 public void onRemoveStream(MediaStream mediaStream) {
82
83 }
84
85 @Override
86 public void onDataChannel(DataChannel dataChannel) {
87
88 }
89
90 @Override
91 public void onRenegotiationNeeded() {
92
93 }
94
95 @Override
96 public void onAddTrack(RtpReceiver rtpReceiver, MediaStream[] mediaStreams) {
97 Log.d(Config.LOGTAG, "onAddTrack()");
98
99 }
100 };
101 @Nullable
102 private PeerConnection peerConnection = null;
103
104 public WebRTCWrapper(final EventCallback eventCallback) {
105 this.eventCallback = eventCallback;
106 }
107
108 public void setup(final Context context) {
109 PeerConnectionFactory.initialize(
110 PeerConnectionFactory.InitializationOptions.builder(context).createInitializationOptions()
111 );
112 }
113
114 public void initializePeerConnection() {
115 final PeerConnectionFactory.Options options = new PeerConnectionFactory.Options();
116 PeerConnectionFactory peerConnectionFactory = PeerConnectionFactory.builder().createPeerConnectionFactory();
117
118 final AudioSource audioSource = peerConnectionFactory.createAudioSource(new MediaConstraints());
119
120 final AudioTrack audioTrack = peerConnectionFactory.createAudioTrack("my-audio-track", audioSource);
121 Log.d(Config.LOGTAG,"audioTrack enabled:"+audioTrack.enabled()+" state="+audioTrack.state());
122 final MediaStream stream = peerConnectionFactory.createLocalMediaStream("my-media-stream");
123 stream.addTrack(audioTrack);
124
125
126 final List<PeerConnection.IceServer> iceServers = ImmutableList.of(
127 PeerConnection.IceServer.builder("stun:xmpp.conversations.im:3478").createIceServer()
128 );
129 final PeerConnection peerConnection = peerConnectionFactory.createPeerConnection(iceServers, peerConnectionObserver);
130 if (peerConnection == null) {
131 throw new IllegalStateException("Unable to create PeerConnection");
132 }
133 peerConnection.addStream(stream);
134 peerConnection.setAudioPlayout(true);
135 peerConnection.setAudioRecording(true);
136 this.peerConnection = peerConnection;
137 }
138
139 public ListenableFuture<SessionDescription> createOffer() {
140 return Futures.transformAsync(getPeerConnectionFuture(), peerConnection -> {
141 final SettableFuture<SessionDescription> future = SettableFuture.create();
142 peerConnection.createOffer(new CreateSdpObserver() {
143 @Override
144 public void onCreateSuccess(SessionDescription sessionDescription) {
145 future.set(sessionDescription);
146 }
147
148 @Override
149 public void onCreateFailure(String s) {
150 future.setException(new IllegalStateException("Unable to create offer: " + s));
151 }
152 }, new MediaConstraints());
153 return future;
154 }, MoreExecutors.directExecutor());
155 }
156
157 public ListenableFuture<SessionDescription> createAnswer() {
158 return Futures.transformAsync(getPeerConnectionFuture(), peerConnection -> {
159 final SettableFuture<SessionDescription> future = SettableFuture.create();
160 peerConnection.createAnswer(new CreateSdpObserver() {
161 @Override
162 public void onCreateSuccess(SessionDescription sessionDescription) {
163 future.set(sessionDescription);
164 }
165
166 @Override
167 public void onCreateFailure(String s) {
168 future.setException(new IllegalStateException("Unable to create answer: " + s));
169 }
170 }, new MediaConstraints());
171 return future;
172 }, MoreExecutors.directExecutor());
173 }
174
175 public ListenableFuture<Void> setLocalDescription(final SessionDescription sessionDescription) {
176 return Futures.transformAsync(getPeerConnectionFuture(), peerConnection -> {
177 final SettableFuture<Void> future = SettableFuture.create();
178 peerConnection.setLocalDescription(new SetSdpObserver() {
179 @Override
180 public void onSetSuccess() {
181 future.set(null);
182 }
183
184 @Override
185 public void onSetFailure(String s) {
186 future.setException(new IllegalArgumentException("unable to set local session description: " + s));
187
188 }
189 }, sessionDescription);
190 return future;
191 }, MoreExecutors.directExecutor());
192 }
193
194 public ListenableFuture<Void> setRemoteDescription(final SessionDescription sessionDescription) {
195 return Futures.transformAsync(getPeerConnectionFuture(), peerConnection -> {
196 final SettableFuture<Void> future = SettableFuture.create();
197 peerConnection.setRemoteDescription(new SetSdpObserver() {
198 @Override
199 public void onSetSuccess() {
200 future.set(null);
201 }
202
203 @Override
204 public void onSetFailure(String s) {
205 future.setException(new IllegalArgumentException("unable to set remote session description: " + s));
206
207 }
208 }, sessionDescription);
209 return future;
210 }, MoreExecutors.directExecutor());
211 }
212
213 @Nonnull
214 private ListenableFuture<PeerConnection> getPeerConnectionFuture() {
215 final PeerConnection peerConnection = this.peerConnection;
216 if (peerConnection == null) {
217 return Futures.immediateFailedFuture(new IllegalStateException("initialize PeerConnection first"));
218 } else {
219 return Futures.immediateFuture(peerConnection);
220 }
221 }
222
223 public void addIceCandidate(IceCandidate iceCandidate) {
224 final PeerConnection peerConnection = this.peerConnection;
225 if (peerConnection == null) {
226 throw new IllegalStateException("initialize PeerConnection first");
227 }
228 peerConnection.addIceCandidate(iceCandidate);
229 }
230
231 public PeerConnection.PeerConnectionState getState() {
232 return this.peerConnection.connectionState();
233 }
234
235 private static abstract class SetSdpObserver implements SdpObserver {
236
237 @Override
238 public void onCreateSuccess(org.webrtc.SessionDescription sessionDescription) {
239 throw new IllegalStateException("Not able to use SetSdpObserver");
240 }
241
242 @Override
243 public void onCreateFailure(String s) {
244 throw new IllegalStateException("Not able to use SetSdpObserver");
245 }
246
247 }
248
249 private static abstract class CreateSdpObserver implements SdpObserver {
250
251
252 @Override
253 public void onSetSuccess() {
254 throw new IllegalStateException("Not able to use CreateSdpObserver");
255 }
256
257
258 @Override
259 public void onSetFailure(String s) {
260 throw new IllegalStateException("Not able to use CreateSdpObserver");
261 }
262 }
263
264 public interface EventCallback {
265 void onIceCandidate(IceCandidate iceCandidate);
266 }
267}