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(final List<PeerConnection.IceServer> iceServers) throws InitializationException {
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        final PeerConnection peerConnection = peerConnectionFactory.createPeerConnection(iceServers, peerConnectionObserver);
197        if (peerConnection == null) {
198            throw new InitializationException("Unable to create PeerConnection");
199        }
200        peerConnection.addStream(stream);
201        peerConnection.setAudioPlayout(true);
202        peerConnection.setAudioRecording(true);
203        this.peerConnection = peerConnection;
204    }
205
206    public void closeOrThrow() {
207        requirePeerConnection().close();
208    }
209
210    public void close() {
211        final PeerConnection peerConnection = this.peerConnection;
212        if (peerConnection != null) {
213            peerConnection.close();
214        }
215    }
216
217
218    public ListenableFuture<SessionDescription> createOffer() {
219        return Futures.transformAsync(getPeerConnectionFuture(), peerConnection -> {
220            final SettableFuture<SessionDescription> future = SettableFuture.create();
221            peerConnection.createOffer(new CreateSdpObserver() {
222                @Override
223                public void onCreateSuccess(SessionDescription sessionDescription) {
224                    future.set(sessionDescription);
225                }
226
227                @Override
228                public void onCreateFailure(String s) {
229                    future.setException(new IllegalStateException("Unable to create offer: " + s));
230                }
231            }, new MediaConstraints());
232            return future;
233        }, MoreExecutors.directExecutor());
234    }
235
236    public ListenableFuture<SessionDescription> createAnswer() {
237        return Futures.transformAsync(getPeerConnectionFuture(), peerConnection -> {
238            final SettableFuture<SessionDescription> future = SettableFuture.create();
239            peerConnection.createAnswer(new CreateSdpObserver() {
240                @Override
241                public void onCreateSuccess(SessionDescription sessionDescription) {
242                    future.set(sessionDescription);
243                }
244
245                @Override
246                public void onCreateFailure(String s) {
247                    future.setException(new IllegalStateException("Unable to create answer: " + s));
248                }
249            }, new MediaConstraints());
250            return future;
251        }, MoreExecutors.directExecutor());
252    }
253
254    public ListenableFuture<Void> setLocalDescription(final SessionDescription sessionDescription) {
255        return Futures.transformAsync(getPeerConnectionFuture(), peerConnection -> {
256            final SettableFuture<Void> future = SettableFuture.create();
257            peerConnection.setLocalDescription(new SetSdpObserver() {
258                @Override
259                public void onSetSuccess() {
260                    future.set(null);
261                }
262
263                @Override
264                public void onSetFailure(String s) {
265                    future.setException(new IllegalArgumentException("unable to set local session description: " + s));
266
267                }
268            }, sessionDescription);
269            return future;
270        }, MoreExecutors.directExecutor());
271    }
272
273    public ListenableFuture<Void> setRemoteDescription(final SessionDescription sessionDescription) {
274        return Futures.transformAsync(getPeerConnectionFuture(), peerConnection -> {
275            final SettableFuture<Void> future = SettableFuture.create();
276            peerConnection.setRemoteDescription(new SetSdpObserver() {
277                @Override
278                public void onSetSuccess() {
279                    future.set(null);
280                }
281
282                @Override
283                public void onSetFailure(String s) {
284                    future.setException(new IllegalArgumentException("unable to set remote session description: " + s));
285
286                }
287            }, sessionDescription);
288            return future;
289        }, MoreExecutors.directExecutor());
290    }
291
292    @Nonnull
293    private ListenableFuture<PeerConnection> getPeerConnectionFuture() {
294        final PeerConnection peerConnection = this.peerConnection;
295        if (peerConnection == null) {
296            return Futures.immediateFailedFuture(new IllegalStateException("initialize PeerConnection first"));
297        } else {
298            return Futures.immediateFuture(peerConnection);
299        }
300    }
301
302    public void addIceCandidate(IceCandidate iceCandidate) {
303        requirePeerConnection().addIceCandidate(iceCandidate);
304    }
305
306    public PeerConnection.PeerConnectionState getState() {
307        return requirePeerConnection().connectionState();
308    }
309
310    private PeerConnection requirePeerConnection() {
311        final PeerConnection peerConnection = this.peerConnection;
312        if (peerConnection == null) {
313            throw new IllegalStateException("initialize PeerConnection first");
314        }
315        return peerConnection;
316    }
317
318    private static abstract class SetSdpObserver implements SdpObserver {
319
320        @Override
321        public void onCreateSuccess(org.webrtc.SessionDescription sessionDescription) {
322            throw new IllegalStateException("Not able to use SetSdpObserver");
323        }
324
325        @Override
326        public void onCreateFailure(String s) {
327            throw new IllegalStateException("Not able to use SetSdpObserver");
328        }
329
330    }
331
332    private static abstract class CreateSdpObserver implements SdpObserver {
333
334
335        @Override
336        public void onSetSuccess() {
337            throw new IllegalStateException("Not able to use CreateSdpObserver");
338        }
339
340
341        @Override
342        public void onSetFailure(String s) {
343            throw new IllegalStateException("Not able to use CreateSdpObserver");
344        }
345    }
346
347    public static class InitializationException extends Exception {
348
349        private InitializationException(String message) {
350            super(message);
351        }
352    }
353
354    public interface EventCallback {
355        void onIceCandidate(IceCandidate iceCandidate);
356
357        void onConnectionChange(PeerConnection.PeerConnectionState newState);
358    }
359}