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 close() {
211 requirePeerConnection().close();
212 }
213
214
215 public ListenableFuture<SessionDescription> createOffer() {
216 return Futures.transformAsync(getPeerConnectionFuture(), peerConnection -> {
217 final SettableFuture<SessionDescription> future = SettableFuture.create();
218 peerConnection.createOffer(new CreateSdpObserver() {
219 @Override
220 public void onCreateSuccess(SessionDescription sessionDescription) {
221 future.set(sessionDescription);
222 }
223
224 @Override
225 public void onCreateFailure(String s) {
226 future.setException(new IllegalStateException("Unable to create offer: " + s));
227 }
228 }, new MediaConstraints());
229 return future;
230 }, MoreExecutors.directExecutor());
231 }
232
233 public ListenableFuture<SessionDescription> createAnswer() {
234 return Futures.transformAsync(getPeerConnectionFuture(), peerConnection -> {
235 final SettableFuture<SessionDescription> future = SettableFuture.create();
236 peerConnection.createAnswer(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 answer: " + s));
245 }
246 }, new MediaConstraints());
247 return future;
248 }, MoreExecutors.directExecutor());
249 }
250
251 public ListenableFuture<Void> setLocalDescription(final SessionDescription sessionDescription) {
252 return Futures.transformAsync(getPeerConnectionFuture(), peerConnection -> {
253 final SettableFuture<Void> future = SettableFuture.create();
254 peerConnection.setLocalDescription(new SetSdpObserver() {
255 @Override
256 public void onSetSuccess() {
257 future.set(null);
258 }
259
260 @Override
261 public void onSetFailure(String s) {
262 future.setException(new IllegalArgumentException("unable to set local session description: " + s));
263
264 }
265 }, sessionDescription);
266 return future;
267 }, MoreExecutors.directExecutor());
268 }
269
270 public ListenableFuture<Void> setRemoteDescription(final SessionDescription sessionDescription) {
271 return Futures.transformAsync(getPeerConnectionFuture(), peerConnection -> {
272 final SettableFuture<Void> future = SettableFuture.create();
273 peerConnection.setRemoteDescription(new SetSdpObserver() {
274 @Override
275 public void onSetSuccess() {
276 future.set(null);
277 }
278
279 @Override
280 public void onSetFailure(String s) {
281 future.setException(new IllegalArgumentException("unable to set remote session description: " + s));
282
283 }
284 }, sessionDescription);
285 return future;
286 }, MoreExecutors.directExecutor());
287 }
288
289 @Nonnull
290 private ListenableFuture<PeerConnection> getPeerConnectionFuture() {
291 final PeerConnection peerConnection = this.peerConnection;
292 if (peerConnection == null) {
293 return Futures.immediateFailedFuture(new IllegalStateException("initialize PeerConnection first"));
294 } else {
295 return Futures.immediateFuture(peerConnection);
296 }
297 }
298
299 public void addIceCandidate(IceCandidate iceCandidate) {
300 requirePeerConnection().addIceCandidate(iceCandidate);
301 }
302
303 public PeerConnection.PeerConnectionState getState() {
304 return requirePeerConnection().connectionState();
305 }
306
307 private PeerConnection requirePeerConnection() {
308 final PeerConnection peerConnection = this.peerConnection;
309 if (peerConnection == null) {
310 throw new IllegalStateException("initialize PeerConnection first");
311 }
312 return peerConnection;
313 }
314
315 private static abstract class SetSdpObserver implements SdpObserver {
316
317 @Override
318 public void onCreateSuccess(org.webrtc.SessionDescription sessionDescription) {
319 throw new IllegalStateException("Not able to use SetSdpObserver");
320 }
321
322 @Override
323 public void onCreateFailure(String s) {
324 throw new IllegalStateException("Not able to use SetSdpObserver");
325 }
326
327 }
328
329 private static abstract class CreateSdpObserver implements SdpObserver {
330
331
332 @Override
333 public void onSetSuccess() {
334 throw new IllegalStateException("Not able to use CreateSdpObserver");
335 }
336
337
338 @Override
339 public void onSetFailure(String s) {
340 throw new IllegalStateException("Not able to use CreateSdpObserver");
341 }
342 }
343
344 public interface EventCallback {
345 void onIceCandidate(IceCandidate iceCandidate);
346
347 void onConnectionChange(PeerConnection.PeerConnectionState newState);
348 }
349}