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.Camera1Capturer;
 15import org.webrtc.Camera1Enumerator;
 16import org.webrtc.CameraVideoCapturer;
 17import org.webrtc.CandidatePairChangeEvent;
 18import org.webrtc.DataChannel;
 19import org.webrtc.IceCandidate;
 20import org.webrtc.MediaConstraints;
 21import org.webrtc.MediaStream;
 22import org.webrtc.PeerConnection;
 23import org.webrtc.PeerConnectionFactory;
 24import org.webrtc.RtpReceiver;
 25import org.webrtc.SdpObserver;
 26import org.webrtc.SessionDescription;
 27import org.webrtc.VideoCapturer;
 28import org.webrtc.VideoSource;
 29import org.webrtc.VideoTrack;
 30
 31import java.util.List;
 32
 33import javax.annotation.Nonnull;
 34import javax.annotation.Nullable;
 35
 36import eu.siacs.conversations.Config;
 37
 38public class WebRTCWrapper {
 39
 40    private VideoTrack localVideoTrack = null;
 41    private VideoTrack remoteVideoTrack = null;
 42
 43    private final EventCallback eventCallback;
 44
 45    private final PeerConnection.Observer peerConnectionObserver = new PeerConnection.Observer() {
 46        @Override
 47        public void onSignalingChange(PeerConnection.SignalingState signalingState) {
 48            Log.d(Config.LOGTAG, "onSignalingChange(" + signalingState + ")");
 49
 50        }
 51
 52        @Override
 53        public void onConnectionChange(PeerConnection.PeerConnectionState newState) {
 54            eventCallback.onConnectionChange(newState);
 55        }
 56
 57        @Override
 58        public void onIceConnectionChange(PeerConnection.IceConnectionState iceConnectionState) {
 59
 60        }
 61
 62        @Override
 63        public void onSelectedCandidatePairChanged(CandidatePairChangeEvent event) {
 64            Log.d(Config.LOGTAG, "remote candidate selected: " + event.remote);
 65            Log.d(Config.LOGTAG, "local candidate selected: " + event.local);
 66        }
 67
 68        @Override
 69        public void onIceConnectionReceivingChange(boolean b) {
 70
 71        }
 72
 73        @Override
 74        public void onIceGatheringChange(PeerConnection.IceGatheringState iceGatheringState) {
 75
 76        }
 77
 78        @Override
 79        public void onIceCandidate(IceCandidate iceCandidate) {
 80            eventCallback.onIceCandidate(iceCandidate);
 81        }
 82
 83        @Override
 84        public void onIceCandidatesRemoved(IceCandidate[] iceCandidates) {
 85
 86        }
 87
 88        @Override
 89        public void onAddStream(MediaStream mediaStream) {
 90            Log.d(Config.LOGTAG, "onAddStream");
 91            for (AudioTrack audioTrack : mediaStream.audioTracks) {
 92                Log.d(Config.LOGTAG, "remote? - audioTrack enabled:" + audioTrack.enabled() + " state=" + audioTrack.state());
 93            }
 94            final List<VideoTrack> videoTracks = mediaStream.videoTracks;
 95            if (videoTracks.size() > 0) {
 96                Log.d(Config.LOGTAG, "more than zero remote video tracks found. using first");
 97                remoteVideoTrack = videoTracks.get(0);
 98            }
 99        }
100
101        @Override
102        public void onRemoveStream(MediaStream mediaStream) {
103
104        }
105
106        @Override
107        public void onDataChannel(DataChannel dataChannel) {
108
109        }
110
111        @Override
112        public void onRenegotiationNeeded() {
113
114        }
115
116        @Override
117        public void onAddTrack(RtpReceiver rtpReceiver, MediaStream[] mediaStreams) {
118            Log.d(Config.LOGTAG, "onAddTrack()");
119
120        }
121    };
122    @Nullable
123    private PeerConnection peerConnection = null;
124
125    public WebRTCWrapper(final EventCallback eventCallback) {
126        this.eventCallback = eventCallback;
127    }
128
129    public void setup(final Context context) {
130        PeerConnectionFactory.initialize(
131                PeerConnectionFactory.InitializationOptions.builder(context).createInitializationOptions()
132        );
133    }
134
135    public void initializePeerConnection() {
136        PeerConnectionFactory peerConnectionFactory = PeerConnectionFactory.builder().createPeerConnectionFactory();
137
138        CameraVideoCapturer capturer = null;
139        Camera1Enumerator camera1Enumerator = new Camera1Enumerator();
140        for (String deviceName : camera1Enumerator.getDeviceNames()) {
141            Log.d(Config.LOGTAG, "camera device name: " + deviceName);
142            if (camera1Enumerator.isFrontFacing(deviceName)) {
143                capturer = camera1Enumerator.createCapturer(deviceName, new CameraVideoCapturer.CameraEventsHandler() {
144                    @Override
145                    public void onCameraError(String s) {
146
147                    }
148
149                    @Override
150                    public void onCameraDisconnected() {
151
152                    }
153
154                    @Override
155                    public void onCameraFreezed(String s) {
156
157                    }
158
159                    @Override
160                    public void onCameraOpening(String s) {
161                        Log.d(Config.LOGTAG, "onCameraOpening");
162                    }
163
164                    @Override
165                    public void onFirstFrameAvailable() {
166                        Log.d(Config.LOGTAG, "onFirstFrameAvailable");
167                    }
168
169                    @Override
170                    public void onCameraClosed() {
171
172                    }
173                });
174            }
175        }
176
177        /*if (capturer != null) {
178            capturer.initialize();
179            Log.d(Config.LOGTAG,"start capturing");
180            capturer.startCapture(800,600,30);
181        }*/
182
183        final VideoSource videoSource = peerConnectionFactory.createVideoSource(false);
184        final VideoTrack videoTrack = peerConnectionFactory.createVideoTrack("my-video-track", videoSource);
185
186        final AudioSource audioSource = peerConnectionFactory.createAudioSource(new MediaConstraints());
187
188        final AudioTrack audioTrack = peerConnectionFactory.createAudioTrack("my-audio-track", audioSource);
189        Log.d(Config.LOGTAG, "audioTrack enabled:" + audioTrack.enabled() + " state=" + audioTrack.state());
190        final MediaStream stream = peerConnectionFactory.createLocalMediaStream("my-media-stream");
191        stream.addTrack(audioTrack);
192        //stream.addTrack(videoTrack);
193
194        this.localVideoTrack = videoTrack;
195
196
197        final List<PeerConnection.IceServer> iceServers = ImmutableList.of(
198                PeerConnection.IceServer.builder("stun:xmpp.conversations.im:3478").createIceServer()
199        );
200        final PeerConnection peerConnection = peerConnectionFactory.createPeerConnection(iceServers, peerConnectionObserver);
201        if (peerConnection == null) {
202            throw new IllegalStateException("Unable to create PeerConnection");
203        }
204        peerConnection.addStream(stream);
205        peerConnection.setAudioPlayout(true);
206        peerConnection.setAudioRecording(true);
207        this.peerConnection = peerConnection;
208    }
209
210    public void close() {
211        requirePeerConnection().close();
212    }
213
214
215    public ListenableFuture<SessionDescription> createOffer() {
216        return Futures.transformAsync(getPeerConnectionFuture(), peerConnection -> {
217            final SettableFuture<SessionDescription> future = SettableFuture.create();
218            peerConnection.createOffer(new CreateSdpObserver() {
219                @Override
220                public void onCreateSuccess(SessionDescription sessionDescription) {
221                    future.set(sessionDescription);
222                }
223
224                @Override
225                public void onCreateFailure(String s) {
226                    future.setException(new IllegalStateException("Unable to create offer: " + s));
227                }
228            }, new MediaConstraints());
229            return future;
230        }, MoreExecutors.directExecutor());
231    }
232
233    public ListenableFuture<SessionDescription> createAnswer() {
234        return Futures.transformAsync(getPeerConnectionFuture(), peerConnection -> {
235            final SettableFuture<SessionDescription> future = SettableFuture.create();
236            peerConnection.createAnswer(new CreateSdpObserver() {
237                @Override
238                public void onCreateSuccess(SessionDescription sessionDescription) {
239                    future.set(sessionDescription);
240                }
241
242                @Override
243                public void onCreateFailure(String s) {
244                    future.setException(new IllegalStateException("Unable to create answer: " + s));
245                }
246            }, new MediaConstraints());
247            return future;
248        }, MoreExecutors.directExecutor());
249    }
250
251    public ListenableFuture<Void> setLocalDescription(final SessionDescription sessionDescription) {
252        return Futures.transformAsync(getPeerConnectionFuture(), peerConnection -> {
253            final SettableFuture<Void> future = SettableFuture.create();
254            peerConnection.setLocalDescription(new SetSdpObserver() {
255                @Override
256                public void onSetSuccess() {
257                    future.set(null);
258                }
259
260                @Override
261                public void onSetFailure(String s) {
262                    future.setException(new IllegalArgumentException("unable to set local session description: " + s));
263
264                }
265            }, sessionDescription);
266            return future;
267        }, MoreExecutors.directExecutor());
268    }
269
270    public ListenableFuture<Void> setRemoteDescription(final SessionDescription sessionDescription) {
271        return Futures.transformAsync(getPeerConnectionFuture(), peerConnection -> {
272            final SettableFuture<Void> future = SettableFuture.create();
273            peerConnection.setRemoteDescription(new SetSdpObserver() {
274                @Override
275                public void onSetSuccess() {
276                    future.set(null);
277                }
278
279                @Override
280                public void onSetFailure(String s) {
281                    future.setException(new IllegalArgumentException("unable to set remote session description: " + s));
282
283                }
284            }, sessionDescription);
285            return future;
286        }, MoreExecutors.directExecutor());
287    }
288
289    @Nonnull
290    private ListenableFuture<PeerConnection> getPeerConnectionFuture() {
291        final PeerConnection peerConnection = this.peerConnection;
292        if (peerConnection == null) {
293            return Futures.immediateFailedFuture(new IllegalStateException("initialize PeerConnection first"));
294        } else {
295            return Futures.immediateFuture(peerConnection);
296        }
297    }
298
299    public void addIceCandidate(IceCandidate iceCandidate) {
300        requirePeerConnection().addIceCandidate(iceCandidate);
301    }
302
303    public PeerConnection.PeerConnectionState getState() {
304        return requirePeerConnection().connectionState();
305    }
306
307    private PeerConnection requirePeerConnection() {
308        final PeerConnection peerConnection = this.peerConnection;
309        if (peerConnection == null) {
310            throw new IllegalStateException("initialize PeerConnection first");
311        }
312        return peerConnection;
313    }
314
315    private static abstract class SetSdpObserver implements SdpObserver {
316
317        @Override
318        public void onCreateSuccess(org.webrtc.SessionDescription sessionDescription) {
319            throw new IllegalStateException("Not able to use SetSdpObserver");
320        }
321
322        @Override
323        public void onCreateFailure(String s) {
324            throw new IllegalStateException("Not able to use SetSdpObserver");
325        }
326
327    }
328
329    private static abstract class CreateSdpObserver implements SdpObserver {
330
331
332        @Override
333        public void onSetSuccess() {
334            throw new IllegalStateException("Not able to use CreateSdpObserver");
335        }
336
337
338        @Override
339        public void onSetFailure(String s) {
340            throw new IllegalStateException("Not able to use CreateSdpObserver");
341        }
342    }
343
344    public interface EventCallback {
345        void onIceCandidate(IceCandidate iceCandidate);
346
347        void onConnectionChange(PeerConnection.PeerConnectionState newState);
348    }
349}