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}