/*
* TeleStax, Open Source Cloud Communications
* Copyright 2011-2015, Telestax Inc and individual contributors
* by the @authors tag.
*
* This program is free software: you can redistribute it and/or modify
* under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation; either version 3 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
* For questions related to commercial use licensing, please contact sales@telestax.com.
*
*/
package org.restcomm.android.sdk.SignalingClient;
import org.webrtc.IceCandidate;
import org.webrtc.PeerConnection;
import org.webrtc.SessionDescription;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class SignalingParameters {
private static final String TAG = "SignalingParameters";
public List<PeerConnection.IceServer> iceServers;
public final boolean initiator;
public final String clientId;
//public final String wssUrl;
public final String sipUrl;
public final String wssPostUrl;
public SessionDescription offerSdp;
public SessionDescription answerSdp;
public List<IceCandidate> iceCandidates;
public HashMap<String, String> sipHeaders;
public boolean videoEnabled;
//public List<IceCandidate> answerIceCandidates;
public SignalingParameters(
List<PeerConnection.IceServer> iceServers,
boolean initiator,
String clientId,
String sipUrl,
String wssPostUrl,
SessionDescription offerSdp,
List<IceCandidate> iceCandidates,
HashMap<String, String> sipHeaders,
boolean videoEnabled)
{
this.iceServers = iceServers;
this.initiator = initiator;
this.clientId = clientId;
this.sipUrl = sipUrl;
this.wssPostUrl = wssPostUrl;
this.offerSdp = offerSdp;
this.answerSdp = null;
this.iceCandidates = iceCandidates;
this.sipHeaders = sipHeaders;
this.videoEnabled = videoEnabled;
//this.answerIceCandidates = null;
}
public SignalingParameters()
{
this.iceServers = null;
this.initiator = false;
this.clientId = "";
this.sipUrl = "";
this.wssPostUrl = "";
this.offerSdp = null;
this.answerSdp = null;
this.iceCandidates = null;
this.sipHeaders = null;
this.videoEnabled = false;
//this.answerIceCandidates = null;
}
// combines offerSdp with iceCandidates and comes up with the full SDP
public String generateSipSdp(SessionDescription offerSdp, List<IceCandidate> iceCandidates)
{
// concatenate all candidates in one String
String audioCandidates = "";
String videoCandidates = "";
boolean isVideo = false;
for (IceCandidate candidate : iceCandidates) {
//Log.e(TAG, "@@@@ candidate.sdp: " + candidate.sdp);
// Remember that chrome distinguishes audio vs video with different names than FF.
// Chrome uses 'audio' while FF uses 'sdparta_0' for audio
// and chrome uses 'video' while FF uses 'sdparta_1' for video
if (candidate.sdpMid.equals("audio") || candidate.sdpMid.equals("sdparta_0")) {
audioCandidates += "a=" + candidate.sdp + "\r\n";
}
if (candidate.sdpMid.equals("video") || candidate.sdpMid.equals("sdparta_1")) {
videoCandidates += "a=" + candidate.sdp + "\r\n";
isVideo = true;
}
}
//Log.e(TAG, "@@@@ audio candidates: " + audioCandidates);
//Log.e(TAG, "@@@@ video candidates: " + videoCandidates);
//Log.e(TAG, "@@@@ Before replace: " + offerSdp.description);
// first, audio
// place the candidates after the 'a=rtcp:' string; use replace all because
// we are supporting both audio and video so more than one replacements will be made
//String resultString = offerSdp.description.replaceFirst("(a=rtcp:.*?\\r\\n)", "$1" + audioCandidates);
Matcher matcher = Pattern.compile("(a=rtcp:.*?\\r\\n)").matcher(offerSdp.description);
int index = 0;
StringBuffer stringBuffer = new StringBuffer();
while (matcher.find()) {
if (index == 0) {
// audio
matcher.appendReplacement(stringBuffer, "$1" + audioCandidates);
}
else {
// video
matcher.appendReplacement(stringBuffer, "$1" + videoCandidates);
}
index++;
}
matcher.appendTail(stringBuffer);
//Log.v(TAG, "@@@@ After replace: " + stringBuffer.toString());
return stringBuffer.toString();
}
// gets a full SDP and a. populates .iceCandidates with individual candidates, and
// b. removes the candidates from the SDP string and returns it as .offerSdp
public static SignalingParameters extractCandidates(SessionDescription sdp)
{
SignalingParameters params = new SignalingParameters();
params.iceCandidates = new LinkedList<IceCandidate>();
// first parse the candidates
// TODO: for video to work properly we need to do some more work to split the full SDP and differentiate candidates
// based on media type (i.e. audio vs. video)
//Matcher matcher = Pattern.compile("a=(candidate.*?)\\r\\n").matcher(sdp.description);
Matcher matcher = Pattern.compile("m=audio|m=video|a=(candidate.*)\\r\\n").matcher(sdp.description);
String collectionState = "none";
while (matcher.find()) {
if (matcher.group(0).equals("m=audio")) {
collectionState = "audio";
continue;
}
if (matcher.group(0).equals("m=video")) {
collectionState = "video";
continue;
}
IceCandidate iceCandidate = new IceCandidate(collectionState, 0, matcher.group(1));
params.iceCandidates.add(iceCandidate);
}
// remove candidates from SDP
params.offerSdp = new SessionDescription(sdp.type, sdp.description.replaceAll("a=candidate.*?\\r\\n", ""));
return params;
}
public void addIceCandidate(IceCandidate iceCandidate)
{
if (this.iceCandidates == null) {
this.iceCandidates = new LinkedList<IceCandidate>();
}
this.iceCandidates.add(iceCandidate);
}
public void addIceServer(PeerConnection.IceServer iceServer)
{
if (this.iceServers == null) {
this.iceServers = new LinkedList<PeerConnection.IceServer>();
}
this.iceServers.add(iceServer);
}
}