1package eu.siacs.conversations.xmpp.jingle;
2
3import android.content.Context;
4
5import com.google.common.collect.ImmutableList;
6import com.google.common.util.concurrent.Futures;
7import com.google.common.util.concurrent.ListenableFuture;
8import com.google.common.util.concurrent.MoreExecutors;
9import com.google.common.util.concurrent.SettableFuture;
10
11import org.webrtc.AudioSource;
12import org.webrtc.AudioTrack;
13import org.webrtc.DataChannel;
14import org.webrtc.IceCandidate;
15import org.webrtc.MediaConstraints;
16import org.webrtc.MediaStream;
17import org.webrtc.PeerConnection;
18import org.webrtc.PeerConnectionFactory;
19import org.webrtc.RtpReceiver;
20import org.webrtc.SdpObserver;
21import org.webrtc.SessionDescription;
22
23import java.util.List;
24
25import javax.annotation.Nonnull;
26import javax.annotation.Nullable;
27
28public class WebRTCWrapper {
29
30 private final EventCallback eventCallback;
31
32 private final PeerConnection.Observer peerConnectionObserver = new PeerConnection.Observer() {
33 @Override
34 public void onSignalingChange(PeerConnection.SignalingState signalingState) {
35
36 }
37
38 @Override
39 public void onIceConnectionChange(PeerConnection.IceConnectionState iceConnectionState) {
40
41 }
42
43 @Override
44 public void onIceConnectionReceivingChange(boolean b) {
45
46 }
47
48 @Override
49 public void onIceGatheringChange(PeerConnection.IceGatheringState iceGatheringState) {
50
51 }
52
53 @Override
54 public void onIceCandidate(IceCandidate iceCandidate) {
55 eventCallback.onIceCandidate(iceCandidate);
56 }
57
58 @Override
59 public void onIceCandidatesRemoved(IceCandidate[] iceCandidates) {
60
61 }
62
63 @Override
64 public void onAddStream(MediaStream mediaStream) {
65
66 }
67
68 @Override
69 public void onRemoveStream(MediaStream mediaStream) {
70
71 }
72
73 @Override
74 public void onDataChannel(DataChannel dataChannel) {
75
76 }
77
78 @Override
79 public void onRenegotiationNeeded() {
80
81 }
82
83 @Override
84 public void onAddTrack(RtpReceiver rtpReceiver, MediaStream[] mediaStreams) {
85
86 }
87 };
88 @Nullable
89 private PeerConnection peerConnection = null;
90
91 public WebRTCWrapper(final EventCallback eventCallback) {
92 this.eventCallback = eventCallback;
93 }
94
95 public void setup(final Context context) {
96 PeerConnectionFactory.initialize(
97 PeerConnectionFactory.InitializationOptions.builder(context).createInitializationOptions()
98 );
99 }
100
101 public void initializePeerConnection() {
102 final PeerConnectionFactory.Options options = new PeerConnectionFactory.Options();
103 PeerConnectionFactory peerConnectionFactory = PeerConnectionFactory.builder().createPeerConnectionFactory();
104
105 final AudioSource audioSource = peerConnectionFactory.createAudioSource(new MediaConstraints());
106
107 final AudioTrack audioTrack = peerConnectionFactory.createAudioTrack("my-audio-track", audioSource);
108 final MediaStream stream = peerConnectionFactory.createLocalMediaStream("my-media-stream");
109 stream.addTrack(audioTrack);
110
111
112 final List<PeerConnection.IceServer> iceServers = ImmutableList.of(
113 PeerConnection.IceServer.builder("stun:xmpp.conversations.im:3478").createIceServer()
114 );
115 final PeerConnection peerConnection = peerConnectionFactory.createPeerConnection(iceServers, peerConnectionObserver);
116 if (peerConnection == null) {
117 throw new IllegalStateException("Unable to create PeerConnection");
118 }
119 peerConnection.addStream(stream);
120 this.peerConnection = peerConnection;
121 }
122
123 public ListenableFuture<SessionDescription> createOffer() {
124 return Futures.transformAsync(getPeerConnectionFuture(), peerConnection -> {
125 final SettableFuture<SessionDescription> future = SettableFuture.create();
126 peerConnection.createOffer(new CreateSdpObserver() {
127 @Override
128 public void onCreateSuccess(SessionDescription sessionDescription) {
129 future.set(sessionDescription);
130 }
131
132 @Override
133 public void onCreateFailure(String s) {
134 future.setException(new IllegalStateException("Unable to create offer: " + s));
135 }
136 }, new MediaConstraints());
137 return future;
138 }, MoreExecutors.directExecutor());
139 }
140
141 public ListenableFuture<SessionDescription> createAnswer() {
142 return Futures.transformAsync(getPeerConnectionFuture(), peerConnection -> {
143 final SettableFuture<SessionDescription> future = SettableFuture.create();
144 peerConnection.createAnswer(new CreateSdpObserver() {
145 @Override
146 public void onCreateSuccess(SessionDescription sessionDescription) {
147 future.set(sessionDescription);
148 }
149
150 @Override
151 public void onCreateFailure(String s) {
152 future.setException(new IllegalStateException("Unable to create answer: " + s));
153 }
154 }, new MediaConstraints());
155 return future;
156 }, MoreExecutors.directExecutor());
157 }
158
159 public ListenableFuture<Void> setLocalDescription(final SessionDescription sessionDescription) {
160 return Futures.transformAsync(getPeerConnectionFuture(), peerConnection -> {
161 final SettableFuture<Void> future = SettableFuture.create();
162 peerConnection.setLocalDescription(new SetSdpObserver() {
163 @Override
164 public void onSetSuccess() {
165 future.set(null);
166 }
167
168 @Override
169 public void onSetFailure(String s) {
170 future.setException(new IllegalArgumentException("unable to set local session description: "+s));
171
172 }
173 }, sessionDescription);
174 return future;
175 }, MoreExecutors.directExecutor());
176 }
177
178 public ListenableFuture<Void> setRemoteDescription(final SessionDescription sessionDescription) {
179 return Futures.transformAsync(getPeerConnectionFuture(), peerConnection -> {
180 final SettableFuture<Void> future = SettableFuture.create();
181 peerConnection.setRemoteDescription(new SetSdpObserver() {
182 @Override
183 public void onSetSuccess() {
184 future.set(null);
185 }
186
187 @Override
188 public void onSetFailure(String s) {
189 future.setException(new IllegalArgumentException("unable to set remote session description: "+s));
190
191 }
192 }, sessionDescription);
193 return future;
194 }, MoreExecutors.directExecutor());
195 }
196
197 @Nonnull
198 private ListenableFuture<PeerConnection> getPeerConnectionFuture() {
199 final PeerConnection peerConnection = this.peerConnection;
200 if (peerConnection == null) {
201 return Futures.immediateFailedFuture(new IllegalStateException("initialize PeerConnection first"));
202 } else {
203 return Futures.immediateFuture(peerConnection);
204 }
205 }
206
207 public void addIceCandidate(IceCandidate iceCandidate) {
208 final PeerConnection peerConnection = this.peerConnection;
209 if (peerConnection == null) {
210 throw new IllegalStateException("initialize PeerConnection first");
211 }
212 peerConnection.addIceCandidate(iceCandidate);
213 }
214
215 private static abstract class SetSdpObserver implements SdpObserver {
216
217 @Override
218 public void onCreateSuccess(org.webrtc.SessionDescription sessionDescription) {
219 throw new IllegalStateException("Not able to use SetSdpObserver");
220 }
221
222 @Override
223 public void onCreateFailure(String s) {
224 throw new IllegalStateException("Not able to use SetSdpObserver");
225 }
226
227 }
228
229 private static abstract class CreateSdpObserver implements SdpObserver {
230
231
232 @Override
233 public void onSetSuccess() {
234 throw new IllegalStateException("Not able to use CreateSdpObserver");
235 }
236
237
238 @Override
239 public void onSetFailure(String s) {
240 throw new IllegalStateException("Not able to use CreateSdpObserver");
241 }
242 }
243
244 public interface EventCallback {
245 void onIceCandidate(IceCandidate iceCandidate);
246 }
247}