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