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