WebRTCWrapper.java

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