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 AudioTrack localAudioTrack = null;
135    private AppRTCAudioManager appRTCAudioManager = null;
136    private final Handler mainHandler = new Handler(Looper.getMainLooper());
137
138    public WebRTCWrapper(final EventCallback eventCallback) {
139        this.eventCallback = eventCallback;
140    }
141
142    public void setup(final Context context) {
143        PeerConnectionFactory.initialize(
144                PeerConnectionFactory.InitializationOptions.builder(context).createInitializationOptions()
145        );
146        mainHandler.post(() -> {
147            appRTCAudioManager = AppRTCAudioManager.create(context, AppRTCAudioManager.SpeakerPhonePreference.EARPIECE);
148            appRTCAudioManager.start(audioManagerEvents);
149            eventCallback.onAudioDeviceChanged(appRTCAudioManager.getSelectedAudioDevice(), appRTCAudioManager.getAudioDevices());
150        });
151    }
152
153    public void initializePeerConnection(final List<PeerConnection.IceServer> iceServers) throws InitializationException {
154        PeerConnectionFactory peerConnectionFactory = PeerConnectionFactory.builder().createPeerConnectionFactory();
155
156        CameraVideoCapturer capturer = null;
157        Camera1Enumerator camera1Enumerator = new Camera1Enumerator();
158        for (String deviceName : camera1Enumerator.getDeviceNames()) {
159            Log.d(Config.LOGTAG, "camera device name: " + deviceName);
160            if (camera1Enumerator.isFrontFacing(deviceName)) {
161                capturer = camera1Enumerator.createCapturer(deviceName, new CameraVideoCapturer.CameraEventsHandler() {
162                    @Override
163                    public void onCameraError(String s) {
164
165                    }
166
167                    @Override
168                    public void onCameraDisconnected() {
169
170                    }
171
172                    @Override
173                    public void onCameraFreezed(String s) {
174
175                    }
176
177                    @Override
178                    public void onCameraOpening(String s) {
179                        Log.d(Config.LOGTAG, "onCameraOpening");
180                    }
181
182                    @Override
183                    public void onFirstFrameAvailable() {
184                        Log.d(Config.LOGTAG, "onFirstFrameAvailable");
185                    }
186
187                    @Override
188                    public void onCameraClosed() {
189
190                    }
191                });
192            }
193        }
194
195        /*if (capturer != null) {
196            capturer.initialize();
197            Log.d(Config.LOGTAG,"start capturing");
198            capturer.startCapture(800,600,30);
199        }*/
200
201        final VideoSource videoSource = peerConnectionFactory.createVideoSource(false);
202        final VideoTrack videoTrack = peerConnectionFactory.createVideoTrack("my-video-track", videoSource);
203
204        final AudioSource audioSource = peerConnectionFactory.createAudioSource(new MediaConstraints());
205
206        this.localAudioTrack = peerConnectionFactory.createAudioTrack("my-audio-track", audioSource);
207        final MediaStream stream = peerConnectionFactory.createLocalMediaStream("my-media-stream");
208        stream.addTrack(this.localAudioTrack);
209        //stream.addTrack(videoTrack);
210
211        this.localVideoTrack = videoTrack;
212
213        final PeerConnection peerConnection = peerConnectionFactory.createPeerConnection(iceServers, peerConnectionObserver);
214        if (peerConnection == null) {
215            throw new InitializationException("Unable to create PeerConnection");
216        }
217        peerConnection.addStream(stream);
218        peerConnection.setAudioPlayout(true);
219        peerConnection.setAudioRecording(true);
220        this.peerConnection = peerConnection;
221    }
222
223    public void close() {
224        final PeerConnection peerConnection = this.peerConnection;
225        if (peerConnection != null) {
226            peerConnection.close();
227        }
228        final AppRTCAudioManager audioManager = this.appRTCAudioManager;
229        if (audioManager != null) {
230            mainHandler.post(audioManager::stop);
231        }
232    }
233
234    public void setMicrophoneEnabled(final boolean enabled) {
235        final AudioTrack audioTrack = this.localAudioTrack;
236        if (audioTrack == null) {
237            throw new IllegalStateException("Local audio track does not exist (yet)");
238        }
239        audioTrack.setEnabled(enabled);
240    }
241
242    public boolean isMicrophoneEnabled() {
243        final AudioTrack audioTrack = this.localAudioTrack;
244        if (audioTrack == null) {
245            throw new IllegalStateException("Local audio track does not exist (yet)");
246        }
247        return audioTrack.enabled();
248    }
249
250
251    public ListenableFuture<SessionDescription> createOffer() {
252        return Futures.transformAsync(getPeerConnectionFuture(), peerConnection -> {
253            final SettableFuture<SessionDescription> future = SettableFuture.create();
254            peerConnection.createOffer(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 offer: " + s));
263                }
264            }, new MediaConstraints());
265            return future;
266        }, MoreExecutors.directExecutor());
267    }
268
269    public ListenableFuture<SessionDescription> createAnswer() {
270        return Futures.transformAsync(getPeerConnectionFuture(), peerConnection -> {
271            final SettableFuture<SessionDescription> future = SettableFuture.create();
272            peerConnection.createAnswer(new CreateSdpObserver() {
273                @Override
274                public void onCreateSuccess(SessionDescription sessionDescription) {
275                    future.set(sessionDescription);
276                }
277
278                @Override
279                public void onCreateFailure(String s) {
280                    future.setException(new IllegalStateException("Unable to create answer: " + s));
281                }
282            }, new MediaConstraints());
283            return future;
284        }, MoreExecutors.directExecutor());
285    }
286
287    public ListenableFuture<Void> setLocalDescription(final SessionDescription sessionDescription) {
288        return Futures.transformAsync(getPeerConnectionFuture(), peerConnection -> {
289            final SettableFuture<Void> future = SettableFuture.create();
290            peerConnection.setLocalDescription(new SetSdpObserver() {
291                @Override
292                public void onSetSuccess() {
293                    future.set(null);
294                }
295
296                @Override
297                public void onSetFailure(String s) {
298                    future.setException(new IllegalArgumentException("unable to set local session description: " + s));
299
300                }
301            }, sessionDescription);
302            return future;
303        }, MoreExecutors.directExecutor());
304    }
305
306    public ListenableFuture<Void> setRemoteDescription(final SessionDescription sessionDescription) {
307        return Futures.transformAsync(getPeerConnectionFuture(), peerConnection -> {
308            final SettableFuture<Void> future = SettableFuture.create();
309            peerConnection.setRemoteDescription(new SetSdpObserver() {
310                @Override
311                public void onSetSuccess() {
312                    future.set(null);
313                }
314
315                @Override
316                public void onSetFailure(String s) {
317                    future.setException(new IllegalArgumentException("unable to set remote session description: " + s));
318
319                }
320            }, sessionDescription);
321            return future;
322        }, MoreExecutors.directExecutor());
323    }
324
325    @Nonnull
326    private ListenableFuture<PeerConnection> getPeerConnectionFuture() {
327        final PeerConnection peerConnection = this.peerConnection;
328        if (peerConnection == null) {
329            return Futures.immediateFailedFuture(new IllegalStateException("initialize PeerConnection first"));
330        } else {
331            return Futures.immediateFuture(peerConnection);
332        }
333    }
334
335    public void addIceCandidate(IceCandidate iceCandidate) {
336        requirePeerConnection().addIceCandidate(iceCandidate);
337    }
338
339    public PeerConnection.PeerConnectionState getState() {
340        return requirePeerConnection().connectionState();
341    }
342
343    private PeerConnection requirePeerConnection() {
344        final PeerConnection peerConnection = this.peerConnection;
345        if (peerConnection == null) {
346            throw new IllegalStateException("initialize PeerConnection first");
347        }
348        return peerConnection;
349    }
350
351    public AppRTCAudioManager getAudioManager() {
352        return appRTCAudioManager;
353    }
354
355    private static abstract class SetSdpObserver implements SdpObserver {
356
357        @Override
358        public void onCreateSuccess(org.webrtc.SessionDescription sessionDescription) {
359            throw new IllegalStateException("Not able to use SetSdpObserver");
360        }
361
362        @Override
363        public void onCreateFailure(String s) {
364            throw new IllegalStateException("Not able to use SetSdpObserver");
365        }
366
367    }
368
369    private static abstract class CreateSdpObserver implements SdpObserver {
370
371
372        @Override
373        public void onSetSuccess() {
374            throw new IllegalStateException("Not able to use CreateSdpObserver");
375        }
376
377
378        @Override
379        public void onSetFailure(String s) {
380            throw new IllegalStateException("Not able to use CreateSdpObserver");
381        }
382    }
383
384    public static class InitializationException extends Exception {
385
386        private InitializationException(String message) {
387            super(message);
388        }
389    }
390
391    public interface EventCallback {
392        void onIceCandidate(IceCandidate iceCandidate);
393
394        void onConnectionChange(PeerConnection.PeerConnectionState newState);
395
396        void onAudioDeviceChanged(AppRTCAudioManager.AudioDevice selectedAudioDevice, Set<AppRTCAudioManager.AudioDevice> availableAudioDevices);
397    }
398}