WebRTCWrapper.java

  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}