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}