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