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