Commit 0d48af65 authored by Dele Olajide's avatar Dele Olajide

ofmeet plugin version 0.1.6

Added support for media relaying using the JingleNodes plugin

jinglenodes plugin 0.1.3
Replaced jingle nodes relay library jar file with latest source code
Added dependencies smack.jar and smackx.jar
parent 9cc37be2
......@@ -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