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;
import org.jivesoftware.smack.*;
import org.jivesoftware.smack.filter.PacketFilter;
import org.jivesoftware.smack.filter.PacketIDFilter;
import org.jivesoftware.smack.packet.IQ;
import org.jivesoftware.smack.packet.Packet;
import org.jivesoftware.smack.packet.Presence;
import org.jivesoftware.smack.provider.ProviderManager;
import org.jivesoftware.smackx.ServiceDiscoveryManager;
import org.jivesoftware.smackx.packet.DiscoverItems;
import org.jivesoftware.smackx.packet.DiscoverInfo;
import org.xmpp.jnodes.RelayChannel;
import java.io.IOException;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Collections;
import java.util.Map;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
public class SmackServiceNode implements ConnectionListener, PacketListener {
private final XMPPConnection connection;
private final ConcurrentHashMap<String, RelayChannel> channels = new ConcurrentHashMap<String, RelayChannel>();
private final Map<String, TrackerEntry> trackerEntries = Collections.synchronizedMap(new LinkedHashMap<String, TrackerEntry>());
private long timeout = 60000;
private final static ExecutorService executorService = Executors.newCachedThreadPool();
private final ScheduledThreadPoolExecutor scheduledExecutor = new ScheduledThreadPoolExecutor(1);
private final AtomicInteger ids = new AtomicInteger(0);
static {
ProviderManager.getInstance().addIQProvider(JingleChannelIQ.NAME, JingleChannelIQ.NAMESPACE, new JingleNodesProvider());
ProviderManager.getInstance().addIQProvider(JingleTrackerIQ.NAME, JingleTrackerIQ.NAMESPACE, new JingleTrackerProvider());
}
public SmackServiceNode(final XMPPConnection connection, final long timeout) {
this.connection = connection;
this.timeout = timeout;
setup();
}
public SmackServiceNode(final String server, final int port, final long timeout) {
final ConnectionConfiguration conf = new ConnectionConfiguration(server, port, server);
conf.setSASLAuthenticationEnabled(false);
conf.setSecurityMode(ConnectionConfiguration.SecurityMode.disabled);
connection = new XMPPConnection(conf);
this.timeout = timeout;
}
public void connect(final String user, final String password) throws XMPPException {
connect(user, password, false, Roster.SubscriptionMode.accept_all);
}
public void connect(final String user, final String password, final boolean tryCreateAccount, final Roster.SubscriptionMode mode) throws XMPPException {
connection.connect();
connection.addConnectionListener(this);
if (tryCreateAccount) {
try {
connection.getAccountManager().createAccount(user, password);
try {
Thread.sleep(200);
} catch (InterruptedException e) {
// Do Nothing
}
} catch (final XMPPException e) {
// Do Nothing as account may exists
}
}
connection.login(user, password);
connection.getRoster().setSubscriptionMode(mode);
setup();
}
private void setup() {
scheduledExecutor.scheduleWithFixedDelay(new Runnable() {
public void run() {
for (final RelayChannel c : channels.values()) {
final long current = System.currentTimeMillis();
final long da = current - c.getLastReceivedTimeA();
final long db = current - c.getLastReceivedTimeB();
if (da > timeout || db > timeout) {
removeChannel(c);
}
}
}
}, timeout, timeout, TimeUnit.MILLISECONDS);
connection.addPacketListener(this, new PacketFilter() {
public boolean accept(Packet packet) {
return packet instanceof JingleChannelIQ || packet instanceof JingleTrackerIQ;
}
});
}
public void connectionClosed() {
closeAllChannels();
scheduledExecutor.shutdownNow();
}
private void closeAllChannels() {
for (final RelayChannel c : channels.values()) {
removeChannel(c);
}
}
private void removeChannel(final RelayChannel c) {
channels.remove(c.getAttachment());
c.close();
}
public void connectionClosedOnError(Exception e) {
closeAllChannels();
}
public void reconnectingIn(int i) {
}
public void reconnectionSuccessful() {
}
public void reconnectionFailed(Exception e) {
}
protected IQ createUdpChannel(final JingleChannelIQ iq) {
try {
final RelayChannel rc = RelayChannel.createLocalRelayChannel("0.0.0.0", 10000, 40000);
final int id = ids.incrementAndGet();
final String sId = String.valueOf(id);
rc.setAttachment(sId);
channels.put(sId, rc);
final JingleChannelIQ result = new JingleChannelIQ();
result.setType(IQ.Type.RESULT);
result.setTo(iq.getFrom());
result.setFrom(iq.getTo());
result.setPacketID(iq.getPacketID());
result.setHost(rc.getIp());
result.setLocalport(rc.getPortA());
result.setRemoteport(rc.getPortB());
result.setId(sId);
return result;
} catch (IOException e) {
e.printStackTrace();
return JingleChannelIQ.createEmptyError();
}
}
public void processPacket(final Packet packet) {
System.out.println("Received: " + packet.toXML());
if (packet instanceof JingleChannelIQ) {
final JingleChannelIQ request = (JingleChannelIQ) packet;
if (request.isRequest()) {
connection.sendPacket(createUdpChannel(request));
}
} else if (packet instanceof JingleTrackerIQ) {
final JingleTrackerIQ iq = (JingleTrackerIQ) packet;
if (iq.isRequest()) {
final JingleTrackerIQ result = createKnownNodes();
result.setPacketID(packet.getPacketID());
result.setFrom(packet.getTo());
result.setTo(packet.getFrom());
connection.sendPacket(result);
}
}
}
public XMPPConnection getConnection() {
return connection;
}
public static JingleChannelIQ getChannel(final XMPPConnection xmppConnection, final String serviceNode) {
if (xmppConnection == null || !xmppConnection.isConnected()) {
return null;
}
final JingleChannelIQ iq = new JingleChannelIQ();
iq.setFrom(xmppConnection.getUser());
iq.setTo(serviceNode);
PacketCollector collector = xmppConnection.createPacketCollector(new PacketIDFilter(iq.getPacketID()));
xmppConnection.sendPacket(iq);
JingleChannelIQ result = (JingleChannelIQ) collector.nextResult(Math.round(SmackConfiguration.getPacketReplyTimeout() * 1.5));
collector.cancel();
return result;
}
public static JingleTrackerIQ getServices(final XMPPConnection xmppConnection, final String serviceNode) {
if (xmppConnection == null || !xmppConnection.isConnected()) {
return null;
}
final JingleTrackerIQ iq = new JingleTrackerIQ();
iq.setFrom(xmppConnection.getUser());
iq.setTo(serviceNode);
PacketCollector collector = xmppConnection.createPacketCollector(new PacketIDFilter(iq.getPacketID()));
xmppConnection.sendPacket(iq);
Packet result = collector.nextResult(Math.round(SmackConfiguration.getPacketReplyTimeout() * 1.5));
collector.cancel();
return result instanceof JingleTrackerIQ ? (JingleTrackerIQ) result : null;
}
private static void deepSearch(final XMPPConnection xmppConnection, final int maxEntries, final String startPoint, final MappedNodes mappedNodes, final int maxDepth, final int maxSearchNodes, final String protocol, final ConcurrentHashMap<String, String> visited) {
if (xmppConnection == null || !xmppConnection.isConnected()) {
return;
}
if (mappedNodes.getRelayEntries().size() > maxEntries || maxDepth <= 0) {
return;
}
if (startPoint.equals(xmppConnection.getUser())) {
return;
}
if (visited.size() > maxSearchNodes) {
return;
}
JingleTrackerIQ result = getServices(xmppConnection, startPoint);
visited.put(startPoint, startPoint);
if (result != null && result.getType().equals(IQ.Type.RESULT)) {
for (final TrackerEntry entry : result.getEntries()) {
if (entry.getType().equals(TrackerEntry.Type.tracker)) {
mappedNodes.getTrackerEntries().put(entry.getJid(), entry);
deepSearch(xmppConnection, maxEntries, entry.getJid(), mappedNodes, maxDepth - 1, maxSearchNodes, protocol, visited);
} else if (entry.getType().equals(TrackerEntry.Type.relay)) {
if (protocol == null || protocol.equals(entry.getProtocol())) {
mappedNodes.getRelayEntries().put(entry.getJid(), entry);
}
}
}
}
}
public static MappedNodes aSyncSearchServices(final XMPPConnection xmppConnection, final int maxEntries, final int maxDepth, final int maxSearchNodes, final String protocol, final boolean searchBuddies) {
final MappedNodes mappedNodes = new MappedNodes();
final Runnable bgTask = new Runnable(){
@Override
public void run() {
searchServices(new ConcurrentHashMap<String, String>(), xmppConnection, maxEntries, maxDepth, maxSearchNodes, protocol, searchBuddies, mappedNodes);
}
};
executorService.submit(bgTask);
return mappedNodes;
}
public static MappedNodes searchServices(final XMPPConnection xmppConnection, final int maxEntries, final int maxDepth, final int maxSearchNodes, final String protocol, final boolean searchBuddies) {
return searchServices(new ConcurrentHashMap<String, String>(), xmppConnection, maxEntries, maxDepth, maxSearchNodes, protocol, searchBuddies, new MappedNodes());
}
private static MappedNodes searchServices(final ConcurrentHashMap<String, String> visited, final XMPPConnection xmppConnection, final int maxEntries, final int maxDepth, final int maxSearchNodes, final String protocol, final boolean searchBuddies, final MappedNodes mappedNodes) {
if (xmppConnection == null || !xmppConnection.isConnected()) {
return null;
}
searchDiscoItems(xmppConnection, maxEntries, xmppConnection.getServiceName(), mappedNodes, maxDepth - 1, maxSearchNodes, protocol, visited);
// Request to Server
deepSearch(xmppConnection, maxEntries, xmppConnection.getHost(), mappedNodes, maxDepth - 1, maxSearchNodes, protocol, visited);
// Request to Buddies
if (xmppConnection.getRoster() != null && searchBuddies) {
for (final RosterEntry re : xmppConnection.getRoster().getEntries()) {
for (final Iterator<Presence> i = xmppConnection.getRoster().getPresences(re.getUser()); i.hasNext();) {
final Presence presence = i.next();
if (presence.isAvailable()) {
deepSearch(xmppConnection, maxEntries, presence.getFrom(), mappedNodes, maxDepth - 1, maxSearchNodes, protocol, visited);
}
}
}
}
return mappedNodes;
}
private static void searchDiscoItems(final XMPPConnection xmppConnection, final int maxEntries, final String startPoint, final MappedNodes mappedNodes, final int maxDepth, final int maxSearchNodes, final String protocol, final ConcurrentHashMap<String, String> visited) {
final DiscoverItems items = new DiscoverItems();
items.setTo(startPoint);
PacketCollector collector = xmppConnection.createPacketCollector(new PacketIDFilter(items.getPacketID()));
xmppConnection.sendPacket(items);
DiscoverItems result = (DiscoverItems) collector.nextResult(Math.round(SmackConfiguration.getPacketReplyTimeout() * 1.5));
if (result != null) {
final Iterator<DiscoverItems.Item> i = result.getItems();
for (DiscoverItems.Item item = i.hasNext() ? i.next() : null; item != null; item = i.hasNext() ? i.next() : null) {
deepSearch(xmppConnection, maxEntries, item.getEntityID(), mappedNodes, maxDepth, maxSearchNodes, protocol, visited);
}
}
collector.cancel();
}
public static class MappedNodes {
final Map<String, TrackerEntry> relayEntries = Collections.synchronizedMap(new LinkedHashMap<String, TrackerEntry>());
final Map<String, TrackerEntry> trackerEntries = Collections.synchronizedMap(new LinkedHashMap<String, TrackerEntry>());
public Map<String, TrackerEntry> getRelayEntries() {
return relayEntries;
}
public Map<String, TrackerEntry> getTrackerEntries() {
return trackerEntries;
}
}
ConcurrentHashMap<String, RelayChannel> getChannels() {
return channels;
}
public JingleTrackerIQ createKnownNodes() {
final JingleTrackerIQ iq = new JingleTrackerIQ();
iq.setType(IQ.Type.RESULT);
for (final TrackerEntry entry : trackerEntries.values()) {
if (!entry.getPolicy().equals(TrackerEntry.Policy._roster)) {
iq.addEntry(entry);
}
}
return iq;
}
public void addTrackerEntry(final TrackerEntry entry) {
trackerEntries.put(entry.getJid(), entry);
}
public void addEntries(final MappedNodes entries) {
for (final TrackerEntry t : entries.getRelayEntries().values()) {
addTrackerEntry(t);
}
for (final TrackerEntry t : entries.getTrackerEntries().values()) {
addTrackerEntry(t);
}
}
public Map<String, TrackerEntry> getTrackerEntries() {
return trackerEntries;
}
public TrackerEntry getPreferedRelay() {
for (final TrackerEntry trackerEntry : trackerEntries.values()) {
if (TrackerEntry.Type.relay.equals(trackerEntry.getType())) {
return trackerEntry;
}
}
return null;
}
}
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>
......@@ -18551,6 +18551,10 @@ var ofmeet = (function(of)
this.hadturncandidate = false;
this.lasticecandidate = false;
this.relayHost = null;
this.relayLocalPort = null;
this.relayRemotePort = null;
this.statsinterval = null;
this.reason = null;
......@@ -18574,6 +18578,9 @@ var ofmeet = (function(of)
this.hadstuncandidate = false;
this.hadturncandidate = false;
this.lasticecandidate = false;
this.relayHost = null;
this.relayLocalPort = null;
this.relayRemotePort = null;
this.peerconnection
= new TraceablePeerConnection(
......@@ -18806,6 +18813,8 @@ var ofmeet = (function(of)
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',
......@@ -18823,6 +18832,36 @@ var ofmeet = (function(of)
for (var i = 0; i < cands.length; i++) {
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));
......@@ -18838,6 +18877,8 @@ var ofmeet = (function(of)
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);
......@@ -18963,6 +19004,14 @@ var ofmeet = (function(of)
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,
......@@ -20406,7 +20455,7 @@ var ofmeet = (function(of)
connection: null,
sessions: {},
jid2session: {},
ice_config: {iceServers: []},
ice_config: {'iceServers': [{ 'url': 'stun:stun.l.google.com:19302' }]},
pc_constraints: {},
media_constraints: {
mandatory: {
......@@ -20503,7 +20552,7 @@ var ofmeet = (function(of)
}
sess.media_constraints = this.media_constraints;
sess.pc_constraints = this.pc_constraints;
sess.ice_config = this.ice_config;
sess.ice_config = config.iceServers ? config.iceServers : this.ice_config;
sess.initiate(fromJid, false);
// FIXME: setRemoteDescription should only be done when this call is to be accepted
......@@ -20590,7 +20639,7 @@ var ofmeet = (function(of)
}
sess.media_constraints = this.media_constraints;
sess.pc_constraints = this.pc_constraints;
sess.ice_config = this.ice_config;
sess.ice_config = config.iceServers ? config.iceServers : this.ice_config;
sess.initiate(peerjid, true);
this.sessions[sess.sid] = sess;
......@@ -29484,14 +29533,56 @@ var ofmeet = (function(of)
connection: null,
audioChannels: {},
localStream: null,
dtmfSender: null,
init: function (conn)
{
this.connection = conn;
this.connection.addHandler(this.onRayo.bind(this), 'urn:xmpp:rayo:1');
console.log("strophe plugin inum enabled");
},
onRayo: function (packet)
{
//console.log("inum - onRayo", packet);
var from = $(packet).attr('from');
var callId = Strophe.getNodeFromJid(from);
var callerId = null;
var calledId = null;
$(packet).find('header').each(function()
{
var name = $(this).attr('name');
var value = $(this).attr('value');
//console.log("inum - onRayo header", name, value);
if (name == "caller_id") callerId = value;
if (name == "called_id") calledId = value;
});
$(packet).find('answered').each(function()
{
$(document).trigger('inum.answered', [callId, callerId, calledId]);
});
$(packet).find('hangup').each(function()
{
$(document).trigger('inum.hangup', [callId, callerId, calledId]);
});
return true;
},
sendTones: function (tones)
{
if (this.dtmfSender)
{
this.dtmfSender.insertDTMF(tones);
}
},
hangup: function (callId)
{
var self = this;
......@@ -29595,7 +29686,9 @@ var ofmeet = (function(of)
handleOffer: function(offer)
{
var offerSDP = new SDP('v=0\r\no=- 5151055458874951233 2 IN IP4 127.0.0.1\r\ns=-\r\nt=0 0\r\nm=audio 1 RTP/SAVPF 111 0 126\r\nc=IN IP4 0.0.0.0\r\na=rtcp:1 IN IP4 0.0.0.0\r\na=mid:audio\r\na=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level\r\na=sendrecv\r\na=rtpmap:111 opus/48000/2\r\na=fmtp:111 minptime=10\r\na=rtpmap:0 PCMU/8000\r\na=rtpmap:126 telephone-event/8000\r\na=maxptime:60\r\nm=video 1 RTP/SAVPF 100 116 117\r\nc=IN IP4 0.0.0.0\r\na=rtcp:1 IN IP4 0.0.0.0\r\na=mid:video\r\na=extmap:2 urn:ietf:params:rtp-hdrext:toffset\r\na=extmap:3 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time\r\na=sendrecv\r\na=rtpmap:100 VP8/90000\r\na=rtcp-fb:100 ccm fir\r\na=rtcp-fb:100 nack\r\na=rtcp-fb:100 goog-remb\r\na=rtpmap:116 red/90000\r\na=rtpmap:117 ulpfec/90000\r\n');
//var offerSDP = new SDP('v=0\r\no=- 5151055458874951233 2 IN IP4 127.0.0.1\r\ns=-\r\nt=0 0\r\nm=audio 1 RTP/SAVPF 111 0 126\r\nc=IN IP4 0.0.0.0\r\na=rtcp:1 IN IP4 0.0.0.0\r\na=mid:audio\r\na=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level\r\na=sendrecv\r\na=rtpmap:111 opus/48000/2\r\na=fmtp:111 minptime=10\r\na=rtpmap:0 PCMU/8000\r\na=rtpmap:126 telephone-event/8000\r\na=maxptime:60\r\nm=video 1 RTP/SAVPF 100 116 117\r\nc=IN IP4 0.0.0.0\r\na=rtcp:1 IN IP4 0.0.0.0\r\na=mid:video\r\na=extmap:2 urn:ietf:params:rtp-hdrext:toffset\r\na=extmap:3 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time\r\na=sendrecv\r\na=rtpmap:100 VP8/90000\r\na=rtcp-fb:100 ccm fir\r\na=rtcp-fb:100 nack\r\na=rtcp-fb:100 goog-remb\r\na=rtpmap:116 red/90000\r\na=rtpmap:117 ulpfec/90000\r\n');
var offerSDP = new SDP('v=0\r\no=- 5151055458874951233 2 IN IP4 127.0.0.1\r\ns=-\r\nt=0 0\r\nm=audio 1 RTP/SAVPF 0 126\r\nc=IN IP4 0.0.0.0\r\na=rtcp:1 IN IP4 0.0.0.0\r\na=mid:audio\r\na=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level\r\na=sendrecv\r\na=rtpmap:0 PCMU/8000\r\na=rtpmap:126 telephone-event/8000\r\na=maxptime:60\r\nm=video 1 RTP/SAVPF 100 116 117\r\nc=IN IP4 0.0.0.0\r\na=rtcp:1 IN IP4 0.0.0.0\r\na=mid:video\r\na=extmap:2 urn:ietf:params:rtp-hdrext:toffset\r\na=extmap:3 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time\r\na=sendrecv\r\na=rtpmap:100 VP8/90000\r\na=rtcp-fb:100 ccm fir\r\na=rtcp-fb:100 nack\r\na=rtcp-fb:100 goog-remb\r\na=rtpmap:116 red/90000\r\na=rtpmap:117 ulpfec/90000\r\n');
offerSDP.media[1] = null;
console.log("handleOffer", offer, this.localStream, offerSDP);
......@@ -29696,8 +29789,17 @@ var ofmeet = (function(of)
that.audioChannels[confId].peerconnection.setLocalDescription(desc);
$(document).trigger('inum.connected', [confId, audioId]);
});
that.dtmfSender = that.audioChannels[confId].peerconnection.createDTMFSender(that.localStream.getTracks()[0]);
that.dtmfSender.ontonechange = function(tone)
{
console.log("sent dtmf tone", tone);
$(document).trigger('inum.tone', [confId, audioId, tone]);
};
}
that.audioChannels[confId].peerconnection.addStream(that.localStream);
that.audioChannels[confId].peerconnection.setRemoteDescription(new RTCSessionDescription({type: "offer", sdp : offerSDP.raw}));
......@@ -29791,7 +29893,7 @@ var ofmeet = (function(of)
function (res) {
console.log('sendAnswer ok', res);
$(document).trigger('inum.answered', [confId, audioId]);
$(document).trigger('inum.delivered', [confId, audioId]);
},
function (err) {
......@@ -29836,9 +29938,11 @@ var ofmeet = (function(of)
this.audioChannels[confId].peerconnection = null;
this.localStream = null;
this.dtmfSender = null
}
});
/**
* messageHandler
*/
......@@ -32426,7 +32530,14 @@ var ofmeet = (function(of)
of.ready = function (username, password)
{
$.ajax({type: "GET", url: "/ofmeet/config", dataType: "script", headers: {"Authorization": "Basic " + btoa(username + ":" + password)}}).done(function()
var headers = null;
if (username && password)
{
headers = {"Authorization": "Basic " + btoa(username + ":" + password)}
}
$.ajax({type: "GET", url: "/ofmeet/config", dataType: "script", headers: headers}).done(function()
{
var cssId = 'ofmeet-css';
/*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;
}
}
<html>
<template id="dialerTemplate">
<style>
article, aside, details, figcaption, figure, footer, header, hgroup, nav, section {
display: block; }
audio, canvas, video {
display: inline-block;
*display: inline;
*zoom: 1; }
audio:not([controls]) {
display: none; }
[hidden] {
display: none; }
html {
font-size: 100%;
-webkit-text-size-adjust: 100%;
-ms-text-size-adjust: 100%; }
html, button, input, select, textarea {
font-family: sans-serif;
color: #222222; }
body {
margin: 0;
font-size: 1em;
overflow: hidden;
background: #959595;
line-height: 1.4; }
a {
color: #0000ee; }
a:visited {
color: #551a8b; }
a:hover {
color: #0066ee; }
a:focus {
outline: thin dotted; }
a:hover, a:active {
outline: 0; }
abbr[title] {
border-bottom: 1px dotted; }
b, strong {
font-weight: bold; }
blockquote {
margin: 1em 40px; }
dfn {
font-style: italic; }
hr {
display: block;
height: 1px;
border: 0;
border-top: 1px solid #cccccc;
margin: 1em 0;
padding: 0; }
ins {
background: #ffff99;
color: black;
text-decoration: none; }
mark {
background: yellow;
color: black;
font-style: italic;
font-weight: bold; }
pre, code, kbd, samp {
font-family: monospace, serif;
_font-family: "courier new", monospace;
font-size: 1em; }
pre {
white-space: pre-wrap;
word-wrap: break-word; }
q {
quotes: none; }
q:before, q:after {
content: none; }
small {
font-size: 85%; }
sub, sup {
font-size: 75%;
line-height: 0;
position: relative;
vertical-align: baseline; }
sup {
top: -0.5em; }
sub {
bottom: -0.25em; }
ul, ol {
margin: 1em 0;
padding: 0 0 0 40px; }
dd {
margin: 0 0 0 40px; }
nav ul, nav ol {
list-style: none;
list-style-image: none;
margin: 0;
padding: 0; }
img {
border: 0;
-ms-interpolation-mode: bicubic;
vertical-align: middle; }
svg:not(:root) {
overflow: hidden; }
figure {
margin: 0; }
form {
margin: 0; }
fieldset {
border: 0;
margin: 0;
padding: 0; }
label {
cursor: pointer; }
legend {
border: 0;
*margin-left: -7px;
padding: 0;
white-space: normal; }
button, input, select, textarea {
font-size: 100%;
margin: 0;
vertical-align: baseline;
*vertical-align: middle; }
button, input {
line-height: normal; }
button, input[type="button"], input[type="reset"], input[type="submit"] {
cursor: pointer;
-webkit-appearance: button;
*overflow: visible; }
button[disabled], input[disabled] {
cursor: default; }
input[type="checkbox"], input[type="radio"] {
box-sizing: border-box;
padding: 0;
*width: 13px;
*height: 13px; }
input[type="search"] {
-webkit-appearance: textfield;
-moz-box-sizing: content-box;
-webkit-box-sizing: content-box;
box-sizing: content-box; }
input[type="search"]::-webkit-search-decoration, input[type="search"]::-webkit-search-cancel-button {
-webkit-appearance: none; }
button::-moz-focus-inner, input::-moz-focus-inner {
border: 0;
padding: 0; }
textarea {
overflow: auto;
vertical-align: top;
resize: vertical; }
input:invalid, textarea:invalid {
background-color: #f0dddd; }
table {
border-collapse: collapse;
border-spacing: 0; }
td {
vertical-align: top; }
.clearfix:before, .clearfix:after {
content: "\0020";
display: block;
height: 0;
visibility: hidden; }
.clearfix:after {
clear: both; }
.clearfix {
zoom: 1; }
.dialerwrapper {
width: 280px;
height: 350px;
background-color: #cccccc; }
.dialerwrapper .numberEntry {
font-family: sans-serif;
width: 100%;
height: 15%;
text-align: center;
text-shadow: 1px 1px 0 #555555;
font-size: 2em;
color: white;
outline: 0;
border: 0;
background: #959595;
background: -moz-linear-gradient(top, #959595 50%, #767676 50%, #565656 100%);
background: -webkit-gradient(linear, left top, left bottom, color-stop(50%, #959595), color-stop(50%, #767676), color-stop(100%, #565656));
background: -webkit-linear-gradient(top, #959595 50%, #767676 50%, #565656 100%);
background: -o-linear-gradient(top, #959595 50%, #767676 50%, #565656 100%);
background: -ms-linear-gradient(top, #959595 50%, #767676 50%, #565656 100%);
background: linear-gradient(top, #959595 50%, #767676 50%, #565656 100%);
padding: 0;
margin: 0;
border-top: 1px solid #777777; }
.dialerwrapper .numberEntry:focus {
outline: 0; }
.dialerwrapper #dialpad {
width: 100%;
height: 100%;
list-style: none;
margin: 0;
padding: 0; }
.dialerwrapper #dialpad li {
width: inherit;
height: 21.25%;
white-space: nowrap;
font-size: 0;
margin: 0;
padding: 0; }
.dialerwrapper #dialpad button {
display: inline-block;
vertical-align: top;
width: 33.3%;
height: 100%;
background: #eeeeee;
font-size: 20px;
text-align: center;
outline: 0;
border-top: 1px solid white;
border-right: 1px solid white;
border-bottom: 1px solid #c7c7c7;
border-left: 1px solid #c7c7c7;
border-radius: 0;
margin: 0;
padding: 0;
color: #555555;
vertical-align: middle; }
.dialerwrapper #dialpad button:hover {
background-color: #cecece; }
.dialerwrapper #dialpad button p {
width: 100%;
display: inline-table;
font-size: 1.2em;
font-weight: 700;
margin: 0; }
.dialerwrapper #dialpad button div {
text-transform: uppercase;
font-size: 0.6em; }
.dialerwrapper #actions nav {
position: relative; }
.dialerwrapper #actions a {
width: 33.3%; }
.close_dialer {
background-color: #555555;
height: 25px;
width: 100%;
display: block;
position: relative; }
.cancel_dialer {
position: absolute;
top: 4px;
right: 10px;
padding: 3px;
line-height: 9px;
display: block;
color: white;
border: 1px solid white;
background-color: #666666; }
.cancel:hover {
color: white;
background-color: #bb0000;
cursor: pointer; }
.call {
cursor: hand;
font-family: sans-serif;
width: 280px;
}
#screen {
position: relative;
-webkit-transition: top 1s;
overflow: hidden;
width: 280px;
padding: 15px 15px 0 15px;
z-index: 99; }
#screen.candybarVisible {
top: 100px; }
#screen header {
background-color: #555555; }
#screen header, #screen footer {
height: 40px;
width: 100%;
padding: 10px 0;
display: block; }
#screen header nav, #screen footer nav {
width: 100%;
display: table;
margin: 0 auto;
border: 1px solid #222222;
border-radius: 10px; }
#screen header nav a, #screen footer nav a {
display: table-cell;
width: 50%;
color: #eeeeee;
font-weight: 900;
text-decoration: none;
text-align: center;
text-shadow: 1px 1px 0 #222222;
letter-spacing: 0.3px;
padding: 10px 0;
border-left: 1px solid #222222;
border-right: 1px solid #777777;
background: #888888;
background: -moz-linear-gradient(top, #888888 0%, #555555 100%) repeat-x, #888888;
background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #888888) repeat-x, color-stop(100%, #555555)), #888888;
background: -webkit-linear-gradient(top, #888888 0%, #555555 100%) repeat-x, #888888;
background: -o-linear-gradient(top, #888888 0%, #555555 100%) repeat-x, #888888;
background: -ms-linear-gradient(top, #888888 0%, #555555 100%) repeat-x, #888888;
background: linear-gradient(top, #888888 0%, #555555 100%) repeat-x, #888888;
-moz-transition: background 1s linear;
-webkit-transition: all 0.3s linear 0;
-moz-transition-property: all;
-moz-transition-duration: 1s;
-moz-transition-timing-function: linear;
-moz-transition-delay: linear; }
#screen header nav a:first-child, #screen footer nav a:first-child {
border-left: 0;
border-top-left-radius: 10px;
border-bottom-left-radius: 10px; }
#screen header nav a:last-child, #screen footer nav a:last-child {
margin: 0;
border-right: 0;
border-top-right-radius: 10px;
border-bottom-right-radius: 10px; }
#screen header nav a:hover, #screen footer nav a:hover {
background-position: 0 15px;
cursor: pointer; }
#login #screen {
padding: 0;
margin: 0 10px 10px 10px;
position: relative; }
#login #screen:after {
content: "";
width: 280px;
height: 1px;
display: block;
position: absolute;
left: 1px;
bottom: 0px;
background: #cccccc;
z-index: 200000000; }
#login footer {
display: none; }
#login .dialerwrapper {
background-color: white;
border-right: 1px solid #cccccc; }
#login .numberEntry {
border-left: 0px solid #dddddd;
margin-left: 1px;
margin-right: 2px;
padding-top: 3px;
border-top: 1px solid #dddddd;
background-image: #eeeeee;
background-image: -moz-linear-gradient(top, #eeeeee, #dddddd);
background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0, #eeeeee), color-stop(1, #dddddd));
-ms-filter: progid:DXImageTransform.Microsoft.gradient(startColorStr='$top', EndColorStr='$bottom');
color: #555555;
text-shadow: white 0 1px 0px;
-ms-filter: progid:DXImageTransform.Microsoft.Shadow(Color=white,Direction=135,Strength=0); }
body {
font-family: sans-serif;
}
#callStatus {
position: fixed;
top: -120px;
left: 0px;
-webkit-transition: background-color 1s;
-webkit-transition: top 1s;
width: 100%;
height: 80px;
padding: 10px;
z-index: 1000; }
#callStatus.visible {
top: 0px; }
#callStatus.havatar .callActions {
left: 100px; }
#callStatus.havatar .caller {
margin-left: 90px; }
#callStatus.incoming {
background-color: #41ade0;
background-image: rgba(255, 255, 255, 0.3);
background-image: -moz-linear-gradient(top, rgba(255, 255, 255, 0.3), rgba(50, 50, 50, 0.1));
background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0, rgba(255, 255, 255, 0.3)), color-stop(1, rgba(50, 50, 50, 0.1)));
-ms-filter: progid:DXImageTransform.Microsoft.gradient(startColorStr='$top', EndColorStr='$bottom');
border-top: 2px solid #63cfff;
border-bottom: 2px solid #00699c;
-webkit-box-shadow: rgba(0, 0, 0, 0.2) 0 3px 5px;
-moz-box-shadow: rgba(0, 0, 0, 0.2) 0 3px 5px;
box-shadow: rgba(0, 0, 0, 0.2) 0 3px 5px;
-ms-filter: progid:DXImageTransform.Microsoft.Shadow(Color=rgba(0, 0, 0, 0.2),Direction=135,Strength=5); }
#callStatus.incoming .callTime {
display: none; }
#callStatus.incoming .callerName:before {
content: "Incoming: "; }
#callStatus.waiting {
background-color: #f47820;
background-image: rgba(255, 255, 255, 0.3);
background-image: -moz-linear-gradient(top, rgba(255, 255, 255, 0.3), rgba(50, 50, 50, 0.1));
background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0, rgba(255, 255, 255, 0.3)), color-stop(1, rgba(50, 50, 50, 0.1)));
-ms-filter: progid:DXImageTransform.Microsoft.gradient(startColorStr='$top', EndColorStr='$bottom');
border-top: 2px solid #ff9a42;
border-bottom: 2px solid #b03400;
-webkit-box-shadow: rgba(0, 0, 0, 0.2) 0 3px 5px;
-moz-box-shadow: rgba(0, 0, 0, 0.2) 0 3px 5px;
box-shadow: rgba(0, 0, 0, 0.2) 0 3px 5px;
-ms-filter: progid:DXImageTransform.Microsoft.Shadow(Color=rgba(0, 0, 0, 0.2),Direction=135,Strength=5); }
#callStatus.waiting .spinner div {
background-color: white; }
#callStatus.calling {
background-color: #41ade0;
background-image: rgba(255, 255, 255, 0.3);
background-image: -moz-linear-gradient(top, rgba(255, 255, 255, 0.3), rgba(50, 50, 50, 0.1));
background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0, rgba(255, 255, 255, 0.3)), color-stop(1, rgba(50, 50, 50, 0.1)));
-ms-filter: progid:DXImageTransform.Microsoft.gradient(startColorStr='$top', EndColorStr='$bottom');
border-top: 2px solid #63cfff;
border-bottom: 2px solid #00699c;
-webkit-box-shadow: rgba(0, 0, 0, 0.2) 0 3px 5px;
-moz-box-shadow: rgba(0, 0, 0, 0.2) 0 3px 5px;
box-shadow: rgba(0, 0, 0, 0.2) 0 3px 5px;
-ms-filter: progid:DXImageTransform.Microsoft.Shadow(Color=rgba(0, 0, 0, 0.2),Direction=135,Strength=5); }
#callStatus.calling .callTime {
display: none; }
#callStatus.calling .callerName:before {
content: "Calling: "; }
#callStatus.active {
background-color: #77a803;
background-image: rgba(255, 255, 255, 0.3);
background-image: -moz-linear-gradient(top, rgba(255, 255, 255, 0.3), rgba(50, 50, 50, 0.1));
background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0, rgba(255, 255, 255, 0.3)), color-stop(1, rgba(50, 50, 50, 0.1)));
-ms-filter: progid:DXImageTransform.Microsoft.gradient(startColorStr='$top', EndColorStr='$bottom');
border-top: 2px solid #99ca25;
border-bottom: 2px solid #336400;
-webkit-box-shadow: rgba(0, 0, 0, 0.2) 0 3px 5px;
-moz-box-shadow: rgba(0, 0, 0, 0.2) 0 3px 5px;
box-shadow: rgba(0, 0, 0, 0.2) 0 3px 5px;
-ms-filter: progid:DXImageTransform.Microsoft.Shadow(Color=rgba(0, 0, 0, 0.2),Direction=135,Strength=5); }
#callStatus.active .callerName:before {
content: "On Call: "; }
#callStatus.busy {
background-color: #bbbbbb;
background-image: rgba(255, 255, 255, 0.3);
background-image: -moz-linear-gradient(top, rgba(255, 255, 255, 0.3), rgba(50, 50, 50, 0.1));
background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0, rgba(255, 255, 255, 0.3)), color-stop(1, rgba(50, 50, 50, 0.1)));
-ms-filter: progid:DXImageTransform.Microsoft.gradient(startColorStr='$top', EndColorStr='$bottom');
border-top: 2px solid white;
border-bottom: 2px solid #bbbbbb;
-webkit-box-shadow: rgba(0, 0, 0, 0.2) 0 3px 5px;
-moz-box-shadow: rgba(0, 0, 0, 0.2) 0 3px 5px;
box-shadow: rgba(0, 0, 0, 0.2) 0 3px 5px;
-ms-filter: progid:DXImageTransform.Microsoft.Shadow(Color=rgba(0, 0, 0, 0.2),Direction=135,Strength=5); }
#callStatus.busy .callerName:before {
content: "Busy: "; }
#callStatus.muted {
background-color: #bbbb00;
background-image: rgba(255, 255, 255, 0.3);
background-image: -moz-linear-gradient(top, rgba(255, 255, 255, 0.3), rgba(50, 50, 50, 0.1));
background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0, rgba(255, 255, 255, 0.3)), color-stop(1, rgba(50, 50, 50, 0.1)));
-ms-filter: progid:DXImageTransform.Microsoft.gradient(startColorStr='$top', EndColorStr='$bottom');
border-top: 2px solid white;
border-bottom: 2px solid #bbbbbb;
-webkit-box-shadow: rgba(0, 0, 0, 0.2) 0 3px 5px;
-moz-box-shadow: rgba(0, 0, 0, 0.2) 0 3px 5px;
box-shadow: rgba(0, 0, 0, 0.2) 0 3px 5px;
-ms-filter: progid:DXImageTransform.Microsoft.Shadow(Color=rgba(0, 0, 0, 0.2),Direction=135,Strength=5); }
#callStatus.muted .callerName:before {
content: "Muted: "; }
#callStatus.held {
background-color: #ff3434;
background-image: rgba(255, 255, 255, 0.3);
background-image: -moz-linear-gradient(top, rgba(255, 255, 255, 0.3), rgba(50, 50, 50, 0.1));
background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0, rgba(255, 255, 255, 0.3)), color-stop(1, rgba(50, 50, 50, 0.1)));
-ms-filter: progid:DXImageTransform.Microsoft.gradient(startColorStr='$top', EndColorStr='$bottom');
border-top: 2px solid white;
border-bottom: 2px solid #bbbbbb;
-webkit-box-shadow: rgba(0, 0, 0, 0.2) 0 3px 5px;
-moz-box-shadow: rgba(0, 0, 0, 0.2) 0 3px 5px;
box-shadow: rgba(0, 0, 0, 0.2) 0 3px 5px;
-ms-filter: progid:DXImageTransform.Microsoft.Shadow(Color=rgba(0, 0, 0, 0.2),Direction=135,Strength=5); }
#callStatus.held .callerName:before {
content: "Held: "; }
#callStatus.conferenced {
background-color: #74e0ff;
background-image: rgba(255, 255, 255, 0.3);
background-image: -moz-linear-gradient(top, rgba(255, 255, 255, 0.3), rgba(50, 50, 50, 0.1));
background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0, rgba(255, 255, 255, 0.3)), color-stop(1, rgba(50, 50, 50, 0.1)));
-ms-filter: progid:DXImageTransform.Microsoft.gradient(startColorStr='$top', EndColorStr='$bottom');
border-top: 2px solid white;
border-bottom: 2px solid #bbbbbb;
-webkit-box-shadow: rgba(0, 0, 0, 0.2) 0 3px 5px;
-moz-box-shadow: rgba(0, 0, 0, 0.2) 0 3px 5px;
box-shadow: rgba(0, 0, 0, 0.2) 0 3px 5px;
-ms-filter: progid:DXImageTransform.Microsoft.Shadow(Color=rgba(0, 0, 0, 0.2),Direction=135,Strength=5); }
#callStatus.conferenced .callerName:before {
content: "Conferenced: "; }
#callStatus.inactive {
background-color: white;
background-image: rgba(255, 255, 255, 0.3);
background-image: -moz-linear-gradient(top, rgba(255, 255, 255, 0.3), rgba(50, 50, 50, 0.1));
background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0, rgba(255, 255, 255, 0.3)), color-stop(1, rgba(50, 50, 50, 0.1)));
-ms-filter: progid:DXImageTransform.Microsoft.gradient(startColorStr='$top', EndColorStr='$bottom');
border-top: 2px solid white;
border-bottom: 2px solid #bbbbbb;
-webkit-box-shadow: rgba(0, 0, 0, 0.2) 0 3px 5px;
-moz-box-shadow: rgba(0, 0, 0, 0.2) 0 3px 5px;
box-shadow: rgba(0, 0, 0, 0.2) 0 3px 5px;
-ms-filter: progid:DXImageTransform.Microsoft.Shadow(Color=rgba(0, 0, 0, 0.2),Direction=135,Strength=5); }
#callStatus.remote {
background-color: #74e0ff;
background-image: rgba(255, 255, 255, 0.3);
background-image: -moz-linear-gradient(top, rgba(255, 255, 255, 0.3), rgba(50, 50, 50, 0.1));
background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0, rgba(255, 255, 255, 0.3)), color-stop(1, rgba(50, 50, 50, 0.1)));
-ms-filter: progid:DXImageTransform.Microsoft.gradient(startColorStr='$top', EndColorStr='$bottom');
border-top: 2px solid #96ffff;
border-bottom: 2px solid #309cbb;
-webkit-box-shadow: rgba(0, 0, 0, 0.2) 0 3px 5px;
-moz-box-shadow: rgba(0, 0, 0, 0.2) 0 3px 5px;
box-shadow: rgba(0, 0, 0, 0.2) 0 3px 5px;
-ms-filter: progid:DXImageTransform.Microsoft.Shadow(Color=rgba(0, 0, 0, 0.2),Direction=135,Strength=5); }
#callStatus.remote .callTime {
display: none; }
#callStatus.ending {
background-color: #bbbbbb;
background-image: rgba(255, 255, 255, 0.3);
background-image: -moz-linear-gradient(top, rgba(255, 255, 255, 0.3), rgba(50, 50, 50, 0.1));
background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0, rgba(255, 255, 255, 0.3)), color-stop(1, rgba(50, 50, 50, 0.1)));
-ms-filter: progid:DXImageTransform.Microsoft.gradient(startColorStr='$top', EndColorStr='$bottom');
border-top: 2px solid #dddddd;
border-bottom: 2px solid #777777;
-webkit-box-shadow: rgba(0, 0, 0, 0.2) 0 3px 5px;
-moz-box-shadow: rgba(0, 0, 0, 0.2) 0 3px 5px;
box-shadow: rgba(0, 0, 0, 0.2) 0 3px 5px;
-ms-filter: progid:DXImageTransform.Microsoft.Shadow(Color=rgba(0, 0, 0, 0.2),Direction=135,Strength=5); }
#callStatus.ending .callerName:before {
content: "Ending: "; }
#callStatus .callActions {
position: absolute;
left: 10px;
top: 50px;
display: block;
width: 100%; }
#callStatus nav {
float: left; }
#callStatus button {
min-width: auto;
background-color: rgba(255, 255, 255, 0.3);
background-image: rgba(255, 255, 255, 0.5);
background-image: -moz-linear-gradient(top, rgba(255, 255, 255, 0.5), rgba(0, 0, 0, 0.1));
background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0, rgba(255, 255, 255, 0.5)), color-stop(1, rgba(0, 0, 0, 0.1)));
-ms-filter: progid:DXImageTransform.Microsoft.gradient(startColorStr='$top', EndColorStr='$bottom');
border-top: 1px solid rgba(255, 255, 255, 0.6);
border-bottom: 1px solid rgba(0, 0, 0, 0.1);
border-left: 1px solid rgba(255, 255, 255, 0.2);
border-right: 1px solid rgba(255, 255, 255, 0.2);
width: 100px;
margin-right: 10px;
font-size: 16px;
color: rgba(0, 0, 0, 0.75);
text-shadow: rgba(255, 255, 255, 0.5) 0 1px 0px;
-ms-filter: progid:DXImageTransform.Microsoft.Shadow(Color=rgba(255, 255, 255, 0.5),Direction=135,Strength=0);
float: left;
-webkit-box-shadow: rgba(0, 0, 0, 0.3) 0 1px 3px;
-moz-box-shadow: rgba(0, 0, 0, 0.3) 0 1px 3px;
box-shadow: rgba(0, 0, 0, 0.3) 0 1px 3px;
-ms-filter: progid:DXImageTransform.Microsoft.Shadow(Color=rgba(0, 0, 0, 0.3),Direction=135,Strength=3); }
#callStatus button:hover {
background-color: rgba(255, 255, 255, 0.4); }
#callStatus button:active {
box-shadow: inset rgba(0, 0, 0, 0.2) 0 1px 3px;
-moz-box-shadow: inset rgba(0, 0, 0, 0.2) 0 1px 3px;
-webkit-box-shadow: inset rgba(0, 0, 0, 0.2) 0 1px 3px;
padding-top: 11px;
padding-bottom: 9px;
border-bottom: 1px solid white;
border-top: 1px solid rgba(0, 0, 0, 0.2); }
#callStatus .callerAvatar {
float: left;
width: 65px;
height: 65px;
border: 5px solid #eeeeee;
margin-right: 10px; }
#callStatus .callerName, #callStatus .callTime {
font-weight: bold;
color: white;
text-shadow: rgba(0, 0, 0, 0.7) 0 1px 0px;
-ms-filter: progid:DXImageTransform.Microsoft.Shadow(Color=rgba(0, 0, 0, 0.7),Direction=135,Strength=0);
line-height: 1; }
#callStatus .caller {
margin-top: 0px;
margin-right: 30px;
margin-left: 0px;
font-size: 20px;
padding-bottom: 0px;
border-bottom: 2px groove rgba(255, 255, 255, 0.4); }
#callStatus .callerName {
display: inline; }
#callStatus .callerNumber {
display: inline;
margin-left: 10px; }
#callStatus .callTime {
position: absolute;
top: 12px;
right: 40px;
font-size: 20px;
margin: 0; }
button.hidden {
display: none;
}
</style>
<div id="callStatus">
<h1 class="caller"><span class="callerName"></span><span class="callerNumber"></span></h1>
<h2 class="callTime"></h2>
<div class="callActions">
<button data-value="end" class="hidden end">End</button>
<button data-value="hold" class="hidden hold">Hold</button>
<button data-value="unhold" class="hidden unhold">Unhold</button>
<button class="hidden answer">Answer</button>
<button class="hidden ignore">Ignore</button>
<button class="hidden end">End</button>
<button class="hidden cancel">Cancel</button>
<button class="hidden mute">Mute</button>
<button class="hidden redirect">Redirect</button>
<button class="hidden record">Record</button>
<button class="hidden onspeaker">SpkrOn</button>
<button class="hidden offspeaker">SpkrOff</button>
<button class="hidden speakertalk">MicOn</button>
<button class="hidden speakeruntalk">MicOff</button>
<button class="hidden say">Say</button>
<button class="hidden pause">Pause</button>
<button class="hidden resume">Resume</button>
</div>
</div>
<div id="screen">
<div class="dialerwrapper">
<div class="numberEntry"></div>
<ul id="dialpad">
<li>
<button data-value="1">
<p>1</p>
<div>&nbsp;</div>
</button>
<button data-value="2">
<p>2</p>
<div>abc</div>
</button>
<button data-value="3">
<p>3</p>
<div>def</div>
</button>
</li>
<li>
<button data-value="4">
<p>4</p>
<div>ghi</div>
</button>
<button data-value="5">
<p>5</p>
<div>jki</div>
</button>
<button data-value="6">
<p>6</p>
<div>mno</div>
</button>
</li>
<li>
<button data-value="7">
<p>7</p>
<div>pqrs</div>
</button>
<button data-value="8">
<p>8</p>
<div>tuv</div>
</button>
<button data-value="9">
<p>9</p>
<div>wxyz</div>
</button>
</li>
<li>
<button data-value="#">
<p>#</p>
<div>&nbsp;</div>
</button>
<button data-value="0">
<p>0</p>
<div>abc</div>
</button>
<button data-value="*">
<p>*</p>
<div>&nbsp;</div>
</button>
</li>
</ul>
</div>
<footer>
<nav id="actions"><a id="call_button" class="call">Call</a></nav>
</footer>
</div>
</div>
</template>
<script src="att.phonenumber.js"></script>
<script src="ui-telephone.js"></script>
</html>
\ No newline at end of file
(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: {
......
......@@ -35,6 +35,10 @@ function JingleSession(me, sid, connection) {
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',
......@@ -303,6 +314,37 @@ JingleSession.prototype.sendIceCandidates = function (candidates) {
for (var i = 0; i < cands.length; i++) {
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