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