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;
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,7 +18578,10 @@ 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(
this.connection.jingle.ice_config,
......@@ -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);
......@@ -29695,8 +29788,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,8 +29938,10 @@ 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';
......@@ -32560,7 +32671,7 @@ var ofmeet = (function(of)
}
of.VideoLayout = VideoLayout;
of.connection = connection;
of.connection = connection;
return of;
}(ofmeet || {}));
\ No newline at end of file
/*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: {
......
......@@ -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