diff --git a/public/webshims/shims/url.js b/public/webshims/shims/url.js
new file mode 100644
index 0000000000000000000000000000000000000000..0e980c790ed1d7bd8d2bf127a6b395b3c06672cb
--- /dev/null
+++ b/public/webshims/shims/url.js
@@ -0,0 +1,316 @@
+webshim.register('url', function($, webshims, window, document, undefined, options){
+'use strict';
+
+// URL Polyfill
+// Draft specification: http://url.spec.whatwg.org
+
+// Notes:
+// - Primarily useful for parsing URLs and modifying query parameters
+// - Should work in IE8+ and everything more modern
+
+	(function (global) {
+		// Browsers may have:
+		// * No global URL object
+		// * URL with static methods only - may have a dummy constructor
+		// * URL with members except searchParams
+		// * Full URL API support
+		var origURL = global.URL;
+		var nativeURL;
+		try {
+			if (origURL) {
+				nativeURL = new global.URL('http://example.com');
+				if ('searchParams' in nativeURL)
+					return;
+				if (!('href' in nativeURL))
+					nativeURL = undefined;
+			}
+		} catch (_) {}
+
+		function URLUtils(url) {
+			if (nativeURL)
+				return new origURL(url);
+			var anchor = document.createElement('a');
+			anchor.href = url;
+			return anchor;
+		}
+
+		global.URL = function URL(url, base) {
+			if (!(this instanceof global.URL))
+				throw new TypeError("Failed to construct 'URL': Please use the 'new' operator.");
+
+			if (base) {
+				url = (function () {
+					if (nativeURL) return new origURL(url, base).href;
+
+					var doc;
+					// Use another document/base tag/anchor for relative URL resolution, if possible
+					if (document.implementation && document.implementation.createHTMLDocument) {
+						doc = document.implementation.createHTMLDocument('');
+					} else if (document.implementation && document.implementation.createDocument) {
+						doc = document.implementation.createElement('http://www.w3.org/1999/xhtml', 'html', null);
+						doc.documentElement.appendChild(doc.createElement('head'));
+						doc.documentElement.appendChild(doc.createElement('body'));
+					} else if (window.ActiveXObject) {
+						doc = new window.ActiveXObject('htmlfile');
+						doc.write('<head></head><body></body>');
+						doc.close();
+					}
+
+					if (!doc) throw Error('base not supported');
+
+					var baseTag = doc.createElement('base');
+					baseTag.href = base;
+					doc.getElementsByTagName('head')[0].appendChild(baseTag);
+					var anchor = doc.createElement('a');
+					anchor.href = url;
+					return anchor.href;
+				}());
+			}
+
+			// An inner object implementing URLUtils (either a native URL
+			// object or an HTMLAnchorElement instance) is used to perform the
+			// URL algorithms. With full ES5 getter/setter support, return a
+			// regular object For IE8's limited getter/setter support, a
+			// different HTMLAnchorElement is returned with properties
+			// overridden
+
+			var instance = URLUtils(url || '');
+
+			// Detect for ES5 getter/setter support
+			var ES5_GET_SET = (Object.defineProperties && (function () {
+				var o = {}; Object.defineProperties(o, { p: { 'get': function () { return true; } } }); return o.p;
+			}()));
+
+			var self = ES5_GET_SET ? this : document.createElement('a');
+
+			// NOTE: Doesn't do the encoding/decoding dance
+			function parse(input, isindex) {
+				var sequences = input.split('&');
+				if (isindex && sequences[0].indexOf('=') === -1)
+					sequences[0] = '=' + sequences[0];
+				var pairs = [];
+				sequences.forEach(function (bytes) {
+					if (bytes.length === 0) return;
+					var index = bytes.indexOf('=');
+					if (index !== -1) {
+						var name = bytes.substring(0, index);
+						var value = bytes.substring(index + 1);
+					} else {
+						name = bytes;
+						value = '';
+					}
+					name = name.replace('+', ' ');
+					value = value.replace('+', ' ');
+					pairs.push({ name: name, value: value });
+				});
+				var output = [];
+				pairs.forEach(function (pair) {
+					output.push({
+						name: decodeURIComponent(pair.name),
+						value: decodeURIComponent(pair.value)
+					});
+				});
+				return output; // Spec bug?
+			}
+
+			function URLSearchParams(url_object, init) {
+				var pairs = [];
+				if (init)
+					pairs = parse(init);
+
+				this._setPairs = function (list) { pairs = list; };
+				this._updateSteps = function () { updateSteps(); };
+
+				var updating = false;
+				function updateSteps() {
+					if (updating) return;
+					updating = true;
+
+					// TODO: For all associated url objects
+					url_object.search = serialize(pairs);
+
+					updating = false;
+				}
+
+				// NOTE: Doesn't do the encoding/decoding dance
+				function serialize(pairs) {
+					var output = '', first = true;
+					pairs.forEach(function (pair) {
+						var name = encodeURIComponent(pair.name);
+						var value = encodeURIComponent(pair.value);
+						if (!first) output += '&';
+						output += name + '=' + value;
+						first = false;
+					});
+					return output;
+				}
+
+				webshim.defineProperties(this, {
+					append: {
+						value: function (name, value) {
+							pairs.push({ name: name, value: value });
+							updateSteps();
+						}
+					},
+
+					'delete': {
+						value: function (name) {
+							for (var i = 0; i < pairs.length;) {
+								if (pairs[i].name === name)
+									pairs.splice(i, 1);
+								else
+									++i;
+							}
+							updateSteps();
+						}
+					},
+
+					get: {
+						value: function (name) {
+							for (var i = 0; i < pairs.length; ++i) {
+								if (pairs[i].name === name)
+									return pairs[i].value;
+							}
+							return null;
+						}
+					},
+
+					getAll: {
+						value: function (name) {
+							var result = [];
+							for (var i = 0; i < pairs.length; ++i) {
+								if (pairs[i].name === name)
+									result.push(pairs[i].value);
+							}
+							return result;
+						}
+					},
+
+					has: {
+						value: function (name) {
+							for (var i = 0; i < pairs.length; ++i) {
+								if (pairs[i].name === name)
+									return true;
+							}
+							return false;
+						}
+					},
+
+					set: {
+						value: function (name, value) {
+							var found = false;
+							for (var i = 0; i < pairs.length;) {
+								if (pairs[i].name === name) {
+									if (!found) {
+										pairs[i].value = value;
+										++i;
+									} else {
+										pairs.splice(i, 1);
+									}
+								} else {
+									++i;
+								}
+							}
+							updateSteps();
+						}
+					},
+
+					toString: {
+						value: function () {
+							return serialize(pairs);
+						}
+					}
+				});
+			};
+
+			var queryObject = new URLSearchParams(
+				self, instance.search ? instance.search.substring(1) : null);
+
+			webshim.defineProperties(self, {
+				href: {
+					get: function () { return instance.href; },
+					set: function (v) { instance.href = v; tidy_instance(); update_steps(); }
+				},
+				origin: {
+					get: function () {
+						if ('origin' in instance) return instance.origin;
+						return this.protocol + '//' + this.host;
+					}
+				},
+				protocol: {
+					get: function () { return instance.protocol; },
+					set: function (v) { instance.protocol = v; }
+				},
+				username: {
+					get: function () { return instance.username; },
+					set: function (v) { instance.username = v; }
+				},
+				password: {
+					get: function () { return instance.password; },
+					set: function (v) { instance.password = v; }
+				},
+				host: {
+					get: function () {
+						// IE returns default port in |host|
+						var re = {'http:': /:80$/, 'https:': /:443$/, 'ftp:': /:21$/}[instance.protocol];
+						return re ? instance.host.replace(re, '') : instance.host;
+					},
+					set: function (v) { instance.host = v; }
+				},
+				hostname: {
+					get: function () { return instance.hostname; },
+					set: function (v) { instance.hostname = v; }
+				},
+				port: {
+					get: function () { return instance.port; },
+					set: function (v) { instance.port = v; }
+				},
+				pathname: {
+					get: function () {
+						// IE does not include leading '/' in |pathname|
+						if (instance.pathname.charAt(0) !== '/') return '/' + instance.pathname;
+						return instance.pathname;
+					},
+					set: function (v) { instance.pathname = v; }
+				},
+				search: {
+					get: function () { return instance.search; },
+					set: function (v) {
+						if (instance.search === v) return;
+						instance.search = v; tidy_instance(); update_steps();
+					}
+				},
+				searchParams: {
+					get: function () { return queryObject; }
+					// TODO: implement setter
+				},
+				hash: {
+					get: function () { return instance.hash; },
+					set: function (v) { instance.hash = v; tidy_instance(); }
+				}
+			});
+
+			function tidy_instance() {
+				var href = instance.href.replace(/#$|\?$|\?(?=#)/g, '');
+				if (instance.href !== href)
+					instance.href = href;
+			}
+
+			function update_steps() {
+				queryObject._setPairs(instance.search ? parse(instance.search.substring(1)) : []);
+				queryObject._updateSteps();
+			}
+
+			return self;
+		};
+
+		if (origURL) {
+			for (var i in origURL) {
+				if (origURL.hasOwnProperty(i))
+					global.URL[i] = origURL[i];
+			}
+		}
+
+	}(window));
+
+});
diff --git a/public/webshims/shims/usermedia-core.js b/public/webshims/shims/usermedia-core.js
new file mode 100644
index 0000000000000000000000000000000000000000..22ab9137523aff5e25b9abe1a03c964c263a7b77
--- /dev/null
+++ b/public/webshims/shims/usermedia-core.js
@@ -0,0 +1,28 @@
+webshim.register('usermedia-core', function($, webshim, window, document, undefined, options){
+	"use strict";
+
+	var srcObjectName = webshim.prefixed('srcObject', document.createElement('video'));
+	var addUnPrefixed = function(){
+		navigator.getUserMedia = navigator[webshim.prefixed('getUserMedia', navigator)];
+	};
+	if(srcObjectName != 'srcObject'){
+		var hasURL = !!(window.URL && URL.createObjectURL);
+		webshim.defineNodeNamesProperty(['audio', 'video'], 'srcObject', {
+			prop: {
+				get: function(){
+					return this[srcObjectName] || null;
+				},
+				set: function(stream){
+					if(srcObjectName){
+						$.prop(this, srcObjectName, stream);
+					} else {
+						$.prop(this, 'src', hasURL ? URL.createObjectURL(stream) : stream);
+					}
+				}
+			}
+		});
+	}
+
+
+	webshim.ready(webshim.modules["usermedia-shim"].loaded ? 'usermedia-api' : 'usermedia-shim', addUnPrefixed);
+});
diff --git a/public/webshims/shims/usermedia-shim.js b/public/webshims/shims/usermedia-shim.js
new file mode 100644
index 0000000000000000000000000000000000000000..3cabe13ca9f506fe974d87ec124f7764136aa1bf
--- /dev/null
+++ b/public/webshims/shims/usermedia-shim.js
@@ -0,0 +1,179 @@
+webshim.register('usermedia-shim', function($, webshim, window, document, undefined, options){
+	"use strict";
+	var addMediaAPI;
+	var streamUrlPrefix = 'webshimstream';
+	var id = 0;
+	var streams = {};
+	var streamCb = {};
+	var hasSwf = swfmini.hasFlashPlayerVersion('11.3');
+	var mediaOptions = webshim.cfg.mediaelement;
+	var mediaelement = webshim.mediaelement;
+	var flashEvents = {
+		NOT_SUPPORTED_ERROR: 1,
+		PERMISSION_DENIED: 1,
+		//not implemented yet
+		MANDATORY_UNSATISFIED_ERROR: 1,
+		onUserSuccess: 1
+	};
+	var noSource = function(){
+		return !$.prop(this, 'currentSrc') && !mediaelement.srces(this).length;
+	};
+
+	function wsGetUserMedia(constraints, successCb, failCb){
+		if(hasSwf){
+			if(!webshim.mediaelement.createSWF){
+				webshim.loader.loadList(['swfmini-embed']);
+				webshim.mediaelement.loadSwf = true;
+				webshim.reTest(['mediaelement-jaris'], true);
+				webshim.ready('mediaelement-jaris', function(){
+					createMediaRequest(constraints, successCb, failCb);
+				});
+			} else {
+				createMediaRequest(constraints, successCb, failCb);
+			}
+		} else {
+			failCb({name: 'NOT_SUPPORTED_ERROR'});
+		}
+	}
+
+	function createMediaRequest(constraints, successCb, failCb){
+		var src;
+		var media = getMediaCandidate();
+		if(!media){return;}
+
+		id++;
+		src = 'webshimstream:stream'+id;
+
+		streamCb[src] = {
+			src: src,
+			success: successCb,
+			fail: failCb
+		};
+
+		addMediaAPI();
+		mediaelement.createSWF(media, {srcProp: src, streamrequest: true, type: 'jarisplayer/stream'});
+	}
+
+
+
+	function getMediaCandidate(){
+		var $media = $('video');
+		$media = $media.filter('.ws-usermedia');
+		if(!$media.length){
+			$media = $media.end();
+		}
+		if($media.length != 1){
+			$media = $media.filter(noSource);
+		}
+		if($media.length != 1){
+			webshim.error('for getUserMedia an empty video element has to be already in the DOM. If you provide multiple empty videos. please mark your suggested video using the "ws-usermedia" class.');
+		}
+		return $media[0];
+	}
+
+
+	function LocalMediaStream(data, api, id){
+		webshim.defineProperties(this, {
+			_swf: {
+				value: api,
+				enumerable: false
+			},
+			_data: {
+				value: data,
+				enumerable: false
+			},
+			_wsStreamId: {
+				value: id,
+				enumerable: false
+			}
+		});
+	}
+
+
+
+	LocalMediaStream.prototype = {
+		currentTime: 0,
+		stop: function(){
+			mediaelement.queueSwfMethod(this._data._elem, 'api_detach', [], this._data);
+		},
+		getAudioTracks: $.noop,
+		getVideoTracks: $.noop
+	};
+
+
+	webshim.usermedia = {
+		attach: function(elem, canPlaySrc, data){
+			if(data._usermedia == canPlaySrc.srcProp){
+				mediaelement.queueSwfMethod(data._elem, 'api_attach', [], data);
+				$(data._elem).trigger('loadstart');
+			} else {
+				webshim.error('something went wrong');
+			}
+		},
+		request: function(elem, canPlaySrc, data){
+			data._usermedia = canPlaySrc.srcProp;
+			if(!options.inline && !$(elem).hasClass('ws-inlineusermedia')){
+				$(data.api).css({position: 'fixed', top: 0, left: 0, width: '100%', height: 150, zIndex: '999999'});
+			} else {
+				$(data.api).css({position: 'relative', zIndex: '999999'});
+			}
+		}
+	};
+
+	URL._nativeCreateObjectURL = URL.createObjectURL;
+	URL._nativeRevokeObjectURL = URL.revokeObjectURL;
+
+	URL.createObjectURL = function(stream){
+
+		var url = '';
+		if(URL._nativeCreateObjectURL && !stream._wsStreamId){
+			url = URL._nativeCreateObjectURL(stream);
+		} else if(stream._wsStreamId) {
+			url = stream._wsStreamId;
+			streams[url] = stream;
+		}
+		return url;
+	};
+
+	URL.revokeObjectURL = function(url){
+		if(streams[url]){
+			delete streams[url];
+		}
+		if(URL._nativeRevokeObjectURL){
+			return URL._nativeRevokeObjectURL(url);
+		}
+	};
+	webshim.usermediastreams = streams;
+
+	addMediaAPI = function(){
+		if(!webshim.mediaelement.createSWF){return;}
+		addMediaAPI = $.noop;
+		var revert = function(data){
+			setTimeout(function(){
+				$(data.api).css({position: '', top: '', left: '', width: '', height: '', zIndex: ''});
+				if($.prop(data._elem, 'controls')){
+					$.prop(data._elem, 'controls', true);
+				}
+			}, 50);
+		};
+		var fail = function(jaris, data){
+			revert(data);
+			streamCb[data._usermedia].fail({name: jaris.type});
+		};
+		$.extend(mediaelement.onEvent, {
+			NOT_SUPPORTED_ERROR: fail,
+			PERMISSION_DENIED: fail,
+			//not implemented yet
+			MANDATORY_UNSATISFIED_ERROR: fail,
+			onUserSuccess: function(jaris, data){
+				revert(data);
+				streamCb[data._usermedia].success(new LocalMediaStream(data, data.api, data._usermedia));
+			}
+		});
+	};
+	webshim.ready('mediaelement-jaris', addMediaAPI);
+
+	webshim.getUserMedia = wsGetUserMedia;
+	navigator.wsGetUserMedia = wsGetUserMedia;
+	webshim.isReady('usermedia-api', true);
+});