WebRTCWrapper.java

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