Commit 1c25ac16 authored by daryl herzmann's avatar daryl herzmann

Merge pull request #214 from deleolajide/ofmeet-ver-0.1.6

ofmeet plugin version 0.1.6
parents 9cc37be2 0d48af65
......@@ -49,9 +49,15 @@
Jingle Nodes Plugin Changelog
</h1>
<p><b>0.1.3</b> -- May 9th, 2015</p>
<ul>
<li>Replaced jingle nodes relay library jar file with latest source code</li>
<li>Added dependencies smack.jar and smackx.jar</li>
</ul>
<p><b>0.1.2</b> -- Apr 22, 2015</p>
<ul>
<li>Not an official ignite release - fixed the plugin to allow advanced configuration of ip addresses, port numbers, and servers. Previously the code could not work right behind a firewall and the test STUN server hardcoded into the file was not operational.</li>
<li>Fixed the plugin to allow advanced configuration of ip addresses, port numbers, and servers. Previously the code could not work right behind a firewall and the test STUN server hardcoded into the file was not operational.</li>
</ul>
<p><b>0.1.1</b> -- Jan 16, 2015</p>
......
......@@ -3,10 +3,10 @@
<plugin>
<class>org.jinglenodes.JingleNodesPlugin</class>
<name>Jingle Nodes Plugin</name>
<description>Provides support for Jingle Nodes. This version is a custom modified version of 1.1.1 with a more advanced admin page exposing more properties and the ability to work behind a firewall.</description>
<description>Provides support for Jingle Nodes</description>
<author>Jingle Nodes (Rodrigo Martins)</author>
<version>0.1.2</version>
<date>04/22/2015</date>
<version>0.1.3</version>
<date>05/09/2015</date>
<minServerVersion>3.9.0</minServerVersion>
<adminconsole>
......
......@@ -43,12 +43,12 @@ public class JingleNodesPlugin implements Plugin {
private static final Logger Log = LoggerFactory.getLogger(JingleNodesPlugin.class);
public static final String JN_LOCAL_IP_PROPERTY = "jinglenodes.localIP";
public static final String JN_PUBLIC_IP_PROPERTY = "jinglenodes.publicIP";
public static final String JN_MIN_PORT_PROPERTY = "jinglenodes.minPort";
public static final String JN_MAX_PORT_PROPERTY = "jinglenodes.maxPort";
public static final String JN_TEST_STUN_SERVER_PROPERTY = "jinglenodes.testSTUNServer";
public static final String JN_TEST_STUN_PORT_PROPERTY = "jinglenodes.testSTUNPort";
public static final String JN_LOCAL_IP_PROPERTY = "jinglenodes.localip";
public static final String JN_PUBLIC_IP_PROPERTY = "jinglenodes.publicip";
public static final String JN_MIN_PORT_PROPERTY = "jinglenodes.minport";
public static final String JN_MAX_PORT_PROPERTY = "jinglenodes.maxport";
public static final String JN_TEST_STUN_SERVER_PROPERTY = "jinglenodes.teststunserver";
public static final String JN_TEST_STUN_PORT_PROPERTY = "jinglenodes.teststunport";
private ComponentManager componentManager;
......
package org.xmpp.jnodes;
import org.xmpp.jnodes.nio.DatagramListener;
import org.xmpp.jnodes.nio.SelDatagramChannel;
import java.io.IOException;
import java.net.BindException;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.nio.ByteBuffer;
public class RelayChannel {
private final SelDatagramChannel channelA;
private final SelDatagramChannel channelB;
private final SocketAddress addressA;
private final SocketAddress addressB;
private SocketAddress lastReceivedA;
private SocketAddress lastReceivedB;
private final SelDatagramChannel channelA_;
private final SelDatagramChannel channelB_;
private SocketAddress lastReceivedA_;
private SocketAddress lastReceivedB_;
private long lastReceivedTimeA;
private long lastReceivedTimeB;
private final int portA;
private final int portB;
private final String ip;
private Object attachment;
public static RelayChannel createLocalRelayChannel(final String host, final int minPort, final int maxPort) throws IOException {
int range = maxPort - minPort;
IOException be = null;
for (int t = 0; t < 50; t++) {
try {
int a = Math.round((int) (Math.random() * range)) + minPort;
a = a % 2 == 0 ? a : a + 1;
return new RelayChannel(host, a);
} catch (BindException e) {
be = e;
} catch (IOException e) {
be = e;
}
}
throw be;
}
public RelayChannel(final String host, final int portA) throws IOException {
final int portB = portA + 2;
addressA = new InetSocketAddress(host, portA);
addressB = new InetSocketAddress(host, portB);
channelA = SelDatagramChannel.open(null, addressA);
channelB = SelDatagramChannel.open(null, addressB);
channelA.setDatagramListener(new DatagramListener() {
public void datagramReceived(final SelDatagramChannel channel, final ByteBuffer buffer, final SocketAddress address) {
lastReceivedA = address;
lastReceivedTimeA = System.currentTimeMillis();
if (lastReceivedB != null) {
try {
buffer.flip();
channelB.send(buffer, lastReceivedB);
} catch (IOException e) {
e.printStackTrace();
}
}
}
});
channelB.setDatagramListener(new DatagramListener() {
public void datagramReceived(final SelDatagramChannel channel, final ByteBuffer buffer, final SocketAddress address) {
lastReceivedB = address;
lastReceivedTimeB = System.currentTimeMillis();
if (lastReceivedA != null) {
try {
buffer.flip();
channelA.send(buffer, lastReceivedA);
} catch (IOException e) {
e.printStackTrace();
}
}
}
});
this.portA = portA;
this.portB = portB;
// RTCP Support
SocketAddress addressA_ = new InetSocketAddress(host, portA + 1);
SocketAddress addressB_ = new InetSocketAddress(host, portB + 1);
channelA_ = SelDatagramChannel.open(null, addressA_);
channelB_ = SelDatagramChannel.open(null, addressB_);
channelA_.setDatagramListener(new DatagramListener() {
public void datagramReceived(final SelDatagramChannel channel, final ByteBuffer buffer, final SocketAddress address) {
lastReceivedA_ = address;
if (lastReceivedB_ != null) {
try {
buffer.flip();
channelB_.send(buffer, lastReceivedB_);
} catch (IOException e) {
e.printStackTrace();
}
}
}
});
channelB_.setDatagramListener(new DatagramListener() {
public void datagramReceived(final SelDatagramChannel channel, final ByteBuffer buffer, final SocketAddress address) {
lastReceivedB_ = address;
if (lastReceivedA_ != null) {
try {
buffer.flip();
channelA_.send(buffer, lastReceivedA_);
} catch (IOException e) {
e.printStackTrace();
}
}
}
});
this.ip = host;
}
public SocketAddress getAddressB() {
return addressB;
}
public SocketAddress getAddressA() {
return addressA;
}
public int getPortA() {
return portA;
}
public int getPortB() {
return portB;
}
public String getIp() {
return ip;
}
public long getLastReceivedTimeA() {
return lastReceivedTimeA;
}
public long getLastReceivedTimeB() {
return lastReceivedTimeB;
}
public Object getAttachment() {
return attachment;
}
public void setAttachment(Object attachment) {
this.attachment = attachment;
}
public void close() {
try {
channelA.close();
} catch (IOException e) {
e.printStackTrace();
}
try {
channelB.close();
} catch (IOException e) {
e.printStackTrace();
}
try {
channelA_.close();
} catch (IOException e) {
e.printStackTrace();
}
try {
channelB_.close();
} catch (IOException e) {
e.printStackTrace();
}
}
public SelDatagramChannel getChannelA() {
return channelA;
}
public SelDatagramChannel getChannelB() {
return channelB;
}
public SelDatagramChannel getChannelA_() {
return channelA_;
}
public SelDatagramChannel getChannelB_() {
return channelB_;
}
}
package org.xmpp.jnodes;
import org.xmpp.jnodes.nio.PublicIPResolver;
import java.net.InetSocketAddress;
public class RelayPublicMask {
private final RelayChannel channel;
private InetSocketAddress addressA, addressB, addressA_, addressB_;
public RelayPublicMask(final RelayChannel channel) {
this.channel = channel;
}
public void discover(final String stunServer, final int port) {
addressA = PublicIPResolver.getPublicAddress(channel.getChannelA(), stunServer, port);
addressA_ = PublicIPResolver.getPublicAddress(channel.getChannelA_(), stunServer, port);
addressB = PublicIPResolver.getPublicAddress(channel.getChannelB(), stunServer, port);
addressB_ = PublicIPResolver.getPublicAddress(channel.getChannelB_(), stunServer, port);
}
public InetSocketAddress getAddressA() {
return addressA;
}
public InetSocketAddress getAddressB() {
return addressB;
}
public InetSocketAddress getAddressA_() {
return addressA_;
}
public InetSocketAddress getAddressB_() {
return addressB_;
}
}
package org.xmpp.jnodes.nio;
import java.net.SocketAddress;
import java.nio.ByteBuffer;
public interface DatagramListener {
public void datagramReceived(SelDatagramChannel channel, ByteBuffer buffer, SocketAddress address);
}
package org.xmpp.jnodes.nio;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.util.Enumeration;
public class LocalIPResolver {
private static String overrideIp;
public static String getLocalIP() {
if (overrideIp != null && overrideIp.length() >= 7) {
return overrideIp;
}
Enumeration ifaces;
try {
ifaces = NetworkInterface.getNetworkInterfaces();
while (ifaces.hasMoreElements()) {
NetworkInterface iface = (NetworkInterface) ifaces.nextElement();
Enumeration iaddresses = iface.getInetAddresses();
while (iaddresses.hasMoreElements()) {
InetAddress iaddress = (InetAddress) iaddresses.nextElement();
if (!iaddress.isLoopbackAddress() && !iaddress.isLinkLocalAddress() && !iaddress.isSiteLocalAddress()) {
return iaddress.getHostAddress() != null ? iaddress.getHostAddress() : iaddress.getHostName();
}
}
}
ifaces = NetworkInterface.getNetworkInterfaces();
while (ifaces.hasMoreElements()) {
NetworkInterface iface = (NetworkInterface) ifaces.nextElement();
Enumeration iaddresses = iface.getInetAddresses();
while (iaddresses.hasMoreElements()) {
InetAddress iaddress = (InetAddress) iaddresses.nextElement();
if (!iaddress.isLoopbackAddress() && !iaddress.isLinkLocalAddress()) {
return iaddress.getHostAddress() != null ? iaddress.getHostAddress() : iaddress.getHostName();
}
}
}
return InetAddress.getLocalHost().getHostAddress() != null ? InetAddress.getLocalHost().getHostAddress() : InetAddress.getLocalHost().getHostName();
}
catch (SocketException e) {
e.printStackTrace();
}
catch (UnknownHostException e) {
e.printStackTrace();
}
return "127.0.0.1";
}
public static String getOverrideIp() {
return overrideIp;
}
public static void setOverrideIp(String overrideIp) {
LocalIPResolver.overrideIp = overrideIp;
}
}
package org.xmpp.jnodes.nio;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.nio.ByteBuffer;
import java.util.Random;
public class PublicIPResolver {
final static byte BINDING_REQUEST_ID = 0x0001;
final static int MAPPED_ADDRESS = 0x0001;
final static byte CHANGE_REQUEST_NO_CHANGE[] = {0, 3, 0, 4, 0, 0, 0, 0};
final static Random r = new Random(System.nanoTime());
private static byte[] getHeader(final int contentLenght) {
final byte header[] = new byte[20];
header[0] = 0;
header[1] = BINDING_REQUEST_ID;
header[2] = 0;
header[3] = (byte) contentLenght;
header[4] = (byte) (r.nextInt(9));
header[5] = (byte) (r.nextInt(8));
header[6] = (byte) (r.nextInt(7));
header[7] = (byte) (r.nextInt(6));
return header;
}
public static ByteBuffer createSTUNChangeRequest() {
final byte header[] = getHeader(CHANGE_REQUEST_NO_CHANGE.length);
final byte data[] = new byte[header.length + CHANGE_REQUEST_NO_CHANGE.length];
System.arraycopy(header, 0, data, 0, header.length);
System.arraycopy(CHANGE_REQUEST_NO_CHANGE, 0, data, header.length, CHANGE_REQUEST_NO_CHANGE.length);
return ByteBuffer.wrap(data);
}
public static Header parseResponse(byte[] data) {
byte[] lengthArray = new byte[2];
System.arraycopy(data, 2, lengthArray, 0, 2);
int length = unsignedShortToInt(lengthArray);
byte[] cuttedData;
int offset = 20;
while (length > 0) {
cuttedData = new byte[length];
System.arraycopy(data, offset, cuttedData, 0, length);
Header h = parseHeader(cuttedData);
if (h.getType() == MAPPED_ADDRESS) {
return h;
}
length -= h.getLength();
offset += h.getLength();
}
return null;
}
private static Header parseHeader(byte[] data) {
byte[] typeArray = new byte[2];
System.arraycopy(data, 0, typeArray, 0, 2);
int type = unsignedShortToInt(typeArray);
byte[] lengthArray = new byte[2];
System.arraycopy(data, 2, lengthArray, 0, 2);
int lengthValue = unsignedShortToInt(lengthArray);
byte[] valueArray = new byte[lengthValue];
System.arraycopy(data, 4, valueArray, 0, lengthValue);
if (data.length >= 8) {
int family = unsignedByteToInt(valueArray[1]);
if (family == 1) {
byte[] portArray = new byte[2];
System.arraycopy(valueArray, 2, portArray, 0, 2);
int port = unsignedShortToInt(portArray);
int firstOctet = unsignedByteToInt(valueArray[4]);
int secondOctet = unsignedByteToInt(valueArray[5]);
int thirdOctet = unsignedByteToInt(valueArray[6]);
int fourthOctet = unsignedByteToInt(valueArray[7]);
final StringBuilder ip = new StringBuilder().append(firstOctet).append(".").append(secondOctet).append(".").append(thirdOctet).append(".").append(fourthOctet);
return new Header(new InetSocketAddress(ip.toString(), port), type, lengthValue + 4);
}
}
return new Header(null, -1, lengthValue + 4);
}
public static int unsignedShortToInt(final byte[] b) {
int a = b[0] & 0xFF;
int aa = b[1] & 0xFF;
return ((a << 8) + aa);
}
public static int unsignedByteToInt(byte b) {
return (int) b & 0xFF;
}
public static class Header {
final InetSocketAddress address;
final int type;
final int length;
public Header(final InetSocketAddress address, int type, int length) {
this.address = address;
this.type = type;
this.length = length;
}
public int getType() {
return type;
}
public InetSocketAddress getAddress() {
return address;
}
public int getLength() {
return length;
}
}
public static InetSocketAddress getPublicAddress(final String stunServer, final int port) {
int lport = 10002;
for (int t = 0; t < 3; t++) {
try {
final SelDatagramChannel channel = SelDatagramChannel.open(null, new InetSocketAddress(System.getProperty("os.name")!=null&&System.getProperty("os.name").toLowerCase().indexOf("win") > -1 ? LocalIPResolver.getLocalIP() : "0.0.0.0", lport));
return getPublicAddress(channel, stunServer, port);
} catch (IOException e) {
lport += r.nextInt(10) + 1;
}
}
return null;
}
public static InetSocketAddress getPublicAddress(final SelDatagramChannel channel, final String stunServer, final int port) {
final Header[] h = new Header[1];
try {
channel.setDatagramListener(new DatagramListener() {
public void datagramReceived(SelDatagramChannel channel, ByteBuffer buffer, SocketAddress address) {
final byte b[] = new byte[buffer.position()];
buffer.rewind();
buffer.get(b, 0, b.length);
h[0] = parseResponse(b);
}
});
channel.send(createSTUNChangeRequest(), new InetSocketAddress(stunServer, port));
Thread.sleep(100);
for (int i = 0; i < 5; i++) {
Thread.sleep(100);
if (h[0] != null) {
return h[0].getAddress();
}
if (i % 2 == 0) {
channel.send(createSTUNChangeRequest(), new InetSocketAddress(stunServer, port));
}
}
return null;
} catch (IOException e) {
return null;
} catch (InterruptedException e) {
return null;
}
}
}
package org.xmpp.jnodes.nio;
import java.io.IOException;
import java.net.SocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.DatagramChannel;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.util.Iterator;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class SelDatagramChannel {
private final static ExecutorService executorService = Executors.newFixedThreadPool(10);
private static Selector selector;
// Instance Properties
protected final DatagramChannel channel;
private DatagramListener datagramListener;
private final static Object obj = new Object();
private static void init() {
try {
selector = Selector.open();
while (!selector.isOpen()) {
Thread.yield();
}
final Runnable task = new Runnable() {
public void run() {
while (true) {
try {
final int n;
synchronized (obj) {
}
n = selector.select();
if (n == 0) {
Thread.sleep(50);
Thread.yield();
continue;
}
final Set keys = selector.selectedKeys();
// Iterate through the Set of keys.
for (Iterator i = keys.iterator(); i.hasNext();) {
// Get a key from the set, and remove it from the set
final SelectionKey key = (SelectionKey) i.next();
i.remove();
// Get the channel associated with the key
final DatagramChannel c = (DatagramChannel) key.channel();
if (key.isValid() && key.isReadable()) {
final SelDatagramChannel sdc = (SelDatagramChannel) key.attachment();
if (sdc == null) {
// Discard Packet
c.receive(ByteBuffer.allocate(0));
continue;
}
final ByteBuffer b = ByteBuffer.allocateDirect(1450);
final SocketAddress clientAddress;
synchronized (sdc) {
clientAddress = sdc.channel.receive(b);
}
// If we got the datagram successfully, broadcast the Event
if (clientAddress != null) {
// Execute in a different Thread avoid serialization
if (sdc.datagramListener != null) {
executorService.submit(new Runnable() {
public void run() {
sdc.datagramListener.datagramReceived(sdc, b, clientAddress);
}
});
}
}
}
}
} catch (IOException e) {
e.printStackTrace();
}
catch (Throwable t) {
t.printStackTrace();
}
}
}
};
executorService.submit(task);
} catch (IOException e) {
e.printStackTrace();
}
}
protected SelDatagramChannel(final DatagramChannel channel, final DatagramListener datagramListener) {
this.channel = channel;
this.datagramListener = datagramListener;
}
public static SelDatagramChannel open(final DatagramListener datagramListener, final SocketAddress localAddress) throws IOException {
synchronized (executorService) {
if (selector == null) {
init();
}
}
final DatagramChannel dc = DatagramChannel.open();
dc.configureBlocking(false);
dc.socket().bind(localAddress);
final SelDatagramChannel c = new SelDatagramChannel(dc, datagramListener);
synchronized (obj) {
selector.wakeup();
dc.register(selector, SelectionKey.OP_READ, c);
}
return c;
}
public int send(final ByteBuffer src, final SocketAddress target) throws IOException {
return this.channel.send(src, target);
}
public void close() throws IOException {
final SelectionKey k = channel.keyFor(selector);
if (k != null) {
synchronized (obj) {
selector.wakeup();
k.cancel();
}
}
synchronized (this) {
channel.close();
}
}
public void setDatagramListener(DatagramListener listener) {
this.datagramListener = listener;
}
}
\ No newline at end of file
package org.xmpp.jnodes.smack;
import org.jivesoftware.smack.packet.IQ;
public class JingleChannelIQ extends IQ {
public static final String NAME = "channel";
public static final String NAMESPACE = "http://jabber.org/protocol/jinglenodes#channel";
public static final String UDP= "udp";
public static final String TCP= "tcp";
private String protocol = UDP;
private String host;
private int localport = -1;
private int remoteport = -1;
private String id;
public JingleChannelIQ() {
this.setType(Type.GET);
this.setPacketID(IQ.nextID());
}
public String getChildElementXML() {
final StringBuilder str = new StringBuilder();
str.append("<").append(NAME).append(" xmlns='").append(NAMESPACE).append("' protocol='").append(protocol).append("' ");
if (localport > 0 && remoteport > 0 && host != null) {
str.append("host='").append(host).append("' ");
str.append("localport='").append(localport).append("' ");
str.append("remoteport='").append(remoteport).append("' ");
}
str.append("/>");
return str.toString();
}
public boolean isRequest() {
return Type.GET.equals(this.getType());
}
public String getProtocol() {
return protocol;
}
public void setProtocol(String protocol) {
this.protocol = protocol;
}
public int getRemoteport() {
return remoteport;
}
public void setRemoteport(int remoteport) {
this.remoteport = remoteport;
}
public String getHost() {
return host;
}
public void setHost(String host) {
this.host = host;
}
public int getLocalport() {
return localport;
}
public void setLocalport(int localport) {
this.localport = localport;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public static IQ createEmptyResult(IQ iq) {
return createIQ(iq.getPacketID(), iq.getFrom(), iq.getTo(), IQ.Type.RESULT);
}
public static IQ createEmptyError(IQ iq) {
return createIQ(iq.getPacketID(), iq.getFrom(), iq.getTo(), IQ.Type.ERROR);
}
public static IQ createEmptyError() {
return createIQ(null, null, null, IQ.Type.ERROR);
}
public static IQ createIQ(String ID, String to, String from, IQ.Type type) {
IQ iqPacket = new IQ() {
public String getChildElementXML() {
return null;
}
};
iqPacket.setPacketID(ID);
iqPacket.setTo(to);
iqPacket.setFrom(from);
iqPacket.setType(type);
return iqPacket;
}
}
package org.xmpp.jnodes.smack;
import org.jivesoftware.smack.provider.IQProvider;
import org.xmlpull.v1.XmlPullParser;
import java.util.IllegalFormatException;
public class JingleNodesProvider implements IQProvider {
public JingleChannelIQ parseIQ(final XmlPullParser parser) throws Exception {
JingleChannelIQ iq = null;
boolean done = false;
int eventType;
String elementName;
String namespace;
while (!done) {
eventType = parser.getEventType();
elementName = parser.getName();
namespace = parser.getNamespace();
if (eventType == XmlPullParser.START_TAG) {
if (elementName.equals(JingleChannelIQ.NAME) && namespace.equals(JingleChannelIQ.NAMESPACE)) {
final String protocol = parser.getAttributeValue(null, "protocol");
final String porta = parser.getAttributeValue(null, "localport");
final String portb = parser.getAttributeValue(null, "remoteport");
final String host = parser.getAttributeValue(null, "host");
try {
iq = new JingleChannelIQ();
iq.setProtocol(protocol == null ? JingleChannelIQ.UDP : protocol);
if (host != null)
iq.setHost(host);
if (porta != null)
iq.setLocalport(Integer.valueOf(porta));
if (portb != null)
iq.setRemoteport(Integer.valueOf(portb));
} catch (final IllegalFormatException e) {
e.printStackTrace();
} catch (NumberFormatException e) {
e.printStackTrace();
}
}
} else if (eventType == XmlPullParser.END_TAG) {
done = true;
}
if (!done)
parser.next();
}
return iq;
}
}
package org.xmpp.jnodes.smack;
import org.jivesoftware.smack.packet.IQ;
import java.util.Collection;
import java.util.concurrent.ConcurrentHashMap;
public class JingleTrackerIQ extends IQ {
public static final String NAME = "services";
public static final String NAMESPACE = "http://jabber.org/protocol/jinglenodes";
private final ConcurrentHashMap<String, TrackerEntry> entries = new ConcurrentHashMap<String, TrackerEntry>();
public JingleTrackerIQ() {
this.setType(Type.GET);
this.setPacketID(IQ.nextID());
}
public boolean isRequest() {
return Type.GET.equals(this.getType());
}
public void addEntry(final TrackerEntry entry) {
entries.put(entry.getJid(), entry);
}
public void removeEntry(final TrackerEntry entry) {
entries.remove(entry.getJid());
}
public String getChildElementXML() {
final StringBuilder str = new StringBuilder();
str.append("<").append(NAME).append(" xmlns='").append(NAMESPACE).append("'>");
for (final TrackerEntry entry : entries.values()) {
str.append("<").append(entry.getType().toString());
str.append(" policy='").append(entry.getPolicy().toString()).append("'");
str.append(" address='").append(entry.getJid()).append("'");
str.append(" protocol='").append(entry.getProtocol()).append("'");
if (entry.isVerified()) {
str.append(" verified='").append(entry.isVerified()).append("'");
}
str.append("/>");
}
str.append("</").append(NAME).append(">");
return str.toString();
}
public Collection<TrackerEntry> getEntries() {
return entries.values();
}
}
package org.xmpp.jnodes.smack;
import org.jivesoftware.smack.provider.IQProvider;
import org.xmlpull.v1.XmlPullParser;
public class JingleTrackerProvider implements IQProvider {
public JingleTrackerIQ parseIQ(final XmlPullParser parser) throws Exception {
JingleTrackerIQ iq = new JingleTrackerIQ();
boolean done = false;
int eventType;
String elementName;
while (!done) {
eventType = parser.getEventType();
elementName = parser.getName();
if (eventType == XmlPullParser.START_TAG) {
final TrackerEntry.Type type;
if (elementName.equals(TrackerEntry.Type.relay.toString())) {
type = TrackerEntry.Type.relay;
} else if (elementName.equals(TrackerEntry.Type.tracker.toString())) {
type = TrackerEntry.Type.tracker;
} else {
parser.next();
continue;
}
final String protocol = parser.getAttributeValue(null, "protocol");
final TrackerEntry.Policy policy = TrackerEntry.Policy.valueOf("_" + parser.getAttributeValue(null, "policy"));
final String address = parser.getAttributeValue(null, "address");
final String verified = parser.getAttributeValue(null, "verified");
if (address != null && address.length() > 0) {
final TrackerEntry entry = new TrackerEntry(type, policy, address, protocol);
if (verified != null && verified.equals("true")) {
entry.setVerified(true);
}
iq.addEntry(entry);
}
} else if (eventType == XmlPullParser.END_TAG) {
if (elementName.equals(JingleTrackerIQ.NAME)) {
done = true;
}
}
if (!done) {
parser.next();
}
}
return iq;
}
}
package org.xmpp.jnodes.smack;
public class TrackerEntry {
public enum Type {
relay, tracker
}
public enum Policy {
_public, _roster;
public String toString() {
return this.name().substring(1);
}
}
private Type type;
private Policy policy;
private boolean verified = false;
private String protocol = JingleChannelIQ.UDP;
private String jid;
public TrackerEntry(final Type type, final Policy policy, final String jid, final String protocol) {
this.type = type;
this.policy = policy;
this.jid = jid;
this.protocol = protocol;
}
public Type getType() {
return type;
}
public void setType(Type type) {
this.type = type;
}
public String getJid() {
return jid;
}
public void setJid(String jid) {
this.jid = jid;
}
public boolean isVerified() {
return verified;
}
public void setVerified(boolean verified) {
this.verified = verified;
}
public Policy getPolicy() {
return policy;
}
public void setPolicy(Policy policy) {
this.policy = policy;
}
public String getProtocol() {
return protocol;
}
public void setProtocol(String protocol) {
this.protocol = protocol;
}
}
......@@ -49,6 +49,12 @@
Openfire Meetings Plugin Changelog
</h1>
<p><b>0.1.6</b> -- May 9th, 2015</p>
<ul>
<li>Added support for media relaying using the JingleNodes plugin</li>
</ul>
<p><b>0.1.5</b> -- May 4th, 2015</p>
<ul>
......
......@@ -5,8 +5,8 @@
<name>Openfire Meetings</name>
<description>Provides high quality, scalable video conferences using Jitsi Meet and Jitsi Videobridge</description>
<author>Ignite Realtime</author>
<version>0.1.5</version>
<date>05/04/2015</date>
<version>0.1.6</version>
<date>05/09/2015</date>
<minServerVersion>3.9.9</minServerVersion>
<adminconsole>
......
......@@ -75,8 +75,12 @@
<p><span>Spark Plugin - </span>https://your-server.com:7443/ofmeet/spark/ofmeet-plugin.jar</p>
<p>Candy - https://your-server.com:7443/ofmeet/candy.html</p>
<p style="min-height: 8pt; padding: 0px;">&nbsp;</p><p><a href="https://community.igniterealtime.org/servlet/JiveServlet/showImage/38-1730-22278/ofmeet5.png"><img alt="ofmeet5.png" height="93" src="https://community.igniterealtime.org/servlet/JiveServlet/downloadImage/38-1730-22278/ofmeet5.png" style="height: auto;" width="300"/></a></p>
</div>
<div>
<p>It also has a meeting planner feature that enables you to schedule meetings in advance using a calendar.When you add a meeting to the calendar, a request to join the meeting is automatically generated and sent to each participant using Openfire's email service 15 mins before the meeting starts. Included in the email is a link to join the meeting from a Chrome web browser.</p><p><a href="https://community.igniterealtime.org/servlet/JiveServlet/showImage/38-1762-66498/Image2.jpg"><img alt="Image2.jpg" height="215" src="https://community.igniterealtime.org/servlet/JiveServlet/downloadImage/38-1762-66498/379-215/Image2.jpg" style="width:379px; height: 215.108108108108px;" width="379"></a><a href="https://community.igniterealtime.org/servlet/JiveServlet/showImage/38-1762-66502/Image5.jpg"><img alt="Image5.jpg" height="175" src="https://community.igniterealtime.org/servlet/JiveServlet/downloadImage/38-1762-66502/212-175/Image5.jpg" style="width:212px; height: 174.9px;" width="212"></a></p><p>In order to use this feature, you will need:<br><br></p><ul><li>Registered Openfire users with valid email address,</li><li>A persistent MUC room to host each planned meeting</li><li>The Openfire ClientControl plugin installed to create a room bookmark that links the room to users or user groups. Bookmarks with all users selected are ignored.<br><a href="https://community.igniterealtime.org/servlet/JiveServlet/showImage/38-1762-66501/Image6.jpg"><img alt="Image6.jpg" height="210" src="https://community.igniterealtime.org/servlet/JiveServlet/downloadImage/38-1762-66501/375-210/Image6.jpg" style="width:375px; height: 209.879032258065px;" width="375"></a></li><li>The Openfire Email Service configured to deliver emails<br><a href="https://community.igniterealtime.org/servlet/JiveServlet/showImage/38-1762-66500/Image4.jpg"><img alt="Image4.jpg" height="408" src="https://community.igniterealtime.org/servlet/JiveServlet/downloadImage/38-1762-66500/Image4.jpg" style="width: 620px; height: 209px;" width="1210"></a></li></ul><p>The calendar is implemented using the excellent open source <a href="http://fullcalendar.io/" rel="nofollow" target="_blank">fullcalendar</a> jquery plugin by Adam Shaw.</p>
</div>
<p>Discussion place is in the <a href="https://community.igniterealtime.org/community/plugins/commplugins/openfire-meetings/activity">community plugins, here</a></p>
<div>
</div>
</body>
</html>
/*global io MediaServices Phono*/
(function () {
// Utils and references
var root = this,
att = {};
// global utils
var _ = att.util = {
_uuidCounter: 0,
uuid: function () {
return Math.random().toString(16).substring(2) + (_._uuidCounter++).toString(16);
},
slice: Array.prototype.slice,
isFunc: function (obj) {
return Object.prototype.toString.call(obj) == '[object Function]';
},
extend: function (obj) {
this.slice.call(arguments, 1).forEach(function (source) {
if (source) {
for (var prop in source) {
obj[prop] = source[prop];
}
}
});
return obj;
},
each: function (obj, func) {
if (!obj) return;
if (obj instanceof Array) {
obj.forEach(func);
} else {
for (var key in obj) {
func(key, obj[key]);
}
}
},
getQueryParam: function (name) {
// query string parser
var cleaned = name.replace(/[\[]/, "\\[").replace(/[\]]/, "\\]"),
regexS = "[\\?&]" + cleaned + "=([^&#]*)",
regex = new RegExp(regexS),
results = regex.exec(window.location.search);
return (results) ? decodeURIComponent(results[1].replace(/\+/g, " ")) : undefined;
},
// used to try to determine whether they're using the ericsson leif browser
// this is not an ideal way to check, but I'm not sure how to do it since
// leif if pretty much just stock chromium.
h2sSupport: function () {
return !!window.webkitPeerConnection00 && window.navigator.userAgent.indexOf('Chrome/24') !== -1;
}
};
var phoneNumber = {};
phoneNumber.stringify = function (text) {
// strip all non numbers
var cleaned = phoneNumber.parse(text),
len = cleaned.length,
countryCode = (cleaned.charAt(0) === '1'),
arr = cleaned.split(''),
diff;
// if it's long just return it unformatted
if (len > (countryCode ? 11 : 10)) return cleaned;
// if it's too short to tell
if (!countryCode && len < 4) return cleaned;
// remove country code if we have it
if (countryCode) arr.splice(0, 1);
// the rules are different enough when we have
// country codes so we just split it out
if (countryCode) {
if (len > 1) {
diff = 4 - len;
diff = (diff > 0) ? diff : 0;
arr.splice(0, 0, " (");
// back fill with spaces
arr.splice(4, 0, (new Array(diff + 1).join(' ') + ") "));
if (len > 7) {
arr.splice(8, 0, '-');
}
}
} else {
if (len > 7) {
arr.splice(0, 0, "(");
arr.splice(4, 0, ") ");
arr.splice(8, 0, "-");
} else if (len > 3) {
arr.splice(3, 0, "-");
}
}
// join it back when we're done with the CC if it's there
return (countryCode ? '1' : '') + arr.join('');
};
phoneNumber.parse = function (input) {
return String(input)
.toUpperCase()
.replace(/[A-Z]/g, function (l) {
return (l.charCodeAt(0) - 65) / 3 + 2 - ("SVYZ".indexOf(l) > -1) | 0;
})
.replace(/\D/g, '');
};
phoneNumber.getCallable = function (input, countryAbr) {
var country = countryAbr || 'us',
cleaned = phoneNumber.parse(input);
if (cleaned.length === 10) {
if (country == 'us') {
return '1' + cleaned;
}
} else if (country == 'us' && cleaned.length === 11 && cleaned.charAt(0) === '1') {
return cleaned;
} else {
return false;
}
};
att.phoneNumber = phoneNumber;
// attach to window or export with commonJS
if (typeof exports !== 'undefined') {
module.exports = att;
} else {
// make sure we've got an "att" global
root.ATT || (root.ATT = {});
_.extend(root.ATT, att);
}
}).call(this);
<html>
<head>
<title>iNum Demo Application</title>
<link rel="import" href="ui-telephone.html"></link>
<script src="../chrome-extension/ofmeet-api.js"></script>
<script type="text/javascript" src="index.js"></script>
<style>
html, body {
height: 100%;
margin: 0;
background: #f0f0f0;
font-family: sans-serif;
}
inum-telephone {
height: 100%;
display: none;
}
</style>
</head>
<body>
<h1 id='pleasewait'>Please Wait...</h1>
<inum-telephone id="telephone1"></inum-telephone>
</body>
</html>
\ No newline at end of file
var dialer = null;
var offHook = false;
var conn = null;
var audioChannelId = null;
var mixerId = null;
var cleared = false;
var ringtone = null;
var domain = urlParam("domain");
var username = urlParam("username");
var password = urlParam("password");
window.addEventListener("unload", function ()
{
if (conn && mixerId && audioChannelId) conn.inum.expireWebrtcDevice(mixerId, audioChannelId);
if (conn) conn.disconnect();
});
window.addEventListener("load", function()
{
dialer = document.querySelector("inum-telephone");
dialer.addEventListener('Telephone.Dialer.Button', function (event)
{
//console.log("Telephone.Dialer.Button", event, dialer);
if (event.detail.label == "Call")
telephoneAction({action: "dial", destination: event.detail.number});
else if (dialer.call) {
telephoneAction({action: "hangup", call_id: dialer.call.id});
}
});
dialer.addEventListener('Telephone.Dialer.Number', function (event)
{
console.log("Telephone.Dialer.Number", event);
//telephoneAction({action: "dial", call_id: event.detail.number});
});
dialer.addEventListener('Telephone.Dialer.Press', function (event)
{
//console.log("Telephone.Dialer.Press", event);
});
dialer.addEventListener('Telephone.Dialer.Action', function (event)
{
console.log("Telephone.Dialer.Action", event);
if (event.detail.action == 'end') telephoneAction({action: "hangup", call_id: event.detail.call.id});
if (event.detail.action == 'hold') telephoneAction({action: "hold", call_id: event.detail.call.id});
if (event.detail.action == 'unhold') telephoneAction({action: "join", mixer: event.detail.call.mixer});
if ("#*0123456789".indexOf(event.detail.action) > -1)
{
telephoneAction({action: "dtmf", tone: event.detail.action});
}
});
$(document).bind('ofmeet.connected', function (event, connection)
{
console.log("ofmeet connected", connection);
ofmeet.visible(false);
connection.inum.createWebrtcDevice();
conn = connection;
});
$(document).bind('inum.offered', function (event, confId, audioId)
{
console.log("inum.offered", confId, audioId);
audioChannelId = audioId;
mixerId = confId;
$("inum-telephone").css("display", "block");
$('#pleasewait').css("display", "none");
});
$(document).bind('inum.streamadded', function (event, confId, audioId, data)
{
console.log("inum.streamadded", confId, audioId, data);
});
$(document).bind('inum.delivered', function (event, confId, audioId)
{
console.log("inum.delivered", confId, audioId);
});
$(document).bind('inum.cleared', function(event, callId)
{
console.log("inum.cleared", callId);
});
$(document).bind('inum.dialled', function (event, confId, to, callId)
{
console.log("inum.dialled", confId, to, callId);
dialer.call = {id: callId, mixer: confId, to: to}
dialer.setLabel("Hangup");
});
$(document).bind('inum.answered', function(event, callId, callerId, calledId)
{
console.log("inum.answered", callId, callerId, calledId);
dialer.setState("active");
stopTone();
});
$(document).bind('inum.hangup', function(event, callId, callerId, calledId)
{
console.log("inum.hangup", callId, callerId, calledId);
dialer.setLabel("Call");
dialer.setState("inactive");
dialer.call = null
stopTone();
});
$(document).bind('ofmeet.ready', function ()
{
console.log("ofmeet.ready");
ofmeet.connect();
});
ofmeet.ready(username, password);
})
function urlParam(name)
{
var results = new RegExp('[\\?&]' + name + '=([^&#]*)').exec(window.location.href);
if (!results) { return undefined; }
return unescape(results[1] || undefined);
};
function telephoneAction(request)
{
console.log("telephoneAction", request);
if (request.action == "dial")
{
conn.inum.dial(mixerId, domain ? "sip:" + request.destination + "@" + domain : request.destination);
startTone("ringback-uk");
}
if (request.action == "hangup")
{
conn.inum.hangup(request.call_id);
stopTone();
}
if (request.action == "dtmf") conn.inum.sendTones(request.tone);
}
function startTone(name)
{
if (!ringtone)
{
ringtone = new Audio();
ringtone.loop = true;
ringtone.src = "ringtones/" + name + ".mp3";
ringtone.play();
}
}
function stopTone()
{
if (ringtone)
{
ringtone.pause();
ringtone = null;
}
}
This diff is collapsed.
(function () {
var importDoc = document.currentScript.ownerDocument;
var proto = Object.create( HTMLElement.prototype );
var phoney = window.ATT && window.ATT.phoneNumber || window.phoney;
proto.createdCallback = function() {
var that = this;
var template = importDoc.querySelector('#dialerTemplate');
this.readAttributes();
this.shadow = this.createShadowRoot();
this.shadow.appendChild(template.content.cloneNode(true));
this.addClickHandlers();
this.number = '';
this.numberField = this.shadow.querySelector('.numberEntry');
this.callStatus = this.shadow.querySelector('#callStatus');
this.dialpad = this.shadow.querySelector('#screen');
this.button = this.shadow.querySelector('.call');
this.boundKeyHandler = function ()
{
that.handleKeyDown.apply(that, arguments);
};
document.addEventListener('keydown', this.boundKeyHandler, true);
};
proto.readAttributes = function() {
};
proto.attributeChangedCallback = function( attrName, oldVal, newVal ) {
};
proto.addClickHandlers = function ()
{
var self = this;
var buttons = this.shadow.querySelectorAll('button');
var callButton = this.shadow.querySelector('.call');
Array.prototype.forEach.call(buttons, function (button)
{
button.addEventListener('click', function (e)
{
var data = this.attributes['data-value'];
var value = data && data.nodeValue;
if (value)
{
if (value == 'del') {
self.removeLastNumber();
} else if (self.call) {
var myEvent = new CustomEvent("Telephone.Dialer.Action", {detail: {action: value, call: self.call}});
self.dispatchEvent(myEvent);
} else {
self.addNumber(value);
}
}
return false;
}, true);
});
if (callButton)
{
callButton.addEventListener('click', function ()
{
var myEvent = new CustomEvent("Telephone.Dialer.Button", {detail: {number: self.getNumber(), label: callButton.innerHTML}});
self.dispatchEvent(myEvent);
}, false);
}
};
proto.getNumber = function ()
{
return this.number;
};
proto.getLabel = function ()
{
return this.button.innerHTML;
};
proto.setLabel = function (label)
{
this.button.innerHTML = label
};
proto.setNumber = function (number)
{
var newNumber = phoney.parse(number);
var oldNumber = this.number;
var callable = phoney.getCallable(newNumber);
this.number = newNumber;
this.numberField.innerHTML = phoney.stringify(this.number);
if (callable)
{
var myEvent = new CustomEvent("Telephone.Dialer.Number", {detail: {number: callable}});
this.dispatchEvent(myEvent);
}
};
proto.clear = function ()
{
this.setNumber('');
};
proto.addNumber = function (number)
{
var newNumber = (this.getNumber() + '') + number;
var myEvent = new CustomEvent("Telephone.Dialer.Press", {detail: {number: number}});
this.dispatchEvent(myEvent);
this.setNumber(newNumber);
};
proto.removeLastNumber = function ()
{
this.setNumber(this.getNumber().slice(0, -1));
};
proto.handleKeyDown = function(e)
{
var number, keyCode = e.which;
if (keyCode >= 48 && keyCode <= 57) {
number = keyCode - 48;
this.addNumber(number + '');
}
if (keyCode === 8) {
this.removeLastNumber();
e.preventDefault();
}
if (keyCode === 13) {
var myEvent = new CustomEvent("Telephone.Dialer.Number", {detail: {number: this.getNumber()}});
this.dispatchEvent(myEvent);
}
}
proto.startTimer = function ()
{
this.timerStartTime = Date.now();
this.timerStopped = false;
this.updateTimer();
return this;
};
proto.stopTimer = function () {
this.timerStopped = true;
return this;
};
proto.resetTimer = function () {
this.timerStopped = true;
this.setTimeInDom('0:00:00');
return this;
};
proto.updateTimer = function ()
{
if (this.timerStopped) return;
var diff = Date.now() - this.timerStartTime,
s = Math.floor(diff / 1000) % 60,
min = Math.floor((diff / 1000) / 60) % 60,
hr = Math.floor(((diff / 1000) / 60) / 60) % 60,
time = [hr, this.zeroPad(min), this.zeroPad(s)].join(':');
if (this.time !== time) {
this.time = time;
this.setTimeInDom(time);
}
setTimeout(this.updateTimer.bind(this), 100);
};
proto.setTimeInDom = function (timeString) {
if (!this.shadow) return;
this.shadow.querySelector('.callTime').innerHTML = timeString;
};
proto.zeroPad = function (num) {
return ((num + '').length === 1) ? '0' + num : num;
};
proto.setState = function (state, number)
{
var noRender = false;
var timer = false;
var self = this;
this.shadow.querySelector('.callerNumber').innerHTML = number ? number : this.getNumber();
this.shadow.querySelector('button.end').classList.add('hidden');
this.shadow.querySelector('button.hold').classList.add('hidden');
this.shadow.querySelector('button.unhold').classList.add('hidden');
this.callStatus.classList.remove("active");
this.callStatus.classList.remove("inactive");
this.callStatus.classList.remove("held");
this.callStatus.classList.remove("ending");
this.callStatus.classList.add(state);
if (state == "active")
{
noRender = false;
timer = true;
this.shadow.querySelector('button.end').classList.remove('hidden');
this.shadow.querySelector('button.hold').classList.remove('hidden');
}
if (state == "held")
{
noRender = false;
timer = true;
this.shadow.querySelector('button.unhold').classList.remove('hidden');
}
if (state == "inactive")
{
noRender = true;
timer = false;
}
if (noRender)
{
this.stopTimer();
this.callStatus.classList.add('ending');
setTimeout(function ()
{
self.callStatus.classList.remove('visible');
setTimeout(function ()
{
self.callStatus.classList.remove('visible');
self.dialpad.classList.remove('candybarVisible');
//self.clearUser();
}, 1000);
}, 1000);
} else {
this.callStatus.classList.add('visible');
this.dialpad.classList.add('candybarVisible');
if (timer) this.startTimer();
}
}
document.registerElement( "inum-telephone", {
prototype: proto
});
})(window);
\ No newline at end of file
......@@ -578,7 +578,7 @@ function setupRTC() {
browser: 'chrome',
getUserMedia: navigator.webkitGetUserMedia.bind(navigator),
attachMediaStream: function (element, stream) {
element.attr('src', webkitURL.createObjectURL(stream));
element.attr('src', URL.createObjectURL(stream));
},
// DTLS should now be enabled by default but..
pc_constraints: {'optional': [{'DtlsSrtpKeyAgreement': 'true'}]},
......
......@@ -3,7 +3,7 @@ Strophe.addConnectionPlugin('jingle', {
connection: null,
sessions: {},
jid2session: {},
ice_config: {iceServers: []},
ice_config: config.iceServers ? config.iceServers : {'iceServers': [{ 'url': 'stun:stun.l.google.com:19302' }]},
pc_constraints: {},
media_constraints: {
mandatory: {
......
......@@ -34,6 +34,10 @@ function JingleSession(me, sid, connection) {
this.statsinterval = null;
this.reason = null;
this.relayHost = null;
this.relayLocalPort = null;
this.relayRemotePort = null;
this.wait = true;
this.localStreamsSSRC = null;
......@@ -54,6 +58,9 @@ JingleSession.prototype.initiate = function (peerjid, isInitiator) {
this.hadstuncandidate = false;
this.hadturncandidate = false;
this.lasticecandidate = false;
this.relayHost = null;
this.relayLocalPort = null;
this.relayRemotePort = null;
this.peerconnection
= new TraceablePeerConnection(
......@@ -215,6 +222,7 @@ JingleSession.prototype.sendIceCandidate = function (candidate) {
// start 20ms callout
window.setTimeout(function () {
if (self.drip_container.length === 0) return;
//console.log('sendIceCandidates timeout', self.usetrickle, self.usedrip, self.drip_container);
self.sendIceCandidates(self.drip_container);
self.drip_container = [];
}, 20);
......@@ -223,7 +231,8 @@ JingleSession.prototype.sendIceCandidate = function (candidate) {
this.drip_container.push(candidate);
return;
} else {
self.sendIceCandidate([candidate]);
//console.log('sendIceCandidates single', self.usetrickle, self.usedrip, candidate);
self.sendIceCandidates([candidate]);
}
}
} else {
......@@ -286,6 +295,8 @@ JingleSession.prototype.sendIceCandidate = function (candidate) {
JingleSession.prototype.sendIceCandidates = function (candidates) {
console.log('sendIceCandidates', candidates);
var self = this;
var relayDone = false;
var cand = $iq({to: this.peerjid, type: 'set'})
.c('jingle', {xmlns: 'urn:xmpp:jingle:1',
action: 'transport-info',
......@@ -301,8 +312,39 @@ JingleSession.prototype.sendIceCandidates = function (candidates) {
name: (cands[0].sdpMid? cands[0].sdpMid : mline.media)
}).c('transport', ice);
for (var i = 0; i < cands.length; i++) {
cand.c('candidate', SDPUtil.candidateToJingle(cands[i].candidate)).up();
cand.c('candidate', SDPUtil.candidateToJingle(cands[i].candidate)).up();
}
if (!self.relayDone)
{
console.log('sendIceCandidates: send jingle nodes request');
var iqRelay = $iq({type: "get", to: "relay." + self.connection.domain}).c('channel', {xmlns: "http://jabber.org/protocol/jinglenodes#channel", protocol: 'udp'});
self.connection.sendIQ(iqRelay, function(response)
{
if ($(response).attr('type') == "result")
{
console.log('sendIceCandidates: jingle nodes response', response);
self.hadturncandidate = true;
$(response).find('channel').each(function()
{
self.relayHost = $(this).attr('host');
self.relayLocalPort = $(this).attr('localport');
self.relayRemotePort = $(this).attr('remoteport');
var relayCandidate = "a=candidate:3707591233 1 udp 2113937151 " + self.relayHost + " " + self.relayRemotePort + " typ relay generation 0 ";
console.log("add JingleNodes candidate: " + self.relayHost + " " + self.relayLocalPort + " " + self.relayRemotePort);
cand.c('candidate', SDPUtil.candidateToJingle(relayCandidate)).up();
});
}
}, function(err) {console.error("jingle nodes request error", err)});
relayDone = true;
}
// add fingerprint
if (SDPUtil.find_line(this.localSDP.media[mid], 'a=fingerprint:', this.localSDP.session)) {
var tmp = SDPUtil.parse_fingerprint(SDPUtil.find_line(this.localSDP.media[mid], 'a=fingerprint:', this.localSDP.session));
......@@ -318,6 +360,7 @@ JingleSession.prototype.sendIceCandidates = function (candidates) {
cand.up(); // transport
cand.up(); // content
}
self.relayDone = relayDone;
}
// might merge last-candidate notification into this, but it is called alot later. See webrtc issue #2340
//console.log('was this the last candidate', this.lasticecandidate);
......@@ -443,6 +486,13 @@ JingleSession.prototype.setRemoteDescription = function (elem, desctype) {
this.remoteSDP.raw = this.remoteSDP.session + this.remoteSDP.media.join('');
}
}
if (this.relayHost != null && this.relayLocalPort != null)
{
var candidate = new RTCIceCandidate({sdpMLineIndex: "0", candidate: "a=candidate:3707591233 1 udp 2113937151 " + this.relayHost + " " + this.relayLocalPort + " typ relay generation 0 "});
this.peerconnection.addIceCandidate(candidate);
this.hadturncandidate = true;
}
var remotedesc = new RTCSessionDescription({type: desctype, sdp: this.remoteSDP.raw});
this.peerconnection.setRemoteDescription(remotedesc,
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment