diff --git a/dist/McuClient.js b/dist/McuClient.js
new file mode 100644
index 0000000..92fe42b
--- /dev/null
+++ b/dist/McuClient.js
@@ -0,0 +1,32410 @@
+(function webpackUniversalModuleDefinition(root, factory) {
+	if(typeof exports === 'object' && typeof module === 'object')
+		module.exports = factory();
+	else if(typeof define === 'function' && define.amd)
+		define([], factory);
+	else if(typeof exports === 'object')
+		exports["MCUClientEngine"] = factory();
+	else
+		root["MCUClientEngine"] = factory();
+})(this, function() {
+return /******/ (function(modules) { // webpackBootstrap
+/******/ 	var parentHotUpdateCallback = this["webpackHotUpdateMCUClientEngine"];
+/******/ 	this["webpackHotUpdateMCUClientEngine"] = function webpackHotUpdateCallback(chunkId, moreModules) { // eslint-disable-line no-unused-vars
+/******/ 		hotAddUpdateChunk(chunkId, moreModules);
+/******/ 		if(parentHotUpdateCallback) parentHotUpdateCallback(chunkId, moreModules);
+/******/ 	}
+
+/******/ 	function hotDownloadUpdateChunk(chunkId) { // eslint-disable-line no-unused-vars
+/******/ 		var head = document.getElementsByTagName("head")[0];
+/******/ 		var script = document.createElement("script");
+/******/ 		script.type = "text/javascript";
+/******/ 		script.charset = "utf-8";
+/******/ 		script.src = __webpack_require__.p + "" + chunkId + "." + hotCurrentHash + ".hot-update.js";
+/******/ 		head.appendChild(script);
+/******/ 	}
+
+/******/ 	function hotDownloadManifest(callback) { // eslint-disable-line no-unused-vars
+/******/ 		if(typeof XMLHttpRequest === "undefined")
+/******/ 			return callback(new Error("No browser support"));
+/******/ 		try {
+/******/ 			var request = new XMLHttpRequest();
+/******/ 			var requestPath = __webpack_require__.p + "" + hotCurrentHash + ".hot-update.json";
+/******/ 			request.open("GET", requestPath, true);
+/******/ 			request.timeout = 10000;
+/******/ 			request.send(null);
+/******/ 		} catch(err) {
+/******/ 			return callback(err);
+/******/ 		}
+/******/ 		request.onreadystatechange = function() {
+/******/ 			if(request.readyState !== 4) return;
+/******/ 			if(request.status === 0) {
+/******/ 				// timeout
+/******/ 				callback(new Error("Manifest request to " + requestPath + " timed out."));
+/******/ 			} else if(request.status === 404) {
+/******/ 				// no update available
+/******/ 				callback();
+/******/ 			} else if(request.status !== 200 && request.status !== 304) {
+/******/ 				// other failure
+/******/ 				callback(new Error("Manifest request to " + requestPath + " failed."));
+/******/ 			} else {
+/******/ 				// success
+/******/ 				try {
+/******/ 					var update = JSON.parse(request.responseText);
+/******/ 				} catch(e) {
+/******/ 					callback(e);
+/******/ 					return;
+/******/ 				}
+/******/ 				callback(null, update);
+/******/ 			}
+/******/ 		};
+/******/ 	}
+
+
+/******/ 	// Copied from https://github.com/facebook/react/blob/bef45b0/src/shared/utils/canDefineProperty.js
+/******/ 	var canDefineProperty = false;
+/******/ 	try {
+/******/ 		Object.defineProperty({}, "x", {
+/******/ 			get: function() {}
+/******/ 		});
+/******/ 		canDefineProperty = true;
+/******/ 	} catch(x) {
+/******/ 		// IE will fail on defineProperty
+/******/ 	}
+
+/******/ 	var hotApplyOnUpdate = true;
+/******/ 	var hotCurrentHash = "c52c58ad9c6e306d2b34"; // eslint-disable-line no-unused-vars
+/******/ 	var hotCurrentModuleData = {};
+/******/ 	var hotCurrentParents = []; // eslint-disable-line no-unused-vars
+
+/******/ 	function hotCreateRequire(moduleId) { // eslint-disable-line no-unused-vars
+/******/ 		var me = installedModules[moduleId];
+/******/ 		if(!me) return __webpack_require__;
+/******/ 		var fn = function(request) {
+/******/ 			if(me.hot.active) {
+/******/ 				if(installedModules[request]) {
+/******/ 					if(installedModules[request].parents.indexOf(moduleId) < 0)
+/******/ 						installedModules[request].parents.push(moduleId);
+/******/ 					if(me.children.indexOf(request) < 0)
+/******/ 						me.children.push(request);
+/******/ 				} else hotCurrentParents = [moduleId];
+/******/ 			} else {
+/******/ 				console.warn("[HMR] unexpected require(" + request + ") from disposed module " + moduleId);
+/******/ 				hotCurrentParents = [];
+/******/ 			}
+/******/ 			return __webpack_require__(request);
+/******/ 		};
+/******/ 		for(var name in __webpack_require__) {
+/******/ 			if(Object.prototype.hasOwnProperty.call(__webpack_require__, name)) {
+/******/ 				if(canDefineProperty) {
+/******/ 					Object.defineProperty(fn, name, (function(name) {
+/******/ 						return {
+/******/ 							configurable: true,
+/******/ 							enumerable: true,
+/******/ 							get: function() {
+/******/ 								return __webpack_require__[name];
+/******/ 							},
+/******/ 							set: function(value) {
+/******/ 								__webpack_require__[name] = value;
+/******/ 							}
+/******/ 						};
+/******/ 					}(name)));
+/******/ 				} else {
+/******/ 					fn[name] = __webpack_require__[name];
+/******/ 				}
+/******/ 			}
+/******/ 		}
+
+/******/ 		function ensure(chunkId, callback) {
+/******/ 			if(hotStatus === "ready")
+/******/ 				hotSetStatus("prepare");
+/******/ 			hotChunksLoading++;
+/******/ 			__webpack_require__.e(chunkId, function() {
+/******/ 				try {
+/******/ 					callback.call(null, fn);
+/******/ 				} finally {
+/******/ 					finishChunkLoading();
+/******/ 				}
+
+/******/ 				function finishChunkLoading() {
+/******/ 					hotChunksLoading--;
+/******/ 					if(hotStatus === "prepare") {
+/******/ 						if(!hotWaitingFilesMap[chunkId]) {
+/******/ 							hotEnsureUpdateChunk(chunkId);
+/******/ 						}
+/******/ 						if(hotChunksLoading === 0 && hotWaitingFiles === 0) {
+/******/ 							hotUpdateDownloaded();
+/******/ 						}
+/******/ 					}
+/******/ 				}
+/******/ 			});
+/******/ 		}
+/******/ 		if(canDefineProperty) {
+/******/ 			Object.defineProperty(fn, "e", {
+/******/ 				enumerable: true,
+/******/ 				value: ensure
+/******/ 			});
+/******/ 		} else {
+/******/ 			fn.e = ensure;
+/******/ 		}
+/******/ 		return fn;
+/******/ 	}
+
+/******/ 	function hotCreateModule(moduleId) { // eslint-disable-line no-unused-vars
+/******/ 		var hot = {
+/******/ 			// private stuff
+/******/ 			_acceptedDependencies: {},
+/******/ 			_declinedDependencies: {},
+/******/ 			_selfAccepted: false,
+/******/ 			_selfDeclined: false,
+/******/ 			_disposeHandlers: [],
+
+/******/ 			// Module API
+/******/ 			active: true,
+/******/ 			accept: function(dep, callback) {
+/******/ 				if(typeof dep === "undefined")
+/******/ 					hot._selfAccepted = true;
+/******/ 				else if(typeof dep === "function")
+/******/ 					hot._selfAccepted = dep;
+/******/ 				else if(typeof dep === "object")
+/******/ 					for(var i = 0; i < dep.length; i++)
+/******/ 						hot._acceptedDependencies[dep[i]] = callback;
+/******/ 				else
+/******/ 					hot._acceptedDependencies[dep] = callback;
+/******/ 			},
+/******/ 			decline: function(dep) {
+/******/ 				if(typeof dep === "undefined")
+/******/ 					hot._selfDeclined = true;
+/******/ 				else if(typeof dep === "number")
+/******/ 					hot._declinedDependencies[dep] = true;
+/******/ 				else
+/******/ 					for(var i = 0; i < dep.length; i++)
+/******/ 						hot._declinedDependencies[dep[i]] = true;
+/******/ 			},
+/******/ 			dispose: function(callback) {
+/******/ 				hot._disposeHandlers.push(callback);
+/******/ 			},
+/******/ 			addDisposeHandler: function(callback) {
+/******/ 				hot._disposeHandlers.push(callback);
+/******/ 			},
+/******/ 			removeDisposeHandler: function(callback) {
+/******/ 				var idx = hot._disposeHandlers.indexOf(callback);
+/******/ 				if(idx >= 0) hot._disposeHandlers.splice(idx, 1);
+/******/ 			},
+
+/******/ 			// Management API
+/******/ 			check: hotCheck,
+/******/ 			apply: hotApply,
+/******/ 			status: function(l) {
+/******/ 				if(!l) return hotStatus;
+/******/ 				hotStatusHandlers.push(l);
+/******/ 			},
+/******/ 			addStatusHandler: function(l) {
+/******/ 				hotStatusHandlers.push(l);
+/******/ 			},
+/******/ 			removeStatusHandler: function(l) {
+/******/ 				var idx = hotStatusHandlers.indexOf(l);
+/******/ 				if(idx >= 0) hotStatusHandlers.splice(idx, 1);
+/******/ 			},
+
+/******/ 			//inherit from previous dispose call
+/******/ 			data: hotCurrentModuleData[moduleId]
+/******/ 		};
+/******/ 		return hot;
+/******/ 	}
+
+/******/ 	var hotStatusHandlers = [];
+/******/ 	var hotStatus = "idle";
+
+/******/ 	function hotSetStatus(newStatus) {
+/******/ 		hotStatus = newStatus;
+/******/ 		for(var i = 0; i < hotStatusHandlers.length; i++)
+/******/ 			hotStatusHandlers[i].call(null, newStatus);
+/******/ 	}
+
+/******/ 	// while downloading
+/******/ 	var hotWaitingFiles = 0;
+/******/ 	var hotChunksLoading = 0;
+/******/ 	var hotWaitingFilesMap = {};
+/******/ 	var hotRequestedFilesMap = {};
+/******/ 	var hotAvailibleFilesMap = {};
+/******/ 	var hotCallback;
+
+/******/ 	// The update info
+/******/ 	var hotUpdate, hotUpdateNewHash;
+
+/******/ 	function toModuleId(id) {
+/******/ 		var isNumber = (+id) + "" === id;
+/******/ 		return isNumber ? +id : id;
+/******/ 	}
+
+/******/ 	function hotCheck(apply, callback) {
+/******/ 		if(hotStatus !== "idle") throw new Error("check() is only allowed in idle status");
+/******/ 		if(typeof apply === "function") {
+/******/ 			hotApplyOnUpdate = false;
+/******/ 			callback = apply;
+/******/ 		} else {
+/******/ 			hotApplyOnUpdate = apply;
+/******/ 			callback = callback || function(err) {
+/******/ 				if(err) throw err;
+/******/ 			};
+/******/ 		}
+/******/ 		hotSetStatus("check");
+/******/ 		hotDownloadManifest(function(err, update) {
+/******/ 			if(err) return callback(err);
+/******/ 			if(!update) {
+/******/ 				hotSetStatus("idle");
+/******/ 				callback(null, null);
+/******/ 				return;
+/******/ 			}
+
+/******/ 			hotRequestedFilesMap = {};
+/******/ 			hotAvailibleFilesMap = {};
+/******/ 			hotWaitingFilesMap = {};
+/******/ 			for(var i = 0; i < update.c.length; i++)
+/******/ 				hotAvailibleFilesMap[update.c[i]] = true;
+/******/ 			hotUpdateNewHash = update.h;
+
+/******/ 			hotSetStatus("prepare");
+/******/ 			hotCallback = callback;
+/******/ 			hotUpdate = {};
+/******/ 			var chunkId = 0;
+/******/ 			{ // eslint-disable-line no-lone-blocks
+/******/ 				/*globals chunkId */
+/******/ 				hotEnsureUpdateChunk(chunkId);
+/******/ 			}
+/******/ 			if(hotStatus === "prepare" && hotChunksLoading === 0 && hotWaitingFiles === 0) {
+/******/ 				hotUpdateDownloaded();
+/******/ 			}
+/******/ 		});
+/******/ 	}
+
+/******/ 	function hotAddUpdateChunk(chunkId, moreModules) { // eslint-disable-line no-unused-vars
+/******/ 		if(!hotAvailibleFilesMap[chunkId] || !hotRequestedFilesMap[chunkId])
+/******/ 			return;
+/******/ 		hotRequestedFilesMap[chunkId] = false;
+/******/ 		for(var moduleId in moreModules) {
+/******/ 			if(Object.prototype.hasOwnProperty.call(moreModules, moduleId)) {
+/******/ 				hotUpdate[moduleId] = moreModules[moduleId];
+/******/ 			}
+/******/ 		}
+/******/ 		if(--hotWaitingFiles === 0 && hotChunksLoading === 0) {
+/******/ 			hotUpdateDownloaded();
+/******/ 		}
+/******/ 	}
+
+/******/ 	function hotEnsureUpdateChunk(chunkId) {
+/******/ 		if(!hotAvailibleFilesMap[chunkId]) {
+/******/ 			hotWaitingFilesMap[chunkId] = true;
+/******/ 		} else {
+/******/ 			hotRequestedFilesMap[chunkId] = true;
+/******/ 			hotWaitingFiles++;
+/******/ 			hotDownloadUpdateChunk(chunkId);
+/******/ 		}
+/******/ 	}
+
+/******/ 	function hotUpdateDownloaded() {
+/******/ 		hotSetStatus("ready");
+/******/ 		var callback = hotCallback;
+/******/ 		hotCallback = null;
+/******/ 		if(!callback) return;
+/******/ 		if(hotApplyOnUpdate) {
+/******/ 			hotApply(hotApplyOnUpdate, callback);
+/******/ 		} else {
+/******/ 			var outdatedModules = [];
+/******/ 			for(var id in hotUpdate) {
+/******/ 				if(Object.prototype.hasOwnProperty.call(hotUpdate, id)) {
+/******/ 					outdatedModules.push(toModuleId(id));
+/******/ 				}
+/******/ 			}
+/******/ 			callback(null, outdatedModules);
+/******/ 		}
+/******/ 	}
+
+/******/ 	function hotApply(options, callback) {
+/******/ 		if(hotStatus !== "ready") throw new Error("apply() is only allowed in ready status");
+/******/ 		if(typeof options === "function") {
+/******/ 			callback = options;
+/******/ 			options = {};
+/******/ 		} else if(options && typeof options === "object") {
+/******/ 			callback = callback || function(err) {
+/******/ 				if(err) throw err;
+/******/ 			};
+/******/ 		} else {
+/******/ 			options = {};
+/******/ 			callback = callback || function(err) {
+/******/ 				if(err) throw err;
+/******/ 			};
+/******/ 		}
+
+/******/ 		function getAffectedStuff(module) {
+/******/ 			var outdatedModules = [module];
+/******/ 			var outdatedDependencies = {};
+
+/******/ 			var queue = outdatedModules.slice();
+/******/ 			while(queue.length > 0) {
+/******/ 				var moduleId = queue.pop();
+/******/ 				var module = installedModules[moduleId];
+/******/ 				if(!module || module.hot._selfAccepted)
+/******/ 					continue;
+/******/ 				if(module.hot._selfDeclined) {
+/******/ 					return new Error("Aborted because of self decline: " + moduleId);
+/******/ 				}
+/******/ 				if(moduleId === 0) {
+/******/ 					return;
+/******/ 				}
+/******/ 				for(var i = 0; i < module.parents.length; i++) {
+/******/ 					var parentId = module.parents[i];
+/******/ 					var parent = installedModules[parentId];
+/******/ 					if(parent.hot._declinedDependencies[moduleId]) {
+/******/ 						return new Error("Aborted because of declined dependency: " + moduleId + " in " + parentId);
+/******/ 					}
+/******/ 					if(outdatedModules.indexOf(parentId) >= 0) continue;
+/******/ 					if(parent.hot._acceptedDependencies[moduleId]) {
+/******/ 						if(!outdatedDependencies[parentId])
+/******/ 							outdatedDependencies[parentId] = [];
+/******/ 						addAllToSet(outdatedDependencies[parentId], [moduleId]);
+/******/ 						continue;
+/******/ 					}
+/******/ 					delete outdatedDependencies[parentId];
+/******/ 					outdatedModules.push(parentId);
+/******/ 					queue.push(parentId);
+/******/ 				}
+/******/ 			}
+
+/******/ 			return [outdatedModules, outdatedDependencies];
+/******/ 		}
+
+/******/ 		function addAllToSet(a, b) {
+/******/ 			for(var i = 0; i < b.length; i++) {
+/******/ 				var item = b[i];
+/******/ 				if(a.indexOf(item) < 0)
+/******/ 					a.push(item);
+/******/ 			}
+/******/ 		}
+
+/******/ 		// at begin all updates modules are outdated
+/******/ 		// the "outdated" status can propagate to parents if they don't accept the children
+/******/ 		var outdatedDependencies = {};
+/******/ 		var outdatedModules = [];
+/******/ 		var appliedUpdate = {};
+/******/ 		for(var id in hotUpdate) {
+/******/ 			if(Object.prototype.hasOwnProperty.call(hotUpdate, id)) {
+/******/ 				var moduleId = toModuleId(id);
+/******/ 				var result = getAffectedStuff(moduleId);
+/******/ 				if(!result) {
+/******/ 					if(options.ignoreUnaccepted)
+/******/ 						continue;
+/******/ 					hotSetStatus("abort");
+/******/ 					return callback(new Error("Aborted because " + moduleId + " is not accepted"));
+/******/ 				}
+/******/ 				if(result instanceof Error) {
+/******/ 					hotSetStatus("abort");
+/******/ 					return callback(result);
+/******/ 				}
+/******/ 				appliedUpdate[moduleId] = hotUpdate[moduleId];
+/******/ 				addAllToSet(outdatedModules, result[0]);
+/******/ 				for(var moduleId in result[1]) {
+/******/ 					if(Object.prototype.hasOwnProperty.call(result[1], moduleId)) {
+/******/ 						if(!outdatedDependencies[moduleId])
+/******/ 							outdatedDependencies[moduleId] = [];
+/******/ 						addAllToSet(outdatedDependencies[moduleId], result[1][moduleId]);
+/******/ 					}
+/******/ 				}
+/******/ 			}
+/******/ 		}
+
+/******/ 		// Store self accepted outdated modules to require them later by the module system
+/******/ 		var outdatedSelfAcceptedModules = [];
+/******/ 		for(var i = 0; i < outdatedModules.length; i++) {
+/******/ 			var moduleId = outdatedModules[i];
+/******/ 			if(installedModules[moduleId] && installedModules[moduleId].hot._selfAccepted)
+/******/ 				outdatedSelfAcceptedModules.push({
+/******/ 					module: moduleId,
+/******/ 					errorHandler: installedModules[moduleId].hot._selfAccepted
+/******/ 				});
+/******/ 		}
+
+/******/ 		// Now in "dispose" phase
+/******/ 		hotSetStatus("dispose");
+/******/ 		var queue = outdatedModules.slice();
+/******/ 		while(queue.length > 0) {
+/******/ 			var moduleId = queue.pop();
+/******/ 			var module = installedModules[moduleId];
+/******/ 			if(!module) continue;
+
+/******/ 			var data = {};
+
+/******/ 			// Call dispose handlers
+/******/ 			var disposeHandlers = module.hot._disposeHandlers;
+/******/ 			for(var j = 0; j < disposeHandlers.length; j++) {
+/******/ 				var cb = disposeHandlers[j];
+/******/ 				cb(data);
+/******/ 			}
+/******/ 			hotCurrentModuleData[moduleId] = data;
+
+/******/ 			// disable module (this disables requires from this module)
+/******/ 			module.hot.active = false;
+
+/******/ 			// remove module from cache
+/******/ 			delete installedModules[moduleId];
+
+/******/ 			// remove "parents" references from all children
+/******/ 			for(var j = 0; j < module.children.length; j++) {
+/******/ 				var child = installedModules[module.children[j]];
+/******/ 				if(!child) continue;
+/******/ 				var idx = child.parents.indexOf(moduleId);
+/******/ 				if(idx >= 0) {
+/******/ 					child.parents.splice(idx, 1);
+/******/ 				}
+/******/ 			}
+/******/ 		}
+
+/******/ 		// remove outdated dependency from module children
+/******/ 		for(var moduleId in outdatedDependencies) {
+/******/ 			if(Object.prototype.hasOwnProperty.call(outdatedDependencies, moduleId)) {
+/******/ 				var module = installedModules[moduleId];
+/******/ 				var moduleOutdatedDependencies = outdatedDependencies[moduleId];
+/******/ 				for(var j = 0; j < moduleOutdatedDependencies.length; j++) {
+/******/ 					var dependency = moduleOutdatedDependencies[j];
+/******/ 					var idx = module.children.indexOf(dependency);
+/******/ 					if(idx >= 0) module.children.splice(idx, 1);
+/******/ 				}
+/******/ 			}
+/******/ 		}
+
+/******/ 		// Not in "apply" phase
+/******/ 		hotSetStatus("apply");
+
+/******/ 		hotCurrentHash = hotUpdateNewHash;
+
+/******/ 		// insert new code
+/******/ 		for(var moduleId in appliedUpdate) {
+/******/ 			if(Object.prototype.hasOwnProperty.call(appliedUpdate, moduleId)) {
+/******/ 				modules[moduleId] = appliedUpdate[moduleId];
+/******/ 			}
+/******/ 		}
+
+/******/ 		// call accept handlers
+/******/ 		var error = null;
+/******/ 		for(var moduleId in outdatedDependencies) {
+/******/ 			if(Object.prototype.hasOwnProperty.call(outdatedDependencies, moduleId)) {
+/******/ 				var module = installedModules[moduleId];
+/******/ 				var moduleOutdatedDependencies = outdatedDependencies[moduleId];
+/******/ 				var callbacks = [];
+/******/ 				for(var i = 0; i < moduleOutdatedDependencies.length; i++) {
+/******/ 					var dependency = moduleOutdatedDependencies[i];
+/******/ 					var cb = module.hot._acceptedDependencies[dependency];
+/******/ 					if(callbacks.indexOf(cb) >= 0) continue;
+/******/ 					callbacks.push(cb);
+/******/ 				}
+/******/ 				for(var i = 0; i < callbacks.length; i++) {
+/******/ 					var cb = callbacks[i];
+/******/ 					try {
+/******/ 						cb(outdatedDependencies);
+/******/ 					} catch(err) {
+/******/ 						if(!error)
+/******/ 							error = err;
+/******/ 					}
+/******/ 				}
+/******/ 			}
+/******/ 		}
+
+/******/ 		// Load self accepted modules
+/******/ 		for(var i = 0; i < outdatedSelfAcceptedModules.length; i++) {
+/******/ 			var item = outdatedSelfAcceptedModules[i];
+/******/ 			var moduleId = item.module;
+/******/ 			hotCurrentParents = [moduleId];
+/******/ 			try {
+/******/ 				__webpack_require__(moduleId);
+/******/ 			} catch(err) {
+/******/ 				if(typeof item.errorHandler === "function") {
+/******/ 					try {
+/******/ 						item.errorHandler(err);
+/******/ 					} catch(err) {
+/******/ 						if(!error)
+/******/ 							error = err;
+/******/ 					}
+/******/ 				} else if(!error)
+/******/ 					error = err;
+/******/ 			}
+/******/ 		}
+
+/******/ 		// handle errors in accept handlers and self accepted module load
+/******/ 		if(error) {
+/******/ 			hotSetStatus("fail");
+/******/ 			return callback(error);
+/******/ 		}
+
+/******/ 		hotSetStatus("idle");
+/******/ 		callback(null, outdatedModules);
+/******/ 	}
+
+/******/ 	// The module cache
+/******/ 	var installedModules = {};
+
+/******/ 	// The require function
+/******/ 	function __webpack_require__(moduleId) {
+
+/******/ 		// Check if module is in cache
+/******/ 		if(installedModules[moduleId])
+/******/ 			return installedModules[moduleId].exports;
+
+/******/ 		// Create a new module (and put it into the cache)
+/******/ 		var module = installedModules[moduleId] = {
+/******/ 			exports: {},
+/******/ 			id: moduleId,
+/******/ 			loaded: false,
+/******/ 			hot: hotCreateModule(moduleId),
+/******/ 			parents: hotCurrentParents,
+/******/ 			children: []
+/******/ 		};
+
+/******/ 		// Execute the module function
+/******/ 		modules[moduleId].call(module.exports, module, module.exports, hotCreateRequire(moduleId));
+
+/******/ 		// Flag the module as loaded
+/******/ 		module.loaded = true;
+
+/******/ 		// Return the exports of the module
+/******/ 		return module.exports;
+/******/ 	}
+
+
+/******/ 	// expose the modules object (__webpack_modules__)
+/******/ 	__webpack_require__.m = modules;
+
+/******/ 	// expose the module cache
+/******/ 	__webpack_require__.c = installedModules;
+
+/******/ 	// __webpack_public_path__
+/******/ 	__webpack_require__.p = "";
+
+/******/ 	// __webpack_hash__
+/******/ 	__webpack_require__.h = function() { return hotCurrentHash; };
+
+/******/ 	// Load entry module and return exports
+/******/ 	return hotCreateRequire(0)(0);
+/******/ })
+/************************************************************************/
+/******/ ([
+/* 0 */
+/***/ function(module, exports, __webpack_require__) {
+
+	module.exports = __webpack_require__(1);
+
+
+/***/ },
+/* 1 */
+/***/ function(module, exports, __webpack_require__) {
+
+	'use strict';
+
+	Object.defineProperty(exports, "__esModule", {
+	  value: true
+	});
+	exports.MessageTypes = undefined;
+	exports.createMcuClient = createMcuClient;
+
+	var _EngineEntrance = __webpack_require__(2);
+
+	var _EngineEntrance2 = _interopRequireDefault(_EngineEntrance);
+
+	var _MessageTypes = __webpack_require__(6);
+
+	var _MessageTypes2 = _interopRequireDefault(_MessageTypes);
+
+	function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
+
+	//对外暴露的对象
+
+
+	var MCU_CLIENT = new _EngineEntrance2.default(); //入口文件
+
+	function createMcuClient() {
+	  return MCU_CLIENT;
+	}
+
+	//监听是事件名和异常定义
+	exports.MessageTypes = _MessageTypes2.default;
+	;
+
+	var _temp = function () {
+	  if (typeof __REACT_HOT_LOADER__ === 'undefined') {
+	    return;
+	  }
+
+	  __REACT_HOT_LOADER__.register(MCU_CLIENT, 'MCU_CLIENT', 'D:/work/McuClient/src/McuClientEngine.js');
+
+	  __REACT_HOT_LOADER__.register(createMcuClient, 'createMcuClient', 'D:/work/McuClient/src/McuClientEngine.js');
+	}();
+
+	;
+
+/***/ },
+/* 2 */
+/***/ function(module, exports, __webpack_require__) {
+
+	'use strict';
+
+	Object.defineProperty(exports, "__esModule", {
+	  value: true
+	});
+
+	var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
+
+	var _Emiter2 = __webpack_require__(3);
+
+	var _Emiter3 = _interopRequireDefault(_Emiter2);
+
+	var _Sass = __webpack_require__(4);
+
+	var _Sass2 = _interopRequireDefault(_Sass);
+
+	var _ServerCheck = __webpack_require__(21);
+
+	var _ServerCheck2 = _interopRequireDefault(_ServerCheck);
+
+	var _mcu2 = __webpack_require__(23);
+
+	var _mcu3 = _interopRequireDefault(_mcu2);
+
+	var _MessageTypes = __webpack_require__(6);
+
+	var _MessageTypes2 = _interopRequireDefault(_MessageTypes);
+
+	var _Loger = __webpack_require__(5);
+
+	var _Loger2 = _interopRequireDefault(_Loger);
+
+	var _ConferApe = __webpack_require__(36);
+
+	var _ConferApe2 = _interopRequireDefault(_ConferApe);
+
+	var _ChatApe = __webpack_require__(41);
+
+	var _ChatApe2 = _interopRequireDefault(_ChatApe);
+
+	var _VideoApe = __webpack_require__(42);
+
+	var _VideoApe2 = _interopRequireDefault(_VideoApe);
+
+	var _AudioApe = __webpack_require__(44);
+
+	var _AudioApe2 = _interopRequireDefault(_AudioApe);
+
+	var _DocApe = __webpack_require__(45);
+
+	var _DocApe2 = _interopRequireDefault(_DocApe);
+
+	var _WhiteBoardApe = __webpack_require__(46);
+
+	var _WhiteBoardApe2 = _interopRequireDefault(_WhiteBoardApe);
+
+	var _EngineUtils = __webpack_require__(9);
+
+	var _EngineUtils2 = _interopRequireDefault(_EngineUtils);
+
+	var _GlobalConfig = __webpack_require__(7);
+
+	var _GlobalConfig2 = _interopRequireDefault(_GlobalConfig);
+
+	var _ApeConsts = __webpack_require__(8);
+
+	var _ApeConsts2 = _interopRequireDefault(_ApeConsts);
+
+	var _base64Js = __webpack_require__(10);
+
+	var _base64Js2 = _interopRequireDefault(_base64Js);
+
+	var _ArrayBufferUtil = __webpack_require__(38);
+
+	var _ArrayBufferUtil2 = _interopRequireDefault(_ArrayBufferUtil);
+
+	var _utf = __webpack_require__(11);
+
+	var _utf2 = _interopRequireDefault(_utf);
+
+	function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
+
+	function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
+
+	function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }
+
+	function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
+
+	__webpack_require__(47).polyfill();
+	__webpack_require__(49);
+	__webpack_require__(50);
+	__webpack_require__(13);
+
+	var loger = _Loger2.default.getLoger('MessageEntrance');
+	var _sdkInfo = { "version": "v.1.0.1", "author": "www.3mang.com" };
+
+	//APE
+	var _sass = void 0;
+	var _serverCheck = void 0;
+	var _mcu = void 0;
+	var _confer_ape = void 0;
+	var _chat_ape = void 0;
+	var _video_ape = void 0;
+	var _audio_ape = void 0;
+	var _doc_ape = void 0;
+	var _whiteboard_ape = void 0;
+
+	//MCUClient 外部实例化主类
+
+	var MessageEntrance = function (_Emiter) {
+	  _inherits(MessageEntrance, _Emiter);
+
+	  function MessageEntrance() {
+	    _classCallCheck(this, MessageEntrance);
+
+	    //sdk 信息
+	    var _this = _possibleConstructorReturn(this, (MessageEntrance.__proto__ || Object.getPrototypeOf(MessageEntrance)).call(this));
+
+	    _this.sdkInfo = _sdkInfo;
+	    loger.log(_this.sdkInfo);
+
+	    //初始化状态
+	    _GlobalConfig2.default.setCurrentStatus(_GlobalConfig2.default.statusCode_0);
+
+	    //全局的Error处理
+	    _this.on(_MessageTypes2.default.MCU_ERROR, _this._mcuErrorHandler.bind(_this));
+
+	    // Sass平台层
+	    _sass = _Sass2.default;
+	    _sass.on('*', function (type, data) {
+	      return _this._emit(type, data);
+	    });
+	    _sass.on(_sass.SUCCESS, _this._sassJoinSuccessHandler.bind(_this)); //通过SASS平台验证(密码和MD5)
+	    _sass.on(_sass.CLASS_INIT_SUCCESS, _this._sassInitSuccessHandler.bind(_this)); //获取会议初始化信息
+	    //_sass.on(_sass.CLASS_GET_CLASS_DETAIL, this._sassGetClassDetailSuccessHandler.bind(this));//获取会议的基本信息
+	    _sass.on(_sass.CLASS_GET_CLASS_PARAM, _this._sassGetClassParamSuccessHandler.bind(_this)); //获取会议的最全信息和历史保存的数据
+
+	    _sass.on(_sass.CLASS_SAVE_STATUS_INFO_SUCCESS, _this._sassSaveClassStatusInfoSuccessHandler.bind(_this)); //保存会议状态信息
+	    _sass.on(_sass.CLASS_SAVE_RECORD_INFO_SUCCESS, _this._sassSaveClassRecordInfoSuccessHandler.bind(_this)); //保存会议录制信息
+	    _sass.on(_sass.DELETE_DOCUMENT_SUCCESS, _this._sassDeleteDocumentSuccess.bind(_this)); //sass删除文档成功
+
+	    //ServerCheck ip
+	    _serverCheck = _ServerCheck2.default;
+	    _serverCheck.on(_serverCheck.SEVER_CHECK_BEST_IP_SUCCESS, _this._serverCheckBestIpSuccessHandler.bind(_this)); //ip选点,获取最佳ip完成
+
+
+	    // 底层MCU消息层
+	    _mcu = _mcu3.default;
+	    _mcu.on('*', function (type, data) {
+	      return _this._emit(type, data);
+	    });
+	    _mcu.on(_MessageTypes2.default.CLASS_JOIN_MCU_SUCCESS, _this._mcuJoinMCUClassSuccessHandler.bind(_this)); //加入MCU会议完成
+
+
+	    // 注册所有应用Ape
+	    _confer_ape = new _ConferApe2.default();
+	    _confer_ape.on('*', function (type, data) {
+	      return _this._emit(type, data);
+	    });
+	    _confer_ape.on(_MessageTypes2.default.CLASS_EXIT, _this._doClassExit.bind(_this)); //监听自己的关闭事件
+	    _confer_ape.on(_MessageTypes2.default.CLASS_STATUS_INFO_CHANGE, _this._onClassStatusInfoChange.bind(_this)); //当前会议状态信息发生改变
+	    _confer_ape.on(_MessageTypes2.default.CLASS_DELETE_ROSTER, _this._onClassDeleteRoster.bind(_this)); //当前会议人员离开
+	    _confer_ape.on(_MessageTypes2.default.CLASS_NONENTITY_ROSTER, _this._onClassNonentityRoster.bind(_this)); //当前会议中视频或音频占用channel的nodeId ,在人员列表中不存在
+	    _confer_ape.on(_MessageTypes2.default.CLASS_RECORD_START, _this._onClassRecordStart.bind(_this)); //会议开始录制
+
+
+	    _chat_ape = new _ChatApe2.default();
+	    _chat_ape.on('*', function (type, data) {
+	      return _this._emit(type, data);
+	    });
+
+	    _video_ape = new _VideoApe2.default();
+	    _video_ape.on('*', function (type, data) {
+	      return _this._emit(type, data);
+	    });
+	    _video_ape.on(_MessageTypes2.default.VIDEO_UPDATE, _this.videoUpdate.bind(_this)); //这个监听事件不能删除,需要通知会议模块,检查channel占用
+
+
+	    _audio_ape = new _AudioApe2.default();
+	    _audio_ape.on('*', function (type, data) {
+	      return _this._emit(type, data);
+	    });
+	    _audio_ape.on(_MessageTypes2.default.AUDIO_UPDATE, _this.audioUpdate.bind(_this)); //这个监听事件不能删除,需要通知会议模块,检查channel占用
+
+	    _whiteboard_ape = new _WhiteBoardApe2.default();
+	    _whiteboard_ape.on('*', function (type, data) {
+	      return _this._emit(type, data);
+	    });
+	    //_whiteboard_ape.on(MessageTypes.WHITEBOARD_ANNOTATION_UPDATE, this.annoUpdateHandler.bind(this));
+
+	    _doc_ape = new _DocApe2.default();
+	    _doc_ape.on('*', function (type, data) {
+	      return _this._emit(type, data);
+	    });
+	    _doc_ape.on(_MessageTypes2.default.DOC_UPDATE, _this.docUpdateHandler.bind(_this));
+	    _doc_ape.on(_MessageTypes2.default.DOC_DELETE, _this.docDeleteHandler.bind(_this));
+	    _doc_ape.on(_DocApe2.default.DOC_JOIN_CHANNEL_SUCCESS, _this.docJoinChannelSuccess.bind(_this));
+
+	    //公开外部调用的方法
+	    //class
+	    _this.init = _this._init.bind(_this);
+	    _this.joinClass = _this._joinClass.bind(_this);
+	    _this.leaveClass = _this._leaveClass.bind(_this);
+	    _this.getMcuClientStatus = _this._getMcuClientStatus.bind(_this);
+	    //this.getClassDetail = this._getClassDetail;//停用
+	    _this.getClassStatusInfo = _this._getClassStatusInfo.bind(_this);
+	    _this.sendStartClass = _this._sendStartClass.bind(_this);
+	    _this.sendPauseClass = _this._sendPauseClass.bind(_this);
+	    _this.sendCloseClass = _this._sendCloseClass.bind(_this);
+
+	    //chatApe
+	    _this.sendChatMsg = _this._sendChatMsg.bind(_this);
+
+	    //videoApe
+	    //this.getVideoPlayPath = this._getVideoPlayPath.bind(this);
+	    _this.getVideoPublishPath = _this._getVideoPublishPath.bind(_this);
+	    _this.getVideoAllChannelInfo = _this._getVideoAllChannelInfo.bind(_this);
+	    _this.publishVideo = _this._publishVideo.bind(_this);
+	    _this.stopPublishVideo = _this._stopPublishVideo.bind(_this);
+	    _this.sendVideoBroadcastMsg = _this._sendVideoBroadcastMsg.bind(_this);
+
+	    //audioApe
+	    //this.getAudioPlayPath = this._getPlayAudioPath.bind(this);
+	    _this.getAudioPublishPath = _this._getPublishAudioPath.bind(_this);
+	    _this.getAudioAllChannelInfo = _this._getAudioAllChannelInfo.bind(_this);
+	    _this.publishAudio = _this._publishAudio.bind(_this);
+	    _this.stopPublishAudio = _this._stopPublishAudio.bind(_this);
+	    _this.sendAudioBroadcastMsg = _this.sendAudioCommandMsg.bind(_this);
+
+	    //whiteBoradApe
+	    _this.sendInsertAnnotaion = _this._sendInsertAnnotaion.bind(_this);
+	    //this.sendDeleteAnnotaion=this._sendDeleteAnnotaion;
+	    _this.sendDeleteAllAnnotation = _this._sendDeleteAllAnnotation.bind(_this);
+	    _this.sendDeleteCurPageAnnotation = _this._sendDeleteCurPageAnnotation.bind(_this);
+	    _this.sendGotoPrev = _this._sendGotoPrev.bind(_this);
+
+	    //DocApe
+	    _this.sendDocumentUpload = _this._sendDocumentUpload.bind(_this); //上传文档
+	    _this.sendDocumentSwitchDoc = _this._sendDocumentSwitchDoc.bind(_this); //切换文档
+	    _this.sendDocumentSwitchPage = _this._sendDocumentSwitchPage.bind(_this); //翻页
+	    _this.sendDocumentDelete = _this._sassDeleteDocument.bind(_this);; //删除文档,先通过Sass删除,sass删除成功之后再同步mcu
+	    //this.sendDocumentDeleteAll= this._documentDeleteAll;//删除所有文档
+	    _this.sendDocumentCommand = _this._sendDocumentCommand.bind(_this);; //操作文档(翻页、缩放、滚动...)
+	    _this.getDocImageFullPath = _this._getDocImageFullPath.bind(_this);; //获取文档图片的完整路径
+	    _this.getDocPDFFullPath = _this._getDocPDFFullPath.bind(_this);; //获取文档的完整路径
+	    return _this;
+	  }
+
+	  //mcu异常监听
+
+
+	  _createClass(MessageEntrance, [{
+	    key: '_mcuErrorHandler',
+	    value: function _mcuErrorHandler(_data, _option) {
+	      var option = _option || "";
+	      var errorMessage = { "code": _data, "reson": _MessageTypes2.default.ErrorReson[_data] + " " + option };
+	      loger.error("MCU_ERROR", errorMessage);
+
+	      this._emit(_MessageTypes2.default.ERROR_EVENT, errorMessage);
+
+	      /*    if (_mcuErrorCallBackFun) {
+	            _mcuErrorCallBackFun(errorMessage);
+	          }*/
+	    }
+
+	    //获取当前的状态
+
+	  }, {
+	    key: '_getMcuClientStatus',
+	    value: function _getMcuClientStatus() {
+	      return _GlobalConfig2.default.getCurrentStatus();
+	    }
+
+	    //获取会议信息
+
+	  }, {
+	    key: '_getClassDetail',
+	    value: function _getClassDetail() {
+	      return _GlobalConfig2.default.getClassDetail();
+	    }
+
+	    //获取当前会议的状态信息
+
+	  }, {
+	    key: '_getClassStatusInfo',
+	    value: function _getClassStatusInfo() {
+	      return _GlobalConfig2.default.classStatusInfo;
+	    }
+	    //关闭会议,所有人都退出
+
+	  }, {
+	    key: '_doClassClose',
+	    value: function _doClassClose(_param) {
+	      this._leaveClass();
+	    }
+
+	    //离开会议,断开连接
+
+	  }, {
+	    key: '_doClassExit',
+	    value: function _doClassExit() {
+	      this._leaveClass();
+	    }
+
+	    //当前的会议状态信息发生改变,需要保存会议状态到Sass
+
+	  }, {
+	    key: '_onClassStatusInfoChange',
+	    value: function _onClassStatusInfoChange(_param) {
+	      //如果MCU连接已经断开,不发送
+	      if (_GlobalConfig2.default.getCurrentStatus().code != _GlobalConfig2.default.statusCode_2.code) {
+	        loger.warn("不能保存会议状态", _GlobalConfig2.default.getCurrentStatus());
+	        return;
+	      }
+	      this._sassSaveClassStatusInfo();
+	    }
+
+	    //如果是第一次点击开始上课,需要创建录制时的文件名
+
+	  }, {
+	    key: '_onClassRecordStart',
+	    value: function _onClassRecordStart(_param) {
+	      if (_GlobalConfig2.default.getCurrentStatus().code != _GlobalConfig2.default.statusCode_2.code) {
+	        loger.warn("不能保存会议状态", _GlobalConfig2.default.getCurrentStatus());
+	        return;
+	      }
+	      if (_sass) {
+	        _sass.saveClassRecordContrlInfo(_param);
+	      }
+	    }
+
+	    //有人员离开
+
+	  }, {
+	    key: '_onClassDeleteRoster',
+	    value: function _onClassDeleteRoster(_data) {}
+	    //{"nodeId":nodeId}
+	    //当有人员离开的时候,如果离开的人员已经推流,那么需要停止推流,然后释放channel;
+	    /*    if(_data!=null&&_data.nodeId!=null){
+	      loger.log("有人员离开,检查一下离开的人员是否关闭推流");
+	      if(_video_ape){
+	        _video_ape.stopPublishVideo(_data);
+	      }
+	      if(_audio_ape){
+	        _audio_ape.stopPublishAudio(_data);
+	      }
+	    }*/
+
+
+	    //当前会议中视频或音频占用channel的nodeId ,在人员列表中不存在,这种情况是占用channel的人员掉线或离开的时候没有释放channel
+	    //的占用状态导致,对于这种情况,需要释放掉
+
+	  }, {
+	    key: '_onClassNonentityRoster',
+	    value: function _onClassNonentityRoster(_param) {
+	      if (_param == null || _param.nodeId == null) {
+	        loger.warn("onClassNonentityRoster.参数错误");
+	        return;
+	      }
+	      var data = { "nodeId": _param.nodeId };
+	      if (_video_ape) {
+	        _video_ape.stopPublishVideo(data);
+	      }
+	      if (_audio_ape) {
+	        _audio_ape.stopPublishAudio(data);
+	      }
+	    }
+
+	    //Sass
+	    //初始化
+
+	  }, {
+	    key: '_init',
+	    value: function _init(_param) {
+	      //{"classId":"1653304953","portal":"112.126.80.182:80","userRole":"normal","userId":0}
+	      //判断传入的参数是否存在
+	      if (_param == null || _EngineUtils2.default.isEmptyObject(_param)) {
+	        loger.error('init初始化失败,参数错误');
+	        this._emit(_MessageTypes2.default.MCU_ERROR, _MessageTypes2.default.ERR_CLASS_INIT_PARAM);
+	        return;
+	      }
+	      //判断必要的参数字段值
+	      if (_param.classId == null || isNaN(_param.classId) || _param.portal == null || _param.portal == "") {
+	        loger.error('init初始化失败', _param);
+	        this._emit(_MessageTypes2.default.MCU_ERROR, _MessageTypes2.default.ERR_CLASS_INIT_PARAM);
+	        return;
+	      }
+	      loger.log('init', _param);
+
+	      //保存参数
+	      _GlobalConfig2.default.classId = parseInt(_param.classId);
+	      _GlobalConfig2.default.portal = _param.portal;
+	      _GlobalConfig2.default.userRole = _param.userRole || _ApeConsts2.default.normal;
+	      _GlobalConfig2.default.userId = _param.userId || "0";
+	      _GlobalConfig2.default.userName = _param.userName || "";
+
+	      //获取课堂校验信息
+	      if (_sass) {
+	        _sass.getJoinParams(_GlobalConfig2.default.getClassInfo());
+	      }
+	    }
+
+	    //外部请求加入会议
+
+	  }, {
+	    key: '_joinClass',
+	    value: function _joinClass(_param) {
+	      //_joinClassSuccessCallBackFun = _onSuccess;
+	      //{"userName":"名字","password":""}
+	      if (_param == null || _EngineUtils2.default.isEmptyObject(_param)) {
+	        this._emit(_MessageTypes2.default.MCU_ERROR, _MessageTypes2.default.ERR_CLASS_JOIN_PARAM);
+	        loger.log('不能进入会议,传递的参数不对.', _param);
+	        return;
+	      }
+	      //判断userName
+	      if (_param.userName == null || _param.userName == "") {
+	        loger.log('不能进入会议,传递的参数不对.名字不能为空');
+	        this._emit(_MessageTypes2.default.MCU_ERROR, _MessageTypes2.default.ERR_CLASS_JOIN_PARAM);
+	        return;
+	      }
+
+	      if (_GlobalConfig2.default.userName == null || _GlobalConfig2.default.userName == "") {
+	        _GlobalConfig2.default.userName = _param.userName;
+	      }
+	      _GlobalConfig2.default.password = _param.password || "";
+	      _GlobalConfig2.default.hasCamera = typeof _param.hasCamera == "boolean" ? _param.hasCamera : false;
+	      _GlobalConfig2.default.hasMicrophone = typeof _param.hasMicrophone == "boolean" ? _param.hasMicrophone : false;
+
+	      //debugger;
+	      //开始校验
+	      if (_sass) {
+	        _sass.passwordAndMd5Checking(_GlobalConfig2.default.getClassInfo());
+	      }
+	    }
+
+	    // 用classId向SASS平台获取入会验证信息成功
+
+	  }, {
+	    key: '_sassInitSuccessHandler',
+	    value: function _sassInitSuccessHandler(_data) {
+	      //{"siteId":"h5test","passwordRequired":true,"md5":"de399d5540b3da2fbc1eb0a770d4fd66","code":0,"msType":1}
+	      //储存数据
+	      _GlobalConfig2.default.md5 = _data.md5 || ""; //这个暂时用假数据,后台接口写完就有数据了
+	      _GlobalConfig2.default.msType = _data.msType || 1;
+	      _GlobalConfig2.default.siteId = _data.siteId || "";
+	      _GlobalConfig2.default.classType = _data.meetingType || 0;
+
+	      //host默认需要密码,Sass服务器只判断学生是否需要密码,没有判断老师的
+	      _GlobalConfig2.default.passwordRequired = _data.passwordRequired || false; //md5验证的时候需要Sass返回的值,不能更改
+
+	      loger.log('SASS平台获取入会验证信息成功.');
+
+	      //设置当前的会议状态
+	      _GlobalConfig2.default.setCurrentStatus(_GlobalConfig2.default.statusCode_1);
+	      //返回给客户端初始化成功的数据
+	      var initSuccessCallBackData = {};
+	      initSuccessCallBackData.siteId = _GlobalConfig2.default.siteId;
+	      initSuccessCallBackData.classId = _GlobalConfig2.default.classId;
+	      initSuccessCallBackData.userRole = _GlobalConfig2.default.userRole;
+	      initSuccessCallBackData.userId = _GlobalConfig2.default.userId;
+	      initSuccessCallBackData.userName = _GlobalConfig2.default.userName;
+	      initSuccessCallBackData.classType = _GlobalConfig2.default.classType;
+
+	      //host默认需要密码,Sass服务器只判断学生是否需要密码,没有判断老师的
+	      if (_GlobalConfig2.default.userRole == _ApeConsts2.default.host) {
+	        initSuccessCallBackData.passwordRequired = true;
+	      } else {
+	        initSuccessCallBackData.passwordRequired = _GlobalConfig2.default.passwordRequired;
+	      }
+
+	      this._emit(_MessageTypes2.default.CLASS_INIT_SUCCESS, initSuccessCallBackData);
+
+	      /*    if (_initSuccessCallBackFun) {
+	            _initSuccessCallBackFun(initSuccessCallBackData);
+	          }*/
+	    }
+
+	    // 通过SASS平台验证(密码和MD5)
+
+	  }, {
+	    key: '_sassJoinSuccessHandler',
+	    value: function _sassJoinSuccessHandler(_data) {
+	      //返回值
+	      /* flag	数值型	无		True:成功
+	       Flag:失败
+	       h5_mcu_list	字符串			H5muc列表
+	       maxVideoChannels	数值型			最大视频路数
+	       maxAudioChannels	数值型			最大音频路数
+	       h5Module	数值型			H5开关
+	       ms	字符串			Ms列表
+	       mcu	字符串			Mcu列表
+	       rs	字符串			Rs列表
+	       doc	字符串			Doc列表*/
+	      //获取会议最完整的数据
+	      if (_sass) {
+	        _sass.getClassParam();
+	      }
+	    }
+
+	    //获取会议所有参数 api/meeting/detail.do?      flash中的接口文件是 getClassParam.do
+
+	  }, {
+	    key: '_sassGetClassParamSuccessHandler',
+	    value: function _sassGetClassParamSuccessHandler(_data) {
+	      //console.log(GlobalConfig.classStatusInfo)
+	      loger.log('获取课堂会议的完整信息完成.');
+	      // console.log(_data);
+	      //包含整个会议最全的信息,储存数据
+	      if (_data) {
+	        _GlobalConfig2.default.mcuDelay = _data.mcuDelay || 60; //mcu消息延迟,用于文档模块
+	        _GlobalConfig2.default.className = _data.meetingName || "";
+	        _GlobalConfig2.default.classBeginTime = _data.beginTime || "";
+	        _GlobalConfig2.default.classEndTime = _data.endTime || "";
+	        _GlobalConfig2.default.userIp = _data.userIp || "";
+
+	        _GlobalConfig2.default.maxVideoChannels = _data.maxVideoChannels;
+	        _GlobalConfig2.default.maxAudioChannels = _data.maxAudioChannels;
+
+	        _GlobalConfig2.default.setDocListPrepare(_data.docListPrepare); //提前上传的文档列表
+	        _GlobalConfig2.default.setRecordList(_data.recordList); //录制回放地址
+	        _GlobalConfig2.default.setDocList(_data.docList); //文档地址
+	        _GlobalConfig2.default.setMsList(_data.msList); //推流播流服务器地址
+	        _GlobalConfig2.default.setRsList(_data.rsList); //播放m3u8格式的地址
+	        _GlobalConfig2.default.setMcuList(_data.mcuList); //mcu
+	        _GlobalConfig2.default.setMusicList(_data.musicList); //
+	        _GlobalConfig2.default.setMusicListPrepare(_data.musicListPrepare); //提前上传的声音文件列表
+
+
+	        if (_data.mcuList) {
+	          //MCU地址默认使用第一个
+	          _GlobalConfig2.default.MCUServerIP = _data.mcuList[0].ip || "";
+	          _GlobalConfig2.default.MCUServerPort = _data.mcuList[0].port || "";
+	        }
+
+	        //视频推流播流地址
+	        if (_data.msList) {
+	          //MS地址默认使用第一个
+	          _GlobalConfig2.default.MSServerIP = _data.msList[0].ip || "";
+	          _GlobalConfig2.default.MSServerPort = _data.msList[0].port || "";
+	        }
+
+	        //m3u8播流地址
+	        if (_data.rsList) {
+	          //RS地址默认使用第一个
+	          _GlobalConfig2.default.RSServerIP = _data.rsList[0].ip || "";
+	          _GlobalConfig2.default.RSServerPort = _data.rsList[0].port || "";
+	        }
+
+	        //文档地址
+	        if (_data.docList) {
+	          //doc地址默认使用第一个
+	          _GlobalConfig2.default.DOCServerIP = _data.docList[0].ip || "";
+	          _GlobalConfig2.default.DOCServerPort = _data.docList[0].port || "";
+	        }
+
+	        //record
+	        if (_data.recordList) {
+	          //地址默认使用第一个
+	          _GlobalConfig2.default.RecordServerIP = _data.recordList[0].ip || "";
+	          _GlobalConfig2.default.RecordServerPort = _data.recordList[0].port || "";
+	        }
+	      }
+
+	      if (_data.currentInfo) {
+	        //根据从Sass获取的数据信息,同步最后一次保存的会议状态信息
+	        loger.log("同步最后一次保存过的会议状态信息");
+	        _GlobalConfig2.default.setClassStatusInfo(_data.currentInfo);
+	        console.log(_GlobalConfig2.default.classStatusInfo);
+	      } else {
+	        loger.log("还没有保存过会议状信息");
+	      }
+
+	      //根据用户的userIp获取信息
+	      this.getUserIpInfo();
+	    }
+	    //根据UserIp获取ip信息
+
+	  }, {
+	    key: 'getUserIpInfo',
+	    value: function getUserIpInfo() {
+	      if (_serverCheck) {
+	        _serverCheck.getUserIpInfo("", _GlobalConfig2.default.userIp);
+	      }
+	    }
+	    //MCU MS ip选点完成,开加入MCU
+
+	  }, {
+	    key: '_serverCheckBestIpSuccessHandler',
+	    value: function _serverCheckBestIpSuccessHandler(_data) {
+	      loger.log("_serverCheckBestIpSuccessHandler,IP选点结束");
+	      this._joinMCU();
+	    }
+
+	    //保存会议状态信息
+
+	  }, {
+	    key: '_sassSaveClassStatusInfo',
+	    value: function _sassSaveClassStatusInfo() {
+	      if (_GlobalConfig2.default.isHost) {
+	        //只有加入会议之后才能保存数据
+	        if (_GlobalConfig2.default.getCurrentStatus().code == _GlobalConfig2.default.statusCode_2.code) {
+	          //POST 保存数据
+	          _sass.saveClassStatusInfo({ "classStatusInfo": _GlobalConfig2.default.classStatusInfo }); //保存会议状态信息
+	        } else {
+	          loger.error("不能保存会议数据", _GlobalConfig2.default.getCurrentStatus());
+	        }
+	      } else {
+	        loger.log("没有保存会议状态信息的权限  isHost", _GlobalConfig2.default.isHost);
+	      }
+	    }
+
+	    //保存会态信息成功
+
+	  }, {
+	    key: '_sassSaveClassStatusInfoSuccessHandler',
+	    value: function _sassSaveClassStatusInfoSuccessHandler(_data) {
+	      loger.log('保存会议状态信息成功.');
+	      console.log(_data);
+	    }
+	  }, {
+	    key: '_sassSaveClassRecordInfoSuccessHandler',
+	    value: function _sassSaveClassRecordInfoSuccessHandler(_data) {
+	      loger.log('保存会议录制信息成功.');
+	      console.log(_data);
+	    }
+
+	    //Sass校验流程结束之后,开始加入MCU
+
+	  }, {
+	    key: '_joinMCU',
+	    value: function _joinMCU() {
+	      loger.log('加入底层MCU会议.');
+	      if (_mcu) {
+	        _mcu.joinMCU(_GlobalConfig2.default.getClassInfo());
+	      }
+	    }
+
+	    // MCU 会议成功
+
+	  }, {
+	    key: '_mcuJoinMCUClassSuccessHandler',
+	    value: function _mcuJoinMCUClassSuccessHandler(_data) {
+	      loger.log('MCU 会议成功.');
+	      _GlobalConfig2.default.setCurrentStatus(_GlobalConfig2.default.statusCode_2);
+
+	      //返回给客户端初始化成功的数据
+	      var joinClassSuccessCallBackData = {};
+
+	      joinClassSuccessCallBackData.DOCServerIP = _GlobalConfig2.default.DOCServerIP;
+	      joinClassSuccessCallBackData.DOCServerPort = _GlobalConfig2.default.DOCServerPort;
+
+	      joinClassSuccessCallBackData.classId = _GlobalConfig2.default.classId;
+	      joinClassSuccessCallBackData.className = _GlobalConfig2.default.className;
+	      joinClassSuccessCallBackData.h5Module = _GlobalConfig2.default.h5Module;
+	      joinClassSuccessCallBackData.isHost = _GlobalConfig2.default.isHost;
+	      joinClassSuccessCallBackData.maxAudioChannels = _GlobalConfig2.default.maxAudioChannels;
+	      joinClassSuccessCallBackData.maxVideoChannels = _GlobalConfig2.default.maxVideoChannels;
+	      joinClassSuccessCallBackData.mcuDelay = _GlobalConfig2.default.mcuDelay;
+
+	      joinClassSuccessCallBackData.msType = _GlobalConfig2.default.msType;
+	      joinClassSuccessCallBackData.nodeId = _GlobalConfig2.default.nodeId;
+	      joinClassSuccessCallBackData.password = _GlobalConfig2.default.password;
+	      joinClassSuccessCallBackData.passwordRequired = _GlobalConfig2.default.passwordRequired; //  老师的默认是true
+	      //GlobalConfig.passwordRequired  老师的默认是true
+	      //GlobalConfig.portal=_data.portal;
+	      joinClassSuccessCallBackData.role = _GlobalConfig2.default.role;
+	      joinClassSuccessCallBackData.siteId = _GlobalConfig2.default.siteId;
+	      joinClassSuccessCallBackData.topNodeID = _GlobalConfig2.default.topNodeID;
+	      joinClassSuccessCallBackData.userId = _GlobalConfig2.default.userId;
+	      joinClassSuccessCallBackData.userName = _GlobalConfig2.default.userName;
+	      joinClassSuccessCallBackData.userRole = _GlobalConfig2.default.userRole;
+	      joinClassSuccessCallBackData.userType = _GlobalConfig2.default.userType;
+
+	      joinClassSuccessCallBackData.siteId = _GlobalConfig2.default.siteId;
+	      joinClassSuccessCallBackData.classId = _GlobalConfig2.default.classId;
+	      joinClassSuccessCallBackData.userRole = _GlobalConfig2.default.userRole;
+	      joinClassSuccessCallBackData.userId = _GlobalConfig2.default.userId;
+	      joinClassSuccessCallBackData.passwordRequired = _GlobalConfig2.default.passwordRequired;
+	      joinClassSuccessCallBackData.classType = _GlobalConfig2.default.classType || _ApeConsts2.default.CLASS_TYPE_INTERACT;
+
+	      joinClassSuccessCallBackData.country = _GlobalConfig2.default.country; //国家
+	      joinClassSuccessCallBackData.city = _GlobalConfig2.default.city; //城市
+	      joinClassSuccessCallBackData.province = _GlobalConfig2.default.province; //服务商
+	      joinClassSuccessCallBackData.isp = _GlobalConfig2.default.isp; //服务商
+
+	      loger.log('加入会议成功');
+	      console.log(joinClassSuccessCallBackData);
+
+	      //加入会议成功,广播消息
+	      this._emit(_MessageTypes2.default.CLASS_JOIN_SUCCESS, joinClassSuccessCallBackData);
+	    }
+
+	    //Sass删除文档数据
+
+	  }, {
+	    key: '_sassDeleteDocument',
+	    value: function _sassDeleteDocument(_param) {
+	      if (!_mcu.connected) {
+	        loger.warn(_GlobalConfig2.default.getCurrentStatus());
+	        return;
+	      }
+
+	      //判断传入的参数是否存在
+	      if (_param == null || _EngineUtils2.default.isEmptyObject(_param)) {
+	        loger.error('sassDeleteDocument失败,参数错误', _param);
+	        this._emit(_MessageTypes2.default.MCU_ERROR, _MessageTypes2.default.ERR_DOC_DELETE_FAILED_PARAM);
+	        return;
+	      }
+	      //判断必要的参数字段值
+	      if (_param.itemIdx == null || isNaN(_param.itemIdx) || _param.docId == null || _param.docId == "") {
+	        loger.error('sassDeleteDocument失败', _param);
+	        this._emit(_MessageTypes2.default.MCU_ERROR, _MessageTypes2.default.ERR_DOC_DELETE_FAILED_PARAM);
+	        return;
+	      }
+	      loger.log('_sassDeleteDocument', _param);
+
+	      if (_sass) {
+	        _sass.sassDeleteDocument(_param);
+	      }
+	    }
+
+	    //Sass删除文档成功之后,同步删除MCU数据
+
+	  }, {
+	    key: '_sassDeleteDocumentSuccess',
+	    value: function _sassDeleteDocumentSuccess(_param) {
+	      loger.log('sassDeleteDocumentSuccess', _param);
+	      this._sendDocumentDelete(_param);
+	    }
+
+	    //ConferApe
+	    //开始上课
+
+	  }, {
+	    key: '_sendStartClass',
+	    value: function _sendStartClass(_param) {
+	      if (!_mcu.connected) {
+	        loger.warn(_GlobalConfig2.default.getCurrentStatus());
+	        return;
+	      }
+
+	      if (_confer_ape) {
+	        _confer_ape.startClass(_param);
+	      }
+	    }
+	    //暂停上课
+
+	  }, {
+	    key: '_sendPauseClass',
+	    value: function _sendPauseClass(_param) {
+	      if (!_mcu.connected) {
+	        loger.warn(_GlobalConfig2.default.getCurrentStatus());
+	        return;
+	      }
+	      if (_confer_ape) {
+	        _confer_ape.pauseClass(_param);
+	      }
+	    }
+	    //停止上课
+
+	  }, {
+	    key: '_sendCloseClass',
+	    value: function _sendCloseClass(_param) {
+	      if (!_mcu.connected) {
+	        loger.warn(_GlobalConfig2.default.getCurrentStatus());
+	        return;
+	      }
+	      if (_confer_ape) {
+	        _confer_ape.closeClass(_param);
+	      }
+	    }
+	    // 离开会议
+
+	  }, {
+	    key: '_leaveClass',
+	    value: function _leaveClass() {
+	      if (!_mcu.connected) {
+	        loger.warn(_GlobalConfig2.default.getCurrentStatus());
+	        return;
+	      }
+
+	      //停止推流
+	      if (_video_ape) {
+	        _video_ape.stopPublishVideo();
+	      }
+	      if (_audio_ape) {
+	        _audio_ape.stopPublishAudio();
+	      }
+	      //离开会议
+	      if (_confer_ape) {
+	        _confer_ape.stopRecord();
+	        _confer_ape.leaveClass();
+	      }
+	      //断开MCU连接
+	      if (_mcu) {
+	        _mcu.leaveMCU();
+	        _GlobalConfig2.default.setCurrentStatus(_GlobalConfig2.default.statusCode_3);
+	      }
+	    }
+
+	    //ChatApe
+	    // 发送聊天消息
+
+	  }, {
+	    key: '_sendChatMsg',
+	    value: function _sendChatMsg(_messageInfo) {
+	      if (!_mcu.connected) {
+	        loger.warn(_GlobalConfig2.default.getCurrentStatus());
+	        return;
+	      }
+	      if (_messageInfo === null || _EngineUtils2.default.isEmptyObject(_messageInfo)) {
+	        loger.log('sendChatMsg 传递的参数不对', _messageInfo);
+	        return;
+	      }
+	      if (_chat_ape) {
+	        _chat_ape.sendChatMsg(_messageInfo);
+	      }
+	    }
+
+	    //VidoeApe
+
+	  }, {
+	    key: 'videoUpdate',
+	    value: function videoUpdate(_data) {
+	      //视频同步的消息发送改变,需要通知ferApe模块中的用户更新状态
+	      if (_confer_ape) {
+	        _confer_ape.updaterRosterStatus(_data);
+	      }
+	    }
+	  }, {
+	    key: '_sendVideoBroadcastMsg',
+	    value: function _sendVideoBroadcastMsg(_param) {
+	      if (!_mcu.connected) {
+	        loger.warn(_GlobalConfig2.default.getCurrentStatus());
+	        return;
+	      }
+	      if (_video_ape) {
+	        return _video_ape.sendVideoBroadcastMsg(_param);
+	      }
+	    }
+	  }, {
+	    key: '_getVideoPlayPath',
+	    value: function _getVideoPlayPath(_param) {
+	      if (_video_ape) {
+	        return _video_ape.getPlayVideoPath(_param);
+	      }
+	    }
+	  }, {
+	    key: '_getVideoPublishPath',
+	    value: function _getVideoPublishPath(_param) {
+	      if (!_mcu.connected) {
+	        loger.warn(_GlobalConfig2.default.getCurrentStatus());
+	        return;
+	      }
+
+	      if (_video_ape) {
+	        return _video_ape.getPublishVideoPath(_param);
+	      }
+	    }
+	  }, {
+	    key: '_getVideoAllChannelInfo',
+	    value: function _getVideoAllChannelInfo(_param) {
+	      if (_video_ape) {
+	        return _video_ape.getAllChannelInfo(_param);
+	      }
+	    }
+	  }, {
+	    key: '_publishVideo',
+	    value: function _publishVideo(_param) {
+	      if (!_mcu.connected) {
+	        loger.warn(_GlobalConfig2.default.getCurrentStatus());
+	        return;
+	      }
+	      if (_video_ape) {
+	        return _video_ape.publishVideo(_param);
+	      }
+	    }
+	  }, {
+	    key: '_stopPublishVideo',
+	    value: function _stopPublishVideo(_param) {
+	      if (!_mcu.connected) {
+	        loger.warn(_GlobalConfig2.default.getCurrentStatus());
+	        return;
+	      }
+	      if (_video_ape) {
+	        return _video_ape.stopPublishVideo(_param);
+	      }
+	    }
+
+	    //AudioApe
+
+	  }, {
+	    key: 'audioUpdate',
+	    value: function audioUpdate(_data) {
+	      //音频同步的消息发送改变,需要通知ferApe模块中的用户更新状态
+	      if (_confer_ape) {
+	        _confer_ape.updaterRosterStatus(_data);
+	      }
+	    }
+	  }, {
+	    key: 'sendAudioCommandMsg',
+	    value: function sendAudioCommandMsg(_param) {
+	      if (!_mcu.connected) {
+	        loger.warn(_GlobalConfig2.default.getCurrentStatus());
+	        return;
+	      }
+	      if (_audio_ape) {
+	        return _audio_ape.sendAudioBroadcastMsg(_param);
+	      }
+	    }
+	  }, {
+	    key: '_getPlayAudioPath',
+	    value: function _getPlayAudioPath(_param) {
+	      if (_audio_ape) {
+	        return _audio_ape.getAudioPlayPath(_param);
+	      }
+	    }
+	  }, {
+	    key: '_getPublishAudioPath',
+	    value: function _getPublishAudioPath(_param) {
+	      if (!_mcu.connected) {
+	        loger.warn(_GlobalConfig2.default.getCurrentStatus());
+	        return;
+	      }
+	      if (_audio_ape) {
+	        return _audio_ape.getAudioPublishPath(_param);
+	      }
+	    }
+	  }, {
+	    key: '_getAudioAllChannelInfo',
+	    value: function _getAudioAllChannelInfo(_param) {
+	      if (_audio_ape) {
+	        return _audio_ape.getAllChannelInfo(_param);
+	      }
+	    }
+	  }, {
+	    key: '_publishAudio',
+	    value: function _publishAudio(_param) {
+	      if (!_mcu.connected) {
+	        loger.warn(_GlobalConfig2.default.getCurrentStatus());
+	        return;
+	      }
+	      if (_audio_ape) {
+	        return _audio_ape.publishAudio(_param);
+	      }
+	    }
+	  }, {
+	    key: '_stopPublishAudio',
+	    value: function _stopPublishAudio(_param) {
+	      if (!_mcu.connected) {
+	        loger.warn(_GlobalConfig2.default.getCurrentStatus());
+	        return;
+	      }
+	      if (_audio_ape) {
+	        return _audio_ape.stopPublishAudio(_param);
+	      }
+	    }
+
+	    //WhiteBoardApe
+	    // 添加标注,发送信息
+
+	  }, {
+	    key: '_sendInsertAnnotaion',
+	    value: function _sendInsertAnnotaion(_param) {
+	      if (!_mcu.connected) {
+	        loger.warn(_GlobalConfig2.default.getCurrentStatus());
+	        return;
+	      }
+	      if (_whiteboard_ape) {
+	        _whiteboard_ape.sendInsetAnnotaion(_param);
+	      }
+	    }
+
+	    //删除标注,发送信息
+
+	  }, {
+	    key: '_sendDeleteAnnotaion',
+	    value: function _sendDeleteAnnotaion(_param) {
+	      if (!_mcu.connected) {
+	        loger.warn(_GlobalConfig2.default.getCurrentStatus());
+	        return;
+	      }
+	      if (_whiteboard_ape) {
+	        _whiteboard_ape.sendDeleteAnnotaion(_param);
+	      }
+	    }
+	    //删除当前页面上的所有标注
+
+	  }, {
+	    key: '_sendDeleteCurPageAnnotation',
+	    value: function _sendDeleteCurPageAnnotation(_param) {
+	      if (!_mcu.connected) {
+	        loger.warn(_GlobalConfig2.default.getCurrentStatus());
+	        return;
+	      }
+	      if (_whiteboard_ape) {
+	        _whiteboard_ape.sendDeleteCurPageAnnotation(_param);
+	      }
+	    }
+	    //删除所有标注
+
+	  }, {
+	    key: '_sendDeleteAllAnnotation',
+	    value: function _sendDeleteAllAnnotation(_param) {
+	      if (!_mcu.connected) {
+	        loger.warn(_GlobalConfig2.default.getCurrentStatus());
+	        return;
+	      }
+	      if (_whiteboard_ape) {
+	        _whiteboard_ape.sendDeleteAllAnnotation(_param);
+	      }
+	    }
+	    //返回上一步标注
+
+	  }, {
+	    key: '_sendGotoPrev',
+	    value: function _sendGotoPrev(_param) {
+	      if (_whiteboard_ape) {
+	        _whiteboard_ape.sendGotoPrev(_param);
+	      }
+	    }
+
+	    //DocApe
+	    //获取文档完整路径
+
+	  }, {
+	    key: '_getDocImageFullPath',
+	    value: function _getDocImageFullPath(_param) {
+	      if (_doc_ape) {
+	        return _doc_ape.getDocImageFullPath(_param);
+	      } else {
+	        loger.error("文档模块还没有创建,无法获取");
+	        return [];
+	      }
+	    }
+	  }, {
+	    key: '_getDocPDFFullPath',
+	    value: function _getDocPDFFullPath(_param) {
+	      if (_doc_ape) {
+	        return _doc_ape.getDocPDFFullPath(_param);
+	      } else {
+	        loger.error("文档模块还没有创建,无法获取");
+	        return [];
+	      }
+	    }
+	    //上传文档
+
+	  }, {
+	    key: '_sendDocumentUpload',
+	    value: function _sendDocumentUpload(_param) {
+	      if (!_mcu.connected) {
+	        loger.warn(_GlobalConfig2.default.getCurrentStatus());
+	        return;
+	      }
+	      if (_doc_ape) {
+	        _doc_ape.documentUpload(_param);
+	      }
+	    }
+	    //切换文档
+
+	  }, {
+	    key: '_sendDocumentSwitchDoc',
+	    value: function _sendDocumentSwitchDoc(_param) {
+	      if (!_mcu.connected) {
+	        loger.warn(_GlobalConfig2.default.getCurrentStatus());
+	        return;
+	      }
+	      if (_doc_ape) {
+	        _doc_ape.documentSwitchDoc(_param);
+	      }
+	    }
+	    //操作文档(翻页)
+
+	  }, {
+	    key: '_sendDocumentSwitchPage',
+	    value: function _sendDocumentSwitchPage(_param) {
+	      if (!_mcu.connected) {
+	        loger.warn(_GlobalConfig2.default.getCurrentStatus());
+	        return;
+	      }
+	      if (_doc_ape) {
+	        _doc_ape.documentSwitchPage(_param);
+	      }
+	    }
+	    //操作文档(缩放、滚动...)
+
+	  }, {
+	    key: '_sendDocumentCommand',
+	    value: function _sendDocumentCommand(_param) {
+	      if (!_mcu.connected) {
+	        loger.warn(_GlobalConfig2.default.getCurrentStatus());
+	        return;
+	      }
+	      if (_doc_ape) {
+	        _doc_ape.documentCommand(_param);
+	      }
+	    }
+	    //删除文档
+
+	  }, {
+	    key: '_sendDocumentDelete',
+	    value: function _sendDocumentDelete(_param) {
+	      if (!_mcu.connected) {
+	        loger.warn(_GlobalConfig2.default.getCurrentStatus());
+	        return;
+	      }
+	      if (_doc_ape) {
+	        _doc_ape.documentDelete(_param);
+	      }
+	    }
+	    //删除所有文档
+
+	  }, {
+	    key: '_documentDeleteAll',
+	    value: function _documentDeleteAll(_param) {
+	      if (!_mcu.connected) {
+	        loger.warn(_GlobalConfig2.default.getCurrentStatus());
+	        return;
+	      }
+	      if (_doc_ape) {
+	        _doc_ape.documentDeleteAll(_param);
+	      }
+	    }
+
+	    //// 文档变更,白板也需要做处理
+
+	  }, {
+	    key: 'docUpdateHandler',
+	    value: function docUpdateHandler(_data) {
+	      if (!_mcu.connected) {
+	        loger.warn(_GlobalConfig2.default.getCurrentStatus());
+	        return;
+	      }
+	      loger.log('Doc UpdateId ->');
+	      console.log(_data);
+	      if (_whiteboard_ape) {
+	        _whiteboard_ape.docUpdateHandler(_data);
+	      }
+	    }
+
+	    //文档删除,白板也需要做处理
+
+	  }, {
+	    key: 'docDeleteHandler',
+	    value: function docDeleteHandler(_data) {
+	      if (_whiteboard_ape) {
+	        _whiteboard_ape.docDeleteHandler(_data);
+	      }
+	    }
+
+	    //文档加入频道成功,同步到MCU服务器上的数据
+
+	  }, {
+	    key: 'docJoinChannelSuccess',
+	    value: function docJoinChannelSuccess() {
+	      loger.log("docJoinChannelSuccess  isHost=", _GlobalConfig2.default.isHost);
+	      console.log(_GlobalConfig2.default.docListPrepare);
+	      loger.log("docJoinChannelSuccess  docListPrepare=");
+	      //如果是主持人,那么需要判断一下文档模块同步的数据和从sass获取的文档数据是否相同,如果mcu服务器不存在的,需要上传
+	      if (_GlobalConfig2.default.isHost) {
+	        var _iteratorNormalCompletion = true;
+	        var _didIteratorError = false;
+	        var _iteratorError = undefined;
+
+	        try {
+	          for (var _iterator = _GlobalConfig2.default.docListPrepare[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
+	            var value = _step.value;
+
+	            if (value) {
+	              /* //提前上传的文档文档信息的结构
+	               {
+	                 "MD5": "f3feb3fac8cd3a953bded00e07a0c66b",
+	                   "absoluteLocation": "http://101.200.150.192/DocSharing/data/526661904/20170203-115400026/f3feb3fac8cd3a953bded00e07a0c66b.swf",
+	                   "createUserID": "972",
+	                   "createUserIP": "114.241.81.175",
+	                   "createUserName": "base",
+	                   "dynamicPPT": 0,
+	                   "dynamicTransferStatic": "",
+	                   "id": "8ab3b0ed5a00f2fa015a0219a3df016c",
+	                   "meetingNumber": "",
+	                   "name": "McuClient_v.1.0.1_API.pdf",
+	                   "orderStr": "",
+	                   "pdfSize": 5,
+	                   "processEndTime": "2017-02-03 11:54:31",
+	                   "processRate": 0,
+	                   "processStartTime": "2017-02-03 11:54:27",
+	                   "relativeLocation": "/DocSharing/data/526661904/20170203-115400026/f3feb3fac8cd3a953bded00e07a0c66b.swf",
+	                   "siteID": "h5test",
+	                   "size": 360920,
+	                   "status": 3,
+	                   "type": "pdf",
+	                   "uploadEndTime": "2017-02-03 11:54:27",
+	                   "uploadStartTime": "2017-02-03 11:54:27"
+	               }*/
+
+	              loger.log("判断是否需要把提前上传的文档上传到mcu", value);
+	              var paramInfo = {
+	                "pageNum": value.pdfSize,
+	                "fileName": value.name,
+	                "fileType": value.type,
+	                "relativeUrl": value.relativeLocation,
+	                "url": value.absoluteLocation,
+	                "creatUserId": value.createUserID,
+	                "docId": value.id,
+	                "md5": value.MD5,
+	                "visible": false
+	              };
+
+	              this._sendDocumentUpload(paramInfo);
+	            }
+	          }
+	        } catch (err) {
+	          _didIteratorError = true;
+	          _iteratorError = err;
+	        } finally {
+	          try {
+	            if (!_iteratorNormalCompletion && _iterator.return) {
+	              _iterator.return();
+	            }
+	          } finally {
+	            if (_didIteratorError) {
+	              throw _iteratorError;
+	            }
+	          }
+	        }
+	      }
+	    }
+	  }]);
+
+	  return MessageEntrance;
+	}(_Emiter3.default);
+
+	var _default = MessageEntrance;
+	exports.default = _default;
+	;
+
+	var _temp = function () {
+	  if (typeof __REACT_HOT_LOADER__ === 'undefined') {
+	    return;
+	  }
+
+	  __REACT_HOT_LOADER__.register(loger, 'loger', 'D:/work/McuClient/src/EngineEntrance.js');
+
+	  __REACT_HOT_LOADER__.register(_sdkInfo, '_sdkInfo', 'D:/work/McuClient/src/EngineEntrance.js');
+
+	  __REACT_HOT_LOADER__.register(_sass, '_sass', 'D:/work/McuClient/src/EngineEntrance.js');
+
+	  __REACT_HOT_LOADER__.register(_serverCheck, '_serverCheck', 'D:/work/McuClient/src/EngineEntrance.js');
+
+	  __REACT_HOT_LOADER__.register(_mcu, '_mcu', 'D:/work/McuClient/src/EngineEntrance.js');
+
+	  __REACT_HOT_LOADER__.register(_confer_ape, '_confer_ape', 'D:/work/McuClient/src/EngineEntrance.js');
+
+	  __REACT_HOT_LOADER__.register(_chat_ape, '_chat_ape', 'D:/work/McuClient/src/EngineEntrance.js');
+
+	  __REACT_HOT_LOADER__.register(_video_ape, '_video_ape', 'D:/work/McuClient/src/EngineEntrance.js');
+
+	  __REACT_HOT_LOADER__.register(_audio_ape, '_audio_ape', 'D:/work/McuClient/src/EngineEntrance.js');
+
+	  __REACT_HOT_LOADER__.register(_doc_ape, '_doc_ape', 'D:/work/McuClient/src/EngineEntrance.js');
+
+	  __REACT_HOT_LOADER__.register(_whiteboard_ape, '_whiteboard_ape', 'D:/work/McuClient/src/EngineEntrance.js');
+
+	  __REACT_HOT_LOADER__.register(MessageEntrance, 'MessageEntrance', 'D:/work/McuClient/src/EngineEntrance.js');
+
+	  __REACT_HOT_LOADER__.register(_default, 'default', 'D:/work/McuClient/src/EngineEntrance.js');
+	}();
+
+	;
+
+/***/ },
+/* 3 */
+/***/ function(module, exports) {
+
+	'use strict';
+
+	Object.defineProperty(exports, "__esModule", {
+	  value: true
+	});
+
+	var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
+
+	function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
+
+	var Emiter = function () {
+	  function Emiter() {
+	    _classCallCheck(this, Emiter);
+
+	    this.MAPS = {};
+	  }
+
+	  _createClass(Emiter, [{
+	    key: 'on',
+	    value: function on(eid, elistener) {
+	      if (eid && elistener) {
+	        var stub = this.MAPS[eid];
+	        if (!stub) {
+	          return this.MAPS[eid] = [elistener];
+	        }
+	        stub.push(elistener);
+	      }
+	    }
+	  }, {
+	    key: 'off',
+	    value: function off(eid, elistener) {
+	      if (eid) {
+	        var stub = this.MAPS[eid];
+	        if (stub) {
+	          if (elistener) {
+	            return stub.splice(stub.indexOf(elistener), 1);
+	          }
+	          stub.length = 0;
+	        }
+	      }
+	    }
+	  }, {
+	    key: '_emit',
+	    value: function _emit(eid, data) {
+	      if (eid) {
+	        //eid=* broadcast
+	        var asteriskStub = this.MAPS['*'];
+	        if (asteriskStub && asteriskStub.length) {
+	          asteriskStub.forEach(function (elistener) {
+	            elistener(eid, data);
+	          });
+	        }
+
+	        // eid= normal
+	        var stub = this.MAPS[eid];
+	        if (stub && stub.length) {
+	          stub.forEach(function (elistener) {
+	            elistener(data);
+	          });
+	        }
+	      }
+	    }
+	  }]);
+
+	  return Emiter;
+	}();
+
+	var _default = Emiter;
+	exports.default = _default;
+	;
+
+	var _temp = function () {
+	  if (typeof __REACT_HOT_LOADER__ === 'undefined') {
+	    return;
+	  }
+
+	  __REACT_HOT_LOADER__.register(Emiter, 'Emiter', 'D:/work/McuClient/src/Emiter.js');
+
+	  __REACT_HOT_LOADER__.register(_default, 'default', 'D:/work/McuClient/src/Emiter.js');
+	}();
+
+	;
+
+/***/ },
+/* 4 */
+/***/ function(module, exports, __webpack_require__) {
+
+	'use strict';
+
+	Object.defineProperty(exports, "__esModule", {
+	    value: true
+	});
+
+	var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
+
+	var _Emiter2 = __webpack_require__(3);
+
+	var _Emiter3 = _interopRequireDefault(_Emiter2);
+
+	var _Loger = __webpack_require__(5);
+
+	var _Loger2 = _interopRequireDefault(_Loger);
+
+	var _MessageTypes = __webpack_require__(6);
+
+	var _MessageTypes2 = _interopRequireDefault(_MessageTypes);
+
+	var _GlobalConfig = __webpack_require__(7);
+
+	var _GlobalConfig2 = _interopRequireDefault(_GlobalConfig);
+
+	var _md = __webpack_require__(15);
+
+	var _md2 = _interopRequireDefault(_md);
+
+	var _ApeConsts = __webpack_require__(8);
+
+	var _ApeConsts2 = _interopRequireDefault(_ApeConsts);
+
+	var _iphunter = __webpack_require__(19);
+
+	var _iphunter2 = _interopRequireDefault(_iphunter);
+
+	var _fetchJsonp = __webpack_require__(20);
+
+	var _fetchJsonp2 = _interopRequireDefault(_fetchJsonp);
+
+	function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
+
+	function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
+
+	function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }
+
+	function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
+
+	// 日志对象
+	var loger = _Loger2.default.getLoger('Sass');
+
+	var confInfo = {};
+
+	var Sass = function (_Emiter) {
+	    _inherits(Sass, _Emiter);
+
+	    function Sass() {
+	        _classCallCheck(this, Sass);
+
+	        return _possibleConstructorReturn(this, (Sass.__proto__ || Object.getPrototypeOf(Sass)).call(this));
+	        //window.findIpInfoCallback=this._findIpInfoCallback.bind(this);
+	    }
+
+	    ///////////////////////////////////////Sass 接口///////////////////////////////////////////////////
+	    //Sass init初始化获取课堂校验信息-----------------------------------------------------------------
+
+
+	    _createClass(Sass, [{
+	        key: 'getJoinParams',
+	        value: function getJoinParams(_initInfo) {
+	            var _this2 = this;
+
+	            /* 获取用于加入课堂的参数
+	             /3m/api/meeting/joinParams.do
+	             参数 (application/x-www-form-urlencoded):
+	             名称	        类型	可选	默认值	说明
+	             meetingNumber	String	否   	null	课堂号
+	             userID	        String	是	     0	   用户id
+	             返回 (application/json):
+	             code	int	0 正常
+	             1 课堂号必填
+	             2 无效的课堂号
+	             3 没有对应的站点
+	             4 站点已过期
+	             siteId	String	站点号
+	             passwordRequired	Boolean	是否需要输入密码
+	             md5	String	用于后续加入课堂验证
+	             msType	int	媒体服务器类型
+	             classType 课堂类型
+	             */
+	            var url = 'http://' + _initInfo.portal + '/3m/api/meeting/joinParams.do?meetingNumber=' + _initInfo.classId + '&userID=' + _initInfo.userId;
+	            loger.log('1.初始化init获取课堂校验信息.');
+	            console.log(url);
+	            console.log(_initInfo);
+	            fetch(url, {
+	                timeout: 5000
+	            }).then(function (ret) {
+	                if (ret.ok) {
+	                    return ret.json();
+	                } else {
+	                    loger.error('\u521D\u59CB\u5316init\u83B7\u53D6\u8BFE\u5802\u6821\u9A8C\u4FE1\u606F-\u7F51\u7EDC\u5F02\u5E38.\u72B6\u6001\u7801:' + ret.status);
+	                    _this2._emit(_MessageTypes2.default.MCU_ERROR, _MessageTypes2.default.ERR_CLASS_INIT_NETWORK);
+	                    throw '';
+	                }
+	            }).then(function (ret) {
+	                //  code	int	0 正常
+	                // 1 课堂号必填
+	                //2 无效的课堂号
+	                //3 没有对应的站点
+	                //4 站点已过期
+	                if (ret.code === 0) {
+	                    loger.log('初始化init获取课堂校验信息-完成');
+	                    _this2._emit(Sass.CLASS_INIT_SUCCESS, ret);
+	                } else if (ret.code === 1) {
+	                    //loger.warn('Sass获取课堂校验信息失败.');
+	                    _this2._emit(_MessageTypes2.default.MCU_ERROR, _MessageTypes2.default.ERR_CLASS_INIT_FAILED_1);
+	                } else if (ret.code === 2) {
+	                    //loger.warn('Sass获取课堂校验信息失败.');
+	                    _this2._emit(_MessageTypes2.default.MCU_ERROR, _MessageTypes2.default.ERR_CLASS_INIT_FAILED_2);
+	                } else if (ret.code === 3) {
+	                    //loger.warn('Sass获取课堂校验信息失败.');
+	                    _this2._emit(_MessageTypes2.default.MCU_ERROR, _MessageTypes2.default.ERR_CLASS_INIT_FAILED_3);
+	                } else if (ret.code === 4) {
+	                    //loger.warn('Sass获取课堂校验信息失败.');
+	                    _this2._emit(_MessageTypes2.default.MCU_ERROR, _MessageTypes2.default.ERR_CLASS_INIT_FAILED_4);
+	                } else {
+	                    _this2._emit(_MessageTypes2.default.MCU_ERROR, _MessageTypes2.default.ERR_CLASS_INIT_FAILED, ret);
+	                }
+	            }).catch(function (err) {
+	                loger.error('\u521D\u59CB\u5316init\u83B7\u53D6\u8BFE\u5802\u6821\u9A8C\u4FE1\u606F-\u5F02\u5E38.\u72B6\u6001\u7801:' + err);
+	                _this2._emit(_MessageTypes2.default.MCU_ERROR, _MessageTypes2.default.ERR_CLASS_INIT_PROTOCOL, err);
+	            });
+	        }
+
+	        // Sass校验开始-->密码校验(如果需要密码)--->MD5校验----------------------------------------------------
+
+	    }, {
+	        key: 'passwordAndMd5Checking',
+	        value: function passwordAndMd5Checking(_param) {
+	            loger.log('2.开始Sass校验');
+	            console.log(_param);
+	            confInfo = _param;
+	            // 密码校验
+	            if (confInfo.passwordRequired === 'true' || confInfo.passwordRequired === true) {
+	                this.sendPWDChecking();
+	                return;
+	            }
+	            // MD5校验
+	            this.sendMD5Checking();
+	        }
+
+	        // 入会密码校验---------------------------------------------------------------------------------------
+
+	    }, {
+	        key: 'sendPWDChecking',
+	        value: function sendPWDChecking() {
+	            var _this3 = this;
+
+	            //let url = `http://${classInfo.portal}/3m/getCheckMeetinig.do?siteId=${classInfo.siteId}&classId=${classInfo.classId}&password=${classInfo.password}`;
+	            /*
+	             /3m/api/meeting/signIn.do
+	             siteId  站点号
+	             classId  课堂号(meetingNumber)
+	             isTeacher 是否是老师:1 是 0 否
+	             password 输入的密码
+	             // 请求格式 http://112.126.80.182/3m/api/meeting/signIn.do?siteId=h5test&classId=526661904&password=111111&isTeacher=0
+	             */
+	            //判断是否是老师
+	            var isTeacher = 0;
+	            if (confInfo.userRole == _ApeConsts2.default.host) {
+	                isTeacher = 1;
+	            }
+
+	            var url = 'http://' + confInfo.portal + '/3m/api/meeting/signIn.do?siteId=' + confInfo.siteId + '&classId=' + confInfo.classId + '&isTeacher=' + isTeacher + '&password=' + confInfo.password;
+	            loger.log('3.会议密码校验', url);
+	            fetch(url, {
+	                timeout: 5000
+	            }).then(function (ret) {
+	                if (ret.status === 200) {
+	                    return ret.text();
+	                } else {
+	                    loger.error('\u4F1A\u8BAE\u5BC6\u7801\u6821\u9A8C-\u7F51\u7EDC\u5F02\u5E38.\u72B6\u6001\u7801:' + ret.status);
+	                    _this3._emit(_MessageTypes2.default.MCU_ERROR, _MessageTypes2.default.ERR_CLASS_JOIN_NETWORK);
+	                    throw '';
+	                }
+	            }).then(function (ret) {
+	                var rectObj = JSON.parse(ret);
+	                if (rectObj.flag === 'false' || rectObj.flag === false) {
+	                    loger.error('\u4F1A\u8BAE\u5BC6\u7801\u6821\u9A8C-\u5931\u8D25.');
+	                    _this3._emit(_MessageTypes2.default.MCU_ERROR, _MessageTypes2.default.ERR_CLASS_PASSWORD_WRONG);
+	                    return;
+	                }
+	                if (rectObj.flag === 'true' || rectObj.flag === true) {
+	                    loger.log('\u4F1A\u8BAE\u5BC6\u7801\u6821\u9A8C-\u6210\u529F.');
+	                    _this3.sendMD5Checking();
+	                    return;
+	                }
+	                loger.error('\u4F1A\u8BAE\u5BC6\u7801\u6821\u9A8C-\u534F\u8BAE\u5F02\u5E38.', rectObj);
+	                _this3._emit(_MessageTypes2.default.MCU_ERROR, _MessageTypes2.default.ERR_CLASS_JOIN_PROTOCOL);
+	            }).catch(function (err) {
+	                loger.error('\u4F1A\u8BAE\u5BC6\u7801\u6821\u9A8C-\u5F02\u5E38.\u72B6\u6001\u7801:' + err);
+	                _this3._emit(_MessageTypes2.default.MCU_ERROR, _MessageTypes2.default.ERR_CLASS_JOIN_FAILED);
+	            });
+	        }
+
+	        //MD5校验-----------------------------------------------------------------------------------------
+
+	    }, {
+	        key: 'sendMD5Checking',
+	        value: function sendMD5Checking() {
+	            var _this4 = this;
+
+	            var url = 'http://' + confInfo.portal + '/3m/meeting/md5CheckMeeting.do?siteId=' + confInfo.siteId + '&meetingNumber=' + confInfo.classId + '&userId=' + confInfo.userId + '&userName=' + confInfo.userName + '&userType=' + confInfo.userType + '&nopassword=' + confInfo.passwordRequired + '&md5=' + confInfo.md5;
+	            loger.log('4.MD5校验', url);
+	            fetch(url, {
+	                timeout: 5000
+	            }).then(function (ret) {
+	                if (ret.status === 200) {
+	                    return ret.json();
+	                } else {
+	                    loger.error('MD5\u6821\u9A8C-\u7F51\u7EDC\u5F02\u5E38.\u72B6\u6001\u7801:' + ret.status);
+	                    _this4._emit(_MessageTypes2.default.MCU_ERROR, _MessageTypes2.default.ERR_CLASS_JOIN_NETWORK);
+	                    throw '';
+	                }
+	            }).then(function (ret) {
+	                if (ret.flag == "true" || ret.flag == true) {
+	                    /*    if (ret.h5_mcu_list) {
+	                     let server = ret.h5_mcu_list.split(";")[0];
+	                     confInfo.MCUServerIP = server.split(":")[0];
+	                     confInfo.MCUServerPort = server.split(":")[1];
+	                       GlobalConfig.MCUServerIP=confInfo.MCUServerIP;
+	                     GlobalConfig.MCUServerPort=confInfo.MCUServerPort;
+	                     }
+	                     confInfo.maxVideoChannels = ret.maxVideoChannels;
+	                     confInfo.maxAudioChannels = ret.maxAudioChannels;
+	                     confInfo.maxMediaChannels = confInfo.maxVideoChannels + confInfo.maxAudioChannels;
+	                       GlobalConfig.maxVideoChannels=confInfo.maxVideoChannels;
+	                     GlobalConfig.maxAudioChannels=confInfo.maxAudioChannels;
+	                     GlobalConfig.maxMediaChannels=confInfo.maxMediaChannels;*/
+	                    loger.log('MD5校验完成');
+	                    console.log(ret);
+	                    _this4._emit(Sass.SUCCESS, ret);
+	                } else {
+	                    loger.log('MD5校验-失败.');
+	                    _this4._emit(_MessageTypes2.default.MCU_ERROR, _MessageTypes2.default.ERR_CLASS_MD5_WRONG);
+	                }
+	            }).catch(function (err) {
+	                loger.error('MD5\u6821\u9A8C-\u5F02\u5E38.\u72B6\u6001\u7801:' + err);
+	                _this4._emit(_MessageTypes2.default.MCU_ERROR, _MessageTypes2.default.ERR_CLASS_JOIN_FAILED);
+	            });
+	        }
+
+	        // 获取会议基本详情------------------------------------------------------------------------------------
+
+	    }, {
+	        key: 'getClassDetail',
+	        value: function getClassDetail() {
+	            var _this5 = this;
+
+	            var url = 'http://' + confInfo.portal + '/3m/meeting/getClassH5.do?classNumber=' + confInfo.classId;
+	            loger.log('获取Class详情.', url);
+	            fetch(url, {
+	                timeout: 5000
+	            }).then(function (ret) {
+	                if (ret.ok) {
+	                    return ret.json();
+	                } else {
+	                    loger.error('\u83B7\u53D6Class\u8BE6\u60C5-\u7F51\u7EDC\u5F02\u5E38.\u72B6\u6001\u7801:' + ret.status);
+	                    _this5._emit(_MessageTypes2.default.MCU_ERROR, _MessageTypes2.default.ERR_GET_CLASS_DETAIL);
+	                    throw '';
+	                }
+	            }).then(function (ret) {
+	                if (ret.errorCode === 0) {
+	                    loger.log('获取Class详情完成');
+	                    _this5._emit(Sass.CLASS_GET_CLASS_DETAIL, ret);
+	                } else {
+	                    loger.warn('获取Class详情失败.');
+	                    _this5._emit(_MessageTypes2.default.MCU_ERROR, _MessageTypes2.default.ERR_GET_CLASS_DETAIL);
+	                }
+	            }).catch(function (err) {
+	                loger.error('\u83B7\u53D6Class\u8BE6\u60C5\u5F02\u5E38.\u72B6\u6001\u7801:' + err);
+	                _this5._emit(_MessageTypes2.default.MCU_ERROR, _MessageTypes2.default.ERR_GET_CLASS_DETAIL);
+	            });
+	        }
+
+	        //获取课堂会议的完整信息--------------------------------------------------------------------------------
+
+	    }, {
+	        key: 'getClassParam',
+	        value: function getClassParam() {
+	            var _this6 = this;
+
+	            /*
+	             参数 (application/x-www-form-urlencoded):
+	             名称	类型	可选	默认值	说明
+	             meetingNumber	String	否	null	课堂号
+	             timestamp	String	否	null	时间戳
+	             authId	String	否	null	验证信息 md5(meetingNumber + timestamp)
+	             返回 (application/json):
+	             名称	类型	说明
+	             code	int	0 正常
+	             1 课堂号必填
+	             2 无效的课堂号
+	             3 没有对应的站点
+	             4 站点已过期
+	             siteId	String	站点号
+	             meetingNumber	String	课堂号 对应的是classId
+	             */
+	            var timestamp = new Date().getTime();
+	            var authId = (0, _md2.default)(confInfo.classId + "" + timestamp); //课堂号+时间戳 的字符串,转成MD5
+	            var url = 'http://' + confInfo.portal + '/3m/api/meeting/detail.do?meetingNumber=' + confInfo.classId + '&timestamp=' + timestamp + '&authId=' + authId;
+	            loger.log('5.获取课堂会议的完整信息 ');
+	            console.log(url);
+	            fetch(url, {
+	                timeout: 5000
+	            }).then(function (ret) {
+	                if (ret.ok) {
+	                    return ret.json();
+	                } else {
+	                    loger.error('\u83B7\u53D6\u8BFE\u5802\u4F1A\u8BAE\u7684\u5B8C\u6574\u4FE1\u606F-\u7F51\u7EDC\u5F02\u5E38.\u72B6\u6001\u7801:' + ret.status);
+	                    _this6._emit(_MessageTypes2.default.MCU_ERROR, _MessageTypes2.default.ERR_GET_CLASS_PARAML);
+
+	                    throw '';
+	                }
+	            }).then(function (ret) {
+	                if (ret.code === 0) {
+	                    loger.log('获取课堂会议的完整信息完成');
+	                    _this6._emit(Sass.CLASS_GET_CLASS_PARAM, ret);
+	                } else {
+	                    loger.warn('获取课堂会议的完整信息 失败.');
+	                    _this6._emit(_MessageTypes2.default.MCU_ERROR, _MessageTypes2.default.ERR_GET_CLASS_PARAML);
+	                }
+	            }).catch(function (err) {
+	                loger.error('\u83B7\u53D6\u8BFE\u5802\u4F1A\u8BAE\u7684\u5B8C\u6574\u4FE1\u606F\u5F02\u5E38.\u72B6\u6001\u7801:' + err);
+	                _this6._emit(_MessageTypes2.default.MCU_ERROR, _MessageTypes2.default.ERR_GET_CLASS_PARAML);
+	            });
+	        }
+
+	        //删除文档-----------------------------------------------------------------------------------------------------
+	        /*
+	         删除课堂中的文档,即删除课堂与文档的关联
+	         /api/document/deleteRelation.do
+	         参数
+	         docId       文档的唯一id
+	         classId     课堂号
+	         timestamp   时间戳
+	         authId       md5(docId+classId+timestamp)
+	         返回 (application/json):
+	         0 成功, 1 验证信息错误
+	         */
+
+	    }, {
+	        key: 'sassDeleteDocument',
+	        value: function sassDeleteDocument(_param) {
+	            var _this7 = this;
+
+	            var timestamp = new Date().getTime();
+	            var authId = (0, _md2.default)(_param.docId + "" + _param.classId + "" + timestamp); // docId+classId+timestamp的字符串,转成MD5
+	            var url = 'http://' + confInfo.portal + '/3m/api/document/deleteRelation.do?docId=' + _param.docId + '&classId=' + confInfo.classId + '&timestamp=' + timestamp + '&authId=' + authId;
+	            loger.log('sassDeleteDocument', url);
+
+	            fetch(url, {
+	                timeout: 5000
+	            }).then(function (ret) {
+	                if (ret.ok) {
+	                    return ret.json();
+	                } else {
+	                    loger.error('sassDeleteDocument-\u7F51\u7EDC\u5F02\u5E38.\u72B6\u6001\u7801:' + ret.status);
+	                    _this7._emit(_MessageTypes2.default.MCU_ERROR, _MessageTypes2.default.ERR_DOC_DELETE_FAILED);
+
+	                    throw '';
+	                }
+	            }).then(function (ret) {
+	                if (ret.code === 0) {
+	                    loger.log('sassDeleteDocument 完成');
+	                    _this7._emit(Sass.DELETE_DOCUMENT_SUCCESS, _param);
+	                } else {
+	                    loger.warn('sassDeleteDocumnt 失败.');
+	                    _this7._emit(_MessageTypes2.default.MCU_ERROR, _MessageTypes2.default.ERR_DOC_DELETE_FAILED);
+	                }
+	            }).catch(function (err) {
+	                loger.error('sassDeleteDocument\u5F02\u5E38.\u72B6\u6001\u7801:' + err);
+	                _this7._emit(_MessageTypes2.default.MCU_ERROR, _MessageTypes2.default.ERR_DOC_DELETE_FAILED);
+	            });
+	        }
+
+	        //保存课堂的当前信息-------------------------------------------------------------------------------------------------
+	        /*保存课堂的当前信息,首次是插入,后面是更新
+	         /api/meeting/saveInfo.do
+	         参数(application/x-www-form-urlencoded):
+	         info Json字符串课堂信息,由前端自己维护
+	         classId  课堂号
+	         timestamp  时间戳
+	         authId  做基本验证,md5(classId+timestamp)
+	           返回 (application/json):
+	         code 0 成功  1 课堂号为空 2 无效的课堂号 3 验证信息错误*/
+
+	    }, {
+	        key: 'saveClassStatusInfo',
+	        value: function saveClassStatusInfo(_param) {
+	            var _this8 = this;
+
+	            //{"classStatusInfo":classStatusInfo}
+	            var timestamp = new Date().getTime();
+	            var authId = (0, _md2.default)(confInfo.classId + "" + timestamp); // (classId+timestamp)的字符串,转成MD5
+	            var classStatusInfo = JSON.stringify(_param.classStatusInfo);
+	            var url = 'http://' + confInfo.portal + '/3m/api/meeting/saveInfo.do';
+	            loger.log('saveClassStatusInfo', url);
+	            fetch(url, {
+	                method: 'POST',
+	                headers: {
+	                    "Content-Type": "application/x-www-form-urlencoded"
+	                },
+	                body: 'classId=' + confInfo.classId + '&info=' + classStatusInfo + '&timestamp=' + timestamp + '&authId=' + authId,
+	                timeout: 5000
+	            }).then(function (ret) {
+	                if (ret.ok) {
+	                    return ret.json();
+	                } else {
+	                    loger.error('saveClassStatusInfo-\u7F51\u7EDC\u5F02\u5E38.\u72B6\u6001\u7801:' + ret.status);
+	                    //this._emit(MessageTypes.MCU_ERROR,MessageTypes.ERR_DOC_DELETE_FAILED);
+	                    throw '';
+	                }
+	            }).then(function (ret) {
+	                if (ret.code === 0) {
+	                    loger.log('saveClassStatusInfo 完成');
+	                    _this8._emit(Sass.CLASS_SAVE_STATUS_INFO_SUCCESS, _param);
+	                } else if (ret.code === 1) {
+	                    loger.log('saveClassStatusInfo 失败 课堂号为空');
+	                } else if (ret.code === 2) {
+	                    loger.log('saveClassStatusInfo 失败 无效的课堂号');
+	                } else if (ret.code === 3) {
+	                    loger.log('saveClassStatusInfo 失败 验证信息错误');
+	                } else {
+	                    loger.warn('saveClassStatusInfo 失败.', ret);
+	                    //this._emit(MessageTypes.MCU_ERROR,MessageTypes.ERR_DOC_DELETE_FAILED);
+	                }
+	            }).catch(function (err) {
+	                loger.error('saveClassStatusInfo.\u72B6\u6001\u7801:' + err);
+	                //this._emit(MessageTypes.MCU_ERROR,MessageTypes.ERR_DOC_DELETE_FAILED);
+	            });
+	        }
+
+	        //保存录制的信息,主要是录制文件的名称,必须和MCU录制的文件名相同
+
+	    }, {
+	        key: 'saveClassRecordContrlInfo',
+	        value: function saveClassRecordContrlInfo(_param) {
+	            var _this9 = this;
+
+	            loger.log('保存开始录制信息');
+	            var key = "3mang123A";
+	            var siteID = _GlobalConfig2.default.siteId;
+	            var meetingID = _GlobalConfig2.default.classId;
+	            var userID = _GlobalConfig2.default.userId;
+	            var userName = _GlobalConfig2.default.userName;
+	            var meetingName = _GlobalConfig2.default.className;
+	            var startTime = _GlobalConfig2.default.classBeginTime;
+	            var endTime = _GlobalConfig2.default.classEndTime;
+	            var playUrl = "";
+	            var streamName = _GlobalConfig2.default.recordFileName;
+	            var confRecordFileName = _GlobalConfig2.default.recordFileName;
+	            var downloadUrl = "";
+	            var recordStatus = _GlobalConfig2.default.classStatus;
+	            var recordTimestamp = _GlobalConfig2.default.classTimestamp;
+
+	            var timestamp = new Date().getTime();
+	            var authId = (0, _md2.default)(key + siteID + meetingID + timestamp);
+	            var url = 'http://' + confInfo.portal + '/3m/recordingMeeting/insertRecordingMeeting.do?siteID=' + siteID + '&meetingID=' + meetingID + '&userID=' + userID + '&userName=' + userName + '&meetingName=' + meetingName + '&startTime=' + startTime + '&endTime=' + endTime + '&playUrl=' + playUrl + '&streamName=' + streamName + '&downloadUrl=' + downloadUrl + '&configFile=' + confRecordFileName + '&timestamp=' + timestamp + '&recordTimestamp=' + recordTimestamp + '&authId=' + authId;
+	            loger.log('saveClassRecordContrlInfo', url);
+
+	            fetch(url, {
+	                timeout: 5000
+	            }).then(function (ret) {
+	                if (ret.ok) {
+	                    return ret.json();
+	                } else {
+	                    loger.error('\u4FDD\u5B58\u5F00\u59CB\u5F55\u5236\u4FE1\u606F-\u7F51\u7EDC\u5F02\u5E38.\u72B6\u6001\u7801:' + ret.status);
+	                    throw '';
+	                }
+	            }).then(function (ret) {
+	                if (ret.errorCode === 0) {
+	                    loger.log('保存开始录制信息 完成');
+	                    _this9._emit(Sass.CLASS_SAVE_RECORD_INFO_SUCCESS, _param);
+	                } else {
+	                    loger.warn('保存开始录制信息 失败.', ret);
+	                }
+	            }).catch(function (err) {
+	                loger.error('\u4FDD\u5B58\u5F00\u59CB\u5F55\u5236\u4FE1\u606F\u5F02\u5E38.\u72B6\u6001\u7801:' + err);
+	            });
+	        }
+	        /*
+	            //根据userIp获取ip相关的信息,参数是userIp
+	            getUserIpInfo(token, userIp) {
+	                let userIpInfo = new Object;
+	                userIpInfo.ret = -1;
+	                let ip = userIp;
+	                let md5Str = MD5("addr=" + ip + "&token=b657c3507b324353e09c1958ee956a98efceb3e3");//("addr=" + ip + "&token=b657c3507b324353e09c1958ee956a98efceb3e3"),转成MD5
+	                let timestamp=new Date().getTime();
+	                //let location = "http://ipapi.ipip.net/find?addr=" + ip + "&callback=?&sid=14&uid=5237&sig=" + $.md5("addr=" + ip + "&token=b657c3507b324353e09c1958ee956a98efceb3e3");
+	                let location = `http://ipapi.ipip.net/find?addr=${ip}&callback=findIpInfoCallback&sid=14&uid=5237&sig=${md5Str}&_=${timestamp}`;
+	                loger.log('getUserIpInfo ', userIp,location);
+	        
+	                fetchJsonp(location, {
+	                    timeout: 3000,
+	                    jsonpCallback: 'findIpInfoCallback'
+	                }) .then(function(response) {
+	                    return response.json()
+	                }).then(function(json) {
+	                    loger.log('getUserIpInfo parsed json', json)
+	                }).catch(function(ex) {
+	                    loger.log('getUserIpInfo parsing failed', ex.message)
+	                })
+	            }*/
+
+	        /* //获取ip信息返回
+	         _findIpInfoCallback(_param){
+	             loger.log("getUserIpInfo->findIpInfoCallback",_param);
+	             let userIpInfo={};
+	             userIpInfo.ret=-1;
+	             if(_param){
+	                 userIpInfo.ret = _param.ret;
+	                 userIpInfo.country = _param.data[0];//国家
+	                 userIpInfo.province = _param.data[1];//省份
+	                 userIpInfo.city = _param.data[2];//城市
+	                 userIpInfo.isp = _param.data[4];//运营商
+	             }
+	             this._emit(Sass.SEVER_CHECK_GET_USER_IP_INFO, userIpInfo);
+	         }
+	           //获取最快的MCU服务器地址,参数是一个ip数组
+	         getBestMcuServer(_param) {
+	             loger.log('getBestMcuServer ', _param);
+	             iphunter(_param, function (fatest_ip_response) {
+	                 if (!fatest_ip_response) {
+	                     loger.warn('getBestMcuServer -> nothing!');
+	                     this._emit(Sass.SEVERCHECK_GET_BEST_MCU, "");
+	                 } else {
+	                     loger.log('getBestMcuServer done -> ', fatest_ip_response);
+	                     this._emit(Sass.SEVERCHECK_GET_BEST_MCU, fatest_ip_response);
+	                 }
+	             }.bind(this), 3000);
+	         }
+	           //获取最快的MS服务器地址,参数是一个ip数组
+	         getBestMsServer(_param) {
+	             loger.log('getBestMsServer ', _param);
+	             iphunter(_param, function (fatest_ip_response) {
+	                 if (!fatest_ip_response) {
+	                     loger.warn('getBestMsServer -> nothing!');
+	                     this._emit(Sass.SEVER_CHECK_GET_BEST_MS, "");
+	                 } else {
+	                     loger.log('getBestMsServer done -> ', fatest_ip_response);
+	                     this._emit(Sass.SEVER_CHECK_GET_BEST_MS, fatest_ip_response);
+	                 }
+	             }.bind(this), 3000);
+	         }*/
+
+	    }]);
+
+	    return Sass;
+	}(_Emiter3.default);
+
+	Sass.prototype.SUCCESS = Sass.SUCCESS = 'Sass_success';
+	Sass.prototype.CLASS_INIT_SUCCESS = Sass.CLASS_INIT_SUCCESS = 'sass_class_init_success';
+	Sass.prototype.CLASS_GET_CLASS_PARAM = Sass.CLASS_GET_CLASS_PARAM = 'sass_class_getClassParam.message';
+	Sass.prototype.CLASS_GET_CLASS_DETAIL = Sass.CLASS_GET_CLASS_DETAIL = 'sass_class_getClassDetail_message';
+	//Sass.prototype.SEVERCHECK_GET_BEST_MCU = Sass.SEVERCHECK_GET_BEST_MCU = 'sass_class_getBestMcu_message';//获取最快的mcu地址
+	//Sass.prototype.SEVER_CHECK_GET_BEST_MS = Sass.SEVER_CHECK_GET_BEST_MS = 'sass_class_getBestMs_message';//获取最快的MS地址
+	//Sass.prototype.SEVER_CHECK_GET_USER_IP_INFO = Sass.SEVER_CHECK_GET_USER_IP_INFO = 'sass_class_getUserIpInfo_message';//获取最快的MS地址
+
+	Sass.prototype.DELETE_DOCUMENT_SUCCESS = Sass.DELETE_DOCUMENT_SUCCESS = 'sass_class_deleteDocumentSuccess_message'; //删除文档成功
+
+	Sass.prototype.CLASS_SAVE_STATUS_INFO_SUCCESS = Sass.CLASS_SAVE_STATUS_INFO_SUCCESS = 'sass_class_saveClassStatusInfoSuccess_message'; //保存会议状态信息
+	Sass.prototype.CLASS_SAVE_RECORD_INFO_SUCCESS = Sass.CLASS_SAVE_RECORD_INFO_SUCCESS = 'sass_class_saveClassRecordInfoSuccess_message'; //保存录制会议信息
+
+
+	var _default = new Sass();
+
+	exports.default = _default;
+	;
+
+	var _temp = function () {
+	    if (typeof __REACT_HOT_LOADER__ === 'undefined') {
+	        return;
+	    }
+
+	    __REACT_HOT_LOADER__.register(loger, 'loger', 'D:/work/McuClient/src/Sass.js');
+
+	    __REACT_HOT_LOADER__.register(confInfo, 'confInfo', 'D:/work/McuClient/src/Sass.js');
+
+	    __REACT_HOT_LOADER__.register(Sass, 'Sass', 'D:/work/McuClient/src/Sass.js');
+
+	    __REACT_HOT_LOADER__.register(_default, 'default', 'D:/work/McuClient/src/Sass.js');
+	}();
+
+	;
+
+/***/ },
+/* 5 */
+/***/ function(module, exports) {
+
+	'use strict';
+
+	Object.defineProperty(exports, "__esModule", {
+	  value: true
+	});
+
+	var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
+
+	function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
+
+	// //////////////////////////////////////////////////////////////////////////////
+	//
+	//  Copyright (C) 2016-present  All Rights Reserved.
+	//  Licensed under the Apache License, Version 2.0 (the "License");
+	//  http://www.apache.org/licenses/LICENSE-2.0
+	//
+	//  Github Home: https://github.com/AlexWang1987
+	//  Author: AlexWang
+	//  Date: 2016-08-27 22:58:47
+	//  QQ Email: 1669499355@qq.com
+	//  Last Modified time: 2016-08-27 23:05:53
+	//  Description: LiveClass-Loger
+	//
+	// //////////////////////////////////////////////////////////////////////////////
+
+	var Loger = function () {
+	  function Loger(info) {
+	    _classCallCheck(this, Loger);
+
+	    this.sdkInfo = info || '';
+	    this.id = this.initId();
+	  }
+
+	  _createClass(Loger, [{
+	    key: 'initId',
+	    value: function initId() {
+	      var infoType = this.sdkInfo.constructor.name.toLowerCase();
+	      if (infoType === 'string') {
+	        return this.sdkInfo;
+	      }
+	      if (infoType === 'object') {
+	        return this.sdkInfo.mid || '';
+	      }
+	      return '';
+	    }
+	  }, {
+	    key: 'log',
+	    value: function log() {
+	      for (var _len = arguments.length, msg = Array(_len), _key = 0; _key < _len; _key++) {
+	        msg[_key] = arguments[_key];
+	      }
+
+	      this._log(Loger.LOG, msg);
+	    }
+	  }, {
+	    key: 'warn',
+	    value: function warn() {
+	      for (var _len2 = arguments.length, msg = Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
+	        msg[_key2] = arguments[_key2];
+	      }
+
+	      this._log(Loger.WARN, msg);
+	    }
+	  }, {
+	    key: 'error',
+	    value: function error() {
+	      for (var _len3 = arguments.length, msg = Array(_len3), _key3 = 0; _key3 < _len3; _key3++) {
+	        msg[_key3] = arguments[_key3];
+	      }
+
+	      this._log(Loger.ERROR, msg);
+	    }
+	  }, {
+	    key: 'data',
+	    value: function data() {
+	      for (var _len4 = arguments.length, msg = Array(_len4), _key4 = 0; _key4 < _len4; _key4++) {
+	        msg[_key4] = arguments[_key4];
+	      }
+
+	      this._log(Loger.DATA, msg);
+	    }
+	  }, {
+	    key: '_log',
+	    value: function _log(type, msg) {
+	      msg = JSON.stringify(msg);
+
+	      var logMsg = this.id + ' -> ' + msg;
+	      if (type >= Loger.logLevel) {
+	        switch (type) {
+	          case Loger.LOG:
+	            console.log(logMsg);
+	            break;
+	          case Loger.WARN:
+	            console.warn(logMsg);
+	            break;
+	          case Loger.ERROR:
+	            console.error(logMsg);
+	            break;
+	          case Loger.DATA:
+	            console.log(logMsg);
+	            break;
+	        }
+	      }
+	    }
+	  }]);
+
+	  return Loger;
+	}();
+
+	Loger.LOG = 0;
+	Loger.WARN = 1;
+	Loger.ERROR = 2;
+	Loger.NO = Infinity;
+	Loger.logLevel = Loger.LOG;
+	Loger.DATA = 5;
+	var _default = {
+	  getLoger: function getLoger(info) {
+	    return new Loger(info);
+	  },
+	  setLogLevel: function setLogLevel(logLevel) {
+	    Loger.logLevel = logLevel;
+	  },
+	  LOG: Loger.LOG,
+	  WARN: Loger.WARN,
+	  ERROR: Loger.ERROR,
+	  NO: Loger.NO,
+	  DATA: Loger.DATA
+	};
+	exports.default = _default;
+	;
+
+	var _temp = function () {
+	  if (typeof __REACT_HOT_LOADER__ === 'undefined') {
+	    return;
+	  }
+
+	  __REACT_HOT_LOADER__.register(Loger, 'Loger', 'D:/work/McuClient/src/Loger.js');
+
+	  __REACT_HOT_LOADER__.register(_default, 'default', 'D:/work/McuClient/src/Loger.js');
+	}();
+
+	;
+
+/***/ },
+/* 6 */
+/***/ function(module, exports) {
+
+	"use strict";
+
+	Object.defineProperty(exports, "__esModule", {
+	  value: true
+	});
+	/**
+	 *事件消息ID和错误码 定义
+	 */
+
+	function MessageTypes() {}
+
+	//--------------------事件相关的定义--------------------------------------
+	//初始化相关事件定义
+	MessageTypes.CLASS_INIT_SUCCESS = "class_init_success"; //'class.init.success';//初始化成功
+	//MessageTypes.CLASS_INIT_FAILED='class.init.failed';//初始化失败
+
+	//加入会议相关事件定义
+	MessageTypes.CLASS_JOIN_MCU_SUCCESS = "class_join_mcu_success"; // 'join.mcu.success';
+	//MessageTypes.CLASS_JOIN_FAILED = 'join.class.failed';
+
+	//会议信息和操作事件定义
+	//MessageTypes.CLASS_SHOW_DETAIL = 'class_detail.message';
+	MessageTypes.CLASS_JOIN_SUCCESS = "class_join_success"; // 'join.class.success';
+	MessageTypes.CLASS_UPDATE_ROSTER_NUM = "class_update_roster_num"; // 'roster_num.message';
+	MessageTypes.CLASS_INSERT_ROSTER = "class_insert_roster"; // 'roster.insert.message';
+	MessageTypes.CLASS_DELETE_ROSTER = "class_delete_roster"; // 'roster.delete.message';
+	MessageTypes.CLASS_NONENTITY_ROSTER = "class_nonenetity_roster"; // 'roster.nonentity.message';
+
+	MessageTypes.CLASS_EXIT = "class_exit"; // 'class.exit';//退出 关闭会议
+	MessageTypes.CLASS_UPTATE_STATUS = "class_update_status"; // 'class.update.status';//更新会议状态信息
+	MessageTypes.CLASS_STATUS_INFO_CHANGE = "class_status_info_change"; // 'class.status.info.change';//会议状态信息发生改变,需要保存数据到sass和同步MCU
+
+	MessageTypes.CLASS_UPDATE_TIMER = "class_update_timer"; //'class.update.timer';//更新当前上课的时间
+
+	MessageTypes.CLASS_RECORD_START = "class_record_start"; //'class.record.start';//开始录制
+
+
+	//聊天模块事件定义
+	MessageTypes.CHAT_RECEIVE = "chat_receive_message"; // 'chat.receive';
+
+	//视频模块事件定义
+	MessageTypes.VIDEO_PLAY = "video_play"; // 'video.play';//播放视频
+	MessageTypes.VIDEO_STOP = "video_stop"; //'video.stop';//停止视频
+	MessageTypes.VIDEO_UPDATE = "video_update"; // //这个监听事件不能删除,需要通知会议模块,检查channel占用(内部使用)
+	MessageTypes.VIDEO_BROADCAST = "video_broadcast"; //'video.broadcast';
+
+	//音频模块事件定义
+	MessageTypes.AUDIO_PLAY = "audio_play"; // 'audio.play';//播放
+	MessageTypes.AUDIO_STOP = "audio_stop"; //'audio.stop';//停止
+	MessageTypes.AUDIO_UPDATE = "audio_update"; //这个监听事件不能删除,需要通知会议模块,检查channel占用(内部使用)
+	MessageTypes.AUDIO_BROADCAST = "audio_broadcast"; //'audio.broadcast';
+
+
+	//文档模块事件定义
+	MessageTypes.DOC_DELETE = "document_delete"; //'document.delete';//删除文档
+	MessageTypes.DOC_UPDATE = "document_update"; // 'document.update';//更新文档(添加、变更)
+	//MessageTypes.DOC_SHOW = 'document.show';
+	//MessageTypes.DOC_UPLOAD='document.upload';//上传文档
+	//MessageTypes.DOC_COMMAND='document.command';//操作文档
+	//MessageTypes.DOC_SWITCH = 'document.switch';//切换文档
+	//MessageTypes.DOC_DELETE='document.delete';//删除文档
+	//MessageTypes.DOC_ANNOTATION = 'document.annotation';//笔记
+
+
+	//白板笔记事件定义
+	MessageTypes.WHITEBOARD_ANNOTATION_UPDATE = "whiteboard_annotation_update"; // 'whiteboard.annotation.update';
+	//MessageTypes.WHITEBOARD_ANNOTAION_INSERT = 'whiteboard.annotation.insert';
+	//MessageTypes.WHITEBOARD_ANNOTAION_DELETE = 'whiteboard.annotation.delete';
+	//MessageTypes.WHITEBOARD_ANNOTATION_CLEAR = 'whiteboard.annotation.clear';
+
+
+	//错误事件定义
+	MessageTypes.MCU_ERROR = "mcu_error"; //"mcuError";//MCU错误(内部使用)
+	MessageTypes.ERROR_EVENT = "error_event"; //外部监听错误的消息ID(外部使用)
+
+
+	//---------------错误消息 ErrorCode 定义-------------------------------------------------
+
+	//会议初始化失败的几种情况
+	MessageTypes.ERR_CLASS_INIT_PARAM = 100; //初始化参数错误
+	MessageTypes.ERR_CLASS_INIT_NETWORK = 101; //初始化网络错误
+	MessageTypes.ERR_CLASS_INIT_PROTOCOL = 102; //初始化协议错误
+	MessageTypes.ERR_CLASS_INIT_FAILED = 103; //初始化验证失败
+	MessageTypes.ERR_CLASS_INIT_FAILED_1 = 104; //初始化验证失败,课堂号必填
+	MessageTypes.ERR_CLASS_INIT_FAILED_2 = 105; //初始化验证失败,无效的课堂号
+	MessageTypes.ERR_CLASS_INIT_FAILED_3 = 106; //初始化验证失败,没有对应的站点
+	MessageTypes.ERR_CLASS_INIT_FAILED_4 = 107; //初始化验证失败,站点已过期
+
+	//加入会议失败的几种情况
+	MessageTypes.ERR_CLASS_JOIN_NETWORK = 200; //加入会议网络错误
+	MessageTypes.ERR_CLASS_JOIN_PROTOCOL = 201; //加入会议化协议错误
+	MessageTypes.ERR_CLASS_JOIN_FAILED = 202; //加入会议化异常错误
+	MessageTypes.ERR_CLASS_JOIN_PARAM = 203; //加入会议参数错误
+	MessageTypes.ERR_CLASS_JOIN_FULL = 204; //人数已满
+	MessageTypes.ERR_CLASS_MD5_WRONG = 205; //MD5验证失败
+	MessageTypes.ERR_CLASS_PASSWORD_WRONG = 206; //密码错误
+	MessageTypes.ERR_CLASS_JOIN_CONFILICT = 207; //已经在其它地方登陆
+	MessageTypes.ERR_CLASS_KICK_OUT = 208; //有相同身份的人员加入课堂,自己被踢出;
+
+	MessageTypes.ERR_GET_CLASS_DETAIL = 300; //获取classDetail失败
+	MessageTypes.ERR_GET_CLASS_PARAML = 301; //获取ClassParam失败
+
+	//APE
+	MessageTypes.ERR_APE_SEND_FAILED_NO_JOIN = 500; //APE在sdk为初始化或未加入会议之前调用发送数据接口
+	MessageTypes.ERR_APE_INTERFACE_PARAM_WRONG = 501; //APE在接口调用时参数错误
+
+	//DOC
+	MessageTypes.ERR_DOC_DELETE_FAILED = 600; //删除文档失败
+	MessageTypes.ERR_DOC_DELETE_FAILED_PARAM = 601; //删除文档失败,参数错误
+
+
+	MessageTypes.ERR_SDK_FAILED = 700; // sdk还没初始化
+	MessageTypes.ERR_INTERFACE_NONE = 701; //调用的接口不存在
+	MessageTypes.ERR_INTERFACE_PARAMS_ERROR = 702; //调用的接口,传递的参数不正确
+
+	MessageTypes.ERR_NETWORK = 10000; //网络错误
+	MessageTypes.ERR_UNKNOWN = 10001; //未知错误
+
+	MessageTypes.ERR_SOCKET_DISCONNECT = 20000; //MCU断开连接,已经离开会议
+
+	//---------------错误消息 Error Reson 定义-------------------------------------------------
+	MessageTypes.ErrorReson = {};
+	MessageTypes.ErrorReson[MessageTypes.ERR_CLASS_INIT_PARAM] = "初始化参数错误";
+	MessageTypes.ErrorReson[MessageTypes.ERR_CLASS_INIT_NETWORK] = "初始化网络错误";
+	MessageTypes.ErrorReson[MessageTypes.ERR_CLASS_INIT_PROTOCOL] = "初始化协议错误";
+
+	MessageTypes.ErrorReson[MessageTypes.ERR_CLASS_INIT_FAILED] = "初始化验证失败";
+	MessageTypes.ErrorReson[MessageTypes.ERR_CLASS_INIT_FAILED_1] = "初始化验证失败,课堂号必填";
+	MessageTypes.ErrorReson[MessageTypes.ERR_CLASS_INIT_FAILED_2] = "初始化验证失败,无效的课堂号";
+	MessageTypes.ErrorReson[MessageTypes.ERR_CLASS_INIT_FAILED_3] = "初始化验证失败,没有对应的站点";
+	MessageTypes.ErrorReson[MessageTypes.ERR_CLASS_INIT_FAILED_4] = "初始化验证失败,站点已过期";
+
+	//加入会议失败的几种情况
+	MessageTypes.ErrorReson[MessageTypes.ERR_CLASS_JOIN_NETWORK] = "加入会议网络错误";
+	MessageTypes.ErrorReson[MessageTypes.ERR_CLASS_JOIN_PROTOCOL] = "加入会议化协议错误";
+	MessageTypes.ErrorReson[MessageTypes.ERR_CLASS_JOIN_FAILED] = "加入会议化异常错误";
+	MessageTypes.ErrorReson[MessageTypes.ERR_CLASS_JOIN_PARAM] = "加入会议参数错误";
+	MessageTypes.ErrorReson[MessageTypes.ERR_CLASS_JOIN_FULL] = "人数已满";
+	MessageTypes.ErrorReson[MessageTypes.ERR_CLASS_MD5_WRONG] = "MD5验证失败";
+	MessageTypes.ErrorReson[MessageTypes.ERR_CLASS_PASSWORD_WRONG] = "密码错误";
+	MessageTypes.ErrorReson[MessageTypes.ERR_CLASS_JOIN_CONFILICT] = "已经在其它地方登陆";
+	MessageTypes.ErrorReson[MessageTypes.ERR_CLASS_KICK_OUT] = "有相同身份的人员加入课堂,自己被踢出课堂";
+
+	MessageTypes.ErrorReson[MessageTypes.ERR_GET_CLASS_DETAIL] = "获取classDetail失败";
+	MessageTypes.ErrorReson[MessageTypes.ERR_GET_CLASS_PARAML] = "获取ClassParam失败";
+
+	//APE
+	MessageTypes.ErrorReson[MessageTypes.ERR_APE_SEND_FAILED_NO_JOIN] = "APE在sdk为初始化或未加入会议之前调用发送数据接口";
+	MessageTypes.ErrorReson[MessageTypes.ERR_APE_INTERFACE_PARAM_WRONG] = "APE在接口调用时参数错误";
+
+	//DOC
+	MessageTypes.ErrorReson[MessageTypes.ERR_DOC_DELETE_FAILED] = "删除文档失败";
+	MessageTypes.ErrorReson[MessageTypes.ERR_DOC_DELETE_FAILED_PARAM] = "删除文档失败,参数错误";
+
+	MessageTypes.ErrorReson[MessageTypes.ERR_SDK_FAILED] = "sdk还没初始化";
+	MessageTypes.ErrorReson[MessageTypes.ERR_INTERFACE_NONE] = "调用的接口不存在";
+	MessageTypes.ErrorReson[MessageTypes.ERR_INTERFACE_PARAMS_ERROR] = "调用的接口,传递的参数不正确";
+
+	MessageTypes.ErrorReson[MessageTypes.ERR_NETWORK] = "网络错误";
+	MessageTypes.ErrorReson[MessageTypes.ERR_UNKNOWN] = "未知错误";
+
+	MessageTypes.ErrorReson[MessageTypes.ERR_SOCKET_DISCONNECT] = "MCU断开连接,已经离开会议";
+
+	var _default = MessageTypes;
+	exports.default = _default;
+	;
+
+	var _temp = function () {
+	  if (typeof __REACT_HOT_LOADER__ === 'undefined') {
+	    return;
+	  }
+
+	  __REACT_HOT_LOADER__.register(MessageTypes, "MessageTypes", "D:/work/McuClient/src/MessageTypes.js");
+
+	  __REACT_HOT_LOADER__.register(_default, "default", "D:/work/McuClient/src/MessageTypes.js");
+	}();
+
+	;
+
+/***/ },
+/* 7 */
+/***/ function(module, exports, __webpack_require__) {
+
+	'use strict';
+
+	Object.defineProperty(exports, "__esModule", {
+	    value: true
+	});
+
+	var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); /*
+	                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     * 全局数据管理
+	                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     * */
+
+
+	var _Loger = __webpack_require__(5);
+
+	var _Loger2 = _interopRequireDefault(_Loger);
+
+	var _ApeConsts = __webpack_require__(8);
+
+	var _ApeConsts2 = _interopRequireDefault(_ApeConsts);
+
+	var _EngineUtils = __webpack_require__(9);
+
+	var _EngineUtils2 = _interopRequireDefault(_EngineUtils);
+
+	function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
+
+	function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
+
+	var loger = _Loger2.default.getLoger('GlobalConfig');
+
+	var GlobalConfig = function () {
+	    function GlobalConfig() {
+	        _classCallCheck(this, GlobalConfig);
+
+	        this._currentStatus = null;
+	    }
+
+	    _createClass(GlobalConfig, null, [{
+	        key: 'getCurrentStatus',
+	        value: function getCurrentStatus() {
+	            if (this._currentStatus == null) {
+	                this._currentStatus = this.statusCode_0;
+	            }
+	            return this._currentStatus;
+	        }
+	    }, {
+	        key: 'setCurrentStatus',
+	        value: function setCurrentStatus(_data) {
+	            this._currentStatus = _data;
+	        }
+	    }, {
+	        key: 'getClassDetail',
+	        value: function getClassDetail() {
+	            return this.classDetail;
+	        }
+	    }, {
+	        key: 'setClassDetail',
+	        value: function setClassDetail(_data) {
+	            this.classDetail = _data;
+	        }
+	    }, {
+	        key: 'getClassInfo',
+	        value: function getClassInfo() {
+	            var classInfo = {};
+	            classInfo.siteId = this.siteId;
+	            classInfo.classId = this.classId;
+	            classInfo.className = this.className;
+	            classInfo.h5Module = this.h5Module;
+	            classInfo.nodeId = this.nodeId;
+	            classInfo.topNodeID = this.topNodeID;
+	            classInfo.userRole = this.userRole;
+	            classInfo.role = this.role;
+	            classInfo.isHost = this.isHost;
+	            classInfo.userId = this.userId;
+	            classInfo.userName = this.userName;
+	            classInfo.password = this.password;
+	            classInfo.userType = this.userType;
+	            classInfo.passwordRequired = this.passwordRequired;
+	            classInfo.md5 = this.md5;
+	            classInfo.msType = this.msType;
+	            classInfo.portal = this.portal;
+	            classInfo.mcuDelay = this.mcuDelay;
+	            classInfo.MCUServerIP = this.MCUServerIP;
+	            classInfo.MCUServerPort = this.MCUServerPort;
+	            classInfo.maxVideoChannels = this.maxVideoChannels;
+	            classInfo.maxAudioChannels = this.maxAudioChannels;
+
+	            return classInfo;
+	        }
+
+	        //获取当前的课堂状态的信息
+
+	    }, {
+	        key: 'setClassStatusInfo',
+
+	        //设置当前的课堂状态的信息
+	        value: function setClassStatusInfo(_data) {
+	            loger.log("setClassStatusInfo");
+	            if (_data == null) {
+	                loger.warn("classStatusInfo error,_data:", _data);
+	                return;
+	            }
+	            var data = _data;
+
+	            this.siteId = data.siteId || this.siteId; //站点号
+
+	            this.classId = data.classId || this.classId;
+	            this.className = data.className || this.className;
+	            this.classType = data.classType || this.classType; //课堂类型
+	            this.classStatus = data.classStatus || this.classStatus; //课堂的状态
+	            this.classStartTime = data.classStartTime || this.classStartTime; //课堂点击开始时间
+
+	            this.classStopTime = data.classStopTime || this.classStopTime; //最后一次停止的时间(点暂停或结束),每次发送数据都获取当前时间戳
+	            this.classTimestamp = data.classTimestamp || this.classTimestamp; //相对于点开始课堂的时间戳
+
+	            this.classBeginTime = data.classBeginTime || this.classBeginTime; //课堂创建的时间,这个是Sass返回的
+	            this.classEndTime = data.classEndTime || this.classEndTime; //课堂结束的时间,这个是Sass返回的
+
+	            this.recordStatus = data.recordStatus || this.recordStatus; //当前录制状态
+	            this.recordTimestamp = data.recordTimestamp || this.recordTimestamp; //相对于首次开始录制的时间戳
+	            this.recordFileName = data.recordFileName || this.recordFileName; //录制的文件名
+	            this.recordDownloadUrl = data.recordDownloadUrl || this.recordDownloadUrl; //下载地址
+
+	            this.activeDocId = data.activeDocId || this.activeDocId; //当前激活的文档ID
+	            this.activeDocCurPage = data.activeDocCurPage || this.activeDocCurPage; //当前激活的文档的当前页
+	        }
+
+	        // 判断自己是否主持人角色
+
+	    }, {
+	        key: 'setDocListPrepare',
+
+	        //储存已经提前上传的文档列表
+	        value: function setDocListPrepare(_data) {
+	            if (_data == null) return;
+	            this.docListPrepare = _data;
+	        }
+	    }, {
+	        key: 'getDocListPrepare',
+	        value: function getDocListPrepare() {
+	            return this.docListPrepare;
+	        }
+	        //储存录制列表
+
+	    }, {
+	        key: 'setRecordList',
+	        value: function setRecordList(_data) {
+	            if (_data == null) return;
+	            this.recordList = _data;
+	        }
+	    }, {
+	        key: 'getRecordList',
+	        value: function getRecordList() {
+	            return this.recordList;
+	        }
+
+	        //文档服务器列表
+
+	    }, {
+	        key: 'setDocList',
+	        value: function setDocList(_data) {
+	            if (_data == null) return;
+	            this.docList = _data;
+	        }
+	    }, {
+	        key: 'getDocList',
+	        value: function getDocList() {
+	            return this.docList;
+	        }
+	        //ms列表
+
+	    }, {
+	        key: 'setMsList',
+	        value: function setMsList(_data) {
+	            if (_data == null) return;
+	            this.msList = _data;
+	        }
+	    }, {
+	        key: 'getMsList',
+	        value: function getMsList() {
+	            return this.msList;
+	        }
+
+	        //mcu列表
+
+	    }, {
+	        key: 'setMcuList',
+	        value: function setMcuList(_data) {
+	            if (_data == null) return;
+	            this.mcuList = _data;
+	        }
+	    }, {
+	        key: 'getMcuList',
+	        value: function getMcuList() {
+	            return this.mcuList;
+	        }
+
+	        //声音列表
+
+	    }, {
+	        key: 'setMusicList',
+	        value: function setMusicList(_data) {
+	            if (_data == null) return;
+	            this.musicList = _data;
+	        }
+	    }, {
+	        key: 'getMusicList',
+	        value: function getMusicList() {
+	            return this.musicList;
+	        }
+	        //已经上传的声音列表
+
+	    }, {
+	        key: 'setMusicListPrepare',
+	        value: function setMusicListPrepare(_data) {
+	            if (_data == null) return;
+	            this.musicListPrepare = _data;
+	        }
+	    }, {
+	        key: 'getMusicListPrepare',
+	        value: function getMusicListPrepare() {
+	            return this.musicListPrepare;
+	        }
+
+	        //rs列表
+
+	    }, {
+	        key: 'setRsList',
+	        value: function setRsList(_data) {
+	            if (_data == null) return;
+	            this.rsList = _data;
+	        }
+	    }, {
+	        key: 'getRsList',
+	        value: function getRsList() {
+	            return this.rsList;
+	        }
+	    }, {
+	        key: 'classStatusInfo',
+	        get: function get() {
+	            var classStatusInfo = {};
+
+	            classStatusInfo.siteId = this.siteId; //站点号
+
+	            classStatusInfo.classId = this.classId;
+	            classStatusInfo.className = this.className;
+	            classStatusInfo.classType = this.classType; //课堂类型
+	            classStatusInfo.classStatus = this.classStatus; //课堂的状态
+	            classStatusInfo.classStartTime = this.classStartTime; //课堂点击开始时间
+	            classStatusInfo.classStopTime = this.classStopTime; //最后一次停止的时间(点暂停或结束),每次发送数据都获取当前时间戳
+	            classStatusInfo.classTimestamp = this.classTimestamp; //相对于点开始课堂的时间戳
+
+	            classStatusInfo.classBeginTime = this.classBeginTime; //课堂创建的时间,这个是Sass返回的
+	            classStatusInfo.classEndTime = this.classEndTime; //课堂结束的时间,这个是Sass返回的
+
+	            classStatusInfo.recordStatus = this.recordStatus; //当前录制状态
+	            classStatusInfo.recordTimestamp = this.recordTimestamp; //相对于首次开始录制的时间戳
+	            classStatusInfo.recordFileName = this.recordFileName; //录制的文件名
+	            classStatusInfo.recordDownloadUrl = this.recordDownloadUrl; //下载地址
+
+	            classStatusInfo.serverTimestamp = this.serverTimestamp; //当前的系统时间戳
+	            classStatusInfo.activeDocId = this.activeDocId; //当前激活的文档ID
+	            classStatusInfo.activeDocCurPage = this.activeDocCurPage; //当前激活的文档的当前页
+	            return classStatusInfo;
+	        }
+	    }, {
+	        key: 'isHost',
+	        get: function get() {
+	            if (this.userRole == _ApeConsts2.default.host) {
+	                return true;
+	            }
+	            return false;
+	        }
+
+	        // 判断自己是否助教角色
+
+	    }, {
+	        key: 'isAssistant',
+	        get: function get() {
+	            if (this.userRole == _ApeConsts2.default.assistant) {
+	                return true;
+	            }
+	            return false;
+	        }
+
+	        // 判断自己是否主讲人角色
+
+	    }, {
+	        key: 'isPresenter',
+	        get: function get() {
+	            if (this.userRole == _ApeConsts2.default.presenter) {
+	                return true;
+	            }
+	            return false;
+	        }
+
+	        // 判断自己是否普通角色
+
+	    }, {
+	        key: 'isNormal',
+	        get: function get() {
+	            if (this.userRole == _ApeConsts2.default.normal) {
+	                return true;
+	            }
+	            return false;
+	        }
+
+	        // 判断自己是否是隐身用户
+
+	    }, {
+	        key: 'isInvisible',
+	        get: function get() {
+	            if (this.userRole == _ApeConsts2.default.invisible) {
+	                return true;
+	            }
+	            return false;
+	        }
+	    }, {
+	        key: 'serverTimestamp',
+	        get: function get() {
+	            return _EngineUtils2.default.creatTimestamp();
+	        }
+	    }]);
+
+	    return GlobalConfig;
+	}();
+
+	GlobalConfig.statusCode_0 = { "code": 0, message: "SDK 未初始化" };
+	GlobalConfig.statusCode_1 = { "code": 1, message: "未加入会议" };
+	GlobalConfig.statusCode_2 = { "code": 2, message: "已经加入会议" };
+	GlobalConfig.statusCode_3 = { "code": 3, message: "已经离开会议" };
+	GlobalConfig.statusCode_4 = { "code": 4, message: "未知状态" };
+
+	GlobalConfig.md5 = "";
+	GlobalConfig.msType = 1; //目前固定用这个
+	GlobalConfig.mcuDelay = 60; //默认的延迟时间 flash中使用的是3000毫秒
+	GlobalConfig.docDelay = 1600; //文档模块加入成功之后延迟发送送成功的消息给主模块
+	GlobalConfig.portal = "112.126.80.182:80"; //Sass IP
+
+	//GlobalConfig.ip="112.126.80.182";
+	//GlobalConfig.port="80";
+
+	GlobalConfig.MCUServerIP = "114.215.195.70";
+	GlobalConfig.MCUServerPort = 9003;
+
+	GlobalConfig.MSServerIP = ""; //推流 播流的地址
+	GlobalConfig.MSServerPort = "";
+
+	//m3u8播流地址
+	GlobalConfig.RSServerIP = "";
+	GlobalConfig.RSServerPort = "";
+
+	//DOC
+	GlobalConfig.DOCServerIP = "";
+	GlobalConfig.DOCServerPort = "";
+
+	GlobalConfig.RecordServerIP = "";
+	GlobalConfig.RecordServerPort = "";
+
+	GlobalConfig.maxVideoChannels = 0;
+	GlobalConfig.maxAudioChannels = 0;
+
+	GlobalConfig.hasCamera = false; //摄像头是否可用
+	GlobalConfig.hasMicrophone = false; //麦克风是否可用
+
+	GlobalConfig.deviceType = 0; //设备类型  0:电脑  1:安卓  2:ios
+	GlobalConfig.userIp = ""; //用户当前IP
+	GlobalConfig.userId = 0;
+	GlobalConfig.userName = "";
+
+	GlobalConfig.nodeId = 0; //随机生成 mcu中的唯一ID
+	GlobalConfig.passwordRequired = false;
+	GlobalConfig.password = "";
+	GlobalConfig.userType = 8;
+	GlobalConfig.userRole = _ApeConsts2.default.normal; //用户的身份,5种类型:
+	GlobalConfig.role = _ApeConsts2.default.NR_NORMAL; //课堂角色身份
+
+	GlobalConfig.h5Module = 1; //是否支持H5
+	GlobalConfig.topNodeID = 101; //现在固定值,还不知道是做什么用
+
+	GlobalConfig.siteId = "gust"; //站点号
+	GlobalConfig.className = ""; // 课程名称
+	GlobalConfig.classId = 0; //课堂号=classId=meetingNumber  之后统一修改为classId
+	GlobalConfig.classType = _ApeConsts2.default.CLASS_TYPE_INTERACT; //课堂类型 1:互动课堂,2:直播课堂
+	GlobalConfig.classStatus = _ApeConsts2.default.CLASS_STATUS_WAIT; // 0;//课堂还未开始  1;//直播中  2 //课间休息  3已经停止
+	GlobalConfig.classStartTime = ""; //课堂点击开始时间
+	GlobalConfig.classStopTime = ""; //最后一次停止的时间(点暂停或结束),每次发送数据都获取当前时间戳
+	GlobalConfig.classBeginTime = ""; //课堂创建的时间,这个是Sass返回的
+	GlobalConfig.classEndTime = ""; //课堂结束的时间,这个是Sass返回的
+	GlobalConfig.classTimestamp = 0; //从课堂开始到现在的时
+
+	GlobalConfig.recordStatus = false; //当前录制状态
+	GlobalConfig.recordTimestamp = 0; //相对于首次开始录制的进行时间
+	GlobalConfig.recordFileName = ""; //录制的文件名,如 果为空就创建一个
+	GlobalConfig.recordDownloadUrl = ""; //下载地址
+	GlobalConfig.recordReplaytickValues = {}; // 滚动条关键点,用于快进快退
+
+	GlobalConfig.updateClassInfoDelay = 30; //(秒),每隔30秒同步一次会议状态的并保存到Sass
+	//GlobalConfig.serverTimestamp=0;//当前的系统时间戳 用get  set 获取
+
+
+	GlobalConfig.activeDocId = 0; //当前激活的文档ID
+	GlobalConfig.activeDocCurPage = 1; //当前激活的文档的当前页
+
+
+	GlobalConfig.classAllParam = {}; //Sass直接返回的所有会议信息(最全)
+	GlobalConfig.classDetail = {}; //Sass直接返回的当前课堂基本信息
+
+	GlobalConfig.docListPrepare = []; // 已经提前上传的文档,进入课堂后需要自动加载
+	GlobalConfig.recordList = []; //录制服务器地址集合
+	GlobalConfig.docList = []; //文档服务器地址集合
+	GlobalConfig.mcuList = []; //录制服务器地址集合
+	GlobalConfig.msList = []; //ms服务器地址集合
+	GlobalConfig.musicList = []; //music服务器地址集合
+	GlobalConfig.musicListPrepare = []; //提提前上传的music集合
+	GlobalConfig.rsList = [];
+
+	GlobalConfig.country = ""; //国家
+	GlobalConfig.city = ""; //城市
+	GlobalConfig.province = ""; //服务商
+	GlobalConfig.isp = ""; //服务商
+
+	var _default = GlobalConfig;
+	exports.default = _default;
+	;
+
+	var _temp = function () {
+	    if (typeof __REACT_HOT_LOADER__ === 'undefined') {
+	        return;
+	    }
+
+	    __REACT_HOT_LOADER__.register(loger, 'loger', 'D:/work/McuClient/src/GlobalConfig.js');
+
+	    __REACT_HOT_LOADER__.register(GlobalConfig, 'GlobalConfig', 'D:/work/McuClient/src/GlobalConfig.js');
+
+	    __REACT_HOT_LOADER__.register(_default, 'default', 'D:/work/McuClient/src/GlobalConfig.js');
+	}();
+
+	;
+
+/***/ },
+/* 8 */
+/***/ function(module, exports) {
+
+	"use strict";
+
+	Object.defineProperty(exports, "__esModule", {
+	  value: true
+	});
+	exports.default = ApeConsts;
+	function ApeConsts(id) {
+	  for (var type_const in ApeConsts) {
+	    if (ApeConsts[type_const] === id) {
+	      return type_const;
+	    }
+	  }
+	}
+
+	//课堂状态
+	ApeConsts.CLASS_STATUS_WAIT = 0; //课堂还未开始
+	ApeConsts.CLASS_STATUS_STARTED = 1; //直播中
+	ApeConsts.CLASS_STATUS_PAUSE = 2; //暂停
+	ApeConsts.CLASS_STATUS_CLOSE = 3; //结束//这个是点击结束会议,把所有人踢出课堂,然后把状态值还原为0*************
+	ApeConsts.CLASS_STATUS_UPTATE = 4; //更新课堂状态信息
+
+	ApeConsts.CLASS_WAIT_START = "class.wait.start"; //课堂还未开始
+	ApeConsts.CLASS_STARTING = "class.started"; //直播中
+	ApeConsts.CLASS_PAUSING = "class.pause"; //暂停
+	ApeConsts.CLASS_PAUSING = "class.closed"; //关闭
+	ApeConsts.CLASS_PAUSING = "class.update"; //更新当前的状态信息
+
+
+	//课堂控制
+	ApeConsts.CLASS_ACTION_CLOSE_ALL = 1; //所有人关闭会议
+
+	//课堂类型
+	ApeConsts.CLASS_TYPE_INTERACT = 1; // 互动课堂,通过MS转发音视频,不能进行H5观看
+	ApeConsts.CLASS_TYPE_LIVE = 2; // 直播课堂,通过CDN转发音视频,不能进行音视频互动
+
+
+	/*
+	flash
+	public static const NR_GUEST:uint   = 0;       // 客人
+	public static const NR_NORMAL:uint  = 1;       // 普通与会者
+	public static const NR_ADMIN:uint   = 2;       // 管理员
+	public static const NR_MASTER:uint  = 4;       // 主持人
+	public static const NR_PRESENTER:uint   = 8;       // 主讲人
+	public static const NR_ASSISTANT:uint   = 16;      // 助教
+	public static const NR_INVISIBLE:uint   = 32;      // 隐身用户
+	*/
+
+	//角色身份
+	//ApeConsts.NR_GUEST = 0; // 客人
+	ApeConsts.NR_NORMAL = 1; // 普通与会者
+	ApeConsts.NR_ADMIN = 2; // 管理员
+	ApeConsts.NR_HOST = 4; // 主持人
+	ApeConsts.NR_PRESENTER = 8; // 主讲人
+	ApeConsts.NR_ASSISTANT = 16; // 助教
+	ApeConsts.NR_INVISIBLE = 32; // 隐身用户
+
+	//用户的身份,5种类型:
+	ApeConsts.host = "host"; //(主持人/老师)
+	ApeConsts.presenter = "presenter"; //(主讲人)
+	ApeConsts.assistant = "assistant"; //(助教)
+	ApeConsts.normal = "normal"; //(普通角色/学生)
+	ApeConsts.record = "record"; //(暂时没用.
+	ApeConsts.invisible = "invisible"; //隐身用户
+
+	//下面做身份的数字和字符串对应关系
+	ApeConsts.userTypes = {};
+	ApeConsts.userTypes[ApeConsts.NR_NORMAL] = ApeConsts.normal;
+	ApeConsts.userTypes[ApeConsts.NR_ADMIN] = ApeConsts.record;
+	ApeConsts.userTypes[ApeConsts.NR_HOST] = ApeConsts.host;
+	ApeConsts.userTypes[ApeConsts.NR_PRESENTER] = ApeConsts.presenter;
+	ApeConsts.userTypes[ApeConsts.NR_ASSISTANT] = ApeConsts.assistant;
+	ApeConsts.userTypes[ApeConsts.NR_INVISIBLE] = ApeConsts.invisible;
+
+	ApeConsts.userTypesToId = {};
+	ApeConsts.userTypesToId[ApeConsts.normal] = ApeConsts.NR_NORMAL;
+	ApeConsts.userTypesToId[ApeConsts.record] = ApeConsts.NR_ADMIN;
+	ApeConsts.userTypesToId[ApeConsts.host] = ApeConsts.NR_HOST;
+	ApeConsts.userTypesToId[ApeConsts.presenter] = ApeConsts.NR_PRESENTER;
+	ApeConsts.userTypesToId[ApeConsts.assistant] = ApeConsts.NR_ASSISTANT;
+	ApeConsts.userTypesToId[ApeConsts.invisible] = ApeConsts.NR_INVISIBLE;
+
+	////最新定义的角色身份 20170220
+	//ApeConsts.USER_TYPE_HOST=1;//(主持人/老师)
+	//ApeConsts.USER_TYPE_ASSISTANT=2;//(助教)
+	//ApeConsts.USER_TYPE_NORMAL=8;//(普通角色/学生)
+	//ApeConsts.USER_TYPE_MONITOR_INVISIBLE=32;//(监课/隐身)
+	//
+	////
+	//ApeConsts.USER_TYPE_HOST_STRING="host";//(主持人/老师)
+	//ApeConsts.USER_TYPE_ASSISTANT_STRING="assistant";//(助教)
+	//ApeConsts.USER_TYPE_NORMAL_STRING="normal";//(普通角色/学生)
+	//ApeConsts.USER_TYPE_MONITOR_INVISIBLE_STRING="invisible";//(监课/隐身)
+
+
+	/*msType type*/
+	ApeConsts.MS_TYPE_DEFAULT = 0; //默认MS,(废弃)
+	ApeConsts.MS_TYPE_FMS = 1; //第三方FMS,目前一直用这个
+
+
+	//用户状态
+	ApeConsts.USER_HAND_UP = 0x0020; // 举手
+	ApeConsts.USER_MIC_OPEN = 0x0040; // 麦克风开启
+	ApeConsts.USER_CAMERA_OPEN = 0x0080; // 视频开启
+
+
+	//VIDEO MIC 流媒体消息操作控制类型
+	ApeConsts.MEDIA_ACTION_DEFAULT = 0;
+
+	ApeConsts.MEDIA_ACTION_OPEN_CAMERA = 1; // "open.camera";
+	ApeConsts.MEDIA_ACTION_CLOSE_CAMERA = 2; // "close.camera";
+
+	ApeConsts.MEDIA_ACTION_OPEN_MIC = 3; //"open.mic";
+	ApeConsts.MEDIA_ACTION_CLOSE_MIC = 4; //"close.mic";
+
+
+	// VIDEO AUDIO CHANNEL使用状态
+	ApeConsts.CHANNEL_STATUS_RELEASED = 0; ///< 无人占用状态
+	ApeConsts.CHANNEL_STATUS_OPENING = 1; ///< 已经占用成功
+
+	//媒体类型
+	ApeConsts.MEDIA_TYPE_DEFAULT = 0; //没有类型
+	ApeConsts.MEDIA_TYPE_VIDEO = 1; //视频流(包含音频)
+	ApeConsts.MEDIA_TYPE_AUDIO = 2; //音频流
+
+	//return返回值状态
+	ApeConsts.RETURN_SUCCESS = 0; //成功
+	ApeConsts.RETURN_FAILED = 1; //失败
+
+	////FLASH中使用下面4个
+	//ApeConsts.CGS_RELEASED = 0;///< 无人占用状态
+	//ApeConsts.CGS_PENDING = 1;///< 占用成功,等待打开
+	//ApeConsts.CGS_OPENNED = 2;///< 打开成功
+	//ApeConsts.CGS_GRABBING = 3; ///< 准备占用中, 属于本地状态机需要用的状态,在多点数据库中不存在。
+	//
+
+
+	ApeConsts.INVALIDATE_CHANNEL_ID = -1;
+	ApeConsts.INVALIDATE_NODE_ID = -1;
+
+	// doc update status
+	ApeConsts.DOC_ACTION_NORMAL = 0; //无操作
+	ApeConsts.DOC_ACTION_SWITCH_DOC = 1; //切换文档
+	ApeConsts.DOC_ACTION_SWITCH_PAGE = 2; //文档翻页
+	ApeConsts.DOC_ACTION_COMMAND = 3; //文档操作:滚动、缩放
+
+
+	// defs for common session id
+	ApeConsts.CONFERENCE_SESSION_ID = 11;
+	ApeConsts.CHAT_SESSION_ID = 12;
+	ApeConsts.GIFT_SESSION_ID = 13;
+	ApeConsts.AUDIO_SESSION_ID = 14;
+	ApeConsts.VIDEO_SESSION_ID = 15;
+	ApeConsts.WEBSHARING_SESSION_ID = 16;
+	ApeConsts.DOCSHARING_SESSION_ID = 17;
+	ApeConsts.WHITEBOARD_SESSION_ID = 18;
+	ApeConsts.MEDIA_SESSION_ID = 19;
+	ApeConsts.SCREENSHARING_SESSION_ID = 20;
+	ApeConsts.POLL_SESSION_ID = 21;
+
+	// defs for common channel id
+	ApeConsts.BROADCAST_CHANNEL_ID = 0;
+	ApeConsts.CONFERENCE_CHANNEL_ID = ApeConsts.CONFERENCE_SESSION_ID;
+	ApeConsts.CHAT_CHANNEL_ID = ApeConsts.CHAT_SESSION_ID;
+	ApeConsts.GIFT_CHANNEL_ID = ApeConsts.GIFT_SESSION_ID;
+	ApeConsts.WEBSHARING_CHANNEL_ID = ApeConsts.WEBSHARING_SESSION_ID;
+	ApeConsts.DOCSHARING_CHANNEL_ID = ApeConsts.DOCSHARING_SESSION_ID;
+	ApeConsts.WHITEBOARD_CHANNEL_ID = ApeConsts.WHITEBOARD_SESSION_ID;
+	ApeConsts.MEDIA_CHANNEL_ID = ApeConsts.MEDIA_SESSION_ID;
+	ApeConsts.SCREENSHARING_CHANNEL_ID = ApeConsts.SCREENSHARING_SESSION_ID;
+
+	// defs for common session name
+	ApeConsts.CONFERENCE_SESSION_NAME = "conference app";
+	ApeConsts.CHAT_SESSION_NAME = "chat app";
+	ApeConsts.GIFT_SESSION_NAME = "gift app";
+	ApeConsts.AUDIO_SESSION_NAME = "audio app";
+	ApeConsts.VIDEO_SESSION_NAME = "video app";
+	ApeConsts.WEBSHARING_SESSION_NAME = "web sharing app";
+	ApeConsts.DOCSHARING_SESSION_NAME = "doc sharing app";
+	ApeConsts.WHITEBOARD_SESSION_NAME = "whiteboard app";
+	ApeConsts.MEDIA_SESSION_NAME = "media sharing app";
+	ApeConsts.SCREENSHARING_SESSION_NAME = "screen sharing app";
+
+	// def for common session tag
+	ApeConsts.CONFERENCE_SESSION_TAG = "con-tag";
+	ApeConsts.CHAT_SESSION_TAG = "cha-tag";
+	ApeConsts.GIFT_SESSION_TAG = "gif-tag";
+	ApeConsts.AUDIO_SESSION_TAG = "aud-tag";
+	ApeConsts.VIDEO_SESSION_TAG = "vid-tag";
+	ApeConsts.WEBSHARING_SESSION_TAG = "web-tag";
+	ApeConsts.DOCSHARING_SESSION_TAG = "doc-tag";
+	ApeConsts.WHITEBOARD_SESSION_TAG = "wbd-tag";
+	ApeConsts.MEDIA_SESSION_TAG = "med-tag";
+	ApeConsts.SCREENSHARING_SESSION_TAG = "scr-tag";
+
+	ApeConsts.CONFERENCE_OBJ_ROSTER_ID = (ApeConsts.CONFERENCE_SESSION_ID << 16) + 1;
+	ApeConsts.CONFERENCE_OBJ_ROSTER_NAME = "node list";
+	ApeConsts.CONFERENCE_OBJ_ROSTER_TAG = "node list tag";
+
+	ApeConsts.CONFERENCE_OBJ_QUEUE_ID = (ApeConsts.CONFERENCE_SESSION_ID << 16) + 2;
+	ApeConsts.CONFERENCE_OBJ_QUEUE_NAME = "mic list";
+	ApeConsts.CONFERENCE_OBJ_QUEUE_TAG = "mic list tag";
+
+	// conference tab pages
+	ApeConsts.CONFERENCE_OBJ_TABLE_ID = (ApeConsts.CONFERENCE_SESSION_ID << 16) + 3;
+	ApeConsts.CONFERENCE_OBJ_TABLE_NAME = "tabbar list";
+	ApeConsts.CONFERENCE_OBJ_TABLE_TAG = "tabbar list tag";
+
+	// owned id list is unique in conference
+	ApeConsts.CONFERENCE_OBJ_COUNTER_ID = (ApeConsts.CONFERENCE_SESSION_ID << 16) + 4;
+	ApeConsts.CONFERENCE_OBJ_COUNTER_NAME = "id list";
+	ApeConsts.CONFERENCE_OBJ_COUNTER_TAG = "id list tag";
+
+	// web sharing objects
+	ApeConsts.WEBSHARING_OBJ_TABLE_ID = (ApeConsts.WEBSHARING_SESSION_ID << 16) + 1;
+	ApeConsts.WEBSHARING_OBJ_TABLE_NAME = "web list";
+	ApeConsts.WEBSHARING_OBJ_TABLE_TAG = "web list tag";
+
+	// doc sharing objects
+	ApeConsts.DOCSHARING_OBJ_TABLE_ID = (ApeConsts.DOCSHARING_SESSION_ID << 16) + 1;
+	ApeConsts.DOCSHARING_OBJ_TABLE_NAME = "doc list";
+	ApeConsts.DOCSHARING_OBJ_TABLE_TAG = "doc list tag";
+
+	// doc sharing objects h5
+	ApeConsts.DOCSHARING_OBJ_TABLE_ID_H5 = (ApeConsts.DOCSHARING_SESSION_ID << 16) + 2;
+	ApeConsts.DOCSHARING_OBJ_TABLE_NAME_H5 = "doc list h5";
+	ApeConsts.DOCSHARING_OBJ_TABLE_TAG_H5 = "doc list tag h5";
+
+	// whiteboard objects
+	ApeConsts.WHITEBOARD_OBJ_TABLE_ID = (ApeConsts.WHITEBOARD_SESSION_ID << 16) + 1;
+	ApeConsts.WHITEBOARD_OBJ_TABLE_NAME = "wbd list";
+	ApeConsts.WHITEBOARD_OBJ_TABLE_TAG = "wbd list tag";
+
+	// media sharing objects
+	ApeConsts.MEDIA_OBJ_TABLE_ID = (ApeConsts.MEDIA_SESSION_ID << 16) + 1;
+	ApeConsts.MEDIA_OBJ_TABLE_NAME = "med list";
+	ApeConsts.MEDIA_OBJ_TABLE_TAG = "med list tag";
+
+	// chat sharing objects
+	ApeConsts.CHAT_OBJ_TABLE_ID = (ApeConsts.CHAT_SESSION_ID << 16) + 1;
+	ApeConsts.CHAT_OBJ_TABLE_NAME = "chat list";
+	ApeConsts.CHAT_OBJ_TABLE_TAG = "chat list tag";
+
+	ApeConsts.AUDIO_OBJ_TABLE_ID = (ApeConsts.AUDIO_SESSION_ID << 16) + 1;
+	ApeConsts.AUDIO_OBJ_TABLE_NAME = "audio channel list";
+	ApeConsts.AUDIO_OBJ_TABLE_TAG = "audio channel list tag";
+
+	ApeConsts.VIDEO_OBJ_TABLE_ID = (ApeConsts.VIDEO_SESSION_ID << 16) + 1;
+	ApeConsts.VIDEO_OBJ_TABLE_NAME = "video channel list";
+	ApeConsts.VIDEO_OBJ_TABLE_TAG = "video channel list tag";
+
+	// screen sharing objects
+	ApeConsts.SCREENSHARING_OBJ_TABLE_ID = (ApeConsts.SCREENSHARING_SESSION_ID << 16) + 1;
+	ApeConsts.SCREEN_OBJ_TABLE_NAME = "scr list";
+	ApeConsts.SCREEN_OBJ_TABLE_TAG = "scr list tag";
+
+	// poll sharing objects
+	ApeConsts.POLL_OBJ_TABLE_ID = (ApeConsts.POLL_SESSION_ID << 16) + 1;
+	ApeConsts.VOTE_OBJ_TABLE_ID = (ApeConsts.POLL_SESSION_ID << 16) + 2;
+	ApeConsts.RECORD_OBJ_TABLE_ID = (ApeConsts.POLL_SESSION_ID << 16) + 3;
+	ApeConsts.SHAMLIVE_OBJ_TABLE_ID = (ApeConsts.POLL_SESSION_ID << 16) + 4;
+
+	ApeConsts.POLL_OBJ_TABLE_NAME = "poll list";
+	ApeConsts.POLL_OBJ_TABLE_TAG = "poll list tag";
+
+	// registry operation const
+	ApeConsts.REG_TABLE_INSERT_TAIL = 0xFFFFFF;
+	ApeConsts.REG_TABLE_DELETE_ALL = 0xFFFFFF;
+
+	ApeConsts.CJS_RELEASED = 0;
+	ApeConsts.CJS_JOINNING = 1;
+	ApeConsts.CJS_JOINNED = 2;
+
+	// 白板动作
+	ApeConsts.WBA_CLOSE = 1;
+	ApeConsts.WBA_CHANGE = 2;
+	ApeConsts.WBA_OPEN = 3;
+	ApeConsts.WBA_DOC_ANNOTATION = 4;
+	ApeConsts.WBA_LASER_PEN = 5;
+	;
+
+	var _temp = function () {
+	  if (typeof __REACT_HOT_LOADER__ === 'undefined') {
+	    return;
+	  }
+
+	  __REACT_HOT_LOADER__.register(ApeConsts, "ApeConsts", "D:/work/McuClient/src/apes/ApeConsts.js");
+	}();
+
+	;
+
+/***/ },
+/* 9 */
+/***/ function(module, exports, __webpack_require__) {
+
+	'use strict';
+
+	Object.defineProperty(exports, "__esModule", {
+	    value: true
+	});
+
+	var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); /**
+	                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      * Created by hoopoe8 on 2017/1/8.
+	                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      */
+
+
+	var _base64Js = __webpack_require__(10);
+
+	var _base64Js2 = _interopRequireDefault(_base64Js);
+
+	var _utf = __webpack_require__(11);
+
+	var _utf2 = _interopRequireDefault(_utf);
+
+	function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
+
+	function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
+
+	var EngineUtils = function () {
+	    function EngineUtils() {
+	        _classCallCheck(this, EngineUtils);
+	    }
+
+	    _createClass(EngineUtils, null, [{
+	        key: 'isEmptyObject',
+	        value: function isEmptyObject(O) {
+	            for (var x in O) {
+	                return false;
+	            }
+	            return true;
+	        }
+	    }, {
+	        key: 'arrayToJsonString',
+	        value: function arrayToJsonString(_param) {
+	            try {
+	                return JSON.stringify(_param);
+	            } catch (err) {
+	                console.log("arrayToJsonString error:" + err.message);
+	            }
+	            return null;
+	        }
+	    }, {
+	        key: 'arrayFromJsonString',
+	        value: function arrayFromJsonString(_param) {
+	            try {
+	                return JSON.parse(_param);
+	            } catch (err) {
+	                console.log("arrayFromJsonString error:" + err.message);
+	            }
+	            return null;
+	        }
+
+	        //生成时间戳后9位 保证唯一
+
+	    }, {
+	        key: 'creatSoleNumberFromTimestamp',
+	        value: function creatSoleNumberFromTimestamp() {
+	            var time = new Date().getTime();
+	            var timestamp = time % 1000000000; //time后9位
+	            return timestamp;
+	        }
+
+	        //生成时间戳毫秒
+
+	    }, {
+	        key: 'creatTimestamp',
+	        value: function creatTimestamp() {
+	            var time = parseInt(new Date().getTime() / 1000); //精确到毫秒
+	            return time;
+	        }
+	        //生成时间戳 string
+
+	    }, {
+	        key: 'creatTimestampStr',
+	        value: function creatTimestampStr() {
+	            var curTime = new Date();
+	            var timeStr = "" + curTime.getFullYear() + "-";
+	            timeStr += curTime.getMonth() + 1 + "-";
+	            timeStr += curTime.getDate() + "-";
+	            timeStr += curTime.getHours() + "-";
+	            timeStr += curTime.getMinutes() + "-";
+	            timeStr += curTime.getSeconds();
+	            return timeStr;
+	        }
+
+	        //生成时间戳  格式:"20170209"
+
+	    }, {
+	        key: 'creatTimestampYMD',
+	        value: function creatTimestampYMD() {
+	            var curTime = new Date();
+	            var year = "" + curTime.getFullYear();
+	            var month = "" + (curTime.getMonth() + 1);
+	            var day = "" + curTime.getDate();
+
+	            if (month.length < 2) {
+	                month = "0" + month;
+	            }
+	            if (day.length < 2) {
+	                day = "0" + day;
+	            }
+	            return year + month + day;
+	        }
+	    }, {
+	        key: 'objectToBase64',
+	        value: function objectToBase64(_object) {
+	            try {
+	                var _objectStr = JSON.stringify(_object);
+	                //console.log("objectToBase64------1----------")
+	                var byte = _utf2.default.setBytesFromString(_objectStr);
+	                //console.log("objectToBase64------2----------")
+	                var _objectBase64 = _base64Js2.default.fromByteArray(byte);
+	                return _objectBase64;
+	            } catch (err) {
+	                console.log("objectToBase64 err:" + err.message);
+	                return "";
+	            }
+	            return "";
+	        }
+	    }, {
+	        key: 'objectFromBase64',
+	        value: function objectFromBase64(_objectBase64) {
+	            try {
+	                var byte = _base64Js2.default.toByteArray(_objectBase64);
+	                var _objectStr = _utf2.default.getStringFromBytes(byte);
+	                var _object = JSON.parse(_objectStr);
+	                return _object;
+	            } catch (err) {
+	                console.log("objectFromBase64 err:" + err.message);
+	                return null;
+	            }
+	            return null;
+	        }
+	    }]);
+
+	    return EngineUtils;
+	}();
+
+	var _default = EngineUtils;
+	exports.default = _default;
+	;
+
+	var _temp = function () {
+	    if (typeof __REACT_HOT_LOADER__ === 'undefined') {
+	        return;
+	    }
+
+	    __REACT_HOT_LOADER__.register(EngineUtils, 'EngineUtils', 'D:/work/McuClient/src/EngineUtils.js');
+
+	    __REACT_HOT_LOADER__.register(_default, 'default', 'D:/work/McuClient/src/EngineUtils.js');
+	}();
+
+	;
+
+/***/ },
+/* 10 */
+/***/ function(module, exports) {
+
+	'use strict'
+
+	exports.byteLength = byteLength
+	exports.toByteArray = toByteArray
+	exports.fromByteArray = fromByteArray
+
+	var lookup = []
+	var revLookup = []
+	var Arr = typeof Uint8Array !== 'undefined' ? Uint8Array : Array
+
+	var code = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
+	for (var i = 0, len = code.length; i < len; ++i) {
+	  lookup[i] = code[i]
+	  revLookup[code.charCodeAt(i)] = i
+	}
+
+	revLookup['-'.charCodeAt(0)] = 62
+	revLookup['_'.charCodeAt(0)] = 63
+
+	function placeHoldersCount (b64) {
+	  var len = b64.length
+	  if (len % 4 > 0) {
+	    throw new Error('Invalid string. Length must be a multiple of 4')
+	  }
+
+	  // the number of equal signs (place holders)
+	  // if there are two placeholders, than the two characters before it
+	  // represent one byte
+	  // if there is only one, then the three characters before it represent 2 bytes
+	  // this is just a cheap hack to not do indexOf twice
+	  return b64[len - 2] === '=' ? 2 : b64[len - 1] === '=' ? 1 : 0
+	}
+
+	function byteLength (b64) {
+	  // base64 is 4/3 + up to two characters of the original data
+	  return b64.length * 3 / 4 - placeHoldersCount(b64)
+	}
+
+	function toByteArray (b64) {
+	  var i, j, l, tmp, placeHolders, arr
+	  var len = b64.length
+	  placeHolders = placeHoldersCount(b64)
+
+	  arr = new Arr(len * 3 / 4 - placeHolders)
+
+	  // if there are placeholders, only get up to the last complete 4 chars
+	  l = placeHolders > 0 ? len - 4 : len
+
+	  var L = 0
+
+	  for (i = 0, j = 0; i < l; i += 4, j += 3) {
+	    tmp = (revLookup[b64.charCodeAt(i)] << 18) | (revLookup[b64.charCodeAt(i + 1)] << 12) | (revLookup[b64.charCodeAt(i + 2)] << 6) | revLookup[b64.charCodeAt(i + 3)]
+	    arr[L++] = (tmp >> 16) & 0xFF
+	    arr[L++] = (tmp >> 8) & 0xFF
+	    arr[L++] = tmp & 0xFF
+	  }
+
+	  if (placeHolders === 2) {
+	    tmp = (revLookup[b64.charCodeAt(i)] << 2) | (revLookup[b64.charCodeAt(i + 1)] >> 4)
+	    arr[L++] = tmp & 0xFF
+	  } else if (placeHolders === 1) {
+	    tmp = (revLookup[b64.charCodeAt(i)] << 10) | (revLookup[b64.charCodeAt(i + 1)] << 4) | (revLookup[b64.charCodeAt(i + 2)] >> 2)
+	    arr[L++] = (tmp >> 8) & 0xFF
+	    arr[L++] = tmp & 0xFF
+	  }
+
+	  return arr
+	}
+
+	function tripletToBase64 (num) {
+	  return lookup[num >> 18 & 0x3F] + lookup[num >> 12 & 0x3F] + lookup[num >> 6 & 0x3F] + lookup[num & 0x3F]
+	}
+
+	function encodeChunk (uint8, start, end) {
+	  var tmp
+	  var output = []
+	  for (var i = start; i < end; i += 3) {
+	    tmp = (uint8[i] << 16) + (uint8[i + 1] << 8) + (uint8[i + 2])
+	    output.push(tripletToBase64(tmp))
+	  }
+	  return output.join('')
+	}
+
+	function fromByteArray (uint8) {
+	  var tmp
+	  var len = uint8.length
+	  var extraBytes = len % 3 // if we have 1 byte left, pad 2 bytes
+	  var output = ''
+	  var parts = []
+	  var maxChunkLength = 16383 // must be multiple of 3
+
+	  // go through the array every three bytes, we'll deal with trailing stuff later
+	  for (var i = 0, len2 = len - extraBytes; i < len2; i += maxChunkLength) {
+	    parts.push(encodeChunk(uint8, i, (i + maxChunkLength) > len2 ? len2 : (i + maxChunkLength)))
+	  }
+
+	  // pad the end with zeros, but make sure to not forget the extra bytes
+	  if (extraBytes === 1) {
+	    tmp = uint8[len - 1]
+	    output += lookup[tmp >> 2]
+	    output += lookup[(tmp << 4) & 0x3F]
+	    output += '=='
+	  } else if (extraBytes === 2) {
+	    tmp = (uint8[len - 2] << 8) + (uint8[len - 1])
+	    output += lookup[tmp >> 10]
+	    output += lookup[(tmp >> 4) & 0x3F]
+	    output += lookup[(tmp << 2) & 0x3F]
+	    output += '='
+	  }
+
+	  parts.push(output)
+
+	  return parts.join('')
+	}
+
+
+/***/ },
+/* 11 */
+/***/ function(module, exports, __webpack_require__) {
+
+	/* WEBPACK VAR INJECTION */(function(module) {// UTF8 : Manage UTF-8 strings in ArrayBuffers
+	if(module.require) {
+	  __webpack_require__(13);
+	  __webpack_require__(14);
+	}
+
+	var UTF8={
+	  // non UTF8 encoding detection (cf README file for details)
+	  'isNotUTF8': function(bytes, byteOffset, byteLength) {
+	    try {
+	      UTF8.getStringFromBytes(bytes, byteOffset, byteLength, true);
+	    } catch(e) {
+	      return true;
+	    }
+	    return false;
+	  },
+	  // UTF8 decoding functions
+	  'getCharLength': function(theByte) {
+	    // 4 bytes encoded char (mask 11110000)
+	    if(0xF0 == (theByte&0xF0)) {
+	      return 4;
+	    // 3 bytes encoded char (mask 11100000)
+	    } else if(0xE0 == (theByte&0xE0)) {
+	      return 3;
+	    // 2 bytes encoded char (mask 11000000)
+	    } else if(0xC0 == (theByte&0xC0)) {
+	      return 2;
+	    // 1 bytes encoded char
+	    } else if(theByte == (theByte&0x7F)) {
+	      return 1;
+	    }
+	    return 0;
+	  },
+	  'getCharCode': function(bytes, byteOffset, charLength) {
+	    var charCode = 0, mask = '';
+	    byteOffset = byteOffset || 0;
+	    // Retrieve charLength if not given
+	    charLength = charLength || UTF8.getCharLength(bytes[byteOffset]);
+	    if(charLength == 0) {
+	      throw new Error(bytes[byteOffset].toString(2)+' is not a significative' +
+	        ' byte (offset:'+byteOffset+').');
+	    }
+	    // Return byte value if charlength is 1
+	    if(1 === charLength) {
+	      return bytes[byteOffset];
+	    }
+	    // Test UTF8 integrity
+	    mask = '00000000'.slice(0, charLength) + 1 + '00000000'.slice(charLength + 1);
+	    if(bytes[byteOffset]&(parseInt(mask, 2))) {
+	      throw Error('Index ' + byteOffset + ': A ' + charLength + ' bytes' +
+	        ' encoded char' +' cannot encode the '+(charLength+1)+'th rank bit to 1.');
+	    }
+	    // Reading the first byte
+	    mask='0000'.slice(0,charLength+1)+'11111111'.slice(charLength+1);
+	    charCode+=(bytes[byteOffset]&parseInt(mask,2))<<((--charLength)*6);
+	    // Reading the next bytes
+	    while(charLength) {
+	      if(0x80!==(bytes[byteOffset+1]&0x80)
+	        ||0x40===(bytes[byteOffset+1]&0x40)) {
+	        throw Error('Index '+(byteOffset+1)+': Next bytes of encoded char'
+	          +' must begin with a "10" bit sequence.');
+	      }
+	      charCode += ((bytes[++byteOffset]&0x3F) << ((--charLength) * 6));
+	    }
+	    return charCode;
+	  },
+	  'getStringFromBytes': function(bytes, byteOffset, byteLength, strict) {
+	    var charLength, chars = [];
+	    byteOffset = byteOffset|0;
+	    byteLength=('number' === typeof byteLength ?
+	      byteLength :
+	      bytes.byteLength || bytes.length
+	    );
+	    for(; byteOffset < byteLength; byteOffset++) {
+	      charLength = UTF8.getCharLength(bytes[byteOffset]);
+	      if(byteOffset + charLength > byteLength) {
+	        if(strict) {
+	          throw Error('Index ' + byteOffset + ': Found a ' + charLength +
+	            ' bytes encoded char declaration but only ' +
+	            (byteLength - byteOffset) +' bytes are available.');
+	        }
+	      } else {
+	        chars.push(String.fromCodePoint(
+	          UTF8.getCharCode(bytes, byteOffset, charLength, strict)
+	        ));
+	      }
+	      byteOffset += charLength - 1;
+	    }
+	    return chars.join('');
+	  },
+	  // UTF8 encoding functions
+	  'getBytesForCharCode': function(charCode) {
+	    if(charCode < 128) {
+	      return 1;
+	    } else if(charCode < 2048) {
+	      return 2;
+	    } else if(charCode < 65536) {
+	      return 3;
+	    } else if(charCode < 2097152) {
+	      return 4;
+	    }
+	    throw new Error('CharCode '+charCode+' cannot be encoded with UTF8.');
+	  },
+	  'setBytesFromCharCode': function(charCode, bytes, byteOffset, neededBytes) {
+	    charCode = charCode|0;
+	    bytes = bytes || [];
+	    byteOffset = byteOffset|0;
+	    neededBytes = neededBytes || UTF8.getBytesForCharCode(charCode);
+	    // Setting the charCode as it to bytes if the byte length is 1
+	    if(1 == neededBytes) {
+	      bytes[byteOffset] = charCode;
+	    } else {
+	      // Computing the first byte
+	      bytes[byteOffset++] =
+	        (parseInt('1111'.slice(0, neededBytes), 2) << 8 - neededBytes) +
+	        (charCode >>> ((--neededBytes) * 6));
+	      // Computing next bytes
+	      for(;neededBytes>0;) {
+	        bytes[byteOffset++] = ((charCode>>>((--neededBytes) * 6))&0x3F)|0x80;
+	      }
+	    }
+	    return bytes;
+	  },
+	  'setBytesFromString': function(string, bytes, byteOffset, byteLength, strict) {
+	    string = string || '';
+	    bytes = bytes || [];
+	    byteOffset = byteOffset|0;
+	    byteLength = ('number' === typeof byteLength ?
+	      byteLength :
+	      bytes.byteLength||Infinity
+	    );
+	    for(var i = 0, j = string.length; i < j; i++) {
+	      var neededBytes = UTF8.getBytesForCharCode(string[i].codePointAt(0));
+	      if(strict && byteOffset + neededBytes > byteLength) {
+	        throw new Error('Not enought bytes to encode the char "' + string[i] +
+	          '" at the offset "' + byteOffset + '".');
+	      }
+	      UTF8.setBytesFromCharCode(string[i].codePointAt(0),
+	        bytes, byteOffset, neededBytes, strict);
+	      byteOffset += neededBytes;
+	    }
+	    return bytes;
+	  }
+	};
+
+	if(true) {
+	  module.exports = UTF8;
+	}
+
+
+	/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(12)(module)))
+
+/***/ },
+/* 12 */
+/***/ function(module, exports) {
+
+	module.exports = function(module) {
+		if(!module.webpackPolyfill) {
+			module.deprecate = function() {};
+			module.paths = [];
+			// module.parent = undefined by default
+			module.children = [];
+			module.webpackPolyfill = 1;
+		}
+		return module;
+	}
+
+
+/***/ },
+/* 13 */
+/***/ function(module, exports) {
+
+	/*! http://mths.be/fromcodepoint v0.2.1 by @mathias */
+	if (!String.fromCodePoint) {
+		(function() {
+			var defineProperty = (function() {
+				// IE 8 only supports `Object.defineProperty` on DOM elements
+				try {
+					var object = {};
+					var $defineProperty = Object.defineProperty;
+					var result = $defineProperty(object, object, object) && $defineProperty;
+				} catch(error) {}
+				return result;
+			}());
+			var stringFromCharCode = String.fromCharCode;
+			var floor = Math.floor;
+			var fromCodePoint = function(_) {
+				var MAX_SIZE = 0x4000;
+				var codeUnits = [];
+				var highSurrogate;
+				var lowSurrogate;
+				var index = -1;
+				var length = arguments.length;
+				if (!length) {
+					return '';
+				}
+				var result = '';
+				while (++index < length) {
+					var codePoint = Number(arguments[index]);
+					if (
+						!isFinite(codePoint) || // `NaN`, `+Infinity`, or `-Infinity`
+						codePoint < 0 || // not a valid Unicode code point
+						codePoint > 0x10FFFF || // not a valid Unicode code point
+						floor(codePoint) != codePoint // not an integer
+					) {
+						throw RangeError('Invalid code point: ' + codePoint);
+					}
+					if (codePoint <= 0xFFFF) { // BMP code point
+						codeUnits.push(codePoint);
+					} else { // Astral code point; split in surrogate halves
+						// http://mathiasbynens.be/notes/javascript-encoding#surrogate-formulae
+						codePoint -= 0x10000;
+						highSurrogate = (codePoint >> 10) + 0xD800;
+						lowSurrogate = (codePoint % 0x400) + 0xDC00;
+						codeUnits.push(highSurrogate, lowSurrogate);
+					}
+					if (index + 1 == length || codeUnits.length > MAX_SIZE) {
+						result += stringFromCharCode.apply(null, codeUnits);
+						codeUnits.length = 0;
+					}
+				}
+				return result;
+			};
+			if (defineProperty) {
+				defineProperty(String, 'fromCodePoint', {
+					'value': fromCodePoint,
+					'configurable': true,
+					'writable': true
+				});
+			} else {
+				String.fromCodePoint = fromCodePoint;
+			}
+		}());
+	}
+
+
+/***/ },
+/* 14 */
+/***/ function(module, exports) {
+
+	/*! http://mths.be/codepointat v0.2.0 by @mathias */
+	if (!String.prototype.codePointAt) {
+		(function() {
+			'use strict'; // needed to support `apply`/`call` with `undefined`/`null`
+			var defineProperty = (function() {
+				// IE 8 only supports `Object.defineProperty` on DOM elements
+				try {
+					var object = {};
+					var $defineProperty = Object.defineProperty;
+					var result = $defineProperty(object, object, object) && $defineProperty;
+				} catch(error) {}
+				return result;
+			}());
+			var codePointAt = function(position) {
+				if (this == null) {
+					throw TypeError();
+				}
+				var string = String(this);
+				var size = string.length;
+				// `ToInteger`
+				var index = position ? Number(position) : 0;
+				if (index != index) { // better `isNaN`
+					index = 0;
+				}
+				// Account for out-of-bounds indices:
+				if (index < 0 || index >= size) {
+					return undefined;
+				}
+				// Get the first code unit
+				var first = string.charCodeAt(index);
+				var second;
+				if ( // check if it’s the start of a surrogate pair
+					first >= 0xD800 && first <= 0xDBFF && // high surrogate
+					size > index + 1 // there is a next code unit
+				) {
+					second = string.charCodeAt(index + 1);
+					if (second >= 0xDC00 && second <= 0xDFFF) { // low surrogate
+						// http://mathiasbynens.be/notes/javascript-encoding#surrogate-formulae
+						return (first - 0xD800) * 0x400 + second - 0xDC00 + 0x10000;
+					}
+				}
+				return first;
+			};
+			if (defineProperty) {
+				defineProperty(String.prototype, 'codePointAt', {
+					'value': codePointAt,
+					'configurable': true,
+					'writable': true
+				});
+			} else {
+				String.prototype.codePointAt = codePointAt;
+			}
+		}());
+	}
+
+
+/***/ },
+/* 15 */
+/***/ function(module, exports, __webpack_require__) {
+
+	(function(){
+	  var crypt = __webpack_require__(16),
+	      utf8 = __webpack_require__(17).utf8,
+	      isBuffer = __webpack_require__(18),
+	      bin = __webpack_require__(17).bin,
+
+	  // The core
+	  md5 = function (message, options) {
+	    // Convert to byte array
+	    if (message.constructor == String)
+	      if (options && options.encoding === 'binary')
+	        message = bin.stringToBytes(message);
+	      else
+	        message = utf8.stringToBytes(message);
+	    else if (isBuffer(message))
+	      message = Array.prototype.slice.call(message, 0);
+	    else if (!Array.isArray(message))
+	      message = message.toString();
+	    // else, assume byte array already
+
+	    var m = crypt.bytesToWords(message),
+	        l = message.length * 8,
+	        a =  1732584193,
+	        b = -271733879,
+	        c = -1732584194,
+	        d =  271733878;
+
+	    // Swap endian
+	    for (var i = 0; i < m.length; i++) {
+	      m[i] = ((m[i] <<  8) | (m[i] >>> 24)) & 0x00FF00FF |
+	             ((m[i] << 24) | (m[i] >>>  8)) & 0xFF00FF00;
+	    }
+
+	    // Padding
+	    m[l >>> 5] |= 0x80 << (l % 32);
+	    m[(((l + 64) >>> 9) << 4) + 14] = l;
+
+	    // Method shortcuts
+	    var FF = md5._ff,
+	        GG = md5._gg,
+	        HH = md5._hh,
+	        II = md5._ii;
+
+	    for (var i = 0; i < m.length; i += 16) {
+
+	      var aa = a,
+	          bb = b,
+	          cc = c,
+	          dd = d;
+
+	      a = FF(a, b, c, d, m[i+ 0],  7, -680876936);
+	      d = FF(d, a, b, c, m[i+ 1], 12, -389564586);
+	      c = FF(c, d, a, b, m[i+ 2], 17,  606105819);
+	      b = FF(b, c, d, a, m[i+ 3], 22, -1044525330);
+	      a = FF(a, b, c, d, m[i+ 4],  7, -176418897);
+	      d = FF(d, a, b, c, m[i+ 5], 12,  1200080426);
+	      c = FF(c, d, a, b, m[i+ 6], 17, -1473231341);
+	      b = FF(b, c, d, a, m[i+ 7], 22, -45705983);
+	      a = FF(a, b, c, d, m[i+ 8],  7,  1770035416);
+	      d = FF(d, a, b, c, m[i+ 9], 12, -1958414417);
+	      c = FF(c, d, a, b, m[i+10], 17, -42063);
+	      b = FF(b, c, d, a, m[i+11], 22, -1990404162);
+	      a = FF(a, b, c, d, m[i+12],  7,  1804603682);
+	      d = FF(d, a, b, c, m[i+13], 12, -40341101);
+	      c = FF(c, d, a, b, m[i+14], 17, -1502002290);
+	      b = FF(b, c, d, a, m[i+15], 22,  1236535329);
+
+	      a = GG(a, b, c, d, m[i+ 1],  5, -165796510);
+	      d = GG(d, a, b, c, m[i+ 6],  9, -1069501632);
+	      c = GG(c, d, a, b, m[i+11], 14,  643717713);
+	      b = GG(b, c, d, a, m[i+ 0], 20, -373897302);
+	      a = GG(a, b, c, d, m[i+ 5],  5, -701558691);
+	      d = GG(d, a, b, c, m[i+10],  9,  38016083);
+	      c = GG(c, d, a, b, m[i+15], 14, -660478335);
+	      b = GG(b, c, d, a, m[i+ 4], 20, -405537848);
+	      a = GG(a, b, c, d, m[i+ 9],  5,  568446438);
+	      d = GG(d, a, b, c, m[i+14],  9, -1019803690);
+	      c = GG(c, d, a, b, m[i+ 3], 14, -187363961);
+	      b = GG(b, c, d, a, m[i+ 8], 20,  1163531501);
+	      a = GG(a, b, c, d, m[i+13],  5, -1444681467);
+	      d = GG(d, a, b, c, m[i+ 2],  9, -51403784);
+	      c = GG(c, d, a, b, m[i+ 7], 14,  1735328473);
+	      b = GG(b, c, d, a, m[i+12], 20, -1926607734);
+
+	      a = HH(a, b, c, d, m[i+ 5],  4, -378558);
+	      d = HH(d, a, b, c, m[i+ 8], 11, -2022574463);
+	      c = HH(c, d, a, b, m[i+11], 16,  1839030562);
+	      b = HH(b, c, d, a, m[i+14], 23, -35309556);
+	      a = HH(a, b, c, d, m[i+ 1],  4, -1530992060);
+	      d = HH(d, a, b, c, m[i+ 4], 11,  1272893353);
+	      c = HH(c, d, a, b, m[i+ 7], 16, -155497632);
+	      b = HH(b, c, d, a, m[i+10], 23, -1094730640);
+	      a = HH(a, b, c, d, m[i+13],  4,  681279174);
+	      d = HH(d, a, b, c, m[i+ 0], 11, -358537222);
+	      c = HH(c, d, a, b, m[i+ 3], 16, -722521979);
+	      b = HH(b, c, d, a, m[i+ 6], 23,  76029189);
+	      a = HH(a, b, c, d, m[i+ 9],  4, -640364487);
+	      d = HH(d, a, b, c, m[i+12], 11, -421815835);
+	      c = HH(c, d, a, b, m[i+15], 16,  530742520);
+	      b = HH(b, c, d, a, m[i+ 2], 23, -995338651);
+
+	      a = II(a, b, c, d, m[i+ 0],  6, -198630844);
+	      d = II(d, a, b, c, m[i+ 7], 10,  1126891415);
+	      c = II(c, d, a, b, m[i+14], 15, -1416354905);
+	      b = II(b, c, d, a, m[i+ 5], 21, -57434055);
+	      a = II(a, b, c, d, m[i+12],  6,  1700485571);
+	      d = II(d, a, b, c, m[i+ 3], 10, -1894986606);
+	      c = II(c, d, a, b, m[i+10], 15, -1051523);
+	      b = II(b, c, d, a, m[i+ 1], 21, -2054922799);
+	      a = II(a, b, c, d, m[i+ 8],  6,  1873313359);
+	      d = II(d, a, b, c, m[i+15], 10, -30611744);
+	      c = II(c, d, a, b, m[i+ 6], 15, -1560198380);
+	      b = II(b, c, d, a, m[i+13], 21,  1309151649);
+	      a = II(a, b, c, d, m[i+ 4],  6, -145523070);
+	      d = II(d, a, b, c, m[i+11], 10, -1120210379);
+	      c = II(c, d, a, b, m[i+ 2], 15,  718787259);
+	      b = II(b, c, d, a, m[i+ 9], 21, -343485551);
+
+	      a = (a + aa) >>> 0;
+	      b = (b + bb) >>> 0;
+	      c = (c + cc) >>> 0;
+	      d = (d + dd) >>> 0;
+	    }
+
+	    return crypt.endian([a, b, c, d]);
+	  };
+
+	  // Auxiliary functions
+	  md5._ff  = function (a, b, c, d, x, s, t) {
+	    var n = a + (b & c | ~b & d) + (x >>> 0) + t;
+	    return ((n << s) | (n >>> (32 - s))) + b;
+	  };
+	  md5._gg  = function (a, b, c, d, x, s, t) {
+	    var n = a + (b & d | c & ~d) + (x >>> 0) + t;
+	    return ((n << s) | (n >>> (32 - s))) + b;
+	  };
+	  md5._hh  = function (a, b, c, d, x, s, t) {
+	    var n = a + (b ^ c ^ d) + (x >>> 0) + t;
+	    return ((n << s) | (n >>> (32 - s))) + b;
+	  };
+	  md5._ii  = function (a, b, c, d, x, s, t) {
+	    var n = a + (c ^ (b | ~d)) + (x >>> 0) + t;
+	    return ((n << s) | (n >>> (32 - s))) + b;
+	  };
+
+	  // Package private blocksize
+	  md5._blocksize = 16;
+	  md5._digestsize = 16;
+
+	  module.exports = function (message, options) {
+	    if (message === undefined || message === null)
+	      throw new Error('Illegal argument ' + message);
+
+	    var digestbytes = crypt.wordsToBytes(md5(message, options));
+	    return options && options.asBytes ? digestbytes :
+	        options && options.asString ? bin.bytesToString(digestbytes) :
+	        crypt.bytesToHex(digestbytes);
+	  };
+
+	})();
+
+
+/***/ },
+/* 16 */
+/***/ function(module, exports) {
+
+	(function() {
+	  var base64map
+	      = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/',
+
+	  crypt = {
+	    // Bit-wise rotation left
+	    rotl: function(n, b) {
+	      return (n << b) | (n >>> (32 - b));
+	    },
+
+	    // Bit-wise rotation right
+	    rotr: function(n, b) {
+	      return (n << (32 - b)) | (n >>> b);
+	    },
+
+	    // Swap big-endian to little-endian and vice versa
+	    endian: function(n) {
+	      // If number given, swap endian
+	      if (n.constructor == Number) {
+	        return crypt.rotl(n, 8) & 0x00FF00FF | crypt.rotl(n, 24) & 0xFF00FF00;
+	      }
+
+	      // Else, assume array and swap all items
+	      for (var i = 0; i < n.length; i++)
+	        n[i] = crypt.endian(n[i]);
+	      return n;
+	    },
+
+	    // Generate an array of any length of random bytes
+	    randomBytes: function(n) {
+	      for (var bytes = []; n > 0; n--)
+	        bytes.push(Math.floor(Math.random() * 256));
+	      return bytes;
+	    },
+
+	    // Convert a byte array to big-endian 32-bit words
+	    bytesToWords: function(bytes) {
+	      for (var words = [], i = 0, b = 0; i < bytes.length; i++, b += 8)
+	        words[b >>> 5] |= bytes[i] << (24 - b % 32);
+	      return words;
+	    },
+
+	    // Convert big-endian 32-bit words to a byte array
+	    wordsToBytes: function(words) {
+	      for (var bytes = [], b = 0; b < words.length * 32; b += 8)
+	        bytes.push((words[b >>> 5] >>> (24 - b % 32)) & 0xFF);
+	      return bytes;
+	    },
+
+	    // Convert a byte array to a hex string
+	    bytesToHex: function(bytes) {
+	      for (var hex = [], i = 0; i < bytes.length; i++) {
+	        hex.push((bytes[i] >>> 4).toString(16));
+	        hex.push((bytes[i] & 0xF).toString(16));
+	      }
+	      return hex.join('');
+	    },
+
+	    // Convert a hex string to a byte array
+	    hexToBytes: function(hex) {
+	      for (var bytes = [], c = 0; c < hex.length; c += 2)
+	        bytes.push(parseInt(hex.substr(c, 2), 16));
+	      return bytes;
+	    },
+
+	    // Convert a byte array to a base-64 string
+	    bytesToBase64: function(bytes) {
+	      for (var base64 = [], i = 0; i < bytes.length; i += 3) {
+	        var triplet = (bytes[i] << 16) | (bytes[i + 1] << 8) | bytes[i + 2];
+	        for (var j = 0; j < 4; j++)
+	          if (i * 8 + j * 6 <= bytes.length * 8)
+	            base64.push(base64map.charAt((triplet >>> 6 * (3 - j)) & 0x3F));
+	          else
+	            base64.push('=');
+	      }
+	      return base64.join('');
+	    },
+
+	    // Convert a base-64 string to a byte array
+	    base64ToBytes: function(base64) {
+	      // Remove non-base-64 characters
+	      base64 = base64.replace(/[^A-Z0-9+\/]/ig, '');
+
+	      for (var bytes = [], i = 0, imod4 = 0; i < base64.length;
+	          imod4 = ++i % 4) {
+	        if (imod4 == 0) continue;
+	        bytes.push(((base64map.indexOf(base64.charAt(i - 1))
+	            & (Math.pow(2, -2 * imod4 + 8) - 1)) << (imod4 * 2))
+	            | (base64map.indexOf(base64.charAt(i)) >>> (6 - imod4 * 2)));
+	      }
+	      return bytes;
+	    }
+	  };
+
+	  module.exports = crypt;
+	})();
+
+
+/***/ },
+/* 17 */
+/***/ function(module, exports) {
+
+	var charenc = {
+	  // UTF-8 encoding
+	  utf8: {
+	    // Convert a string to a byte array
+	    stringToBytes: function(str) {
+	      return charenc.bin.stringToBytes(unescape(encodeURIComponent(str)));
+	    },
+
+	    // Convert a byte array to a string
+	    bytesToString: function(bytes) {
+	      return decodeURIComponent(escape(charenc.bin.bytesToString(bytes)));
+	    }
+	  },
+
+	  // Binary encoding
+	  bin: {
+	    // Convert a string to a byte array
+	    stringToBytes: function(str) {
+	      for (var bytes = [], i = 0; i < str.length; i++)
+	        bytes.push(str.charCodeAt(i) & 0xFF);
+	      return bytes;
+	    },
+
+	    // Convert a byte array to a string
+	    bytesToString: function(bytes) {
+	      for (var str = [], i = 0; i < bytes.length; i++)
+	        str.push(String.fromCharCode(bytes[i]));
+	      return str.join('');
+	    }
+	  }
+	};
+
+	module.exports = charenc;
+
+
+/***/ },
+/* 18 */
+/***/ function(module, exports) {
+
+	/*!
+	 * Determine if an object is a Buffer
+	 *
+	 * @author   Feross Aboukhadijeh <feross@feross.org> <http://feross.org>
+	 * @license  MIT
+	 */
+
+	// The _isBuffer check is for Safari 5-7 support, because it's missing
+	// Object.prototype.constructor. Remove this eventually
+	module.exports = function (obj) {
+	  return obj != null && (isBuffer(obj) || isSlowBuffer(obj) || !!obj._isBuffer)
+	}
+
+	function isBuffer (obj) {
+	  return !!obj.constructor && typeof obj.constructor.isBuffer === 'function' && obj.constructor.isBuffer(obj)
+	}
+
+	// For Node v0.10 support. Remove this eventually.
+	function isSlowBuffer (obj) {
+	  return typeof obj.readFloatLE === 'function' && typeof obj.slice === 'function' && isBuffer(obj.slice(0, 0))
+	}
+
+
+/***/ },
+/* 19 */
+/***/ function(module, exports, __webpack_require__) {
+
+	!function(e,t){ true?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.iphunter=t():e.iphunter=t()}(this,function(){return function(e){function t(i){if(n[i])return n[i].exports;var r=n[i]={exports:{},id:i,loaded:!1};return e[i].call(r.exports,r,r.exports,t),r.loaded=!0,r.exports}var n={};return t.m=e,t.c=n,t.p="",t(0)}([function(e,t,n){e.exports=n(1)},function(e,t){"use strict";function n(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function i(e,t){var n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:3e3;if(!(e&&e.length&&t))throw new Error("ips and callback are required.");new o(e,t,n)}Object.defineProperty(t,"__esModule",{value:!0});var r=function(){function e(e,t){for(var n=0;n<t.length;n++){var i=t[n];i.enumerable=i.enumerable||!1,i.configurable=!0,"value"in i&&(i.writable=!0),Object.defineProperty(e,i.key,i)}}return function(t,n,i){return n&&e(t.prototype,n),i&&e(t,i),t}}();t.default=i;var o=function(){function e(t,i,r){n(this,e),this.ip="",this.ipcallback=i,this.timeoutId=null,this.reqsCache=[];for(var o=0;o<t.length;o++)this.reqsCache.push(this.send(t[o],r-10));this.timeoutId=setTimeout(this.notify.bind(this),r)}return r(e,[{key:"clearAll",value:function(){this.reqsCache&&this.reqsCache.length&&this.reqsCache.forEach(function(e){e.abort()}),clearTimeout(this.timeoutId),this.ip="",this.ipcallback=null,this.timeoutId=null,this.reqsCache=[]}},{key:"clearReq",value:function(e){this.reqsCache.splice(this.reqsCache.indexOf(e),1)}},{key:"notify",value:function(){this.ipcallback(this.ip),this.clearAll()}},{key:"send",value:function(e,t){var n=this,i=new XMLHttpRequest;return i.open("HEAD","//"+e+"/?_="+Date.now()),i.timeout=t,i.onload=function(){n.ip=e,n.clearReq(i),i.onload=null,n.notify()},i.ontimeout=function(){n.clearReq(i),i.ontimeout=null},i.onerror=function(){n.clearReq(i),i.onerror=null},i.onabort=function(){n.clearReq(i),i.onabort=null},i.send(),i}}]),e}();(function(){"undefined"!=typeof __REACT_HOT_LOADER__&&(__REACT_HOT_LOADER__.register(o,"IpHunter","/Users/AlexWang/ws/iphunter/src/main.js"),__REACT_HOT_LOADER__.register(i,"check","/Users/AlexWang/ws/iphunter/src/main.js"))})()}])});
+
+/***/ },
+/* 20 */
+/***/ function(module, exports, __webpack_require__) {
+
+	var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;(function (global, factory) {
+	  if (true) {
+	    !(__WEBPACK_AMD_DEFINE_ARRAY__ = [exports, module], __WEBPACK_AMD_DEFINE_FACTORY__ = (factory), __WEBPACK_AMD_DEFINE_RESULT__ = (typeof __WEBPACK_AMD_DEFINE_FACTORY__ === 'function' ? (__WEBPACK_AMD_DEFINE_FACTORY__.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__)) : __WEBPACK_AMD_DEFINE_FACTORY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));
+	  } else if (typeof exports !== 'undefined' && typeof module !== 'undefined') {
+	    factory(exports, module);
+	  } else {
+	    var mod = {
+	      exports: {}
+	    };
+	    factory(mod.exports, mod);
+	    global.fetchJsonp = mod.exports;
+	  }
+	})(this, function (exports, module) {
+	  'use strict';
+
+	  var defaultOptions = {
+	    timeout: 5000,
+	    jsonpCallback: 'callback',
+	    jsonpCallbackFunction: null
+	  };
+
+	  function generateCallbackFunction() {
+	    return 'jsonp_' + Date.now() + '_' + Math.ceil(Math.random() * 100000);
+	  }
+
+	  // Known issue: Will throw 'Uncaught ReferenceError: callback_*** is not defined'
+	  // error if request timeout
+	  function clearFunction(functionName) {
+	    // IE8 throws an exception when you try to delete a property on window
+	    // http://stackoverflow.com/a/1824228/751089
+	    try {
+	      delete window[functionName];
+	    } catch (e) {
+	      window[functionName] = undefined;
+	    }
+	  }
+
+	  function removeScript(scriptId) {
+	    var script = document.getElementById(scriptId);
+	    document.getElementsByTagName('head')[0].removeChild(script);
+	  }
+
+	  function fetchJsonp(_url) {
+	    var options = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1];
+
+	    // to avoid param reassign
+	    var url = _url;
+	    var timeout = options.timeout || defaultOptions.timeout;
+	    var jsonpCallback = options.jsonpCallback || defaultOptions.jsonpCallback;
+
+	    var timeoutId = undefined;
+
+	    return new Promise(function (resolve, reject) {
+	      var callbackFunction = options.jsonpCallbackFunction || generateCallbackFunction();
+	      var scriptId = jsonpCallback + '_' + callbackFunction;
+
+	      window[callbackFunction] = function (response) {
+	        resolve({
+	          ok: true,
+	          // keep consistent with fetch API
+	          json: function json() {
+	            return Promise.resolve(response);
+	          }
+	        });
+
+	        if (timeoutId) clearTimeout(timeoutId);
+
+	        removeScript(scriptId);
+
+	        clearFunction(callbackFunction);
+	      };
+
+	      // Check if the user set their own params, and if not add a ? to start a list of params
+	      url += url.indexOf('?') === -1 ? '?' : '&';
+
+	      var jsonpScript = document.createElement('script');
+	      jsonpScript.setAttribute('src', '' + url + jsonpCallback + '=' + callbackFunction);
+	      jsonpScript.id = scriptId;
+	      document.getElementsByTagName('head')[0].appendChild(jsonpScript);
+
+	      timeoutId = setTimeout(function () {
+	        reject(new Error('JSONP request to ' + _url + ' timed out'));
+
+	        clearFunction(callbackFunction);
+	        removeScript(scriptId);
+	      }, timeout);
+	    });
+	  }
+
+	  // export as global function
+	  /*
+	  let local;
+	  if (typeof global !== 'undefined') {
+	    local = global;
+	  } else if (typeof self !== 'undefined') {
+	    local = self;
+	  } else {
+	    try {
+	      local = Function('return this')();
+	    } catch (e) {
+	      throw new Error('polyfill failed because global object is unavailable in this environment');
+	    }
+	  }
+	  local.fetchJsonp = fetchJsonp;
+	  */
+
+	  module.exports = fetchJsonp;
+	});
+
+/***/ },
+/* 21 */
+/***/ function(module, exports, __webpack_require__) {
+
+	'use strict';
+
+	Object.defineProperty(exports, "__esModule", {
+	    value: true
+	});
+
+	var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
+
+	var _Emiter2 = __webpack_require__(3);
+
+	var _Emiter3 = _interopRequireDefault(_Emiter2);
+
+	var _Loger = __webpack_require__(5);
+
+	var _Loger2 = _interopRequireDefault(_Loger);
+
+	var _MessageTypes = __webpack_require__(6);
+
+	var _MessageTypes2 = _interopRequireDefault(_MessageTypes);
+
+	var _GlobalConfig = __webpack_require__(7);
+
+	var _GlobalConfig2 = _interopRequireDefault(_GlobalConfig);
+
+	var _md = __webpack_require__(15);
+
+	var _md2 = _interopRequireDefault(_md);
+
+	var _ApeConsts = __webpack_require__(8);
+
+	var _ApeConsts2 = _interopRequireDefault(_ApeConsts);
+
+	var _iphunter = __webpack_require__(19);
+
+	var _iphunter2 = _interopRequireDefault(_iphunter);
+
+	var _Server = __webpack_require__(22);
+
+	var _Server2 = _interopRequireDefault(_Server);
+
+	var _fetchJsonp = __webpack_require__(20);
+
+	var _fetchJsonp2 = _interopRequireDefault(_fetchJsonp);
+
+	function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
+
+	function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
+
+	function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }
+
+	function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
+
+	// 日志对象
+	var loger = _Loger2.default.getLoger('ServerCheck');
+
+	//ip选点流程的状态
+	var isRequestMcuCallback = false; //是否获取最佳mcu返回
+	var isRequestMsCallback = false; //是否获取ms最佳返回
+	var isTestFromSass = false;
+	var isTestFromServer = false;
+
+	var tempMcuIp = "";
+	var tempMcuPort = "";
+	var tempMsIp = "";
+	var tempMsPort = "";
+
+	var ServerCheck = function (_Emiter) {
+	    _inherits(ServerCheck, _Emiter);
+
+	    function ServerCheck() {
+	        _classCallCheck(this, ServerCheck);
+
+	        return _possibleConstructorReturn(this, (ServerCheck.__proto__ || Object.getPrototypeOf(ServerCheck)).call(this));
+	    }
+
+	    //根据userIp获取ip相关的信息,参数是userIp
+
+
+	    _createClass(ServerCheck, [{
+	        key: 'getUserIpInfo',
+	        value: function getUserIpInfo(token, userIp) {
+	            //重置ip选点流程状态
+	            isRequestMcuCallback = false;
+	            isRequestMsCallback = false;
+	            isTestFromSass = false;
+	            isTestFromServer = false;
+
+	            var userIpInfo = new Object();
+	            userIpInfo.ret = -1;
+
+	            var ip = userIp;
+	            var md5Str = (0, _md2.default)("addr=" + ip + "&token=b657c3507b324353e09c1958ee956a98efceb3e3"); //("addr=" + ip + "&token=b657c3507b324353e09c1958ee956a98efceb3e3"),转成MD5
+	            var timestamp = new Date().getTime();
+	            var location = 'http://ipapi.ipip.net/find?addr=' + ip + '&sid=14&uid=5237&sig=' + md5Str + '&_=' + timestamp;
+	            loger.log('获取IP信息 ', userIp, location);
+
+	            (0, _fetchJsonp2.default)(location, {
+	                timeout: 3000
+	            }).then(function (response) {
+	                return response.json();
+	            }).then(function (json) {
+	                loger.log('获取IP信息返回', json);
+	                if (json) {
+	                    userIpInfo.ret = json.ret;
+	                    userIpInfo.country = json.data[0]; //国家
+	                    userIpInfo.province = json.data[1]; //省份
+	                    userIpInfo.city = json.data[2]; //城市
+	                    userIpInfo.isp = json.data[4]; //运营商
+	                }
+	                this.serverGetUserIpInfoCallback(userIpInfo);
+	            }.bind(this)).catch(function (ex) {
+	                loger.log('获取IP信息失败', ex.message);
+	                this.serverGetUserIpInfoCallback(userIpInfo);
+	            }.bind(this));
+	        }
+
+	        //获取ip信息返回
+
+	    }, {
+	        key: 'serverGetUserIpInfoCallback',
+	        value: function serverGetUserIpInfoCallback(userIpInfo) {
+	            loger.log("获取IP详情,开始处理", userIpInfo);
+	            if (userIpInfo.ret == "ok") {
+	                _GlobalConfig2.default.country = userIpInfo.country; //国家
+	                _GlobalConfig2.default.city = userIpInfo.city; //城市
+	                _GlobalConfig2.default.province = userIpInfo.province; //服务商
+	                _GlobalConfig2.default.isp = userIpInfo.isp; //服务商
+	                loger.log("获取ip详情成功,country:" + _GlobalConfig2.default.country + ",city:" + _GlobalConfig2.default.city + ",isp:" + _GlobalConfig2.default.isp);
+	                this._chooseBestIpFromServer();
+	            } else {
+	                loger.log("获取ip详情失败");
+	                this._chooseBestIpFromSassParam();
+	            }
+	        }
+
+	        //从IPIP服务列表中选择最快的IP
+
+	    }, {
+	        key: '_chooseBestIpFromServer',
+	        value: function _chooseBestIpFromServer() {
+	            loger.log("从IPIP服务列表中选择最快的IP");
+	            isRequestMcuCallback = false;
+	            isRequestMsCallback = false;
+	            isTestFromServer = true;
+	            isTestFromSass = false;
+
+	            var mcuIpGroup = this._returnServerMS();
+	            var msIpGroup = this._returnServerMCU();
+	            this.getBestMcuServer(mcuIpGroup);
+	            this.getBestMsServer(msIpGroup);
+	        }
+	        //从Sass返回的msList   mcuList中选点
+
+	    }, {
+	        key: '_chooseBestIpFromSassParam',
+	        value: function _chooseBestIpFromSassParam() {
+	            loger.log("从Sass服务列表中选择最快的IP");
+	            isRequestMcuCallback = false;
+	            isRequestMsCallback = false;
+	            isTestFromServer = true;
+	            isTestFromSass = true;
+	            //MCU
+	            var mcuIpGroup = [];
+	            var speedTestPort = ':8080'; //测速端口统一
+	            for (var i = 0; i < _GlobalConfig2.default.mcuList.length; i++) {
+	                var ipPort = _GlobalConfig2.default.mcuList[i].ip + speedTestPort;
+	                mcuIpGroup.push(ipPort);
+	            }
+	            this.getBestMcuServer(mcuIpGroup);
+
+	            //MS
+	            var msIpGroup = [];
+	            for (var _i = 0; _i < _GlobalConfig2.default.msList.length; _i++) {
+	                var _ipPort = _GlobalConfig2.default.msList[_i].ip + speedTestPort;
+	                msIpGroup.push(_ipPort);
+	            }
+	            this.getBestMsServer(msIpGroup);
+	        }
+
+	        //获取最快的MCU服务器地址,参数是一个ip数组
+
+	    }, {
+	        key: 'getBestMcuServer',
+	        value: function getBestMcuServer(_param) {
+	            loger.log('getBestMcuServer ', _param);
+	            if (_param == null || _param.length < 1) {
+	                this._getBestMcuServerCallbackHandler("");
+	                return;
+	            }
+	            (0, _iphunter2.default)(_param, function (fatest_ip_response) {
+	                if (!fatest_ip_response) {
+	                    loger.warn('getBestMcuServer -> nothing!');
+	                    this._getBestMcuServerCallbackHandler("");
+	                } else {
+	                    loger.log('getBestMcuServer done -> ', fatest_ip_response);
+	                    this._getBestMcuServerCallbackHandler(fatest_ip_response);
+	                }
+	            }.bind(this), 3000);
+	        }
+
+	        //获取最快的MS服务器地址,参数是一个ip数组
+
+	    }, {
+	        key: 'getBestMsServer',
+	        value: function getBestMsServer(_param) {
+	            loger.log('getBestMsServer ', _param);
+	            if (_param == null || _param.length < 1) {
+	                this._getBestMsServerCallbackHandler("");
+	                return;
+	            }
+	            (0, _iphunter2.default)(_param, function (fatest_ip_response) {
+	                if (!fatest_ip_response) {
+	                    loger.warn('getBestMsServer -> nothing!');
+	                    this._getBestMsServerCallbackHandler("");
+	                } else {
+	                    loger.log('getBestMsServer done -> ', fatest_ip_response);
+	                    this._getBestMsServerCallbackHandler(fatest_ip_response);
+	                }
+	            }.bind(this), 3000);
+	        }
+	    }, {
+	        key: '_getBestMcuServerCallbackHandler',
+	        value: function _getBestMcuServerCallbackHandler(_data) {
+	            loger.log("_getBestMcuServerCallbackHandler", _data);
+	            if (isRequestMcuCallback) {
+	                loger.log("_getBestMcuServerCallbackHandler,已经有返回");
+	                return;
+	            }
+	            isRequestMcuCallback = true;
+	            if (_data) {
+	                var server = _data.split(":");
+	                if (server[0]) {
+	                    tempMcuIp = server[0];
+	                }
+	                if (server[1]) {
+	                    tempMcuPort = server[1];
+	                }
+	            }
+	            //loger.log("_getBestMcuServerCallbackHandler",tempMcuIp,tempMcuPort);
+	            this._startConnectMcu();
+	        }
+	    }, {
+	        key: '_getBestMsServerCallbackHandler',
+	        value: function _getBestMsServerCallbackHandler(_data) {
+	            loger.log("_getBestMsServerCallbackHandler", _data);
+	            if (isRequestMsCallback) {
+	                loger.log("_getBestMsServerCallbackHandler,已经有返回");
+	                return;
+	            }
+	            isRequestMsCallback = true;
+	            if (_data) {
+	                var server = _data.split(":");
+	                if (server[0]) {
+	                    tempMsIp = server[0];
+	                }
+	                if (server[1]) {
+	                    tempMsPort = server[1];
+	                }
+	            }
+	            //loger.log("_getBestMsServerCallbackHandler", tempMsIp,tempMsPort);
+	            this._startConnectMcu();
+	        }
+
+	        //ip选点结束,开始连接MCU
+
+	    }, {
+	        key: '_startConnectMcu',
+	        value: function _startConnectMcu() {
+	            if (isRequestMcuCallback && isRequestMsCallback) {
+	                if (isTestFromServer && !isTestFromSass) {
+	                    //从Server服务列表中选点结束,如果没有选到合适的,从Sass的列表中获取
+	                    if (!tempMcuIp || !tempMsIp) {
+	                        this._chooseBestIpFromSassParam();
+	                    } else {
+	                        this._emit(ServerCheck.SEVER_CHECK_BEST_IP_SUCCESS);
+	                    }
+	                } else {
+	                    //从Sass返回的服务列表中选点结束
+	                    this._emit(ServerCheck.SEVER_CHECK_BEST_IP_SUCCESS);
+	                }
+	            } else {
+	                loger.warn("_startConnectMcu 正在选点", isRequestMcuCallback, isRequestMsCallback);
+	            }
+	        }
+
+	        //检测MCU连接地址
+
+	    }, {
+	        key: '_returnServerMCU',
+	        value: function _returnServerMCU(country, province, ctiy, isp, jsona) {
+	            var arr = [];
+	            return arr;
+	        }
+	        //检测MS连接地址
+	        //Config.ipInfo
+
+	    }, {
+	        key: '_returnServerMS',
+	        value: function _returnServerMS(country, province, ctiy, isp, jsona) {
+	            var arr = [];
+	            /*   let acquire = false;
+	               if (isp != "") {
+	                   for (let obja in jsona.MS.isp) {
+	                       if (isp.indexOf(obja.idc) != -1) {
+	                           arr = obja.mslist;
+	                           acquire = true;
+	                           break;
+	                       }
+	                   }
+	               }
+	               if (country == "中国" && !acquire) {
+	                   for (let obja in jsona.MS.china) {
+	                       if (obja.province.indexOf(province) != -1 && province != "") {
+	                           arr = obja.mslist;
+	                           acquire = true;
+	                           break;
+	                       }
+	                   }
+	                   if (!acquire) {
+	                       arr = jsona.MS.china[jsona.MS.china.length - 1].mslist;
+	                       acquire = true;
+	                   }
+	               }
+	                 if (country != "中国" && country != "") {
+	                   for (let obja:Object in jsona.MS.international) {
+	                       if (obja.country.indexOf(country) != -1) {
+	                           arr = obja.mslist;
+	                           acquire = true;
+	                           break;
+	                       }
+	                   }
+	                   if (!acquire) {
+	                       arr = jsona.MS.international[jsona.MS.international.length - 1].mslist;
+	                       acquire = true;
+	                   }
+	               }
+	               else if (!acquire) {
+	                   arr = jsona.MS.Default;
+	               }
+	                 loger.info("ms匹配结束;" + arr.length);*/
+	            return arr;
+	        }
+	    }]);
+
+	    return ServerCheck;
+	}(_Emiter3.default);
+
+	ServerCheck.prototype.SEVER_CHECK_BEST_IP_SUCCESS = ServerCheck.SEVER_CHECK_BEST_IP_SUCCESS = 'severCheck_checkBestIpSuccess_message'; //获取最快的MS地址
+
+	var _default = new ServerCheck();
+
+	exports.default = _default;
+	;
+
+	var _temp = function () {
+	    if (typeof __REACT_HOT_LOADER__ === 'undefined') {
+	        return;
+	    }
+
+	    __REACT_HOT_LOADER__.register(loger, 'loger', 'D:/work/McuClient/src/ServerCheck.js');
+
+	    __REACT_HOT_LOADER__.register(isRequestMcuCallback, 'isRequestMcuCallback', 'D:/work/McuClient/src/ServerCheck.js');
+
+	    __REACT_HOT_LOADER__.register(isRequestMsCallback, 'isRequestMsCallback', 'D:/work/McuClient/src/ServerCheck.js');
+
+	    __REACT_HOT_LOADER__.register(isTestFromSass, 'isTestFromSass', 'D:/work/McuClient/src/ServerCheck.js');
+
+	    __REACT_HOT_LOADER__.register(isTestFromServer, 'isTestFromServer', 'D:/work/McuClient/src/ServerCheck.js');
+
+	    __REACT_HOT_LOADER__.register(tempMcuIp, 'tempMcuIp', 'D:/work/McuClient/src/ServerCheck.js');
+
+	    __REACT_HOT_LOADER__.register(tempMcuPort, 'tempMcuPort', 'D:/work/McuClient/src/ServerCheck.js');
+
+	    __REACT_HOT_LOADER__.register(tempMsIp, 'tempMsIp', 'D:/work/McuClient/src/ServerCheck.js');
+
+	    __REACT_HOT_LOADER__.register(tempMsPort, 'tempMsPort', 'D:/work/McuClient/src/ServerCheck.js');
+
+	    __REACT_HOT_LOADER__.register(ServerCheck, 'ServerCheck', 'D:/work/McuClient/src/ServerCheck.js');
+
+	    __REACT_HOT_LOADER__.register(_default, 'default', 'D:/work/McuClient/src/ServerCheck.js');
+	}();
+
+	;
+
+/***/ },
+/* 22 */
+/***/ function(module, exports, __webpack_require__) {
+
+	'use strict';
+
+	Object.defineProperty(exports, "__esModule", {
+	    value: true
+	});
+
+	var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); /*
+	                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      * 全局数据管理
+	                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      * */
+
+
+	var _Loger = __webpack_require__(5);
+
+	var _Loger2 = _interopRequireDefault(_Loger);
+
+	var _ApeConsts = __webpack_require__(8);
+
+	var _ApeConsts2 = _interopRequireDefault(_ApeConsts);
+
+	var _EngineUtils = __webpack_require__(9);
+
+	var _EngineUtils2 = _interopRequireDefault(_EngineUtils);
+
+	function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
+
+	function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
+
+	var loger = _Loger2.default.getLoger('Server');
+
+	var Server = function () {
+	    function Server() {
+	        _classCallCheck(this, Server);
+	    }
+
+	    _createClass(Server, null, [{
+	        key: 'serverList',
+	        get: function get() {
+	            return {
+	                "共享地址": [{
+	                    "ip": "106.3.130.98",
+	                    "name": "BGP多线3"
+	                }, {
+	                    "ip": "123.56.75.60",
+	                    "name": "BGP多线4"
+	                }, {
+	                    "ip": "221.228.109.123",
+	                    "name": "无锡"
+	                }, {
+	                    "ip": "qims.3mang.com",
+	                    "name": "全球通-备"
+	                }, {
+	                    "ip": "liantong.ms.3mang.com",
+	                    "name": "联通专线"
+	                }, {
+	                    "ip": "yidong.ms.3mang.com",
+	                    "name": "移动专线"
+	                }, {
+	                    "ip": "dianxin.ms.3mang.com",
+	                    "name": "电信专线"
+	                }, {
+	                    "ip": "103.235.232.128",
+	                    "name": "BGP多线2"
+	                }, {
+	                    "ip": "lanxms.3mang.com",
+	                    "name": "国内专线"
+	                }, {
+	                    "ip": "116.213.102.217",
+	                    "name": "BGP多线1"
+	                }],
+	                "局域网": [{
+	                    "ip": "106.3.130.98",
+	                    "name": "BGP多线3"
+	                }, {
+	                    "ip": "123.56.75.60",
+	                    "name": "BGP多线4"
+	                }, {
+	                    "ip": "221.228.109.123",
+	                    "name": "无锡"
+	                }, {
+	                    "ip": "qims.3mang.com",
+	                    "name": "全球通-备"
+	                }, {
+	                    "ip": "liantong.ms.3mang.com",
+	                    "name": "联通专线"
+	                }, {
+	                    "ip": "yidong.ms.3mang.com",
+	                    "name": "移动专线"
+	                }, {
+	                    "ip": "dianxin.ms.3mang.com",
+	                    "name": "电信专线"
+	                }, {
+	                    "ip": "103.235.232.128",
+	                    "name": "BGP多线2"
+	                }, {
+	                    "ip": "lanxms.3mang.com",
+	                    "name": "国内专线"
+	                }, {
+	                    "ip": "116.213.102.217",
+	                    "name": "BGP多线1"
+	                }],
+	                "中国": {
+	                    "province": {
+	                        "香港": [{
+	                            "ip": "bjms1.3mang.com",
+	                            "name": "全球通"
+	                        }, {
+	                            "ip": "199.59.231.234",
+	                            "name": "全球通香港"
+	                        }, {
+	                            "ip": "qims.3mang.com",
+	                            "name": "全球通-备"
+	                        }],
+	                        "台湾": [{
+	                            "ip": "bjms1.3mang.com",
+	                            "name": "全球通"
+	                        }, {
+	                            "ip": "159.100.205.129",
+	                            "name": "全球通台湾"
+	                        }, {
+	                            "ip": "qims.3mang.com",
+	                            "name": "全球通-备"
+	                        }]
+	                    },
+	                    "isp": {
+	                        "鹏博士": [{
+	                            "ip": "124.192.148.139",
+	                            "name": "鹏博士"
+	                        }, {
+	                            "ip": "123.56.75.60",
+	                            "name": "BGP多线4"
+	                        }, {
+	                            "ip": "106.3.130.98",
+	                            "name": "BGP多线3"
+	                        }, {
+	                            "ip": "lanxms.3mang.com",
+	                            "name": "国内专线"
+	                        }, {
+	                            "ip": "qims.3mang.com",
+	                            "name": "全球通-备"
+	                        }],
+	                        "长城": [{
+	                            "ip": "124.192.148.139",
+	                            "name": "鹏博士"
+	                        }, {
+	                            "ip": "123.56.75.60",
+	                            "name": "BGP多线4"
+	                        }, {
+	                            "ip": "106.3.130.98",
+	                            "name": "BGP多线3"
+	                        }, {
+	                            "ip": "lanxms.3mang.com",
+	                            "name": "国内专线"
+	                        }, {
+	                            "ip": "qims.3mang.com",
+	                            "name": "全球通-备"
+	                        }],
+	                        "移动": [{
+	                            "ip": "yidong.ms.3mang.com",
+	                            "name": "移动专线"
+	                        }, {
+	                            "ip": "116.213.102.217",
+	                            "name": "BGP多线1"
+	                        }, {
+	                            "ip": "123.56.75.60",
+	                            "name": "BGP多线4"
+	                        }, {
+	                            "ip": "103.235.232.128",
+	                            "name": "BGP多线2"
+	                        }, {
+	                            "ip": "lanxms.3mang.com",
+	                            "name": "国内专线"
+	                        }, {
+	                            "ip": "qims.3mang.com",
+	                            "name": "全球通-备"
+	                        }],
+	                        "电信": [{
+	                            "ip": "dianxin.ms.3mang.com",
+	                            "name": "电信专线"
+	                        }, {
+	                            "ip": "116.213.102.217",
+	                            "name": "BGP多线1"
+	                        }, {
+	                            "ip": "103.235.232.128",
+	                            "name": "BGP多线2"
+	                        }, {
+	                            "ip": "123.56.75.60",
+	                            "name": "BGP多线4"
+	                        }, {
+	                            "ip": "lanxms.3mang.com",
+	                            "name": "国内专线"
+	                        }, {
+	                            "ip": "qims.3mang.com",
+	                            "name": "全球通-备"
+	                        }],
+	                        "联通": [{
+	                            "ip": "116.213.102.217",
+	                            "name": "BGP多线1"
+	                        }, {
+	                            "ip": "103.235.232.128",
+	                            "name": "BGP多线2"
+	                        }, {
+	                            "ip": "liantong.ms.3mang.com",
+	                            "name": "联通专线"
+	                        }, {
+	                            "ip": "123.56.75.60",
+	                            "name": "BGP多线4"
+	                        }, {
+	                            "ip": "106.3.130.98",
+	                            "name": "BGP多线3"
+	                        }, {
+	                            "ip": "lanxms.3mang.com",
+	                            "name": "国内专线"
+	                        }, {
+	                            "ip": "qims.3mang.com",
+	                            "name": "全球通-备"
+	                        }]
+	                    },
+	                    "default": [{
+	                        "ip": "106.3.130.98",
+	                        "name": "BGP多线3"
+	                    }, {
+	                        "ip": "221.228.109.123",
+	                        "name": "无锡"
+	                    }, {
+	                        "ip": "qims.3mang.com",
+	                        "name": "全球通-备"
+	                    }, {
+	                        "ip": "liantong.ms.3mang.com",
+	                        "name": "联通专线"
+	                    }, {
+	                        "ip": "yidong.ms.3mang.com",
+	                        "name": "移动专线"
+	                    }, {
+	                        "ip": "dianxin.ms.3mang.com",
+	                        "name": "电信专线"
+	                    }, {
+	                        "ip": "lanxms.3mang.com",
+	                        "name": "国内专线"
+	                    }, {
+	                        "ip": "123.56.75.60",
+	                        "name": "BGP多线4"
+	                    }, {
+	                        "ip": "103.235.232.128",
+	                        "name": "BGP多线2"
+	                    }, {
+	                        "ip": "116.213.102.217",
+	                        "name": "BGP多线1"
+	                    }]
+	                },
+	                "美国": [{
+	                    "ip": "38.83.109.142",
+	                    "name": "达拉斯"
+	                }, {
+	                    "ip": "45.126.244.41",
+	                    "name": "全球通达拉斯"
+	                }, {
+	                    "ip": "185.114.76.243",
+	                    "name": "全球通阿什本"
+	                }, {
+	                    "ip": "159.100.196.217",
+	                    "name": "全球通迈阿密"
+	                }, {
+	                    "ip": "185.114.77.84",
+	                    "name": "全球通圣何塞"
+	                }, {
+	                    "ip": "159.100.195.230",
+	                    "name": "全球通洛杉矶"
+	                }, {
+	                    "ip": "bjms1.3mang.com",
+	                    "name": "全球通"
+	                }, {
+	                    "ip": "159.100.192.188",
+	                    "name": "全球通芝加哥"
+	                }],
+	                "加拿大": [{
+	                    "ip": "38.83.109.142",
+	                    "name": "达拉斯"
+	                }, {
+	                    "ip": "45.126.244.41",
+	                    "name": "全球通达拉斯"
+	                }, {
+	                    "ip": "185.114.76.243",
+	                    "name": "全球通阿什本"
+	                }, {
+	                    "ip": "159.100.196.217",
+	                    "name": "全球通迈阿密"
+	                }, {
+	                    "ip": "185.114.77.84",
+	                    "name": "全球通圣何塞"
+	                }, {
+	                    "ip": "159.100.195.230",
+	                    "name": "全球通洛杉矶"
+	                }, {
+	                    "ip": "bjms1.3mang.com",
+	                    "name": "全球通"
+	                }, {
+	                    "ip": "159.100.192.188",
+	                    "name": "全球通芝加哥"
+	                }],
+	                "墨西哥": [{
+	                    "ip": "38.83.109.142",
+	                    "name": "达拉斯"
+	                }, {
+	                    "ip": "45.126.244.41",
+	                    "name": "全球通达拉斯"
+	                }, {
+	                    "ip": "185.114.76.243",
+	                    "name": "全球通阿什本"
+	                }, {
+	                    "ip": "159.100.196.217",
+	                    "name": "全球通迈阿密"
+	                }, {
+	                    "ip": "185.114.77.84",
+	                    "name": "全球通圣何塞"
+	                }, {
+	                    "ip": "159.100.195.230",
+	                    "name": "全球通洛杉矶"
+	                }, {
+	                    "ip": "bjms1.3mang.com",
+	                    "name": "全球通"
+	                }, {
+	                    "ip": "159.100.192.188",
+	                    "name": "全球通芝加哥"
+	                }],
+	                "菲律宾": [{
+	                    "ip": "223.255.248.122",
+	                    "name": "香港"
+	                }, {
+	                    "ip": "118.193.20.130",
+	                    "name": "日本"
+	                }, {
+	                    "ip": "118.193.24.38",
+	                    "name": "新加坡"
+	                }, {
+	                    "ip": "bjms1.3mang.com",
+	                    "name": "全球通"
+	                }, {
+	                    "ip": "159.100.194.120",
+	                    "name": "全球通日本"
+	                }, {
+	                    "ip": "185.114.78.179",
+	                    "name": "全球通新加坡"
+	                }, {
+	                    "ip": "159.100.205.129",
+	                    "name": "全球通台湾"
+	                }, {
+	                    "ip": "192.158.245.185",
+	                    "name": "全球通韩国"
+	                }, {
+	                    "ip": "103.29.35.63",
+	                    "name": "全球通印度"
+	                }, {
+	                    "ip": "199.59.231.234",
+	                    "name": "全球通香港"
+	                }],
+	                "越南": [{
+	                    "ip": "223.255.248.122",
+	                    "name": "香港"
+	                }, {
+	                    "ip": "118.193.20.130",
+	                    "name": "日本"
+	                }, {
+	                    "ip": "118.193.24.38",
+	                    "name": "新加坡"
+	                }, {
+	                    "ip": "bjms1.3mang.com",
+	                    "name": "全球通"
+	                }, {
+	                    "ip": "159.100.194.120",
+	                    "name": "全球通日本"
+	                }, {
+	                    "ip": "185.114.78.179",
+	                    "name": "全球通新加坡"
+	                }, {
+	                    "ip": "159.100.205.129",
+	                    "name": "全球通台湾"
+	                }, {
+	                    "ip": "192.158.245.185",
+	                    "name": "全球通韩国"
+	                }, {
+	                    "ip": "103.29.35.63",
+	                    "name": "全球通印度"
+	                }, {
+	                    "ip": "199.59.231.234",
+	                    "name": "全球通香港"
+	                }],
+	                "泰国": [{
+	                    "ip": "223.255.248.122",
+	                    "name": "香港"
+	                }, {
+	                    "ip": "118.193.20.130",
+	                    "name": "日本"
+	                }, {
+	                    "ip": "118.193.24.38",
+	                    "name": "新加坡"
+	                }, {
+	                    "ip": "bjms1.3mang.com",
+	                    "name": "全球通"
+	                }, {
+	                    "ip": "159.100.194.120",
+	                    "name": "全球通日本"
+	                }, {
+	                    "ip": "185.114.78.179",
+	                    "name": "全球通新加坡"
+	                }, {
+	                    "ip": "159.100.205.129",
+	                    "name": "全球通台湾"
+	                }, {
+	                    "ip": "192.158.245.185",
+	                    "name": "全球通韩国"
+	                }, {
+	                    "ip": "103.29.35.63",
+	                    "name": "全球通印度"
+	                }, {
+	                    "ip": "199.59.231.234",
+	                    "name": "全球通香港"
+	                }],
+	                "default": [{
+	                    "ip": "bjms1.3mang.com",
+	                    "name": "全球通"
+	                }, {
+	                    "ip": "qims.3mang.com",
+	                    "name": "全球通-备"
+	                }, {
+	                    "ip": "38.83.109.142",
+	                    "name": "达拉斯"
+	                }, {
+	                    "ip": "118.193.20.130",
+	                    "name": "日本"
+	                }, {
+	                    "ip": "38.123.107.18",
+	                    "name": "德国"
+	                }, {
+	                    "ip": "223.255.248.122",
+	                    "name": "香港"
+	                }, {
+	                    "ip": "148.153.8.22",
+	                    "port": "1935",
+	                    "name": "纽约"
+	                }, {
+	                    "ip": "38.121.61.242",
+	                    "port": "1935",
+	                    "name": "洛杉矶"
+	                }, {
+	                    "ip": "118.193.24.38",
+	                    "name": "新加坡"
+	                }, {
+	                    "ip": "159.100.194.120",
+	                    "name": "全球通日本"
+	                }, {
+	                    "ip": "159.100.192.188",
+	                    "name": "全球通芝加哥"
+	                }, {
+	                    "ip": "45.126.244.41",
+	                    "name": "全球通达拉斯"
+	                }, {
+	                    "ip": "45.126.246.148",
+	                    "name": "全球通德国"
+	                }, {
+	                    "ip": "192.158.245.185",
+	                    "name": "全球通韩国"
+	                }, {
+	                    "ip": "103.29.35.63",
+	                    "name": "全球通印度"
+	                }, {
+	                    "ip": "202.127.74.126",
+	                    "name": "全球通新加坡"
+	                }, {
+	                    "ip": "199.59.231.234",
+	                    "name": "全球通香港"
+	                }, {
+	                    "ip": "159.100.205.129",
+	                    "name": "全球通台湾"
+	                }]
+	            };
+	        }
+	    }]);
+
+	    return Server;
+	}();
+
+	var _default = Server;
+	exports.default = _default;
+	;
+
+	var _temp = function () {
+	    if (typeof __REACT_HOT_LOADER__ === 'undefined') {
+	        return;
+	    }
+
+	    __REACT_HOT_LOADER__.register(loger, 'loger', 'D:/work/McuClient/src/config/Server.js');
+
+	    __REACT_HOT_LOADER__.register(Server, 'Server', 'D:/work/McuClient/src/config/Server.js');
+
+	    __REACT_HOT_LOADER__.register(_default, 'default', 'D:/work/McuClient/src/config/Server.js');
+	}();
+
+	;
+
+/***/ },
+/* 23 */
+/***/ function(module, exports, __webpack_require__) {
+
+	'use strict';
+
+	Object.defineProperty(exports, "__esModule", {
+	  value: true
+	});
+
+	var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
+
+	var _Emiter2 = __webpack_require__(3);
+
+	var _Emiter3 = _interopRequireDefault(_Emiter2);
+
+	var _MessageTypes = __webpack_require__(6);
+
+	var _MessageTypes2 = _interopRequireDefault(_MessageTypes);
+
+	var _Loger = __webpack_require__(5);
+
+	var _Loger2 = _interopRequireDefault(_Loger);
+
+	var _everSocket = __webpack_require__(24);
+
+	var _everSocket2 = _interopRequireDefault(_everSocket);
+
+	var _index = __webpack_require__(25);
+
+	var _index2 = _interopRequireDefault(_index);
+
+	var _PduType = __webpack_require__(34);
+
+	var _PduType2 = _interopRequireDefault(_PduType);
+
+	var _PduConsts = __webpack_require__(35);
+
+	var _PduConsts2 = _interopRequireDefault(_PduConsts);
+
+	var _ApeConsts = __webpack_require__(8);
+
+	var _ApeConsts2 = _interopRequireDefault(_ApeConsts);
+
+	var _ConferApe = __webpack_require__(36);
+
+	var _ConferApe2 = _interopRequireDefault(_ConferApe);
+
+	var _ArrayBufferUtil = __webpack_require__(38);
+
+	var _ArrayBufferUtil2 = _interopRequireDefault(_ArrayBufferUtil);
+
+	var _base64Js = __webpack_require__(10);
+
+	var _base64Js2 = _interopRequireDefault(_base64Js);
+
+	var _GlobalConfig = __webpack_require__(7);
+
+	var _GlobalConfig2 = _interopRequireDefault(_GlobalConfig);
+
+	var _EngineUtils = __webpack_require__(9);
+
+	var _EngineUtils2 = _interopRequireDefault(_EngineUtils);
+
+	function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
+
+	function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
+
+	function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }
+
+	function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } /*eslint-disable*/
+
+	var loger = _Loger2.default.getLoger('MCU');
+
+	var MCU = function (_Emiter) {
+	  _inherits(MCU, _Emiter);
+
+	  function MCU() {
+	    _classCallCheck(this, MCU);
+
+	    var _this = _possibleConstructorReturn(this, (MCU.__proto__ || Object.getPrototypeOf(MCU)).call(this));
+
+	    _this._apes = {};
+	    _this._everSocket = _everSocket2.default;
+	    _this._everSocket.on(_everSocket2.default.OPEN, _this._everSocketOpenHandler.bind(_this));
+	    _this._everSocket.on(_everSocket2.default.MESSAGE, _this._everSocketMsgReceivedHandler.bind(_this));
+	    _this._everSocket.on(_everSocket2.default.CLOSED, _this._everSocketCloseHandler.bind(_this));
+	    return _this;
+	  }
+
+	  // 注册Ape
+
+
+	  _createClass(MCU, [{
+	    key: 'registerApe',
+	    value: function registerApe(ape) {
+	      this._apes[ape._session_id] = ape;
+	    }
+
+	    // EverSocket建立通道完毕
+
+	  }, {
+	    key: '_everSocketOpenHandler',
+	    value: function _everSocketOpenHandler() {
+	      this._sendJoinClassRequest();
+	    }
+	    // EverSocket连接断开
+
+	  }, {
+	    key: '_everSocketCloseHandler',
+	    value: function _everSocketCloseHandler() {
+	      _GlobalConfig2.default.setCurrentStatus(_GlobalConfig2.default.statusCode_3);
+	      this._emit(_MessageTypes2.default.MCU_ERROR, _MessageTypes2.default.ERR_SOCKET_DISCONNECT);
+	    }
+
+	    //MCU-发送加入会议请求
+
+	  }, {
+	    key: '_sendJoinClassRequest',
+	    value: function _sendJoinClassRequest() {
+	      //const classInfo = this.classInfo;
+	      loger.log('MCU-发送加入会议请求.');
+	      console.log(this.classInfo);
+	      var descriptorPdu = new _index2.default['RCConferenceDescriptorPdu']();
+	      descriptorPdu.id = this.classInfo.classId;
+	      descriptorPdu.name = this.classInfo.className || "";
+	      descriptorPdu.mode = 0;
+	      descriptorPdu.capacity = 1;
+
+	      var joinRequestPdu = new _index2.default['RCConferenceJoinRequestPdu']();
+	      joinRequestPdu.type = 2;
+	      joinRequestPdu.initiator = this.classInfo.nodeId;
+	      joinRequestPdu.nodeType = _PduConsts2.default.NT_TERMINAL; //normal
+	      joinRequestPdu.classDescription = descriptorPdu; //  classDescription
+
+	      var pduMsg = _index2.default.create_join_class_request_pdu(joinRequestPdu.type, this.classInfo.nodeId, this.classInfo.classId, 0, _ApeConsts2.default.BROADCAST_CHANNEL_ID, true, _PduConsts2.default.DP_TOP, this.classInfo.topNodeID, _PduConsts2.default.SEG_ONCE);
+
+	      pduMsg.set("site", this.classInfo.siteId); //课堂号对应的名称
+	      pduMsg.set("userId", this.classInfo.userId);
+	      pduMsg.set("userName", _base64Js2.default.fromByteArray(_ArrayBufferUtil2.default.strToUint8Array(this.classInfo.userName)));
+	      pduMsg.set("userRole", this.classInfo.userRole);
+	      pduMsg.set("deviceType", "" + _GlobalConfig2.default.deviceType);
+	      pduMsg.set("data", joinRequestPdu.toArrayBuffer());
+
+	      this._everSocket.send(pduMsg.toArrayBuffer());
+	    }
+
+	    // EverSocket底层消息处理
+
+	  }, {
+	    key: '_everSocketMsgReceivedHandler',
+	    value: function _everSocketMsgReceivedHandler(data) {
+	      var pduMsg = _index2.default.decode_pdu(data);
+	      var pduType = pduMsg.get("type");
+	      var pduData = pduMsg.get("data");
+	      //loger.data('MCU-FirstLayer封装消息', 'type', pdu.id2type(pduMsg.type), pduMsg.type, 'sessionId', ApeConsts(pduMsg.sessionId), pduMsg.sessionId);
+	      //loger.log('MCU-FirstLayer封装消息', 'type', pdu.id2type(pduMsg.type), pduMsg.type, 'sessionId', ApeConsts(pduMsg.sessionId), pduMsg.sessionId);
+	      switch (pduType) {
+	        case _PduType2.default.RCPDU_CONNECT_PROVIDER_RESPONSE:
+	          //加入会议请求返回数据处理
+	          var joinConfPdu = _index2.default['RCConferenceJoinResponsePdu'].decode(pduData);
+	          var pduResultCode = joinConfPdu.result;
+	          loger.warn('RCPDU_CONNECT_PROVIDER_RESPONSE  ->pduResultCode:' + pduResultCode);
+	          switch (pduResultCode) {
+	            case _PduConsts2.default.RET_SUCCESS:
+	              //加入成功
+	              this._updateMCUConfInfoDescription(joinConfPdu.classDescription);
+	              this._emit(_MessageTypes2.default.CLASS_JOIN_MCU_SUCCESS, this.classInfo);
+	              break;
+	            case _PduConsts2.default.RET_FULL_CAPACITY:
+	              this._emit(_MessageTypes2.default.MCU_ERROR, _MessageTypes2.default.ERR_CLASS_JOIN_FULL);
+	              //this._emit(MessageTypes.CLASS_JOIN_FAILED,MessageTypes.ERR_CLASS_JOIN_FULL);
+	              //this._emit(MessageTypes.CLASS_JOIN_FULL);
+	              break;
+	            default:
+	              loger.arn('JoinConfPdu-未知类型-等待处理.', pduResultCode);
+	              break;
+	          }
+	          break;
+	        case _PduType2.default.RCPDU_SEND_DATA_REQUEST:
+	          //先判断当前消息属于哪个APE 根据 sessionId来判断
+	          var ape = this._apes[pduMsg.sessionId];
+	          var sessionLabel = (0, _ApeConsts2.default)(pduMsg.sessionId);
+	          if (ape) {
+	            var subTypeLabel = _index2.default.id2type(pduMsg.subType);
+	            //loger.log('MCU-SecondLayer封装消息', 'sessionId', sessionLabel, pduMsg.sessionId, 'subtype', subTypeLabel, pduMsg.subType);
+	            //ape广播事件,只要ape中监听就能收到
+	            ape._emit(pduMsg.subType, pduMsg.data);
+	          } else {
+	            loger.warn(sessionLabel + '尚未注册');
+	          }
+	          break;
+	        default:
+	          loger.warn('PDU-未知类型-等待处理.', pduType);
+	      }
+	    }
+	  }, {
+	    key: '_updateMCUConfInfoDescription',
+	    value: function _updateMCUConfInfoDescription(_data) {
+	      // let _mcuConfDesc=new pdu['RCConferenceDescriptorPdu'].decode(mcuConfDesc);
+	      loger.log('_updateMCUConfInfoDescription.');
+	      //let classDescription=new pdu['RCConferenceDescriptorPdu'].decode(_data);
+	      console.log(_data);
+	      //let info = this.mcuClassInfo.info;
+	      //info._conference_name = ArrayBufferUtil.uint8ArrayToStr(mcuConfDesc.name, 0);
+	      //info._capacity = mcuConfDesc.capacity;
+	      //info._mode = mcuConfDesc.mode;
+	    }
+
+	    // MU服务是否连接
+
+	  }, {
+	    key: 'send',
+
+
+	    // 会议发送消息 -- 消息同意序列号
+	    value: function send(msg) {
+	      if (this.connected) {
+	        loger.log('MCU-发送会议数据....');
+	        this._everSocket.send(msg.toArrayBuffer());
+	      } else {
+	        loger.log('MCU-发送会议数据失败,MCU底层通道不可用');
+	        this._emit(_MessageTypes2.default.MCU_ERROR, _MessageTypes2.default.ERR_SOCKET_DISCONNECT);
+	      }
+	    }
+
+	    // 主动断开MCU连接
+
+	  }, {
+	    key: 'leaveMCU',
+	    value: function leaveMCU() {
+	      // for (let ape in this._apes) {
+	      //   this._apes[ape].stop();
+	      // }
+	      loger.log('leaveMCU');
+	      _GlobalConfig2.default.setCurrentStatus(_GlobalConfig2.default.statusCode_3);
+	      this._everSocket.end();
+	    }
+
+	    // 主动建立MCU连接
+
+	  }, {
+	    key: 'joinMCU',
+	    value: function joinMCU(_classInfo) {
+	      loger.log('开始建立EverSocket通道.');
+	      console.log(_classInfo);
+	      _classInfo.classId = parseInt(_classInfo.classId); // classId 必须整形
+	      this.classInfo = _classInfo;
+	      // 创建刷新nodeId
+	      this.classInfo.nodeId = _EngineUtils2.default.creatSoleNumberFromTimestamp();
+	      _GlobalConfig2.default.nodeId = this.classInfo.nodeId; //这是标识自己身份的id
+
+	      var nodeInfoRecordPdu = new _index2.default['RCNodeInfoRecordPdu']();
+	      nodeInfoRecordPdu.name = this.classInfo.userName;
+	      nodeInfoRecordPdu.nodeId = this.classInfo.nodeId;
+	      nodeInfoRecordPdu.userId = this.classInfo.userId;
+	      nodeInfoRecordPdu.role = _ApeConsts2.default.userTypesToId[this.classInfo.userRole] || 1; //NR_NORMAL用户的身份,根据用户登录时的身份设置
+	      nodeInfoRecordPdu.level = 0;
+
+	      var conferenceRecord = {}; //RCConferenceRecord_T
+	      conferenceRecord._conference_id = this.classInfo.classId;
+	      conferenceRecord._top_node_id = this.classInfo.topNodeID;
+
+	      this.mcuClassInfo = {}; //RCMeetingInfo_T
+	      this.mcuClassInfo.self = nodeInfoRecordPdu;
+	      this.mcuClassInfo.info = conferenceRecord;
+
+	      // 内部mcuConfInfo
+	      this.classInfo.mcuClassInfo = this.mcuClassInfo;
+
+	      //开启EverSocket
+	      this._everSocket.begin(this.classInfo.MCUServerIP, this.classInfo.MCUServerPort);
+	    }
+	  }, {
+	    key: 'connected',
+	    get: function get() {
+	      if (this._everSocket && this._everSocket.connected) return true;
+	      return false;
+	    }
+	  }]);
+
+	  return MCU;
+	}(_Emiter3.default);
+
+	var _default = new MCU();
+
+	exports.default = _default;
+	;
+
+	var _temp = function () {
+	  if (typeof __REACT_HOT_LOADER__ === 'undefined') {
+	    return;
+	  }
+
+	  __REACT_HOT_LOADER__.register(loger, 'loger', 'D:/work/McuClient/src/mcu.js');
+
+	  __REACT_HOT_LOADER__.register(MCU, 'MCU', 'D:/work/McuClient/src/mcu.js');
+
+	  __REACT_HOT_LOADER__.register(_default, 'default', 'D:/work/McuClient/src/mcu.js');
+	}();
+
+	;
+
+/***/ },
+/* 24 */
+/***/ function(module, exports, __webpack_require__) {
+
+	'use strict';
+
+	Object.defineProperty(exports, "__esModule", {
+	  value: true
+	});
+
+	var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
+
+	var _Emiter2 = __webpack_require__(3);
+
+	var _Emiter3 = _interopRequireDefault(_Emiter2);
+
+	var _Loger = __webpack_require__(5);
+
+	var _Loger2 = _interopRequireDefault(_Loger);
+
+	function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
+
+	function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
+
+	function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }
+
+	function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } // //////////////////////////////////////////////////////////////////////////////
+	//
+	//  Copyright (C) 2016-present
+	//  All Rights Reserved.
+	//
+	//  Author: AlexWang
+	//  Date: 2016-08-27 21:40:49
+	//  Last Modified by:   AlexWang
+	//  Last Modified time: 2016-12-05 12:02:48
+	//  QQ Email: 1669499355@qq.com
+	//  Description: 底层Socket管理器,保持一直在线及异常重连.
+	//
+	// //////////////////////////////////////////////////////////////////////////////
+
+	var loger = _Loger2.default.getLoger('EverSocket');
+
+	var EverSocket = function (_Emiter) {
+	  _inherits(EverSocket, _Emiter);
+
+	  function EverSocket() {
+	    _classCallCheck(this, EverSocket);
+
+	    var _this = _possibleConstructorReturn(this, (EverSocket.__proto__ || Object.getPrototypeOf(EverSocket)).call(this));
+
+	    _this._connected = false;
+	    _this._lastActiveTime = 0; //最后一次收到消息的时间
+	    _this._enableEverSocket = false;
+	    return _this;
+	  }
+
+	  _createClass(EverSocket, [{
+	    key: 'begin',
+	    value: function begin(ip, port) {
+	      loger.log('开始WebSocket应用.');
+	      this._enableEverSocket = true;
+	      this.wsURL = 'ws://' + ip + ':' + port;
+	      this._newConnection();
+	    }
+	  }, {
+	    key: 'end',
+	    value: function end() {
+	      loger.log('停止WebSocket应用.');
+	      this._clear();
+	    }
+	  }, {
+	    key: 'send',
+	    value: function send(data) {
+	      if (this._connected) {
+	        if (data) {
+	          loger.log('SEND MESSAGE,byteLength---->', data.byteLength);
+	        } else {
+	          loger.log('SEND MESSAGE---->');
+	        }
+	        this.websocket.send(data);
+	      } else {
+	        loger.warn('WebSocket未建立连接.消息忽略');
+	      }
+	    }
+	  }, {
+	    key: '_setConnected',
+	    value: function _setConnected() {
+	      var isConn = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : true;
+
+	      this._connected = isConn;
+	      if (this._connected) {
+	        this._emit(EverSocket.OPEN);
+	      } else {
+	        this._emit(EverSocket.CLOSED);
+	      }
+	    }
+	  }, {
+	    key: '_newConnection',
+	    value: function _newConnection() {
+	      this.websocket = new WebSocket(this.wsURL);
+	      this.websocket.binaryType = 'arraybuffer';
+	      this.websocket.onopen = this._onOpen.bind(this);
+	      this.websocket.onclose = this._onClose.bind(this);
+	      this.websocket.onerror = this._onError.bind(this);
+	      this.websocket.onmessage = this._onMessage.bind(this);
+	    }
+	  }, {
+	    key: '_reConnection',
+	    value: function _reConnection() {
+	      var _this2 = this;
+
+	      this._clear();
+	      this.reConnectionTimeout = window.setTimeout(function () {
+	        loger.log('WebSocket重新建立.');
+	        window.clearTimeout(_this2.reConnectionTimeout);
+	        _this2._newConnection();
+	      }, EverSocket.RECONN_INTERVAL);
+	    }
+	  }, {
+	    key: '_clear',
+	    value: function _clear() {
+	      //this._emit(EverSocket.CLOSED);
+	      loger.log('WebSocket,Timers销毁');
+	      window.clearInterval(this.pingTimer);
+	      window.clearInterval(this.pongTimer);
+	      if (this.websocket == null) {
+	        loger.log('WebSocket,Timers已经销毁');
+	        return;
+	      }
+	      this._setConnected(false); //先设置状态
+	      this._enableEverSocket = false;
+	      this.websocket.onopen = undefined;
+	      this.websocket.onclose = undefined;
+	      this.websocket.onerror = undefined;
+	      this.websocket.onmessage = undefined;
+	      try {
+	        this.websocket.close();
+	      } catch (e) {
+	        loger.log('ignore errors');
+	      }
+	      this.websocket = undefined;
+	    }
+	  }, {
+	    key: '_onOpen',
+	    value: function _onOpen() {
+	      loger.log('WebSocket建立成功', this.wsURL);
+
+	      //启动心跳,检查socket链接状态
+	      this.pingTimer = window.setInterval(this._sendPingHandler.bind(this), EverSocket.PING_INTERVAL);
+	      this.pongTimer = window.setInterval(this._checkPongHandler.bind(this), EverSocket.PONG_INTERVAL);
+
+	      this._setConnected();
+	    }
+	  }, {
+	    key: '_onClose',
+	    value: function _onClose(closeEvent) {
+	      loger.log('WebSocket\u8FDE\u63A5\u65AD\u5F00 CODE:' + closeEvent.code + ' REASON:' + closeEvent.reason + ' CLEAN: ' + closeEvent.wasClean, this.wsURL);
+	      this._reConnection();
+	    }
+	  }, {
+	    key: '_onError',
+	    value: function _onError() {
+	      loger.log('WebSocket错误出现');
+	      this._connected = false;
+	      this._reConnection();
+	    }
+	  }, {
+	    key: '_onMessage',
+	    value: function _onMessage(messageEvent) {
+	      loger.log('<----RECEIVE MESSAGE');
+	      this._lastActiveTime = Date.now();
+	      var bufferData = messageEvent.data;
+	      if (bufferData.byteLength > 0) {
+	        this._emit(EverSocket.MESSAGE, bufferData);
+	      }
+	    }
+	  }, {
+	    key: '_sendPingHandler',
+	    value: function _sendPingHandler() {
+	      if (this._connected) {
+	        this.websocket.send(new ArrayBuffer());
+	      } else {
+	        this._reConnection();
+	      }
+	    }
+	  }, {
+	    key: '_checkPongHandler',
+	    value: function _checkPongHandler() {
+	      var pongTime = Date.now();
+	      if (this._lastActiveTime && this._lastActiveTime >= pongTime - EverSocket.PONG_INTERVAL && this._lastActiveTime <= pongTime) {} else {
+	        loger.warn('---服务器PINGPONG超时-----');
+	        this._reConnection();
+	      }
+	    }
+	  }, {
+	    key: 'connected',
+	    get: function get() {
+	      return this._connected;
+	    }
+	  }]);
+
+	  return EverSocket;
+	}(_Emiter3.default);
+
+	/*//修改之前的
+	EverSocket.prototype.PONG_INTERVAL = EverSocket.PONG_INTERVAL = 5000;
+	EverSocket.prototype.PING_INTERVAL = EverSocket.PING_INTERVAL = 3000;
+	EverSocket.prototype.RECONN_INTERVAL = EverSocket.RECONN_INTERVAL = 2000;*/
+
+	//20170223-mcu服务端修改了心跳的逻辑,如果客户端30秒没有请求,就会按离开的处理
+	//目前客户端发送心跳请求的空数据,服务端不会每次都回,暂定为10秒心跳一次
+
+
+	EverSocket.prototype.PONG_INTERVAL = EverSocket.PONG_INTERVAL = 21000; //
+	EverSocket.prototype.PING_INTERVAL = EverSocket.PING_INTERVAL = 10000; //心跳间隔
+	EverSocket.prototype.RECONN_INTERVAL = EverSocket.RECONN_INTERVAL = 3000; //重连的间隔
+
+	EverSocket.prototype.CONNECTING = EverSocket.CONNECTING = 0;
+	EverSocket.prototype.OPEN = EverSocket.OPEN = 1;
+	EverSocket.prototype.CLOSING = EverSocket.CLOSING = 2;
+	EverSocket.prototype.CLOSED = EverSocket.CLOSED = 3;
+	EverSocket.prototype.MESSAGE = EverSocket.MESSAGE = 4;
+
+	var _default = new EverSocket();
+
+	exports.default = _default;
+	;
+
+	var _temp = function () {
+	  if (typeof __REACT_HOT_LOADER__ === 'undefined') {
+	    return;
+	  }
+
+	  __REACT_HOT_LOADER__.register(loger, 'loger', 'D:/work/McuClient/src/everSocket.js');
+
+	  __REACT_HOT_LOADER__.register(EverSocket, 'EverSocket', 'D:/work/McuClient/src/everSocket.js');
+
+	  __REACT_HOT_LOADER__.register(_default, 'default', 'D:/work/McuClient/src/everSocket.js');
+	}();
+
+	;
+
+/***/ },
+/* 25 */
+/***/ function(module, exports, __webpack_require__) {
+
+	'use strict';
+
+	Object.defineProperty(exports, "__esModule", {
+	  value: true
+	});
+
+	var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
+
+	var _protobufjs = __webpack_require__(26);
+
+	var _protobufjs2 = _interopRequireDefault(_protobufjs);
+
+	var _pro = __webpack_require__(33);
+
+	var _pro2 = _interopRequireDefault(_pro);
+
+	var _PduType = __webpack_require__(34);
+
+	var _PduType2 = _interopRequireDefault(_PduType);
+
+	var _PduConsts = __webpack_require__(35);
+
+	var _PduConsts2 = _interopRequireDefault(_PduConsts);
+
+	function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
+
+	var builder = _protobufjs2.default.newBuilder({ convertFieldsToCamelCase: true });
+	_protobufjs2.default.loadProto(_pro2.default, builder);
+
+	var pdu = builder.build();
+
+	// 底层通信层[Firstlayer] - RCSendDataPdu封包
+	function create_pdu(type, sub_type, initiator, conference_id, session_id, channel_id, upward, reliability, priority, peer, seg) {
+	  var pduMsg = new pdu['RCSendDataPdu']();
+	  pduMsg.set("type", type);
+	  pduMsg.set("subType", sub_type);
+	  pduMsg.set("initiator", initiator);
+	  pduMsg.set("confId", conference_id); //***confId mcu服务用的是这个字段,客户端在其他地方统一为classId
+	  pduMsg.set("sessionId", session_id);
+	  pduMsg.set("channelId", channel_id);
+	  pduMsg.set("upward", upward);
+	  pduMsg.set("reliability", reliability);
+	  pduMsg.set("priority", priority);
+	  pduMsg.set("peer", peer);
+	  pduMsg.set("seg", seg);
+	  return pduMsg;
+	}
+	// 底层通信层[Firstlayer] - RCSendDataPdu解包
+	pdu.decode_pdu = function (buffer) {
+	  return pdu['RCSendDataPdu'].decode(buffer);
+	};
+
+	pdu.create_join_class_request_pdu = function (sub_type, initiator, conference_id, session_id, channel_id, reliability, priority, peer, seg) {
+	  return create_pdu(_PduType2.default.RCPDU_CONNECT_PROVIDER_REQUEST, sub_type, initiator, conference_id, session_id, channel_id, true, reliability, priority, peer, seg);
+	};
+
+	//upward 是否向顶层抛  create_uniform_pdu默认为true
+	pdu.create_uniform_pdu = function (sub_type, initiator, conference_id, session_id, channel_id, reliability, priority, peer, seg) {
+	  return create_pdu(_PduType2.default.RCPDU_UNIFORM_SEND_DATA_REQUEST, sub_type, initiator, conference_id, session_id, channel_id, true, reliability, priority, peer, seg);
+	};
+
+	//upward 是否向顶层抛  create_normal_pdu 中由外部定义
+	pdu.create_normal_pdu = function (sub_type, initiator, conference_id, session_id, channel_id, upward, reliability, priority, peer, seg) {
+	  return create_pdu(_PduType2.default.RCPDU_SEND_DATA_REQUEST, sub_type, initiator, conference_id, session_id, channel_id, upward, reliability, priority, peer, seg);
+	};
+
+	pdu.id2type = function (id) {
+	  for (var type_const in _PduType2.default) {
+	    if (_PduType2.default[type_const] === id) {
+	      return type_const;
+	    }
+	  }
+	};
+
+	// 合并统一对外
+	pdu = _extends({}, pdu, _PduType2.default, _PduConsts2.default);
+
+	var _default = pdu;
+	exports.default = _default;
+	;
+
+	var _temp = function () {
+	  if (typeof __REACT_HOT_LOADER__ === 'undefined') {
+	    return;
+	  }
+
+	  __REACT_HOT_LOADER__.register(builder, 'builder', 'D:/work/McuClient/src/pdus/index.js');
+
+	  __REACT_HOT_LOADER__.register(pdu, 'pdu', 'D:/work/McuClient/src/pdus/index.js');
+
+	  __REACT_HOT_LOADER__.register(create_pdu, 'create_pdu', 'D:/work/McuClient/src/pdus/index.js');
+
+	  __REACT_HOT_LOADER__.register(_default, 'default', 'D:/work/McuClient/src/pdus/index.js');
+	}();
+
+	;
+
+/***/ },
+/* 26 */
+/***/ function(module, exports, __webpack_require__) {
+
+	var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/* WEBPACK VAR INJECTION */(function(process, module) {/*
+	 Copyright 2013 Daniel Wirtz <dcode@dcode.io>
+
+	 Licensed under the Apache License, Version 2.0 (the "License");
+	 you may not use this file except in compliance with the License.
+	 You may obtain a copy of the License at
+
+	 http://www.apache.org/licenses/LICENSE-2.0
+
+	 Unless required by applicable law or agreed to in writing, software
+	 distributed under the License is distributed on an "AS IS" BASIS,
+	 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+	 See the License for the specific language governing permissions and
+	 limitations under the License.
+	 */
+
+	/**
+	 * @license protobuf.js (c) 2013 Daniel Wirtz <dcode@dcode.io>
+	 * Released under the Apache License, Version 2.0
+	 * see: https://github.com/dcodeIO/protobuf.js for details
+	 */
+	(function (global, factory) {
+
+	  /* AMD */
+	  if ("function" === 'function' && __webpack_require__(28)["amd"])
+	    !(__WEBPACK_AMD_DEFINE_ARRAY__ = [__webpack_require__(29)], __WEBPACK_AMD_DEFINE_FACTORY__ = (factory), __WEBPACK_AMD_DEFINE_RESULT__ = (typeof __WEBPACK_AMD_DEFINE_FACTORY__ === 'function' ? (__WEBPACK_AMD_DEFINE_FACTORY__.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__)) : __WEBPACK_AMD_DEFINE_FACTORY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));
+	  /* CommonJS */
+	  else if ("function" === "function" && typeof module === "object" && module && module["exports"])
+	    module["exports"] = factory(__webpack_require__(29), true);
+	  /* Global */
+	  else
+	    (global["dcodeIO"] = global["dcodeIO"] || {})["ProtoBuf"] = factory(global["dcodeIO"]["ByteBuffer"]);
+
+	})(this, function (ByteBuffer, isCommonJS) {
+	  "use strict";
+
+	  /**
+	   * The ProtoBuf namespace.
+	   * @exports ProtoBuf
+	   * @namespace
+	   * @expose
+	   */
+	  var ProtoBuf = {};
+
+	  /**
+	   * @type {!function(new: ByteBuffer, ...[*])}
+	   * @expose
+	   */
+	  ProtoBuf.ByteBuffer = ByteBuffer;
+
+	  /**
+	   * @type {?function(new: Long, ...[*])}
+	   * @expose
+	   */
+	  ProtoBuf.Long = ByteBuffer.Long || null;
+
+	  /**
+	   * ProtoBuf.js version.
+	   * @type {string}
+	   * @const
+	   * @expose
+	   */
+	  ProtoBuf.VERSION = "5.0.1";
+
+	  /**
+	   * Wire types.
+	   * @type {Object.<string,number>}
+	   * @const
+	   * @expose
+	   */
+	  ProtoBuf.WIRE_TYPES = {};
+
+	  /**
+	   * Varint wire type.
+	   * @type {number}
+	   * @expose
+	   */
+	  ProtoBuf.WIRE_TYPES.VARINT = 0;
+
+	  /**
+	   * Fixed 64 bits wire type.
+	   * @type {number}
+	   * @const
+	   * @expose
+	   */
+	  ProtoBuf.WIRE_TYPES.BITS64 = 1;
+
+	  /**
+	   * Length delimited wire type.
+	   * @type {number}
+	   * @const
+	   * @expose
+	   */
+	  ProtoBuf.WIRE_TYPES.LDELIM = 2;
+
+	  /**
+	   * Start group wire type.
+	   * @type {number}
+	   * @const
+	   * @expose
+	   */
+	  ProtoBuf.WIRE_TYPES.STARTGROUP = 3;
+
+	  /**
+	   * End group wire type.
+	   * @type {number}
+	   * @const
+	   * @expose
+	   */
+	  ProtoBuf.WIRE_TYPES.ENDGROUP = 4;
+
+	  /**
+	   * Fixed 32 bits wire type.
+	   * @type {number}
+	   * @const
+	   * @expose
+	   */
+	  ProtoBuf.WIRE_TYPES.BITS32 = 5;
+
+	  /**
+	   * Packable wire types.
+	   * @type {!Array.<number>}
+	   * @const
+	   * @expose
+	   */
+	  ProtoBuf.PACKABLE_WIRE_TYPES = [
+	    ProtoBuf.WIRE_TYPES.VARINT,
+	    ProtoBuf.WIRE_TYPES.BITS64,
+	    ProtoBuf.WIRE_TYPES.BITS32
+	  ];
+
+	  /**
+	   * Types.
+	   * @dict
+	   * @type {!Object.<string,{name: string, wireType: number, defaultValue: *}>}
+	   * @const
+	   * @expose
+	   */
+	  ProtoBuf.TYPES = {
+	    // According to the protobuf spec.
+	    "int32": {
+	      name: "int32",
+	      wireType: ProtoBuf.WIRE_TYPES.VARINT,
+	      defaultValue: 0
+	    },
+	    "uint32": {
+	      name: "uint32",
+	      wireType: ProtoBuf.WIRE_TYPES.VARINT,
+	      defaultValue: 0
+	    },
+	    "sint32": {
+	      name: "sint32",
+	      wireType: ProtoBuf.WIRE_TYPES.VARINT,
+	      defaultValue: 0
+	    },
+	    "int64": {
+	      name: "int64",
+	      wireType: ProtoBuf.WIRE_TYPES.VARINT,
+	      defaultValue: ProtoBuf.Long ? ProtoBuf.Long.ZERO : undefined
+	    },
+	    "uint64": {
+	      name: "uint64",
+	      wireType: ProtoBuf.WIRE_TYPES.VARINT,
+	      defaultValue: ProtoBuf.Long ? ProtoBuf.Long.UZERO : undefined
+	    },
+	    "sint64": {
+	      name: "sint64",
+	      wireType: ProtoBuf.WIRE_TYPES.VARINT,
+	      defaultValue: ProtoBuf.Long ? ProtoBuf.Long.ZERO : undefined
+	    },
+	    "bool": {
+	      name: "bool",
+	      wireType: ProtoBuf.WIRE_TYPES.VARINT,
+	      defaultValue: false
+	    },
+	    "double": {
+	      name: "double",
+	      wireType: ProtoBuf.WIRE_TYPES.BITS64,
+	      defaultValue: 0
+	    },
+	    "string": {
+	      name: "string",
+	      wireType: ProtoBuf.WIRE_TYPES.LDELIM,
+	      defaultValue: ""
+	    },
+	    "bytes": {
+	      name: "bytes",
+	      wireType: ProtoBuf.WIRE_TYPES.LDELIM,
+	      defaultValue: null // overridden in the code, must be a unique instance
+	    },
+	    "fixed32": {
+	      name: "fixed32",
+	      wireType: ProtoBuf.WIRE_TYPES.BITS32,
+	      defaultValue: 0
+	    },
+	    "sfixed32": {
+	      name: "sfixed32",
+	      wireType: ProtoBuf.WIRE_TYPES.BITS32,
+	      defaultValue: 0
+	    },
+	    "fixed64": {
+	      name: "fixed64",
+	      wireType: ProtoBuf.WIRE_TYPES.BITS64,
+	      defaultValue: ProtoBuf.Long ? ProtoBuf.Long.UZERO : undefined
+	    },
+	    "sfixed64": {
+	      name: "sfixed64",
+	      wireType: ProtoBuf.WIRE_TYPES.BITS64,
+	      defaultValue: ProtoBuf.Long ? ProtoBuf.Long.ZERO : undefined
+	    },
+	    "float": {
+	      name: "float",
+	      wireType: ProtoBuf.WIRE_TYPES.BITS32,
+	      defaultValue: 0
+	    },
+	    "enum": {
+	      name: "enum",
+	      wireType: ProtoBuf.WIRE_TYPES.VARINT,
+	      defaultValue: 0
+	    },
+	    "message": {
+	      name: "message",
+	      wireType: ProtoBuf.WIRE_TYPES.LDELIM,
+	      defaultValue: null
+	    },
+	    "group": {
+	      name: "group",
+	      wireType: ProtoBuf.WIRE_TYPES.STARTGROUP,
+	      defaultValue: null
+	    }
+	  };
+
+	  /**
+	   * Valid map key types.
+	   * @type {!Array.<!Object.<string,{name: string, wireType: number, defaultValue: *}>>}
+	   * @const
+	   * @expose
+	   */
+	  ProtoBuf.MAP_KEY_TYPES = [
+	    ProtoBuf.TYPES["int32"],
+	    ProtoBuf.TYPES["sint32"],
+	    ProtoBuf.TYPES["sfixed32"],
+	    ProtoBuf.TYPES["uint32"],
+	    ProtoBuf.TYPES["fixed32"],
+	    ProtoBuf.TYPES["int64"],
+	    ProtoBuf.TYPES["sint64"],
+	    ProtoBuf.TYPES["sfixed64"],
+	    ProtoBuf.TYPES["uint64"],
+	    ProtoBuf.TYPES["fixed64"],
+	    ProtoBuf.TYPES["bool"],
+	    ProtoBuf.TYPES["string"],
+	    ProtoBuf.TYPES["bytes"]
+	  ];
+
+	  /**
+	   * Minimum field id.
+	   * @type {number}
+	   * @const
+	   * @expose
+	   */
+	  ProtoBuf.ID_MIN = 1;
+
+	  /**
+	   * Maximum field id.
+	   * @type {number}
+	   * @const
+	   * @expose
+	   */
+	  ProtoBuf.ID_MAX = 0x1FFFFFFF;
+
+	  /**
+	   * If set to `true`, field names will be converted from underscore notation to camel case. Defaults to `false`.
+	   *  Must be set prior to parsing.
+	   * @type {boolean}
+	   * @expose
+	   */
+	  ProtoBuf.convertFieldsToCamelCase = false;
+
+	  /**
+	   * By default, messages are populated with (setX, set_x) accessors for each field. This can be disabled by
+	   *  setting this to `false` prior to building messages.
+	   * @type {boolean}
+	   * @expose
+	   */
+	  ProtoBuf.populateAccessors = true;
+
+	  /**
+	   * By default, messages are populated with default values if a field is not present on the wire. To disable
+	   *  this behavior, set this setting to `false`.
+	   * @type {boolean}
+	   * @expose
+	   */
+	  ProtoBuf.populateDefaults = true;
+
+	  /**
+	   * @alias ProtoBuf.Util
+	   * @expose
+	   */
+	  ProtoBuf.Util = (function () {
+	    "use strict";
+
+	    /**
+	     * ProtoBuf utilities.
+	     * @exports ProtoBuf.Util
+	     * @namespace
+	     */
+	    var Util = {};
+
+	    /**
+	     * Flag if running in node or not.
+	     * @type {boolean}
+	     * @const
+	     * @expose
+	     */
+	    Util.IS_NODE = !!(
+	      typeof process === 'object' && process + '' === '[object process]' && !process['browser']
+	    );
+
+	    /**
+	     * Constructs a XMLHttpRequest object.
+	     * @return {XMLHttpRequest}
+	     * @throws {Error} If XMLHttpRequest is not supported
+	     * @expose
+	     */
+	    Util.XHR = function () {
+	      // No dependencies please, ref: http://www.quirksmode.org/js/xmlhttp.html
+	      var XMLHttpFactories = [
+	        function () {
+	          return new XMLHttpRequest()
+	        },
+	        function () {
+	          return new ActiveXObject("Msxml2.XMLHTTP")
+	        },
+	        function () {
+	          return new ActiveXObject("Msxml3.XMLHTTP")
+	        },
+	        function () {
+	          return new ActiveXObject("Microsoft.XMLHTTP")
+	        }
+	      ];
+	      /** @type {?XMLHttpRequest} */
+	      var xhr = null;
+	      for (var i = 0; i < XMLHttpFactories.length; i++) {
+	        try { xhr = XMLHttpFactories[i](); } catch (e) {
+	          continue;
+	        }
+	        break;
+	      }
+	      if (!xhr)
+	        throw Error("XMLHttpRequest is not supported");
+	      return xhr;
+	    };
+
+	    /**
+	     * Fetches a resource.
+	     * @param {string} path Resource path
+	     * @param {function(?string)=} callback Callback receiving the resource's contents. If omitted the resource will
+	     *   be fetched synchronously. If the request failed, contents will be null.
+	     * @return {?string|undefined} Resource contents if callback is omitted (null if the request failed), else undefined.
+	     * @expose
+	     */
+	    Util.fetch = function (path, callback) {
+	      if (callback && typeof callback != 'function')
+	        callback = null;
+	      if (Util.IS_NODE) {
+	        var fs = __webpack_require__(31);
+	        if (callback) {
+	          fs.readFile(path, function (err, data) {
+	            if (err)
+	              callback(null);
+	            else
+	              callback("" + data);
+	          });
+	        } else
+	          try {
+	            return fs.readFileSync(path);
+	          } catch (e) {
+	            return null;
+	          }
+	      } else {
+	        var xhr = Util.XHR();
+	        xhr.open('GET', path, callback ? true : false);
+	        // xhr.setRequestHeader('User-Agent', 'XMLHTTP/1.0');
+	        xhr.setRequestHeader('Accept', 'text/plain');
+	        if (typeof xhr.overrideMimeType === 'function') xhr.overrideMimeType('text/plain');
+	        if (callback) {
+	          xhr.onreadystatechange = function () {
+	            if (xhr.readyState != 4) return;
+	            if ( /* remote */ xhr.status == 200 || /* local */ (xhr.status == 0 && typeof xhr.responseText === 'string'))
+	              callback(xhr.responseText);
+	            else
+	              callback(null);
+	          };
+	          if (xhr.readyState == 4)
+	            return;
+	          xhr.send(null);
+	        } else {
+	          xhr.send(null);
+	          if ( /* remote */ xhr.status == 200 || /* local */ (xhr.status == 0 && typeof xhr.responseText === 'string'))
+	            return xhr.responseText;
+	          return null;
+	        }
+	      }
+	    };
+
+	    /**
+	     * Converts a string to camel case.
+	     * @param {string} str
+	     * @returns {string}
+	     * @expose
+	     */
+	    Util.toCamelCase = function (str) {
+	      return str.replace(/_([a-zA-Z])/g, function ($0, $1) {
+	        return $1.toUpperCase();
+	      });
+	    };
+
+	    return Util;
+	  })();
+
+	  /**
+	   * Language expressions.
+	   * @type {!Object.<string,!RegExp>}
+	   * @expose
+	   */
+	  ProtoBuf.Lang = {
+
+	    // Characters always ending a statement
+	    DELIM: /[\s\{\}=;:\[\],'"\(\)<>]/g,
+
+	    // Field rules
+	    RULE: /^(?:required|optional|repeated|map)$/,
+
+	    // Field types
+	    TYPE: /^(?:double|float|int32|uint32|sint32|int64|uint64|sint64|fixed32|sfixed32|fixed64|sfixed64|bool|string|bytes)$/,
+
+	    // Names
+	    NAME: /^[a-zA-Z_][a-zA-Z_0-9]*$/,
+
+	    // Type definitions
+	    TYPEDEF: /^[a-zA-Z][a-zA-Z_0-9]*$/,
+
+	    // Type references
+	    TYPEREF: /^(?:\.?[a-zA-Z_][a-zA-Z_0-9]*)+$/,
+
+	    // Fully qualified type references
+	    FQTYPEREF: /^(?:\.[a-zA-Z][a-zA-Z_0-9]*)+$/,
+
+	    // All numbers
+	    NUMBER: /^-?(?:[1-9][0-9]*|0|0[xX][0-9a-fA-F]+|0[0-7]+|([0-9]*(\.[0-9]*)?([Ee][+-]?[0-9]+)?)|inf|nan)$/,
+
+	    // Decimal numbers
+	    NUMBER_DEC: /^(?:[1-9][0-9]*|0)$/,
+
+	    // Hexadecimal numbers
+	    NUMBER_HEX: /^0[xX][0-9a-fA-F]+$/,
+
+	    // Octal numbers
+	    NUMBER_OCT: /^0[0-7]+$/,
+
+	    // Floating point numbers
+	    NUMBER_FLT: /^([0-9]*(\.[0-9]*)?([Ee][+-]?[0-9]+)?|inf|nan)$/,
+
+	    // Booleans
+	    BOOL: /^(?:true|false)$/i,
+
+	    // Id numbers
+	    ID: /^(?:[1-9][0-9]*|0|0[xX][0-9a-fA-F]+|0[0-7]+)$/,
+
+	    // Negative id numbers (enum values)
+	    NEGID: /^\-?(?:[1-9][0-9]*|0|0[xX][0-9a-fA-F]+|0[0-7]+)$/,
+
+	    // Whitespaces
+	    WHITESPACE: /\s/,
+
+	    // All strings
+	    STRING: /(?:"([^"\\]*(?:\\.[^"\\]*)*)")|(?:'([^'\\]*(?:\\.[^'\\]*)*)')/g,
+
+	    // Double quoted strings
+	    STRING_DQ: /(?:"([^"\\]*(?:\\.[^"\\]*)*)")/g,
+
+	    // Single quoted strings
+	    STRING_SQ: /(?:'([^'\\]*(?:\\.[^'\\]*)*)')/g
+	  };
+
+	  /**
+	   * @alias ProtoBuf.DotProto
+	   * @expose
+	   */
+	  ProtoBuf.DotProto = (function (ProtoBuf, Lang) {
+	    "use strict";
+
+	    /**
+	     * Utilities to parse .proto files.
+	     * @exports ProtoBuf.DotProto
+	     * @namespace
+	     */
+	    var DotProto = {};
+
+	    /**
+	     * Constructs a new Tokenizer.
+	     * @exports ProtoBuf.DotProto.Tokenizer
+	     * @class prototype tokenizer
+	     * @param {string} proto Proto to tokenize
+	     * @constructor
+	     */
+	    var Tokenizer = function (proto) {
+
+	      /**
+	       * Source to parse.
+	       * @type {string}
+	       * @expose
+	       */
+	      this.source = proto + "";
+
+	      /**
+	       * Current index.
+	       * @type {number}
+	       * @expose
+	       */
+	      this.index = 0;
+
+	      /**
+	       * Current line.
+	       * @type {number}
+	       * @expose
+	       */
+	      this.line = 1;
+
+	      /**
+	       * Token stack.
+	       * @type {!Array.<string>}
+	       * @expose
+	       */
+	      this.stack = [];
+
+	      /**
+	       * Opening character of the current string read, if any.
+	       * @type {?string}
+	       * @private
+	       */
+	      this._stringOpen = null;
+	    };
+
+	    /**
+	     * @alias ProtoBuf.DotProto.Tokenizer.prototype
+	     * @inner
+	     */
+	    var TokenizerPrototype = Tokenizer.prototype;
+
+	    /**
+	     * Reads a string beginning at the current index.
+	     * @return {string}
+	     * @private
+	     */
+	    TokenizerPrototype._readString = function () {
+	      var re = this._stringOpen === '"' ? Lang.STRING_DQ : Lang.STRING_SQ;
+	      re.lastIndex = this.index - 1; // Include the open quote
+	      var match = re.exec(this.source);
+	      if (!match)
+	        throw Error("unterminated string");
+	      this.index = re.lastIndex;
+	      this.stack.push(this._stringOpen);
+	      this._stringOpen = null;
+	      return match[1];
+	    };
+
+	    /**
+	     * Gets the next token and advances by one.
+	     * @return {?string} Token or `null` on EOF
+	     * @expose
+	     */
+	    TokenizerPrototype.next = function () {
+	      if (this.stack.length > 0)
+	        return this.stack.shift();
+	      if (this.index >= this.source.length)
+	        return null;
+	      if (this._stringOpen !== null)
+	        return this._readString();
+
+	      var repeat,
+	        prev,
+	        next;
+	      do {
+	        repeat = false;
+
+	        // Strip white spaces
+	        while (Lang.WHITESPACE.test(next = this.source.charAt(this.index))) {
+	          if (next === '\n')
+	            ++this.line;
+	          if (++this.index === this.source.length)
+	            return null;
+	        }
+
+	        // Strip comments
+	        if (this.source.charAt(this.index) === '/') {
+	          ++this.index;
+	          if (this.source.charAt(this.index) === '/') { // Line
+	            while (this.source.charAt(++this.index) !== '\n')
+	              if (this.index == this.source.length)
+	                return null;
+	              ++this.index;
+	            ++this.line;
+	            repeat = true;
+	          } else if ((next = this.source.charAt(this.index)) === '*') { /* Block */
+	            do {
+	              if (next === '\n')
+	                ++this.line;
+	              if (++this.index === this.source.length)
+	                return null;
+	              prev = next;
+	              next = this.source.charAt(this.index);
+	            } while (prev !== '*' || next !== '/');
+	            ++this.index;
+	            repeat = true;
+	          } else
+	            return '/';
+	        }
+	      } while (repeat);
+
+	      if (this.index === this.source.length)
+	        return null;
+
+	      // Read the next token
+	      var end = this.index;
+	      Lang.DELIM.lastIndex = 0;
+	      var delim = Lang.DELIM.test(this.source.charAt(end++));
+	      if (!delim)
+	        while (end < this.source.length && !Lang.DELIM.test(this.source.charAt(end)))
+	          ++end;
+	      var token = this.source.substring(this.index, this.index = end);
+	      if (token === '"' || token === "'")
+	        this._stringOpen = token;
+	      return token;
+	    };
+
+	    /**
+	     * Peeks for the next token.
+	     * @return {?string} Token or `null` on EOF
+	     * @expose
+	     */
+	    TokenizerPrototype.peek = function () {
+	      if (this.stack.length === 0) {
+	        var token = this.next();
+	        if (token === null)
+	          return null;
+	        this.stack.push(token);
+	      }
+	      return this.stack[0];
+	    };
+
+	    /**
+	     * Skips a specific token and throws if it differs.
+	     * @param {string} expected Expected token
+	     * @throws {Error} If the actual token differs
+	     */
+	    TokenizerPrototype.skip = function (expected) {
+	      var actual = this.next();
+	      if (actual !== expected)
+	        throw Error("illegal '" + actual + "', '" + expected + "' expected");
+	    };
+
+	    /**
+	     * Omits an optional token.
+	     * @param {string} expected Expected optional token
+	     * @returns {boolean} `true` if the token exists
+	     */
+	    TokenizerPrototype.omit = function (expected) {
+	      if (this.peek() === expected) {
+	        this.next();
+	        return true;
+	      }
+	      return false;
+	    };
+
+	    /**
+	     * Returns a string representation of this object.
+	     * @return {string} String representation as of "Tokenizer(index/length)"
+	     * @expose
+	     */
+	    TokenizerPrototype.toString = function () {
+	      return "Tokenizer (" + this.index + "/" + this.source.length + " at line " + this.line + ")";
+	    };
+
+	    /**
+	     * @alias ProtoBuf.DotProto.Tokenizer
+	     * @expose
+	     */
+	    DotProto.Tokenizer = Tokenizer;
+
+	    /**
+	     * Constructs a new Parser.
+	     * @exports ProtoBuf.DotProto.Parser
+	     * @class prototype parser
+	     * @param {string} source Source
+	     * @constructor
+	     */
+	    var Parser = function (source) {
+
+	      /**
+	       * Tokenizer.
+	       * @type {!ProtoBuf.DotProto.Tokenizer}
+	       * @expose
+	       */
+	      this.tn = new Tokenizer(source);
+
+	      /**
+	       * Whether parsing proto3 or not.
+	       * @type {boolean}
+	       */
+	      this.proto3 = false;
+	    };
+
+	    /**
+	     * @alias ProtoBuf.DotProto.Parser.prototype
+	     * @inner
+	     */
+	    var ParserPrototype = Parser.prototype;
+
+	    /**
+	     * Parses the source.
+	     * @returns {!Object}
+	     * @throws {Error} If the source cannot be parsed
+	     * @expose
+	     */
+	    ParserPrototype.parse = function () {
+	      var topLevel = {
+	        "name": "[ROOT]", // temporary
+	        "package": null,
+	        "messages": [],
+	        "enums": [],
+	        "imports": [],
+	        "options": {},
+	        "services": []
+	          // "syntax": undefined
+	      };
+	      var token,
+	        head = true,
+	        weak;
+	      try {
+	        while (token = this.tn.next()) {
+	          switch (token) {
+	          case 'package':
+	            if (!head || topLevel["package"] !== null)
+	              throw Error("unexpected 'package'");
+	            token = this.tn.next();
+	            if (!Lang.TYPEREF.test(token))
+	              throw Error("illegal package name: " + token);
+	            this.tn.skip(";");
+	            topLevel["package"] = token;
+	            break;
+	          case 'import':
+	            if (!head)
+	              throw Error("unexpected 'import'");
+	            token = this.tn.peek();
+	            if (token === "public" || (weak = token === "weak")) // token ignored
+	              this.tn.next();
+	            token = this._readString();
+	            this.tn.skip(";");
+	            if (!weak) // import ignored
+	              topLevel["imports"].push(token);
+	            break;
+	          case 'syntax':
+	            if (!head)
+	              throw Error("unexpected 'syntax'");
+	            this.tn.skip("=");
+	            if ((topLevel["syntax"] = this._readString()) === "proto3")
+	              this.proto3 = true;
+	            this.tn.skip(";");
+	            break;
+	          case 'message':
+	            this._parseMessage(topLevel, null);
+	            head = false;
+	            break;
+	          case 'enum':
+	            this._parseEnum(topLevel);
+	            head = false;
+	            break;
+	          case 'option':
+	            this._parseOption(topLevel);
+	            break;
+	          case 'service':
+	            this._parseService(topLevel);
+	            break;
+	          case 'extend':
+	            this._parseExtend(topLevel);
+	            break;
+	          default:
+	            throw Error("unexpected '" + token + "'");
+	          }
+	        }
+	      } catch (e) {
+	        e.message = "Parse error at line " + this.tn.line + ": " + e.message;
+	        throw e;
+	      }
+	      delete topLevel["name"];
+	      return topLevel;
+	    };
+
+	    /**
+	     * Parses the specified source.
+	     * @returns {!Object}
+	     * @throws {Error} If the source cannot be parsed
+	     * @expose
+	     */
+	    Parser.parse = function (source) {
+	      return new Parser(source).parse();
+	    };
+
+	    // ----- Conversion ------
+
+	    /**
+	     * Converts a numerical string to an id.
+	     * @param {string} value
+	     * @param {boolean=} mayBeNegative
+	     * @returns {number}
+	     * @inner
+	     */
+	    function mkId(value, mayBeNegative) {
+	      var id = -1,
+	        sign = 1;
+	      if (value.charAt(0) == '-') {
+	        sign = -1;
+	        value = value.substring(1);
+	      }
+	      if (Lang.NUMBER_DEC.test(value))
+	        id = parseInt(value);
+	      else if (Lang.NUMBER_HEX.test(value))
+	        id = parseInt(value.substring(2), 16);
+	      else if (Lang.NUMBER_OCT.test(value))
+	        id = parseInt(value.substring(1), 8);
+	      else
+	        throw Error("illegal id value: " + (sign < 0 ? '-' : '') + value);
+	      id = (sign * id) | 0; // Force to 32bit
+	      if (!mayBeNegative && id < 0)
+	        throw Error("illegal id value: " + (sign < 0 ? '-' : '') + value);
+	      return id;
+	    }
+
+	    /**
+	     * Converts a numerical string to a number.
+	     * @param {string} val
+	     * @returns {number}
+	     * @inner
+	     */
+	    function mkNumber(val) {
+	      var sign = 1;
+	      if (val.charAt(0) == '-') {
+	        sign = -1;
+	        val = val.substring(1);
+	      }
+	      if (Lang.NUMBER_DEC.test(val))
+	        return sign * parseInt(val, 10);
+	      else if (Lang.NUMBER_HEX.test(val))
+	        return sign * parseInt(val.substring(2), 16);
+	      else if (Lang.NUMBER_OCT.test(val))
+	        return sign * parseInt(val.substring(1), 8);
+	      else if (val === 'inf')
+	        return sign * Infinity;
+	      else if (val === 'nan')
+	        return NaN;
+	      else if (Lang.NUMBER_FLT.test(val))
+	        return sign * parseFloat(val);
+	      throw Error("illegal number value: " + (sign < 0 ? '-' : '') + val);
+	    }
+
+	    // ----- Reading ------
+
+	    /**
+	     * Reads a string.
+	     * @returns {string}
+	     * @private
+	     */
+	    ParserPrototype._readString = function () {
+	      var value = "",
+	        token,
+	        delim;
+	      do {
+	        delim = this.tn.next();
+	        if (delim !== "'" && delim !== '"')
+	          throw Error("illegal string delimiter: " + delim);
+	        value += this.tn.next();
+	        this.tn.skip(delim);
+	        token = this.tn.peek();
+	      } while (token === '"' || token === '"'); // multi line?
+	      return value;
+	    };
+
+	    /**
+	     * Reads a value.
+	     * @param {boolean=} mayBeTypeRef
+	     * @returns {number|boolean|string}
+	     * @private
+	     */
+	    ParserPrototype._readValue = function (mayBeTypeRef) {
+	      var token = this.tn.peek(),
+	        value;
+	      if (token === '"' || token === "'")
+	        return this._readString();
+	      this.tn.next();
+	      if (Lang.NUMBER.test(token))
+	        return mkNumber(token);
+	      if (Lang.BOOL.test(token))
+	        return (token.toLowerCase() === 'true');
+	      if (mayBeTypeRef && Lang.TYPEREF.test(token))
+	        return token;
+	      throw Error("illegal value: " + token);
+
+	    };
+
+	    // ----- Parsing constructs -----
+
+	    /**
+	     * Parses a namespace option.
+	     * @param {!Object} parent Parent definition
+	     * @param {boolean=} isList
+	     * @private
+	     */
+	    ParserPrototype._parseOption = function (parent, isList) {
+	      var token = this.tn.next(),
+	        custom = false;
+	      if (token === '(') {
+	        custom = true;
+	        token = this.tn.next();
+	      }
+	      if (!Lang.TYPEREF.test(token))
+	      // we can allow options of the form google.protobuf.* since they will just get ignored anyways
+	      // if (!/google\.protobuf\./.test(token)) // FIXME: Why should that not be a valid typeref?
+	        throw Error("illegal option name: " + token);
+	      var name = token;
+	      if (custom) { // (my_method_option).foo, (my_method_option), some_method_option, (foo.my_option).bar
+	        this.tn.skip(')');
+	        name = '(' + name + ')';
+	        token = this.tn.peek();
+	        if (Lang.FQTYPEREF.test(token)) {
+	          name += token;
+	          this.tn.next();
+	        }
+	      }
+	      this.tn.skip('=');
+	      this._parseOptionValue(parent, name);
+	      if (!isList)
+	        this.tn.skip(";");
+	    };
+
+	    /**
+	     * Sets an option on the specified options object.
+	     * @param {!Object.<string,*>} options
+	     * @param {string} name
+	     * @param {string|number|boolean} value
+	     * @inner
+	     */
+	    function setOption(options, name, value) {
+	      if (typeof options[name] === 'undefined')
+	        options[name] = value;
+	      else {
+	        if (!Array.isArray(options[name]))
+	          options[name] = [options[name]];
+	        options[name].push(value);
+	      }
+	    }
+
+	    /**
+	     * Parses an option value.
+	     * @param {!Object} parent
+	     * @param {string} name
+	     * @private
+	     */
+	    ParserPrototype._parseOptionValue = function (parent, name) {
+	      var token = this.tn.peek();
+	      if (token !== '{') { // Plain value
+	        setOption(parent["options"], name, this._readValue(true));
+	      } else { // Aggregate options
+	        this.tn.skip("{");
+	        while ((token = this.tn.next()) !== '}') {
+	          if (!Lang.NAME.test(token))
+	            throw Error("illegal option name: " + name + "." + token);
+	          if (this.tn.omit(":"))
+	            setOption(parent["options"], name + "." + token, this._readValue(true));
+	          else
+	            this._parseOptionValue(parent, name + "." + token);
+	        }
+	      }
+	    };
+
+	    /**
+	     * Parses a service definition.
+	     * @param {!Object} parent Parent definition
+	     * @private
+	     */
+	    ParserPrototype._parseService = function (parent) {
+	      var token = this.tn.next();
+	      if (!Lang.NAME.test(token))
+	        throw Error("illegal service name at line " + this.tn.line + ": " + token);
+	      var name = token;
+	      var svc = {
+	        "name": name,
+	        "rpc": {},
+	        "options": {}
+	      };
+	      this.tn.skip("{");
+	      while ((token = this.tn.next()) !== '}') {
+	        if (token === "option")
+	          this._parseOption(svc);
+	        else if (token === 'rpc')
+	          this._parseServiceRPC(svc);
+	        else
+	          throw Error("illegal service token: " + token);
+	      }
+	      this.tn.omit(";");
+	      parent["services"].push(svc);
+	    };
+
+	    /**
+	     * Parses a RPC service definition of the form ['rpc', name, (request), 'returns', (response)].
+	     * @param {!Object} svc Service definition
+	     * @private
+	     */
+	    ParserPrototype._parseServiceRPC = function (svc) {
+	      var type = "rpc",
+	        token = this.tn.next();
+	      if (!Lang.NAME.test(token))
+	        throw Error("illegal rpc service method name: " + token);
+	      var name = token;
+	      var method = {
+	        "request": null,
+	        "response": null,
+	        "request_stream": false,
+	        "response_stream": false,
+	        "options": {}
+	      };
+	      this.tn.skip("(");
+	      token = this.tn.next();
+	      if (token.toLowerCase() === "stream") {
+	        method["request_stream"] = true;
+	        token = this.tn.next();
+	      }
+	      if (!Lang.TYPEREF.test(token))
+	        throw Error("illegal rpc service request type: " + token);
+	      method["request"] = token;
+	      this.tn.skip(")");
+	      token = this.tn.next();
+	      if (token.toLowerCase() !== "returns")
+	        throw Error("illegal rpc service request type delimiter: " + token);
+	      this.tn.skip("(");
+	      token = this.tn.next();
+	      if (token.toLowerCase() === "stream") {
+	        method["response_stream"] = true;
+	        token = this.tn.next();
+	      }
+	      method["response"] = token;
+	      this.tn.skip(")");
+	      token = this.tn.peek();
+	      if (token === '{') {
+	        this.tn.next();
+	        while ((token = this.tn.next()) !== '}') {
+	          if (token === 'option')
+	            this._parseOption(method);
+	          else
+	            throw Error("illegal rpc service token: " + token);
+	        }
+	        this.tn.omit(";");
+	      } else
+	        this.tn.skip(";");
+	      if (typeof svc[type] === 'undefined')
+	        svc[type] = {};
+	      svc[type][name] = method;
+	    };
+
+	    /**
+	     * Parses a message definition.
+	     * @param {!Object} parent Parent definition
+	     * @param {!Object=} fld Field definition if this is a group
+	     * @returns {!Object}
+	     * @private
+	     */
+	    ParserPrototype._parseMessage = function (parent, fld) {
+	      var isGroup = !!fld,
+	        token = this.tn.next();
+	      var msg = {
+	        "name": "",
+	        "fields": [],
+	        "enums": [],
+	        "messages": [],
+	        "options": {},
+	        "services": [],
+	        "oneofs": {}
+	        // "extensions": undefined
+	      };
+	      if (!Lang.NAME.test(token))
+	        throw Error("illegal " + (isGroup ? "group" : "message") + " name: " + token);
+	      msg["name"] = token;
+	      if (isGroup) {
+	        this.tn.skip("=");
+	        fld["id"] = mkId(this.tn.next());
+	        msg["isGroup"] = true;
+	      }
+	      token = this.tn.peek();
+	      if (token === '[' && fld)
+	        this._parseFieldOptions(fld);
+	      this.tn.skip("{");
+	      while ((token = this.tn.next()) !== '}') {
+	        if (Lang.RULE.test(token))
+	          this._parseMessageField(msg, token);
+	        else if (token === "oneof")
+	          this._parseMessageOneOf(msg);
+	        else if (token === "enum")
+	          this._parseEnum(msg);
+	        else if (token === "message")
+	          this._parseMessage(msg);
+	        else if (token === "option")
+	          this._parseOption(msg);
+	        else if (token === "service")
+	          this._parseService(msg);
+	        else if (token === "extensions")
+	          msg["extensions"] = this._parseExtensionRanges();
+	        else if (token === "reserved")
+	          this._parseIgnored(); // TODO
+	        else if (token === "extend")
+	          this._parseExtend(msg);
+	        else if (Lang.TYPEREF.test(token)) {
+	          if (!this.proto3)
+	            throw Error("illegal field rule: " + token);
+	          this._parseMessageField(msg, "optional", token);
+	        } else
+	          throw Error("illegal message token: " + token);
+	      }
+	      this.tn.omit(";");
+	      parent["messages"].push(msg);
+	      return msg;
+	    };
+
+	    /**
+	     * Parses an ignored statement.
+	     * @private
+	     */
+	    ParserPrototype._parseIgnored = function () {
+	      while (this.tn.peek() !== ';')
+	        this.tn.next();
+	      this.tn.skip(";");
+	    };
+
+	    /**
+	     * Parses a message field.
+	     * @param {!Object} msg Message definition
+	     * @param {string} rule Field rule
+	     * @param {string=} type Field type if already known (never known for maps)
+	     * @returns {!Object} Field descriptor
+	     * @private
+	     */
+	    ParserPrototype._parseMessageField = function (msg, rule, type) {
+	      if (!Lang.RULE.test(rule))
+	        throw Error("illegal message field rule: " + rule);
+	      var fld = {
+	        "rule": rule,
+	        "type": "",
+	        "name": "",
+	        "options": {},
+	        "id": 0
+	      };
+	      var token;
+	      if (rule === "map") {
+
+	        if (type)
+	          throw Error("illegal type: " + type);
+	        this.tn.skip('<');
+	        token = this.tn.next();
+	        if (!Lang.TYPE.test(token) && !Lang.TYPEREF.test(token))
+	          throw Error("illegal message field type: " + token);
+	        fld["keytype"] = token;
+	        this.tn.skip(',');
+	        token = this.tn.next();
+	        if (!Lang.TYPE.test(token) && !Lang.TYPEREF.test(token))
+	          throw Error("illegal message field: " + token);
+	        fld["type"] = token;
+	        this.tn.skip('>');
+	        token = this.tn.next();
+	        if (!Lang.NAME.test(token))
+	          throw Error("illegal message field name: " + token);
+	        fld["name"] = token;
+	        this.tn.skip("=");
+	        fld["id"] = mkId(this.tn.next());
+	        token = this.tn.peek();
+	        if (token === '[')
+	          this._parseFieldOptions(fld);
+	        this.tn.skip(";");
+
+	      } else {
+
+	        type = typeof type !== 'undefined' ? type : this.tn.next();
+
+	        if (type === "group") {
+
+	          // "A [legacy] group simply combines a nested message type and a field into a single declaration. In your
+	          // code, you can treat this message just as if it had a Result type field called result (the latter name is
+	          // converted to lower-case so that it does not conflict with the former)."
+	          var grp = this._parseMessage(msg, fld);
+	          if (!/^[A-Z]/.test(grp["name"]))
+	            throw Error('illegal group name: ' + grp["name"]);
+	          fld["type"] = grp["name"];
+	          fld["name"] = grp["name"].toLowerCase();
+	          this.tn.omit(";");
+
+	        } else {
+
+	          if (!Lang.TYPE.test(type) && !Lang.TYPEREF.test(type))
+	            throw Error("illegal message field type: " + type);
+	          fld["type"] = type;
+	          token = this.tn.next();
+	          if (!Lang.NAME.test(token))
+	            throw Error("illegal message field name: " + token);
+	          fld["name"] = token;
+	          this.tn.skip("=");
+	          fld["id"] = mkId(this.tn.next());
+	          token = this.tn.peek();
+	          if (token === "[")
+	            this._parseFieldOptions(fld);
+	          this.tn.skip(";");
+
+	        }
+	      }
+	      msg["fields"].push(fld);
+	      return fld;
+	    };
+
+	    /**
+	     * Parses a message oneof.
+	     * @param {!Object} msg Message definition
+	     * @private
+	     */
+	    ParserPrototype._parseMessageOneOf = function (msg) {
+	      var token = this.tn.next();
+	      if (!Lang.NAME.test(token))
+	        throw Error("illegal oneof name: " + token);
+	      var name = token,
+	        fld;
+	      var fields = [];
+	      this.tn.skip("{");
+	      while ((token = this.tn.next()) !== "}") {
+	        fld = this._parseMessageField(msg, "optional", token);
+	        fld["oneof"] = name;
+	        fields.push(fld["id"]);
+	      }
+	      this.tn.omit(";");
+	      msg["oneofs"][name] = fields;
+	    };
+
+	    /**
+	     * Parses a set of field option definitions.
+	     * @param {!Object} fld Field definition
+	     * @private
+	     */
+	    ParserPrototype._parseFieldOptions = function (fld) {
+	      this.tn.skip("[");
+	      var token,
+	        first = true;
+	      while ((token = this.tn.peek()) !== ']') {
+	        if (!first)
+	          this.tn.skip(",");
+	        this._parseOption(fld, true);
+	        first = false;
+	      }
+	      this.tn.next();
+	    };
+
+	    /**
+	     * Parses an enum.
+	     * @param {!Object} msg Message definition
+	     * @private
+	     */
+	    ParserPrototype._parseEnum = function (msg) {
+	      var enm = {
+	        "name": "",
+	        "values": [],
+	        "options": {}
+	      };
+	      var token = this.tn.next();
+	      if (!Lang.NAME.test(token))
+	        throw Error("illegal name: " + token);
+	      enm["name"] = token;
+	      this.tn.skip("{");
+	      while ((token = this.tn.next()) !== '}') {
+	        if (token === "option")
+	          this._parseOption(enm);
+	        else {
+	          if (!Lang.NAME.test(token))
+	            throw Error("illegal name: " + token);
+	          this.tn.skip("=");
+	          var val = {
+	            "name": token,
+	            "id": mkId(this.tn.next(), true)
+	          };
+	          token = this.tn.peek();
+	          if (token === "[")
+	            this._parseFieldOptions({ "options": {} });
+	          this.tn.skip(";");
+	          enm["values"].push(val);
+	        }
+	      }
+	      this.tn.omit(";");
+	      msg["enums"].push(enm);
+	    };
+
+	    /**
+	     * Parses extension / reserved ranges.
+	     * @returns {!Array.<!Array.<number>>}
+	     * @private
+	     */
+	    ParserPrototype._parseExtensionRanges = function () {
+	      var ranges = [];
+	      var token,
+	        range,
+	        value;
+	      do {
+	        range = [];
+	        while (true) {
+	          token = this.tn.next();
+	          switch (token) {
+	          case "min":
+	            value = ProtoBuf.ID_MIN;
+	            break;
+	          case "max":
+	            value = ProtoBuf.ID_MAX;
+	            break;
+	          default:
+	            value = mkNumber(token);
+	            break;
+	          }
+	          range.push(value);
+	          if (range.length === 2)
+	            break;
+	          if (this.tn.peek() !== "to") {
+	            range.push(value);
+	            break;
+	          }
+	          this.tn.next();
+	        }
+	        ranges.push(range);
+	      } while (this.tn.omit(","));
+	      this.tn.skip(";");
+	      return ranges;
+	    };
+
+	    /**
+	     * Parses an extend block.
+	     * @param {!Object} parent Parent object
+	     * @private
+	     */
+	    ParserPrototype._parseExtend = function (parent) {
+	      var token = this.tn.next();
+	      if (!Lang.TYPEREF.test(token))
+	        throw Error("illegal extend reference: " + token);
+	      var ext = {
+	        "ref": token,
+	        "fields": []
+	      };
+	      this.tn.skip("{");
+	      while ((token = this.tn.next()) !== '}') {
+	        if (Lang.RULE.test(token))
+	          this._parseMessageField(ext, token);
+	        else if (Lang.TYPEREF.test(token)) {
+	          if (!this.proto3)
+	            throw Error("illegal field rule: " + token);
+	          this._parseMessageField(ext, "optional", token);
+	        } else
+	          throw Error("illegal extend token: " + token);
+	      }
+	      this.tn.omit(";");
+	      parent["messages"].push(ext);
+	      return ext;
+	    };
+
+	    // ----- General -----
+
+	    /**
+	     * Returns a string representation of this parser.
+	     * @returns {string}
+	     */
+	    ParserPrototype.toString = function () {
+	      return "Parser at line " + this.tn.line;
+	    };
+
+	    /**
+	     * @alias ProtoBuf.DotProto.Parser
+	     * @expose
+	     */
+	    DotProto.Parser = Parser;
+
+	    return DotProto;
+
+	  })(ProtoBuf, ProtoBuf.Lang);
+
+	  /**
+	   * @alias ProtoBuf.Reflect
+	   * @expose
+	   */
+	  ProtoBuf.Reflect = (function (ProtoBuf) {
+	    "use strict";
+
+	    /**
+	     * Reflection types.
+	     * @exports ProtoBuf.Reflect
+	     * @namespace
+	     */
+	    var Reflect = {};
+
+	    /**
+	     * Constructs a Reflect base class.
+	     * @exports ProtoBuf.Reflect.T
+	     * @constructor
+	     * @abstract
+	     * @param {!ProtoBuf.Builder} builder Builder reference
+	     * @param {?ProtoBuf.Reflect.T} parent Parent object
+	     * @param {string} name Object name
+	     */
+	    var T = function (builder, parent, name) {
+
+	      /**
+	       * Builder reference.
+	       * @type {!ProtoBuf.Builder}
+	       * @expose
+	       */
+	      this.builder = builder;
+
+	      /**
+	       * Parent object.
+	       * @type {?ProtoBuf.Reflect.T}
+	       * @expose
+	       */
+	      this.parent = parent;
+
+	      /**
+	       * Object name in namespace.
+	       * @type {string}
+	       * @expose
+	       */
+	      this.name = name;
+
+	      /**
+	       * Fully qualified class name
+	       * @type {string}
+	       * @expose
+	       */
+	      this.className;
+	    };
+
+	    /**
+	     * @alias ProtoBuf.Reflect.T.prototype
+	     * @inner
+	     */
+	    var TPrototype = T.prototype;
+
+	    /**
+	     * Returns the fully qualified name of this object.
+	     * @returns {string} Fully qualified name as of ".PATH.TO.THIS"
+	     * @expose
+	     */
+	    TPrototype.fqn = function () {
+	      var name = this.name,
+	        ptr = this;
+	      do {
+	        ptr = ptr.parent;
+	        if (ptr == null)
+	          break;
+	        name = ptr.name + "." + name;
+	      } while (true);
+	      return name;
+	    };
+
+	    /**
+	     * Returns a string representation of this Reflect object (its fully qualified name).
+	     * @param {boolean=} includeClass Set to true to include the class name. Defaults to false.
+	     * @return String representation
+	     * @expose
+	     */
+	    TPrototype.toString = function (includeClass) {
+	      return (includeClass ? this.className + " " : "") + this.fqn();
+	    };
+
+	    /**
+	     * Builds this type.
+	     * @throws {Error} If this type cannot be built directly
+	     * @expose
+	     */
+	    TPrototype.build = function () {
+	      throw Error(this.toString(true) + " cannot be built directly");
+	    };
+
+	    /**
+	     * @alias ProtoBuf.Reflect.T
+	     * @expose
+	     */
+	    Reflect.T = T;
+
+	    /**
+	     * Constructs a new Namespace.
+	     * @exports ProtoBuf.Reflect.Namespace
+	     * @param {!ProtoBuf.Builder} builder Builder reference
+	     * @param {?ProtoBuf.Reflect.Namespace} parent Namespace parent
+	     * @param {string} name Namespace name
+	     * @param {Object.<string,*>=} options Namespace options
+	     * @param {string?} syntax The syntax level of this definition (e.g., proto3)
+	     * @constructor
+	     * @extends ProtoBuf.Reflect.T
+	     */
+	    var Namespace = function (builder, parent, name, options, syntax) {
+	      T.call(this, builder, parent, name);
+
+	      /**
+	       * @override
+	       */
+	      this.className = "Namespace";
+
+	      /**
+	       * Children inside the namespace.
+	       * @type {!Array.<ProtoBuf.Reflect.T>}
+	       */
+	      this.children = [];
+
+	      /**
+	       * Options.
+	       * @type {!Object.<string, *>}
+	       */
+	      this.options = options || {};
+
+	      /**
+	       * Syntax level (e.g., proto2 or proto3).
+	       * @type {!string}
+	       */
+	      this.syntax = syntax || "proto2";
+	    };
+
+	    /**
+	     * @alias ProtoBuf.Reflect.Namespace.prototype
+	     * @inner
+	     */
+	    var NamespacePrototype = Namespace.prototype = Object.create(T.prototype);
+
+	    /**
+	     * Returns an array of the namespace's children.
+	     * @param {ProtoBuf.Reflect.T=} type Filter type (returns instances of this type only). Defaults to null (all children).
+	     * @return {Array.<ProtoBuf.Reflect.T>}
+	     * @expose
+	     */
+	    NamespacePrototype.getChildren = function (type) {
+	      type = type || null;
+	      if (type == null)
+	        return this.children.slice();
+	      var children = [];
+	      for (var i = 0, k = this.children.length; i < k; ++i)
+	        if (this.children[i] instanceof type)
+	          children.push(this.children[i]);
+	      return children;
+	    };
+
+	    /**
+	     * Adds a child to the namespace.
+	     * @param {ProtoBuf.Reflect.T} child Child
+	     * @throws {Error} If the child cannot be added (duplicate)
+	     * @expose
+	     */
+	    NamespacePrototype.addChild = function (child) {
+	      var other;
+	      if (other = this.getChild(child.name)) {
+	        // Try to revert camelcase transformation on collision
+	        if (other instanceof Message.Field && other.name !== other.originalName && this.getChild(other.originalName) === null)
+	          other.name = other.originalName; // Revert previous first (effectively keeps both originals)
+	        else if (child instanceof Message.Field && child.name !== child.originalName && this.getChild(child.originalName) === null)
+	          child.name = child.originalName;
+	        else
+	          throw Error("Duplicate name in namespace " + this.toString(true) + ": " + child.name);
+	      }
+	      this.children.push(child);
+	    };
+
+	    /**
+	     * Gets a child by its name or id.
+	     * @param {string|number} nameOrId Child name or id
+	     * @return {?ProtoBuf.Reflect.T} The child or null if not found
+	     * @expose
+	     */
+	    NamespacePrototype.getChild = function (nameOrId) {
+	      var key = typeof nameOrId === 'number' ? 'id' : 'name';
+	      for (var i = 0, k = this.children.length; i < k; ++i)
+	        if (this.children[i][key] === nameOrId)
+	          return this.children[i];
+	      return null;
+	    };
+
+	    /**
+	     * Resolves a reflect object inside of this namespace.
+	     * @param {string|!Array.<string>} qn Qualified name to resolve
+	     * @param {boolean=} excludeNonNamespace Excludes non-namespace types, defaults to `false`
+	     * @return {?ProtoBuf.Reflect.Namespace} The resolved type or null if not found
+	     * @expose
+	     */
+	    NamespacePrototype.resolve = function (qn, excludeNonNamespace) {
+	      var part = typeof qn === 'string' ? qn.split(".") : qn,
+	        ptr = this,
+	        i = 0;
+	      if (part[i] === "") { // Fully qualified name, e.g. ".My.Message'
+	        while (ptr.parent !== null)
+	          ptr = ptr.parent;
+	        i++;
+	      }
+	      var child;
+	      do {
+	        do {
+	          if (!(ptr instanceof Reflect.Namespace)) {
+	            ptr = null;
+	            break;
+	          }
+	          child = ptr.getChild(part[i]);
+	          if (!child || !(child instanceof Reflect.T) || (excludeNonNamespace && !(child instanceof Reflect.Namespace))) {
+	            ptr = null;
+	            break;
+	          }
+	          ptr = child;
+	          i++;
+	        } while (i < part.length);
+	        if (ptr != null)
+	          break; // Found
+	        // Else search the parent
+	        if (this.parent !== null)
+	          return this.parent.resolve(qn, excludeNonNamespace);
+	      } while (ptr != null);
+	      return ptr;
+	    };
+
+	    /**
+	     * Determines the shortest qualified name of the specified type, if any, relative to this namespace.
+	     * @param {!ProtoBuf.Reflect.T} t Reflection type
+	     * @returns {string} The shortest qualified name or, if there is none, the fqn
+	     * @expose
+	     */
+	    NamespacePrototype.qn = function (t) {
+	      var part = [],
+	        ptr = t;
+	      do {
+	        part.unshift(ptr.name);
+	        ptr = ptr.parent;
+	      } while (ptr !== null);
+	      for (var len = 1; len <= part.length; len++) {
+	        var qn = part.slice(part.length - len);
+	        if (t === this.resolve(qn, t instanceof Reflect.Namespace))
+	          return qn.join(".");
+	      }
+	      return t.fqn();
+	    };
+
+	    /**
+	     * Builds the namespace and returns the runtime counterpart.
+	     * @return {Object.<string,Function|Object>} Runtime namespace
+	     * @expose
+	     */
+	    NamespacePrototype.build = function () {
+	      /** @dict */
+	      var ns = {};
+	      var children = this.children;
+	      for (var i = 0, k = children.length, child; i < k; ++i) {
+	        child = children[i];
+	        if (child instanceof Namespace)
+	          ns[child.name] = child.build();
+	      }
+	      if (Object.defineProperty)
+	        Object.defineProperty(ns, "$options", { "value": this.buildOpt() });
+	      return ns;
+	    };
+
+	    /**
+	     * Builds the namespace's '$options' property.
+	     * @return {Object.<string,*>}
+	     */
+	    NamespacePrototype.buildOpt = function () {
+	      var opt = {},
+	        keys = Object.keys(this.options);
+	      for (var i = 0, k = keys.length; i < k; ++i) {
+	        var key = keys[i],
+	          val = this.options[keys[i]];
+	        // TODO: Options are not resolved, yet.
+	        // if (val instanceof Namespace) {
+	        //     opt[key] = val.build();
+	        // } else {
+	        opt[key] = val;
+	        // }
+	      }
+	      return opt;
+	    };
+
+	    /**
+	     * Gets the value assigned to the option with the specified name.
+	     * @param {string=} name Returns the option value if specified, otherwise all options are returned.
+	     * @return {*|Object.<string,*>}null} Option value or NULL if there is no such option
+	     */
+	    NamespacePrototype.getOption = function (name) {
+	      if (typeof name === 'undefined')
+	        return this.options;
+	      return typeof this.options[name] !== 'undefined' ? this.options[name] : null;
+	    };
+
+	    /**
+	     * @alias ProtoBuf.Reflect.Namespace
+	     * @expose
+	     */
+	    Reflect.Namespace = Namespace;
+
+	    /**
+	     * Constructs a new Element implementation that checks and converts values for a
+	     * particular field type, as appropriate.
+	     *
+	     * An Element represents a single value: either the value of a singular field,
+	     * or a value contained in one entry of a repeated field or map field. This
+	     * class does not implement these higher-level concepts; it only encapsulates
+	     * the low-level typechecking and conversion.
+	     *
+	     * @exports ProtoBuf.Reflect.Element
+	     * @param {{name: string, wireType: number}} type Resolved data type
+	     * @param {ProtoBuf.Reflect.T|null} resolvedType Resolved type, if relevant
+	     * (e.g. submessage field).
+	     * @param {boolean} isMapKey Is this element a Map key? The value will be
+	     * converted to string form if so.
+	     * @param {string} syntax Syntax level of defining message type, e.g.,
+	     * proto2 or proto3.
+	     * @constructor
+	     */
+	    var Element = function (type, resolvedType, isMapKey, syntax) {
+
+	      /**
+	       * Element type, as a string (e.g., int32).
+	       * @type {{name: string, wireType: number}}
+	       */
+	      this.type = type;
+
+	      /**
+	       * Element type reference to submessage or enum definition, if needed.
+	       * @type {ProtoBuf.Reflect.T|null}
+	       */
+	      this.resolvedType = resolvedType;
+
+	      /**
+	       * Element is a map key.
+	       * @type {boolean}
+	       */
+	      this.isMapKey = isMapKey;
+
+	      /**
+	       * Syntax level of defining message type, e.g., proto2 or proto3.
+	       * @type {string}
+	       */
+	      this.syntax = syntax;
+
+	      if (isMapKey && ProtoBuf.MAP_KEY_TYPES.indexOf(type) < 0)
+	        throw Error("Invalid map key type: " + type.name);
+	    };
+
+	    var ElementPrototype = Element.prototype;
+
+	    /**
+	     * Obtains a (new) default value for the specified type.
+	     * @param type {string|{name: string, wireType: number}} Field type
+	     * @returns {*} Default value
+	     * @inner
+	     */
+	    function mkDefault(type) {
+	      if (typeof type === 'string')
+	        type = ProtoBuf.TYPES[type];
+	      if (typeof type.defaultValue === 'undefined')
+	        throw Error("default value for type " + type.name + " is not supported");
+	      if (type == ProtoBuf.TYPES["bytes"])
+	        return new ByteBuffer(0);
+	      return type.defaultValue;
+	    }
+
+	    /**
+	     * Returns the default value for this field in proto3.
+	     * @function
+	     * @param type {string|{name: string, wireType: number}} the field type
+	     * @returns {*} Default value
+	     */
+	    Element.defaultFieldValue = mkDefault;
+
+	    /**
+	     * Makes a Long from a value.
+	     * @param {{low: number, high: number, unsigned: boolean}|string|number} value Value
+	     * @param {boolean=} unsigned Whether unsigned or not, defaults to reuse it from Long-like objects or to signed for
+	     *  strings and numbers
+	     * @returns {!Long}
+	     * @throws {Error} If the value cannot be converted to a Long
+	     * @inner
+	     */
+	    function mkLong(value, unsigned) {
+	      if (value && typeof value.low === 'number' && typeof value.high === 'number' && typeof value.unsigned === 'boolean' && value.low === value.low && value.high === value.high)
+	        return new ProtoBuf.Long(value.low, value.high, typeof unsigned === 'undefined' ? value.unsigned : unsigned);
+	      if (typeof value === 'string')
+	        return ProtoBuf.Long.fromString(value, unsigned || false, 10);
+	      if (typeof value === 'number')
+	        return ProtoBuf.Long.fromNumber(value, unsigned || false);
+	      throw Error("not convertible to Long");
+	    }
+
+	    /**
+	     * Checks if the given value can be set for an element of this type (singular
+	     * field or one element of a repeated field or map).
+	     * @param {*} value Value to check
+	     * @return {*} Verified, maybe adjusted, value
+	     * @throws {Error} If the value cannot be verified for this element slot
+	     * @expose
+	     */
+	    ElementPrototype.verifyValue = function (value) {
+	      var self = this;
+
+	      function fail(val, msg) {
+	        throw Error("Illegal value for " + self.toString(true) + " of type " + self.type.name + ": " + val + " (" + msg + ")");
+	      }
+	      switch (this.type) {
+	        // Signed 32bit
+	      case ProtoBuf.TYPES["int32"]:
+	      case ProtoBuf.TYPES["sint32"]:
+	      case ProtoBuf.TYPES["sfixed32"]:
+	        // Account for !NaN: value === value
+	        if (typeof value !== 'number' || (value === value && value % 1 !== 0))
+	          fail(typeof value, "not an integer");
+	        return value > 4294967295 ? value | 0 : value;
+
+	        // Unsigned 32bit
+	      case ProtoBuf.TYPES["uint32"]:
+	      case ProtoBuf.TYPES["fixed32"]:
+	        if (typeof value !== 'number' || (value === value && value % 1 !== 0))
+	          fail(typeof value, "not an integer");
+	        return value < 0 ? value >>> 0 : value;
+
+	        // Signed 64bit
+	      case ProtoBuf.TYPES["int64"]:
+	      case ProtoBuf.TYPES["sint64"]:
+	      case ProtoBuf.TYPES["sfixed64"]:
+	        {
+	          if (ProtoBuf.Long)
+	            try {
+	              return mkLong(value, false);
+	            } catch (e) {
+	              fail(typeof value, e.message);
+	            }
+	          else
+	            fail(typeof value, "requires Long.js");
+	        }
+
+	        // Unsigned 64bit
+	      case ProtoBuf.TYPES["uint64"]:
+	      case ProtoBuf.TYPES["fixed64"]:
+	        {
+	          if (ProtoBuf.Long)
+	            try {
+	              return mkLong(value, true);
+	            } catch (e) {
+	              fail(typeof value, e.message);
+	            }
+	          else
+	            fail(typeof value, "requires Long.js");
+	        }
+
+	        // Bool
+	      case ProtoBuf.TYPES["bool"]:
+	        if (typeof value !== 'boolean')
+	          fail(typeof value, "not a boolean");
+	        return value;
+
+	        // Float
+	      case ProtoBuf.TYPES["float"]:
+	      case ProtoBuf.TYPES["double"]:
+	        if (typeof value !== 'number')
+	          fail(typeof value, "not a number");
+	        return value;
+
+	        // Length-delimited string
+	      case ProtoBuf.TYPES["string"]:
+	        if (typeof value !== 'string' && !(value && value instanceof String))
+	          fail(typeof value, "not a string");
+	        return "" + value; // Convert String object to string
+
+	        // Length-delimited bytes
+	      case ProtoBuf.TYPES["bytes"]:
+	        if (ByteBuffer.isByteBuffer(value))
+	          return value;
+	        return ByteBuffer.wrap(value, "base64");
+
+	        // Constant enum value
+	      case ProtoBuf.TYPES["enum"]:
+	        {
+	          var values = this.resolvedType.getChildren(ProtoBuf.Reflect.Enum.Value);
+	          for (i = 0; i < values.length; i++)
+	            if (values[i].name == value)
+	              return values[i].id;
+	            else if (values[i].id == value)
+	            return values[i].id;
+
+	          if (this.syntax === 'proto3') {
+	            // proto3: just make sure it's an integer.
+	            if (typeof value !== 'number' || (value === value && value % 1 !== 0))
+	              fail(typeof value, "not an integer");
+	            if (value > 4294967295 || value < 0)
+	              fail(typeof value, "not in range for uint32")
+	            return value;
+	          } else {
+	            // proto2 requires enum values to be valid.
+	            fail(value, "not a valid enum value");
+	          }
+	        }
+	        // Embedded message
+	      case ProtoBuf.TYPES["group"]:
+	      case ProtoBuf.TYPES["message"]:
+	        {
+	          if (!value || typeof value !== 'object')
+	            fail(typeof value, "object expected");
+	          if (value instanceof this.resolvedType.clazz)
+	            return value;
+	          if (value instanceof ProtoBuf.Builder.Message) {
+	            // Mismatched type: Convert to object (see: https://github.com/dcodeIO/ProtoBuf.js/issues/180)
+	            var obj = {};
+	            for (var i in value)
+	              if (value.hasOwnProperty(i))
+	                obj[i] = value[i];
+	            value = obj;
+	          }
+	          // Else let's try to construct one from a key-value object
+	          return new(this.resolvedType.clazz)(value); // May throw for a hundred of reasons
+	        }
+	      }
+
+	      // We should never end here
+	      throw Error("[INTERNAL] Illegal value for " + this.toString(true) + ": " + value + " (undefined type " + this.type + ")");
+	    };
+
+	    /**
+	     * Calculates the byte length of an element on the wire.
+	     * @param {number} id Field number
+	     * @param {*} value Field value
+	     * @returns {number} Byte length
+	     * @throws {Error} If the value cannot be calculated
+	     * @expose
+	     */
+	    ElementPrototype.calculateLength = function (id, value) {
+	      if (value === null) return 0; // Nothing to encode
+	      // Tag has already been written
+	      var n;
+	      switch (this.type) {
+	      case ProtoBuf.TYPES["int32"]:
+	        return value < 0 ? ByteBuffer.calculateVarint64(value) : ByteBuffer.calculateVarint32(value);
+	      case ProtoBuf.TYPES["uint32"]:
+	        return ByteBuffer.calculateVarint32(value);
+	      case ProtoBuf.TYPES["sint32"]:
+	        return ByteBuffer.calculateVarint32(ByteBuffer.zigZagEncode32(value));
+	      case ProtoBuf.TYPES["fixed32"]:
+	      case ProtoBuf.TYPES["sfixed32"]:
+	      case ProtoBuf.TYPES["float"]:
+	        return 4;
+	      case ProtoBuf.TYPES["int64"]:
+	      case ProtoBuf.TYPES["uint64"]:
+	        return ByteBuffer.calculateVarint64(value);
+	      case ProtoBuf.TYPES["sint64"]:
+	        return ByteBuffer.calculateVarint64(ByteBuffer.zigZagEncode64(value));
+	      case ProtoBuf.TYPES["fixed64"]:
+	      case ProtoBuf.TYPES["sfixed64"]:
+	        return 8;
+	      case ProtoBuf.TYPES["bool"]:
+	        return 1;
+	      case ProtoBuf.TYPES["enum"]:
+	        return ByteBuffer.calculateVarint32(value);
+	      case ProtoBuf.TYPES["double"]:
+	        return 8;
+	      case ProtoBuf.TYPES["string"]:
+	        n = ByteBuffer.calculateUTF8Bytes(value);
+	        return ByteBuffer.calculateVarint32(n) + n;
+	      case ProtoBuf.TYPES["bytes"]:
+	        if (value.remaining() < 0)
+	          throw Error("Illegal value for " + this.toString(true) + ": " + value.remaining() + " bytes remaining");
+	        return ByteBuffer.calculateVarint32(value.remaining()) + value.remaining();
+	      case ProtoBuf.TYPES["message"]:
+	        n = this.resolvedType.calculate(value);
+	        return ByteBuffer.calculateVarint32(n) + n;
+	      case ProtoBuf.TYPES["group"]:
+	        n = this.resolvedType.calculate(value);
+	        return n + ByteBuffer.calculateVarint32((id << 3) | ProtoBuf.WIRE_TYPES.ENDGROUP);
+	      }
+	      // We should never end here
+	      throw Error("[INTERNAL] Illegal value to encode in " + this.toString(true) + ": " + value + " (unknown type)");
+	    };
+
+	    /**
+	     * Encodes a value to the specified buffer. Does not encode the key.
+	     * @param {number} id Field number
+	     * @param {*} value Field value
+	     * @param {ByteBuffer} buffer ByteBuffer to encode to
+	     * @return {ByteBuffer} The ByteBuffer for chaining
+	     * @throws {Error} If the value cannot be encoded
+	     * @expose
+	     */
+	    ElementPrototype.encodeValue = function (id, value, buffer) {
+	      if (value === null) return buffer; // Nothing to encode
+	      // Tag has already been written
+
+	      switch (this.type) {
+	        // 32bit signed varint
+	      case ProtoBuf.TYPES["int32"]:
+	        // "If you use int32 or int64 as the type for a negative number, the resulting varint is always ten bytes
+	        // long – it is, effectively, treated like a very large unsigned integer." (see #122)
+	        if (value < 0)
+	          buffer.writeVarint64(value);
+	        else
+	          buffer.writeVarint32(value);
+	        break;
+
+	        // 32bit unsigned varint
+	      case ProtoBuf.TYPES["uint32"]:
+	        buffer.writeVarint32(value);
+	        break;
+
+	        // 32bit varint zig-zag
+	      case ProtoBuf.TYPES["sint32"]:
+	        buffer.writeVarint32ZigZag(value);
+	        break;
+
+	        // Fixed unsigned 32bit
+	      case ProtoBuf.TYPES["fixed32"]:
+	        buffer.writeUint32(value);
+	        break;
+
+	        // Fixed signed 32bit
+	      case ProtoBuf.TYPES["sfixed32"]:
+	        buffer.writeInt32(value);
+	        break;
+
+	        // 64bit varint as-is
+	      case ProtoBuf.TYPES["int64"]:
+	      case ProtoBuf.TYPES["uint64"]:
+	        buffer.writeVarint64(value); // throws
+	        break;
+
+	        // 64bit varint zig-zag
+	      case ProtoBuf.TYPES["sint64"]:
+	        buffer.writeVarint64ZigZag(value); // throws
+	        break;
+
+	        // Fixed unsigned 64bit
+	      case ProtoBuf.TYPES["fixed64"]:
+	        buffer.writeUint64(value); // throws
+	        break;
+
+	        // Fixed signed 64bit
+	      case ProtoBuf.TYPES["sfixed64"]:
+	        buffer.writeInt64(value); // throws
+	        break;
+
+	        // Bool
+	      case ProtoBuf.TYPES["bool"]:
+	        if (typeof value === 'string')
+	          buffer.writeVarint32(value.toLowerCase() === 'false' ? 0 : !!value);
+	        else
+	          buffer.writeVarint32(value ? 1 : 0);
+	        break;
+
+	        // Constant enum value
+	      case ProtoBuf.TYPES["enum"]:
+	        buffer.writeVarint32(value);
+	        break;
+
+	        // 32bit float
+	      case ProtoBuf.TYPES["float"]:
+	        buffer.writeFloat32(value);
+	        break;
+
+	        // 64bit float
+	      case ProtoBuf.TYPES["double"]:
+	        buffer.writeFloat64(value);
+	        break;
+
+	        // Length-delimited string
+	      case ProtoBuf.TYPES["string"]:
+	        buffer.writeVString(value);
+	        break;
+
+	        // Length-delimited bytes
+	      case ProtoBuf.TYPES["bytes"]:
+	        if (value.remaining() < 0)
+	          throw Error("Illegal value for " + this.toString(true) + ": " + value.remaining() + " bytes remaining");
+	        var prevOffset = value.offset;
+	        buffer.writeVarint32(value.remaining());
+	        buffer.append(value);
+	        value.offset = prevOffset;
+	        break;
+
+	        // Embedded message
+	      case ProtoBuf.TYPES["message"]:
+	        var bb = new ByteBuffer().LE();
+	        this.resolvedType.encode(value, bb);
+	        buffer.writeVarint32(bb.offset);
+	        buffer.append(bb.flip());
+	        break;
+
+	        // Legacy group
+	      case ProtoBuf.TYPES["group"]:
+	        this.resolvedType.encode(value, buffer);
+	        buffer.writeVarint32((id << 3) | ProtoBuf.WIRE_TYPES.ENDGROUP);
+	        break;
+
+	      default:
+	        // We should never end here
+	        throw Error("[INTERNAL] Illegal value to encode in " + this.toString(true) + ": " + value + " (unknown type)");
+	      }
+	      return buffer;
+	    };
+
+	    /**
+	     * Decode one element value from the specified buffer.
+	     * @param {ByteBuffer} buffer ByteBuffer to decode from
+	     * @param {number} wireType The field wire type
+	     * @param {number} id The field number
+	     * @return {*} Decoded value
+	     * @throws {Error} If the field cannot be decoded
+	     * @expose
+	     */
+	    ElementPrototype.decode = function (buffer, wireType, id) {
+	      if (wireType != this.type.wireType)
+	        throw Error("Unexpected wire type for element");
+
+	      var value, nBytes;
+	      switch (this.type) {
+	        // 32bit signed varint
+	      case ProtoBuf.TYPES["int32"]:
+	        return buffer.readVarint32() | 0;
+
+	        // 32bit unsigned varint
+	      case ProtoBuf.TYPES["uint32"]:
+	        return buffer.readVarint32() >>> 0;
+
+	        // 32bit signed varint zig-zag
+	      case ProtoBuf.TYPES["sint32"]:
+	        return buffer.readVarint32ZigZag() | 0;
+
+	        // Fixed 32bit unsigned
+	      case ProtoBuf.TYPES["fixed32"]:
+	        return buffer.readUint32() >>> 0;
+
+	      case ProtoBuf.TYPES["sfixed32"]:
+	        return buffer.readInt32() | 0;
+
+	        // 64bit signed varint
+	      case ProtoBuf.TYPES["int64"]:
+	        return buffer.readVarint64();
+
+	        // 64bit unsigned varint
+	      case ProtoBuf.TYPES["uint64"]:
+	        return buffer.readVarint64().toUnsigned();
+
+	        // 64bit signed varint zig-zag
+	      case ProtoBuf.TYPES["sint64"]:
+	        return buffer.readVarint64ZigZag();
+
+	        // Fixed 64bit unsigned
+	      case ProtoBuf.TYPES["fixed64"]:
+	        return buffer.readUint64();
+
+	        // Fixed 64bit signed
+	      case ProtoBuf.TYPES["sfixed64"]:
+	        return buffer.readInt64();
+
+	        // Bool varint
+	      case ProtoBuf.TYPES["bool"]:
+	        return !!buffer.readVarint32();
+
+	        // Constant enum value (varint)
+	      case ProtoBuf.TYPES["enum"]:
+	        // The following Builder.Message#set will already throw
+	        return buffer.readVarint32();
+
+	        // 32bit float
+	      case ProtoBuf.TYPES["float"]:
+	        return buffer.readFloat();
+
+	        // 64bit float
+	      case ProtoBuf.TYPES["double"]:
+	        return buffer.readDouble();
+
+	        // Length-delimited string
+	      case ProtoBuf.TYPES["string"]:
+	        return buffer.readVString();
+
+	        // Length-delimited bytes
+	      case ProtoBuf.TYPES["bytes"]:
+	        {
+	          nBytes = buffer.readVarint32();
+	          if (buffer.remaining() < nBytes)
+	            throw Error("Illegal number of bytes for " + this.toString(true) + ": " + nBytes + " required but got only " + buffer.remaining());
+	          value = buffer.clone(); // Offset already set
+	          value.limit = value.offset + nBytes;
+	          buffer.offset += nBytes;
+	          return value;
+	        }
+
+	        // Length-delimited embedded message
+	      case ProtoBuf.TYPES["message"]:
+	        {
+	          nBytes = buffer.readVarint32();
+	          return this.resolvedType.decode(buffer, nBytes);
+	        }
+
+	        // Legacy group
+	      case ProtoBuf.TYPES["group"]:
+	        return this.resolvedType.decode(buffer, -1, id);
+	      }
+
+	      // We should never end here
+	      throw Error("[INTERNAL] Illegal decode type");
+	    };
+
+	    /**
+	     * Converts a value from a string to the canonical element type.
+	     *
+	     * Legal only when isMapKey is true.
+	     *
+	     * @param {string} str The string value
+	     * @returns {*} The value
+	     */
+	    ElementPrototype.valueFromString = function (str) {
+	      if (!this.isMapKey) {
+	        throw Error("valueFromString() called on non-map-key element");
+	      }
+
+	      switch (this.type) {
+	      case ProtoBuf.TYPES["int32"]:
+	      case ProtoBuf.TYPES["sint32"]:
+	      case ProtoBuf.TYPES["sfixed32"]:
+	      case ProtoBuf.TYPES["uint32"]:
+	      case ProtoBuf.TYPES["fixed32"]:
+	        return this.verifyValue(parseInt(str));
+
+	      case ProtoBuf.TYPES["int64"]:
+	      case ProtoBuf.TYPES["sint64"]:
+	      case ProtoBuf.TYPES["sfixed64"]:
+	      case ProtoBuf.TYPES["uint64"]:
+	      case ProtoBuf.TYPES["fixed64"]:
+	        // Long-based fields support conversions from string already.
+	        return this.verifyValue(str);
+
+	      case ProtoBuf.TYPES["bool"]:
+	        return str === "true";
+
+	      case ProtoBuf.TYPES["string"]:
+	        return this.verifyValue(str);
+
+	      case ProtoBuf.TYPES["bytes"]:
+	        return ByteBuffer.fromBinary(str);
+	      }
+	    };
+
+	    /**
+	     * Converts a value from the canonical element type to a string.
+	     *
+	     * It should be the case that `valueFromString(valueToString(val))` returns
+	     * a value equivalent to `verifyValue(val)` for every legal value of `val`
+	     * according to this element type.
+	     *
+	     * This may be used when the element must be stored or used as a string,
+	     * e.g., as a map key on an Object.
+	     *
+	     * Legal only when isMapKey is true.
+	     *
+	     * @param {*} val The value
+	     * @returns {string} The string form of the value.
+	     */
+	    ElementPrototype.valueToString = function (value) {
+	      if (!this.isMapKey) {
+	        throw Error("valueToString() called on non-map-key element");
+	      }
+
+	      if (this.type === ProtoBuf.TYPES["bytes"]) {
+	        return value.toString("binary");
+	      } else {
+	        return value.toString();
+	      }
+	    };
+
+	    /**
+	     * @alias ProtoBuf.Reflect.Element
+	     * @expose
+	     */
+	    Reflect.Element = Element;
+
+	    /**
+	     * Constructs a new Message.
+	     * @exports ProtoBuf.Reflect.Message
+	     * @param {!ProtoBuf.Builder} builder Builder reference
+	     * @param {!ProtoBuf.Reflect.Namespace} parent Parent message or namespace
+	     * @param {string} name Message name
+	     * @param {Object.<string,*>=} options Message options
+	     * @param {boolean=} isGroup `true` if this is a legacy group
+	     * @param {string?} syntax The syntax level of this definition (e.g., proto3)
+	     * @constructor
+	     * @extends ProtoBuf.Reflect.Namespace
+	     */
+	    var Message = function (builder, parent, name, options, isGroup, syntax) {
+	      Namespace.call(this, builder, parent, name, options, syntax);
+
+	      /**
+	       * @override
+	       */
+	      this.className = "Message";
+
+	      /**
+	       * Extensions range.
+	       * @type {!Array.<number>|undefined}
+	       * @expose
+	       */
+	      this.extensions = undefined;
+
+	      /**
+	       * Runtime message class.
+	       * @type {?function(new:ProtoBuf.Builder.Message)}
+	       * @expose
+	       */
+	      this.clazz = null;
+
+	      /**
+	       * Whether this is a legacy group or not.
+	       * @type {boolean}
+	       * @expose
+	       */
+	      this.isGroup = !!isGroup;
+
+	      // The following cached collections are used to efficiently iterate over or look up fields when decoding.
+
+	      /**
+	       * Cached fields.
+	       * @type {?Array.<!ProtoBuf.Reflect.Message.Field>}
+	       * @private
+	       */
+	      this._fields = null;
+
+	      /**
+	       * Cached fields by id.
+	       * @type {?Object.<number,!ProtoBuf.Reflect.Message.Field>}
+	       * @private
+	       */
+	      this._fieldsById = null;
+
+	      /**
+	       * Cached fields by name.
+	       * @type {?Object.<string,!ProtoBuf.Reflect.Message.Field>}
+	       * @private
+	       */
+	      this._fieldsByName = null;
+	    };
+
+	    /**
+	     * @alias ProtoBuf.Reflect.Message.prototype
+	     * @inner
+	     */
+	    var MessagePrototype = Message.prototype = Object.create(Namespace.prototype);
+
+	    /**
+	     * Builds the message and returns the runtime counterpart, which is a fully functional class.
+	     * @see ProtoBuf.Builder.Message
+	     * @param {boolean=} rebuild Whether to rebuild or not, defaults to false
+	     * @return {ProtoBuf.Reflect.Message} Message class
+	     * @throws {Error} If the message cannot be built
+	     * @expose
+	     */
+	    MessagePrototype.build = function (rebuild) {
+	      if (this.clazz && !rebuild)
+	        return this.clazz;
+
+	      // Create the runtime Message class in its own scope
+	      var clazz = (function (ProtoBuf, T) {
+
+	        var fields = T.getChildren(ProtoBuf.Reflect.Message.Field),
+	          oneofs = T.getChildren(ProtoBuf.Reflect.Message.OneOf);
+
+	        /**
+	         * Constructs a new runtime Message.
+	         * @name ProtoBuf.Builder.Message
+	         * @class Barebone of all runtime messages.
+	         * @param {!Object.<string,*>|string} values Preset values
+	         * @param {...string} var_args
+	         * @constructor
+	         * @throws {Error} If the message cannot be created
+	         */
+	        var Message = function (values, var_args) {
+	          ProtoBuf.Builder.Message.call(this);
+	          // Create virtual oneof properties
+	          for (var i = 0, k = oneofs.length; i < k; ++i)
+	            this[oneofs[i].name] = null;
+	          // Create fields and set default values
+	          for (i = 0, k = fields.length; i < k; ++i) {
+	            var field = fields[i];
+	            this[field.name] =
+	              field.repeated ? [] :
+	              (field.map ? new ProtoBuf.Map(field) : null);
+	            if ((field.required || T.syntax === 'proto3') &&
+	              field.defaultValue !== null)
+	              this[field.name] = field.defaultValue;
+	          }
+
+	          if (arguments.length > 0) {
+	            var value;
+	            // Set field values from a values object
+	            if (arguments.length === 1 && values !== null && typeof values === 'object' &&
+	              /* not _another_ Message */
+	              (typeof values.encode !== 'function' || values instanceof Message) &&
+	              /* not a repeated field */
+	              !Array.isArray(values) &&
+	              /* not a Map */
+	              !(values instanceof ProtoBuf.Map) &&
+	              /* not a ByteBuffer */
+	              !ByteBuffer.isByteBuffer(values) &&
+	              /* not an ArrayBuffer */
+	              !(values instanceof ArrayBuffer) &&
+	              /* not a Long */
+	              !(ProtoBuf.Long && values instanceof ProtoBuf.Long)) {
+	              this.$set(values);
+	            } else // Set field values from arguments, in declaration order
+	              for (i = 0, k = arguments.length; i < k; ++i)
+	              if (typeof (value = arguments[i]) !== 'undefined')
+	                this.$set(fields[i].name, value); // May throw
+	          }
+	        };
+
+	        /**
+	         * @alias ProtoBuf.Builder.Message.prototype
+	         * @inner
+	         */
+	        var MessagePrototype = Message.prototype = Object.create(ProtoBuf.Builder.Message.prototype);
+
+	        /**
+	         * Adds a value to a repeated field.
+	         * @name ProtoBuf.Builder.Message#add
+	         * @function
+	         * @param {string} key Field name
+	         * @param {*} value Value to add
+	         * @param {boolean=} noAssert Whether to assert the value or not (asserts by default)
+	         * @returns {!ProtoBuf.Builder.Message} this
+	         * @throws {Error} If the value cannot be added
+	         * @expose
+	         */
+	        MessagePrototype.add = function (key, value, noAssert) {
+	          var field = T._fieldsByName[key];
+	          if (!noAssert) {
+	            if (!field)
+	              throw Error(this + "#" + key + " is undefined");
+	            if (!(field instanceof ProtoBuf.Reflect.Message.Field))
+	              throw Error(this + "#" + key + " is not a field: " + field.toString(true)); // May throw if it's an enum or embedded message
+	            if (!field.repeated)
+	              throw Error(this + "#" + key + " is not a repeated field");
+	            value = field.verifyValue(value, true);
+	          }
+	          if (this[key] === null)
+	            this[key] = [];
+	          this[key].push(value);
+	          return this;
+	        };
+
+	        /**
+	         * Adds a value to a repeated field. This is an alias for {@link ProtoBuf.Builder.Message#add}.
+	         * @name ProtoBuf.Builder.Message#$add
+	         * @function
+	         * @param {string} key Field name
+	         * @param {*} value Value to add
+	         * @param {boolean=} noAssert Whether to assert the value or not (asserts by default)
+	         * @returns {!ProtoBuf.Builder.Message} this
+	         * @throws {Error} If the value cannot be added
+	         * @expose
+	         */
+	        MessagePrototype.$add = MessagePrototype.add;
+
+	        /**
+	         * Sets a field's value.
+	         * @name ProtoBuf.Builder.Message#set
+	         * @function
+	         * @param {string|!Object.<string,*>} keyOrObj String key or plain object holding multiple values
+	         * @param {(*|boolean)=} value Value to set if key is a string, otherwise omitted
+	         * @param {boolean=} noAssert Whether to not assert for an actual field / proper value type, defaults to `false`
+	         * @returns {!ProtoBuf.Builder.Message} this
+	         * @throws {Error} If the value cannot be set
+	         * @expose
+	         */
+	        MessagePrototype.set = function (keyOrObj, value, noAssert) {
+	          if (keyOrObj && typeof keyOrObj === 'object') {
+	            noAssert = value;
+	            for (var ikey in keyOrObj)
+	              if (keyOrObj.hasOwnProperty(ikey) && typeof (value = keyOrObj[ikey]) !== 'undefined')
+	                this.$set(ikey, value, noAssert);
+	            return this;
+	          }
+	          var field = T._fieldsByName[keyOrObj];
+	          if (!noAssert) {
+	            if (!field)
+	              throw Error(this + "#" + keyOrObj + " is not a field: undefined");
+	            if (!(field instanceof ProtoBuf.Reflect.Message.Field))
+	              throw Error(this + "#" + keyOrObj + " is not a field: " + field.toString(true));
+	            this[field.name] = (value = field.verifyValue(value)); // May throw
+	          } else
+	            this[keyOrObj] = value;
+	          if (field && field.oneof) { // Field is part of an OneOf (not a virtual OneOf field)
+	            var currentField = this[field.oneof.name]; // Virtual field references currently set field
+	            if (value !== null) {
+	              if (currentField !== null && currentField !== field.name)
+	                this[currentField] = null; // Clear currently set field
+	              this[field.oneof.name] = field.name; // Point virtual field at this field
+	            } else if ( /* value === null && */ currentField === keyOrObj)
+	              this[field.oneof.name] = null; // Clear virtual field (current field explicitly cleared)
+	          }
+	          return this;
+	        };
+
+	        /**
+	         * Sets a field's value. This is an alias for [@link ProtoBuf.Builder.Message#set}.
+	         * @name ProtoBuf.Builder.Message#$set
+	         * @function
+	         * @param {string|!Object.<string,*>} keyOrObj String key or plain object holding multiple values
+	         * @param {(*|boolean)=} value Value to set if key is a string, otherwise omitted
+	         * @param {boolean=} noAssert Whether to not assert the value, defaults to `false`
+	         * @throws {Error} If the value cannot be set
+	         * @expose
+	         */
+	        MessagePrototype.$set = MessagePrototype.set;
+
+	        /**
+	         * Gets a field's value.
+	         * @name ProtoBuf.Builder.Message#get
+	         * @function
+	         * @param {string} key Key
+	         * @param {boolean=} noAssert Whether to not assert for an actual field, defaults to `false`
+	         * @return {*} Value
+	         * @throws {Error} If there is no such field
+	         * @expose
+	         */
+	        MessagePrototype.get = function (key, noAssert) {
+	          if (noAssert)
+	            return this[key];
+	          var field = T._fieldsByName[key];
+	          if (!field || !(field instanceof ProtoBuf.Reflect.Message.Field))
+	            throw Error(this + "#" + key + " is not a field: undefined");
+	          if (!(field instanceof ProtoBuf.Reflect.Message.Field))
+	            throw Error(this + "#" + key + " is not a field: " + field.toString(true));
+	          return this[field.name];
+	        };
+
+	        /**
+	         * Gets a field's value. This is an alias for {@link ProtoBuf.Builder.Message#$get}.
+	         * @name ProtoBuf.Builder.Message#$get
+	         * @function
+	         * @param {string} key Key
+	         * @return {*} Value
+	         * @throws {Error} If there is no such field
+	         * @expose
+	         */
+	        MessagePrototype.$get = MessagePrototype.get;
+
+	        // Getters and setters
+
+	        for (var i = 0; i < fields.length; i++) {
+	          var field = fields[i];
+	          // no setters for extension fields as these are named by their fqn
+	          if (field instanceof ProtoBuf.Reflect.Message.ExtensionField)
+	            continue;
+
+	          if (T.builder.options['populateAccessors'])
+	            (function (field) {
+	              // set/get[SomeValue]
+	              var Name = field.originalName.replace(/(_[a-zA-Z])/g, function (match) {
+	                return match.toUpperCase().replace('_', '');
+	              });
+	              Name = Name.substring(0, 1).toUpperCase() + Name.substring(1);
+
+	              // set/get_[some_value] FIXME: Do we really need these?
+	              var name = field.originalName.replace(/([A-Z])/g, function (match) {
+	                return "_" + match;
+	              });
+
+	              /**
+	               * The current field's unbound setter function.
+	               * @function
+	               * @param {*} value
+	               * @param {boolean=} noAssert
+	               * @returns {!ProtoBuf.Builder.Message}
+	               * @inner
+	               */
+	              var setter = function (value, noAssert) {
+	                this[field.name] = noAssert ? value : field.verifyValue(value);
+	                return this;
+	              };
+
+	              /**
+	               * The current field's unbound getter function.
+	               * @function
+	               * @returns {*}
+	               * @inner
+	               */
+	              var getter = function () {
+	                return this[field.name];
+	              };
+
+	              if (T.getChild("set" + Name) === null)
+	              /**
+	               * Sets a value. This method is present for each field, but only if there is no name conflict with
+	               *  another field.
+	               * @name ProtoBuf.Builder.Message#set[SomeField]
+	               * @function
+	               * @param {*} value Value to set
+	               * @param {boolean=} noAssert Whether to not assert the value, defaults to `false`
+	               * @returns {!ProtoBuf.Builder.Message} this
+	               * @abstract
+	               * @throws {Error} If the value cannot be set
+	               */
+	                MessagePrototype["set" + Name] = setter;
+
+	              if (T.getChild("set_" + name) === null)
+	              /**
+	               * Sets a value. This method is present for each field, but only if there is no name conflict with
+	               *  another field.
+	               * @name ProtoBuf.Builder.Message#set_[some_field]
+	               * @function
+	               * @param {*} value Value to set
+	               * @param {boolean=} noAssert Whether to not assert the value, defaults to `false`
+	               * @returns {!ProtoBuf.Builder.Message} this
+	               * @abstract
+	               * @throws {Error} If the value cannot be set
+	               */
+	                MessagePrototype["set_" + name] = setter;
+
+	              if (T.getChild("get" + Name) === null)
+	              /**
+	               * Gets a value. This method is present for each field, but only if there is no name conflict with
+	               *  another field.
+	               * @name ProtoBuf.Builder.Message#get[SomeField]
+	               * @function
+	               * @abstract
+	               * @return {*} The value
+	               */
+	                MessagePrototype["get" + Name] = getter;
+
+	              if (T.getChild("get_" + name) === null)
+	              /**
+	               * Gets a value. This method is present for each field, but only if there is no name conflict with
+	               *  another field.
+	               * @name ProtoBuf.Builder.Message#get_[some_field]
+	               * @function
+	               * @return {*} The value
+	               * @abstract
+	               */
+	                MessagePrototype["get_" + name] = getter;
+
+	            })(field);
+	        }
+
+	        // En-/decoding
+
+	        /**
+	         * Encodes the message.
+	         * @name ProtoBuf.Builder.Message#$encode
+	         * @function
+	         * @param {(!ByteBuffer|boolean)=} buffer ByteBuffer to encode to. Will create a new one and flip it if omitted.
+	         * @param {boolean=} noVerify Whether to not verify field values, defaults to `false`
+	         * @return {!ByteBuffer} Encoded message as a ByteBuffer
+	         * @throws {Error} If the message cannot be encoded or if required fields are missing. The later still
+	         *  returns the encoded ByteBuffer in the `encoded` property on the error.
+	         * @expose
+	         * @see ProtoBuf.Builder.Message#encode64
+	         * @see ProtoBuf.Builder.Message#encodeHex
+	         * @see ProtoBuf.Builder.Message#encodeAB
+	         */
+	        MessagePrototype.encode = function (buffer, noVerify) {
+	          if (typeof buffer === 'boolean')
+	            noVerify = buffer,
+	            buffer = undefined;
+	          var isNew = false;
+	          if (!buffer)
+	            buffer = new ByteBuffer(),
+	            isNew = true;
+	          var le = buffer.littleEndian;
+	          try {
+	            T.encode(this, buffer.LE(), noVerify);
+	            return (isNew ? buffer.flip() : buffer).LE(le);
+	          } catch (e) {
+	            buffer.LE(le);
+	            throw (e);
+	          }
+	        };
+
+	        /**
+	         * Encodes a message using the specified data payload.
+	         * @param {!Object.<string,*>} data Data payload
+	         * @param {(!ByteBuffer|boolean)=} buffer ByteBuffer to encode to. Will create a new one and flip it if omitted.
+	         * @param {boolean=} noVerify Whether to not verify field values, defaults to `false`
+	         * @return {!ByteBuffer} Encoded message as a ByteBuffer
+	         * @expose
+	         */
+	        Message.encode = function (data, buffer, noVerify) {
+	          return new Message(data).encode(buffer, noVerify);
+	        };
+
+	        /**
+	         * Calculates the byte length of the message.
+	         * @name ProtoBuf.Builder.Message#calculate
+	         * @function
+	         * @returns {number} Byte length
+	         * @throws {Error} If the message cannot be calculated or if required fields are missing.
+	         * @expose
+	         */
+	        MessagePrototype.calculate = function () {
+	          return T.calculate(this);
+	        };
+
+	        /**
+	         * Encodes the varint32 length-delimited message.
+	         * @name ProtoBuf.Builder.Message#encodeDelimited
+	         * @function
+	         * @param {(!ByteBuffer|boolean)=} buffer ByteBuffer to encode to. Will create a new one and flip it if omitted.
+	         * @param {boolean=} noVerify Whether to not verify field values, defaults to `false`
+	         * @return {!ByteBuffer} Encoded message as a ByteBuffer
+	         * @throws {Error} If the message cannot be encoded or if required fields are missing. The later still
+	         *  returns the encoded ByteBuffer in the `encoded` property on the error.
+	         * @expose
+	         */
+	        MessagePrototype.encodeDelimited = function (buffer, noVerify) {
+	          var isNew = false;
+	          if (!buffer)
+	            buffer = new ByteBuffer(),
+	            isNew = true;
+	          var enc = new ByteBuffer().LE();
+	          T.encode(this, enc, noVerify).flip();
+	          buffer.writeVarint32(enc.remaining());
+	          buffer.append(enc);
+	          return isNew ? buffer.flip() : buffer;
+	        };
+
+	        /**
+	         * Directly encodes the message to an ArrayBuffer.
+	         * @name ProtoBuf.Builder.Message#encodeAB
+	         * @function
+	         * @return {ArrayBuffer} Encoded message as ArrayBuffer
+	         * @throws {Error} If the message cannot be encoded or if required fields are missing. The later still
+	         *  returns the encoded ArrayBuffer in the `encoded` property on the error.
+	         * @expose
+	         */
+	        MessagePrototype.encodeAB = function () {
+	          try {
+	            return this.encode().toArrayBuffer();
+	          } catch (e) {
+	            if (e["encoded"]) e["encoded"] = e["encoded"].toArrayBuffer();
+	            throw (e);
+	          }
+	        };
+
+	        /**
+	         * Returns the message as an ArrayBuffer. This is an alias for {@link ProtoBuf.Builder.Message#encodeAB}.
+	         * @name ProtoBuf.Builder.Message#toArrayBuffer
+	         * @function
+	         * @return {ArrayBuffer} Encoded message as ArrayBuffer
+	         * @throws {Error} If the message cannot be encoded or if required fields are missing. The later still
+	         *  returns the encoded ArrayBuffer in the `encoded` property on the error.
+	         * @expose
+	         */
+	        MessagePrototype.toArrayBuffer = MessagePrototype.encodeAB;
+
+	        /**
+	         * Directly encodes the message to a node Buffer.
+	         * @name ProtoBuf.Builder.Message#encodeNB
+	         * @function
+	         * @return {!Buffer}
+	         * @throws {Error} If the message cannot be encoded, not running under node.js or if required fields are
+	         *  missing. The later still returns the encoded node Buffer in the `encoded` property on the error.
+	         * @expose
+	         */
+	        MessagePrototype.encodeNB = function () {
+	          try {
+	            return this.encode().toBuffer();
+	          } catch (e) {
+	            if (e["encoded"]) e["encoded"] = e["encoded"].toBuffer();
+	            throw (e);
+	          }
+	        };
+
+	        /**
+	         * Returns the message as a node Buffer. This is an alias for {@link ProtoBuf.Builder.Message#encodeNB}.
+	         * @name ProtoBuf.Builder.Message#toBuffer
+	         * @function
+	         * @return {!Buffer}
+	         * @throws {Error} If the message cannot be encoded or if required fields are missing. The later still
+	         *  returns the encoded node Buffer in the `encoded` property on the error.
+	         * @expose
+	         */
+	        MessagePrototype.toBuffer = MessagePrototype.encodeNB;
+
+	        /**
+	         * Directly encodes the message to a base64 encoded string.
+	         * @name ProtoBuf.Builder.Message#encode64
+	         * @function
+	         * @return {string} Base64 encoded string
+	         * @throws {Error} If the underlying buffer cannot be encoded or if required fields are missing. The later
+	         *  still returns the encoded base64 string in the `encoded` property on the error.
+	         * @expose
+	         */
+	        MessagePrototype.encode64 = function () {
+	          try {
+	            return this.encode().toBase64();
+	          } catch (e) {
+	            if (e["encoded"]) e["encoded"] = e["encoded"].toBase64();
+	            throw (e);
+	          }
+	        };
+
+	        /**
+	         * Returns the message as a base64 encoded string. This is an alias for {@link ProtoBuf.Builder.Message#encode64}.
+	         * @name ProtoBuf.Builder.Message#toBase64
+	         * @function
+	         * @return {string} Base64 encoded string
+	         * @throws {Error} If the message cannot be encoded or if required fields are missing. The later still
+	         *  returns the encoded base64 string in the `encoded` property on the error.
+	         * @expose
+	         */
+	        MessagePrototype.toBase64 = MessagePrototype.encode64;
+
+	        /**
+	         * Directly encodes the message to a hex encoded string.
+	         * @name ProtoBuf.Builder.Message#encodeHex
+	         * @function
+	         * @return {string} Hex encoded string
+	         * @throws {Error} If the underlying buffer cannot be encoded or if required fields are missing. The later
+	         *  still returns the encoded hex string in the `encoded` property on the error.
+	         * @expose
+	         */
+	        MessagePrototype.encodeHex = function () {
+	          try {
+	            return this.encode().toHex();
+	          } catch (e) {
+	            if (e["encoded"]) e["encoded"] = e["encoded"].toHex();
+	            throw (e);
+	          }
+	        };
+
+	        /**
+	         * Returns the message as a hex encoded string. This is an alias for {@link ProtoBuf.Builder.Message#encodeHex}.
+	         * @name ProtoBuf.Builder.Message#toHex
+	         * @function
+	         * @return {string} Hex encoded string
+	         * @throws {Error} If the message cannot be encoded or if required fields are missing. The later still
+	         *  returns the encoded hex string in the `encoded` property on the error.
+	         * @expose
+	         */
+	        MessagePrototype.toHex = MessagePrototype.encodeHex;
+
+	        /**
+	         * Clones a message object or field value to a raw object.
+	         * @param {*} obj Object to clone
+	         * @param {boolean} binaryAsBase64 Whether to include binary data as base64 strings or as a buffer otherwise
+	         * @param {boolean} longsAsStrings Whether to encode longs as strings
+	         * @param {!ProtoBuf.Reflect.T=} resolvedType The resolved field type if a field
+	         * @returns {*} Cloned object
+	         * @inner
+	         */
+	        function cloneRaw(obj, binaryAsBase64, longsAsStrings, resolvedType) {
+	          if (obj === null || typeof obj !== 'object') {
+	            // Convert enum values to their respective names
+	            if (resolvedType && resolvedType instanceof ProtoBuf.Reflect.Enum) {
+	              var name = ProtoBuf.Reflect.Enum.getName(resolvedType.object, obj);
+	              if (name !== null)
+	                return name;
+	            }
+	            // Pass-through string, number, boolean, null...
+	            return obj;
+	          }
+	          // Convert ByteBuffers to raw buffer or strings
+	          if (ByteBuffer.isByteBuffer(obj))
+	            return binaryAsBase64 ? obj.toBase64() : obj.toBuffer();
+	          // Convert Longs to proper objects or strings
+	          if (ProtoBuf.Long.isLong(obj))
+	            return longsAsStrings ? obj.toString() : ProtoBuf.Long.fromValue(obj);
+	          var clone;
+	          // Clone arrays
+	          if (Array.isArray(obj)) {
+	            clone = [];
+	            obj.forEach(function (v, k) {
+	              clone[k] = cloneRaw(v, binaryAsBase64, longsAsStrings, resolvedType);
+	            });
+	            return clone;
+	          }
+	          clone = {};
+	          // Convert maps to objects
+	          if (obj instanceof ProtoBuf.Map) {
+	            var it = obj.entries();
+	            for (var e = it.next(); !e.done; e = it.next())
+	              clone[obj.keyElem.valueToString(e.value[0])] = cloneRaw(e.value[1], binaryAsBase64, longsAsStrings, obj.valueElem.resolvedType);
+	            return clone;
+	          }
+	          // Everything else is a non-null object
+	          var type = obj.$type,
+	            field = undefined;
+	          for (var i in obj)
+	            if (obj.hasOwnProperty(i)) {
+	              if (type && (field = type.getChild(i)))
+	                clone[i] = cloneRaw(obj[i], binaryAsBase64, longsAsStrings, field.resolvedType);
+	              else
+	                clone[i] = cloneRaw(obj[i], binaryAsBase64, longsAsStrings);
+	            }
+	          return clone;
+	        }
+
+	        /**
+	         * Returns the message's raw payload.
+	         * @param {boolean=} binaryAsBase64 Whether to include binary data as base64 strings instead of Buffers, defaults to `false`
+	         * @param {boolean} longsAsStrings Whether to encode longs as strings
+	         * @returns {Object.<string,*>} Raw payload
+	         * @expose
+	         */
+	        MessagePrototype.toRaw = function (binaryAsBase64, longsAsStrings) {
+	          return cloneRaw(this, !!binaryAsBase64, !!longsAsStrings, this.$type);
+	        };
+
+	        /**
+	         * Encodes a message to JSON.
+	         * @returns {string} JSON string
+	         * @expose
+	         */
+	        MessagePrototype.encodeJSON = function () {
+	          return JSON.stringify(
+	            cloneRaw(this,
+	              /* binary-as-base64 */
+	              true,
+	              /* longs-as-strings */
+	              true,
+	              this.$type
+	            )
+	          );
+	        };
+
+	        /**
+	         * Decodes a message from the specified buffer or string.
+	         * @name ProtoBuf.Builder.Message.decode
+	         * @function
+	         * @param {!ByteBuffer|!ArrayBuffer|!Buffer|string} buffer Buffer to decode from
+	         * @param {(number|string)=} length Message length. Defaults to decode all the remainig data.
+	         * @param {string=} enc Encoding if buffer is a string: hex, utf8 (not recommended), defaults to base64
+	         * @return {!ProtoBuf.Builder.Message} Decoded message
+	         * @throws {Error} If the message cannot be decoded or if required fields are missing. The later still
+	         *  returns the decoded message with missing fields in the `decoded` property on the error.
+	         * @expose
+	         * @see ProtoBuf.Builder.Message.decode64
+	         * @see ProtoBuf.Builder.Message.decodeHex
+	         */
+	        Message.decode = function (buffer, length, enc) {
+	          if (typeof length === 'string')
+	            enc = length,
+	            length = -1;
+	          if (typeof buffer === 'string')
+	            buffer = ByteBuffer.wrap(buffer, enc ? enc : "base64");
+	          buffer = ByteBuffer.isByteBuffer(buffer) ? buffer : ByteBuffer.wrap(buffer); // May throw
+	          var le = buffer.littleEndian;
+	          try {
+	            var msg = T.decode(buffer.LE());
+	            buffer.LE(le);
+	            return msg;
+	          } catch (e) {
+	            buffer.LE(le);
+	            throw (e);
+	          }
+	        };
+
+	        /**
+	         * Decodes a varint32 length-delimited message from the specified buffer or string.
+	         * @name ProtoBuf.Builder.Message.decodeDelimited
+	         * @function
+	         * @param {!ByteBuffer|!ArrayBuffer|!Buffer|string} buffer Buffer to decode from
+	         * @param {string=} enc Encoding if buffer is a string: hex, utf8 (not recommended), defaults to base64
+	         * @return {ProtoBuf.Builder.Message} Decoded message or `null` if not enough bytes are available yet
+	         * @throws {Error} If the message cannot be decoded or if required fields are missing. The later still
+	         *  returns the decoded message with missing fields in the `decoded` property on the error.
+	         * @expose
+	         */
+	        Message.decodeDelimited = function (buffer, enc) {
+	          if (typeof buffer === 'string')
+	            buffer = ByteBuffer.wrap(buffer, enc ? enc : "base64");
+	          buffer = ByteBuffer.isByteBuffer(buffer) ? buffer : ByteBuffer.wrap(buffer); // May throw
+	          if (buffer.remaining() < 1)
+	            return null;
+	          var off = buffer.offset,
+	            len = buffer.readVarint32();
+	          if (buffer.remaining() < len) {
+	            buffer.offset = off;
+	            return null;
+	          }
+	          try {
+	            var msg = T.decode(buffer.slice(buffer.offset, buffer.offset + len).LE());
+	            buffer.offset += len;
+	            return msg;
+	          } catch (err) {
+	            buffer.offset += len;
+	            throw err;
+	          }
+	        };
+
+	        /**
+	         * Decodes the message from the specified base64 encoded string.
+	         * @name ProtoBuf.Builder.Message.decode64
+	         * @function
+	         * @param {string} str String to decode from
+	         * @return {!ProtoBuf.Builder.Message} Decoded message
+	         * @throws {Error} If the message cannot be decoded or if required fields are missing. The later still
+	         *  returns the decoded message with missing fields in the `decoded` property on the error.
+	         * @expose
+	         */
+	        Message.decode64 = function (str) {
+	          return Message.decode(str, "base64");
+	        };
+
+	        /**
+	         * Decodes the message from the specified hex encoded string.
+	         * @name ProtoBuf.Builder.Message.decodeHex
+	         * @function
+	         * @param {string} str String to decode from
+	         * @return {!ProtoBuf.Builder.Message} Decoded message
+	         * @throws {Error} If the message cannot be decoded or if required fields are missing. The later still
+	         *  returns the decoded message with missing fields in the `decoded` property on the error.
+	         * @expose
+	         */
+	        Message.decodeHex = function (str) {
+	          return Message.decode(str, "hex");
+	        };
+
+	        /**
+	         * Decodes the message from a JSON string.
+	         * @name ProtoBuf.Builder.Message.decodeJSON
+	         * @function
+	         * @param {string} str String to decode from
+	         * @return {!ProtoBuf.Builder.Message} Decoded message
+	         * @throws {Error} If the message cannot be decoded or if required fields are
+	         * missing.
+	         * @expose
+	         */
+	        Message.decodeJSON = function (str) {
+	          return new Message(JSON.parse(str));
+	        };
+
+	        // Utility
+
+	        /**
+	         * Returns a string representation of this Message.
+	         * @name ProtoBuf.Builder.Message#toString
+	         * @function
+	         * @return {string} String representation as of ".Fully.Qualified.MessageName"
+	         * @expose
+	         */
+	        MessagePrototype.toString = function () {
+	          return T.toString();
+	        };
+
+	        // Properties
+
+	        /**
+	         * Message options.
+	         * @name ProtoBuf.Builder.Message.$options
+	         * @type {Object.<string,*>}
+	         * @expose
+	         */
+	        var $optionsS; // cc needs this
+
+	        /**
+	         * Message options.
+	         * @name ProtoBuf.Builder.Message#$options
+	         * @type {Object.<string,*>}
+	         * @expose
+	         */
+	        var $options;
+
+	        /**
+	         * Reflection type.
+	         * @name ProtoBuf.Builder.Message.$type
+	         * @type {!ProtoBuf.Reflect.Message}
+	         * @expose
+	         */
+	        var $typeS;
+
+	        /**
+	         * Reflection type.
+	         * @name ProtoBuf.Builder.Message#$type
+	         * @type {!ProtoBuf.Reflect.Message}
+	         * @expose
+	         */
+	        var $type;
+
+	        if (Object.defineProperty)
+	          Object.defineProperty(Message, '$options', { "value": T.buildOpt() }),
+	          Object.defineProperty(MessagePrototype, "$options", { "value": Message["$options"] }),
+	          Object.defineProperty(Message, "$type", { "value": T }),
+	          Object.defineProperty(MessagePrototype, "$type", { "value": T });
+
+	        return Message;
+
+	      })(ProtoBuf, this);
+
+	      // Static enums and prototyped sub-messages / cached collections
+	      this._fields = [];
+	      this._fieldsById = {};
+	      this._fieldsByName = {};
+	      for (var i = 0, k = this.children.length, child; i < k; i++) {
+	        child = this.children[i];
+	        if (child instanceof Enum || child instanceof Message || child instanceof Service) {
+	          if (clazz.hasOwnProperty(child.name))
+	            throw Error("Illegal reflect child of " + this.toString(true) + ": " + child.toString(true) + " cannot override static property '" + child.name + "'");
+	          clazz[child.name] = child.build();
+	        } else if (child instanceof Message.Field)
+	          child.build(),
+	          this._fields.push(child),
+	          this._fieldsById[child.id] = child,
+	          this._fieldsByName[child.name] = child;
+	        else if (!(child instanceof Message.OneOf) && !(child instanceof Extension)) // Not built
+	          throw Error("Illegal reflect child of " + this.toString(true) + ": " + this.children[i].toString(true));
+	      }
+
+	      return this.clazz = clazz;
+	    };
+
+	    /**
+	     * Encodes a runtime message's contents to the specified buffer.
+	     * @param {!ProtoBuf.Builder.Message} message Runtime message to encode
+	     * @param {ByteBuffer} buffer ByteBuffer to write to
+	     * @param {boolean=} noVerify Whether to not verify field values, defaults to `false`
+	     * @return {ByteBuffer} The ByteBuffer for chaining
+	     * @throws {Error} If required fields are missing or the message cannot be encoded for another reason
+	     * @expose
+	     */
+	    MessagePrototype.encode = function (message, buffer, noVerify) {
+	      var fieldMissing = null,
+	        field;
+	      for (var i = 0, k = this._fields.length, val; i < k; ++i) {
+	        field = this._fields[i];
+	        val = message[field.name];
+	        if (field.required && val === null) {
+	          if (fieldMissing === null)
+	            fieldMissing = field;
+	        } else
+	          field.encode(noVerify ? val : field.verifyValue(val), buffer, message);
+	      }
+	      if (fieldMissing !== null) {
+	        var err = Error("Missing at least one required field for " + this.toString(true) + ": " + fieldMissing);
+	        err["encoded"] = buffer; // Still expose what we got
+	        throw (err);
+	      }
+	      return buffer;
+	    };
+
+	    /**
+	     * Calculates a runtime message's byte length.
+	     * @param {!ProtoBuf.Builder.Message} message Runtime message to encode
+	     * @returns {number} Byte length
+	     * @throws {Error} If required fields are missing or the message cannot be calculated for another reason
+	     * @expose
+	     */
+	    MessagePrototype.calculate = function (message) {
+	      for (var n = 0, i = 0, k = this._fields.length, field, val; i < k; ++i) {
+	        field = this._fields[i];
+	        val = message[field.name];
+	        if (field.required && val === null)
+	          throw Error("Missing at least one required field for " + this.toString(true) + ": " + field);
+	        else
+	          n += field.calculate(val, message);
+	      }
+	      return n;
+	    };
+
+	    /**
+	     * Skips all data until the end of the specified group has been reached.
+	     * @param {number} expectedId Expected GROUPEND id
+	     * @param {!ByteBuffer} buf ByteBuffer
+	     * @returns {boolean} `true` if a value as been skipped, `false` if the end has been reached
+	     * @throws {Error} If it wasn't possible to find the end of the group (buffer overrun or end tag mismatch)
+	     * @inner
+	     */
+	    function skipTillGroupEnd(expectedId, buf) {
+	      var tag = buf.readVarint32(), // Throws on OOB
+	        wireType = tag & 0x07,
+	        id = tag >>> 3;
+	      switch (wireType) {
+	      case ProtoBuf.WIRE_TYPES.VARINT:
+	        do tag = buf.readUint8();
+	        while ((tag & 0x80) === 0x80);
+	        break;
+	      case ProtoBuf.WIRE_TYPES.BITS64:
+	        buf.offset += 8;
+	        break;
+	      case ProtoBuf.WIRE_TYPES.LDELIM:
+	        tag = buf.readVarint32(); // reads the varint
+	        buf.offset += tag; // skips n bytes
+	        break;
+	      case ProtoBuf.WIRE_TYPES.STARTGROUP:
+	        skipTillGroupEnd(id, buf);
+	        break;
+	      case ProtoBuf.WIRE_TYPES.ENDGROUP:
+	        if (id === expectedId)
+	          return false;
+	        else
+	          throw Error("Illegal GROUPEND after unknown group: " + id + " (" + expectedId + " expected)");
+	      case ProtoBuf.WIRE_TYPES.BITS32:
+	        buf.offset += 4;
+	        break;
+	      default:
+	        throw Error("Illegal wire type in unknown group " + expectedId + ": " + wireType);
+	      }
+	      return true;
+	    }
+
+	    /**
+	     * Decodes an encoded message and returns the decoded message.
+	     * @param {ByteBuffer} buffer ByteBuffer to decode from
+	     * @param {number=} length Message length. Defaults to decode all remaining data.
+	     * @param {number=} expectedGroupEndId Expected GROUPEND id if this is a legacy group
+	     * @return {ProtoBuf.Builder.Message} Decoded message
+	     * @throws {Error} If the message cannot be decoded
+	     * @expose
+	     */
+	    MessagePrototype.decode = function (buffer, length, expectedGroupEndId) {
+	      length = typeof length === 'number' ? length : -1;
+	      var start = buffer.offset,
+	        msg = new(this.clazz)(),
+	        tag, wireType, id, field;
+	      while (buffer.offset < start + length || (length === -1 && buffer.remaining() > 0)) {
+	        tag = buffer.readVarint32();
+	        wireType = tag & 0x07;
+	        id = tag >>> 3;
+	        if (wireType === ProtoBuf.WIRE_TYPES.ENDGROUP) {
+	          if (id !== expectedGroupEndId)
+	            throw Error("Illegal group end indicator for " + this.toString(true) + ": " + id + " (" + (expectedGroupEndId ? expectedGroupEndId + " expected" : "not a group") + ")");
+	          break;
+	        }
+	        if (!(field = this._fieldsById[id])) {
+	          // "messages created by your new code can be parsed by your old code: old binaries simply ignore the new field when parsing."
+	          switch (wireType) {
+	          case ProtoBuf.WIRE_TYPES.VARINT:
+	            buffer.readVarint32();
+	            break;
+	          case ProtoBuf.WIRE_TYPES.BITS32:
+	            buffer.offset += 4;
+	            break;
+	          case ProtoBuf.WIRE_TYPES.BITS64:
+	            buffer.offset += 8;
+	            break;
+	          case ProtoBuf.WIRE_TYPES.LDELIM:
+	            var len = buffer.readVarint32();
+	            buffer.offset += len;
+	            break;
+	          case ProtoBuf.WIRE_TYPES.STARTGROUP:
+	            while (skipTillGroupEnd(id, buffer)) {}
+	            break;
+	          default:
+	            throw Error("Illegal wire type for unknown field " + id + " in " + this.toString(true) + "#decode: " + wireType);
+	          }
+	          continue;
+	        }
+	        if (field.repeated && !field.options["packed"]) {
+	          msg[field.name].push(field.decode(wireType, buffer));
+	        } else if (field.map) {
+	          var keyval = field.decode(wireType, buffer);
+	          msg[field.name].set(keyval[0], keyval[1]);
+	        } else {
+	          msg[field.name] = field.decode(wireType, buffer);
+	          if (field.oneof) { // Field is part of an OneOf (not a virtual OneOf field)
+	            var currentField = msg[field.oneof.name]; // Virtual field references currently set field
+	            if (currentField !== null && currentField !== field.name)
+	              msg[currentField] = null; // Clear currently set field
+	            msg[field.oneof.name] = field.name; // Point virtual field at this field
+	          }
+	        }
+	      }
+
+	      // Check if all required fields are present and set default values for optional fields that are not
+	      for (var i = 0, k = this._fields.length; i < k; ++i) {
+	        field = this._fields[i];
+	        if (msg[field.name] === null) {
+	          if (this.syntax === "proto3") { // Proto3 sets default values by specification
+	            msg[field.name] = field.defaultValue;
+	          } else if (field.required) {
+	            var err = Error("Missing at least one required field for " + this.toString(true) + ": " + field.name);
+	            err["decoded"] = msg; // Still expose what we got
+	            throw (err);
+	          } else if (ProtoBuf.populateDefaults && field.defaultValue !== null)
+	            msg[field.name] = field.defaultValue;
+	        }
+	      }
+	      return msg;
+	    };
+
+	    /**
+	     * @alias ProtoBuf.Reflect.Message
+	     * @expose
+	     */
+	    Reflect.Message = Message;
+
+	    /**
+	     * Constructs a new Message Field.
+	     * @exports ProtoBuf.Reflect.Message.Field
+	     * @param {!ProtoBuf.Builder} builder Builder reference
+	     * @param {!ProtoBuf.Reflect.Message} message Message reference
+	     * @param {string} rule Rule, one of requried, optional, repeated
+	     * @param {string?} keytype Key data type, if any.
+	     * @param {string} type Data type, e.g. int32
+	     * @param {string} name Field name
+	     * @param {number} id Unique field id
+	     * @param {Object.<string,*>=} options Options
+	     * @param {!ProtoBuf.Reflect.Message.OneOf=} oneof Enclosing OneOf
+	     * @param {string?} syntax The syntax level of this definition (e.g., proto3)
+	     * @constructor
+	     * @extends ProtoBuf.Reflect.T
+	     */
+	    var Field = function (builder, message, rule, keytype, type, name, id, options, oneof, syntax) {
+	      T.call(this, builder, message, name);
+
+	      /**
+	       * @override
+	       */
+	      this.className = "Message.Field";
+
+	      /**
+	       * Message field required flag.
+	       * @type {boolean}
+	       * @expose
+	       */
+	      this.required = rule === "required";
+
+	      /**
+	       * Message field repeated flag.
+	       * @type {boolean}
+	       * @expose
+	       */
+	      this.repeated = rule === "repeated";
+
+	      /**
+	       * Message field map flag.
+	       * @type {boolean}
+	       * @expose
+	       */
+	      this.map = rule === "map";
+
+	      /**
+	       * Message field key type. Type reference string if unresolved, protobuf
+	       * type if resolved. Valid only if this.map === true, null otherwise.
+	       * @type {string|{name: string, wireType: number}|null}
+	       * @expose
+	       */
+	      this.keyType = keytype || null;
+
+	      /**
+	       * Message field type. Type reference string if unresolved, protobuf type if
+	       * resolved. In a map field, this is the value type.
+	       * @type {string|{name: string, wireType: number}}
+	       * @expose
+	       */
+	      this.type = type;
+
+	      /**
+	       * Resolved type reference inside the global namespace.
+	       * @type {ProtoBuf.Reflect.T|null}
+	       * @expose
+	       */
+	      this.resolvedType = null;
+
+	      /**
+	       * Unique message field id.
+	       * @type {number}
+	       * @expose
+	       */
+	      this.id = id;
+
+	      /**
+	       * Message field options.
+	       * @type {!Object.<string,*>}
+	       * @dict
+	       * @expose
+	       */
+	      this.options = options || {};
+
+	      /**
+	       * Default value.
+	       * @type {*}
+	       * @expose
+	       */
+	      this.defaultValue = null;
+
+	      /**
+	       * Enclosing OneOf.
+	       * @type {?ProtoBuf.Reflect.Message.OneOf}
+	       * @expose
+	       */
+	      this.oneof = oneof || null;
+
+	      /**
+	       * Syntax level of this definition (e.g., proto3).
+	       * @type {string}
+	       * @expose
+	       */
+	      this.syntax = syntax || 'proto2';
+
+	      /**
+	       * Original field name.
+	       * @type {string}
+	       * @expose
+	       */
+	      this.originalName = this.name; // Used to revert camelcase transformation on naming collisions
+
+	      /**
+	       * Element implementation. Created in build() after types are resolved.
+	       * @type {ProtoBuf.Element}
+	       * @expose
+	       */
+	      this.element = null;
+
+	      /**
+	       * Key element implementation, for map fields. Created in build() after
+	       * types are resolved.
+	       * @type {ProtoBuf.Element}
+	       * @expose
+	       */
+	      this.keyElement = null;
+
+	      // Convert field names to camel case notation if the override is set
+	      if (this.builder.options['convertFieldsToCamelCase'] && !(this instanceof Message.ExtensionField))
+	        this.name = ProtoBuf.Util.toCamelCase(this.name);
+	    };
+
+	    /**
+	     * @alias ProtoBuf.Reflect.Message.Field.prototype
+	     * @inner
+	     */
+	    var FieldPrototype = Field.prototype = Object.create(T.prototype);
+
+	    /**
+	     * Builds the field.
+	     * @override
+	     * @expose
+	     */
+	    FieldPrototype.build = function () {
+	      this.element = new Element(this.type, this.resolvedType, false, this.syntax);
+	      if (this.map)
+	        this.keyElement = new Element(this.keyType, undefined, true, this.syntax);
+
+	      // In proto3, fields do not have field presence, and every field is set to
+	      // its type's default value ("", 0, 0.0, or false).
+	      if (this.syntax === 'proto3' && !this.repeated && !this.map)
+	        this.defaultValue = Element.defaultFieldValue(this.type);
+
+	      // Otherwise, default values are present when explicitly specified
+	      else if (typeof this.options['default'] !== 'undefined')
+	        this.defaultValue = this.verifyValue(this.options['default']);
+	    };
+
+	    /**
+	     * Checks if the given value can be set for this field.
+	     * @param {*} value Value to check
+	     * @param {boolean=} skipRepeated Whether to skip the repeated value check or not. Defaults to false.
+	     * @return {*} Verified, maybe adjusted, value
+	     * @throws {Error} If the value cannot be set for this field
+	     * @expose
+	     */
+	    FieldPrototype.verifyValue = function (value, skipRepeated) {
+	      skipRepeated = skipRepeated || false;
+	      var self = this;
+
+	      function fail(val, msg) {
+	        throw Error("Illegal value for " + self.toString(true) + " of type " + self.type.name + ": " + val + " (" + msg + ")");
+	      }
+	      if (value === null) { // NULL values for optional fields
+	        if (this.required)
+	          fail(typeof value, "required");
+	        if (this.syntax === 'proto3' && this.type !== ProtoBuf.TYPES["message"])
+	          fail(typeof value, "proto3 field without field presence cannot be null");
+	        return null;
+	      }
+	      var i;
+	      if (this.repeated && !skipRepeated) { // Repeated values as arrays
+	        if (!Array.isArray(value))
+	          value = [value];
+	        var res = [];
+	        for (i = 0; i < value.length; i++)
+	          res.push(this.element.verifyValue(value[i]));
+	        return res;
+	      }
+	      if (this.map && !skipRepeated) { // Map values as objects
+	        if (!(value instanceof ProtoBuf.Map)) {
+	          // If not already a Map, attempt to convert.
+	          if (!(value instanceof Object)) {
+	            fail(typeof value,
+	              "expected ProtoBuf.Map or raw object for map field");
+	          }
+	          return new ProtoBuf.Map(this, value);
+	        } else {
+	          return value;
+	        }
+	      }
+	      // All non-repeated fields expect no array
+	      if (!this.repeated && Array.isArray(value))
+	        fail(typeof value, "no array expected");
+
+	      return this.element.verifyValue(value);
+	    };
+
+	    /**
+	     * Determines whether the field will have a presence on the wire given its
+	     * value.
+	     * @param {*} value Verified field value
+	     * @param {!ProtoBuf.Builder.Message} message Runtime message
+	     * @return {boolean} Whether the field will be present on the wire
+	     */
+	    FieldPrototype.hasWirePresence = function (value, message) {
+	      if (this.syntax !== 'proto3')
+	        return (value !== null);
+	      if (this.oneof && message[this.oneof.name] === this.name)
+	        return true;
+	      switch (this.type) {
+	      case ProtoBuf.TYPES["int32"]:
+	      case ProtoBuf.TYPES["sint32"]:
+	      case ProtoBuf.TYPES["sfixed32"]:
+	      case ProtoBuf.TYPES["uint32"]:
+	      case ProtoBuf.TYPES["fixed32"]:
+	        return value !== 0;
+
+	      case ProtoBuf.TYPES["int64"]:
+	      case ProtoBuf.TYPES["sint64"]:
+	      case ProtoBuf.TYPES["sfixed64"]:
+	      case ProtoBuf.TYPES["uint64"]:
+	      case ProtoBuf.TYPES["fixed64"]:
+	        return value.low !== 0 || value.high !== 0;
+
+	      case ProtoBuf.TYPES["bool"]:
+	        return value;
+
+	      case ProtoBuf.TYPES["float"]:
+	      case ProtoBuf.TYPES["double"]:
+	        return value !== 0.0;
+
+	      case ProtoBuf.TYPES["string"]:
+	        return value.length > 0;
+
+	      case ProtoBuf.TYPES["bytes"]:
+	        return value.remaining() > 0;
+
+	      case ProtoBuf.TYPES["enum"]:
+	        return value !== 0;
+
+	      case ProtoBuf.TYPES["message"]:
+	        return value !== null;
+	      default:
+	        return true;
+	      }
+	    };
+
+	    /**
+	     * Encodes the specified field value to the specified buffer.
+	     * @param {*} value Verified field value
+	     * @param {ByteBuffer} buffer ByteBuffer to encode to
+	     * @param {!ProtoBuf.Builder.Message} message Runtime message
+	     * @return {ByteBuffer} The ByteBuffer for chaining
+	     * @throws {Error} If the field cannot be encoded
+	     * @expose
+	     */
+	    FieldPrototype.encode = function (value, buffer, message) {
+	      if (this.type === null || typeof this.type !== 'object')
+	        throw Error("[INTERNAL] Unresolved type in " + this.toString(true) + ": " + this.type);
+	      if (value === null || (this.repeated && value.length == 0))
+	        return buffer; // Optional omitted
+	      try {
+	        if (this.repeated) {
+	          var i;
+	          // "Only repeated fields of primitive numeric types (types which use the varint, 32-bit, or 64-bit wire
+	          // types) can be declared 'packed'."
+	          if (this.options["packed"] && ProtoBuf.PACKABLE_WIRE_TYPES.indexOf(this.type.wireType) >= 0) {
+	            // "All of the elements of the field are packed into a single key-value pair with wire type 2
+	            // (length-delimited). Each element is encoded the same way it would be normally, except without a
+	            // tag preceding it."
+	            buffer.writeVarint32((this.id << 3) | ProtoBuf.WIRE_TYPES.LDELIM);
+	            buffer.ensureCapacity(buffer.offset += 1); // We do not know the length yet, so let's assume a varint of length 1
+	            var start = buffer.offset; // Remember where the contents begin
+	            for (i = 0; i < value.length; i++)
+	              this.element.encodeValue(this.id, value[i], buffer);
+	            var len = buffer.offset - start,
+	              varintLen = ByteBuffer.calculateVarint32(len);
+	            if (varintLen > 1) { // We need to move the contents
+	              var contents = buffer.slice(start, buffer.offset);
+	              start += varintLen - 1;
+	              buffer.offset = start;
+	              buffer.append(contents);
+	            }
+	            buffer.writeVarint32(len, start - varintLen);
+	          } else {
+	            // "If your message definition has repeated elements (without the [packed=true] option), the encoded
+	            // message has zero or more key-value pairs with the same tag number"
+	            for (i = 0; i < value.length; i++)
+	              buffer.writeVarint32((this.id << 3) | this.type.wireType),
+	              this.element.encodeValue(this.id, value[i], buffer);
+	          }
+	        } else if (this.map) {
+	          // Write out each map entry as a submessage.
+	          value.forEach(function (val, key, m) {
+	            // Compute the length of the submessage (key, val) pair.
+	            var length =
+	              ByteBuffer.calculateVarint32((1 << 3) | this.keyType.wireType) +
+	              this.keyElement.calculateLength(1, key) +
+	              ByteBuffer.calculateVarint32((2 << 3) | this.type.wireType) +
+	              this.element.calculateLength(2, val);
+
+	            // Submessage with wire type of length-delimited.
+	            buffer.writeVarint32((this.id << 3) | ProtoBuf.WIRE_TYPES.LDELIM);
+	            buffer.writeVarint32(length);
+
+	            // Write out the key and val.
+	            buffer.writeVarint32((1 << 3) | this.keyType.wireType);
+	            this.keyElement.encodeValue(1, key, buffer);
+	            buffer.writeVarint32((2 << 3) | this.type.wireType);
+	            this.element.encodeValue(2, val, buffer);
+	          }, this);
+	        } else {
+	          if (this.hasWirePresence(value, message)) {
+	            buffer.writeVarint32((this.id << 3) | this.type.wireType);
+	            this.element.encodeValue(this.id, value, buffer);
+	          }
+	        }
+	      } catch (e) {
+	        throw Error("Illegal value for " + this.toString(true) + ": " + value + " (" + e + ")");
+	      }
+	      return buffer;
+	    };
+
+	    /**
+	     * Calculates the length of this field's value on the network level.
+	     * @param {*} value Field value
+	     * @param {!ProtoBuf.Builder.Message} message Runtime message
+	     * @returns {number} Byte length
+	     * @expose
+	     */
+	    FieldPrototype.calculate = function (value, message) {
+	      value = this.verifyValue(value); // May throw
+	      if (this.type === null || typeof this.type !== 'object')
+	        throw Error("[INTERNAL] Unresolved type in " + this.toString(true) + ": " + this.type);
+	      if (value === null || (this.repeated && value.length == 0))
+	        return 0; // Optional omitted
+	      var n = 0;
+	      try {
+	        if (this.repeated) {
+	          var i, ni;
+	          if (this.options["packed"] && ProtoBuf.PACKABLE_WIRE_TYPES.indexOf(this.type.wireType) >= 0) {
+	            n += ByteBuffer.calculateVarint32((this.id << 3) | ProtoBuf.WIRE_TYPES.LDELIM);
+	            ni = 0;
+	            for (i = 0; i < value.length; i++)
+	              ni += this.element.calculateLength(this.id, value[i]);
+	            n += ByteBuffer.calculateVarint32(ni);
+	            n += ni;
+	          } else {
+	            for (i = 0; i < value.length; i++)
+	              n += ByteBuffer.calculateVarint32((this.id << 3) | this.type.wireType),
+	              n += this.element.calculateLength(this.id, value[i]);
+	          }
+	        } else if (this.map) {
+	          // Each map entry becomes a submessage.
+	          value.forEach(function (val, key, m) {
+	            // Compute the length of the submessage (key, val) pair.
+	            var length =
+	              ByteBuffer.calculateVarint32((1 << 3) | this.keyType.wireType) +
+	              this.keyElement.calculateLength(1, key) +
+	              ByteBuffer.calculateVarint32((2 << 3) | this.type.wireType) +
+	              this.element.calculateLength(2, val);
+
+	            n += ByteBuffer.calculateVarint32((this.id << 3) | ProtoBuf.WIRE_TYPES.LDELIM);
+	            n += ByteBuffer.calculateVarint32(length);
+	            n += length;
+	          }, this);
+	        } else {
+	          if (this.hasWirePresence(value, message)) {
+	            n += ByteBuffer.calculateVarint32((this.id << 3) | this.type.wireType);
+	            n += this.element.calculateLength(this.id, value);
+	          }
+	        }
+	      } catch (e) {
+	        throw Error("Illegal value for " + this.toString(true) + ": " + value + " (" + e + ")");
+	      }
+	      return n;
+	    };
+
+	    /**
+	     * Decode the field value from the specified buffer.
+	     * @param {number} wireType Leading wire type
+	     * @param {ByteBuffer} buffer ByteBuffer to decode from
+	     * @param {boolean=} skipRepeated Whether to skip the repeated check or not. Defaults to false.
+	     * @return {*} Decoded value: array for packed repeated fields, [key, value] for
+	     *             map fields, or an individual value otherwise.
+	     * @throws {Error} If the field cannot be decoded
+	     * @expose
+	     */
+	    FieldPrototype.decode = function (wireType, buffer, skipRepeated) {
+	      var value, nBytes;
+
+	      // We expect wireType to match the underlying type's wireType unless we see
+	      // a packed repeated field, or unless this is a map field.
+	      var wireTypeOK =
+	        (!this.map && wireType == this.type.wireType) ||
+	        (!skipRepeated && this.repeated && this.options["packed"] &&
+	          wireType == ProtoBuf.WIRE_TYPES.LDELIM) ||
+	        (this.map && wireType == ProtoBuf.WIRE_TYPES.LDELIM);
+	      if (!wireTypeOK)
+	        throw Error("Illegal wire type for field " + this.toString(true) + ": " + wireType + " (" + this.type.wireType + " expected)");
+
+	      // Handle packed repeated fields.
+	      if (wireType == ProtoBuf.WIRE_TYPES.LDELIM && this.repeated && this.options["packed"] && ProtoBuf.PACKABLE_WIRE_TYPES.indexOf(this.type.wireType) >= 0) {
+	        if (!skipRepeated) {
+	          nBytes = buffer.readVarint32();
+	          nBytes = buffer.offset + nBytes; // Limit
+	          var values = [];
+	          while (buffer.offset < nBytes)
+	            values.push(this.decode(this.type.wireType, buffer, true));
+	          return values;
+	        }
+	        // Read the next value otherwise...
+	      }
+
+	      // Handle maps.
+	      if (this.map) {
+	        // Read one (key, value) submessage, and return [key, value]
+	        var key = Element.defaultFieldValue(this.keyType);
+	        value = Element.defaultFieldValue(this.type);
+
+	        // Read the length
+	        nBytes = buffer.readVarint32();
+	        if (buffer.remaining() < nBytes)
+	          throw Error("Illegal number of bytes for " + this.toString(true) + ": " + nBytes + " required but got only " + buffer.remaining());
+
+	        // Get a sub-buffer of this key/value submessage
+	        var msgbuf = buffer.clone();
+	        msgbuf.limit = msgbuf.offset + nBytes;
+	        buffer.offset += nBytes;
+
+	        while (msgbuf.remaining() > 0) {
+	          var tag = msgbuf.readVarint32();
+	          wireType = tag & 0x07;
+	          var id = tag >>> 3;
+	          if (id === 1) {
+	            key = this.keyElement.decode(msgbuf, wireType, id);
+	          } else if (id === 2) {
+	            value = this.element.decode(msgbuf, wireType, id);
+	          } else {
+	            throw Error("Unexpected tag in map field key/value submessage");
+	          }
+	        }
+
+	        return [key, value];
+	      }
+
+	      // Handle singular and non-packed repeated field values.
+	      return this.element.decode(buffer, wireType, this.id);
+	    };
+
+	    /**
+	     * @alias ProtoBuf.Reflect.Message.Field
+	     * @expose
+	     */
+	    Reflect.Message.Field = Field;
+
+	    /**
+	     * Constructs a new Message ExtensionField.
+	     * @exports ProtoBuf.Reflect.Message.ExtensionField
+	     * @param {!ProtoBuf.Builder} builder Builder reference
+	     * @param {!ProtoBuf.Reflect.Message} message Message reference
+	     * @param {string} rule Rule, one of requried, optional, repeated
+	     * @param {string} type Data type, e.g. int32
+	     * @param {string} name Field name
+	     * @param {number} id Unique field id
+	     * @param {!Object.<string,*>=} options Options
+	     * @constructor
+	     * @extends ProtoBuf.Reflect.Message.Field
+	     */
+	    var ExtensionField = function (builder, message, rule, type, name, id, options) {
+	      Field.call(this, builder, message, rule, /* keytype = */ null, type, name, id, options);
+
+	      /**
+	       * Extension reference.
+	       * @type {!ProtoBuf.Reflect.Extension}
+	       * @expose
+	       */
+	      this.extension;
+	    };
+
+	    // Extends Field
+	    ExtensionField.prototype = Object.create(Field.prototype);
+
+	    /**
+	     * @alias ProtoBuf.Reflect.Message.ExtensionField
+	     * @expose
+	     */
+	    Reflect.Message.ExtensionField = ExtensionField;
+
+	    /**
+	     * Constructs a new Message OneOf.
+	     * @exports ProtoBuf.Reflect.Message.OneOf
+	     * @param {!ProtoBuf.Builder} builder Builder reference
+	     * @param {!ProtoBuf.Reflect.Message} message Message reference
+	     * @param {string} name OneOf name
+	     * @constructor
+	     * @extends ProtoBuf.Reflect.T
+	     */
+	    var OneOf = function (builder, message, name) {
+	      T.call(this, builder, message, name);
+
+	      /**
+	       * Enclosed fields.
+	       * @type {!Array.<!ProtoBuf.Reflect.Message.Field>}
+	       * @expose
+	       */
+	      this.fields = [];
+	    };
+
+	    /**
+	     * @alias ProtoBuf.Reflect.Message.OneOf
+	     * @expose
+	     */
+	    Reflect.Message.OneOf = OneOf;
+
+	    /**
+	     * Constructs a new Enum.
+	     * @exports ProtoBuf.Reflect.Enum
+	     * @param {!ProtoBuf.Builder} builder Builder reference
+	     * @param {!ProtoBuf.Reflect.T} parent Parent Reflect object
+	     * @param {string} name Enum name
+	     * @param {Object.<string,*>=} options Enum options
+	     * @param {string?} syntax The syntax level (e.g., proto3)
+	     * @constructor
+	     * @extends ProtoBuf.Reflect.Namespace
+	     */
+	    var Enum = function (builder, parent, name, options, syntax) {
+	      Namespace.call(this, builder, parent, name, options, syntax);
+
+	      /**
+	       * @override
+	       */
+	      this.className = "Enum";
+
+	      /**
+	       * Runtime enum object.
+	       * @type {Object.<string,number>|null}
+	       * @expose
+	       */
+	      this.object = null;
+	    };
+
+	    /**
+	     * Gets the string name of an enum value.
+	     * @param {!ProtoBuf.Builder.Enum} enm Runtime enum
+	     * @param {number} value Enum value
+	     * @returns {?string} Name or `null` if not present
+	     * @expose
+	     */
+	    Enum.getName = function (enm, value) {
+	      var keys = Object.keys(enm);
+	      for (var i = 0, key; i < keys.length; ++i)
+	        if (enm[key = keys[i]] === value)
+	          return key;
+	      return null;
+	    };
+
+	    /**
+	     * @alias ProtoBuf.Reflect.Enum.prototype
+	     * @inner
+	     */
+	    var EnumPrototype = Enum.prototype = Object.create(Namespace.prototype);
+
+	    /**
+	     * Builds this enum and returns the runtime counterpart.
+	     * @param {boolean} rebuild Whether to rebuild or not, defaults to false
+	     * @returns {!Object.<string,number>}
+	     * @expose
+	     */
+	    EnumPrototype.build = function (rebuild) {
+	      if (this.object && !rebuild)
+	        return this.object;
+	      var enm = new ProtoBuf.Builder.Enum(),
+	        values = this.getChildren(Enum.Value);
+	      for (var i = 0, k = values.length; i < k; ++i)
+	        enm[values[i]['name']] = values[i]['id'];
+	      if (Object.defineProperty)
+	        Object.defineProperty(enm, '$options', {
+	          "value": this.buildOpt(),
+	          "enumerable": false
+	        });
+	      return this.object = enm;
+	    };
+
+	    /**
+	     * @alias ProtoBuf.Reflect.Enum
+	     * @expose
+	     */
+	    Reflect.Enum = Enum;
+
+	    /**
+	     * Constructs a new Enum Value.
+	     * @exports ProtoBuf.Reflect.Enum.Value
+	     * @param {!ProtoBuf.Builder} builder Builder reference
+	     * @param {!ProtoBuf.Reflect.Enum} enm Enum reference
+	     * @param {string} name Field name
+	     * @param {number} id Unique field id
+	     * @constructor
+	     * @extends ProtoBuf.Reflect.T
+	     */
+	    var Value = function (builder, enm, name, id) {
+	      T.call(this, builder, enm, name);
+
+	      /**
+	       * @override
+	       */
+	      this.className = "Enum.Value";
+
+	      /**
+	       * Unique enum value id.
+	       * @type {number}
+	       * @expose
+	       */
+	      this.id = id;
+	    };
+
+	    // Extends T
+	    Value.prototype = Object.create(T.prototype);
+
+	    /**
+	     * @alias ProtoBuf.Reflect.Enum.Value
+	     * @expose
+	     */
+	    Reflect.Enum.Value = Value;
+
+	    /**
+	     * An extension (field).
+	     * @exports ProtoBuf.Reflect.Extension
+	     * @constructor
+	     * @param {!ProtoBuf.Builder} builder Builder reference
+	     * @param {!ProtoBuf.Reflect.T} parent Parent object
+	     * @param {string} name Object name
+	     * @param {!ProtoBuf.Reflect.Message.Field} field Extension field
+	     */
+	    var Extension = function (builder, parent, name, field) {
+	      T.call(this, builder, parent, name);
+
+	      /**
+	       * Extended message field.
+	       * @type {!ProtoBuf.Reflect.Message.Field}
+	       * @expose
+	       */
+	      this.field = field;
+	    };
+
+	    // Extends T
+	    Extension.prototype = Object.create(T.prototype);
+
+	    /**
+	     * @alias ProtoBuf.Reflect.Extension
+	     * @expose
+	     */
+	    Reflect.Extension = Extension;
+
+	    /**
+	     * Constructs a new Service.
+	     * @exports ProtoBuf.Reflect.Service
+	     * @param {!ProtoBuf.Builder} builder Builder reference
+	     * @param {!ProtoBuf.Reflect.Namespace} root Root
+	     * @param {string} name Service name
+	     * @param {Object.<string,*>=} options Options
+	     * @constructor
+	     * @extends ProtoBuf.Reflect.Namespace
+	     */
+	    var Service = function (builder, root, name, options) {
+	      Namespace.call(this, builder, root, name, options);
+
+	      /**
+	       * @override
+	       */
+	      this.className = "Service";
+
+	      /**
+	       * Built runtime service class.
+	       * @type {?function(new:ProtoBuf.Builder.Service)}
+	       */
+	      this.clazz = null;
+	    };
+
+	    /**
+	     * @alias ProtoBuf.Reflect.Service.prototype
+	     * @inner
+	     */
+	    var ServicePrototype = Service.prototype = Object.create(Namespace.prototype);
+
+	    /**
+	     * Builds the service and returns the runtime counterpart, which is a fully functional class.
+	     * @see ProtoBuf.Builder.Service
+	     * @param {boolean=} rebuild Whether to rebuild or not
+	     * @return {Function} Service class
+	     * @throws {Error} If the message cannot be built
+	     * @expose
+	     */
+	    ServicePrototype.build = function (rebuild) {
+	      if (this.clazz && !rebuild)
+	        return this.clazz;
+
+	      // Create the runtime Service class in its own scope
+	      return this.clazz = (function (ProtoBuf, T) {
+
+	        /**
+	         * Constructs a new runtime Service.
+	         * @name ProtoBuf.Builder.Service
+	         * @param {function(string, ProtoBuf.Builder.Message, function(Error, ProtoBuf.Builder.Message=))=} rpcImpl RPC implementation receiving the method name and the message
+	         * @class Barebone of all runtime services.
+	         * @constructor
+	         * @throws {Error} If the service cannot be created
+	         */
+	        var Service = function (rpcImpl) {
+	          ProtoBuf.Builder.Service.call(this);
+
+	          /**
+	           * Service implementation.
+	           * @name ProtoBuf.Builder.Service#rpcImpl
+	           * @type {!function(string, ProtoBuf.Builder.Message, function(Error, ProtoBuf.Builder.Message=))}
+	           * @expose
+	           */
+	          this.rpcImpl = rpcImpl || function (name, msg, callback) {
+	            // This is what a user has to implement: A function receiving the method name, the actual message to
+	            // send (type checked) and the callback that's either provided with the error as its first
+	            // argument or null and the actual response message.
+	            setTimeout(callback.bind(this, Error("Not implemented, see: https://github.com/dcodeIO/ProtoBuf.js/wiki/Services")), 0); // Must be async!
+	          };
+	        };
+
+	        /**
+	         * @alias ProtoBuf.Builder.Service.prototype
+	         * @inner
+	         */
+	        var ServicePrototype = Service.prototype = Object.create(ProtoBuf.Builder.Service.prototype);
+
+	        /**
+	         * Asynchronously performs an RPC call using the given RPC implementation.
+	         * @name ProtoBuf.Builder.Service.[Method]
+	         * @function
+	         * @param {!function(string, ProtoBuf.Builder.Message, function(Error, ProtoBuf.Builder.Message=))} rpcImpl RPC implementation
+	         * @param {ProtoBuf.Builder.Message} req Request
+	         * @param {function(Error, (ProtoBuf.Builder.Message|ByteBuffer|Buffer|string)=)} callback Callback receiving
+	         *  the error if any and the response either as a pre-parsed message or as its raw bytes
+	         * @abstract
+	         */
+
+	        /**
+	         * Asynchronously performs an RPC call using the instance's RPC implementation.
+	         * @name ProtoBuf.Builder.Service#[Method]
+	         * @function
+	         * @param {ProtoBuf.Builder.Message} req Request
+	         * @param {function(Error, (ProtoBuf.Builder.Message|ByteBuffer|Buffer|string)=)} callback Callback receiving
+	         *  the error if any and the response either as a pre-parsed message or as its raw bytes
+	         * @abstract
+	         */
+
+	        var rpc = T.getChildren(ProtoBuf.Reflect.Service.RPCMethod);
+	        for (var i = 0; i < rpc.length; i++) {
+	          (function (method) {
+
+	            // service#Method(message, callback)
+	            ServicePrototype[method.name] = function (req, callback) {
+	              try {
+	                try {
+	                  // If given as a buffer, decode the request. Will throw a TypeError if not a valid buffer.
+	                  req = method.resolvedRequestType.clazz.decode(ByteBuffer.wrap(req));
+	                } catch (err) {
+	                  if (!(err instanceof TypeError))
+	                    throw err;
+	                }
+	                if (req === null || typeof req !== 'object')
+	                  throw Error("Illegal arguments");
+	                if (!(req instanceof method.resolvedRequestType.clazz))
+	                  req = new method.resolvedRequestType.clazz(req);
+	                this.rpcImpl(method.fqn(), req, function (err, res) { // Assumes that this is properly async
+	                  if (err) {
+	                    callback(err);
+	                    return;
+	                  }
+	                  // Coalesce to empty string when service response has empty content
+	                  if (res === null)
+	                    res = ''
+	                  try { res = method.resolvedResponseType.clazz.decode(res); } catch (notABuffer) {}
+	                  if (!res || !(res instanceof method.resolvedResponseType.clazz)) {
+	                    callback(Error("Illegal response type received in service method " + T.name + "#" + method.name));
+	                    return;
+	                  }
+	                  callback(null, res);
+	                });
+	              } catch (err) {
+	                setTimeout(callback.bind(this, err), 0);
+	              }
+	            };
+
+	            // Service.Method(rpcImpl, message, callback)
+	            Service[method.name] = function (rpcImpl, req, callback) {
+	              new Service(rpcImpl)[method.name](req, callback);
+	            };
+
+	            if (Object.defineProperty)
+	              Object.defineProperty(Service[method.name], "$options", { "value": method.buildOpt() }),
+	              Object.defineProperty(ServicePrototype[method.name], "$options", { "value": Service[method.name]["$options"] });
+	          })(rpc[i]);
+	        }
+
+	        // Properties
+
+	        /**
+	         * Service options.
+	         * @name ProtoBuf.Builder.Service.$options
+	         * @type {Object.<string,*>}
+	         * @expose
+	         */
+	        var $optionsS; // cc needs this
+
+	        /**
+	         * Service options.
+	         * @name ProtoBuf.Builder.Service#$options
+	         * @type {Object.<string,*>}
+	         * @expose
+	         */
+	        var $options;
+
+	        /**
+	         * Reflection type.
+	         * @name ProtoBuf.Builder.Service.$type
+	         * @type {!ProtoBuf.Reflect.Service}
+	         * @expose
+	         */
+	        var $typeS;
+
+	        /**
+	         * Reflection type.
+	         * @name ProtoBuf.Builder.Service#$type
+	         * @type {!ProtoBuf.Reflect.Service}
+	         * @expose
+	         */
+	        var $type;
+
+	        if (Object.defineProperty)
+	          Object.defineProperty(Service, "$options", { "value": T.buildOpt() }),
+	          Object.defineProperty(ServicePrototype, "$options", { "value": Service["$options"] }),
+	          Object.defineProperty(Service, "$type", { "value": T }),
+	          Object.defineProperty(ServicePrototype, "$type", { "value": T });
+
+	        return Service;
+
+	      })(ProtoBuf, this);
+	    };
+
+	    /**
+	     * @alias ProtoBuf.Reflect.Service
+	     * @expose
+	     */
+	    Reflect.Service = Service;
+
+	    /**
+	     * Abstract service method.
+	     * @exports ProtoBuf.Reflect.Service.Method
+	     * @param {!ProtoBuf.Builder} builder Builder reference
+	     * @param {!ProtoBuf.Reflect.Service} svc Service
+	     * @param {string} name Method name
+	     * @param {Object.<string,*>=} options Options
+	     * @constructor
+	     * @extends ProtoBuf.Reflect.T
+	     */
+	    var Method = function (builder, svc, name, options) {
+	      T.call(this, builder, svc, name);
+
+	      /**
+	       * @override
+	       */
+	      this.className = "Service.Method";
+
+	      /**
+	       * Options.
+	       * @type {Object.<string, *>}
+	       * @expose
+	       */
+	      this.options = options || {};
+	    };
+
+	    /**
+	     * @alias ProtoBuf.Reflect.Service.Method.prototype
+	     * @inner
+	     */
+	    var MethodPrototype = Method.prototype = Object.create(T.prototype);
+
+	    /**
+	     * Builds the method's '$options' property.
+	     * @name ProtoBuf.Reflect.Service.Method#buildOpt
+	     * @function
+	     * @return {Object.<string,*>}
+	     */
+	    MethodPrototype.buildOpt = NamespacePrototype.buildOpt;
+
+	    /**
+	     * @alias ProtoBuf.Reflect.Service.Method
+	     * @expose
+	     */
+	    Reflect.Service.Method = Method;
+
+	    /**
+	     * RPC service method.
+	     * @exports ProtoBuf.Reflect.Service.RPCMethod
+	     * @param {!ProtoBuf.Builder} builder Builder reference
+	     * @param {!ProtoBuf.Reflect.Service} svc Service
+	     * @param {string} name Method name
+	     * @param {string} request Request message name
+	     * @param {string} response Response message name
+	     * @param {boolean} request_stream Whether requests are streamed
+	     * @param {boolean} response_stream Whether responses are streamed
+	     * @param {Object.<string,*>=} options Options
+	     * @constructor
+	     * @extends ProtoBuf.Reflect.Service.Method
+	     */
+	    var RPCMethod = function (builder, svc, name, request, response, request_stream, response_stream, options) {
+	      Method.call(this, builder, svc, name, options);
+
+	      /**
+	       * @override
+	       */
+	      this.className = "Service.RPCMethod";
+
+	      /**
+	       * Request message name.
+	       * @type {string}
+	       * @expose
+	       */
+	      this.requestName = request;
+
+	      /**
+	       * Response message name.
+	       * @type {string}
+	       * @expose
+	       */
+	      this.responseName = response;
+
+	      /**
+	       * Whether requests are streamed
+	       * @type {bool}
+	       * @expose
+	       */
+	      this.requestStream = request_stream;
+
+	      /**
+	       * Whether responses are streamed
+	       * @type {bool}
+	       * @expose
+	       */
+	      this.responseStream = response_stream;
+
+	      /**
+	       * Resolved request message type.
+	       * @type {ProtoBuf.Reflect.Message}
+	       * @expose
+	       */
+	      this.resolvedRequestType = null;
+
+	      /**
+	       * Resolved response message type.
+	       * @type {ProtoBuf.Reflect.Message}
+	       * @expose
+	       */
+	      this.resolvedResponseType = null;
+	    };
+
+	    // Extends Method
+	    RPCMethod.prototype = Object.create(Method.prototype);
+
+	    /**
+	     * @alias ProtoBuf.Reflect.Service.RPCMethod
+	     * @expose
+	     */
+	    Reflect.Service.RPCMethod = RPCMethod;
+
+	    return Reflect;
+
+	  })(ProtoBuf);
+
+	  /**
+	   * @alias ProtoBuf.Builder
+	   * @expose
+	   */
+	  ProtoBuf.Builder = (function (ProtoBuf, Lang, Reflect) {
+	    "use strict";
+
+	    /**
+	     * Constructs a new Builder.
+	     * @exports ProtoBuf.Builder
+	     * @class Provides the functionality to build protocol messages.
+	     * @param {Object.<string,*>=} options Options
+	     * @constructor
+	     */
+	    var Builder = function (options) {
+
+	      /**
+	       * Namespace.
+	       * @type {ProtoBuf.Reflect.Namespace}
+	       * @expose
+	       */
+	      this.ns = new Reflect.Namespace(this, null, ""); // Global namespace
+
+	      /**
+	       * Namespace pointer.
+	       * @type {ProtoBuf.Reflect.T}
+	       * @expose
+	       */
+	      this.ptr = this.ns;
+
+	      /**
+	       * Resolved flag.
+	       * @type {boolean}
+	       * @expose
+	       */
+	      this.resolved = false;
+
+	      /**
+	       * The current building result.
+	       * @type {Object.<string,ProtoBuf.Builder.Message|Object>|null}
+	       * @expose
+	       */
+	      this.result = null;
+
+	      /**
+	       * Imported files.
+	       * @type {Array.<string>}
+	       * @expose
+	       */
+	      this.files = {};
+
+	      /**
+	       * Import root override.
+	       * @type {?string}
+	       * @expose
+	       */
+	      this.importRoot = null;
+
+	      /**
+	       * Options.
+	       * @type {!Object.<string, *>}
+	       * @expose
+	       */
+	      this.options = options || {};
+	    };
+
+	    /**
+	     * @alias ProtoBuf.Builder.prototype
+	     * @inner
+	     */
+	    var BuilderPrototype = Builder.prototype;
+
+	    // ----- Definition tests -----
+
+	    /**
+	     * Tests if a definition most likely describes a message.
+	     * @param {!Object} def
+	     * @returns {boolean}
+	     * @expose
+	     */
+	    Builder.isMessage = function (def) {
+	      // Messages require a string name
+	      if (typeof def["name"] !== 'string')
+	        return false;
+	      // Messages do not contain values (enum) or rpc methods (service)
+	      if (typeof def["values"] !== 'undefined' || typeof def["rpc"] !== 'undefined')
+	        return false;
+	      return true;
+	    };
+
+	    /**
+	     * Tests if a definition most likely describes a message field.
+	     * @param {!Object} def
+	     * @returns {boolean}
+	     * @expose
+	     */
+	    Builder.isMessageField = function (def) {
+	      // Message fields require a string rule, name and type and an id
+	      if (typeof def["rule"] !== 'string' || typeof def["name"] !== 'string' || typeof def["type"] !== 'string' || typeof def["id"] === 'undefined')
+	        return false;
+	      return true;
+	    };
+
+	    /**
+	     * Tests if a definition most likely describes an enum.
+	     * @param {!Object} def
+	     * @returns {boolean}
+	     * @expose
+	     */
+	    Builder.isEnum = function (def) {
+	      // Enums require a string name
+	      if (typeof def["name"] !== 'string')
+	        return false;
+	      // Enums require at least one value
+	      if (typeof def["values"] === 'undefined' || !Array.isArray(def["values"]) || def["values"].length === 0)
+	        return false;
+	      return true;
+	    };
+
+	    /**
+	     * Tests if a definition most likely describes a service.
+	     * @param {!Object} def
+	     * @returns {boolean}
+	     * @expose
+	     */
+	    Builder.isService = function (def) {
+	      // Services require a string name and an rpc object
+	      if (typeof def["name"] !== 'string' || typeof def["rpc"] !== 'object' || !def["rpc"])
+	        return false;
+	      return true;
+	    };
+
+	    /**
+	     * Tests if a definition most likely describes an extended message
+	     * @param {!Object} def
+	     * @returns {boolean}
+	     * @expose
+	     */
+	    Builder.isExtend = function (def) {
+	      // Extends rquire a string ref
+	      if (typeof def["ref"] !== 'string')
+	        return false;
+	      return true;
+	    };
+
+	    // ----- Building -----
+
+	    /**
+	     * Resets the pointer to the root namespace.
+	     * @returns {!ProtoBuf.Builder} this
+	     * @expose
+	     */
+	    BuilderPrototype.reset = function () {
+	      this.ptr = this.ns;
+	      return this;
+	    };
+
+	    /**
+	     * Defines a namespace on top of the current pointer position and places the pointer on it.
+	     * @param {string} namespace
+	     * @return {!ProtoBuf.Builder} this
+	     * @expose
+	     */
+	    BuilderPrototype.define = function (namespace) {
+	      if (typeof namespace !== 'string' || !Lang.TYPEREF.test(namespace))
+	        throw Error("illegal namespace: " + namespace);
+	      namespace.split(".").forEach(function (part) {
+	        var ns = this.ptr.getChild(part);
+	        if (ns === null) // Keep existing
+	          this.ptr.addChild(ns = new Reflect.Namespace(this, this.ptr, part));
+	        this.ptr = ns;
+	      }, this);
+	      return this;
+	    };
+
+	    /**
+	     * Creates the specified definitions at the current pointer position.
+	     * @param {!Array.<!Object>} defs Messages, enums or services to create
+	     * @returns {!ProtoBuf.Builder} this
+	     * @throws {Error} If a message definition is invalid
+	     * @expose
+	     */
+	    BuilderPrototype.create = function (defs) {
+	      if (!defs)
+	        return this; // Nothing to create
+	      if (!Array.isArray(defs))
+	        defs = [defs];
+	      else {
+	        if (defs.length === 0)
+	          return this;
+	        defs = defs.slice();
+	      }
+
+	      // It's quite hard to keep track of scopes and memory here, so let's do this iteratively.
+	      var stack = [defs];
+	      while (stack.length > 0) {
+	        defs = stack.pop();
+
+	        if (!Array.isArray(defs)) // Stack always contains entire namespaces
+	          throw Error("not a valid namespace: " + JSON.stringify(defs));
+
+	        while (defs.length > 0) {
+	          var def = defs.shift(); // Namespaces always contain an array of messages, enums and services
+
+	          if (Builder.isMessage(def)) {
+	            var obj = new Reflect.Message(this, this.ptr, def["name"], def["options"], def["isGroup"], def["syntax"]);
+
+	            // Create OneOfs
+	            var oneofs = {};
+	            if (def["oneofs"])
+	              Object.keys(def["oneofs"]).forEach(function (name) {
+	                obj.addChild(oneofs[name] = new Reflect.Message.OneOf(this, obj, name));
+	              }, this);
+
+	            // Create fields
+	            if (def["fields"])
+	              def["fields"].forEach(function (fld) {
+	                if (obj.getChild(fld["id"] | 0) !== null)
+	                  throw Error("duplicate or invalid field id in " + obj.name + ": " + fld['id']);
+	                if (fld["options"] && typeof fld["options"] !== 'object')
+	                  throw Error("illegal field options in " + obj.name + "#" + fld["name"]);
+	                var oneof = null;
+	                if (typeof fld["oneof"] === 'string' && !(oneof = oneofs[fld["oneof"]]))
+	                  throw Error("illegal oneof in " + obj.name + "#" + fld["name"] + ": " + fld["oneof"]);
+	                fld = new Reflect.Message.Field(this, obj, fld["rule"], fld["keytype"], fld["type"], fld["name"], fld["id"], fld["options"], oneof, def["syntax"]);
+	                if (oneof)
+	                  oneof.fields.push(fld);
+	                obj.addChild(fld);
+	              }, this);
+
+	            // Push children to stack
+	            var subObj = [];
+	            if (def["enums"])
+	              def["enums"].forEach(function (enm) {
+	                subObj.push(enm);
+	              });
+	            if (def["messages"])
+	              def["messages"].forEach(function (msg) {
+	                subObj.push(msg);
+	              });
+	            if (def["services"])
+	              def["services"].forEach(function (svc) {
+	                subObj.push(svc);
+	              });
+
+	            // Set extension ranges
+	            if (def["extensions"]) {
+	              if (typeof def["extensions"][0] === 'number') // pre 5.0.1
+	                obj.extensions = [def["extensions"]];
+	              else
+	                obj.extensions = def["extensions"];
+	            }
+
+	            // Create on top of current namespace
+	            this.ptr.addChild(obj);
+	            if (subObj.length > 0) {
+	              stack.push(defs); // Push the current level back
+	              defs = subObj; // Continue processing sub level
+	              subObj = null;
+	              this.ptr = obj; // And move the pointer to this namespace
+	              obj = null;
+	              continue;
+	            }
+	            subObj = null;
+
+	          } else if (Builder.isEnum(def)) {
+
+	            obj = new Reflect.Enum(this, this.ptr, def["name"], def["options"], def["syntax"]);
+	            def["values"].forEach(function (val) {
+	              obj.addChild(new Reflect.Enum.Value(this, obj, val["name"], val["id"]));
+	            }, this);
+	            this.ptr.addChild(obj);
+
+	          } else if (Builder.isService(def)) {
+
+	            obj = new Reflect.Service(this, this.ptr, def["name"], def["options"]);
+	            Object.keys(def["rpc"]).forEach(function (name) {
+	              var mtd = def["rpc"][name];
+	              obj.addChild(new Reflect.Service.RPCMethod(this, obj, name, mtd["request"], mtd["response"], !!mtd["request_stream"], !!mtd["response_stream"], mtd["options"]));
+	            }, this);
+	            this.ptr.addChild(obj);
+
+	          } else if (Builder.isExtend(def)) {
+
+	            obj = this.ptr.resolve(def["ref"], true);
+	            if (obj) {
+	              def["fields"].forEach(function (fld) {
+	                if (obj.getChild(fld['id'] | 0) !== null)
+	                  throw Error("duplicate extended field id in " + obj.name + ": " + fld['id']);
+	                // Check if field id is allowed to be extended
+	                if (obj.extensions) {
+	                  var valid = false;
+	                  obj.extensions.forEach(function (range) {
+	                    if (fld["id"] >= range[0] && fld["id"] <= range[1])
+	                      valid = true;
+	                  });
+	                  if (!valid)
+	                    throw Error("illegal extended field id in " + obj.name + ": " + fld['id'] + " (not within valid ranges)");
+	                }
+	                // Convert extension field names to camel case notation if the override is set
+	                var name = fld["name"];
+	                if (this.options['convertFieldsToCamelCase'])
+	                  name = ProtoBuf.Util.toCamelCase(name);
+	                // see #161: Extensions use their fully qualified name as their runtime key and...
+	                var field = new Reflect.Message.ExtensionField(this, obj, fld["rule"], fld["type"], this.ptr.fqn() + '.' + name, fld["id"], fld["options"]);
+	                // ...are added on top of the current namespace as an extension which is used for
+	                // resolving their type later on (the extension always keeps the original name to
+	                // prevent naming collisions)
+	                var ext = new Reflect.Extension(this, this.ptr, fld["name"], field);
+	                field.extension = ext;
+	                this.ptr.addChild(ext);
+	                obj.addChild(field);
+	              }, this);
+
+	            } else if (!/\.?google\.protobuf\./.test(def["ref"])) // Silently skip internal extensions
+	              throw Error("extended message " + def["ref"] + " is not defined");
+
+	          } else
+	            throw Error("not a valid definition: " + JSON.stringify(def));
+
+	          def = null;
+	          obj = null;
+	        }
+	        // Break goes here
+	        defs = null;
+	        this.ptr = this.ptr.parent; // Namespace done, continue at parent
+	      }
+	      this.resolved = false; // Require re-resolve
+	      this.result = null; // Require re-build
+	      return this;
+	    };
+
+	    /**
+	     * Propagates syntax to all children.
+	     * @param {!Object} parent
+	     * @inner
+	     */
+	    function propagateSyntax(parent) {
+	      if (parent['messages']) {
+	        parent['messages'].forEach(function (child) {
+	          child["syntax"] = parent["syntax"];
+	          propagateSyntax(child);
+	        });
+	      }
+	      if (parent['enums']) {
+	        parent['enums'].forEach(function (child) {
+	          child["syntax"] = parent["syntax"];
+	        });
+	      }
+	    }
+
+	    /**
+	     * Imports another definition into this builder.
+	     * @param {Object.<string,*>} json Parsed import
+	     * @param {(string|{root: string, file: string})=} filename Imported file name
+	     * @returns {!ProtoBuf.Builder} this
+	     * @throws {Error} If the definition or file cannot be imported
+	     * @expose
+	     */
+	    BuilderPrototype["import"] = function (json, filename) {
+	      var delim = '/';
+
+	      // Make sure to skip duplicate imports
+
+	      if (typeof filename === 'string') {
+
+	        if (ProtoBuf.Util.IS_NODE)
+	          filename = __webpack_require__(32)['resolve'](filename);
+	        if (this.files[filename] === true)
+	          return this.reset();
+	        this.files[filename] = true;
+
+	      } else if (typeof filename === 'object') { // Object with root, file.
+
+	        var root = filename.root;
+	        if (ProtoBuf.Util.IS_NODE)
+	          root = __webpack_require__(32)['resolve'](root);
+	        if (root.indexOf("\\") >= 0 || filename.file.indexOf("\\") >= 0)
+	          delim = '\\';
+	        var fname = root + delim + filename.file;
+	        if (this.files[fname] === true)
+	          return this.reset();
+	        this.files[fname] = true;
+	      }
+
+	      // Import imports
+
+	      if (json['imports'] && json['imports'].length > 0) {
+	        var importRoot,
+	          resetRoot = false;
+
+	        if (typeof filename === 'object') { // If an import root is specified, override
+
+	          this.importRoot = filename["root"];
+	          resetRoot = true; // ... and reset afterwards
+	          importRoot = this.importRoot;
+	          filename = filename["file"];
+	          if (importRoot.indexOf("\\") >= 0 || filename.indexOf("\\") >= 0)
+	            delim = '\\';
+
+	        } else if (typeof filename === 'string') {
+
+	          if (this.importRoot) // If import root is overridden, use it
+	            importRoot = this.importRoot;
+	          else { // Otherwise compute from filename
+	            if (filename.indexOf("/") >= 0) { // Unix
+	              importRoot = filename.replace(/\/[^\/]*$/, "");
+	              if ( /* /file.proto */ importRoot === "")
+	                importRoot = "/";
+	            } else if (filename.indexOf("\\") >= 0) { // Windows
+	              importRoot = filename.replace(/\\[^\\]*$/, "");
+	              delim = '\\';
+	            } else
+	              importRoot = ".";
+	          }
+
+	        } else
+	          importRoot = null;
+
+	        for (var i = 0; i < json['imports'].length; i++) {
+	          if (typeof json['imports'][i] === 'string') { // Import file
+	            if (!importRoot)
+	              throw Error("cannot determine import root");
+	            var importFilename = json['imports'][i];
+	            if (importFilename === "google/protobuf/descriptor.proto")
+	              continue; // Not needed and therefore not used
+	            importFilename = importRoot + delim + importFilename;
+	            if (this.files[importFilename] === true)
+	              continue; // Already imported
+	            if (/\.proto$/i.test(importFilename) && !ProtoBuf.DotProto) // If this is a light build
+	              importFilename = importFilename.replace(/\.proto$/, ".json"); // always load the JSON file
+	            var contents = ProtoBuf.Util.fetch(importFilename);
+	            if (contents === null)
+	              throw Error("failed to import '" + importFilename + "' in '" + filename + "': file not found");
+	            if (/\.json$/i.test(importFilename)) // Always possible
+	              this["import"](JSON.parse(contents + ""), importFilename); // May throw
+	            else
+	              this["import"](ProtoBuf.DotProto.Parser.parse(contents), importFilename); // May throw
+	          } else // Import structure
+	          if (!filename)
+	            this["import"](json['imports'][i]);
+	          else if (/\.(\w+)$/.test(filename)) // With extension: Append _importN to the name portion to make it unique
+	            this["import"](json['imports'][i], filename.replace(/^(.+)\.(\w+)$/, function ($0, $1, $2) {
+	            return $1 + "_import" + i + "." + $2;
+	          }));
+	          else // Without extension: Append _importN to make it unique
+	            this["import"](json['imports'][i], filename + "_import" + i);
+	        }
+	        if (resetRoot) // Reset import root override when all imports are done
+	          this.importRoot = null;
+	      }
+
+	      // Import structures
+
+	      if (json['package'])
+	        this.define(json['package']);
+	      if (json['syntax'])
+	        propagateSyntax(json);
+	      var base = this.ptr;
+	      if (json['options'])
+	        Object.keys(json['options']).forEach(function (key) {
+	          base.options[key] = json['options'][key];
+	        });
+	      if (json['messages'])
+	        this.create(json['messages']),
+	        this.ptr = base;
+	      if (json['enums'])
+	        this.create(json['enums']),
+	        this.ptr = base;
+	      if (json['services'])
+	        this.create(json['services']),
+	        this.ptr = base;
+	      if (json['extends'])
+	        this.create(json['extends']);
+
+	      return this.reset();
+	    };
+
+	    /**
+	     * Resolves all namespace objects.
+	     * @throws {Error} If a type cannot be resolved
+	     * @returns {!ProtoBuf.Builder} this
+	     * @expose
+	     */
+	    BuilderPrototype.resolveAll = function () {
+	      // Resolve all reflected objects
+	      var res;
+	      if (this.ptr == null || typeof this.ptr.type === 'object')
+	        return this; // Done (already resolved)
+
+	      if (this.ptr instanceof Reflect.Namespace) { // Resolve children
+
+	        this.ptr.children.forEach(function (child) {
+	          this.ptr = child;
+	          this.resolveAll();
+	        }, this);
+
+	      } else if (this.ptr instanceof Reflect.Message.Field) { // Resolve type
+
+	        if (!Lang.TYPE.test(this.ptr.type)) {
+	          if (!Lang.TYPEREF.test(this.ptr.type))
+	            throw Error("illegal type reference in " + this.ptr.toString(true) + ": " + this.ptr.type);
+	          res = (this.ptr instanceof Reflect.Message.ExtensionField ? this.ptr.extension.parent : this.ptr.parent).resolve(this.ptr.type, true);
+	          if (!res)
+	            throw Error("unresolvable type reference in " + this.ptr.toString(true) + ": " + this.ptr.type);
+	          this.ptr.resolvedType = res;
+	          if (res instanceof Reflect.Enum) {
+	            this.ptr.type = ProtoBuf.TYPES["enum"];
+	            if (this.ptr.syntax === 'proto3' && res.syntax !== 'proto3')
+	              throw Error("proto3 message cannot reference proto2 enum");
+	          } else if (res instanceof Reflect.Message)
+	            this.ptr.type = res.isGroup ? ProtoBuf.TYPES["group"] : ProtoBuf.TYPES["message"];
+	          else
+	            throw Error("illegal type reference in " + this.ptr.toString(true) + ": " + this.ptr.type);
+	        } else
+	          this.ptr.type = ProtoBuf.TYPES[this.ptr.type];
+
+	        // If it's a map field, also resolve the key type. The key type can be only a numeric, string, or bool type
+	        // (i.e., no enums or messages), so we don't need to resolve against the current namespace.
+	        if (this.ptr.map) {
+	          if (!Lang.TYPE.test(this.ptr.keyType))
+	            throw Error("illegal key type for map field in " + this.ptr.toString(true) + ": " + this.ptr.keyType);
+	          this.ptr.keyType = ProtoBuf.TYPES[this.ptr.keyType];
+	        }
+
+	      } else if (this.ptr instanceof ProtoBuf.Reflect.Service.Method) {
+
+	        if (this.ptr instanceof ProtoBuf.Reflect.Service.RPCMethod) {
+	          res = this.ptr.parent.resolve(this.ptr.requestName, true);
+	          if (!res || !(res instanceof ProtoBuf.Reflect.Message))
+	            throw Error("Illegal type reference in " + this.ptr.toString(true) + ": " + this.ptr.requestName);
+	          this.ptr.resolvedRequestType = res;
+	          res = this.ptr.parent.resolve(this.ptr.responseName, true);
+	          if (!res || !(res instanceof ProtoBuf.Reflect.Message))
+	            throw Error("Illegal type reference in " + this.ptr.toString(true) + ": " + this.ptr.responseName);
+	          this.ptr.resolvedResponseType = res;
+	        } else // Should not happen as nothing else is implemented
+	          throw Error("illegal service type in " + this.ptr.toString(true));
+
+	      } else if (!(this.ptr instanceof ProtoBuf.Reflect.Message.OneOf) && // Not built
+	        !(this.ptr instanceof ProtoBuf.Reflect.Extension) && // Not built
+	        !(this.ptr instanceof ProtoBuf.Reflect.Enum.Value) // Built in enum
+	      )
+	        throw Error("illegal object in namespace: " + typeof (this.ptr) + ": " + this.ptr);
+
+	      return this.reset();
+	    };
+
+	    /**
+	     * Builds the protocol. This will first try to resolve all definitions and, if this has been successful,
+	     * return the built package.
+	     * @param {(string|Array.<string>)=} path Specifies what to return. If omitted, the entire namespace will be returned.
+	     * @returns {!ProtoBuf.Builder.Message|!Object.<string,*>}
+	     * @throws {Error} If a type could not be resolved
+	     * @expose
+	     */
+	    BuilderPrototype.build = function (path) {
+	      this.reset();
+	      if (!this.resolved)
+	        this.resolveAll(),
+	        this.resolved = true,
+	        this.result = null; // Require re-build
+	      if (this.result === null) // (Re-)Build
+	        this.result = this.ns.build();
+	      if (!path)
+	        return this.result;
+	      var part = typeof path === 'string' ? path.split(".") : path,
+	        ptr = this.result; // Build namespace pointer (no hasChild etc.)
+	      for (var i = 0; i < part.length; i++)
+	        if (ptr[part[i]])
+	          ptr = ptr[part[i]];
+	        else {
+	          ptr = null;
+	          break;
+	        }
+	      return ptr;
+	    };
+
+	    /**
+	     * Similar to {@link ProtoBuf.Builder#build}, but looks up the internal reflection descriptor.
+	     * @param {string=} path Specifies what to return. If omitted, the entire namespace wiil be returned.
+	     * @param {boolean=} excludeNonNamespace Excludes non-namespace types like fields, defaults to `false`
+	     * @returns {?ProtoBuf.Reflect.T} Reflection descriptor or `null` if not found
+	     */
+	    BuilderPrototype.lookup = function (path, excludeNonNamespace) {
+	      return path ? this.ns.resolve(path, excludeNonNamespace) : this.ns;
+	    };
+
+	    /**
+	     * Returns a string representation of this object.
+	     * @return {string} String representation as of "Builder"
+	     * @expose
+	     */
+	    BuilderPrototype.toString = function () {
+	      return "Builder";
+	    };
+
+	    // ----- Base classes -----
+	    // Exist for the sole purpose of being able to "... instanceof ProtoBuf.Builder.Message" etc.
+
+	    /**
+	     * @alias ProtoBuf.Builder.Message
+	     */
+	    Builder.Message = function () {};
+
+	    /**
+	     * @alias ProtoBuf.Builder.Enum
+	     */
+	    Builder.Enum = function () {};
+
+	    /**
+	     * @alias ProtoBuf.Builder.Message
+	     */
+	    Builder.Service = function () {};
+
+	    return Builder;
+
+	  })(ProtoBuf, ProtoBuf.Lang, ProtoBuf.Reflect);
+
+	  /**
+	   * @alias ProtoBuf.Map
+	   * @expose
+	   */
+	  ProtoBuf.Map = (function (ProtoBuf, Reflect) {
+	    "use strict";
+
+	    /**
+	     * Constructs a new Map. A Map is a container that is used to implement map
+	     * fields on message objects. It closely follows the ES6 Map API; however,
+	     * it is distinct because we do not want to depend on external polyfills or
+	     * on ES6 itself.
+	     *
+	     * @exports ProtoBuf.Map
+	     * @param {!ProtoBuf.Reflect.Field} field Map field
+	     * @param {Object.<string,*>=} contents Initial contents
+	     * @constructor
+	     */
+	    var Map = function (field, contents) {
+	      if (!field.map)
+	        throw Error("field is not a map");
+
+	      /**
+	       * The field corresponding to this map.
+	       * @type {!ProtoBuf.Reflect.Field}
+	       */
+	      this.field = field;
+
+	      /**
+	       * Element instance corresponding to key type.
+	       * @type {!ProtoBuf.Reflect.Element}
+	       */
+	      this.keyElem = new Reflect.Element(field.keyType, null, true, field.syntax);
+
+	      /**
+	       * Element instance corresponding to value type.
+	       * @type {!ProtoBuf.Reflect.Element}
+	       */
+	      this.valueElem = new Reflect.Element(field.type, field.resolvedType, false, field.syntax);
+
+	      /**
+	       * Internal map: stores mapping of (string form of key) -> (key, value)
+	       * pair.
+	       *
+	       * We provide map semantics for arbitrary key types, but we build on top
+	       * of an Object, which has only string keys. In order to avoid the need
+	       * to convert a string key back to its native type in many situations,
+	       * we store the native key value alongside the value. Thus, we only need
+	       * a one-way mapping from a key type to its string form that guarantees
+	       * uniqueness and equality (i.e., str(K1) === str(K2) if and only if K1
+	       * === K2).
+	       *
+	       * @type {!Object<string, {key: *, value: *}>}
+	       */
+	      this.map = {};
+
+	      /**
+	       * Returns the number of elements in the map.
+	       */
+	      Object.defineProperty(this, "size", {
+	        get: function () {
+	          return Object.keys(this.map).length;
+	        }
+	      });
+
+	      // Fill initial contents from a raw object.
+	      if (contents) {
+	        var keys = Object.keys(contents);
+	        for (var i = 0; i < keys.length; i++) {
+	          var key = this.keyElem.valueFromString(keys[i]);
+	          var val = this.valueElem.verifyValue(contents[keys[i]]);
+	          this.map[this.keyElem.valueToString(key)] = { key: key, value: val };
+	        }
+	      }
+	    };
+
+	    var MapPrototype = Map.prototype;
+
+	    /**
+	     * Helper: return an iterator over an array.
+	     * @param {!Array<*>} arr the array
+	     * @returns {!Object} an iterator
+	     * @inner
+	     */
+	    function arrayIterator(arr) {
+	      var idx = 0;
+	      return {
+	        next: function () {
+	          if (idx < arr.length)
+	            return { done: false, value: arr[idx++] };
+	          return { done: true };
+	        }
+	      }
+	    }
+
+	    /**
+	     * Clears the map.
+	     */
+	    MapPrototype.clear = function () {
+	      this.map = {};
+	    };
+
+	    /**
+	     * Deletes a particular key from the map.
+	     * @returns {boolean} Whether any entry with this key was deleted.
+	     */
+	    MapPrototype["delete"] = function (key) {
+	      var keyValue = this.keyElem.valueToString(this.keyElem.verifyValue(key));
+	      var hadKey = keyValue in this.map;
+	      delete this.map[keyValue];
+	      return hadKey;
+	    };
+
+	    /**
+	     * Returns an iterator over [key, value] pairs in the map.
+	     * @returns {Object} The iterator
+	     */
+	    MapPrototype.entries = function () {
+	      var entries = [];
+	      var strKeys = Object.keys(this.map);
+	      for (var i = 0, entry; i < strKeys.length; i++)
+	        entries.push([(entry = this.map[strKeys[i]]).key, entry.value]);
+	      return arrayIterator(entries);
+	    };
+
+	    /**
+	     * Returns an iterator over keys in the map.
+	     * @returns {Object} The iterator
+	     */
+	    MapPrototype.keys = function () {
+	      var keys = [];
+	      var strKeys = Object.keys(this.map);
+	      for (var i = 0; i < strKeys.length; i++)
+	        keys.push(this.map[strKeys[i]].key);
+	      return arrayIterator(keys);
+	    };
+
+	    /**
+	     * Returns an iterator over values in the map.
+	     * @returns {!Object} The iterator
+	     */
+	    MapPrototype.values = function () {
+	      var values = [];
+	      var strKeys = Object.keys(this.map);
+	      for (var i = 0; i < strKeys.length; i++)
+	        values.push(this.map[strKeys[i]].value);
+	      return arrayIterator(values);
+	    };
+
+	    /**
+	     * Iterates over entries in the map, calling a function on each.
+	     * @param {function(this:*, *, *, *)} cb The callback to invoke with value, key, and map arguments.
+	     * @param {Object=} thisArg The `this` value for the callback
+	     */
+	    MapPrototype.forEach = function (cb, thisArg) {
+	      var strKeys = Object.keys(this.map);
+	      for (var i = 0, entry; i < strKeys.length; i++)
+	        cb.call(thisArg, (entry = this.map[strKeys[i]]).value, entry.key, this);
+	    };
+
+	    /**
+	     * Sets a key in the map to the given value.
+	     * @param {*} key The key
+	     * @param {*} value The value
+	     * @returns {!ProtoBuf.Map} The map instance
+	     */
+	    MapPrototype.set = function (key, value) {
+	      var keyValue = this.keyElem.verifyValue(key);
+	      var valValue = this.valueElem.verifyValue(value);
+	      this.map[this.keyElem.valueToString(keyValue)] = { key: keyValue, value: valValue };
+	      return this;
+	    };
+
+	    /**
+	     * Gets the value corresponding to a key in the map.
+	     * @param {*} key The key
+	     * @returns {*|undefined} The value, or `undefined` if key not present
+	     */
+	    MapPrototype.get = function (key) {
+	      var keyValue = this.keyElem.valueToString(this.keyElem.verifyValue(key));
+	      if (!(keyValue in this.map))
+	        return undefined;
+	      return this.map[keyValue].value;
+	    };
+
+	    /**
+	     * Determines whether the given key is present in the map.
+	     * @param {*} key The key
+	     * @returns {boolean} `true` if the key is present
+	     */
+	    MapPrototype.has = function (key) {
+	      var keyValue = this.keyElem.valueToString(this.keyElem.verifyValue(key));
+	      return (keyValue in this.map);
+	    };
+
+	    return Map;
+	  })(ProtoBuf, ProtoBuf.Reflect);
+
+	  /**
+	   * Loads a .proto string and returns the Builder.
+	   * @param {string} proto .proto file contents
+	   * @param {(ProtoBuf.Builder|string|{root: string, file: string})=} builder Builder to append to. Will create a new one if omitted.
+	   * @param {(string|{root: string, file: string})=} filename The corresponding file name if known. Must be specified for imports.
+	   * @return {ProtoBuf.Builder} Builder to create new messages
+	   * @throws {Error} If the definition cannot be parsed or built
+	   * @expose
+	   */
+	  ProtoBuf.loadProto = function (proto, builder, filename) {
+	    if (typeof builder === 'string' || (builder && typeof builder["file"] === 'string' && typeof builder["root"] === 'string'))
+	      filename = builder,
+	      builder = undefined;
+	    return ProtoBuf.loadJson(ProtoBuf.DotProto.Parser.parse(proto), builder, filename);
+	  };
+
+	  /**
+	   * Loads a .proto string and returns the Builder. This is an alias of {@link ProtoBuf.loadProto}.
+	   * @function
+	   * @param {string} proto .proto file contents
+	   * @param {(ProtoBuf.Builder|string)=} builder Builder to append to. Will create a new one if omitted.
+	   * @param {(string|{root: string, file: string})=} filename The corresponding file name if known. Must be specified for imports.
+	   * @return {ProtoBuf.Builder} Builder to create new messages
+	   * @throws {Error} If the definition cannot be parsed or built
+	   * @expose
+	   */
+	  ProtoBuf.protoFromString = ProtoBuf.loadProto; // Legacy
+
+	  /**
+	   * Loads a .proto file and returns the Builder.
+	   * @param {string|{root: string, file: string}} filename Path to proto file or an object specifying 'file' with
+	   *  an overridden 'root' path for all imported files.
+	   * @param {function(?Error, !ProtoBuf.Builder=)=} callback Callback that will receive `null` as the first and
+	   *  the Builder as its second argument on success, otherwise the error as its first argument. If omitted, the
+	   *  file will be read synchronously and this function will return the Builder.
+	   * @param {ProtoBuf.Builder=} builder Builder to append to. Will create a new one if omitted.
+	   * @return {?ProtoBuf.Builder|undefined} The Builder if synchronous (no callback specified, will be NULL if the
+	   *   request has failed), else undefined
+	   * @expose
+	   */
+	  ProtoBuf.loadProtoFile = function (filename, callback, builder) {
+	    if (callback && typeof callback === 'object')
+	      builder = callback,
+	      callback = null;
+	    else if (!callback || typeof callback !== 'function')
+	      callback = null;
+	    if (callback)
+	      return ProtoBuf.Util.fetch(typeof filename === 'string' ? filename : filename["root"] + "/" + filename["file"], function (contents) {
+	        if (contents === null) {
+	          callback(Error("Failed to fetch file"));
+	          return;
+	        }
+	        try {
+	          callback(null, ProtoBuf.loadProto(contents, builder, filename));
+	        } catch (e) {
+	          callback(e);
+	        }
+	      });
+	    var contents = ProtoBuf.Util.fetch(typeof filename === 'object' ? filename["root"] + "/" + filename["file"] : filename);
+	    return contents === null ? null : ProtoBuf.loadProto(contents, builder, filename);
+	  };
+
+	  /**
+	   * Loads a .proto file and returns the Builder. This is an alias of {@link ProtoBuf.loadProtoFile}.
+	   * @function
+	   * @param {string|{root: string, file: string}} filename Path to proto file or an object specifying 'file' with
+	   *  an overridden 'root' path for all imported files.
+	   * @param {function(?Error, !ProtoBuf.Builder=)=} callback Callback that will receive `null` as the first and
+	   *  the Builder as its second argument on success, otherwise the error as its first argument. If omitted, the
+	   *  file will be read synchronously and this function will return the Builder.
+	   * @param {ProtoBuf.Builder=} builder Builder to append to. Will create a new one if omitted.
+	   * @return {!ProtoBuf.Builder|undefined} The Builder if synchronous (no callback specified, will be NULL if the
+	   *   request has failed), else undefined
+	   * @expose
+	   */
+	  ProtoBuf.protoFromFile = ProtoBuf.loadProtoFile; // Legacy
+
+	  /**
+	   * Constructs a new empty Builder.
+	   * @param {Object.<string,*>=} options Builder options, defaults to global options set on ProtoBuf
+	   * @return {!ProtoBuf.Builder} Builder
+	   * @expose
+	   */
+	  ProtoBuf.newBuilder = function (options) {
+	    options = options || {};
+	    if (typeof options['convertFieldsToCamelCase'] === 'undefined')
+	      options['convertFieldsToCamelCase'] = ProtoBuf.convertFieldsToCamelCase;
+	    if (typeof options['populateAccessors'] === 'undefined')
+	      options['populateAccessors'] = ProtoBuf.populateAccessors;
+	    return new ProtoBuf.Builder(options);
+	  };
+
+	  /**
+	   * Loads a .json definition and returns the Builder.
+	   * @param {!*|string} json JSON definition
+	   * @param {(ProtoBuf.Builder|string|{root: string, file: string})=} builder Builder to append to. Will create a new one if omitted.
+	   * @param {(string|{root: string, file: string})=} filename The corresponding file name if known. Must be specified for imports.
+	   * @return {ProtoBuf.Builder} Builder to create new messages
+	   * @throws {Error} If the definition cannot be parsed or built
+	   * @expose
+	   */
+	  ProtoBuf.loadJson = function (json, builder, filename) {
+	    if (typeof builder === 'string' || (builder && typeof builder["file"] === 'string' && typeof builder["root"] === 'string'))
+	      filename = builder,
+	      builder = null;
+	    if (!builder || typeof builder !== 'object')
+	      builder = ProtoBuf.newBuilder();
+	    if (typeof json === 'string')
+	      json = JSON.parse(json);
+	    builder["import"](json, filename);
+	    builder.resolveAll();
+	    return builder;
+	  };
+
+	  /**
+	   * Loads a .json file and returns the Builder.
+	   * @param {string|!{root: string, file: string}} filename Path to json file or an object specifying 'file' with
+	   *  an overridden 'root' path for all imported files.
+	   * @param {function(?Error, !ProtoBuf.Builder=)=} callback Callback that will receive `null` as the first and
+	   *  the Builder as its second argument on success, otherwise the error as its first argument. If omitted, the
+	   *  file will be read synchronously and this function will return the Builder.
+	   * @param {ProtoBuf.Builder=} builder Builder to append to. Will create a new one if omitted.
+	   * @return {?ProtoBuf.Builder|undefined} The Builder if synchronous (no callback specified, will be NULL if the
+	   *   request has failed), else undefined
+	   * @expose
+	   */
+	  ProtoBuf.loadJsonFile = function (filename, callback, builder) {
+	    if (callback && typeof callback === 'object')
+	      builder = callback,
+	      callback = null;
+	    else if (!callback || typeof callback !== 'function')
+	      callback = null;
+	    if (callback)
+	      return ProtoBuf.Util.fetch(typeof filename === 'string' ? filename : filename["root"] + "/" + filename["file"], function (contents) {
+	        if (contents === null) {
+	          callback(Error("Failed to fetch file"));
+	          return;
+	        }
+	        try {
+	          callback(null, ProtoBuf.loadJson(JSON.parse(contents), builder, filename));
+	        } catch (e) {
+	          callback(e);
+	        }
+	      });
+	    var contents = ProtoBuf.Util.fetch(typeof filename === 'object' ? filename["root"] + "/" + filename["file"] : filename);
+	    return contents === null ? null : ProtoBuf.loadJson(JSON.parse(contents), builder, filename);
+	  };
+
+	  return ProtoBuf;
+	});
+
+
+	/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(27), __webpack_require__(12)(module)))
+
+/***/ },
+/* 27 */
+/***/ function(module, exports) {
+
+	// shim for using process in browser
+	var process = module.exports = {};
+
+	// cached from whatever global is present so that test runners that stub it
+	// don't break things.  But we need to wrap it in a try catch in case it is
+	// wrapped in strict mode code which doesn't define any globals.  It's inside a
+	// function because try/catches deoptimize in certain engines.
+
+	var cachedSetTimeout;
+	var cachedClearTimeout;
+
+	function defaultSetTimout() {
+	    throw new Error('setTimeout has not been defined');
+	}
+	function defaultClearTimeout () {
+	    throw new Error('clearTimeout has not been defined');
+	}
+	(function () {
+	    try {
+	        if (typeof setTimeout === 'function') {
+	            cachedSetTimeout = setTimeout;
+	        } else {
+	            cachedSetTimeout = defaultSetTimout;
+	        }
+	    } catch (e) {
+	        cachedSetTimeout = defaultSetTimout;
+	    }
+	    try {
+	        if (typeof clearTimeout === 'function') {
+	            cachedClearTimeout = clearTimeout;
+	        } else {
+	            cachedClearTimeout = defaultClearTimeout;
+	        }
+	    } catch (e) {
+	        cachedClearTimeout = defaultClearTimeout;
+	    }
+	} ())
+	function runTimeout(fun) {
+	    if (cachedSetTimeout === setTimeout) {
+	        //normal enviroments in sane situations
+	        return setTimeout(fun, 0);
+	    }
+	    // if setTimeout wasn't available but was latter defined
+	    if ((cachedSetTimeout === defaultSetTimout || !cachedSetTimeout) && setTimeout) {
+	        cachedSetTimeout = setTimeout;
+	        return setTimeout(fun, 0);
+	    }
+	    try {
+	        // when when somebody has screwed with setTimeout but no I.E. maddness
+	        return cachedSetTimeout(fun, 0);
+	    } catch(e){
+	        try {
+	            // When we are in I.E. but the script has been evaled so I.E. doesn't trust the global object when called normally
+	            return cachedSetTimeout.call(null, fun, 0);
+	        } catch(e){
+	            // same as above but when it's a version of I.E. that must have the global object for 'this', hopfully our context correct otherwise it will throw a global error
+	            return cachedSetTimeout.call(this, fun, 0);
+	        }
+	    }
+
+
+	}
+	function runClearTimeout(marker) {
+	    if (cachedClearTimeout === clearTimeout) {
+	        //normal enviroments in sane situations
+	        return clearTimeout(marker);
+	    }
+	    // if clearTimeout wasn't available but was latter defined
+	    if ((cachedClearTimeout === defaultClearTimeout || !cachedClearTimeout) && clearTimeout) {
+	        cachedClearTimeout = clearTimeout;
+	        return clearTimeout(marker);
+	    }
+	    try {
+	        // when when somebody has screwed with setTimeout but no I.E. maddness
+	        return cachedClearTimeout(marker);
+	    } catch (e){
+	        try {
+	            // When we are in I.E. but the script has been evaled so I.E. doesn't  trust the global object when called normally
+	            return cachedClearTimeout.call(null, marker);
+	        } catch (e){
+	            // same as above but when it's a version of I.E. that must have the global object for 'this', hopfully our context correct otherwise it will throw a global error.
+	            // Some versions of I.E. have different rules for clearTimeout vs setTimeout
+	            return cachedClearTimeout.call(this, marker);
+	        }
+	    }
+
+
+
+	}
+	var queue = [];
+	var draining = false;
+	var currentQueue;
+	var queueIndex = -1;
+
+	function cleanUpNextTick() {
+	    if (!draining || !currentQueue) {
+	        return;
+	    }
+	    draining = false;
+	    if (currentQueue.length) {
+	        queue = currentQueue.concat(queue);
+	    } else {
+	        queueIndex = -1;
+	    }
+	    if (queue.length) {
+	        drainQueue();
+	    }
+	}
+
+	function drainQueue() {
+	    if (draining) {
+	        return;
+	    }
+	    var timeout = runTimeout(cleanUpNextTick);
+	    draining = true;
+
+	    var len = queue.length;
+	    while(len) {
+	        currentQueue = queue;
+	        queue = [];
+	        while (++queueIndex < len) {
+	            if (currentQueue) {
+	                currentQueue[queueIndex].run();
+	            }
+	        }
+	        queueIndex = -1;
+	        len = queue.length;
+	    }
+	    currentQueue = null;
+	    draining = false;
+	    runClearTimeout(timeout);
+	}
+
+	process.nextTick = function (fun) {
+	    var args = new Array(arguments.length - 1);
+	    if (arguments.length > 1) {
+	        for (var i = 1; i < arguments.length; i++) {
+	            args[i - 1] = arguments[i];
+	        }
+	    }
+	    queue.push(new Item(fun, args));
+	    if (queue.length === 1 && !draining) {
+	        runTimeout(drainQueue);
+	    }
+	};
+
+	// v8 likes predictible objects
+	function Item(fun, array) {
+	    this.fun = fun;
+	    this.array = array;
+	}
+	Item.prototype.run = function () {
+	    this.fun.apply(null, this.array);
+	};
+	process.title = 'browser';
+	process.browser = true;
+	process.env = {};
+	process.argv = [];
+	process.version = ''; // empty string to avoid regexp issues
+	process.versions = {};
+
+	function noop() {}
+
+	process.on = noop;
+	process.addListener = noop;
+	process.once = noop;
+	process.off = noop;
+	process.removeListener = noop;
+	process.removeAllListeners = noop;
+	process.emit = noop;
+
+	process.binding = function (name) {
+	    throw new Error('process.binding is not supported');
+	};
+
+	process.cwd = function () { return '/' };
+	process.chdir = function (dir) {
+	    throw new Error('process.chdir is not supported');
+	};
+	process.umask = function() { return 0; };
+
+
+/***/ },
+/* 28 */
+/***/ function(module, exports) {
+
+	module.exports = function() { throw new Error("define cannot be used indirect"); };
+
+
+/***/ },
+/* 29 */
+/***/ function(module, exports, __webpack_require__) {
+
+	var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/* WEBPACK VAR INJECTION */(function(module) {/*
+	 Copyright 2013-2014 Daniel Wirtz <dcode@dcode.io>
+
+	 Licensed under the Apache License, Version 2.0 (the "License");
+	 you may not use this file except in compliance with the License.
+	 You may obtain a copy of the License at
+
+	 http://www.apache.org/licenses/LICENSE-2.0
+
+	 Unless required by applicable law or agreed to in writing, software
+	 distributed under the License is distributed on an "AS IS" BASIS,
+	 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+	 See the License for the specific language governing permissions and
+	 limitations under the License.
+	 */
+
+	/**
+	 * @license bytebuffer.js (c) 2015 Daniel Wirtz <dcode@dcode.io>
+	 * Backing buffer: ArrayBuffer, Accessor: Uint8Array
+	 * Released under the Apache License, Version 2.0
+	 * see: https://github.com/dcodeIO/bytebuffer.js for details
+	 */
+	(function(global, factory) {
+
+	    /* AMD */ if ("function" === 'function' && __webpack_require__(28)["amd"])
+	        !(__WEBPACK_AMD_DEFINE_ARRAY__ = [__webpack_require__(30)], __WEBPACK_AMD_DEFINE_FACTORY__ = (factory), __WEBPACK_AMD_DEFINE_RESULT__ = (typeof __WEBPACK_AMD_DEFINE_FACTORY__ === 'function' ? (__WEBPACK_AMD_DEFINE_FACTORY__.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__)) : __WEBPACK_AMD_DEFINE_FACTORY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));
+	    /* CommonJS */ else if ("function" === 'function' && typeof module === "object" && module && module["exports"])
+	        module['exports'] = (function() {
+	            var Long; try { Long = __webpack_require__(30); } catch (e) {}
+	            return factory(Long);
+	        })();
+	    /* Global */ else
+	        (global["dcodeIO"] = global["dcodeIO"] || {})["ByteBuffer"] = factory(global["dcodeIO"]["Long"]);
+
+	})(this, function(Long) {
+	    "use strict";
+
+	    /**
+	     * Constructs a new ByteBuffer.
+	     * @class The swiss army knife for binary data in JavaScript.
+	     * @exports ByteBuffer
+	     * @constructor
+	     * @param {number=} capacity Initial capacity. Defaults to {@link ByteBuffer.DEFAULT_CAPACITY}.
+	     * @param {boolean=} littleEndian Whether to use little or big endian byte order. Defaults to
+	     *  {@link ByteBuffer.DEFAULT_ENDIAN}.
+	     * @param {boolean=} noAssert Whether to skip assertions of offsets and values. Defaults to
+	     *  {@link ByteBuffer.DEFAULT_NOASSERT}.
+	     * @expose
+	     */
+	    var ByteBuffer = function(capacity, littleEndian, noAssert) {
+	        if (typeof capacity === 'undefined')
+	            capacity = ByteBuffer.DEFAULT_CAPACITY;
+	        if (typeof littleEndian === 'undefined')
+	            littleEndian = ByteBuffer.DEFAULT_ENDIAN;
+	        if (typeof noAssert === 'undefined')
+	            noAssert = ByteBuffer.DEFAULT_NOASSERT;
+	        if (!noAssert) {
+	            capacity = capacity | 0;
+	            if (capacity < 0)
+	                throw RangeError("Illegal capacity");
+	            littleEndian = !!littleEndian;
+	            noAssert = !!noAssert;
+	        }
+
+	        /**
+	         * Backing ArrayBuffer.
+	         * @type {!ArrayBuffer}
+	         * @expose
+	         */
+	        this.buffer = capacity === 0 ? EMPTY_BUFFER : new ArrayBuffer(capacity);
+
+	        /**
+	         * Uint8Array utilized to manipulate the backing buffer. Becomes `null` if the backing buffer has a capacity of `0`.
+	         * @type {?Uint8Array}
+	         * @expose
+	         */
+	        this.view = capacity === 0 ? null : new Uint8Array(this.buffer);
+
+	        /**
+	         * Absolute read/write offset.
+	         * @type {number}
+	         * @expose
+	         * @see ByteBuffer#flip
+	         * @see ByteBuffer#clear
+	         */
+	        this.offset = 0;
+
+	        /**
+	         * Marked offset.
+	         * @type {number}
+	         * @expose
+	         * @see ByteBuffer#mark
+	         * @see ByteBuffer#reset
+	         */
+	        this.markedOffset = -1;
+
+	        /**
+	         * Absolute limit of the contained data. Set to the backing buffer's capacity upon allocation.
+	         * @type {number}
+	         * @expose
+	         * @see ByteBuffer#flip
+	         * @see ByteBuffer#clear
+	         */
+	        this.limit = capacity;
+
+	        /**
+	         * Whether to use little endian byte order, defaults to `false` for big endian.
+	         * @type {boolean}
+	         * @expose
+	         */
+	        this.littleEndian = littleEndian;
+
+	        /**
+	         * Whether to skip assertions of offsets and values, defaults to `false`.
+	         * @type {boolean}
+	         * @expose
+	         */
+	        this.noAssert = noAssert;
+	    };
+
+	    /**
+	     * ByteBuffer version.
+	     * @type {string}
+	     * @const
+	     * @expose
+	     */
+	    ByteBuffer.VERSION = "5.0.1";
+
+	    /**
+	     * Little endian constant that can be used instead of its boolean value. Evaluates to `true`.
+	     * @type {boolean}
+	     * @const
+	     * @expose
+	     */
+	    ByteBuffer.LITTLE_ENDIAN = true;
+
+	    /**
+	     * Big endian constant that can be used instead of its boolean value. Evaluates to `false`.
+	     * @type {boolean}
+	     * @const
+	     * @expose
+	     */
+	    ByteBuffer.BIG_ENDIAN = false;
+
+	    /**
+	     * Default initial capacity of `16`.
+	     * @type {number}
+	     * @expose
+	     */
+	    ByteBuffer.DEFAULT_CAPACITY = 16;
+
+	    /**
+	     * Default endianess of `false` for big endian.
+	     * @type {boolean}
+	     * @expose
+	     */
+	    ByteBuffer.DEFAULT_ENDIAN = ByteBuffer.BIG_ENDIAN;
+
+	    /**
+	     * Default no assertions flag of `false`.
+	     * @type {boolean}
+	     * @expose
+	     */
+	    ByteBuffer.DEFAULT_NOASSERT = false;
+
+	    /**
+	     * A `Long` class for representing a 64-bit two's-complement integer value. May be `null` if Long.js has not been loaded
+	     *  and int64 support is not available.
+	     * @type {?Long}
+	     * @const
+	     * @see https://github.com/dcodeIO/long.js
+	     * @expose
+	     */
+	    ByteBuffer.Long = Long || null;
+
+	    /**
+	     * @alias ByteBuffer.prototype
+	     * @inner
+	     */
+	    var ByteBufferPrototype = ByteBuffer.prototype;
+
+	    /**
+	     * An indicator used to reliably determine if an object is a ByteBuffer or not.
+	     * @type {boolean}
+	     * @const
+	     * @expose
+	     * @private
+	     */
+	    ByteBufferPrototype.__isByteBuffer__;
+
+	    Object.defineProperty(ByteBufferPrototype, "__isByteBuffer__", {
+	        value: true,
+	        enumerable: false,
+	        configurable: false
+	    });
+
+	    // helpers
+
+	    /**
+	     * @type {!ArrayBuffer}
+	     * @inner
+	     */
+	    var EMPTY_BUFFER = new ArrayBuffer(0);
+
+	    /**
+	     * String.fromCharCode reference for compile-time renaming.
+	     * @type {function(...number):string}
+	     * @inner
+	     */
+	    var stringFromCharCode = String.fromCharCode;
+
+	    /**
+	     * Creates a source function for a string.
+	     * @param {string} s String to read from
+	     * @returns {function():number|null} Source function returning the next char code respectively `null` if there are
+	     *  no more characters left.
+	     * @throws {TypeError} If the argument is invalid
+	     * @inner
+	     */
+	    function stringSource(s) {
+	        var i=0; return function() {
+	            return i < s.length ? s.charCodeAt(i++) : null;
+	        };
+	    }
+
+	    /**
+	     * Creates a destination function for a string.
+	     * @returns {function(number=):undefined|string} Destination function successively called with the next char code.
+	     *  Returns the final string when called without arguments.
+	     * @inner
+	     */
+	    function stringDestination() {
+	        var cs = [], ps = []; return function() {
+	            if (arguments.length === 0)
+	                return ps.join('')+stringFromCharCode.apply(String, cs);
+	            if (cs.length + arguments.length > 1024)
+	                ps.push(stringFromCharCode.apply(String, cs)),
+	                    cs.length = 0;
+	            Array.prototype.push.apply(cs, arguments);
+	        };
+	    }
+
+	    /**
+	     * Gets the accessor type.
+	     * @returns {Function} `Buffer` under node.js, `Uint8Array` respectively `DataView` in the browser (classes)
+	     * @expose
+	     */
+	    ByteBuffer.accessor = function() {
+	        return Uint8Array;
+	    };
+	    /**
+	     * Allocates a new ByteBuffer backed by a buffer of the specified capacity.
+	     * @param {number=} capacity Initial capacity. Defaults to {@link ByteBuffer.DEFAULT_CAPACITY}.
+	     * @param {boolean=} littleEndian Whether to use little or big endian byte order. Defaults to
+	     *  {@link ByteBuffer.DEFAULT_ENDIAN}.
+	     * @param {boolean=} noAssert Whether to skip assertions of offsets and values. Defaults to
+	     *  {@link ByteBuffer.DEFAULT_NOASSERT}.
+	     * @returns {!ByteBuffer}
+	     * @expose
+	     */
+	    ByteBuffer.allocate = function(capacity, littleEndian, noAssert) {
+	        return new ByteBuffer(capacity, littleEndian, noAssert);
+	    };
+
+	    /**
+	     * Concatenates multiple ByteBuffers into one.
+	     * @param {!Array.<!ByteBuffer|!ArrayBuffer|!Uint8Array|string>} buffers Buffers to concatenate
+	     * @param {(string|boolean)=} encoding String encoding if `buffers` contains a string ("base64", "hex", "binary",
+	     *  defaults to "utf8")
+	     * @param {boolean=} littleEndian Whether to use little or big endian byte order for the resulting ByteBuffer. Defaults
+	     *  to {@link ByteBuffer.DEFAULT_ENDIAN}.
+	     * @param {boolean=} noAssert Whether to skip assertions of offsets and values for the resulting ByteBuffer. Defaults to
+	     *  {@link ByteBuffer.DEFAULT_NOASSERT}.
+	     * @returns {!ByteBuffer} Concatenated ByteBuffer
+	     * @expose
+	     */
+	    ByteBuffer.concat = function(buffers, encoding, littleEndian, noAssert) {
+	        if (typeof encoding === 'boolean' || typeof encoding !== 'string') {
+	            noAssert = littleEndian;
+	            littleEndian = encoding;
+	            encoding = undefined;
+	        }
+	        var capacity = 0;
+	        for (var i=0, k=buffers.length, length; i<k; ++i) {
+	            if (!ByteBuffer.isByteBuffer(buffers[i]))
+	                buffers[i] = ByteBuffer.wrap(buffers[i], encoding);
+	            length = buffers[i].limit - buffers[i].offset;
+	            if (length > 0) capacity += length;
+	        }
+	        if (capacity === 0)
+	            return new ByteBuffer(0, littleEndian, noAssert);
+	        var bb = new ByteBuffer(capacity, littleEndian, noAssert),
+	            bi;
+	        i=0; while (i<k) {
+	            bi = buffers[i++];
+	            length = bi.limit - bi.offset;
+	            if (length <= 0) continue;
+	            bb.view.set(bi.view.subarray(bi.offset, bi.limit), bb.offset);
+	            bb.offset += length;
+	        }
+	        bb.limit = bb.offset;
+	        bb.offset = 0;
+	        return bb;
+	    };
+
+	    /**
+	     * Tests if the specified type is a ByteBuffer.
+	     * @param {*} bb ByteBuffer to test
+	     * @returns {boolean} `true` if it is a ByteBuffer, otherwise `false`
+	     * @expose
+	     */
+	    ByteBuffer.isByteBuffer = function(bb) {
+	        return (bb && bb["__isByteBuffer__"]) === true;
+	    };
+	    /**
+	     * Gets the backing buffer type.
+	     * @returns {Function} `Buffer` under node.js, `ArrayBuffer` in the browser (classes)
+	     * @expose
+	     */
+	    ByteBuffer.type = function() {
+	        return ArrayBuffer;
+	    };
+	    /**
+	     * Wraps a buffer or a string. Sets the allocated ByteBuffer's {@link ByteBuffer#offset} to `0` and its
+	     *  {@link ByteBuffer#limit} to the length of the wrapped data.
+	     * @param {!ByteBuffer|!ArrayBuffer|!Uint8Array|string|!Array.<number>} buffer Anything that can be wrapped
+	     * @param {(string|boolean)=} encoding String encoding if `buffer` is a string ("base64", "hex", "binary", defaults to
+	     *  "utf8")
+	     * @param {boolean=} littleEndian Whether to use little or big endian byte order. Defaults to
+	     *  {@link ByteBuffer.DEFAULT_ENDIAN}.
+	     * @param {boolean=} noAssert Whether to skip assertions of offsets and values. Defaults to
+	     *  {@link ByteBuffer.DEFAULT_NOASSERT}.
+	     * @returns {!ByteBuffer} A ByteBuffer wrapping `buffer`
+	     * @expose
+	     */
+	    ByteBuffer.wrap = function(buffer, encoding, littleEndian, noAssert) {
+	        if (typeof encoding !== 'string') {
+	            noAssert = littleEndian;
+	            littleEndian = encoding;
+	            encoding = undefined;
+	        }
+	        if (typeof buffer === 'string') {
+	            if (typeof encoding === 'undefined')
+	                encoding = "utf8";
+	            switch (encoding) {
+	                case "base64":
+	                    return ByteBuffer.fromBase64(buffer, littleEndian);
+	                case "hex":
+	                    return ByteBuffer.fromHex(buffer, littleEndian);
+	                case "binary":
+	                    return ByteBuffer.fromBinary(buffer, littleEndian);
+	                case "utf8":
+	                    return ByteBuffer.fromUTF8(buffer, littleEndian);
+	                case "debug":
+	                    return ByteBuffer.fromDebug(buffer, littleEndian);
+	                default:
+	                    throw Error("Unsupported encoding: "+encoding);
+	            }
+	        }
+	        if (buffer === null || typeof buffer !== 'object')
+	            throw TypeError("Illegal buffer");
+	        var bb;
+	        if (ByteBuffer.isByteBuffer(buffer)) {
+	            bb = ByteBufferPrototype.clone.call(buffer);
+	            bb.markedOffset = -1;
+	            return bb;
+	        }
+	        if (buffer instanceof Uint8Array) { // Extract ArrayBuffer from Uint8Array
+	            bb = new ByteBuffer(0, littleEndian, noAssert);
+	            if (buffer.length > 0) { // Avoid references to more than one EMPTY_BUFFER
+	                bb.buffer = buffer.buffer;
+	                bb.offset = buffer.byteOffset;
+	                bb.limit = buffer.byteOffset + buffer.byteLength;
+	                bb.view = new Uint8Array(buffer.buffer);
+	            }
+	        } else if (buffer instanceof ArrayBuffer) { // Reuse ArrayBuffer
+	            bb = new ByteBuffer(0, littleEndian, noAssert);
+	            if (buffer.byteLength > 0) {
+	                bb.buffer = buffer;
+	                bb.offset = 0;
+	                bb.limit = buffer.byteLength;
+	                bb.view = buffer.byteLength > 0 ? new Uint8Array(buffer) : null;
+	            }
+	        } else if (Object.prototype.toString.call(buffer) === "[object Array]") { // Create from octets
+	            bb = new ByteBuffer(buffer.length, littleEndian, noAssert);
+	            bb.limit = buffer.length;
+	            for (var i=0; i<buffer.length; ++i)
+	                bb.view[i] = buffer[i];
+	        } else
+	            throw TypeError("Illegal buffer"); // Otherwise fail
+	        return bb;
+	    };
+
+	    /**
+	     * Writes the array as a bitset.
+	     * @param {Array<boolean>} value Array of booleans to write
+	     * @param {number=} offset Offset to read from. Will use and increase {@link ByteBuffer#offset} by `length` if omitted.
+	     * @returns {!ByteBuffer}
+	     * @expose
+	     */
+	    ByteBufferPrototype.writeBitSet = function(value, offset) {
+	      var relative = typeof offset === 'undefined';
+	      if (relative) offset = this.offset;
+	      if (!this.noAssert) {
+	        if (!(value instanceof Array))
+	          throw TypeError("Illegal BitSet: Not an array");
+	        if (typeof offset !== 'number' || offset % 1 !== 0)
+	            throw TypeError("Illegal offset: "+offset+" (not an integer)");
+	        offset >>>= 0;
+	        if (offset < 0 || offset + 0 > this.buffer.byteLength)
+	            throw RangeError("Illegal offset: 0 <= "+offset+" (+"+0+") <= "+this.buffer.byteLength);
+	      }
+
+	      var start = offset,
+	          bits = value.length,
+	          bytes = (bits >> 3),
+	          bit = 0,
+	          k;
+
+	      offset += this.writeVarint32(bits,offset);
+
+	      while(bytes--) {
+	        k = (!!value[bit++] & 1) |
+	            ((!!value[bit++] & 1) << 1) |
+	            ((!!value[bit++] & 1) << 2) |
+	            ((!!value[bit++] & 1) << 3) |
+	            ((!!value[bit++] & 1) << 4) |
+	            ((!!value[bit++] & 1) << 5) |
+	            ((!!value[bit++] & 1) << 6) |
+	            ((!!value[bit++] & 1) << 7);
+	        this.writeByte(k,offset++);
+	      }
+
+	      if(bit < bits) {
+	        var m = 0; k = 0;
+	        while(bit < bits) k = k | ((!!value[bit++] & 1) << (m++));
+	        this.writeByte(k,offset++);
+	      }
+
+	      if (relative) {
+	        this.offset = offset;
+	        return this;
+	      }
+	      return offset - start;
+	    }
+
+	    /**
+	     * Reads a BitSet as an array of booleans.
+	     * @param {number=} offset Offset to read from. Will use and increase {@link ByteBuffer#offset} by `length` if omitted.
+	     * @returns {Array<boolean>
+	     * @expose
+	     */
+	    ByteBufferPrototype.readBitSet = function(offset) {
+	      var relative = typeof offset === 'undefined';
+	      if (relative) offset = this.offset;
+
+	      var ret = this.readVarint32(offset),
+	          bits = ret.value,
+	          bytes = (bits >> 3),
+	          bit = 0,
+	          value = [],
+	          k;
+
+	      offset += ret.length;
+
+	      while(bytes--) {
+	        k = this.readByte(offset++);
+	        value[bit++] = !!(k & 0x01);
+	        value[bit++] = !!(k & 0x02);
+	        value[bit++] = !!(k & 0x04);
+	        value[bit++] = !!(k & 0x08);
+	        value[bit++] = !!(k & 0x10);
+	        value[bit++] = !!(k & 0x20);
+	        value[bit++] = !!(k & 0x40);
+	        value[bit++] = !!(k & 0x80);
+	      }
+
+	      if(bit < bits) {
+	        var m = 0;
+	        k = this.readByte(offset++);
+	        while(bit < bits) value[bit++] = !!((k >> (m++)) & 1);
+	      }
+
+	      if (relative) {
+	        this.offset = offset;
+	      }
+	      return value;
+	    }
+	    /**
+	     * Reads the specified number of bytes.
+	     * @param {number} length Number of bytes to read
+	     * @param {number=} offset Offset to read from. Will use and increase {@link ByteBuffer#offset} by `length` if omitted.
+	     * @returns {!ByteBuffer}
+	     * @expose
+	     */
+	    ByteBufferPrototype.readBytes = function(length, offset) {
+	        var relative = typeof offset === 'undefined';
+	        if (relative) offset = this.offset;
+	        if (!this.noAssert) {
+	            if (typeof offset !== 'number' || offset % 1 !== 0)
+	                throw TypeError("Illegal offset: "+offset+" (not an integer)");
+	            offset >>>= 0;
+	            if (offset < 0 || offset + length > this.buffer.byteLength)
+	                throw RangeError("Illegal offset: 0 <= "+offset+" (+"+length+") <= "+this.buffer.byteLength);
+	        }
+	        var slice = this.slice(offset, offset + length);
+	        if (relative) this.offset += length;
+	        return slice;
+	    };
+
+	    /**
+	     * Writes a payload of bytes. This is an alias of {@link ByteBuffer#append}.
+	     * @function
+	     * @param {!ByteBuffer|!ArrayBuffer|!Uint8Array|string} source Data to write. If `source` is a ByteBuffer, its offsets
+	     *  will be modified according to the performed read operation.
+	     * @param {(string|number)=} encoding Encoding if `data` is a string ("base64", "hex", "binary", defaults to "utf8")
+	     * @param {number=} offset Offset to write to. Will use and increase {@link ByteBuffer#offset} by the number of bytes
+	     *  written if omitted.
+	     * @returns {!ByteBuffer} this
+	     * @expose
+	     */
+	    ByteBufferPrototype.writeBytes = ByteBufferPrototype.append;
+
+	    // types/ints/int8
+
+	    /**
+	     * Writes an 8bit signed integer.
+	     * @param {number} value Value to write
+	     * @param {number=} offset Offset to write to. Will use and advance {@link ByteBuffer#offset} by `1` if omitted.
+	     * @returns {!ByteBuffer} this
+	     * @expose
+	     */
+	    ByteBufferPrototype.writeInt8 = function(value, offset) {
+	        var relative = typeof offset === 'undefined';
+	        if (relative) offset = this.offset;
+	        if (!this.noAssert) {
+	            if (typeof value !== 'number' || value % 1 !== 0)
+	                throw TypeError("Illegal value: "+value+" (not an integer)");
+	            value |= 0;
+	            if (typeof offset !== 'number' || offset % 1 !== 0)
+	                throw TypeError("Illegal offset: "+offset+" (not an integer)");
+	            offset >>>= 0;
+	            if (offset < 0 || offset + 0 > this.buffer.byteLength)
+	                throw RangeError("Illegal offset: 0 <= "+offset+" (+"+0+") <= "+this.buffer.byteLength);
+	        }
+	        offset += 1;
+	        var capacity0 = this.buffer.byteLength;
+	        if (offset > capacity0)
+	            this.resize((capacity0 *= 2) > offset ? capacity0 : offset);
+	        offset -= 1;
+	        this.view[offset] = value;
+	        if (relative) this.offset += 1;
+	        return this;
+	    };
+
+	    /**
+	     * Writes an 8bit signed integer. This is an alias of {@link ByteBuffer#writeInt8}.
+	     * @function
+	     * @param {number} value Value to write
+	     * @param {number=} offset Offset to write to. Will use and advance {@link ByteBuffer#offset} by `1` if omitted.
+	     * @returns {!ByteBuffer} this
+	     * @expose
+	     */
+	    ByteBufferPrototype.writeByte = ByteBufferPrototype.writeInt8;
+
+	    /**
+	     * Reads an 8bit signed integer.
+	     * @param {number=} offset Offset to read from. Will use and advance {@link ByteBuffer#offset} by `1` if omitted.
+	     * @returns {number} Value read
+	     * @expose
+	     */
+	    ByteBufferPrototype.readInt8 = function(offset) {
+	        var relative = typeof offset === 'undefined';
+	        if (relative) offset = this.offset;
+	        if (!this.noAssert) {
+	            if (typeof offset !== 'number' || offset % 1 !== 0)
+	                throw TypeError("Illegal offset: "+offset+" (not an integer)");
+	            offset >>>= 0;
+	            if (offset < 0 || offset + 1 > this.buffer.byteLength)
+	                throw RangeError("Illegal offset: 0 <= "+offset+" (+"+1+") <= "+this.buffer.byteLength);
+	        }
+	        var value = this.view[offset];
+	        if ((value & 0x80) === 0x80) value = -(0xFF - value + 1); // Cast to signed
+	        if (relative) this.offset += 1;
+	        return value;
+	    };
+
+	    /**
+	     * Reads an 8bit signed integer. This is an alias of {@link ByteBuffer#readInt8}.
+	     * @function
+	     * @param {number=} offset Offset to read from. Will use and advance {@link ByteBuffer#offset} by `1` if omitted.
+	     * @returns {number} Value read
+	     * @expose
+	     */
+	    ByteBufferPrototype.readByte = ByteBufferPrototype.readInt8;
+
+	    /**
+	     * Writes an 8bit unsigned integer.
+	     * @param {number} value Value to write
+	     * @param {number=} offset Offset to write to. Will use and advance {@link ByteBuffer#offset} by `1` if omitted.
+	     * @returns {!ByteBuffer} this
+	     * @expose
+	     */
+	    ByteBufferPrototype.writeUint8 = function(value, offset) {
+	        var relative = typeof offset === 'undefined';
+	        if (relative) offset = this.offset;
+	        if (!this.noAssert) {
+	            if (typeof value !== 'number' || value % 1 !== 0)
+	                throw TypeError("Illegal value: "+value+" (not an integer)");
+	            value >>>= 0;
+	            if (typeof offset !== 'number' || offset % 1 !== 0)
+	                throw TypeError("Illegal offset: "+offset+" (not an integer)");
+	            offset >>>= 0;
+	            if (offset < 0 || offset + 0 > this.buffer.byteLength)
+	                throw RangeError("Illegal offset: 0 <= "+offset+" (+"+0+") <= "+this.buffer.byteLength);
+	        }
+	        offset += 1;
+	        var capacity1 = this.buffer.byteLength;
+	        if (offset > capacity1)
+	            this.resize((capacity1 *= 2) > offset ? capacity1 : offset);
+	        offset -= 1;
+	        this.view[offset] = value;
+	        if (relative) this.offset += 1;
+	        return this;
+	    };
+
+	    /**
+	     * Writes an 8bit unsigned integer. This is an alias of {@link ByteBuffer#writeUint8}.
+	     * @function
+	     * @param {number} value Value to write
+	     * @param {number=} offset Offset to write to. Will use and advance {@link ByteBuffer#offset} by `1` if omitted.
+	     * @returns {!ByteBuffer} this
+	     * @expose
+	     */
+	    ByteBufferPrototype.writeUInt8 = ByteBufferPrototype.writeUint8;
+
+	    /**
+	     * Reads an 8bit unsigned integer.
+	     * @param {number=} offset Offset to read from. Will use and advance {@link ByteBuffer#offset} by `1` if omitted.
+	     * @returns {number} Value read
+	     * @expose
+	     */
+	    ByteBufferPrototype.readUint8 = function(offset) {
+	        var relative = typeof offset === 'undefined';
+	        if (relative) offset = this.offset;
+	        if (!this.noAssert) {
+	            if (typeof offset !== 'number' || offset % 1 !== 0)
+	                throw TypeError("Illegal offset: "+offset+" (not an integer)");
+	            offset >>>= 0;
+	            if (offset < 0 || offset + 1 > this.buffer.byteLength)
+	                throw RangeError("Illegal offset: 0 <= "+offset+" (+"+1+") <= "+this.buffer.byteLength);
+	        }
+	        var value = this.view[offset];
+	        if (relative) this.offset += 1;
+	        return value;
+	    };
+
+	    /**
+	     * Reads an 8bit unsigned integer. This is an alias of {@link ByteBuffer#readUint8}.
+	     * @function
+	     * @param {number=} offset Offset to read from. Will use and advance {@link ByteBuffer#offset} by `1` if omitted.
+	     * @returns {number} Value read
+	     * @expose
+	     */
+	    ByteBufferPrototype.readUInt8 = ByteBufferPrototype.readUint8;
+
+	    // types/ints/int16
+
+	    /**
+	     * Writes a 16bit signed integer.
+	     * @param {number} value Value to write
+	     * @param {number=} offset Offset to write to. Will use and advance {@link ByteBuffer#offset} by `2` if omitted.
+	     * @throws {TypeError} If `offset` or `value` is not a valid number
+	     * @throws {RangeError} If `offset` is out of bounds
+	     * @expose
+	     */
+	    ByteBufferPrototype.writeInt16 = function(value, offset) {
+	        var relative = typeof offset === 'undefined';
+	        if (relative) offset = this.offset;
+	        if (!this.noAssert) {
+	            if (typeof value !== 'number' || value % 1 !== 0)
+	                throw TypeError("Illegal value: "+value+" (not an integer)");
+	            value |= 0;
+	            if (typeof offset !== 'number' || offset % 1 !== 0)
+	                throw TypeError("Illegal offset: "+offset+" (not an integer)");
+	            offset >>>= 0;
+	            if (offset < 0 || offset + 0 > this.buffer.byteLength)
+	                throw RangeError("Illegal offset: 0 <= "+offset+" (+"+0+") <= "+this.buffer.byteLength);
+	        }
+	        offset += 2;
+	        var capacity2 = this.buffer.byteLength;
+	        if (offset > capacity2)
+	            this.resize((capacity2 *= 2) > offset ? capacity2 : offset);
+	        offset -= 2;
+	        if (this.littleEndian) {
+	            this.view[offset+1] = (value & 0xFF00) >>> 8;
+	            this.view[offset  ] =  value & 0x00FF;
+	        } else {
+	            this.view[offset]   = (value & 0xFF00) >>> 8;
+	            this.view[offset+1] =  value & 0x00FF;
+	        }
+	        if (relative) this.offset += 2;
+	        return this;
+	    };
+
+	    /**
+	     * Writes a 16bit signed integer. This is an alias of {@link ByteBuffer#writeInt16}.
+	     * @function
+	     * @param {number} value Value to write
+	     * @param {number=} offset Offset to write to. Will use and advance {@link ByteBuffer#offset} by `2` if omitted.
+	     * @throws {TypeError} If `offset` or `value` is not a valid number
+	     * @throws {RangeError} If `offset` is out of bounds
+	     * @expose
+	     */
+	    ByteBufferPrototype.writeShort = ByteBufferPrototype.writeInt16;
+
+	    /**
+	     * Reads a 16bit signed integer.
+	     * @param {number=} offset Offset to read from. Will use and advance {@link ByteBuffer#offset} by `2` if omitted.
+	     * @returns {number} Value read
+	     * @throws {TypeError} If `offset` is not a valid number
+	     * @throws {RangeError} If `offset` is out of bounds
+	     * @expose
+	     */
+	    ByteBufferPrototype.readInt16 = function(offset) {
+	        var relative = typeof offset === 'undefined';
+	        if (relative) offset = this.offset;
+	        if (!this.noAssert) {
+	            if (typeof offset !== 'number' || offset % 1 !== 0)
+	                throw TypeError("Illegal offset: "+offset+" (not an integer)");
+	            offset >>>= 0;
+	            if (offset < 0 || offset + 2 > this.buffer.byteLength)
+	                throw RangeError("Illegal offset: 0 <= "+offset+" (+"+2+") <= "+this.buffer.byteLength);
+	        }
+	        var value = 0;
+	        if (this.littleEndian) {
+	            value  = this.view[offset  ];
+	            value |= this.view[offset+1] << 8;
+	        } else {
+	            value  = this.view[offset  ] << 8;
+	            value |= this.view[offset+1];
+	        }
+	        if ((value & 0x8000) === 0x8000) value = -(0xFFFF - value + 1); // Cast to signed
+	        if (relative) this.offset += 2;
+	        return value;
+	    };
+
+	    /**
+	     * Reads a 16bit signed integer. This is an alias of {@link ByteBuffer#readInt16}.
+	     * @function
+	     * @param {number=} offset Offset to read from. Will use and advance {@link ByteBuffer#offset} by `2` if omitted.
+	     * @returns {number} Value read
+	     * @throws {TypeError} If `offset` is not a valid number
+	     * @throws {RangeError} If `offset` is out of bounds
+	     * @expose
+	     */
+	    ByteBufferPrototype.readShort = ByteBufferPrototype.readInt16;
+
+	    /**
+	     * Writes a 16bit unsigned integer.
+	     * @param {number} value Value to write
+	     * @param {number=} offset Offset to write to. Will use and advance {@link ByteBuffer#offset} by `2` if omitted.
+	     * @throws {TypeError} If `offset` or `value` is not a valid number
+	     * @throws {RangeError} If `offset` is out of bounds
+	     * @expose
+	     */
+	    ByteBufferPrototype.writeUint16 = function(value, offset) {
+	        var relative = typeof offset === 'undefined';
+	        if (relative) offset = this.offset;
+	        if (!this.noAssert) {
+	            if (typeof value !== 'number' || value % 1 !== 0)
+	                throw TypeError("Illegal value: "+value+" (not an integer)");
+	            value >>>= 0;
+	            if (typeof offset !== 'number' || offset % 1 !== 0)
+	                throw TypeError("Illegal offset: "+offset+" (not an integer)");
+	            offset >>>= 0;
+	            if (offset < 0 || offset + 0 > this.buffer.byteLength)
+	                throw RangeError("Illegal offset: 0 <= "+offset+" (+"+0+") <= "+this.buffer.byteLength);
+	        }
+	        offset += 2;
+	        var capacity3 = this.buffer.byteLength;
+	        if (offset > capacity3)
+	            this.resize((capacity3 *= 2) > offset ? capacity3 : offset);
+	        offset -= 2;
+	        if (this.littleEndian) {
+	            this.view[offset+1] = (value & 0xFF00) >>> 8;
+	            this.view[offset  ] =  value & 0x00FF;
+	        } else {
+	            this.view[offset]   = (value & 0xFF00) >>> 8;
+	            this.view[offset+1] =  value & 0x00FF;
+	        }
+	        if (relative) this.offset += 2;
+	        return this;
+	    };
+
+	    /**
+	     * Writes a 16bit unsigned integer. This is an alias of {@link ByteBuffer#writeUint16}.
+	     * @function
+	     * @param {number} value Value to write
+	     * @param {number=} offset Offset to write to. Will use and advance {@link ByteBuffer#offset} by `2` if omitted.
+	     * @throws {TypeError} If `offset` or `value` is not a valid number
+	     * @throws {RangeError} If `offset` is out of bounds
+	     * @expose
+	     */
+	    ByteBufferPrototype.writeUInt16 = ByteBufferPrototype.writeUint16;
+
+	    /**
+	     * Reads a 16bit unsigned integer.
+	     * @param {number=} offset Offset to read from. Will use and advance {@link ByteBuffer#offset} by `2` if omitted.
+	     * @returns {number} Value read
+	     * @throws {TypeError} If `offset` is not a valid number
+	     * @throws {RangeError} If `offset` is out of bounds
+	     * @expose
+	     */
+	    ByteBufferPrototype.readUint16 = function(offset) {
+	        var relative = typeof offset === 'undefined';
+	        if (relative) offset = this.offset;
+	        if (!this.noAssert) {
+	            if (typeof offset !== 'number' || offset % 1 !== 0)
+	                throw TypeError("Illegal offset: "+offset+" (not an integer)");
+	            offset >>>= 0;
+	            if (offset < 0 || offset + 2 > this.buffer.byteLength)
+	                throw RangeError("Illegal offset: 0 <= "+offset+" (+"+2+") <= "+this.buffer.byteLength);
+	        }
+	        var value = 0;
+	        if (this.littleEndian) {
+	            value  = this.view[offset  ];
+	            value |= this.view[offset+1] << 8;
+	        } else {
+	            value  = this.view[offset  ] << 8;
+	            value |= this.view[offset+1];
+	        }
+	        if (relative) this.offset += 2;
+	        return value;
+	    };
+
+	    /**
+	     * Reads a 16bit unsigned integer. This is an alias of {@link ByteBuffer#readUint16}.
+	     * @function
+	     * @param {number=} offset Offset to read from. Will use and advance {@link ByteBuffer#offset} by `2` if omitted.
+	     * @returns {number} Value read
+	     * @throws {TypeError} If `offset` is not a valid number
+	     * @throws {RangeError} If `offset` is out of bounds
+	     * @expose
+	     */
+	    ByteBufferPrototype.readUInt16 = ByteBufferPrototype.readUint16;
+
+	    // types/ints/int32
+
+	    /**
+	     * Writes a 32bit signed integer.
+	     * @param {number} value Value to write
+	     * @param {number=} offset Offset to write to. Will use and increase {@link ByteBuffer#offset} by `4` if omitted.
+	     * @expose
+	     */
+	    ByteBufferPrototype.writeInt32 = function(value, offset) {
+	        var relative = typeof offset === 'undefined';
+	        if (relative) offset = this.offset;
+	        if (!this.noAssert) {
+	            if (typeof value !== 'number' || value % 1 !== 0)
+	                throw TypeError("Illegal value: "+value+" (not an integer)");
+	            value |= 0;
+	            if (typeof offset !== 'number' || offset % 1 !== 0)
+	                throw TypeError("Illegal offset: "+offset+" (not an integer)");
+	            offset >>>= 0;
+	            if (offset < 0 || offset + 0 > this.buffer.byteLength)
+	                throw RangeError("Illegal offset: 0 <= "+offset+" (+"+0+") <= "+this.buffer.byteLength);
+	        }
+	        offset += 4;
+	        var capacity4 = this.buffer.byteLength;
+	        if (offset > capacity4)
+	            this.resize((capacity4 *= 2) > offset ? capacity4 : offset);
+	        offset -= 4;
+	        if (this.littleEndian) {
+	            this.view[offset+3] = (value >>> 24) & 0xFF;
+	            this.view[offset+2] = (value >>> 16) & 0xFF;
+	            this.view[offset+1] = (value >>>  8) & 0xFF;
+	            this.view[offset  ] =  value         & 0xFF;
+	        } else {
+	            this.view[offset  ] = (value >>> 24) & 0xFF;
+	            this.view[offset+1] = (value >>> 16) & 0xFF;
+	            this.view[offset+2] = (value >>>  8) & 0xFF;
+	            this.view[offset+3] =  value         & 0xFF;
+	        }
+	        if (relative) this.offset += 4;
+	        return this;
+	    };
+
+	    /**
+	     * Writes a 32bit signed integer. This is an alias of {@link ByteBuffer#writeInt32}.
+	     * @param {number} value Value to write
+	     * @param {number=} offset Offset to write to. Will use and increase {@link ByteBuffer#offset} by `4` if omitted.
+	     * @expose
+	     */
+	    ByteBufferPrototype.writeInt = ByteBufferPrototype.writeInt32;
+
+	    /**
+	     * Reads a 32bit signed integer.
+	     * @param {number=} offset Offset to read from. Will use and increase {@link ByteBuffer#offset} by `4` if omitted.
+	     * @returns {number} Value read
+	     * @expose
+	     */
+	    ByteBufferPrototype.readInt32 = function(offset) {
+	        var relative = typeof offset === 'undefined';
+	        if (relative) offset = this.offset;
+	        if (!this.noAssert) {
+	            if (typeof offset !== 'number' || offset % 1 !== 0)
+	                throw TypeError("Illegal offset: "+offset+" (not an integer)");
+	            offset >>>= 0;
+	            if (offset < 0 || offset + 4 > this.buffer.byteLength)
+	                throw RangeError("Illegal offset: 0 <= "+offset+" (+"+4+") <= "+this.buffer.byteLength);
+	        }
+	        var value = 0;
+	        if (this.littleEndian) {
+	            value  = this.view[offset+2] << 16;
+	            value |= this.view[offset+1] <<  8;
+	            value |= this.view[offset  ];
+	            value += this.view[offset+3] << 24 >>> 0;
+	        } else {
+	            value  = this.view[offset+1] << 16;
+	            value |= this.view[offset+2] <<  8;
+	            value |= this.view[offset+3];
+	            value += this.view[offset  ] << 24 >>> 0;
+	        }
+	        value |= 0; // Cast to signed
+	        if (relative) this.offset += 4;
+	        return value;
+	    };
+
+	    /**
+	     * Reads a 32bit signed integer. This is an alias of {@link ByteBuffer#readInt32}.
+	     * @param {number=} offset Offset to read from. Will use and advance {@link ByteBuffer#offset} by `4` if omitted.
+	     * @returns {number} Value read
+	     * @expose
+	     */
+	    ByteBufferPrototype.readInt = ByteBufferPrototype.readInt32;
+
+	    /**
+	     * Writes a 32bit unsigned integer.
+	     * @param {number} value Value to write
+	     * @param {number=} offset Offset to write to. Will use and increase {@link ByteBuffer#offset} by `4` if omitted.
+	     * @expose
+	     */
+	    ByteBufferPrototype.writeUint32 = function(value, offset) {
+	        var relative = typeof offset === 'undefined';
+	        if (relative) offset = this.offset;
+	        if (!this.noAssert) {
+	            if (typeof value !== 'number' || value % 1 !== 0)
+	                throw TypeError("Illegal value: "+value+" (not an integer)");
+	            value >>>= 0;
+	            if (typeof offset !== 'number' || offset % 1 !== 0)
+	                throw TypeError("Illegal offset: "+offset+" (not an integer)");
+	            offset >>>= 0;
+	            if (offset < 0 || offset + 0 > this.buffer.byteLength)
+	                throw RangeError("Illegal offset: 0 <= "+offset+" (+"+0+") <= "+this.buffer.byteLength);
+	        }
+	        offset += 4;
+	        var capacity5 = this.buffer.byteLength;
+	        if (offset > capacity5)
+	            this.resize((capacity5 *= 2) > offset ? capacity5 : offset);
+	        offset -= 4;
+	        if (this.littleEndian) {
+	            this.view[offset+3] = (value >>> 24) & 0xFF;
+	            this.view[offset+2] = (value >>> 16) & 0xFF;
+	            this.view[offset+1] = (value >>>  8) & 0xFF;
+	            this.view[offset  ] =  value         & 0xFF;
+	        } else {
+	            this.view[offset  ] = (value >>> 24) & 0xFF;
+	            this.view[offset+1] = (value >>> 16) & 0xFF;
+	            this.view[offset+2] = (value >>>  8) & 0xFF;
+	            this.view[offset+3] =  value         & 0xFF;
+	        }
+	        if (relative) this.offset += 4;
+	        return this;
+	    };
+
+	    /**
+	     * Writes a 32bit unsigned integer. This is an alias of {@link ByteBuffer#writeUint32}.
+	     * @function
+	     * @param {number} value Value to write
+	     * @param {number=} offset Offset to write to. Will use and increase {@link ByteBuffer#offset} by `4` if omitted.
+	     * @expose
+	     */
+	    ByteBufferPrototype.writeUInt32 = ByteBufferPrototype.writeUint32;
+
+	    /**
+	     * Reads a 32bit unsigned integer.
+	     * @param {number=} offset Offset to read from. Will use and increase {@link ByteBuffer#offset} by `4` if omitted.
+	     * @returns {number} Value read
+	     * @expose
+	     */
+	    ByteBufferPrototype.readUint32 = function(offset) {
+	        var relative = typeof offset === 'undefined';
+	        if (relative) offset = this.offset;
+	        if (!this.noAssert) {
+	            if (typeof offset !== 'number' || offset % 1 !== 0)
+	                throw TypeError("Illegal offset: "+offset+" (not an integer)");
+	            offset >>>= 0;
+	            if (offset < 0 || offset + 4 > this.buffer.byteLength)
+	                throw RangeError("Illegal offset: 0 <= "+offset+" (+"+4+") <= "+this.buffer.byteLength);
+	        }
+	        var value = 0;
+	        if (this.littleEndian) {
+	            value  = this.view[offset+2] << 16;
+	            value |= this.view[offset+1] <<  8;
+	            value |= this.view[offset  ];
+	            value += this.view[offset+3] << 24 >>> 0;
+	        } else {
+	            value  = this.view[offset+1] << 16;
+	            value |= this.view[offset+2] <<  8;
+	            value |= this.view[offset+3];
+	            value += this.view[offset  ] << 24 >>> 0;
+	        }
+	        if (relative) this.offset += 4;
+	        return value;
+	    };
+
+	    /**
+	     * Reads a 32bit unsigned integer. This is an alias of {@link ByteBuffer#readUint32}.
+	     * @function
+	     * @param {number=} offset Offset to read from. Will use and increase {@link ByteBuffer#offset} by `4` if omitted.
+	     * @returns {number} Value read
+	     * @expose
+	     */
+	    ByteBufferPrototype.readUInt32 = ByteBufferPrototype.readUint32;
+
+	    // types/ints/int64
+
+	    if (Long) {
+
+	        /**
+	         * Writes a 64bit signed integer.
+	         * @param {number|!Long} value Value to write
+	         * @param {number=} offset Offset to write to. Will use and increase {@link ByteBuffer#offset} by `8` if omitted.
+	         * @returns {!ByteBuffer} this
+	         * @expose
+	         */
+	        ByteBufferPrototype.writeInt64 = function(value, offset) {
+	            var relative = typeof offset === 'undefined';
+	            if (relative) offset = this.offset;
+	            if (!this.noAssert) {
+	                if (typeof value === 'number')
+	                    value = Long.fromNumber(value);
+	                else if (typeof value === 'string')
+	                    value = Long.fromString(value);
+	                else if (!(value && value instanceof Long))
+	                    throw TypeError("Illegal value: "+value+" (not an integer or Long)");
+	                if (typeof offset !== 'number' || offset % 1 !== 0)
+	                    throw TypeError("Illegal offset: "+offset+" (not an integer)");
+	                offset >>>= 0;
+	                if (offset < 0 || offset + 0 > this.buffer.byteLength)
+	                    throw RangeError("Illegal offset: 0 <= "+offset+" (+"+0+") <= "+this.buffer.byteLength);
+	            }
+	            if (typeof value === 'number')
+	                value = Long.fromNumber(value);
+	            else if (typeof value === 'string')
+	                value = Long.fromString(value);
+	            offset += 8;
+	            var capacity6 = this.buffer.byteLength;
+	            if (offset > capacity6)
+	                this.resize((capacity6 *= 2) > offset ? capacity6 : offset);
+	            offset -= 8;
+	            var lo = value.low,
+	                hi = value.high;
+	            if (this.littleEndian) {
+	                this.view[offset+3] = (lo >>> 24) & 0xFF;
+	                this.view[offset+2] = (lo >>> 16) & 0xFF;
+	                this.view[offset+1] = (lo >>>  8) & 0xFF;
+	                this.view[offset  ] =  lo         & 0xFF;
+	                offset += 4;
+	                this.view[offset+3] = (hi >>> 24) & 0xFF;
+	                this.view[offset+2] = (hi >>> 16) & 0xFF;
+	                this.view[offset+1] = (hi >>>  8) & 0xFF;
+	                this.view[offset  ] =  hi         & 0xFF;
+	            } else {
+	                this.view[offset  ] = (hi >>> 24) & 0xFF;
+	                this.view[offset+1] = (hi >>> 16) & 0xFF;
+	                this.view[offset+2] = (hi >>>  8) & 0xFF;
+	                this.view[offset+3] =  hi         & 0xFF;
+	                offset += 4;
+	                this.view[offset  ] = (lo >>> 24) & 0xFF;
+	                this.view[offset+1] = (lo >>> 16) & 0xFF;
+	                this.view[offset+2] = (lo >>>  8) & 0xFF;
+	                this.view[offset+3] =  lo         & 0xFF;
+	            }
+	            if (relative) this.offset += 8;
+	            return this;
+	        };
+
+	        /**
+	         * Writes a 64bit signed integer. This is an alias of {@link ByteBuffer#writeInt64}.
+	         * @param {number|!Long} value Value to write
+	         * @param {number=} offset Offset to write to. Will use and increase {@link ByteBuffer#offset} by `8` if omitted.
+	         * @returns {!ByteBuffer} this
+	         * @expose
+	         */
+	        ByteBufferPrototype.writeLong = ByteBufferPrototype.writeInt64;
+
+	        /**
+	         * Reads a 64bit signed integer.
+	         * @param {number=} offset Offset to read from. Will use and increase {@link ByteBuffer#offset} by `8` if omitted.
+	         * @returns {!Long}
+	         * @expose
+	         */
+	        ByteBufferPrototype.readInt64 = function(offset) {
+	            var relative = typeof offset === 'undefined';
+	            if (relative) offset = this.offset;
+	            if (!this.noAssert) {
+	                if (typeof offset !== 'number' || offset % 1 !== 0)
+	                    throw TypeError("Illegal offset: "+offset+" (not an integer)");
+	                offset >>>= 0;
+	                if (offset < 0 || offset + 8 > this.buffer.byteLength)
+	                    throw RangeError("Illegal offset: 0 <= "+offset+" (+"+8+") <= "+this.buffer.byteLength);
+	            }
+	            var lo = 0,
+	                hi = 0;
+	            if (this.littleEndian) {
+	                lo  = this.view[offset+2] << 16;
+	                lo |= this.view[offset+1] <<  8;
+	                lo |= this.view[offset  ];
+	                lo += this.view[offset+3] << 24 >>> 0;
+	                offset += 4;
+	                hi  = this.view[offset+2] << 16;
+	                hi |= this.view[offset+1] <<  8;
+	                hi |= this.view[offset  ];
+	                hi += this.view[offset+3] << 24 >>> 0;
+	            } else {
+	                hi  = this.view[offset+1] << 16;
+	                hi |= this.view[offset+2] <<  8;
+	                hi |= this.view[offset+3];
+	                hi += this.view[offset  ] << 24 >>> 0;
+	                offset += 4;
+	                lo  = this.view[offset+1] << 16;
+	                lo |= this.view[offset+2] <<  8;
+	                lo |= this.view[offset+3];
+	                lo += this.view[offset  ] << 24 >>> 0;
+	            }
+	            var value = new Long(lo, hi, false);
+	            if (relative) this.offset += 8;
+	            return value;
+	        };
+
+	        /**
+	         * Reads a 64bit signed integer. This is an alias of {@link ByteBuffer#readInt64}.
+	         * @param {number=} offset Offset to read from. Will use and increase {@link ByteBuffer#offset} by `8` if omitted.
+	         * @returns {!Long}
+	         * @expose
+	         */
+	        ByteBufferPrototype.readLong = ByteBufferPrototype.readInt64;
+
+	        /**
+	         * Writes a 64bit unsigned integer.
+	         * @param {number|!Long} value Value to write
+	         * @param {number=} offset Offset to write to. Will use and increase {@link ByteBuffer#offset} by `8` if omitted.
+	         * @returns {!ByteBuffer} this
+	         * @expose
+	         */
+	        ByteBufferPrototype.writeUint64 = function(value, offset) {
+	            var relative = typeof offset === 'undefined';
+	            if (relative) offset = this.offset;
+	            if (!this.noAssert) {
+	                if (typeof value === 'number')
+	                    value = Long.fromNumber(value);
+	                else if (typeof value === 'string')
+	                    value = Long.fromString(value);
+	                else if (!(value && value instanceof Long))
+	                    throw TypeError("Illegal value: "+value+" (not an integer or Long)");
+	                if (typeof offset !== 'number' || offset % 1 !== 0)
+	                    throw TypeError("Illegal offset: "+offset+" (not an integer)");
+	                offset >>>= 0;
+	                if (offset < 0 || offset + 0 > this.buffer.byteLength)
+	                    throw RangeError("Illegal offset: 0 <= "+offset+" (+"+0+") <= "+this.buffer.byteLength);
+	            }
+	            if (typeof value === 'number')
+	                value = Long.fromNumber(value);
+	            else if (typeof value === 'string')
+	                value = Long.fromString(value);
+	            offset += 8;
+	            var capacity7 = this.buffer.byteLength;
+	            if (offset > capacity7)
+	                this.resize((capacity7 *= 2) > offset ? capacity7 : offset);
+	            offset -= 8;
+	            var lo = value.low,
+	                hi = value.high;
+	            if (this.littleEndian) {
+	                this.view[offset+3] = (lo >>> 24) & 0xFF;
+	                this.view[offset+2] = (lo >>> 16) & 0xFF;
+	                this.view[offset+1] = (lo >>>  8) & 0xFF;
+	                this.view[offset  ] =  lo         & 0xFF;
+	                offset += 4;
+	                this.view[offset+3] = (hi >>> 24) & 0xFF;
+	                this.view[offset+2] = (hi >>> 16) & 0xFF;
+	                this.view[offset+1] = (hi >>>  8) & 0xFF;
+	                this.view[offset  ] =  hi         & 0xFF;
+	            } else {
+	                this.view[offset  ] = (hi >>> 24) & 0xFF;
+	                this.view[offset+1] = (hi >>> 16) & 0xFF;
+	                this.view[offset+2] = (hi >>>  8) & 0xFF;
+	                this.view[offset+3] =  hi         & 0xFF;
+	                offset += 4;
+	                this.view[offset  ] = (lo >>> 24) & 0xFF;
+	                this.view[offset+1] = (lo >>> 16) & 0xFF;
+	                this.view[offset+2] = (lo >>>  8) & 0xFF;
+	                this.view[offset+3] =  lo         & 0xFF;
+	            }
+	            if (relative) this.offset += 8;
+	            return this;
+	        };
+
+	        /**
+	         * Writes a 64bit unsigned integer. This is an alias of {@link ByteBuffer#writeUint64}.
+	         * @function
+	         * @param {number|!Long} value Value to write
+	         * @param {number=} offset Offset to write to. Will use and increase {@link ByteBuffer#offset} by `8` if omitted.
+	         * @returns {!ByteBuffer} this
+	         * @expose
+	         */
+	        ByteBufferPrototype.writeUInt64 = ByteBufferPrototype.writeUint64;
+
+	        /**
+	         * Reads a 64bit unsigned integer.
+	         * @param {number=} offset Offset to read from. Will use and increase {@link ByteBuffer#offset} by `8` if omitted.
+	         * @returns {!Long}
+	         * @expose
+	         */
+	        ByteBufferPrototype.readUint64 = function(offset) {
+	            var relative = typeof offset === 'undefined';
+	            if (relative) offset = this.offset;
+	            if (!this.noAssert) {
+	                if (typeof offset !== 'number' || offset % 1 !== 0)
+	                    throw TypeError("Illegal offset: "+offset+" (not an integer)");
+	                offset >>>= 0;
+	                if (offset < 0 || offset + 8 > this.buffer.byteLength)
+	                    throw RangeError("Illegal offset: 0 <= "+offset+" (+"+8+") <= "+this.buffer.byteLength);
+	            }
+	            var lo = 0,
+	                hi = 0;
+	            if (this.littleEndian) {
+	                lo  = this.view[offset+2] << 16;
+	                lo |= this.view[offset+1] <<  8;
+	                lo |= this.view[offset  ];
+	                lo += this.view[offset+3] << 24 >>> 0;
+	                offset += 4;
+	                hi  = this.view[offset+2] << 16;
+	                hi |= this.view[offset+1] <<  8;
+	                hi |= this.view[offset  ];
+	                hi += this.view[offset+3] << 24 >>> 0;
+	            } else {
+	                hi  = this.view[offset+1] << 16;
+	                hi |= this.view[offset+2] <<  8;
+	                hi |= this.view[offset+3];
+	                hi += this.view[offset  ] << 24 >>> 0;
+	                offset += 4;
+	                lo  = this.view[offset+1] << 16;
+	                lo |= this.view[offset+2] <<  8;
+	                lo |= this.view[offset+3];
+	                lo += this.view[offset  ] << 24 >>> 0;
+	            }
+	            var value = new Long(lo, hi, true);
+	            if (relative) this.offset += 8;
+	            return value;
+	        };
+
+	        /**
+	         * Reads a 64bit unsigned integer. This is an alias of {@link ByteBuffer#readUint64}.
+	         * @function
+	         * @param {number=} offset Offset to read from. Will use and increase {@link ByteBuffer#offset} by `8` if omitted.
+	         * @returns {!Long}
+	         * @expose
+	         */
+	        ByteBufferPrototype.readUInt64 = ByteBufferPrototype.readUint64;
+
+	    } // Long
+
+
+	    // types/floats/float32
+
+	    /*
+	     ieee754 - https://github.com/feross/ieee754
+
+	     The MIT License (MIT)
+
+	     Copyright (c) Feross Aboukhadijeh
+
+	     Permission is hereby granted, free of charge, to any person obtaining a copy
+	     of this software and associated documentation files (the "Software"), to deal
+	     in the Software without restriction, including without limitation the rights
+	     to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+	     copies of the Software, and to permit persons to whom the Software is
+	     furnished to do so, subject to the following conditions:
+
+	     The above copyright notice and this permission notice shall be included in
+	     all copies or substantial portions of the Software.
+
+	     THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+	     IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+	     FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+	     AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+	     LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+	     OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+	     THE SOFTWARE.
+	    */
+
+	    /**
+	     * Reads an IEEE754 float from a byte array.
+	     * @param {!Array} buffer
+	     * @param {number} offset
+	     * @param {boolean} isLE
+	     * @param {number} mLen
+	     * @param {number} nBytes
+	     * @returns {number}
+	     * @inner
+	     */
+	    function ieee754_read(buffer, offset, isLE, mLen, nBytes) {
+	        var e, m,
+	            eLen = nBytes * 8 - mLen - 1,
+	            eMax = (1 << eLen) - 1,
+	            eBias = eMax >> 1,
+	            nBits = -7,
+	            i = isLE ? (nBytes - 1) : 0,
+	            d = isLE ? -1 : 1,
+	            s = buffer[offset + i];
+
+	        i += d;
+
+	        e = s & ((1 << (-nBits)) - 1);
+	        s >>= (-nBits);
+	        nBits += eLen;
+	        for (; nBits > 0; e = e * 256 + buffer[offset + i], i += d, nBits -= 8) {}
+
+	        m = e & ((1 << (-nBits)) - 1);
+	        e >>= (-nBits);
+	        nBits += mLen;
+	        for (; nBits > 0; m = m * 256 + buffer[offset + i], i += d, nBits -= 8) {}
+
+	        if (e === 0) {
+	            e = 1 - eBias;
+	        } else if (e === eMax) {
+	            return m ? NaN : ((s ? -1 : 1) * Infinity);
+	        } else {
+	            m = m + Math.pow(2, mLen);
+	            e = e - eBias;
+	        }
+	        return (s ? -1 : 1) * m * Math.pow(2, e - mLen);
+	    }
+
+	    /**
+	     * Writes an IEEE754 float to a byte array.
+	     * @param {!Array} buffer
+	     * @param {number} value
+	     * @param {number} offset
+	     * @param {boolean} isLE
+	     * @param {number} mLen
+	     * @param {number} nBytes
+	     * @inner
+	     */
+	    function ieee754_write(buffer, value, offset, isLE, mLen, nBytes) {
+	        var e, m, c,
+	            eLen = nBytes * 8 - mLen - 1,
+	            eMax = (1 << eLen) - 1,
+	            eBias = eMax >> 1,
+	            rt = (mLen === 23 ? Math.pow(2, -24) - Math.pow(2, -77) : 0),
+	            i = isLE ? 0 : (nBytes - 1),
+	            d = isLE ? 1 : -1,
+	            s = value < 0 || (value === 0 && 1 / value < 0) ? 1 : 0;
+
+	        value = Math.abs(value);
+
+	        if (isNaN(value) || value === Infinity) {
+	            m = isNaN(value) ? 1 : 0;
+	            e = eMax;
+	        } else {
+	            e = Math.floor(Math.log(value) / Math.LN2);
+	            if (value * (c = Math.pow(2, -e)) < 1) {
+	                e--;
+	                c *= 2;
+	            }
+	            if (e + eBias >= 1) {
+	                value += rt / c;
+	            } else {
+	                value += rt * Math.pow(2, 1 - eBias);
+	            }
+	            if (value * c >= 2) {
+	                e++;
+	                c /= 2;
+	            }
+
+	            if (e + eBias >= eMax) {
+	                m = 0;
+	                e = eMax;
+	            } else if (e + eBias >= 1) {
+	                m = (value * c - 1) * Math.pow(2, mLen);
+	                e = e + eBias;
+	            } else {
+	                m = value * Math.pow(2, eBias - 1) * Math.pow(2, mLen);
+	                e = 0;
+	            }
+	        }
+
+	        for (; mLen >= 8; buffer[offset + i] = m & 0xff, i += d, m /= 256, mLen -= 8) {}
+
+	        e = (e << mLen) | m;
+	        eLen += mLen;
+	        for (; eLen > 0; buffer[offset + i] = e & 0xff, i += d, e /= 256, eLen -= 8) {}
+
+	        buffer[offset + i - d] |= s * 128;
+	    }
+
+	    /**
+	     * Writes a 32bit float.
+	     * @param {number} value Value to write
+	     * @param {number=} offset Offset to write to. Will use and increase {@link ByteBuffer#offset} by `4` if omitted.
+	     * @returns {!ByteBuffer} this
+	     * @expose
+	     */
+	    ByteBufferPrototype.writeFloat32 = function(value, offset) {
+	        var relative = typeof offset === 'undefined';
+	        if (relative) offset = this.offset;
+	        if (!this.noAssert) {
+	            if (typeof value !== 'number')
+	                throw TypeError("Illegal value: "+value+" (not a number)");
+	            if (typeof offset !== 'number' || offset % 1 !== 0)
+	                throw TypeError("Illegal offset: "+offset+" (not an integer)");
+	            offset >>>= 0;
+	            if (offset < 0 || offset + 0 > this.buffer.byteLength)
+	                throw RangeError("Illegal offset: 0 <= "+offset+" (+"+0+") <= "+this.buffer.byteLength);
+	        }
+	        offset += 4;
+	        var capacity8 = this.buffer.byteLength;
+	        if (offset > capacity8)
+	            this.resize((capacity8 *= 2) > offset ? capacity8 : offset);
+	        offset -= 4;
+	        ieee754_write(this.view, value, offset, this.littleEndian, 23, 4);
+	        if (relative) this.offset += 4;
+	        return this;
+	    };
+
+	    /**
+	     * Writes a 32bit float. This is an alias of {@link ByteBuffer#writeFloat32}.
+	     * @function
+	     * @param {number} value Value to write
+	     * @param {number=} offset Offset to write to. Will use and increase {@link ByteBuffer#offset} by `4` if omitted.
+	     * @returns {!ByteBuffer} this
+	     * @expose
+	     */
+	    ByteBufferPrototype.writeFloat = ByteBufferPrototype.writeFloat32;
+
+	    /**
+	     * Reads a 32bit float.
+	     * @param {number=} offset Offset to read from. Will use and increase {@link ByteBuffer#offset} by `4` if omitted.
+	     * @returns {number}
+	     * @expose
+	     */
+	    ByteBufferPrototype.readFloat32 = function(offset) {
+	        var relative = typeof offset === 'undefined';
+	        if (relative) offset = this.offset;
+	        if (!this.noAssert) {
+	            if (typeof offset !== 'number' || offset % 1 !== 0)
+	                throw TypeError("Illegal offset: "+offset+" (not an integer)");
+	            offset >>>= 0;
+	            if (offset < 0 || offset + 4 > this.buffer.byteLength)
+	                throw RangeError("Illegal offset: 0 <= "+offset+" (+"+4+") <= "+this.buffer.byteLength);
+	        }
+	        var value = ieee754_read(this.view, offset, this.littleEndian, 23, 4);
+	        if (relative) this.offset += 4;
+	        return value;
+	    };
+
+	    /**
+	     * Reads a 32bit float. This is an alias of {@link ByteBuffer#readFloat32}.
+	     * @function
+	     * @param {number=} offset Offset to read from. Will use and increase {@link ByteBuffer#offset} by `4` if omitted.
+	     * @returns {number}
+	     * @expose
+	     */
+	    ByteBufferPrototype.readFloat = ByteBufferPrototype.readFloat32;
+
+	    // types/floats/float64
+
+	    /**
+	     * Writes a 64bit float.
+	     * @param {number} value Value to write
+	     * @param {number=} offset Offset to write to. Will use and increase {@link ByteBuffer#offset} by `8` if omitted.
+	     * @returns {!ByteBuffer} this
+	     * @expose
+	     */
+	    ByteBufferPrototype.writeFloat64 = function(value, offset) {
+	        var relative = typeof offset === 'undefined';
+	        if (relative) offset = this.offset;
+	        if (!this.noAssert) {
+	            if (typeof value !== 'number')
+	                throw TypeError("Illegal value: "+value+" (not a number)");
+	            if (typeof offset !== 'number' || offset % 1 !== 0)
+	                throw TypeError("Illegal offset: "+offset+" (not an integer)");
+	            offset >>>= 0;
+	            if (offset < 0 || offset + 0 > this.buffer.byteLength)
+	                throw RangeError("Illegal offset: 0 <= "+offset+" (+"+0+") <= "+this.buffer.byteLength);
+	        }
+	        offset += 8;
+	        var capacity9 = this.buffer.byteLength;
+	        if (offset > capacity9)
+	            this.resize((capacity9 *= 2) > offset ? capacity9 : offset);
+	        offset -= 8;
+	        ieee754_write(this.view, value, offset, this.littleEndian, 52, 8);
+	        if (relative) this.offset += 8;
+	        return this;
+	    };
+
+	    /**
+	     * Writes a 64bit float. This is an alias of {@link ByteBuffer#writeFloat64}.
+	     * @function
+	     * @param {number} value Value to write
+	     * @param {number=} offset Offset to write to. Will use and increase {@link ByteBuffer#offset} by `8` if omitted.
+	     * @returns {!ByteBuffer} this
+	     * @expose
+	     */
+	    ByteBufferPrototype.writeDouble = ByteBufferPrototype.writeFloat64;
+
+	    /**
+	     * Reads a 64bit float.
+	     * @param {number=} offset Offset to read from. Will use and increase {@link ByteBuffer#offset} by `8` if omitted.
+	     * @returns {number}
+	     * @expose
+	     */
+	    ByteBufferPrototype.readFloat64 = function(offset) {
+	        var relative = typeof offset === 'undefined';
+	        if (relative) offset = this.offset;
+	        if (!this.noAssert) {
+	            if (typeof offset !== 'number' || offset % 1 !== 0)
+	                throw TypeError("Illegal offset: "+offset+" (not an integer)");
+	            offset >>>= 0;
+	            if (offset < 0 || offset + 8 > this.buffer.byteLength)
+	                throw RangeError("Illegal offset: 0 <= "+offset+" (+"+8+") <= "+this.buffer.byteLength);
+	        }
+	        var value = ieee754_read(this.view, offset, this.littleEndian, 52, 8);
+	        if (relative) this.offset += 8;
+	        return value;
+	    };
+
+	    /**
+	     * Reads a 64bit float. This is an alias of {@link ByteBuffer#readFloat64}.
+	     * @function
+	     * @param {number=} offset Offset to read from. Will use and increase {@link ByteBuffer#offset} by `8` if omitted.
+	     * @returns {number}
+	     * @expose
+	     */
+	    ByteBufferPrototype.readDouble = ByteBufferPrototype.readFloat64;
+
+
+	    // types/varints/varint32
+
+	    /**
+	     * Maximum number of bytes required to store a 32bit base 128 variable-length integer.
+	     * @type {number}
+	     * @const
+	     * @expose
+	     */
+	    ByteBuffer.MAX_VARINT32_BYTES = 5;
+
+	    /**
+	     * Calculates the actual number of bytes required to store a 32bit base 128 variable-length integer.
+	     * @param {number} value Value to encode
+	     * @returns {number} Number of bytes required. Capped to {@link ByteBuffer.MAX_VARINT32_BYTES}
+	     * @expose
+	     */
+	    ByteBuffer.calculateVarint32 = function(value) {
+	        // ref: src/google/protobuf/io/coded_stream.cc
+	        value = value >>> 0;
+	             if (value < 1 << 7 ) return 1;
+	        else if (value < 1 << 14) return 2;
+	        else if (value < 1 << 21) return 3;
+	        else if (value < 1 << 28) return 4;
+	        else                      return 5;
+	    };
+
+	    /**
+	     * Zigzag encodes a signed 32bit integer so that it can be effectively used with varint encoding.
+	     * @param {number} n Signed 32bit integer
+	     * @returns {number} Unsigned zigzag encoded 32bit integer
+	     * @expose
+	     */
+	    ByteBuffer.zigZagEncode32 = function(n) {
+	        return (((n |= 0) << 1) ^ (n >> 31)) >>> 0; // ref: src/google/protobuf/wire_format_lite.h
+	    };
+
+	    /**
+	     * Decodes a zigzag encoded signed 32bit integer.
+	     * @param {number} n Unsigned zigzag encoded 32bit integer
+	     * @returns {number} Signed 32bit integer
+	     * @expose
+	     */
+	    ByteBuffer.zigZagDecode32 = function(n) {
+	        return ((n >>> 1) ^ -(n & 1)) | 0; // // ref: src/google/protobuf/wire_format_lite.h
+	    };
+
+	    /**
+	     * Writes a 32bit base 128 variable-length integer.
+	     * @param {number} value Value to write
+	     * @param {number=} offset Offset to write to. Will use and increase {@link ByteBuffer#offset} by the number of bytes
+	     *  written if omitted.
+	     * @returns {!ByteBuffer|number} this if `offset` is omitted, else the actual number of bytes written
+	     * @expose
+	     */
+	    ByteBufferPrototype.writeVarint32 = function(value, offset) {
+	        var relative = typeof offset === 'undefined';
+	        if (relative) offset = this.offset;
+	        if (!this.noAssert) {
+	            if (typeof value !== 'number' || value % 1 !== 0)
+	                throw TypeError("Illegal value: "+value+" (not an integer)");
+	            value |= 0;
+	            if (typeof offset !== 'number' || offset % 1 !== 0)
+	                throw TypeError("Illegal offset: "+offset+" (not an integer)");
+	            offset >>>= 0;
+	            if (offset < 0 || offset + 0 > this.buffer.byteLength)
+	                throw RangeError("Illegal offset: 0 <= "+offset+" (+"+0+") <= "+this.buffer.byteLength);
+	        }
+	        var size = ByteBuffer.calculateVarint32(value),
+	            b;
+	        offset += size;
+	        var capacity10 = this.buffer.byteLength;
+	        if (offset > capacity10)
+	            this.resize((capacity10 *= 2) > offset ? capacity10 : offset);
+	        offset -= size;
+	        value >>>= 0;
+	        while (value >= 0x80) {
+	            b = (value & 0x7f) | 0x80;
+	            this.view[offset++] = b;
+	            value >>>= 7;
+	        }
+	        this.view[offset++] = value;
+	        if (relative) {
+	            this.offset = offset;
+	            return this;
+	        }
+	        return size;
+	    };
+
+	    /**
+	     * Writes a zig-zag encoded (signed) 32bit base 128 variable-length integer.
+	     * @param {number} value Value to write
+	     * @param {number=} offset Offset to write to. Will use and increase {@link ByteBuffer#offset} by the number of bytes
+	     *  written if omitted.
+	     * @returns {!ByteBuffer|number} this if `offset` is omitted, else the actual number of bytes written
+	     * @expose
+	     */
+	    ByteBufferPrototype.writeVarint32ZigZag = function(value, offset) {
+	        return this.writeVarint32(ByteBuffer.zigZagEncode32(value), offset);
+	    };
+
+	    /**
+	     * Reads a 32bit base 128 variable-length integer.
+	     * @param {number=} offset Offset to read from. Will use and increase {@link ByteBuffer#offset} by the number of bytes
+	     *  written if omitted.
+	     * @returns {number|!{value: number, length: number}} The value read if offset is omitted, else the value read
+	     *  and the actual number of bytes read.
+	     * @throws {Error} If it's not a valid varint. Has a property `truncated = true` if there is not enough data available
+	     *  to fully decode the varint.
+	     * @expose
+	     */
+	    ByteBufferPrototype.readVarint32 = function(offset) {
+	        var relative = typeof offset === 'undefined';
+	        if (relative) offset = this.offset;
+	        if (!this.noAssert) {
+	            if (typeof offset !== 'number' || offset % 1 !== 0)
+	                throw TypeError("Illegal offset: "+offset+" (not an integer)");
+	            offset >>>= 0;
+	            if (offset < 0 || offset + 1 > this.buffer.byteLength)
+	                throw RangeError("Illegal offset: 0 <= "+offset+" (+"+1+") <= "+this.buffer.byteLength);
+	        }
+	        var c = 0,
+	            value = 0 >>> 0,
+	            b;
+	        do {
+	            if (!this.noAssert && offset > this.limit) {
+	                var err = Error("Truncated");
+	                err['truncated'] = true;
+	                throw err;
+	            }
+	            b = this.view[offset++];
+	            if (c < 5)
+	                value |= (b & 0x7f) << (7*c);
+	            ++c;
+	        } while ((b & 0x80) !== 0);
+	        value |= 0;
+	        if (relative) {
+	            this.offset = offset;
+	            return value;
+	        }
+	        return {
+	            "value": value,
+	            "length": c
+	        };
+	    };
+
+	    /**
+	     * Reads a zig-zag encoded (signed) 32bit base 128 variable-length integer.
+	     * @param {number=} offset Offset to read from. Will use and increase {@link ByteBuffer#offset} by the number of bytes
+	     *  written if omitted.
+	     * @returns {number|!{value: number, length: number}} The value read if offset is omitted, else the value read
+	     *  and the actual number of bytes read.
+	     * @throws {Error} If it's not a valid varint
+	     * @expose
+	     */
+	    ByteBufferPrototype.readVarint32ZigZag = function(offset) {
+	        var val = this.readVarint32(offset);
+	        if (typeof val === 'object')
+	            val["value"] = ByteBuffer.zigZagDecode32(val["value"]);
+	        else
+	            val = ByteBuffer.zigZagDecode32(val);
+	        return val;
+	    };
+
+	    // types/varints/varint64
+
+	    if (Long) {
+
+	        /**
+	         * Maximum number of bytes required to store a 64bit base 128 variable-length integer.
+	         * @type {number}
+	         * @const
+	         * @expose
+	         */
+	        ByteBuffer.MAX_VARINT64_BYTES = 10;
+
+	        /**
+	         * Calculates the actual number of bytes required to store a 64bit base 128 variable-length integer.
+	         * @param {number|!Long} value Value to encode
+	         * @returns {number} Number of bytes required. Capped to {@link ByteBuffer.MAX_VARINT64_BYTES}
+	         * @expose
+	         */
+	        ByteBuffer.calculateVarint64 = function(value) {
+	            if (typeof value === 'number')
+	                value = Long.fromNumber(value);
+	            else if (typeof value === 'string')
+	                value = Long.fromString(value);
+	            // ref: src/google/protobuf/io/coded_stream.cc
+	            var part0 = value.toInt() >>> 0,
+	                part1 = value.shiftRightUnsigned(28).toInt() >>> 0,
+	                part2 = value.shiftRightUnsigned(56).toInt() >>> 0;
+	            if (part2 == 0) {
+	                if (part1 == 0) {
+	                    if (part0 < 1 << 14)
+	                        return part0 < 1 << 7 ? 1 : 2;
+	                    else
+	                        return part0 < 1 << 21 ? 3 : 4;
+	                } else {
+	                    if (part1 < 1 << 14)
+	                        return part1 < 1 << 7 ? 5 : 6;
+	                    else
+	                        return part1 < 1 << 21 ? 7 : 8;
+	                }
+	            } else
+	                return part2 < 1 << 7 ? 9 : 10;
+	        };
+
+	        /**
+	         * Zigzag encodes a signed 64bit integer so that it can be effectively used with varint encoding.
+	         * @param {number|!Long} value Signed long
+	         * @returns {!Long} Unsigned zigzag encoded long
+	         * @expose
+	         */
+	        ByteBuffer.zigZagEncode64 = function(value) {
+	            if (typeof value === 'number')
+	                value = Long.fromNumber(value, false);
+	            else if (typeof value === 'string')
+	                value = Long.fromString(value, false);
+	            else if (value.unsigned !== false) value = value.toSigned();
+	            // ref: src/google/protobuf/wire_format_lite.h
+	            return value.shiftLeft(1).xor(value.shiftRight(63)).toUnsigned();
+	        };
+
+	        /**
+	         * Decodes a zigzag encoded signed 64bit integer.
+	         * @param {!Long|number} value Unsigned zigzag encoded long or JavaScript number
+	         * @returns {!Long} Signed long
+	         * @expose
+	         */
+	        ByteBuffer.zigZagDecode64 = function(value) {
+	            if (typeof value === 'number')
+	                value = Long.fromNumber(value, false);
+	            else if (typeof value === 'string')
+	                value = Long.fromString(value, false);
+	            else if (value.unsigned !== false) value = value.toSigned();
+	            // ref: src/google/protobuf/wire_format_lite.h
+	            return value.shiftRightUnsigned(1).xor(value.and(Long.ONE).toSigned().negate()).toSigned();
+	        };
+
+	        /**
+	         * Writes a 64bit base 128 variable-length integer.
+	         * @param {number|Long} value Value to write
+	         * @param {number=} offset Offset to write to. Will use and increase {@link ByteBuffer#offset} by the number of bytes
+	         *  written if omitted.
+	         * @returns {!ByteBuffer|number} `this` if offset is omitted, else the actual number of bytes written.
+	         * @expose
+	         */
+	        ByteBufferPrototype.writeVarint64 = function(value, offset) {
+	            var relative = typeof offset === 'undefined';
+	            if (relative) offset = this.offset;
+	            if (!this.noAssert) {
+	                if (typeof value === 'number')
+	                    value = Long.fromNumber(value);
+	                else if (typeof value === 'string')
+	                    value = Long.fromString(value);
+	                else if (!(value && value instanceof Long))
+	                    throw TypeError("Illegal value: "+value+" (not an integer or Long)");
+	                if (typeof offset !== 'number' || offset % 1 !== 0)
+	                    throw TypeError("Illegal offset: "+offset+" (not an integer)");
+	                offset >>>= 0;
+	                if (offset < 0 || offset + 0 > this.buffer.byteLength)
+	                    throw RangeError("Illegal offset: 0 <= "+offset+" (+"+0+") <= "+this.buffer.byteLength);
+	            }
+	            if (typeof value === 'number')
+	                value = Long.fromNumber(value, false);
+	            else if (typeof value === 'string')
+	                value = Long.fromString(value, false);
+	            else if (value.unsigned !== false) value = value.toSigned();
+	            var size = ByteBuffer.calculateVarint64(value),
+	                part0 = value.toInt() >>> 0,
+	                part1 = value.shiftRightUnsigned(28).toInt() >>> 0,
+	                part2 = value.shiftRightUnsigned(56).toInt() >>> 0;
+	            offset += size;
+	            var capacity11 = this.buffer.byteLength;
+	            if (offset > capacity11)
+	                this.resize((capacity11 *= 2) > offset ? capacity11 : offset);
+	            offset -= size;
+	            switch (size) {
+	                case 10: this.view[offset+9] = (part2 >>>  7) & 0x01;
+	                case 9 : this.view[offset+8] = size !== 9 ? (part2       ) | 0x80 : (part2       ) & 0x7F;
+	                case 8 : this.view[offset+7] = size !== 8 ? (part1 >>> 21) | 0x80 : (part1 >>> 21) & 0x7F;
+	                case 7 : this.view[offset+6] = size !== 7 ? (part1 >>> 14) | 0x80 : (part1 >>> 14) & 0x7F;
+	                case 6 : this.view[offset+5] = size !== 6 ? (part1 >>>  7) | 0x80 : (part1 >>>  7) & 0x7F;
+	                case 5 : this.view[offset+4] = size !== 5 ? (part1       ) | 0x80 : (part1       ) & 0x7F;
+	                case 4 : this.view[offset+3] = size !== 4 ? (part0 >>> 21) | 0x80 : (part0 >>> 21) & 0x7F;
+	                case 3 : this.view[offset+2] = size !== 3 ? (part0 >>> 14) | 0x80 : (part0 >>> 14) & 0x7F;
+	                case 2 : this.view[offset+1] = size !== 2 ? (part0 >>>  7) | 0x80 : (part0 >>>  7) & 0x7F;
+	                case 1 : this.view[offset  ] = size !== 1 ? (part0       ) | 0x80 : (part0       ) & 0x7F;
+	            }
+	            if (relative) {
+	                this.offset += size;
+	                return this;
+	            } else {
+	                return size;
+	            }
+	        };
+
+	        /**
+	         * Writes a zig-zag encoded 64bit base 128 variable-length integer.
+	         * @param {number|Long} value Value to write
+	         * @param {number=} offset Offset to write to. Will use and increase {@link ByteBuffer#offset} by the number of bytes
+	         *  written if omitted.
+	         * @returns {!ByteBuffer|number} `this` if offset is omitted, else the actual number of bytes written.
+	         * @expose
+	         */
+	        ByteBufferPrototype.writeVarint64ZigZag = function(value, offset) {
+	            return this.writeVarint64(ByteBuffer.zigZagEncode64(value), offset);
+	        };
+
+	        /**
+	         * Reads a 64bit base 128 variable-length integer. Requires Long.js.
+	         * @param {number=} offset Offset to read from. Will use and increase {@link ByteBuffer#offset} by the number of bytes
+	         *  read if omitted.
+	         * @returns {!Long|!{value: Long, length: number}} The value read if offset is omitted, else the value read and
+	         *  the actual number of bytes read.
+	         * @throws {Error} If it's not a valid varint
+	         * @expose
+	         */
+	        ByteBufferPrototype.readVarint64 = function(offset) {
+	            var relative = typeof offset === 'undefined';
+	            if (relative) offset = this.offset;
+	            if (!this.noAssert) {
+	                if (typeof offset !== 'number' || offset % 1 !== 0)
+	                    throw TypeError("Illegal offset: "+offset+" (not an integer)");
+	                offset >>>= 0;
+	                if (offset < 0 || offset + 1 > this.buffer.byteLength)
+	                    throw RangeError("Illegal offset: 0 <= "+offset+" (+"+1+") <= "+this.buffer.byteLength);
+	            }
+	            // ref: src/google/protobuf/io/coded_stream.cc
+	            var start = offset,
+	                part0 = 0,
+	                part1 = 0,
+	                part2 = 0,
+	                b  = 0;
+	            b = this.view[offset++]; part0  = (b & 0x7F)      ; if ( b & 0x80                                                   ) {
+	            b = this.view[offset++]; part0 |= (b & 0x7F) <<  7; if ((b & 0x80) || (this.noAssert && typeof b === 'undefined')) {
+	            b = this.view[offset++]; part0 |= (b & 0x7F) << 14; if ((b & 0x80) || (this.noAssert && typeof b === 'undefined')) {
+	            b = this.view[offset++]; part0 |= (b & 0x7F) << 21; if ((b & 0x80) || (this.noAssert && typeof b === 'undefined')) {
+	            b = this.view[offset++]; part1  = (b & 0x7F)      ; if ((b & 0x80) || (this.noAssert && typeof b === 'undefined')) {
+	            b = this.view[offset++]; part1 |= (b & 0x7F) <<  7; if ((b & 0x80) || (this.noAssert && typeof b === 'undefined')) {
+	            b = this.view[offset++]; part1 |= (b & 0x7F) << 14; if ((b & 0x80) || (this.noAssert && typeof b === 'undefined')) {
+	            b = this.view[offset++]; part1 |= (b & 0x7F) << 21; if ((b & 0x80) || (this.noAssert && typeof b === 'undefined')) {
+	            b = this.view[offset++]; part2  = (b & 0x7F)      ; if ((b & 0x80) || (this.noAssert && typeof b === 'undefined')) {
+	            b = this.view[offset++]; part2 |= (b & 0x7F) <<  7; if ((b & 0x80) || (this.noAssert && typeof b === 'undefined')) {
+	            throw Error("Buffer overrun"); }}}}}}}}}}
+	            var value = Long.fromBits(part0 | (part1 << 28), (part1 >>> 4) | (part2) << 24, false);
+	            if (relative) {
+	                this.offset = offset;
+	                return value;
+	            } else {
+	                return {
+	                    'value': value,
+	                    'length': offset-start
+	                };
+	            }
+	        };
+
+	        /**
+	         * Reads a zig-zag encoded 64bit base 128 variable-length integer. Requires Long.js.
+	         * @param {number=} offset Offset to read from. Will use and increase {@link ByteBuffer#offset} by the number of bytes
+	         *  read if omitted.
+	         * @returns {!Long|!{value: Long, length: number}} The value read if offset is omitted, else the value read and
+	         *  the actual number of bytes read.
+	         * @throws {Error} If it's not a valid varint
+	         * @expose
+	         */
+	        ByteBufferPrototype.readVarint64ZigZag = function(offset) {
+	            var val = this.readVarint64(offset);
+	            if (val && val['value'] instanceof Long)
+	                val["value"] = ByteBuffer.zigZagDecode64(val["value"]);
+	            else
+	                val = ByteBuffer.zigZagDecode64(val);
+	            return val;
+	        };
+
+	    } // Long
+
+
+	    // types/strings/cstring
+
+	    /**
+	     * Writes a NULL-terminated UTF8 encoded string. For this to work the specified string must not contain any NULL
+	     *  characters itself.
+	     * @param {string} str String to write
+	     * @param {number=} offset Offset to write to. Will use and increase {@link ByteBuffer#offset} by the number of bytes
+	     *  contained in `str` + 1 if omitted.
+	     * @returns {!ByteBuffer|number} this if offset is omitted, else the actual number of bytes written
+	     * @expose
+	     */
+	    ByteBufferPrototype.writeCString = function(str, offset) {
+	        var relative = typeof offset === 'undefined';
+	        if (relative) offset = this.offset;
+	        var i,
+	            k = str.length;
+	        if (!this.noAssert) {
+	            if (typeof str !== 'string')
+	                throw TypeError("Illegal str: Not a string");
+	            for (i=0; i<k; ++i) {
+	                if (str.charCodeAt(i) === 0)
+	                    throw RangeError("Illegal str: Contains NULL-characters");
+	            }
+	            if (typeof offset !== 'number' || offset % 1 !== 0)
+	                throw TypeError("Illegal offset: "+offset+" (not an integer)");
+	            offset >>>= 0;
+	            if (offset < 0 || offset + 0 > this.buffer.byteLength)
+	                throw RangeError("Illegal offset: 0 <= "+offset+" (+"+0+") <= "+this.buffer.byteLength);
+	        }
+	        // UTF8 strings do not contain zero bytes in between except for the zero character, so:
+	        k = utfx.calculateUTF16asUTF8(stringSource(str))[1];
+	        offset += k+1;
+	        var capacity12 = this.buffer.byteLength;
+	        if (offset > capacity12)
+	            this.resize((capacity12 *= 2) > offset ? capacity12 : offset);
+	        offset -= k+1;
+	        utfx.encodeUTF16toUTF8(stringSource(str), function(b) {
+	            this.view[offset++] = b;
+	        }.bind(this));
+	        this.view[offset++] = 0;
+	        if (relative) {
+	            this.offset = offset;
+	            return this;
+	        }
+	        return k;
+	    };
+
+	    /**
+	     * Reads a NULL-terminated UTF8 encoded string. For this to work the string read must not contain any NULL characters
+	     *  itself.
+	     * @param {number=} offset Offset to read from. Will use and increase {@link ByteBuffer#offset} by the number of bytes
+	     *  read if omitted.
+	     * @returns {string|!{string: string, length: number}} The string read if offset is omitted, else the string
+	     *  read and the actual number of bytes read.
+	     * @expose
+	     */
+	    ByteBufferPrototype.readCString = function(offset) {
+	        var relative = typeof offset === 'undefined';
+	        if (relative) offset = this.offset;
+	        if (!this.noAssert) {
+	            if (typeof offset !== 'number' || offset % 1 !== 0)
+	                throw TypeError("Illegal offset: "+offset+" (not an integer)");
+	            offset >>>= 0;
+	            if (offset < 0 || offset + 1 > this.buffer.byteLength)
+	                throw RangeError("Illegal offset: 0 <= "+offset+" (+"+1+") <= "+this.buffer.byteLength);
+	        }
+	        var start = offset,
+	            temp;
+	        // UTF8 strings do not contain zero bytes in between except for the zero character itself, so:
+	        var sd, b = -1;
+	        utfx.decodeUTF8toUTF16(function() {
+	            if (b === 0) return null;
+	            if (offset >= this.limit)
+	                throw RangeError("Illegal range: Truncated data, "+offset+" < "+this.limit);
+	            b = this.view[offset++];
+	            return b === 0 ? null : b;
+	        }.bind(this), sd = stringDestination(), true);
+	        if (relative) {
+	            this.offset = offset;
+	            return sd();
+	        } else {
+	            return {
+	                "string": sd(),
+	                "length": offset - start
+	            };
+	        }
+	    };
+
+	    // types/strings/istring
+
+	    /**
+	     * Writes a length as uint32 prefixed UTF8 encoded string.
+	     * @param {string} str String to write
+	     * @param {number=} offset Offset to write to. Will use and increase {@link ByteBuffer#offset} by the number of bytes
+	     *  written if omitted.
+	     * @returns {!ByteBuffer|number} `this` if `offset` is omitted, else the actual number of bytes written
+	     * @expose
+	     * @see ByteBuffer#writeVarint32
+	     */
+	    ByteBufferPrototype.writeIString = function(str, offset) {
+	        var relative = typeof offset === 'undefined';
+	        if (relative) offset = this.offset;
+	        if (!this.noAssert) {
+	            if (typeof str !== 'string')
+	                throw TypeError("Illegal str: Not a string");
+	            if (typeof offset !== 'number' || offset % 1 !== 0)
+	                throw TypeError("Illegal offset: "+offset+" (not an integer)");
+	            offset >>>= 0;
+	            if (offset < 0 || offset + 0 > this.buffer.byteLength)
+	                throw RangeError("Illegal offset: 0 <= "+offset+" (+"+0+") <= "+this.buffer.byteLength);
+	        }
+	        var start = offset,
+	            k;
+	        k = utfx.calculateUTF16asUTF8(stringSource(str), this.noAssert)[1];
+	        offset += 4+k;
+	        var capacity13 = this.buffer.byteLength;
+	        if (offset > capacity13)
+	            this.resize((capacity13 *= 2) > offset ? capacity13 : offset);
+	        offset -= 4+k;
+	        if (this.littleEndian) {
+	            this.view[offset+3] = (k >>> 24) & 0xFF;
+	            this.view[offset+2] = (k >>> 16) & 0xFF;
+	            this.view[offset+1] = (k >>>  8) & 0xFF;
+	            this.view[offset  ] =  k         & 0xFF;
+	        } else {
+	            this.view[offset  ] = (k >>> 24) & 0xFF;
+	            this.view[offset+1] = (k >>> 16) & 0xFF;
+	            this.view[offset+2] = (k >>>  8) & 0xFF;
+	            this.view[offset+3] =  k         & 0xFF;
+	        }
+	        offset += 4;
+	        utfx.encodeUTF16toUTF8(stringSource(str), function(b) {
+	            this.view[offset++] = b;
+	        }.bind(this));
+	        if (offset !== start + 4 + k)
+	            throw RangeError("Illegal range: Truncated data, "+offset+" == "+(offset+4+k));
+	        if (relative) {
+	            this.offset = offset;
+	            return this;
+	        }
+	        return offset - start;
+	    };
+
+	    /**
+	     * Reads a length as uint32 prefixed UTF8 encoded string.
+	     * @param {number=} offset Offset to read from. Will use and increase {@link ByteBuffer#offset} by the number of bytes
+	     *  read if omitted.
+	     * @returns {string|!{string: string, length: number}} The string read if offset is omitted, else the string
+	     *  read and the actual number of bytes read.
+	     * @expose
+	     * @see ByteBuffer#readVarint32
+	     */
+	    ByteBufferPrototype.readIString = function(offset) {
+	        var relative = typeof offset === 'undefined';
+	        if (relative) offset = this.offset;
+	        if (!this.noAssert) {
+	            if (typeof offset !== 'number' || offset % 1 !== 0)
+	                throw TypeError("Illegal offset: "+offset+" (not an integer)");
+	            offset >>>= 0;
+	            if (offset < 0 || offset + 4 > this.buffer.byteLength)
+	                throw RangeError("Illegal offset: 0 <= "+offset+" (+"+4+") <= "+this.buffer.byteLength);
+	        }
+	        var start = offset;
+	        var len = this.readUint32(offset);
+	        var str = this.readUTF8String(len, ByteBuffer.METRICS_BYTES, offset += 4);
+	        offset += str['length'];
+	        if (relative) {
+	            this.offset = offset;
+	            return str['string'];
+	        } else {
+	            return {
+	                'string': str['string'],
+	                'length': offset - start
+	            };
+	        }
+	    };
+
+	    // types/strings/utf8string
+
+	    /**
+	     * Metrics representing number of UTF8 characters. Evaluates to `c`.
+	     * @type {string}
+	     * @const
+	     * @expose
+	     */
+	    ByteBuffer.METRICS_CHARS = 'c';
+
+	    /**
+	     * Metrics representing number of bytes. Evaluates to `b`.
+	     * @type {string}
+	     * @const
+	     * @expose
+	     */
+	    ByteBuffer.METRICS_BYTES = 'b';
+
+	    /**
+	     * Writes an UTF8 encoded string.
+	     * @param {string} str String to write
+	     * @param {number=} offset Offset to write to. Will use and increase {@link ByteBuffer#offset} if omitted.
+	     * @returns {!ByteBuffer|number} this if offset is omitted, else the actual number of bytes written.
+	     * @expose
+	     */
+	    ByteBufferPrototype.writeUTF8String = function(str, offset) {
+	        var relative = typeof offset === 'undefined';
+	        if (relative) offset = this.offset;
+	        if (!this.noAssert) {
+	            if (typeof offset !== 'number' || offset % 1 !== 0)
+	                throw TypeError("Illegal offset: "+offset+" (not an integer)");
+	            offset >>>= 0;
+	            if (offset < 0 || offset + 0 > this.buffer.byteLength)
+	                throw RangeError("Illegal offset: 0 <= "+offset+" (+"+0+") <= "+this.buffer.byteLength);
+	        }
+	        var k;
+	        var start = offset;
+	        k = utfx.calculateUTF16asUTF8(stringSource(str))[1];
+	        offset += k;
+	        var capacity14 = this.buffer.byteLength;
+	        if (offset > capacity14)
+	            this.resize((capacity14 *= 2) > offset ? capacity14 : offset);
+	        offset -= k;
+	        utfx.encodeUTF16toUTF8(stringSource(str), function(b) {
+	            this.view[offset++] = b;
+	        }.bind(this));
+	        if (relative) {
+	            this.offset = offset;
+	            return this;
+	        }
+	        return offset - start;
+	    };
+
+	    /**
+	     * Writes an UTF8 encoded string. This is an alias of {@link ByteBuffer#writeUTF8String}.
+	     * @function
+	     * @param {string} str String to write
+	     * @param {number=} offset Offset to write to. Will use and increase {@link ByteBuffer#offset} if omitted.
+	     * @returns {!ByteBuffer|number} this if offset is omitted, else the actual number of bytes written.
+	     * @expose
+	     */
+	    ByteBufferPrototype.writeString = ByteBufferPrototype.writeUTF8String;
+
+	    /**
+	     * Calculates the number of UTF8 characters of a string. JavaScript itself uses UTF-16, so that a string's
+	     *  `length` property does not reflect its actual UTF8 size if it contains code points larger than 0xFFFF.
+	     * @param {string} str String to calculate
+	     * @returns {number} Number of UTF8 characters
+	     * @expose
+	     */
+	    ByteBuffer.calculateUTF8Chars = function(str) {
+	        return utfx.calculateUTF16asUTF8(stringSource(str))[0];
+	    };
+
+	    /**
+	     * Calculates the number of UTF8 bytes of a string.
+	     * @param {string} str String to calculate
+	     * @returns {number} Number of UTF8 bytes
+	     * @expose
+	     */
+	    ByteBuffer.calculateUTF8Bytes = function(str) {
+	        return utfx.calculateUTF16asUTF8(stringSource(str))[1];
+	    };
+
+	    /**
+	     * Calculates the number of UTF8 bytes of a string. This is an alias of {@link ByteBuffer.calculateUTF8Bytes}.
+	     * @function
+	     * @param {string} str String to calculate
+	     * @returns {number} Number of UTF8 bytes
+	     * @expose
+	     */
+	    ByteBuffer.calculateString = ByteBuffer.calculateUTF8Bytes;
+
+	    /**
+	     * Reads an UTF8 encoded string.
+	     * @param {number} length Number of characters or bytes to read.
+	     * @param {string=} metrics Metrics specifying what `length` is meant to count. Defaults to
+	     *  {@link ByteBuffer.METRICS_CHARS}.
+	     * @param {number=} offset Offset to read from. Will use and increase {@link ByteBuffer#offset} by the number of bytes
+	     *  read if omitted.
+	     * @returns {string|!{string: string, length: number}} The string read if offset is omitted, else the string
+	     *  read and the actual number of bytes read.
+	     * @expose
+	     */
+	    ByteBufferPrototype.readUTF8String = function(length, metrics, offset) {
+	        if (typeof metrics === 'number') {
+	            offset = metrics;
+	            metrics = undefined;
+	        }
+	        var relative = typeof offset === 'undefined';
+	        if (relative) offset = this.offset;
+	        if (typeof metrics === 'undefined') metrics = ByteBuffer.METRICS_CHARS;
+	        if (!this.noAssert) {
+	            if (typeof length !== 'number' || length % 1 !== 0)
+	                throw TypeError("Illegal length: "+length+" (not an integer)");
+	            length |= 0;
+	            if (typeof offset !== 'number' || offset % 1 !== 0)
+	                throw TypeError("Illegal offset: "+offset+" (not an integer)");
+	            offset >>>= 0;
+	            if (offset < 0 || offset + 0 > this.buffer.byteLength)
+	                throw RangeError("Illegal offset: 0 <= "+offset+" (+"+0+") <= "+this.buffer.byteLength);
+	        }
+	        var i = 0,
+	            start = offset,
+	            sd;
+	        if (metrics === ByteBuffer.METRICS_CHARS) { // The same for node and the browser
+	            sd = stringDestination();
+	            utfx.decodeUTF8(function() {
+	                return i < length && offset < this.limit ? this.view[offset++] : null;
+	            }.bind(this), function(cp) {
+	                ++i; utfx.UTF8toUTF16(cp, sd);
+	            });
+	            if (i !== length)
+	                throw RangeError("Illegal range: Truncated data, "+i+" == "+length);
+	            if (relative) {
+	                this.offset = offset;
+	                return sd();
+	            } else {
+	                return {
+	                    "string": sd(),
+	                    "length": offset - start
+	                };
+	            }
+	        } else if (metrics === ByteBuffer.METRICS_BYTES) {
+	            if (!this.noAssert) {
+	                if (typeof offset !== 'number' || offset % 1 !== 0)
+	                    throw TypeError("Illegal offset: "+offset+" (not an integer)");
+	                offset >>>= 0;
+	                if (offset < 0 || offset + length > this.buffer.byteLength)
+	                    throw RangeError("Illegal offset: 0 <= "+offset+" (+"+length+") <= "+this.buffer.byteLength);
+	            }
+	            var k = offset + length;
+	            utfx.decodeUTF8toUTF16(function() {
+	                return offset < k ? this.view[offset++] : null;
+	            }.bind(this), sd = stringDestination(), this.noAssert);
+	            if (offset !== k)
+	                throw RangeError("Illegal range: Truncated data, "+offset+" == "+k);
+	            if (relative) {
+	                this.offset = offset;
+	                return sd();
+	            } else {
+	                return {
+	                    'string': sd(),
+	                    'length': offset - start
+	                };
+	            }
+	        } else
+	            throw TypeError("Unsupported metrics: "+metrics);
+	    };
+
+	    /**
+	     * Reads an UTF8 encoded string. This is an alias of {@link ByteBuffer#readUTF8String}.
+	     * @function
+	     * @param {number} length Number of characters or bytes to read
+	     * @param {number=} metrics Metrics specifying what `n` is meant to count. Defaults to
+	     *  {@link ByteBuffer.METRICS_CHARS}.
+	     * @param {number=} offset Offset to read from. Will use and increase {@link ByteBuffer#offset} by the number of bytes
+	     *  read if omitted.
+	     * @returns {string|!{string: string, length: number}} The string read if offset is omitted, else the string
+	     *  read and the actual number of bytes read.
+	     * @expose
+	     */
+	    ByteBufferPrototype.readString = ByteBufferPrototype.readUTF8String;
+
+	    // types/strings/vstring
+
+	    /**
+	     * Writes a length as varint32 prefixed UTF8 encoded string.
+	     * @param {string} str String to write
+	     * @param {number=} offset Offset to write to. Will use and increase {@link ByteBuffer#offset} by the number of bytes
+	     *  written if omitted.
+	     * @returns {!ByteBuffer|number} `this` if `offset` is omitted, else the actual number of bytes written
+	     * @expose
+	     * @see ByteBuffer#writeVarint32
+	     */
+	    ByteBufferPrototype.writeVString = function(str, offset) {
+	        var relative = typeof offset === 'undefined';
+	        if (relative) offset = this.offset;
+	        if (!this.noAssert) {
+	            if (typeof str !== 'string')
+	                throw TypeError("Illegal str: Not a string");
+	            if (typeof offset !== 'number' || offset % 1 !== 0)
+	                throw TypeError("Illegal offset: "+offset+" (not an integer)");
+	            offset >>>= 0;
+	            if (offset < 0 || offset + 0 > this.buffer.byteLength)
+	                throw RangeError("Illegal offset: 0 <= "+offset+" (+"+0+") <= "+this.buffer.byteLength);
+	        }
+	        var start = offset,
+	            k, l;
+	        k = utfx.calculateUTF16asUTF8(stringSource(str), this.noAssert)[1];
+	        l = ByteBuffer.calculateVarint32(k);
+	        offset += l+k;
+	        var capacity15 = this.buffer.byteLength;
+	        if (offset > capacity15)
+	            this.resize((capacity15 *= 2) > offset ? capacity15 : offset);
+	        offset -= l+k;
+	        offset += this.writeVarint32(k, offset);
+	        utfx.encodeUTF16toUTF8(stringSource(str), function(b) {
+	            this.view[offset++] = b;
+	        }.bind(this));
+	        if (offset !== start+k+l)
+	            throw RangeError("Illegal range: Truncated data, "+offset+" == "+(offset+k+l));
+	        if (relative) {
+	            this.offset = offset;
+	            return this;
+	        }
+	        return offset - start;
+	    };
+
+	    /**
+	     * Reads a length as varint32 prefixed UTF8 encoded string.
+	     * @param {number=} offset Offset to read from. Will use and increase {@link ByteBuffer#offset} by the number of bytes
+	     *  read if omitted.
+	     * @returns {string|!{string: string, length: number}} The string read if offset is omitted, else the string
+	     *  read and the actual number of bytes read.
+	     * @expose
+	     * @see ByteBuffer#readVarint32
+	     */
+	    ByteBufferPrototype.readVString = function(offset) {
+	        var relative = typeof offset === 'undefined';
+	        if (relative) offset = this.offset;
+	        if (!this.noAssert) {
+	            if (typeof offset !== 'number' || offset % 1 !== 0)
+	                throw TypeError("Illegal offset: "+offset+" (not an integer)");
+	            offset >>>= 0;
+	            if (offset < 0 || offset + 1 > this.buffer.byteLength)
+	                throw RangeError("Illegal offset: 0 <= "+offset+" (+"+1+") <= "+this.buffer.byteLength);
+	        }
+	        var start = offset;
+	        var len = this.readVarint32(offset);
+	        var str = this.readUTF8String(len['value'], ByteBuffer.METRICS_BYTES, offset += len['length']);
+	        offset += str['length'];
+	        if (relative) {
+	            this.offset = offset;
+	            return str['string'];
+	        } else {
+	            return {
+	                'string': str['string'],
+	                'length': offset - start
+	            };
+	        }
+	    };
+
+
+	    /**
+	     * Appends some data to this ByteBuffer. This will overwrite any contents behind the specified offset up to the appended
+	     *  data's length.
+	     * @param {!ByteBuffer|!ArrayBuffer|!Uint8Array|string} source Data to append. If `source` is a ByteBuffer, its offsets
+	     *  will be modified according to the performed read operation.
+	     * @param {(string|number)=} encoding Encoding if `data` is a string ("base64", "hex", "binary", defaults to "utf8")
+	     * @param {number=} offset Offset to append at. Will use and increase {@link ByteBuffer#offset} by the number of bytes
+	     *  written if omitted.
+	     * @returns {!ByteBuffer} this
+	     * @expose
+	     * @example A relative `<01 02>03.append(<04 05>)` will result in `<01 02 04 05>, 04 05|`
+	     * @example An absolute `<01 02>03.append(04 05>, 1)` will result in `<01 04>05, 04 05|`
+	     */
+	    ByteBufferPrototype.append = function(source, encoding, offset) {
+	        if (typeof encoding === 'number' || typeof encoding !== 'string') {
+	            offset = encoding;
+	            encoding = undefined;
+	        }
+	        var relative = typeof offset === 'undefined';
+	        if (relative) offset = this.offset;
+	        if (!this.noAssert) {
+	            if (typeof offset !== 'number' || offset % 1 !== 0)
+	                throw TypeError("Illegal offset: "+offset+" (not an integer)");
+	            offset >>>= 0;
+	            if (offset < 0 || offset + 0 > this.buffer.byteLength)
+	                throw RangeError("Illegal offset: 0 <= "+offset+" (+"+0+") <= "+this.buffer.byteLength);
+	        }
+	        if (!(source instanceof ByteBuffer))
+	            source = ByteBuffer.wrap(source, encoding);
+	        var length = source.limit - source.offset;
+	        if (length <= 0) return this; // Nothing to append
+	        offset += length;
+	        var capacity16 = this.buffer.byteLength;
+	        if (offset > capacity16)
+	            this.resize((capacity16 *= 2) > offset ? capacity16 : offset);
+	        offset -= length;
+	        this.view.set(source.view.subarray(source.offset, source.limit), offset);
+	        source.offset += length;
+	        if (relative) this.offset += length;
+	        return this;
+	    };
+
+	    /**
+	     * Appends this ByteBuffer's contents to another ByteBuffer. This will overwrite any contents at and after the
+	        specified offset up to the length of this ByteBuffer's data.
+	     * @param {!ByteBuffer} target Target ByteBuffer
+	     * @param {number=} offset Offset to append to. Will use and increase {@link ByteBuffer#offset} by the number of bytes
+	     *  read if omitted.
+	     * @returns {!ByteBuffer} this
+	     * @expose
+	     * @see ByteBuffer#append
+	     */
+	    ByteBufferPrototype.appendTo = function(target, offset) {
+	        target.append(this, offset);
+	        return this;
+	    };
+
+	    /**
+	     * Enables or disables assertions of argument types and offsets. Assertions are enabled by default but you can opt to
+	     *  disable them if your code already makes sure that everything is valid.
+	     * @param {boolean} assert `true` to enable assertions, otherwise `false`
+	     * @returns {!ByteBuffer} this
+	     * @expose
+	     */
+	    ByteBufferPrototype.assert = function(assert) {
+	        this.noAssert = !assert;
+	        return this;
+	    };
+
+	    /**
+	     * Gets the capacity of this ByteBuffer's backing buffer.
+	     * @returns {number} Capacity of the backing buffer
+	     * @expose
+	     */
+	    ByteBufferPrototype.capacity = function() {
+	        return this.buffer.byteLength;
+	    };
+	    /**
+	     * Clears this ByteBuffer's offsets by setting {@link ByteBuffer#offset} to `0` and {@link ByteBuffer#limit} to the
+	     *  backing buffer's capacity. Discards {@link ByteBuffer#markedOffset}.
+	     * @returns {!ByteBuffer} this
+	     * @expose
+	     */
+	    ByteBufferPrototype.clear = function() {
+	        this.offset = 0;
+	        this.limit = this.buffer.byteLength;
+	        this.markedOffset = -1;
+	        return this;
+	    };
+
+	    /**
+	     * Creates a cloned instance of this ByteBuffer, preset with this ByteBuffer's values for {@link ByteBuffer#offset},
+	     *  {@link ByteBuffer#markedOffset} and {@link ByteBuffer#limit}.
+	     * @param {boolean=} copy Whether to copy the backing buffer or to return another view on the same, defaults to `false`
+	     * @returns {!ByteBuffer} Cloned instance
+	     * @expose
+	     */
+	    ByteBufferPrototype.clone = function(copy) {
+	        var bb = new ByteBuffer(0, this.littleEndian, this.noAssert);
+	        if (copy) {
+	            bb.buffer = new ArrayBuffer(this.buffer.byteLength);
+	            bb.view = new Uint8Array(bb.buffer);
+	        } else {
+	            bb.buffer = this.buffer;
+	            bb.view = this.view;
+	        }
+	        bb.offset = this.offset;
+	        bb.markedOffset = this.markedOffset;
+	        bb.limit = this.limit;
+	        return bb;
+	    };
+
+	    /**
+	     * Compacts this ByteBuffer to be backed by a {@link ByteBuffer#buffer} of its contents' length. Contents are the bytes
+	     *  between {@link ByteBuffer#offset} and {@link ByteBuffer#limit}. Will set `offset = 0` and `limit = capacity` and
+	     *  adapt {@link ByteBuffer#markedOffset} to the same relative position if set.
+	     * @param {number=} begin Offset to start at, defaults to {@link ByteBuffer#offset}
+	     * @param {number=} end Offset to end at, defaults to {@link ByteBuffer#limit}
+	     * @returns {!ByteBuffer} this
+	     * @expose
+	     */
+	    ByteBufferPrototype.compact = function(begin, end) {
+	        if (typeof begin === 'undefined') begin = this.offset;
+	        if (typeof end === 'undefined') end = this.limit;
+	        if (!this.noAssert) {
+	            if (typeof begin !== 'number' || begin % 1 !== 0)
+	                throw TypeError("Illegal begin: Not an integer");
+	            begin >>>= 0;
+	            if (typeof end !== 'number' || end % 1 !== 0)
+	                throw TypeError("Illegal end: Not an integer");
+	            end >>>= 0;
+	            if (begin < 0 || begin > end || end > this.buffer.byteLength)
+	                throw RangeError("Illegal range: 0 <= "+begin+" <= "+end+" <= "+this.buffer.byteLength);
+	        }
+	        if (begin === 0 && end === this.buffer.byteLength)
+	            return this; // Already compacted
+	        var len = end - begin;
+	        if (len === 0) {
+	            this.buffer = EMPTY_BUFFER;
+	            this.view = null;
+	            if (this.markedOffset >= 0) this.markedOffset -= begin;
+	            this.offset = 0;
+	            this.limit = 0;
+	            return this;
+	        }
+	        var buffer = new ArrayBuffer(len);
+	        var view = new Uint8Array(buffer);
+	        view.set(this.view.subarray(begin, end));
+	        this.buffer = buffer;
+	        this.view = view;
+	        if (this.markedOffset >= 0) this.markedOffset -= begin;
+	        this.offset = 0;
+	        this.limit = len;
+	        return this;
+	    };
+
+	    /**
+	     * Creates a copy of this ByteBuffer's contents. Contents are the bytes between {@link ByteBuffer#offset} and
+	     *  {@link ByteBuffer#limit}.
+	     * @param {number=} begin Begin offset, defaults to {@link ByteBuffer#offset}.
+	     * @param {number=} end End offset, defaults to {@link ByteBuffer#limit}.
+	     * @returns {!ByteBuffer} Copy
+	     * @expose
+	     */
+	    ByteBufferPrototype.copy = function(begin, end) {
+	        if (typeof begin === 'undefined') begin = this.offset;
+	        if (typeof end === 'undefined') end = this.limit;
+	        if (!this.noAssert) {
+	            if (typeof begin !== 'number' || begin % 1 !== 0)
+	                throw TypeError("Illegal begin: Not an integer");
+	            begin >>>= 0;
+	            if (typeof end !== 'number' || end % 1 !== 0)
+	                throw TypeError("Illegal end: Not an integer");
+	            end >>>= 0;
+	            if (begin < 0 || begin > end || end > this.buffer.byteLength)
+	                throw RangeError("Illegal range: 0 <= "+begin+" <= "+end+" <= "+this.buffer.byteLength);
+	        }
+	        if (begin === end)
+	            return new ByteBuffer(0, this.littleEndian, this.noAssert);
+	        var capacity = end - begin,
+	            bb = new ByteBuffer(capacity, this.littleEndian, this.noAssert);
+	        bb.offset = 0;
+	        bb.limit = capacity;
+	        if (bb.markedOffset >= 0) bb.markedOffset -= begin;
+	        this.copyTo(bb, 0, begin, end);
+	        return bb;
+	    };
+
+	    /**
+	     * Copies this ByteBuffer's contents to another ByteBuffer. Contents are the bytes between {@link ByteBuffer#offset} and
+	     *  {@link ByteBuffer#limit}.
+	     * @param {!ByteBuffer} target Target ByteBuffer
+	     * @param {number=} targetOffset Offset to copy to. Will use and increase the target's {@link ByteBuffer#offset}
+	     *  by the number of bytes copied if omitted.
+	     * @param {number=} sourceOffset Offset to start copying from. Will use and increase {@link ByteBuffer#offset} by the
+	     *  number of bytes copied if omitted.
+	     * @param {number=} sourceLimit Offset to end copying from, defaults to {@link ByteBuffer#limit}
+	     * @returns {!ByteBuffer} this
+	     * @expose
+	     */
+	    ByteBufferPrototype.copyTo = function(target, targetOffset, sourceOffset, sourceLimit) {
+	        var relative,
+	            targetRelative;
+	        if (!this.noAssert) {
+	            if (!ByteBuffer.isByteBuffer(target))
+	                throw TypeError("Illegal target: Not a ByteBuffer");
+	        }
+	        targetOffset = (targetRelative = typeof targetOffset === 'undefined') ? target.offset : targetOffset | 0;
+	        sourceOffset = (relative = typeof sourceOffset === 'undefined') ? this.offset : sourceOffset | 0;
+	        sourceLimit = typeof sourceLimit === 'undefined' ? this.limit : sourceLimit | 0;
+
+	        if (targetOffset < 0 || targetOffset > target.buffer.byteLength)
+	            throw RangeError("Illegal target range: 0 <= "+targetOffset+" <= "+target.buffer.byteLength);
+	        if (sourceOffset < 0 || sourceLimit > this.buffer.byteLength)
+	            throw RangeError("Illegal source range: 0 <= "+sourceOffset+" <= "+this.buffer.byteLength);
+
+	        var len = sourceLimit - sourceOffset;
+	        if (len === 0)
+	            return target; // Nothing to copy
+
+	        target.ensureCapacity(targetOffset + len);
+
+	        target.view.set(this.view.subarray(sourceOffset, sourceLimit), targetOffset);
+
+	        if (relative) this.offset += len;
+	        if (targetRelative) target.offset += len;
+
+	        return this;
+	    };
+
+	    /**
+	     * Makes sure that this ByteBuffer is backed by a {@link ByteBuffer#buffer} of at least the specified capacity. If the
+	     *  current capacity is exceeded, it will be doubled. If double the current capacity is less than the required capacity,
+	     *  the required capacity will be used instead.
+	     * @param {number} capacity Required capacity
+	     * @returns {!ByteBuffer} this
+	     * @expose
+	     */
+	    ByteBufferPrototype.ensureCapacity = function(capacity) {
+	        var current = this.buffer.byteLength;
+	        if (current < capacity)
+	            return this.resize((current *= 2) > capacity ? current : capacity);
+	        return this;
+	    };
+
+	    /**
+	     * Overwrites this ByteBuffer's contents with the specified value. Contents are the bytes between
+	     *  {@link ByteBuffer#offset} and {@link ByteBuffer#limit}.
+	     * @param {number|string} value Byte value to fill with. If given as a string, the first character is used.
+	     * @param {number=} begin Begin offset. Will use and increase {@link ByteBuffer#offset} by the number of bytes
+	     *  written if omitted. defaults to {@link ByteBuffer#offset}.
+	     * @param {number=} end End offset, defaults to {@link ByteBuffer#limit}.
+	     * @returns {!ByteBuffer} this
+	     * @expose
+	     * @example `someByteBuffer.clear().fill(0)` fills the entire backing buffer with zeroes
+	     */
+	    ByteBufferPrototype.fill = function(value, begin, end) {
+	        var relative = typeof begin === 'undefined';
+	        if (relative) begin = this.offset;
+	        if (typeof value === 'string' && value.length > 0)
+	            value = value.charCodeAt(0);
+	        if (typeof begin === 'undefined') begin = this.offset;
+	        if (typeof end === 'undefined') end = this.limit;
+	        if (!this.noAssert) {
+	            if (typeof value !== 'number' || value % 1 !== 0)
+	                throw TypeError("Illegal value: "+value+" (not an integer)");
+	            value |= 0;
+	            if (typeof begin !== 'number' || begin % 1 !== 0)
+	                throw TypeError("Illegal begin: Not an integer");
+	            begin >>>= 0;
+	            if (typeof end !== 'number' || end % 1 !== 0)
+	                throw TypeError("Illegal end: Not an integer");
+	            end >>>= 0;
+	            if (begin < 0 || begin > end || end > this.buffer.byteLength)
+	                throw RangeError("Illegal range: 0 <= "+begin+" <= "+end+" <= "+this.buffer.byteLength);
+	        }
+	        if (begin >= end)
+	            return this; // Nothing to fill
+	        while (begin < end) this.view[begin++] = value;
+	        if (relative) this.offset = begin;
+	        return this;
+	    };
+
+	    /**
+	     * Makes this ByteBuffer ready for a new sequence of write or relative read operations. Sets `limit = offset` and
+	     *  `offset = 0`. Make sure always to flip a ByteBuffer when all relative read or write operations are complete.
+	     * @returns {!ByteBuffer} this
+	     * @expose
+	     */
+	    ByteBufferPrototype.flip = function() {
+	        this.limit = this.offset;
+	        this.offset = 0;
+	        return this;
+	    };
+	    /**
+	     * Marks an offset on this ByteBuffer to be used later.
+	     * @param {number=} offset Offset to mark. Defaults to {@link ByteBuffer#offset}.
+	     * @returns {!ByteBuffer} this
+	     * @throws {TypeError} If `offset` is not a valid number
+	     * @throws {RangeError} If `offset` is out of bounds
+	     * @see ByteBuffer#reset
+	     * @expose
+	     */
+	    ByteBufferPrototype.mark = function(offset) {
+	        offset = typeof offset === 'undefined' ? this.offset : offset;
+	        if (!this.noAssert) {
+	            if (typeof offset !== 'number' || offset % 1 !== 0)
+	                throw TypeError("Illegal offset: "+offset+" (not an integer)");
+	            offset >>>= 0;
+	            if (offset < 0 || offset + 0 > this.buffer.byteLength)
+	                throw RangeError("Illegal offset: 0 <= "+offset+" (+"+0+") <= "+this.buffer.byteLength);
+	        }
+	        this.markedOffset = offset;
+	        return this;
+	    };
+	    /**
+	     * Sets the byte order.
+	     * @param {boolean} littleEndian `true` for little endian byte order, `false` for big endian
+	     * @returns {!ByteBuffer} this
+	     * @expose
+	     */
+	    ByteBufferPrototype.order = function(littleEndian) {
+	        if (!this.noAssert) {
+	            if (typeof littleEndian !== 'boolean')
+	                throw TypeError("Illegal littleEndian: Not a boolean");
+	        }
+	        this.littleEndian = !!littleEndian;
+	        return this;
+	    };
+
+	    /**
+	     * Switches (to) little endian byte order.
+	     * @param {boolean=} littleEndian Defaults to `true`, otherwise uses big endian
+	     * @returns {!ByteBuffer} this
+	     * @expose
+	     */
+	    ByteBufferPrototype.LE = function(littleEndian) {
+	        this.littleEndian = typeof littleEndian !== 'undefined' ? !!littleEndian : true;
+	        return this;
+	    };
+
+	    /**
+	     * Switches (to) big endian byte order.
+	     * @param {boolean=} bigEndian Defaults to `true`, otherwise uses little endian
+	     * @returns {!ByteBuffer} this
+	     * @expose
+	     */
+	    ByteBufferPrototype.BE = function(bigEndian) {
+	        this.littleEndian = typeof bigEndian !== 'undefined' ? !bigEndian : false;
+	        return this;
+	    };
+	    /**
+	     * Prepends some data to this ByteBuffer. This will overwrite any contents before the specified offset up to the
+	     *  prepended data's length. If there is not enough space available before the specified `offset`, the backing buffer
+	     *  will be resized and its contents moved accordingly.
+	     * @param {!ByteBuffer|string|!ArrayBuffer} source Data to prepend. If `source` is a ByteBuffer, its offset will be
+	     *  modified according to the performed read operation.
+	     * @param {(string|number)=} encoding Encoding if `data` is a string ("base64", "hex", "binary", defaults to "utf8")
+	     * @param {number=} offset Offset to prepend at. Will use and decrease {@link ByteBuffer#offset} by the number of bytes
+	     *  prepended if omitted.
+	     * @returns {!ByteBuffer} this
+	     * @expose
+	     * @example A relative `00<01 02 03>.prepend(<04 05>)` results in `<04 05 01 02 03>, 04 05|`
+	     * @example An absolute `00<01 02 03>.prepend(<04 05>, 2)` results in `04<05 02 03>, 04 05|`
+	     */
+	    ByteBufferPrototype.prepend = function(source, encoding, offset) {
+	        if (typeof encoding === 'number' || typeof encoding !== 'string') {
+	            offset = encoding;
+	            encoding = undefined;
+	        }
+	        var relative = typeof offset === 'undefined';
+	        if (relative) offset = this.offset;
+	        if (!this.noAssert) {
+	            if (typeof offset !== 'number' || offset % 1 !== 0)
+	                throw TypeError("Illegal offset: "+offset+" (not an integer)");
+	            offset >>>= 0;
+	            if (offset < 0 || offset + 0 > this.buffer.byteLength)
+	                throw RangeError("Illegal offset: 0 <= "+offset+" (+"+0+") <= "+this.buffer.byteLength);
+	        }
+	        if (!(source instanceof ByteBuffer))
+	            source = ByteBuffer.wrap(source, encoding);
+	        var len = source.limit - source.offset;
+	        if (len <= 0) return this; // Nothing to prepend
+	        var diff = len - offset;
+	        if (diff > 0) { // Not enough space before offset, so resize + move
+	            var buffer = new ArrayBuffer(this.buffer.byteLength + diff);
+	            var view = new Uint8Array(buffer);
+	            view.set(this.view.subarray(offset, this.buffer.byteLength), len);
+	            this.buffer = buffer;
+	            this.view = view;
+	            this.offset += diff;
+	            if (this.markedOffset >= 0) this.markedOffset += diff;
+	            this.limit += diff;
+	            offset += diff;
+	        } else {
+	            var arrayView = new Uint8Array(this.buffer);
+	        }
+	        this.view.set(source.view.subarray(source.offset, source.limit), offset - len);
+
+	        source.offset = source.limit;
+	        if (relative)
+	            this.offset -= len;
+	        return this;
+	    };
+
+	    /**
+	     * Prepends this ByteBuffer to another ByteBuffer. This will overwrite any contents before the specified offset up to the
+	     *  prepended data's length. If there is not enough space available before the specified `offset`, the backing buffer
+	     *  will be resized and its contents moved accordingly.
+	     * @param {!ByteBuffer} target Target ByteBuffer
+	     * @param {number=} offset Offset to prepend at. Will use and decrease {@link ByteBuffer#offset} by the number of bytes
+	     *  prepended if omitted.
+	     * @returns {!ByteBuffer} this
+	     * @expose
+	     * @see ByteBuffer#prepend
+	     */
+	    ByteBufferPrototype.prependTo = function(target, offset) {
+	        target.prepend(this, offset);
+	        return this;
+	    };
+	    /**
+	     * Prints debug information about this ByteBuffer's contents.
+	     * @param {function(string)=} out Output function to call, defaults to console.log
+	     * @expose
+	     */
+	    ByteBufferPrototype.printDebug = function(out) {
+	        if (typeof out !== 'function') out = console.log.bind(console);
+	        out(
+	            this.toString()+"\n"+
+	            "-------------------------------------------------------------------\n"+
+	            this.toDebug(/* columns */ true)
+	        );
+	    };
+
+	    /**
+	     * Gets the number of remaining readable bytes. Contents are the bytes between {@link ByteBuffer#offset} and
+	     *  {@link ByteBuffer#limit}, so this returns `limit - offset`.
+	     * @returns {number} Remaining readable bytes. May be negative if `offset > limit`.
+	     * @expose
+	     */
+	    ByteBufferPrototype.remaining = function() {
+	        return this.limit - this.offset;
+	    };
+	    /**
+	     * Resets this ByteBuffer's {@link ByteBuffer#offset}. If an offset has been marked through {@link ByteBuffer#mark}
+	     *  before, `offset` will be set to {@link ByteBuffer#markedOffset}, which will then be discarded. If no offset has been
+	     *  marked, sets `offset = 0`.
+	     * @returns {!ByteBuffer} this
+	     * @see ByteBuffer#mark
+	     * @expose
+	     */
+	    ByteBufferPrototype.reset = function() {
+	        if (this.markedOffset >= 0) {
+	            this.offset = this.markedOffset;
+	            this.markedOffset = -1;
+	        } else {
+	            this.offset = 0;
+	        }
+	        return this;
+	    };
+	    /**
+	     * Resizes this ByteBuffer to be backed by a buffer of at least the given capacity. Will do nothing if already that
+	     *  large or larger.
+	     * @param {number} capacity Capacity required
+	     * @returns {!ByteBuffer} this
+	     * @throws {TypeError} If `capacity` is not a number
+	     * @throws {RangeError} If `capacity < 0`
+	     * @expose
+	     */
+	    ByteBufferPrototype.resize = function(capacity) {
+	        if (!this.noAssert) {
+	            if (typeof capacity !== 'number' || capacity % 1 !== 0)
+	                throw TypeError("Illegal capacity: "+capacity+" (not an integer)");
+	            capacity |= 0;
+	            if (capacity < 0)
+	                throw RangeError("Illegal capacity: 0 <= "+capacity);
+	        }
+	        if (this.buffer.byteLength < capacity) {
+	            var buffer = new ArrayBuffer(capacity);
+	            var view = new Uint8Array(buffer);
+	            view.set(this.view);
+	            this.buffer = buffer;
+	            this.view = view;
+	        }
+	        return this;
+	    };
+	    /**
+	     * Reverses this ByteBuffer's contents.
+	     * @param {number=} begin Offset to start at, defaults to {@link ByteBuffer#offset}
+	     * @param {number=} end Offset to end at, defaults to {@link ByteBuffer#limit}
+	     * @returns {!ByteBuffer} this
+	     * @expose
+	     */
+	    ByteBufferPrototype.reverse = function(begin, end) {
+	        if (typeof begin === 'undefined') begin = this.offset;
+	        if (typeof end === 'undefined') end = this.limit;
+	        if (!this.noAssert) {
+	            if (typeof begin !== 'number' || begin % 1 !== 0)
+	                throw TypeError("Illegal begin: Not an integer");
+	            begin >>>= 0;
+	            if (typeof end !== 'number' || end % 1 !== 0)
+	                throw TypeError("Illegal end: Not an integer");
+	            end >>>= 0;
+	            if (begin < 0 || begin > end || end > this.buffer.byteLength)
+	                throw RangeError("Illegal range: 0 <= "+begin+" <= "+end+" <= "+this.buffer.byteLength);
+	        }
+	        if (begin === end)
+	            return this; // Nothing to reverse
+	        Array.prototype.reverse.call(this.view.subarray(begin, end));
+	        return this;
+	    };
+	    /**
+	     * Skips the next `length` bytes. This will just advance
+	     * @param {number} length Number of bytes to skip. May also be negative to move the offset back.
+	     * @returns {!ByteBuffer} this
+	     * @expose
+	     */
+	    ByteBufferPrototype.skip = function(length) {
+	        if (!this.noAssert) {
+	            if (typeof length !== 'number' || length % 1 !== 0)
+	                throw TypeError("Illegal length: "+length+" (not an integer)");
+	            length |= 0;
+	        }
+	        var offset = this.offset + length;
+	        if (!this.noAssert) {
+	            if (offset < 0 || offset > this.buffer.byteLength)
+	                throw RangeError("Illegal length: 0 <= "+this.offset+" + "+length+" <= "+this.buffer.byteLength);
+	        }
+	        this.offset = offset;
+	        return this;
+	    };
+
+	    /**
+	     * Slices this ByteBuffer by creating a cloned instance with `offset = begin` and `limit = end`.
+	     * @param {number=} begin Begin offset, defaults to {@link ByteBuffer#offset}.
+	     * @param {number=} end End offset, defaults to {@link ByteBuffer#limit}.
+	     * @returns {!ByteBuffer} Clone of this ByteBuffer with slicing applied, backed by the same {@link ByteBuffer#buffer}
+	     * @expose
+	     */
+	    ByteBufferPrototype.slice = function(begin, end) {
+	        if (typeof begin === 'undefined') begin = this.offset;
+	        if (typeof end === 'undefined') end = this.limit;
+	        if (!this.noAssert) {
+	            if (typeof begin !== 'number' || begin % 1 !== 0)
+	                throw TypeError("Illegal begin: Not an integer");
+	            begin >>>= 0;
+	            if (typeof end !== 'number' || end % 1 !== 0)
+	                throw TypeError("Illegal end: Not an integer");
+	            end >>>= 0;
+	            if (begin < 0 || begin > end || end > this.buffer.byteLength)
+	                throw RangeError("Illegal range: 0 <= "+begin+" <= "+end+" <= "+this.buffer.byteLength);
+	        }
+	        var bb = this.clone();
+	        bb.offset = begin;
+	        bb.limit = end;
+	        return bb;
+	    };
+	    /**
+	     * Returns a copy of the backing buffer that contains this ByteBuffer's contents. Contents are the bytes between
+	     *  {@link ByteBuffer#offset} and {@link ByteBuffer#limit}.
+	     * @param {boolean=} forceCopy If `true` returns a copy, otherwise returns a view referencing the same memory if
+	     *  possible. Defaults to `false`
+	     * @returns {!ArrayBuffer} Contents as an ArrayBuffer
+	     * @expose
+	     */
+	    ByteBufferPrototype.toBuffer = function(forceCopy) {
+	        var offset = this.offset,
+	            limit = this.limit;
+	        if (!this.noAssert) {
+	            if (typeof offset !== 'number' || offset % 1 !== 0)
+	                throw TypeError("Illegal offset: Not an integer");
+	            offset >>>= 0;
+	            if (typeof limit !== 'number' || limit % 1 !== 0)
+	                throw TypeError("Illegal limit: Not an integer");
+	            limit >>>= 0;
+	            if (offset < 0 || offset > limit || limit > this.buffer.byteLength)
+	                throw RangeError("Illegal range: 0 <= "+offset+" <= "+limit+" <= "+this.buffer.byteLength);
+	        }
+	        // NOTE: It's not possible to have another ArrayBuffer reference the same memory as the backing buffer. This is
+	        // possible with Uint8Array#subarray only, but we have to return an ArrayBuffer by contract. So:
+	        if (!forceCopy && offset === 0 && limit === this.buffer.byteLength)
+	            return this.buffer;
+	        if (offset === limit)
+	            return EMPTY_BUFFER;
+	        var buffer = new ArrayBuffer(limit - offset);
+	        new Uint8Array(buffer).set(new Uint8Array(this.buffer).subarray(offset, limit), 0);
+	        return buffer;
+	    };
+
+	    /**
+	     * Returns a raw buffer compacted to contain this ByteBuffer's contents. Contents are the bytes between
+	     *  {@link ByteBuffer#offset} and {@link ByteBuffer#limit}. This is an alias of {@link ByteBuffer#toBuffer}.
+	     * @function
+	     * @param {boolean=} forceCopy If `true` returns a copy, otherwise returns a view referencing the same memory.
+	     *  Defaults to `false`
+	     * @returns {!ArrayBuffer} Contents as an ArrayBuffer
+	     * @expose
+	     */
+	    ByteBufferPrototype.toArrayBuffer = ByteBufferPrototype.toBuffer;
+
+	    /**
+	     * Converts the ByteBuffer's contents to a string.
+	     * @param {string=} encoding Output encoding. Returns an informative string representation if omitted but also allows
+	     *  direct conversion to "utf8", "hex", "base64" and "binary" encoding. "debug" returns a hex representation with
+	     *  highlighted offsets.
+	     * @param {number=} begin Offset to begin at, defaults to {@link ByteBuffer#offset}
+	     * @param {number=} end Offset to end at, defaults to {@link ByteBuffer#limit}
+	     * @returns {string} String representation
+	     * @throws {Error} If `encoding` is invalid
+	     * @expose
+	     */
+	    ByteBufferPrototype.toString = function(encoding, begin, end) {
+	        if (typeof encoding === 'undefined')
+	            return "ByteBufferAB(offset="+this.offset+",markedOffset="+this.markedOffset+",limit="+this.limit+",capacity="+this.capacity()+")";
+	        if (typeof encoding === 'number')
+	            encoding = "utf8",
+	            begin = encoding,
+	            end = begin;
+	        switch (encoding) {
+	            case "utf8":
+	                return this.toUTF8(begin, end);
+	            case "base64":
+	                return this.toBase64(begin, end);
+	            case "hex":
+	                return this.toHex(begin, end);
+	            case "binary":
+	                return this.toBinary(begin, end);
+	            case "debug":
+	                return this.toDebug();
+	            case "columns":
+	                return this.toColumns();
+	            default:
+	                throw Error("Unsupported encoding: "+encoding);
+	        }
+	    };
+
+	    // lxiv-embeddable
+
+	    /**
+	     * lxiv-embeddable (c) 2014 Daniel Wirtz <dcode@dcode.io>
+	     * Released under the Apache License, Version 2.0
+	     * see: https://github.com/dcodeIO/lxiv for details
+	     */
+	    var lxiv = function() {
+	        "use strict";
+
+	        /**
+	         * lxiv namespace.
+	         * @type {!Object.<string,*>}
+	         * @exports lxiv
+	         */
+	        var lxiv = {};
+
+	        /**
+	         * Character codes for output.
+	         * @type {!Array.<number>}
+	         * @inner
+	         */
+	        var aout = [
+	            65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80,
+	            81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 97, 98, 99, 100, 101, 102,
+	            103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118,
+	            119, 120, 121, 122, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 43, 47
+	        ];
+
+	        /**
+	         * Character codes for input.
+	         * @type {!Array.<number>}
+	         * @inner
+	         */
+	        var ain = [];
+	        for (var i=0, k=aout.length; i<k; ++i)
+	            ain[aout[i]] = i;
+
+	        /**
+	         * Encodes bytes to base64 char codes.
+	         * @param {!function():number|null} src Bytes source as a function returning the next byte respectively `null` if
+	         *  there are no more bytes left.
+	         * @param {!function(number)} dst Characters destination as a function successively called with each encoded char
+	         *  code.
+	         */
+	        lxiv.encode = function(src, dst) {
+	            var b, t;
+	            while ((b = src()) !== null) {
+	                dst(aout[(b>>2)&0x3f]);
+	                t = (b&0x3)<<4;
+	                if ((b = src()) !== null) {
+	                    t |= (b>>4)&0xf;
+	                    dst(aout[(t|((b>>4)&0xf))&0x3f]);
+	                    t = (b&0xf)<<2;
+	                    if ((b = src()) !== null)
+	                        dst(aout[(t|((b>>6)&0x3))&0x3f]),
+	                        dst(aout[b&0x3f]);
+	                    else
+	                        dst(aout[t&0x3f]),
+	                        dst(61);
+	                } else
+	                    dst(aout[t&0x3f]),
+	                    dst(61),
+	                    dst(61);
+	            }
+	        };
+
+	        /**
+	         * Decodes base64 char codes to bytes.
+	         * @param {!function():number|null} src Characters source as a function returning the next char code respectively
+	         *  `null` if there are no more characters left.
+	         * @param {!function(number)} dst Bytes destination as a function successively called with the next byte.
+	         * @throws {Error} If a character code is invalid
+	         */
+	        lxiv.decode = function(src, dst) {
+	            var c, t1, t2;
+	            function fail(c) {
+	                throw Error("Illegal character code: "+c);
+	            }
+	            while ((c = src()) !== null) {
+	                t1 = ain[c];
+	                if (typeof t1 === 'undefined') fail(c);
+	                if ((c = src()) !== null) {
+	                    t2 = ain[c];
+	                    if (typeof t2 === 'undefined') fail(c);
+	                    dst((t1<<2)>>>0|(t2&0x30)>>4);
+	                    if ((c = src()) !== null) {
+	                        t1 = ain[c];
+	                        if (typeof t1 === 'undefined')
+	                            if (c === 61) break; else fail(c);
+	                        dst(((t2&0xf)<<4)>>>0|(t1&0x3c)>>2);
+	                        if ((c = src()) !== null) {
+	                            t2 = ain[c];
+	                            if (typeof t2 === 'undefined')
+	                                if (c === 61) break; else fail(c);
+	                            dst(((t1&0x3)<<6)>>>0|t2);
+	                        }
+	                    }
+	                }
+	            }
+	        };
+
+	        /**
+	         * Tests if a string is valid base64.
+	         * @param {string} str String to test
+	         * @returns {boolean} `true` if valid, otherwise `false`
+	         */
+	        lxiv.test = function(str) {
+	            return /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/.test(str);
+	        };
+
+	        return lxiv;
+	    }();
+
+	    // encodings/base64
+
+	    /**
+	     * Encodes this ByteBuffer's contents to a base64 encoded string.
+	     * @param {number=} begin Offset to begin at, defaults to {@link ByteBuffer#offset}.
+	     * @param {number=} end Offset to end at, defaults to {@link ByteBuffer#limit}.
+	     * @returns {string} Base64 encoded string
+	     * @throws {RangeError} If `begin` or `end` is out of bounds
+	     * @expose
+	     */
+	    ByteBufferPrototype.toBase64 = function(begin, end) {
+	        if (typeof begin === 'undefined')
+	            begin = this.offset;
+	        if (typeof end === 'undefined')
+	            end = this.limit;
+	        begin = begin | 0; end = end | 0;
+	        if (begin < 0 || end > this.capacity || begin > end)
+	            throw RangeError("begin, end");
+	        var sd; lxiv.encode(function() {
+	            return begin < end ? this.view[begin++] : null;
+	        }.bind(this), sd = stringDestination());
+	        return sd();
+	    };
+
+	    /**
+	     * Decodes a base64 encoded string to a ByteBuffer.
+	     * @param {string} str String to decode
+	     * @param {boolean=} littleEndian Whether to use little or big endian byte order. Defaults to
+	     *  {@link ByteBuffer.DEFAULT_ENDIAN}.
+	     * @returns {!ByteBuffer} ByteBuffer
+	     * @expose
+	     */
+	    ByteBuffer.fromBase64 = function(str, littleEndian) {
+	        if (typeof str !== 'string')
+	            throw TypeError("str");
+	        var bb = new ByteBuffer(str.length/4*3, littleEndian),
+	            i = 0;
+	        lxiv.decode(stringSource(str), function(b) {
+	            bb.view[i++] = b;
+	        });
+	        bb.limit = i;
+	        return bb;
+	    };
+
+	    /**
+	     * Encodes a binary string to base64 like `window.btoa` does.
+	     * @param {string} str Binary string
+	     * @returns {string} Base64 encoded string
+	     * @see https://developer.mozilla.org/en-US/docs/Web/API/Window.btoa
+	     * @expose
+	     */
+	    ByteBuffer.btoa = function(str) {
+	        return ByteBuffer.fromBinary(str).toBase64();
+	    };
+
+	    /**
+	     * Decodes a base64 encoded string to binary like `window.atob` does.
+	     * @param {string} b64 Base64 encoded string
+	     * @returns {string} Binary string
+	     * @see https://developer.mozilla.org/en-US/docs/Web/API/Window.atob
+	     * @expose
+	     */
+	    ByteBuffer.atob = function(b64) {
+	        return ByteBuffer.fromBase64(b64).toBinary();
+	    };
+
+	    // encodings/binary
+
+	    /**
+	     * Encodes this ByteBuffer to a binary encoded string, that is using only characters 0x00-0xFF as bytes.
+	     * @param {number=} begin Offset to begin at. Defaults to {@link ByteBuffer#offset}.
+	     * @param {number=} end Offset to end at. Defaults to {@link ByteBuffer#limit}.
+	     * @returns {string} Binary encoded string
+	     * @throws {RangeError} If `offset > limit`
+	     * @expose
+	     */
+	    ByteBufferPrototype.toBinary = function(begin, end) {
+	        if (typeof begin === 'undefined')
+	            begin = this.offset;
+	        if (typeof end === 'undefined')
+	            end = this.limit;
+	        begin |= 0; end |= 0;
+	        if (begin < 0 || end > this.capacity() || begin > end)
+	            throw RangeError("begin, end");
+	        if (begin === end)
+	            return "";
+	        var chars = [],
+	            parts = [];
+	        while (begin < end) {
+	            chars.push(this.view[begin++]);
+	            if (chars.length >= 1024)
+	                parts.push(String.fromCharCode.apply(String, chars)),
+	                chars = [];
+	        }
+	        return parts.join('') + String.fromCharCode.apply(String, chars);
+	    };
+
+	    /**
+	     * Decodes a binary encoded string, that is using only characters 0x00-0xFF as bytes, to a ByteBuffer.
+	     * @param {string} str String to decode
+	     * @param {boolean=} littleEndian Whether to use little or big endian byte order. Defaults to
+	     *  {@link ByteBuffer.DEFAULT_ENDIAN}.
+	     * @returns {!ByteBuffer} ByteBuffer
+	     * @expose
+	     */
+	    ByteBuffer.fromBinary = function(str, littleEndian) {
+	        if (typeof str !== 'string')
+	            throw TypeError("str");
+	        var i = 0,
+	            k = str.length,
+	            charCode,
+	            bb = new ByteBuffer(k, littleEndian);
+	        while (i<k) {
+	            charCode = str.charCodeAt(i);
+	            if (charCode > 0xff)
+	                throw RangeError("illegal char code: "+charCode);
+	            bb.view[i++] = charCode;
+	        }
+	        bb.limit = k;
+	        return bb;
+	    };
+
+	    // encodings/debug
+
+	    /**
+	     * Encodes this ByteBuffer to a hex encoded string with marked offsets. Offset symbols are:
+	     * * `<` : offset,
+	     * * `'` : markedOffset,
+	     * * `>` : limit,
+	     * * `|` : offset and limit,
+	     * * `[` : offset and markedOffset,
+	     * * `]` : markedOffset and limit,
+	     * * `!` : offset, markedOffset and limit
+	     * @param {boolean=} columns If `true` returns two columns hex + ascii, defaults to `false`
+	     * @returns {string|!Array.<string>} Debug string or array of lines if `asArray = true`
+	     * @expose
+	     * @example `>00'01 02<03` contains four bytes with `limit=0, markedOffset=1, offset=3`
+	     * @example `00[01 02 03>` contains four bytes with `offset=markedOffset=1, limit=4`
+	     * @example `00|01 02 03` contains four bytes with `offset=limit=1, markedOffset=-1`
+	     * @example `|` contains zero bytes with `offset=limit=0, markedOffset=-1`
+	     */
+	    ByteBufferPrototype.toDebug = function(columns) {
+	        var i = -1,
+	            k = this.buffer.byteLength,
+	            b,
+	            hex = "",
+	            asc = "",
+	            out = "";
+	        while (i<k) {
+	            if (i !== -1) {
+	                b = this.view[i];
+	                if (b < 0x10) hex += "0"+b.toString(16).toUpperCase();
+	                else hex += b.toString(16).toUpperCase();
+	                if (columns)
+	                    asc += b > 32 && b < 127 ? String.fromCharCode(b) : '.';
+	            }
+	            ++i;
+	            if (columns) {
+	                if (i > 0 && i % 16 === 0 && i !== k) {
+	                    while (hex.length < 3*16+3) hex += " ";
+	                    out += hex+asc+"\n";
+	                    hex = asc = "";
+	                }
+	            }
+	            if (i === this.offset && i === this.limit)
+	                hex += i === this.markedOffset ? "!" : "|";
+	            else if (i === this.offset)
+	                hex += i === this.markedOffset ? "[" : "<";
+	            else if (i === this.limit)
+	                hex += i === this.markedOffset ? "]" : ">";
+	            else
+	                hex += i === this.markedOffset ? "'" : (columns || (i !== 0 && i !== k) ? " " : "");
+	        }
+	        if (columns && hex !== " ") {
+	            while (hex.length < 3*16+3)
+	                hex += " ";
+	            out += hex + asc + "\n";
+	        }
+	        return columns ? out : hex;
+	    };
+
+	    /**
+	     * Decodes a hex encoded string with marked offsets to a ByteBuffer.
+	     * @param {string} str Debug string to decode (not be generated with `columns = true`)
+	     * @param {boolean=} littleEndian Whether to use little or big endian byte order. Defaults to
+	     *  {@link ByteBuffer.DEFAULT_ENDIAN}.
+	     * @param {boolean=} noAssert Whether to skip assertions of offsets and values. Defaults to
+	     *  {@link ByteBuffer.DEFAULT_NOASSERT}.
+	     * @returns {!ByteBuffer} ByteBuffer
+	     * @expose
+	     * @see ByteBuffer#toDebug
+	     */
+	    ByteBuffer.fromDebug = function(str, littleEndian, noAssert) {
+	        var k = str.length,
+	            bb = new ByteBuffer(((k+1)/3)|0, littleEndian, noAssert);
+	        var i = 0, j = 0, ch, b,
+	            rs = false, // Require symbol next
+	            ho = false, hm = false, hl = false, // Already has offset (ho), markedOffset (hm), limit (hl)?
+	            fail = false;
+	        while (i<k) {
+	            switch (ch = str.charAt(i++)) {
+	                case '!':
+	                    if (!noAssert) {
+	                        if (ho || hm || hl) {
+	                            fail = true;
+	                            break;
+	                        }
+	                        ho = hm = hl = true;
+	                    }
+	                    bb.offset = bb.markedOffset = bb.limit = j;
+	                    rs = false;
+	                    break;
+	                case '|':
+	                    if (!noAssert) {
+	                        if (ho || hl) {
+	                            fail = true;
+	                            break;
+	                        }
+	                        ho = hl = true;
+	                    }
+	                    bb.offset = bb.limit = j;
+	                    rs = false;
+	                    break;
+	                case '[':
+	                    if (!noAssert) {
+	                        if (ho || hm) {
+	                            fail = true;
+	                            break;
+	                        }
+	                        ho = hm = true;
+	                    }
+	                    bb.offset = bb.markedOffset = j;
+	                    rs = false;
+	                    break;
+	                case '<':
+	                    if (!noAssert) {
+	                        if (ho) {
+	                            fail = true;
+	                            break;
+	                        }
+	                        ho = true;
+	                    }
+	                    bb.offset = j;
+	                    rs = false;
+	                    break;
+	                case ']':
+	                    if (!noAssert) {
+	                        if (hl || hm) {
+	                            fail = true;
+	                            break;
+	                        }
+	                        hl = hm = true;
+	                    }
+	                    bb.limit = bb.markedOffset = j;
+	                    rs = false;
+	                    break;
+	                case '>':
+	                    if (!noAssert) {
+	                        if (hl) {
+	                            fail = true;
+	                            break;
+	                        }
+	                        hl = true;
+	                    }
+	                    bb.limit = j;
+	                    rs = false;
+	                    break;
+	                case "'":
+	                    if (!noAssert) {
+	                        if (hm) {
+	                            fail = true;
+	                            break;
+	                        }
+	                        hm = true;
+	                    }
+	                    bb.markedOffset = j;
+	                    rs = false;
+	                    break;
+	                case ' ':
+	                    rs = false;
+	                    break;
+	                default:
+	                    if (!noAssert) {
+	                        if (rs) {
+	                            fail = true;
+	                            break;
+	                        }
+	                    }
+	                    b = parseInt(ch+str.charAt(i++), 16);
+	                    if (!noAssert) {
+	                        if (isNaN(b) || b < 0 || b > 255)
+	                            throw TypeError("Illegal str: Not a debug encoded string");
+	                    }
+	                    bb.view[j++] = b;
+	                    rs = true;
+	            }
+	            if (fail)
+	                throw TypeError("Illegal str: Invalid symbol at "+i);
+	        }
+	        if (!noAssert) {
+	            if (!ho || !hl)
+	                throw TypeError("Illegal str: Missing offset or limit");
+	            if (j<bb.buffer.byteLength)
+	                throw TypeError("Illegal str: Not a debug encoded string (is it hex?) "+j+" < "+k);
+	        }
+	        return bb;
+	    };
+
+	    // encodings/hex
+
+	    /**
+	     * Encodes this ByteBuffer's contents to a hex encoded string.
+	     * @param {number=} begin Offset to begin at. Defaults to {@link ByteBuffer#offset}.
+	     * @param {number=} end Offset to end at. Defaults to {@link ByteBuffer#limit}.
+	     * @returns {string} Hex encoded string
+	     * @expose
+	     */
+	    ByteBufferPrototype.toHex = function(begin, end) {
+	        begin = typeof begin === 'undefined' ? this.offset : begin;
+	        end = typeof end === 'undefined' ? this.limit : end;
+	        if (!this.noAssert) {
+	            if (typeof begin !== 'number' || begin % 1 !== 0)
+	                throw TypeError("Illegal begin: Not an integer");
+	            begin >>>= 0;
+	            if (typeof end !== 'number' || end % 1 !== 0)
+	                throw TypeError("Illegal end: Not an integer");
+	            end >>>= 0;
+	            if (begin < 0 || begin > end || end > this.buffer.byteLength)
+	                throw RangeError("Illegal range: 0 <= "+begin+" <= "+end+" <= "+this.buffer.byteLength);
+	        }
+	        var out = new Array(end - begin),
+	            b;
+	        while (begin < end) {
+	            b = this.view[begin++];
+	            if (b < 0x10)
+	                out.push("0", b.toString(16));
+	            else out.push(b.toString(16));
+	        }
+	        return out.join('');
+	    };
+
+	    /**
+	     * Decodes a hex encoded string to a ByteBuffer.
+	     * @param {string} str String to decode
+	     * @param {boolean=} littleEndian Whether to use little or big endian byte order. Defaults to
+	     *  {@link ByteBuffer.DEFAULT_ENDIAN}.
+	     * @param {boolean=} noAssert Whether to skip assertions of offsets and values. Defaults to
+	     *  {@link ByteBuffer.DEFAULT_NOASSERT}.
+	     * @returns {!ByteBuffer} ByteBuffer
+	     * @expose
+	     */
+	    ByteBuffer.fromHex = function(str, littleEndian, noAssert) {
+	        if (!noAssert) {
+	            if (typeof str !== 'string')
+	                throw TypeError("Illegal str: Not a string");
+	            if (str.length % 2 !== 0)
+	                throw TypeError("Illegal str: Length not a multiple of 2");
+	        }
+	        var k = str.length,
+	            bb = new ByteBuffer((k / 2) | 0, littleEndian),
+	            b;
+	        for (var i=0, j=0; i<k; i+=2) {
+	            b = parseInt(str.substring(i, i+2), 16);
+	            if (!noAssert)
+	                if (!isFinite(b) || b < 0 || b > 255)
+	                    throw TypeError("Illegal str: Contains non-hex characters");
+	            bb.view[j++] = b;
+	        }
+	        bb.limit = j;
+	        return bb;
+	    };
+
+	    // utfx-embeddable
+
+	    /**
+	     * utfx-embeddable (c) 2014 Daniel Wirtz <dcode@dcode.io>
+	     * Released under the Apache License, Version 2.0
+	     * see: https://github.com/dcodeIO/utfx for details
+	     */
+	    var utfx = function() {
+	        "use strict";
+
+	        /**
+	         * utfx namespace.
+	         * @inner
+	         * @type {!Object.<string,*>}
+	         */
+	        var utfx = {};
+
+	        /**
+	         * Maximum valid code point.
+	         * @type {number}
+	         * @const
+	         */
+	        utfx.MAX_CODEPOINT = 0x10FFFF;
+
+	        /**
+	         * Encodes UTF8 code points to UTF8 bytes.
+	         * @param {(!function():number|null) | number} src Code points source, either as a function returning the next code point
+	         *  respectively `null` if there are no more code points left or a single numeric code point.
+	         * @param {!function(number)} dst Bytes destination as a function successively called with the next byte
+	         */
+	        utfx.encodeUTF8 = function(src, dst) {
+	            var cp = null;
+	            if (typeof src === 'number')
+	                cp = src,
+	                src = function() { return null; };
+	            while (cp !== null || (cp = src()) !== null) {
+	                if (cp < 0x80)
+	                    dst(cp&0x7F);
+	                else if (cp < 0x800)
+	                    dst(((cp>>6)&0x1F)|0xC0),
+	                    dst((cp&0x3F)|0x80);
+	                else if (cp < 0x10000)
+	                    dst(((cp>>12)&0x0F)|0xE0),
+	                    dst(((cp>>6)&0x3F)|0x80),
+	                    dst((cp&0x3F)|0x80);
+	                else
+	                    dst(((cp>>18)&0x07)|0xF0),
+	                    dst(((cp>>12)&0x3F)|0x80),
+	                    dst(((cp>>6)&0x3F)|0x80),
+	                    dst((cp&0x3F)|0x80);
+	                cp = null;
+	            }
+	        };
+
+	        /**
+	         * Decodes UTF8 bytes to UTF8 code points.
+	         * @param {!function():number|null} src Bytes source as a function returning the next byte respectively `null` if there
+	         *  are no more bytes left.
+	         * @param {!function(number)} dst Code points destination as a function successively called with each decoded code point.
+	         * @throws {RangeError} If a starting byte is invalid in UTF8
+	         * @throws {Error} If the last sequence is truncated. Has an array property `bytes` holding the
+	         *  remaining bytes.
+	         */
+	        utfx.decodeUTF8 = function(src, dst) {
+	            var a, b, c, d, fail = function(b) {
+	                b = b.slice(0, b.indexOf(null));
+	                var err = Error(b.toString());
+	                err.name = "TruncatedError";
+	                err['bytes'] = b;
+	                throw err;
+	            };
+	            while ((a = src()) !== null) {
+	                if ((a&0x80) === 0)
+	                    dst(a);
+	                else if ((a&0xE0) === 0xC0)
+	                    ((b = src()) === null) && fail([a, b]),
+	                    dst(((a&0x1F)<<6) | (b&0x3F));
+	                else if ((a&0xF0) === 0xE0)
+	                    ((b=src()) === null || (c=src()) === null) && fail([a, b, c]),
+	                    dst(((a&0x0F)<<12) | ((b&0x3F)<<6) | (c&0x3F));
+	                else if ((a&0xF8) === 0xF0)
+	                    ((b=src()) === null || (c=src()) === null || (d=src()) === null) && fail([a, b, c ,d]),
+	                    dst(((a&0x07)<<18) | ((b&0x3F)<<12) | ((c&0x3F)<<6) | (d&0x3F));
+	                else throw RangeError("Illegal starting byte: "+a);
+	            }
+	        };
+
+	        /**
+	         * Converts UTF16 characters to UTF8 code points.
+	         * @param {!function():number|null} src Characters source as a function returning the next char code respectively
+	         *  `null` if there are no more characters left.
+	         * @param {!function(number)} dst Code points destination as a function successively called with each converted code
+	         *  point.
+	         */
+	        utfx.UTF16toUTF8 = function(src, dst) {
+	            var c1, c2 = null;
+	            while (true) {
+	                if ((c1 = c2 !== null ? c2 : src()) === null)
+	                    break;
+	                if (c1 >= 0xD800 && c1 <= 0xDFFF) {
+	                    if ((c2 = src()) !== null) {
+	                        if (c2 >= 0xDC00 && c2 <= 0xDFFF) {
+	                            dst((c1-0xD800)*0x400+c2-0xDC00+0x10000);
+	                            c2 = null; continue;
+	                        }
+	                    }
+	                }
+	                dst(c1);
+	            }
+	            if (c2 !== null) dst(c2);
+	        };
+
+	        /**
+	         * Converts UTF8 code points to UTF16 characters.
+	         * @param {(!function():number|null) | number} src Code points source, either as a function returning the next code point
+	         *  respectively `null` if there are no more code points left or a single numeric code point.
+	         * @param {!function(number)} dst Characters destination as a function successively called with each converted char code.
+	         * @throws {RangeError} If a code point is out of range
+	         */
+	        utfx.UTF8toUTF16 = function(src, dst) {
+	            var cp = null;
+	            if (typeof src === 'number')
+	                cp = src, src = function() { return null; };
+	            while (cp !== null || (cp = src()) !== null) {
+	                if (cp <= 0xFFFF)
+	                    dst(cp);
+	                else
+	                    cp -= 0x10000,
+	                    dst((cp>>10)+0xD800),
+	                    dst((cp%0x400)+0xDC00);
+	                cp = null;
+	            }
+	        };
+
+	        /**
+	         * Converts and encodes UTF16 characters to UTF8 bytes.
+	         * @param {!function():number|null} src Characters source as a function returning the next char code respectively `null`
+	         *  if there are no more characters left.
+	         * @param {!function(number)} dst Bytes destination as a function successively called with the next byte.
+	         */
+	        utfx.encodeUTF16toUTF8 = function(src, dst) {
+	            utfx.UTF16toUTF8(src, function(cp) {
+	                utfx.encodeUTF8(cp, dst);
+	            });
+	        };
+
+	        /**
+	         * Decodes and converts UTF8 bytes to UTF16 characters.
+	         * @param {!function():number|null} src Bytes source as a function returning the next byte respectively `null` if there
+	         *  are no more bytes left.
+	         * @param {!function(number)} dst Characters destination as a function successively called with each converted char code.
+	         * @throws {RangeError} If a starting byte is invalid in UTF8
+	         * @throws {Error} If the last sequence is truncated. Has an array property `bytes` holding the remaining bytes.
+	         */
+	        utfx.decodeUTF8toUTF16 = function(src, dst) {
+	            utfx.decodeUTF8(src, function(cp) {
+	                utfx.UTF8toUTF16(cp, dst);
+	            });
+	        };
+
+	        /**
+	         * Calculates the byte length of an UTF8 code point.
+	         * @param {number} cp UTF8 code point
+	         * @returns {number} Byte length
+	         */
+	        utfx.calculateCodePoint = function(cp) {
+	            return (cp < 0x80) ? 1 : (cp < 0x800) ? 2 : (cp < 0x10000) ? 3 : 4;
+	        };
+
+	        /**
+	         * Calculates the number of UTF8 bytes required to store UTF8 code points.
+	         * @param {(!function():number|null)} src Code points source as a function returning the next code point respectively
+	         *  `null` if there are no more code points left.
+	         * @returns {number} The number of UTF8 bytes required
+	         */
+	        utfx.calculateUTF8 = function(src) {
+	            var cp, l=0;
+	            while ((cp = src()) !== null)
+	                l += (cp < 0x80) ? 1 : (cp < 0x800) ? 2 : (cp < 0x10000) ? 3 : 4;
+	            return l;
+	        };
+
+	        /**
+	         * Calculates the number of UTF8 code points respectively UTF8 bytes required to store UTF16 char codes.
+	         * @param {(!function():number|null)} src Characters source as a function returning the next char code respectively
+	         *  `null` if there are no more characters left.
+	         * @returns {!Array.<number>} The number of UTF8 code points at index 0 and the number of UTF8 bytes required at index 1.
+	         */
+	        utfx.calculateUTF16asUTF8 = function(src) {
+	            var n=0, l=0;
+	            utfx.UTF16toUTF8(src, function(cp) {
+	                ++n; l += (cp < 0x80) ? 1 : (cp < 0x800) ? 2 : (cp < 0x10000) ? 3 : 4;
+	            });
+	            return [n,l];
+	        };
+
+	        return utfx;
+	    }();
+
+	    // encodings/utf8
+
+	    /**
+	     * Encodes this ByteBuffer's contents between {@link ByteBuffer#offset} and {@link ByteBuffer#limit} to an UTF8 encoded
+	     *  string.
+	     * @returns {string} Hex encoded string
+	     * @throws {RangeError} If `offset > limit`
+	     * @expose
+	     */
+	    ByteBufferPrototype.toUTF8 = function(begin, end) {
+	        if (typeof begin === 'undefined') begin = this.offset;
+	        if (typeof end === 'undefined') end = this.limit;
+	        if (!this.noAssert) {
+	            if (typeof begin !== 'number' || begin % 1 !== 0)
+	                throw TypeError("Illegal begin: Not an integer");
+	            begin >>>= 0;
+	            if (typeof end !== 'number' || end % 1 !== 0)
+	                throw TypeError("Illegal end: Not an integer");
+	            end >>>= 0;
+	            if (begin < 0 || begin > end || end > this.buffer.byteLength)
+	                throw RangeError("Illegal range: 0 <= "+begin+" <= "+end+" <= "+this.buffer.byteLength);
+	        }
+	        var sd; try {
+	            utfx.decodeUTF8toUTF16(function() {
+	                return begin < end ? this.view[begin++] : null;
+	            }.bind(this), sd = stringDestination());
+	        } catch (e) {
+	            if (begin !== end)
+	                throw RangeError("Illegal range: Truncated data, "+begin+" != "+end);
+	        }
+	        return sd();
+	    };
+
+	    /**
+	     * Decodes an UTF8 encoded string to a ByteBuffer.
+	     * @param {string} str String to decode
+	     * @param {boolean=} littleEndian Whether to use little or big endian byte order. Defaults to
+	     *  {@link ByteBuffer.DEFAULT_ENDIAN}.
+	     * @param {boolean=} noAssert Whether to skip assertions of offsets and values. Defaults to
+	     *  {@link ByteBuffer.DEFAULT_NOASSERT}.
+	     * @returns {!ByteBuffer} ByteBuffer
+	     * @expose
+	     */
+	    ByteBuffer.fromUTF8 = function(str, littleEndian, noAssert) {
+	        if (!noAssert)
+	            if (typeof str !== 'string')
+	                throw TypeError("Illegal str: Not a string");
+	        var bb = new ByteBuffer(utfx.calculateUTF16asUTF8(stringSource(str), true)[1], littleEndian, noAssert),
+	            i = 0;
+	        utfx.encodeUTF16toUTF8(stringSource(str), function(b) {
+	            bb.view[i++] = b;
+	        });
+	        bb.limit = i;
+	        return bb;
+	    };
+
+	    return ByteBuffer;
+	});
+
+	/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(12)(module)))
+
+/***/ },
+/* 30 */
+/***/ function(module, exports, __webpack_require__) {
+
+	var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/* WEBPACK VAR INJECTION */(function(module) {/*
+	 Copyright 2013 Daniel Wirtz <dcode@dcode.io>
+	 Copyright 2009 The Closure Library Authors. All Rights Reserved.
+
+	 Licensed under the Apache License, Version 2.0 (the "License");
+	 you may not use this file except in compliance with the License.
+	 You may obtain a copy of the License at
+
+	 http://www.apache.org/licenses/LICENSE-2.0
+
+	 Unless required by applicable law or agreed to in writing, software
+	 distributed under the License is distributed on an "AS-IS" BASIS,
+	 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+	 See the License for the specific language governing permissions and
+	 limitations under the License.
+	 */
+
+	/**
+	 * @license long.js (c) 2013 Daniel Wirtz <dcode@dcode.io>
+	 * Released under the Apache License, Version 2.0
+	 * see: https://github.com/dcodeIO/long.js for details
+	 */
+	(function(global, factory) {
+
+	    /* AMD */ if ("function" === 'function' && __webpack_require__(28)["amd"])
+	        !(__WEBPACK_AMD_DEFINE_ARRAY__ = [], __WEBPACK_AMD_DEFINE_FACTORY__ = (factory), __WEBPACK_AMD_DEFINE_RESULT__ = (typeof __WEBPACK_AMD_DEFINE_FACTORY__ === 'function' ? (__WEBPACK_AMD_DEFINE_FACTORY__.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__)) : __WEBPACK_AMD_DEFINE_FACTORY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));
+	    /* CommonJS */ else if ("function" === 'function' && typeof module === "object" && module && module["exports"])
+	        module["exports"] = factory();
+	    /* Global */ else
+	        (global["dcodeIO"] = global["dcodeIO"] || {})["Long"] = factory();
+
+	})(this, function() {
+	    "use strict";
+
+	    /**
+	     * Constructs a 64 bit two's-complement integer, given its low and high 32 bit values as *signed* integers.
+	     *  See the from* functions below for more convenient ways of constructing Longs.
+	     * @exports Long
+	     * @class A Long class for representing a 64 bit two's-complement integer value.
+	     * @param {number} low The low (signed) 32 bits of the long
+	     * @param {number} high The high (signed) 32 bits of the long
+	     * @param {boolean=} unsigned Whether unsigned or not, defaults to `false` for signed
+	     * @constructor
+	     */
+	    function Long(low, high, unsigned) {
+
+	        /**
+	         * The low 32 bits as a signed value.
+	         * @type {number}
+	         */
+	        this.low = low | 0;
+
+	        /**
+	         * The high 32 bits as a signed value.
+	         * @type {number}
+	         */
+	        this.high = high | 0;
+
+	        /**
+	         * Whether unsigned or not.
+	         * @type {boolean}
+	         */
+	        this.unsigned = !!unsigned;
+	    }
+
+	    // The internal representation of a long is the two given signed, 32-bit values.
+	    // We use 32-bit pieces because these are the size of integers on which
+	    // Javascript performs bit-operations.  For operations like addition and
+	    // multiplication, we split each number into 16 bit pieces, which can easily be
+	    // multiplied within Javascript's floating-point representation without overflow
+	    // or change in sign.
+	    //
+	    // In the algorithms below, we frequently reduce the negative case to the
+	    // positive case by negating the input(s) and then post-processing the result.
+	    // Note that we must ALWAYS check specially whether those values are MIN_VALUE
+	    // (-2^63) because -MIN_VALUE == MIN_VALUE (since 2^63 cannot be represented as
+	    // a positive number, it overflows back into a negative).  Not handling this
+	    // case would often result in infinite recursion.
+	    //
+	    // Common constant values ZERO, ONE, NEG_ONE, etc. are defined below the from*
+	    // methods on which they depend.
+
+	    /**
+	     * An indicator used to reliably determine if an object is a Long or not.
+	     * @type {boolean}
+	     * @const
+	     * @private
+	     */
+	    Long.prototype.__isLong__;
+
+	    Object.defineProperty(Long.prototype, "__isLong__", {
+	        value: true,
+	        enumerable: false,
+	        configurable: false
+	    });
+
+	    /**
+	     * @function
+	     * @param {*} obj Object
+	     * @returns {boolean}
+	     * @inner
+	     */
+	    function isLong(obj) {
+	        return (obj && obj["__isLong__"]) === true;
+	    }
+
+	    /**
+	     * Tests if the specified object is a Long.
+	     * @function
+	     * @param {*} obj Object
+	     * @returns {boolean}
+	     */
+	    Long.isLong = isLong;
+
+	    /**
+	     * A cache of the Long representations of small integer values.
+	     * @type {!Object}
+	     * @inner
+	     */
+	    var INT_CACHE = {};
+
+	    /**
+	     * A cache of the Long representations of small unsigned integer values.
+	     * @type {!Object}
+	     * @inner
+	     */
+	    var UINT_CACHE = {};
+
+	    /**
+	     * @param {number} value
+	     * @param {boolean=} unsigned
+	     * @returns {!Long}
+	     * @inner
+	     */
+	    function fromInt(value, unsigned) {
+	        var obj, cachedObj, cache;
+	        if (unsigned) {
+	            value >>>= 0;
+	            if (cache = (0 <= value && value < 256)) {
+	                cachedObj = UINT_CACHE[value];
+	                if (cachedObj)
+	                    return cachedObj;
+	            }
+	            obj = fromBits(value, (value | 0) < 0 ? -1 : 0, true);
+	            if (cache)
+	                UINT_CACHE[value] = obj;
+	            return obj;
+	        } else {
+	            value |= 0;
+	            if (cache = (-128 <= value && value < 128)) {
+	                cachedObj = INT_CACHE[value];
+	                if (cachedObj)
+	                    return cachedObj;
+	            }
+	            obj = fromBits(value, value < 0 ? -1 : 0, false);
+	            if (cache)
+	                INT_CACHE[value] = obj;
+	            return obj;
+	        }
+	    }
+
+	    /**
+	     * Returns a Long representing the given 32 bit integer value.
+	     * @function
+	     * @param {number} value The 32 bit integer in question
+	     * @param {boolean=} unsigned Whether unsigned or not, defaults to `false` for signed
+	     * @returns {!Long} The corresponding Long value
+	     */
+	    Long.fromInt = fromInt;
+
+	    /**
+	     * @param {number} value
+	     * @param {boolean=} unsigned
+	     * @returns {!Long}
+	     * @inner
+	     */
+	    function fromNumber(value, unsigned) {
+	        if (isNaN(value) || !isFinite(value))
+	            return unsigned ? UZERO : ZERO;
+	        if (unsigned) {
+	            if (value < 0)
+	                return UZERO;
+	            if (value >= TWO_PWR_64_DBL)
+	                return MAX_UNSIGNED_VALUE;
+	        } else {
+	            if (value <= -TWO_PWR_63_DBL)
+	                return MIN_VALUE;
+	            if (value + 1 >= TWO_PWR_63_DBL)
+	                return MAX_VALUE;
+	        }
+	        if (value < 0)
+	            return fromNumber(-value, unsigned).neg();
+	        return fromBits((value % TWO_PWR_32_DBL) | 0, (value / TWO_PWR_32_DBL) | 0, unsigned);
+	    }
+
+	    /**
+	     * Returns a Long representing the given value, provided that it is a finite number. Otherwise, zero is returned.
+	     * @function
+	     * @param {number} value The number in question
+	     * @param {boolean=} unsigned Whether unsigned or not, defaults to `false` for signed
+	     * @returns {!Long} The corresponding Long value
+	     */
+	    Long.fromNumber = fromNumber;
+
+	    /**
+	     * @param {number} lowBits
+	     * @param {number} highBits
+	     * @param {boolean=} unsigned
+	     * @returns {!Long}
+	     * @inner
+	     */
+	    function fromBits(lowBits, highBits, unsigned) {
+	        return new Long(lowBits, highBits, unsigned);
+	    }
+
+	    /**
+	     * Returns a Long representing the 64 bit integer that comes by concatenating the given low and high bits. Each is
+	     *  assumed to use 32 bits.
+	     * @function
+	     * @param {number} lowBits The low 32 bits
+	     * @param {number} highBits The high 32 bits
+	     * @param {boolean=} unsigned Whether unsigned or not, defaults to `false` for signed
+	     * @returns {!Long} The corresponding Long value
+	     */
+	    Long.fromBits = fromBits;
+
+	    /**
+	     * @function
+	     * @param {number} base
+	     * @param {number} exponent
+	     * @returns {number}
+	     * @inner
+	     */
+	    var pow_dbl = Math.pow; // Used 4 times (4*8 to 15+4)
+
+	    /**
+	     * @param {string} str
+	     * @param {(boolean|number)=} unsigned
+	     * @param {number=} radix
+	     * @returns {!Long}
+	     * @inner
+	     */
+	    function fromString(str, unsigned, radix) {
+	        if (str.length === 0)
+	            throw Error('empty string');
+	        if (str === "NaN" || str === "Infinity" || str === "+Infinity" || str === "-Infinity")
+	            return ZERO;
+	        if (typeof unsigned === 'number') {
+	            // For goog.math.long compatibility
+	            radix = unsigned,
+	            unsigned = false;
+	        } else {
+	            unsigned = !! unsigned;
+	        }
+	        radix = radix || 10;
+	        if (radix < 2 || 36 < radix)
+	            throw RangeError('radix');
+
+	        var p;
+	        if ((p = str.indexOf('-')) > 0)
+	            throw Error('interior hyphen');
+	        else if (p === 0) {
+	            return fromString(str.substring(1), unsigned, radix).neg();
+	        }
+
+	        // Do several (8) digits each time through the loop, so as to
+	        // minimize the calls to the very expensive emulated div.
+	        var radixToPower = fromNumber(pow_dbl(radix, 8));
+
+	        var result = ZERO;
+	        for (var i = 0; i < str.length; i += 8) {
+	            var size = Math.min(8, str.length - i),
+	                value = parseInt(str.substring(i, i + size), radix);
+	            if (size < 8) {
+	                var power = fromNumber(pow_dbl(radix, size));
+	                result = result.mul(power).add(fromNumber(value));
+	            } else {
+	                result = result.mul(radixToPower);
+	                result = result.add(fromNumber(value));
+	            }
+	        }
+	        result.unsigned = unsigned;
+	        return result;
+	    }
+
+	    /**
+	     * Returns a Long representation of the given string, written using the specified radix.
+	     * @function
+	     * @param {string} str The textual representation of the Long
+	     * @param {(boolean|number)=} unsigned Whether unsigned or not, defaults to `false` for signed
+	     * @param {number=} radix The radix in which the text is written (2-36), defaults to 10
+	     * @returns {!Long} The corresponding Long value
+	     */
+	    Long.fromString = fromString;
+
+	    /**
+	     * @function
+	     * @param {!Long|number|string|!{low: number, high: number, unsigned: boolean}} val
+	     * @returns {!Long}
+	     * @inner
+	     */
+	    function fromValue(val) {
+	        if (val /* is compatible */ instanceof Long)
+	            return val;
+	        if (typeof val === 'number')
+	            return fromNumber(val);
+	        if (typeof val === 'string')
+	            return fromString(val);
+	        // Throws for non-objects, converts non-instanceof Long:
+	        return fromBits(val.low, val.high, val.unsigned);
+	    }
+
+	    /**
+	     * Converts the specified value to a Long.
+	     * @function
+	     * @param {!Long|number|string|!{low: number, high: number, unsigned: boolean}} val Value
+	     * @returns {!Long}
+	     */
+	    Long.fromValue = fromValue;
+
+	    // NOTE: the compiler should inline these constant values below and then remove these variables, so there should be
+	    // no runtime penalty for these.
+
+	    /**
+	     * @type {number}
+	     * @const
+	     * @inner
+	     */
+	    var TWO_PWR_16_DBL = 1 << 16;
+
+	    /**
+	     * @type {number}
+	     * @const
+	     * @inner
+	     */
+	    var TWO_PWR_24_DBL = 1 << 24;
+
+	    /**
+	     * @type {number}
+	     * @const
+	     * @inner
+	     */
+	    var TWO_PWR_32_DBL = TWO_PWR_16_DBL * TWO_PWR_16_DBL;
+
+	    /**
+	     * @type {number}
+	     * @const
+	     * @inner
+	     */
+	    var TWO_PWR_64_DBL = TWO_PWR_32_DBL * TWO_PWR_32_DBL;
+
+	    /**
+	     * @type {number}
+	     * @const
+	     * @inner
+	     */
+	    var TWO_PWR_63_DBL = TWO_PWR_64_DBL / 2;
+
+	    /**
+	     * @type {!Long}
+	     * @const
+	     * @inner
+	     */
+	    var TWO_PWR_24 = fromInt(TWO_PWR_24_DBL);
+
+	    /**
+	     * @type {!Long}
+	     * @inner
+	     */
+	    var ZERO = fromInt(0);
+
+	    /**
+	     * Signed zero.
+	     * @type {!Long}
+	     */
+	    Long.ZERO = ZERO;
+
+	    /**
+	     * @type {!Long}
+	     * @inner
+	     */
+	    var UZERO = fromInt(0, true);
+
+	    /**
+	     * Unsigned zero.
+	     * @type {!Long}
+	     */
+	    Long.UZERO = UZERO;
+
+	    /**
+	     * @type {!Long}
+	     * @inner
+	     */
+	    var ONE = fromInt(1);
+
+	    /**
+	     * Signed one.
+	     * @type {!Long}
+	     */
+	    Long.ONE = ONE;
+
+	    /**
+	     * @type {!Long}
+	     * @inner
+	     */
+	    var UONE = fromInt(1, true);
+
+	    /**
+	     * Unsigned one.
+	     * @type {!Long}
+	     */
+	    Long.UONE = UONE;
+
+	    /**
+	     * @type {!Long}
+	     * @inner
+	     */
+	    var NEG_ONE = fromInt(-1);
+
+	    /**
+	     * Signed negative one.
+	     * @type {!Long}
+	     */
+	    Long.NEG_ONE = NEG_ONE;
+
+	    /**
+	     * @type {!Long}
+	     * @inner
+	     */
+	    var MAX_VALUE = fromBits(0xFFFFFFFF|0, 0x7FFFFFFF|0, false);
+
+	    /**
+	     * Maximum signed value.
+	     * @type {!Long}
+	     */
+	    Long.MAX_VALUE = MAX_VALUE;
+
+	    /**
+	     * @type {!Long}
+	     * @inner
+	     */
+	    var MAX_UNSIGNED_VALUE = fromBits(0xFFFFFFFF|0, 0xFFFFFFFF|0, true);
+
+	    /**
+	     * Maximum unsigned value.
+	     * @type {!Long}
+	     */
+	    Long.MAX_UNSIGNED_VALUE = MAX_UNSIGNED_VALUE;
+
+	    /**
+	     * @type {!Long}
+	     * @inner
+	     */
+	    var MIN_VALUE = fromBits(0, 0x80000000|0, false);
+
+	    /**
+	     * Minimum signed value.
+	     * @type {!Long}
+	     */
+	    Long.MIN_VALUE = MIN_VALUE;
+
+	    /**
+	     * @alias Long.prototype
+	     * @inner
+	     */
+	    var LongPrototype = Long.prototype;
+
+	    /**
+	     * Converts the Long to a 32 bit integer, assuming it is a 32 bit integer.
+	     * @returns {number}
+	     */
+	    LongPrototype.toInt = function toInt() {
+	        return this.unsigned ? this.low >>> 0 : this.low;
+	    };
+
+	    /**
+	     * Converts the Long to a the nearest floating-point representation of this value (double, 53 bit mantissa).
+	     * @returns {number}
+	     */
+	    LongPrototype.toNumber = function toNumber() {
+	        if (this.unsigned)
+	            return ((this.high >>> 0) * TWO_PWR_32_DBL) + (this.low >>> 0);
+	        return this.high * TWO_PWR_32_DBL + (this.low >>> 0);
+	    };
+
+	    /**
+	     * Converts the Long to a string written in the specified radix.
+	     * @param {number=} radix Radix (2-36), defaults to 10
+	     * @returns {string}
+	     * @override
+	     * @throws {RangeError} If `radix` is out of range
+	     */
+	    LongPrototype.toString = function toString(radix) {
+	        radix = radix || 10;
+	        if (radix < 2 || 36 < radix)
+	            throw RangeError('radix');
+	        if (this.isZero())
+	            return '0';
+	        if (this.isNegative()) { // Unsigned Longs are never negative
+	            if (this.eq(MIN_VALUE)) {
+	                // We need to change the Long value before it can be negated, so we remove
+	                // the bottom-most digit in this base and then recurse to do the rest.
+	                var radixLong = fromNumber(radix),
+	                    div = this.div(radixLong),
+	                    rem1 = div.mul(radixLong).sub(this);
+	                return div.toString(radix) + rem1.toInt().toString(radix);
+	            } else
+	                return '-' + this.neg().toString(radix);
+	        }
+
+	        // Do several (6) digits each time through the loop, so as to
+	        // minimize the calls to the very expensive emulated div.
+	        var radixToPower = fromNumber(pow_dbl(radix, 6), this.unsigned),
+	            rem = this;
+	        var result = '';
+	        while (true) {
+	            var remDiv = rem.div(radixToPower),
+	                intval = rem.sub(remDiv.mul(radixToPower)).toInt() >>> 0,
+	                digits = intval.toString(radix);
+	            rem = remDiv;
+	            if (rem.isZero())
+	                return digits + result;
+	            else {
+	                while (digits.length < 6)
+	                    digits = '0' + digits;
+	                result = '' + digits + result;
+	            }
+	        }
+	    };
+
+	    /**
+	     * Gets the high 32 bits as a signed integer.
+	     * @returns {number} Signed high bits
+	     */
+	    LongPrototype.getHighBits = function getHighBits() {
+	        return this.high;
+	    };
+
+	    /**
+	     * Gets the high 32 bits as an unsigned integer.
+	     * @returns {number} Unsigned high bits
+	     */
+	    LongPrototype.getHighBitsUnsigned = function getHighBitsUnsigned() {
+	        return this.high >>> 0;
+	    };
+
+	    /**
+	     * Gets the low 32 bits as a signed integer.
+	     * @returns {number} Signed low bits
+	     */
+	    LongPrototype.getLowBits = function getLowBits() {
+	        return this.low;
+	    };
+
+	    /**
+	     * Gets the low 32 bits as an unsigned integer.
+	     * @returns {number} Unsigned low bits
+	     */
+	    LongPrototype.getLowBitsUnsigned = function getLowBitsUnsigned() {
+	        return this.low >>> 0;
+	    };
+
+	    /**
+	     * Gets the number of bits needed to represent the absolute value of this Long.
+	     * @returns {number}
+	     */
+	    LongPrototype.getNumBitsAbs = function getNumBitsAbs() {
+	        if (this.isNegative()) // Unsigned Longs are never negative
+	            return this.eq(MIN_VALUE) ? 64 : this.neg().getNumBitsAbs();
+	        var val = this.high != 0 ? this.high : this.low;
+	        for (var bit = 31; bit > 0; bit--)
+	            if ((val & (1 << bit)) != 0)
+	                break;
+	        return this.high != 0 ? bit + 33 : bit + 1;
+	    };
+
+	    /**
+	     * Tests if this Long's value equals zero.
+	     * @returns {boolean}
+	     */
+	    LongPrototype.isZero = function isZero() {
+	        return this.high === 0 && this.low === 0;
+	    };
+
+	    /**
+	     * Tests if this Long's value is negative.
+	     * @returns {boolean}
+	     */
+	    LongPrototype.isNegative = function isNegative() {
+	        return !this.unsigned && this.high < 0;
+	    };
+
+	    /**
+	     * Tests if this Long's value is positive.
+	     * @returns {boolean}
+	     */
+	    LongPrototype.isPositive = function isPositive() {
+	        return this.unsigned || this.high >= 0;
+	    };
+
+	    /**
+	     * Tests if this Long's value is odd.
+	     * @returns {boolean}
+	     */
+	    LongPrototype.isOdd = function isOdd() {
+	        return (this.low & 1) === 1;
+	    };
+
+	    /**
+	     * Tests if this Long's value is even.
+	     * @returns {boolean}
+	     */
+	    LongPrototype.isEven = function isEven() {
+	        return (this.low & 1) === 0;
+	    };
+
+	    /**
+	     * Tests if this Long's value equals the specified's.
+	     * @param {!Long|number|string} other Other value
+	     * @returns {boolean}
+	     */
+	    LongPrototype.equals = function equals(other) {
+	        if (!isLong(other))
+	            other = fromValue(other);
+	        if (this.unsigned !== other.unsigned && (this.high >>> 31) === 1 && (other.high >>> 31) === 1)
+	            return false;
+	        return this.high === other.high && this.low === other.low;
+	    };
+
+	    /**
+	     * Tests if this Long's value equals the specified's. This is an alias of {@link Long#equals}.
+	     * @function
+	     * @param {!Long|number|string} other Other value
+	     * @returns {boolean}
+	     */
+	    LongPrototype.eq = LongPrototype.equals;
+
+	    /**
+	     * Tests if this Long's value differs from the specified's.
+	     * @param {!Long|number|string} other Other value
+	     * @returns {boolean}
+	     */
+	    LongPrototype.notEquals = function notEquals(other) {
+	        return !this.eq(/* validates */ other);
+	    };
+
+	    /**
+	     * Tests if this Long's value differs from the specified's. This is an alias of {@link Long#notEquals}.
+	     * @function
+	     * @param {!Long|number|string} other Other value
+	     * @returns {boolean}
+	     */
+	    LongPrototype.neq = LongPrototype.notEquals;
+
+	    /**
+	     * Tests if this Long's value is less than the specified's.
+	     * @param {!Long|number|string} other Other value
+	     * @returns {boolean}
+	     */
+	    LongPrototype.lessThan = function lessThan(other) {
+	        return this.comp(/* validates */ other) < 0;
+	    };
+
+	    /**
+	     * Tests if this Long's value is less than the specified's. This is an alias of {@link Long#lessThan}.
+	     * @function
+	     * @param {!Long|number|string} other Other value
+	     * @returns {boolean}
+	     */
+	    LongPrototype.lt = LongPrototype.lessThan;
+
+	    /**
+	     * Tests if this Long's value is less than or equal the specified's.
+	     * @param {!Long|number|string} other Other value
+	     * @returns {boolean}
+	     */
+	    LongPrototype.lessThanOrEqual = function lessThanOrEqual(other) {
+	        return this.comp(/* validates */ other) <= 0;
+	    };
+
+	    /**
+	     * Tests if this Long's value is less than or equal the specified's. This is an alias of {@link Long#lessThanOrEqual}.
+	     * @function
+	     * @param {!Long|number|string} other Other value
+	     * @returns {boolean}
+	     */
+	    LongPrototype.lte = LongPrototype.lessThanOrEqual;
+
+	    /**
+	     * Tests if this Long's value is greater than the specified's.
+	     * @param {!Long|number|string} other Other value
+	     * @returns {boolean}
+	     */
+	    LongPrototype.greaterThan = function greaterThan(other) {
+	        return this.comp(/* validates */ other) > 0;
+	    };
+
+	    /**
+	     * Tests if this Long's value is greater than the specified's. This is an alias of {@link Long#greaterThan}.
+	     * @function
+	     * @param {!Long|number|string} other Other value
+	     * @returns {boolean}
+	     */
+	    LongPrototype.gt = LongPrototype.greaterThan;
+
+	    /**
+	     * Tests if this Long's value is greater than or equal the specified's.
+	     * @param {!Long|number|string} other Other value
+	     * @returns {boolean}
+	     */
+	    LongPrototype.greaterThanOrEqual = function greaterThanOrEqual(other) {
+	        return this.comp(/* validates */ other) >= 0;
+	    };
+
+	    /**
+	     * Tests if this Long's value is greater than or equal the specified's. This is an alias of {@link Long#greaterThanOrEqual}.
+	     * @function
+	     * @param {!Long|number|string} other Other value
+	     * @returns {boolean}
+	     */
+	    LongPrototype.gte = LongPrototype.greaterThanOrEqual;
+
+	    /**
+	     * Compares this Long's value with the specified's.
+	     * @param {!Long|number|string} other Other value
+	     * @returns {number} 0 if they are the same, 1 if the this is greater and -1
+	     *  if the given one is greater
+	     */
+	    LongPrototype.compare = function compare(other) {
+	        if (!isLong(other))
+	            other = fromValue(other);
+	        if (this.eq(other))
+	            return 0;
+	        var thisNeg = this.isNegative(),
+	            otherNeg = other.isNegative();
+	        if (thisNeg && !otherNeg)
+	            return -1;
+	        if (!thisNeg && otherNeg)
+	            return 1;
+	        // At this point the sign bits are the same
+	        if (!this.unsigned)
+	            return this.sub(other).isNegative() ? -1 : 1;
+	        // Both are positive if at least one is unsigned
+	        return (other.high >>> 0) > (this.high >>> 0) || (other.high === this.high && (other.low >>> 0) > (this.low >>> 0)) ? -1 : 1;
+	    };
+
+	    /**
+	     * Compares this Long's value with the specified's. This is an alias of {@link Long#compare}.
+	     * @function
+	     * @param {!Long|number|string} other Other value
+	     * @returns {number} 0 if they are the same, 1 if the this is greater and -1
+	     *  if the given one is greater
+	     */
+	    LongPrototype.comp = LongPrototype.compare;
+
+	    /**
+	     * Negates this Long's value.
+	     * @returns {!Long} Negated Long
+	     */
+	    LongPrototype.negate = function negate() {
+	        if (!this.unsigned && this.eq(MIN_VALUE))
+	            return MIN_VALUE;
+	        return this.not().add(ONE);
+	    };
+
+	    /**
+	     * Negates this Long's value. This is an alias of {@link Long#negate}.
+	     * @function
+	     * @returns {!Long} Negated Long
+	     */
+	    LongPrototype.neg = LongPrototype.negate;
+
+	    /**
+	     * Returns the sum of this and the specified Long.
+	     * @param {!Long|number|string} addend Addend
+	     * @returns {!Long} Sum
+	     */
+	    LongPrototype.add = function add(addend) {
+	        if (!isLong(addend))
+	            addend = fromValue(addend);
+
+	        // Divide each number into 4 chunks of 16 bits, and then sum the chunks.
+
+	        var a48 = this.high >>> 16;
+	        var a32 = this.high & 0xFFFF;
+	        var a16 = this.low >>> 16;
+	        var a00 = this.low & 0xFFFF;
+
+	        var b48 = addend.high >>> 16;
+	        var b32 = addend.high & 0xFFFF;
+	        var b16 = addend.low >>> 16;
+	        var b00 = addend.low & 0xFFFF;
+
+	        var c48 = 0, c32 = 0, c16 = 0, c00 = 0;
+	        c00 += a00 + b00;
+	        c16 += c00 >>> 16;
+	        c00 &= 0xFFFF;
+	        c16 += a16 + b16;
+	        c32 += c16 >>> 16;
+	        c16 &= 0xFFFF;
+	        c32 += a32 + b32;
+	        c48 += c32 >>> 16;
+	        c32 &= 0xFFFF;
+	        c48 += a48 + b48;
+	        c48 &= 0xFFFF;
+	        return fromBits((c16 << 16) | c00, (c48 << 16) | c32, this.unsigned);
+	    };
+
+	    /**
+	     * Returns the difference of this and the specified Long.
+	     * @param {!Long|number|string} subtrahend Subtrahend
+	     * @returns {!Long} Difference
+	     */
+	    LongPrototype.subtract = function subtract(subtrahend) {
+	        if (!isLong(subtrahend))
+	            subtrahend = fromValue(subtrahend);
+	        return this.add(subtrahend.neg());
+	    };
+
+	    /**
+	     * Returns the difference of this and the specified Long. This is an alias of {@link Long#subtract}.
+	     * @function
+	     * @param {!Long|number|string} subtrahend Subtrahend
+	     * @returns {!Long} Difference
+	     */
+	    LongPrototype.sub = LongPrototype.subtract;
+
+	    /**
+	     * Returns the product of this and the specified Long.
+	     * @param {!Long|number|string} multiplier Multiplier
+	     * @returns {!Long} Product
+	     */
+	    LongPrototype.multiply = function multiply(multiplier) {
+	        if (this.isZero())
+	            return ZERO;
+	        if (!isLong(multiplier))
+	            multiplier = fromValue(multiplier);
+	        if (multiplier.isZero())
+	            return ZERO;
+	        if (this.eq(MIN_VALUE))
+	            return multiplier.isOdd() ? MIN_VALUE : ZERO;
+	        if (multiplier.eq(MIN_VALUE))
+	            return this.isOdd() ? MIN_VALUE : ZERO;
+
+	        if (this.isNegative()) {
+	            if (multiplier.isNegative())
+	                return this.neg().mul(multiplier.neg());
+	            else
+	                return this.neg().mul(multiplier).neg();
+	        } else if (multiplier.isNegative())
+	            return this.mul(multiplier.neg()).neg();
+
+	        // If both longs are small, use float multiplication
+	        if (this.lt(TWO_PWR_24) && multiplier.lt(TWO_PWR_24))
+	            return fromNumber(this.toNumber() * multiplier.toNumber(), this.unsigned);
+
+	        // Divide each long into 4 chunks of 16 bits, and then add up 4x4 products.
+	        // We can skip products that would overflow.
+
+	        var a48 = this.high >>> 16;
+	        var a32 = this.high & 0xFFFF;
+	        var a16 = this.low >>> 16;
+	        var a00 = this.low & 0xFFFF;
+
+	        var b48 = multiplier.high >>> 16;
+	        var b32 = multiplier.high & 0xFFFF;
+	        var b16 = multiplier.low >>> 16;
+	        var b00 = multiplier.low & 0xFFFF;
+
+	        var c48 = 0, c32 = 0, c16 = 0, c00 = 0;
+	        c00 += a00 * b00;
+	        c16 += c00 >>> 16;
+	        c00 &= 0xFFFF;
+	        c16 += a16 * b00;
+	        c32 += c16 >>> 16;
+	        c16 &= 0xFFFF;
+	        c16 += a00 * b16;
+	        c32 += c16 >>> 16;
+	        c16 &= 0xFFFF;
+	        c32 += a32 * b00;
+	        c48 += c32 >>> 16;
+	        c32 &= 0xFFFF;
+	        c32 += a16 * b16;
+	        c48 += c32 >>> 16;
+	        c32 &= 0xFFFF;
+	        c32 += a00 * b32;
+	        c48 += c32 >>> 16;
+	        c32 &= 0xFFFF;
+	        c48 += a48 * b00 + a32 * b16 + a16 * b32 + a00 * b48;
+	        c48 &= 0xFFFF;
+	        return fromBits((c16 << 16) | c00, (c48 << 16) | c32, this.unsigned);
+	    };
+
+	    /**
+	     * Returns the product of this and the specified Long. This is an alias of {@link Long#multiply}.
+	     * @function
+	     * @param {!Long|number|string} multiplier Multiplier
+	     * @returns {!Long} Product
+	     */
+	    LongPrototype.mul = LongPrototype.multiply;
+
+	    /**
+	     * Returns this Long divided by the specified. The result is signed if this Long is signed or
+	     *  unsigned if this Long is unsigned.
+	     * @param {!Long|number|string} divisor Divisor
+	     * @returns {!Long} Quotient
+	     */
+	    LongPrototype.divide = function divide(divisor) {
+	        if (!isLong(divisor))
+	            divisor = fromValue(divisor);
+	        if (divisor.isZero())
+	            throw Error('division by zero');
+	        if (this.isZero())
+	            return this.unsigned ? UZERO : ZERO;
+	        var approx, rem, res;
+	        if (!this.unsigned) {
+	            // This section is only relevant for signed longs and is derived from the
+	            // closure library as a whole.
+	            if (this.eq(MIN_VALUE)) {
+	                if (divisor.eq(ONE) || divisor.eq(NEG_ONE))
+	                    return MIN_VALUE;  // recall that -MIN_VALUE == MIN_VALUE
+	                else if (divisor.eq(MIN_VALUE))
+	                    return ONE;
+	                else {
+	                    // At this point, we have |other| >= 2, so |this/other| < |MIN_VALUE|.
+	                    var halfThis = this.shr(1);
+	                    approx = halfThis.div(divisor).shl(1);
+	                    if (approx.eq(ZERO)) {
+	                        return divisor.isNegative() ? ONE : NEG_ONE;
+	                    } else {
+	                        rem = this.sub(divisor.mul(approx));
+	                        res = approx.add(rem.div(divisor));
+	                        return res;
+	                    }
+	                }
+	            } else if (divisor.eq(MIN_VALUE))
+	                return this.unsigned ? UZERO : ZERO;
+	            if (this.isNegative()) {
+	                if (divisor.isNegative())
+	                    return this.neg().div(divisor.neg());
+	                return this.neg().div(divisor).neg();
+	            } else if (divisor.isNegative())
+	                return this.div(divisor.neg()).neg();
+	            res = ZERO;
+	        } else {
+	            // The algorithm below has not been made for unsigned longs. It's therefore
+	            // required to take special care of the MSB prior to running it.
+	            if (!divisor.unsigned)
+	                divisor = divisor.toUnsigned();
+	            if (divisor.gt(this))
+	                return UZERO;
+	            if (divisor.gt(this.shru(1))) // 15 >>> 1 = 7 ; with divisor = 8 ; true
+	                return UONE;
+	            res = UZERO;
+	        }
+
+	        // Repeat the following until the remainder is less than other:  find a
+	        // floating-point that approximates remainder / other *from below*, add this
+	        // into the result, and subtract it from the remainder.  It is critical that
+	        // the approximate value is less than or equal to the real value so that the
+	        // remainder never becomes negative.
+	        rem = this;
+	        while (rem.gte(divisor)) {
+	            // Approximate the result of division. This may be a little greater or
+	            // smaller than the actual value.
+	            approx = Math.max(1, Math.floor(rem.toNumber() / divisor.toNumber()));
+
+	            // We will tweak the approximate result by changing it in the 48-th digit or
+	            // the smallest non-fractional digit, whichever is larger.
+	            var log2 = Math.ceil(Math.log(approx) / Math.LN2),
+	                delta = (log2 <= 48) ? 1 : pow_dbl(2, log2 - 48),
+
+	            // Decrease the approximation until it is smaller than the remainder.  Note
+	            // that if it is too large, the product overflows and is negative.
+	                approxRes = fromNumber(approx),
+	                approxRem = approxRes.mul(divisor);
+	            while (approxRem.isNegative() || approxRem.gt(rem)) {
+	                approx -= delta;
+	                approxRes = fromNumber(approx, this.unsigned);
+	                approxRem = approxRes.mul(divisor);
+	            }
+
+	            // We know the answer can't be zero... and actually, zero would cause
+	            // infinite recursion since we would make no progress.
+	            if (approxRes.isZero())
+	                approxRes = ONE;
+
+	            res = res.add(approxRes);
+	            rem = rem.sub(approxRem);
+	        }
+	        return res;
+	    };
+
+	    /**
+	     * Returns this Long divided by the specified. This is an alias of {@link Long#divide}.
+	     * @function
+	     * @param {!Long|number|string} divisor Divisor
+	     * @returns {!Long} Quotient
+	     */
+	    LongPrototype.div = LongPrototype.divide;
+
+	    /**
+	     * Returns this Long modulo the specified.
+	     * @param {!Long|number|string} divisor Divisor
+	     * @returns {!Long} Remainder
+	     */
+	    LongPrototype.modulo = function modulo(divisor) {
+	        if (!isLong(divisor))
+	            divisor = fromValue(divisor);
+	        return this.sub(this.div(divisor).mul(divisor));
+	    };
+
+	    /**
+	     * Returns this Long modulo the specified. This is an alias of {@link Long#modulo}.
+	     * @function
+	     * @param {!Long|number|string} divisor Divisor
+	     * @returns {!Long} Remainder
+	     */
+	    LongPrototype.mod = LongPrototype.modulo;
+
+	    /**
+	     * Returns the bitwise NOT of this Long.
+	     * @returns {!Long}
+	     */
+	    LongPrototype.not = function not() {
+	        return fromBits(~this.low, ~this.high, this.unsigned);
+	    };
+
+	    /**
+	     * Returns the bitwise AND of this Long and the specified.
+	     * @param {!Long|number|string} other Other Long
+	     * @returns {!Long}
+	     */
+	    LongPrototype.and = function and(other) {
+	        if (!isLong(other))
+	            other = fromValue(other);
+	        return fromBits(this.low & other.low, this.high & other.high, this.unsigned);
+	    };
+
+	    /**
+	     * Returns the bitwise OR of this Long and the specified.
+	     * @param {!Long|number|string} other Other Long
+	     * @returns {!Long}
+	     */
+	    LongPrototype.or = function or(other) {
+	        if (!isLong(other))
+	            other = fromValue(other);
+	        return fromBits(this.low | other.low, this.high | other.high, this.unsigned);
+	    };
+
+	    /**
+	     * Returns the bitwise XOR of this Long and the given one.
+	     * @param {!Long|number|string} other Other Long
+	     * @returns {!Long}
+	     */
+	    LongPrototype.xor = function xor(other) {
+	        if (!isLong(other))
+	            other = fromValue(other);
+	        return fromBits(this.low ^ other.low, this.high ^ other.high, this.unsigned);
+	    };
+
+	    /**
+	     * Returns this Long with bits shifted to the left by the given amount.
+	     * @param {number|!Long} numBits Number of bits
+	     * @returns {!Long} Shifted Long
+	     */
+	    LongPrototype.shiftLeft = function shiftLeft(numBits) {
+	        if (isLong(numBits))
+	            numBits = numBits.toInt();
+	        if ((numBits &= 63) === 0)
+	            return this;
+	        else if (numBits < 32)
+	            return fromBits(this.low << numBits, (this.high << numBits) | (this.low >>> (32 - numBits)), this.unsigned);
+	        else
+	            return fromBits(0, this.low << (numBits - 32), this.unsigned);
+	    };
+
+	    /**
+	     * Returns this Long with bits shifted to the left by the given amount. This is an alias of {@link Long#shiftLeft}.
+	     * @function
+	     * @param {number|!Long} numBits Number of bits
+	     * @returns {!Long} Shifted Long
+	     */
+	    LongPrototype.shl = LongPrototype.shiftLeft;
+
+	    /**
+	     * Returns this Long with bits arithmetically shifted to the right by the given amount.
+	     * @param {number|!Long} numBits Number of bits
+	     * @returns {!Long} Shifted Long
+	     */
+	    LongPrototype.shiftRight = function shiftRight(numBits) {
+	        if (isLong(numBits))
+	            numBits = numBits.toInt();
+	        if ((numBits &= 63) === 0)
+	            return this;
+	        else if (numBits < 32)
+	            return fromBits((this.low >>> numBits) | (this.high << (32 - numBits)), this.high >> numBits, this.unsigned);
+	        else
+	            return fromBits(this.high >> (numBits - 32), this.high >= 0 ? 0 : -1, this.unsigned);
+	    };
+
+	    /**
+	     * Returns this Long with bits arithmetically shifted to the right by the given amount. This is an alias of {@link Long#shiftRight}.
+	     * @function
+	     * @param {number|!Long} numBits Number of bits
+	     * @returns {!Long} Shifted Long
+	     */
+	    LongPrototype.shr = LongPrototype.shiftRight;
+
+	    /**
+	     * Returns this Long with bits logically shifted to the right by the given amount.
+	     * @param {number|!Long} numBits Number of bits
+	     * @returns {!Long} Shifted Long
+	     */
+	    LongPrototype.shiftRightUnsigned = function shiftRightUnsigned(numBits) {
+	        if (isLong(numBits))
+	            numBits = numBits.toInt();
+	        numBits &= 63;
+	        if (numBits === 0)
+	            return this;
+	        else {
+	            var high = this.high;
+	            if (numBits < 32) {
+	                var low = this.low;
+	                return fromBits((low >>> numBits) | (high << (32 - numBits)), high >>> numBits, this.unsigned);
+	            } else if (numBits === 32)
+	                return fromBits(high, 0, this.unsigned);
+	            else
+	                return fromBits(high >>> (numBits - 32), 0, this.unsigned);
+	        }
+	    };
+
+	    /**
+	     * Returns this Long with bits logically shifted to the right by the given amount. This is an alias of {@link Long#shiftRightUnsigned}.
+	     * @function
+	     * @param {number|!Long} numBits Number of bits
+	     * @returns {!Long} Shifted Long
+	     */
+	    LongPrototype.shru = LongPrototype.shiftRightUnsigned;
+
+	    /**
+	     * Converts this Long to signed.
+	     * @returns {!Long} Signed long
+	     */
+	    LongPrototype.toSigned = function toSigned() {
+	        if (!this.unsigned)
+	            return this;
+	        return fromBits(this.low, this.high, false);
+	    };
+
+	    /**
+	     * Converts this Long to unsigned.
+	     * @returns {!Long} Unsigned long
+	     */
+	    LongPrototype.toUnsigned = function toUnsigned() {
+	        if (this.unsigned)
+	            return this;
+	        return fromBits(this.low, this.high, true);
+	    };
+
+	    /**
+	     * Converts this Long to its byte representation.
+	     * @param {boolean=} le Whether little or big endian, defaults to big endian
+	     * @returns {!Array.<number>} Byte representation
+	     */
+	    LongPrototype.toBytes = function(le) {
+	        return le ? this.toBytesLE() : this.toBytesBE();
+	    }
+
+	    /**
+	     * Converts this Long to its little endian byte representation.
+	     * @returns {!Array.<number>} Little endian byte representation
+	     */
+	    LongPrototype.toBytesLE = function() {
+	        var hi = this.high,
+	            lo = this.low;
+	        return [
+	             lo         & 0xff,
+	            (lo >>>  8) & 0xff,
+	            (lo >>> 16) & 0xff,
+	            (lo >>> 24) & 0xff,
+	             hi         & 0xff,
+	            (hi >>>  8) & 0xff,
+	            (hi >>> 16) & 0xff,
+	            (hi >>> 24) & 0xff
+	        ];
+	    }
+
+	    /**
+	     * Converts this Long to its big endian byte representation.
+	     * @returns {!Array.<number>} Big endian byte representation
+	     */
+	    LongPrototype.toBytesBE = function() {
+	        var hi = this.high,
+	            lo = this.low;
+	        return [
+	            (hi >>> 24) & 0xff,
+	            (hi >>> 16) & 0xff,
+	            (hi >>>  8) & 0xff,
+	             hi         & 0xff,
+	            (lo >>> 24) & 0xff,
+	            (lo >>> 16) & 0xff,
+	            (lo >>>  8) & 0xff,
+	             lo         & 0xff
+	        ];
+	    }
+
+	    return Long;
+	});
+
+	/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(12)(module)))
+
+/***/ },
+/* 31 */
+/***/ function(module, exports) {
+
+	/* (ignored) */
+
+/***/ },
+/* 32 */
+/***/ function(module, exports) {
+
+	/* (ignored) */
+
+/***/ },
+/* 33 */
+/***/ function(module, exports) {
+
+	"use strict";
+
+	Object.defineProperty(exports, "__esModule", {
+	    value: true
+	});
+	var _default = "\n//start\n// pdu_def.proto\n\n// syntax = \"proto3\";\n\noption optimize_for = LITE_RUNTIME;\nenum RCPduPriority_E {\n    DP_TOP = 0;\n    DP_HIGH = 1;\n    DP_MEDIUM = 2;\n    DP_LOW = 3;\n}\n\nenum RCPduSegment_E {\n    SEG_BEGIN = 0;\n    SEG_END = 1;\n    SEG_ONCE = 2;\n}\n\nenum RCPduTokenStatus_E {\n    TS_NOT_IN_USE = 0;\n    TS_SELF_GRABBED = 1;\n    TS_OTHER_GRABBED = 2;\n    TS_SELF_INHIBITED = 3;\n    TS_OTHER_INHIBITED = 4;\n    TS_SELF_RECIPIENT = 5;\n    TS_SELF_GIVING = 6;\n    TS_OTHER_GIVING = 7;\n}\n\nenum RCPduType_E {\n// GCC PDU\n    RCPDU_CONNECT_PROVIDER_REQUEST = 0;\n    RCPDU_CONNECT_PROVIDER_RESPONSE = 1;\n    RCPDU_CONFERENCE_JOIN_REQUEST = 2;\n    RCPDU_CONFERENCE_JOIN_RESPONSE = 3;\n    RCPDU_CONFERENCE_INVITE_REQUEST = 10;\n    RCPDU_CONFERENCE_INVITE_RESPONSE = 11;\n    RCPDU_CONFERENCE_LOCK_REQUEST = 20;\n    RCPDU_CONFERENCE_LOCK_RESPONSE = 21;\n    RCPDU_CONFERENCE_LOCK_INDICATION = 22;\n    RCPDU_CONFERENCE_UNLOCK_REQUEST = 30;\n    RCPDU_CONFERENCE_UNLOCK_RESPONSE = 31;\n    RCPDU_CONFERENCE_UNLOCK_INDICATION = 32;\n    RCPDU_CONFERENCE_LEAVE_REQUEST = 39;\n    RCPDU_CONFERENCE_TERMINATE_REQUEST = 40;\n    RCPDU_CONFERENCE_TERMINATE_RESPONSE = 41;\n    RCPDU_CONFERENCE_TERMINATE_INDICATION = 42;\n    RCPDU_CONFERENCE_EJECT_USER_REQUEST = 50;\n    RCPDU_CONFERENCE_EJECT_USER_RESPONSE = 51;\n    RCPDU_CONFERENCE_EJECT_USER_INDICATION = 52;\n    RCPDU_ROSTER_UPDATE_INDICATION = 60;\n    RCPDU_REGISTRY_UPDATE_REQUEST = 70; // INCLUDE ALL OBJS OPERATION\n    RCPDU_REGISTRY_UPDATE_RESPONSE = 71;\n    RCPDU_REGISTRY_UPDATE_INDICATION = 72;\n    RCPDU_FUNCTION_NOT_SUPPORTED_RESPONSE = 80;\n\n    // MCS PDU\n    RCPDU_SESSION_JOIN_REQUEST = 90;\n    RCPDU_SESSION_JOIN_RESPONSE = 91;\n    RCPDU_CHANNEL_GRAB_REQUEST = 100;\n    RCPDU_CHANNEL_GRAB_RESPONSE = 101;\n    RCPDU_CHANNEL_GRAB_INDICATION = 102;\n    RCPDU_CHANNEL_JOIN_REQUEST = 103;\n    RCPDU_CHANNEL_JOIN_RESPONSE = 104;\n    RCPDU_CHANNEL_LEAVE_REQUEST = 105;\n    RCPDU_CHANNEL_RELEASE_REQUEST = 106;\n    RCPDU_CHANNEL_RELEASE_INDICATION = 107;\n    RCPDU_SEND_DATA_REQUEST = 120;\n    RCPDU_SEND_DATA_INDICATION = 121;\n    RCPDU_UNIFORM_SEND_DATA_REQUEST = 125;\n    RCPDU_UNIFORM_SEND_DATA_INDICATION = 126;\n    RCPDU_TOKEN_GRAB_REQUEST = 130;\n    RCPDU_TOKEN_GRAB_CONFIRM = 131;\n    RCPDU_TOKEN_INHIBIT_REQUEST = 132;\n    RCPDU_TOKEN_INHIBIT_CONFIRM = 133;\n    RCPDU_TOKEN_GIVE_REQUEST = 134;\n    RCPDU_TOKEN_GIVE_INDICATION = 135;\n    RCPDU_TOKEN_GIVE_RESPONSE = 136;\n    RCPDU_TOKEN_GIVE_CONFIRM = 137;\n    RCPDU_TOKEN_PLEASE_REQUEST = 138;\n    RCPDU_TOKEN_PLEASE_INDICATION = 139;\n    RCPDU_TOKEN_RELEASE_REQUEST = 140;\n    RCPDU_TOKEN_RELEASE_CONFIRM = 141;\n    RCPDU_TOKEN_TEST_REQUEST = 142;\n    RCPDU_TOKEN_TEST_CONFIRM = 143;\n\n    // Registry PDU\n    RCPDU_REG_REGISTER_KEY = 200;\n    RCPDU_REG_UNREGISTER_KEY = 201;\n    RCPDU_REG_REGISTER_ROSTER = 202;\n    RCPDU_REG_REGISTER_TOKEN = 203;\n    RCPDU_REG_REGISTER_PARAMETER = 204;\n    RCPDU_REG_REGISTER_COUNTER = 205;\n    RCPDU_REG_REGISTER_TABLE = 206;\n    RCPDU_REG_REGISTER_CACHE = 207;\n    RCPDU_REG_REGISTER_OBJ = 208;\n    RCPDU_REG_UNREGISTER_OBJ = 209;\n    RCPDU_REG_UPDATE_OBJ = 210;\n    RCPDU_REG_ADAPTER = 211;\n    RCPDU_REG_CLEANUP_NODE = 212;\n    RCPDU_REG_REGISTER_QUEUE = 213;\n\n    // Registry Obj update PDU\n    RCPDU_REG_TABLE_INSERT_PDU = 230;\n    RCPDU_REG_TABLE_DELETE_PDU = 231;\n    RCPDU_REG_TABLE_UPDATE_PDU = 232;\n    RCPDU_REG_ROSTER_INSERT_PDU = 240;\n    RCPDU_REG_ROSTER_DELETE_PDU = 241;\n    RCPDU_REG_ROSTER_UPDATE_PDU = 242;\n    RCPDU_REG_PARAMETER_UPDATE_PDU = 250;\n    RCPDU_REG_QUEUE_INSERT_PDU = 255;\n    RCPDU_REG_QUEUE_DELETE_PDU = 256;\n    RCPDU_REG_QUEUE_UPDATE_PDU = 257;\n\n    // data\n    RCPDU_CONFERENCE_SEND_DATA_REQUEST = 259;\n    RCPDU_VIDEO_SEND_DATA_REQUEST = 260;\n    RCPDU_AUDIO_SEND_DATA_REQUEST = 261;\n    RCPDU_GIFT_SEND_DATA_REQUEST = 262;\n    RCPDU_CHAT_SEND_DATA_REQUEST = 263;\n    RCPDU_VOTING_POLL_RECORD = 265;\n    RCPDU_CONFERENCE_RECORD_REQUEST = 270;\n\n    // Registry resource request or response PDU\n    RCPDU_REG_REQUEST_OBJ = 290;\n    RCPDU_REG_RESPONSE_OBJ = 291;\n    RCPDU_REG_COUNTER_REQUEST_PDU = 292;\n    RCPDU_REG_COUNTER_RESPONSE_PDU = 293;\n\n    // Index exchange\n    RCPDU_INDEX_ADAPTER = 300;\n    RCPDU_INDEX_SERVER_USERS = 301;\n    RCPDU_INDEX_CONFERENCE_USER_JOINED = 302;\n    RCPDU_INDEX_CONFERENCE_USER_EXITED = 303;\n    RCPDU_INDEX_CONFERENCE_USERS = 304;\n\n\n\n\n    //new data\n    RCPDU_SEND_CONFERENCE_DATA_REQUEST =500;\n    RCPDU_SEND_VIDEO_DATA_REQUEST = 501;\n    RCPDU_SEND_AUDIO_DATA_REQUEST = 502;\n    RCPDU_SEND_GIFT_DATA_REQUEST = 503;\n    RCPDU_SEND_CHAT_DATA_REQUEST = 504;\n\n}\n\nenum RCPduNodeType_E {\n    NT_TERMINAL = 0;\n    NT_MULTIPORT_TERMINAL = 1;\n    NT_MCU = 2;\n}\n\nenum RCPduReason_E {\n    RSN_USERINITIATED = 0;\n    RSN_DISCONNECTED = 1;\n    RSN_SUPER_LEFT = 2;\n}\n\nenum RCPduResult_E {\n    RET_SUCCESS = 0;\n    RET_USER_REJECTED = 1;\n    RET_INVALID_CONFERENCE = 2;\n    RET_INVALID_PASSWORD = 3;\n    RET_INVALID_CONVENER_PASSWORD = 4;\n    RET_CHALLENGE_RESPONSE_REQUIRED = 5;\n    RET_INVALID_CHALLENGE_RESPONSE = 6;\n    RET_NO_CONNECTION = 7;\n    RET_FULL_CAPACITY = 8;\n}\n\nenum RCPduNodeCategory_E {\n    NC_CONVENTIONAL = 0;\n    NC_COUNTED = 1;\n    NC_ANONYMOUS = 2;\n}\n\nmessage RCConferenceDescriptorPdu {\n    required uint32 id = 1;\n    required string name = 2;\n    optional bytes description = 3;\n    optional uint32 mode = 4;\n    optional string password = 5;\n    optional uint32 capacity = 6;\n    optional bytes user_data = 7;\n}\n\nmessage RCNodeRecordPdu {\n    required uint32 id = 1;\n    optional uint32 superior_node = 2;\n    required RCPduNodeType_E type = 3;\n    required string name = 4;\n    required uint32 capability = 5;\n    optional string net_address = 6;\n    optional RCPduNodeCategory_E category = 7;\n}\n\nmessage RCApplicationRecordPdu {\n    required uint32 id = 1; // session id\n    required string name = 2;\n    required string tag = 3;\n    repeated uint32 channel_ids = 4 [packed = true];\n    optional uint32 capability = 5;\n}\n\n//reg.proto\n\noption optimize_for = LITE_RUNTIME;\nmessage RCRegistryRegisterKeyPdu {\n    required RCPduType_E type = 1 [default = RCPDU_REG_REGISTER_KEY];\n    required uint32 id = 2;\n    required string name = 3;\n    required string tag = 4;\n    optional bytes user_data = 5;\n}\n\nmessage RCRegistryUnregisterKeyPdu {\n    optional RCPduType_E type = 1 [default = RCPDU_REG_UNREGISTER_KEY];\n    required uint32 key_id = 2;\n}\n\nmessage RCRegistryRegisterObjPdu {\n    optional RCPduType_E type = 1;\n    required uint32 obj_id = 2;\n    required string name = 3;\n    required string tag = 4;\n    optional uint32 owner = 5;\n    optional bytes user_data = 6;\n}\n\nmessage RCRegistryUnregisterObjPdu {\n    optional RCPduType_E type = 1 [default = RCPDU_REG_UNREGISTER_OBJ];\n    required uint32 obj_id = 2;\n}\n\nmessage RCRegistryUpdateObjPdu {\n    optional RCPduType_E type = 1 [default = RCPDU_REG_UPDATE_OBJ];\n    required RCPduType_E sub_type = 2;\n    required uint32 obj_id = 3;\n    required bytes user_data = 4;\n}\n\nmessage RCAdapterItemPdu {\n    required RCPduType_E type = 1;\n    required bytes item_data = 2;\n}\n\n// adapter pdu that used to package a list of pdu.\nmessage RCAdapterPdu {\n    optional RCPduType_E type = 1 [default = RCPDU_REG_ADAPTER];\n    repeated RCAdapterItemPdu item = 2;\n}\n\n// table operation pdu\nmessage RCRegistryTableItemPdu {\n    required uint32 item_idx = 1;\n    required uint32 owner = 2;\n    required bytes item_data = 3;\n    optional uint32 register_obj_id=4;\n}\n\nmessage RCRegistryTableInsertItemPdu {\n    optional RCPduType_E type = 1 [default = RCPDU_REG_TABLE_INSERT_PDU];\n    repeated RCRegistryTableItemPdu items = 2;\n}\n\nmessage RCRegistryTableDeleteItemPdu {\n    optional RCPduType_E type = 1 [default = RCPDU_REG_TABLE_DELETE_PDU];\n    repeated uint32 item_idx = 2;\n}\n\nmessage RCRegistryTableUpdateItemPdu {\n    optional RCPduType_E type = 1 [default = RCPDU_REG_TABLE_UPDATE_PDU];\n    repeated RCRegistryTableItemPdu items = 2;\n}\n\n// roster operation pdu\nmessage RCRegistryRosterItemPdu {\n    required uint32 node_id = 1;\n    required bytes node_data = 2;\n}\nmessage RCRegistryWBItemPdu {\n    required uint32 node_id = 1;\n    required bytes node_data = 2;\n}\nmessage RCRegistryRosterInsertItemPdu {\n    optional RCPduType_E type = 1 [default = RCPDU_REG_ROSTER_INSERT_PDU];\n    repeated RCRegistryRosterItemPdu items = 2;\n}\n\nmessage RCRegistryRosterDeleteItemPdu {\n    optional RCPduType_E type = 1 [default = RCPDU_REG_ROSTER_DELETE_PDU];\n    required uint32 node_id = 2;\n}\n\nmessage RCRegistryRosterUpdateItemPdu {\n    optional RCPduType_E type = 1 [default = RCPDU_REG_ROSTER_UPDATE_PDU];\n    repeated RCRegistryRosterItemPdu items = 2;\n}\n\n//message RCCleanupNodePdu\n//{\n//  optional RCPduType_E    type                = 1 [default = RCPDU_REG_CLEANUP_NODE];\n//  required uint32         node_id             = 2;\n//}\n\n// parameter operation pdu\nmessage RCRegistryParameterUpdatePdu {\n    optional RCPduType_E type = 1 [default = RCPDU_REG_PARAMETER_UPDATE_PDU];\n    required uint32 value = 2 [default = 0];\n    optional uint32 begin_bit = 3 [default = 31];\n    optional uint32 end_bit = 4;\n}\n\n// queue operation pdu\nmessage RCRegistryQueueItemPdu {\n    required uint32 owner = 1;\n    required uint32 item_id = 2;\n    optional bytes item_data = 3;\n    optional uint32 item_idx = 4;\n}\n\nmessage RCRegstryQueueInsertItemPdu {\n    optional RCPduType_E type = 1 [default = RCPDU_REG_QUEUE_INSERT_PDU];\n    repeated RCRegistryQueueItemPdu items = 2;\n}\n\nmessage RCRegistryQueueDeleteItemPdu {\n    optional RCPduType_E type = 1 [default = RCPDU_REG_QUEUE_DELETE_PDU];\n    required uint32 item_id = 2;\n}\n\nmessage RCRegistryQueueUpdateItemPdu {\n    optional RCPduType_E type = 1 [default = RCPDU_REG_QUEUE_UPDATE_PDU];\n    repeated RCRegistryQueueItemPdu items = 2;\n}\n\nmessage RCRegistryRequestObjPdu {\n    optional RCPduType_E type = 1 [default = RCPDU_REG_REQUEST_OBJ];\n    required RCPduType_E sub_type = 2;\n    required uint32 obj_id = 3;\n    required bytes user_data = 4;\n}\n\nmessage RCRegistryResponseObjPdu {\n    optional RCPduType_E type = 1 [default = RCPDU_REG_RESPONSE_OBJ];\n    required RCPduType_E sub_type = 2;\n    required uint32 obj_id = 3;\n    required bytes user_data = 4;\n}\n\nmessage RCRegistryCounterRequestPdu {\n    optional RCPduType_E type = 1 [default = RCPDU_REG_COUNTER_REQUEST_PDU];\n    required uint32 count = 2;\n}\n\nmessage RCRegistryCounterResponsePdu {\n    optional RCPduType_E type = 1 [default = RCPDU_REG_COUNTER_RESPONSE_PDU];\n    required uint32 start = 2;\n    required uint32 end = 3;\n}\n\n// mcs\noption optimize_for = LITE_RUNTIME;\n\n// Session management\nmessage RCSessionJoinRequestPdu {\n    required RCPduType_E type = 1 [default = RCPDU_SESSION_JOIN_REQUEST];\n    required uint32 id = 2;\n    required string name = 3;\n    required string tag = 4;\n    optional bytes session_data = 5; //\u805A\u5408\u5728\u4E00\u8D77\u7684registry\u4FE1\u606F\n}\n\nmessage RCSessionJoinResponsePdu {\n    optional RCPduType_E type = 1 [default = RCPDU_SESSION_JOIN_RESPONSE];\n    required uint32 id = 2;\n    optional bytes response_data = 3;\n}\n\n// Channel management\nmessage RCChannelGrabRequestPdu {\n    optional RCPduType_E type = 1 [default = RCPDU_CHANNEL_GRAB_REQUEST];\n    required uint32 initiator = 2;\n    required uint32 channel_id = 3;\n}\n\nmessage RCChannelGrabResponsePdu {\n    optional RCPduType_E type = 1 [default = RCPDU_CHANNEL_GRAB_RESPONSE];\n    required uint32 initiator = 2;\n    required uint32 requested_channel_id = 3;\n    required RCPduResult_E result = 4;\n    optional uint32 channel_id = 5;\n}\n\nmessage RCChannelGrabIndicationPdu {\n    optional RCPduType_E type = 1 [default = RCPDU_CHANNEL_GRAB_INDICATION];\n    required uint32 initiator = 2;\n    optional uint32 channel_id = 3;\n}\n\nmessage RCChannelJoinRequestPdu {\n    required RCPduType_E type = 1 [default = RCPDU_CHANNEL_JOIN_REQUEST];\n    required uint32 initiator = 2;\n    required uint32 channel_id = 3;\n}\n\nmessage RCChannelJoinResponsePdu {\n    optional RCPduType_E type = 1 [default = RCPDU_CHANNEL_JOIN_RESPONSE];\n    required uint32 initiator = 2;\n    required uint32 requested_channel_id = 3;\n    required RCPduResult_E result = 4;\n}\n\nmessage RCChannelLeaveRequestPdu {\n    optional RCPduType_E type = 1 [default = RCPDU_CHANNEL_LEAVE_REQUEST];\n    required uint32 initiator = 2;\n    repeated uint32 channel_ids = 3 [packed = true];\n}\n\nmessage RCChannelReleaseRequestPdu {\n    optional RCPduType_E type = 1 [default = RCPDU_CHANNEL_RELEASE_REQUEST];\n    required uint32 initiator = 2;\n    required uint32 channel_id = 3;\n}\n\nmessage RCChannelReleaseIndicationPdu {\n    optional RCPduType_E type = 1\n    [default = RCPDU_CHANNEL_RELEASE_INDICATION];\n    required uint32 initiator = 2;\n    required uint32 channel_id = 3;\n}\n\n// Data transfer\nmessage RCSendDataPdu {\n    required RCPduType_E type = 1 [default = RCPDU_SEND_DATA_REQUEST];\n    required RCPduType_E sub_type = 2;\n    required uint32 initiator = 3;\n    required uint32 conf_id = 4;\n    required uint32 session_id = 5;\n    required uint32 channel_id = 6;\n    required bool upward = 7;\n    required bool reliability = 8;\n    required RCPduPriority_E priority = 9;\n    required bytes data = 10;\n    optional uint32 peer = 11;\n    optional RCPduSegment_E seg = 12;\n    optional uint32 total_size = 13;\n    optional uint32 site_id = 14;\n    optional string user_id = 15;\n    optional string user_name = 16;\n    optional string user_role = 17;\n    optional string device_type = 18;\n    optional string site = 19;\n}\n\n//  Token management\nmessage RCTokenGrabRequestPdu {\n    optional RCPduType_E type = 1 [default = RCPDU_TOKEN_GRAB_REQUEST];\n    required uint32 initiator = 2;\n    required uint32 token_id = 3;\n}\n\nmessage RCTokenGrabConfirmPdu {\n    optional RCPduType_E type = 1 [default = RCPDU_TOKEN_GRAB_CONFIRM];\n    required uint32 initiator = 2;\n    required uint32 token_id = 3;\n    required RCPduResult_E result = 4;\n    required RCPduTokenStatus_E status = 5;\n}\n\nmessage RCTokenInhibitRequestPdu {\n    optional RCPduType_E type = 1 [default = RCPDU_TOKEN_INHIBIT_REQUEST];\n    required uint32 initiator = 2;\n    required uint32 token_id = 3;\n}\n\nmessage RCTokenInhibitConfirmPdu {\n    optional RCPduType_E type = 1 [default = RCPDU_TOKEN_INHIBIT_CONFIRM];\n    required uint32 initiator = 2;\n    required uint32 token_id = 3;\n    required RCPduResult_E result = 4;\n    required RCPduTokenStatus_E status = 5;\n}\n\nmessage RCTokenGiveRequestPdu {\n    optional RCPduType_E type = 1 [default = RCPDU_TOKEN_GIVE_REQUEST];\n    required uint32 initiator = 2;\n    required uint32 token_id = 3;\n    required uint64 recipient = 4;\n}\n\nmessage RCTokenGiveIndicationPdu {\n    optional RCPduType_E type = 1 [default = RCPDU_TOKEN_GIVE_INDICATION];\n    required uint32 initiator = 2;\n    required uint32 token_id = 3;\n    required uint64 recipient = 4;\n}\n\nmessage RCTokenGiveResponsePdu {\n    optional RCPduType_E type = 1 [default = RCPDU_TOKEN_GIVE_RESPONSE];\n    required uint32 token_id = 2;\n    required uint64 recipient = 3;\n    required RCPduResult_E result = 4;\n}\n\nmessage RCTokenGiveConfirmPdu {\n    optional RCPduType_E type = 1 [default = RCPDU_TOKEN_GIVE_CONFIRM];\n    required uint32 token_id = 2;\n    required uint64 recipient = 3;\n    required RCPduResult_E result = 4;\n    required RCPduTokenStatus_E status = 5;\n}\n\nmessage RCTokenPleaseRequestPdu {\n    optional RCPduType_E type = 1 [default = RCPDU_TOKEN_PLEASE_REQUEST];\n    required uint32 initiator = 2;\n    required uint32 token_id = 3;\n}\n\nmessage RCTokenPleaseIndicationPdu {\n    optional RCPduType_E type = 1 [default = RCPDU_TOKEN_PLEASE_INDICATION];\n    required uint32 initiator = 2;\n    required uint32 token_id = 3;\n}\n\nmessage RCTokenReleaseRequestPdu {\n    optional RCPduType_E type = 1 [default = RCPDU_TOKEN_RELEASE_REQUEST];\n    required uint32 initiator = 2;\n    required uint32 token_id = 3;\n}\n\nmessage RCTokenReleaseConfirmPdu {\n    optional RCPduType_E type = 1 [default = RCPDU_TOKEN_TEST_REQUEST];\n    required uint32 initiator = 2;\n    required uint32 token_id = 3;\n    required RCPduResult_E result = 4;\n    required RCPduTokenStatus_E status = 5;\n}\n\nmessage RCTokenTestRequestPdu {\n    optional RCPduType_E type = 1 [default = RCPDU_TOKEN_TEST_REQUEST];\n    required uint32 initiator = 2;\n    required uint32 token_id = 3;\n}\n\nmessage RCTokenTestConfirmPdu {\n    optional RCPduType_E type = 1 [default = RCPDU_TOKEN_TEST_CONFIRM];\n    required uint32 initiator = 2;\n    required uint32 token_id = 3;\n    required RCPduTokenStatus_E status = 4;\n}\n\n//gcc.proto\n\noption optimize_for = LITE_RUNTIME;\nmessage RCConferenceJoinRequestPdu {\n    required RCPduType_E type = 1 [default = RCPDU_CONFERENCE_JOIN_REQUEST];\n    required uint32 initiator = 2;\n    required RCPduNodeType_E node_type = 3;\n    required RCConferenceDescriptorPdu class_description = 4;//conf_desc\n}\n\nmessage RCConferenceJoinResponsePdu {\n    optional RCPduType_E type = 1 [default = RCPDU_CONFERENCE_JOIN_RESPONSE];\n    required uint32 conf_id = 2;\n    required RCPduResult_E result = 3;\n    optional RCConferenceDescriptorPdu class_description = 4;\n}\n\nmessage RCConferenceInviteRequestPdu {\n    optional RCPduType_E type = 1 [default = RCPDU_CONFERENCE_INVITE_REQUEST];\n    required uint32 initiator = 2;\n    required RCConferenceDescriptorPdu class_description = 3;\n}\n\nmessage RCConferenceInviteResponsePdu {\n    optional RCPduType_E type = 1\n    [default = RCPDU_CONFERENCE_INVITE_RESPONSE];\n    required RCPduResult_E result = 2;\n    optional bytes user_data = 3;\n}\n\nmessage RCConferenceLockRequestPdu {\n    optional RCPduType_E type = 1 [default = RCPDU_CONFERENCE_LOCK_REQUEST];\n}\n\nmessage RCConferenceLockResponsePdu {\n    optional RCPduType_E type = 1 [default = RCPDU_CONFERENCE_LOCK_RESPONSE];\n    required RCPduResult_E result = 2;\n}\n\nmessage RCConferenceLockIndicationPdu {\n    optional RCPduType_E type = 1\n    [default = RCPDU_CONFERENCE_LOCK_INDICATION];\n}\n\nmessage RCConferenceUnlockRequestPdu {\n    optional RCPduType_E type = 1 [default = RCPDU_CONFERENCE_UNLOCK_REQUEST];\n}\n\nmessage RCConferenceUnlockResponsePdu {\n    optional RCPduType_E type = 1\n    [default = RCPDU_CONFERENCE_UNLOCK_RESPONSE];\n    required RCPduResult_E result = 2;\n}\n\nmessage RCConferenceUnlockIndicationPdu {\n    optional RCPduType_E type = 1\n    [default = RCPDU_CONFERENCE_UNLOCK_INDICATION];\n}\n\nmessage RCConferenceLeaveRequestPdu {\n    optional RCPduType_E type = 1 [default = RCPDU_CONFERENCE_LEAVE_REQUEST];\n    required RCPduReason_E reason = 2;\n}\n\nmessage RCConferenceTerminateRequestPdu {\n    optional RCPduType_E type = 1\n    [default = RCPDU_CONFERENCE_TERMINATE_REQUEST];\n    required RCPduReason_E reason = 2;\n}\n\nmessage RCConferenceTerminateResponsePdu {\n    optional RCPduType_E type = 1\n    [default = RCPDU_CONFERENCE_TERMINATE_RESPONSE];\n    required RCPduResult_E result = 2;\n}\n\nmessage RCConferenceTerminateIndicationPdu { // MCS_Uniform_Send_Data on GCC_Broadcast_Channel\n    optional RCPduType_E type = 1\n    [default = RCPDU_CONFERENCE_TERMINATE_INDICATION];\n    required RCPduReason_E reason = 2;\n}\n\nmessage RCConferenceEjectUserRequestPdu { // MCS_Send_Data on Node ID Channel of Top GCC\n    optional RCPduType_E type = 1\n    [default = RCPDU_CONFERENCE_EJECT_USER_REQUEST];\n    required uint32 ejected_node_id = 2;\n    required RCPduReason_E reason = 3;\n}\n\nmessage RCConferenceEjectUserResponsePdu { // MCS_Send_Data on Node ID Channel of requester\n    optional RCPduType_E type = 1\n    [default = RCPDU_CONFERENCE_EJECT_USER_RESPONSE];\n    required uint32 ejected_node_id = 2;\n    required RCPduResult_E result = 3;\n}\n\nmessage RCConferenceEjectUserIndicationPdu { // MCS_Uniform_Send_Data on GCC_Broadcast_Channel\n    optional RCPduType_E type = 1\n    [default = RCPDU_CONFERENCE_EJECT_USER_INDICATION];\n    required uint32 ejected_node_id = 2;\n    required RCPduReason_E reason = 3;\n}\n\nmessage RCRosterUpdateIndicationPdu { // MCS_Send_Data on Node ID Channel or\n// MCS_Uniform_Send_Data on GCC_Broadcast_Channel\n    optional RCPduType_E type = 1 [default = RCPDU_ROSTER_UPDATE_INDICATION];\n    required bool full_refresh = 2; // Conference Roster and all\n    repeated RCNodeRecordPdu node_record = 3;\n    repeated RCApplicationRecordPdu app_record = 4;\n}\n\nmessage RCRegistryUpdateRequestPdu { // MCS_Send_Data on Node ID Channel of Top GCC\n    optional RCPduType_E type = 1 [default = RCPDU_REGISTRY_UPDATE_REQUEST];\n    required uint32 key_id = 2;\n    required uint32 obj_id = 3;\n    required bytes user_data = 4;\n}\n\nmessage RCRegistryUpdateIndicationPdu { // MCS_Send_Data on Node ID Channel of Top GCC\n    optional RCPduType_E type = 1 [default = RCPDU_REGISTRY_UPDATE_RESPONSE];\n    required uint32 key_id = 2;\n    required uint32 obj_id = 3;\n    required bytes user_data = 4;\n}\n\nmessage RCRegistryUpdateResponsePdu { // MCS_Send_Data on Node ID Channel of requester\n    optional RCPduType_E type = 1 [default = RCPDU_REGISTRY_UPDATE_INDICATION];\n    required uint32 key_id = 2;\n    required uint32 obj_id = 3;\n    required RCPduResult_E result = 4;\n}\n\nmessage RCFunctionNotSupportedResponsePdu {\n    optional RCPduType_E type = 1\n    [default = RCPDU_FUNCTION_NOT_SUPPORTED_RESPONSE];\n    required uint32 request_pdu_id = 2;\n}\n\n//ape.proto\n\noption optimize_for = LITE_RUNTIME;\nmessage RCConferenceSendDataRequestPdu {\n    optional uint32 initiator = 1;\n    optional uint32 peer = 2;\n    required bool is_public = 3;\n    required bytes user_data = 4;\n    optional uint32 action_type = 5;//\u6D88\u606F\u7684\u6307\u4EE4\u7C7B\u578B\n}\n\nmessage RCChatSendDataRequestPdu {\n    optional uint32 initiator = 1;\n    optional uint32 peer = 2;\n    required bool is_public = 3;\n    required bytes user_data = 4;\n    required string from_role = 5;\n    required bytes from_name = 6;\n}\n\nmessage RCDocSendDataModelPdu {\n    required uint32 item_idx=1;//\u552F\u4E00\u6807\u8BC6\n    required uint32 owner=2;\n    optional uint32 from=3;\n    optional uint32 cur_page_no=4;\n    optional uint32 page_num  =5;\n    optional string file_type=6;\n    optional string creat_user_id=7;//\u521B\u5EFA\u6587\u6863userid\n    optional string relative_url=8;//\u6587\u6863\u76F8\u5BF9\u5730\u5740\n    optional string url  =9;//\u6587\u6863\u5730\u5740\n    optional uint32 cur_V=10;\n    optional uint32 cur_H=11;\n    optional uint32 scale=12;\n    optional bool visible=13;\n    optional uint32 action=14;//0\uFF0C\u65E0\u64CD\u4F5C\uFF0C 1\u7FFB\u9875\u30012.\u663E\u793A/\u9690\u85CF\n    optional string doc_id=15;//\u6587\u6863\u5728\u670D\u52A1\u5668\u6570\u636E\u5E93\u4E2D\u7684\u552F\u4E00id\n    optional string file_name=16;//\u6587\u6863\u7684\u540D\u5B57\n    optional string dynamic_TS=17;//\"dynamicTransferStatic\": \"0\"\n    optional string md5=18;//md5\n\n}\nmessage RCGiftSendDataRequestPdu {\n    optional uint32 initiator = 1;\n    required uint32 peer = 2;\n    required uint32 index = 3;\n    required uint32 num = 4;\n    optional bytes user_data = 5;\n}\n\nmessage RCAudioSendDataRequestPdu1 {\n    optional uint32 initiator = 1;\n    required bytes user_data = 2;\n}\nmessage RCAudioSendDataRequestPdu {\n    required uint32 from_node_id = 1;//\u53D1\u8D77\u4EBA\n    optional uint32 to_node_id = 2;//\u63A5\u6536\u4EBA\uFF0C\u5982\u679C\u662F0\u5C31\u662F\u6240\u6709\u4EBA\u90FD\u63A5\u6536\n    optional uint32 actionType = 3;//\u6D88\u606F\u6307\u4EE4\u7C7B\u578B;\n    optional bytes  data = 4;//\u5176\u4ED6\u6570\u636E\uFF0C\u8FD9\u4E2A\u6839\u636EactionType\u6765\u786E\u5B9A\u6570\u636E\u7684\u7ED3\u6784\n}\n\nmessage RCVideoSendDataRequestPdu {\n    required uint32 from_node_id = 1;//\u53D1\u8D77\u4EBA\n    optional uint32 to_node_id = 2;//\u63A5\u6536\u4EBA\uFF0C\u5982\u679C\u662F0\u5C31\u662F\u6240\u6709\u4EBA\u90FD\u63A5\u6536\n    optional uint32 actionType = 3;//\u6D88\u606F\u6307\u4EE4\u7C7B\u578B;\n    optional bytes  data = 4;//\u5176\u4ED6\u6570\u636E\uFF0C\u8FD9\u4E2A\u6839\u636EactionType\u6765\u786E\u5B9A\u6570\u636E\u7684\u7ED3\u6784\n}\n\nmessage RCAudioChannelInfoRecordPdu {\n    required uint32 status = 1;\n    required uint32 device_id = 2;\n    required uint32 framerate = 3;\n    required uint32 bitrate = 4;\n    required uint32 codec = 5;\n}\nmessage RCAudioChannelInfoPdu {\n     optional uint32 status = 1;//\u5F00\u542F\u7684\u72B6\u6001\n    optional uint32 channel_id = 2;//\u552F\u4E00\u7684\u9891\u9053id\n    optional uint32 timestamp = 3;//\u66F4\u65B0\u7684\u65F6\u95F4\u6233\n    optional uint32 from_node_id = 4;//\u53D1\u8D77\u8005\u7684id\n    optional uint32 to_node_id = 5;//\u63A5\u6536\u8005\u7684id\uFF0C(\u5982\u679C\u662F0\uFF0C\u6240\u6709\u4EBA\u90FD\u63A5\u6536)\n    optional uint32 media_type = 6;//\u5A92\u4F53\u7C7B\u578B\uFF1A\u89C6\u9891(\u5305\u542B\u97F3\u9891)\u6216\u97F3\u9891\n    optional uint32 class_id = 7;//\u8BFE\u5802\u53F7\n    optional string site_id = 8;//\u7AD9\u70B9\u53F7\n    optional string user_id = 9;//\u7528\u6237\u7684userId\n    optional string stream_id = 10;//\u6D41\u540D\u79F0\n}\n\nmessage RCVideoChannelInfoPdu {\n    optional uint32 status = 1;//\u5F00\u542F\u7684\u72B6\u6001\n    optional uint32 channel_id = 2;//\u552F\u4E00\u7684\u9891\u9053id\n    optional uint32 timestamp = 3;//\u66F4\u65B0\u7684\u65F6\u95F4\u6233\n    optional uint32 from_node_id = 4;//\u53D1\u8D77\u8005\u7684id\n    optional uint32 to_node_id = 5;//\u63A5\u6536\u8005\u7684id\uFF0C(\u5982\u679C\u662F0\uFF0C\u6240\u6709\u4EBA\u90FD\u63A5\u6536)\n    optional uint32 media_type = 6;//\u5A92\u4F53\u7C7B\u578B\uFF1A\u89C6\u9891(\u5305\u542B\u97F3\u9891)\u6216\u97F3\u9891\n    optional uint32 class_id = 7;//\u8BFE\u5802\u53F7\n    optional string site_id = 8;//\u7AD9\u70B9\u53F7\n    optional string user_id = 9;//\u7528\u6237\u7684userId\n    optional string stream_id = 10;//\u6D41\u540D\u79F0\n}\n\nmessage RCVideoChannelInfoRecordPdu {\n    optional uint32 status = 1;\n    optional uint32 device_id = 2;\n    optional uint32 width = 3;\n    optional uint32 height = 4;\n    optional uint32 framerate = 5;\n    optional uint32 bitrate = 6;\n    optional uint32 codec = 7;\n    optional string peer_id = 8;\n    optional string url = 9;\n    optional uint32 type = 10;\n    optional string shamlive = 11;\n    optional uint32 livetype = 12;\n    optional uint32 releaseGrab = 13;\n    optional string curTime = 14;\n}\n\nmessage RCAudioDeviceInfoRecordPdu {\n    required uint32 device_id = 1;\n    required string device_name = 2;\n}\n\nmessage RCVideoDeviceInfoRecordPdu {\n    required uint32 device_id = 1;\n    required string device_name = 2;\n}\n\nmessage RCNodeInfoRecordPdu {\n    required uint32 node_id = 1;\n    required string name = 2;\n    required uint32 role = 3;\n    required uint32 level = 4;\n    repeated RCAudioDeviceInfoRecordPdu audio_records = 5;\n    repeated RCVideoDeviceInfoRecordPdu video_records = 6;\n    optional uint32 status = 7;\n    optional bytes user_data = 8;\n    optional string user_id = 9;\n    optional uint32 handUpTime = 10;\n    optional uint32 deviceType = 11;\n    optional uint32 mobileDirection = 12;\n}\n\nmessage RCVotingPollSettingsPdu {\n    required bool timer = 1;\n    optional uint32 time_limit = 2;\n    optional uint32 total_score = 3;\n}\n\nmessage RCVotingPollResultPdu {\n    required string title = 1;\n    required string content = 2;\n    optional uint32 score = 3;\n}\n\nmessage RCVotingPollQuestionPdu {\n    required uint32 index = 1;\n    required uint32 type = 2;\n    required string title = 3;\n    repeated string options = 4;\n    optional uint32 score = 5;\n    optional uint32 time_limit = 6;\n    optional string restrict_input = 7;\n    optional uint32 char_limit = 8;\n    optional string answer = 9;\n    repeated uint32 selections = 10;\n    repeated string responses = 11;\n}\n\nmessage RCVotingPollRecordPdu {\n    required RCVotingPollSettingsPdu settings = 1;\n    required string title = 2;\n    repeated RCVotingPollResultPdu results = 3;\n    repeated RCVotingPollQuestionPdu questions = 4;\n}\n\nmessage RCNodeInfoUserDataPdu {\n    optional string device = 1;//\u8BBE\u5907\u540D\u79F0\n    optional bool has_camera = 2;//\u662F\u5426\u6709\u6444\u50CF\u5934\u53EF\u7528\n    optional bool has_microphone = 3;//\u9EA6\u514B\u98CE\u662F\u5426\u53EF\u7528\n    optional string browser = 4;//\u6D4F\u89C8\u5668\n    optional string qq = 5;//qq\n    optional string skype = 6;//skype\n}\n\nmessage RCTabUpdateDataRequestPdu {\n    optional uint32 id = 1;\n    optional bytes action = 2;\n    optional uint32 uncomprLen =3;\n}\n\nmessage RCWhiteBoardDataModelPdu {\n     required uint32 type= 1;//\u767D\u677F\u7C7B\u578B\n     required uint32 itemIdx= 2;//itemIdx \u6BCF\u4E00\u6B21\u7ED8\u5236\u7684\u552F\u4E00\u6807\u8BC6\n     required uint32 initiator=3; //\u7ED8\u5236\u6765\u81EA\u8C01\n     required uint32 parentId=4; //\u7236\u7EA7\u7684id\n     required uint32 cur_page_no= 5;//\u9875\u7801\n     optional string pointGroup=6; //\u5750\u6807\u70B9\u96C6\u6570\u7EC4\u7684JSON\u5B57\u7B26\u4E32\n     optional string color=7  [default = \"#000000\"]; //\u989C\u8272\n     optional uint32 thickness= 8 ;//\u7EBF\u6761\u7C97\u7EC6\n     optional uint32 radius= 9;//\u56ED\u7684\u534A\u5F84\n     optional uint32 fontSize= 10;//\u5B57\u4F53\u5927\u5C0F\n     optional string fontName= 11;//\u5B57\u4F53\u540D\u79F0\n     optional string text= 12;//\u6587\u672C\u5185\u5BB9\n     optional bytes data = 13;//\u6682\u65F6\u9884\u7559\u7684\u53C2\u6570\n}\nmessage RCClassSendDataModelPdu {\n     optional uint32 item_idx=1;\n     optional uint32 from=2;\n     optional uint32 owner=3;\n     optional uint32 action_type=4;//\u72B6\u6001\u6539\u53D8\u7684\u7C7B\u578B\n     optional RCClassStatusInfoPdu class_status_info=5;//\u5F53\u524D\u8BFE\u5802\u72B6\u6001\u7684\u4FE1\u606F\n}\nmessage RCClassStatusInfoPdu {\n     optional uint32 node_id=1;//mcu\u4E2D\u7684\u552F\u4E00ID\n     optional string user_id=2;\n     optional string user_name=3;\n     optional string site_id=4;//\u7AD9\u70B9\u53F7\n     optional uint32 class_id=5;\n     optional string class_name=6;\n     required uint32 class_type=7;//\u8BFE\u5802\u7C7B\u578B\n     required uint32 class_status=9;//\u8BFE\u5802\u7684\u72B6\u6001\n     optional string class_startTime=10;//\u8BFE\u5802\u70B9\u51FB\u5F00\u59CB\u65F6\u95F4\n     optional string class_stopTime=11;//\u6700\u540E\u4E00\u6B21\u505C\u6B62\u7684\u65F6\u95F4(\u70B9\u6682\u505C\u6216\u7ED3\u675F)\uFF0C\u6BCF\u6B21\u53D1\u9001\u6570\u636E\u90FD\u83B7\u53D6\u5F53\u524D\u65F6\u95F4\u6233\n     optional uint32 class_timestamp=12;//\u76F8\u5BF9\u4E8E\u70B9\u5F00\u59CB\u8BFE\u5802\u7684\u65F6\u95F4\u6233\n     optional string class_beginTime=13;//\u8BFE\u5802\u521B\u5EFA\u7684\u65F6\u95F4,\u8FD9\u4E2A\u662FSass\u8FD4\u56DE\u7684\n     optional string class_endTime=14;//\u8BFE\u5802\u7ED3\u675F\u7684\u65F6\u95F4\uFF0C\u8FD9\u4E2A\u662FSass\u8FD4\u56DE\u7684\n     optional bool record_status=15;//\u5F53\u524D\u5F55\u5236\u72B6\u6001\n     optional uint32 record_timestamp=16;//\u76F8\u5BF9\u4E8E\u9996\u6B21\u5F00\u59CB\u5F55\u5236\u7684\u65F6\u95F4\u6233\n     optional string record_fileName=17;//\u5F55\u5236\u7684\u6587\u4EF6\u540D\n     optional string record_downloadUrl=18;//\u4E0B\u8F7D\u5730\u5740\n     optional uint32 server_timestamp=19;//\u5F53\u524D\u7684\u7CFB\u7EDF\u65F6\u95F4\u6233\n     optional uint32 active_doc_id=20;//\u5F53\u524D\u6FC0\u6D3B\u7684\u6587\u6863id\n     optional uint32 active_doc_cur_page=21;//\u5F53\u524D\u6FC0\u6D3B\u7684\u6587\u6863\u7684\u5F53\u524D\u9875\n}\n\nmessage RCConferenceRecordRequestPdu {\n    \toptional uint32 initiator = 1;\t// \u53D1\u8D77\u5F55\u50CF\u6307\u4EE4\u7684node id\n\t\toptional bool record = 2;\t\t// \u5F55\u50CF\u6307\u4EE4 true\uFF1A\u5F00\u59CB\u5F55\u50CF\uFF0C false\uFF1A\u505C\u6B62\u5F55\u50CF\n    \toptional uint32 class_time = 3;\t// \u8BFE\u5802\u8FDB\u884C\u65F6\u95F4\uFF08\u79D2\uFF09\n\t\toptional string filename = 4;\t// \u5F55\u50CF\u6587\u4EF6\u540D\u79F0,filename\u4E2D\u589E\u52A0\u76EE\u5F55\u90E8\u5206\n}\n\n//end\n";
+	exports.default = _default;
+	;
+
+	var _temp = function () {
+	    if (typeof __REACT_HOT_LOADER__ === 'undefined') {
+	        return;
+	    }
+
+	    __REACT_HOT_LOADER__.register(_default, "default", "D:/work/McuClient/src/pdus/pro.js");
+	}();
+
+	;
+
+/***/ },
+/* 34 */
+/***/ function(module, exports) {
+
+	"use strict";
+
+	Object.defineProperty(exports, "__esModule", {
+	  value: true
+	});
+	exports.default = RCPduPackage;
+	function RCPduPackage(targe_type_id) {};
+
+	RCPduPackage.RCPDU_CONNECT_PROVIDER_REQUEST = 0; //加入MCU会议的请求
+	RCPduPackage.RCPDU_CONNECT_PROVIDER_RESPONSE = 1; //返回MCU会议请求结果
+
+	//下面两个是在入会成功之后,创建pdu包时设置的type值
+	RCPduPackage.RCPDU_UNIFORM_SEND_DATA_REQUEST = 125; //发送uniform_pdu
+	RCPduPackage.RCPDU_SEND_DATA_REQUEST = 120; //入会成功之后,客户端接收消息都通过这个,发送私聊的消息也用这个
+
+
+	RCPduPackage.RCPDU_CONFERENCE_JOIN_REQUEST = 2;
+	RCPduPackage.RCPDU_CONFERENCE_JOIN_RESPONSE = 3;
+	RCPduPackage.RCPDU_CONFERENCE_INVITE_REQUEST = 10;
+	RCPduPackage.RCPDU_CONFERENCE_INVITE_RESPONSE = 11;
+	RCPduPackage.RCPDU_CONFERENCE_LOCK_REQUEST = 20;
+	RCPduPackage.RCPDU_CONFERENCE_LOCK_RESPONSE = 21;
+	RCPduPackage.RCPDU_CONFERENCE_LOCK_INDICATION = 22;
+	RCPduPackage.RCPDU_CONFERENCE_UNLOCK_REQUEST = 30;
+	RCPduPackage.RCPDU_CONFERENCE_UNLOCK_RESPONSE = 31;
+	RCPduPackage.RCPDU_CONFERENCE_UNLOCK_INDICATION = 32;
+	RCPduPackage.RCPDU_CONFERENCE_LEAVE_REQUEST = 39;
+	RCPduPackage.RCPDU_CONFERENCE_TERMINATE_REQUEST = 40;
+	RCPduPackage.RCPDU_CONFERENCE_TERMINATE_RESPONSE = 41;
+	RCPduPackage.RCPDU_CONFERENCE_TERMINATE_INDICATION = 42;
+	RCPduPackage.RCPDU_CONFERENCE_EJECT_USER_REQUEST = 50;
+	RCPduPackage.RCPDU_CONFERENCE_EJECT_USER_RESPONSE = 51;
+	RCPduPackage.RCPDU_CONFERENCE_EJECT_USER_INDICATION = 52;
+	RCPduPackage.RCPDU_ROSTER_UPDATE_INDICATION = 60;
+	RCPduPackage.RCPDU_REGISTRY_UPDATE_REQUEST = 70;
+	RCPduPackage.RCPDU_REGISTRY_UPDATE_RESPONSE = 71;
+	RCPduPackage.RCPDU_REGISTRY_UPDATE_INDICATION = 72;
+	RCPduPackage.RCPDU_FUNCTION_NOT_SUPPORTED_RESPONSE = 80;
+	RCPduPackage.RCPDU_SESSION_JOIN_REQUEST = 90;
+	RCPduPackage.RCPDU_SESSION_JOIN_RESPONSE = 91;
+	RCPduPackage.RCPDU_CHANNEL_GRAB_REQUEST = 100;
+	RCPduPackage.RCPDU_CHANNEL_GRAB_RESPONSE = 101;
+	RCPduPackage.RCPDU_CHANNEL_GRAB_INDICATION = 102;
+	RCPduPackage.RCPDU_CHANNEL_JOIN_REQUEST = 103;
+	RCPduPackage.RCPDU_CHANNEL_JOIN_RESPONSE = 104;
+	RCPduPackage.RCPDU_CHANNEL_LEAVE_REQUEST = 105;
+	RCPduPackage.RCPDU_CHANNEL_RELEASE_REQUEST = 106;
+	RCPduPackage.RCPDU_CHANNEL_RELEASE_INDICATION = 107;
+
+	RCPduPackage.RCPDU_SEND_DATA_INDICATION = 121;
+
+	RCPduPackage.RCPDU_UNIFORM_SEND_DATA_INDICATION = 126;
+	RCPduPackage.RCPDU_TOKEN_GRAB_REQUEST = 130;
+	RCPduPackage.RCPDU_TOKEN_GRAB_CONFIRM = 131;
+	RCPduPackage.RCPDU_TOKEN_INHIBIT_REQUEST = 132;
+	RCPduPackage.RCPDU_TOKEN_INHIBIT_CONFIRM = 133;
+	RCPduPackage.RCPDU_TOKEN_GIVE_REQUEST = 134;
+	RCPduPackage.RCPDU_TOKEN_GIVE_INDICATION = 135;
+	RCPduPackage.RCPDU_TOKEN_GIVE_RESPONSE = 136;
+	RCPduPackage.RCPDU_TOKEN_GIVE_CONFIRM = 137;
+	RCPduPackage.RCPDU_TOKEN_PLEASE_REQUEST = 138;
+	RCPduPackage.RCPDU_TOKEN_PLEASE_INDICATION = 139;
+	RCPduPackage.RCPDU_TOKEN_RELEASE_REQUEST = 140;
+	RCPduPackage.RCPDU_TOKEN_RELEASE_CONFIRM = 141;
+	RCPduPackage.RCPDU_TOKEN_TEST_REQUEST = 142;
+	RCPduPackage.RCPDU_TOKEN_TEST_CONFIRM = 143;
+	RCPduPackage.RCPDU_REG_REGISTER_KEY = 200;
+	RCPduPackage.RCPDU_REG_UNREGISTER_KEY = 201;
+	RCPduPackage.RCPDU_REG_REGISTER_ROSTER = 202;
+	RCPduPackage.RCPDU_REG_REGISTER_TOKEN = 203;
+	RCPduPackage.RCPDU_REG_REGISTER_PARAMETER = 204;
+	RCPduPackage.RCPDU_REG_REGISTER_COUNTER = 205;
+	RCPduPackage.RCPDU_REG_REGISTER_TABLE = 206;
+	RCPduPackage.RCPDU_REG_REGISTER_CACHE = 207;
+	RCPduPackage.RCPDU_REG_REGISTER_OBJ = 208;
+	RCPduPackage.RCPDU_REG_UNREGISTER_OBJ = 209;
+	RCPduPackage.RCPDU_REG_UPDATE_OBJ = 210;
+	RCPduPackage.RCPDU_REG_ADAPTER = 211;
+	RCPduPackage.RCPDU_REG_CLEANUP_NODE = 212;
+	RCPduPackage.RCPDU_REG_REGISTER_QUEUE = 213;
+	RCPduPackage.RCPDU_REG_TABLE_INSERT_PDU = 230;
+	RCPduPackage.RCPDU_REG_TABLE_DELETE_PDU = 231;
+	RCPduPackage.RCPDU_REG_TABLE_UPDATE_PDU = 232;
+	RCPduPackage.RCPDU_REG_ROSTER_INSERT_PDU = 240;
+	RCPduPackage.RCPDU_REG_ROSTER_DELETE_PDU = 241;
+	RCPduPackage.RCPDU_REG_ROSTER_UPDATE_PDU = 242;
+	RCPduPackage.RCPDU_REG_PARAMETER_UPDATE_PDU = 250;
+	RCPduPackage.RCPDU_REG_QUEUE_INSERT_PDU = 255;
+	RCPduPackage.RCPDU_REG_QUEUE_DELETE_PDU = 256;
+	RCPduPackage.RCPDU_REG_QUEUE_UPDATE_PDU = 257;
+	RCPduPackage.RCPDU_CONFERENCE_SEND_DATA_REQUEST = 259;
+	RCPduPackage.RCPDU_VIDEO_SEND_DATA_REQUEST = 260;
+	RCPduPackage.RCPDU_AUDIO_SEND_DATA_REQUEST = 261;
+	RCPduPackage.RCPDU_GIFT_SEND_DATA_REQUEST = 262;
+	RCPduPackage.RCPDU_CHAT_SEND_DATA_REQUEST = 263;
+	RCPduPackage.RCPDU_VOTING_POLL_RECORD = 265;
+	RCPduPackage.RCPDU_CONFERENCE_RECORD_REQUEST = 270; //录制和停止录制的协议号
+
+	RCPduPackage.RCPDU_REG_REQUEST_OBJ = 290;
+	RCPduPackage.RCPDU_REG_RESPONSE_OBJ = 291;
+	RCPduPackage.RCPDU_REG_COUNTER_REQUEST_PDU = 292;
+	RCPduPackage.RCPDU_REG_COUNTER_RESPONSE_PDU = 293;
+	RCPduPackage.RCPDU_INDEX_ADAPTER = 300;
+	RCPduPackage.RCPDU_INDEX_SERVER_USERS = 301;
+	RCPduPackage.RCPDU_INDEX_CONFERENCE_USER_JOINED = 302;
+	RCPduPackage.RCPDU_INDEX_CONFERENCE_USER_EXITED = 303;
+	RCPduPackage.RCPDU_INDEX_CONFERENCE_USERS = 304;
+
+	RCPduPackage.RCPDU_SEND_CONFERENCE_DATA_REQUEST = 500;
+	RCPduPackage.RCPDU_SEND_VIDEO_DATA_REQUEST = 501;
+	RCPduPackage.RCPDU_SEND_AUDIO_DATA_REQUEST = 502;
+	RCPduPackage.RCPDU_SEND_GIFT_DATA_REQUEST = 503;
+	RCPduPackage.RCPDU_SEND_CHAT_DATA_REQUEST = 504;
+	;
+
+	var _temp = function () {
+	  if (typeof __REACT_HOT_LOADER__ === 'undefined') {
+	    return;
+	  }
+
+	  __REACT_HOT_LOADER__.register(RCPduPackage, "RCPduPackage", "D:/work/McuClient/src/pdus/PduType.js");
+	}();
+
+	;
+
+/***/ },
+/* 35 */
+/***/ function(module, exports) {
+
+	"use strict";
+
+	Object.defineProperty(exports, "__esModule", {
+	  value: true
+	});
+	exports.default = PduConsts;
+	function PduConsts() {}
+
+	//nodeType ,目前在加入课堂的时候用到 NT_TERMINAL,其他两个还没用到
+	PduConsts.NT_TERMINAL = 0; //终端适配器
+	PduConsts.NT_MULTIPORT_TERMINAL = 1; //多端口ERMINAL
+	PduConsts.NT_MCU = 2;
+
+	// PduPriority  发送pdu包的 优先级
+	PduConsts.DP_TOP = 0;
+	PduConsts.DP_HIGH = 1;
+	PduConsts.DP_MEDIUM = 2;
+	PduConsts.DP_LOW = 3;
+
+	// PduSegment
+	PduConsts.SEG_BEGIN = 0;
+	PduConsts.SEG_END = 1;
+	PduConsts.SEG_ONCE = 2; //目前发送消息封包的时候都用的这个
+
+
+	// PduReturnType
+	PduConsts.RET_SUCCESS = 0;
+	PduConsts.RET_USER_REJECTED = 1;
+	PduConsts.RET_INVALID_CONFERENCE = 2;
+	PduConsts.RET_INVALID_PASSWORD = 3;
+	PduConsts.RET_INVALID_CONVENER_PASSWORD = 4;
+	PduConsts.RET_CHALLENGE_RESPONSE_REQUIRED = 5;
+	PduConsts.RET_INVALID_CHALLENGE_RESPONSE = 6;
+	PduConsts.RET_NO_CONNECTION = 7;
+	PduConsts.RET_FULL_CAPACITY = 8;
+	;
+
+	var _temp = function () {
+	  if (typeof __REACT_HOT_LOADER__ === 'undefined') {
+	    return;
+	  }
+
+	  __REACT_HOT_LOADER__.register(PduConsts, "PduConsts", "D:/work/McuClient/src/pdus/PduConsts.js");
+	}();
+
+	;
+
+/***/ },
+/* 36 */
+/***/ function(module, exports, __webpack_require__) {
+
+	'use strict';
+
+	Object.defineProperty(exports, "__esModule", {
+	    value: true
+	});
+
+	var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
+
+	var _Ape2 = __webpack_require__(37);
+
+	var _Ape3 = _interopRequireDefault(_Ape2);
+
+	var _ApeConsts = __webpack_require__(8);
+
+	var _ApeConsts2 = _interopRequireDefault(_ApeConsts);
+
+	var _MessageTypes = __webpack_require__(6);
+
+	var _MessageTypes2 = _interopRequireDefault(_MessageTypes);
+
+	var _pdus = __webpack_require__(25);
+
+	var _pdus2 = _interopRequireDefault(_pdus);
+
+	var _zlib = __webpack_require__(39);
+
+	var _utf = __webpack_require__(11);
+
+	var _utf2 = _interopRequireDefault(_utf);
+
+	var _Loger = __webpack_require__(5);
+
+	var _Loger2 = _interopRequireDefault(_Loger);
+
+	var _GlobalConfig = __webpack_require__(7);
+
+	var _GlobalConfig2 = _interopRequireDefault(_GlobalConfig);
+
+	var _EngineUtils = __webpack_require__(9);
+
+	var _EngineUtils2 = _interopRequireDefault(_EngineUtils);
+
+	var _TimerCounter = __webpack_require__(40);
+
+	var _TimerCounter2 = _interopRequireDefault(_TimerCounter);
+
+	function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
+
+	function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
+
+	function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }
+
+	function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } // //////////////////////////////////////////////////////////////////////////////
+	//会议控制APE
+	// //////////////////////////////////////////////////////////////////////////////
+
+	var loger = _Loger2.default.getLoger('ConferApe');
+	var itemIdx = 0; //table插入新数据的计数id,目前用时间戳
+
+	var ConferApe = function (_Ape) {
+	    _inherits(ConferApe, _Ape);
+
+	    function ConferApe() {
+	        _classCallCheck(this, ConferApe);
+
+	        /*
+	         // Attribures
+	         this.hostNodeId = -1;//主持人的nodeId
+	         //    用户的身份,5种类型:
+	         //    host(主持人/老师)
+	         //    presenter(主讲人)
+	         //    assistant(助教)
+	         //    normal(普通角色/学生)
+	         //    record(暂时没用.
+	         //    默认值: normal
+	         this.hostUserId = '';//主持人的 第三方userId
+	         */
+
+	        var _this = _possibleConstructorReturn(this, (ConferApe.__proto__ || Object.getPrototypeOf(ConferApe)).call(this, _ApeConsts2.default.CONFERENCE_SESSION_ID, _ApeConsts2.default.CONFERENCE_SESSION_NAME, _ApeConsts2.default.CONFERENCE_SESSION_TAG));
+
+	        _this.rosters = {}; //用户列表
+	        _this.timerCounter = new _TimerCounter2.default(); //计时器
+
+	        // Ape Models
+	        _this.registerKey(_this._session_id, _this._session_name, _this._session_tag, new ArrayBuffer());
+
+	        _this.registerObj(_pdus2.default.RCPDU_REG_REGISTER_ROSTER, _ApeConsts2.default.CONFERENCE_OBJ_ROSTER_ID, _ApeConsts2.default.CONFERENCE_OBJ_ROSTER_NAME, _ApeConsts2.default.CONFERENCE_OBJ_ROSTER_TAG, 0, new ArrayBuffer());
+
+	        _this.registerObj(_pdus2.default.RCPDU_REG_REGISTER_QUEUE, _ApeConsts2.default.CONFERENCE_OBJ_QUEUE_ID, _ApeConsts2.default.CONFERENCE_OBJ_QUEUE_NAME, _ApeConsts2.default.CONFERENCE_OBJ_QUEUE_TAG, 0, new ArrayBuffer());
+
+	        _this.registerObj(_pdus2.default.RCPDU_REG_REGISTER_TABLE, _ApeConsts2.default.CONFERENCE_OBJ_TABLE_ID, _ApeConsts2.default.CONFERENCE_OBJ_TABLE_NAME, _ApeConsts2.default.CONFERENCE_OBJ_TABLE_TAG, 0, new ArrayBuffer());
+
+	        _this.registerObj(_pdus2.default.RCPDU_REG_REGISTER_COUNTER, _ApeConsts2.default.CONFERENCE_OBJ_COUNTER_ID, _ApeConsts2.default.CONFERENCE_OBJ_COUNTER_NAME, _ApeConsts2.default.CONFERENCE_OBJ_COUNTER_TAG, 0, new ArrayBuffer());
+
+	        _this.on(_pdus2.default.RCPDU_SESSION_JOIN_RESPONSE, _this._joinSessionHandler.bind(_this));
+
+	        _this.on(_pdus2.default.RCPDU_SEND_CONFERENCE_DATA_REQUEST, _this.conferMsgComingHandler.bind(_this)); //这个是会议消息类型,flash里在使用这里不再使用,各个模块的消息由模块自己来处理
+	        _this.on(_pdus2.default.RCPDU_CONFERENCE_RECORD_REQUEST, _this.onSendConferRecordRequestHandler.bind(_this)); //发送录制和停止录制消息
+	        return _this;
+	    }
+
+	    //加入会议
+
+
+	    _createClass(ConferApe, [{
+	        key: '_joinSessionHandler',
+	        value: function _joinSessionHandler(_data) {
+	            var nodeInfoRecordPdu = this.mcu.mcuClassInfo.self;
+	            loger.log("_joinSessionHandler nodeInfoRecordPdu=");
+	            console.log(nodeInfoRecordPdu);
+	            var userDataPdu = new _pdus2.default['RCNodeInfoUserDataPdu']();
+	            userDataPdu.qq = '';
+	            userDataPdu.skype = '';
+
+	            nodeInfoRecordPdu.userData = userDataPdu.toArrayBuffer();
+	            nodeInfoRecordPdu.deviceType = _GlobalConfig2.default.deviceType; //设备类型
+
+	            var item = new _pdus2.default['RCRegistryRosterItemPdu']();
+	            item.nodeId = nodeInfoRecordPdu.nodeId;
+	            item.nodeData = nodeInfoRecordPdu.toArrayBuffer();
+
+	            var rosterUpdateItem = new _pdus2.default['RCRegistryRosterUpdateItemPdu']();
+	            rosterUpdateItem.type = _pdus2.default.RCPDU_REG_ROSTER_UPDATE_PDU;
+	            rosterUpdateItem.items.push(item);
+
+	            var updateObjPdu = new _pdus2.default['RCRegistryUpdateObjPdu']();
+	            updateObjPdu.objId = _ApeConsts2.default.CONFERENCE_OBJ_ROSTER_ID;
+	            updateObjPdu.subType = rosterUpdateItem.type;
+	            updateObjPdu.userData = rosterUpdateItem.toArrayBuffer();
+
+	            //同步
+	            var adapterItemPdu = new _pdus2.default['RCAdapterItemPdu']();
+	            adapterItemPdu.type = _pdus2.default.RCPDU_REG_UPDATE_OBJ;
+	            adapterItemPdu.itemData = updateObjPdu.toArrayBuffer();
+
+	            var adapterPdu = new _pdus2.default['RCAdapterPdu']();
+	            adapterPdu.type = _pdus2.default.RCPDU_REG_ADAPTER;
+	            adapterPdu.item.push(adapterItemPdu);
+	            this.sendUniform(adapterPdu, true);
+	        }
+	    }, {
+	        key: 'sendConferMsg',
+	        value: function sendConferMsg(_messageInfo) {
+	            if (this._classInfo == null || _EngineUtils2.default.isEmptyObject(this._classInfo)) {
+	                loger.log('不能发送会议消息.McuClient还未初始化数据!');
+	                if (_GlobalConfig2.default.getCurrentStatus().code == 0 || _GlobalConfig2.default.getCurrentStatus().code == 1) {
+	                    this._emit(_MessageTypes2.default.MCU_ERROR, _MessageTypes2.default.ERR_APE_SEND_FAILED_NO_JOIN);
+	                    return;
+	                }
+	                return;
+	            }
+
+	            // to, message
+	            loger.log('发送会议消息.', _messageInfo);
+
+	            /*    message RCConferenceSendDataRequestPdu {
+	             optional uint32 initiator = 1;
+	             optional uint32 peer = 2;
+	             required bool is_public = 3;
+	             required bytes user_data = 4;
+	             }
+	             */
+
+	            var conferSendPdu = new _pdus2.default['RCConferenceSendDataRequestPdu']();
+	            conferSendPdu.type = _pdus2.default.RCPDU_SEND_CONFERENCE_DATA_REQUEST;
+	            conferSendPdu.initiator = this._classInfo.nodeId; //发起人
+	            conferSendPdu.peer = parseInt(_messageInfo.to); //发送给谁,公聊的时候是0,私聊的时候是指定的用户id
+
+	            conferSendPdu.userData = this._rCArrayBufferUtil.strToUint8Array("h5" + _messageInfo.message);
+	            //conferSendPdu.userData =UTF8.setBytesFromString(_messageInfo.message);
+	            conferSendPdu.isPublic = true;
+	            conferSendPdu.actionType = _messageInfo.actionType;
+	            // if (!(conferSendPdu.isPublic || 0 === conferSendPdu.peer)) {
+	            if (!conferSendPdu.isPublic && 0 != conferSendPdu.peer) {
+	                //发送给制定的人
+	                loger.log('发送私聊会议消息.');
+	                this.send(conferSendPdu);
+	            } else {
+	                //发送给所有人
+	                loger.log('发送公聊会议消息.');
+	                this.sendChatUniform(conferSendPdu);
+	            }
+	        }
+
+	        //发送录制或停止录制的消息,{"recordStatus":true};true为开始录制,false为停止录制
+
+	    }, {
+	        key: 'sendConferRecordMsg',
+	        value: function sendConferRecordMsg(_param) {
+	            if (!this.mcu.connected) {
+	                loger.warn(_GlobalConfig2.default.getCurrentStatus());
+	                return { "code": _ApeConsts2.default.RETURN_FAILED, "data": "已经断开连接" };
+	            }
+	            // to, message
+	            loger.log('发送录制消息.', _param);
+
+	            if (_param == null) {
+	                loger.warn("控制录制状的消息发送失败,参数错误", _param);
+	                return;
+	            }
+
+	            /*  message RCConferenceRecordRequestPdu {
+	             optional uint32 initiator = 1;	// 发起录像指令的node id
+	             optional bool record = 2;		// 录像指令 true:开始录像, false:停止录像
+	             optional uint32 class_time = 3;	// 课堂进行时间(秒)
+	             optional string filename = 4;	// 录像文件名称,filename中增加目录部分
+	             }*/
+
+	            //保存当前的录制状态
+	            _GlobalConfig2.default.recordStatus = _param.recordStatus || false;
+
+	            var conferRecordSendPdu = new _pdus2.default['RCConferenceRecordRequestPdu']();
+	            conferRecordSendPdu.type = _pdus2.default.RCPDU_CONFERENCE_RECORD_REQUEST;
+	            conferRecordSendPdu.peer = 0; //channel 为0
+	            conferRecordSendPdu.isPublic = true;
+
+	            conferRecordSendPdu.initiator = this._classInfo.nodeId; //发起人
+	            conferRecordSendPdu.record = _GlobalConfig2.default.recordStatus;
+	            conferRecordSendPdu.classTime = _GlobalConfig2.default.classTimestamp;
+	            conferRecordSendPdu.filename = _GlobalConfig2.default.recordFileName || _GlobalConfig2.default.classId + "_" + _EngineUtils2.default.creatTimestampYMD() + ".rec";
+	            this.sendChatUniform(conferRecordSendPdu);
+	        }
+
+	        //开启录制
+
+	    }, {
+	        key: 'startRecord',
+	        value: function startRecord() {
+	            loger.log('startRecord', "isHost", _GlobalConfig2.default.isHost, "recordStatus", _GlobalConfig2.default.recordStatus);
+	            //如果是host
+	            if (_GlobalConfig2.default.isHost) {
+	                _GlobalConfig2.default.classStopTime = _EngineUtils2.default.creatTimestampStr();
+	                this.sendConferRecordMsg({ "recordStatus": true });
+	                this._emit(_MessageTypes2.default.CLASS_STATUS_INFO_CHANGE);
+	                this._emit(_MessageTypes2.default.CLASS_RECORD_START); //会议开始录制
+	            }
+	        }
+
+	        //停止录制
+
+	    }, {
+	        key: 'stopRecord',
+	        value: function stopRecord() {
+	            loger.log('stopRecord', "isHost", _GlobalConfig2.default.isHost, "recordStatus", _GlobalConfig2.default.recordStatus);
+	            //如果是host,并且当前正在录制中
+	            if (_GlobalConfig2.default.isHost && _GlobalConfig2.default.recordStatus) {
+	                _GlobalConfig2.default.classStopTime = _EngineUtils2.default.creatTimestampStr();
+	                this.sendConferRecordMsg({ "recordStatus": false });
+	                this._emit(_MessageTypes2.default.CLASS_STATUS_INFO_CHANGE);
+	            }
+	        }
+
+	        //主动离开会议,发送通知到服务器
+
+	    }, {
+	        key: 'leaveClass',
+	        value: function leaveClass() {
+	            var nodeInfoRecordPdu = this.mcu.mcuClassInfo.self;
+	            var userDataPdu = new _pdus2.default['RCNodeInfoUserDataPdu']();
+	            userDataPdu.qq = '';
+	            userDataPdu.skype = '';
+
+	            nodeInfoRecordPdu.userData = userDataPdu.toArrayBuffer();
+	            nodeInfoRecordPdu.deviceType = _GlobalConfig2.default.deviceType;
+
+	            var item = new _pdus2.default['RCRegistryRosterItemPdu']();
+	            item.nodeId = nodeInfoRecordPdu.nodeId;
+	            item.nodeData = nodeInfoRecordPdu.toArrayBuffer();
+
+	            var rosterUpdateItem = new _pdus2.default['RCRegistryRosterDeleteItemPdu']();
+	            rosterUpdateItem.type = _pdus2.default.RCPDU_REG_ROSTER_DELETE_PDU;
+	            rosterUpdateItem.nodeId = _GlobalConfig2.default.nodeId;
+
+	            var updateObjPdu = new _pdus2.default['RCRegistryUpdateObjPdu']();
+	            updateObjPdu.objId = _ApeConsts2.default.CONFERENCE_OBJ_ROSTER_ID;
+	            updateObjPdu.subType = rosterUpdateItem.type;
+	            updateObjPdu.userData = rosterUpdateItem.toArrayBuffer();
+
+	            var adapterItemPdu = new _pdus2.default['RCAdapterItemPdu']();
+	            adapterItemPdu.type = _pdus2.default.RCPDU_REG_UPDATE_OBJ;
+	            adapterItemPdu.itemData = updateObjPdu.toArrayBuffer();
+
+	            var adapterPdu = new _pdus2.default['RCAdapterPdu']();
+	            adapterPdu.type = _pdus2.default.RCPDU_REG_ADAPTER;
+	            adapterPdu.item.push(adapterItemPdu);
+
+	            this.sendUniform(adapterPdu, true);
+	        }
+
+	        //还原课堂状态
+
+	    }, {
+	        key: 'restorClass',
+	        value: function restorClass() {
+	            _GlobalConfig2.default.classTimestamp = 0;
+	            _GlobalConfig2.default.classStatus = _ApeConsts2.default.CLASS_STATUS_WAIT;
+	            _GlobalConfig2.default.classStopTime = _EngineUtils2.default.creatTimestampStr();
+	            this.stopRecord();
+	            this._emit(_MessageTypes2.default.CLASS_STATUS_INFO_CHANGE);
+	            this.sendUpdaterClassStatusInfo({ "actionType": 0 });
+	            loger.log('restorClass');
+	        }
+
+	        //开始上课
+
+	    }, {
+	        key: 'startClass',
+	        value: function startClass(_param) {
+	            if (_GlobalConfig2.default.isHost) {
+
+	                var timestamp = _EngineUtils2.default.creatTimestampStr();
+	                _GlobalConfig2.default.classStopTime = timestamp;
+
+	                //如果录制的文件名不存在,需要创建一个名字
+	                var timestampYMD = _EngineUtils2.default.creatTimestampYMD();
+	                _GlobalConfig2.default.recordFileName = _GlobalConfig2.default.recordFileName || _GlobalConfig2.default.siteId + "/" + timestampYMD + "/" + _GlobalConfig2.default.classId + "_" + timestampYMD + ".rec"; //4、文件名称 $RECORD_HOME/`site id`/`日期`/`filename`	例:/data/record/su/20161216/`filename`
+
+	                if (_GlobalConfig2.default.classStatus == _ApeConsts2.default.CLASS_STATUS_WAIT) {
+	                    //之前是为开始状态,第一次点开始
+	                    _GlobalConfig2.default.classStartTime = timestamp;
+	                }
+
+	                _GlobalConfig2.default.classStatus = _ApeConsts2.default.CLASS_STATUS_STARTED;
+	                //开始录制
+	                this.startRecord();
+	                //会议状态改变
+	                this._emit(_MessageTypes2.default.CLASS_STATUS_INFO_CHANGE);
+	                //同步会议状态
+	                this.sendUpdaterClassStatusInfo({ "actionType": 1 });
+
+	                //开始计时
+	                this.startTimerCounter();
+	            } else {
+	                loger.warn('没有权限');
+	            }
+	        }
+
+	        //暂停上课
+
+	    }, {
+	        key: 'pauseClass',
+	        value: function pauseClass(_param) {
+	            if (_GlobalConfig2.default.classStatus == _ApeConsts2.default.CLASS_STATUS_WAIT) {
+	                loger.warn('还没有开始,不能点暂停');
+	                return;
+	            }
+
+	            _GlobalConfig2.default.classStatus = _ApeConsts2.default.CLASS_STATUS_PAUSE;
+	            _GlobalConfig2.default.classStopTime = _EngineUtils2.default.creatTimestampStr();
+
+	            this.stopRecord();
+	            this._emit(_MessageTypes2.default.CLASS_STATUS_INFO_CHANGE);
+	            this.sendUpdaterClassStatusInfo({ "actionType": 2 });
+	            this.stopTimerCounter();
+	        }
+
+	        //关闭课堂
+
+	    }, {
+	        key: 'closeClass',
+	        value: function closeClass(_param) {
+	            if (_GlobalConfig2.default.classStatus == _ApeConsts2.default.CLASS_STATUS_WAIT) {
+	                loger.warn('还没有开始,不能点关闭');
+	                return;
+	            }
+
+	            this.stopTimerCounter();
+	            this.restorClass();
+	            //把所有人都踢出课堂
+	            this.sendConferMsg({ "to": 0, "message": "所有人退出会议", "actionType": _ApeConsts2.default.CLASS_ACTION_CLOSE_ALL });
+	        }
+
+	        //更新会议信息
+
+	    }, {
+	        key: 'sendUpdaterClassStatusInfo',
+	        value: function sendUpdaterClassStatusInfo(_param) {
+	            loger.log('sendUpdaterClassStatusInfo');
+	            if (_param == null || _EngineUtils2.default.isEmptyObject(_param)) {
+	                loger.log('sendUpdaterClassStatusInfo,参数错误');
+	                this._emit(_MessageTypes2.default.MCU_ERROR, _MessageTypes2.default.ERR_APE_INTERFACE_PARAM_WRONG);
+	                return;
+	            }
+	            itemIdx = _ApeConsts2.default.CONFERENCE_OBJ_TABLE_ID; // itemIdx=_param.itemIdx;
+	            var modelPdu = this.packPdu(_param, itemIdx);
+	            //loger.log('sendUpdaterClassStatusInfo----2------');
+	            console.log(modelPdu);
+
+	            if (modelPdu == null) {
+	                loger.log('sendUpdaterClassStatusInfo,参数错误');
+	                this._emit(_MessageTypes2.default.MCU_ERROR, _MessageTypes2.default.ERR_APE_INTERFACE_PARAM_WRONG);
+	                return;
+	            }
+
+	            var tableItemPdu = new _pdus2.default['RCRegistryTableItemPdu']();
+	            tableItemPdu.itemIdx = itemIdx;
+	            tableItemPdu.owner = 0; //收到flash的是这个值,不清楚先写固定
+	            tableItemPdu.registerObjId = _ApeConsts2.default.CONFERENCE_OBJ_TABLE_ID;
+	            tableItemPdu.itemData = modelPdu.toArrayBuffer();
+
+	            //updater
+	            var tableUpdateItem = new _pdus2.default['RCRegistryTableUpdateItemPdu']();
+	            //optional RCPduType_E type = 1 [default = RCPDU_REG_TABLE_UPDATE_PDU];
+	            //repeated RCRegistryTableItemPdu items = 2;
+	            tableUpdateItem.type = _pdus2.default.RCPDU_REG_TABLE_UPDATE_PDU; //
+	            tableUpdateItem.items.push(tableItemPdu);
+
+	            var updateObjPdu = new _pdus2.default['RCRegistryUpdateObjPdu']();
+	            updateObjPdu.objId = _ApeConsts2.default.CONFERENCE_OBJ_TABLE_ID;
+	            updateObjPdu.subType = tableUpdateItem.type;
+	            updateObjPdu.userData = tableUpdateItem.toArrayBuffer();
+
+	            //同步
+	            var adapterItemPdu = new _pdus2.default['RCAdapterItemPdu']();
+	            adapterItemPdu.type = _pdus2.default.RCPDU_REG_UPDATE_OBJ;
+	            adapterItemPdu.itemData = updateObjPdu.toArrayBuffer();
+
+	            var adapterPdu = new _pdus2.default['RCAdapterPdu']();
+	            adapterPdu.type = _pdus2.default.RCPDU_REG_ADAPTER;
+	            adapterPdu.item.push(adapterItemPdu);
+
+	            console.log("会议发送更新数据============");
+	            this.sendUniform(adapterPdu, true);
+	        }
+
+	        /////收到消息处理/////////////////////////////////////////////////////////////////////////////////
+	        //加入channel成功
+
+	    }, {
+	        key: 'onJoinChannelHandlerSuccess',
+	        value: function onJoinChannelHandlerSuccess() {
+	            loger.log('ConferApe.onJoinChannelHandlerSuccess', _GlobalConfig2.default.classStatus);
+	            this.timerCounter.addTimerCallBack(this.timerCounterUptate.bind(this), 1);
+	            //如果当前会议正在进行中,开启计时器
+	            if (_GlobalConfig2.default.classStatus == _ApeConsts2.default.CLASS_STATUS_STARTED) {
+	                //开始计时
+	                this.startTimerCounter();
+
+	                //如果是host ,开始录制
+	                this.startRecord();
+	            }
+	        }
+
+	        //开启计时器
+
+	    }, {
+	        key: 'startTimerCounter',
+	        value: function startTimerCounter() {
+	            this.timerCounter.startTimer();
+	        }
+
+	        //停止计时器
+
+	    }, {
+	        key: 'stopTimerCounter',
+	        value: function stopTimerCounter() {
+	            this.timerCounter.stopTimer();
+	        }
+	    }, {
+	        key: 'timerCounterUptate',
+	        value: function timerCounterUptate() {
+	            if (!this.mcu.connected) {
+	                loger.warn('MCU 连接已经断开');
+	                this.stopTimerCounter();
+	            }
+	            //如果还没开始或已经暂停、关闭,不做计时处理
+	            if (_GlobalConfig2.default.classStatus != _ApeConsts2.default.CLASS_STATUS_STARTED) {
+	                loger.warn('当前课堂已经暂停或者未开始,不计时', "classStatus-->", _GlobalConfig2.default.classStatus);
+	                return;
+	            }
+	            _GlobalConfig2.default.classTimestamp = _GlobalConfig2.default.classTimestamp + 1; //计时
+	            //loger.log('课堂进行时间',GlobalConfig.classTimestamp);
+	            this._emit(_MessageTypes2.default.CLASS_UPDATE_TIMER, { "classTimestamp": _GlobalConfig2.default.classTimestamp });
+
+	            if (_GlobalConfig2.default.classTimestamp % _GlobalConfig2.default.updateClassInfoDelay == 0) {
+	                //如果是host身份,需要同步时间给其他人,同时把当前的状态上传到服务器
+	                if (_GlobalConfig2.default.isHost) {
+	                    //保存数据到Sass
+	                    this._emit(_MessageTypes2.default.CLASS_STATUS_INFO_CHANGE);
+
+	                    //同步消息给其他人
+	                    this.sendUpdaterClassStatusInfo({ "actionType": 1 });
+	                }
+	            }
+	        }
+	    }, {
+	        key: 'tableUpdateHandler',
+	        value: function tableUpdateHandler(owner, itemIdx, itemData) {
+	            try {
+	                var model = this.unPackPdu(owner, itemIdx, itemData);
+	                loger.log('tableUpdateHandler');
+	                console.log(model);
+
+	                //处理会议更新的信息
+	                if (model && model.classStatusInfo) {
+	                    _GlobalConfig2.default.setClassStatusInfo(model.classStatusInfo);
+	                }
+	                //通知应用层更新会议状态
+	                this._emit(_MessageTypes2.default.CLASS_UPTATE_STATUS, _GlobalConfig2.default.classStatusInfo);
+
+	                //如果MCU已经断开连接,停止计时器
+	                if (!this.mcu.connected) {
+	                    //停止计时
+	                    this.stopTimerCounter();
+	                    return;
+	                }
+
+	                if (_GlobalConfig2.default.classStatus == _ApeConsts2.default.CLASS_STATUS_STARTED) {
+	                    //如果会议在进行中,开始计时器
+	                    this.startTimerCounter();
+	                } else {
+	                    //停止计时
+	                    this.stopTimerCounter();
+	                }
+	            } catch (e) {
+	                loger.warn('ConferApe table update got exception.   itemIdx', itemIdx);
+	            }
+	        }
+	    }, {
+	        key: 'conferMsgComingHandler',
+	        value: function conferMsgComingHandler(_data) {
+	            //flash    RCConferenceSendDataRequestPdu
+	            //loger.warn('conferMsgComingHandler needs to be handled.');
+	            //const recordInfo = pdu['RCWhiteboardDataRequestPdu'].decode(pdu);
+	            //loger.log("conferMsgComingHandler",recordInfo);
+
+	            var chatReceivePdu = _pdus2.default['RCConferenceSendDataRequestPdu'].decode(_data);
+
+	            var chatMsg = {};
+	            chatMsg.fromNodeID = chatReceivePdu.initiator;
+	            chatMsg.toNodeID = chatReceivePdu.peer;
+	            chatMsg.message = this._rCArrayBufferUtil.uint8ArrayToStr(chatReceivePdu.userData, 2);
+	            chatMsg.actionType = chatReceivePdu.actionType;
+	            loger.log("conferMsgComingHandler", chatMsg);
+	            switch (chatMsg.actionType) {
+	                case _ApeConsts2.default.CLASS_ACTION_CLOSE_ALL:
+	                    loger.log(chatMsg.message);
+	                    //收到会议关闭,所有人都退出,执行自己关闭的流程
+	                    this._emit(_MessageTypes2.default.CLASS_EXIT);
+	                    break;
+	                default:
+	                    break;
+	            }
+	        }
+	    }, {
+	        key: 'onSendConferRecordRequestHandler',
+	        value: function onSendConferRecordRequestHandler(_data) {
+	            loger.log("onSendConferRecordRequestHandler");
+	            try {
+	                var conferRecordSendPdu = _pdus2.default['RCConferenceRecordRequestPdu'].decode(_data);
+	                console.log(conferRecordSendPdu);
+	            } catch (err) {
+	                loger.warn("onSendConferRecordRequestHandler err", err.message);
+	            }
+	        }
+	    }, {
+	        key: 'rosterInsertHandler',
+	        value: function rosterInsertHandler(nodeId, nodeData) {
+	            if (_GlobalConfig2.default.nodeId == nodeId) {
+	                // loger.log("自己加入 rosterInsertHandler");
+	            } else {
+	                // loger.log("有人加入 rosterInsertHandler");
+	                this.rosterUpdateHandler(nodeId, nodeData);
+	            }
+	        }
+
+	        //更新人员列表数据
+
+	    }, {
+	        key: 'rosterUpdateHandler',
+	        value: function rosterUpdateHandler(nodeId, nodeData) {
+	            //如果是自己的信息,不处理跳过
+	            if (nodeId == _GlobalConfig2.default.nodeId) {
+	                loger.log("自己加入课堂的消息,不需要处理");
+	                this.rosters[nodeId] = nodeData;
+	                return;
+	            }
+
+	            loger.log(nodeId, "加入课堂,role-->", nodeData.role, _ApeConsts2.default.userTypes[nodeData.role]);
+
+	            //新加入的人员不是自己
+	            //1.判断进入的用户身份,如果进入的人身份是host,助教,监课,并且和自己的身份冲突,自己会被踢掉
+	            //2.最后进入的人会踢掉之前进入的人,nodeId是按时间戳生成的,最后进入的人nodeId的值比之前进入的人大
+	            if (parseInt(nodeId) > _GlobalConfig2.default.nodeId) {
+	                if (nodeData.role == _ApeConsts2.default.NR_HOST && _GlobalConfig2.default.isHost) {
+	                    this.kickOutRoster();
+	                    return;
+	                } else if (nodeData.role == _ApeConsts2.default.NR_PRESENTER && _GlobalConfig2.default.isPresenter) {
+	                    this.kickOutRoster();
+	                    return;
+	                } else if (nodeData.role == _ApeConsts2.default.NR_ASSISTANT && _GlobalConfig2.default.isAssistant) {
+	                    this.kickOutRoster();
+	                    return;
+	                } else if (nodeData.role == _ApeConsts2.default.NR_INVISIBLE && _GlobalConfig2.default.isInvisible) {
+	                    this.kickOutRoster();
+	                    return;
+	                }
+	            }
+
+	            //处理用户信息
+	            var rosterExists = this.rosters[nodeId];
+	            this.rosters[nodeId] = nodeData;
+	            var userDataObj = null;
+	            if (!rosterExists) {
+	                try {
+	                    userDataObj = _pdus2.default['RCNodeInfoUserDataPdu'].decode(nodeData.userData);
+	                } catch (err) {
+	                    loger.log("RCNodeInfoUserDataPdu decode err", err.message);
+	                }
+
+	                var newNodeData = nodeData;
+	                newNodeData.userData = userDataObj;
+
+	                //如果是监课,不告诉其他人
+	                if (nodeData.role == _ApeConsts2.default.NR_INVISIBLE) {
+	                    loger.log("NR_INVISIBLE");
+	                    return;
+	                }
+
+	                this._emit(_MessageTypes2.default.CLASS_INSERT_ROSTER, { "nodeId": nodeId, "nodeData": newNodeData });
+	                this.emitRosterChange();
+	            } else {
+	                //loger.log("更新人员列表数据,rosterExists已经存在",rosterExists);
+	            }
+	        }
+
+	        //踢出用户
+
+	    }, {
+	        key: 'kickOutRoster',
+	        value: function kickOutRoster() {
+	            this._emit(_MessageTypes2.default.MCU_ERROR, _MessageTypes2.default.ERR_CLASS_KICK_OUT);
+	            this._emit(_MessageTypes2.default.CLASS_EXIT);
+	        }
+
+	        //视频模块发生更新,人员状态需要更新
+
+	    }, {
+	        key: 'updaterRosterStatus',
+	        value: function updaterRosterStatus(_param) {
+	            //loger.log("媒体模块发生更新,人员状态需要更新,fromNodeId->",_param.fromNodeId);
+	            //如果视频消息中channel的占用人 fromNodeId在人员列表中不存在,需要释放这channel,因为这个有可能是之前没释放成功的
+	            if (_param && _param.status == _ApeConsts2.default.CHANNEL_STATUS_OPENING && this.rosters[_param.fromNodeId] == null) {
+	                loger.log("媒体模块被占用,占有人已经不存在课堂中,释放Channel,_param->", _param);
+	                this._emit(_MessageTypes2.default.CLASS_NONENTITY_ROSTER, { "nodeId": _param.fromNodeId });
+	            }
+	        }
+
+	        //删除用户
+
+	    }, {
+	        key: 'rosterDelHandler',
+	        value: function rosterDelHandler(nodeId) {
+	            if (_GlobalConfig2.default.nodeId == nodeId) {
+	                loger.log("自己离开课堂");
+	                // 自己退出
+	                this._emit(_MessageTypes2.default.CLASS_EXIT);
+	            } else {
+	                loger.log(nodeId, "离开课堂");
+	                delete this.rosters[nodeId];
+	                this.emitRosterChange();
+	                this._emit(_MessageTypes2.default.CLASS_DELETE_ROSTER, { "nodeId": nodeId });
+
+	                //当前人员列表中抽一个人来检查离开人员是否占用频道
+	                for (var key in this.rosters) {
+	                    var randNodeId = parseInt(key);
+	                    if (randNodeId == _GlobalConfig2.default.nodeId) {
+	                        loger.log(randNodeId, "有权限检查离开的人员是否占用channel");
+	                        this._emit(_MessageTypes2.default.CLASS_NONENTITY_ROSTER, { "nodeId": nodeId });
+	                    } else {
+	                        loger.warn(_GlobalConfig2.default.nodeId, "没有权限检查离开的人员是否占用channel");
+	                    }
+	                    return;
+	                }
+	            }
+	        }
+
+	        //广播当前的人数
+
+	    }, {
+	        key: 'emitRosterChange',
+	        value: function emitRosterChange() {
+	            this._emit(_MessageTypes2.default.CLASS_UPDATE_ROSTER_NUM, Object.keys(this.rosters).length);
+	        }
+
+	        ///////数据的封包和解包/////////////////////////////////////////
+
+	    }, {
+	        key: 'packPdu',
+	        value: function packPdu(_param, _itemIdx) {
+	            loger.log("会议===packPdu ");
+	            //验证坐标点集合数组是否合法
+	            if (_param == null || _itemIdx == null) {
+	                this._emit(_MessageTypes2.default.MCU_ERROR, _MessageTypes2.default.ERR_APE_INTERFACE_PARAM_WRONG);
+	                return null;
+	            }
+	            var classStatusInfo = new _pdus2.default['RCClassStatusInfoPdu']();
+	            classStatusInfo.nodeId = _GlobalConfig2.default.nodeId; //mcu中的唯一ID
+	            classStatusInfo.userId = _GlobalConfig2.default.userId;
+	            classStatusInfo.userName = _GlobalConfig2.default.userName;
+	            classStatusInfo.siteId = _GlobalConfig2.default.siteId; //站点号
+	            classStatusInfo.classId = _GlobalConfig2.default.classId;
+	            classStatusInfo.className = _GlobalConfig2.default.className;
+	            classStatusInfo.classType = _GlobalConfig2.default.classType; //课堂类型
+	            classStatusInfo.classStatus = _GlobalConfig2.default.classStatus; //课堂的状态
+	            classStatusInfo.classStartTime = _GlobalConfig2.default.classStartTime; //课堂点击开始时间
+	            classStatusInfo.classStopTime = _GlobalConfig2.default.classStopTime; //最后一次停止的时间(点暂停或结束),每次发送数据都获取当前时间戳
+	            classStatusInfo.classTimestamp = _GlobalConfig2.default.classTimestamp; //相对于点开始课堂的时间戳
+	            classStatusInfo.classBeginTime = _GlobalConfig2.default.classBeginTime; //课堂创建的时间,这个是Sass返回的
+	            classStatusInfo.classEndTime = _GlobalConfig2.default.classEndTime; //课堂结束的时间,这个是Sass返回的
+	            classStatusInfo.recordStatus = _GlobalConfig2.default.recordStatus; //当前录制状态
+	            classStatusInfo.recordTimestamp = _GlobalConfig2.default.recordTimestamp; //相对于首次开始录制的时间戳
+	            classStatusInfo.recordFileName = _GlobalConfig2.default.recordFileName; //录制的文件名
+	            classStatusInfo.recordDownloadUrl = _GlobalConfig2.default.recordDownloadUrl; //下载地址
+	            classStatusInfo.serverTimestamp = _GlobalConfig2.default.serverTimestamp; //当前的系统时间戳
+	            classStatusInfo.activeDocId = _GlobalConfig2.default.activeDocId; //当前激活的文档id
+	            classStatusInfo.activeDocCurPage = _GlobalConfig2.default.activeDocCurPage; //当前激活的文档的当前页
+
+	            loger.log("classStatusInfo-------------", classStatusInfo);
+
+	            /*
+	             optional uint32 item_idx=1;
+	             optional uint32 from=2;
+	             optional uint32 owner=3;
+	             optional uint32 action_type=4;//状态改变的类型
+	             optional RCClassStatusInfoPdu class_status_info=5;//当前课堂状态的信息
+	             */
+	            //判断type类型,根据type设置不同的参数
+	            var modelPdu = new _pdus2.default['RCClassSendDataModelPdu']();
+	            modelPdu.itemIdx = _itemIdx;
+	            modelPdu.from = _GlobalConfig2.default.nodeId;
+	            modelPdu.owner = _GlobalConfig2.default.nodeId;
+	            //modelPdu.actionType  =_param.actionType;
+	            modelPdu.classStatusInfo = classStatusInfo;
+	            return modelPdu;
+	        }
+	    }, {
+	        key: 'unPackPdu',
+	        value: function unPackPdu(owner, itemIdx, itemData) {
+	            loger.log("会议===unPackPdu ");
+	            if (owner == null || itemIdx == null || itemData == null) {
+	                this._emit(_MessageTypes2.default.MCU_ERROR, _MessageTypes2.default.ERR_APE_INTERFACE_PARAM_WRONG);
+	                return null;
+	            }
+
+	            try {
+	                var modelPdu = _pdus2.default['RCClassSendDataModelPdu'].decode(itemData);
+	                return modelPdu;
+	            } catch (err) {
+	                loger.log("会议收到数据 unPackPdu Pdu解析错误,itemIdx=" + itemIdx + "  err:" + err.message);
+	            }
+	            return null;
+	        }
+	    }]);
+
+	    return ConferApe;
+	}(_Ape3.default);
+
+	var _default = ConferApe;
+	exports.default = _default;
+	;
+
+	var _temp = function () {
+	    if (typeof __REACT_HOT_LOADER__ === 'undefined') {
+	        return;
+	    }
+
+	    __REACT_HOT_LOADER__.register(loger, 'loger', 'D:/work/McuClient/src/apes/ConferApe.js');
+
+	    __REACT_HOT_LOADER__.register(itemIdx, 'itemIdx', 'D:/work/McuClient/src/apes/ConferApe.js');
+
+	    __REACT_HOT_LOADER__.register(ConferApe, 'ConferApe', 'D:/work/McuClient/src/apes/ConferApe.js');
+
+	    __REACT_HOT_LOADER__.register(_default, 'default', 'D:/work/McuClient/src/apes/ConferApe.js');
+	}();
+
+	;
+
+/***/ },
+/* 37 */
+/***/ function(module, exports, __webpack_require__) {
+
+	'use strict';
+
+	Object.defineProperty(exports, "__esModule", {
+	  value: true
+	});
+
+	var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
+
+	var _pdus = __webpack_require__(25);
+
+	var _pdus2 = _interopRequireDefault(_pdus);
+
+	var _Emiter2 = __webpack_require__(3);
+
+	var _Emiter3 = _interopRequireDefault(_Emiter2);
+
+	var _mcu = __webpack_require__(23);
+
+	var _mcu2 = _interopRequireDefault(_mcu);
+
+	var _Loger = __webpack_require__(5);
+
+	var _Loger2 = _interopRequireDefault(_Loger);
+
+	var _MessageTypes = __webpack_require__(6);
+
+	var _MessageTypes2 = _interopRequireDefault(_MessageTypes);
+
+	var _ApeConsts = __webpack_require__(8);
+
+	var _ApeConsts2 = _interopRequireDefault(_ApeConsts);
+
+	var _ArrayBufferUtil = __webpack_require__(38);
+
+	var _ArrayBufferUtil2 = _interopRequireDefault(_ArrayBufferUtil);
+
+	var _PduConsts = __webpack_require__(35);
+
+	var _PduConsts2 = _interopRequireDefault(_PduConsts);
+
+	var _GlobalConfig = __webpack_require__(7);
+
+	var _GlobalConfig2 = _interopRequireDefault(_GlobalConfig);
+
+	function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
+
+	function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
+
+	function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }
+
+	function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } // //////////////////////////////////////////////////////////////////////////////
+	//
+	//  Copyright (C) 2016-present  All Rights Reserved.
+	//  Licensed under the Apache License, Version 2.0 (the "License");
+	//  http://www.apache.org/licenses/LICENSE-2.0
+	//
+	//  Github Home: https://github.com/AlexWang1987
+	//  Author: AlexWang
+	//  Date: 2016-08-26 11:19:56
+	//  QQ Email: 1669499355@qq.com
+	//  Last Modified time: 2017-01-04 14:38:20
+	//  Description: LiveClass-Ape
+	//
+	// //////////////////////////////////////////////////////////////////////////////
+
+	// 日志对象
+	var loger = _Loger2.default.getLoger('Ape');
+
+	var Ape = function (_Emiter) {
+	  _inherits(Ape, _Emiter);
+
+	  function Ape(session_id, session_name, session_tag) {
+	    _classCallCheck(this, Ape);
+
+	    var _this = _possibleConstructorReturn(this, (Ape.__proto__ || Object.getPrototypeOf(Ape)).call(this));
+
+	    _this._session_id = session_id;
+	    _this._channel_id = session_id; // session_id === channel_id
+	    _this._session_name = session_name;
+	    _this._session_tag = session_tag;
+	    _this._session_channels = {};
+	    _this._adapter_pdu = new _pdus2.default['RCAdapterPdu']();
+	    _this._classInfo = null;
+	    _this._rCArrayBufferUtil = _ArrayBufferUtil2.default;
+	    _this._apeDelayed = false;
+	    _this._apeDelayedMsgs = [];
+	    _this._apeDelayedTimer = 0;
+
+	    //Ape 通用消息处理
+	    _this.on(_pdus2.default.RCPDU_SESSION_JOIN_RESPONSE, _this._joinSessionHandler.bind(_this));
+	    _this.on(_pdus2.default.RCPDU_CHANNEL_JOIN_RESPONSE, _this._joinChannelHandler.bind(_this));
+	    _this.on(_pdus2.default.RCPDU_REG_ADAPTER, _this._pduMessageHandler.bind(_this));
+
+	    //先收到onJoinSessionHandlerSuccess   后收到 onJoinChannelHandlerSuccess
+
+	    // 监听底层MCU会议
+	    _this.mcu = _mcu2.default;
+	    _this.mcu.on(_MessageTypes2.default.CLASS_JOIN_MCU_SUCCESS, _this._mcuConferenceJoinSuccessHandler.bind(_this));
+	    _this.mcu.registerApe(_this);
+	    return _this;
+	  }
+
+	  _createClass(Ape, [{
+	    key: 'regResponsePduHandler',
+	    value: function regResponsePduHandler() {}
+
+	    // 消息处理
+
+	  }, {
+	    key: '_pduMessageHandler',
+	    value: function _pduMessageHandler(regBuffer) {
+	      var _this2 = this;
+
+	      //loger.log("RCPDU_REG_ADAPTER==============================");
+	      if (this._apeDelayed) {
+	        // this._apeDelayedMsgs.push(regBuffer);
+	        // this._apeDelayedStart();
+	        setTimeout(function () {
+	          _this2._pduRegAdapterHandler(regBuffer);
+	        }, _GlobalConfig2.default.mcuDelay || 2000);
+	        return;
+	      }
+	      this._pduRegAdapterHandler(regBuffer);
+	    }
+
+	    // _apeDelayedStart() {
+	    //   if (this._apeDelayed && !this._apeDelayedTimer) {
+	    //     this._apeDelayedTimer = setInterval(this._delayedMsgHandler.bind(this), this._classInfo['mcuDelay'] || 10000);
+	    //   }
+	    // }
+
+	    // _apeDelayedStop() {
+	    //   clearInterval(this._apeDelayedTimer);
+	    //   this._apeDelayedTimer = 0;
+	    // }
+
+	    // // 延迟消息处理
+	    // _delayedMsgHandler() {
+	    //   if (this._apeDelayedMsgs.length) {
+	    //     this._pduRegAdapterHandler(this._apeDelayedMsgs.pop());
+	    //     if (!this._apeDelayedMsgs.length) this._apeDelayedStop();
+	    //   }
+	    // }
+
+	    // 数据同步处理
+
+	  }, {
+	    key: '_pduRegAdapterHandler',
+	    value: function _pduRegAdapterHandler(regBuffer) {
+	      var regPdu = _pdus2.default['RCAdapterPdu'].decode(regBuffer);
+	      var regItems = regPdu.item;
+	      var regItemSize = regItems.length;
+	      //loger.log(this._session_name + '数据同步消息');
+	      loger.log(this._session_name + '数据同步消息.同步条数', regItemSize);
+	      //console.log(regPdu);
+
+	      for (var i = 0; i < regItemSize; ++i) {
+	        var regItem = regItems[i];
+	        var regItemType = regItem.type;
+	        var regItemData = regItem.itemData;
+
+	        //根据数据包中的type处理数据是否同步
+
+	        if (_pdus2.default.RCPDU_REG_UPDATE_OBJ !== regItemType) {
+	          if (_pdus2.default.RCPDU_REG_RESPONSE_OBJ == regItemType) {
+	            var regResponsePdu = _pdus2.default['RCRegistryResponseObjPdu'].decode(regItemData);
+	            this.regResponsePduHandler(regResponsePdu);
+	          }
+	          // 只处理两种类型
+	          continue;
+	        }
+
+	        //具体的数据包
+	        var regUpdatedItem = _pdus2.default['RCRegistryUpdateObjPdu'].decode(regItemData);
+	        var sub_type = regUpdatedItem.subType;
+	        var object_id = regUpdatedItem.objId;
+	        var user_data = regUpdatedItem.userData;
+
+	        loger.log('REG OBJECT EVENT ->', _pdus2.default.id2type(sub_type));
+	        switch (sub_type) {
+	          case _pdus2.default.RCPDU_REG_ROSTER_INSERT_PDU:
+	            var rosterInsertData = _pdus2.default['RCRegstryRosterInsertItemPdu'].decode(user_data);
+	            var rosterInsertItems = rosterInsertData.items;
+	            var rosterInsertItemsLen = rosterInsertItems.length;
+	            for (var _i = 0; _i < rosterInsertItemsLen; ++_i) {
+	              var record = rosterInsertItems[_i];
+	              var recordId = record.item_id;
+	              var recordData = _pdus2.default['RCNodeInfoRecordPdu'].decode(record.item_data);
+	              this.rosterInsertHandler(recordId, recordData);
+	            }
+	            break;
+	          case _pdus2.default.RCPDU_REG_ROSTER_DELETE_PDU:
+	            var rosterDelData = _pdus2.default['RCRegistryRosterDeleteItemPdu'].decode(user_data);
+	            this.rosterDelHandler(rosterDelData.nodeId);
+	            break;
+	          case _pdus2.default.RCPDU_REG_ROSTER_UPDATE_PDU:
+	            var rosterUpdateData = _pdus2.default['RCRegistryRosterUpdateItemPdu'].decode(user_data);
+	            var rosterUpdateItems = rosterUpdateData.items;
+	            var rosterUpdateItemsLen = rosterUpdateItems.length;
+	            for (var _i2 = 0; _i2 < rosterUpdateItemsLen; ++_i2) {
+	              var node = rosterUpdateItems[_i2];
+	              var nodeId = node.nodeId;
+	              var nodeData = _pdus2.default['RCNodeInfoRecordPdu'].decode(node.nodeData);
+	              this.rosterUpdateHandler(nodeId, nodeData);
+	            }
+	            break;
+	          case _pdus2.default.RCPDU_REG_TABLE_INSERT_PDU:
+	            var tableInsertData = _pdus2.default['RCRegistryTableInsertItemPdu'].decode(user_data);
+	            var tableInsertItems = tableInsertData.items;
+	            var tableInsertItemsLen = tableInsertItems.length;
+	            for (var _i3 = 0; _i3 < tableInsertItemsLen; ++_i3) {
+	              var insertItem = tableInsertItems[_i3];
+	              //loger.log("insertItem",insertItem);
+	              this.tableInsertHandler(insertItem.owner, insertItem.itemIdx, insertItem.itemData);
+	            }
+	            break;
+	          case _pdus2.default.RCPDU_REG_TABLE_DELETE_PDU:
+	            var tableDeleteData = _pdus2.default['RCRegistryTableDeleteItemPdu'].decode(user_data);
+	            //console.log("tableDeleteData",object_id,tableDeleteData);
+	            this.tableDeleteHandler(object_id, tableDeleteData);
+	            break;
+	          case _pdus2.default.RCPDU_REG_TABLE_UPDATE_PDU:
+	            var tableUpdateData = _pdus2.default['RCRegistryTableUpdateItemPdu'].decode(user_data);
+	            var tableUpdateItems = tableUpdateData.items;
+	            var tableUpdateItemsLen = tableUpdateItems.length;
+	            loger.log("RCRegistryTableUpdateItemPdu " + tableUpdateItemsLen);
+	            console.log(tableUpdateData);
+
+	            for (var _i4 = 0; _i4 < tableUpdateItemsLen; ++_i4) {
+	              var tableItem = tableUpdateItems[_i4];
+	              this.tableUpdateHandler(tableItem.owner, tableItem.itemIdx, tableItem.itemData);
+	            }
+	            break;
+	          case _pdus2.default.RCPDU_REG_QUEUE_UPDATE_PDU:
+	          case _pdus2.default.RCPDU_REG_QUEUE_DELETE_PDU:
+	          case _pdus2.default.RCPDU_REG_QUEUE_INSERT_PDU:
+	            loger.warn('REG QUEUE ARE IGNORED');
+	            break;
+
+	        }
+	      }
+	    }
+	  }, {
+	    key: 'rosterInsertHandler',
+	    value: function rosterInsertHandler(recordId, recordData) {
+	      loger.warn(this._session_name + ' rosterInsertHandler 应有子类具体覆盖处理.');
+	    }
+	  }, {
+	    key: 'rosterUpdateHandler',
+	    value: function rosterUpdateHandler(nodeId, nodeData) {
+	      loger.warn(this._session_name + ' rosterUpdateHandler 应有子类具体覆盖处理.');
+	    }
+	  }, {
+	    key: 'rosterDelHandler',
+	    value: function rosterDelHandler(recordData) {
+	      loger.warn(this._session_name + ' rosterDelHandler 应有子类具体覆盖处理.');
+	    }
+	  }, {
+	    key: 'tableInsertHandler',
+	    value: function tableInsertHandler(tableId, record) {
+	      loger.warn(this._session_name + ' tableInsertHandler 应有子类具体覆盖处理.');
+	    }
+	  }, {
+	    key: 'tableUpdateHandler',
+	    value: function tableUpdateHandler(ownerId, recordId, recordData) {
+	      loger.warn(this._session_name + ' tableUpdateHandler 应有子类具体覆盖处理.');
+	    }
+	  }, {
+	    key: 'tableDeleteHandler',
+	    value: function tableDeleteHandler(tableId, record) {
+	      loger.warn(this._session_name + ' tableDelHandler 应有子类具体覆盖处理.');
+	    }
+	  }, {
+	    key: 'onJoinChannelHandlerSuccess',
+	    value: function onJoinChannelHandlerSuccess() {
+	      loger.warn(this._session_name + ' onJoinChannelHandlerSuccess 应有子类具体覆盖处理.');
+	    }
+	  }, {
+	    key: 'onJoinSessionHandlerSuccess',
+	    value: function onJoinSessionHandlerSuccess() {
+	      loger.warn(this._session_name + ' onJoinSessionHandlerSuccess 应有子类具体覆盖处理.');
+	    }
+	    // 加入Session处理
+
+	  }, {
+	    key: '_joinSessionHandler',
+	    value: function _joinSessionHandler(data) {
+	      loger.log(this._session_name, ' -> 加入Session');
+	      this.onJoinSessionHandlerSuccess();
+	    }
+
+	    // 加入Channel处理
+
+	  }, {
+	    key: '_joinChannelHandler',
+	    value: function _joinChannelHandler(data) {
+	      var joinedChannel = _pdus2.default['RCChannelJoinResponsePdu'].decode(data);
+	      if (joinedChannel.result === _pdus2.default.RET_SUCCESS) {
+	        loger.log(this._session_name, ' -> 加入Channel成功. ChannelId', joinedChannel.requestedChannelId);
+	        this._session_channels[joinedChannel.requestedChannelId] = _ApeConsts2.default.CJS_JOINNED;
+	        this.onJoinChannelHandlerSuccess();
+	      } else {
+	        loger.log(this._session_name, ' -> 加入Channel失败.', joinedChannel);
+	      }
+	    }
+
+	    // 依赖的会议创建完毕 - 发起Ape加入
+
+	  }, {
+	    key: '_mcuConferenceJoinSuccessHandler',
+	    value: function _mcuConferenceJoinSuccessHandler(_data) {
+	      loger.log('创建Ape->', 'SessionId', this._session_id, 'SessionName', this._session_name, 'SessionTag', this._session_tag);
+
+	      // 会议依赖底层会议信息
+	      //this._classInfo = classInfo;
+	      this._classInfo = _GlobalConfig2.default.getClassInfo();
+
+	      var joinSessionPdu = new _pdus2.default['RCSessionJoinRequestPdu']();
+	      joinSessionPdu.id = this._session_id;
+	      joinSessionPdu.name = this._session_name;
+	      joinSessionPdu.tag = this._session_tag;
+	      joinSessionPdu.sessionData = this._adapter_pdu.toArrayBuffer();
+	      this.sendUniform(joinSessionPdu, true);
+
+	      var joinChannelPdu = new _pdus2.default['RCChannelJoinRequestPdu']();
+	      joinChannelPdu.initiator = this.mcu.classInfo.nodeId;
+	      joinChannelPdu.channelId = this._session_id;
+	      this.send(joinChannelPdu);
+	    }
+
+	    // 注册Key对象
+
+	  }, {
+	    key: 'registerKey',
+	    value: function registerKey(id, name, tag, user_data) {
+	      var adapterItemPdu = new _pdus2.default['RCAdapterItemPdu']();
+	      adapterItemPdu.type = _pdus2.default.RCPDU_REG_REGISTER_KEY;
+
+	      // pack register key pdus
+	      var registerKeyPdu = new _pdus2.default['RCRegistryRegisterKeyPdu']();
+	      registerKeyPdu.id = id;
+	      registerKeyPdu.name = name;
+	      registerKeyPdu.tag = tag;
+	      if (user_data.length) {
+	        registerKeyPdu.userData = user_data;
+	      }
+
+	      adapterItemPdu.itemData = registerKeyPdu.toArrayBuffer();
+	      this._adapter_pdu.item.push(adapterItemPdu);
+	    }
+
+	    // 注册Object对象  等同于flash中的 RCRegistryOperator
+
+	  }, {
+	    key: 'registerObj',
+	    value: function registerObj(type, id, name, tag, owner, user_data) {
+	      var adapterItemPdu = new _pdus2.default['RCAdapterItemPdu']();
+	      adapterItemPdu.type = _pdus2.default.RCPDU_REG_REGISTER_OBJ;
+
+	      var registerObjPdu = new _pdus2.default['RCRegistryRegisterObjPdu']();
+	      registerObjPdu.type = type;
+	      registerObjPdu.objId = id;
+	      registerObjPdu.name = name;
+	      registerObjPdu.tag = tag;
+	      if (owner) {
+	        registerObjPdu.owner = owner;
+	      }
+	      if (user_data.length) {
+	        registerObjPdu.userData = user_data;
+	      }
+
+	      adapterItemPdu.itemData = registerObjPdu.toArrayBuffer();
+	      this._adapter_pdu.item.push(adapterItemPdu);
+	    }
+	  }, {
+	    key: 'send',
+	    value: function send(appPdu) {
+	      loger.log('Ape发送数据NORMAL PDU');
+	      console.log(appPdu);
+	      //loger.log('当前的状态============',GlobalConfig.getCurrentStatus().code);
+	      if (_GlobalConfig2.default.getCurrentStatus().code == 0 || _GlobalConfig2.default.getCurrentStatus().code == 1) {
+	        this._emit(_MessageTypes2.default.MCU_ERROR, _MessageTypes2.default.ERR_APE_SEND_FAILED_NO_JOIN);
+	        return;
+	      }
+	      var normalPdu = _pdus2.default.create_normal_pdu(appPdu.type, this._classInfo.nodeId, this._classInfo.classId, this._session_id, this._channel_id, true, true, _PduConsts2.default.DP_TOP, this._classInfo.topNodeID, _PduConsts2.default.SEG_ONCE);
+	      normalPdu.data = appPdu.toArrayBuffer();
+	      // Mcu发送
+	      this.mcu.send(normalPdu);
+	    }
+
+	    // 发送当前APE(session uniform包)
+
+	  }, {
+	    key: 'sendUniform',
+	    value: function sendUniform(appPdu, top) {
+	      loger.log('Ape发送数据UNIFORM PDU');
+	      console.log(appPdu);
+	      //loger.log('当前的状态============',GlobalConfig.getCurrentStatus().code);
+	      if (_GlobalConfig2.default.getCurrentStatus().code == 0 || _GlobalConfig2.default.getCurrentStatus().code == 1) {
+	        this._emit(_MessageTypes2.default.MCU_ERROR, _MessageTypes2.default.ERR_APE_SEND_FAILED_NO_JOIN);
+	        return;
+	      }
+
+	      var uniformPdu = _pdus2.default.create_uniform_pdu(appPdu.type, this._classInfo.nodeId, this._classInfo.classId, this._session_id, top ? _ApeConsts2.default.BROADCAST_CHANNEL_ID : this._channel_id, true, _PduConsts2.default.DP_TOP, top ? this._classInfo.topNodeID : appPdu.peer || 0, _PduConsts2.default.SEG_ONCE);
+	      uniformPdu.data = appPdu.toArrayBuffer();
+	      // Mcu发送
+	      this.mcu.send(uniformPdu);
+	    }
+	  }, {
+	    key: 'sendChatUniform',
+	    value: function sendChatUniform(appPdu, top) {
+	      loger.log('Ape发送数据UNIFORM PDU');
+	      console.log(appPdu);
+	      //loger.log('当前的状态============',GlobalConfig.getCurrentStatus().code);
+	      if (_GlobalConfig2.default.getCurrentStatus().code == 0 || _GlobalConfig2.default.getCurrentStatus().code == 1) {
+	        this._emit(_MessageTypes2.default.MCU_ERROR, _MessageTypes2.default.ERR_APE_SEND_FAILED_NO_JOIN);
+	        return;
+	      }
+
+	      var uniformPdu = _pdus2.default.create_uniform_pdu(appPdu.type, this._classInfo.nodeId, this._classInfo.classId, this._session_id, top ? _ApeConsts2.default.BROADCAST_CHANNEL_ID : this._channel_id, true, _PduConsts2.default.DP_TOP, 0, //flash中这个值设置为0
+	      _PduConsts2.default.SEG_ONCE);
+	      uniformPdu.data = appPdu.toArrayBuffer();
+	      // Mcu发送
+	      this.mcu.send(uniformPdu);
+	    }
+	  }]);
+
+	  return Ape;
+	}(_Emiter3.default);
+
+	var _default = Ape;
+	exports.default = _default;
+	;
+
+	var _temp = function () {
+	  if (typeof __REACT_HOT_LOADER__ === 'undefined') {
+	    return;
+	  }
+
+	  __REACT_HOT_LOADER__.register(loger, 'loger', 'D:/work/McuClient/src/apes/Ape.js');
+
+	  __REACT_HOT_LOADER__.register(Ape, 'Ape', 'D:/work/McuClient/src/apes/Ape.js');
+
+	  __REACT_HOT_LOADER__.register(_default, 'default', 'D:/work/McuClient/src/apes/Ape.js');
+	}();
+
+	;
+
+/***/ },
+/* 38 */
+/***/ function(module, exports) {
+
+	"use strict";
+
+	Object.defineProperty(exports, "__esModule", {
+	  value: true
+	});
+
+	var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
+
+	function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
+
+	var ArrayBufferUtil = function () {
+	  function ArrayBufferUtil() {
+	    _classCallCheck(this, ArrayBufferUtil);
+	  }
+
+	  _createClass(ArrayBufferUtil, null, [{
+	    key: "ab2str",
+	    value: function ab2str(buf) {
+	      return String.fromCharCode.apply(null, new Uint16Array(buf));
+	    }
+	  }, {
+	    key: "str2ab",
+	    value: function str2ab(str) {
+	      var buf = new ArrayBuffer(str.length * 2); // one char use 2 byte
+	      var bufView = new Uint16Array(buf);
+	      for (var i = 0, strLen = str.length; i < strLen; i++) {
+	        bufView[i] = str.charCodeAt(i);
+	      }
+	      return buf;
+	    }
+	  }, {
+	    key: "strToUint8Array",
+	    value: function strToUint8Array(str) {
+	      var out = void 0,
+	          i = void 0,
+	          len = void 0,
+	          c = void 0;
+	      out = "";
+	      len = str.length;
+	      var uintArray = [];
+	      for (i = 0; i < len; i++) {
+	        c = str.charCodeAt(i);
+	        if (c >= 0x0001 && c <= 0x007F) {
+	          uintArray.push(c);
+	        } else if (c > 0x07FF) {
+	          uintArray.push(0xE0 | c >> 12 & 0x0F);
+	          uintArray.push(0x80 | c >> 6 & 0x3F);
+	          uintArray.push(0x80 | c >> 0 & 0x3F);
+	          //out += String.fromCharCode(0xE0 | ((c >> 12) & 0x0F));
+	          //out += String.fromCharCode(0x80 | ((c >> 6) & 0x3F));
+	          //out += String.fromCharCode(0x80 | ((c >> 0) & 0x3F));
+	        } else {
+	          uintArray.push(0xC0 | c >> 6 & 0x1F);
+	          uintArray.push(0x80 | c >> 0 & 0x3F);
+	          //out += String.fromCharCode(0xC0 | ((c >> 6) & 0x1F));
+	          //out += String.fromCharCode(0x80 | ((c >> 0) & 0x3F));
+	        }
+	      }
+	      return new Uint8Array(uintArray);
+	    }
+	  }, {
+	    key: "uint8ArrayToStr",
+	    value: function uint8ArrayToStr(uint8, index) {
+	      var out = void 0,
+	          i = void 0,
+	          len = void 0,
+	          c = void 0;
+	      var char2 = void 0,
+	          char3 = void 0;
+	      out = "";
+	      len = uint8.buffer.byteLength;
+	      i = uint8.offset + index; //pzm update 前两个字符乱码
+	      while (i < uint8.limit) {
+	        c = uint8.view[i++];
+	        switch (c >> 4) {
+	          case 0:
+	          case 1:
+	          case 2:
+	          case 3:
+	          case 4:
+	          case 5:
+	          case 6:
+	          case 7:
+	            // 0xxxxxxx
+	            out += String.fromCharCode(uint8.view[i - 1]);
+	            break;
+	          case 12:
+	          case 13:
+	            // 110x xxxx 10xx xxxx
+	            char2 = uint8.view[i++];
+	            out += String.fromCharCode((c & 0x1F) << 6 | char2 & 0x3F);
+	            break;
+	          case 14:
+	            // 1110 xxxx 10xx xxxx 10xx xxxx
+	            char2 = uint8.view[i++];
+	            char3 = uint8.view[i++];
+	            out += String.fromCharCode((c & 0x0F) << 12 | (char2 & 0x3F) << 6 | (char3 & 0x3F) << 0);
+	            break;
+	        }
+	      }
+	      return out;
+	    }
+	  }]);
+
+	  return ArrayBufferUtil;
+	}();
+
+	var _default = ArrayBufferUtil;
+	exports.default = _default;
+	;
+
+	var _temp = function () {
+	  if (typeof __REACT_HOT_LOADER__ === 'undefined') {
+	    return;
+	  }
+
+	  __REACT_HOT_LOADER__.register(ArrayBufferUtil, "ArrayBufferUtil", "D:/work/McuClient/src/libs/ArrayBufferUtil.js");
+
+	  __REACT_HOT_LOADER__.register(_default, "default", "D:/work/McuClient/src/libs/ArrayBufferUtil.js");
+	}();
+
+	;
+
+/***/ },
+/* 39 */
+/***/ function(module, exports) {
+
+	/** @license zlib.js 2012 - imaya [ https://github.com/imaya/zlib.js ] The MIT License */(function() {'use strict';function l(d){throw d;}var v=void 0,x=!0,aa=this;function D(d,a){var c=d.split("."),e=aa;!(c[0]in e)&&e.execScript&&e.execScript("var "+c[0]);for(var b;c.length&&(b=c.shift());)!c.length&&a!==v?e[b]=a:e=e[b]?e[b]:e[b]={}};var F="undefined"!==typeof Uint8Array&&"undefined"!==typeof Uint16Array&&"undefined"!==typeof Uint32Array&&"undefined"!==typeof DataView;function H(d,a){this.index="number"===typeof a?a:0;this.i=0;this.buffer=d instanceof(F?Uint8Array:Array)?d:new (F?Uint8Array:Array)(32768);2*this.buffer.length<=this.index&&l(Error("invalid index"));this.buffer.length<=this.index&&this.f()}H.prototype.f=function(){var d=this.buffer,a,c=d.length,e=new (F?Uint8Array:Array)(c<<1);if(F)e.set(d);else for(a=0;a<c;++a)e[a]=d[a];return this.buffer=e};
+	H.prototype.d=function(d,a,c){var e=this.buffer,b=this.index,f=this.i,g=e[b],h;c&&1<a&&(d=8<a?(N[d&255]<<24|N[d>>>8&255]<<16|N[d>>>16&255]<<8|N[d>>>24&255])>>32-a:N[d]>>8-a);if(8>a+f)g=g<<a|d,f+=a;else for(h=0;h<a;++h)g=g<<1|d>>a-h-1&1,8===++f&&(f=0,e[b++]=N[g],g=0,b===e.length&&(e=this.f()));e[b]=g;this.buffer=e;this.i=f;this.index=b};H.prototype.finish=function(){var d=this.buffer,a=this.index,c;0<this.i&&(d[a]<<=8-this.i,d[a]=N[d[a]],a++);F?c=d.subarray(0,a):(d.length=a,c=d);return c};
+	var fa=new (F?Uint8Array:Array)(256),O;for(O=0;256>O;++O){for(var P=O,Q=P,ga=7,P=P>>>1;P;P>>>=1)Q<<=1,Q|=P&1,--ga;fa[O]=(Q<<ga&255)>>>0}var N=fa;function ha(d){this.buffer=new (F?Uint16Array:Array)(2*d);this.length=0}ha.prototype.getParent=function(d){return 2*((d-2)/4|0)};ha.prototype.push=function(d,a){var c,e,b=this.buffer,f;c=this.length;b[this.length++]=a;for(b[this.length++]=d;0<c;)if(e=this.getParent(c),b[c]>b[e])f=b[c],b[c]=b[e],b[e]=f,f=b[c+1],b[c+1]=b[e+1],b[e+1]=f,c=e;else break;return this.length};
+	ha.prototype.pop=function(){var d,a,c=this.buffer,e,b,f;a=c[0];d=c[1];this.length-=2;c[0]=c[this.length];c[1]=c[this.length+1];for(f=0;;){b=2*f+2;if(b>=this.length)break;b+2<this.length&&c[b+2]>c[b]&&(b+=2);if(c[b]>c[f])e=c[f],c[f]=c[b],c[b]=e,e=c[f+1],c[f+1]=c[b+1],c[b+1]=e;else break;f=b}return{index:d,value:a,length:this.length}};function R(d){var a=d.length,c=0,e=Number.POSITIVE_INFINITY,b,f,g,h,k,n,q,r,p,m;for(r=0;r<a;++r)d[r]>c&&(c=d[r]),d[r]<e&&(e=d[r]);b=1<<c;f=new (F?Uint32Array:Array)(b);g=1;h=0;for(k=2;g<=c;){for(r=0;r<a;++r)if(d[r]===g){n=0;q=h;for(p=0;p<g;++p)n=n<<1|q&1,q>>=1;m=g<<16|r;for(p=n;p<b;p+=k)f[p]=m;++h}++g;h<<=1;k<<=1}return[f,c,e]};function ia(d,a){this.h=ma;this.w=0;this.input=F&&d instanceof Array?new Uint8Array(d):d;this.b=0;a&&(a.lazy&&(this.w=a.lazy),"number"===typeof a.compressionType&&(this.h=a.compressionType),a.outputBuffer&&(this.a=F&&a.outputBuffer instanceof Array?new Uint8Array(a.outputBuffer):a.outputBuffer),"number"===typeof a.outputIndex&&(this.b=a.outputIndex));this.a||(this.a=new (F?Uint8Array:Array)(32768))}var ma=2,na={NONE:0,r:1,k:ma,O:3},oa=[],S;
+	for(S=0;288>S;S++)switch(x){case 143>=S:oa.push([S+48,8]);break;case 255>=S:oa.push([S-144+400,9]);break;case 279>=S:oa.push([S-256+0,7]);break;case 287>=S:oa.push([S-280+192,8]);break;default:l("invalid literal: "+S)}
+	ia.prototype.j=function(){var d,a,c,e,b=this.input;switch(this.h){case 0:c=0;for(e=b.length;c<e;){a=F?b.subarray(c,c+65535):b.slice(c,c+65535);c+=a.length;var f=a,g=c===e,h=v,k=v,n=v,q=v,r=v,p=this.a,m=this.b;if(F){for(p=new Uint8Array(this.a.buffer);p.length<=m+f.length+5;)p=new Uint8Array(p.length<<1);p.set(this.a)}h=g?1:0;p[m++]=h|0;k=f.length;n=~k+65536&65535;p[m++]=k&255;p[m++]=k>>>8&255;p[m++]=n&255;p[m++]=n>>>8&255;if(F)p.set(f,m),m+=f.length,p=p.subarray(0,m);else{q=0;for(r=f.length;q<r;++q)p[m++]=
+	f[q];p.length=m}this.b=m;this.a=p}break;case 1:var s=new H(F?new Uint8Array(this.a.buffer):this.a,this.b);s.d(1,1,x);s.d(1,2,x);var w=pa(this,b),y,ja,A;y=0;for(ja=w.length;y<ja;y++)if(A=w[y],H.prototype.d.apply(s,oa[A]),256<A)s.d(w[++y],w[++y],x),s.d(w[++y],5),s.d(w[++y],w[++y],x);else if(256===A)break;this.a=s.finish();this.b=this.a.length;break;case ma:var C=new H(F?new Uint8Array(this.a.buffer):this.a,this.b),Ea,M,U,V,W,gb=[16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15],ba,Fa,ca,Ga,ka,ra=Array(19),
+	Ha,X,la,z,Ia;Ea=ma;C.d(1,1,x);C.d(Ea,2,x);M=pa(this,b);ba=qa(this.M,15);Fa=sa(ba);ca=qa(this.L,7);Ga=sa(ca);for(U=286;257<U&&0===ba[U-1];U--);for(V=30;1<V&&0===ca[V-1];V--);var Ja=U,Ka=V,I=new (F?Uint32Array:Array)(Ja+Ka),t,J,u,da,G=new (F?Uint32Array:Array)(316),E,B,K=new (F?Uint8Array:Array)(19);for(t=J=0;t<Ja;t++)I[J++]=ba[t];for(t=0;t<Ka;t++)I[J++]=ca[t];if(!F){t=0;for(da=K.length;t<da;++t)K[t]=0}t=E=0;for(da=I.length;t<da;t+=J){for(J=1;t+J<da&&I[t+J]===I[t];++J);u=J;if(0===I[t])if(3>u)for(;0<
+	u--;)G[E++]=0,K[0]++;else for(;0<u;)B=138>u?u:138,B>u-3&&B<u&&(B=u-3),10>=B?(G[E++]=17,G[E++]=B-3,K[17]++):(G[E++]=18,G[E++]=B-11,K[18]++),u-=B;else if(G[E++]=I[t],K[I[t]]++,u--,3>u)for(;0<u--;)G[E++]=I[t],K[I[t]]++;else for(;0<u;)B=6>u?u:6,B>u-3&&B<u&&(B=u-3),G[E++]=16,G[E++]=B-3,K[16]++,u-=B}d=F?G.subarray(0,E):G.slice(0,E);ka=qa(K,7);for(z=0;19>z;z++)ra[z]=ka[gb[z]];for(W=19;4<W&&0===ra[W-1];W--);Ha=sa(ka);C.d(U-257,5,x);C.d(V-1,5,x);C.d(W-4,4,x);for(z=0;z<W;z++)C.d(ra[z],3,x);z=0;for(Ia=d.length;z<
+	Ia;z++)if(X=d[z],C.d(Ha[X],ka[X],x),16<=X){z++;switch(X){case 16:la=2;break;case 17:la=3;break;case 18:la=7;break;default:l("invalid code: "+X)}C.d(d[z],la,x)}var La=[Fa,ba],Ma=[Ga,ca],L,Na,ea,ua,Oa,Pa,Qa,Ra;Oa=La[0];Pa=La[1];Qa=Ma[0];Ra=Ma[1];L=0;for(Na=M.length;L<Na;++L)if(ea=M[L],C.d(Oa[ea],Pa[ea],x),256<ea)C.d(M[++L],M[++L],x),ua=M[++L],C.d(Qa[ua],Ra[ua],x),C.d(M[++L],M[++L],x);else if(256===ea)break;this.a=C.finish();this.b=this.a.length;break;default:l("invalid compression type")}return this.a};
+	function ta(d,a){this.length=d;this.H=a}
+	var va=function(){function d(b){switch(x){case 3===b:return[257,b-3,0];case 4===b:return[258,b-4,0];case 5===b:return[259,b-5,0];case 6===b:return[260,b-6,0];case 7===b:return[261,b-7,0];case 8===b:return[262,b-8,0];case 9===b:return[263,b-9,0];case 10===b:return[264,b-10,0];case 12>=b:return[265,b-11,1];case 14>=b:return[266,b-13,1];case 16>=b:return[267,b-15,1];case 18>=b:return[268,b-17,1];case 22>=b:return[269,b-19,2];case 26>=b:return[270,b-23,2];case 30>=b:return[271,b-27,2];case 34>=b:return[272,
+	b-31,2];case 42>=b:return[273,b-35,3];case 50>=b:return[274,b-43,3];case 58>=b:return[275,b-51,3];case 66>=b:return[276,b-59,3];case 82>=b:return[277,b-67,4];case 98>=b:return[278,b-83,4];case 114>=b:return[279,b-99,4];case 130>=b:return[280,b-115,4];case 162>=b:return[281,b-131,5];case 194>=b:return[282,b-163,5];case 226>=b:return[283,b-195,5];case 257>=b:return[284,b-227,5];case 258===b:return[285,b-258,0];default:l("invalid length: "+b)}}var a=[],c,e;for(c=3;258>=c;c++)e=d(c),a[c]=e[2]<<24|e[1]<<
+	16|e[0];return a}(),wa=F?new Uint32Array(va):va;
+	function pa(d,a){function c(b,c){var a=b.H,d=[],e=0,f;f=wa[b.length];d[e++]=f&65535;d[e++]=f>>16&255;d[e++]=f>>24;var g;switch(x){case 1===a:g=[0,a-1,0];break;case 2===a:g=[1,a-2,0];break;case 3===a:g=[2,a-3,0];break;case 4===a:g=[3,a-4,0];break;case 6>=a:g=[4,a-5,1];break;case 8>=a:g=[5,a-7,1];break;case 12>=a:g=[6,a-9,2];break;case 16>=a:g=[7,a-13,2];break;case 24>=a:g=[8,a-17,3];break;case 32>=a:g=[9,a-25,3];break;case 48>=a:g=[10,a-33,4];break;case 64>=a:g=[11,a-49,4];break;case 96>=a:g=[12,a-
+	65,5];break;case 128>=a:g=[13,a-97,5];break;case 192>=a:g=[14,a-129,6];break;case 256>=a:g=[15,a-193,6];break;case 384>=a:g=[16,a-257,7];break;case 512>=a:g=[17,a-385,7];break;case 768>=a:g=[18,a-513,8];break;case 1024>=a:g=[19,a-769,8];break;case 1536>=a:g=[20,a-1025,9];break;case 2048>=a:g=[21,a-1537,9];break;case 3072>=a:g=[22,a-2049,10];break;case 4096>=a:g=[23,a-3073,10];break;case 6144>=a:g=[24,a-4097,11];break;case 8192>=a:g=[25,a-6145,11];break;case 12288>=a:g=[26,a-8193,12];break;case 16384>=
+	a:g=[27,a-12289,12];break;case 24576>=a:g=[28,a-16385,13];break;case 32768>=a:g=[29,a-24577,13];break;default:l("invalid distance")}f=g;d[e++]=f[0];d[e++]=f[1];d[e++]=f[2];var h,k;h=0;for(k=d.length;h<k;++h)p[m++]=d[h];w[d[0]]++;y[d[3]]++;s=b.length+c-1;r=null}var e,b,f,g,h,k={},n,q,r,p=F?new Uint16Array(2*a.length):[],m=0,s=0,w=new (F?Uint32Array:Array)(286),y=new (F?Uint32Array:Array)(30),ja=d.w,A;if(!F){for(f=0;285>=f;)w[f++]=0;for(f=0;29>=f;)y[f++]=0}w[256]=1;e=0;for(b=a.length;e<b;++e){f=h=0;
+	for(g=3;f<g&&e+f!==b;++f)h=h<<8|a[e+f];k[h]===v&&(k[h]=[]);n=k[h];if(!(0<s--)){for(;0<n.length&&32768<e-n[0];)n.shift();if(e+3>=b){r&&c(r,-1);f=0;for(g=b-e;f<g;++f)A=a[e+f],p[m++]=A,++w[A];break}0<n.length?(q=xa(a,e,n),r?r.length<q.length?(A=a[e-1],p[m++]=A,++w[A],c(q,0)):c(r,-1):q.length<ja?r=q:c(q,0)):r?c(r,-1):(A=a[e],p[m++]=A,++w[A])}n.push(e)}p[m++]=256;w[256]++;d.M=w;d.L=y;return F?p.subarray(0,m):p}
+	function xa(d,a,c){var e,b,f=0,g,h,k,n,q=d.length;h=0;n=c.length;a:for(;h<n;h++){e=c[n-h-1];g=3;if(3<f){for(k=f;3<k;k--)if(d[e+k-1]!==d[a+k-1])continue a;g=f}for(;258>g&&a+g<q&&d[e+g]===d[a+g];)++g;g>f&&(b=e,f=g);if(258===g)break}return new ta(f,a-b)}
+	function qa(d,a){var c=d.length,e=new ha(572),b=new (F?Uint8Array:Array)(c),f,g,h,k,n;if(!F)for(k=0;k<c;k++)b[k]=0;for(k=0;k<c;++k)0<d[k]&&e.push(k,d[k]);f=Array(e.length/2);g=new (F?Uint32Array:Array)(e.length/2);if(1===f.length)return b[e.pop().index]=1,b;k=0;for(n=e.length/2;k<n;++k)f[k]=e.pop(),g[k]=f[k].value;h=ya(g,g.length,a);k=0;for(n=f.length;k<n;++k)b[f[k].index]=h[k];return b}
+	function ya(d,a,c){function e(b){var c=k[b][n[b]];c===a?(e(b+1),e(b+1)):--g[c];++n[b]}var b=new (F?Uint16Array:Array)(c),f=new (F?Uint8Array:Array)(c),g=new (F?Uint8Array:Array)(a),h=Array(c),k=Array(c),n=Array(c),q=(1<<c)-a,r=1<<c-1,p,m,s,w,y;b[c-1]=a;for(m=0;m<c;++m)q<r?f[m]=0:(f[m]=1,q-=r),q<<=1,b[c-2-m]=(b[c-1-m]/2|0)+a;b[0]=f[0];h[0]=Array(b[0]);k[0]=Array(b[0]);for(m=1;m<c;++m)b[m]>2*b[m-1]+f[m]&&(b[m]=2*b[m-1]+f[m]),h[m]=Array(b[m]),k[m]=Array(b[m]);for(p=0;p<a;++p)g[p]=c;for(s=0;s<b[c-1];++s)h[c-
+	1][s]=d[s],k[c-1][s]=s;for(p=0;p<c;++p)n[p]=0;1===f[c-1]&&(--g[0],++n[c-1]);for(m=c-2;0<=m;--m){w=p=0;y=n[m+1];for(s=0;s<b[m];s++)w=h[m+1][y]+h[m+1][y+1],w>d[p]?(h[m][s]=w,k[m][s]=a,y+=2):(h[m][s]=d[p],k[m][s]=p,++p);n[m]=0;1===f[m]&&e(m)}return g}
+	function sa(d){var a=new (F?Uint16Array:Array)(d.length),c=[],e=[],b=0,f,g,h,k;f=0;for(g=d.length;f<g;f++)c[d[f]]=(c[d[f]]|0)+1;f=1;for(g=16;f<=g;f++)e[f]=b,b+=c[f]|0,b<<=1;f=0;for(g=d.length;f<g;f++){b=e[d[f]];e[d[f]]+=1;h=a[f]=0;for(k=d[f];h<k;h++)a[f]=a[f]<<1|b&1,b>>>=1}return a};function T(d,a){this.l=[];this.m=32768;this.e=this.g=this.c=this.q=0;this.input=F?new Uint8Array(d):d;this.s=!1;this.n=za;this.C=!1;if(a||!(a={}))a.index&&(this.c=a.index),a.bufferSize&&(this.m=a.bufferSize),a.bufferType&&(this.n=a.bufferType),a.resize&&(this.C=a.resize);switch(this.n){case Aa:this.b=32768;this.a=new (F?Uint8Array:Array)(32768+this.m+258);break;case za:this.b=0;this.a=new (F?Uint8Array:Array)(this.m);this.f=this.K;this.t=this.I;this.o=this.J;break;default:l(Error("invalid inflate mode"))}}
+	var Aa=0,za=1,Ba={F:Aa,D:za};
+	T.prototype.p=function(){for(;!this.s;){var d=Y(this,3);d&1&&(this.s=x);d>>>=1;switch(d){case 0:var a=this.input,c=this.c,e=this.a,b=this.b,f=a.length,g=v,h=v,k=e.length,n=v;this.e=this.g=0;c+1>=f&&l(Error("invalid uncompressed block header: LEN"));g=a[c++]|a[c++]<<8;c+1>=f&&l(Error("invalid uncompressed block header: NLEN"));h=a[c++]|a[c++]<<8;g===~h&&l(Error("invalid uncompressed block header: length verify"));c+g>a.length&&l(Error("input buffer is broken"));switch(this.n){case Aa:for(;b+g>e.length;){n=
+	k-b;g-=n;if(F)e.set(a.subarray(c,c+n),b),b+=n,c+=n;else for(;n--;)e[b++]=a[c++];this.b=b;e=this.f();b=this.b}break;case za:for(;b+g>e.length;)e=this.f({v:2});break;default:l(Error("invalid inflate mode"))}if(F)e.set(a.subarray(c,c+g),b),b+=g,c+=g;else for(;g--;)e[b++]=a[c++];this.c=c;this.b=b;this.a=e;break;case 1:this.o(Ca,Da);break;case 2:Sa(this);break;default:l(Error("unknown BTYPE: "+d))}}return this.t()};
+	var Ta=[16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15],Ua=F?new Uint16Array(Ta):Ta,Va=[3,4,5,6,7,8,9,10,11,13,15,17,19,23,27,31,35,43,51,59,67,83,99,115,131,163,195,227,258,258,258],Wa=F?new Uint16Array(Va):Va,Xa=[0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0,0,0],Ya=F?new Uint8Array(Xa):Xa,Za=[1,2,3,4,5,7,9,13,17,25,33,49,65,97,129,193,257,385,513,769,1025,1537,2049,3073,4097,6145,8193,12289,16385,24577],$a=F?new Uint16Array(Za):Za,ab=[0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,
+	10,11,11,12,12,13,13],bb=F?new Uint8Array(ab):ab,cb=new (F?Uint8Array:Array)(288),Z,db;Z=0;for(db=cb.length;Z<db;++Z)cb[Z]=143>=Z?8:255>=Z?9:279>=Z?7:8;var Ca=R(cb),eb=new (F?Uint8Array:Array)(30),fb,hb;fb=0;for(hb=eb.length;fb<hb;++fb)eb[fb]=5;var Da=R(eb);function Y(d,a){for(var c=d.g,e=d.e,b=d.input,f=d.c,g=b.length,h;e<a;)f>=g&&l(Error("input buffer is broken")),c|=b[f++]<<e,e+=8;h=c&(1<<a)-1;d.g=c>>>a;d.e=e-a;d.c=f;return h}
+	function ib(d,a){for(var c=d.g,e=d.e,b=d.input,f=d.c,g=b.length,h=a[0],k=a[1],n,q;e<k&&!(f>=g);)c|=b[f++]<<e,e+=8;n=h[c&(1<<k)-1];q=n>>>16;d.g=c>>q;d.e=e-q;d.c=f;return n&65535}
+	function Sa(d){function a(a,b,c){var d,e=this.z,f,g;for(g=0;g<a;)switch(d=ib(this,b),d){case 16:for(f=3+Y(this,2);f--;)c[g++]=e;break;case 17:for(f=3+Y(this,3);f--;)c[g++]=0;e=0;break;case 18:for(f=11+Y(this,7);f--;)c[g++]=0;e=0;break;default:e=c[g++]=d}this.z=e;return c}var c=Y(d,5)+257,e=Y(d,5)+1,b=Y(d,4)+4,f=new (F?Uint8Array:Array)(Ua.length),g,h,k,n;for(n=0;n<b;++n)f[Ua[n]]=Y(d,3);if(!F){n=b;for(b=f.length;n<b;++n)f[Ua[n]]=0}g=R(f);h=new (F?Uint8Array:Array)(c);k=new (F?Uint8Array:Array)(e);
+	d.z=0;d.o(R(a.call(d,c,g,h)),R(a.call(d,e,g,k)))}T.prototype.o=function(d,a){var c=this.a,e=this.b;this.u=d;for(var b=c.length-258,f,g,h,k;256!==(f=ib(this,d));)if(256>f)e>=b&&(this.b=e,c=this.f(),e=this.b),c[e++]=f;else{g=f-257;k=Wa[g];0<Ya[g]&&(k+=Y(this,Ya[g]));f=ib(this,a);h=$a[f];0<bb[f]&&(h+=Y(this,bb[f]));e>=b&&(this.b=e,c=this.f(),e=this.b);for(;k--;)c[e]=c[e++-h]}for(;8<=this.e;)this.e-=8,this.c--;this.b=e};
+	T.prototype.J=function(d,a){var c=this.a,e=this.b;this.u=d;for(var b=c.length,f,g,h,k;256!==(f=ib(this,d));)if(256>f)e>=b&&(c=this.f(),b=c.length),c[e++]=f;else{g=f-257;k=Wa[g];0<Ya[g]&&(k+=Y(this,Ya[g]));f=ib(this,a);h=$a[f];0<bb[f]&&(h+=Y(this,bb[f]));e+k>b&&(c=this.f(),b=c.length);for(;k--;)c[e]=c[e++-h]}for(;8<=this.e;)this.e-=8,this.c--;this.b=e};
+	T.prototype.f=function(){var d=new (F?Uint8Array:Array)(this.b-32768),a=this.b-32768,c,e,b=this.a;if(F)d.set(b.subarray(32768,d.length));else{c=0;for(e=d.length;c<e;++c)d[c]=b[c+32768]}this.l.push(d);this.q+=d.length;if(F)b.set(b.subarray(a,a+32768));else for(c=0;32768>c;++c)b[c]=b[a+c];this.b=32768;return b};
+	T.prototype.K=function(d){var a,c=this.input.length/this.c+1|0,e,b,f,g=this.input,h=this.a;d&&("number"===typeof d.v&&(c=d.v),"number"===typeof d.G&&(c+=d.G));2>c?(e=(g.length-this.c)/this.u[2],f=258*(e/2)|0,b=f<h.length?h.length+f:h.length<<1):b=h.length*c;F?(a=new Uint8Array(b),a.set(h)):a=h;return this.a=a};
+	T.prototype.t=function(){var d=0,a=this.a,c=this.l,e,b=new (F?Uint8Array:Array)(this.q+(this.b-32768)),f,g,h,k;if(0===c.length)return F?this.a.subarray(32768,this.b):this.a.slice(32768,this.b);f=0;for(g=c.length;f<g;++f){e=c[f];h=0;for(k=e.length;h<k;++h)b[d++]=e[h]}f=32768;for(g=this.b;f<g;++f)b[d++]=a[f];this.l=[];return this.buffer=b};
+	T.prototype.I=function(){var d,a=this.b;F?this.C?(d=new Uint8Array(a),d.set(this.a.subarray(0,a))):d=this.a.subarray(0,a):(this.a.length>a&&(this.a.length=a),d=this.a);return this.buffer=d};function jb(d){if("string"===typeof d){var a=d.split(""),c,e;c=0;for(e=a.length;c<e;c++)a[c]=(a[c].charCodeAt(0)&255)>>>0;d=a}for(var b=1,f=0,g=d.length,h,k=0;0<g;){h=1024<g?1024:g;g-=h;do b+=d[k++],f+=b;while(--h);b%=65521;f%=65521}return(f<<16|b)>>>0};function kb(d,a){var c,e;this.input=d;this.c=0;if(a||!(a={}))a.index&&(this.c=a.index),a.verify&&(this.N=a.verify);c=d[this.c++];e=d[this.c++];switch(c&15){case lb:this.method=lb;break;default:l(Error("unsupported compression method"))}0!==((c<<8)+e)%31&&l(Error("invalid fcheck flag:"+((c<<8)+e)%31));e&32&&l(Error("fdict flag is not supported"));this.B=new T(d,{index:this.c,bufferSize:a.bufferSize,bufferType:a.bufferType,resize:a.resize})}
+	kb.prototype.p=function(){var d=this.input,a,c;a=this.B.p();this.c=this.B.c;this.N&&(c=(d[this.c++]<<24|d[this.c++]<<16|d[this.c++]<<8|d[this.c++])>>>0,c!==jb(a)&&l(Error("invalid adler-32 checksum")));return a};var lb=8;function mb(d,a){this.input=d;this.a=new (F?Uint8Array:Array)(32768);this.h=$.k;var c={},e;if((a||!(a={}))&&"number"===typeof a.compressionType)this.h=a.compressionType;for(e in a)c[e]=a[e];c.outputBuffer=this.a;this.A=new ia(this.input,c)}var $=na;
+	mb.prototype.j=function(){var d,a,c,e,b,f,g,h=0;g=this.a;d=lb;switch(d){case lb:a=Math.LOG2E*Math.log(32768)-8;break;default:l(Error("invalid compression method"))}c=a<<4|d;g[h++]=c;switch(d){case lb:switch(this.h){case $.NONE:b=0;break;case $.r:b=1;break;case $.k:b=2;break;default:l(Error("unsupported compression type"))}break;default:l(Error("invalid compression method"))}e=b<<6|0;g[h++]=e|31-(256*c+e)%31;f=jb(this.input);this.A.b=h;g=this.A.j();h=g.length;F&&(g=new Uint8Array(g.buffer),g.length<=
+	h+4&&(this.a=new Uint8Array(g.length+4),this.a.set(g),g=this.a),g=g.subarray(0,h+4));g[h++]=f>>24&255;g[h++]=f>>16&255;g[h++]=f>>8&255;g[h++]=f&255;return g};function nb(d,a){var c,e,b,f;if(Object.keys)c=Object.keys(a);else for(e in c=[],b=0,a)c[b++]=e;b=0;for(f=c.length;b<f;++b)e=c[b],D(d+"."+e,a[e])};D("Zlib.Inflate",kb);D("Zlib.Inflate.prototype.decompress",kb.prototype.p);nb("Zlib.Inflate.BufferType",{ADAPTIVE:Ba.D,BLOCK:Ba.F});D("Zlib.Deflate",mb);D("Zlib.Deflate.compress",function(d,a){return(new mb(d,a)).j()});D("Zlib.Deflate.prototype.compress",mb.prototype.j);nb("Zlib.Deflate.CompressionType",{NONE:$.NONE,FIXED:$.r,DYNAMIC:$.k});}).call(this); //@ sourceMappingURL=zlib.min.js.map
+
+
+/***/ },
+/* 40 */
+/***/ function(module, exports) {
+
+	"use strict";
+
+	Object.defineProperty(exports, "__esModule", {
+	    value: true
+	});
+
+	var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
+
+	function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
+
+	// //////////////////////////////////////////////////////////////////////////////
+	//  计时器
+	// //////////////////////////////////////////////////////////////////////////////
+
+	var TimerCounter = function () {
+	    function TimerCounter() {
+	        _classCallCheck(this, TimerCounter);
+
+	        this.timer = 0;
+	        this.delay = 1000;
+	        this.counter = 0;
+	        this.callBackDelay = 1; //回调间隔,单位(秒)
+	        this.callBackFun = null; //回调函数
+	        this.isStart = false;
+	    }
+
+	    _createClass(TimerCounter, [{
+	        key: "addTimerCallBack",
+	        value: function addTimerCallBack(_callBackFun, _callBackDelay) {
+	            this.callBackFun = _callBackFun;
+	            this.callBackDelay = _callBackDelay;
+	        }
+	        //开计时
+
+	    }, {
+	        key: "startTimer",
+	        value: function startTimer() {
+	            var _position = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 0;
+
+	            if (this.isStart) return;
+	            this.isStart = true;
+	            if (_position && parseInt(_position) > 0) {
+	                this.counter = _position;
+	            } else {
+	                this.counter = 0;
+	            }
+	            console.log("startTimer", this.counter);
+	            this.timerClear();
+	            this.timerStart();
+	        }
+	        //停止
+
+	    }, {
+	        key: "stopTimer",
+	        value: function stopTimer() {
+	            console.log("stopTimer", this.counter);
+	            this.isStart = false;
+	            this.timerClear();
+	        }
+	        //计数
+
+	    }, {
+	        key: "updateCounter",
+	        value: function updateCounter() {
+	            this.counter++;
+	            //this.counter++;
+	            //console.log("TimerCounter",counter);
+	            if (this.callBackFun != null && this.counter % this.callBackDelay == 0) {
+	                this.callBackFun();
+	            }
+	        }
+	    }, {
+	        key: "timerStart",
+	        value: function timerStart() {
+	            this.timer = setInterval(this.updateCounter.bind(this), this.delay);
+	        }
+	    }, {
+	        key: "timerClear",
+	        value: function timerClear() {
+	            clearInterval(this.timer);
+	        }
+	    }]);
+
+	    return TimerCounter;
+	}();
+
+	var _default = TimerCounter;
+	exports.default = _default;
+	;
+
+	var _temp = function () {
+	    if (typeof __REACT_HOT_LOADER__ === 'undefined') {
+	        return;
+	    }
+
+	    __REACT_HOT_LOADER__.register(TimerCounter, "TimerCounter", "D:/work/McuClient/src/TimerCounter.js");
+
+	    __REACT_HOT_LOADER__.register(_default, "default", "D:/work/McuClient/src/TimerCounter.js");
+	}();
+
+	;
+
+/***/ },
+/* 41 */
+/***/ function(module, exports, __webpack_require__) {
+
+	'use strict';
+
+	Object.defineProperty(exports, "__esModule", {
+	  value: true
+	});
+
+	var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
+
+	var _Ape2 = __webpack_require__(37);
+
+	var _Ape3 = _interopRequireDefault(_Ape2);
+
+	var _ApeConsts = __webpack_require__(8);
+
+	var _ApeConsts2 = _interopRequireDefault(_ApeConsts);
+
+	var _pdus = __webpack_require__(25);
+
+	var _pdus2 = _interopRequireDefault(_pdus);
+
+	var _Loger = __webpack_require__(5);
+
+	var _Loger2 = _interopRequireDefault(_Loger);
+
+	var _MessageTypes = __webpack_require__(6);
+
+	var _MessageTypes2 = _interopRequireDefault(_MessageTypes);
+
+	var _EngineUtils = __webpack_require__(9);
+
+	var _EngineUtils2 = _interopRequireDefault(_EngineUtils);
+
+	function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
+
+	function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
+
+	function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }
+
+	function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } // //////////////////////////////////////////////////////////////////////////////
+	//
+	//  Copyright (C) 2016-present  All Rights Reserved.
+	//  Licensed under the Apache License, Version 2.0 (the "License");
+	//  http://www.apache.org/licenses/LICENSE-2.0
+	//
+	//  Github Home: https://github.com/AlexWang1987
+	//  Author: AlexWang
+	//  Date: 2016-08-14 15:55:42
+	//  QQ Email: 1669499355@qq.com
+	//  Last Modified time: 2016-09-06 16:28:14
+	//  Description: LiveClass-ChatApe
+	//
+	// //////////////////////////////////////////////////////////////////////////////
+
+	var loger = _Loger2.default.getLoger('ChatApe');
+
+	var ChatApe = function (_Ape) {
+	  _inherits(ChatApe, _Ape);
+
+	  function ChatApe() {
+	    _classCallCheck(this, ChatApe);
+
+	    //Ape Models
+	    var _this = _possibleConstructorReturn(this, (ChatApe.__proto__ || Object.getPrototypeOf(ChatApe)).call(this, _ApeConsts2.default.CHAT_SESSION_ID, _ApeConsts2.default.CHAT_SESSION_NAME, _ApeConsts2.default.CHAT_SESSION_TAG));
+
+	    _this.registerKey(_this._session_id, _this._session_name, _this._session_tag, new ArrayBuffer());
+	    _this.registerObj(_pdus2.default.RCPDU_REG_REGISTER_TABLE, _ApeConsts2.default.CHAT_OBJ_TABLE_ID, _ApeConsts2.default.CHAT_OBJ_TABLE_NAME, _ApeConsts2.default.CHAT_OBJ_TABLE_TAG, 0, new ArrayBuffer());
+
+	    // ape listeners
+	    //this.on(pdu.RCPDU_CHAT_SEND_DATA_REQUEST, this.chatMsgIncomingHandler.bind(this));
+	    _this.on(_pdus2.default.RCPDU_SEND_CHAT_DATA_REQUEST, _this.chatMsgIncomingHandler.bind(_this));
+	    return _this;
+	  }
+
+	  _createClass(ChatApe, [{
+	    key: 'sendChatMsg',
+	    value: function sendChatMsg(_messageInfo) {
+	      if (this._classInfo === null || _EngineUtils2.default.isEmptyObject(this._classInfo)) {
+	        loger.log('不能发送聊天消息.McuClient还未初始化数据!');
+	        if (GlobalConfig.getCurrentStatus().code == 0 || GlobalConfig.getCurrentStatus().code == 1) {
+	          this._emit(_MessageTypes2.default.MCU_ERROR, _MessageTypes2.default.ERR_APE_SEND_FAILED_NO_JOIN);
+	          return;
+	        }
+	        return;
+	      }
+
+	      // to, message
+	      loger.log('发送聊天消息.', _messageInfo.to, _messageInfo.message);
+
+	      var chatSendPdu = new _pdus2.default['RCChatSendDataRequestPdu']();
+	      //chatSendPdu.type = pdu.RCPDU_CHAT_SEND_DATA_REQUEST;
+	      chatSendPdu.type = _pdus2.default.RCPDU_SEND_CHAT_DATA_REQUEST;
+	      chatSendPdu.initiator = this._classInfo.nodeId; //发起人
+	      chatSendPdu.peer = parseInt(_messageInfo.to); //发送给谁,公聊的时候是0,私聊的时候是指定的用户id
+
+	      chatSendPdu.userData = this._rCArrayBufferUtil.strToUint8Array("h5" + _messageInfo.message);
+	      chatSendPdu.fromName = this._rCArrayBufferUtil.strToUint8Array("h5" + this._classInfo.userName);
+	      chatSendPdu.fromRole = this._classInfo.userRole; //    classRole已经废弃
+	      chatSendPdu.isPublic = true;
+	      // if (!(chatSendPdu.isPublic || 0 === chatSendPdu.peer)) {
+	      if (!chatSendPdu.isPublic && 0 != chatSendPdu.peer) {
+	        //发送给制定的人
+	        loger.log('发送私聊消息.');
+	        this.send(chatSendPdu);
+	      } else {
+	        //发送给所有人
+	        loger.log('发送公聊消息.');
+	        //this.sendUniform(chatSendPdu);
+	        this.sendChatUniform(chatSendPdu);
+	      }
+	    }
+	  }, {
+	    key: 'chatMsgIncomingHandler',
+	    value: function chatMsgIncomingHandler(chatBuffer) {
+	      var chatReceivePdu = _pdus2.default['RCChatSendDataRequestPdu'].decode(chatBuffer);
+
+	      var chatMsg = {};
+	      chatMsg.fromNodeId = chatReceivePdu.initiator;
+	      chatMsg.toNodeId = chatReceivePdu.peer;
+	      chatMsg.message = this._rCArrayBufferUtil.uint8ArrayToStr(chatReceivePdu.userData, 2);
+	      chatMsg.fromName = this._rCArrayBufferUtil.uint8ArrayToStr(chatReceivePdu.fromName, 2);
+	      chatMsg.fromRole = chatReceivePdu.fromRole;
+
+	      loger.log('接收聊天消息.', chatMsg);
+
+	      this._emit(_MessageTypes2.default.CHAT_RECEIVE, chatMsg);
+	    }
+	  }]);
+
+	  return ChatApe;
+	}(_Ape3.default);
+
+	var _default = ChatApe;
+	exports.default = _default;
+	;
+
+	var _temp = function () {
+	  if (typeof __REACT_HOT_LOADER__ === 'undefined') {
+	    return;
+	  }
+
+	  __REACT_HOT_LOADER__.register(loger, 'loger', 'D:/work/McuClient/src/apes/ChatApe.js');
+
+	  __REACT_HOT_LOADER__.register(ChatApe, 'ChatApe', 'D:/work/McuClient/src/apes/ChatApe.js');
+
+	  __REACT_HOT_LOADER__.register(_default, 'default', 'D:/work/McuClient/src/apes/ChatApe.js');
+	}();
+
+	;
+
+/***/ },
+/* 42 */
+/***/ function(module, exports, __webpack_require__) {
+
+	'use strict';
+
+	Object.defineProperty(exports, "__esModule", {
+	    value: true
+	});
+
+	var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
+
+	var _Ape2 = __webpack_require__(37);
+
+	var _Ape3 = _interopRequireDefault(_Ape2);
+
+	var _ApeConsts = __webpack_require__(8);
+
+	var _ApeConsts2 = _interopRequireDefault(_ApeConsts);
+
+	var _pdus = __webpack_require__(25);
+
+	var _pdus2 = _interopRequireDefault(_pdus);
+
+	var _Loger = __webpack_require__(5);
+
+	var _Loger2 = _interopRequireDefault(_Loger);
+
+	var _MessageTypes = __webpack_require__(6);
+
+	var _MessageTypes2 = _interopRequireDefault(_MessageTypes);
+
+	var _GlobalConfig = __webpack_require__(7);
+
+	var _GlobalConfig2 = _interopRequireDefault(_GlobalConfig);
+
+	var _EngineUtils = __webpack_require__(9);
+
+	var _EngineUtils2 = _interopRequireDefault(_EngineUtils);
+
+	var _MediaModule = __webpack_require__(43);
+
+	var _MediaModule2 = _interopRequireDefault(_MediaModule);
+
+	function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
+
+	function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
+
+	function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }
+
+	function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } // //////////////////////////////////////////////////////////////////////////////
+	//视频模块
+	// //////////////////////////////////////////////////////////////////////////////
+
+	var loger = _Loger2.default.getLoger('VideoApe');
+
+	var VideoApe = function (_Ape) {
+	    _inherits(VideoApe, _Ape);
+
+	    function VideoApe() {
+	        _classCallCheck(this, VideoApe);
+
+	        var _this = _possibleConstructorReturn(this, (VideoApe.__proto__ || Object.getPrototypeOf(VideoApe)).call(this, _ApeConsts2.default.VIDEO_SESSION_ID, _ApeConsts2.default.VIDEO_SESSION_NAME, _ApeConsts2.default.VIDEO_SESSION_TAG));
+
+	        _this.mediaModule = new _MediaModule2.default();
+	        _this.mediaModule.MEDIA_OBJ_TABLE_ID = _ApeConsts2.default.VIDEO_OBJ_TABLE_ID;
+	        _this.mediaModule.mediaChannels = {};
+
+	        // Ape Models
+	        _this.registerKey(_this._session_id, _this._session_name, _this._session_tag, new ArrayBuffer());
+	        _this.registerObj(_pdus2.default.RCPDU_REG_REGISTER_TABLE, _ApeConsts2.default.VIDEO_OBJ_TABLE_ID, _ApeConsts2.default.VIDEO_OBJ_TABLE_NAME, _ApeConsts2.default.VIDEO_OBJ_TABLE_TAG, 0, new ArrayBuffer());
+
+	        // videoApe 监听视频控制消息,用户之间的消息传递
+	        _this.on(_pdus2.default.RCPDU_SEND_VIDEO_DATA_REQUEST, _this.receiveVideoCommandHandler.bind(_this));
+	        return _this;
+	    }
+	    //ape加入成功
+
+
+	    _createClass(VideoApe, [{
+	        key: 'onJoinChannelHandlerSuccess',
+	        value: function onJoinChannelHandlerSuccess() {
+	            //这个设置很重要,因为只有Sass流程完成之后,APE才能取得GlobalConfig中的数据
+	            this.mediaModule.maxMediaChannel = _GlobalConfig2.default.maxVideoChannels;
+	        }
+
+	        /////////////发送数据操作////////////////////////////////////////////
+	        //获取播流地址
+
+	    }, {
+	        key: 'getPlayVideoPath',
+	        value: function getPlayVideoPath(_param) {
+	            loger.log('getPlayVideoPath');
+	            return this.mediaModule.getMediaPlayPath(_param);
+	        }
+
+	        //获取推流地址
+
+	    }, {
+	        key: 'getPublishVideoPath',
+	        value: function getPublishVideoPath(_param) {
+	            loger.log('getPublishVideoPath');
+	            return this.mediaModule.getMediaPublishPath(_param);
+	        }
+
+	        //获取当前所有频道信息
+
+	    }, {
+	        key: 'getAllChannelInfo',
+	        value: function getAllChannelInfo(_param) {
+	            loger.log('getAllChannelInfo');
+	            return this.mediaModule.getAllMediaChannelInfo();
+	        }
+
+	        //推流
+
+	    }, {
+	        key: 'publishVideo',
+	        value: function publishVideo(_param) {
+	            if (!this.mcu.connected) {
+	                loger.warn(_GlobalConfig2.default.getCurrentStatus());
+	                return { "code": _ApeConsts2.default.RETURN_FAILED, "data": "已经断开连接" };
+	            }
+
+	            if (_param == null || _param.publishUrl == null) {
+	                loger.warn('publishVideo,参数错误', _param);
+	                this._emit(_MessageTypes2.default.MCU_ERROR, _MessageTypes2.default.ERR_APE_INTERFACE_PARAM_WRONG);
+	                return { "code": _ApeConsts2.default.RETURN_FAILED, "data": "参数错误" };
+	            }
+
+	            //根据推流的地址获取对应的频道信息
+	            var needPublishChannelInfo = this.mediaModule.getNeedPublishMediaChannel(_param.publishUrl);
+	            if (needPublishChannelInfo == null) {
+	                loger.warn('publishVideo,推流数据已经无效', _param);
+	                return { "code": _ApeConsts2.default.RETURN_FAILED, "data": "推流数据已经无效" };
+	            }
+
+	            /* //20170302 修改频道占用规则,同一个人可以推多路流,暂停下面的限制
+	               //同一个nodeId只允许推一个流,如果已经推了就不能再推
+	             if(this.mediaModule.getOpeningMediaChannel(GlobalConfig.nodeId)!=0){
+	                 loger.warn("publishVideo,已经存在一个流,不能再推");
+	                 return {"code": ApeConsts.RETURN_FAILED,  "data": "已经存在一个流,不能再推"};
+	             }*/
+
+	            //判断当前是否还有空闲的channle
+	            var freeChannel = this.mediaModule.getFreeMediaChannel();
+	            if (freeChannel == 0) {
+	                loger.warn("publishVideo,没有空闲的channel ");
+	                return { "code": _ApeConsts2.default.RETURN_FAILED, "data": "不能再打开更多的设备", "mediaChannels": this.mediaModule.mediaChannels };
+	            }
+
+	            //判断当前的频道是否已经占用
+	            if (this.mediaModule.checkChannelIsOpening(needPublishChannelInfo.channelId)) {
+	                loger.warn(needPublishChannelInfo.channelId, "频道已经被占用");
+	                return { "code": _ApeConsts2.default.RETURN_FAILED, "data": "频道已经被占用!", "mediaChannels": this.mediaModule.mediaChannels };
+	            }
+
+	            var channelInfo = this.mediaModule.getDefaultChannelInfo();
+	            channelInfo.owner = _GlobalConfig2.default.nodeId;
+	            channelInfo.status = _ApeConsts2.default.CHANNEL_STATUS_OPENING;
+	            channelInfo.channelId = needPublishChannelInfo.channelId;
+	            channelInfo.streamId = needPublishChannelInfo.streamId; //按规则拼接的流名称
+	            channelInfo.mediaType = _ApeConsts2.default.MEDIA_TYPE_VIDEO;
+	            this.sendTableUpdateHandler(channelInfo);
+
+	            return { "code": _ApeConsts2.default.RETURN_SUCCESS, "data": "推流成功!", "mediaId": needPublishChannelInfo.channelId };
+	        }
+
+	        //停止推流,
+
+	    }, {
+	        key: 'stopPublishVideo',
+	        value: function stopPublishVideo(_param) {
+	            loger.log('stopPublishVideo ->_param', _param);
+	            if (!this.mcu.connected) {
+	                loger.warn(_GlobalConfig2.default.getCurrentStatus());
+	                return { "code": _ApeConsts2.default.RETURN_FAILED, "data": "已经断开连接" };
+	            }
+
+	            //默认为自己的nodeId,_param如果为空,那么默认就是当前自己的nodeId,否则用_param
+	            var nodeId = _GlobalConfig2.default.nodeId;
+	            if (_param && parseInt(_param.nodeId) > 0) {
+	                nodeId = parseInt(_param.nodeId);
+	            }
+
+	            //默认为0,如果releaseChannelId 存在就释放releaseChannelId通道
+	            var releaseChannelId = 0;
+	            if (_param && parseInt(_param.mediaId) > 0) {
+	                releaseChannelId = parseInt(_param.mediaId);
+	            }
+
+	            //释放channelId 的占用
+	            if (releaseChannelId > 0) {
+	                //第一种情况,释放nodeId占用的指定mediaId (channelId)
+	                this._releaseChannelForNodeId(nodeId, releaseChannelId);
+	            } else {
+	                //第二种情况,释放nodeId占用的所有channelId
+	                this._releaseNodeIdAllChannel(nodeId);
+	            }
+	        }
+	        //释放nodeId占用的指定的channelId频道
+
+	    }, {
+	        key: '_releaseChannelForNodeId',
+	        value: function _releaseChannelForNodeId(nodeId, channelId) {
+	            loger.log(nodeId, "_releaseChannelForNodeId-->channelId", channelId);
+	            var channelInfo = this.mediaModule.mediaChannels[channelId];
+	            if (channelInfo && channelInfo.status == _ApeConsts2.default.CHANNEL_STATUS_OPENING) {
+	                if (channelInfo.fromNodeId == nodeId) {
+
+	                    var _channelInfo2 = this.mediaModule.getDefaultChannelInfo();
+	                    _channelInfo2.status = _ApeConsts2.default.CHANNEL_STATUS_RELEASED;
+	                    _channelInfo2.channelId = channelId;
+
+	                    this.sendTableUpdateHandler(_channelInfo2);
+	                } else {
+	                    loger.warn(channelId, "不属于nodeId", nodeId, "不能释放", channelInfo);
+	                }
+	            } else {
+	                loger.warn(nodeId, "要释放的channel不存在或者已经释放-->channelId", channelInfo);
+	            }
+	        }
+	        //释放nodeId占用的所有频道
+
+	    }, {
+	        key: '_releaseNodeIdAllChannel',
+	        value: function _releaseNodeIdAllChannel(nodeId) {
+	            loger.log(nodeId, "_releaseNodeIdAllChannel", this.mcu.connected);
+	            if (!this.mcu.connected) {
+	                loger.warn(_GlobalConfig2.default.getCurrentStatus());
+	                return { "code": _ApeConsts2.default.RETURN_FAILED, "data": "已经断开连接" };
+	            }
+
+	            var openingChannel = this.mediaModule.getOpeningMediaChannel(nodeId);
+	            if (openingChannel == 0) {
+	                loger.warn(nodeId, "没有占用channel不需要处理");
+	                return { "code": _ApeConsts2.default.RETURN_FAILED, "data": "没有占用channel不需要处理" };
+	            }
+
+	            var channelInfo = this.mediaModule.getDefaultChannelInfo();
+	            channelInfo.status = _ApeConsts2.default.CHANNEL_STATUS_RELEASED;
+	            channelInfo.channelId = openingChannel;
+
+	            this.sendTableUpdateHandler(channelInfo);
+	            //递归检查,800毫秒之后执行
+	            setTimeout(function () {
+	                loger.warn(nodeId, "递归检查频道是否占用");
+	                this._releaseNodeIdAllChannel(nodeId);
+	            }.bind(this), 800);
+	        }
+	    }, {
+	        key: 'sendVideoBroadcastMsg',
+	        value: function sendVideoBroadcastMsg(_param) {
+	            if (!this.mcu.connected) {
+	                loger.warn(_GlobalConfig2.default.getCurrentStatus());
+	                return { "code": _ApeConsts2.default.RETURN_FAILED, "data": "已经断开连接" };
+	            }
+
+	            if (this._classInfo === null || _EngineUtils2.default.isEmptyObject(this._classInfo)) {
+	                loger.log('不能发送Video消息.McuClient还未初始化数据!');
+	                if (_GlobalConfig2.default.getCurrentStatus().code == 0 || _GlobalConfig2.default.getCurrentStatus().code == 1) {
+	                    this._emit(_MessageTypes2.default.MCU_ERROR, _MessageTypes2.default.ERR_APE_SEND_FAILED_NO_JOIN);
+	                    return { "code": 1, "data": "不能发送Video消息.McuClient还未初始化数据" };
+	                }
+	                return { "code": _ApeConsts2.default.RETURN_FAILED, "data": "不能发送Video消息.McuClient还未初始化数据" };
+	            }
+	            if (_param == null) {
+	                loger.warn('sendVideoCommandMsg失败,参数错误', _param);
+	                this._emit(_MessageTypes2.default.MCU_ERROR, _MessageTypes2.default.ERR_APE_INTERFACE_PARAM_WRONG);
+	                return { "code": _ApeConsts2.default.RETURN_FAILED, "data": "sendVideoCommandMsg失败,参数错误" };
+	            }
+	            // to, message
+	            loger.log('发送Video消息.', _param);
+
+	            if (_param.actionType != null && _param.actionType == _ApeConsts2.default.MEDIA_ACTION_OPEN_CAMERA) {
+	                //判断当前开启的视频数量是否已经是最大值,如果已经是最大值,不能再开启
+	                var freeChannel = this.mediaModule.getFreeMediaChannel();
+	                if (freeChannel == 0) {
+	                    loger.warn('sendVideoCommandMsg,不能再打开更多的设备', _param);
+	                    return { "code": _ApeConsts2.default.RETURN_FAILED, "data": "不能再打开更多的设备", "mediaChannels": this.mediaModule.mediaChannels };
+	                }
+	            }
+
+	            var videoSendPdu = new _pdus2.default['RCVideoSendDataRequestPdu']();
+	            videoSendPdu.type = _pdus2.default.RCPDU_SEND_VIDEO_DATA_REQUEST;
+	            videoSendPdu.isPublic = true;
+
+	            videoSendPdu.fromNodeId = _GlobalConfig2.default.nodeId; //发起人
+	            videoSendPdu.toNodeId = parseInt(_param.toNodeId) || 0; //接收者,0就是所有人
+	            videoSendPdu.actionType = parseInt(_param.actionType) || _ApeConsts2.default.MEDIA_ACTION_DEFAULT;
+
+	            videoSendPdu.data = this._rCArrayBufferUtil.strToUint8Array("h5" + _param.data); //开头两个字会乱码
+
+	            if (!videoSendPdu.isPublic && 0 != videoSendPdu.toNodeId) {
+	                //发送给制定的人
+	                loger.log('发送私聊Video消息.');
+	                this.send(videoSendPdu);
+	            } else {
+	                //发送给所有人
+	                loger.log('发送公聊Video消息.');
+	                this.sendChatUniform(videoSendPdu);
+	            }
+	            return { "code": _ApeConsts2.default.RETURN_SUCCESS, "data": "" };
+	        }
+	    }, {
+	        key: 'sendTableUpdateHandler',
+	        value: function sendTableUpdateHandler(_channelInfo) {
+	            loger.log("video===sendTableUpdateHandler ");
+	            var updateModelPdu = this.packPdu(_channelInfo, _channelInfo.channelId); //let updateModelPdu=this.packPdu({},ApeConsts.VIDEO_OBJ_TABLE_ID+2);
+
+	            if (updateModelPdu == null) {
+	                loger.warn("sendTableUpdateHandler error,updateModelPdu=null");
+	                return;
+	            }
+
+	            var tableItemPdu = new _pdus2.default['RCRegistryTableItemPdu']();
+	            tableItemPdu.itemIdx = _channelInfo.channelId; //tableItemPdu.itemIdx=ApeConsts.VIDEO_OBJ_TABLE_ID+2;
+	            tableItemPdu.owner = _channelInfo.owner; //0收到flash的是这个值,MCU做了了用户掉线处理,30秒之后会清理owner为0
+	            tableItemPdu.itemData = updateModelPdu.toArrayBuffer();
+
+	            //insert
+	            var tableInsertItemPdu = new _pdus2.default['RCRegistryTableUpdateItemPdu']();
+	            tableInsertItemPdu.type = _pdus2.default.RCPDU_REG_TABLE_UPDATE_PDU; //
+	            tableInsertItemPdu.items.push(tableItemPdu);
+
+	            var updateObjPdu = new _pdus2.default['RCRegistryUpdateObjPdu']();
+	            updateObjPdu.objId = _ApeConsts2.default.VIDEO_OBJ_TABLE_ID; //
+	            updateObjPdu.subType = tableInsertItemPdu.type;
+	            updateObjPdu.userData = tableInsertItemPdu.toArrayBuffer();
+
+	            //同步
+	            var adapterItemPdu = new _pdus2.default['RCAdapterItemPdu']();
+	            adapterItemPdu.type = _pdus2.default.RCPDU_REG_UPDATE_OBJ;
+	            adapterItemPdu.itemData = updateObjPdu.toArrayBuffer();
+
+	            var adapterPdu = new _pdus2.default['RCAdapterPdu']();
+	            adapterPdu.type = _pdus2.default.RCPDU_REG_ADAPTER;
+	            adapterPdu.item.push(adapterItemPdu);
+
+	            loger.log("发送更新VIDEO.itemIdx=" + tableItemPdu.itemIdx);
+	            this.sendUniform(adapterPdu, true);
+	        }
+
+	        /////收到消息处理//////////////////////////////////////////////////
+
+	        // 视频消息处理,内部处理,不需要告诉应用层
+
+	    }, {
+	        key: 'receiveVideoCommandHandler',
+	        value: function receiveVideoCommandHandler(_data) {
+	            var videoReceivePdu = _pdus2.default['RCVideoSendDataRequestPdu'].decode(_data);
+	            if (videoReceivePdu == null) {
+	                loger.warn("视频控制消息处理,收到的消息为null,不做处理");
+	                return;
+	            }
+	            videoReceivePdu.data = this._rCArrayBufferUtil.uint8ArrayToStr(videoReceivePdu.data, 2); //开头两个字会乱码
+	            loger.log('视频控制消息处理 .', videoReceivePdu);
+	            console.log(videoReceivePdu);
+
+	            //判断接收者的id,如果不是0,并且也不是自己的nodeId,那么消息不做处理
+	            if (videoReceivePdu.toNodeId != 0 && videoReceivePdu.toNodeId != _GlobalConfig2.default.nodeId) {
+	                loger.log('视频消息不处理 toNodeId=', videoReceivePdu.toNodeId, "my nodeId=", _GlobalConfig2.default.nodeId);
+	            } else {
+	                this._emit(_MessageTypes2.default.VIDEO_BROADCAST, videoReceivePdu);
+	            }
+	        }
+	    }, {
+	        key: 'tableUpdateHandler',
+	        value: function tableUpdateHandler(owner, itemIdx, itemData) {
+	            // debugger;
+	            var unpackChannelInfo = this.unPackPdu(owner, itemIdx, itemData);
+	            loger.log("tableUpdateHandler,channel", itemIdx);
+
+	            //****很重要********
+	            //如果owner的值为0,代表的是这个歌频道已经被释放了(mcu服务端对于占用channel的掉线用户,就是把owner设置为0)
+	            if (owner == 0) {
+	                loger.log("释放占用的频道,channel", itemIdx);
+	                unpackChannelInfo.status = _ApeConsts2.default.CHANNEL_STATUS_RELEASED;
+	                unpackChannelInfo.streamId = "";
+	            }
+
+	            this.mediaModule.mediaChannels[itemIdx] = unpackChannelInfo;
+
+	            if (unpackChannelInfo && unpackChannelInfo.fromNodeId != _GlobalConfig2.default.nodeId) {
+	                var receiveChannelInfo = {};
+	                receiveChannelInfo.mediaId = unpackChannelInfo.channelId;
+
+	                //消息不是自己同步的,需要处理
+	                if (unpackChannelInfo.status == _ApeConsts2.default.CHANNEL_STATUS_OPENING) {
+	                    //正在推流
+	                    receiveChannelInfo.m3u8Url = "";
+	                    receiveChannelInfo.rtmpUrl = "";
+	                    var m3u8Stream = this.mediaModule.getMediaPlayPath({ "type": "m3u8", "streamId": unpackChannelInfo.streamId });
+	                    var rtmpStream = this.mediaModule.getMediaPlayPath({ "type": "rtmp", "streamId": unpackChannelInfo.streamId });
+
+	                    if (m3u8Stream.code == 0) {
+	                        receiveChannelInfo.m3u8Url = m3u8Stream.playUrl;
+	                    }
+	                    if (rtmpStream.code == 0) {
+	                        receiveChannelInfo.rtmpUrl = rtmpStream.playUrl;
+	                    }
+	                    loger.log("VIDEO_PLAY", receiveChannelInfo);
+	                    //广播播放视频的消息
+	                    this._emit(_MessageTypes2.default.VIDEO_PLAY, receiveChannelInfo);
+	                } else {
+	                    loger.log("VIDEO_STOP", receiveChannelInfo);
+	                    //流已经停止
+	                    this._emit(_MessageTypes2.default.VIDEO_STOP, receiveChannelInfo);
+	                }
+	            } else {
+	                loger.warn("视频消息是自己发送的或者是视频消息无效,不需要处理,消息内容如下:");
+	                console.log(unpackChannelInfo);
+	            }
+
+	            this._emit(_MessageTypes2.default.VIDEO_UPDATE, unpackChannelInfo);
+	        }
+
+	        ///////数据的封包和解包/////////////////////////////////////////
+
+	    }, {
+	        key: 'packPdu',
+	        value: function packPdu(_param, _itemIdx) {
+	            loger.log("packPdu ");
+	            //验证坐标点集合数组是否合法
+	            if (_param == null || _itemIdx == null) {
+	                this._emit(_MessageTypes2.default.MCU_ERROR, _MessageTypes2.default.ERR_APE_INTERFACE_PARAM_WRONG);
+	                return null;
+	            }
+
+	            //判断type类型,根据type设置不同的参数
+	            var packPduModel = new _pdus2.default['RCVideoChannelInfoPdu']();
+	            packPduModel.status = _param.status || _ApeConsts2.default.CHANNEL_STATUS_RELEASED;
+	            packPduModel.channelId = _itemIdx;
+	            packPduModel.streamId = _param.streamId || "";
+	            packPduModel.siteId = _param.siteId || _GlobalConfig2.default.siteId; //GlobalConfig.siteId;
+	            packPduModel.classId = parseInt(_param.classId) || parseInt(_GlobalConfig2.default.classId);
+	            packPduModel.userId = _param.userId || "0";
+	            packPduModel.mediaType = _param.mediaType || _ApeConsts2.default.MEDIA_TYPE_VIDEO;
+	            packPduModel.timestamp = _param.timestamp || _EngineUtils2.default.creatTimestamp();
+	            packPduModel.fromNodeId = _GlobalConfig2.default.nodeId;
+	            packPduModel.toNodeId = 0;
+	            console.log(packPduModel);
+	            return packPduModel;
+	        }
+	    }, {
+	        key: 'unPackPdu',
+	        value: function unPackPdu(owner, itemIdx, itemData) {
+	            loger.log("unPackPdu->owner:", owner, "itemIdx->", itemIdx);
+	            if (owner == null || itemIdx == null || itemData == null) {
+	                this._emit(_MessageTypes2.default.MCU_ERROR, _MessageTypes2.default.ERR_APE_INTERFACE_PARAM_WRONG);
+	                return null;
+	            }
+	            try {
+	                var videoChannelInfo = _pdus2.default['RCVideoChannelInfoPdu'].decode(itemData);
+	                console.log(videoChannelInfo);
+	                return videoChannelInfo;
+	            } catch (err) {
+	                loger.log("unPackPdu error,itemIdx=" + itemIdx + "  err:" + err.message);
+	            }
+	            return null;
+	        }
+	    }]);
+
+	    return VideoApe;
+	}(_Ape3.default);
+
+	var _default = VideoApe;
+	exports.default = _default;
+	;
+
+	var _temp = function () {
+	    if (typeof __REACT_HOT_LOADER__ === 'undefined') {
+	        return;
+	    }
+
+	    __REACT_HOT_LOADER__.register(loger, 'loger', 'D:/work/McuClient/src/apes/VideoApe.js');
+
+	    __REACT_HOT_LOADER__.register(VideoApe, 'VideoApe', 'D:/work/McuClient/src/apes/VideoApe.js');
+
+	    __REACT_HOT_LOADER__.register(_default, 'default', 'D:/work/McuClient/src/apes/VideoApe.js');
+	}();
+
+	;
+
+/***/ },
+/* 43 */
+/***/ function(module, exports, __webpack_require__) {
+
+	'use strict';
+
+	Object.defineProperty(exports, "__esModule", {
+	    value: true
+	});
+
+	var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); // //////////////////////////////////////////////////////////////////////////////
+	//  VideoApe、AudioApe 共用的方法单独提取处理
+	// //////////////////////////////////////////////////////////////////////////////
+
+	var _ApeConsts = __webpack_require__(8);
+
+	var _ApeConsts2 = _interopRequireDefault(_ApeConsts);
+
+	var _Loger = __webpack_require__(5);
+
+	var _Loger2 = _interopRequireDefault(_Loger);
+
+	var _MessageTypes = __webpack_require__(6);
+
+	var _MessageTypes2 = _interopRequireDefault(_MessageTypes);
+
+	var _GlobalConfig = __webpack_require__(7);
+
+	var _GlobalConfig2 = _interopRequireDefault(_GlobalConfig);
+
+	var _EngineUtils = __webpack_require__(9);
+
+	var _EngineUtils2 = _interopRequireDefault(_EngineUtils);
+
+	function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
+
+	function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
+
+	var loger = _Loger2.default.getLoger('MediaModule');
+
+	var MediaModule = function () {
+	    function MediaModule() {
+	        _classCallCheck(this, MediaModule);
+
+	        this.needPublishMediaChannel = {}; //记录准备推流的频道信息
+	        this.mediaChannels = {};
+	        this.maxMediaChannel = 0;
+	        this.MEDIA_OBJ_TABLE_ID = 0;
+	    }
+
+	    //获取播流地址
+
+
+	    _createClass(MediaModule, [{
+	        key: 'getMediaPlayPath',
+	        value: function getMediaPlayPath(_param) {
+	            loger.log('getMediaPlayPath');
+	            if (_param == null || _param.streamId == null) {
+	                loger.warn('getMediaPlayPath,参数错误', _param);
+	                //this._emit(MessageTypes.MCU_ERROR, MessageTypes.ERR_APE_INTERFACE_PARAM_WRONG);
+	                return { "code": _ApeConsts2.default.RETURN_FAILED, "data": "" };
+	            }
+
+	            var path = "";
+	            var port = "";
+	            if (_param.type == "m3u8") {
+	                //M3U8
+	                //http://123.56.73.119:6001/hls/h5dev_403074980_0_983041_1487663265/index.m3u8
+	                port = _GlobalConfig2.default.RSServerPort == "" || _GlobalConfig2.default.RSServerPort == null ? "" : ":" + _GlobalConfig2.default.RSServerPort;
+	                path = "http://" + _GlobalConfig2.default.RSServerIP + port + "/live/" + _param.streamId + "/index.m3u8";
+	            } else {
+	                port = _GlobalConfig2.default.MSServerPort == "" || _GlobalConfig2.default.MSServerPort == null ? "" : ":" + _GlobalConfig2.default.MSServerPort;
+	                path = "rtmp://" + _GlobalConfig2.default.MSServerIP + port + "/live/" + _param.streamId;
+	            }
+	            return { "code": _ApeConsts2.default.RETURN_SUCCESS, "data": "", "playUrl": path };
+	        }
+
+	        //获取推流地址
+
+	    }, {
+	        key: 'getMediaPublishPath',
+	        value: function getMediaPublishPath(_param) {
+	            loger.log('getMediaPublishPath');
+	            //判断当前开启的视频数量是否已经是最大值,如果已经是最大值,不能再开启
+	            var freeChannel = this.getFreeMediaChannel();
+	            if (freeChannel == 0) {
+	                return { "code": _ApeConsts2.default.RETURN_FAILED, "data": "不能再打开更多的设备", "mediaChannels": this.mediaChannels };
+	            }
+
+	            //默认方式推流
+	            var pubType = "live";
+	            //flash推流
+	            if (_param && _param.type == "flash") {
+	                pubType = "flash";
+	            }
+
+	            //端口,有端口就显示 ":xxx",没有端口就是""
+	            var port = _GlobalConfig2.default.MSServerPort == "" || _GlobalConfig2.default.MSServerPort == null ? "" : ":" + _GlobalConfig2.default.MSServerPort;
+	            //时间戳
+	            var timestamp = _EngineUtils2.default.creatTimestamp();
+
+	            var streamId = _GlobalConfig2.default.siteId + "_" + _GlobalConfig2.default.classId + "_" + _GlobalConfig2.default.userId + "_" + freeChannel + "_" + timestamp;
+
+	            //生成推流地址和推流数据(同步数据的时候用)
+	            var publishUrl = "rtmp://" + _GlobalConfig2.default.MSServerIP + port + "/" + pubType + "/" + streamId;
+
+	            this.needPublishMediaChannel[publishUrl] = {
+	                "channelId": freeChannel,
+	                "publishUrl": publishUrl,
+	                "streamId": streamId
+	            };
+	            return { "code": _ApeConsts2.default.RETURN_SUCCESS,
+	                "data": "",
+	                "publishUrl": publishUrl
+	            };
+	        }
+
+	        //获取当前空闲的channel,返回值为0代表没有空闲的,否则返回的就是空闲的channelId
+
+	    }, {
+	        key: 'getFreeMediaChannel',
+	        value: function getFreeMediaChannel() {
+	            var counter = 0;
+	            for (var key in this.mediaChannels) {
+	                var item = this.mediaChannels[key];
+	                if (item && item.status == _ApeConsts2.default.CHANNEL_STATUS_RELEASED) {
+	                    return item.channelId;
+	                }
+	                counter++;
+	            }
+	            loger.log("getFreeMediaChannel", "maxMediaChannel", this.maxMediaChannel, "counter:", counter);
+	            console.log(this.mediaChannels);
+	            if (counter < this.maxMediaChannel) {
+	                return this.MEDIA_OBJ_TABLE_ID + counter;
+	            }
+	            return 0; //没有空闲的
+	        }
+
+	        //获取准备推流的频道信息
+
+	    }, {
+	        key: 'getNeedPublishMediaChannel',
+	        value: function getNeedPublishMediaChannel(_publishUrl) {
+	            return this.needPublishMediaChannel[_publishUrl];
+	        }
+
+	        //获取当前属于nodeId的已经打开的的channel,返回值为0代表没有打开的,否则返回的就是打开的channelId
+
+	    }, {
+	        key: 'getOpeningMediaChannel',
+	        value: function getOpeningMediaChannel(_nodeId) {
+	            loger.log("getOpeningMediaChannel", "nodeId", _nodeId, "mediaChannels:", this.mediaChannels);
+	            if (_nodeId == null || _nodeId == 0) {
+	                return 0;
+	            }
+	            for (var key in this.mediaChannels) {
+	                var item = this.mediaChannels[key];
+	                if (item && item.status == _ApeConsts2.default.CHANNEL_STATUS_OPENING && item.fromNodeId == _nodeId) {
+	                    return item.channelId;
+	                }
+	            }
+	            return 0;
+	        }
+
+	        //检查频道是否已经被占用
+
+	    }, {
+	        key: 'checkChannelIsOpening',
+	        value: function checkChannelIsOpening(_channelId) {
+	            if (_channelId == null) {
+	                loger.warn("checkChannelIsOpening error,channel=", _channelId);
+	                return true;
+	            }
+	            var channelInfo = this.mediaChannels[_channelId];
+	            if (channelInfo == null || channelInfo.status == _ApeConsts2.default.CHANNEL_STATUS_RELEASED) {
+	                return false;
+	            }
+	            return true;
+	        }
+
+	        //获取当前所有频道的信息
+
+	    }, {
+	        key: 'getAllMediaChannelInfo',
+	        value: function getAllMediaChannelInfo() {
+	            var channels = [];
+	            for (var key in this.mediaChannels) {
+	                var channel = this.mediaChannels[key];
+	                if (channel) {
+	                    channels.push({ "mediaId": channel.channelId, "status": channel.status, "fromNodeId": channel.fromNodeId });
+	                }
+	            }
+	            if (channels.length < this.maxMediaChannel) {
+	                for (var i = channels.length; i < this.maxMediaChannel; i++) {
+	                    var channelId = this.MEDIA_OBJ_TABLE_ID + i;
+	                    channels.push({ "mediaId": channelId, "status": _ApeConsts2.default.CHANNEL_STATUS_RELEASED, "fromNodeId": 0 });
+	                }
+	            }
+	            return channels;
+	        }
+	        //获取默认的频道信息
+
+	    }, {
+	        key: 'getDefaultChannelInfo',
+	        value: function getDefaultChannelInfo() {
+	            var channelInfo = {};
+	            channelInfo.owner = 0; //这个很重要,释放的时候必须设置为0,占用的时候设置为自己的nodeId
+	            channelInfo.status = _ApeConsts2.default.CHANNEL_STATUS_RELEASED;
+	            channelInfo.fromNodeId = _GlobalConfig2.default.nodeId;
+	            channelInfo.channelId = 0;
+	            channelInfo.streamId = "";
+	            channelInfo.classId = _GlobalConfig2.default.classId;
+	            channelInfo.siteId = _GlobalConfig2.default.siteId;
+	            channelInfo.toNodeId = 0;
+	            channelInfo.userId = _GlobalConfig2.default.userId;
+	            channelInfo.mediaType = _ApeConsts2.default.MEDIA_TYPE_DEFAULT;
+	            return channelInfo;
+	        }
+	    }]);
+
+	    return MediaModule;
+	}();
+
+	var _default = MediaModule;
+	exports.default = _default;
+	;
+
+	var _temp = function () {
+	    if (typeof __REACT_HOT_LOADER__ === 'undefined') {
+	        return;
+	    }
+
+	    __REACT_HOT_LOADER__.register(loger, 'loger', 'D:/work/McuClient/src/apes/MediaModule.js');
+
+	    __REACT_HOT_LOADER__.register(MediaModule, 'MediaModule', 'D:/work/McuClient/src/apes/MediaModule.js');
+
+	    __REACT_HOT_LOADER__.register(_default, 'default', 'D:/work/McuClient/src/apes/MediaModule.js');
+	}();
+
+	;
+
+/***/ },
+/* 44 */
+/***/ function(module, exports, __webpack_require__) {
+
+	'use strict';
+
+	Object.defineProperty(exports, "__esModule", {
+	    value: true
+	});
+
+	var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
+
+	var _Ape2 = __webpack_require__(37);
+
+	var _Ape3 = _interopRequireDefault(_Ape2);
+
+	var _ApeConsts = __webpack_require__(8);
+
+	var _ApeConsts2 = _interopRequireDefault(_ApeConsts);
+
+	var _pdus = __webpack_require__(25);
+
+	var _pdus2 = _interopRequireDefault(_pdus);
+
+	var _Loger = __webpack_require__(5);
+
+	var _Loger2 = _interopRequireDefault(_Loger);
+
+	var _MessageTypes = __webpack_require__(6);
+
+	var _MessageTypes2 = _interopRequireDefault(_MessageTypes);
+
+	var _GlobalConfig = __webpack_require__(7);
+
+	var _GlobalConfig2 = _interopRequireDefault(_GlobalConfig);
+
+	var _EngineUtils = __webpack_require__(9);
+
+	var _EngineUtils2 = _interopRequireDefault(_EngineUtils);
+
+	var _MediaModule = __webpack_require__(43);
+
+	var _MediaModule2 = _interopRequireDefault(_MediaModule);
+
+	function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
+
+	function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
+
+	function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }
+
+	function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } // //////////////////////////////////////////////////////////////////////////////
+	//音频模块
+	// //////////////////////////////////////////////////////////////////////////////
+
+	var loger = _Loger2.default.getLoger('AudioApe');
+
+	var AudioApe = function (_Ape) {
+	    _inherits(AudioApe, _Ape);
+
+	    function AudioApe() {
+	        _classCallCheck(this, AudioApe);
+
+	        var _this = _possibleConstructorReturn(this, (AudioApe.__proto__ || Object.getPrototypeOf(AudioApe)).call(this, _ApeConsts2.default.AUDIO_SESSION_ID, _ApeConsts2.default.AUDIO_SESSION_NAME, _ApeConsts2.default.AUDIO_SESSION_TAG));
+
+	        _this.mediaModule = new _MediaModule2.default();
+	        _this.mediaModule.MEDIA_OBJ_TABLE_ID = _ApeConsts2.default.AUDIO_OBJ_TABLE_ID;
+	        _this.mediaModule.mediaChannels = {};
+	        // Ape Models
+	        _this.registerKey(_this._session_id, _this._session_name, _this._session_tag, new ArrayBuffer());
+	        _this.registerObj(_pdus2.default.RCPDU_REG_REGISTER_TABLE, _ApeConsts2.default.AUDIO_OBJ_TABLE_ID, _ApeConsts2.default.AUDIO_OBJ_TABLE_NAME, _ApeConsts2.default.AUDIO_OBJ_TABLE_TAG, 0, new ArrayBuffer());
+
+	        // 广播消息,用户之间的消息传递
+	        _this.on(_pdus2.default.RCPDU_SEND_AUDIO_DATA_REQUEST, _this.receiveAudiooCommandHandler.bind(_this));
+	        return _this;
+	    }
+	    //ape加入成功
+
+
+	    _createClass(AudioApe, [{
+	        key: 'onJoinChannelHandlerSuccess',
+	        value: function onJoinChannelHandlerSuccess() {
+	            //这个设置很重要,因为只有Sass流程完成之后,APE才能取得GlobalConfig中的数据
+	            this.mediaModule.maxMediaChannel = _GlobalConfig2.default.maxAudioChannels;
+	        }
+
+	        /////////////发送数据操作////////////////////////////////////////////
+	        //获取播流地址
+
+	    }, {
+	        key: 'getAudioPlayPath',
+	        value: function getAudioPlayPath(_param) {
+	            loger.log('getAudioPlayPath');
+	            return this.mediaModule.getMediaPlayPath(_param);
+	        }
+
+	        //获取推流地址
+
+	    }, {
+	        key: 'getAudioPublishPath',
+	        value: function getAudioPublishPath(_param) {
+	            loger.log('getAudioPublishPath');
+	            return this.mediaModule.getMediaPublishPath(_param);
+	        }
+
+	        //获取当前所有频道信息
+
+	    }, {
+	        key: 'getAllChannelInfo',
+	        value: function getAllChannelInfo(_param) {
+	            loger.log('getAllChannelInfo');
+	            return this.mediaModule.getAllMediaChannelInfo();
+	        }
+
+	        //推流
+
+	    }, {
+	        key: 'publishAudio',
+	        value: function publishAudio(_param) {
+	            if (!this.mcu.connected) {
+	                loger.warn(_GlobalConfig2.default.getCurrentStatus());
+	                return { "code": _ApeConsts2.default.RETURN_FAILED, "data": "已经断开连接" };
+	            }
+
+	            if (_param == null || _param.publishUrl == null) {
+	                loger.warn('publishAudio,参数错误', _param);
+	                this._emit(_MessageTypes2.default.MCU_ERROR, _MessageTypes2.default.ERR_APE_INTERFACE_PARAM_WRONG);
+	                return { "code": _ApeConsts2.default.RETURN_FAILED, "data": "参数错误" };
+	            }
+
+	            //根据推流的地址获取对应的频道信息
+	            var needPublishChannelInfo = this.mediaModule.getNeedPublishMediaChannel(_param.publishUrl);
+	            if (needPublishChannelInfo == null) {
+	                loger.warn('publishVideo,推流数据已经无效', _param);
+	                return { "code": _ApeConsts2.default.RETURN_FAILED, "data": "推流数据已经无效" };
+	            }
+
+	            /* //20170302 修改频道占用规则,同一个人可以推多路流,暂停下面的限制
+	             //同一个nodeId只允许推一个流,如果已经推了就不能再推
+	             if(this.mediaModule.getOpeningMediaChannel(GlobalConfig.nodeId)!=0){
+	                 loger.warn("publishAudio,已经存在一个流,不能再推");
+	                 return {"code": ApeConsts.RETURN_FAILED, "data": "已经存在一个流,不能再推","mediaChannels":this.mediaModule.mediaChannels};
+	             }*/
+
+	            //判断当前是否还有空闲的channle
+	            var freeChannel = this.mediaModule.getFreeMediaChannel();
+	            if (freeChannel == 0) {
+	                loger.warn("publishAudio,没有空闲的channel ");
+	                return { "code": _ApeConsts2.default.RETURN_FAILED, "data": "不能再打开更多的设备", "mediaChannels": this.mediaModule.mediaChannels };
+	            }
+
+	            //判断当前的频道是否已经占用
+	            if (this.mediaModule.checkChannelIsOpening(needPublishChannelInfo.channelId)) {
+	                loger.warn(needPublishChannelInfo.channelId, "频道已经被占用");
+	                return { "code": _ApeConsts2.default.RETURN_FAILED, "data": "频道已经被占用!" };
+	            }
+
+	            var channelInfo = this.mediaModule.getDefaultChannelInfo();
+	            channelInfo.owner = _GlobalConfig2.default.nodeId;
+	            channelInfo.status = _ApeConsts2.default.CHANNEL_STATUS_OPENING;
+	            channelInfo.channelId = needPublishChannelInfo.channelId; //freeChannel
+	            channelInfo.streamId = needPublishChannelInfo.streamId; //按规则拼接的流名称
+	            channelInfo.timestamp = needPublishChannelInfo.timestamp; //EngineUtils.creatTimestamp();
+	            channelInfo.mediaType = _ApeConsts2.default.MEDIA_TYPE_AUDIO;
+	            this.sendTableUpdateHandler(channelInfo);
+	            return { "code": _ApeConsts2.default.RETURN_SUCCESS, "data": "推流成功!", "mediaId": needPublishChannelInfo.channelId };
+	        }
+
+	        //停止推流,
+
+	    }, {
+	        key: 'stopPublishAudio',
+	        value: function stopPublishAudio(_param) {
+	            loger.log('stopPublishAudio ->_param', _param);
+	            if (!this.mcu.connected) {
+	                loger.warn(_GlobalConfig2.default.getCurrentStatus());
+	                return { "code": _ApeConsts2.default.RETURN_FAILED, "data": "已经断开连接" };
+	            }
+
+	            //默认为自己的nodeId,_param如果为空,那么默认就是当前自己的nodeId,否则用_param
+	            var nodeId = _GlobalConfig2.default.nodeId;
+	            if (_param && parseInt(_param.nodeId) > 0) {
+	                nodeId = parseInt(_param.nodeId);
+	            }
+
+	            //默认为0,如果releaseChannelId 存在就释放releaseChannelId通道
+	            var releaseChannelId = 0;
+	            if (_param && parseInt(_param.mediaId) > 0) {
+	                releaseChannelId = parseInt(_param.mediaId);
+	            }
+
+	            //释放channelId 的占用
+	            if (releaseChannelId > 0) {
+	                //第一种情况,释放nodeId占用的指定mediaId (channelId)
+	                this._releaseChannelForNodeId(nodeId, releaseChannelId);
+	            } else {
+	                //第二种情况,释放nodeId占用的所有channelId
+	                this._releaseNodeIdAllChannel(nodeId);
+	            }
+	        }
+
+	        //释放nodeId占用的指定的channelId频道
+
+	    }, {
+	        key: '_releaseChannelForNodeId',
+	        value: function _releaseChannelForNodeId(nodeId, channelId) {
+	            loger.log(nodeId, "_releaseChannelForNodeId-->channelId", channelId);
+	            var channelInfo = this.mediaModule.mediaChannels[channelId];
+	            if (channelInfo && channelInfo.status == _ApeConsts2.default.CHANNEL_STATUS_OPENING) {
+	                if (channelInfo.fromNodeId == nodeId) {
+
+	                    var _channelInfo2 = this.mediaModule.getDefaultChannelInfo();
+	                    _channelInfo2.status = _ApeConsts2.default.CHANNEL_STATUS_RELEASED;
+	                    _channelInfo2.channelId = channelId;
+
+	                    this.sendTableUpdateHandler(_channelInfo2);
+	                } else {
+	                    loger.warn(channelId, "不属于nodeId", nodeId, "不能释放", channelInfo);
+	                }
+	            } else {
+	                loger.warn(nodeId, "要释放的channel不存在或者已经释放-->channelId", channelInfo);
+	            }
+	        }
+	        //释放nodeId占用的所有频道
+
+	    }, {
+	        key: '_releaseNodeIdAllChannel',
+	        value: function _releaseNodeIdAllChannel(nodeId) {
+	            loger.log(nodeId, "_releaseNodeIdAllChannel", this.mcu.connected);
+	            if (!this.mcu.connected) {
+	                loger.warn(_GlobalConfig2.default.getCurrentStatus());
+	                return { "code": _ApeConsts2.default.RETURN_FAILED, "data": "已经断开连接" };
+	            }
+
+	            var openingChannel = this.mediaModule.getOpeningMediaChannel(nodeId);
+	            if (openingChannel == 0) {
+	                loger.warn(nodeId, "没有占用channel不需要处理");
+	                return { "code": _ApeConsts2.default.RETURN_FAILED, "data": "没有占用channel不需要处理" };
+	            }
+
+	            var channelInfo = this.mediaModule.getDefaultChannelInfo();
+	            channelInfo.status = _ApeConsts2.default.CHANNEL_STATUS_RELEASED;
+	            channelInfo.channelId = openingChannel;
+
+	            this.sendTableUpdateHandler(channelInfo);
+	            //递归检查,800毫秒之后执行
+	            setTimeout(function () {
+	                loger.warn(nodeId, "递归检查频道是否占用");
+	                this._releaseNodeIdAllChannel(nodeId);
+	            }.bind(this), 800);
+	        }
+	    }, {
+	        key: 'sendAudioBroadcastMsg',
+	        value: function sendAudioBroadcastMsg(_param) {
+	            loger.log('sendAudioBroadcastMsg', _param);
+	            if (!this.mcu.connected) {
+	                loger.warn(_GlobalConfig2.default.getCurrentStatus());
+	                return { "code": _ApeConsts2.default.RETURN_FAILED, "data": "已经断开连接" };
+	            }
+	            if (this._classInfo === null || _EngineUtils2.default.isEmptyObject(this._classInfo)) {
+	                loger.log('sendAudioBroadcastMsg.McuClient还未初始化数据!');
+	                if (_GlobalConfig2.default.getCurrentStatus().code == 0 || _GlobalConfig2.default.getCurrentStatus().code == 1) {
+	                    this._emit(_MessageTypes2.default.MCU_ERROR, _MessageTypes2.default.ERR_APE_SEND_FAILED_NO_JOIN);
+	                    return { "code": _ApeConsts2.default.RETURN_FAILED, "data": "sendAudioBroadcastMsg.McuClient还未初始化数据" };
+	                }
+	                return { "code": _ApeConsts2.default.RETURN_FAILED, "data": "sendAudioBroadcastMsg.McuClient还未初始化数据" };
+	            }
+	            if (_param == null) {
+	                loger.warn('sendAudioBroadcastMsg,参数错误', _param);
+	                this._emit(_MessageTypes2.default.MCU_ERROR, _MessageTypes2.default.ERR_APE_INTERFACE_PARAM_WRONG);
+	                return { "code": _ApeConsts2.default.RETURN_FAILED, "data": "sendAudioBroadcastMsg,参数错误" };
+	            }
+	            // to, message
+	            loger.log('发送Audio消息.', _param);
+
+	            if (_param.actionType != null && _param.actionType == _ApeConsts2.default.MEDIA_ACTION_OPEN_CAMERA) {
+	                //判断当前开启的视频数量是否已经是最大值,如果已经是最大值,不能再开启
+	                var freeChannel = this.mediaModule.getFreeMediaChannel();
+	                if (freeChannel == 0) {
+	                    loger.warn('sendAudioBroadcastMsg,不能再打开更多的设备', _param);
+	                    return { "code": _ApeConsts2.default.RETURN_FAILED, "data": "不能再打开更多的设备", "mediaChannels": this.mediaModule.mediaChannels };
+	                }
+	            }
+
+	            var audioSendPdu = new _pdus2.default['RCAudioSendDataRequestPdu']();
+	            audioSendPdu.type = _pdus2.default.RCPDU_SEND_AUDIO_DATA_REQUEST;
+	            audioSendPdu.isPublic = true;
+
+	            audioSendPdu.fromNodeId = _GlobalConfig2.default.nodeId; //发起人
+	            audioSendPdu.toNodeId = parseInt(_param.toNodeId) || 0; //接收者,0就是所有人
+	            audioSendPdu.actionType = parseInt(_param.actionType) || _ApeConsts2.default.MEDIA_ACTION_DEFAULT;
+
+	            audioSendPdu.data = this._rCArrayBufferUtil.strToUint8Array("h5" + _param.data); //开头两个字会乱码
+
+	            if (!audioSendPdu.isPublic && 0 != audioSendPdu.toNodeId) {
+	                //发送给制定的人
+	                loger.log('发送私聊消息.');
+	                this.send(audioSendPdu);
+	            } else {
+	                //发送给所有人
+	                loger.log('发送公聊消息.');
+	                this.sendChatUniform(audioSendPdu);
+	            }
+	            return { "code": _ApeConsts2.default.RETURN_SUCCESS, "data": "" };
+	        }
+	    }, {
+	        key: 'sendTableUpdateHandler',
+	        value: function sendTableUpdateHandler(_channelInfo) {
+	            loger.log("audio,sendTableUpdateHandler ");
+	            var updateModelPdu = this.packPdu(_channelInfo, _channelInfo.channelId);
+	            if (updateModelPdu == null) {
+	                loger.warn("sendTableUpdateHandler error,updateModelPdu=null");
+	                return;
+	            }
+
+	            var tableItemPdu = new _pdus2.default['RCRegistryTableItemPdu']();
+	            tableItemPdu.itemIdx = _channelInfo.channelId;
+	            tableItemPdu.owner = _channelInfo.owner; //0收到flash的是这个值,MCU做了了用户掉线处理,30秒之后会清理owner为0
+	            tableItemPdu.itemData = updateModelPdu.toArrayBuffer();
+
+	            //insert
+	            var tableInsertItemPdu = new _pdus2.default['RCRegistryTableUpdateItemPdu']();
+	            tableInsertItemPdu.type = _pdus2.default.RCPDU_REG_TABLE_UPDATE_PDU;
+	            tableInsertItemPdu.items.push(tableItemPdu);
+
+	            var updateObjPdu = new _pdus2.default['RCRegistryUpdateObjPdu']();
+	            updateObjPdu.objId = _ApeConsts2.default.AUDIO_OBJ_TABLE_ID; //
+	            updateObjPdu.subType = tableInsertItemPdu.type;
+	            updateObjPdu.userData = tableInsertItemPdu.toArrayBuffer();
+
+	            //同步
+	            var adapterItemPdu = new _pdus2.default['RCAdapterItemPdu']();
+	            adapterItemPdu.type = _pdus2.default.RCPDU_REG_UPDATE_OBJ;
+	            adapterItemPdu.itemData = updateObjPdu.toArrayBuffer();
+
+	            var adapterPdu = new _pdus2.default['RCAdapterPdu']();
+	            adapterPdu.type = _pdus2.default.RCPDU_REG_ADAPTER;
+	            adapterPdu.item.push(adapterItemPdu);
+
+	            loger.log("发送更新AUDIO.itemIdx=" + tableItemPdu.itemIdx);
+	            this.sendUniform(adapterPdu, true);
+	        }
+
+	        /////收到消息处理//////////////////////////////////////////////////
+
+	        // 消息处理,内部处理,不需要告诉应用层
+
+	    }, {
+	        key: 'receiveAudiooCommandHandler',
+	        value: function receiveAudiooCommandHandler(_data) {
+	            var audioReceivePdu = _pdus2.default['RCAudioSendDataRequestPdu'].decode(_data);
+	            if (audioReceivePdu == null) {
+	                loger.warn("音频消息处理,收到的消息为null,不做处理");
+	                return;
+	            }
+	            audioReceivePdu.data = this._rCArrayBufferUtil.uint8ArrayToStr(audioReceivePdu.data, 2); //开头两个字会乱码
+	            loger.log('音频消息处理 receiveAudiooCommandHandler.', audioReceivePdu);
+
+	            //判断接收者的id,如果不是0,并且也不是自己的nodeId,那么消息不做处理
+	            if (audioReceivePdu.toNodeId != 0 && audioReceivePdu.toNodeId != _GlobalConfig2.default.nodeId) {
+	                loger.log('音频消息不处理 toNodeId=', audioReceivePdu.toNodeId, "my nodeId=", _GlobalConfig2.default.nodeId);
+	            } else {
+	                this._emit(_MessageTypes2.default.AUDIO_BROADCAST, audioReceivePdu);
+	            }
+	        }
+	    }, {
+	        key: 'tableUpdateHandler',
+	        value: function tableUpdateHandler(owner, itemIdx, itemData) {
+	            var unpackChannelInfo = this.unPackPdu(owner, itemIdx, itemData);
+	            loger.log("tableUpdateHandler,channel", itemIdx);
+
+	            //****很重要********
+	            //如果owner的值为0,代表的是这个歌频道已经被释放了(mcu服务端对于占用channel的掉线用户,就是把owner设置为0)
+	            if (owner == 0) {
+	                loger.log("释放占用的频道,channel", itemIdx);
+	                unpackChannelInfo.status = _ApeConsts2.default.CHANNEL_STATUS_RELEASED;
+	                unpackChannelInfo.streamId = "";
+	            }
+
+	            this.mediaModule.mediaChannels[itemIdx] = unpackChannelInfo;
+
+	            if (unpackChannelInfo && unpackChannelInfo.fromNodeId != _GlobalConfig2.default.nodeId) {
+	                var receiveChannelInfo = {};
+	                receiveChannelInfo.mediaId = unpackChannelInfo.channelId;
+
+	                //消息不是自己同步的,需要处理
+	                if (unpackChannelInfo.status == _ApeConsts2.default.CHANNEL_STATUS_OPENING) {
+	                    //正在推流
+	                    receiveChannelInfo.m3u8Url = "";
+	                    receiveChannelInfo.rtmpUrl = "";
+	                    var m3u8Stream = this.mediaModule.getMediaPlayPath({ "type": "m3u8", "streamId": unpackChannelInfo.streamId });
+	                    var rtmpStream = this.mediaModule.getMediaPlayPath({ "type": "rtmp", "streamId": unpackChannelInfo.streamId });
+
+	                    if (m3u8Stream.code == 0) {
+	                        receiveChannelInfo.m3u8Url = m3u8Stream.playUrl;
+	                    }
+	                    if (rtmpStream.code == 0) {
+	                        receiveChannelInfo.rtmpUrl = rtmpStream.playUrl;
+	                    }
+	                    loger.log("AUDIO_PLAY", receiveChannelInfo);
+	                    //广播播放视频的消息
+	                    this._emit(_MessageTypes2.default.AUDIO_PLAY, receiveChannelInfo);
+	                } else {
+	                    loger.log("AUDIO_STOP", receiveChannelInfo);
+	                    //流已经停止
+	                    this._emit(_MessageTypes2.default.AUDIO_STOP, receiveChannelInfo);
+	                }
+	            } else {
+	                loger.warn("消息是自己发送的或者是消息无效,不需要处理,消息内容如下:");
+	                console.log(unpackChannelInfo);
+	            }
+	            this._emit(_MessageTypes2.default.AUDIO_UPDATE, unpackChannelInfo);
+	        }
+
+	        ///////数据的封包和解包/////////////////////////////////////////
+
+	    }, {
+	        key: 'packPdu',
+	        value: function packPdu(_param, _itemIdx) {
+	            loger.log("packPdu ");
+	            //验证坐标点集合数组是否合法
+	            if (_param == null || _itemIdx == null) {
+	                this._emit(_MessageTypes2.default.MCU_ERROR, _MessageTypes2.default.ERR_APE_INTERFACE_PARAM_WRONG);
+	                return null;
+	            }
+
+	            //判断type类型,根据type设置不同的参数
+	            var packPduModel = new _pdus2.default['RCAudioChannelInfoPdu']();
+	            packPduModel.status = _param.status || _ApeConsts2.default.CHANNEL_STATUS_RELEASED;
+	            packPduModel.channelId = _itemIdx;
+	            packPduModel.streamId = _param.streamId || "";
+	            packPduModel.siteId = _param.siteId || _GlobalConfig2.default.siteId; //GlobalConfig.siteId;
+	            packPduModel.classId = parseInt(_param.classId) || parseInt(_GlobalConfig2.default.classId);
+	            packPduModel.userId = _param.userId || "0";
+	            packPduModel.mediaType = _param.mediaType || _ApeConsts2.default.MEDIA_TYPE_AUDIO;
+	            packPduModel.timestamp = _param.timestamp || _EngineUtils2.default.creatTimestamp();
+	            packPduModel.fromNodeId = _GlobalConfig2.default.nodeId;
+	            packPduModel.toNodeId = 0;
+	            console.log("packPdu", packPduModel);
+	            return packPduModel;
+	        }
+	    }, {
+	        key: 'unPackPdu',
+	        value: function unPackPdu(owner, itemIdx, itemData) {
+	            loger.log("unPackPdu ");
+	            if (owner == null || itemIdx == null || itemData == null) {
+	                this._emit(_MessageTypes2.default.MCU_ERROR, _MessageTypes2.default.ERR_APE_INTERFACE_PARAM_WRONG);
+	                return null;
+	            }
+	            try {
+	                var packChannelInfo = _pdus2.default['RCAudioChannelInfoPdu'].decode(itemData);
+	                console.log(packChannelInfo);
+	                return packChannelInfo;
+	            } catch (err) {
+	                loger.log("unPackPdu error,itemIdx=" + itemIdx + "  err:" + err.message);
+	            }
+	            return null;
+	        }
+	    }]);
+
+	    return AudioApe;
+	}(_Ape3.default);
+
+	var _default = AudioApe;
+	exports.default = _default;
+	;
+
+	var _temp = function () {
+	    if (typeof __REACT_HOT_LOADER__ === 'undefined') {
+	        return;
+	    }
+
+	    __REACT_HOT_LOADER__.register(loger, 'loger', 'D:/work/McuClient/src/apes/AudioApe.js');
+
+	    __REACT_HOT_LOADER__.register(AudioApe, 'AudioApe', 'D:/work/McuClient/src/apes/AudioApe.js');
+
+	    __REACT_HOT_LOADER__.register(_default, 'default', 'D:/work/McuClient/src/apes/AudioApe.js');
+	}();
+
+	;
+
+/***/ },
+/* 45 */
+/***/ function(module, exports, __webpack_require__) {
+
+	'use strict';
+
+	Object.defineProperty(exports, "__esModule", {
+	  value: true
+	});
+
+	var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
+
+	var _Ape2 = __webpack_require__(37);
+
+	var _Ape3 = _interopRequireDefault(_Ape2);
+
+	var _ApeConsts = __webpack_require__(8);
+
+	var _ApeConsts2 = _interopRequireDefault(_ApeConsts);
+
+	var _pdus = __webpack_require__(25);
+
+	var _pdus2 = _interopRequireDefault(_pdus);
+
+	var _Loger = __webpack_require__(5);
+
+	var _Loger2 = _interopRequireDefault(_Loger);
+
+	var _MessageTypes = __webpack_require__(6);
+
+	var _MessageTypes2 = _interopRequireDefault(_MessageTypes);
+
+	var _GlobalConfig = __webpack_require__(7);
+
+	var _GlobalConfig2 = _interopRequireDefault(_GlobalConfig);
+
+	var _EngineUtils = __webpack_require__(9);
+
+	var _EngineUtils2 = _interopRequireDefault(_EngineUtils);
+
+	function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
+
+	function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
+
+	function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }
+
+	function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } // //////////////////////////////////////////////////////////////////////////////
+	//
+	//  Copyright (C) 2016-present  All Rights Reserved.
+	//  Licensed under the Apache License, Version 2.0 (the "License");
+	//  http://www.apache.org/licenses/LICENSE-2.0
+	//
+	//  Github Home: https://github.com/AlexWang1987
+	//  Author: AlexWang
+	//  Date: 2016-08-26 17:30:43
+	//  QQ Email: 1669499355@qq.com
+	//  Last Modified time: 2016-09-21 14:12:25
+	//  Description: LiveClass-DocApe
+	//
+	// //////////////////////////////////////////////////////////////////////////////
+
+	var loger = _Loger2.default.getLoger('DocApe');
+	var itemIdx = 0; //table插入新数据的计数id,目前用时间戳
+
+	var DocApe = function (_Ape) {
+	  _inherits(DocApe, _Ape);
+
+	  function DocApe() {
+	    _classCallCheck(this, DocApe);
+
+	    var _this = _possibleConstructorReturn(this, (DocApe.__proto__ || Object.getPrototypeOf(DocApe)).call(this, _ApeConsts2.default.DOCSHARING_SESSION_ID, _ApeConsts2.default.DOCSHARING_SESSION_NAME, _ApeConsts2.default.DOCSHARING_SESSION_TAG));
+
+	    _this.docList = {}; //记录文档的数组this.docList[itemIdx]=itemIdx的数据
+	    //this.activeDocItemIdx =0;//当前激活的文档itemIdx
+	    //this.activeDocCurPage=1;//当前激活的文档的当前页
+	    // 延迟
+	    _this._apeDelayed = false;
+
+	    // Ape Models
+	    _this.registerKey(_this._session_id, _this._session_name, _this._session_tag, new ArrayBuffer());
+	    _this.registerObj(_pdus2.default.RCPDU_REG_REGISTER_TABLE, _ApeConsts2.default.DOCSHARING_OBJ_TABLE_ID, _ApeConsts2.default.DOCSHARING_OBJ_TABLE_NAME, _ApeConsts2.default.DOCSHARING_OBJ_TABLE_TAG, 0, new ArrayBuffer());
+	    //this.registerObj(pdu.RCPDU_REG_REGISTER_TABLE, ApeConsts.DOCSHARING_OBJ_TABLE_ID_H5, ApeConsts.DOCSHARING_OBJ_TABLE_NAME_H5, ApeConsts.DOCSHARING_OBJ_TABLE_TAG_H5, 0, new ArrayBuffer);
+	    return _this;
+	  }
+
+	  /////////////发送数据操作//////////////////////////////////////////////////////
+	  //上传文档
+
+
+	  _createClass(DocApe, [{
+	    key: 'documentUpload',
+	    value: function documentUpload(paramInfo) {
+	      if (paramInfo == null || _EngineUtils2.default.isEmptyObject(paramInfo)) {
+	        loger.log('documentUpload失败,参数错误');
+	        this._emit(_MessageTypes2.default.MCU_ERROR, _MessageTypes2.default.ERR_APE_INTERFACE_PARAM_WRONG);
+	        return;
+	      }
+	      //判断文档是否已经存在,每个文档都有唯一的docId,如果已经同步的文档中存在相同的docId就不需要再同步
+	      if (this.checkDocId(paramInfo.docId)) {
+	        //文档已经存在相同的docId,不需要同步上传
+	        loger.warn('documentUpload  文档的docId不无效或已经存在相同的docId,不需要上传');
+	        return;
+	      }
+
+	      itemIdx = _EngineUtils2.default.creatSoleNumberFromTimestamp();
+	      var docDataModelPdu = this.packPdu(paramInfo, itemIdx);
+	      if (docDataModelPdu == null) {
+	        loger.log('documentUpload失败,参数错误');
+	        this._emit(_MessageTypes2.default.MCU_ERROR, _MessageTypes2.default.ERR_APE_INTERFACE_PARAM_WRONG);
+	        return;
+	      }
+	      //console.log(docDataModelPdu);
+
+	      var tableItemPdu = new _pdus2.default['RCRegistryTableItemPdu']();
+	      tableItemPdu.itemIdx = itemIdx; //直接用时间戳作为id
+	      tableItemPdu.registerObjId = _ApeConsts2.default.DOCSHARING_OBJ_TABLE_ID; // tableItemPdu.registerObjId=ApeConsts.DOCSHARING_OBJ_TABLE_ID_H5;
+	      tableItemPdu.owner = 0; //收到flash的是这个值,不清楚先写固定
+	      tableItemPdu.itemData = docDataModelPdu.toArrayBuffer();
+
+	      //insert
+	      var tableInsertItemPdu = new _pdus2.default['RCRegistryTableInsertItemPdu']();
+	      //optional RCPduType_E type = 1 [default = RCPDU_REG_TABLE_UPDATE_PDU];
+	      //repeated RCRegistryTableItemPdu items = 2;
+	      tableInsertItemPdu.type = _pdus2.default.RCPDU_REG_TABLE_INSERT_PDU; //
+	      tableInsertItemPdu.items.push(tableItemPdu);
+
+	      var updateObjPdu = new _pdus2.default['RCRegistryUpdateObjPdu']();
+	      updateObjPdu.objId = _ApeConsts2.default.DOCSHARING_OBJ_TABLE_ID; // updateObjPdu.objId = ApeConsts.DOCSHARING_OBJ_TABLE_ID_H5;
+	      updateObjPdu.subType = tableInsertItemPdu.type;
+	      updateObjPdu.userData = tableInsertItemPdu.toArrayBuffer();
+
+	      //同步
+	      var adapterItemPdu = new _pdus2.default['RCAdapterItemPdu']();
+	      adapterItemPdu.type = _pdus2.default.RCPDU_REG_UPDATE_OBJ;
+	      adapterItemPdu.itemData = updateObjPdu.toArrayBuffer();
+
+	      var adapterPdu = new _pdus2.default['RCAdapterPdu']();
+	      adapterPdu.type = _pdus2.default.RCPDU_REG_ADAPTER;
+	      adapterPdu.item.push(adapterItemPdu);
+
+	      loger.log("文档upload  tableItemPdu.itemIdx=" + tableItemPdu.itemIdx);
+	      this.sendUniform(adapterPdu, true);
+	    }
+	  }, {
+	    key: 'updaterDoc',
+	    value: function updaterDoc(_docDataModel, _itemIdx) {
+	      loger.log("文档===updaterDoc ", _itemIdx);
+	      //验证坐标点集合数组是否合法
+	      if (_docDataModel == null || _itemIdx == null) {
+	        this._emit(_MessageTypes2.default.MCU_ERROR, _MessageTypes2.default.ERR_APE_INTERFACE_PARAM_WRONG);
+	        return null;
+	      }
+	      loger.log("文档===updaterDoc ", _docDataModel);
+
+	      var docDataModelPdu = this.packPdu(_docDataModel, _itemIdx);
+	      var tableItemPdu = new _pdus2.default['RCRegistryTableItemPdu']();
+	      tableItemPdu.itemIdx = _itemIdx; //直接用时间戳作为id
+	      //tableItemPdu.registerObjId=ApeConsts.DOCSHARING_OBJ_TABLE_ID;// tableItemPdu.registerObjId=ApeConsts.DOCSHARING_OBJ_TABLE_ID_H5;
+	      tableItemPdu.owner = 0; //收到flash的是这个值,不清楚先写固定
+	      tableItemPdu.itemData = docDataModelPdu.toArrayBuffer();
+
+	      //insert
+	      var tableInsertItemPdu = new _pdus2.default['RCRegistryTableUpdateItemPdu']();
+	      //optional RCPduType_E type = 1 [default = RCPDU_REG_TABLE_UPDATE_PDU];
+	      //repeated RCRegistryTableItemPdu items = 2;
+	      tableInsertItemPdu.type = _pdus2.default.RCPDU_REG_TABLE_UPDATE_PDU; //
+	      tableInsertItemPdu.items.push(tableItemPdu);
+
+	      var updateObjPdu = new _pdus2.default['RCRegistryUpdateObjPdu']();
+	      updateObjPdu.objId = _ApeConsts2.default.DOCSHARING_OBJ_TABLE_ID; // updateObjPdu.objId = ApeConsts.DOCSHARING_OBJ_TABLE_ID_H5;
+	      updateObjPdu.subType = tableInsertItemPdu.type;
+	      updateObjPdu.userData = tableInsertItemPdu.toArrayBuffer();
+
+	      //同步
+	      var adapterItemPdu = new _pdus2.default['RCAdapterItemPdu']();
+	      adapterItemPdu.type = _pdus2.default.RCPDU_REG_UPDATE_OBJ;
+	      adapterItemPdu.itemData = updateObjPdu.toArrayBuffer();
+
+	      var adapterPdu = new _pdus2.default['RCAdapterPdu']();
+	      adapterPdu.type = _pdus2.default.RCPDU_REG_ADAPTER;
+	      adapterPdu.item.push(adapterItemPdu);
+
+	      loger.log("发送更新文档.itemIdx=" + tableItemPdu.itemIdx);
+	      this.sendUniform(adapterPdu, true);
+	    }
+	    //获取文档的图片完整地址
+
+	  }, {
+	    key: 'getDocImageFullPath',
+	    value: function getDocImageFullPath(_param) {
+	      if (_param == null || _param.relativeUrl == null || _param.relativeUrl == "") {
+	        loger.warn('获取文档完整地址,传递的参数不对.', _param);
+	        return [];
+	      }
+
+	      var port = _GlobalConfig2.default.DOCServerPort == "" || _GlobalConfig2.default.DOCServerPort == null ? "" : ":" + _GlobalConfig2.default.DOCServerPort;
+	      var fullPath = _GlobalConfig2.default.DOCServerIP + port + _param.relativeUrl;
+	      if (fullPath && fullPath.indexOf("http://") < 0) {
+	        fullPath = "http://" + fullPath;
+	      }
+
+	      var fileType = "jpg";
+	      switch (_param.type) {
+	        case "jpg":
+	          fileType = "jpg";
+	          fullPath = this.replacePathType(fullPath) + ".jpg";
+	          break;
+	        case "png":
+	          fileType = "png";
+	          fullPath = this.replacePathType(fullPath) + ".png";
+	          break;
+	        default:
+	          //不做处理,直接返回拼接的地址
+	          break;
+	      }
+
+	      if (_param.pageNum && parseInt(_param.pageNum) > 1) {
+	        //如果是多页的,需要返回序列
+	        var lastIndex = fullPath.lastIndexOf("/");
+	        if (lastIndex > 0) {
+	          var newPath = fullPath.substr(0, lastIndex);
+	          var pathArr = [];
+	          //页数从1开始
+	          for (var i = 1; i <= _param.pageNum; i++) {
+	            pathArr.push(newPath + "/" + i + "." + fileType);
+	          }
+	          return pathArr;
+	        } else {
+	          return [fullPath];
+	        }
+	      } else {
+	        return [fullPath];
+	      }
+	    }
+
+	    //获取文档的pdf完整地址
+
+	  }, {
+	    key: 'getDocPDFFullPath',
+	    value: function getDocPDFFullPath(_param) {
+	      if (_param == null || _param.relativeUrl == null || _param.relativeUrl == "") {
+	        loger.warn('获取文档完整地址,传递的参数不对.', _param);
+	        return [];
+	      }
+	      var port = _GlobalConfig2.default.DOCServerPort == "" || _GlobalConfig2.default.DOCServerPort == null ? "" : ":" + _GlobalConfig2.default.DOCServerPort;
+	      var fullPath = _GlobalConfig2.default.DOCServerIP + port + _param.relativeUrl;
+	      if (fullPath && fullPath.indexOf("http://") < 0) {
+	        fullPath = "http://" + fullPath;
+	      }
+	      fullPath = this.replacePathType(fullPath) + ".pdf";
+	      return [fullPath];
+	    }
+
+	    // 去除文件的后缀格式名称
+
+	  }, {
+	    key: 'replacePathType',
+	    value: function replacePathType(_path) {
+	      var path = _path;
+	      path = path.replace(/.jpg/g, "");
+	      path = path.replace(/.png/g, "");
+	      path = path.replace(/.swf/g, "");
+	      path = path.replace(/.pdf/g, "");
+	      return path;
+	    }
+
+	    //切换文档
+
+	  }, {
+	    key: 'documentSwitchDoc',
+	    value: function documentSwitchDoc(paramInfo) {
+	      loger.log('切换文档,documentSwitchDoc');
+	      console.log(paramInfo);
+	      if (paramInfo == null || paramInfo.itemIdx == null) {
+	        loger.warn('documentSwitch失败,参数错误', paramInfo);
+	        this._emit(_MessageTypes2.default.MCU_ERROR, _MessageTypes2.default.ERR_APE_INTERFACE_PARAM_WRONG);
+	        return;
+	      }
+
+	      if (paramInfo.itemIdx == _GlobalConfig2.default.activeDocId && _GlobalConfig2.default.activeDocId != 0) {
+	        loger.warn('文档已经显示', paramInfo.itemIdx, _GlobalConfig2.default.activeDocId);
+	        return;
+	      }
+
+	      //更新切换之前的文档的数据,要显示当前切换的文档,上一个文档需要隐藏
+	      var oldDocModel = void 0;
+	      if (_GlobalConfig2.default.activeDocId != 0) {
+	        oldDocModel = this.docList[_GlobalConfig2.default.activeDocId];
+	        if (oldDocModel) {
+	          oldDocModel.action = _ApeConsts2.default.DOC_ACTION_NORMAL;
+	          oldDocModel.visible = false; //设置为不可见
+	        }
+	      }
+
+	      //获取已经存在的数据
+	      var docDataModel = this.docList[paramInfo.itemIdx];
+	      if (docDataModel == null) {
+	        loger.warn('documentSwitch失败,文档不存在', paramInfo);
+	        this._emit(_MessageTypes2.default.MCU_ERROR, _MessageTypes2.default.ERR_APE_INTERFACE_PARAM_WRONG);
+	        return;
+	      }
+
+	      //更新当前选择的文档数据的字段
+	      docDataModel.action = _ApeConsts2.default.DOC_ACTION_SWITCH_DOC;
+	      docDataModel.visible = paramInfo.visible || false; //默认是false
+
+	      //loger.log('切换文档,当前文档和上一个显示的文档都需要更新状态');
+	      console.log({ "oldDoc": oldDocModel, "nowDoc": docDataModel });
+	      //更新当前选择的文档
+	      this.updaterDoc(docDataModel, docDataModel.itemIdx);
+
+	      //更新上一个文档 隐藏
+	      if (oldDocModel) {
+	        this.updaterDoc(oldDocModel, oldDocModel.itemIdx);
+	      }
+	    }
+
+	    //文档翻页
+
+	  }, {
+	    key: 'documentSwitchPage',
+	    value: function documentSwitchPage(paramInfo) {
+	      loger.log('文档翻页,documentSwitchPage');
+	      console.log(paramInfo);
+	      //console.log(this.docList);
+	      //获取已经存在的数据
+	      var docDataModel = this.docList[paramInfo.itemIdx];
+	      //console.log(docDataModelPdu);
+	      //console.log(docDataModel);
+	      if (docDataModel == null) {
+	        loger.log('documentCommand失败,文档不存在', paramInfo);
+	        this._emit(_MessageTypes2.default.MCU_ERROR, _MessageTypes2.default.ERR_APE_INTERFACE_PARAM_WRONG);
+	        return;
+	      }
+	      //更新数据的字段
+	      docDataModel.action = _ApeConsts2.default.DOC_ACTION_SWITCH_PAGE;
+	      docDataModel.curPageNo = parseInt(paramInfo.curPageNo) || 1;
+	      if (docDataModel.curPageNo < 1) {
+	        docDataModel.curPageNo = 1; //默认值最小是1
+	      }
+	      this.updaterDoc(docDataModel, docDataModel.itemIdx);
+	    }
+
+	    //缩放/滚动
+
+	  }, {
+	    key: 'documentCommand',
+	    value: function documentCommand(paramInfo) {
+	      //console.log(this.docList);
+	      //获取已经存在的数据
+	      var docDataModel = this.docList[paramInfo.itemIdx];
+	      //console.log(docDataModelPdu);
+	      //console.log(docDataModel);
+	      if (docDataModel == null) {
+	        loger.log('documentCommand失败,文档不存在', paramInfo);
+	        this._emit(_MessageTypes2.default.MCU_ERROR, _MessageTypes2.default.ERR_APE_INTERFACE_PARAM_WRONG);
+	        return;
+	      }
+	      //更新数据的字段
+	      docDataModel.action = _ApeConsts2.default.DOC_ACTION_COMMAND;
+	      //docDataModel.scale=parseInt(paramInfo.scale)||100;//默认是100(百分比)
+	      this.updaterDoc(docDataModel, docDataModel.itemIdx);
+	    }
+
+	    //删除所有文档
+
+	  }, {
+	    key: 'documentDeleteAll',
+	    value: function documentDeleteAll(_param) {
+	      for (var key in this.docList) {
+	        //console.log("key:"+key);
+	        loger.log("删除文档数据,itemIdx:" + key);
+	        this.documentDelete({ "itemIdx": key });
+	      }
+	    }
+
+	    //删除文档
+
+	  }, {
+	    key: 'documentDelete',
+	    value: function documentDelete(paramInfo) {
+	      //{"itemIdx":itemIdx}
+	      var tableDeleteItemPdu = new _pdus2.default['RCRegistryTableDeleteItemPdu']();
+	      //optional RCPduType_E type = 1 [default = RCPDU_REG_TABLE_DELETE_PDU];
+	      // repeated uint32 item_idx = 2;
+	      tableDeleteItemPdu.type = _pdus2.default.RCPDU_REG_TABLE_DELETE_PDU; //
+	      tableDeleteItemPdu.itemIdx = parseInt(paramInfo.itemIdx); //这里需要设置要删除的数据的itemIdx,每条数据的这个id都不一样
+
+	      var updateObjPdu = new _pdus2.default['RCRegistryUpdateObjPdu']();
+	      updateObjPdu.objId = _ApeConsts2.default.DOCSHARING_OBJ_TABLE_ID; //    updateObjPdu.objId = ApeConsts.DOCSHARING_OBJ_TABLE_ID_H5;
+	      updateObjPdu.subType = tableDeleteItemPdu.type;
+	      updateObjPdu.userData = tableDeleteItemPdu.toArrayBuffer();
+
+	      //同步
+	      var adapterItemPdu = new _pdus2.default['RCAdapterItemPdu']();
+	      adapterItemPdu.type = _pdus2.default.RCPDU_REG_UPDATE_OBJ;
+	      adapterItemPdu.itemData = updateObjPdu.toArrayBuffer();
+
+	      var adapterPdu = new _pdus2.default['RCAdapterPdu']();
+	      adapterPdu.type = _pdus2.default.RCPDU_REG_ADAPTER;
+	      adapterPdu.item.push(adapterItemPdu);
+
+	      loger.log("文档发送删除数据=============" + tableDeleteItemPdu.itemIdx);
+	      this.sendUniform(adapterPdu, true);
+	    }
+
+	    /////收到消息处理/////////////////////////////////////////////////////////////////////////////////
+
+	  }, {
+	    key: 'tableInsertHandler',
+	    value: function tableInsertHandler(owner, itemIdx, itemData) {
+	      //this.tableUpdateHandler(owner, tableId, itemData);
+	      //loger.log('tableInsertHandler---',itemData);
+	      var itemDataInfo = this.unPackPdu(owner, itemIdx, itemData);
+	      this.docList[itemIdx] = itemDataInfo;
+
+	      if (itemDataInfo.visible == "true" || itemDataInfo.visible == true) {
+	        _GlobalConfig2.default.activeDocId = itemDataInfo.itemIdx; //当前激活的文档ID
+	        _GlobalConfig2.default.activeDocCurPage = itemDataInfo.curPageNo; //当前激活的文档的当前页
+	        loger.log('tableInsertHandler 设置当前激活的文档id');
+	      }
+
+	      loger.log('tableInsertHandler 发送给客户端');
+	      console.log(itemDataInfo);
+	      this._emit(_MessageTypes2.default.DOC_UPDATE, itemDataInfo); //用添加和更新都统一DOC_UPDATE
+	    }
+	  }, {
+	    key: 'tableDeleteHandler',
+	    value: function tableDeleteHandler(object_id, tableDeleteData) {
+	      /*const re={};
+	      re.type=ApeConsts.DOCUMENT_DEL;
+	      this._emit(MessageTypes.DOC_DELETE, re);*/
+
+	      loger.log('tableDeleteHandler', object_id, tableDeleteData); //["tableDeleteHandler",1179649,{"type":231,"itemIdx":[1486301768]}]
+	      if (tableDeleteData && tableDeleteData.itemIdx) {
+	        var len = tableDeleteData.itemIdx.length;
+	        var itemIdxs = tableDeleteData.itemIdx;
+	        for (var i = 0; i < len; i++) {
+	          if (this.docList[itemIdxs[i]]) {
+	            loger.log("删除文档数据:", itemIdxs[i]);
+	            this._emit(_MessageTypes2.default.DOC_DELETE, itemIdxs[i]);
+	            var itemDataInfo = this.docList[itemIdxs[i]];
+	            if (itemDataInfo && (itemDataInfo.visible == "true" || itemDataInfo.visible == true)) {
+	              _GlobalConfig2.default.activeDocId = 0; //当前激活的文档ID
+	              _GlobalConfig2.default.activeDocCurPage = 1; //当前激活的文档的当前页
+	              loger.log('tableDeleteHandler 设置当前激活的文档id');
+	            }
+	            delete this.docList[itemIdxs[i]];
+	          }
+	        }
+	      }
+	    }
+	  }, {
+	    key: 'tableUpdateHandler',
+	    value: function tableUpdateHandler(owner, itemIdx, itemData) {
+	      //let itemDataInfo = pdu['RCDocSendDataRequestPdu'].decode(itemData);
+	      var itemDataInfo = this.unPackPdu(owner, itemIdx, itemData);
+	      if (itemDataInfo != null) {
+	        this.docList[itemIdx] = itemDataInfo;
+
+	        /*      switch (itemDataInfo.action){
+	                case DOC_ACTION_SWITCH_DOC:
+	                  break;
+	                case DOC_ACTION_COMMAND:
+	                      break;
+	                case DOC_ACTION_NORMAL:
+	                      break;
+	                default :
+	                      break;
+	              }*/
+	        //GlobalConfig.activeDocId=0;//默认id
+	        // GlobalConfig.activeDocCurPage=1;//默认页数
+	        if (itemDataInfo && (itemDataInfo.visible == "true" || itemDataInfo.visible == true)) {
+	          _GlobalConfig2.default.activeDocId = itemDataInfo.itemIdx; //当前激活的文档ID
+	          _GlobalConfig2.default.activeDocCurPage = itemDataInfo.curPageNo; //当前激活的文档的当前页
+	          loger.log('tableUpdateHandler 设置当前激活的文档id');
+	        }
+	        loger.log('tableUpdateHandler 发送给客户端');
+	        console.log(itemDataInfo);
+	        this._emit(_MessageTypes2.default.DOC_UPDATE, itemDataInfo);
+	      } else {
+	        loger.log('tableUpdateHandler 数据无效--> itemIdx', itemIdx);
+	      }
+
+	      /*try {
+	        const recordInfo = pdu['RCDocSendDataRequestPdu'].decode(itemData);
+	        recordInfo.type = ApeConsts.DOCUMENT_LOAD;
+	          recordInfo.ext = recordInfo.name.substr(recordInfo.name.indexOf('.') + 1);
+	        recordInfo.wbid = (recordInfo.id << 10) + recordInfo.curPageNo;
+	        recordInfo.isPicture = ~['bmp', 'png', 'gif', 'jpg', 'jpeg'].indexOf(recordInfo.ext);
+	          if (recordInfo.isPicture) {
+	          recordInfo.namePath = recordInfo.uri.substring(0, recordInfo.uri.lastIndexOf('.'));
+	          recordInfo.loadURL = recordInfo.namePath + '.' + recordInfo.ext;
+	        } else {
+	          recordInfo.namePath = recordInfo.uri.substring(0, recordInfo.uri.lastIndexOf('/'));
+	          recordInfo.loadURL = `${recordInfo.namePath}/${recordInfo.curPageNo}.jpg`;
+	        }
+	        this.docList[recordId] = recordInfo;
+	        this._emit(MessageTypes.DOC_UPDATE, recordInfo);
+	        loger.log('Doc update ->' + itemIdx);
+	      } catch (e) {
+	        loger.warn('Doc Table Update Decode包异常');
+	      }*/
+	    }
+	  }, {
+	    key: 'onJoinChannelHandlerSuccess',
+	    value: function onJoinChannelHandlerSuccess() {
+	      var _this2 = this;
+
+	      loger.log(this._session_name + ' onJoinChannelHandlerSuccess===========================');
+	      if (this._apeDelayed) {
+	        // this._apeDelayedMsgs.push(regBuffer);
+	        // this._apeDelayedStart();
+	        setTimeout(function () {
+	          _this2._emit(DocApe.DOC_JOIN_CHANNEL_SUCCESS);
+	        }, _GlobalConfig2.default.mcuDelay + _GlobalConfig2.default.docDelay || 12000 + _GlobalConfig2.default.docDelay);
+	      } else {
+	        setTimeout(function () {
+	          _this2._emit(DocApe.DOC_JOIN_CHANNEL_SUCCESS);
+	        }, _GlobalConfig2.default.docDelay);
+	      }
+	    }
+
+	    //检查文档是否已经存在,如果存在 返回true,否则返回false
+
+	  }, {
+	    key: 'checkDocId',
+	    value: function checkDocId(_docId) {
+	      if (_docId == null) {
+	        loger.warn('checkDocId  _docId参数为null');
+	        return true;
+	      }
+	      //遍历查找
+	      for (var key in this.docList) {
+	        var item = this.docList[key];
+	        loger.log('item.docId==============_docId', item.docId, _docId);
+	        if (item && item.docId == _docId) {
+	          return true;
+	        }
+	      }
+
+	      //储存的数据中没有查找到
+	      return false;
+	    }
+
+	    ///////数据的封包和解包/////////////////////////////////////////
+
+	  }, {
+	    key: 'packPdu',
+	    value: function packPdu(_param, _itemIdx) {
+	      loger.log("文档===packPdu ");
+	      //验证坐标点集合数组是否合法
+	      if (_param == null || _itemIdx == null) {
+	        this._emit(_MessageTypes2.default.MCU_ERROR, _MessageTypes2.default.ERR_APE_INTERFACE_PARAM_WRONG);
+	        return null;
+	      }
+
+	      /*   message RCDocSendDataModelPdu {
+	       required uint32 item_idx=1;//唯一标识
+	       required uint32 owner=2;
+	       optional uint32 from=3;
+	       optional uint32 cur_page_no=4;
+	       optional uint32 page_num  =5;
+	       optional string file_type=6;
+	       optional string creat_user_id=7;//创建文档userid
+	       optional string relative_url=8;//文档相对地址
+	       optional string url  =9;//文档地址
+	       optional uint32 cur_V=10;
+	       optional uint32 cur_H=11;
+	       optional uint32 scale=12;
+	       optional bool visible=13;
+	       optional uint32 action=14;//0,无操作, 1翻页 /缩放/滚动、2.显示/隐藏
+	       optional string doc_id=15;//文档在服务器数据库中的唯一id
+	       optional string file_name=16;//文档的名字
+	       optional string dynamic_TS=17;//"dynamicTransferStatic": "0"
+	       optional string md5=18;//md5
+	       }*/
+
+	      //判断type类型,根据type设置不同的参数
+	      var docModelPdu = new _pdus2.default['RCDocSendDataModelPdu']();
+	      docModelPdu.itemIdx = _itemIdx;
+	      docModelPdu.owner = _GlobalConfig2.default.nodeId;
+	      docModelPdu.from = _GlobalConfig2.default.nodeId;
+	      docModelPdu.curPageNo = _param.curPageNo || 1;
+	      docModelPdu.pageNum = _param.pageNum || 1;
+	      docModelPdu.fileType = _param.fileType || "";
+	      docModelPdu.creatUserId = _param.creatUserId || "0";
+	      docModelPdu.url = _param.url || ""; //"http://101.200.150.192/DocSharing/data/h5test/20170206-171100025/7e9c4178cac1133e0dd9d5b583439122.jpg";
+	      docModelPdu.relativeUrl = _param.relativeUrl || ""; //"/DocSharing/data/h5test/20170206-171100025/7e9c4178cac1133e0dd9d5b583439122.jpg";
+	      docModelPdu.curV = _param.curV || 0;
+	      docModelPdu.curH = _param.curH || 0;
+	      docModelPdu.scale = _param.scale || 100; //按百分比
+	      docModelPdu.visible = _param.visible || false;
+	      docModelPdu.action = _param.action || _ApeConsts2.default.DOC_ACTION_NORMAL; //0,无操作, 1翻页、2.显示/隐藏, 3缩放/滚动
+	      docModelPdu.docId = _param.docId || ""; //文档在服务器数据库中的唯一id,必须有
+	      docModelPdu.md5 = _param.md5 || ""; //MD5
+	      docModelPdu.fileName = _param.fileName || "doc_" + _itemIdx; //文档的名字
+	      docModelPdu.dynamicTS = _param.dynamicTS || "0"; //文档上传后返回值中的字段dynamicTransferStatic
+	      console.log(docModelPdu);
+	      return docModelPdu;
+	    }
+	  }, {
+	    key: 'unPackPdu',
+	    value: function unPackPdu(owner, itemIdx, itemData) {
+	      loger.log("文档===unPackPdu ");
+	      if (owner == null || itemIdx == null || itemData == null) {
+	        this._emit(_MessageTypes2.default.MCU_ERROR, _MessageTypes2.default.ERR_APE_INTERFACE_PARAM_WRONG);
+	        return null;
+	      }
+
+	      try {
+	        var docModelPdu = _pdus2.default['RCDocSendDataModelPdu'].decode(itemData);
+	        loger.log(docModelPdu);
+	        return docModelPdu;
+	      } catch (err) {
+	        loger.log("文档收到数据 unPackPdu Pdu解析错误,itemIdx=" + itemIdx + "  err:" + err.message);
+	      }
+	      return null;
+	    }
+	  }]);
+
+	  return DocApe;
+	}(_Ape3.default);
+
+	DocApe.prototype.DOC_JOIN_CHANNEL_SUCCESS = DocApe.DOC_JOIN_CHANNEL_SUCCESS = 'docServer.join.channel.success';
+	var _default = DocApe;
+	exports.default = _default;
+	;
+
+	var _temp = function () {
+	  if (typeof __REACT_HOT_LOADER__ === 'undefined') {
+	    return;
+	  }
+
+	  __REACT_HOT_LOADER__.register(loger, 'loger', 'D:/work/McuClient/src/apes/DocApe.js');
+
+	  __REACT_HOT_LOADER__.register(itemIdx, 'itemIdx', 'D:/work/McuClient/src/apes/DocApe.js');
+
+	  __REACT_HOT_LOADER__.register(DocApe, 'DocApe', 'D:/work/McuClient/src/apes/DocApe.js');
+
+	  __REACT_HOT_LOADER__.register(_default, 'default', 'D:/work/McuClient/src/apes/DocApe.js');
+	}();
+
+	;
+
+/***/ },
+/* 46 */
+/***/ function(module, exports, __webpack_require__) {
+
+	'use strict';
+
+	Object.defineProperty(exports, "__esModule", {
+	    value: true
+	});
+
+	var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
+
+	var _Ape2 = __webpack_require__(37);
+
+	var _Ape3 = _interopRequireDefault(_Ape2);
+
+	var _ApeConsts = __webpack_require__(8);
+
+	var _ApeConsts2 = _interopRequireDefault(_ApeConsts);
+
+	var _pdus = __webpack_require__(25);
+
+	var _pdus2 = _interopRequireDefault(_pdus);
+
+	var _Loger = __webpack_require__(5);
+
+	var _Loger2 = _interopRequireDefault(_Loger);
+
+	var _MessageTypes = __webpack_require__(6);
+
+	var _MessageTypes2 = _interopRequireDefault(_MessageTypes);
+
+	var _zlib = __webpack_require__(39);
+
+	var _utf = __webpack_require__(11);
+
+	var _utf2 = _interopRequireDefault(_utf);
+
+	var _GlobalConfig = __webpack_require__(7);
+
+	var _GlobalConfig2 = _interopRequireDefault(_GlobalConfig);
+
+	var _EngineUtils = __webpack_require__(9);
+
+	var _EngineUtils2 = _interopRequireDefault(_EngineUtils);
+
+	function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
+
+	function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
+
+	function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }
+
+	function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } // //////////////////////////////////////////////////////////////////////////////
+	//
+	//  Copyright (C) 2016-present  All Rights Reserved.
+	//  Licensed under the Apache License, Version 2.0 (the "License");
+	//  http://www.apache.org/licenses/LICENSE-2.0
+	//
+	//  Github Home: https://github.com/AlexWang1987
+	//  Author: AlexWang
+	//  Date: 2016-08-26 17:36:20
+	//  QQ Email: 1669499355@qq.com
+	//  Last Modified time: 2016-09-21 14:06:12
+	//  Description: LiveClass-WhiteBoardApe
+	//
+	// //////////////////////////////////////////////////////////////////////////////
+
+	var loger = _Loger2.default.getLoger('WhiteBoardApe');
+
+	var itemIdx = 0; //table插入新数据的计数id,目前用时间戳
+	var TYPE_BIGHT = 0; //任意线段
+	var TYPE_LINE = 1; //直线
+	var TYPE_RECT = 2; //矩形
+	var TYPE_CIRCLE = 3; //圆形
+	var TYPE_TEXT = 4; //文本
+
+	var WhiteBoardApe = function (_Ape) {
+	    _inherits(WhiteBoardApe, _Ape);
+
+	    function WhiteBoardApe() {
+	        _classCallCheck(this, WhiteBoardApe);
+
+	        // properties
+	        var _this = _possibleConstructorReturn(this, (WhiteBoardApe.__proto__ || Object.getPrototypeOf(WhiteBoardApe)).call(this, _ApeConsts2.default.WHITEBOARD_SESSION_ID, _ApeConsts2.default.WHITEBOARD_SESSION_NAME, _ApeConsts2.default.WHITEBOARD_SESSION_TAG));
+
+	        _this.annoInfos = {}; //储存所有的标注数据
+	        _this.insertHistory = []; //添加的白板记录,用于撤回操作
+	        // 白板延迟
+	        // this._apeDelayed = true;
+
+	        //Ape Models
+	        _this.registerKey(_this._session_id, _this._session_name, _this._session_tag, new ArrayBuffer());
+	        _this.registerObj(_pdus2.default.RCPDU_REG_REGISTER_TABLE, _ApeConsts2.default.WHITEBOARD_OBJ_TABLE_ID, _ApeConsts2.default.WHITEBOARD_OBJ_TABLE_NAME, _ApeConsts2.default.WHITEBOARD_OBJ_TABLE_TAG, 0, new ArrayBuffer());
+
+	        // ape listeners
+	        _this.on(_pdus2.default.RCPDU_SESSION_JOIN_RESPONSE, _this._joinSessionHandler.bind(_this));
+	        //this.on(pdu.RCPDU_CONFERENCE_SEND_DATA_REQUEST, this.whiteboardMsgComingHandler.bind(this));//这个是会议消息类型,flash里在使用这里不再使用,各个模块的消息由模块自己来处理
+	        return _this;
+	    }
+
+	    _createClass(WhiteBoardApe, [{
+	        key: '_joinSessionHandler',
+	        value: function _joinSessionHandler(_data) {
+	            loger.log("RCPDU_SESSION_JOIN_RESPONSE");
+	            this.insertHistory = [];
+	        }
+
+	        /////////////发送数据操作//////////////////////////////////////////////////////
+	        // 添加标注,发送信息
+
+	    }, {
+	        key: 'sendInsetAnnotaion',
+	        value: function sendInsetAnnotaion(_param) {
+	            if (_param == null || _EngineUtils2.default.isEmptyObject(_param)) {
+	                loger.log('sendInsetAnnotaion失败,参数错误');
+	                this._emit(_MessageTypes2.default.MCU_ERROR, _MessageTypes2.default.ERR_APE_INTERFACE_PARAM_WRONG);
+	                return;
+	            }
+
+	            itemIdx = _EngineUtils2.default.creatSoleNumberFromTimestamp();
+	            var whiteBoardModelPdu = this.packPdu(_param, itemIdx);
+	            if (whiteBoardModelPdu == null) {
+	                loger.log('sendInsetAnnotaion失败,参数错误');
+	                this._emit(_MessageTypes2.default.MCU_ERROR, _MessageTypes2.default.ERR_APE_INTERFACE_PARAM_WRONG);
+	                return;
+	            }
+	            //console.log(whiteBoardModelPdu);
+
+	            //储存记录,用于返回上一步操作
+	            this.insertHistory.push(whiteBoardModelPdu);
+
+	            var tableItemPdu = new _pdus2.default['RCRegistryTableItemPdu']();
+	            tableItemPdu.itemIdx = itemIdx; //直接用时间戳作为id
+	            tableItemPdu.registerObjId = _ApeConsts2.default.WHITEBOARD_OBJ_TABLE_ID;
+	            tableItemPdu.owner = 0; //收到flash的是这个值,不清楚先写固定
+	            tableItemPdu.itemData = whiteBoardModelPdu.toArrayBuffer();
+
+	            //insert
+	            var tableInsertItemPdu = new _pdus2.default['RCRegistryTableInsertItemPdu']();
+	            //optional RCPduType_E type = 1 [default = RCPDU_REG_TABLE_UPDATE_PDU];
+	            //repeated RCRegistryTableItemPdu items = 2;
+	            tableInsertItemPdu.type = _pdus2.default.RCPDU_REG_TABLE_INSERT_PDU; //
+	            tableInsertItemPdu.items.push(tableItemPdu);
+
+	            var updateObjPdu = new _pdus2.default['RCRegistryUpdateObjPdu']();
+	            updateObjPdu.objId = _ApeConsts2.default.WHITEBOARD_OBJ_TABLE_ID;
+	            updateObjPdu.subType = tableInsertItemPdu.type;
+	            updateObjPdu.userData = tableInsertItemPdu.toArrayBuffer();
+
+	            //同步
+	            var adapterItemPdu = new _pdus2.default['RCAdapterItemPdu']();
+	            adapterItemPdu.type = _pdus2.default.RCPDU_REG_UPDATE_OBJ;
+	            adapterItemPdu.itemData = updateObjPdu.toArrayBuffer();
+
+	            var adapterPdu = new _pdus2.default['RCAdapterPdu']();
+	            adapterPdu.type = _pdus2.default.RCPDU_REG_ADAPTER;
+	            adapterPdu.item.push(adapterItemPdu);
+
+	            console.log("添加白板数据=====itemIdx=" + tableItemPdu.itemIdx);
+	            this.sendUniform(adapterPdu, true);
+	        }
+	        //撤销上一步
+
+	    }, {
+	        key: 'sendGotoPrev',
+	        value: function sendGotoPrev() {
+	            loger.log("白板返回上一步");
+	            if (this.insertHistory == null || this.insertHistory.length < 1) {
+	                loger.warn("无法继续上一步操作,已经没有可以撤销的数据");
+	                return;
+	            }
+	            console.log(this.insertHistory);
+	            this.sendDeleteAnnotaion(this.insertHistory.pop());
+	        }
+	        //删除当前页码的所有标注
+
+	    }, {
+	        key: 'sendDeleteCurPageAnnotation',
+	        value: function sendDeleteCurPageAnnotation(_param) {
+	            for (var key in this.annoInfos) {
+	                var item = this.annoInfos[key];
+	                if (item && item.parentId == _GlobalConfig2.default.activeDocId && item.curPageNo == _GlobalConfig2.default.activeDocCurPage) {
+	                    loger.log("sendDeleteCurPageAnnotation 删除当前页面上的标注", key);
+	                    this.sendDeleteAnnotaion({ "itemIdx": key });
+	                }
+	            }
+	        }
+	        //删除所有标注
+
+	    }, {
+	        key: 'sendDeleteAllAnnotation',
+	        value: function sendDeleteAllAnnotation(_param) {
+	            for (var key in this.annoInfos) {
+	                this.sendDeleteAnnotaion({ "itemIdx": key });
+	            }
+	        }
+
+	        //删除标注,发送信息
+
+	    }, {
+	        key: 'sendDeleteAnnotaion',
+	        value: function sendDeleteAnnotaion(_param) {
+	            if (_param == null) {
+	                loger.warn("要删除的数据不存在");
+	                return;
+	            }
+	            //{"itemIdx":itemIdx}
+	            var tableDeleteItemPdu = new _pdus2.default['RCRegistryTableDeleteItemPdu']();
+	            //optional RCPduType_E type = 1 [default = RCPDU_REG_TABLE_DELETE_PDU];
+	            // repeated uint32 item_idx = 2;
+	            tableDeleteItemPdu.type = _pdus2.default.RCPDU_REG_TABLE_DELETE_PDU; //
+	            tableDeleteItemPdu.itemIdx = parseInt(_param.itemIdx); //这里需要设置要删除的数据的itemIdx,每条数据的这个id都不一样
+
+	            var updateObjPdu = new _pdus2.default['RCRegistryUpdateObjPdu']();
+	            updateObjPdu.objId = _ApeConsts2.default.WHITEBOARD_OBJ_TABLE_ID;
+	            updateObjPdu.subType = tableDeleteItemPdu.type;
+	            updateObjPdu.userData = tableDeleteItemPdu.toArrayBuffer();
+
+	            //同步
+	            var adapterItemPdu = new _pdus2.default['RCAdapterItemPdu']();
+	            adapterItemPdu.type = _pdus2.default.RCPDU_REG_UPDATE_OBJ;
+	            adapterItemPdu.itemData = updateObjPdu.toArrayBuffer();
+
+	            var adapterPdu = new _pdus2.default['RCAdapterPdu']();
+	            adapterPdu.type = _pdus2.default.RCPDU_REG_ADAPTER;
+	            adapterPdu.item.push(adapterItemPdu);
+
+	            console.log("白板发送删除数据=============" + tableDeleteItemPdu.itemIdx);
+	            this.sendUniform(adapterPdu, true);
+	        }
+
+	        //更新标注
+
+	    }, {
+	        key: 'sendUpdaterAnnotaion',
+	        value: function sendUpdaterAnnotaion(_param) {
+	            if (_param == null || _EngineUtils2.default.isEmptyObject(_param)) {
+	                loger.log('sendUpdaterAnnotaion失败,参数错误');
+	                this._emit(_MessageTypes2.default.MCU_ERROR, _MessageTypes2.default.ERR_APE_INTERFACE_PARAM_WRONG);
+	                return;
+	            }
+	            itemIdx = _param.itemIdx;
+	            var whiteBoardModelPdu = this.packPdu(_param, itemIdx);
+	            //console.log(whiteBoardModelPdu);
+
+	            if (whiteBoardModelPdu == null) {
+	                loger.log('sendInsetAnnotaion失败,参数错误');
+	                this._emit(_MessageTypes2.default.MCU_ERROR, _MessageTypes2.default.ERR_APE_INTERFACE_PARAM_WRONG);
+	                return;
+	            }
+
+	            var tableItemPdu = new _pdus2.default['RCRegistryTableItemPdu']();
+	            tableItemPdu.itemIdx = itemIdx;
+	            tableItemPdu.owner = 0; //收到flash的是这个值,不清楚先写固定
+	            tableItemPdu.registerObjId = _ApeConsts2.default.WHITEBOARD_OBJ_TABLE_ID;
+	            tableItemPdu.itemData = whiteBoardModelPdu.toArrayBuffer();
+
+	            //updater
+	            var whiteBoardUpdateItem = new _pdus2.default['RCRegistryTableUpdateItemPdu']();
+	            //optional RCPduType_E type = 1 [default = RCPDU_REG_TABLE_UPDATE_PDU];
+	            //repeated RCRegistryTableItemPdu items = 2;
+	            whiteBoardUpdateItem.type = _pdus2.default.RCPDU_REG_TABLE_UPDATE_PDU; //
+	            whiteBoardUpdateItem.items.push(tableItemPdu);
+
+	            var updateObjPdu = new _pdus2.default['RCRegistryUpdateObjPdu']();
+	            updateObjPdu.objId = _ApeConsts2.default.WHITEBOARD_OBJ_TABLE_ID;
+	            updateObjPdu.subType = whiteBoardUpdateItem.type;
+	            updateObjPdu.userData = whiteBoardUpdateItem.toArrayBuffer();
+
+	            //同步
+	            var adapterItemPdu = new _pdus2.default['RCAdapterItemPdu']();
+	            adapterItemPdu.type = _pdus2.default.RCPDU_REG_UPDATE_OBJ;
+	            adapterItemPdu.itemData = updateObjPdu.toArrayBuffer();
+
+	            var adapterPdu = new _pdus2.default['RCAdapterPdu']();
+	            adapterPdu.type = _pdus2.default.RCPDU_REG_ADAPTER;
+	            adapterPdu.item.push(adapterItemPdu);
+
+	            console.log("白板发送更新数据===============22");
+	            this.sendUniform(adapterPdu, true);
+	        }
+
+	        /////收到消息处理/////////////////////////////////////////////////////////////////////////////////
+	        /*  whiteboardMsgComingHandler(_data) {
+	         //flash    RCConferenceSendDataRequestPdu
+	         //loger.warn('whiteboardMsgComingHandler needs to be handled.');
+	         //const recordInfo = pdu['RCWhiteboardDataRequestPdu'].decode(pdu);
+	         //loger.log("whiteboardMsgComingHandler",recordInfo);
+	           let receiveInfo = pdu['RCConferenceSendDataRequestPdu'].decode(_data);
+	         loger.log("whiteboardMsgComingHandler",receiveInfo);
+	         }*/
+
+	    }, {
+	        key: 'tableInsertHandler',
+	        value: function tableInsertHandler(owner, itemIdx, itemData) {
+	            var whiteBoardModel = this.unPackPdu(owner, itemIdx, itemData);
+	            loger.log('tableInsertHandler');
+	            console.log(whiteBoardModel);
+	            if (whiteBoardModel) {
+	                if (_GlobalConfig2.default.activeDocId == whiteBoardModel.parentId && _GlobalConfig2.default.activeDocCurPage == whiteBoardModel.curPageNo) {
+	                    loger.log('WHITEBOARD_ANNOTAION_INSERT 显示到界面上', whiteBoardModel);
+	                    //this._emit(MessageTypes.WHITEBOARD_ANNOTAION_INSERT,whiteBoardModel);
+	                    this.insertAandShowAnnotaion(whiteBoardModel);
+	                }
+	            }
+	        }
+	    }, {
+	        key: 'tableUpdateHandler',
+	        value: function tableUpdateHandler(owner, itemIdx, itemData) {
+	            var whiteBoardModel = this.unPackPdu(owner, itemIdx, itemData);
+	            loger.log('tableUpdateHandler');
+	            console.log(whiteBoardModel);
+	            if (whiteBoardModel && whiteBoardModel.parentId == _GlobalConfig2.default.activeDocId && whiteBoardModel.curPageNo == _GlobalConfig2.default.activeDocCurPage) {
+	                this.updateAandShowAnnotaion();
+	            }
+	        }
+	    }, {
+	        key: 'tableDeleteHandler',
+	        value: function tableDeleteHandler(object_id, tableDeleteData) {
+	            // console.log("白板收到数据,tableDeleteHandler  object_id="+object_id);//((18<< 16) + 1)=1179649
+	            loger.log('tableDeleteHandler', object_id, tableDeleteData); //["tableDeleteHandler",1179649,{"type":231,"itemIdx":[1486301768]}]
+	            if (tableDeleteData && tableDeleteData.itemIdx) {
+	                var len = tableDeleteData.itemIdx.length;
+	                var itemIdxs = tableDeleteData.itemIdx;
+	                for (var i = 0; i < len; i++) {
+	                    if (this.annoInfos[itemIdxs[i]]) {
+	                        loger.log("删除白板数据:", itemIdxs[i]);
+	                        //this._emit(MessageTypes.WHITEBOARD_ANNOTAION_DELETE,{ "itemIdx":itemIdxs[i]});
+	                        delete this.annoInfos[itemIdxs[i]];
+	                    }
+	                }
+	            }
+	            this.updateAandShowAnnotaion();
+	        }
+	        //文档更新,白板也要更新
+
+	    }, {
+	        key: 'docUpdateHandler',
+	        value: function docUpdateHandler(_data) {
+	            loger.log("白板收到文档更新的消息docUpdateHandler");
+	            console.log(_data);
+
+	            //如果切换了文档或翻页,清除之前的添加步骤记录
+	            if (_data.action == _ApeConsts2.default.DOC_ACTION_SWITCH_DOC || _data.action == _ApeConsts2.default.DOC_ACTION_SWITCH_PAGE) {
+	                this.insertHistory = [];
+	            }
+	            this.updateAandShowAnnotaion();
+	        }
+	        //删除白板数据
+
+	    }, {
+	        key: 'docDeleteHandler',
+	        value: function docDeleteHandler(_parentId) {
+	            loger.log("白板收到文档删除的消息docDeleteHandler", _parentId);
+	            for (var key in this.annoInfos) {
+	                var item = this.annoInfos[key];
+	                if (item && item.parentId == _parentId) {
+	                    loger.log("文档删除,白板数据也删除,itemIdx:" + key, "_parentId:", _parentId);
+	                    this.sendDeleteAnnotaion({ "itemIdx": key });
+	                }
+	            }
+	        }
+	        //增量添加标注
+
+	    }, {
+	        key: 'insertAandShowAnnotaion',
+	        value: function insertAandShowAnnotaion(_item) {
+	            var annotaionItems = [_item];
+	            var updateObj = {
+	                "isFresh": false,
+	                "annotaionItems": annotaionItems
+	            };
+	            loger.log("WHITEBOARD_ANNOTATION_UPDATE", annotaionItems.length);
+	            this._emit(_MessageTypes2.default.WHITEBOARD_ANNOTATION_UPDATE, updateObj);
+	        }
+	        //整体更新并且显示标注
+
+	    }, {
+	        key: 'updateAandShowAnnotaion',
+	        value: function updateAandShowAnnotaion() {
+	            var annotaionItems = [];
+	            for (var key in this.annoInfos) {
+	                var item = this.annoInfos[key];
+	                if (item && item.parentId == _GlobalConfig2.default.activeDocId && item.curPageNo == _GlobalConfig2.default.activeDocCurPage) {
+	                    annotaionItems.push(item);
+	                    loger.log("显示和文档对应的白板数据docUpdateHandler itemIdx:", item.itemIdx, "doc itemIdx:", _GlobalConfig2.default.activeDocId, "curPageNo:", _GlobalConfig2.default.activeDocCurPage);
+	                } else {
+	                    //loger.log("不显示白板数据docUpdateHandler",item);
+	                }
+	            }
+	            var updateObj = {
+	                "isFresh": true,
+	                "annotaionItems": annotaionItems
+	            };
+	            loger.log("WHITEBOARD_ANNOTATION_UPDATE", annotaionItems.length);
+	            this._emit(_MessageTypes2.default.WHITEBOARD_ANNOTATION_UPDATE, updateObj);
+	        }
+
+	        ///////白板数据的封包和解包/////////////////////////////////////////
+
+	    }, {
+	        key: 'packPdu',
+	        value: function packPdu(_param, _itemIdx) {
+	            /* required uint32 type= 1;//白板类型
+	             required uint32 id= 2;//id 每一次绘制的唯一标识
+	             required uint32 initiator=3; //绘制来自谁
+	             optional string pointGroup=4; //坐标点集数组的JSON字符串
+	             optional string color= 5  [default = "#000000"]; //颜色
+	             optional uint32 thickness= 6 ;//线条粗细
+	             optional uint32 radius= 7;//园的半径
+	             optional uint32 fontSize= 8;//字体大小
+	             optional uint32 fontName= 9;//字体名称
+	             optional uint32 text= 10;//文本内容*/
+
+	            //验证坐标点集合数组是否合法
+	            if (_param.pointGroup == null || _param.pointGroup.length < 1) {
+	                this._emit(_MessageTypes2.default.MCU_ERROR, _MessageTypes2.default.ERR_APE_INTERFACE_PARAM_WRONG);
+	                return null;
+	            }
+	            //判断type类型,根据type设置不同的参数
+	            var whiteBoardModelPdu = new _pdus2.default['RCWhiteBoardDataModelPdu']();
+	            switch (_param.type) {
+	                case TYPE_BIGHT:
+	                    break;
+	                case TYPE_LINE:
+	                    break;
+	                case TYPE_RECT:
+	                    break;
+	                case TYPE_CIRCLE:
+	                    whiteBoardModelPdu.radius = parseInt(_param.radius);
+	                    break;
+	                case TYPE_TEXT:
+	                    whiteBoardModelPdu.fontSize = parseInt(_param.fontSize);
+	                    whiteBoardModelPdu.fontName = _param.fontName || null;
+	                    whiteBoardModelPdu.text = _param.text || null;
+	                    break;
+	                default:
+	                    //其它类型不做处理
+	                    return null;
+	                    break;
+	            }
+	            //下面4个是必须的参数
+	            whiteBoardModelPdu.type = _param.type;
+	            whiteBoardModelPdu.itemIdx = _itemIdx;
+	            whiteBoardModelPdu.initiator = _GlobalConfig2.default.nodeId;
+
+	            /* whiteBoardModelPdu.parentId=_param.parentId||0;
+	             whiteBoardModelPdu.curPage=_param.curPage||1;*/
+
+	            whiteBoardModelPdu.parentId = _GlobalConfig2.default.activeDocId; //当前激活的文档id
+	            whiteBoardModelPdu.curPageNo = _GlobalConfig2.default.activeDocCurPage; //当前激活的文档页码
+
+	            whiteBoardModelPdu.pointGroup = _EngineUtils2.default.arrayToJsonString(_param.pointGroup);
+	            whiteBoardModelPdu.color = _param.color || "#000000";
+
+	            return whiteBoardModelPdu;
+	        }
+	    }, {
+	        key: 'unPackPdu',
+	        value: function unPackPdu(owner, itemIdx, itemData) {
+	            try {
+	                loger.log("白板收到数据===unPackPdu ");
+	                var whiteBoardModelPdu = _pdus2.default['RCWhiteBoardDataModelPdu'].decode(itemData);
+	                //console.log(whiteBoardModelPdu);
+	                //loger.log(whiteBoardModelPdu);
+	                var _pointGroup = _EngineUtils2.default.arrayFromJsonString(whiteBoardModelPdu.pointGroup);
+	                whiteBoardModelPdu.pointGroup = _pointGroup;
+	                this.annoInfos[itemIdx] = whiteBoardModelPdu;
+	                return whiteBoardModelPdu;
+	            } catch (err) {
+	                console.log("unPackPdu Pdu解析错误,itemIdx=" + itemIdx + "  err:" + err.message);
+	            }
+	            return null;
+	        }
+	    }]);
+
+	    return WhiteBoardApe;
+	}(_Ape3.default);
+
+	var _default = WhiteBoardApe;
+	exports.default = _default;
+	;
+
+	var _temp = function () {
+	    if (typeof __REACT_HOT_LOADER__ === 'undefined') {
+	        return;
+	    }
+
+	    __REACT_HOT_LOADER__.register(loger, 'loger', 'D:/work/McuClient/src/apes/WhiteBoardApe.js');
+
+	    __REACT_HOT_LOADER__.register(itemIdx, 'itemIdx', 'D:/work/McuClient/src/apes/WhiteBoardApe.js');
+
+	    __REACT_HOT_LOADER__.register(TYPE_BIGHT, 'TYPE_BIGHT', 'D:/work/McuClient/src/apes/WhiteBoardApe.js');
+
+	    __REACT_HOT_LOADER__.register(TYPE_LINE, 'TYPE_LINE', 'D:/work/McuClient/src/apes/WhiteBoardApe.js');
+
+	    __REACT_HOT_LOADER__.register(TYPE_RECT, 'TYPE_RECT', 'D:/work/McuClient/src/apes/WhiteBoardApe.js');
+
+	    __REACT_HOT_LOADER__.register(TYPE_CIRCLE, 'TYPE_CIRCLE', 'D:/work/McuClient/src/apes/WhiteBoardApe.js');
+
+	    __REACT_HOT_LOADER__.register(TYPE_TEXT, 'TYPE_TEXT', 'D:/work/McuClient/src/apes/WhiteBoardApe.js');
+
+	    __REACT_HOT_LOADER__.register(WhiteBoardApe, 'WhiteBoardApe', 'D:/work/McuClient/src/apes/WhiteBoardApe.js');
+
+	    __REACT_HOT_LOADER__.register(_default, 'default', 'D:/work/McuClient/src/apes/WhiteBoardApe.js');
+	}();
+
+	;
+
+/***/ },
+/* 47 */
+/***/ function(module, exports, __webpack_require__) {
+
+	var require;var __WEBPACK_AMD_DEFINE_RESULT__;/* WEBPACK VAR INJECTION */(function(process, global, module) {/*!
+	 * @overview es6-promise - a tiny implementation of Promises/A+.
+	 * @copyright Copyright (c) 2014 Yehuda Katz, Tom Dale, Stefan Penner and contributors (Conversion to ES6 API by Jake Archibald)
+	 * @license   Licensed under MIT license
+	 *            See https://raw.githubusercontent.com/jakearchibald/es6-promise/master/LICENSE
+	 * @version   3.2.1
+	 */
+
+	(function() {
+	    "use strict";
+	    function lib$es6$promise$utils$$objectOrFunction(x) {
+	      return typeof x === 'function' || (typeof x === 'object' && x !== null);
+	    }
+
+	    function lib$es6$promise$utils$$isFunction(x) {
+	      return typeof x === 'function';
+	    }
+
+	    function lib$es6$promise$utils$$isMaybeThenable(x) {
+	      return typeof x === 'object' && x !== null;
+	    }
+
+	    var lib$es6$promise$utils$$_isArray;
+	    if (!Array.isArray) {
+	      lib$es6$promise$utils$$_isArray = function (x) {
+	        return Object.prototype.toString.call(x) === '[object Array]';
+	      };
+	    } else {
+	      lib$es6$promise$utils$$_isArray = Array.isArray;
+	    }
+
+	    var lib$es6$promise$utils$$isArray = lib$es6$promise$utils$$_isArray;
+	    var lib$es6$promise$asap$$len = 0;
+	    var lib$es6$promise$asap$$vertxNext;
+	    var lib$es6$promise$asap$$customSchedulerFn;
+
+	    var lib$es6$promise$asap$$asap = function asap(callback, arg) {
+	      lib$es6$promise$asap$$queue[lib$es6$promise$asap$$len] = callback;
+	      lib$es6$promise$asap$$queue[lib$es6$promise$asap$$len + 1] = arg;
+	      lib$es6$promise$asap$$len += 2;
+	      if (lib$es6$promise$asap$$len === 2) {
+	        // If len is 2, that means that we need to schedule an async flush.
+	        // If additional callbacks are queued before the queue is flushed, they
+	        // will be processed by this flush that we are scheduling.
+	        if (lib$es6$promise$asap$$customSchedulerFn) {
+	          lib$es6$promise$asap$$customSchedulerFn(lib$es6$promise$asap$$flush);
+	        } else {
+	          lib$es6$promise$asap$$scheduleFlush();
+	        }
+	      }
+	    }
+
+	    function lib$es6$promise$asap$$setScheduler(scheduleFn) {
+	      lib$es6$promise$asap$$customSchedulerFn = scheduleFn;
+	    }
+
+	    function lib$es6$promise$asap$$setAsap(asapFn) {
+	      lib$es6$promise$asap$$asap = asapFn;
+	    }
+
+	    var lib$es6$promise$asap$$browserWindow = (typeof window !== 'undefined') ? window : undefined;
+	    var lib$es6$promise$asap$$browserGlobal = lib$es6$promise$asap$$browserWindow || {};
+	    var lib$es6$promise$asap$$BrowserMutationObserver = lib$es6$promise$asap$$browserGlobal.MutationObserver || lib$es6$promise$asap$$browserGlobal.WebKitMutationObserver;
+	    var lib$es6$promise$asap$$isNode = typeof self === 'undefined' && typeof process !== 'undefined' && {}.toString.call(process) === '[object process]';
+
+	    // test for web worker but not in IE10
+	    var lib$es6$promise$asap$$isWorker = typeof Uint8ClampedArray !== 'undefined' &&
+	      typeof importScripts !== 'undefined' &&
+	      typeof MessageChannel !== 'undefined';
+
+	    // node
+	    function lib$es6$promise$asap$$useNextTick() {
+	      // node version 0.10.x displays a deprecation warning when nextTick is used recursively
+	      // see https://github.com/cujojs/when/issues/410 for details
+	      return function() {
+	        process.nextTick(lib$es6$promise$asap$$flush);
+	      };
+	    }
+
+	    // vertx
+	    function lib$es6$promise$asap$$useVertxTimer() {
+	      return function() {
+	        lib$es6$promise$asap$$vertxNext(lib$es6$promise$asap$$flush);
+	      };
+	    }
+
+	    function lib$es6$promise$asap$$useMutationObserver() {
+	      var iterations = 0;
+	      var observer = new lib$es6$promise$asap$$BrowserMutationObserver(lib$es6$promise$asap$$flush);
+	      var node = document.createTextNode('');
+	      observer.observe(node, { characterData: true });
+
+	      return function() {
+	        node.data = (iterations = ++iterations % 2);
+	      };
+	    }
+
+	    // web worker
+	    function lib$es6$promise$asap$$useMessageChannel() {
+	      var channel = new MessageChannel();
+	      channel.port1.onmessage = lib$es6$promise$asap$$flush;
+	      return function () {
+	        channel.port2.postMessage(0);
+	      };
+	    }
+
+	    function lib$es6$promise$asap$$useSetTimeout() {
+	      return function() {
+	        setTimeout(lib$es6$promise$asap$$flush, 1);
+	      };
+	    }
+
+	    var lib$es6$promise$asap$$queue = new Array(1000);
+	    function lib$es6$promise$asap$$flush() {
+	      for (var i = 0; i < lib$es6$promise$asap$$len; i+=2) {
+	        var callback = lib$es6$promise$asap$$queue[i];
+	        var arg = lib$es6$promise$asap$$queue[i+1];
+
+	        callback(arg);
+
+	        lib$es6$promise$asap$$queue[i] = undefined;
+	        lib$es6$promise$asap$$queue[i+1] = undefined;
+	      }
+
+	      lib$es6$promise$asap$$len = 0;
+	    }
+
+	    function lib$es6$promise$asap$$attemptVertx() {
+	      try {
+	        var r = require;
+	        var vertx = __webpack_require__(48);
+	        lib$es6$promise$asap$$vertxNext = vertx.runOnLoop || vertx.runOnContext;
+	        return lib$es6$promise$asap$$useVertxTimer();
+	      } catch(e) {
+	        return lib$es6$promise$asap$$useSetTimeout();
+	      }
+	    }
+
+	    var lib$es6$promise$asap$$scheduleFlush;
+	    // Decide what async method to use to triggering processing of queued callbacks:
+	    if (lib$es6$promise$asap$$isNode) {
+	      lib$es6$promise$asap$$scheduleFlush = lib$es6$promise$asap$$useNextTick();
+	    } else if (lib$es6$promise$asap$$BrowserMutationObserver) {
+	      lib$es6$promise$asap$$scheduleFlush = lib$es6$promise$asap$$useMutationObserver();
+	    } else if (lib$es6$promise$asap$$isWorker) {
+	      lib$es6$promise$asap$$scheduleFlush = lib$es6$promise$asap$$useMessageChannel();
+	    } else if (lib$es6$promise$asap$$browserWindow === undefined && "function" === 'function') {
+	      lib$es6$promise$asap$$scheduleFlush = lib$es6$promise$asap$$attemptVertx();
+	    } else {
+	      lib$es6$promise$asap$$scheduleFlush = lib$es6$promise$asap$$useSetTimeout();
+	    }
+	    function lib$es6$promise$then$$then(onFulfillment, onRejection) {
+	      var parent = this;
+
+	      var child = new this.constructor(lib$es6$promise$$internal$$noop);
+
+	      if (child[lib$es6$promise$$internal$$PROMISE_ID] === undefined) {
+	        lib$es6$promise$$internal$$makePromise(child);
+	      }
+
+	      var state = parent._state;
+
+	      if (state) {
+	        var callback = arguments[state - 1];
+	        lib$es6$promise$asap$$asap(function(){
+	          lib$es6$promise$$internal$$invokeCallback(state, child, callback, parent._result);
+	        });
+	      } else {
+	        lib$es6$promise$$internal$$subscribe(parent, child, onFulfillment, onRejection);
+	      }
+
+	      return child;
+	    }
+	    var lib$es6$promise$then$$default = lib$es6$promise$then$$then;
+	    function lib$es6$promise$promise$resolve$$resolve(object) {
+	      /*jshint validthis:true */
+	      var Constructor = this;
+
+	      if (object && typeof object === 'object' && object.constructor === Constructor) {
+	        return object;
+	      }
+
+	      var promise = new Constructor(lib$es6$promise$$internal$$noop);
+	      lib$es6$promise$$internal$$resolve(promise, object);
+	      return promise;
+	    }
+	    var lib$es6$promise$promise$resolve$$default = lib$es6$promise$promise$resolve$$resolve;
+	    var lib$es6$promise$$internal$$PROMISE_ID = Math.random().toString(36).substring(16);
+
+	    function lib$es6$promise$$internal$$noop() {}
+
+	    var lib$es6$promise$$internal$$PENDING   = void 0;
+	    var lib$es6$promise$$internal$$FULFILLED = 1;
+	    var lib$es6$promise$$internal$$REJECTED  = 2;
+
+	    var lib$es6$promise$$internal$$GET_THEN_ERROR = new lib$es6$promise$$internal$$ErrorObject();
+
+	    function lib$es6$promise$$internal$$selfFulfillment() {
+	      return new TypeError("You cannot resolve a promise with itself");
+	    }
+
+	    function lib$es6$promise$$internal$$cannotReturnOwn() {
+	      return new TypeError('A promises callback cannot return that same promise.');
+	    }
+
+	    function lib$es6$promise$$internal$$getThen(promise) {
+	      try {
+	        return promise.then;
+	      } catch(error) {
+	        lib$es6$promise$$internal$$GET_THEN_ERROR.error = error;
+	        return lib$es6$promise$$internal$$GET_THEN_ERROR;
+	      }
+	    }
+
+	    function lib$es6$promise$$internal$$tryThen(then, value, fulfillmentHandler, rejectionHandler) {
+	      try {
+	        then.call(value, fulfillmentHandler, rejectionHandler);
+	      } catch(e) {
+	        return e;
+	      }
+	    }
+
+	    function lib$es6$promise$$internal$$handleForeignThenable(promise, thenable, then) {
+	       lib$es6$promise$asap$$asap(function(promise) {
+	        var sealed = false;
+	        var error = lib$es6$promise$$internal$$tryThen(then, thenable, function(value) {
+	          if (sealed) { return; }
+	          sealed = true;
+	          if (thenable !== value) {
+	            lib$es6$promise$$internal$$resolve(promise, value);
+	          } else {
+	            lib$es6$promise$$internal$$fulfill(promise, value);
+	          }
+	        }, function(reason) {
+	          if (sealed) { return; }
+	          sealed = true;
+
+	          lib$es6$promise$$internal$$reject(promise, reason);
+	        }, 'Settle: ' + (promise._label || ' unknown promise'));
+
+	        if (!sealed && error) {
+	          sealed = true;
+	          lib$es6$promise$$internal$$reject(promise, error);
+	        }
+	      }, promise);
+	    }
+
+	    function lib$es6$promise$$internal$$handleOwnThenable(promise, thenable) {
+	      if (thenable._state === lib$es6$promise$$internal$$FULFILLED) {
+	        lib$es6$promise$$internal$$fulfill(promise, thenable._result);
+	      } else if (thenable._state === lib$es6$promise$$internal$$REJECTED) {
+	        lib$es6$promise$$internal$$reject(promise, thenable._result);
+	      } else {
+	        lib$es6$promise$$internal$$subscribe(thenable, undefined, function(value) {
+	          lib$es6$promise$$internal$$resolve(promise, value);
+	        }, function(reason) {
+	          lib$es6$promise$$internal$$reject(promise, reason);
+	        });
+	      }
+	    }
+
+	    function lib$es6$promise$$internal$$handleMaybeThenable(promise, maybeThenable, then) {
+	      if (maybeThenable.constructor === promise.constructor &&
+	          then === lib$es6$promise$then$$default &&
+	          constructor.resolve === lib$es6$promise$promise$resolve$$default) {
+	        lib$es6$promise$$internal$$handleOwnThenable(promise, maybeThenable);
+	      } else {
+	        if (then === lib$es6$promise$$internal$$GET_THEN_ERROR) {
+	          lib$es6$promise$$internal$$reject(promise, lib$es6$promise$$internal$$GET_THEN_ERROR.error);
+	        } else if (then === undefined) {
+	          lib$es6$promise$$internal$$fulfill(promise, maybeThenable);
+	        } else if (lib$es6$promise$utils$$isFunction(then)) {
+	          lib$es6$promise$$internal$$handleForeignThenable(promise, maybeThenable, then);
+	        } else {
+	          lib$es6$promise$$internal$$fulfill(promise, maybeThenable);
+	        }
+	      }
+	    }
+
+	    function lib$es6$promise$$internal$$resolve(promise, value) {
+	      if (promise === value) {
+	        lib$es6$promise$$internal$$reject(promise, lib$es6$promise$$internal$$selfFulfillment());
+	      } else if (lib$es6$promise$utils$$objectOrFunction(value)) {
+	        lib$es6$promise$$internal$$handleMaybeThenable(promise, value, lib$es6$promise$$internal$$getThen(value));
+	      } else {
+	        lib$es6$promise$$internal$$fulfill(promise, value);
+	      }
+	    }
+
+	    function lib$es6$promise$$internal$$publishRejection(promise) {
+	      if (promise._onerror) {
+	        promise._onerror(promise._result);
+	      }
+
+	      lib$es6$promise$$internal$$publish(promise);
+	    }
+
+	    function lib$es6$promise$$internal$$fulfill(promise, value) {
+	      if (promise._state !== lib$es6$promise$$internal$$PENDING) { return; }
+
+	      promise._result = value;
+	      promise._state = lib$es6$promise$$internal$$FULFILLED;
+
+	      if (promise._subscribers.length !== 0) {
+	        lib$es6$promise$asap$$asap(lib$es6$promise$$internal$$publish, promise);
+	      }
+	    }
+
+	    function lib$es6$promise$$internal$$reject(promise, reason) {
+	      if (promise._state !== lib$es6$promise$$internal$$PENDING) { return; }
+	      promise._state = lib$es6$promise$$internal$$REJECTED;
+	      promise._result = reason;
+
+	      lib$es6$promise$asap$$asap(lib$es6$promise$$internal$$publishRejection, promise);
+	    }
+
+	    function lib$es6$promise$$internal$$subscribe(parent, child, onFulfillment, onRejection) {
+	      var subscribers = parent._subscribers;
+	      var length = subscribers.length;
+
+	      parent._onerror = null;
+
+	      subscribers[length] = child;
+	      subscribers[length + lib$es6$promise$$internal$$FULFILLED] = onFulfillment;
+	      subscribers[length + lib$es6$promise$$internal$$REJECTED]  = onRejection;
+
+	      if (length === 0 && parent._state) {
+	        lib$es6$promise$asap$$asap(lib$es6$promise$$internal$$publish, parent);
+	      }
+	    }
+
+	    function lib$es6$promise$$internal$$publish(promise) {
+	      var subscribers = promise._subscribers;
+	      var settled = promise._state;
+
+	      if (subscribers.length === 0) { return; }
+
+	      var child, callback, detail = promise._result;
+
+	      for (var i = 0; i < subscribers.length; i += 3) {
+	        child = subscribers[i];
+	        callback = subscribers[i + settled];
+
+	        if (child) {
+	          lib$es6$promise$$internal$$invokeCallback(settled, child, callback, detail);
+	        } else {
+	          callback(detail);
+	        }
+	      }
+
+	      promise._subscribers.length = 0;
+	    }
+
+	    function lib$es6$promise$$internal$$ErrorObject() {
+	      this.error = null;
+	    }
+
+	    var lib$es6$promise$$internal$$TRY_CATCH_ERROR = new lib$es6$promise$$internal$$ErrorObject();
+
+	    function lib$es6$promise$$internal$$tryCatch(callback, detail) {
+	      try {
+	        return callback(detail);
+	      } catch(e) {
+	        lib$es6$promise$$internal$$TRY_CATCH_ERROR.error = e;
+	        return lib$es6$promise$$internal$$TRY_CATCH_ERROR;
+	      }
+	    }
+
+	    function lib$es6$promise$$internal$$invokeCallback(settled, promise, callback, detail) {
+	      var hasCallback = lib$es6$promise$utils$$isFunction(callback),
+	          value, error, succeeded, failed;
+
+	      if (hasCallback) {
+	        value = lib$es6$promise$$internal$$tryCatch(callback, detail);
+
+	        if (value === lib$es6$promise$$internal$$TRY_CATCH_ERROR) {
+	          failed = true;
+	          error = value.error;
+	          value = null;
+	        } else {
+	          succeeded = true;
+	        }
+
+	        if (promise === value) {
+	          lib$es6$promise$$internal$$reject(promise, lib$es6$promise$$internal$$cannotReturnOwn());
+	          return;
+	        }
+
+	      } else {
+	        value = detail;
+	        succeeded = true;
+	      }
+
+	      if (promise._state !== lib$es6$promise$$internal$$PENDING) {
+	        // noop
+	      } else if (hasCallback && succeeded) {
+	        lib$es6$promise$$internal$$resolve(promise, value);
+	      } else if (failed) {
+	        lib$es6$promise$$internal$$reject(promise, error);
+	      } else if (settled === lib$es6$promise$$internal$$FULFILLED) {
+	        lib$es6$promise$$internal$$fulfill(promise, value);
+	      } else if (settled === lib$es6$promise$$internal$$REJECTED) {
+	        lib$es6$promise$$internal$$reject(promise, value);
+	      }
+	    }
+
+	    function lib$es6$promise$$internal$$initializePromise(promise, resolver) {
+	      try {
+	        resolver(function resolvePromise(value){
+	          lib$es6$promise$$internal$$resolve(promise, value);
+	        }, function rejectPromise(reason) {
+	          lib$es6$promise$$internal$$reject(promise, reason);
+	        });
+	      } catch(e) {
+	        lib$es6$promise$$internal$$reject(promise, e);
+	      }
+	    }
+
+	    var lib$es6$promise$$internal$$id = 0;
+	    function lib$es6$promise$$internal$$nextId() {
+	      return lib$es6$promise$$internal$$id++;
+	    }
+
+	    function lib$es6$promise$$internal$$makePromise(promise) {
+	      promise[lib$es6$promise$$internal$$PROMISE_ID] = lib$es6$promise$$internal$$id++;
+	      promise._state = undefined;
+	      promise._result = undefined;
+	      promise._subscribers = [];
+	    }
+
+	    function lib$es6$promise$promise$all$$all(entries) {
+	      return new lib$es6$promise$enumerator$$default(this, entries).promise;
+	    }
+	    var lib$es6$promise$promise$all$$default = lib$es6$promise$promise$all$$all;
+	    function lib$es6$promise$promise$race$$race(entries) {
+	      /*jshint validthis:true */
+	      var Constructor = this;
+
+	      if (!lib$es6$promise$utils$$isArray(entries)) {
+	        return new Constructor(function(resolve, reject) {
+	          reject(new TypeError('You must pass an array to race.'));
+	        });
+	      } else {
+	        return new Constructor(function(resolve, reject) {
+	          var length = entries.length;
+	          for (var i = 0; i < length; i++) {
+	            Constructor.resolve(entries[i]).then(resolve, reject);
+	          }
+	        });
+	      }
+	    }
+	    var lib$es6$promise$promise$race$$default = lib$es6$promise$promise$race$$race;
+	    function lib$es6$promise$promise$reject$$reject(reason) {
+	      /*jshint validthis:true */
+	      var Constructor = this;
+	      var promise = new Constructor(lib$es6$promise$$internal$$noop);
+	      lib$es6$promise$$internal$$reject(promise, reason);
+	      return promise;
+	    }
+	    var lib$es6$promise$promise$reject$$default = lib$es6$promise$promise$reject$$reject;
+
+
+	    function lib$es6$promise$promise$$needsResolver() {
+	      throw new TypeError('You must pass a resolver function as the first argument to the promise constructor');
+	    }
+
+	    function lib$es6$promise$promise$$needsNew() {
+	      throw new TypeError("Failed to construct 'Promise': Please use the 'new' operator, this object constructor cannot be called as a function.");
+	    }
+
+	    var lib$es6$promise$promise$$default = lib$es6$promise$promise$$Promise;
+	    /**
+	      Promise objects represent the eventual result of an asynchronous operation. The
+	      primary way of interacting with a promise is through its `then` method, which
+	      registers callbacks to receive either a promise's eventual value or the reason
+	      why the promise cannot be fulfilled.
+
+	      Terminology
+	      -----------
+
+	      - `promise` is an object or function with a `then` method whose behavior conforms to this specification.
+	      - `thenable` is an object or function that defines a `then` method.
+	      - `value` is any legal JavaScript value (including undefined, a thenable, or a promise).
+	      - `exception` is a value that is thrown using the throw statement.
+	      - `reason` is a value that indicates why a promise was rejected.
+	      - `settled` the final resting state of a promise, fulfilled or rejected.
+
+	      A promise can be in one of three states: pending, fulfilled, or rejected.
+
+	      Promises that are fulfilled have a fulfillment value and are in the fulfilled
+	      state.  Promises that are rejected have a rejection reason and are in the
+	      rejected state.  A fulfillment value is never a thenable.
+
+	      Promises can also be said to *resolve* a value.  If this value is also a
+	      promise, then the original promise's settled state will match the value's
+	      settled state.  So a promise that *resolves* a promise that rejects will
+	      itself reject, and a promise that *resolves* a promise that fulfills will
+	      itself fulfill.
+
+
+	      Basic Usage:
+	      ------------
+
+	      ```js
+	      var promise = new Promise(function(resolve, reject) {
+	        // on success
+	        resolve(value);
+
+	        // on failure
+	        reject(reason);
+	      });
+
+	      promise.then(function(value) {
+	        // on fulfillment
+	      }, function(reason) {
+	        // on rejection
+	      });
+	      ```
+
+	      Advanced Usage:
+	      ---------------
+
+	      Promises shine when abstracting away asynchronous interactions such as
+	      `XMLHttpRequest`s.
+
+	      ```js
+	      function getJSON(url) {
+	        return new Promise(function(resolve, reject){
+	          var xhr = new XMLHttpRequest();
+
+	          xhr.open('GET', url);
+	          xhr.onreadystatechange = handler;
+	          xhr.responseType = 'json';
+	          xhr.setRequestHeader('Accept', 'application/json');
+	          xhr.send();
+
+	          function handler() {
+	            if (this.readyState === this.DONE) {
+	              if (this.status === 200) {
+	                resolve(this.response);
+	              } else {
+	                reject(new Error('getJSON: `' + url + '` failed with status: [' + this.status + ']'));
+	              }
+	            }
+	          };
+	        });
+	      }
+
+	      getJSON('/posts.json').then(function(json) {
+	        // on fulfillment
+	      }, function(reason) {
+	        // on rejection
+	      });
+	      ```
+
+	      Unlike callbacks, promises are great composable primitives.
+
+	      ```js
+	      Promise.all([
+	        getJSON('/posts'),
+	        getJSON('/comments')
+	      ]).then(function(values){
+	        values[0] // => postsJSON
+	        values[1] // => commentsJSON
+
+	        return values;
+	      });
+	      ```
+
+	      @class Promise
+	      @param {function} resolver
+	      Useful for tooling.
+	      @constructor
+	    */
+	    function lib$es6$promise$promise$$Promise(resolver) {
+	      this[lib$es6$promise$$internal$$PROMISE_ID] = lib$es6$promise$$internal$$nextId();
+	      this._result = this._state = undefined;
+	      this._subscribers = [];
+
+	      if (lib$es6$promise$$internal$$noop !== resolver) {
+	        typeof resolver !== 'function' && lib$es6$promise$promise$$needsResolver();
+	        this instanceof lib$es6$promise$promise$$Promise ? lib$es6$promise$$internal$$initializePromise(this, resolver) : lib$es6$promise$promise$$needsNew();
+	      }
+	    }
+
+	    lib$es6$promise$promise$$Promise.all = lib$es6$promise$promise$all$$default;
+	    lib$es6$promise$promise$$Promise.race = lib$es6$promise$promise$race$$default;
+	    lib$es6$promise$promise$$Promise.resolve = lib$es6$promise$promise$resolve$$default;
+	    lib$es6$promise$promise$$Promise.reject = lib$es6$promise$promise$reject$$default;
+	    lib$es6$promise$promise$$Promise._setScheduler = lib$es6$promise$asap$$setScheduler;
+	    lib$es6$promise$promise$$Promise._setAsap = lib$es6$promise$asap$$setAsap;
+	    lib$es6$promise$promise$$Promise._asap = lib$es6$promise$asap$$asap;
+
+	    lib$es6$promise$promise$$Promise.prototype = {
+	      constructor: lib$es6$promise$promise$$Promise,
+
+	    /**
+	      The primary way of interacting with a promise is through its `then` method,
+	      which registers callbacks to receive either a promise's eventual value or the
+	      reason why the promise cannot be fulfilled.
+
+	      ```js
+	      findUser().then(function(user){
+	        // user is available
+	      }, function(reason){
+	        // user is unavailable, and you are given the reason why
+	      });
+	      ```
+
+	      Chaining
+	      --------
+
+	      The return value of `then` is itself a promise.  This second, 'downstream'
+	      promise is resolved with the return value of the first promise's fulfillment
+	      or rejection handler, or rejected if the handler throws an exception.
+
+	      ```js
+	      findUser().then(function (user) {
+	        return user.name;
+	      }, function (reason) {
+	        return 'default name';
+	      }).then(function (userName) {
+	        // If `findUser` fulfilled, `userName` will be the user's name, otherwise it
+	        // will be `'default name'`
+	      });
+
+	      findUser().then(function (user) {
+	        throw new Error('Found user, but still unhappy');
+	      }, function (reason) {
+	        throw new Error('`findUser` rejected and we're unhappy');
+	      }).then(function (value) {
+	        // never reached
+	      }, function (reason) {
+	        // if `findUser` fulfilled, `reason` will be 'Found user, but still unhappy'.
+	        // If `findUser` rejected, `reason` will be '`findUser` rejected and we're unhappy'.
+	      });
+	      ```
+	      If the downstream promise does not specify a rejection handler, rejection reasons will be propagated further downstream.
+
+	      ```js
+	      findUser().then(function (user) {
+	        throw new PedagogicalException('Upstream error');
+	      }).then(function (value) {
+	        // never reached
+	      }).then(function (value) {
+	        // never reached
+	      }, function (reason) {
+	        // The `PedgagocialException` is propagated all the way down to here
+	      });
+	      ```
+
+	      Assimilation
+	      ------------
+
+	      Sometimes the value you want to propagate to a downstream promise can only be
+	      retrieved asynchronously. This can be achieved by returning a promise in the
+	      fulfillment or rejection handler. The downstream promise will then be pending
+	      until the returned promise is settled. This is called *assimilation*.
+
+	      ```js
+	      findUser().then(function (user) {
+	        return findCommentsByAuthor(user);
+	      }).then(function (comments) {
+	        // The user's comments are now available
+	      });
+	      ```
+
+	      If the assimliated promise rejects, then the downstream promise will also reject.
+
+	      ```js
+	      findUser().then(function (user) {
+	        return findCommentsByAuthor(user);
+	      }).then(function (comments) {
+	        // If `findCommentsByAuthor` fulfills, we'll have the value here
+	      }, function (reason) {
+	        // If `findCommentsByAuthor` rejects, we'll have the reason here
+	      });
+	      ```
+
+	      Simple Example
+	      --------------
+
+	      Synchronous Example
+
+	      ```javascript
+	      var result;
+
+	      try {
+	        result = findResult();
+	        // success
+	      } catch(reason) {
+	        // failure
+	      }
+	      ```
+
+	      Errback Example
+
+	      ```js
+	      findResult(function(result, err){
+	        if (err) {
+	          // failure
+	        } else {
+	          // success
+	        }
+	      });
+	      ```
+
+	      Promise Example;
+
+	      ```javascript
+	      findResult().then(function(result){
+	        // success
+	      }, function(reason){
+	        // failure
+	      });
+	      ```
+
+	      Advanced Example
+	      --------------
+
+	      Synchronous Example
+
+	      ```javascript
+	      var author, books;
+
+	      try {
+	        author = findAuthor();
+	        books  = findBooksByAuthor(author);
+	        // success
+	      } catch(reason) {
+	        // failure
+	      }
+	      ```
+
+	      Errback Example
+
+	      ```js
+
+	      function foundBooks(books) {
+
+	      }
+
+	      function failure(reason) {
+
+	      }
+
+	      findAuthor(function(author, err){
+	        if (err) {
+	          failure(err);
+	          // failure
+	        } else {
+	          try {
+	            findBoooksByAuthor(author, function(books, err) {
+	              if (err) {
+	                failure(err);
+	              } else {
+	                try {
+	                  foundBooks(books);
+	                } catch(reason) {
+	                  failure(reason);
+	                }
+	              }
+	            });
+	          } catch(error) {
+	            failure(err);
+	          }
+	          // success
+	        }
+	      });
+	      ```
+
+	      Promise Example;
+
+	      ```javascript
+	      findAuthor().
+	        then(findBooksByAuthor).
+	        then(function(books){
+	          // found books
+	      }).catch(function(reason){
+	        // something went wrong
+	      });
+	      ```
+
+	      @method then
+	      @param {Function} onFulfilled
+	      @param {Function} onRejected
+	      Useful for tooling.
+	      @return {Promise}
+	    */
+	      then: lib$es6$promise$then$$default,
+
+	    /**
+	      `catch` is simply sugar for `then(undefined, onRejection)` which makes it the same
+	      as the catch block of a try/catch statement.
+
+	      ```js
+	      function findAuthor(){
+	        throw new Error('couldn't find that author');
+	      }
+
+	      // synchronous
+	      try {
+	        findAuthor();
+	      } catch(reason) {
+	        // something went wrong
+	      }
+
+	      // async with promises
+	      findAuthor().catch(function(reason){
+	        // something went wrong
+	      });
+	      ```
+
+	      @method catch
+	      @param {Function} onRejection
+	      Useful for tooling.
+	      @return {Promise}
+	    */
+	      'catch': function(onRejection) {
+	        return this.then(null, onRejection);
+	      }
+	    };
+	    var lib$es6$promise$enumerator$$default = lib$es6$promise$enumerator$$Enumerator;
+	    function lib$es6$promise$enumerator$$Enumerator(Constructor, input) {
+	      this._instanceConstructor = Constructor;
+	      this.promise = new Constructor(lib$es6$promise$$internal$$noop);
+
+	      if (!this.promise[lib$es6$promise$$internal$$PROMISE_ID]) {
+	        lib$es6$promise$$internal$$makePromise(this.promise);
+	      }
+
+	      if (lib$es6$promise$utils$$isArray(input)) {
+	        this._input     = input;
+	        this.length     = input.length;
+	        this._remaining = input.length;
+
+	        this._result = new Array(this.length);
+
+	        if (this.length === 0) {
+	          lib$es6$promise$$internal$$fulfill(this.promise, this._result);
+	        } else {
+	          this.length = this.length || 0;
+	          this._enumerate();
+	          if (this._remaining === 0) {
+	            lib$es6$promise$$internal$$fulfill(this.promise, this._result);
+	          }
+	        }
+	      } else {
+	        lib$es6$promise$$internal$$reject(this.promise, lib$es6$promise$enumerator$$validationError());
+	      }
+	    }
+
+	    function lib$es6$promise$enumerator$$validationError() {
+	      return new Error('Array Methods must be provided an Array');
+	    }
+
+	    lib$es6$promise$enumerator$$Enumerator.prototype._enumerate = function() {
+	      var length  = this.length;
+	      var input   = this._input;
+
+	      for (var i = 0; this._state === lib$es6$promise$$internal$$PENDING && i < length; i++) {
+	        this._eachEntry(input[i], i);
+	      }
+	    };
+
+	    lib$es6$promise$enumerator$$Enumerator.prototype._eachEntry = function(entry, i) {
+	      var c = this._instanceConstructor;
+	      var resolve = c.resolve;
+
+	      if (resolve === lib$es6$promise$promise$resolve$$default) {
+	        var then = lib$es6$promise$$internal$$getThen(entry);
+
+	        if (then === lib$es6$promise$then$$default &&
+	            entry._state !== lib$es6$promise$$internal$$PENDING) {
+	          this._settledAt(entry._state, i, entry._result);
+	        } else if (typeof then !== 'function') {
+	          this._remaining--;
+	          this._result[i] = entry;
+	        } else if (c === lib$es6$promise$promise$$default) {
+	          var promise = new c(lib$es6$promise$$internal$$noop);
+	          lib$es6$promise$$internal$$handleMaybeThenable(promise, entry, then);
+	          this._willSettleAt(promise, i);
+	        } else {
+	          this._willSettleAt(new c(function(resolve) { resolve(entry); }), i);
+	        }
+	      } else {
+	        this._willSettleAt(resolve(entry), i);
+	      }
+	    };
+
+	    lib$es6$promise$enumerator$$Enumerator.prototype._settledAt = function(state, i, value) {
+	      var promise = this.promise;
+
+	      if (promise._state === lib$es6$promise$$internal$$PENDING) {
+	        this._remaining--;
+
+	        if (state === lib$es6$promise$$internal$$REJECTED) {
+	          lib$es6$promise$$internal$$reject(promise, value);
+	        } else {
+	          this._result[i] = value;
+	        }
+	      }
+
+	      if (this._remaining === 0) {
+	        lib$es6$promise$$internal$$fulfill(promise, this._result);
+	      }
+	    };
+
+	    lib$es6$promise$enumerator$$Enumerator.prototype._willSettleAt = function(promise, i) {
+	      var enumerator = this;
+
+	      lib$es6$promise$$internal$$subscribe(promise, undefined, function(value) {
+	        enumerator._settledAt(lib$es6$promise$$internal$$FULFILLED, i, value);
+	      }, function(reason) {
+	        enumerator._settledAt(lib$es6$promise$$internal$$REJECTED, i, reason);
+	      });
+	    };
+	    function lib$es6$promise$polyfill$$polyfill() {
+	      var local;
+
+	      if (typeof global !== 'undefined') {
+	          local = global;
+	      } else if (typeof self !== 'undefined') {
+	          local = self;
+	      } else {
+	          try {
+	              local = Function('return this')();
+	          } catch (e) {
+	              throw new Error('polyfill failed because global object is unavailable in this environment');
+	          }
+	      }
+
+	      var P = local.Promise;
+
+	      if (P && Object.prototype.toString.call(P.resolve()) === '[object Promise]' && !P.cast) {
+	        return;
+	      }
+
+	      local.Promise = lib$es6$promise$promise$$default;
+	    }
+	    var lib$es6$promise$polyfill$$default = lib$es6$promise$polyfill$$polyfill;
+
+	    var lib$es6$promise$umd$$ES6Promise = {
+	      'Promise': lib$es6$promise$promise$$default,
+	      'polyfill': lib$es6$promise$polyfill$$default
+	    };
+
+	    /* global define:true module:true window: true */
+	    if ("function" === 'function' && __webpack_require__(28)['amd']) {
+	      !(__WEBPACK_AMD_DEFINE_RESULT__ = function() { return lib$es6$promise$umd$$ES6Promise; }.call(exports, __webpack_require__, exports, module), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));
+	    } else if (typeof module !== 'undefined' && module['exports']) {
+	      module['exports'] = lib$es6$promise$umd$$ES6Promise;
+	    } else if (typeof this !== 'undefined') {
+	      this['ES6Promise'] = lib$es6$promise$umd$$ES6Promise;
+	    }
+
+	    lib$es6$promise$polyfill$$default();
+	}).call(this);
+
+
+	/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(27), (function() { return this; }()), __webpack_require__(12)(module)))
+
+/***/ },
+/* 48 */
+/***/ function(module, exports) {
+
+	/* (ignored) */
+
+/***/ },
+/* 49 */
+/***/ function(module, exports) {
+
+	(function(self) {
+	  'use strict';
+
+	  if (self.fetch) {
+	    return
+	  }
+
+	  var support = {
+	    searchParams: 'URLSearchParams' in self,
+	    iterable: 'Symbol' in self && 'iterator' in Symbol,
+	    blob: 'FileReader' in self && 'Blob' in self && (function() {
+	      try {
+	        new Blob()
+	        return true
+	      } catch(e) {
+	        return false
+	      }
+	    })(),
+	    formData: 'FormData' in self,
+	    arrayBuffer: 'ArrayBuffer' in self
+	  }
+
+	  function normalizeName(name) {
+	    if (typeof name !== 'string') {
+	      name = String(name)
+	    }
+	    if (/[^a-z0-9\-#$%&'*+.\^_`|~]/i.test(name)) {
+	      throw new TypeError('Invalid character in header field name')
+	    }
+	    return name.toLowerCase()
+	  }
+
+	  function normalizeValue(value) {
+	    if (typeof value !== 'string') {
+	      value = String(value)
+	    }
+	    return value
+	  }
+
+	  // Build a destructive iterator for the value list
+	  function iteratorFor(items) {
+	    var iterator = {
+	      next: function() {
+	        var value = items.shift()
+	        return {done: value === undefined, value: value}
+	      }
+	    }
+
+	    if (support.iterable) {
+	      iterator[Symbol.iterator] = function() {
+	        return iterator
+	      }
+	    }
+
+	    return iterator
+	  }
+
+	  function Headers(headers) {
+	    this.map = {}
+
+	    if (headers instanceof Headers) {
+	      headers.forEach(function(value, name) {
+	        this.append(name, value)
+	      }, this)
+
+	    } else if (headers) {
+	      Object.getOwnPropertyNames(headers).forEach(function(name) {
+	        this.append(name, headers[name])
+	      }, this)
+	    }
+	  }
+
+	  Headers.prototype.append = function(name, value) {
+	    name = normalizeName(name)
+	    value = normalizeValue(value)
+	    var list = this.map[name]
+	    if (!list) {
+	      list = []
+	      this.map[name] = list
+	    }
+	    list.push(value)
+	  }
+
+	  Headers.prototype['delete'] = function(name) {
+	    delete this.map[normalizeName(name)]
+	  }
+
+	  Headers.prototype.get = function(name) {
+	    var values = this.map[normalizeName(name)]
+	    return values ? values[0] : null
+	  }
+
+	  Headers.prototype.getAll = function(name) {
+	    return this.map[normalizeName(name)] || []
+	  }
+
+	  Headers.prototype.has = function(name) {
+	    return this.map.hasOwnProperty(normalizeName(name))
+	  }
+
+	  Headers.prototype.set = function(name, value) {
+	    this.map[normalizeName(name)] = [normalizeValue(value)]
+	  }
+
+	  Headers.prototype.forEach = function(callback, thisArg) {
+	    Object.getOwnPropertyNames(this.map).forEach(function(name) {
+	      this.map[name].forEach(function(value) {
+	        callback.call(thisArg, value, name, this)
+	      }, this)
+	    }, this)
+	  }
+
+	  Headers.prototype.keys = function() {
+	    var items = []
+	    this.forEach(function(value, name) { items.push(name) })
+	    return iteratorFor(items)
+	  }
+
+	  Headers.prototype.values = function() {
+	    var items = []
+	    this.forEach(function(value) { items.push(value) })
+	    return iteratorFor(items)
+	  }
+
+	  Headers.prototype.entries = function() {
+	    var items = []
+	    this.forEach(function(value, name) { items.push([name, value]) })
+	    return iteratorFor(items)
+	  }
+
+	  if (support.iterable) {
+	    Headers.prototype[Symbol.iterator] = Headers.prototype.entries
+	  }
+
+	  function consumed(body) {
+	    if (body.bodyUsed) {
+	      return Promise.reject(new TypeError('Already read'))
+	    }
+	    body.bodyUsed = true
+	  }
+
+	  function fileReaderReady(reader) {
+	    return new Promise(function(resolve, reject) {
+	      reader.onload = function() {
+	        resolve(reader.result)
+	      }
+	      reader.onerror = function() {
+	        reject(reader.error)
+	      }
+	    })
+	  }
+
+	  function readBlobAsArrayBuffer(blob) {
+	    var reader = new FileReader()
+	    reader.readAsArrayBuffer(blob)
+	    return fileReaderReady(reader)
+	  }
+
+	  function readBlobAsText(blob) {
+	    var reader = new FileReader()
+	    reader.readAsText(blob)
+	    return fileReaderReady(reader)
+	  }
+
+	  function Body() {
+	    this.bodyUsed = false
+
+	    this._initBody = function(body) {
+	      this._bodyInit = body
+	      if (typeof body === 'string') {
+	        this._bodyText = body
+	      } else if (support.blob && Blob.prototype.isPrototypeOf(body)) {
+	        this._bodyBlob = body
+	      } else if (support.formData && FormData.prototype.isPrototypeOf(body)) {
+	        this._bodyFormData = body
+	      } else if (support.searchParams && URLSearchParams.prototype.isPrototypeOf(body)) {
+	        this._bodyText = body.toString()
+	      } else if (!body) {
+	        this._bodyText = ''
+	      } else if (support.arrayBuffer && ArrayBuffer.prototype.isPrototypeOf(body)) {
+	        // Only support ArrayBuffers for POST method.
+	        // Receiving ArrayBuffers happens via Blobs, instead.
+	      } else {
+	        throw new Error('unsupported BodyInit type')
+	      }
+
+	      if (!this.headers.get('content-type')) {
+	        if (typeof body === 'string') {
+	          this.headers.set('content-type', 'text/plain;charset=UTF-8')
+	        } else if (this._bodyBlob && this._bodyBlob.type) {
+	          this.headers.set('content-type', this._bodyBlob.type)
+	        } else if (support.searchParams && URLSearchParams.prototype.isPrototypeOf(body)) {
+	          this.headers.set('content-type', 'application/x-www-form-urlencoded;charset=UTF-8')
+	        }
+	      }
+	    }
+
+	    if (support.blob) {
+	      this.blob = function() {
+	        var rejected = consumed(this)
+	        if (rejected) {
+	          return rejected
+	        }
+
+	        if (this._bodyBlob) {
+	          return Promise.resolve(this._bodyBlob)
+	        } else if (this._bodyFormData) {
+	          throw new Error('could not read FormData body as blob')
+	        } else {
+	          return Promise.resolve(new Blob([this._bodyText]))
+	        }
+	      }
+
+	      this.arrayBuffer = function() {
+	        return this.blob().then(readBlobAsArrayBuffer)
+	      }
+
+	      this.text = function() {
+	        var rejected = consumed(this)
+	        if (rejected) {
+	          return rejected
+	        }
+
+	        if (this._bodyBlob) {
+	          return readBlobAsText(this._bodyBlob)
+	        } else if (this._bodyFormData) {
+	          throw new Error('could not read FormData body as text')
+	        } else {
+	          return Promise.resolve(this._bodyText)
+	        }
+	      }
+	    } else {
+	      this.text = function() {
+	        var rejected = consumed(this)
+	        return rejected ? rejected : Promise.resolve(this._bodyText)
+	      }
+	    }
+
+	    if (support.formData) {
+	      this.formData = function() {
+	        return this.text().then(decode)
+	      }
+	    }
+
+	    this.json = function() {
+	      return this.text().then(JSON.parse)
+	    }
+
+	    return this
+	  }
+
+	  // HTTP methods whose capitalization should be normalized
+	  var methods = ['DELETE', 'GET', 'HEAD', 'OPTIONS', 'POST', 'PUT']
+
+	  function normalizeMethod(method) {
+	    var upcased = method.toUpperCase()
+	    return (methods.indexOf(upcased) > -1) ? upcased : method
+	  }
+
+	  function Request(input, options) {
+	    options = options || {}
+	    var body = options.body
+	    if (Request.prototype.isPrototypeOf(input)) {
+	      if (input.bodyUsed) {
+	        throw new TypeError('Already read')
+	      }
+	      this.url = input.url
+	      this.credentials = input.credentials
+	      if (!options.headers) {
+	        this.headers = new Headers(input.headers)
+	      }
+	      this.method = input.method
+	      this.mode = input.mode
+	      if (!body) {
+	        body = input._bodyInit
+	        input.bodyUsed = true
+	      }
+	    } else {
+	      this.url = input
+	    }
+
+	    this.credentials = options.credentials || this.credentials || 'omit'
+	    if (options.headers || !this.headers) {
+	      this.headers = new Headers(options.headers)
+	    }
+	    this.method = normalizeMethod(options.method || this.method || 'GET')
+	    this.mode = options.mode || this.mode || null
+	    this.referrer = null
+
+	    if ((this.method === 'GET' || this.method === 'HEAD') && body) {
+	      throw new TypeError('Body not allowed for GET or HEAD requests')
+	    }
+	    this._initBody(body)
+	  }
+
+	  Request.prototype.clone = function() {
+	    return new Request(this)
+	  }
+
+	  function decode(body) {
+	    var form = new FormData()
+	    body.trim().split('&').forEach(function(bytes) {
+	      if (bytes) {
+	        var split = bytes.split('=')
+	        var name = split.shift().replace(/\+/g, ' ')
+	        var value = split.join('=').replace(/\+/g, ' ')
+	        form.append(decodeURIComponent(name), decodeURIComponent(value))
+	      }
+	    })
+	    return form
+	  }
+
+	  function headers(xhr) {
+	    var head = new Headers()
+	    var pairs = (xhr.getAllResponseHeaders() || '').trim().split('\n')
+	    pairs.forEach(function(header) {
+	      var split = header.trim().split(':')
+	      var key = split.shift().trim()
+	      var value = split.join(':').trim()
+	      head.append(key, value)
+	    })
+	    return head
+	  }
+
+	  Body.call(Request.prototype)
+
+	  function Response(bodyInit, options) {
+	    if (!options) {
+	      options = {}
+	    }
+
+	    this.type = 'default'
+	    this.status = options.status
+	    this.ok = this.status >= 200 && this.status < 300
+	    this.statusText = options.statusText
+	    this.headers = options.headers instanceof Headers ? options.headers : new Headers(options.headers)
+	    this.url = options.url || ''
+	    this._initBody(bodyInit)
+	  }
+
+	  Body.call(Response.prototype)
+
+	  Response.prototype.clone = function() {
+	    return new Response(this._bodyInit, {
+	      status: this.status,
+	      statusText: this.statusText,
+	      headers: new Headers(this.headers),
+	      url: this.url
+	    })
+	  }
+
+	  Response.error = function() {
+	    var response = new Response(null, {status: 0, statusText: ''})
+	    response.type = 'error'
+	    return response
+	  }
+
+	  var redirectStatuses = [301, 302, 303, 307, 308]
+
+	  Response.redirect = function(url, status) {
+	    if (redirectStatuses.indexOf(status) === -1) {
+	      throw new RangeError('Invalid status code')
+	    }
+
+	    return new Response(null, {status: status, headers: {location: url}})
+	  }
+
+	  self.Headers = Headers
+	  self.Request = Request
+	  self.Response = Response
+
+	  self.fetch = function(input, init) {
+	    return new Promise(function(resolve, reject) {
+	      var request
+	      if (Request.prototype.isPrototypeOf(input) && !init) {
+	        request = input
+	      } else {
+	        request = new Request(input, init)
+	      }
+
+	      var xhr = new XMLHttpRequest()
+
+	      function responseURL() {
+	        if ('responseURL' in xhr) {
+	          return xhr.responseURL
+	        }
+
+	        // Avoid security warnings on getResponseHeader when not allowed by CORS
+	        if (/^X-Request-URL:/m.test(xhr.getAllResponseHeaders())) {
+	          return xhr.getResponseHeader('X-Request-URL')
+	        }
+
+	        return
+	      }
+
+	      xhr.onload = function() {
+	        var options = {
+	          status: xhr.status,
+	          statusText: xhr.statusText,
+	          headers: headers(xhr),
+	          url: responseURL()
+	        }
+	        var body = 'response' in xhr ? xhr.response : xhr.responseText
+	        resolve(new Response(body, options))
+	      }
+
+	      xhr.onerror = function() {
+	        reject(new TypeError('Network request failed'))
+	      }
+
+	      xhr.ontimeout = function() {
+	        reject(new TypeError('Network request failed'))
+	      }
+
+	      xhr.open(request.method, request.url, true)
+
+	      if (request.credentials === 'include') {
+	        xhr.withCredentials = true
+	      }
+
+	      if ('responseType' in xhr && support.blob) {
+	        xhr.responseType = 'blob'
+	      }
+
+	      request.headers.forEach(function(value, name) {
+	        xhr.setRequestHeader(name, value)
+	      })
+
+	      xhr.send(typeof request._bodyInit === 'undefined' ? null : request._bodyInit)
+	    })
+	  }
+	  self.fetch.polyfill = true
+	})(typeof self !== 'undefined' ? self : this);
+
+
+/***/ },
+/* 50 */
+/***/ function(module, exports, __webpack_require__) {
+
+	var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;(function(a){if("function"==="function"&&__webpack_require__(51)&&__webpack_require__(51).jQuery){!(__WEBPACK_AMD_DEFINE_ARRAY__ = [__webpack_require__(52)], __WEBPACK_AMD_DEFINE_FACTORY__ = (a), __WEBPACK_AMD_DEFINE_RESULT__ = (typeof __WEBPACK_AMD_DEFINE_FACTORY__ === 'function' ? (__WEBPACK_AMD_DEFINE_FACTORY__.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__)) : __WEBPACK_AMD_DEFINE_FACTORY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__))}else{if(typeof module!=="undefined"&&module.exports){a(__webpack_require__(52))}else{a(jQuery)}}}(function(f){var y="1.6.15",p="left",o="right",e="up",x="down",c="in",A="out",m="none",s="auto",l="swipe",t="pinch",B="tap",j="doubletap",b="longtap",z="hold",E="horizontal",u="vertical",i="all",r=10,g="start",k="move",h="end",q="cancel",a="ontouchstart" in window,v=window.navigator.msPointerEnabled&&!window.navigator.pointerEnabled&&!a,d=(window.navigator.pointerEnabled||window.navigator.msPointerEnabled)&&!a,C="TouchSwipe";var n={fingers:1,threshold:75,cancelThreshold:null,pinchThreshold:20,maxTimeThreshold:null,fingerReleaseThreshold:250,longTapThreshold:500,doubleTapThreshold:200,swipe:null,swipeLeft:null,swipeRight:null,swipeUp:null,swipeDown:null,swipeStatus:null,pinchIn:null,pinchOut:null,pinchStatus:null,click:null,tap:null,doubleTap:null,longTap:null,hold:null,triggerOnTouchEnd:true,triggerOnTouchLeave:false,allowPageScroll:"auto",fallbackToMouseEvents:true,excludedElements:"label, button, input, select, textarea, a, .noSwipe",preventDefaultEvents:true};f.fn.swipe=function(H){var G=f(this),F=G.data(C);if(F&&typeof H==="string"){if(F[H]){return F[H].apply(this,Array.prototype.slice.call(arguments,1))}else{f.error("Method "+H+" does not exist on jQuery.swipe")}}else{if(F&&typeof H==="object"){F.option.apply(this,arguments)}else{if(!F&&(typeof H==="object"||!H)){return w.apply(this,arguments)}}}return G};f.fn.swipe.version=y;f.fn.swipe.defaults=n;f.fn.swipe.phases={PHASE_START:g,PHASE_MOVE:k,PHASE_END:h,PHASE_CANCEL:q};f.fn.swipe.directions={LEFT:p,RIGHT:o,UP:e,DOWN:x,IN:c,OUT:A};f.fn.swipe.pageScroll={NONE:m,HORIZONTAL:E,VERTICAL:u,AUTO:s};f.fn.swipe.fingers={ONE:1,TWO:2,THREE:3,FOUR:4,FIVE:5,ALL:i};function w(F){if(F&&(F.allowPageScroll===undefined&&(F.swipe!==undefined||F.swipeStatus!==undefined))){F.allowPageScroll=m}if(F.click!==undefined&&F.tap===undefined){F.tap=F.click}if(!F){F={}}F=f.extend({},f.fn.swipe.defaults,F);return this.each(function(){var H=f(this);var G=H.data(C);if(!G){G=new D(this,F);H.data(C,G)}})}function D(a5,au){var au=f.extend({},au);var az=(a||d||!au.fallbackToMouseEvents),K=az?(d?(v?"MSPointerDown":"pointerdown"):"touchstart"):"mousedown",ax=az?(d?(v?"MSPointerMove":"pointermove"):"touchmove"):"mousemove",V=az?(d?(v?"MSPointerUp":"pointerup"):"touchend"):"mouseup",T=az?(d?"mouseleave":null):"mouseleave",aD=(d?(v?"MSPointerCancel":"pointercancel"):"touchcancel");var ag=0,aP=null,a2=null,ac=0,a1=0,aZ=0,H=1,ap=0,aJ=0,N=null;var aR=f(a5);var aa="start";var X=0;var aQ={};var U=0,a3=0,a6=0,ay=0,O=0;var aW=null,af=null;try{aR.bind(K,aN);aR.bind(aD,ba)}catch(aj){f.error("events not supported "+K+","+aD+" on jQuery.swipe")}this.enable=function(){aR.bind(K,aN);aR.bind(aD,ba);return aR};this.disable=function(){aK();return aR};this.destroy=function(){aK();aR.data(C,null);aR=null};this.option=function(bd,bc){if(typeof bd==="object"){au=f.extend(au,bd)}else{if(au[bd]!==undefined){if(bc===undefined){return au[bd]}else{au[bd]=bc}}else{if(!bd){return au}else{f.error("Option "+bd+" does not exist on jQuery.swipe.options")}}}return null};function aN(be){if(aB()){return}if(f(be.target).closest(au.excludedElements,aR).length>0){return}var bf=be.originalEvent?be.originalEvent:be;var bd,bg=bf.touches,bc=bg?bg[0]:bf;aa=g;if(bg){X=bg.length}else{if(au.preventDefaultEvents!==false){be.preventDefault()}}ag=0;aP=null;a2=null;aJ=null;ac=0;a1=0;aZ=0;H=1;ap=0;N=ab();S();ai(0,bc);if(!bg||(X===au.fingers||au.fingers===i)||aX()){U=ar();if(X==2){ai(1,bg[1]);a1=aZ=at(aQ[0].start,aQ[1].start)}if(au.swipeStatus||au.pinchStatus){bd=P(bf,aa)}}else{bd=false}if(bd===false){aa=q;P(bf,aa);return bd}else{if(au.hold){af=setTimeout(f.proxy(function(){aR.trigger("hold",[bf.target]);if(au.hold){bd=au.hold.call(aR,bf,bf.target)}},this),au.longTapThreshold)}an(true)}return null}function a4(bf){var bi=bf.originalEvent?bf.originalEvent:bf;if(aa===h||aa===q||al()){return}var be,bj=bi.touches,bd=bj?bj[0]:bi;var bg=aH(bd);a3=ar();if(bj){X=bj.length}if(au.hold){clearTimeout(af)}aa=k;if(X==2){if(a1==0){ai(1,bj[1]);a1=aZ=at(aQ[0].start,aQ[1].start)}else{aH(bj[1]);aZ=at(aQ[0].end,aQ[1].end);aJ=aq(aQ[0].end,aQ[1].end)}H=a8(a1,aZ);ap=Math.abs(a1-aZ)}if((X===au.fingers||au.fingers===i)||!bj||aX()){aP=aL(bg.start,bg.end);a2=aL(bg.last,bg.end);ak(bf,a2);ag=aS(bg.start,bg.end);ac=aM();aI(aP,ag);be=P(bi,aa);if(!au.triggerOnTouchEnd||au.triggerOnTouchLeave){var bc=true;if(au.triggerOnTouchLeave){var bh=aY(this);bc=F(bg.end,bh)}if(!au.triggerOnTouchEnd&&bc){aa=aC(k)}else{if(au.triggerOnTouchLeave&&!bc){aa=aC(h)}}if(aa==q||aa==h){P(bi,aa)}}}else{aa=q;P(bi,aa)}if(be===false){aa=q;P(bi,aa)}}function M(bc){var bd=bc.originalEvent?bc.originalEvent:bc,be=bd.touches;if(be){if(be.length&&!al()){G(bd);return true}else{if(be.length&&al()){return true}}}if(al()){X=ay}a3=ar();ac=aM();if(bb()||!am()){aa=q;P(bd,aa)}else{if(au.triggerOnTouchEnd||(au.triggerOnTouchEnd==false&&aa===k)){if(au.preventDefaultEvents!==false){bc.preventDefault()}aa=h;P(bd,aa)}else{if(!au.triggerOnTouchEnd&&a7()){aa=h;aF(bd,aa,B)}else{if(aa===k){aa=q;P(bd,aa)}}}}an(false);return null}function ba(){X=0;a3=0;U=0;a1=0;aZ=0;H=1;S();an(false)}function L(bc){var bd=bc.originalEvent?bc.originalEvent:bc;if(au.triggerOnTouchLeave){aa=aC(h);P(bd,aa)}}function aK(){aR.unbind(K,aN);aR.unbind(aD,ba);aR.unbind(ax,a4);aR.unbind(V,M);if(T){aR.unbind(T,L)}an(false)}function aC(bg){var bf=bg;var be=aA();var bd=am();var bc=bb();if(!be||bc){bf=q}else{if(bd&&bg==k&&(!au.triggerOnTouchEnd||au.triggerOnTouchLeave)){bf=h}else{if(!bd&&bg==h&&au.triggerOnTouchLeave){bf=q}}}return bf}function P(be,bc){var bd,bf=be.touches;if(J()||W()){bd=aF(be,bc,l)}if((Q()||aX())&&bd!==false){bd=aF(be,bc,t)}if(aG()&&bd!==false){bd=aF(be,bc,j)}else{if(ao()&&bd!==false){bd=aF(be,bc,b)}else{if(ah()&&bd!==false){bd=aF(be,bc,B)}}}if(bc===q){if(W()){bd=aF(be,bc,l)}if(aX()){bd=aF(be,bc,t)}ba(be)}if(bc===h){if(bf){if(!bf.length){ba(be)}}else{ba(be)}}return bd}function aF(bf,bc,be){var bd;if(be==l){aR.trigger("swipeStatus",[bc,aP||null,ag||0,ac||0,X,aQ,a2]);if(au.swipeStatus){bd=au.swipeStatus.call(aR,bf,bc,aP||null,ag||0,ac||0,X,aQ,a2);if(bd===false){return false}}if(bc==h&&aV()){clearTimeout(aW);clearTimeout(af);aR.trigger("swipe",[aP,ag,ac,X,aQ,a2]);if(au.swipe){bd=au.swipe.call(aR,bf,aP,ag,ac,X,aQ,a2);if(bd===false){return false}}switch(aP){case p:aR.trigger("swipeLeft",[aP,ag,ac,X,aQ,a2]);if(au.swipeLeft){bd=au.swipeLeft.call(aR,bf,aP,ag,ac,X,aQ,a2)}break;case o:aR.trigger("swipeRight",[aP,ag,ac,X,aQ,a2]);if(au.swipeRight){bd=au.swipeRight.call(aR,bf,aP,ag,ac,X,aQ,a2)}break;case e:aR.trigger("swipeUp",[aP,ag,ac,X,aQ,a2]);if(au.swipeUp){bd=au.swipeUp.call(aR,bf,aP,ag,ac,X,aQ,a2)}break;case x:aR.trigger("swipeDown",[aP,ag,ac,X,aQ,a2]);if(au.swipeDown){bd=au.swipeDown.call(aR,bf,aP,ag,ac,X,aQ,a2)}break}}}if(be==t){aR.trigger("pinchStatus",[bc,aJ||null,ap||0,ac||0,X,H,aQ]);if(au.pinchStatus){bd=au.pinchStatus.call(aR,bf,bc,aJ||null,ap||0,ac||0,X,H,aQ);if(bd===false){return false}}if(bc==h&&a9()){switch(aJ){case c:aR.trigger("pinchIn",[aJ||null,ap||0,ac||0,X,H,aQ]);if(au.pinchIn){bd=au.pinchIn.call(aR,bf,aJ||null,ap||0,ac||0,X,H,aQ)}break;case A:aR.trigger("pinchOut",[aJ||null,ap||0,ac||0,X,H,aQ]);if(au.pinchOut){bd=au.pinchOut.call(aR,bf,aJ||null,ap||0,ac||0,X,H,aQ)}break}}}if(be==B){if(bc===q||bc===h){clearTimeout(aW);clearTimeout(af);if(Z()&&!I()){O=ar();aW=setTimeout(f.proxy(function(){O=null;aR.trigger("tap",[bf.target]);if(au.tap){bd=au.tap.call(aR,bf,bf.target)}},this),au.doubleTapThreshold)}else{O=null;aR.trigger("tap",[bf.target]);if(au.tap){bd=au.tap.call(aR,bf,bf.target)}}}}else{if(be==j){if(bc===q||bc===h){clearTimeout(aW);clearTimeout(af);O=null;aR.trigger("doubletap",[bf.target]);if(au.doubleTap){bd=au.doubleTap.call(aR,bf,bf.target)}}}else{if(be==b){if(bc===q||bc===h){clearTimeout(aW);O=null;aR.trigger("longtap",[bf.target]);if(au.longTap){bd=au.longTap.call(aR,bf,bf.target)}}}}}return bd}function am(){var bc=true;if(au.threshold!==null){bc=ag>=au.threshold}return bc}function bb(){var bc=false;if(au.cancelThreshold!==null&&aP!==null){bc=(aT(aP)-ag)>=au.cancelThreshold}return bc}function ae(){if(au.pinchThreshold!==null){return ap>=au.pinchThreshold}return true}function aA(){var bc;if(au.maxTimeThreshold){if(ac>=au.maxTimeThreshold){bc=false}else{bc=true}}else{bc=true}return bc}function ak(bc,bd){if(au.preventDefaultEvents===false){return}if(au.allowPageScroll===m){bc.preventDefault()}else{var be=au.allowPageScroll===s;switch(bd){case p:if((au.swipeLeft&&be)||(!be&&au.allowPageScroll!=E)){bc.preventDefault()}break;case o:if((au.swipeRight&&be)||(!be&&au.allowPageScroll!=E)){bc.preventDefault()}break;case e:if((au.swipeUp&&be)||(!be&&au.allowPageScroll!=u)){bc.preventDefault()}break;case x:if((au.swipeDown&&be)||(!be&&au.allowPageScroll!=u)){bc.preventDefault()}break}}}function a9(){var bd=aO();var bc=Y();var be=ae();return bd&&bc&&be}function aX(){return !!(au.pinchStatus||au.pinchIn||au.pinchOut)}function Q(){return !!(a9()&&aX())}function aV(){var bf=aA();var bh=am();var be=aO();var bc=Y();var bd=bb();var bg=!bd&&bc&&be&&bh&&bf;return bg}function W(){return !!(au.swipe||au.swipeStatus||au.swipeLeft||au.swipeRight||au.swipeUp||au.swipeDown)}function J(){return !!(aV()&&W())}function aO(){return((X===au.fingers||au.fingers===i)||!a)}function Y(){return aQ[0].end.x!==0}function a7(){return !!(au.tap)}function Z(){return !!(au.doubleTap)}function aU(){return !!(au.longTap)}function R(){if(O==null){return false}var bc=ar();return(Z()&&((bc-O)<=au.doubleTapThreshold))}function I(){return R()}function aw(){return((X===1||!a)&&(isNaN(ag)||ag<au.threshold))}function a0(){return((ac>au.longTapThreshold)&&(ag<r))}function ah(){return !!(aw()&&a7())}function aG(){return !!(R()&&Z())}function ao(){return !!(a0()&&aU())}function G(bc){a6=ar();ay=bc.touches.length+1}function S(){a6=0;ay=0}function al(){var bc=false;if(a6){var bd=ar()-a6;if(bd<=au.fingerReleaseThreshold){bc=true}}return bc}function aB(){return !!(aR.data(C+"_intouch")===true)}function an(bc){if(!aR){return}if(bc===true){aR.bind(ax,a4);aR.bind(V,M);if(T){aR.bind(T,L)}}else{aR.unbind(ax,a4,false);aR.unbind(V,M,false);if(T){aR.unbind(T,L,false)}}aR.data(C+"_intouch",bc===true)}function ai(be,bc){var bd={start:{x:0,y:0},last:{x:0,y:0},end:{x:0,y:0}};bd.start.x=bd.last.x=bd.end.x=bc.pageX||bc.clientX;bd.start.y=bd.last.y=bd.end.y=bc.pageY||bc.clientY;aQ[be]=bd;return bd}function aH(bc){var be=bc.identifier!==undefined?bc.identifier:0;var bd=ad(be);if(bd===null){bd=ai(be,bc)}bd.last.x=bd.end.x;bd.last.y=bd.end.y;bd.end.x=bc.pageX||bc.clientX;bd.end.y=bc.pageY||bc.clientY;return bd}function ad(bc){return aQ[bc]||null}function aI(bc,bd){bd=Math.max(bd,aT(bc));N[bc].distance=bd}function aT(bc){if(N[bc]){return N[bc].distance}return undefined}function ab(){var bc={};bc[p]=av(p);bc[o]=av(o);bc[e]=av(e);bc[x]=av(x);return bc}function av(bc){return{direction:bc,distance:0}}function aM(){return a3-U}function at(bf,be){var bd=Math.abs(bf.x-be.x);var bc=Math.abs(bf.y-be.y);return Math.round(Math.sqrt(bd*bd+bc*bc))}function a8(bc,bd){var be=(bd/bc)*1;return be.toFixed(2)}function aq(){if(H<1){return A}else{return c}}function aS(bd,bc){return Math.round(Math.sqrt(Math.pow(bc.x-bd.x,2)+Math.pow(bc.y-bd.y,2)))}function aE(bf,bd){var bc=bf.x-bd.x;var bh=bd.y-bf.y;var be=Math.atan2(bh,bc);var bg=Math.round(be*180/Math.PI);if(bg<0){bg=360-Math.abs(bg)}return bg}function aL(bd,bc){var be=aE(bd,bc);if((be<=45)&&(be>=0)){return p}else{if((be<=360)&&(be>=315)){return p}else{if((be>=135)&&(be<=225)){return o}else{if((be>45)&&(be<135)){return x}else{return e}}}}}function ar(){var bc=new Date();return bc.getTime()}function aY(bc){bc=f(bc);var be=bc.offset();var bd={left:be.left,right:be.left+bc.outerWidth(),top:be.top,bottom:be.top+bc.outerHeight()};return bd}function F(bc,bd){return(bc.x>bd.left&&bc.x<bd.right&&bc.y>bd.top&&bc.y<bd.bottom)}}}));
+
+/***/ },
+/* 51 */
+/***/ function(module, exports) {
+
+	/* WEBPACK VAR INJECTION */(function(__webpack_amd_options__) {module.exports = __webpack_amd_options__;
+
+	/* WEBPACK VAR INJECTION */}.call(exports, {}))
+
+/***/ },
+/* 52 */
+/***/ function(module, exports, __webpack_require__) {
+
+	var __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/*eslint-disable no-unused-vars*/
+	/*!
+	 * jQuery JavaScript Library v3.1.0
+	 * https://jquery.com/
+	 *
+	 * Includes Sizzle.js
+	 * https://sizzlejs.com/
+	 *
+	 * Copyright jQuery Foundation and other contributors
+	 * Released under the MIT license
+	 * https://jquery.org/license
+	 *
+	 * Date: 2016-07-07T21:44Z
+	 */
+	( function( global, factory ) {
+
+		"use strict";
+
+		if ( typeof module === "object" && typeof module.exports === "object" ) {
+
+			// For CommonJS and CommonJS-like environments where a proper `window`
+			// is present, execute the factory and get jQuery.
+			// For environments that do not have a `window` with a `document`
+			// (such as Node.js), expose a factory as module.exports.
+			// This accentuates the need for the creation of a real `window`.
+			// e.g. var jQuery = require("jquery")(window);
+			// See ticket #14549 for more info.
+			module.exports = global.document ?
+				factory( global, true ) :
+				function( w ) {
+					if ( !w.document ) {
+						throw new Error( "jQuery requires a window with a document" );
+					}
+					return factory( w );
+				};
+		} else {
+			factory( global );
+		}
+
+	// Pass this if window is not defined yet
+	} )( typeof window !== "undefined" ? window : this, function( window, noGlobal ) {
+
+	// Edge <= 12 - 13+, Firefox <=18 - 45+, IE 10 - 11, Safari 5.1 - 9+, iOS 6 - 9.1
+	// throw exceptions when non-strict code (e.g., ASP.NET 4.5) accesses strict mode
+	// arguments.callee.caller (trac-13335). But as of jQuery 3.0 (2016), strict mode should be common
+	// enough that all such attempts are guarded in a try block.
+	"use strict";
+
+	var arr = [];
+
+	var document = window.document;
+
+	var getProto = Object.getPrototypeOf;
+
+	var slice = arr.slice;
+
+	var concat = arr.concat;
+
+	var push = arr.push;
+
+	var indexOf = arr.indexOf;
+
+	var class2type = {};
+
+	var toString = class2type.toString;
+
+	var hasOwn = class2type.hasOwnProperty;
+
+	var fnToString = hasOwn.toString;
+
+	var ObjectFunctionString = fnToString.call( Object );
+
+	var support = {};
+
+
+
+		function DOMEval( code, doc ) {
+			doc = doc || document;
+
+			var script = doc.createElement( "script" );
+
+			script.text = code;
+			doc.head.appendChild( script ).parentNode.removeChild( script );
+		}
+	/* global Symbol */
+	// Defining this global in .eslintrc would create a danger of using the global
+	// unguarded in another place, it seems safer to define global only for this module
+
+
+
+	var
+		version = "3.1.0",
+
+		// Define a local copy of jQuery
+		jQuery = function( selector, context ) {
+
+			// The jQuery object is actually just the init constructor 'enhanced'
+			// Need init if jQuery is called (just allow error to be thrown if not included)
+			return new jQuery.fn.init( selector, context );
+		},
+
+		// Support: Android <=4.0 only
+		// Make sure we trim BOM and NBSP
+		rtrim = /^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,
+
+		// Matches dashed string for camelizing
+		rmsPrefix = /^-ms-/,
+		rdashAlpha = /-([a-z])/g,
+
+		// Used by jQuery.camelCase as callback to replace()
+		fcamelCase = function( all, letter ) {
+			return letter.toUpperCase();
+		};
+
+	jQuery.fn = jQuery.prototype = {
+
+		// The current version of jQuery being used
+		jquery: version,
+
+		constructor: jQuery,
+
+		// The default length of a jQuery object is 0
+		length: 0,
+
+		toArray: function() {
+			return slice.call( this );
+		},
+
+		// Get the Nth element in the matched element set OR
+		// Get the whole matched element set as a clean array
+		get: function( num ) {
+			return num != null ?
+
+				// Return just the one element from the set
+				( num < 0 ? this[ num + this.length ] : this[ num ] ) :
+
+				// Return all the elements in a clean array
+				slice.call( this );
+		},
+
+		// Take an array of elements and push it onto the stack
+		// (returning the new matched element set)
+		pushStack: function( elems ) {
+
+			// Build a new jQuery matched element set
+			var ret = jQuery.merge( this.constructor(), elems );
+
+			// Add the old object onto the stack (as a reference)
+			ret.prevObject = this;
+
+			// Return the newly-formed element set
+			return ret;
+		},
+
+		// Execute a callback for every element in the matched set.
+		each: function( callback ) {
+			return jQuery.each( this, callback );
+		},
+
+		map: function( callback ) {
+			return this.pushStack( jQuery.map( this, function( elem, i ) {
+				return callback.call( elem, i, elem );
+			} ) );
+		},
+
+		slice: function() {
+			return this.pushStack( slice.apply( this, arguments ) );
+		},
+
+		first: function() {
+			return this.eq( 0 );
+		},
+
+		last: function() {
+			return this.eq( -1 );
+		},
+
+		eq: function( i ) {
+			var len = this.length,
+				j = +i + ( i < 0 ? len : 0 );
+			return this.pushStack( j >= 0 && j < len ? [ this[ j ] ] : [] );
+		},
+
+		end: function() {
+			return this.prevObject || this.constructor();
+		},
+
+		// For internal use only.
+		// Behaves like an Array's method, not like a jQuery method.
+		push: push,
+		sort: arr.sort,
+		splice: arr.splice
+	};
+
+	jQuery.extend = jQuery.fn.extend = function() {
+		var options, name, src, copy, copyIsArray, clone,
+			target = arguments[ 0 ] || {},
+			i = 1,
+			length = arguments.length,
+			deep = false;
+
+		// Handle a deep copy situation
+		if ( typeof target === "boolean" ) {
+			deep = target;
+
+			// Skip the boolean and the target
+			target = arguments[ i ] || {};
+			i++;
+		}
+
+		// Handle case when target is a string or something (possible in deep copy)
+		if ( typeof target !== "object" && !jQuery.isFunction( target ) ) {
+			target = {};
+		}
+
+		// Extend jQuery itself if only one argument is passed
+		if ( i === length ) {
+			target = this;
+			i--;
+		}
+
+		for ( ; i < length; i++ ) {
+
+			// Only deal with non-null/undefined values
+			if ( ( options = arguments[ i ] ) != null ) {
+
+				// Extend the base object
+				for ( name in options ) {
+					src = target[ name ];
+					copy = options[ name ];
+
+					// Prevent never-ending loop
+					if ( target === copy ) {
+						continue;
+					}
+
+					// Recurse if we're merging plain objects or arrays
+					if ( deep && copy && ( jQuery.isPlainObject( copy ) ||
+						( copyIsArray = jQuery.isArray( copy ) ) ) ) {
+
+						if ( copyIsArray ) {
+							copyIsArray = false;
+							clone = src && jQuery.isArray( src ) ? src : [];
+
+						} else {
+							clone = src && jQuery.isPlainObject( src ) ? src : {};
+						}
+
+						// Never move original objects, clone them
+						target[ name ] = jQuery.extend( deep, clone, copy );
+
+					// Don't bring in undefined values
+					} else if ( copy !== undefined ) {
+						target[ name ] = copy;
+					}
+				}
+			}
+		}
+
+		// Return the modified object
+		return target;
+	};
+
+	jQuery.extend( {
+
+		// Unique for each copy of jQuery on the page
+		expando: "jQuery" + ( version + Math.random() ).replace( /\D/g, "" ),
+
+		// Assume jQuery is ready without the ready module
+		isReady: true,
+
+		error: function( msg ) {
+			throw new Error( msg );
+		},
+
+		noop: function() {},
+
+		isFunction: function( obj ) {
+			return jQuery.type( obj ) === "function";
+		},
+
+		isArray: Array.isArray,
+
+		isWindow: function( obj ) {
+			return obj != null && obj === obj.window;
+		},
+
+		isNumeric: function( obj ) {
+
+			// As of jQuery 3.0, isNumeric is limited to
+			// strings and numbers (primitives or objects)
+			// that can be coerced to finite numbers (gh-2662)
+			var type = jQuery.type( obj );
+			return ( type === "number" || type === "string" ) &&
+
+				// parseFloat NaNs numeric-cast false positives ("")
+				// ...but misinterprets leading-number strings, particularly hex literals ("0x...")
+				// subtraction forces infinities to NaN
+				!isNaN( obj - parseFloat( obj ) );
+		},
+
+		isPlainObject: function( obj ) {
+			var proto, Ctor;
+
+			// Detect obvious negatives
+			// Use toString instead of jQuery.type to catch host objects
+			if ( !obj || toString.call( obj ) !== "[object Object]" ) {
+				return false;
+			}
+
+			proto = getProto( obj );
+
+			// Objects with no prototype (e.g., `Object.create( null )`) are plain
+			if ( !proto ) {
+				return true;
+			}
+
+			// Objects with prototype are plain iff they were constructed by a global Object function
+			Ctor = hasOwn.call( proto, "constructor" ) && proto.constructor;
+			return typeof Ctor === "function" && fnToString.call( Ctor ) === ObjectFunctionString;
+		},
+
+		isEmptyObject: function( obj ) {
+
+			/* eslint-disable no-unused-vars */
+			// See https://github.com/eslint/eslint/issues/6125
+			var name;
+
+			for ( name in obj ) {
+				return false;
+			}
+			return true;
+		},
+
+		type: function( obj ) {
+			if ( obj == null ) {
+				return obj + "";
+			}
+
+			// Support: Android <=2.3 only (functionish RegExp)
+			return typeof obj === "object" || typeof obj === "function" ?
+				class2type[ toString.call( obj ) ] || "object" :
+				typeof obj;
+		},
+
+		// Evaluates a script in a global context
+		globalEval: function( code ) {
+			DOMEval( code );
+		},
+
+		// Convert dashed to camelCase; used by the css and data modules
+		// Support: IE <=9 - 11, Edge 12 - 13
+		// Microsoft forgot to hump their vendor prefix (#9572)
+		camelCase: function( string ) {
+			return string.replace( rmsPrefix, "ms-" ).replace( rdashAlpha, fcamelCase );
+		},
+
+		nodeName: function( elem, name ) {
+			return elem.nodeName && elem.nodeName.toLowerCase() === name.toLowerCase();
+		},
+
+		each: function( obj, callback ) {
+			var length, i = 0;
+
+			if ( isArrayLike( obj ) ) {
+				length = obj.length;
+				for ( ; i < length; i++ ) {
+					if ( callback.call( obj[ i ], i, obj[ i ] ) === false ) {
+						break;
+					}
+				}
+			} else {
+				for ( i in obj ) {
+					if ( callback.call( obj[ i ], i, obj[ i ] ) === false ) {
+						break;
+					}
+				}
+			}
+
+			return obj;
+		},
+
+		// Support: Android <=4.0 only
+		trim: function( text ) {
+			return text == null ?
+				"" :
+				( text + "" ).replace( rtrim, "" );
+		},
+
+		// results is for internal usage only
+		makeArray: function( arr, results ) {
+			var ret = results || [];
+
+			if ( arr != null ) {
+				if ( isArrayLike( Object( arr ) ) ) {
+					jQuery.merge( ret,
+						typeof arr === "string" ?
+						[ arr ] : arr
+					);
+				} else {
+					push.call( ret, arr );
+				}
+			}
+
+			return ret;
+		},
+
+		inArray: function( elem, arr, i ) {
+			return arr == null ? -1 : indexOf.call( arr, elem, i );
+		},
+
+		// Support: Android <=4.0 only, PhantomJS 1 only
+		// push.apply(_, arraylike) throws on ancient WebKit
+		merge: function( first, second ) {
+			var len = +second.length,
+				j = 0,
+				i = first.length;
+
+			for ( ; j < len; j++ ) {
+				first[ i++ ] = second[ j ];
+			}
+
+			first.length = i;
+
+			return first;
+		},
+
+		grep: function( elems, callback, invert ) {
+			var callbackInverse,
+				matches = [],
+				i = 0,
+				length = elems.length,
+				callbackExpect = !invert;
+
+			// Go through the array, only saving the items
+			// that pass the validator function
+			for ( ; i < length; i++ ) {
+				callbackInverse = !callback( elems[ i ], i );
+				if ( callbackInverse !== callbackExpect ) {
+					matches.push( elems[ i ] );
+				}
+			}
+
+			return matches;
+		},
+
+		// arg is for internal usage only
+		map: function( elems, callback, arg ) {
+			var length, value,
+				i = 0,
+				ret = [];
+
+			// Go through the array, translating each of the items to their new values
+			if ( isArrayLike( elems ) ) {
+				length = elems.length;
+				for ( ; i < length; i++ ) {
+					value = callback( elems[ i ], i, arg );
+
+					if ( value != null ) {
+						ret.push( value );
+					}
+				}
+
+			// Go through every key on the object,
+			} else {
+				for ( i in elems ) {
+					value = callback( elems[ i ], i, arg );
+
+					if ( value != null ) {
+						ret.push( value );
+					}
+				}
+			}
+
+			// Flatten any nested arrays
+			return concat.apply( [], ret );
+		},
+
+		// A global GUID counter for objects
+		guid: 1,
+
+		// Bind a function to a context, optionally partially applying any
+		// arguments.
+		proxy: function( fn, context ) {
+			var tmp, args, proxy;
+
+			if ( typeof context === "string" ) {
+				tmp = fn[ context ];
+				context = fn;
+				fn = tmp;
+			}
+
+			// Quick check to determine if target is callable, in the spec
+			// this throws a TypeError, but we will just return undefined.
+			if ( !jQuery.isFunction( fn ) ) {
+				return undefined;
+			}
+
+			// Simulated bind
+			args = slice.call( arguments, 2 );
+			proxy = function() {
+				return fn.apply( context || this, args.concat( slice.call( arguments ) ) );
+			};
+
+			// Set the guid of unique handler to the same of original handler, so it can be removed
+			proxy.guid = fn.guid = fn.guid || jQuery.guid++;
+
+			return proxy;
+		},
+
+		now: Date.now,
+
+		// jQuery.support is not used in Core but other projects attach their
+		// properties to it so it needs to exist.
+		support: support
+	} );
+
+	if ( typeof Symbol === "function" ) {
+		jQuery.fn[ Symbol.iterator ] = arr[ Symbol.iterator ];
+	}
+
+	// Populate the class2type map
+	jQuery.each( "Boolean Number String Function Array Date RegExp Object Error Symbol".split( " " ),
+	function( i, name ) {
+		class2type[ "[object " + name + "]" ] = name.toLowerCase();
+	} );
+
+	function isArrayLike( obj ) {
+
+		// Support: real iOS 8.2 only (not reproducible in simulator)
+		// `in` check used to prevent JIT error (gh-2145)
+		// hasOwn isn't used here due to false negatives
+		// regarding Nodelist length in IE
+		var length = !!obj && "length" in obj && obj.length,
+			type = jQuery.type( obj );
+
+		if ( type === "function" || jQuery.isWindow( obj ) ) {
+			return false;
+		}
+
+		return type === "array" || length === 0 ||
+			typeof length === "number" && length > 0 && ( length - 1 ) in obj;
+	}
+	var Sizzle =
+	/*!
+	 * Sizzle CSS Selector Engine v2.3.0
+	 * https://sizzlejs.com/
+	 *
+	 * Copyright jQuery Foundation and other contributors
+	 * Released under the MIT license
+	 * http://jquery.org/license
+	 *
+	 * Date: 2016-01-04
+	 */
+	(function( window ) {
+
+	var i,
+		support,
+		Expr,
+		getText,
+		isXML,
+		tokenize,
+		compile,
+		select,
+		outermostContext,
+		sortInput,
+		hasDuplicate,
+
+		// Local document vars
+		setDocument,
+		document,
+		docElem,
+		documentIsHTML,
+		rbuggyQSA,
+		rbuggyMatches,
+		matches,
+		contains,
+
+		// Instance-specific data
+		expando = "sizzle" + 1 * new Date(),
+		preferredDoc = window.document,
+		dirruns = 0,
+		done = 0,
+		classCache = createCache(),
+		tokenCache = createCache(),
+		compilerCache = createCache(),
+		sortOrder = function( a, b ) {
+			if ( a === b ) {
+				hasDuplicate = true;
+			}
+			return 0;
+		},
+
+		// Instance methods
+		hasOwn = ({}).hasOwnProperty,
+		arr = [],
+		pop = arr.pop,
+		push_native = arr.push,
+		push = arr.push,
+		slice = arr.slice,
+		// Use a stripped-down indexOf as it's faster than native
+		// https://jsperf.com/thor-indexof-vs-for/5
+		indexOf = function( list, elem ) {
+			var i = 0,
+				len = list.length;
+			for ( ; i < len; i++ ) {
+				if ( list[i] === elem ) {
+					return i;
+				}
+			}
+			return -1;
+		},
+
+		booleans = "checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",
+
+		// Regular expressions
+
+		// http://www.w3.org/TR/css3-selectors/#whitespace
+		whitespace = "[\\x20\\t\\r\\n\\f]",
+
+		// http://www.w3.org/TR/CSS21/syndata.html#value-def-identifier
+		identifier = "(?:\\\\.|[\\w-]|[^\0-\\xa0])+",
+
+		// Attribute selectors: http://www.w3.org/TR/selectors/#attribute-selectors
+		attributes = "\\[" + whitespace + "*(" + identifier + ")(?:" + whitespace +
+			// Operator (capture 2)
+			"*([*^$|!~]?=)" + whitespace +
+			// "Attribute values must be CSS identifiers [capture 5] or strings [capture 3 or capture 4]"
+			"*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|(" + identifier + "))|)" + whitespace +
+			"*\\]",
+
+		pseudos = ":(" + identifier + ")(?:\\((" +
+			// To reduce the number of selectors needing tokenize in the preFilter, prefer arguments:
+			// 1. quoted (capture 3; capture 4 or capture 5)
+			"('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|" +
+			// 2. simple (capture 6)
+			"((?:\\\\.|[^\\\\()[\\]]|" + attributes + ")*)|" +
+			// 3. anything else (capture 2)
+			".*" +
+			")\\)|)",
+
+		// Leading and non-escaped trailing whitespace, capturing some non-whitespace characters preceding the latter
+		rwhitespace = new RegExp( whitespace + "+", "g" ),
+		rtrim = new RegExp( "^" + whitespace + "+|((?:^|[^\\\\])(?:\\\\.)*)" + whitespace + "+$", "g" ),
+
+		rcomma = new RegExp( "^" + whitespace + "*," + whitespace + "*" ),
+		rcombinators = new RegExp( "^" + whitespace + "*([>+~]|" + whitespace + ")" + whitespace + "*" ),
+
+		rattributeQuotes = new RegExp( "=" + whitespace + "*([^\\]'\"]*?)" + whitespace + "*\\]", "g" ),
+
+		rpseudo = new RegExp( pseudos ),
+		ridentifier = new RegExp( "^" + identifier + "$" ),
+
+		matchExpr = {
+			"ID": new RegExp( "^#(" + identifier + ")" ),
+			"CLASS": new RegExp( "^\\.(" + identifier + ")" ),
+			"TAG": new RegExp( "^(" + identifier + "|[*])" ),
+			"ATTR": new RegExp( "^" + attributes ),
+			"PSEUDO": new RegExp( "^" + pseudos ),
+			"CHILD": new RegExp( "^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\(" + whitespace +
+				"*(even|odd|(([+-]|)(\\d*)n|)" + whitespace + "*(?:([+-]|)" + whitespace +
+				"*(\\d+)|))" + whitespace + "*\\)|)", "i" ),
+			"bool": new RegExp( "^(?:" + booleans + ")$", "i" ),
+			// For use in libraries implementing .is()
+			// We use this for POS matching in `select`
+			"needsContext": new RegExp( "^" + whitespace + "*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\(" +
+				whitespace + "*((?:-\\d)?\\d*)" + whitespace + "*\\)|)(?=[^-]|$)", "i" )
+		},
+
+		rinputs = /^(?:input|select|textarea|button)$/i,
+		rheader = /^h\d$/i,
+
+		rnative = /^[^{]+\{\s*\[native \w/,
+
+		// Easily-parseable/retrievable ID or TAG or CLASS selectors
+		rquickExpr = /^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,
+
+		rsibling = /[+~]/,
+
+		// CSS escapes
+		// http://www.w3.org/TR/CSS21/syndata.html#escaped-characters
+		runescape = new RegExp( "\\\\([\\da-f]{1,6}" + whitespace + "?|(" + whitespace + ")|.)", "ig" ),
+		funescape = function( _, escaped, escapedWhitespace ) {
+			var high = "0x" + escaped - 0x10000;
+			// NaN means non-codepoint
+			// Support: Firefox<24
+			// Workaround erroneous numeric interpretation of +"0x"
+			return high !== high || escapedWhitespace ?
+				escaped :
+				high < 0 ?
+					// BMP codepoint
+					String.fromCharCode( high + 0x10000 ) :
+					// Supplemental Plane codepoint (surrogate pair)
+					String.fromCharCode( high >> 10 | 0xD800, high & 0x3FF | 0xDC00 );
+		},
+
+		// CSS string/identifier serialization
+		// https://drafts.csswg.org/cssom/#common-serializing-idioms
+		rcssescape = /([\0-\x1f\x7f]|^-?\d)|^-$|[^\x80-\uFFFF\w-]/g,
+		fcssescape = function( ch, asCodePoint ) {
+			if ( asCodePoint ) {
+
+				// U+0000 NULL becomes U+FFFD REPLACEMENT CHARACTER
+				if ( ch === "\0" ) {
+					return "\uFFFD";
+				}
+
+				// Control characters and (dependent upon position) numbers get escaped as code points
+				return ch.slice( 0, -1 ) + "\\" + ch.charCodeAt( ch.length - 1 ).toString( 16 ) + " ";
+			}
+
+			// Other potentially-special ASCII characters get backslash-escaped
+			return "\\" + ch;
+		},
+
+		// Used for iframes
+		// See setDocument()
+		// Removing the function wrapper causes a "Permission Denied"
+		// error in IE
+		unloadHandler = function() {
+			setDocument();
+		},
+
+		disabledAncestor = addCombinator(
+			function( elem ) {
+				return elem.disabled === true;
+			},
+			{ dir: "parentNode", next: "legend" }
+		);
+
+	// Optimize for push.apply( _, NodeList )
+	try {
+		push.apply(
+			(arr = slice.call( preferredDoc.childNodes )),
+			preferredDoc.childNodes
+		);
+		// Support: Android<4.0
+		// Detect silently failing push.apply
+		arr[ preferredDoc.childNodes.length ].nodeType;
+	} catch ( e ) {
+		push = { apply: arr.length ?
+
+			// Leverage slice if possible
+			function( target, els ) {
+				push_native.apply( target, slice.call(els) );
+			} :
+
+			// Support: IE<9
+			// Otherwise append directly
+			function( target, els ) {
+				var j = target.length,
+					i = 0;
+				// Can't trust NodeList.length
+				while ( (target[j++] = els[i++]) ) {}
+				target.length = j - 1;
+			}
+		};
+	}
+
+	function Sizzle( selector, context, results, seed ) {
+		var m, i, elem, nid, match, groups, newSelector,
+			newContext = context && context.ownerDocument,
+
+			// nodeType defaults to 9, since context defaults to document
+			nodeType = context ? context.nodeType : 9;
+
+		results = results || [];
+
+		// Return early from calls with invalid selector or context
+		if ( typeof selector !== "string" || !selector ||
+			nodeType !== 1 && nodeType !== 9 && nodeType !== 11 ) {
+
+			return results;
+		}
+
+		// Try to shortcut find operations (as opposed to filters) in HTML documents
+		if ( !seed ) {
+
+			if ( ( context ? context.ownerDocument || context : preferredDoc ) !== document ) {
+				setDocument( context );
+			}
+			context = context || document;
+
+			if ( documentIsHTML ) {
+
+				// If the selector is sufficiently simple, try using a "get*By*" DOM method
+				// (excepting DocumentFragment context, where the methods don't exist)
+				if ( nodeType !== 11 && (match = rquickExpr.exec( selector )) ) {
+
+					// ID selector
+					if ( (m = match[1]) ) {
+
+						// Document context
+						if ( nodeType === 9 ) {
+							if ( (elem = context.getElementById( m )) ) {
+
+								// Support: IE, Opera, Webkit
+								// TODO: identify versions
+								// getElementById can match elements by name instead of ID
+								if ( elem.id === m ) {
+									results.push( elem );
+									return results;
+								}
+							} else {
+								return results;
+							}
+
+						// Element context
+						} else {
+
+							// Support: IE, Opera, Webkit
+							// TODO: identify versions
+							// getElementById can match elements by name instead of ID
+							if ( newContext && (elem = newContext.getElementById( m )) &&
+								contains( context, elem ) &&
+								elem.id === m ) {
+
+								results.push( elem );
+								return results;
+							}
+						}
+
+					// Type selector
+					} else if ( match[2] ) {
+						push.apply( results, context.getElementsByTagName( selector ) );
+						return results;
+
+					// Class selector
+					} else if ( (m = match[3]) && support.getElementsByClassName &&
+						context.getElementsByClassName ) {
+
+						push.apply( results, context.getElementsByClassName( m ) );
+						return results;
+					}
+				}
+
+				// Take advantage of querySelectorAll
+				if ( support.qsa &&
+					!compilerCache[ selector + " " ] &&
+					(!rbuggyQSA || !rbuggyQSA.test( selector )) ) {
+
+					if ( nodeType !== 1 ) {
+						newContext = context;
+						newSelector = selector;
+
+					// qSA looks outside Element context, which is not what we want
+					// Thanks to Andrew Dupont for this workaround technique
+					// Support: IE <=8
+					// Exclude object elements
+					} else if ( context.nodeName.toLowerCase() !== "object" ) {
+
+						// Capture the context ID, setting it first if necessary
+						if ( (nid = context.getAttribute( "id" )) ) {
+							nid = nid.replace( rcssescape, fcssescape );
+						} else {
+							context.setAttribute( "id", (nid = expando) );
+						}
+
+						// Prefix every selector in the list
+						groups = tokenize( selector );
+						i = groups.length;
+						while ( i-- ) {
+							groups[i] = "#" + nid + " " + toSelector( groups[i] );
+						}
+						newSelector = groups.join( "," );
+
+						// Expand context for sibling selectors
+						newContext = rsibling.test( selector ) && testContext( context.parentNode ) ||
+							context;
+					}
+
+					if ( newSelector ) {
+						try {
+							push.apply( results,
+								newContext.querySelectorAll( newSelector )
+							);
+							return results;
+						} catch ( qsaError ) {
+						} finally {
+							if ( nid === expando ) {
+								context.removeAttribute( "id" );
+							}
+						}
+					}
+				}
+			}
+		}
+
+		// All others
+		return select( selector.replace( rtrim, "$1" ), context, results, seed );
+	}
+
+	/**
+	 * Create key-value caches of limited size
+	 * @returns {function(string, object)} Returns the Object data after storing it on itself with
+	 *	property name the (space-suffixed) string and (if the cache is larger than Expr.cacheLength)
+	 *	deleting the oldest entry
+	 */
+	function createCache() {
+		var keys = [];
+
+		function cache( key, value ) {
+			// Use (key + " ") to avoid collision with native prototype properties (see Issue #157)
+			if ( keys.push( key + " " ) > Expr.cacheLength ) {
+				// Only keep the most recent entries
+				delete cache[ keys.shift() ];
+			}
+			return (cache[ key + " " ] = value);
+		}
+		return cache;
+	}
+
+	/**
+	 * Mark a function for special use by Sizzle
+	 * @param {Function} fn The function to mark
+	 */
+	function markFunction( fn ) {
+		fn[ expando ] = true;
+		return fn;
+	}
+
+	/**
+	 * Support testing using an element
+	 * @param {Function} fn Passed the created element and returns a boolean result
+	 */
+	function assert( fn ) {
+		var el = document.createElement("fieldset");
+
+		try {
+			return !!fn( el );
+		} catch (e) {
+			return false;
+		} finally {
+			// Remove from its parent by default
+			if ( el.parentNode ) {
+				el.parentNode.removeChild( el );
+			}
+			// release memory in IE
+			el = null;
+		}
+	}
+
+	/**
+	 * Adds the same handler for all of the specified attrs
+	 * @param {String} attrs Pipe-separated list of attributes
+	 * @param {Function} handler The method that will be applied
+	 */
+	function addHandle( attrs, handler ) {
+		var arr = attrs.split("|"),
+			i = arr.length;
+
+		while ( i-- ) {
+			Expr.attrHandle[ arr[i] ] = handler;
+		}
+	}
+
+	/**
+	 * Checks document order of two siblings
+	 * @param {Element} a
+	 * @param {Element} b
+	 * @returns {Number} Returns less than 0 if a precedes b, greater than 0 if a follows b
+	 */
+	function siblingCheck( a, b ) {
+		var cur = b && a,
+			diff = cur && a.nodeType === 1 && b.nodeType === 1 &&
+				a.sourceIndex - b.sourceIndex;
+
+		// Use IE sourceIndex if available on both nodes
+		if ( diff ) {
+			return diff;
+		}
+
+		// Check if b follows a
+		if ( cur ) {
+			while ( (cur = cur.nextSibling) ) {
+				if ( cur === b ) {
+					return -1;
+				}
+			}
+		}
+
+		return a ? 1 : -1;
+	}
+
+	/**
+	 * Returns a function to use in pseudos for input types
+	 * @param {String} type
+	 */
+	function createInputPseudo( type ) {
+		return function( elem ) {
+			var name = elem.nodeName.toLowerCase();
+			return name === "input" && elem.type === type;
+		};
+	}
+
+	/**
+	 * Returns a function to use in pseudos for buttons
+	 * @param {String} type
+	 */
+	function createButtonPseudo( type ) {
+		return function( elem ) {
+			var name = elem.nodeName.toLowerCase();
+			return (name === "input" || name === "button") && elem.type === type;
+		};
+	}
+
+	/**
+	 * Returns a function to use in pseudos for :enabled/:disabled
+	 * @param {Boolean} disabled true for :disabled; false for :enabled
+	 */
+	function createDisabledPseudo( disabled ) {
+		// Known :disabled false positives:
+		// IE: *[disabled]:not(button, input, select, textarea, optgroup, option, menuitem, fieldset)
+		// not IE: fieldset[disabled] > legend:nth-of-type(n+2) :can-disable
+		return function( elem ) {
+
+			// Check form elements and option elements for explicit disabling
+			return "label" in elem && elem.disabled === disabled ||
+				"form" in elem && elem.disabled === disabled ||
+
+				// Check non-disabled form elements for fieldset[disabled] ancestors
+				"form" in elem && elem.disabled === false && (
+					// Support: IE6-11+
+					// Ancestry is covered for us
+					elem.isDisabled === disabled ||
+
+					// Otherwise, assume any non-<option> under fieldset[disabled] is disabled
+					/* jshint -W018 */
+					elem.isDisabled !== !disabled &&
+						("label" in elem || !disabledAncestor( elem )) !== disabled
+				);
+		};
+	}
+
+	/**
+	 * Returns a function to use in pseudos for positionals
+	 * @param {Function} fn
+	 */
+	function createPositionalPseudo( fn ) {
+		return markFunction(function( argument ) {
+			argument = +argument;
+			return markFunction(function( seed, matches ) {
+				var j,
+					matchIndexes = fn( [], seed.length, argument ),
+					i = matchIndexes.length;
+
+				// Match elements found at the specified indexes
+				while ( i-- ) {
+					if ( seed[ (j = matchIndexes[i]) ] ) {
+						seed[j] = !(matches[j] = seed[j]);
+					}
+				}
+			});
+		});
+	}
+
+	/**
+	 * Checks a node for validity as a Sizzle context
+	 * @param {Element|Object=} context
+	 * @returns {Element|Object|Boolean} The input node if acceptable, otherwise a falsy value
+	 */
+	function testContext( context ) {
+		return context && typeof context.getElementsByTagName !== "undefined" && context;
+	}
+
+	// Expose support vars for convenience
+	support = Sizzle.support = {};
+
+	/**
+	 * Detects XML nodes
+	 * @param {Element|Object} elem An element or a document
+	 * @returns {Boolean} True iff elem is a non-HTML XML node
+	 */
+	isXML = Sizzle.isXML = function( elem ) {
+		// documentElement is verified for cases where it doesn't yet exist
+		// (such as loading iframes in IE - #4833)
+		var documentElement = elem && (elem.ownerDocument || elem).documentElement;
+		return documentElement ? documentElement.nodeName !== "HTML" : false;
+	};
+
+	/**
+	 * Sets document-related variables once based on the current document
+	 * @param {Element|Object} [doc] An element or document object to use to set the document
+	 * @returns {Object} Returns the current document
+	 */
+	setDocument = Sizzle.setDocument = function( node ) {
+		var hasCompare, subWindow,
+			doc = node ? node.ownerDocument || node : preferredDoc;
+
+		// Return early if doc is invalid or already selected
+		if ( doc === document || doc.nodeType !== 9 || !doc.documentElement ) {
+			return document;
+		}
+
+		// Update global variables
+		document = doc;
+		docElem = document.documentElement;
+		documentIsHTML = !isXML( document );
+
+		// Support: IE 9-11, Edge
+		// Accessing iframe documents after unload throws "permission denied" errors (jQuery #13936)
+		if ( preferredDoc !== document &&
+			(subWindow = document.defaultView) && subWindow.top !== subWindow ) {
+
+			// Support: IE 11, Edge
+			if ( subWindow.addEventListener ) {
+				subWindow.addEventListener( "unload", unloadHandler, false );
+
+			// Support: IE 9 - 10 only
+			} else if ( subWindow.attachEvent ) {
+				subWindow.attachEvent( "onunload", unloadHandler );
+			}
+		}
+
+		/* Attributes
+		---------------------------------------------------------------------- */
+
+		// Support: IE<8
+		// Verify that getAttribute really returns attributes and not properties
+		// (excepting IE8 booleans)
+		support.attributes = assert(function( el ) {
+			el.className = "i";
+			return !el.getAttribute("className");
+		});
+
+		/* getElement(s)By*
+		---------------------------------------------------------------------- */
+
+		// Check if getElementsByTagName("*") returns only elements
+		support.getElementsByTagName = assert(function( el ) {
+			el.appendChild( document.createComment("") );
+			return !el.getElementsByTagName("*").length;
+		});
+
+		// Support: IE<9
+		support.getElementsByClassName = rnative.test( document.getElementsByClassName );
+
+		// Support: IE<10
+		// Check if getElementById returns elements by name
+		// The broken getElementById methods don't pick up programmatically-set names,
+		// so use a roundabout getElementsByName test
+		support.getById = assert(function( el ) {
+			docElem.appendChild( el ).id = expando;
+			return !document.getElementsByName || !document.getElementsByName( expando ).length;
+		});
+
+		// ID find and filter
+		if ( support.getById ) {
+			Expr.find["ID"] = function( id, context ) {
+				if ( typeof context.getElementById !== "undefined" && documentIsHTML ) {
+					var m = context.getElementById( id );
+					return m ? [ m ] : [];
+				}
+			};
+			Expr.filter["ID"] = function( id ) {
+				var attrId = id.replace( runescape, funescape );
+				return function( elem ) {
+					return elem.getAttribute("id") === attrId;
+				};
+			};
+		} else {
+			// Support: IE6/7
+			// getElementById is not reliable as a find shortcut
+			delete Expr.find["ID"];
+
+			Expr.filter["ID"] =  function( id ) {
+				var attrId = id.replace( runescape, funescape );
+				return function( elem ) {
+					var node = typeof elem.getAttributeNode !== "undefined" &&
+						elem.getAttributeNode("id");
+					return node && node.value === attrId;
+				};
+			};
+		}
+
+		// Tag
+		Expr.find["TAG"] = support.getElementsByTagName ?
+			function( tag, context ) {
+				if ( typeof context.getElementsByTagName !== "undefined" ) {
+					return context.getElementsByTagName( tag );
+
+				// DocumentFragment nodes don't have gEBTN
+				} else if ( support.qsa ) {
+					return context.querySelectorAll( tag );
+				}
+			} :
+
+			function( tag, context ) {
+				var elem,
+					tmp = [],
+					i = 0,
+					// By happy coincidence, a (broken) gEBTN appears on DocumentFragment nodes too
+					results = context.getElementsByTagName( tag );
+
+				// Filter out possible comments
+				if ( tag === "*" ) {
+					while ( (elem = results[i++]) ) {
+						if ( elem.nodeType === 1 ) {
+							tmp.push( elem );
+						}
+					}
+
+					return tmp;
+				}
+				return results;
+			};
+
+		// Class
+		Expr.find["CLASS"] = support.getElementsByClassName && function( className, context ) {
+			if ( typeof context.getElementsByClassName !== "undefined" && documentIsHTML ) {
+				return context.getElementsByClassName( className );
+			}
+		};
+
+		/* QSA/matchesSelector
+		---------------------------------------------------------------------- */
+
+		// QSA and matchesSelector support
+
+		// matchesSelector(:active) reports false when true (IE9/Opera 11.5)
+		rbuggyMatches = [];
+
+		// qSa(:focus) reports false when true (Chrome 21)
+		// We allow this because of a bug in IE8/9 that throws an error
+		// whenever `document.activeElement` is accessed on an iframe
+		// So, we allow :focus to pass through QSA all the time to avoid the IE error
+		// See https://bugs.jquery.com/ticket/13378
+		rbuggyQSA = [];
+
+		if ( (support.qsa = rnative.test( document.querySelectorAll )) ) {
+			// Build QSA regex
+			// Regex strategy adopted from Diego Perini
+			assert(function( el ) {
+				// Select is set to empty string on purpose
+				// This is to test IE's treatment of not explicitly
+				// setting a boolean content attribute,
+				// since its presence should be enough
+				// https://bugs.jquery.com/ticket/12359
+				docElem.appendChild( el ).innerHTML = "<a id='" + expando + "'></a>" +
+					"<select id='" + expando + "-\r\\' msallowcapture=''>" +
+					"<option selected=''></option></select>";
+
+				// Support: IE8, Opera 11-12.16
+				// Nothing should be selected when empty strings follow ^= or $= or *=
+				// The test attribute must be unknown in Opera but "safe" for WinRT
+				// https://msdn.microsoft.com/en-us/library/ie/hh465388.aspx#attribute_section
+				if ( el.querySelectorAll("[msallowcapture^='']").length ) {
+					rbuggyQSA.push( "[*^$]=" + whitespace + "*(?:''|\"\")" );
+				}
+
+				// Support: IE8
+				// Boolean attributes and "value" are not treated correctly
+				if ( !el.querySelectorAll("[selected]").length ) {
+					rbuggyQSA.push( "\\[" + whitespace + "*(?:value|" + booleans + ")" );
+				}
+
+				// Support: Chrome<29, Android<4.4, Safari<7.0+, iOS<7.0+, PhantomJS<1.9.8+
+				if ( !el.querySelectorAll( "[id~=" + expando + "-]" ).length ) {
+					rbuggyQSA.push("~=");
+				}
+
+				// Webkit/Opera - :checked should return selected option elements
+				// http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked
+				// IE8 throws error here and will not see later tests
+				if ( !el.querySelectorAll(":checked").length ) {
+					rbuggyQSA.push(":checked");
+				}
+
+				// Support: Safari 8+, iOS 8+
+				// https://bugs.webkit.org/show_bug.cgi?id=136851
+				// In-page `selector#id sibling-combinator selector` fails
+				if ( !el.querySelectorAll( "a#" + expando + "+*" ).length ) {
+					rbuggyQSA.push(".#.+[+~]");
+				}
+			});
+
+			assert(function( el ) {
+				el.innerHTML = "<a href='' disabled='disabled'></a>" +
+					"<select disabled='disabled'><option/></select>";
+
+				// Support: Windows 8 Native Apps
+				// The type and name attributes are restricted during .innerHTML assignment
+				var input = document.createElement("input");
+				input.setAttribute( "type", "hidden" );
+				el.appendChild( input ).setAttribute( "name", "D" );
+
+				// Support: IE8
+				// Enforce case-sensitivity of name attribute
+				if ( el.querySelectorAll("[name=d]").length ) {
+					rbuggyQSA.push( "name" + whitespace + "*[*^$|!~]?=" );
+				}
+
+				// FF 3.5 - :enabled/:disabled and hidden elements (hidden elements are still enabled)
+				// IE8 throws error here and will not see later tests
+				if ( el.querySelectorAll(":enabled").length !== 2 ) {
+					rbuggyQSA.push( ":enabled", ":disabled" );
+				}
+
+				// Support: IE9-11+
+				// IE's :disabled selector does not pick up the children of disabled fieldsets
+				docElem.appendChild( el ).disabled = true;
+				if ( el.querySelectorAll(":disabled").length !== 2 ) {
+					rbuggyQSA.push( ":enabled", ":disabled" );
+				}
+
+				// Opera 10-11 does not throw on post-comma invalid pseudos
+				el.querySelectorAll("*,:x");
+				rbuggyQSA.push(",.*:");
+			});
+		}
+
+		if ( (support.matchesSelector = rnative.test( (matches = docElem.matches ||
+			docElem.webkitMatchesSelector ||
+			docElem.mozMatchesSelector ||
+			docElem.oMatchesSelector ||
+			docElem.msMatchesSelector) )) ) {
+
+			assert(function( el ) {
+				// Check to see if it's possible to do matchesSelector
+				// on a disconnected node (IE 9)
+				support.disconnectedMatch = matches.call( el, "*" );
+
+				// This should fail with an exception
+				// Gecko does not error, returns false instead
+				matches.call( el, "[s!='']:x" );
+				rbuggyMatches.push( "!=", pseudos );
+			});
+		}
+
+		rbuggyQSA = rbuggyQSA.length && new RegExp( rbuggyQSA.join("|") );
+		rbuggyMatches = rbuggyMatches.length && new RegExp( rbuggyMatches.join("|") );
+
+		/* Contains
+		---------------------------------------------------------------------- */
+		hasCompare = rnative.test( docElem.compareDocumentPosition );
+
+		// Element contains another
+		// Purposefully self-exclusive
+		// As in, an element does not contain itself
+		contains = hasCompare || rnative.test( docElem.contains ) ?
+			function( a, b ) {
+				var adown = a.nodeType === 9 ? a.documentElement : a,
+					bup = b && b.parentNode;
+				return a === bup || !!( bup && bup.nodeType === 1 && (
+					adown.contains ?
+						adown.contains( bup ) :
+						a.compareDocumentPosition && a.compareDocumentPosition( bup ) & 16
+				));
+			} :
+			function( a, b ) {
+				if ( b ) {
+					while ( (b = b.parentNode) ) {
+						if ( b === a ) {
+							return true;
+						}
+					}
+				}
+				return false;
+			};
+
+		/* Sorting
+		---------------------------------------------------------------------- */
+
+		// Document order sorting
+		sortOrder = hasCompare ?
+		function( a, b ) {
+
+			// Flag for duplicate removal
+			if ( a === b ) {
+				hasDuplicate = true;
+				return 0;
+			}
+
+			// Sort on method existence if only one input has compareDocumentPosition
+			var compare = !a.compareDocumentPosition - !b.compareDocumentPosition;
+			if ( compare ) {
+				return compare;
+			}
+
+			// Calculate position if both inputs belong to the same document
+			compare = ( a.ownerDocument || a ) === ( b.ownerDocument || b ) ?
+				a.compareDocumentPosition( b ) :
+
+				// Otherwise we know they are disconnected
+				1;
+
+			// Disconnected nodes
+			if ( compare & 1 ||
+				(!support.sortDetached && b.compareDocumentPosition( a ) === compare) ) {
+
+				// Choose the first element that is related to our preferred document
+				if ( a === document || a.ownerDocument === preferredDoc && contains(preferredDoc, a) ) {
+					return -1;
+				}
+				if ( b === document || b.ownerDocument === preferredDoc && contains(preferredDoc, b) ) {
+					return 1;
+				}
+
+				// Maintain original order
+				return sortInput ?
+					( indexOf( sortInput, a ) - indexOf( sortInput, b ) ) :
+					0;
+			}
+
+			return compare & 4 ? -1 : 1;
+		} :
+		function( a, b ) {
+			// Exit early if the nodes are identical
+			if ( a === b ) {
+				hasDuplicate = true;
+				return 0;
+			}
+
+			var cur,
+				i = 0,
+				aup = a.parentNode,
+				bup = b.parentNode,
+				ap = [ a ],
+				bp = [ b ];
+
+			// Parentless nodes are either documents or disconnected
+			if ( !aup || !bup ) {
+				return a === document ? -1 :
+					b === document ? 1 :
+					aup ? -1 :
+					bup ? 1 :
+					sortInput ?
+					( indexOf( sortInput, a ) - indexOf( sortInput, b ) ) :
+					0;
+
+			// If the nodes are siblings, we can do a quick check
+			} else if ( aup === bup ) {
+				return siblingCheck( a, b );
+			}
+
+			// Otherwise we need full lists of their ancestors for comparison
+			cur = a;
+			while ( (cur = cur.parentNode) ) {
+				ap.unshift( cur );
+			}
+			cur = b;
+			while ( (cur = cur.parentNode) ) {
+				bp.unshift( cur );
+			}
+
+			// Walk down the tree looking for a discrepancy
+			while ( ap[i] === bp[i] ) {
+				i++;
+			}
+
+			return i ?
+				// Do a sibling check if the nodes have a common ancestor
+				siblingCheck( ap[i], bp[i] ) :
+
+				// Otherwise nodes in our document sort first
+				ap[i] === preferredDoc ? -1 :
+				bp[i] === preferredDoc ? 1 :
+				0;
+		};
+
+		return document;
+	};
+
+	Sizzle.matches = function( expr, elements ) {
+		return Sizzle( expr, null, null, elements );
+	};
+
+	Sizzle.matchesSelector = function( elem, expr ) {
+		// Set document vars if needed
+		if ( ( elem.ownerDocument || elem ) !== document ) {
+			setDocument( elem );
+		}
+
+		// Make sure that attribute selectors are quoted
+		expr = expr.replace( rattributeQuotes, "='$1']" );
+
+		if ( support.matchesSelector && documentIsHTML &&
+			!compilerCache[ expr + " " ] &&
+			( !rbuggyMatches || !rbuggyMatches.test( expr ) ) &&
+			( !rbuggyQSA     || !rbuggyQSA.test( expr ) ) ) {
+
+			try {
+				var ret = matches.call( elem, expr );
+
+				// IE 9's matchesSelector returns false on disconnected nodes
+				if ( ret || support.disconnectedMatch ||
+						// As well, disconnected nodes are said to be in a document
+						// fragment in IE 9
+						elem.document && elem.document.nodeType !== 11 ) {
+					return ret;
+				}
+			} catch (e) {}
+		}
+
+		return Sizzle( expr, document, null, [ elem ] ).length > 0;
+	};
+
+	Sizzle.contains = function( context, elem ) {
+		// Set document vars if needed
+		if ( ( context.ownerDocument || context ) !== document ) {
+			setDocument( context );
+		}
+		return contains( context, elem );
+	};
+
+	Sizzle.attr = function( elem, name ) {
+		// Set document vars if needed
+		if ( ( elem.ownerDocument || elem ) !== document ) {
+			setDocument( elem );
+		}
+
+		var fn = Expr.attrHandle[ name.toLowerCase() ],
+			// Don't get fooled by Object.prototype properties (jQuery #13807)
+			val = fn && hasOwn.call( Expr.attrHandle, name.toLowerCase() ) ?
+				fn( elem, name, !documentIsHTML ) :
+				undefined;
+
+		return val !== undefined ?
+			val :
+			support.attributes || !documentIsHTML ?
+				elem.getAttribute( name ) :
+				(val = elem.getAttributeNode(name)) && val.specified ?
+					val.value :
+					null;
+	};
+
+	Sizzle.escape = function( sel ) {
+		return (sel + "").replace( rcssescape, fcssescape );
+	};
+
+	Sizzle.error = function( msg ) {
+		throw new Error( "Syntax error, unrecognized expression: " + msg );
+	};
+
+	/**
+	 * Document sorting and removing duplicates
+	 * @param {ArrayLike} results
+	 */
+	Sizzle.uniqueSort = function( results ) {
+		var elem,
+			duplicates = [],
+			j = 0,
+			i = 0;
+
+		// Unless we *know* we can detect duplicates, assume their presence
+		hasDuplicate = !support.detectDuplicates;
+		sortInput = !support.sortStable && results.slice( 0 );
+		results.sort( sortOrder );
+
+		if ( hasDuplicate ) {
+			while ( (elem = results[i++]) ) {
+				if ( elem === results[ i ] ) {
+					j = duplicates.push( i );
+				}
+			}
+			while ( j-- ) {
+				results.splice( duplicates[ j ], 1 );
+			}
+		}
+
+		// Clear input after sorting to release objects
+		// See https://github.com/jquery/sizzle/pull/225
+		sortInput = null;
+
+		return results;
+	};
+
+	/**
+	 * Utility function for retrieving the text value of an array of DOM nodes
+	 * @param {Array|Element} elem
+	 */
+	getText = Sizzle.getText = function( elem ) {
+		var node,
+			ret = "",
+			i = 0,
+			nodeType = elem.nodeType;
+
+		if ( !nodeType ) {
+			// If no nodeType, this is expected to be an array
+			while ( (node = elem[i++]) ) {
+				// Do not traverse comment nodes
+				ret += getText( node );
+			}
+		} else if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) {
+			// Use textContent for elements
+			// innerText usage removed for consistency of new lines (jQuery #11153)
+			if ( typeof elem.textContent === "string" ) {
+				return elem.textContent;
+			} else {
+				// Traverse its children
+				for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) {
+					ret += getText( elem );
+				}
+			}
+		} else if ( nodeType === 3 || nodeType === 4 ) {
+			return elem.nodeValue;
+		}
+		// Do not include comment or processing instruction nodes
+
+		return ret;
+	};
+
+	Expr = Sizzle.selectors = {
+
+		// Can be adjusted by the user
+		cacheLength: 50,
+
+		createPseudo: markFunction,
+
+		match: matchExpr,
+
+		attrHandle: {},
+
+		find: {},
+
+		relative: {
+			">": { dir: "parentNode", first: true },
+			" ": { dir: "parentNode" },
+			"+": { dir: "previousSibling", first: true },
+			"~": { dir: "previousSibling" }
+		},
+
+		preFilter: {
+			"ATTR": function( match ) {
+				match[1] = match[1].replace( runescape, funescape );
+
+				// Move the given value to match[3] whether quoted or unquoted
+				match[3] = ( match[3] || match[4] || match[5] || "" ).replace( runescape, funescape );
+
+				if ( match[2] === "~=" ) {
+					match[3] = " " + match[3] + " ";
+				}
+
+				return match.slice( 0, 4 );
+			},
+
+			"CHILD": function( match ) {
+				/* matches from matchExpr["CHILD"]
+					1 type (only|nth|...)
+					2 what (child|of-type)
+					3 argument (even|odd|\d*|\d*n([+-]\d+)?|...)
+					4 xn-component of xn+y argument ([+-]?\d*n|)
+					5 sign of xn-component
+					6 x of xn-component
+					7 sign of y-component
+					8 y of y-component
+				*/
+				match[1] = match[1].toLowerCase();
+
+				if ( match[1].slice( 0, 3 ) === "nth" ) {
+					// nth-* requires argument
+					if ( !match[3] ) {
+						Sizzle.error( match[0] );
+					}
+
+					// numeric x and y parameters for Expr.filter.CHILD
+					// remember that false/true cast respectively to 0/1
+					match[4] = +( match[4] ? match[5] + (match[6] || 1) : 2 * ( match[3] === "even" || match[3] === "odd" ) );
+					match[5] = +( ( match[7] + match[8] ) || match[3] === "odd" );
+
+				// other types prohibit arguments
+				} else if ( match[3] ) {
+					Sizzle.error( match[0] );
+				}
+
+				return match;
+			},
+
+			"PSEUDO": function( match ) {
+				var excess,
+					unquoted = !match[6] && match[2];
+
+				if ( matchExpr["CHILD"].test( match[0] ) ) {
+					return null;
+				}
+
+				// Accept quoted arguments as-is
+				if ( match[3] ) {
+					match[2] = match[4] || match[5] || "";
+
+				// Strip excess characters from unquoted arguments
+				} else if ( unquoted && rpseudo.test( unquoted ) &&
+					// Get excess from tokenize (recursively)
+					(excess = tokenize( unquoted, true )) &&
+					// advance to the next closing parenthesis
+					(excess = unquoted.indexOf( ")", unquoted.length - excess ) - unquoted.length) ) {
+
+					// excess is a negative index
+					match[0] = match[0].slice( 0, excess );
+					match[2] = unquoted.slice( 0, excess );
+				}
+
+				// Return only captures needed by the pseudo filter method (type and argument)
+				return match.slice( 0, 3 );
+			}
+		},
+
+		filter: {
+
+			"TAG": function( nodeNameSelector ) {
+				var nodeName = nodeNameSelector.replace( runescape, funescape ).toLowerCase();
+				return nodeNameSelector === "*" ?
+					function() { return true; } :
+					function( elem ) {
+						return elem.nodeName && elem.nodeName.toLowerCase() === nodeName;
+					};
+			},
+
+			"CLASS": function( className ) {
+				var pattern = classCache[ className + " " ];
+
+				return pattern ||
+					(pattern = new RegExp( "(^|" + whitespace + ")" + className + "(" + whitespace + "|$)" )) &&
+					classCache( className, function( elem ) {
+						return pattern.test( typeof elem.className === "string" && elem.className || typeof elem.getAttribute !== "undefined" && elem.getAttribute("class") || "" );
+					});
+			},
+
+			"ATTR": function( name, operator, check ) {
+				return function( elem ) {
+					var result = Sizzle.attr( elem, name );
+
+					if ( result == null ) {
+						return operator === "!=";
+					}
+					if ( !operator ) {
+						return true;
+					}
+
+					result += "";
+
+					return operator === "=" ? result === check :
+						operator === "!=" ? result !== check :
+						operator === "^=" ? check && result.indexOf( check ) === 0 :
+						operator === "*=" ? check && result.indexOf( check ) > -1 :
+						operator === "$=" ? check && result.slice( -check.length ) === check :
+						operator === "~=" ? ( " " + result.replace( rwhitespace, " " ) + " " ).indexOf( check ) > -1 :
+						operator === "|=" ? result === check || result.slice( 0, check.length + 1 ) === check + "-" :
+						false;
+				};
+			},
+
+			"CHILD": function( type, what, argument, first, last ) {
+				var simple = type.slice( 0, 3 ) !== "nth",
+					forward = type.slice( -4 ) !== "last",
+					ofType = what === "of-type";
+
+				return first === 1 && last === 0 ?
+
+					// Shortcut for :nth-*(n)
+					function( elem ) {
+						return !!elem.parentNode;
+					} :
+
+					function( elem, context, xml ) {
+						var cache, uniqueCache, outerCache, node, nodeIndex, start,
+							dir = simple !== forward ? "nextSibling" : "previousSibling",
+							parent = elem.parentNode,
+							name = ofType && elem.nodeName.toLowerCase(),
+							useCache = !xml && !ofType,
+							diff = false;
+
+						if ( parent ) {
+
+							// :(first|last|only)-(child|of-type)
+							if ( simple ) {
+								while ( dir ) {
+									node = elem;
+									while ( (node = node[ dir ]) ) {
+										if ( ofType ?
+											node.nodeName.toLowerCase() === name :
+											node.nodeType === 1 ) {
+
+											return false;
+										}
+									}
+									// Reverse direction for :only-* (if we haven't yet done so)
+									start = dir = type === "only" && !start && "nextSibling";
+								}
+								return true;
+							}
+
+							start = [ forward ? parent.firstChild : parent.lastChild ];
+
+							// non-xml :nth-child(...) stores cache data on `parent`
+							if ( forward && useCache ) {
+
+								// Seek `elem` from a previously-cached index
+
+								// ...in a gzip-friendly way
+								node = parent;
+								outerCache = node[ expando ] || (node[ expando ] = {});
+
+								// Support: IE <9 only
+								// Defend against cloned attroperties (jQuery gh-1709)
+								uniqueCache = outerCache[ node.uniqueID ] ||
+									(outerCache[ node.uniqueID ] = {});
+
+								cache = uniqueCache[ type ] || [];
+								nodeIndex = cache[ 0 ] === dirruns && cache[ 1 ];
+								diff = nodeIndex && cache[ 2 ];
+								node = nodeIndex && parent.childNodes[ nodeIndex ];
+
+								while ( (node = ++nodeIndex && node && node[ dir ] ||
+
+									// Fallback to seeking `elem` from the start
+									(diff = nodeIndex = 0) || start.pop()) ) {
+
+									// When found, cache indexes on `parent` and break
+									if ( node.nodeType === 1 && ++diff && node === elem ) {
+										uniqueCache[ type ] = [ dirruns, nodeIndex, diff ];
+										break;
+									}
+								}
+
+							} else {
+								// Use previously-cached element index if available
+								if ( useCache ) {
+									// ...in a gzip-friendly way
+									node = elem;
+									outerCache = node[ expando ] || (node[ expando ] = {});
+
+									// Support: IE <9 only
+									// Defend against cloned attroperties (jQuery gh-1709)
+									uniqueCache = outerCache[ node.uniqueID ] ||
+										(outerCache[ node.uniqueID ] = {});
+
+									cache = uniqueCache[ type ] || [];
+									nodeIndex = cache[ 0 ] === dirruns && cache[ 1 ];
+									diff = nodeIndex;
+								}
+
+								// xml :nth-child(...)
+								// or :nth-last-child(...) or :nth(-last)?-of-type(...)
+								if ( diff === false ) {
+									// Use the same loop as above to seek `elem` from the start
+									while ( (node = ++nodeIndex && node && node[ dir ] ||
+										(diff = nodeIndex = 0) || start.pop()) ) {
+
+										if ( ( ofType ?
+											node.nodeName.toLowerCase() === name :
+											node.nodeType === 1 ) &&
+											++diff ) {
+
+											// Cache the index of each encountered element
+											if ( useCache ) {
+												outerCache = node[ expando ] || (node[ expando ] = {});
+
+												// Support: IE <9 only
+												// Defend against cloned attroperties (jQuery gh-1709)
+												uniqueCache = outerCache[ node.uniqueID ] ||
+													(outerCache[ node.uniqueID ] = {});
+
+												uniqueCache[ type ] = [ dirruns, diff ];
+											}
+
+											if ( node === elem ) {
+												break;
+											}
+										}
+									}
+								}
+							}
+
+							// Incorporate the offset, then check against cycle size
+							diff -= last;
+							return diff === first || ( diff % first === 0 && diff / first >= 0 );
+						}
+					};
+			},
+
+			"PSEUDO": function( pseudo, argument ) {
+				// pseudo-class names are case-insensitive
+				// http://www.w3.org/TR/selectors/#pseudo-classes
+				// Prioritize by case sensitivity in case custom pseudos are added with uppercase letters
+				// Remember that setFilters inherits from pseudos
+				var args,
+					fn = Expr.pseudos[ pseudo ] || Expr.setFilters[ pseudo.toLowerCase() ] ||
+						Sizzle.error( "unsupported pseudo: " + pseudo );
+
+				// The user may use createPseudo to indicate that
+				// arguments are needed to create the filter function
+				// just as Sizzle does
+				if ( fn[ expando ] ) {
+					return fn( argument );
+				}
+
+				// But maintain support for old signatures
+				if ( fn.length > 1 ) {
+					args = [ pseudo, pseudo, "", argument ];
+					return Expr.setFilters.hasOwnProperty( pseudo.toLowerCase() ) ?
+						markFunction(function( seed, matches ) {
+							var idx,
+								matched = fn( seed, argument ),
+								i = matched.length;
+							while ( i-- ) {
+								idx = indexOf( seed, matched[i] );
+								seed[ idx ] = !( matches[ idx ] = matched[i] );
+							}
+						}) :
+						function( elem ) {
+							return fn( elem, 0, args );
+						};
+				}
+
+				return fn;
+			}
+		},
+
+		pseudos: {
+			// Potentially complex pseudos
+			"not": markFunction(function( selector ) {
+				// Trim the selector passed to compile
+				// to avoid treating leading and trailing
+				// spaces as combinators
+				var input = [],
+					results = [],
+					matcher = compile( selector.replace( rtrim, "$1" ) );
+
+				return matcher[ expando ] ?
+					markFunction(function( seed, matches, context, xml ) {
+						var elem,
+							unmatched = matcher( seed, null, xml, [] ),
+							i = seed.length;
+
+						// Match elements unmatched by `matcher`
+						while ( i-- ) {
+							if ( (elem = unmatched[i]) ) {
+								seed[i] = !(matches[i] = elem);
+							}
+						}
+					}) :
+					function( elem, context, xml ) {
+						input[0] = elem;
+						matcher( input, null, xml, results );
+						// Don't keep the element (issue #299)
+						input[0] = null;
+						return !results.pop();
+					};
+			}),
+
+			"has": markFunction(function( selector ) {
+				return function( elem ) {
+					return Sizzle( selector, elem ).length > 0;
+				};
+			}),
+
+			"contains": markFunction(function( text ) {
+				text = text.replace( runescape, funescape );
+				return function( elem ) {
+					return ( elem.textContent || elem.innerText || getText( elem ) ).indexOf( text ) > -1;
+				};
+			}),
+
+			// "Whether an element is represented by a :lang() selector
+			// is based solely on the element's language value
+			// being equal to the identifier C,
+			// or beginning with the identifier C immediately followed by "-".
+			// The matching of C against the element's language value is performed case-insensitively.
+			// The identifier C does not have to be a valid language name."
+			// http://www.w3.org/TR/selectors/#lang-pseudo
+			"lang": markFunction( function( lang ) {
+				// lang value must be a valid identifier
+				if ( !ridentifier.test(lang || "") ) {
+					Sizzle.error( "unsupported lang: " + lang );
+				}
+				lang = lang.replace( runescape, funescape ).toLowerCase();
+				return function( elem ) {
+					var elemLang;
+					do {
+						if ( (elemLang = documentIsHTML ?
+							elem.lang :
+							elem.getAttribute("xml:lang") || elem.getAttribute("lang")) ) {
+
+							elemLang = elemLang.toLowerCase();
+							return elemLang === lang || elemLang.indexOf( lang + "-" ) === 0;
+						}
+					} while ( (elem = elem.parentNode) && elem.nodeType === 1 );
+					return false;
+				};
+			}),
+
+			// Miscellaneous
+			"target": function( elem ) {
+				var hash = window.location && window.location.hash;
+				return hash && hash.slice( 1 ) === elem.id;
+			},
+
+			"root": function( elem ) {
+				return elem === docElem;
+			},
+
+			"focus": function( elem ) {
+				return elem === document.activeElement && (!document.hasFocus || document.hasFocus()) && !!(elem.type || elem.href || ~elem.tabIndex);
+			},
+
+			// Boolean properties
+			"enabled": createDisabledPseudo( false ),
+			"disabled": createDisabledPseudo( true ),
+
+			"checked": function( elem ) {
+				// In CSS3, :checked should return both checked and selected elements
+				// http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked
+				var nodeName = elem.nodeName.toLowerCase();
+				return (nodeName === "input" && !!elem.checked) || (nodeName === "option" && !!elem.selected);
+			},
+
+			"selected": function( elem ) {
+				// Accessing this property makes selected-by-default
+				// options in Safari work properly
+				if ( elem.parentNode ) {
+					elem.parentNode.selectedIndex;
+				}
+
+				return elem.selected === true;
+			},
+
+			// Contents
+			"empty": function( elem ) {
+				// http://www.w3.org/TR/selectors/#empty-pseudo
+				// :empty is negated by element (1) or content nodes (text: 3; cdata: 4; entity ref: 5),
+				//   but not by others (comment: 8; processing instruction: 7; etc.)
+				// nodeType < 6 works because attributes (2) do not appear as children
+				for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) {
+					if ( elem.nodeType < 6 ) {
+						return false;
+					}
+				}
+				return true;
+			},
+
+			"parent": function( elem ) {
+				return !Expr.pseudos["empty"]( elem );
+			},
+
+			// Element/input types
+			"header": function( elem ) {
+				return rheader.test( elem.nodeName );
+			},
+
+			"input": function( elem ) {
+				return rinputs.test( elem.nodeName );
+			},
+
+			"button": function( elem ) {
+				var name = elem.nodeName.toLowerCase();
+				return name === "input" && elem.type === "button" || name === "button";
+			},
+
+			"text": function( elem ) {
+				var attr;
+				return elem.nodeName.toLowerCase() === "input" &&
+					elem.type === "text" &&
+
+					// Support: IE<8
+					// New HTML5 attribute values (e.g., "search") appear with elem.type === "text"
+					( (attr = elem.getAttribute("type")) == null || attr.toLowerCase() === "text" );
+			},
+
+			// Position-in-collection
+			"first": createPositionalPseudo(function() {
+				return [ 0 ];
+			}),
+
+			"last": createPositionalPseudo(function( matchIndexes, length ) {
+				return [ length - 1 ];
+			}),
+
+			"eq": createPositionalPseudo(function( matchIndexes, length, argument ) {
+				return [ argument < 0 ? argument + length : argument ];
+			}),
+
+			"even": createPositionalPseudo(function( matchIndexes, length ) {
+				var i = 0;
+				for ( ; i < length; i += 2 ) {
+					matchIndexes.push( i );
+				}
+				return matchIndexes;
+			}),
+
+			"odd": createPositionalPseudo(function( matchIndexes, length ) {
+				var i = 1;
+				for ( ; i < length; i += 2 ) {
+					matchIndexes.push( i );
+				}
+				return matchIndexes;
+			}),
+
+			"lt": createPositionalPseudo(function( matchIndexes, length, argument ) {
+				var i = argument < 0 ? argument + length : argument;
+				for ( ; --i >= 0; ) {
+					matchIndexes.push( i );
+				}
+				return matchIndexes;
+			}),
+
+			"gt": createPositionalPseudo(function( matchIndexes, length, argument ) {
+				var i = argument < 0 ? argument + length : argument;
+				for ( ; ++i < length; ) {
+					matchIndexes.push( i );
+				}
+				return matchIndexes;
+			})
+		}
+	};
+
+	Expr.pseudos["nth"] = Expr.pseudos["eq"];
+
+	// Add button/input type pseudos
+	for ( i in { radio: true, checkbox: true, file: true, password: true, image: true } ) {
+		Expr.pseudos[ i ] = createInputPseudo( i );
+	}
+	for ( i in { submit: true, reset: true } ) {
+		Expr.pseudos[ i ] = createButtonPseudo( i );
+	}
+
+	// Easy API for creating new setFilters
+	function setFilters() {}
+	setFilters.prototype = Expr.filters = Expr.pseudos;
+	Expr.setFilters = new setFilters();
+
+	tokenize = Sizzle.tokenize = function( selector, parseOnly ) {
+		var matched, match, tokens, type,
+			soFar, groups, preFilters,
+			cached = tokenCache[ selector + " " ];
+
+		if ( cached ) {
+			return parseOnly ? 0 : cached.slice( 0 );
+		}
+
+		soFar = selector;
+		groups = [];
+		preFilters = Expr.preFilter;
+
+		while ( soFar ) {
+
+			// Comma and first run
+			if ( !matched || (match = rcomma.exec( soFar )) ) {
+				if ( match ) {
+					// Don't consume trailing commas as valid
+					soFar = soFar.slice( match[0].length ) || soFar;
+				}
+				groups.push( (tokens = []) );
+			}
+
+			matched = false;
+
+			// Combinators
+			if ( (match = rcombinators.exec( soFar )) ) {
+				matched = match.shift();
+				tokens.push({
+					value: matched,
+					// Cast descendant combinators to space
+					type: match[0].replace( rtrim, " " )
+				});
+				soFar = soFar.slice( matched.length );
+			}
+
+			// Filters
+			for ( type in Expr.filter ) {
+				if ( (match = matchExpr[ type ].exec( soFar )) && (!preFilters[ type ] ||
+					(match = preFilters[ type ]( match ))) ) {
+					matched = match.shift();
+					tokens.push({
+						value: matched,
+						type: type,
+						matches: match
+					});
+					soFar = soFar.slice( matched.length );
+				}
+			}
+
+			if ( !matched ) {
+				break;
+			}
+		}
+
+		// Return the length of the invalid excess
+		// if we're just parsing
+		// Otherwise, throw an error or return tokens
+		return parseOnly ?
+			soFar.length :
+			soFar ?
+				Sizzle.error( selector ) :
+				// Cache the tokens
+				tokenCache( selector, groups ).slice( 0 );
+	};
+
+	function toSelector( tokens ) {
+		var i = 0,
+			len = tokens.length,
+			selector = "";
+		for ( ; i < len; i++ ) {
+			selector += tokens[i].value;
+		}
+		return selector;
+	}
+
+	function addCombinator( matcher, combinator, base ) {
+		var dir = combinator.dir,
+			skip = combinator.next,
+			key = skip || dir,
+			checkNonElements = base && key === "parentNode",
+			doneName = done++;
+
+		return combinator.first ?
+			// Check against closest ancestor/preceding element
+			function( elem, context, xml ) {
+				while ( (elem = elem[ dir ]) ) {
+					if ( elem.nodeType === 1 || checkNonElements ) {
+						return matcher( elem, context, xml );
+					}
+				}
+			} :
+
+			// Check against all ancestor/preceding elements
+			function( elem, context, xml ) {
+				var oldCache, uniqueCache, outerCache,
+					newCache = [ dirruns, doneName ];
+
+				// We can't set arbitrary data on XML nodes, so they don't benefit from combinator caching
+				if ( xml ) {
+					while ( (elem = elem[ dir ]) ) {
+						if ( elem.nodeType === 1 || checkNonElements ) {
+							if ( matcher( elem, context, xml ) ) {
+								return true;
+							}
+						}
+					}
+				} else {
+					while ( (elem = elem[ dir ]) ) {
+						if ( elem.nodeType === 1 || checkNonElements ) {
+							outerCache = elem[ expando ] || (elem[ expando ] = {});
+
+							// Support: IE <9 only
+							// Defend against cloned attroperties (jQuery gh-1709)
+							uniqueCache = outerCache[ elem.uniqueID ] || (outerCache[ elem.uniqueID ] = {});
+
+							if ( skip && skip === elem.nodeName.toLowerCase() ) {
+								elem = elem[ dir ] || elem;
+							} else if ( (oldCache = uniqueCache[ key ]) &&
+								oldCache[ 0 ] === dirruns && oldCache[ 1 ] === doneName ) {
+
+								// Assign to newCache so results back-propagate to previous elements
+								return (newCache[ 2 ] = oldCache[ 2 ]);
+							} else {
+								// Reuse newcache so results back-propagate to previous elements
+								uniqueCache[ key ] = newCache;
+
+								// A match means we're done; a fail means we have to keep checking
+								if ( (newCache[ 2 ] = matcher( elem, context, xml )) ) {
+									return true;
+								}
+							}
+						}
+					}
+				}
+			};
+	}
+
+	function elementMatcher( matchers ) {
+		return matchers.length > 1 ?
+			function( elem, context, xml ) {
+				var i = matchers.length;
+				while ( i-- ) {
+					if ( !matchers[i]( elem, context, xml ) ) {
+						return false;
+					}
+				}
+				return true;
+			} :
+			matchers[0];
+	}
+
+	function multipleContexts( selector, contexts, results ) {
+		var i = 0,
+			len = contexts.length;
+		for ( ; i < len; i++ ) {
+			Sizzle( selector, contexts[i], results );
+		}
+		return results;
+	}
+
+	function condense( unmatched, map, filter, context, xml ) {
+		var elem,
+			newUnmatched = [],
+			i = 0,
+			len = unmatched.length,
+			mapped = map != null;
+
+		for ( ; i < len; i++ ) {
+			if ( (elem = unmatched[i]) ) {
+				if ( !filter || filter( elem, context, xml ) ) {
+					newUnmatched.push( elem );
+					if ( mapped ) {
+						map.push( i );
+					}
+				}
+			}
+		}
+
+		return newUnmatched;
+	}
+
+	function setMatcher( preFilter, selector, matcher, postFilter, postFinder, postSelector ) {
+		if ( postFilter && !postFilter[ expando ] ) {
+			postFilter = setMatcher( postFilter );
+		}
+		if ( postFinder && !postFinder[ expando ] ) {
+			postFinder = setMatcher( postFinder, postSelector );
+		}
+		return markFunction(function( seed, results, context, xml ) {
+			var temp, i, elem,
+				preMap = [],
+				postMap = [],
+				preexisting = results.length,
+
+				// Get initial elements from seed or context
+				elems = seed || multipleContexts( selector || "*", context.nodeType ? [ context ] : context, [] ),
+
+				// Prefilter to get matcher input, preserving a map for seed-results synchronization
+				matcherIn = preFilter && ( seed || !selector ) ?
+					condense( elems, preMap, preFilter, context, xml ) :
+					elems,
+
+				matcherOut = matcher ?
+					// If we have a postFinder, or filtered seed, or non-seed postFilter or preexisting results,
+					postFinder || ( seed ? preFilter : preexisting || postFilter ) ?
+
+						// ...intermediate processing is necessary
+						[] :
+
+						// ...otherwise use results directly
+						results :
+					matcherIn;
+
+			// Find primary matches
+			if ( matcher ) {
+				matcher( matcherIn, matcherOut, context, xml );
+			}
+
+			// Apply postFilter
+			if ( postFilter ) {
+				temp = condense( matcherOut, postMap );
+				postFilter( temp, [], context, xml );
+
+				// Un-match failing elements by moving them back to matcherIn
+				i = temp.length;
+				while ( i-- ) {
+					if ( (elem = temp[i]) ) {
+						matcherOut[ postMap[i] ] = !(matcherIn[ postMap[i] ] = elem);
+					}
+				}
+			}
+
+			if ( seed ) {
+				if ( postFinder || preFilter ) {
+					if ( postFinder ) {
+						// Get the final matcherOut by condensing this intermediate into postFinder contexts
+						temp = [];
+						i = matcherOut.length;
+						while ( i-- ) {
+							if ( (elem = matcherOut[i]) ) {
+								// Restore matcherIn since elem is not yet a final match
+								temp.push( (matcherIn[i] = elem) );
+							}
+						}
+						postFinder( null, (matcherOut = []), temp, xml );
+					}
+
+					// Move matched elements from seed to results to keep them synchronized
+					i = matcherOut.length;
+					while ( i-- ) {
+						if ( (elem = matcherOut[i]) &&
+							(temp = postFinder ? indexOf( seed, elem ) : preMap[i]) > -1 ) {
+
+							seed[temp] = !(results[temp] = elem);
+						}
+					}
+				}
+
+			// Add elements to results, through postFinder if defined
+			} else {
+				matcherOut = condense(
+					matcherOut === results ?
+						matcherOut.splice( preexisting, matcherOut.length ) :
+						matcherOut
+				);
+				if ( postFinder ) {
+					postFinder( null, results, matcherOut, xml );
+				} else {
+					push.apply( results, matcherOut );
+				}
+			}
+		});
+	}
+
+	function matcherFromTokens( tokens ) {
+		var checkContext, matcher, j,
+			len = tokens.length,
+			leadingRelative = Expr.relative[ tokens[0].type ],
+			implicitRelative = leadingRelative || Expr.relative[" "],
+			i = leadingRelative ? 1 : 0,
+
+			// The foundational matcher ensures that elements are reachable from top-level context(s)
+			matchContext = addCombinator( function( elem ) {
+				return elem === checkContext;
+			}, implicitRelative, true ),
+			matchAnyContext = addCombinator( function( elem ) {
+				return indexOf( checkContext, elem ) > -1;
+			}, implicitRelative, true ),
+			matchers = [ function( elem, context, xml ) {
+				var ret = ( !leadingRelative && ( xml || context !== outermostContext ) ) || (
+					(checkContext = context).nodeType ?
+						matchContext( elem, context, xml ) :
+						matchAnyContext( elem, context, xml ) );
+				// Avoid hanging onto element (issue #299)
+				checkContext = null;
+				return ret;
+			} ];
+
+		for ( ; i < len; i++ ) {
+			if ( (matcher = Expr.relative[ tokens[i].type ]) ) {
+				matchers = [ addCombinator(elementMatcher( matchers ), matcher) ];
+			} else {
+				matcher = Expr.filter[ tokens[i].type ].apply( null, tokens[i].matches );
+
+				// Return special upon seeing a positional matcher
+				if ( matcher[ expando ] ) {
+					// Find the next relative operator (if any) for proper handling
+					j = ++i;
+					for ( ; j < len; j++ ) {
+						if ( Expr.relative[ tokens[j].type ] ) {
+							break;
+						}
+					}
+					return setMatcher(
+						i > 1 && elementMatcher( matchers ),
+						i > 1 && toSelector(
+							// If the preceding token was a descendant combinator, insert an implicit any-element `*`
+							tokens.slice( 0, i - 1 ).concat({ value: tokens[ i - 2 ].type === " " ? "*" : "" })
+						).replace( rtrim, "$1" ),
+						matcher,
+						i < j && matcherFromTokens( tokens.slice( i, j ) ),
+						j < len && matcherFromTokens( (tokens = tokens.slice( j )) ),
+						j < len && toSelector( tokens )
+					);
+				}
+				matchers.push( matcher );
+			}
+		}
+
+		return elementMatcher( matchers );
+	}
+
+	function matcherFromGroupMatchers( elementMatchers, setMatchers ) {
+		var bySet = setMatchers.length > 0,
+			byElement = elementMatchers.length > 0,
+			superMatcher = function( seed, context, xml, results, outermost ) {
+				var elem, j, matcher,
+					matchedCount = 0,
+					i = "0",
+					unmatched = seed && [],
+					setMatched = [],
+					contextBackup = outermostContext,
+					// We must always have either seed elements or outermost context
+					elems = seed || byElement && Expr.find["TAG"]( "*", outermost ),
+					// Use integer dirruns iff this is the outermost matcher
+					dirrunsUnique = (dirruns += contextBackup == null ? 1 : Math.random() || 0.1),
+					len = elems.length;
+
+				if ( outermost ) {
+					outermostContext = context === document || context || outermost;
+				}
+
+				// Add elements passing elementMatchers directly to results
+				// Support: IE<9, Safari
+				// Tolerate NodeList properties (IE: "length"; Safari: <number>) matching elements by id
+				for ( ; i !== len && (elem = elems[i]) != null; i++ ) {
+					if ( byElement && elem ) {
+						j = 0;
+						if ( !context && elem.ownerDocument !== document ) {
+							setDocument( elem );
+							xml = !documentIsHTML;
+						}
+						while ( (matcher = elementMatchers[j++]) ) {
+							if ( matcher( elem, context || document, xml) ) {
+								results.push( elem );
+								break;
+							}
+						}
+						if ( outermost ) {
+							dirruns = dirrunsUnique;
+						}
+					}
+
+					// Track unmatched elements for set filters
+					if ( bySet ) {
+						// They will have gone through all possible matchers
+						if ( (elem = !matcher && elem) ) {
+							matchedCount--;
+						}
+
+						// Lengthen the array for every element, matched or not
+						if ( seed ) {
+							unmatched.push( elem );
+						}
+					}
+				}
+
+				// `i` is now the count of elements visited above, and adding it to `matchedCount`
+				// makes the latter nonnegative.
+				matchedCount += i;
+
+				// Apply set filters to unmatched elements
+				// NOTE: This can be skipped if there are no unmatched elements (i.e., `matchedCount`
+				// equals `i`), unless we didn't visit _any_ elements in the above loop because we have
+				// no element matchers and no seed.
+				// Incrementing an initially-string "0" `i` allows `i` to remain a string only in that
+				// case, which will result in a "00" `matchedCount` that differs from `i` but is also
+				// numerically zero.
+				if ( bySet && i !== matchedCount ) {
+					j = 0;
+					while ( (matcher = setMatchers[j++]) ) {
+						matcher( unmatched, setMatched, context, xml );
+					}
+
+					if ( seed ) {
+						// Reintegrate element matches to eliminate the need for sorting
+						if ( matchedCount > 0 ) {
+							while ( i-- ) {
+								if ( !(unmatched[i] || setMatched[i]) ) {
+									setMatched[i] = pop.call( results );
+								}
+							}
+						}
+
+						// Discard index placeholder values to get only actual matches
+						setMatched = condense( setMatched );
+					}
+
+					// Add matches to results
+					push.apply( results, setMatched );
+
+					// Seedless set matches succeeding multiple successful matchers stipulate sorting
+					if ( outermost && !seed && setMatched.length > 0 &&
+						( matchedCount + setMatchers.length ) > 1 ) {
+
+						Sizzle.uniqueSort( results );
+					}
+				}
+
+				// Override manipulation of globals by nested matchers
+				if ( outermost ) {
+					dirruns = dirrunsUnique;
+					outermostContext = contextBackup;
+				}
+
+				return unmatched;
+			};
+
+		return bySet ?
+			markFunction( superMatcher ) :
+			superMatcher;
+	}
+
+	compile = Sizzle.compile = function( selector, match /* Internal Use Only */ ) {
+		var i,
+			setMatchers = [],
+			elementMatchers = [],
+			cached = compilerCache[ selector + " " ];
+
+		if ( !cached ) {
+			// Generate a function of recursive functions that can be used to check each element
+			if ( !match ) {
+				match = tokenize( selector );
+			}
+			i = match.length;
+			while ( i-- ) {
+				cached = matcherFromTokens( match[i] );
+				if ( cached[ expando ] ) {
+					setMatchers.push( cached );
+				} else {
+					elementMatchers.push( cached );
+				}
+			}
+
+			// Cache the compiled function
+			cached = compilerCache( selector, matcherFromGroupMatchers( elementMatchers, setMatchers ) );
+
+			// Save selector and tokenization
+			cached.selector = selector;
+		}
+		return cached;
+	};
+
+	/**
+	 * A low-level selection function that works with Sizzle's compiled
+	 *  selector functions
+	 * @param {String|Function} selector A selector or a pre-compiled
+	 *  selector function built with Sizzle.compile
+	 * @param {Element} context
+	 * @param {Array} [results]
+	 * @param {Array} [seed] A set of elements to match against
+	 */
+	select = Sizzle.select = function( selector, context, results, seed ) {
+		var i, tokens, token, type, find,
+			compiled = typeof selector === "function" && selector,
+			match = !seed && tokenize( (selector = compiled.selector || selector) );
+
+		results = results || [];
+
+		// Try to minimize operations if there is only one selector in the list and no seed
+		// (the latter of which guarantees us context)
+		if ( match.length === 1 ) {
+
+			// Reduce context if the leading compound selector is an ID
+			tokens = match[0] = match[0].slice( 0 );
+			if ( tokens.length > 2 && (token = tokens[0]).type === "ID" &&
+					support.getById && context.nodeType === 9 && documentIsHTML &&
+					Expr.relative[ tokens[1].type ] ) {
+
+				context = ( Expr.find["ID"]( token.matches[0].replace(runescape, funescape), context ) || [] )[0];
+				if ( !context ) {
+					return results;
+
+				// Precompiled matchers will still verify ancestry, so step up a level
+				} else if ( compiled ) {
+					context = context.parentNode;
+				}
+
+				selector = selector.slice( tokens.shift().value.length );
+			}
+
+			// Fetch a seed set for right-to-left matching
+			i = matchExpr["needsContext"].test( selector ) ? 0 : tokens.length;
+			while ( i-- ) {
+				token = tokens[i];
+
+				// Abort if we hit a combinator
+				if ( Expr.relative[ (type = token.type) ] ) {
+					break;
+				}
+				if ( (find = Expr.find[ type ]) ) {
+					// Search, expanding context for leading sibling combinators
+					if ( (seed = find(
+						token.matches[0].replace( runescape, funescape ),
+						rsibling.test( tokens[0].type ) && testContext( context.parentNode ) || context
+					)) ) {
+
+						// If seed is empty or no tokens remain, we can return early
+						tokens.splice( i, 1 );
+						selector = seed.length && toSelector( tokens );
+						if ( !selector ) {
+							push.apply( results, seed );
+							return results;
+						}
+
+						break;
+					}
+				}
+			}
+		}
+
+		// Compile and execute a filtering function if one is not provided
+		// Provide `match` to avoid retokenization if we modified the selector above
+		( compiled || compile( selector, match ) )(
+			seed,
+			context,
+			!documentIsHTML,
+			results,
+			!context || rsibling.test( selector ) && testContext( context.parentNode ) || context
+		);
+		return results;
+	};
+
+	// One-time assignments
+
+	// Sort stability
+	support.sortStable = expando.split("").sort( sortOrder ).join("") === expando;
+
+	// Support: Chrome 14-35+
+	// Always assume duplicates if they aren't passed to the comparison function
+	support.detectDuplicates = !!hasDuplicate;
+
+	// Initialize against the default document
+	setDocument();
+
+	// Support: Webkit<537.32 - Safari 6.0.3/Chrome 25 (fixed in Chrome 27)
+	// Detached nodes confoundingly follow *each other*
+	support.sortDetached = assert(function( el ) {
+		// Should return 1, but returns 4 (following)
+		return el.compareDocumentPosition( document.createElement("fieldset") ) & 1;
+	});
+
+	// Support: IE<8
+	// Prevent attribute/property "interpolation"
+	// https://msdn.microsoft.com/en-us/library/ms536429%28VS.85%29.aspx
+	if ( !assert(function( el ) {
+		el.innerHTML = "<a href='#'></a>";
+		return el.firstChild.getAttribute("href") === "#" ;
+	}) ) {
+		addHandle( "type|href|height|width", function( elem, name, isXML ) {
+			if ( !isXML ) {
+				return elem.getAttribute( name, name.toLowerCase() === "type" ? 1 : 2 );
+			}
+		});
+	}
+
+	// Support: IE<9
+	// Use defaultValue in place of getAttribute("value")
+	if ( !support.attributes || !assert(function( el ) {
+		el.innerHTML = "<input/>";
+		el.firstChild.setAttribute( "value", "" );
+		return el.firstChild.getAttribute( "value" ) === "";
+	}) ) {
+		addHandle( "value", function( elem, name, isXML ) {
+			if ( !isXML && elem.nodeName.toLowerCase() === "input" ) {
+				return elem.defaultValue;
+			}
+		});
+	}
+
+	// Support: IE<9
+	// Use getAttributeNode to fetch booleans when getAttribute lies
+	if ( !assert(function( el ) {
+		return el.getAttribute("disabled") == null;
+	}) ) {
+		addHandle( booleans, function( elem, name, isXML ) {
+			var val;
+			if ( !isXML ) {
+				return elem[ name ] === true ? name.toLowerCase() :
+						(val = elem.getAttributeNode( name )) && val.specified ?
+						val.value :
+					null;
+			}
+		});
+	}
+
+	return Sizzle;
+
+	})( window );
+
+
+
+	jQuery.find = Sizzle;
+	jQuery.expr = Sizzle.selectors;
+
+	// Deprecated
+	jQuery.expr[ ":" ] = jQuery.expr.pseudos;
+	jQuery.uniqueSort = jQuery.unique = Sizzle.uniqueSort;
+	jQuery.text = Sizzle.getText;
+	jQuery.isXMLDoc = Sizzle.isXML;
+	jQuery.contains = Sizzle.contains;
+	jQuery.escapeSelector = Sizzle.escape;
+
+
+
+
+	var dir = function( elem, dir, until ) {
+		var matched = [],
+			truncate = until !== undefined;
+
+		while ( ( elem = elem[ dir ] ) && elem.nodeType !== 9 ) {
+			if ( elem.nodeType === 1 ) {
+				if ( truncate && jQuery( elem ).is( until ) ) {
+					break;
+				}
+				matched.push( elem );
+			}
+		}
+		return matched;
+	};
+
+
+	var siblings = function( n, elem ) {
+		var matched = [];
+
+		for ( ; n; n = n.nextSibling ) {
+			if ( n.nodeType === 1 && n !== elem ) {
+				matched.push( n );
+			}
+		}
+
+		return matched;
+	};
+
+
+	var rneedsContext = jQuery.expr.match.needsContext;
+
+	var rsingleTag = ( /^<([a-z][^\/\0>:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i );
+
+
+
+	var risSimple = /^.[^:#\[\.,]*$/;
+
+	// Implement the identical functionality for filter and not
+	function winnow( elements, qualifier, not ) {
+		if ( jQuery.isFunction( qualifier ) ) {
+			return jQuery.grep( elements, function( elem, i ) {
+				return !!qualifier.call( elem, i, elem ) !== not;
+			} );
+
+		}
+
+		if ( qualifier.nodeType ) {
+			return jQuery.grep( elements, function( elem ) {
+				return ( elem === qualifier ) !== not;
+			} );
+
+		}
+
+		if ( typeof qualifier === "string" ) {
+			if ( risSimple.test( qualifier ) ) {
+				return jQuery.filter( qualifier, elements, not );
+			}
+
+			qualifier = jQuery.filter( qualifier, elements );
+		}
+
+		return jQuery.grep( elements, function( elem ) {
+			return ( indexOf.call( qualifier, elem ) > -1 ) !== not && elem.nodeType === 1;
+		} );
+	}
+
+	jQuery.filter = function( expr, elems, not ) {
+		var elem = elems[ 0 ];
+
+		if ( not ) {
+			expr = ":not(" + expr + ")";
+		}
+
+		return elems.length === 1 && elem.nodeType === 1 ?
+			jQuery.find.matchesSelector( elem, expr ) ? [ elem ] : [] :
+			jQuery.find.matches( expr, jQuery.grep( elems, function( elem ) {
+				return elem.nodeType === 1;
+			} ) );
+	};
+
+	jQuery.fn.extend( {
+		find: function( selector ) {
+			var i, ret,
+				len = this.length,
+				self = this;
+
+			if ( typeof selector !== "string" ) {
+				return this.pushStack( jQuery( selector ).filter( function() {
+					for ( i = 0; i < len; i++ ) {
+						if ( jQuery.contains( self[ i ], this ) ) {
+							return true;
+						}
+					}
+				} ) );
+			}
+
+			ret = this.pushStack( [] );
+
+			for ( i = 0; i < len; i++ ) {
+				jQuery.find( selector, self[ i ], ret );
+			}
+
+			return len > 1 ? jQuery.uniqueSort( ret ) : ret;
+		},
+		filter: function( selector ) {
+			return this.pushStack( winnow( this, selector || [], false ) );
+		},
+		not: function( selector ) {
+			return this.pushStack( winnow( this, selector || [], true ) );
+		},
+		is: function( selector ) {
+			return !!winnow(
+				this,
+
+				// If this is a positional/relative selector, check membership in the returned set
+				// so $("p:first").is("p:last") won't return true for a doc with two "p".
+				typeof selector === "string" && rneedsContext.test( selector ) ?
+					jQuery( selector ) :
+					selector || [],
+				false
+			).length;
+		}
+	} );
+
+
+	// Initialize a jQuery object
+
+
+	// A central reference to the root jQuery(document)
+	var rootjQuery,
+
+		// A simple way to check for HTML strings
+		// Prioritize #id over <tag> to avoid XSS via location.hash (#9521)
+		// Strict HTML recognition (#11290: must start with <)
+		// Shortcut simple #id case for speed
+		rquickExpr = /^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]+))$/,
+
+		init = jQuery.fn.init = function( selector, context, root ) {
+			var match, elem;
+
+			// HANDLE: $(""), $(null), $(undefined), $(false)
+			if ( !selector ) {
+				return this;
+			}
+
+			// Method init() accepts an alternate rootjQuery
+			// so migrate can support jQuery.sub (gh-2101)
+			root = root || rootjQuery;
+
+			// Handle HTML strings
+			if ( typeof selector === "string" ) {
+				if ( selector[ 0 ] === "<" &&
+					selector[ selector.length - 1 ] === ">" &&
+					selector.length >= 3 ) {
+
+					// Assume that strings that start and end with <> are HTML and skip the regex check
+					match = [ null, selector, null ];
+
+				} else {
+					match = rquickExpr.exec( selector );
+				}
+
+				// Match html or make sure no context is specified for #id
+				if ( match && ( match[ 1 ] || !context ) ) {
+
+					// HANDLE: $(html) -> $(array)
+					if ( match[ 1 ] ) {
+						context = context instanceof jQuery ? context[ 0 ] : context;
+
+						// Option to run scripts is true for back-compat
+						// Intentionally let the error be thrown if parseHTML is not present
+						jQuery.merge( this, jQuery.parseHTML(
+							match[ 1 ],
+							context && context.nodeType ? context.ownerDocument || context : document,
+							true
+						) );
+
+						// HANDLE: $(html, props)
+						if ( rsingleTag.test( match[ 1 ] ) && jQuery.isPlainObject( context ) ) {
+							for ( match in context ) {
+
+								// Properties of context are called as methods if possible
+								if ( jQuery.isFunction( this[ match ] ) ) {
+									this[ match ]( context[ match ] );
+
+								// ...and otherwise set as attributes
+								} else {
+									this.attr( match, context[ match ] );
+								}
+							}
+						}
+
+						return this;
+
+					// HANDLE: $(#id)
+					} else {
+						elem = document.getElementById( match[ 2 ] );
+
+						if ( elem ) {
+
+							// Inject the element directly into the jQuery object
+							this[ 0 ] = elem;
+							this.length = 1;
+						}
+						return this;
+					}
+
+				// HANDLE: $(expr, $(...))
+				} else if ( !context || context.jquery ) {
+					return ( context || root ).find( selector );
+
+				// HANDLE: $(expr, context)
+				// (which is just equivalent to: $(context).find(expr)
+				} else {
+					return this.constructor( context ).find( selector );
+				}
+
+			// HANDLE: $(DOMElement)
+			} else if ( selector.nodeType ) {
+				this[ 0 ] = selector;
+				this.length = 1;
+				return this;
+
+			// HANDLE: $(function)
+			// Shortcut for document ready
+			} else if ( jQuery.isFunction( selector ) ) {
+				return root.ready !== undefined ?
+					root.ready( selector ) :
+
+					// Execute immediately if ready is not present
+					selector( jQuery );
+			}
+
+			return jQuery.makeArray( selector, this );
+		};
+
+	// Give the init function the jQuery prototype for later instantiation
+	init.prototype = jQuery.fn;
+
+	// Initialize central reference
+	rootjQuery = jQuery( document );
+
+
+	var rparentsprev = /^(?:parents|prev(?:Until|All))/,
+
+		// Methods guaranteed to produce a unique set when starting from a unique set
+		guaranteedUnique = {
+			children: true,
+			contents: true,
+			next: true,
+			prev: true
+		};
+
+	jQuery.fn.extend( {
+		has: function( target ) {
+			var targets = jQuery( target, this ),
+				l = targets.length;
+
+			return this.filter( function() {
+				var i = 0;
+				for ( ; i < l; i++ ) {
+					if ( jQuery.contains( this, targets[ i ] ) ) {
+						return true;
+					}
+				}
+			} );
+		},
+
+		closest: function( selectors, context ) {
+			var cur,
+				i = 0,
+				l = this.length,
+				matched = [],
+				targets = typeof selectors !== "string" && jQuery( selectors );
+
+			// Positional selectors never match, since there's no _selection_ context
+			if ( !rneedsContext.test( selectors ) ) {
+				for ( ; i < l; i++ ) {
+					for ( cur = this[ i ]; cur && cur !== context; cur = cur.parentNode ) {
+
+						// Always skip document fragments
+						if ( cur.nodeType < 11 && ( targets ?
+							targets.index( cur ) > -1 :
+
+							// Don't pass non-elements to Sizzle
+							cur.nodeType === 1 &&
+								jQuery.find.matchesSelector( cur, selectors ) ) ) {
+
+							matched.push( cur );
+							break;
+						}
+					}
+				}
+			}
+
+			return this.pushStack( matched.length > 1 ? jQuery.uniqueSort( matched ) : matched );
+		},
+
+		// Determine the position of an element within the set
+		index: function( elem ) {
+
+			// No argument, return index in parent
+			if ( !elem ) {
+				return ( this[ 0 ] && this[ 0 ].parentNode ) ? this.first().prevAll().length : -1;
+			}
+
+			// Index in selector
+			if ( typeof elem === "string" ) {
+				return indexOf.call( jQuery( elem ), this[ 0 ] );
+			}
+
+			// Locate the position of the desired element
+			return indexOf.call( this,
+
+				// If it receives a jQuery object, the first element is used
+				elem.jquery ? elem[ 0 ] : elem
+			);
+		},
+
+		add: function( selector, context ) {
+			return this.pushStack(
+				jQuery.uniqueSort(
+					jQuery.merge( this.get(), jQuery( selector, context ) )
+				)
+			);
+		},
+
+		addBack: function( selector ) {
+			return this.add( selector == null ?
+				this.prevObject : this.prevObject.filter( selector )
+			);
+		}
+	} );
+
+	function sibling( cur, dir ) {
+		while ( ( cur = cur[ dir ] ) && cur.nodeType !== 1 ) {}
+		return cur;
+	}
+
+	jQuery.each( {
+		parent: function( elem ) {
+			var parent = elem.parentNode;
+			return parent && parent.nodeType !== 11 ? parent : null;
+		},
+		parents: function( elem ) {
+			return dir( elem, "parentNode" );
+		},
+		parentsUntil: function( elem, i, until ) {
+			return dir( elem, "parentNode", until );
+		},
+		next: function( elem ) {
+			return sibling( elem, "nextSibling" );
+		},
+		prev: function( elem ) {
+			return sibling( elem, "previousSibling" );
+		},
+		nextAll: function( elem ) {
+			return dir( elem, "nextSibling" );
+		},
+		prevAll: function( elem ) {
+			return dir( elem, "previousSibling" );
+		},
+		nextUntil: function( elem, i, until ) {
+			return dir( elem, "nextSibling", until );
+		},
+		prevUntil: function( elem, i, until ) {
+			return dir( elem, "previousSibling", until );
+		},
+		siblings: function( elem ) {
+			return siblings( ( elem.parentNode || {} ).firstChild, elem );
+		},
+		children: function( elem ) {
+			return siblings( elem.firstChild );
+		},
+		contents: function( elem ) {
+			return elem.contentDocument || jQuery.merge( [], elem.childNodes );
+		}
+	}, function( name, fn ) {
+		jQuery.fn[ name ] = function( until, selector ) {
+			var matched = jQuery.map( this, fn, until );
+
+			if ( name.slice( -5 ) !== "Until" ) {
+				selector = until;
+			}
+
+			if ( selector && typeof selector === "string" ) {
+				matched = jQuery.filter( selector, matched );
+			}
+
+			if ( this.length > 1 ) {
+
+				// Remove duplicates
+				if ( !guaranteedUnique[ name ] ) {
+					jQuery.uniqueSort( matched );
+				}
+
+				// Reverse order for parents* and prev-derivatives
+				if ( rparentsprev.test( name ) ) {
+					matched.reverse();
+				}
+			}
+
+			return this.pushStack( matched );
+		};
+	} );
+	var rnotwhite = ( /\S+/g );
+
+
+
+	// Convert String-formatted options into Object-formatted ones
+	function createOptions( options ) {
+		var object = {};
+		jQuery.each( options.match( rnotwhite ) || [], function( _, flag ) {
+			object[ flag ] = true;
+		} );
+		return object;
+	}
+
+	/*
+	 * Create a callback list using the following parameters:
+	 *
+	 *	options: an optional list of space-separated options that will change how
+	 *			the callback list behaves or a more traditional option object
+	 *
+	 * By default a callback list will act like an event callback list and can be
+	 * "fired" multiple times.
+	 *
+	 * Possible options:
+	 *
+	 *	once:			will ensure the callback list can only be fired once (like a Deferred)
+	 *
+	 *	memory:			will keep track of previous values and will call any callback added
+	 *					after the list has been fired right away with the latest "memorized"
+	 *					values (like a Deferred)
+	 *
+	 *	unique:			will ensure a callback can only be added once (no duplicate in the list)
+	 *
+	 *	stopOnFalse:	interrupt callings when a callback returns false
+	 *
+	 */
+	jQuery.Callbacks = function( options ) {
+
+		// Convert options from String-formatted to Object-formatted if needed
+		// (we check in cache first)
+		options = typeof options === "string" ?
+			createOptions( options ) :
+			jQuery.extend( {}, options );
+
+		var // Flag to know if list is currently firing
+			firing,
+
+			// Last fire value for non-forgettable lists
+			memory,
+
+			// Flag to know if list was already fired
+			fired,
+
+			// Flag to prevent firing
+			locked,
+
+			// Actual callback list
+			list = [],
+
+			// Queue of execution data for repeatable lists
+			queue = [],
+
+			// Index of currently firing callback (modified by add/remove as needed)
+			firingIndex = -1,
+
+			// Fire callbacks
+			fire = function() {
+
+				// Enforce single-firing
+				locked = options.once;
+
+				// Execute callbacks for all pending executions,
+				// respecting firingIndex overrides and runtime changes
+				fired = firing = true;
+				for ( ; queue.length; firingIndex = -1 ) {
+					memory = queue.shift();
+					while ( ++firingIndex < list.length ) {
+
+						// Run callback and check for early termination
+						if ( list[ firingIndex ].apply( memory[ 0 ], memory[ 1 ] ) === false &&
+							options.stopOnFalse ) {
+
+							// Jump to end and forget the data so .add doesn't re-fire
+							firingIndex = list.length;
+							memory = false;
+						}
+					}
+				}
+
+				// Forget the data if we're done with it
+				if ( !options.memory ) {
+					memory = false;
+				}
+
+				firing = false;
+
+				// Clean up if we're done firing for good
+				if ( locked ) {
+
+					// Keep an empty list if we have data for future add calls
+					if ( memory ) {
+						list = [];
+
+					// Otherwise, this object is spent
+					} else {
+						list = "";
+					}
+				}
+			},
+
+			// Actual Callbacks object
+			self = {
+
+				// Add a callback or a collection of callbacks to the list
+				add: function() {
+					if ( list ) {
+
+						// If we have memory from a past run, we should fire after adding
+						if ( memory && !firing ) {
+							firingIndex = list.length - 1;
+							queue.push( memory );
+						}
+
+						( function add( args ) {
+							jQuery.each( args, function( _, arg ) {
+								if ( jQuery.isFunction( arg ) ) {
+									if ( !options.unique || !self.has( arg ) ) {
+										list.push( arg );
+									}
+								} else if ( arg && arg.length && jQuery.type( arg ) !== "string" ) {
+
+									// Inspect recursively
+									add( arg );
+								}
+							} );
+						} )( arguments );
+
+						if ( memory && !firing ) {
+							fire();
+						}
+					}
+					return this;
+				},
+
+				// Remove a callback from the list
+				remove: function() {
+					jQuery.each( arguments, function( _, arg ) {
+						var index;
+						while ( ( index = jQuery.inArray( arg, list, index ) ) > -1 ) {
+							list.splice( index, 1 );
+
+							// Handle firing indexes
+							if ( index <= firingIndex ) {
+								firingIndex--;
+							}
+						}
+					} );
+					return this;
+				},
+
+				// Check if a given callback is in the list.
+				// If no argument is given, return whether or not list has callbacks attached.
+				has: function( fn ) {
+					return fn ?
+						jQuery.inArray( fn, list ) > -1 :
+						list.length > 0;
+				},
+
+				// Remove all callbacks from the list
+				empty: function() {
+					if ( list ) {
+						list = [];
+					}
+					return this;
+				},
+
+				// Disable .fire and .add
+				// Abort any current/pending executions
+				// Clear all callbacks and values
+				disable: function() {
+					locked = queue = [];
+					list = memory = "";
+					return this;
+				},
+				disabled: function() {
+					return !list;
+				},
+
+				// Disable .fire
+				// Also disable .add unless we have memory (since it would have no effect)
+				// Abort any pending executions
+				lock: function() {
+					locked = queue = [];
+					if ( !memory && !firing ) {
+						list = memory = "";
+					}
+					return this;
+				},
+				locked: function() {
+					return !!locked;
+				},
+
+				// Call all callbacks with the given context and arguments
+				fireWith: function( context, args ) {
+					if ( !locked ) {
+						args = args || [];
+						args = [ context, args.slice ? args.slice() : args ];
+						queue.push( args );
+						if ( !firing ) {
+							fire();
+						}
+					}
+					return this;
+				},
+
+				// Call all the callbacks with the given arguments
+				fire: function() {
+					self.fireWith( this, arguments );
+					return this;
+				},
+
+				// To know if the callbacks have already been called at least once
+				fired: function() {
+					return !!fired;
+				}
+			};
+
+		return self;
+	};
+
+
+	function Identity( v ) {
+		return v;
+	}
+	function Thrower( ex ) {
+		throw ex;
+	}
+
+	function adoptValue( value, resolve, reject ) {
+		var method;
+
+		try {
+
+			// Check for promise aspect first to privilege synchronous behavior
+			if ( value && jQuery.isFunction( ( method = value.promise ) ) ) {
+				method.call( value ).done( resolve ).fail( reject );
+
+			// Other thenables
+			} else if ( value && jQuery.isFunction( ( method = value.then ) ) ) {
+				method.call( value, resolve, reject );
+
+			// Other non-thenables
+			} else {
+
+				// Support: Android 4.0 only
+				// Strict mode functions invoked without .call/.apply get global-object context
+				resolve.call( undefined, value );
+			}
+
+		// For Promises/A+, convert exceptions into rejections
+		// Since jQuery.when doesn't unwrap thenables, we can skip the extra checks appearing in
+		// Deferred#then to conditionally suppress rejection.
+		} catch ( value ) {
+
+			// Support: Android 4.0 only
+			// Strict mode functions invoked without .call/.apply get global-object context
+			reject.call( undefined, value );
+		}
+	}
+
+	jQuery.extend( {
+
+		Deferred: function( func ) {
+			var tuples = [
+
+					// action, add listener, callbacks,
+					// ... .then handlers, argument index, [final state]
+					[ "notify", "progress", jQuery.Callbacks( "memory" ),
+						jQuery.Callbacks( "memory" ), 2 ],
+					[ "resolve", "done", jQuery.Callbacks( "once memory" ),
+						jQuery.Callbacks( "once memory" ), 0, "resolved" ],
+					[ "reject", "fail", jQuery.Callbacks( "once memory" ),
+						jQuery.Callbacks( "once memory" ), 1, "rejected" ]
+				],
+				state = "pending",
+				promise = {
+					state: function() {
+						return state;
+					},
+					always: function() {
+						deferred.done( arguments ).fail( arguments );
+						return this;
+					},
+					"catch": function( fn ) {
+						return promise.then( null, fn );
+					},
+
+					// Keep pipe for back-compat
+					pipe: function( /* fnDone, fnFail, fnProgress */ ) {
+						var fns = arguments;
+
+						return jQuery.Deferred( function( newDefer ) {
+							jQuery.each( tuples, function( i, tuple ) {
+
+								// Map tuples (progress, done, fail) to arguments (done, fail, progress)
+								var fn = jQuery.isFunction( fns[ tuple[ 4 ] ] ) && fns[ tuple[ 4 ] ];
+
+								// deferred.progress(function() { bind to newDefer or newDefer.notify })
+								// deferred.done(function() { bind to newDefer or newDefer.resolve })
+								// deferred.fail(function() { bind to newDefer or newDefer.reject })
+								deferred[ tuple[ 1 ] ]( function() {
+									var returned = fn && fn.apply( this, arguments );
+									if ( returned && jQuery.isFunction( returned.promise ) ) {
+										returned.promise()
+											.progress( newDefer.notify )
+											.done( newDefer.resolve )
+											.fail( newDefer.reject );
+									} else {
+										newDefer[ tuple[ 0 ] + "With" ](
+											this,
+											fn ? [ returned ] : arguments
+										);
+									}
+								} );
+							} );
+							fns = null;
+						} ).promise();
+					},
+					then: function( onFulfilled, onRejected, onProgress ) {
+						var maxDepth = 0;
+						function resolve( depth, deferred, handler, special ) {
+							return function() {
+								var that = this,
+									args = arguments,
+									mightThrow = function() {
+										var returned, then;
+
+										// Support: Promises/A+ section 2.3.3.3.3
+										// https://promisesaplus.com/#point-59
+										// Ignore double-resolution attempts
+										if ( depth < maxDepth ) {
+											return;
+										}
+
+										returned = handler.apply( that, args );
+
+										// Support: Promises/A+ section 2.3.1
+										// https://promisesaplus.com/#point-48
+										if ( returned === deferred.promise() ) {
+											throw new TypeError( "Thenable self-resolution" );
+										}
+
+										// Support: Promises/A+ sections 2.3.3.1, 3.5
+										// https://promisesaplus.com/#point-54
+										// https://promisesaplus.com/#point-75
+										// Retrieve `then` only once
+										then = returned &&
+
+											// Support: Promises/A+ section 2.3.4
+											// https://promisesaplus.com/#point-64
+											// Only check objects and functions for thenability
+											( typeof returned === "object" ||
+												typeof returned === "function" ) &&
+											returned.then;
+
+										// Handle a returned thenable
+										if ( jQuery.isFunction( then ) ) {
+
+											// Special processors (notify) just wait for resolution
+											if ( special ) {
+												then.call(
+													returned,
+													resolve( maxDepth, deferred, Identity, special ),
+													resolve( maxDepth, deferred, Thrower, special )
+												);
+
+											// Normal processors (resolve) also hook into progress
+											} else {
+
+												// ...and disregard older resolution values
+												maxDepth++;
+
+												then.call(
+													returned,
+													resolve( maxDepth, deferred, Identity, special ),
+													resolve( maxDepth, deferred, Thrower, special ),
+													resolve( maxDepth, deferred, Identity,
+														deferred.notifyWith )
+												);
+											}
+
+										// Handle all other returned values
+										} else {
+
+											// Only substitute handlers pass on context
+											// and multiple values (non-spec behavior)
+											if ( handler !== Identity ) {
+												that = undefined;
+												args = [ returned ];
+											}
+
+											// Process the value(s)
+											// Default process is resolve
+											( special || deferred.resolveWith )( that, args );
+										}
+									},
+
+									// Only normal processors (resolve) catch and reject exceptions
+									process = special ?
+										mightThrow :
+										function() {
+											try {
+												mightThrow();
+											} catch ( e ) {
+
+												if ( jQuery.Deferred.exceptionHook ) {
+													jQuery.Deferred.exceptionHook( e,
+														process.stackTrace );
+												}
+
+												// Support: Promises/A+ section 2.3.3.3.4.1
+												// https://promisesaplus.com/#point-61
+												// Ignore post-resolution exceptions
+												if ( depth + 1 >= maxDepth ) {
+
+													// Only substitute handlers pass on context
+													// and multiple values (non-spec behavior)
+													if ( handler !== Thrower ) {
+														that = undefined;
+														args = [ e ];
+													}
+
+													deferred.rejectWith( that, args );
+												}
+											}
+										};
+
+								// Support: Promises/A+ section 2.3.3.3.1
+								// https://promisesaplus.com/#point-57
+								// Re-resolve promises immediately to dodge false rejection from
+								// subsequent errors
+								if ( depth ) {
+									process();
+								} else {
+
+									// Call an optional hook to record the stack, in case of exception
+									// since it's otherwise lost when execution goes async
+									if ( jQuery.Deferred.getStackHook ) {
+										process.stackTrace = jQuery.Deferred.getStackHook();
+									}
+									window.setTimeout( process );
+								}
+							};
+						}
+
+						return jQuery.Deferred( function( newDefer ) {
+
+							// progress_handlers.add( ... )
+							tuples[ 0 ][ 3 ].add(
+								resolve(
+									0,
+									newDefer,
+									jQuery.isFunction( onProgress ) ?
+										onProgress :
+										Identity,
+									newDefer.notifyWith
+								)
+							);
+
+							// fulfilled_handlers.add( ... )
+							tuples[ 1 ][ 3 ].add(
+								resolve(
+									0,
+									newDefer,
+									jQuery.isFunction( onFulfilled ) ?
+										onFulfilled :
+										Identity
+								)
+							);
+
+							// rejected_handlers.add( ... )
+							tuples[ 2 ][ 3 ].add(
+								resolve(
+									0,
+									newDefer,
+									jQuery.isFunction( onRejected ) ?
+										onRejected :
+										Thrower
+								)
+							);
+						} ).promise();
+					},
+
+					// Get a promise for this deferred
+					// If obj is provided, the promise aspect is added to the object
+					promise: function( obj ) {
+						return obj != null ? jQuery.extend( obj, promise ) : promise;
+					}
+				},
+				deferred = {};
+
+			// Add list-specific methods
+			jQuery.each( tuples, function( i, tuple ) {
+				var list = tuple[ 2 ],
+					stateString = tuple[ 5 ];
+
+				// promise.progress = list.add
+				// promise.done = list.add
+				// promise.fail = list.add
+				promise[ tuple[ 1 ] ] = list.add;
+
+				// Handle state
+				if ( stateString ) {
+					list.add(
+						function() {
+
+							// state = "resolved" (i.e., fulfilled)
+							// state = "rejected"
+							state = stateString;
+						},
+
+						// rejected_callbacks.disable
+						// fulfilled_callbacks.disable
+						tuples[ 3 - i ][ 2 ].disable,
+
+						// progress_callbacks.lock
+						tuples[ 0 ][ 2 ].lock
+					);
+				}
+
+				// progress_handlers.fire
+				// fulfilled_handlers.fire
+				// rejected_handlers.fire
+				list.add( tuple[ 3 ].fire );
+
+				// deferred.notify = function() { deferred.notifyWith(...) }
+				// deferred.resolve = function() { deferred.resolveWith(...) }
+				// deferred.reject = function() { deferred.rejectWith(...) }
+				deferred[ tuple[ 0 ] ] = function() {
+					deferred[ tuple[ 0 ] + "With" ]( this === deferred ? undefined : this, arguments );
+					return this;
+				};
+
+				// deferred.notifyWith = list.fireWith
+				// deferred.resolveWith = list.fireWith
+				// deferred.rejectWith = list.fireWith
+				deferred[ tuple[ 0 ] + "With" ] = list.fireWith;
+			} );
+
+			// Make the deferred a promise
+			promise.promise( deferred );
+
+			// Call given func if any
+			if ( func ) {
+				func.call( deferred, deferred );
+			}
+
+			// All done!
+			return deferred;
+		},
+
+		// Deferred helper
+		when: function( singleValue ) {
+			var
+
+				// count of uncompleted subordinates
+				remaining = arguments.length,
+
+				// count of unprocessed arguments
+				i = remaining,
+
+				// subordinate fulfillment data
+				resolveContexts = Array( i ),
+				resolveValues = slice.call( arguments ),
+
+				// the master Deferred
+				master = jQuery.Deferred(),
+
+				// subordinate callback factory
+				updateFunc = function( i ) {
+					return function( value ) {
+						resolveContexts[ i ] = this;
+						resolveValues[ i ] = arguments.length > 1 ? slice.call( arguments ) : value;
+						if ( !( --remaining ) ) {
+							master.resolveWith( resolveContexts, resolveValues );
+						}
+					};
+				};
+
+			// Single- and empty arguments are adopted like Promise.resolve
+			if ( remaining <= 1 ) {
+				adoptValue( singleValue, master.done( updateFunc( i ) ).resolve, master.reject );
+
+				// Use .then() to unwrap secondary thenables (cf. gh-3000)
+				if ( master.state() === "pending" ||
+					jQuery.isFunction( resolveValues[ i ] && resolveValues[ i ].then ) ) {
+
+					return master.then();
+				}
+			}
+
+			// Multiple arguments are aggregated like Promise.all array elements
+			while ( i-- ) {
+				adoptValue( resolveValues[ i ], updateFunc( i ), master.reject );
+			}
+
+			return master.promise();
+		}
+	} );
+
+
+	// These usually indicate a programmer mistake during development,
+	// warn about them ASAP rather than swallowing them by default.
+	var rerrorNames = /^(Eval|Internal|Range|Reference|Syntax|Type|URI)Error$/;
+
+	jQuery.Deferred.exceptionHook = function( error, stack ) {
+
+		// Support: IE 8 - 9 only
+		// Console exists when dev tools are open, which can happen at any time
+		if ( window.console && window.console.warn && error && rerrorNames.test( error.name ) ) {
+			window.console.warn( "jQuery.Deferred exception: " + error.message, error.stack, stack );
+		}
+	};
+
+
+
+
+	jQuery.readyException = function( error ) {
+		window.setTimeout( function() {
+			throw error;
+		} );
+	};
+
+
+
+
+	// The deferred used on DOM ready
+	var readyList = jQuery.Deferred();
+
+	jQuery.fn.ready = function( fn ) {
+
+		readyList
+			.then( fn )
+
+			// Wrap jQuery.readyException in a function so that the lookup
+			// happens at the time of error handling instead of callback
+			// registration.
+			.catch( function( error ) {
+				jQuery.readyException( error );
+			} );
+
+		return this;
+	};
+
+	jQuery.extend( {
+
+		// Is the DOM ready to be used? Set to true once it occurs.
+		isReady: false,
+
+		// A counter to track how many items to wait for before
+		// the ready event fires. See #6781
+		readyWait: 1,
+
+		// Hold (or release) the ready event
+		holdReady: function( hold ) {
+			if ( hold ) {
+				jQuery.readyWait++;
+			} else {
+				jQuery.ready( true );
+			}
+		},
+
+		// Handle when the DOM is ready
+		ready: function( wait ) {
+
+			// Abort if there are pending holds or we're already ready
+			if ( wait === true ? --jQuery.readyWait : jQuery.isReady ) {
+				return;
+			}
+
+			// Remember that the DOM is ready
+			jQuery.isReady = true;
+
+			// If a normal DOM Ready event fired, decrement, and wait if need be
+			if ( wait !== true && --jQuery.readyWait > 0 ) {
+				return;
+			}
+
+			// If there are functions bound, to execute
+			readyList.resolveWith( document, [ jQuery ] );
+		}
+	} );
+
+	jQuery.ready.then = readyList.then;
+
+	// The ready event handler and self cleanup method
+	function completed() {
+		document.removeEventListener( "DOMContentLoaded", completed );
+		window.removeEventListener( "load", completed );
+		jQuery.ready();
+	}
+
+	// Catch cases where $(document).ready() is called
+	// after the browser event has already occurred.
+	// Support: IE <=9 - 10 only
+	// Older IE sometimes signals "interactive" too soon
+	if ( document.readyState === "complete" ||
+		( document.readyState !== "loading" && !document.documentElement.doScroll ) ) {
+
+		// Handle it asynchronously to allow scripts the opportunity to delay ready
+		window.setTimeout( jQuery.ready );
+
+	} else {
+
+		// Use the handy event callback
+		document.addEventListener( "DOMContentLoaded", completed );
+
+		// A fallback to window.onload, that will always work
+		window.addEventListener( "load", completed );
+	}
+
+
+
+
+	// Multifunctional method to get and set values of a collection
+	// The value/s can optionally be executed if it's a function
+	var access = function( elems, fn, key, value, chainable, emptyGet, raw ) {
+		var i = 0,
+			len = elems.length,
+			bulk = key == null;
+
+		// Sets many values
+		if ( jQuery.type( key ) === "object" ) {
+			chainable = true;
+			for ( i in key ) {
+				access( elems, fn, i, key[ i ], true, emptyGet, raw );
+			}
+
+		// Sets one value
+		} else if ( value !== undefined ) {
+			chainable = true;
+
+			if ( !jQuery.isFunction( value ) ) {
+				raw = true;
+			}
+
+			if ( bulk ) {
+
+				// Bulk operations run against the entire set
+				if ( raw ) {
+					fn.call( elems, value );
+					fn = null;
+
+				// ...except when executing function values
+				} else {
+					bulk = fn;
+					fn = function( elem, key, value ) {
+						return bulk.call( jQuery( elem ), value );
+					};
+				}
+			}
+
+			if ( fn ) {
+				for ( ; i < len; i++ ) {
+					fn(
+						elems[ i ], key, raw ?
+						value :
+						value.call( elems[ i ], i, fn( elems[ i ], key ) )
+					);
+				}
+			}
+		}
+
+		return chainable ?
+			elems :
+
+			// Gets
+			bulk ?
+				fn.call( elems ) :
+				len ? fn( elems[ 0 ], key ) : emptyGet;
+	};
+	var acceptData = function( owner ) {
+
+		// Accepts only:
+		//  - Node
+		//    - Node.ELEMENT_NODE
+		//    - Node.DOCUMENT_NODE
+		//  - Object
+		//    - Any
+		return owner.nodeType === 1 || owner.nodeType === 9 || !( +owner.nodeType );
+	};
+
+
+
+
+	function Data() {
+		this.expando = jQuery.expando + Data.uid++;
+	}
+
+	Data.uid = 1;
+
+	Data.prototype = {
+
+		cache: function( owner ) {
+
+			// Check if the owner object already has a cache
+			var value = owner[ this.expando ];
+
+			// If not, create one
+			if ( !value ) {
+				value = {};
+
+				// We can accept data for non-element nodes in modern browsers,
+				// but we should not, see #8335.
+				// Always return an empty object.
+				if ( acceptData( owner ) ) {
+
+					// If it is a node unlikely to be stringify-ed or looped over
+					// use plain assignment
+					if ( owner.nodeType ) {
+						owner[ this.expando ] = value;
+
+					// Otherwise secure it in a non-enumerable property
+					// configurable must be true to allow the property to be
+					// deleted when data is removed
+					} else {
+						Object.defineProperty( owner, this.expando, {
+							value: value,
+							configurable: true
+						} );
+					}
+				}
+			}
+
+			return value;
+		},
+		set: function( owner, data, value ) {
+			var prop,
+				cache = this.cache( owner );
+
+			// Handle: [ owner, key, value ] args
+			// Always use camelCase key (gh-2257)
+			if ( typeof data === "string" ) {
+				cache[ jQuery.camelCase( data ) ] = value;
+
+			// Handle: [ owner, { properties } ] args
+			} else {
+
+				// Copy the properties one-by-one to the cache object
+				for ( prop in data ) {
+					cache[ jQuery.camelCase( prop ) ] = data[ prop ];
+				}
+			}
+			return cache;
+		},
+		get: function( owner, key ) {
+			return key === undefined ?
+				this.cache( owner ) :
+
+				// Always use camelCase key (gh-2257)
+				owner[ this.expando ] && owner[ this.expando ][ jQuery.camelCase( key ) ];
+		},
+		access: function( owner, key, value ) {
+
+			// In cases where either:
+			//
+			//   1. No key was specified
+			//   2. A string key was specified, but no value provided
+			//
+			// Take the "read" path and allow the get method to determine
+			// which value to return, respectively either:
+			//
+			//   1. The entire cache object
+			//   2. The data stored at the key
+			//
+			if ( key === undefined ||
+					( ( key && typeof key === "string" ) && value === undefined ) ) {
+
+				return this.get( owner, key );
+			}
+
+			// When the key is not a string, or both a key and value
+			// are specified, set or extend (existing objects) with either:
+			//
+			//   1. An object of properties
+			//   2. A key and value
+			//
+			this.set( owner, key, value );
+
+			// Since the "set" path can have two possible entry points
+			// return the expected data based on which path was taken[*]
+			return value !== undefined ? value : key;
+		},
+		remove: function( owner, key ) {
+			var i,
+				cache = owner[ this.expando ];
+
+			if ( cache === undefined ) {
+				return;
+			}
+
+			if ( key !== undefined ) {
+
+				// Support array or space separated string of keys
+				if ( jQuery.isArray( key ) ) {
+
+					// If key is an array of keys...
+					// We always set camelCase keys, so remove that.
+					key = key.map( jQuery.camelCase );
+				} else {
+					key = jQuery.camelCase( key );
+
+					// If a key with the spaces exists, use it.
+					// Otherwise, create an array by matching non-whitespace
+					key = key in cache ?
+						[ key ] :
+						( key.match( rnotwhite ) || [] );
+				}
+
+				i = key.length;
+
+				while ( i-- ) {
+					delete cache[ key[ i ] ];
+				}
+			}
+
+			// Remove the expando if there's no more data
+			if ( key === undefined || jQuery.isEmptyObject( cache ) ) {
+
+				// Support: Chrome <=35 - 45
+				// Webkit & Blink performance suffers when deleting properties
+				// from DOM nodes, so set to undefined instead
+				// https://bugs.chromium.org/p/chromium/issues/detail?id=378607 (bug restricted)
+				if ( owner.nodeType ) {
+					owner[ this.expando ] = undefined;
+				} else {
+					delete owner[ this.expando ];
+				}
+			}
+		},
+		hasData: function( owner ) {
+			var cache = owner[ this.expando ];
+			return cache !== undefined && !jQuery.isEmptyObject( cache );
+		}
+	};
+	var dataPriv = new Data();
+
+	var dataUser = new Data();
+
+
+
+	//	Implementation Summary
+	//
+	//	1. Enforce API surface and semantic compatibility with 1.9.x branch
+	//	2. Improve the module's maintainability by reducing the storage
+	//		paths to a single mechanism.
+	//	3. Use the same single mechanism to support "private" and "user" data.
+	//	4. _Never_ expose "private" data to user code (TODO: Drop _data, _removeData)
+	//	5. Avoid exposing implementation details on user objects (eg. expando properties)
+	//	6. Provide a clear path for implementation upgrade to WeakMap in 2014
+
+	var rbrace = /^(?:\{[\w\W]*\}|\[[\w\W]*\])$/,
+		rmultiDash = /[A-Z]/g;
+
+	function dataAttr( elem, key, data ) {
+		var name;
+
+		// If nothing was found internally, try to fetch any
+		// data from the HTML5 data-* attribute
+		if ( data === undefined && elem.nodeType === 1 ) {
+			name = "data-" + key.replace( rmultiDash, "-$&" ).toLowerCase();
+			data = elem.getAttribute( name );
+
+			if ( typeof data === "string" ) {
+				try {
+					data = data === "true" ? true :
+						data === "false" ? false :
+						data === "null" ? null :
+
+						// Only convert to a number if it doesn't change the string
+						+data + "" === data ? +data :
+						rbrace.test( data ) ? JSON.parse( data ) :
+						data;
+				} catch ( e ) {}
+
+				// Make sure we set the data so it isn't changed later
+				dataUser.set( elem, key, data );
+			} else {
+				data = undefined;
+			}
+		}
+		return data;
+	}
+
+	jQuery.extend( {
+		hasData: function( elem ) {
+			return dataUser.hasData( elem ) || dataPriv.hasData( elem );
+		},
+
+		data: function( elem, name, data ) {
+			return dataUser.access( elem, name, data );
+		},
+
+		removeData: function( elem, name ) {
+			dataUser.remove( elem, name );
+		},
+
+		// TODO: Now that all calls to _data and _removeData have been replaced
+		// with direct calls to dataPriv methods, these can be deprecated.
+		_data: function( elem, name, data ) {
+			return dataPriv.access( elem, name, data );
+		},
+
+		_removeData: function( elem, name ) {
+			dataPriv.remove( elem, name );
+		}
+	} );
+
+	jQuery.fn.extend( {
+		data: function( key, value ) {
+			var i, name, data,
+				elem = this[ 0 ],
+				attrs = elem && elem.attributes;
+
+			// Gets all values
+			if ( key === undefined ) {
+				if ( this.length ) {
+					data = dataUser.get( elem );
+
+					if ( elem.nodeType === 1 && !dataPriv.get( elem, "hasDataAttrs" ) ) {
+						i = attrs.length;
+						while ( i-- ) {
+
+							// Support: IE 11 only
+							// The attrs elements can be null (#14894)
+							if ( attrs[ i ] ) {
+								name = attrs[ i ].name;
+								if ( name.indexOf( "data-" ) === 0 ) {
+									name = jQuery.camelCase( name.slice( 5 ) );
+									dataAttr( elem, name, data[ name ] );
+								}
+							}
+						}
+						dataPriv.set( elem, "hasDataAttrs", true );
+					}
+				}
+
+				return data;
+			}
+
+			// Sets multiple values
+			if ( typeof key === "object" ) {
+				return this.each( function() {
+					dataUser.set( this, key );
+				} );
+			}
+
+			return access( this, function( value ) {
+				var data;
+
+				// The calling jQuery object (element matches) is not empty
+				// (and therefore has an element appears at this[ 0 ]) and the
+				// `value` parameter was not undefined. An empty jQuery object
+				// will result in `undefined` for elem = this[ 0 ] which will
+				// throw an exception if an attempt to read a data cache is made.
+				if ( elem && value === undefined ) {
+
+					// Attempt to get data from the cache
+					// The key will always be camelCased in Data
+					data = dataUser.get( elem, key );
+					if ( data !== undefined ) {
+						return data;
+					}
+
+					// Attempt to "discover" the data in
+					// HTML5 custom data-* attrs
+					data = dataAttr( elem, key );
+					if ( data !== undefined ) {
+						return data;
+					}
+
+					// We tried really hard, but the data doesn't exist.
+					return;
+				}
+
+				// Set the data...
+				this.each( function() {
+
+					// We always store the camelCased key
+					dataUser.set( this, key, value );
+				} );
+			}, null, value, arguments.length > 1, null, true );
+		},
+
+		removeData: function( key ) {
+			return this.each( function() {
+				dataUser.remove( this, key );
+			} );
+		}
+	} );
+
+
+	jQuery.extend( {
+		queue: function( elem, type, data ) {
+			var queue;
+
+			if ( elem ) {
+				type = ( type || "fx" ) + "queue";
+				queue = dataPriv.get( elem, type );
+
+				// Speed up dequeue by getting out quickly if this is just a lookup
+				if ( data ) {
+					if ( !queue || jQuery.isArray( data ) ) {
+						queue = dataPriv.access( elem, type, jQuery.makeArray( data ) );
+					} else {
+						queue.push( data );
+					}
+				}
+				return queue || [];
+			}
+		},
+
+		dequeue: function( elem, type ) {
+			type = type || "fx";
+
+			var queue = jQuery.queue( elem, type ),
+				startLength = queue.length,
+				fn = queue.shift(),
+				hooks = jQuery._queueHooks( elem, type ),
+				next = function() {
+					jQuery.dequeue( elem, type );
+				};
+
+			// If the fx queue is dequeued, always remove the progress sentinel
+			if ( fn === "inprogress" ) {
+				fn = queue.shift();
+				startLength--;
+			}
+
+			if ( fn ) {
+
+				// Add a progress sentinel to prevent the fx queue from being
+				// automatically dequeued
+				if ( type === "fx" ) {
+					queue.unshift( "inprogress" );
+				}
+
+				// Clear up the last queue stop function
+				delete hooks.stop;
+				fn.call( elem, next, hooks );
+			}
+
+			if ( !startLength && hooks ) {
+				hooks.empty.fire();
+			}
+		},
+
+		// Not public - generate a queueHooks object, or return the current one
+		_queueHooks: function( elem, type ) {
+			var key = type + "queueHooks";
+			return dataPriv.get( elem, key ) || dataPriv.access( elem, key, {
+				empty: jQuery.Callbacks( "once memory" ).add( function() {
+					dataPriv.remove( elem, [ type + "queue", key ] );
+				} )
+			} );
+		}
+	} );
+
+	jQuery.fn.extend( {
+		queue: function( type, data ) {
+			var setter = 2;
+
+			if ( typeof type !== "string" ) {
+				data = type;
+				type = "fx";
+				setter--;
+			}
+
+			if ( arguments.length < setter ) {
+				return jQuery.queue( this[ 0 ], type );
+			}
+
+			return data === undefined ?
+				this :
+				this.each( function() {
+					var queue = jQuery.queue( this, type, data );
+
+					// Ensure a hooks for this queue
+					jQuery._queueHooks( this, type );
+
+					if ( type === "fx" && queue[ 0 ] !== "inprogress" ) {
+						jQuery.dequeue( this, type );
+					}
+				} );
+		},
+		dequeue: function( type ) {
+			return this.each( function() {
+				jQuery.dequeue( this, type );
+			} );
+		},
+		clearQueue: function( type ) {
+			return this.queue( type || "fx", [] );
+		},
+
+		// Get a promise resolved when queues of a certain type
+		// are emptied (fx is the type by default)
+		promise: function( type, obj ) {
+			var tmp,
+				count = 1,
+				defer = jQuery.Deferred(),
+				elements = this,
+				i = this.length,
+				resolve = function() {
+					if ( !( --count ) ) {
+						defer.resolveWith( elements, [ elements ] );
+					}
+				};
+
+			if ( typeof type !== "string" ) {
+				obj = type;
+				type = undefined;
+			}
+			type = type || "fx";
+
+			while ( i-- ) {
+				tmp = dataPriv.get( elements[ i ], type + "queueHooks" );
+				if ( tmp && tmp.empty ) {
+					count++;
+					tmp.empty.add( resolve );
+				}
+			}
+			resolve();
+			return defer.promise( obj );
+		}
+	} );
+	var pnum = ( /[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/ ).source;
+
+	var rcssNum = new RegExp( "^(?:([+-])=|)(" + pnum + ")([a-z%]*)$", "i" );
+
+
+	var cssExpand = [ "Top", "Right", "Bottom", "Left" ];
+
+	var isHiddenWithinTree = function( elem, el ) {
+
+			// isHiddenWithinTree might be called from jQuery#filter function;
+			// in that case, element will be second argument
+			elem = el || elem;
+
+			// Inline style trumps all
+			return elem.style.display === "none" ||
+				elem.style.display === "" &&
+
+				// Otherwise, check computed style
+				// Support: Firefox <=43 - 45
+				// Disconnected elements can have computed display: none, so first confirm that elem is
+				// in the document.
+				jQuery.contains( elem.ownerDocument, elem ) &&
+
+				jQuery.css( elem, "display" ) === "none";
+		};
+
+	var swap = function( elem, options, callback, args ) {
+		var ret, name,
+			old = {};
+
+		// Remember the old values, and insert the new ones
+		for ( name in options ) {
+			old[ name ] = elem.style[ name ];
+			elem.style[ name ] = options[ name ];
+		}
+
+		ret = callback.apply( elem, args || [] );
+
+		// Revert the old values
+		for ( name in options ) {
+			elem.style[ name ] = old[ name ];
+		}
+
+		return ret;
+	};
+
+
+
+
+	function adjustCSS( elem, prop, valueParts, tween ) {
+		var adjusted,
+			scale = 1,
+			maxIterations = 20,
+			currentValue = tween ?
+				function() {
+					return tween.cur();
+				} :
+				function() {
+					return jQuery.css( elem, prop, "" );
+				},
+			initial = currentValue(),
+			unit = valueParts && valueParts[ 3 ] || ( jQuery.cssNumber[ prop ] ? "" : "px" ),
+
+			// Starting value computation is required for potential unit mismatches
+			initialInUnit = ( jQuery.cssNumber[ prop ] || unit !== "px" && +initial ) &&
+				rcssNum.exec( jQuery.css( elem, prop ) );
+
+		if ( initialInUnit && initialInUnit[ 3 ] !== unit ) {
+
+			// Trust units reported by jQuery.css
+			unit = unit || initialInUnit[ 3 ];
+
+			// Make sure we update the tween properties later on
+			valueParts = valueParts || [];
+
+			// Iteratively approximate from a nonzero starting point
+			initialInUnit = +initial || 1;
+
+			do {
+
+				// If previous iteration zeroed out, double until we get *something*.
+				// Use string for doubling so we don't accidentally see scale as unchanged below
+				scale = scale || ".5";
+
+				// Adjust and apply
+				initialInUnit = initialInUnit / scale;
+				jQuery.style( elem, prop, initialInUnit + unit );
+
+			// Update scale, tolerating zero or NaN from tween.cur()
+			// Break the loop if scale is unchanged or perfect, or if we've just had enough.
+			} while (
+				scale !== ( scale = currentValue() / initial ) && scale !== 1 && --maxIterations
+			);
+		}
+
+		if ( valueParts ) {
+			initialInUnit = +initialInUnit || +initial || 0;
+
+			// Apply relative offset (+=/-=) if specified
+			adjusted = valueParts[ 1 ] ?
+				initialInUnit + ( valueParts[ 1 ] + 1 ) * valueParts[ 2 ] :
+				+valueParts[ 2 ];
+			if ( tween ) {
+				tween.unit = unit;
+				tween.start = initialInUnit;
+				tween.end = adjusted;
+			}
+		}
+		return adjusted;
+	}
+
+
+	var defaultDisplayMap = {};
+
+	function getDefaultDisplay( elem ) {
+		var temp,
+			doc = elem.ownerDocument,
+			nodeName = elem.nodeName,
+			display = defaultDisplayMap[ nodeName ];
+
+		if ( display ) {
+			return display;
+		}
+
+		temp = doc.body.appendChild( doc.createElement( nodeName ) ),
+		display = jQuery.css( temp, "display" );
+
+		temp.parentNode.removeChild( temp );
+
+		if ( display === "none" ) {
+			display = "block";
+		}
+		defaultDisplayMap[ nodeName ] = display;
+
+		return display;
+	}
+
+	function showHide( elements, show ) {
+		var display, elem,
+			values = [],
+			index = 0,
+			length = elements.length;
+
+		// Determine new display value for elements that need to change
+		for ( ; index < length; index++ ) {
+			elem = elements[ index ];
+			if ( !elem.style ) {
+				continue;
+			}
+
+			display = elem.style.display;
+			if ( show ) {
+
+				// Since we force visibility upon cascade-hidden elements, an immediate (and slow)
+				// check is required in this first loop unless we have a nonempty display value (either
+				// inline or about-to-be-restored)
+				if ( display === "none" ) {
+					values[ index ] = dataPriv.get( elem, "display" ) || null;
+					if ( !values[ index ] ) {
+						elem.style.display = "";
+					}
+				}
+				if ( elem.style.display === "" && isHiddenWithinTree( elem ) ) {
+					values[ index ] = getDefaultDisplay( elem );
+				}
+			} else {
+				if ( display !== "none" ) {
+					values[ index ] = "none";
+
+					// Remember what we're overwriting
+					dataPriv.set( elem, "display", display );
+				}
+			}
+		}
+
+		// Set the display of the elements in a second loop to avoid constant reflow
+		for ( index = 0; index < length; index++ ) {
+			if ( values[ index ] != null ) {
+				elements[ index ].style.display = values[ index ];
+			}
+		}
+
+		return elements;
+	}
+
+	jQuery.fn.extend( {
+		show: function() {
+			return showHide( this, true );
+		},
+		hide: function() {
+			return showHide( this );
+		},
+		toggle: function( state ) {
+			if ( typeof state === "boolean" ) {
+				return state ? this.show() : this.hide();
+			}
+
+			return this.each( function() {
+				if ( isHiddenWithinTree( this ) ) {
+					jQuery( this ).show();
+				} else {
+					jQuery( this ).hide();
+				}
+			} );
+		}
+	} );
+	var rcheckableType = ( /^(?:checkbox|radio)$/i );
+
+	var rtagName = ( /<([a-z][^\/\0>\x20\t\r\n\f]+)/i );
+
+	var rscriptType = ( /^$|\/(?:java|ecma)script/i );
+
+
+
+	// We have to close these tags to support XHTML (#13200)
+	var wrapMap = {
+
+		// Support: IE <=9 only
+		option: [ 1, "<select multiple='multiple'>", "</select>" ],
+
+		// XHTML parsers do not magically insert elements in the
+		// same way that tag soup parsers do. So we cannot shorten
+		// this by omitting <tbody> or other required elements.
+		thead: [ 1, "<table>", "</table>" ],
+		col: [ 2, "<table><colgroup>", "</colgroup></table>" ],
+		tr: [ 2, "<table><tbody>", "</tbody></table>" ],
+		td: [ 3, "<table><tbody><tr>", "</tr></tbody></table>" ],
+
+		_default: [ 0, "", "" ]
+	};
+
+	// Support: IE <=9 only
+	wrapMap.optgroup = wrapMap.option;
+
+	wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead;
+	wrapMap.th = wrapMap.td;
+
+
+	function getAll( context, tag ) {
+
+		// Support: IE <=9 - 11 only
+		// Use typeof to avoid zero-argument method invocation on host objects (#15151)
+		var ret = typeof context.getElementsByTagName !== "undefined" ?
+				context.getElementsByTagName( tag || "*" ) :
+				typeof context.querySelectorAll !== "undefined" ?
+					context.querySelectorAll( tag || "*" ) :
+				[];
+
+		return tag === undefined || tag && jQuery.nodeName( context, tag ) ?
+			jQuery.merge( [ context ], ret ) :
+			ret;
+	}
+
+
+	// Mark scripts as having already been evaluated
+	function setGlobalEval( elems, refElements ) {
+		var i = 0,
+			l = elems.length;
+
+		for ( ; i < l; i++ ) {
+			dataPriv.set(
+				elems[ i ],
+				"globalEval",
+				!refElements || dataPriv.get( refElements[ i ], "globalEval" )
+			);
+		}
+	}
+
+
+	var rhtml = /<|&#?\w+;/;
+
+	function buildFragment( elems, context, scripts, selection, ignored ) {
+		var elem, tmp, tag, wrap, contains, j,
+			fragment = context.createDocumentFragment(),
+			nodes = [],
+			i = 0,
+			l = elems.length;
+
+		for ( ; i < l; i++ ) {
+			elem = elems[ i ];
+
+			if ( elem || elem === 0 ) {
+
+				// Add nodes directly
+				if ( jQuery.type( elem ) === "object" ) {
+
+					// Support: Android <=4.0 only, PhantomJS 1 only
+					// push.apply(_, arraylike) throws on ancient WebKit
+					jQuery.merge( nodes, elem.nodeType ? [ elem ] : elem );
+
+				// Convert non-html into a text node
+				} else if ( !rhtml.test( elem ) ) {
+					nodes.push( context.createTextNode( elem ) );
+
+				// Convert html into DOM nodes
+				} else {
+					tmp = tmp || fragment.appendChild( context.createElement( "div" ) );
+
+					// Deserialize a standard representation
+					tag = ( rtagName.exec( elem ) || [ "", "" ] )[ 1 ].toLowerCase();
+					wrap = wrapMap[ tag ] || wrapMap._default;
+					tmp.innerHTML = wrap[ 1 ] + jQuery.htmlPrefilter( elem ) + wrap[ 2 ];
+
+					// Descend through wrappers to the right content
+					j = wrap[ 0 ];
+					while ( j-- ) {
+						tmp = tmp.lastChild;
+					}
+
+					// Support: Android <=4.0 only, PhantomJS 1 only
+					// push.apply(_, arraylike) throws on ancient WebKit
+					jQuery.merge( nodes, tmp.childNodes );
+
+					// Remember the top-level container
+					tmp = fragment.firstChild;
+
+					// Ensure the created nodes are orphaned (#12392)
+					tmp.textContent = "";
+				}
+			}
+		}
+
+		// Remove wrapper from fragment
+		fragment.textContent = "";
+
+		i = 0;
+		while ( ( elem = nodes[ i++ ] ) ) {
+
+			// Skip elements already in the context collection (trac-4087)
+			if ( selection && jQuery.inArray( elem, selection ) > -1 ) {
+				if ( ignored ) {
+					ignored.push( elem );
+				}
+				continue;
+			}
+
+			contains = jQuery.contains( elem.ownerDocument, elem );
+
+			// Append to fragment
+			tmp = getAll( fragment.appendChild( elem ), "script" );
+
+			// Preserve script evaluation history
+			if ( contains ) {
+				setGlobalEval( tmp );
+			}
+
+			// Capture executables
+			if ( scripts ) {
+				j = 0;
+				while ( ( elem = tmp[ j++ ] ) ) {
+					if ( rscriptType.test( elem.type || "" ) ) {
+						scripts.push( elem );
+					}
+				}
+			}
+		}
+
+		return fragment;
+	}
+
+
+	( function() {
+		var fragment = document.createDocumentFragment(),
+			div = fragment.appendChild( document.createElement( "div" ) ),
+			input = document.createElement( "input" );
+
+		// Support: Android 4.0 - 4.3 only
+		// Check state lost if the name is set (#11217)
+		// Support: Windows Web Apps (WWA)
+		// `name` and `type` must use .setAttribute for WWA (#14901)
+		input.setAttribute( "type", "radio" );
+		input.setAttribute( "checked", "checked" );
+		input.setAttribute( "name", "t" );
+
+		div.appendChild( input );
+
+		// Support: Android <=4.1 only
+		// Older WebKit doesn't clone checked state correctly in fragments
+		support.checkClone = div.cloneNode( true ).cloneNode( true ).lastChild.checked;
+
+		// Support: IE <=11 only
+		// Make sure textarea (and checkbox) defaultValue is properly cloned
+		div.innerHTML = "<textarea>x</textarea>";
+		support.noCloneChecked = !!div.cloneNode( true ).lastChild.defaultValue;
+	} )();
+	var documentElement = document.documentElement;
+
+
+
+	var
+		rkeyEvent = /^key/,
+		rmouseEvent = /^(?:mouse|pointer|contextmenu|drag|drop)|click/,
+		rtypenamespace = /^([^.]*)(?:\.(.+)|)/;
+
+	function returnTrue() {
+		return true;
+	}
+
+	function returnFalse() {
+		return false;
+	}
+
+	// Support: IE <=9 only
+	// See #13393 for more info
+	function safeActiveElement() {
+		try {
+			return document.activeElement;
+		} catch ( err ) { }
+	}
+
+	function on( elem, types, selector, data, fn, one ) {
+		var origFn, type;
+
+		// Types can be a map of types/handlers
+		if ( typeof types === "object" ) {
+
+			// ( types-Object, selector, data )
+			if ( typeof selector !== "string" ) {
+
+				// ( types-Object, data )
+				data = data || selector;
+				selector = undefined;
+			}
+			for ( type in types ) {
+				on( elem, type, selector, data, types[ type ], one );
+			}
+			return elem;
+		}
+
+		if ( data == null && fn == null ) {
+
+			// ( types, fn )
+			fn = selector;
+			data = selector = undefined;
+		} else if ( fn == null ) {
+			if ( typeof selector === "string" ) {
+
+				// ( types, selector, fn )
+				fn = data;
+				data = undefined;
+			} else {
+
+				// ( types, data, fn )
+				fn = data;
+				data = selector;
+				selector = undefined;
+			}
+		}
+		if ( fn === false ) {
+			fn = returnFalse;
+		} else if ( !fn ) {
+			return elem;
+		}
+
+		if ( one === 1 ) {
+			origFn = fn;
+			fn = function( event ) {
+
+				// Can use an empty set, since event contains the info
+				jQuery().off( event );
+				return origFn.apply( this, arguments );
+			};
+
+			// Use same guid so caller can remove using origFn
+			fn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ );
+		}
+		return elem.each( function() {
+			jQuery.event.add( this, types, fn, data, selector );
+		} );
+	}
+
+	/*
+	 * Helper functions for managing events -- not part of the public interface.
+	 * Props to Dean Edwards' addEvent library for many of the ideas.
+	 */
+	jQuery.event = {
+
+		global: {},
+
+		add: function( elem, types, handler, data, selector ) {
+
+			var handleObjIn, eventHandle, tmp,
+				events, t, handleObj,
+				special, handlers, type, namespaces, origType,
+				elemData = dataPriv.get( elem );
+
+			// Don't attach events to noData or text/comment nodes (but allow plain objects)
+			if ( !elemData ) {
+				return;
+			}
+
+			// Caller can pass in an object of custom data in lieu of the handler
+			if ( handler.handler ) {
+				handleObjIn = handler;
+				handler = handleObjIn.handler;
+				selector = handleObjIn.selector;
+			}
+
+			// Ensure that invalid selectors throw exceptions at attach time
+			// Evaluate against documentElement in case elem is a non-element node (e.g., document)
+			if ( selector ) {
+				jQuery.find.matchesSelector( documentElement, selector );
+			}
+
+			// Make sure that the handler has a unique ID, used to find/remove it later
+			if ( !handler.guid ) {
+				handler.guid = jQuery.guid++;
+			}
+
+			// Init the element's event structure and main handler, if this is the first
+			if ( !( events = elemData.events ) ) {
+				events = elemData.events = {};
+			}
+			if ( !( eventHandle = elemData.handle ) ) {
+				eventHandle = elemData.handle = function( e ) {
+
+					// Discard the second event of a jQuery.event.trigger() and
+					// when an event is called after a page has unloaded
+					return typeof jQuery !== "undefined" && jQuery.event.triggered !== e.type ?
+						jQuery.event.dispatch.apply( elem, arguments ) : undefined;
+				};
+			}
+
+			// Handle multiple events separated by a space
+			types = ( types || "" ).match( rnotwhite ) || [ "" ];
+			t = types.length;
+			while ( t-- ) {
+				tmp = rtypenamespace.exec( types[ t ] ) || [];
+				type = origType = tmp[ 1 ];
+				namespaces = ( tmp[ 2 ] || "" ).split( "." ).sort();
+
+				// There *must* be a type, no attaching namespace-only handlers
+				if ( !type ) {
+					continue;
+				}
+
+				// If event changes its type, use the special event handlers for the changed type
+				special = jQuery.event.special[ type ] || {};
+
+				// If selector defined, determine special event api type, otherwise given type
+				type = ( selector ? special.delegateType : special.bindType ) || type;
+
+				// Update special based on newly reset type
+				special = jQuery.event.special[ type ] || {};
+
+				// handleObj is passed to all event handlers
+				handleObj = jQuery.extend( {
+					type: type,
+					origType: origType,
+					data: data,
+					handler: handler,
+					guid: handler.guid,
+					selector: selector,
+					needsContext: selector && jQuery.expr.match.needsContext.test( selector ),
+					namespace: namespaces.join( "." )
+				}, handleObjIn );
+
+				// Init the event handler queue if we're the first
+				if ( !( handlers = events[ type ] ) ) {
+					handlers = events[ type ] = [];
+					handlers.delegateCount = 0;
+
+					// Only use addEventListener if the special events handler returns false
+					if ( !special.setup ||
+						special.setup.call( elem, data, namespaces, eventHandle ) === false ) {
+
+						if ( elem.addEventListener ) {
+							elem.addEventListener( type, eventHandle );
+						}
+					}
+				}
+
+				if ( special.add ) {
+					special.add.call( elem, handleObj );
+
+					if ( !handleObj.handler.guid ) {
+						handleObj.handler.guid = handler.guid;
+					}
+				}
+
+				// Add to the element's handler list, delegates in front
+				if ( selector ) {
+					handlers.splice( handlers.delegateCount++, 0, handleObj );
+				} else {
+					handlers.push( handleObj );
+				}
+
+				// Keep track of which events have ever been used, for event optimization
+				jQuery.event.global[ type ] = true;
+			}
+
+		},
+
+		// Detach an event or set of events from an element
+		remove: function( elem, types, handler, selector, mappedTypes ) {
+
+			var j, origCount, tmp,
+				events, t, handleObj,
+				special, handlers, type, namespaces, origType,
+				elemData = dataPriv.hasData( elem ) && dataPriv.get( elem );
+
+			if ( !elemData || !( events = elemData.events ) ) {
+				return;
+			}
+
+			// Once for each type.namespace in types; type may be omitted
+			types = ( types || "" ).match( rnotwhite ) || [ "" ];
+			t = types.length;
+			while ( t-- ) {
+				tmp = rtypenamespace.exec( types[ t ] ) || [];
+				type = origType = tmp[ 1 ];
+				namespaces = ( tmp[ 2 ] || "" ).split( "." ).sort();
+
+				// Unbind all events (on this namespace, if provided) for the element
+				if ( !type ) {
+					for ( type in events ) {
+						jQuery.event.remove( elem, type + types[ t ], handler, selector, true );
+					}
+					continue;
+				}
+
+				special = jQuery.event.special[ type ] || {};
+				type = ( selector ? special.delegateType : special.bindType ) || type;
+				handlers = events[ type ] || [];
+				tmp = tmp[ 2 ] &&
+					new RegExp( "(^|\\.)" + namespaces.join( "\\.(?:.*\\.|)" ) + "(\\.|$)" );
+
+				// Remove matching events
+				origCount = j = handlers.length;
+				while ( j-- ) {
+					handleObj = handlers[ j ];
+
+					if ( ( mappedTypes || origType === handleObj.origType ) &&
+						( !handler || handler.guid === handleObj.guid ) &&
+						( !tmp || tmp.test( handleObj.namespace ) ) &&
+						( !selector || selector === handleObj.selector ||
+							selector === "**" && handleObj.selector ) ) {
+						handlers.splice( j, 1 );
+
+						if ( handleObj.selector ) {
+							handlers.delegateCount--;
+						}
+						if ( special.remove ) {
+							special.remove.call( elem, handleObj );
+						}
+					}
+				}
+
+				// Remove generic event handler if we removed something and no more handlers exist
+				// (avoids potential for endless recursion during removal of special event handlers)
+				if ( origCount && !handlers.length ) {
+					if ( !special.teardown ||
+						special.teardown.call( elem, namespaces, elemData.handle ) === false ) {
+
+						jQuery.removeEvent( elem, type, elemData.handle );
+					}
+
+					delete events[ type ];
+				}
+			}
+
+			// Remove data and the expando if it's no longer used
+			if ( jQuery.isEmptyObject( events ) ) {
+				dataPriv.remove( elem, "handle events" );
+			}
+		},
+
+		dispatch: function( nativeEvent ) {
+
+			// Make a writable jQuery.Event from the native event object
+			var event = jQuery.event.fix( nativeEvent );
+
+			var i, j, ret, matched, handleObj, handlerQueue,
+				args = new Array( arguments.length ),
+				handlers = ( dataPriv.get( this, "events" ) || {} )[ event.type ] || [],
+				special = jQuery.event.special[ event.type ] || {};
+
+			// Use the fix-ed jQuery.Event rather than the (read-only) native event
+			args[ 0 ] = event;
+
+			for ( i = 1; i < arguments.length; i++ ) {
+				args[ i ] = arguments[ i ];
+			}
+
+			event.delegateTarget = this;
+
+			// Call the preDispatch hook for the mapped type, and let it bail if desired
+			if ( special.preDispatch && special.preDispatch.call( this, event ) === false ) {
+				return;
+			}
+
+			// Determine handlers
+			handlerQueue = jQuery.event.handlers.call( this, event, handlers );
+
+			// Run delegates first; they may want to stop propagation beneath us
+			i = 0;
+			while ( ( matched = handlerQueue[ i++ ] ) && !event.isPropagationStopped() ) {
+				event.currentTarget = matched.elem;
+
+				j = 0;
+				while ( ( handleObj = matched.handlers[ j++ ] ) &&
+					!event.isImmediatePropagationStopped() ) {
+
+					// Triggered event must either 1) have no namespace, or 2) have namespace(s)
+					// a subset or equal to those in the bound event (both can have no namespace).
+					if ( !event.rnamespace || event.rnamespace.test( handleObj.namespace ) ) {
+
+						event.handleObj = handleObj;
+						event.data = handleObj.data;
+
+						ret = ( ( jQuery.event.special[ handleObj.origType ] || {} ).handle ||
+							handleObj.handler ).apply( matched.elem, args );
+
+						if ( ret !== undefined ) {
+							if ( ( event.result = ret ) === false ) {
+								event.preventDefault();
+								event.stopPropagation();
+							}
+						}
+					}
+				}
+			}
+
+			// Call the postDispatch hook for the mapped type
+			if ( special.postDispatch ) {
+				special.postDispatch.call( this, event );
+			}
+
+			return event.result;
+		},
+
+		handlers: function( event, handlers ) {
+			var i, matches, sel, handleObj,
+				handlerQueue = [],
+				delegateCount = handlers.delegateCount,
+				cur = event.target;
+
+			// Support: IE <=9
+			// Find delegate handlers
+			// Black-hole SVG <use> instance trees (#13180)
+			//
+			// Support: Firefox <=42
+			// Avoid non-left-click in FF but don't block IE radio events (#3861, gh-2343)
+			if ( delegateCount && cur.nodeType &&
+				( event.type !== "click" || isNaN( event.button ) || event.button < 1 ) ) {
+
+				for ( ; cur !== this; cur = cur.parentNode || this ) {
+
+					// Don't check non-elements (#13208)
+					// Don't process clicks on disabled elements (#6911, #8165, #11382, #11764)
+					if ( cur.nodeType === 1 && ( cur.disabled !== true || event.type !== "click" ) ) {
+						matches = [];
+						for ( i = 0; i < delegateCount; i++ ) {
+							handleObj = handlers[ i ];
+
+							// Don't conflict with Object.prototype properties (#13203)
+							sel = handleObj.selector + " ";
+
+							if ( matches[ sel ] === undefined ) {
+								matches[ sel ] = handleObj.needsContext ?
+									jQuery( sel, this ).index( cur ) > -1 :
+									jQuery.find( sel, this, null, [ cur ] ).length;
+							}
+							if ( matches[ sel ] ) {
+								matches.push( handleObj );
+							}
+						}
+						if ( matches.length ) {
+							handlerQueue.push( { elem: cur, handlers: matches } );
+						}
+					}
+				}
+			}
+
+			// Add the remaining (directly-bound) handlers
+			if ( delegateCount < handlers.length ) {
+				handlerQueue.push( { elem: this, handlers: handlers.slice( delegateCount ) } );
+			}
+
+			return handlerQueue;
+		},
+
+		addProp: function( name, hook ) {
+			Object.defineProperty( jQuery.Event.prototype, name, {
+				enumerable: true,
+				configurable: true,
+
+				get: jQuery.isFunction( hook ) ?
+					function() {
+						if ( this.originalEvent ) {
+								return hook( this.originalEvent );
+						}
+					} :
+					function() {
+						if ( this.originalEvent ) {
+								return this.originalEvent[ name ];
+						}
+					},
+
+				set: function( value ) {
+					Object.defineProperty( this, name, {
+						enumerable: true,
+						configurable: true,
+						writable: true,
+						value: value
+					} );
+				}
+			} );
+		},
+
+		fix: function( originalEvent ) {
+			return originalEvent[ jQuery.expando ] ?
+				originalEvent :
+				new jQuery.Event( originalEvent );
+		},
+
+		special: {
+			load: {
+
+				// Prevent triggered image.load events from bubbling to window.load
+				noBubble: true
+			},
+			focus: {
+
+				// Fire native event if possible so blur/focus sequence is correct
+				trigger: function() {
+					if ( this !== safeActiveElement() && this.focus ) {
+						this.focus();
+						return false;
+					}
+				},
+				delegateType: "focusin"
+			},
+			blur: {
+				trigger: function() {
+					if ( this === safeActiveElement() && this.blur ) {
+						this.blur();
+						return false;
+					}
+				},
+				delegateType: "focusout"
+			},
+			click: {
+
+				// For checkbox, fire native event so checked state will be right
+				trigger: function() {
+					if ( this.type === "checkbox" && this.click && jQuery.nodeName( this, "input" ) ) {
+						this.click();
+						return false;
+					}
+				},
+
+				// For cross-browser consistency, don't fire native .click() on links
+				_default: function( event ) {
+					return jQuery.nodeName( event.target, "a" );
+				}
+			},
+
+			beforeunload: {
+				postDispatch: function( event ) {
+
+					// Support: Firefox 20+
+					// Firefox doesn't alert if the returnValue field is not set.
+					if ( event.result !== undefined && event.originalEvent ) {
+						event.originalEvent.returnValue = event.result;
+					}
+				}
+			}
+		}
+	};
+
+	jQuery.removeEvent = function( elem, type, handle ) {
+
+		// This "if" is needed for plain objects
+		if ( elem.removeEventListener ) {
+			elem.removeEventListener( type, handle );
+		}
+	};
+
+	jQuery.Event = function( src, props ) {
+
+		// Allow instantiation without the 'new' keyword
+		if ( !( this instanceof jQuery.Event ) ) {
+			return new jQuery.Event( src, props );
+		}
+
+		// Event object
+		if ( src && src.type ) {
+			this.originalEvent = src;
+			this.type = src.type;
+
+			// Events bubbling up the document may have been marked as prevented
+			// by a handler lower down the tree; reflect the correct value.
+			this.isDefaultPrevented = src.defaultPrevented ||
+					src.defaultPrevented === undefined &&
+
+					// Support: Android <=2.3 only
+					src.returnValue === false ?
+				returnTrue :
+				returnFalse;
+
+			// Create target properties
+			// Support: Safari <=6 - 7 only
+			// Target should not be a text node (#504, #13143)
+			this.target = ( src.target && src.target.nodeType === 3 ) ?
+				src.target.parentNode :
+				src.target;
+
+			this.currentTarget = src.currentTarget;
+			this.relatedTarget = src.relatedTarget;
+
+		// Event type
+		} else {
+			this.type = src;
+		}
+
+		// Put explicitly provided properties onto the event object
+		if ( props ) {
+			jQuery.extend( this, props );
+		}
+
+		// Create a timestamp if incoming event doesn't have one
+		this.timeStamp = src && src.timeStamp || jQuery.now();
+
+		// Mark it as fixed
+		this[ jQuery.expando ] = true;
+	};
+
+	// jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding
+	// https://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html
+	jQuery.Event.prototype = {
+		constructor: jQuery.Event,
+		isDefaultPrevented: returnFalse,
+		isPropagationStopped: returnFalse,
+		isImmediatePropagationStopped: returnFalse,
+		isSimulated: false,
+
+		preventDefault: function() {
+			var e = this.originalEvent;
+
+			this.isDefaultPrevented = returnTrue;
+
+			if ( e && !this.isSimulated ) {
+				e.preventDefault();
+			}
+		},
+		stopPropagation: function() {
+			var e = this.originalEvent;
+
+			this.isPropagationStopped = returnTrue;
+
+			if ( e && !this.isSimulated ) {
+				e.stopPropagation();
+			}
+		},
+		stopImmediatePropagation: function() {
+			var e = this.originalEvent;
+
+			this.isImmediatePropagationStopped = returnTrue;
+
+			if ( e && !this.isSimulated ) {
+				e.stopImmediatePropagation();
+			}
+
+			this.stopPropagation();
+		}
+	};
+
+	// Includes all common event props including KeyEvent and MouseEvent specific props
+	jQuery.each( {
+		altKey: true,
+		bubbles: true,
+		cancelable: true,
+		changedTouches: true,
+		ctrlKey: true,
+		detail: true,
+		eventPhase: true,
+		metaKey: true,
+		pageX: true,
+		pageY: true,
+		shiftKey: true,
+		view: true,
+		"char": true,
+		charCode: true,
+		key: true,
+		keyCode: true,
+		button: true,
+		buttons: true,
+		clientX: true,
+		clientY: true,
+		offsetX: true,
+		offsetY: true,
+		pointerId: true,
+		pointerType: true,
+		screenX: true,
+		screenY: true,
+		targetTouches: true,
+		toElement: true,
+		touches: true,
+
+		which: function( event ) {
+			var button = event.button;
+
+			// Add which for key events
+			if ( event.which == null && rkeyEvent.test( event.type ) ) {
+				return event.charCode != null ? event.charCode : event.keyCode;
+			}
+
+			// Add which for click: 1 === left; 2 === middle; 3 === right
+			if ( !event.which && button !== undefined && rmouseEvent.test( event.type ) ) {
+				return ( button & 1 ? 1 : ( button & 2 ? 3 : ( button & 4 ? 2 : 0 ) ) );
+			}
+
+			return event.which;
+		}
+	}, jQuery.event.addProp );
+
+	// Create mouseenter/leave events using mouseover/out and event-time checks
+	// so that event delegation works in jQuery.
+	// Do the same for pointerenter/pointerleave and pointerover/pointerout
+	//
+	// Support: Safari 7 only
+	// Safari sends mouseenter too often; see:
+	// https://bugs.chromium.org/p/chromium/issues/detail?id=470258
+	// for the description of the bug (it existed in older Chrome versions as well).
+	jQuery.each( {
+		mouseenter: "mouseover",
+		mouseleave: "mouseout",
+		pointerenter: "pointerover",
+		pointerleave: "pointerout"
+	}, function( orig, fix ) {
+		jQuery.event.special[ orig ] = {
+			delegateType: fix,
+			bindType: fix,
+
+			handle: function( event ) {
+				var ret,
+					target = this,
+					related = event.relatedTarget,
+					handleObj = event.handleObj;
+
+				// For mouseenter/leave call the handler if related is outside the target.
+				// NB: No relatedTarget if the mouse left/entered the browser window
+				if ( !related || ( related !== target && !jQuery.contains( target, related ) ) ) {
+					event.type = handleObj.origType;
+					ret = handleObj.handler.apply( this, arguments );
+					event.type = fix;
+				}
+				return ret;
+			}
+		};
+	} );
+
+	jQuery.fn.extend( {
+
+		on: function( types, selector, data, fn ) {
+			return on( this, types, selector, data, fn );
+		},
+		one: function( types, selector, data, fn ) {
+			return on( this, types, selector, data, fn, 1 );
+		},
+		off: function( types, selector, fn ) {
+			var handleObj, type;
+			if ( types && types.preventDefault && types.handleObj ) {
+
+				// ( event )  dispatched jQuery.Event
+				handleObj = types.handleObj;
+				jQuery( types.delegateTarget ).off(
+					handleObj.namespace ?
+						handleObj.origType + "." + handleObj.namespace :
+						handleObj.origType,
+					handleObj.selector,
+					handleObj.handler
+				);
+				return this;
+			}
+			if ( typeof types === "object" ) {
+
+				// ( types-object [, selector] )
+				for ( type in types ) {
+					this.off( type, selector, types[ type ] );
+				}
+				return this;
+			}
+			if ( selector === false || typeof selector === "function" ) {
+
+				// ( types [, fn] )
+				fn = selector;
+				selector = undefined;
+			}
+			if ( fn === false ) {
+				fn = returnFalse;
+			}
+			return this.each( function() {
+				jQuery.event.remove( this, types, fn, selector );
+			} );
+		}
+	} );
+
+
+	var
+
+		/* eslint-disable max-len */
+
+		// See https://github.com/eslint/eslint/issues/3229
+		rxhtmlTag = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([a-z][^\/\0>\x20\t\r\n\f]*)[^>]*)\/>/gi,
+
+		/* eslint-enable */
+
+		// Support: IE <=10 - 11, Edge 12 - 13
+		// In IE/Edge using regex groups here causes severe slowdowns.
+		// See https://connect.microsoft.com/IE/feedback/details/1736512/
+		rnoInnerhtml = /<script|<style|<link/i,
+
+		// checked="checked" or checked
+		rchecked = /checked\s*(?:[^=]|=\s*.checked.)/i,
+		rscriptTypeMasked = /^true\/(.*)/,
+		rcleanScript = /^\s*<!(?:\[CDATA\[|--)|(?:\]\]|--)>\s*$/g;
+
+	function manipulationTarget( elem, content ) {
+		if ( jQuery.nodeName( elem, "table" ) &&
+			jQuery.nodeName( content.nodeType !== 11 ? content : content.firstChild, "tr" ) ) {
+
+			return elem.getElementsByTagName( "tbody" )[ 0 ] || elem;
+		}
+
+		return elem;
+	}
+
+	// Replace/restore the type attribute of script elements for safe DOM manipulation
+	function disableScript( elem ) {
+		elem.type = ( elem.getAttribute( "type" ) !== null ) + "/" + elem.type;
+		return elem;
+	}
+	function restoreScript( elem ) {
+		var match = rscriptTypeMasked.exec( elem.type );
+
+		if ( match ) {
+			elem.type = match[ 1 ];
+		} else {
+			elem.removeAttribute( "type" );
+		}
+
+		return elem;
+	}
+
+	function cloneCopyEvent( src, dest ) {
+		var i, l, type, pdataOld, pdataCur, udataOld, udataCur, events;
+
+		if ( dest.nodeType !== 1 ) {
+			return;
+		}
+
+		// 1. Copy private data: events, handlers, etc.
+		if ( dataPriv.hasData( src ) ) {
+			pdataOld = dataPriv.access( src );
+			pdataCur = dataPriv.set( dest, pdataOld );
+			events = pdataOld.events;
+
+			if ( events ) {
+				delete pdataCur.handle;
+				pdataCur.events = {};
+
+				for ( type in events ) {
+					for ( i = 0, l = events[ type ].length; i < l; i++ ) {
+						jQuery.event.add( dest, type, events[ type ][ i ] );
+					}
+				}
+			}
+		}
+
+		// 2. Copy user data
+		if ( dataUser.hasData( src ) ) {
+			udataOld = dataUser.access( src );
+			udataCur = jQuery.extend( {}, udataOld );
+
+			dataUser.set( dest, udataCur );
+		}
+	}
+
+	// Fix IE bugs, see support tests
+	function fixInput( src, dest ) {
+		var nodeName = dest.nodeName.toLowerCase();
+
+		// Fails to persist the checked state of a cloned checkbox or radio button.
+		if ( nodeName === "input" && rcheckableType.test( src.type ) ) {
+			dest.checked = src.checked;
+
+		// Fails to return the selected option to the default selected state when cloning options
+		} else if ( nodeName === "input" || nodeName === "textarea" ) {
+			dest.defaultValue = src.defaultValue;
+		}
+	}
+
+	function domManip( collection, args, callback, ignored ) {
+
+		// Flatten any nested arrays
+		args = concat.apply( [], args );
+
+		var fragment, first, scripts, hasScripts, node, doc,
+			i = 0,
+			l = collection.length,
+			iNoClone = l - 1,
+			value = args[ 0 ],
+			isFunction = jQuery.isFunction( value );
+
+		// We can't cloneNode fragments that contain checked, in WebKit
+		if ( isFunction ||
+				( l > 1 && typeof value === "string" &&
+					!support.checkClone && rchecked.test( value ) ) ) {
+			return collection.each( function( index ) {
+				var self = collection.eq( index );
+				if ( isFunction ) {
+					args[ 0 ] = value.call( this, index, self.html() );
+				}
+				domManip( self, args, callback, ignored );
+			} );
+		}
+
+		if ( l ) {
+			fragment = buildFragment( args, collection[ 0 ].ownerDocument, false, collection, ignored );
+			first = fragment.firstChild;
+
+			if ( fragment.childNodes.length === 1 ) {
+				fragment = first;
+			}
+
+			// Require either new content or an interest in ignored elements to invoke the callback
+			if ( first || ignored ) {
+				scripts = jQuery.map( getAll( fragment, "script" ), disableScript );
+				hasScripts = scripts.length;
+
+				// Use the original fragment for the last item
+				// instead of the first because it can end up
+				// being emptied incorrectly in certain situations (#8070).
+				for ( ; i < l; i++ ) {
+					node = fragment;
+
+					if ( i !== iNoClone ) {
+						node = jQuery.clone( node, true, true );
+
+						// Keep references to cloned scripts for later restoration
+						if ( hasScripts ) {
+
+							// Support: Android <=4.0 only, PhantomJS 1 only
+							// push.apply(_, arraylike) throws on ancient WebKit
+							jQuery.merge( scripts, getAll( node, "script" ) );
+						}
+					}
+
+					callback.call( collection[ i ], node, i );
+				}
+
+				if ( hasScripts ) {
+					doc = scripts[ scripts.length - 1 ].ownerDocument;
+
+					// Reenable scripts
+					jQuery.map( scripts, restoreScript );
+
+					// Evaluate executable scripts on first document insertion
+					for ( i = 0; i < hasScripts; i++ ) {
+						node = scripts[ i ];
+						if ( rscriptType.test( node.type || "" ) &&
+							!dataPriv.access( node, "globalEval" ) &&
+							jQuery.contains( doc, node ) ) {
+
+							if ( node.src ) {
+
+								// Optional AJAX dependency, but won't run scripts if not present
+								if ( jQuery._evalUrl ) {
+									jQuery._evalUrl( node.src );
+								}
+							} else {
+								DOMEval( node.textContent.replace( rcleanScript, "" ), doc );
+							}
+						}
+					}
+				}
+			}
+		}
+
+		return collection;
+	}
+
+	function remove( elem, selector, keepData ) {
+		var node,
+			nodes = selector ? jQuery.filter( selector, elem ) : elem,
+			i = 0;
+
+		for ( ; ( node = nodes[ i ] ) != null; i++ ) {
+			if ( !keepData && node.nodeType === 1 ) {
+				jQuery.cleanData( getAll( node ) );
+			}
+
+			if ( node.parentNode ) {
+				if ( keepData && jQuery.contains( node.ownerDocument, node ) ) {
+					setGlobalEval( getAll( node, "script" ) );
+				}
+				node.parentNode.removeChild( node );
+			}
+		}
+
+		return elem;
+	}
+
+	jQuery.extend( {
+		htmlPrefilter: function( html ) {
+			return html.replace( rxhtmlTag, "<$1></$2>" );
+		},
+
+		clone: function( elem, dataAndEvents, deepDataAndEvents ) {
+			var i, l, srcElements, destElements,
+				clone = elem.cloneNode( true ),
+				inPage = jQuery.contains( elem.ownerDocument, elem );
+
+			// Fix IE cloning issues
+			if ( !support.noCloneChecked && ( elem.nodeType === 1 || elem.nodeType === 11 ) &&
+					!jQuery.isXMLDoc( elem ) ) {
+
+				// We eschew Sizzle here for performance reasons: https://jsperf.com/getall-vs-sizzle/2
+				destElements = getAll( clone );
+				srcElements = getAll( elem );
+
+				for ( i = 0, l = srcElements.length; i < l; i++ ) {
+					fixInput( srcElements[ i ], destElements[ i ] );
+				}
+			}
+
+			// Copy the events from the original to the clone
+			if ( dataAndEvents ) {
+				if ( deepDataAndEvents ) {
+					srcElements = srcElements || getAll( elem );
+					destElements = destElements || getAll( clone );
+
+					for ( i = 0, l = srcElements.length; i < l; i++ ) {
+						cloneCopyEvent( srcElements[ i ], destElements[ i ] );
+					}
+				} else {
+					cloneCopyEvent( elem, clone );
+				}
+			}
+
+			// Preserve script evaluation history
+			destElements = getAll( clone, "script" );
+			if ( destElements.length > 0 ) {
+				setGlobalEval( destElements, !inPage && getAll( elem, "script" ) );
+			}
+
+			// Return the cloned set
+			return clone;
+		},
+
+		cleanData: function( elems ) {
+			var data, elem, type,
+				special = jQuery.event.special,
+				i = 0;
+
+			for ( ; ( elem = elems[ i ] ) !== undefined; i++ ) {
+				if ( acceptData( elem ) ) {
+					if ( ( data = elem[ dataPriv.expando ] ) ) {
+						if ( data.events ) {
+							for ( type in data.events ) {
+								if ( special[ type ] ) {
+									jQuery.event.remove( elem, type );
+
+								// This is a shortcut to avoid jQuery.event.remove's overhead
+								} else {
+									jQuery.removeEvent( elem, type, data.handle );
+								}
+							}
+						}
+
+						// Support: Chrome <=35 - 45+
+						// Assign undefined instead of using delete, see Data#remove
+						elem[ dataPriv.expando ] = undefined;
+					}
+					if ( elem[ dataUser.expando ] ) {
+
+						// Support: Chrome <=35 - 45+
+						// Assign undefined instead of using delete, see Data#remove
+						elem[ dataUser.expando ] = undefined;
+					}
+				}
+			}
+		}
+	} );
+
+	jQuery.fn.extend( {
+		detach: function( selector ) {
+			return remove( this, selector, true );
+		},
+
+		remove: function( selector ) {
+			return remove( this, selector );
+		},
+
+		text: function( value ) {
+			return access( this, function( value ) {
+				return value === undefined ?
+					jQuery.text( this ) :
+					this.empty().each( function() {
+						if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) {
+							this.textContent = value;
+						}
+					} );
+			}, null, value, arguments.length );
+		},
+
+		append: function() {
+			return domManip( this, arguments, function( elem ) {
+				if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) {
+					var target = manipulationTarget( this, elem );
+					target.appendChild( elem );
+				}
+			} );
+		},
+
+		prepend: function() {
+			return domManip( this, arguments, function( elem ) {
+				if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) {
+					var target = manipulationTarget( this, elem );
+					target.insertBefore( elem, target.firstChild );
+				}
+			} );
+		},
+
+		before: function() {
+			return domManip( this, arguments, function( elem ) {
+				if ( this.parentNode ) {
+					this.parentNode.insertBefore( elem, this );
+				}
+			} );
+		},
+
+		after: function() {
+			return domManip( this, arguments, function( elem ) {
+				if ( this.parentNode ) {
+					this.parentNode.insertBefore( elem, this.nextSibling );
+				}
+			} );
+		},
+
+		empty: function() {
+			var elem,
+				i = 0;
+
+			for ( ; ( elem = this[ i ] ) != null; i++ ) {
+				if ( elem.nodeType === 1 ) {
+
+					// Prevent memory leaks
+					jQuery.cleanData( getAll( elem, false ) );
+
+					// Remove any remaining nodes
+					elem.textContent = "";
+				}
+			}
+
+			return this;
+		},
+
+		clone: function( dataAndEvents, deepDataAndEvents ) {
+			dataAndEvents = dataAndEvents == null ? false : dataAndEvents;
+			deepDataAndEvents = deepDataAndEvents == null ? dataAndEvents : deepDataAndEvents;
+
+			return this.map( function() {
+				return jQuery.clone( this, dataAndEvents, deepDataAndEvents );
+			} );
+		},
+
+		html: function( value ) {
+			return access( this, function( value ) {
+				var elem = this[ 0 ] || {},
+					i = 0,
+					l = this.length;
+
+				if ( value === undefined && elem.nodeType === 1 ) {
+					return elem.innerHTML;
+				}
+
+				// See if we can take a shortcut and just use innerHTML
+				if ( typeof value === "string" && !rnoInnerhtml.test( value ) &&
+					!wrapMap[ ( rtagName.exec( value ) || [ "", "" ] )[ 1 ].toLowerCase() ] ) {
+
+					value = jQuery.htmlPrefilter( value );
+
+					try {
+						for ( ; i < l; i++ ) {
+							elem = this[ i ] || {};
+
+							// Remove element nodes and prevent memory leaks
+							if ( elem.nodeType === 1 ) {
+								jQuery.cleanData( getAll( elem, false ) );
+								elem.innerHTML = value;
+							}
+						}
+
+						elem = 0;
+
+					// If using innerHTML throws an exception, use the fallback method
+					} catch ( e ) {}
+				}
+
+				if ( elem ) {
+					this.empty().append( value );
+				}
+			}, null, value, arguments.length );
+		},
+
+		replaceWith: function() {
+			var ignored = [];
+
+			// Make the changes, replacing each non-ignored context element with the new content
+			return domManip( this, arguments, function( elem ) {
+				var parent = this.parentNode;
+
+				if ( jQuery.inArray( this, ignored ) < 0 ) {
+					jQuery.cleanData( getAll( this ) );
+					if ( parent ) {
+						parent.replaceChild( elem, this );
+					}
+				}
+
+			// Force callback invocation
+			}, ignored );
+		}
+	} );
+
+	jQuery.each( {
+		appendTo: "append",
+		prependTo: "prepend",
+		insertBefore: "before",
+		insertAfter: "after",
+		replaceAll: "replaceWith"
+	}, function( name, original ) {
+		jQuery.fn[ name ] = function( selector ) {
+			var elems,
+				ret = [],
+				insert = jQuery( selector ),
+				last = insert.length - 1,
+				i = 0;
+
+			for ( ; i <= last; i++ ) {
+				elems = i === last ? this : this.clone( true );
+				jQuery( insert[ i ] )[ original ]( elems );
+
+				// Support: Android <=4.0 only, PhantomJS 1 only
+				// .get() because push.apply(_, arraylike) throws on ancient WebKit
+				push.apply( ret, elems.get() );
+			}
+
+			return this.pushStack( ret );
+		};
+	} );
+	var rmargin = ( /^margin/ );
+
+	var rnumnonpx = new RegExp( "^(" + pnum + ")(?!px)[a-z%]+$", "i" );
+
+	var getStyles = function( elem ) {
+
+			// Support: IE <=11 only, Firefox <=30 (#15098, #14150)
+			// IE throws on elements created in popups
+			// FF meanwhile throws on frame elements through "defaultView.getComputedStyle"
+			var view = elem.ownerDocument.defaultView;
+
+			if ( !view || !view.opener ) {
+				view = window;
+			}
+
+			return view.getComputedStyle( elem );
+		};
+
+
+
+	( function() {
+
+		// Executing both pixelPosition & boxSizingReliable tests require only one layout
+		// so they're executed at the same time to save the second computation.
+		function computeStyleTests() {
+
+			// This is a singleton, we need to execute it only once
+			if ( !div ) {
+				return;
+			}
+
+			div.style.cssText =
+				"box-sizing:border-box;" +
+				"position:relative;display:block;" +
+				"margin:auto;border:1px;padding:1px;" +
+				"top:1%;width:50%";
+			div.innerHTML = "";
+			documentElement.appendChild( container );
+
+			var divStyle = window.getComputedStyle( div );
+			pixelPositionVal = divStyle.top !== "1%";
+
+			// Support: Android 4.0 - 4.3 only, Firefox <=3 - 44
+			reliableMarginLeftVal = divStyle.marginLeft === "2px";
+			boxSizingReliableVal = divStyle.width === "4px";
+
+			// Support: Android 4.0 - 4.3 only
+			// Some styles come back with percentage values, even though they shouldn't
+			div.style.marginRight = "50%";
+			pixelMarginRightVal = divStyle.marginRight === "4px";
+
+			documentElement.removeChild( container );
+
+			// Nullify the div so it wouldn't be stored in the memory and
+			// it will also be a sign that checks already performed
+			div = null;
+		}
+
+		var pixelPositionVal, boxSizingReliableVal, pixelMarginRightVal, reliableMarginLeftVal,
+			container = document.createElement( "div" ),
+			div = document.createElement( "div" );
+
+		// Finish early in limited (non-browser) environments
+		if ( !div.style ) {
+			return;
+		}
+
+		// Support: IE <=9 - 11 only
+		// Style of cloned element affects source element cloned (#8908)
+		div.style.backgroundClip = "content-box";
+		div.cloneNode( true ).style.backgroundClip = "";
+		support.clearCloneStyle = div.style.backgroundClip === "content-box";
+
+		container.style.cssText = "border:0;width:8px;height:0;top:0;left:-9999px;" +
+			"padding:0;margin-top:1px;position:absolute";
+		container.appendChild( div );
+
+		jQuery.extend( support, {
+			pixelPosition: function() {
+				computeStyleTests();
+				return pixelPositionVal;
+			},
+			boxSizingReliable: function() {
+				computeStyleTests();
+				return boxSizingReliableVal;
+			},
+			pixelMarginRight: function() {
+				computeStyleTests();
+				return pixelMarginRightVal;
+			},
+			reliableMarginLeft: function() {
+				computeStyleTests();
+				return reliableMarginLeftVal;
+			}
+		} );
+	} )();
+
+
+	function curCSS( elem, name, computed ) {
+		var width, minWidth, maxWidth, ret,
+			style = elem.style;
+
+		computed = computed || getStyles( elem );
+
+		// Support: IE <=9 only
+		// getPropertyValue is only needed for .css('filter') (#12537)
+		if ( computed ) {
+			ret = computed.getPropertyValue( name ) || computed[ name ];
+
+			if ( ret === "" && !jQuery.contains( elem.ownerDocument, elem ) ) {
+				ret = jQuery.style( elem, name );
+			}
+
+			// A tribute to the "awesome hack by Dean Edwards"
+			// Android Browser returns percentage for some values,
+			// but width seems to be reliably pixels.
+			// This is against the CSSOM draft spec:
+			// https://drafts.csswg.org/cssom/#resolved-values
+			if ( !support.pixelMarginRight() && rnumnonpx.test( ret ) && rmargin.test( name ) ) {
+
+				// Remember the original values
+				width = style.width;
+				minWidth = style.minWidth;
+				maxWidth = style.maxWidth;
+
+				// Put in the new values to get a computed value out
+				style.minWidth = style.maxWidth = style.width = ret;
+				ret = computed.width;
+
+				// Revert the changed values
+				style.width = width;
+				style.minWidth = minWidth;
+				style.maxWidth = maxWidth;
+			}
+		}
+
+		return ret !== undefined ?
+
+			// Support: IE <=9 - 11 only
+			// IE returns zIndex value as an integer.
+			ret + "" :
+			ret;
+	}
+
+
+	function addGetHookIf( conditionFn, hookFn ) {
+
+		// Define the hook, we'll check on the first run if it's really needed.
+		return {
+			get: function() {
+				if ( conditionFn() ) {
+
+					// Hook not needed (or it's not possible to use it due
+					// to missing dependency), remove it.
+					delete this.get;
+					return;
+				}
+
+				// Hook needed; redefine it so that the support test is not executed again.
+				return ( this.get = hookFn ).apply( this, arguments );
+			}
+		};
+	}
+
+
+	var
+
+		// Swappable if display is none or starts with table
+		// except "table", "table-cell", or "table-caption"
+		// See here for display values: https://developer.mozilla.org/en-US/docs/CSS/display
+		rdisplayswap = /^(none|table(?!-c[ea]).+)/,
+		cssShow = { position: "absolute", visibility: "hidden", display: "block" },
+		cssNormalTransform = {
+			letterSpacing: "0",
+			fontWeight: "400"
+		},
+
+		cssPrefixes = [ "Webkit", "Moz", "ms" ],
+		emptyStyle = document.createElement( "div" ).style;
+
+	// Return a css property mapped to a potentially vendor prefixed property
+	function vendorPropName( name ) {
+
+		// Shortcut for names that are not vendor prefixed
+		if ( name in emptyStyle ) {
+			return name;
+		}
+
+		// Check for vendor prefixed names
+		var capName = name[ 0 ].toUpperCase() + name.slice( 1 ),
+			i = cssPrefixes.length;
+
+		while ( i-- ) {
+			name = cssPrefixes[ i ] + capName;
+			if ( name in emptyStyle ) {
+				return name;
+			}
+		}
+	}
+
+	function setPositiveNumber( elem, value, subtract ) {
+
+		// Any relative (+/-) values have already been
+		// normalized at this point
+		var matches = rcssNum.exec( value );
+		return matches ?
+
+			// Guard against undefined "subtract", e.g., when used as in cssHooks
+			Math.max( 0, matches[ 2 ] - ( subtract || 0 ) ) + ( matches[ 3 ] || "px" ) :
+			value;
+	}
+
+	function augmentWidthOrHeight( elem, name, extra, isBorderBox, styles ) {
+		var i = extra === ( isBorderBox ? "border" : "content" ) ?
+
+			// If we already have the right measurement, avoid augmentation
+			4 :
+
+			// Otherwise initialize for horizontal or vertical properties
+			name === "width" ? 1 : 0,
+
+			val = 0;
+
+		for ( ; i < 4; i += 2 ) {
+
+			// Both box models exclude margin, so add it if we want it
+			if ( extra === "margin" ) {
+				val += jQuery.css( elem, extra + cssExpand[ i ], true, styles );
+			}
+
+			if ( isBorderBox ) {
+
+				// border-box includes padding, so remove it if we want content
+				if ( extra === "content" ) {
+					val -= jQuery.css( elem, "padding" + cssExpand[ i ], true, styles );
+				}
+
+				// At this point, extra isn't border nor margin, so remove border
+				if ( extra !== "margin" ) {
+					val -= jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles );
+				}
+			} else {
+
+				// At this point, extra isn't content, so add padding
+				val += jQuery.css( elem, "padding" + cssExpand[ i ], true, styles );
+
+				// At this point, extra isn't content nor padding, so add border
+				if ( extra !== "padding" ) {
+					val += jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles );
+				}
+			}
+		}
+
+		return val;
+	}
+
+	function getWidthOrHeight( elem, name, extra ) {
+
+		// Start with offset property, which is equivalent to the border-box value
+		var val,
+			valueIsBorderBox = true,
+			styles = getStyles( elem ),
+			isBorderBox = jQuery.css( elem, "boxSizing", false, styles ) === "border-box";
+
+		// Support: IE <=11 only
+		// Running getBoundingClientRect on a disconnected node
+		// in IE throws an error.
+		if ( elem.getClientRects().length ) {
+			val = elem.getBoundingClientRect()[ name ];
+		}
+
+		// Some non-html elements return undefined for offsetWidth, so check for null/undefined
+		// svg - https://bugzilla.mozilla.org/show_bug.cgi?id=649285
+		// MathML - https://bugzilla.mozilla.org/show_bug.cgi?id=491668
+		if ( val <= 0 || val == null ) {
+
+			// Fall back to computed then uncomputed css if necessary
+			val = curCSS( elem, name, styles );
+			if ( val < 0 || val == null ) {
+				val = elem.style[ name ];
+			}
+
+			// Computed unit is not pixels. Stop here and return.
+			if ( rnumnonpx.test( val ) ) {
+				return val;
+			}
+
+			// Check for style in case a browser which returns unreliable values
+			// for getComputedStyle silently falls back to the reliable elem.style
+			valueIsBorderBox = isBorderBox &&
+				( support.boxSizingReliable() || val === elem.style[ name ] );
+
+			// Normalize "", auto, and prepare for extra
+			val = parseFloat( val ) || 0;
+		}
+
+		// Use the active box-sizing model to add/subtract irrelevant styles
+		return ( val +
+			augmentWidthOrHeight(
+				elem,
+				name,
+				extra || ( isBorderBox ? "border" : "content" ),
+				valueIsBorderBox,
+				styles
+			)
+		) + "px";
+	}
+
+	jQuery.extend( {
+
+		// Add in style property hooks for overriding the default
+		// behavior of getting and setting a style property
+		cssHooks: {
+			opacity: {
+				get: function( elem, computed ) {
+					if ( computed ) {
+
+						// We should always get a number back from opacity
+						var ret = curCSS( elem, "opacity" );
+						return ret === "" ? "1" : ret;
+					}
+				}
+			}
+		},
+
+		// Don't automatically add "px" to these possibly-unitless properties
+		cssNumber: {
+			"animationIterationCount": true,
+			"columnCount": true,
+			"fillOpacity": true,
+			"flexGrow": true,
+			"flexShrink": true,
+			"fontWeight": true,
+			"lineHeight": true,
+			"opacity": true,
+			"order": true,
+			"orphans": true,
+			"widows": true,
+			"zIndex": true,
+			"zoom": true
+		},
+
+		// Add in properties whose names you wish to fix before
+		// setting or getting the value
+		cssProps: {
+			"float": "cssFloat"
+		},
+
+		// Get and set the style property on a DOM Node
+		style: function( elem, name, value, extra ) {
+
+			// Don't set styles on text and comment nodes
+			if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 || !elem.style ) {
+				return;
+			}
+
+			// Make sure that we're working with the right name
+			var ret, type, hooks,
+				origName = jQuery.camelCase( name ),
+				style = elem.style;
+
+			name = jQuery.cssProps[ origName ] ||
+				( jQuery.cssProps[ origName ] = vendorPropName( origName ) || origName );
+
+			// Gets hook for the prefixed version, then unprefixed version
+			hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ];
+
+			// Check if we're setting a value
+			if ( value !== undefined ) {
+				type = typeof value;
+
+				// Convert "+=" or "-=" to relative numbers (#7345)
+				if ( type === "string" && ( ret = rcssNum.exec( value ) ) && ret[ 1 ] ) {
+					value = adjustCSS( elem, name, ret );
+
+					// Fixes bug #9237
+					type = "number";
+				}
+
+				// Make sure that null and NaN values aren't set (#7116)
+				if ( value == null || value !== value ) {
+					return;
+				}
+
+				// If a number was passed in, add the unit (except for certain CSS properties)
+				if ( type === "number" ) {
+					value += ret && ret[ 3 ] || ( jQuery.cssNumber[ origName ] ? "" : "px" );
+				}
+
+				// background-* props affect original clone's values
+				if ( !support.clearCloneStyle && value === "" && name.indexOf( "background" ) === 0 ) {
+					style[ name ] = "inherit";
+				}
+
+				// If a hook was provided, use that value, otherwise just set the specified value
+				if ( !hooks || !( "set" in hooks ) ||
+					( value = hooks.set( elem, value, extra ) ) !== undefined ) {
+
+					style[ name ] = value;
+				}
+
+			} else {
+
+				// If a hook was provided get the non-computed value from there
+				if ( hooks && "get" in hooks &&
+					( ret = hooks.get( elem, false, extra ) ) !== undefined ) {
+
+					return ret;
+				}
+
+				// Otherwise just get the value from the style object
+				return style[ name ];
+			}
+		},
+
+		css: function( elem, name, extra, styles ) {
+			var val, num, hooks,
+				origName = jQuery.camelCase( name );
+
+			// Make sure that we're working with the right name
+			name = jQuery.cssProps[ origName ] ||
+				( jQuery.cssProps[ origName ] = vendorPropName( origName ) || origName );
+
+			// Try prefixed name followed by the unprefixed name
+			hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ];
+
+			// If a hook was provided get the computed value from there
+			if ( hooks && "get" in hooks ) {
+				val = hooks.get( elem, true, extra );
+			}
+
+			// Otherwise, if a way to get the computed value exists, use that
+			if ( val === undefined ) {
+				val = curCSS( elem, name, styles );
+			}
+
+			// Convert "normal" to computed value
+			if ( val === "normal" && name in cssNormalTransform ) {
+				val = cssNormalTransform[ name ];
+			}
+
+			// Make numeric if forced or a qualifier was provided and val looks numeric
+			if ( extra === "" || extra ) {
+				num = parseFloat( val );
+				return extra === true || isFinite( num ) ? num || 0 : val;
+			}
+			return val;
+		}
+	} );
+
+	jQuery.each( [ "height", "width" ], function( i, name ) {
+		jQuery.cssHooks[ name ] = {
+			get: function( elem, computed, extra ) {
+				if ( computed ) {
+
+					// Certain elements can have dimension info if we invisibly show them
+					// but it must have a current display style that would benefit
+					return rdisplayswap.test( jQuery.css( elem, "display" ) ) &&
+
+						// Support: Safari 8+
+						// Table columns in Safari have non-zero offsetWidth & zero
+						// getBoundingClientRect().width unless display is changed.
+						// Support: IE <=11 only
+						// Running getBoundingClientRect on a disconnected node
+						// in IE throws an error.
+						( !elem.getClientRects().length || !elem.getBoundingClientRect().width ) ?
+							swap( elem, cssShow, function() {
+								return getWidthOrHeight( elem, name, extra );
+							} ) :
+							getWidthOrHeight( elem, name, extra );
+				}
+			},
+
+			set: function( elem, value, extra ) {
+				var matches,
+					styles = extra && getStyles( elem ),
+					subtract = extra && augmentWidthOrHeight(
+						elem,
+						name,
+						extra,
+						jQuery.css( elem, "boxSizing", false, styles ) === "border-box",
+						styles
+					);
+
+				// Convert to pixels if value adjustment is needed
+				if ( subtract && ( matches = rcssNum.exec( value ) ) &&
+					( matches[ 3 ] || "px" ) !== "px" ) {
+
+					elem.style[ name ] = value;
+					value = jQuery.css( elem, name );
+				}
+
+				return setPositiveNumber( elem, value, subtract );
+			}
+		};
+	} );
+
+	jQuery.cssHooks.marginLeft = addGetHookIf( support.reliableMarginLeft,
+		function( elem, computed ) {
+			if ( computed ) {
+				return ( parseFloat( curCSS( elem, "marginLeft" ) ) ||
+					elem.getBoundingClientRect().left -
+						swap( elem, { marginLeft: 0 }, function() {
+							return elem.getBoundingClientRect().left;
+						} )
+					) + "px";
+			}
+		}
+	);
+
+	// These hooks are used by animate to expand properties
+	jQuery.each( {
+		margin: "",
+		padding: "",
+		border: "Width"
+	}, function( prefix, suffix ) {
+		jQuery.cssHooks[ prefix + suffix ] = {
+			expand: function( value ) {
+				var i = 0,
+					expanded = {},
+
+					// Assumes a single number if not a string
+					parts = typeof value === "string" ? value.split( " " ) : [ value ];
+
+				for ( ; i < 4; i++ ) {
+					expanded[ prefix + cssExpand[ i ] + suffix ] =
+						parts[ i ] || parts[ i - 2 ] || parts[ 0 ];
+				}
+
+				return expanded;
+			}
+		};
+
+		if ( !rmargin.test( prefix ) ) {
+			jQuery.cssHooks[ prefix + suffix ].set = setPositiveNumber;
+		}
+	} );
+
+	jQuery.fn.extend( {
+		css: function( name, value ) {
+			return access( this, function( elem, name, value ) {
+				var styles, len,
+					map = {},
+					i = 0;
+
+				if ( jQuery.isArray( name ) ) {
+					styles = getStyles( elem );
+					len = name.length;
+
+					for ( ; i < len; i++ ) {
+						map[ name[ i ] ] = jQuery.css( elem, name[ i ], false, styles );
+					}
+
+					return map;
+				}
+
+				return value !== undefined ?
+					jQuery.style( elem, name, value ) :
+					jQuery.css( elem, name );
+			}, name, value, arguments.length > 1 );
+		}
+	} );
+
+
+	function Tween( elem, options, prop, end, easing ) {
+		return new Tween.prototype.init( elem, options, prop, end, easing );
+	}
+	jQuery.Tween = Tween;
+
+	Tween.prototype = {
+		constructor: Tween,
+		init: function( elem, options, prop, end, easing, unit ) {
+			this.elem = elem;
+			this.prop = prop;
+			this.easing = easing || jQuery.easing._default;
+			this.options = options;
+			this.start = this.now = this.cur();
+			this.end = end;
+			this.unit = unit || ( jQuery.cssNumber[ prop ] ? "" : "px" );
+		},
+		cur: function() {
+			var hooks = Tween.propHooks[ this.prop ];
+
+			return hooks && hooks.get ?
+				hooks.get( this ) :
+				Tween.propHooks._default.get( this );
+		},
+		run: function( percent ) {
+			var eased,
+				hooks = Tween.propHooks[ this.prop ];
+
+			if ( this.options.duration ) {
+				this.pos = eased = jQuery.easing[ this.easing ](
+					percent, this.options.duration * percent, 0, 1, this.options.duration
+				);
+			} else {
+				this.pos = eased = percent;
+			}
+			this.now = ( this.end - this.start ) * eased + this.start;
+
+			if ( this.options.step ) {
+				this.options.step.call( this.elem, this.now, this );
+			}
+
+			if ( hooks && hooks.set ) {
+				hooks.set( this );
+			} else {
+				Tween.propHooks._default.set( this );
+			}
+			return this;
+		}
+	};
+
+	Tween.prototype.init.prototype = Tween.prototype;
+
+	Tween.propHooks = {
+		_default: {
+			get: function( tween ) {
+				var result;
+
+				// Use a property on the element directly when it is not a DOM element,
+				// or when there is no matching style property that exists.
+				if ( tween.elem.nodeType !== 1 ||
+					tween.elem[ tween.prop ] != null && tween.elem.style[ tween.prop ] == null ) {
+					return tween.elem[ tween.prop ];
+				}
+
+				// Passing an empty string as a 3rd parameter to .css will automatically
+				// attempt a parseFloat and fallback to a string if the parse fails.
+				// Simple values such as "10px" are parsed to Float;
+				// complex values such as "rotate(1rad)" are returned as-is.
+				result = jQuery.css( tween.elem, tween.prop, "" );
+
+				// Empty strings, null, undefined and "auto" are converted to 0.
+				return !result || result === "auto" ? 0 : result;
+			},
+			set: function( tween ) {
+
+				// Use step hook for back compat.
+				// Use cssHook if its there.
+				// Use .style if available and use plain properties where available.
+				if ( jQuery.fx.step[ tween.prop ] ) {
+					jQuery.fx.step[ tween.prop ]( tween );
+				} else if ( tween.elem.nodeType === 1 &&
+					( tween.elem.style[ jQuery.cssProps[ tween.prop ] ] != null ||
+						jQuery.cssHooks[ tween.prop ] ) ) {
+					jQuery.style( tween.elem, tween.prop, tween.now + tween.unit );
+				} else {
+					tween.elem[ tween.prop ] = tween.now;
+				}
+			}
+		}
+	};
+
+	// Support: IE <=9 only
+	// Panic based approach to setting things on disconnected nodes
+	Tween.propHooks.scrollTop = Tween.propHooks.scrollLeft = {
+		set: function( tween ) {
+			if ( tween.elem.nodeType && tween.elem.parentNode ) {
+				tween.elem[ tween.prop ] = tween.now;
+			}
+		}
+	};
+
+	jQuery.easing = {
+		linear: function( p ) {
+			return p;
+		},
+		swing: function( p ) {
+			return 0.5 - Math.cos( p * Math.PI ) / 2;
+		},
+		_default: "swing"
+	};
+
+	jQuery.fx = Tween.prototype.init;
+
+	// Back compat <1.8 extension point
+	jQuery.fx.step = {};
+
+
+
+
+	var
+		fxNow, timerId,
+		rfxtypes = /^(?:toggle|show|hide)$/,
+		rrun = /queueHooks$/;
+
+	function raf() {
+		if ( timerId ) {
+			window.requestAnimationFrame( raf );
+			jQuery.fx.tick();
+		}
+	}
+
+	// Animations created synchronously will run synchronously
+	function createFxNow() {
+		window.setTimeout( function() {
+			fxNow = undefined;
+		} );
+		return ( fxNow = jQuery.now() );
+	}
+
+	// Generate parameters to create a standard animation
+	function genFx( type, includeWidth ) {
+		var which,
+			i = 0,
+			attrs = { height: type };
+
+		// If we include width, step value is 1 to do all cssExpand values,
+		// otherwise step value is 2 to skip over Left and Right
+		includeWidth = includeWidth ? 1 : 0;
+		for ( ; i < 4; i += 2 - includeWidth ) {
+			which = cssExpand[ i ];
+			attrs[ "margin" + which ] = attrs[ "padding" + which ] = type;
+		}
+
+		if ( includeWidth ) {
+			attrs.opacity = attrs.width = type;
+		}
+
+		return attrs;
+	}
+
+	function createTween( value, prop, animation ) {
+		var tween,
+			collection = ( Animation.tweeners[ prop ] || [] ).concat( Animation.tweeners[ "*" ] ),
+			index = 0,
+			length = collection.length;
+		for ( ; index < length; index++ ) {
+			if ( ( tween = collection[ index ].call( animation, prop, value ) ) ) {
+
+				// We're done with this property
+				return tween;
+			}
+		}
+	}
+
+	function defaultPrefilter( elem, props, opts ) {
+		var prop, value, toggle, hooks, oldfire, propTween, restoreDisplay, display,
+			isBox = "width" in props || "height" in props,
+			anim = this,
+			orig = {},
+			style = elem.style,
+			hidden = elem.nodeType && isHiddenWithinTree( elem ),
+			dataShow = dataPriv.get( elem, "fxshow" );
+
+		// Queue-skipping animations hijack the fx hooks
+		if ( !opts.queue ) {
+			hooks = jQuery._queueHooks( elem, "fx" );
+			if ( hooks.unqueued == null ) {
+				hooks.unqueued = 0;
+				oldfire = hooks.empty.fire;
+				hooks.empty.fire = function() {
+					if ( !hooks.unqueued ) {
+						oldfire();
+					}
+				};
+			}
+			hooks.unqueued++;
+
+			anim.always( function() {
+
+				// Ensure the complete handler is called before this completes
+				anim.always( function() {
+					hooks.unqueued--;
+					if ( !jQuery.queue( elem, "fx" ).length ) {
+						hooks.empty.fire();
+					}
+				} );
+			} );
+		}
+
+		// Detect show/hide animations
+		for ( prop in props ) {
+			value = props[ prop ];
+			if ( rfxtypes.test( value ) ) {
+				delete props[ prop ];
+				toggle = toggle || value === "toggle";
+				if ( value === ( hidden ? "hide" : "show" ) ) {
+
+					// Pretend to be hidden if this is a "show" and
+					// there is still data from a stopped show/hide
+					if ( value === "show" && dataShow && dataShow[ prop ] !== undefined ) {
+						hidden = true;
+
+					// Ignore all other no-op show/hide data
+					} else {
+						continue;
+					}
+				}
+				orig[ prop ] = dataShow && dataShow[ prop ] || jQuery.style( elem, prop );
+			}
+		}
+
+		// Bail out if this is a no-op like .hide().hide()
+		propTween = !jQuery.isEmptyObject( props );
+		if ( !propTween && jQuery.isEmptyObject( orig ) ) {
+			return;
+		}
+
+		// Restrict "overflow" and "display" styles during box animations
+		if ( isBox && elem.nodeType === 1 ) {
+
+			// Support: IE <=9 - 11, Edge 12 - 13
+			// Record all 3 overflow attributes because IE does not infer the shorthand
+			// from identically-valued overflowX and overflowY
+			opts.overflow = [ style.overflow, style.overflowX, style.overflowY ];
+
+			// Identify a display type, preferring old show/hide data over the CSS cascade
+			restoreDisplay = dataShow && dataShow.display;
+			if ( restoreDisplay == null ) {
+				restoreDisplay = dataPriv.get( elem, "display" );
+			}
+			display = jQuery.css( elem, "display" );
+			if ( display === "none" ) {
+				if ( restoreDisplay ) {
+					display = restoreDisplay;
+				} else {
+
+					// Get nonempty value(s) by temporarily forcing visibility
+					showHide( [ elem ], true );
+					restoreDisplay = elem.style.display || restoreDisplay;
+					display = jQuery.css( elem, "display" );
+					showHide( [ elem ] );
+				}
+			}
+
+			// Animate inline elements as inline-block
+			if ( display === "inline" || display === "inline-block" && restoreDisplay != null ) {
+				if ( jQuery.css( elem, "float" ) === "none" ) {
+
+					// Restore the original display value at the end of pure show/hide animations
+					if ( !propTween ) {
+						anim.done( function() {
+							style.display = restoreDisplay;
+						} );
+						if ( restoreDisplay == null ) {
+							display = style.display;
+							restoreDisplay = display === "none" ? "" : display;
+						}
+					}
+					style.display = "inline-block";
+				}
+			}
+		}
+
+		if ( opts.overflow ) {
+			style.overflow = "hidden";
+			anim.always( function() {
+				style.overflow = opts.overflow[ 0 ];
+				style.overflowX = opts.overflow[ 1 ];
+				style.overflowY = opts.overflow[ 2 ];
+			} );
+		}
+
+		// Implement show/hide animations
+		propTween = false;
+		for ( prop in orig ) {
+
+			// General show/hide setup for this element animation
+			if ( !propTween ) {
+				if ( dataShow ) {
+					if ( "hidden" in dataShow ) {
+						hidden = dataShow.hidden;
+					}
+				} else {
+					dataShow = dataPriv.access( elem, "fxshow", { display: restoreDisplay } );
+				}
+
+				// Store hidden/visible for toggle so `.stop().toggle()` "reverses"
+				if ( toggle ) {
+					dataShow.hidden = !hidden;
+				}
+
+				// Show elements before animating them
+				if ( hidden ) {
+					showHide( [ elem ], true );
+				}
+
+				/* eslint-disable no-loop-func */
+
+				anim.done( function() {
+
+				/* eslint-enable no-loop-func */
+
+					// The final step of a "hide" animation is actually hiding the element
+					if ( !hidden ) {
+						showHide( [ elem ] );
+					}
+					dataPriv.remove( elem, "fxshow" );
+					for ( prop in orig ) {
+						jQuery.style( elem, prop, orig[ prop ] );
+					}
+				} );
+			}
+
+			// Per-property setup
+			propTween = createTween( hidden ? dataShow[ prop ] : 0, prop, anim );
+			if ( !( prop in dataShow ) ) {
+				dataShow[ prop ] = propTween.start;
+				if ( hidden ) {
+					propTween.end = propTween.start;
+					propTween.start = 0;
+				}
+			}
+		}
+	}
+
+	function propFilter( props, specialEasing ) {
+		var index, name, easing, value, hooks;
+
+		// camelCase, specialEasing and expand cssHook pass
+		for ( index in props ) {
+			name = jQuery.camelCase( index );
+			easing = specialEasing[ name ];
+			value = props[ index ];
+			if ( jQuery.isArray( value ) ) {
+				easing = value[ 1 ];
+				value = props[ index ] = value[ 0 ];
+			}
+
+			if ( index !== name ) {
+				props[ name ] = value;
+				delete props[ index ];
+			}
+
+			hooks = jQuery.cssHooks[ name ];
+			if ( hooks && "expand" in hooks ) {
+				value = hooks.expand( value );
+				delete props[ name ];
+
+				// Not quite $.extend, this won't overwrite existing keys.
+				// Reusing 'index' because we have the correct "name"
+				for ( index in value ) {
+					if ( !( index in props ) ) {
+						props[ index ] = value[ index ];
+						specialEasing[ index ] = easing;
+					}
+				}
+			} else {
+				specialEasing[ name ] = easing;
+			}
+		}
+	}
+
+	function Animation( elem, properties, options ) {
+		var result,
+			stopped,
+			index = 0,
+			length = Animation.prefilters.length,
+			deferred = jQuery.Deferred().always( function() {
+
+				// Don't match elem in the :animated selector
+				delete tick.elem;
+			} ),
+			tick = function() {
+				if ( stopped ) {
+					return false;
+				}
+				var currentTime = fxNow || createFxNow(),
+					remaining = Math.max( 0, animation.startTime + animation.duration - currentTime ),
+
+					// Support: Android 2.3 only
+					// Archaic crash bug won't allow us to use `1 - ( 0.5 || 0 )` (#12497)
+					temp = remaining / animation.duration || 0,
+					percent = 1 - temp,
+					index = 0,
+					length = animation.tweens.length;
+
+				for ( ; index < length; index++ ) {
+					animation.tweens[ index ].run( percent );
+				}
+
+				deferred.notifyWith( elem, [ animation, percent, remaining ] );
+
+				if ( percent < 1 && length ) {
+					return remaining;
+				} else {
+					deferred.resolveWith( elem, [ animation ] );
+					return false;
+				}
+			},
+			animation = deferred.promise( {
+				elem: elem,
+				props: jQuery.extend( {}, properties ),
+				opts: jQuery.extend( true, {
+					specialEasing: {},
+					easing: jQuery.easing._default
+				}, options ),
+				originalProperties: properties,
+				originalOptions: options,
+				startTime: fxNow || createFxNow(),
+				duration: options.duration,
+				tweens: [],
+				createTween: function( prop, end ) {
+					var tween = jQuery.Tween( elem, animation.opts, prop, end,
+							animation.opts.specialEasing[ prop ] || animation.opts.easing );
+					animation.tweens.push( tween );
+					return tween;
+				},
+				stop: function( gotoEnd ) {
+					var index = 0,
+
+						// If we are going to the end, we want to run all the tweens
+						// otherwise we skip this part
+						length = gotoEnd ? animation.tweens.length : 0;
+					if ( stopped ) {
+						return this;
+					}
+					stopped = true;
+					for ( ; index < length; index++ ) {
+						animation.tweens[ index ].run( 1 );
+					}
+
+					// Resolve when we played the last frame; otherwise, reject
+					if ( gotoEnd ) {
+						deferred.notifyWith( elem, [ animation, 1, 0 ] );
+						deferred.resolveWith( elem, [ animation, gotoEnd ] );
+					} else {
+						deferred.rejectWith( elem, [ animation, gotoEnd ] );
+					}
+					return this;
+				}
+			} ),
+			props = animation.props;
+
+		propFilter( props, animation.opts.specialEasing );
+
+		for ( ; index < length; index++ ) {
+			result = Animation.prefilters[ index ].call( animation, elem, props, animation.opts );
+			if ( result ) {
+				if ( jQuery.isFunction( result.stop ) ) {
+					jQuery._queueHooks( animation.elem, animation.opts.queue ).stop =
+						jQuery.proxy( result.stop, result );
+				}
+				return result;
+			}
+		}
+
+		jQuery.map( props, createTween, animation );
+
+		if ( jQuery.isFunction( animation.opts.start ) ) {
+			animation.opts.start.call( elem, animation );
+		}
+
+		jQuery.fx.timer(
+			jQuery.extend( tick, {
+				elem: elem,
+				anim: animation,
+				queue: animation.opts.queue
+			} )
+		);
+
+		// attach callbacks from options
+		return animation.progress( animation.opts.progress )
+			.done( animation.opts.done, animation.opts.complete )
+			.fail( animation.opts.fail )
+			.always( animation.opts.always );
+	}
+
+	jQuery.Animation = jQuery.extend( Animation, {
+
+		tweeners: {
+			"*": [ function( prop, value ) {
+				var tween = this.createTween( prop, value );
+				adjustCSS( tween.elem, prop, rcssNum.exec( value ), tween );
+				return tween;
+			} ]
+		},
+
+		tweener: function( props, callback ) {
+			if ( jQuery.isFunction( props ) ) {
+				callback = props;
+				props = [ "*" ];
+			} else {
+				props = props.match( rnotwhite );
+			}
+
+			var prop,
+				index = 0,
+				length = props.length;
+
+			for ( ; index < length; index++ ) {
+				prop = props[ index ];
+				Animation.tweeners[ prop ] = Animation.tweeners[ prop ] || [];
+				Animation.tweeners[ prop ].unshift( callback );
+			}
+		},
+
+		prefilters: [ defaultPrefilter ],
+
+		prefilter: function( callback, prepend ) {
+			if ( prepend ) {
+				Animation.prefilters.unshift( callback );
+			} else {
+				Animation.prefilters.push( callback );
+			}
+		}
+	} );
+
+	jQuery.speed = function( speed, easing, fn ) {
+		var opt = speed && typeof speed === "object" ? jQuery.extend( {}, speed ) : {
+			complete: fn || !fn && easing ||
+				jQuery.isFunction( speed ) && speed,
+			duration: speed,
+			easing: fn && easing || easing && !jQuery.isFunction( easing ) && easing
+		};
+
+		// Go to the end state if fx are off or if document is hidden
+		if ( jQuery.fx.off || document.hidden ) {
+			opt.duration = 0;
+
+		} else {
+			opt.duration = typeof opt.duration === "number" ?
+				opt.duration : opt.duration in jQuery.fx.speeds ?
+					jQuery.fx.speeds[ opt.duration ] : jQuery.fx.speeds._default;
+		}
+
+		// Normalize opt.queue - true/undefined/null -> "fx"
+		if ( opt.queue == null || opt.queue === true ) {
+			opt.queue = "fx";
+		}
+
+		// Queueing
+		opt.old = opt.complete;
+
+		opt.complete = function() {
+			if ( jQuery.isFunction( opt.old ) ) {
+				opt.old.call( this );
+			}
+
+			if ( opt.queue ) {
+				jQuery.dequeue( this, opt.queue );
+			}
+		};
+
+		return opt;
+	};
+
+	jQuery.fn.extend( {
+		fadeTo: function( speed, to, easing, callback ) {
+
+			// Show any hidden elements after setting opacity to 0
+			return this.filter( isHiddenWithinTree ).css( "opacity", 0 ).show()
+
+				// Animate to the value specified
+				.end().animate( { opacity: to }, speed, easing, callback );
+		},
+		animate: function( prop, speed, easing, callback ) {
+			var empty = jQuery.isEmptyObject( prop ),
+				optall = jQuery.speed( speed, easing, callback ),
+				doAnimation = function() {
+
+					// Operate on a copy of prop so per-property easing won't be lost
+					var anim = Animation( this, jQuery.extend( {}, prop ), optall );
+
+					// Empty animations, or finishing resolves immediately
+					if ( empty || dataPriv.get( this, "finish" ) ) {
+						anim.stop( true );
+					}
+				};
+				doAnimation.finish = doAnimation;
+
+			return empty || optall.queue === false ?
+				this.each( doAnimation ) :
+				this.queue( optall.queue, doAnimation );
+		},
+		stop: function( type, clearQueue, gotoEnd ) {
+			var stopQueue = function( hooks ) {
+				var stop = hooks.stop;
+				delete hooks.stop;
+				stop( gotoEnd );
+			};
+
+			if ( typeof type !== "string" ) {
+				gotoEnd = clearQueue;
+				clearQueue = type;
+				type = undefined;
+			}
+			if ( clearQueue && type !== false ) {
+				this.queue( type || "fx", [] );
+			}
+
+			return this.each( function() {
+				var dequeue = true,
+					index = type != null && type + "queueHooks",
+					timers = jQuery.timers,
+					data = dataPriv.get( this );
+
+				if ( index ) {
+					if ( data[ index ] && data[ index ].stop ) {
+						stopQueue( data[ index ] );
+					}
+				} else {
+					for ( index in data ) {
+						if ( data[ index ] && data[ index ].stop && rrun.test( index ) ) {
+							stopQueue( data[ index ] );
+						}
+					}
+				}
+
+				for ( index = timers.length; index--; ) {
+					if ( timers[ index ].elem === this &&
+						( type == null || timers[ index ].queue === type ) ) {
+
+						timers[ index ].anim.stop( gotoEnd );
+						dequeue = false;
+						timers.splice( index, 1 );
+					}
+				}
+
+				// Start the next in the queue if the last step wasn't forced.
+				// Timers currently will call their complete callbacks, which
+				// will dequeue but only if they were gotoEnd.
+				if ( dequeue || !gotoEnd ) {
+					jQuery.dequeue( this, type );
+				}
+			} );
+		},
+		finish: function( type ) {
+			if ( type !== false ) {
+				type = type || "fx";
+			}
+			return this.each( function() {
+				var index,
+					data = dataPriv.get( this ),
+					queue = data[ type + "queue" ],
+					hooks = data[ type + "queueHooks" ],
+					timers = jQuery.timers,
+					length = queue ? queue.length : 0;
+
+				// Enable finishing flag on private data
+				data.finish = true;
+
+				// Empty the queue first
+				jQuery.queue( this, type, [] );
+
+				if ( hooks && hooks.stop ) {
+					hooks.stop.call( this, true );
+				}
+
+				// Look for any active animations, and finish them
+				for ( index = timers.length; index--; ) {
+					if ( timers[ index ].elem === this && timers[ index ].queue === type ) {
+						timers[ index ].anim.stop( true );
+						timers.splice( index, 1 );
+					}
+				}
+
+				// Look for any animations in the old queue and finish them
+				for ( index = 0; index < length; index++ ) {
+					if ( queue[ index ] && queue[ index ].finish ) {
+						queue[ index ].finish.call( this );
+					}
+				}
+
+				// Turn off finishing flag
+				delete data.finish;
+			} );
+		}
+	} );
+
+	jQuery.each( [ "toggle", "show", "hide" ], function( i, name ) {
+		var cssFn = jQuery.fn[ name ];
+		jQuery.fn[ name ] = function( speed, easing, callback ) {
+			return speed == null || typeof speed === "boolean" ?
+				cssFn.apply( this, arguments ) :
+				this.animate( genFx( name, true ), speed, easing, callback );
+		};
+	} );
+
+	// Generate shortcuts for custom animations
+	jQuery.each( {
+		slideDown: genFx( "show" ),
+		slideUp: genFx( "hide" ),
+		slideToggle: genFx( "toggle" ),
+		fadeIn: { opacity: "show" },
+		fadeOut: { opacity: "hide" },
+		fadeToggle: { opacity: "toggle" }
+	}, function( name, props ) {
+		jQuery.fn[ name ] = function( speed, easing, callback ) {
+			return this.animate( props, speed, easing, callback );
+		};
+	} );
+
+	jQuery.timers = [];
+	jQuery.fx.tick = function() {
+		var timer,
+			i = 0,
+			timers = jQuery.timers;
+
+		fxNow = jQuery.now();
+
+		for ( ; i < timers.length; i++ ) {
+			timer = timers[ i ];
+
+			// Checks the timer has not already been removed
+			if ( !timer() && timers[ i ] === timer ) {
+				timers.splice( i--, 1 );
+			}
+		}
+
+		if ( !timers.length ) {
+			jQuery.fx.stop();
+		}
+		fxNow = undefined;
+	};
+
+	jQuery.fx.timer = function( timer ) {
+		jQuery.timers.push( timer );
+		if ( timer() ) {
+			jQuery.fx.start();
+		} else {
+			jQuery.timers.pop();
+		}
+	};
+
+	jQuery.fx.interval = 13;
+	jQuery.fx.start = function() {
+		if ( !timerId ) {
+			timerId = window.requestAnimationFrame ?
+				window.requestAnimationFrame( raf ) :
+				window.setInterval( jQuery.fx.tick, jQuery.fx.interval );
+		}
+	};
+
+	jQuery.fx.stop = function() {
+		if ( window.cancelAnimationFrame ) {
+			window.cancelAnimationFrame( timerId );
+		} else {
+			window.clearInterval( timerId );
+		}
+
+		timerId = null;
+	};
+
+	jQuery.fx.speeds = {
+		slow: 600,
+		fast: 200,
+
+		// Default speed
+		_default: 400
+	};
+
+
+	// Based off of the plugin by Clint Helfers, with permission.
+	// https://web.archive.org/web/20100324014747/http://blindsignals.com/index.php/2009/07/jquery-delay/
+	jQuery.fn.delay = function( time, type ) {
+		time = jQuery.fx ? jQuery.fx.speeds[ time ] || time : time;
+		type = type || "fx";
+
+		return this.queue( type, function( next, hooks ) {
+			var timeout = window.setTimeout( next, time );
+			hooks.stop = function() {
+				window.clearTimeout( timeout );
+			};
+		} );
+	};
+
+
+	( function() {
+		var input = document.createElement( "input" ),
+			select = document.createElement( "select" ),
+			opt = select.appendChild( document.createElement( "option" ) );
+
+		input.type = "checkbox";
+
+		// Support: Android <=4.3 only
+		// Default value for a checkbox should be "on"
+		support.checkOn = input.value !== "";
+
+		// Support: IE <=11 only
+		// Must access selectedIndex to make default options select
+		support.optSelected = opt.selected;
+
+		// Support: IE <=11 only
+		// An input loses its value after becoming a radio
+		input = document.createElement( "input" );
+		input.value = "t";
+		input.type = "radio";
+		support.radioValue = input.value === "t";
+	} )();
+
+
+	var boolHook,
+		attrHandle = jQuery.expr.attrHandle;
+
+	jQuery.fn.extend( {
+		attr: function( name, value ) {
+			return access( this, jQuery.attr, name, value, arguments.length > 1 );
+		},
+
+		removeAttr: function( name ) {
+			return this.each( function() {
+				jQuery.removeAttr( this, name );
+			} );
+		}
+	} );
+
+	jQuery.extend( {
+		attr: function( elem, name, value ) {
+			var ret, hooks,
+				nType = elem.nodeType;
+
+			// Don't get/set attributes on text, comment and attribute nodes
+			if ( nType === 3 || nType === 8 || nType === 2 ) {
+				return;
+			}
+
+			// Fallback to prop when attributes are not supported
+			if ( typeof elem.getAttribute === "undefined" ) {
+				return jQuery.prop( elem, name, value );
+			}
+
+			// Attribute hooks are determined by the lowercase version
+			// Grab necessary hook if one is defined
+			if ( nType !== 1 || !jQuery.isXMLDoc( elem ) ) {
+				hooks = jQuery.attrHooks[ name.toLowerCase() ] ||
+					( jQuery.expr.match.bool.test( name ) ? boolHook : undefined );
+			}
+
+			if ( value !== undefined ) {
+				if ( value === null ) {
+					jQuery.removeAttr( elem, name );
+					return;
+				}
+
+				if ( hooks && "set" in hooks &&
+					( ret = hooks.set( elem, value, name ) ) !== undefined ) {
+					return ret;
+				}
+
+				elem.setAttribute( name, value + "" );
+				return value;
+			}
+
+			if ( hooks && "get" in hooks && ( ret = hooks.get( elem, name ) ) !== null ) {
+				return ret;
+			}
+
+			ret = jQuery.find.attr( elem, name );
+
+			// Non-existent attributes return null, we normalize to undefined
+			return ret == null ? undefined : ret;
+		},
+
+		attrHooks: {
+			type: {
+				set: function( elem, value ) {
+					if ( !support.radioValue && value === "radio" &&
+						jQuery.nodeName( elem, "input" ) ) {
+						var val = elem.value;
+						elem.setAttribute( "type", value );
+						if ( val ) {
+							elem.value = val;
+						}
+						return value;
+					}
+				}
+			}
+		},
+
+		removeAttr: function( elem, value ) {
+			var name,
+				i = 0,
+				attrNames = value && value.match( rnotwhite );
+
+			if ( attrNames && elem.nodeType === 1 ) {
+				while ( ( name = attrNames[ i++ ] ) ) {
+					elem.removeAttribute( name );
+				}
+			}
+		}
+	} );
+
+	// Hooks for boolean attributes
+	boolHook = {
+		set: function( elem, value, name ) {
+			if ( value === false ) {
+
+				// Remove boolean attributes when set to false
+				jQuery.removeAttr( elem, name );
+			} else {
+				elem.setAttribute( name, name );
+			}
+			return name;
+		}
+	};
+
+	jQuery.each( jQuery.expr.match.bool.source.match( /\w+/g ), function( i, name ) {
+		var getter = attrHandle[ name ] || jQuery.find.attr;
+
+		attrHandle[ name ] = function( elem, name, isXML ) {
+			var ret, handle,
+				lowercaseName = name.toLowerCase();
+
+			if ( !isXML ) {
+
+				// Avoid an infinite loop by temporarily removing this function from the getter
+				handle = attrHandle[ lowercaseName ];
+				attrHandle[ lowercaseName ] = ret;
+				ret = getter( elem, name, isXML ) != null ?
+					lowercaseName :
+					null;
+				attrHandle[ lowercaseName ] = handle;
+			}
+			return ret;
+		};
+	} );
+
+
+
+
+	var rfocusable = /^(?:input|select|textarea|button)$/i,
+		rclickable = /^(?:a|area)$/i;
+
+	jQuery.fn.extend( {
+		prop: function( name, value ) {
+			return access( this, jQuery.prop, name, value, arguments.length > 1 );
+		},
+
+		removeProp: function( name ) {
+			return this.each( function() {
+				delete this[ jQuery.propFix[ name ] || name ];
+			} );
+		}
+	} );
+
+	jQuery.extend( {
+		prop: function( elem, name, value ) {
+			var ret, hooks,
+				nType = elem.nodeType;
+
+			// Don't get/set properties on text, comment and attribute nodes
+			if ( nType === 3 || nType === 8 || nType === 2 ) {
+				return;
+			}
+
+			if ( nType !== 1 || !jQuery.isXMLDoc( elem ) ) {
+
+				// Fix name and attach hooks
+				name = jQuery.propFix[ name ] || name;
+				hooks = jQuery.propHooks[ name ];
+			}
+
+			if ( value !== undefined ) {
+				if ( hooks && "set" in hooks &&
+					( ret = hooks.set( elem, value, name ) ) !== undefined ) {
+					return ret;
+				}
+
+				return ( elem[ name ] = value );
+			}
+
+			if ( hooks && "get" in hooks && ( ret = hooks.get( elem, name ) ) !== null ) {
+				return ret;
+			}
+
+			return elem[ name ];
+		},
+
+		propHooks: {
+			tabIndex: {
+				get: function( elem ) {
+
+					// Support: IE <=9 - 11 only
+					// elem.tabIndex doesn't always return the
+					// correct value when it hasn't been explicitly set
+					// https://web.archive.org/web/20141116233347/http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/
+					// Use proper attribute retrieval(#12072)
+					var tabindex = jQuery.find.attr( elem, "tabindex" );
+
+					return tabindex ?
+						parseInt( tabindex, 10 ) :
+						rfocusable.test( elem.nodeName ) ||
+							rclickable.test( elem.nodeName ) && elem.href ?
+								0 :
+								-1;
+				}
+			}
+		},
+
+		propFix: {
+			"for": "htmlFor",
+			"class": "className"
+		}
+	} );
+
+	// Support: IE <=11 only
+	// Accessing the selectedIndex property
+	// forces the browser to respect setting selected
+	// on the option
+	// The getter ensures a default option is selected
+	// when in an optgroup
+	if ( !support.optSelected ) {
+		jQuery.propHooks.selected = {
+			get: function( elem ) {
+				var parent = elem.parentNode;
+				if ( parent && parent.parentNode ) {
+					parent.parentNode.selectedIndex;
+				}
+				return null;
+			},
+			set: function( elem ) {
+				var parent = elem.parentNode;
+				if ( parent ) {
+					parent.selectedIndex;
+
+					if ( parent.parentNode ) {
+						parent.parentNode.selectedIndex;
+					}
+				}
+			}
+		};
+	}
+
+	jQuery.each( [
+		"tabIndex",
+		"readOnly",
+		"maxLength",
+		"cellSpacing",
+		"cellPadding",
+		"rowSpan",
+		"colSpan",
+		"useMap",
+		"frameBorder",
+		"contentEditable"
+	], function() {
+		jQuery.propFix[ this.toLowerCase() ] = this;
+	} );
+
+
+
+
+	var rclass = /[\t\r\n\f]/g;
+
+	function getClass( elem ) {
+		return elem.getAttribute && elem.getAttribute( "class" ) || "";
+	}
+
+	jQuery.fn.extend( {
+		addClass: function( value ) {
+			var classes, elem, cur, curValue, clazz, j, finalValue,
+				i = 0;
+
+			if ( jQuery.isFunction( value ) ) {
+				return this.each( function( j ) {
+					jQuery( this ).addClass( value.call( this, j, getClass( this ) ) );
+				} );
+			}
+
+			if ( typeof value === "string" && value ) {
+				classes = value.match( rnotwhite ) || [];
+
+				while ( ( elem = this[ i++ ] ) ) {
+					curValue = getClass( elem );
+					cur = elem.nodeType === 1 &&
+						( " " + curValue + " " ).replace( rclass, " " );
+
+					if ( cur ) {
+						j = 0;
+						while ( ( clazz = classes[ j++ ] ) ) {
+							if ( cur.indexOf( " " + clazz + " " ) < 0 ) {
+								cur += clazz + " ";
+							}
+						}
+
+						// Only assign if different to avoid unneeded rendering.
+						finalValue = jQuery.trim( cur );
+						if ( curValue !== finalValue ) {
+							elem.setAttribute( "class", finalValue );
+						}
+					}
+				}
+			}
+
+			return this;
+		},
+
+		removeClass: function( value ) {
+			var classes, elem, cur, curValue, clazz, j, finalValue,
+				i = 0;
+
+			if ( jQuery.isFunction( value ) ) {
+				return this.each( function( j ) {
+					jQuery( this ).removeClass( value.call( this, j, getClass( this ) ) );
+				} );
+			}
+
+			if ( !arguments.length ) {
+				return this.attr( "class", "" );
+			}
+
+			if ( typeof value === "string" && value ) {
+				classes = value.match( rnotwhite ) || [];
+
+				while ( ( elem = this[ i++ ] ) ) {
+					curValue = getClass( elem );
+
+					// This expression is here for better compressibility (see addClass)
+					cur = elem.nodeType === 1 &&
+						( " " + curValue + " " ).replace( rclass, " " );
+
+					if ( cur ) {
+						j = 0;
+						while ( ( clazz = classes[ j++ ] ) ) {
+
+							// Remove *all* instances
+							while ( cur.indexOf( " " + clazz + " " ) > -1 ) {
+								cur = cur.replace( " " + clazz + " ", " " );
+							}
+						}
+
+						// Only assign if different to avoid unneeded rendering.
+						finalValue = jQuery.trim( cur );
+						if ( curValue !== finalValue ) {
+							elem.setAttribute( "class", finalValue );
+						}
+					}
+				}
+			}
+
+			return this;
+		},
+
+		toggleClass: function( value, stateVal ) {
+			var type = typeof value;
+
+			if ( typeof stateVal === "boolean" && type === "string" ) {
+				return stateVal ? this.addClass( value ) : this.removeClass( value );
+			}
+
+			if ( jQuery.isFunction( value ) ) {
+				return this.each( function( i ) {
+					jQuery( this ).toggleClass(
+						value.call( this, i, getClass( this ), stateVal ),
+						stateVal
+					);
+				} );
+			}
+
+			return this.each( function() {
+				var className, i, self, classNames;
+
+				if ( type === "string" ) {
+
+					// Toggle individual class names
+					i = 0;
+					self = jQuery( this );
+					classNames = value.match( rnotwhite ) || [];
+
+					while ( ( className = classNames[ i++ ] ) ) {
+
+						// Check each className given, space separated list
+						if ( self.hasClass( className ) ) {
+							self.removeClass( className );
+						} else {
+							self.addClass( className );
+						}
+					}
+
+				// Toggle whole class name
+				} else if ( value === undefined || type === "boolean" ) {
+					className = getClass( this );
+					if ( className ) {
+
+						// Store className if set
+						dataPriv.set( this, "__className__", className );
+					}
+
+					// If the element has a class name or if we're passed `false`,
+					// then remove the whole classname (if there was one, the above saved it).
+					// Otherwise bring back whatever was previously saved (if anything),
+					// falling back to the empty string if nothing was stored.
+					if ( this.setAttribute ) {
+						this.setAttribute( "class",
+							className || value === false ?
+							"" :
+							dataPriv.get( this, "__className__" ) || ""
+						);
+					}
+				}
+			} );
+		},
+
+		hasClass: function( selector ) {
+			var className, elem,
+				i = 0;
+
+			className = " " + selector + " ";
+			while ( ( elem = this[ i++ ] ) ) {
+				if ( elem.nodeType === 1 &&
+					( " " + getClass( elem ) + " " ).replace( rclass, " " )
+						.indexOf( className ) > -1
+				) {
+					return true;
+				}
+			}
+
+			return false;
+		}
+	} );
+
+
+
+
+	var rreturn = /\r/g,
+		rspaces = /[\x20\t\r\n\f]+/g;
+
+	jQuery.fn.extend( {
+		val: function( value ) {
+			var hooks, ret, isFunction,
+				elem = this[ 0 ];
+
+			if ( !arguments.length ) {
+				if ( elem ) {
+					hooks = jQuery.valHooks[ elem.type ] ||
+						jQuery.valHooks[ elem.nodeName.toLowerCase() ];
+
+					if ( hooks &&
+						"get" in hooks &&
+						( ret = hooks.get( elem, "value" ) ) !== undefined
+					) {
+						return ret;
+					}
+
+					ret = elem.value;
+
+					return typeof ret === "string" ?
+
+						// Handle most common string cases
+						ret.replace( rreturn, "" ) :
+
+						// Handle cases where value is null/undef or number
+						ret == null ? "" : ret;
+				}
+
+				return;
+			}
+
+			isFunction = jQuery.isFunction( value );
+
+			return this.each( function( i ) {
+				var val;
+
+				if ( this.nodeType !== 1 ) {
+					return;
+				}
+
+				if ( isFunction ) {
+					val = value.call( this, i, jQuery( this ).val() );
+				} else {
+					val = value;
+				}
+
+				// Treat null/undefined as ""; convert numbers to string
+				if ( val == null ) {
+					val = "";
+
+				} else if ( typeof val === "number" ) {
+					val += "";
+
+				} else if ( jQuery.isArray( val ) ) {
+					val = jQuery.map( val, function( value ) {
+						return value == null ? "" : value + "";
+					} );
+				}
+
+				hooks = jQuery.valHooks[ this.type ] || jQuery.valHooks[ this.nodeName.toLowerCase() ];
+
+				// If set returns undefined, fall back to normal setting
+				if ( !hooks || !( "set" in hooks ) || hooks.set( this, val, "value" ) === undefined ) {
+					this.value = val;
+				}
+			} );
+		}
+	} );
+
+	jQuery.extend( {
+		valHooks: {
+			option: {
+				get: function( elem ) {
+
+					var val = jQuery.find.attr( elem, "value" );
+					return val != null ?
+						val :
+
+						// Support: IE <=10 - 11 only
+						// option.text throws exceptions (#14686, #14858)
+						// Strip and collapse whitespace
+						// https://html.spec.whatwg.org/#strip-and-collapse-whitespace
+						jQuery.trim( jQuery.text( elem ) ).replace( rspaces, " " );
+				}
+			},
+			select: {
+				get: function( elem ) {
+					var value, option,
+						options = elem.options,
+						index = elem.selectedIndex,
+						one = elem.type === "select-one",
+						values = one ? null : [],
+						max = one ? index + 1 : options.length,
+						i = index < 0 ?
+							max :
+							one ? index : 0;
+
+					// Loop through all the selected options
+					for ( ; i < max; i++ ) {
+						option = options[ i ];
+
+						// Support: IE <=9 only
+						// IE8-9 doesn't update selected after form reset (#2551)
+						if ( ( option.selected || i === index ) &&
+
+								// Don't return options that are disabled or in a disabled optgroup
+								!option.disabled &&
+								( !option.parentNode.disabled ||
+									!jQuery.nodeName( option.parentNode, "optgroup" ) ) ) {
+
+							// Get the specific value for the option
+							value = jQuery( option ).val();
+
+							// We don't need an array for one selects
+							if ( one ) {
+								return value;
+							}
+
+							// Multi-Selects return an array
+							values.push( value );
+						}
+					}
+
+					return values;
+				},
+
+				set: function( elem, value ) {
+					var optionSet, option,
+						options = elem.options,
+						values = jQuery.makeArray( value ),
+						i = options.length;
+
+					while ( i-- ) {
+						option = options[ i ];
+
+						/* eslint-disable no-cond-assign */
+
+						if ( option.selected =
+							jQuery.inArray( jQuery.valHooks.option.get( option ), values ) > -1
+						) {
+							optionSet = true;
+						}
+
+						/* eslint-enable no-cond-assign */
+					}
+
+					// Force browsers to behave consistently when non-matching value is set
+					if ( !optionSet ) {
+						elem.selectedIndex = -1;
+					}
+					return values;
+				}
+			}
+		}
+	} );
+
+	// Radios and checkboxes getter/setter
+	jQuery.each( [ "radio", "checkbox" ], function() {
+		jQuery.valHooks[ this ] = {
+			set: function( elem, value ) {
+				if ( jQuery.isArray( value ) ) {
+					return ( elem.checked = jQuery.inArray( jQuery( elem ).val(), value ) > -1 );
+				}
+			}
+		};
+		if ( !support.checkOn ) {
+			jQuery.valHooks[ this ].get = function( elem ) {
+				return elem.getAttribute( "value" ) === null ? "on" : elem.value;
+			};
+		}
+	} );
+
+
+
+
+	// Return jQuery for attributes-only inclusion
+
+
+	var rfocusMorph = /^(?:focusinfocus|focusoutblur)$/;
+
+	jQuery.extend( jQuery.event, {
+
+		trigger: function( event, data, elem, onlyHandlers ) {
+
+			var i, cur, tmp, bubbleType, ontype, handle, special,
+				eventPath = [ elem || document ],
+				type = hasOwn.call( event, "type" ) ? event.type : event,
+				namespaces = hasOwn.call( event, "namespace" ) ? event.namespace.split( "." ) : [];
+
+			cur = tmp = elem = elem || document;
+
+			// Don't do events on text and comment nodes
+			if ( elem.nodeType === 3 || elem.nodeType === 8 ) {
+				return;
+			}
+
+			// focus/blur morphs to focusin/out; ensure we're not firing them right now
+			if ( rfocusMorph.test( type + jQuery.event.triggered ) ) {
+				return;
+			}
+
+			if ( type.indexOf( "." ) > -1 ) {
+
+				// Namespaced trigger; create a regexp to match event type in handle()
+				namespaces = type.split( "." );
+				type = namespaces.shift();
+				namespaces.sort();
+			}
+			ontype = type.indexOf( ":" ) < 0 && "on" + type;
+
+			// Caller can pass in a jQuery.Event object, Object, or just an event type string
+			event = event[ jQuery.expando ] ?
+				event :
+				new jQuery.Event( type, typeof event === "object" && event );
+
+			// Trigger bitmask: & 1 for native handlers; & 2 for jQuery (always true)
+			event.isTrigger = onlyHandlers ? 2 : 3;
+			event.namespace = namespaces.join( "." );
+			event.rnamespace = event.namespace ?
+				new RegExp( "(^|\\.)" + namespaces.join( "\\.(?:.*\\.|)" ) + "(\\.|$)" ) :
+				null;
+
+			// Clean up the event in case it is being reused
+			event.result = undefined;
+			if ( !event.target ) {
+				event.target = elem;
+			}
+
+			// Clone any incoming data and prepend the event, creating the handler arg list
+			data = data == null ?
+				[ event ] :
+				jQuery.makeArray( data, [ event ] );
+
+			// Allow special events to draw outside the lines
+			special = jQuery.event.special[ type ] || {};
+			if ( !onlyHandlers && special.trigger && special.trigger.apply( elem, data ) === false ) {
+				return;
+			}
+
+			// Determine event propagation path in advance, per W3C events spec (#9951)
+			// Bubble up to document, then to window; watch for a global ownerDocument var (#9724)
+			if ( !onlyHandlers && !special.noBubble && !jQuery.isWindow( elem ) ) {
+
+				bubbleType = special.delegateType || type;
+				if ( !rfocusMorph.test( bubbleType + type ) ) {
+					cur = cur.parentNode;
+				}
+				for ( ; cur; cur = cur.parentNode ) {
+					eventPath.push( cur );
+					tmp = cur;
+				}
+
+				// Only add window if we got to document (e.g., not plain obj or detached DOM)
+				if ( tmp === ( elem.ownerDocument || document ) ) {
+					eventPath.push( tmp.defaultView || tmp.parentWindow || window );
+				}
+			}
+
+			// Fire handlers on the event path
+			i = 0;
+			while ( ( cur = eventPath[ i++ ] ) && !event.isPropagationStopped() ) {
+
+				event.type = i > 1 ?
+					bubbleType :
+					special.bindType || type;
+
+				// jQuery handler
+				handle = ( dataPriv.get( cur, "events" ) || {} )[ event.type ] &&
+					dataPriv.get( cur, "handle" );
+				if ( handle ) {
+					handle.apply( cur, data );
+				}
+
+				// Native handler
+				handle = ontype && cur[ ontype ];
+				if ( handle && handle.apply && acceptData( cur ) ) {
+					event.result = handle.apply( cur, data );
+					if ( event.result === false ) {
+						event.preventDefault();
+					}
+				}
+			}
+			event.type = type;
+
+			// If nobody prevented the default action, do it now
+			if ( !onlyHandlers && !event.isDefaultPrevented() ) {
+
+				if ( ( !special._default ||
+					special._default.apply( eventPath.pop(), data ) === false ) &&
+					acceptData( elem ) ) {
+
+					// Call a native DOM method on the target with the same name as the event.
+					// Don't do default actions on window, that's where global variables be (#6170)
+					if ( ontype && jQuery.isFunction( elem[ type ] ) && !jQuery.isWindow( elem ) ) {
+
+						// Don't re-trigger an onFOO event when we call its FOO() method
+						tmp = elem[ ontype ];
+
+						if ( tmp ) {
+							elem[ ontype ] = null;
+						}
+
+						// Prevent re-triggering of the same event, since we already bubbled it above
+						jQuery.event.triggered = type;
+						elem[ type ]();
+						jQuery.event.triggered = undefined;
+
+						if ( tmp ) {
+							elem[ ontype ] = tmp;
+						}
+					}
+				}
+			}
+
+			return event.result;
+		},
+
+		// Piggyback on a donor event to simulate a different one
+		// Used only for `focus(in | out)` events
+		simulate: function( type, elem, event ) {
+			var e = jQuery.extend(
+				new jQuery.Event(),
+				event,
+				{
+					type: type,
+					isSimulated: true
+				}
+			);
+
+			jQuery.event.trigger( e, null, elem );
+		}
+
+	} );
+
+	jQuery.fn.extend( {
+
+		trigger: function( type, data ) {
+			return this.each( function() {
+				jQuery.event.trigger( type, data, this );
+			} );
+		},
+		triggerHandler: function( type, data ) {
+			var elem = this[ 0 ];
+			if ( elem ) {
+				return jQuery.event.trigger( type, data, elem, true );
+			}
+		}
+	} );
+
+
+	jQuery.each( ( "blur focus focusin focusout resize scroll click dblclick " +
+		"mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave " +
+		"change select submit keydown keypress keyup contextmenu" ).split( " " ),
+		function( i, name ) {
+
+		// Handle event binding
+		jQuery.fn[ name ] = function( data, fn ) {
+			return arguments.length > 0 ?
+				this.on( name, null, data, fn ) :
+				this.trigger( name );
+		};
+	} );
+
+	jQuery.fn.extend( {
+		hover: function( fnOver, fnOut ) {
+			return this.mouseenter( fnOver ).mouseleave( fnOut || fnOver );
+		}
+	} );
+
+
+
+
+	support.focusin = "onfocusin" in window;
+
+
+	// Support: Firefox <=44
+	// Firefox doesn't have focus(in | out) events
+	// Related ticket - https://bugzilla.mozilla.org/show_bug.cgi?id=687787
+	//
+	// Support: Chrome <=48 - 49, Safari <=9.0 - 9.1
+	// focus(in | out) events fire after focus & blur events,
+	// which is spec violation - http://www.w3.org/TR/DOM-Level-3-Events/#events-focusevent-event-order
+	// Related ticket - https://bugs.chromium.org/p/chromium/issues/detail?id=449857
+	if ( !support.focusin ) {
+		jQuery.each( { focus: "focusin", blur: "focusout" }, function( orig, fix ) {
+
+			// Attach a single capturing handler on the document while someone wants focusin/focusout
+			var handler = function( event ) {
+				jQuery.event.simulate( fix, event.target, jQuery.event.fix( event ) );
+			};
+
+			jQuery.event.special[ fix ] = {
+				setup: function() {
+					var doc = this.ownerDocument || this,
+						attaches = dataPriv.access( doc, fix );
+
+					if ( !attaches ) {
+						doc.addEventListener( orig, handler, true );
+					}
+					dataPriv.access( doc, fix, ( attaches || 0 ) + 1 );
+				},
+				teardown: function() {
+					var doc = this.ownerDocument || this,
+						attaches = dataPriv.access( doc, fix ) - 1;
+
+					if ( !attaches ) {
+						doc.removeEventListener( orig, handler, true );
+						dataPriv.remove( doc, fix );
+
+					} else {
+						dataPriv.access( doc, fix, attaches );
+					}
+				}
+			};
+		} );
+	}
+	var location = window.location;
+
+	var nonce = jQuery.now();
+
+	var rquery = ( /\?/ );
+
+
+
+	// Cross-browser xml parsing
+	jQuery.parseXML = function( data ) {
+		var xml;
+		if ( !data || typeof data !== "string" ) {
+			return null;
+		}
+
+		// Support: IE 9 - 11 only
+		// IE throws on parseFromString with invalid input.
+		try {
+			xml = ( new window.DOMParser() ).parseFromString( data, "text/xml" );
+		} catch ( e ) {
+			xml = undefined;
+		}
+
+		if ( !xml || xml.getElementsByTagName( "parsererror" ).length ) {
+			jQuery.error( "Invalid XML: " + data );
+		}
+		return xml;
+	};
+
+
+	var
+		rbracket = /\[\]$/,
+		rCRLF = /\r?\n/g,
+		rsubmitterTypes = /^(?:submit|button|image|reset|file)$/i,
+		rsubmittable = /^(?:input|select|textarea|keygen)/i;
+
+	function buildParams( prefix, obj, traditional, add ) {
+		var name;
+
+		if ( jQuery.isArray( obj ) ) {
+
+			// Serialize array item.
+			jQuery.each( obj, function( i, v ) {
+				if ( traditional || rbracket.test( prefix ) ) {
+
+					// Treat each array item as a scalar.
+					add( prefix, v );
+
+				} else {
+
+					// Item is non-scalar (array or object), encode its numeric index.
+					buildParams(
+						prefix + "[" + ( typeof v === "object" && v != null ? i : "" ) + "]",
+						v,
+						traditional,
+						add
+					);
+				}
+			} );
+
+		} else if ( !traditional && jQuery.type( obj ) === "object" ) {
+
+			// Serialize object item.
+			for ( name in obj ) {
+				buildParams( prefix + "[" + name + "]", obj[ name ], traditional, add );
+			}
+
+		} else {
+
+			// Serialize scalar item.
+			add( prefix, obj );
+		}
+	}
+
+	// Serialize an array of form elements or a set of
+	// key/values into a query string
+	jQuery.param = function( a, traditional ) {
+		var prefix,
+			s = [],
+			add = function( key, valueOrFunction ) {
+
+				// If value is a function, invoke it and use its return value
+				var value = jQuery.isFunction( valueOrFunction ) ?
+					valueOrFunction() :
+					valueOrFunction;
+
+				s[ s.length ] = encodeURIComponent( key ) + "=" +
+					encodeURIComponent( value == null ? "" : value );
+			};
+
+		// If an array was passed in, assume that it is an array of form elements.
+		if ( jQuery.isArray( a ) || ( a.jquery && !jQuery.isPlainObject( a ) ) ) {
+
+			// Serialize the form elements
+			jQuery.each( a, function() {
+				add( this.name, this.value );
+			} );
+
+		} else {
+
+			// If traditional, encode the "old" way (the way 1.3.2 or older
+			// did it), otherwise encode params recursively.
+			for ( prefix in a ) {
+				buildParams( prefix, a[ prefix ], traditional, add );
+			}
+		}
+
+		// Return the resulting serialization
+		return s.join( "&" );
+	};
+
+	jQuery.fn.extend( {
+		serialize: function() {
+			return jQuery.param( this.serializeArray() );
+		},
+		serializeArray: function() {
+			return this.map( function() {
+
+				// Can add propHook for "elements" to filter or add form elements
+				var elements = jQuery.prop( this, "elements" );
+				return elements ? jQuery.makeArray( elements ) : this;
+			} )
+			.filter( function() {
+				var type = this.type;
+
+				// Use .is( ":disabled" ) so that fieldset[disabled] works
+				return this.name && !jQuery( this ).is( ":disabled" ) &&
+					rsubmittable.test( this.nodeName ) && !rsubmitterTypes.test( type ) &&
+					( this.checked || !rcheckableType.test( type ) );
+			} )
+			.map( function( i, elem ) {
+				var val = jQuery( this ).val();
+
+				return val == null ?
+					null :
+					jQuery.isArray( val ) ?
+						jQuery.map( val, function( val ) {
+							return { name: elem.name, value: val.replace( rCRLF, "\r\n" ) };
+						} ) :
+						{ name: elem.name, value: val.replace( rCRLF, "\r\n" ) };
+			} ).get();
+		}
+	} );
+
+
+	var
+		r20 = /%20/g,
+		rhash = /#.*$/,
+		rts = /([?&])_=[^&]*/,
+		rheaders = /^(.*?):[ \t]*([^\r\n]*)$/mg,
+
+		// #7653, #8125, #8152: local protocol detection
+		rlocalProtocol = /^(?:about|app|app-storage|.+-extension|file|res|widget):$/,
+		rnoContent = /^(?:GET|HEAD)$/,
+		rprotocol = /^\/\//,
+
+		/* Prefilters
+		 * 1) They are useful to introduce custom dataTypes (see ajax/jsonp.js for an example)
+		 * 2) These are called:
+		 *    - BEFORE asking for a transport
+		 *    - AFTER param serialization (s.data is a string if s.processData is true)
+		 * 3) key is the dataType
+		 * 4) the catchall symbol "*" can be used
+		 * 5) execution will start with transport dataType and THEN continue down to "*" if needed
+		 */
+		prefilters = {},
+
+		/* Transports bindings
+		 * 1) key is the dataType
+		 * 2) the catchall symbol "*" can be used
+		 * 3) selection will start with transport dataType and THEN go to "*" if needed
+		 */
+		transports = {},
+
+		// Avoid comment-prolog char sequence (#10098); must appease lint and evade compression
+		allTypes = "*/".concat( "*" ),
+
+		// Anchor tag for parsing the document origin
+		originAnchor = document.createElement( "a" );
+		originAnchor.href = location.href;
+
+	// Base "constructor" for jQuery.ajaxPrefilter and jQuery.ajaxTransport
+	function addToPrefiltersOrTransports( structure ) {
+
+		// dataTypeExpression is optional and defaults to "*"
+		return function( dataTypeExpression, func ) {
+
+			if ( typeof dataTypeExpression !== "string" ) {
+				func = dataTypeExpression;
+				dataTypeExpression = "*";
+			}
+
+			var dataType,
+				i = 0,
+				dataTypes = dataTypeExpression.toLowerCase().match( rnotwhite ) || [];
+
+			if ( jQuery.isFunction( func ) ) {
+
+				// For each dataType in the dataTypeExpression
+				while ( ( dataType = dataTypes[ i++ ] ) ) {
+
+					// Prepend if requested
+					if ( dataType[ 0 ] === "+" ) {
+						dataType = dataType.slice( 1 ) || "*";
+						( structure[ dataType ] = structure[ dataType ] || [] ).unshift( func );
+
+					// Otherwise append
+					} else {
+						( structure[ dataType ] = structure[ dataType ] || [] ).push( func );
+					}
+				}
+			}
+		};
+	}
+
+	// Base inspection function for prefilters and transports
+	function inspectPrefiltersOrTransports( structure, options, originalOptions, jqXHR ) {
+
+		var inspected = {},
+			seekingTransport = ( structure === transports );
+
+		function inspect( dataType ) {
+			var selected;
+			inspected[ dataType ] = true;
+			jQuery.each( structure[ dataType ] || [], function( _, prefilterOrFactory ) {
+				var dataTypeOrTransport = prefilterOrFactory( options, originalOptions, jqXHR );
+				if ( typeof dataTypeOrTransport === "string" &&
+					!seekingTransport && !inspected[ dataTypeOrTransport ] ) {
+
+					options.dataTypes.unshift( dataTypeOrTransport );
+					inspect( dataTypeOrTransport );
+					return false;
+				} else if ( seekingTransport ) {
+					return !( selected = dataTypeOrTransport );
+				}
+			} );
+			return selected;
+		}
+
+		return inspect( options.dataTypes[ 0 ] ) || !inspected[ "*" ] && inspect( "*" );
+	}
+
+	// A special extend for ajax options
+	// that takes "flat" options (not to be deep extended)
+	// Fixes #9887
+	function ajaxExtend( target, src ) {
+		var key, deep,
+			flatOptions = jQuery.ajaxSettings.flatOptions || {};
+
+		for ( key in src ) {
+			if ( src[ key ] !== undefined ) {
+				( flatOptions[ key ] ? target : ( deep || ( deep = {} ) ) )[ key ] = src[ key ];
+			}
+		}
+		if ( deep ) {
+			jQuery.extend( true, target, deep );
+		}
+
+		return target;
+	}
+
+	/* Handles responses to an ajax request:
+	 * - finds the right dataType (mediates between content-type and expected dataType)
+	 * - returns the corresponding response
+	 */
+	function ajaxHandleResponses( s, jqXHR, responses ) {
+
+		var ct, type, finalDataType, firstDataType,
+			contents = s.contents,
+			dataTypes = s.dataTypes;
+
+		// Remove auto dataType and get content-type in the process
+		while ( dataTypes[ 0 ] === "*" ) {
+			dataTypes.shift();
+			if ( ct === undefined ) {
+				ct = s.mimeType || jqXHR.getResponseHeader( "Content-Type" );
+			}
+		}
+
+		// Check if we're dealing with a known content-type
+		if ( ct ) {
+			for ( type in contents ) {
+				if ( contents[ type ] && contents[ type ].test( ct ) ) {
+					dataTypes.unshift( type );
+					break;
+				}
+			}
+		}
+
+		// Check to see if we have a response for the expected dataType
+		if ( dataTypes[ 0 ] in responses ) {
+			finalDataType = dataTypes[ 0 ];
+		} else {
+
+			// Try convertible dataTypes
+			for ( type in responses ) {
+				if ( !dataTypes[ 0 ] || s.converters[ type + " " + dataTypes[ 0 ] ] ) {
+					finalDataType = type;
+					break;
+				}
+				if ( !firstDataType ) {
+					firstDataType = type;
+				}
+			}
+
+			// Or just use first one
+			finalDataType = finalDataType || firstDataType;
+		}
+
+		// If we found a dataType
+		// We add the dataType to the list if needed
+		// and return the corresponding response
+		if ( finalDataType ) {
+			if ( finalDataType !== dataTypes[ 0 ] ) {
+				dataTypes.unshift( finalDataType );
+			}
+			return responses[ finalDataType ];
+		}
+	}
+
+	/* Chain conversions given the request and the original response
+	 * Also sets the responseXXX fields on the jqXHR instance
+	 */
+	function ajaxConvert( s, response, jqXHR, isSuccess ) {
+		var conv2, current, conv, tmp, prev,
+			converters = {},
+
+			// Work with a copy of dataTypes in case we need to modify it for conversion
+			dataTypes = s.dataTypes.slice();
+
+		// Create converters map with lowercased keys
+		if ( dataTypes[ 1 ] ) {
+			for ( conv in s.converters ) {
+				converters[ conv.toLowerCase() ] = s.converters[ conv ];
+			}
+		}
+
+		current = dataTypes.shift();
+
+		// Convert to each sequential dataType
+		while ( current ) {
+
+			if ( s.responseFields[ current ] ) {
+				jqXHR[ s.responseFields[ current ] ] = response;
+			}
+
+			// Apply the dataFilter if provided
+			if ( !prev && isSuccess && s.dataFilter ) {
+				response = s.dataFilter( response, s.dataType );
+			}
+
+			prev = current;
+			current = dataTypes.shift();
+
+			if ( current ) {
+
+				// There's only work to do if current dataType is non-auto
+				if ( current === "*" ) {
+
+					current = prev;
+
+				// Convert response if prev dataType is non-auto and differs from current
+				} else if ( prev !== "*" && prev !== current ) {
+
+					// Seek a direct converter
+					conv = converters[ prev + " " + current ] || converters[ "* " + current ];
+
+					// If none found, seek a pair
+					if ( !conv ) {
+						for ( conv2 in converters ) {
+
+							// If conv2 outputs current
+							tmp = conv2.split( " " );
+							if ( tmp[ 1 ] === current ) {
+
+								// If prev can be converted to accepted input
+								conv = converters[ prev + " " + tmp[ 0 ] ] ||
+									converters[ "* " + tmp[ 0 ] ];
+								if ( conv ) {
+
+									// Condense equivalence converters
+									if ( conv === true ) {
+										conv = converters[ conv2 ];
+
+									// Otherwise, insert the intermediate dataType
+									} else if ( converters[ conv2 ] !== true ) {
+										current = tmp[ 0 ];
+										dataTypes.unshift( tmp[ 1 ] );
+									}
+									break;
+								}
+							}
+						}
+					}
+
+					// Apply converter (if not an equivalence)
+					if ( conv !== true ) {
+
+						// Unless errors are allowed to bubble, catch and return them
+						if ( conv && s.throws ) {
+							response = conv( response );
+						} else {
+							try {
+								response = conv( response );
+							} catch ( e ) {
+								return {
+									state: "parsererror",
+									error: conv ? e : "No conversion from " + prev + " to " + current
+								};
+							}
+						}
+					}
+				}
+			}
+		}
+
+		return { state: "success", data: response };
+	}
+
+	jQuery.extend( {
+
+		// Counter for holding the number of active queries
+		active: 0,
+
+		// Last-Modified header cache for next request
+		lastModified: {},
+		etag: {},
+
+		ajaxSettings: {
+			url: location.href,
+			type: "GET",
+			isLocal: rlocalProtocol.test( location.protocol ),
+			global: true,
+			processData: true,
+			async: true,
+			contentType: "application/x-www-form-urlencoded; charset=UTF-8",
+
+			/*
+			timeout: 0,
+			data: null,
+			dataType: null,
+			username: null,
+			password: null,
+			cache: null,
+			throws: false,
+			traditional: false,
+			headers: {},
+			*/
+
+			accepts: {
+				"*": allTypes,
+				text: "text/plain",
+				html: "text/html",
+				xml: "application/xml, text/xml",
+				json: "application/json, text/javascript"
+			},
+
+			contents: {
+				xml: /\bxml\b/,
+				html: /\bhtml/,
+				json: /\bjson\b/
+			},
+
+			responseFields: {
+				xml: "responseXML",
+				text: "responseText",
+				json: "responseJSON"
+			},
+
+			// Data converters
+			// Keys separate source (or catchall "*") and destination types with a single space
+			converters: {
+
+				// Convert anything to text
+				"* text": String,
+
+				// Text to html (true = no transformation)
+				"text html": true,
+
+				// Evaluate text as a json expression
+				"text json": JSON.parse,
+
+				// Parse text as xml
+				"text xml": jQuery.parseXML
+			},
+
+			// For options that shouldn't be deep extended:
+			// you can add your own custom options here if
+			// and when you create one that shouldn't be
+			// deep extended (see ajaxExtend)
+			flatOptions: {
+				url: true,
+				context: true
+			}
+		},
+
+		// Creates a full fledged settings object into target
+		// with both ajaxSettings and settings fields.
+		// If target is omitted, writes into ajaxSettings.
+		ajaxSetup: function( target, settings ) {
+			return settings ?
+
+				// Building a settings object
+				ajaxExtend( ajaxExtend( target, jQuery.ajaxSettings ), settings ) :
+
+				// Extending ajaxSettings
+				ajaxExtend( jQuery.ajaxSettings, target );
+		},
+
+		ajaxPrefilter: addToPrefiltersOrTransports( prefilters ),
+		ajaxTransport: addToPrefiltersOrTransports( transports ),
+
+		// Main method
+		ajax: function( url, options ) {
+
+			// If url is an object, simulate pre-1.5 signature
+			if ( typeof url === "object" ) {
+				options = url;
+				url = undefined;
+			}
+
+			// Force options to be an object
+			options = options || {};
+
+			var transport,
+
+				// URL without anti-cache param
+				cacheURL,
+
+				// Response headers
+				responseHeadersString,
+				responseHeaders,
+
+				// timeout handle
+				timeoutTimer,
+
+				// Url cleanup var
+				urlAnchor,
+
+				// Request state (becomes false upon send and true upon completion)
+				completed,
+
+				// To know if global events are to be dispatched
+				fireGlobals,
+
+				// Loop variable
+				i,
+
+				// uncached part of the url
+				uncached,
+
+				// Create the final options object
+				s = jQuery.ajaxSetup( {}, options ),
+
+				// Callbacks context
+				callbackContext = s.context || s,
+
+				// Context for global events is callbackContext if it is a DOM node or jQuery collection
+				globalEventContext = s.context &&
+					( callbackContext.nodeType || callbackContext.jquery ) ?
+						jQuery( callbackContext ) :
+						jQuery.event,
+
+				// Deferreds
+				deferred = jQuery.Deferred(),
+				completeDeferred = jQuery.Callbacks( "once memory" ),
+
+				// Status-dependent callbacks
+				statusCode = s.statusCode || {},
+
+				// Headers (they are sent all at once)
+				requestHeaders = {},
+				requestHeadersNames = {},
+
+				// Default abort message
+				strAbort = "canceled",
+
+				// Fake xhr
+				jqXHR = {
+					readyState: 0,
+
+					// Builds headers hashtable if needed
+					getResponseHeader: function( key ) {
+						var match;
+						if ( completed ) {
+							if ( !responseHeaders ) {
+								responseHeaders = {};
+								while ( ( match = rheaders.exec( responseHeadersString ) ) ) {
+									responseHeaders[ match[ 1 ].toLowerCase() ] = match[ 2 ];
+								}
+							}
+							match = responseHeaders[ key.toLowerCase() ];
+						}
+						return match == null ? null : match;
+					},
+
+					// Raw string
+					getAllResponseHeaders: function() {
+						return completed ? responseHeadersString : null;
+					},
+
+					// Caches the header
+					setRequestHeader: function( name, value ) {
+						if ( completed == null ) {
+							name = requestHeadersNames[ name.toLowerCase() ] =
+								requestHeadersNames[ name.toLowerCase() ] || name;
+							requestHeaders[ name ] = value;
+						}
+						return this;
+					},
+
+					// Overrides response content-type header
+					overrideMimeType: function( type ) {
+						if ( completed == null ) {
+							s.mimeType = type;
+						}
+						return this;
+					},
+
+					// Status-dependent callbacks
+					statusCode: function( map ) {
+						var code;
+						if ( map ) {
+							if ( completed ) {
+
+								// Execute the appropriate callbacks
+								jqXHR.always( map[ jqXHR.status ] );
+							} else {
+
+								// Lazy-add the new callbacks in a way that preserves old ones
+								for ( code in map ) {
+									statusCode[ code ] = [ statusCode[ code ], map[ code ] ];
+								}
+							}
+						}
+						return this;
+					},
+
+					// Cancel the request
+					abort: function( statusText ) {
+						var finalText = statusText || strAbort;
+						if ( transport ) {
+							transport.abort( finalText );
+						}
+						done( 0, finalText );
+						return this;
+					}
+				};
+
+			// Attach deferreds
+			deferred.promise( jqXHR );
+
+			// Add protocol if not provided (prefilters might expect it)
+			// Handle falsy url in the settings object (#10093: consistency with old signature)
+			// We also use the url parameter if available
+			s.url = ( ( url || s.url || location.href ) + "" )
+				.replace( rprotocol, location.protocol + "//" );
+
+			// Alias method option to type as per ticket #12004
+			s.type = options.method || options.type || s.method || s.type;
+
+			// Extract dataTypes list
+			s.dataTypes = ( s.dataType || "*" ).toLowerCase().match( rnotwhite ) || [ "" ];
+
+			// A cross-domain request is in order when the origin doesn't match the current origin.
+			if ( s.crossDomain == null ) {
+				urlAnchor = document.createElement( "a" );
+
+				// Support: IE <=8 - 11, Edge 12 - 13
+				// IE throws exception on accessing the href property if url is malformed,
+				// e.g. http://example.com:80x/
+				try {
+					urlAnchor.href = s.url;
+
+					// Support: IE <=8 - 11 only
+					// Anchor's host property isn't correctly set when s.url is relative
+					urlAnchor.href = urlAnchor.href;
+					s.crossDomain = originAnchor.protocol + "//" + originAnchor.host !==
+						urlAnchor.protocol + "//" + urlAnchor.host;
+				} catch ( e ) {
+
+					// If there is an error parsing the URL, assume it is crossDomain,
+					// it can be rejected by the transport if it is invalid
+					s.crossDomain = true;
+				}
+			}
+
+			// Convert data if not already a string
+			if ( s.data && s.processData && typeof s.data !== "string" ) {
+				s.data = jQuery.param( s.data, s.traditional );
+			}
+
+			// Apply prefilters
+			inspectPrefiltersOrTransports( prefilters, s, options, jqXHR );
+
+			// If request was aborted inside a prefilter, stop there
+			if ( completed ) {
+				return jqXHR;
+			}
+
+			// We can fire global events as of now if asked to
+			// Don't fire events if jQuery.event is undefined in an AMD-usage scenario (#15118)
+			fireGlobals = jQuery.event && s.global;
+
+			// Watch for a new set of requests
+			if ( fireGlobals && jQuery.active++ === 0 ) {
+				jQuery.event.trigger( "ajaxStart" );
+			}
+
+			// Uppercase the type
+			s.type = s.type.toUpperCase();
+
+			// Determine if request has content
+			s.hasContent = !rnoContent.test( s.type );
+
+			// Save the URL in case we're toying with the If-Modified-Since
+			// and/or If-None-Match header later on
+			// Remove hash to simplify url manipulation
+			cacheURL = s.url.replace( rhash, "" );
+
+			// More options handling for requests with no content
+			if ( !s.hasContent ) {
+
+				// Remember the hash so we can put it back
+				uncached = s.url.slice( cacheURL.length );
+
+				// If data is available, append data to url
+				if ( s.data ) {
+					cacheURL += ( rquery.test( cacheURL ) ? "&" : "?" ) + s.data;
+
+					// #9682: remove data so that it's not used in an eventual retry
+					delete s.data;
+				}
+
+				// Add anti-cache in uncached url if needed
+				if ( s.cache === false ) {
+					cacheURL = cacheURL.replace( rts, "" );
+					uncached = ( rquery.test( cacheURL ) ? "&" : "?" ) + "_=" + ( nonce++ ) + uncached;
+				}
+
+				// Put hash and anti-cache on the URL that will be requested (gh-1732)
+				s.url = cacheURL + uncached;
+
+			// Change '%20' to '+' if this is encoded form body content (gh-2658)
+			} else if ( s.data && s.processData &&
+				( s.contentType || "" ).indexOf( "application/x-www-form-urlencoded" ) === 0 ) {
+				s.data = s.data.replace( r20, "+" );
+			}
+
+			// Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode.
+			if ( s.ifModified ) {
+				if ( jQuery.lastModified[ cacheURL ] ) {
+					jqXHR.setRequestHeader( "If-Modified-Since", jQuery.lastModified[ cacheURL ] );
+				}
+				if ( jQuery.etag[ cacheURL ] ) {
+					jqXHR.setRequestHeader( "If-None-Match", jQuery.etag[ cacheURL ] );
+				}
+			}
+
+			// Set the correct header, if data is being sent
+			if ( s.data && s.hasContent && s.contentType !== false || options.contentType ) {
+				jqXHR.setRequestHeader( "Content-Type", s.contentType );
+			}
+
+			// Set the Accepts header for the server, depending on the dataType
+			jqXHR.setRequestHeader(
+				"Accept",
+				s.dataTypes[ 0 ] && s.accepts[ s.dataTypes[ 0 ] ] ?
+					s.accepts[ s.dataTypes[ 0 ] ] +
+						( s.dataTypes[ 0 ] !== "*" ? ", " + allTypes + "; q=0.01" : "" ) :
+					s.accepts[ "*" ]
+			);
+
+			// Check for headers option
+			for ( i in s.headers ) {
+				jqXHR.setRequestHeader( i, s.headers[ i ] );
+			}
+
+			// Allow custom headers/mimetypes and early abort
+			if ( s.beforeSend &&
+				( s.beforeSend.call( callbackContext, jqXHR, s ) === false || completed ) ) {
+
+				// Abort if not done already and return
+				return jqXHR.abort();
+			}
+
+			// Aborting is no longer a cancellation
+			strAbort = "abort";
+
+			// Install callbacks on deferreds
+			completeDeferred.add( s.complete );
+			jqXHR.done( s.success );
+			jqXHR.fail( s.error );
+
+			// Get transport
+			transport = inspectPrefiltersOrTransports( transports, s, options, jqXHR );
+
+			// If no transport, we auto-abort
+			if ( !transport ) {
+				done( -1, "No Transport" );
+			} else {
+				jqXHR.readyState = 1;
+
+				// Send global event
+				if ( fireGlobals ) {
+					globalEventContext.trigger( "ajaxSend", [ jqXHR, s ] );
+				}
+
+				// If request was aborted inside ajaxSend, stop there
+				if ( completed ) {
+					return jqXHR;
+				}
+
+				// Timeout
+				if ( s.async && s.timeout > 0 ) {
+					timeoutTimer = window.setTimeout( function() {
+						jqXHR.abort( "timeout" );
+					}, s.timeout );
+				}
+
+				try {
+					completed = false;
+					transport.send( requestHeaders, done );
+				} catch ( e ) {
+
+					// Rethrow post-completion exceptions
+					if ( completed ) {
+						throw e;
+					}
+
+					// Propagate others as results
+					done( -1, e );
+				}
+			}
+
+			// Callback for when everything is done
+			function done( status, nativeStatusText, responses, headers ) {
+				var isSuccess, success, error, response, modified,
+					statusText = nativeStatusText;
+
+				// Ignore repeat invocations
+				if ( completed ) {
+					return;
+				}
+
+				completed = true;
+
+				// Clear timeout if it exists
+				if ( timeoutTimer ) {
+					window.clearTimeout( timeoutTimer );
+				}
+
+				// Dereference transport for early garbage collection
+				// (no matter how long the jqXHR object will be used)
+				transport = undefined;
+
+				// Cache response headers
+				responseHeadersString = headers || "";
+
+				// Set readyState
+				jqXHR.readyState = status > 0 ? 4 : 0;
+
+				// Determine if successful
+				isSuccess = status >= 200 && status < 300 || status === 304;
+
+				// Get response data
+				if ( responses ) {
+					response = ajaxHandleResponses( s, jqXHR, responses );
+				}
+
+				// Convert no matter what (that way responseXXX fields are always set)
+				response = ajaxConvert( s, response, jqXHR, isSuccess );
+
+				// If successful, handle type chaining
+				if ( isSuccess ) {
+
+					// Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode.
+					if ( s.ifModified ) {
+						modified = jqXHR.getResponseHeader( "Last-Modified" );
+						if ( modified ) {
+							jQuery.lastModified[ cacheURL ] = modified;
+						}
+						modified = jqXHR.getResponseHeader( "etag" );
+						if ( modified ) {
+							jQuery.etag[ cacheURL ] = modified;
+						}
+					}
+
+					// if no content
+					if ( status === 204 || s.type === "HEAD" ) {
+						statusText = "nocontent";
+
+					// if not modified
+					} else if ( status === 304 ) {
+						statusText = "notmodified";
+
+					// If we have data, let's convert it
+					} else {
+						statusText = response.state;
+						success = response.data;
+						error = response.error;
+						isSuccess = !error;
+					}
+				} else {
+
+					// Extract error from statusText and normalize for non-aborts
+					error = statusText;
+					if ( status || !statusText ) {
+						statusText = "error";
+						if ( status < 0 ) {
+							status = 0;
+						}
+					}
+				}
+
+				// Set data for the fake xhr object
+				jqXHR.status = status;
+				jqXHR.statusText = ( nativeStatusText || statusText ) + "";
+
+				// Success/Error
+				if ( isSuccess ) {
+					deferred.resolveWith( callbackContext, [ success, statusText, jqXHR ] );
+				} else {
+					deferred.rejectWith( callbackContext, [ jqXHR, statusText, error ] );
+				}
+
+				// Status-dependent callbacks
+				jqXHR.statusCode( statusCode );
+				statusCode = undefined;
+
+				if ( fireGlobals ) {
+					globalEventContext.trigger( isSuccess ? "ajaxSuccess" : "ajaxError",
+						[ jqXHR, s, isSuccess ? success : error ] );
+				}
+
+				// Complete
+				completeDeferred.fireWith( callbackContext, [ jqXHR, statusText ] );
+
+				if ( fireGlobals ) {
+					globalEventContext.trigger( "ajaxComplete", [ jqXHR, s ] );
+
+					// Handle the global AJAX counter
+					if ( !( --jQuery.active ) ) {
+						jQuery.event.trigger( "ajaxStop" );
+					}
+				}
+			}
+
+			return jqXHR;
+		},
+
+		getJSON: function( url, data, callback ) {
+			return jQuery.get( url, data, callback, "json" );
+		},
+
+		getScript: function( url, callback ) {
+			return jQuery.get( url, undefined, callback, "script" );
+		}
+	} );
+
+	jQuery.each( [ "get", "post" ], function( i, method ) {
+		jQuery[ method ] = function( url, data, callback, type ) {
+
+			// Shift arguments if data argument was omitted
+			if ( jQuery.isFunction( data ) ) {
+				type = type || callback;
+				callback = data;
+				data = undefined;
+			}
+
+			// The url can be an options object (which then must have .url)
+			return jQuery.ajax( jQuery.extend( {
+				url: url,
+				type: method,
+				dataType: type,
+				data: data,
+				success: callback
+			}, jQuery.isPlainObject( url ) && url ) );
+		};
+	} );
+
+
+	jQuery._evalUrl = function( url ) {
+		return jQuery.ajax( {
+			url: url,
+
+			// Make this explicit, since user can override this through ajaxSetup (#11264)
+			type: "GET",
+			dataType: "script",
+			cache: true,
+			async: false,
+			global: false,
+			"throws": true
+		} );
+	};
+
+
+	jQuery.fn.extend( {
+		wrapAll: function( html ) {
+			var wrap;
+
+			if ( this[ 0 ] ) {
+				if ( jQuery.isFunction( html ) ) {
+					html = html.call( this[ 0 ] );
+				}
+
+				// The elements to wrap the target around
+				wrap = jQuery( html, this[ 0 ].ownerDocument ).eq( 0 ).clone( true );
+
+				if ( this[ 0 ].parentNode ) {
+					wrap.insertBefore( this[ 0 ] );
+				}
+
+				wrap.map( function() {
+					var elem = this;
+
+					while ( elem.firstElementChild ) {
+						elem = elem.firstElementChild;
+					}
+
+					return elem;
+				} ).append( this );
+			}
+
+			return this;
+		},
+
+		wrapInner: function( html ) {
+			if ( jQuery.isFunction( html ) ) {
+				return this.each( function( i ) {
+					jQuery( this ).wrapInner( html.call( this, i ) );
+				} );
+			}
+
+			return this.each( function() {
+				var self = jQuery( this ),
+					contents = self.contents();
+
+				if ( contents.length ) {
+					contents.wrapAll( html );
+
+				} else {
+					self.append( html );
+				}
+			} );
+		},
+
+		wrap: function( html ) {
+			var isFunction = jQuery.isFunction( html );
+
+			return this.each( function( i ) {
+				jQuery( this ).wrapAll( isFunction ? html.call( this, i ) : html );
+			} );
+		},
+
+		unwrap: function( selector ) {
+			this.parent( selector ).not( "body" ).each( function() {
+				jQuery( this ).replaceWith( this.childNodes );
+			} );
+			return this;
+		}
+	} );
+
+
+	jQuery.expr.pseudos.hidden = function( elem ) {
+		return !jQuery.expr.pseudos.visible( elem );
+	};
+	jQuery.expr.pseudos.visible = function( elem ) {
+		return !!( elem.offsetWidth || elem.offsetHeight || elem.getClientRects().length );
+	};
+
+
+
+
+	jQuery.ajaxSettings.xhr = function() {
+		try {
+			return new window.XMLHttpRequest();
+		} catch ( e ) {}
+	};
+
+	var xhrSuccessStatus = {
+
+			// File protocol always yields status code 0, assume 200
+			0: 200,
+
+			// Support: IE <=9 only
+			// #1450: sometimes IE returns 1223 when it should be 204
+			1223: 204
+		},
+		xhrSupported = jQuery.ajaxSettings.xhr();
+
+	support.cors = !!xhrSupported && ( "withCredentials" in xhrSupported );
+	support.ajax = xhrSupported = !!xhrSupported;
+
+	jQuery.ajaxTransport( function( options ) {
+		var callback, errorCallback;
+
+		// Cross domain only allowed if supported through XMLHttpRequest
+		if ( support.cors || xhrSupported && !options.crossDomain ) {
+			return {
+				send: function( headers, complete ) {
+					var i,
+						xhr = options.xhr();
+
+					xhr.open(
+						options.type,
+						options.url,
+						options.async,
+						options.username,
+						options.password
+					);
+
+					// Apply custom fields if provided
+					if ( options.xhrFields ) {
+						for ( i in options.xhrFields ) {
+							xhr[ i ] = options.xhrFields[ i ];
+						}
+					}
+
+					// Override mime type if needed
+					if ( options.mimeType && xhr.overrideMimeType ) {
+						xhr.overrideMimeType( options.mimeType );
+					}
+
+					// X-Requested-With header
+					// For cross-domain requests, seeing as conditions for a preflight are
+					// akin to a jigsaw puzzle, we simply never set it to be sure.
+					// (it can always be set on a per-request basis or even using ajaxSetup)
+					// For same-domain requests, won't change header if already provided.
+					if ( !options.crossDomain && !headers[ "X-Requested-With" ] ) {
+						headers[ "X-Requested-With" ] = "XMLHttpRequest";
+					}
+
+					// Set headers
+					for ( i in headers ) {
+						xhr.setRequestHeader( i, headers[ i ] );
+					}
+
+					// Callback
+					callback = function( type ) {
+						return function() {
+							if ( callback ) {
+								callback = errorCallback = xhr.onload =
+									xhr.onerror = xhr.onabort = xhr.onreadystatechange = null;
+
+								if ( type === "abort" ) {
+									xhr.abort();
+								} else if ( type === "error" ) {
+
+									// Support: IE <=9 only
+									// On a manual native abort, IE9 throws
+									// errors on any property access that is not readyState
+									if ( typeof xhr.status !== "number" ) {
+										complete( 0, "error" );
+									} else {
+										complete(
+
+											// File: protocol always yields status 0; see #8605, #14207
+											xhr.status,
+											xhr.statusText
+										);
+									}
+								} else {
+									complete(
+										xhrSuccessStatus[ xhr.status ] || xhr.status,
+										xhr.statusText,
+
+										// Support: IE <=9 only
+										// IE9 has no XHR2 but throws on binary (trac-11426)
+										// For XHR2 non-text, let the caller handle it (gh-2498)
+										( xhr.responseType || "text" ) !== "text"  ||
+										typeof xhr.responseText !== "string" ?
+											{ binary: xhr.response } :
+											{ text: xhr.responseText },
+										xhr.getAllResponseHeaders()
+									);
+								}
+							}
+						};
+					};
+
+					// Listen to events
+					xhr.onload = callback();
+					errorCallback = xhr.onerror = callback( "error" );
+
+					// Support: IE 9 only
+					// Use onreadystatechange to replace onabort
+					// to handle uncaught aborts
+					if ( xhr.onabort !== undefined ) {
+						xhr.onabort = errorCallback;
+					} else {
+						xhr.onreadystatechange = function() {
+
+							// Check readyState before timeout as it changes
+							if ( xhr.readyState === 4 ) {
+
+								// Allow onerror to be called first,
+								// but that will not handle a native abort
+								// Also, save errorCallback to a variable
+								// as xhr.onerror cannot be accessed
+								window.setTimeout( function() {
+									if ( callback ) {
+										errorCallback();
+									}
+								} );
+							}
+						};
+					}
+
+					// Create the abort callback
+					callback = callback( "abort" );
+
+					try {
+
+						// Do send the request (this may raise an exception)
+						xhr.send( options.hasContent && options.data || null );
+					} catch ( e ) {
+
+						// #14683: Only rethrow if this hasn't been notified as an error yet
+						if ( callback ) {
+							throw e;
+						}
+					}
+				},
+
+				abort: function() {
+					if ( callback ) {
+						callback();
+					}
+				}
+			};
+		}
+	} );
+
+
+
+
+	// Prevent auto-execution of scripts when no explicit dataType was provided (See gh-2432)
+	jQuery.ajaxPrefilter( function( s ) {
+		if ( s.crossDomain ) {
+			s.contents.script = false;
+		}
+	} );
+
+	// Install script dataType
+	jQuery.ajaxSetup( {
+		accepts: {
+			script: "text/javascript, application/javascript, " +
+				"application/ecmascript, application/x-ecmascript"
+		},
+		contents: {
+			script: /\b(?:java|ecma)script\b/
+		},
+		converters: {
+			"text script": function( text ) {
+				jQuery.globalEval( text );
+				return text;
+			}
+		}
+	} );
+
+	// Handle cache's special case and crossDomain
+	jQuery.ajaxPrefilter( "script", function( s ) {
+		if ( s.cache === undefined ) {
+			s.cache = false;
+		}
+		if ( s.crossDomain ) {
+			s.type = "GET";
+		}
+	} );
+
+	// Bind script tag hack transport
+	jQuery.ajaxTransport( "script", function( s ) {
+
+		// This transport only deals with cross domain requests
+		if ( s.crossDomain ) {
+			var script, callback;
+			return {
+				send: function( _, complete ) {
+					script = jQuery( "<script>" ).prop( {
+						charset: s.scriptCharset,
+						src: s.url
+					} ).on(
+						"load error",
+						callback = function( evt ) {
+							script.remove();
+							callback = null;
+							if ( evt ) {
+								complete( evt.type === "error" ? 404 : 200, evt.type );
+							}
+						}
+					);
+
+					// Use native DOM manipulation to avoid our domManip AJAX trickery
+					document.head.appendChild( script[ 0 ] );
+				},
+				abort: function() {
+					if ( callback ) {
+						callback();
+					}
+				}
+			};
+		}
+	} );
+
+
+
+
+	var oldCallbacks = [],
+		rjsonp = /(=)\?(?=&|$)|\?\?/;
+
+	// Default jsonp settings
+	jQuery.ajaxSetup( {
+		jsonp: "callback",
+		jsonpCallback: function() {
+			var callback = oldCallbacks.pop() || ( jQuery.expando + "_" + ( nonce++ ) );
+			this[ callback ] = true;
+			return callback;
+		}
+	} );
+
+	// Detect, normalize options and install callbacks for jsonp requests
+	jQuery.ajaxPrefilter( "json jsonp", function( s, originalSettings, jqXHR ) {
+
+		var callbackName, overwritten, responseContainer,
+			jsonProp = s.jsonp !== false && ( rjsonp.test( s.url ) ?
+				"url" :
+				typeof s.data === "string" &&
+					( s.contentType || "" )
+						.indexOf( "application/x-www-form-urlencoded" ) === 0 &&
+					rjsonp.test( s.data ) && "data"
+			);
+
+		// Handle iff the expected data type is "jsonp" or we have a parameter to set
+		if ( jsonProp || s.dataTypes[ 0 ] === "jsonp" ) {
+
+			// Get callback name, remembering preexisting value associated with it
+			callbackName = s.jsonpCallback = jQuery.isFunction( s.jsonpCallback ) ?
+				s.jsonpCallback() :
+				s.jsonpCallback;
+
+			// Insert callback into url or form data
+			if ( jsonProp ) {
+				s[ jsonProp ] = s[ jsonProp ].replace( rjsonp, "$1" + callbackName );
+			} else if ( s.jsonp !== false ) {
+				s.url += ( rquery.test( s.url ) ? "&" : "?" ) + s.jsonp + "=" + callbackName;
+			}
+
+			// Use data converter to retrieve json after script execution
+			s.converters[ "script json" ] = function() {
+				if ( !responseContainer ) {
+					jQuery.error( callbackName + " was not called" );
+				}
+				return responseContainer[ 0 ];
+			};
+
+			// Force json dataType
+			s.dataTypes[ 0 ] = "json";
+
+			// Install callback
+			overwritten = window[ callbackName ];
+			window[ callbackName ] = function() {
+				responseContainer = arguments;
+			};
+
+			// Clean-up function (fires after converters)
+			jqXHR.always( function() {
+
+				// If previous value didn't exist - remove it
+				if ( overwritten === undefined ) {
+					jQuery( window ).removeProp( callbackName );
+
+				// Otherwise restore preexisting value
+				} else {
+					window[ callbackName ] = overwritten;
+				}
+
+				// Save back as free
+				if ( s[ callbackName ] ) {
+
+					// Make sure that re-using the options doesn't screw things around
+					s.jsonpCallback = originalSettings.jsonpCallback;
+
+					// Save the callback name for future use
+					oldCallbacks.push( callbackName );
+				}
+
+				// Call if it was a function and we have a response
+				if ( responseContainer && jQuery.isFunction( overwritten ) ) {
+					overwritten( responseContainer[ 0 ] );
+				}
+
+				responseContainer = overwritten = undefined;
+			} );
+
+			// Delegate to script
+			return "script";
+		}
+	} );
+
+
+
+
+	// Support: Safari 8 only
+	// In Safari 8 documents created via document.implementation.createHTMLDocument
+	// collapse sibling forms: the second one becomes a child of the first one.
+	// Because of that, this security measure has to be disabled in Safari 8.
+	// https://bugs.webkit.org/show_bug.cgi?id=137337
+	support.createHTMLDocument = ( function() {
+		var body = document.implementation.createHTMLDocument( "" ).body;
+		body.innerHTML = "<form></form><form></form>";
+		return body.childNodes.length === 2;
+	} )();
+
+
+	// Argument "data" should be string of html
+	// context (optional): If specified, the fragment will be created in this context,
+	// defaults to document
+	// keepScripts (optional): If true, will include scripts passed in the html string
+	jQuery.parseHTML = function( data, context, keepScripts ) {
+		if ( typeof data !== "string" ) {
+			return [];
+		}
+		if ( typeof context === "boolean" ) {
+			keepScripts = context;
+			context = false;
+		}
+
+		var base, parsed, scripts;
+
+		if ( !context ) {
+
+			// Stop scripts or inline event handlers from being executed immediately
+			// by using document.implementation
+			if ( support.createHTMLDocument ) {
+				context = document.implementation.createHTMLDocument( "" );
+
+				// Set the base href for the created document
+				// so any parsed elements with URLs
+				// are based on the document's URL (gh-2965)
+				base = context.createElement( "base" );
+				base.href = document.location.href;
+				context.head.appendChild( base );
+			} else {
+				context = document;
+			}
+		}
+
+		parsed = rsingleTag.exec( data );
+		scripts = !keepScripts && [];
+
+		// Single tag
+		if ( parsed ) {
+			return [ context.createElement( parsed[ 1 ] ) ];
+		}
+
+		parsed = buildFragment( [ data ], context, scripts );
+
+		if ( scripts && scripts.length ) {
+			jQuery( scripts ).remove();
+		}
+
+		return jQuery.merge( [], parsed.childNodes );
+	};
+
+
+	/**
+	 * Load a url into a page
+	 */
+	jQuery.fn.load = function( url, params, callback ) {
+		var selector, type, response,
+			self = this,
+			off = url.indexOf( " " );
+
+		if ( off > -1 ) {
+			selector = jQuery.trim( url.slice( off ) );
+			url = url.slice( 0, off );
+		}
+
+		// If it's a function
+		if ( jQuery.isFunction( params ) ) {
+
+			// We assume that it's the callback
+			callback = params;
+			params = undefined;
+
+		// Otherwise, build a param string
+		} else if ( params && typeof params === "object" ) {
+			type = "POST";
+		}
+
+		// If we have elements to modify, make the request
+		if ( self.length > 0 ) {
+			jQuery.ajax( {
+				url: url,
+
+				// If "type" variable is undefined, then "GET" method will be used.
+				// Make value of this field explicit since
+				// user can override it through ajaxSetup method
+				type: type || "GET",
+				dataType: "html",
+				data: params
+			} ).done( function( responseText ) {
+
+				// Save response for use in complete callback
+				response = arguments;
+
+				self.html( selector ?
+
+					// If a selector was specified, locate the right elements in a dummy div
+					// Exclude scripts to avoid IE 'Permission Denied' errors
+					jQuery( "<div>" ).append( jQuery.parseHTML( responseText ) ).find( selector ) :
+
+					// Otherwise use the full result
+					responseText );
+
+			// If the request succeeds, this function gets "data", "status", "jqXHR"
+			// but they are ignored because response was set above.
+			// If it fails, this function gets "jqXHR", "status", "error"
+			} ).always( callback && function( jqXHR, status ) {
+				self.each( function() {
+					callback.apply( this, response || [ jqXHR.responseText, status, jqXHR ] );
+				} );
+			} );
+		}
+
+		return this;
+	};
+
+
+
+
+	// Attach a bunch of functions for handling common AJAX events
+	jQuery.each( [
+		"ajaxStart",
+		"ajaxStop",
+		"ajaxComplete",
+		"ajaxError",
+		"ajaxSuccess",
+		"ajaxSend"
+	], function( i, type ) {
+		jQuery.fn[ type ] = function( fn ) {
+			return this.on( type, fn );
+		};
+	} );
+
+
+
+
+	jQuery.expr.pseudos.animated = function( elem ) {
+		return jQuery.grep( jQuery.timers, function( fn ) {
+			return elem === fn.elem;
+		} ).length;
+	};
+
+
+
+
+	/**
+	 * Gets a window from an element
+	 */
+	function getWindow( elem ) {
+		return jQuery.isWindow( elem ) ? elem : elem.nodeType === 9 && elem.defaultView;
+	}
+
+	jQuery.offset = {
+		setOffset: function( elem, options, i ) {
+			var curPosition, curLeft, curCSSTop, curTop, curOffset, curCSSLeft, calculatePosition,
+				position = jQuery.css( elem, "position" ),
+				curElem = jQuery( elem ),
+				props = {};
+
+			// Set position first, in-case top/left are set even on static elem
+			if ( position === "static" ) {
+				elem.style.position = "relative";
+			}
+
+			curOffset = curElem.offset();
+			curCSSTop = jQuery.css( elem, "top" );
+			curCSSLeft = jQuery.css( elem, "left" );
+			calculatePosition = ( position === "absolute" || position === "fixed" ) &&
+				( curCSSTop + curCSSLeft ).indexOf( "auto" ) > -1;
+
+			// Need to be able to calculate position if either
+			// top or left is auto and position is either absolute or fixed
+			if ( calculatePosition ) {
+				curPosition = curElem.position();
+				curTop = curPosition.top;
+				curLeft = curPosition.left;
+
+			} else {
+				curTop = parseFloat( curCSSTop ) || 0;
+				curLeft = parseFloat( curCSSLeft ) || 0;
+			}
+
+			if ( jQuery.isFunction( options ) ) {
+
+				// Use jQuery.extend here to allow modification of coordinates argument (gh-1848)
+				options = options.call( elem, i, jQuery.extend( {}, curOffset ) );
+			}
+
+			if ( options.top != null ) {
+				props.top = ( options.top - curOffset.top ) + curTop;
+			}
+			if ( options.left != null ) {
+				props.left = ( options.left - curOffset.left ) + curLeft;
+			}
+
+			if ( "using" in options ) {
+				options.using.call( elem, props );
+
+			} else {
+				curElem.css( props );
+			}
+		}
+	};
+
+	jQuery.fn.extend( {
+		offset: function( options ) {
+
+			// Preserve chaining for setter
+			if ( arguments.length ) {
+				return options === undefined ?
+					this :
+					this.each( function( i ) {
+						jQuery.offset.setOffset( this, options, i );
+					} );
+			}
+
+			var docElem, win, rect, doc,
+				elem = this[ 0 ];
+
+			if ( !elem ) {
+				return;
+			}
+
+			// Support: IE <=11 only
+			// Running getBoundingClientRect on a
+			// disconnected node in IE throws an error
+			if ( !elem.getClientRects().length ) {
+				return { top: 0, left: 0 };
+			}
+
+			rect = elem.getBoundingClientRect();
+
+			// Make sure element is not hidden (display: none)
+			if ( rect.width || rect.height ) {
+				doc = elem.ownerDocument;
+				win = getWindow( doc );
+				docElem = doc.documentElement;
+
+				return {
+					top: rect.top + win.pageYOffset - docElem.clientTop,
+					left: rect.left + win.pageXOffset - docElem.clientLeft
+				};
+			}
+
+			// Return zeros for disconnected and hidden elements (gh-2310)
+			return rect;
+		},
+
+		position: function() {
+			if ( !this[ 0 ] ) {
+				return;
+			}
+
+			var offsetParent, offset,
+				elem = this[ 0 ],
+				parentOffset = { top: 0, left: 0 };
+
+			// Fixed elements are offset from window (parentOffset = {top:0, left: 0},
+			// because it is its only offset parent
+			if ( jQuery.css( elem, "position" ) === "fixed" ) {
+
+				// Assume getBoundingClientRect is there when computed position is fixed
+				offset = elem.getBoundingClientRect();
+
+			} else {
+
+				// Get *real* offsetParent
+				offsetParent = this.offsetParent();
+
+				// Get correct offsets
+				offset = this.offset();
+				if ( !jQuery.nodeName( offsetParent[ 0 ], "html" ) ) {
+					parentOffset = offsetParent.offset();
+				}
+
+				// Add offsetParent borders
+				parentOffset = {
+					top: parentOffset.top + jQuery.css( offsetParent[ 0 ], "borderTopWidth", true ),
+					left: parentOffset.left + jQuery.css( offsetParent[ 0 ], "borderLeftWidth", true )
+				};
+			}
+
+			// Subtract parent offsets and element margins
+			return {
+				top: offset.top - parentOffset.top - jQuery.css( elem, "marginTop", true ),
+				left: offset.left - parentOffset.left - jQuery.css( elem, "marginLeft", true )
+			};
+		},
+
+		// This method will return documentElement in the following cases:
+		// 1) For the element inside the iframe without offsetParent, this method will return
+		//    documentElement of the parent window
+		// 2) For the hidden or detached element
+		// 3) For body or html element, i.e. in case of the html node - it will return itself
+		//
+		// but those exceptions were never presented as a real life use-cases
+		// and might be considered as more preferable results.
+		//
+		// This logic, however, is not guaranteed and can change at any point in the future
+		offsetParent: function() {
+			return this.map( function() {
+				var offsetParent = this.offsetParent;
+
+				while ( offsetParent && jQuery.css( offsetParent, "position" ) === "static" ) {
+					offsetParent = offsetParent.offsetParent;
+				}
+
+				return offsetParent || documentElement;
+			} );
+		}
+	} );
+
+	// Create scrollLeft and scrollTop methods
+	jQuery.each( { scrollLeft: "pageXOffset", scrollTop: "pageYOffset" }, function( method, prop ) {
+		var top = "pageYOffset" === prop;
+
+		jQuery.fn[ method ] = function( val ) {
+			return access( this, function( elem, method, val ) {
+				var win = getWindow( elem );
+
+				if ( val === undefined ) {
+					return win ? win[ prop ] : elem[ method ];
+				}
+
+				if ( win ) {
+					win.scrollTo(
+						!top ? val : win.pageXOffset,
+						top ? val : win.pageYOffset
+					);
+
+				} else {
+					elem[ method ] = val;
+				}
+			}, method, val, arguments.length );
+		};
+	} );
+
+	// Support: Safari <=7 - 9.1, Chrome <=37 - 49
+	// Add the top/left cssHooks using jQuery.fn.position
+	// Webkit bug: https://bugs.webkit.org/show_bug.cgi?id=29084
+	// Blink bug: https://bugs.chromium.org/p/chromium/issues/detail?id=589347
+	// getComputedStyle returns percent when specified for top/left/bottom/right;
+	// rather than make the css module depend on the offset module, just check for it here
+	jQuery.each( [ "top", "left" ], function( i, prop ) {
+		jQuery.cssHooks[ prop ] = addGetHookIf( support.pixelPosition,
+			function( elem, computed ) {
+				if ( computed ) {
+					computed = curCSS( elem, prop );
+
+					// If curCSS returns percentage, fallback to offset
+					return rnumnonpx.test( computed ) ?
+						jQuery( elem ).position()[ prop ] + "px" :
+						computed;
+				}
+			}
+		);
+	} );
+
+
+	// Create innerHeight, innerWidth, height, width, outerHeight and outerWidth methods
+	jQuery.each( { Height: "height", Width: "width" }, function( name, type ) {
+		jQuery.each( { padding: "inner" + name, content: type, "": "outer" + name },
+			function( defaultExtra, funcName ) {
+
+			// Margin is only for outerHeight, outerWidth
+			jQuery.fn[ funcName ] = function( margin, value ) {
+				var chainable = arguments.length && ( defaultExtra || typeof margin !== "boolean" ),
+					extra = defaultExtra || ( margin === true || value === true ? "margin" : "border" );
+
+				return access( this, function( elem, type, value ) {
+					var doc;
+
+					if ( jQuery.isWindow( elem ) ) {
+
+						// $( window ).outerWidth/Height return w/h including scrollbars (gh-1729)
+						return funcName.indexOf( "outer" ) === 0 ?
+							elem[ "inner" + name ] :
+							elem.document.documentElement[ "client" + name ];
+					}
+
+					// Get document width or height
+					if ( elem.nodeType === 9 ) {
+						doc = elem.documentElement;
+
+						// Either scroll[Width/Height] or offset[Width/Height] or client[Width/Height],
+						// whichever is greatest
+						return Math.max(
+							elem.body[ "scroll" + name ], doc[ "scroll" + name ],
+							elem.body[ "offset" + name ], doc[ "offset" + name ],
+							doc[ "client" + name ]
+						);
+					}
+
+					return value === undefined ?
+
+						// Get width or height on the element, requesting but not forcing parseFloat
+						jQuery.css( elem, type, extra ) :
+
+						// Set width or height on the element
+						jQuery.style( elem, type, value, extra );
+				}, type, chainable ? margin : undefined, chainable );
+			};
+		} );
+	} );
+
+
+	jQuery.fn.extend( {
+
+		bind: function( types, data, fn ) {
+			return this.on( types, null, data, fn );
+		},
+		unbind: function( types, fn ) {
+			return this.off( types, null, fn );
+		},
+
+		delegate: function( selector, types, data, fn ) {
+			return this.on( types, selector, data, fn );
+		},
+		undelegate: function( selector, types, fn ) {
+
+			// ( namespace ) or ( selector, types [, fn] )
+			return arguments.length === 1 ?
+				this.off( selector, "**" ) :
+				this.off( types, selector || "**", fn );
+		}
+	} );
+
+	jQuery.parseJSON = JSON.parse;
+
+
+
+
+	// Register as a named AMD module, since jQuery can be concatenated with other
+	// files that may use define, but not via a proper concatenation script that
+	// understands anonymous AMD modules. A named AMD is safest and most robust
+	// way to register. Lowercase jquery is used because AMD module names are
+	// derived from file names, and jQuery is normally delivered in a lowercase
+	// file name. Do this after creating the global so that if an AMD module wants
+	// to call noConflict to hide this version of jQuery, it will work.
+
+	// Note that for maximum portability, libraries that are not jQuery should
+	// declare themselves as anonymous modules, and avoid setting a global if an
+	// AMD loader is present. jQuery is a special case. For more information, see
+	// https://github.com/jrburke/requirejs/wiki/Updating-existing-libraries#wiki-anon
+
+	if ( true ) {
+		!(__WEBPACK_AMD_DEFINE_ARRAY__ = [], __WEBPACK_AMD_DEFINE_RESULT__ = function() {
+			return jQuery;
+		}.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));
+	}
+
+
+
+
+
+	var
+
+		// Map over jQuery in case of overwrite
+		_jQuery = window.jQuery,
+
+		// Map over the $ in case of overwrite
+		_$ = window.$;
+
+	jQuery.noConflict = function( deep ) {
+		if ( window.$ === jQuery ) {
+			window.$ = _$;
+		}
+
+		if ( deep && window.jQuery === jQuery ) {
+			window.jQuery = _jQuery;
+		}
+
+		return jQuery;
+	};
+
+	// Expose jQuery and $ identifiers, even in AMD
+	// (#7102#comment:10, https://github.com/jquery/jquery/pull/557)
+	// and CommonJS for browser emulators (#13566)
+	if ( !noGlobal ) {
+		window.jQuery = window.$ = jQuery;
+	}
+
+
+	return jQuery;
+	} );
+
+
+/***/ }
+/******/ ])
+});
+;
\ No newline at end of file
diff --git a/node_modules/fetch-jsonp/.eslintrc b/node_modules/fetch-jsonp/.eslintrc
new file mode 100644
index 0000000..099c4f2
--- /dev/null
+++ b/node_modules/fetch-jsonp/.eslintrc
@@ -0,0 +1,21 @@
+root: true
+
+env:
+  es6: true
+  node: true
+  browser: true
+  mocha: true
+
+ecmaFeatures:
+  modules: true
+
+extends:
+  "airbnb"
+
+rules:
+  quotes: 0
+  func-names: 0
+  comma-dangle: 0
+  no-new-func: 0
+  no-eq-null: 0
+  no-param-reassign: 0
diff --git a/node_modules/fetch-jsonp/.npmignore b/node_modules/fetch-jsonp/.npmignore
new file mode 100644
index 0000000..5221c38
--- /dev/null
+++ b/node_modules/fetch-jsonp/.npmignore
@@ -0,0 +1,7 @@
+.DS_Store
+*.log
+node_modules
+src
+test
+examples
+coverage
diff --git a/node_modules/fetch-jsonp/.travis.yml b/node_modules/fetch-jsonp/.travis.yml
new file mode 100644
index 0000000..c42701f
--- /dev/null
+++ b/node_modules/fetch-jsonp/.travis.yml
@@ -0,0 +1,3 @@
+language: node_js
+node_js:
+  - "iojs"
diff --git a/node_modules/fetch-jsonp/HISTORY.md b/node_modules/fetch-jsonp/HISTORY.md
new file mode 100644
index 0000000..ad36ba0
--- /dev/null
+++ b/node_modules/fetch-jsonp/HISTORY.md
@@ -0,0 +1,38 @@
+1.0.6 / 2017-2-3
+==================
+* update typescript config
+
+1.0.5 / 2016-12-29
+==================
+* update typescript support
+
+1.0.4 / 2016-12-23
+==================
+* add typescript support
+
+1.0.3 / 2016-12-04
+==================
+* add examples index-ie8.html
+* remove `es6-promise` dependent
+
+1.0.2 / 2016-09-26
+==================
+* Use original url when Request error
+
+1.0.1 / 2016-08-14
+==================
+* Format code
+* Update Readme
+
+1.0.0 / 2015-11-19
+==================
+* Remove Bower support
+* Add jsonpCallback and jsonpCallbackFunction as options
+
+0.9.2 / 2015-08-11
+==================
+* Remove global export of fetchJsonp
+
+0.9.1 / 2015-08-11
+==================
+* Update removeScript fix legacy IE
diff --git a/node_modules/fetch-jsonp/MAINTAINING.md b/node_modules/fetch-jsonp/MAINTAINING.md
new file mode 100644
index 0000000..77129ca
--- /dev/null
+++ b/node_modules/fetch-jsonp/MAINTAINING.md
@@ -0,0 +1,26 @@
+# Maintaining
+
+## Releasing a new version
+
+This project follows [semver](http://semver.org/). So if you are making a bug
+fix, only increment the patch level "1.0.x". If any new files are added, a
+minor version "1.x.x" bump is in order.
+
+### Make a release commit
+
+To prepare the release commit:
+
+1. Change the npm [package.json](https://github.com/camsong/fetch-jsonp/blob/master/package.json)
+`version` value to match.
+2. Make a single commit with the description as "Fetch JSONP 1.x.x".
+3. Finally, tag the commit with `v1.x.x`.
+
+```
+$ git pull
+$ vim package.json
+$ git add package.json
+$ git commit -m "Fetch JSONP 1.x.x"
+$ git tag v1.x.x
+$ git push
+$ git push --tags
+```
diff --git a/node_modules/fetch-jsonp/README.md b/node_modules/fetch-jsonp/README.md
new file mode 100644
index 0000000..42d4802
--- /dev/null
+++ b/node_modules/fetch-jsonp/README.md
@@ -0,0 +1,91 @@
+# Fetch JSONP [![Build Status](https://travis-ci.org/camsong/fetch-jsonp.svg)](https://travis-ci.org/camsong/fetch-jsonp) [![npm version](https://badge.fury.io/js/fetch-jsonp.svg)](http://badge.fury.io/js/fetch-jsonp) [![npm downloads](https://img.shields.io/npm/dm/fetch-jsonp.svg?style=flat-square)](https://www.npmjs.com/package/fetch-jsonp)
+
+JSONP is NOT supported in standard Fetch API, https://fetch.spec.whatwg.org.
+fetch-jsonp provides you same API to fetch JSONP like naive Fetch, also comes
+with global `fetchJsonp` function.
+
+If you need a `fetch` polyfill for old browsers, try [github/fetch](http://github.com/github/fetch).
+
+## Installation
+
+You can install with `npm`.
+
+```
+npm install fetch-jsonp
+```
+
+## Promise Polyfill for IE
+
+IE8/9/10/11 does not support [ES6 Promise](https://tc39.github.io/ecma262/#sec-promise-constructor), run this to polyfill the global environment at the beginning of your application.
+
+```js
+require('es6-promise').polyfill();
+```
+
+## Usage
+
+The `fetch-jsonp` function supports any HTTP method. We'll focus on GET and POST
+example requests.
+
+### Fetch JSONP in simple way
+
+```javascript
+fetchJsonp('/users.jsonp')
+  .then(function(response) {
+    return response.json()
+  }).then(function(json) {
+    console.log('parsed json', json)
+  }).catch(function(ex) {
+    console.log('parsing failed', ex)
+  })
+```
+
+### Set JSONP callback name, default is 'callback'
+
+```javascript
+fetchJsonp('/users.jsonp', {
+    jsonpCallback: 'custom_callback'
+  })
+  .then(function(response) {
+    return response.json()
+  }).then(function(json) {
+    console.log('parsed json', json)
+  }).catch(function(ex) {
+    console.log('parsing failed', ex)
+  })
+```
+
+### Set JSONP request timeout, default is 5000ms
+
+```javascript
+fetchJsonp('/users.jsonp', {
+    timeout: 3000,
+    jsonpCallback: 'custom_callback'
+  })
+  .then(function(response) {
+    return response.json()
+  }).then(function(json) {
+    console.log('parsed json', json)
+  }).catch(function(ex) {
+    console.log('parsing failed', ex)
+  })
+```
+
+### Caveats
+
+You need to call `.then(function(response) { return response.json(); })` in order
+to keep consistent with Fetch API.
+
+## Browser Support
+
+![Chrome](https://raw.github.com/alrra/browser-logos/master/chrome/chrome_48x48.png) | ![Firefox](https://raw.github.com/alrra/browser-logos/master/firefox/firefox_48x48.png) | ![IE](https://raw.github.com/alrra/browser-logos/master/internet-explorer/internet-explorer_48x48.png) | ![Opera](https://raw.github.com/alrra/browser-logos/master/opera/opera_48x48.png) | ![Safari](https://raw.github.com/alrra/browser-logos/master/safari/safari_48x48.png)
+--- | --- | --- | --- | --- |
+Latest ✔ | Latest ✔ | 8+ ✔ | Latest ✔ | 6.1+ ✔ |
+
+# License
+
+MIT
+
+# Acknowledgement
+
+Thanks to [github/fetch](https://github.com/github/fetch) for bring Fetch to old browsers.
diff --git a/node_modules/fetch-jsonp/build/fetch-jsonp.js b/node_modules/fetch-jsonp/build/fetch-jsonp.js
new file mode 100644
index 0000000..0581787
--- /dev/null
+++ b/node_modules/fetch-jsonp/build/fetch-jsonp.js
@@ -0,0 +1,108 @@
+(function (global, factory) {
+  if (typeof define === 'function' && define.amd) {
+    define(['exports', 'module'], factory);
+  } else if (typeof exports !== 'undefined' && typeof module !== 'undefined') {
+    factory(exports, module);
+  } else {
+    var mod = {
+      exports: {}
+    };
+    factory(mod.exports, mod);
+    global.fetchJsonp = mod.exports;
+  }
+})(this, function (exports, module) {
+  'use strict';
+
+  var defaultOptions = {
+    timeout: 5000,
+    jsonpCallback: 'callback',
+    jsonpCallbackFunction: null
+  };
+
+  function generateCallbackFunction() {
+    return 'jsonp_' + Date.now() + '_' + Math.ceil(Math.random() * 100000);
+  }
+
+  // Known issue: Will throw 'Uncaught ReferenceError: callback_*** is not defined'
+  // error if request timeout
+  function clearFunction(functionName) {
+    // IE8 throws an exception when you try to delete a property on window
+    // http://stackoverflow.com/a/1824228/751089
+    try {
+      delete window[functionName];
+    } catch (e) {
+      window[functionName] = undefined;
+    }
+  }
+
+  function removeScript(scriptId) {
+    var script = document.getElementById(scriptId);
+    document.getElementsByTagName('head')[0].removeChild(script);
+  }
+
+  function fetchJsonp(_url) {
+    var options = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1];
+
+    // to avoid param reassign
+    var url = _url;
+    var timeout = options.timeout || defaultOptions.timeout;
+    var jsonpCallback = options.jsonpCallback || defaultOptions.jsonpCallback;
+
+    var timeoutId = undefined;
+
+    return new Promise(function (resolve, reject) {
+      var callbackFunction = options.jsonpCallbackFunction || generateCallbackFunction();
+      var scriptId = jsonpCallback + '_' + callbackFunction;
+
+      window[callbackFunction] = function (response) {
+        resolve({
+          ok: true,
+          // keep consistent with fetch API
+          json: function json() {
+            return Promise.resolve(response);
+          }
+        });
+
+        if (timeoutId) clearTimeout(timeoutId);
+
+        removeScript(scriptId);
+
+        clearFunction(callbackFunction);
+      };
+
+      // Check if the user set their own params, and if not add a ? to start a list of params
+      url += url.indexOf('?') === -1 ? '?' : '&';
+
+      var jsonpScript = document.createElement('script');
+      jsonpScript.setAttribute('src', '' + url + jsonpCallback + '=' + callbackFunction);
+      jsonpScript.id = scriptId;
+      document.getElementsByTagName('head')[0].appendChild(jsonpScript);
+
+      timeoutId = setTimeout(function () {
+        reject(new Error('JSONP request to ' + _url + ' timed out'));
+
+        clearFunction(callbackFunction);
+        removeScript(scriptId);
+      }, timeout);
+    });
+  }
+
+  // export as global function
+  /*
+  let local;
+  if (typeof global !== 'undefined') {
+    local = global;
+  } else if (typeof self !== 'undefined') {
+    local = self;
+  } else {
+    try {
+      local = Function('return this')();
+    } catch (e) {
+      throw new Error('polyfill failed because global object is unavailable in this environment');
+    }
+  }
+  local.fetchJsonp = fetchJsonp;
+  */
+
+  module.exports = fetchJsonp;
+});
\ No newline at end of file
diff --git a/node_modules/fetch-jsonp/index.d.ts b/node_modules/fetch-jsonp/index.d.ts
new file mode 100644
index 0000000..aac476e
--- /dev/null
+++ b/node_modules/fetch-jsonp/index.d.ts
@@ -0,0 +1,17 @@
+declare function fetchJsonp(url: string, options?: fetchJsonp.Options): Promise<fetchJsonp.Response>;
+
+declare namespace fetchJsonp {
+  interface Options {
+    timeout?: number;
+    jsonpCallback?: string;
+    jsonpCallbackFunction?: string;
+  }
+
+  interface Response {
+    json(): Promise<any>;
+    json<T>(): Promise<T>;
+    ok: boolean;
+  }
+}
+
+export = fetchJsonp;
diff --git a/node_modules/fetch-jsonp/package.json b/node_modules/fetch-jsonp/package.json
new file mode 100644
index 0000000..46f14f7
--- /dev/null
+++ b/node_modules/fetch-jsonp/package.json
@@ -0,0 +1,93 @@
+{
+  "_args": [
+    [
+      "fetch-jsonp",
+      "D:\\work\\McuClient"
+    ]
+  ],
+  "_from": "fetch-jsonp@latest",
+  "_id": "fetch-jsonp@1.0.6",
+  "_inCache": true,
+  "_installable": true,
+  "_location": "/fetch-jsonp",
+  "_nodeVersion": "5.7.1",
+  "_npmOperationalInternal": {
+    "host": "packages-12-west.internal.npmjs.com",
+    "tmp": "tmp/fetch-jsonp-1.0.6.tgz_1486087842804_0.46444737794809043"
+  },
+  "_npmUser": {
+    "email": "neosoyn@gmail.com",
+    "name": "camsong"
+  },
+  "_npmVersion": "3.6.0",
+  "_phantomChildren": {},
+  "_requested": {
+    "name": "fetch-jsonp",
+    "raw": "fetch-jsonp",
+    "rawSpec": "",
+    "scope": null,
+    "spec": "latest",
+    "type": "tag"
+  },
+  "_requiredBy": [
+    "#USER"
+  ],
+  "_resolved": "https://registry.npmjs.org/fetch-jsonp/-/fetch-jsonp-1.0.6.tgz",
+  "_shasum": "8d2ae174ed14108292f025f43fa07d2078de6736",
+  "_shrinkwrap": null,
+  "_spec": "fetch-jsonp",
+  "_where": "D:\\work\\McuClient",
+  "author": {
+    "name": "Cam Song"
+  },
+  "bugs": {
+    "url": "https://github.com/camsong/fetch-jsonp/issues"
+  },
+  "dependencies": {},
+  "description": "Fetch JSONP like a boss using Fetch API",
+  "devDependencies": {
+    "babel": "^5.8.21",
+    "babel-core": "^5.8.21",
+    "babel-eslint": "^4.0.5",
+    "chai": "^3.2.0",
+    "eslint": "^1.1.0",
+    "eslint-config-airbnb": "^0.0.7",
+    "eslint-plugin-react": "^3.2.1",
+    "mocha": "^2.2.5"
+  },
+  "directories": {},
+  "dist": {
+    "shasum": "8d2ae174ed14108292f025f43fa07d2078de6736",
+    "tarball": "https://registry.npmjs.org/fetch-jsonp/-/fetch-jsonp-1.0.6.tgz"
+  },
+  "gitHead": "3b1be53776fa36a548fbf2aa16063c27ca318661",
+  "homepage": "https://github.com/camsong/fetch-jsonp#readme",
+  "keywords": [
+    "fetch",
+    "jsonp",
+    "github fetch",
+    "ajax"
+  ],
+  "license": "MIT",
+  "main": "build/fetch-jsonp.js",
+  "maintainers": [
+    {
+      "email": "neosoyn@gmail.com",
+      "name": "camsong"
+    }
+  ],
+  "name": "fetch-jsonp",
+  "optionalDependencies": {},
+  "readme": "ERROR: No README data found!",
+  "repository": {
+    "type": "git",
+    "url": "git+https://github.com/camsong/fetch-jsonp.git"
+  },
+  "scripts": {
+    "build": "babel src/ --modules umd --out-dir build",
+    "clean": "rm -rf build",
+    "lint": "eslint src/ test/",
+    "test": "mocha --compilers js:babel/register --recursive --ui bdd --reporter spec"
+  },
+  "version": "1.0.6"
+}
diff --git a/node_modules/iphunter/.npmignore b/node_modules/iphunter/.npmignore
new file mode 100644
index 0000000..f1fd89a
--- /dev/null
+++ b/node_modules/iphunter/.npmignore
@@ -0,0 +1,16 @@
+# exclude all
+/*
+
+# project structure
+!/src/
+!/etc/
+!/doc/
+!/test/
+!/dist/
+
+# project files
+!.gitignore
+!README.md
+!package.json
+!LICENSE
+!webpack.config.umd.js
diff --git a/node_modules/iphunter/README.md b/node_modules/iphunter/README.md
new file mode 100644
index 0000000..d6a3e65
--- /dev/null
+++ b/node_modules/iphunter/README.md
@@ -0,0 +1,28 @@
+iphunter 
+
+to hunt a fatest http response.
+
+# usage
+
+iphunter(iplist,check_call_back[,timeout]);
+
+```javascript
+
+import iphunter from 'iphunter';
+
+iphunter([
+  '192.168.99.199',
+  'baidu.com',
+  'aliyun.com',
+  'qq.com',
+  '192.168.99.100',
+  '127.0.0.0:8080',
+  '192.168.1.48:8080',
+  'localhost:8080'
+], function (fatest_ip_response) {
+  if(!fatest_ip_response) return console.error('nothing!');
+  
+  console.log('done -> ', fatest_ip_response);
+},3000)
+
+```
diff --git a/node_modules/iphunter/dist/main.html b/node_modules/iphunter/dist/main.html
new file mode 100644
index 0000000..cf939fb
--- /dev/null
+++ b/node_modules/iphunter/dist/main.html
@@ -0,0 +1 @@
+<!DOCTYPE html><html><head><meta charset="UTF-8"><title>UMD PLAYGROUND</title></head><body><div id="stage"></div><script type="text/javascript" src="main.js"></script></body></html>
\ No newline at end of file
diff --git a/node_modules/iphunter/dist/main.js b/node_modules/iphunter/dist/main.js
new file mode 100644
index 0000000..fd0ef89
--- /dev/null
+++ b/node_modules/iphunter/dist/main.js
@@ -0,0 +1 @@
+!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.iphunter=t():e.iphunter=t()}(this,function(){return function(e){function t(i){if(n[i])return n[i].exports;var r=n[i]={exports:{},id:i,loaded:!1};return e[i].call(r.exports,r,r.exports,t),r.loaded=!0,r.exports}var n={};return t.m=e,t.c=n,t.p="",t(0)}([function(e,t,n){e.exports=n(1)},function(e,t){"use strict";function n(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function i(e,t){var n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:3e3;if(!(e&&e.length&&t))throw new Error("ips and callback are required.");new o(e,t,n)}Object.defineProperty(t,"__esModule",{value:!0});var r=function(){function e(e,t){for(var n=0;n<t.length;n++){var i=t[n];i.enumerable=i.enumerable||!1,i.configurable=!0,"value"in i&&(i.writable=!0),Object.defineProperty(e,i.key,i)}}return function(t,n,i){return n&&e(t.prototype,n),i&&e(t,i),t}}();t.default=i;var o=function(){function e(t,i,r){n(this,e),this.ip="",this.ipcallback=i,this.timeoutId=null,this.reqsCache=[];for(var o=0;o<t.length;o++)this.reqsCache.push(this.send(t[o],r-10));this.timeoutId=setTimeout(this.notify.bind(this),r)}return r(e,[{key:"clearAll",value:function(){this.reqsCache&&this.reqsCache.length&&this.reqsCache.forEach(function(e){e.abort()}),clearTimeout(this.timeoutId),this.ip="",this.ipcallback=null,this.timeoutId=null,this.reqsCache=[]}},{key:"clearReq",value:function(e){this.reqsCache.splice(this.reqsCache.indexOf(e),1)}},{key:"notify",value:function(){this.ipcallback(this.ip),this.clearAll()}},{key:"send",value:function(e,t){var n=this,i=new XMLHttpRequest;return i.open("HEAD","//"+e+"/?_="+Date.now()),i.timeout=t,i.onload=function(){n.ip=e,n.clearReq(i),i.onload=null,n.notify()},i.ontimeout=function(){n.clearReq(i),i.ontimeout=null},i.onerror=function(){n.clearReq(i),i.onerror=null},i.onabort=function(){n.clearReq(i),i.onabort=null},i.send(),i}}]),e}();(function(){"undefined"!=typeof __REACT_HOT_LOADER__&&(__REACT_HOT_LOADER__.register(o,"IpHunter","/Users/AlexWang/ws/iphunter/src/main.js"),__REACT_HOT_LOADER__.register(i,"check","/Users/AlexWang/ws/iphunter/src/main.js"))})()}])});
\ No newline at end of file
diff --git a/node_modules/iphunter/dist/test.html b/node_modules/iphunter/dist/test.html
new file mode 100644
index 0000000..7b00d78
--- /dev/null
+++ b/node_modules/iphunter/dist/test.html
@@ -0,0 +1 @@
+<!DOCTYPE html><html><head><meta charset="UTF-8"><title>UMD PLAYGROUND</title></head><body><div id="stage"></div><script type="text/javascript" src="test.js"></script></body></html>
\ No newline at end of file
diff --git a/node_modules/iphunter/dist/test.js b/node_modules/iphunter/dist/test.js
new file mode 100644
index 0000000..4ebe57f
--- /dev/null
+++ b/node_modules/iphunter/dist/test.js
@@ -0,0 +1 @@
+!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.iphunter=t():e.iphunter=t()}(this,function(){return function(e){function t(o){if(n[o])return n[o].exports;var i=n[o]={exports:{},id:o,loaded:!1};return e[o].call(i.exports,i,i.exports,t),i.loaded=!0,i.exports}var n={};return t.m=e,t.c=n,t.p="",t(0)}([function(e,t,n){e.exports=n(2)},function(e,t){"use strict";function n(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function o(e,t){var n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:3e3;if(!(e&&e.length&&t))throw new Error("ips and callback are required.");new r(e,t,n)}Object.defineProperty(t,"__esModule",{value:!0});var i=function(){function e(e,t){for(var n=0;n<t.length;n++){var o=t[n];o.enumerable=o.enumerable||!1,o.configurable=!0,"value"in o&&(o.writable=!0),Object.defineProperty(e,o.key,o)}}return function(t,n,o){return n&&e(t.prototype,n),o&&e(t,o),t}}();t.default=o;var r=function(){function e(t,o,i){n(this,e),this.ip="",this.ipcallback=o,this.timeoutId=null,this.reqsCache=[];for(var r=0;r<t.length;r++)this.reqsCache.push(this.send(t[r],i-10));this.timeoutId=setTimeout(this.notify.bind(this),i)}return i(e,[{key:"clearAll",value:function(){this.reqsCache&&this.reqsCache.length&&this.reqsCache.forEach(function(e){e.abort()}),clearTimeout(this.timeoutId),this.ip="",this.ipcallback=null,this.timeoutId=null,this.reqsCache=[]}},{key:"clearReq",value:function(e){this.reqsCache.splice(this.reqsCache.indexOf(e),1)}},{key:"notify",value:function(){this.ipcallback(this.ip),this.clearAll()}},{key:"send",value:function(e,t){var n=this,o=new XMLHttpRequest;return o.open("HEAD","//"+e+"/?_="+Date.now()),o.timeout=t,o.onload=function(){n.ip=e,n.clearReq(o),o.onload=null,n.notify()},o.ontimeout=function(){n.clearReq(o),o.ontimeout=null},o.onerror=function(){n.clearReq(o),o.onerror=null},o.onabort=function(){n.clearReq(o),o.onabort=null},o.send(),o}}]),e}();(function(){"undefined"!=typeof __REACT_HOT_LOADER__&&(__REACT_HOT_LOADER__.register(r,"IpHunter","/Users/AlexWang/ws/iphunter/src/main.js"),__REACT_HOT_LOADER__.register(o,"check","/Users/AlexWang/ws/iphunter/src/main.js"))})()},function(e,t,n){"use strict";function o(e){return e&&e.__esModule?e:{default:e}}var i=n(1),r=o(i);(0,r.default)(["192.168.99.199","baidu.com","aliyun.com","qq.com","192.168.99.100","127.0.0.0:8080","192.168.1.48:8080","localhost:8080"],function(e){console.log("done -> ",e)});(function(){"undefined"==typeof __REACT_HOT_LOADER__})()}])});
\ No newline at end of file
diff --git a/node_modules/iphunter/etc/umd.template.html b/node_modules/iphunter/etc/umd.template.html
new file mode 100644
index 0000000..74748e9
--- /dev/null
+++ b/node_modules/iphunter/etc/umd.template.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <meta charset="UTF-8">
+  <title>UMD PLAYGROUND</title>
+</head>
+
+<body>
+  <div id="stage"></div>
+</body>
+
+</html>
diff --git a/node_modules/iphunter/package.json b/node_modules/iphunter/package.json
new file mode 100644
index 0000000..7e81a36
--- /dev/null
+++ b/node_modules/iphunter/package.json
@@ -0,0 +1,82 @@
+{
+  "_args": [
+    [
+      "iphunter",
+      "D:\\work\\McuClient"
+    ]
+  ],
+  "_from": "iphunter@latest",
+  "_id": "iphunter@1.0.6",
+  "_inCache": true,
+  "_installable": true,
+  "_location": "/iphunter",
+  "_nodeVersion": "6.9.1",
+  "_npmOperationalInternal": {
+    "host": "packages-12-west.internal.npmjs.com",
+    "tmp": "tmp/iphunter-1.0.6.tgz_1488779370023_0.8911087329033762"
+  },
+  "_npmUser": {
+    "email": "1669499355@qq.com",
+    "name": "xinwangwang"
+  },
+  "_npmVersion": "3.10.8",
+  "_phantomChildren": {},
+  "_requested": {
+    "name": "iphunter",
+    "raw": "iphunter",
+    "rawSpec": "",
+    "scope": null,
+    "spec": "latest",
+    "type": "tag"
+  },
+  "_requiredBy": [
+    "#USER"
+  ],
+  "_resolved": "https://registry.npmjs.org/iphunter/-/iphunter-1.0.6.tgz",
+  "_shasum": "6b7559d73002bd8cc93d558a604d64518d180840",
+  "_shrinkwrap": null,
+  "_spec": "iphunter",
+  "_where": "D:\\work\\McuClient",
+  "author": {
+    "name": "AlexWang"
+  },
+  "dependencies": {},
+  "description": "to hunt a fatest http response.",
+  "devDependencies": {},
+  "directories": {
+    "doc": "doc",
+    "test": "test"
+  },
+  "dist": {
+    "shasum": "6b7559d73002bd8cc93d558a604d64518d180840",
+    "tarball": "https://registry.npmjs.org/iphunter/-/iphunter-1.0.6.tgz"
+  },
+  "gitHead": "c9958f825f514736bd0451f632c227db4bf0a7b7",
+  "keywords": [
+    "ipcheck"
+  ],
+  "license": "MIT",
+  "main": "dist/main.js",
+  "maintainers": [
+    {
+      "email": "1669499355@qq.com",
+      "name": "xinwangwang"
+    }
+  ],
+  "name": "iphunter",
+  "optionalDependencies": {},
+  "readme": "ERROR: No README data found!",
+  "scripts": {
+    "test": "test/index.js"
+  },
+  "version": "1.0.6",
+  "wbp": {
+    "build": "dist/",
+    "entries": {
+      "main": "./src/main.js",
+      "test": "./test/test.js"
+    },
+    "project": "umd",
+    "source": "src/"
+  }
+}
diff --git a/node_modules/iphunter/src/main.js b/node_modules/iphunter/src/main.js
new file mode 100644
index 0000000..0399baf
--- /dev/null
+++ b/node_modules/iphunter/src/main.js
@@ -0,0 +1,73 @@
+class IpHunter {
+  constructor(ips, callback, timeout) {
+    this.ip = '';
+    this.ipcallback = callback;
+    this.timeoutId = null;
+    this.reqsCache = [];
+
+    for (let i = 0; i < ips.length; i++) {
+      this.reqsCache.push(this.send(ips[i], timeout - 10));
+    }
+    this.timeoutId = setTimeout(this.notify.bind(this), timeout);
+  }
+
+  clearAll() {
+    if (this.reqsCache && this.reqsCache.length) {
+      this.reqsCache.forEach((req) => {
+        req.abort();
+      })
+    }
+    clearTimeout(this.timeoutId);
+    this.ip = '';
+    this.ipcallback = null;
+    this.timeoutId = null;
+    this.reqsCache = [];
+  }
+
+  clearReq(req) {
+    this.reqsCache.splice(this.reqsCache.indexOf(req), 1);
+  }
+
+  notify() {
+    this.ipcallback(this.ip);
+    this.clearAll();
+  }
+
+  send(pip, timeout) {
+    const req = new XMLHttpRequest();
+    req.open('HEAD', `//${pip}/?_=${Date.now()}`);
+    req.timeout = timeout;
+    req.onload = () => {
+      this.ip = pip;
+      this.clearReq(req);
+      req.onload = null;
+      this.notify();
+    }
+    req.ontimeout = () => {
+      this.clearReq(req);
+      req.ontimeout = null;
+    }
+    req.onerror = () => {
+      this.clearReq(req);
+      req.onerror = null;
+    }
+    req.onabort = () => {
+      this.clearReq(req);
+      req.onabort = null;
+    }
+    req.send();
+    return req
+  }
+}
+
+/**
+ * ips check
+ * @param  {array} ips
+ * @param  {number} callback
+ * @return {null}
+ */
+export default function check(ips, callback, timeout = 3000) {
+  if (!(ips && ips.length && callback)) throw new Error('ips and callback are required.');
+  new IpHunter(ips, callback, timeout);
+}
+
diff --git a/node_modules/iphunter/test/test.js b/node_modules/iphunter/test/test.js
new file mode 100644
index 0000000..77311f3
--- /dev/null
+++ b/node_modules/iphunter/test/test.js
@@ -0,0 +1,15 @@
+import check from 'main.js';
+
+check([
+  '192.168.99.199',
+  'baidu.com',
+  'aliyun.com',
+  'qq.com',
+  '192.168.99.100',
+  '127.0.0.0:8080',
+  '192.168.1.48:8080',
+  'localhost:8080'
+], function (ip) {
+  console.log('done -> ', ip);
+})
+
diff --git a/node_modules/iphunter/webpack.config.umd.js b/node_modules/iphunter/webpack.config.umd.js
new file mode 100644
index 0000000..5d0d21a
--- /dev/null
+++ b/node_modules/iphunter/webpack.config.umd.js
@@ -0,0 +1,15 @@
+module.exports = function (umdConf) {
+  umdConf.devServer.host = '0.0.0.0';
+  umdConf.webpackFeatures.enableEntryHTML();
+  umdConf.output.publicPath = '';
+
+  if (umdConf.devMode) {
+    umdConf.webpackFeatures.enableEntryHot('test');
+  } else {
+    umdConf.webpackFeatures.enableUglifyJs({
+      comments: false
+    });
+  }
+  // console.log(umdConf);
+};
+
diff --git a/src/EngineEntrance.js b/src/EngineEntrance.js
index c624338..39b8ac6 100644
--- a/src/EngineEntrance.js
+++ b/src/EngineEntrance.js
@@ -5,6 +5,7 @@ require('string.fromcodepoint');
 
 import Emiter from './Emiter';
 import Sass from 'Sass';
+import ServerCheck from 'ServerCheck';
 import Mcu from 'mcu';
 import MessageTypes from 'MessageTypes';
 import Loger from 'Loger';
@@ -26,6 +27,7 @@ let _sdkInfo={"version":"v.1.0.1","author":"www.3mang.com"};
 
 //APE
 let _sass;
+let _serverCheck;
 let _mcu ;
 let _confer_ape;
 let _chat_ape;
@@ -34,6 +36,7 @@ let _audio_ape;
 let _doc_ape;
 let _whiteboard_ape;
 
+
 //MCUClient 外部实例化主类
 export default class MessageEntrance extends Emiter {
   constructor() {
@@ -53,12 +56,18 @@ export default class MessageEntrance extends Emiter {
     _sass.on('*', (type, data) => this._emit(type, data));
     _sass.on(_sass.SUCCESS, this._sassJoinSuccessHandler.bind(this));//通过SASS平台验证(密码和MD5)
     _sass.on(_sass.CLASS_INIT_SUCCESS, this._sassInitSuccessHandler.bind(this));//获取会议初始化信息
-    _sass.on(_sass.CLASS_GET_CLASS_DETAIL, this._sassGetClassDetailSuccessHandler.bind(this));//获取会议的基本信息
+    //_sass.on(_sass.CLASS_GET_CLASS_DETAIL, this._sassGetClassDetailSuccessHandler.bind(this));//获取会议的基本信息
     _sass.on(_sass.CLASS_GET_CLASS_PARAM, this._sassGetClassParamSuccessHandler.bind(this));//获取会议的最全信息和历史保存的数据
+
     _sass.on(_sass.CLASS_SAVE_STATUS_INFO_SUCCESS, this._sassSaveClassStatusInfoSuccessHandler.bind(this));//保存会议状态信息
     _sass.on(_sass.CLASS_SAVE_RECORD_INFO_SUCCESS, this._sassSaveClassRecordInfoSuccessHandler.bind(this));//保存会议录制信息
     _sass.on(_sass.DELETE_DOCUMENT_SUCCESS, this._sassDeleteDocumentSuccess.bind(this));//sass删除文档成功
 
+    //ServerCheck ip
+    _serverCheck=ServerCheck;
+    _serverCheck.on(_serverCheck.SEVER_CHECK_BEST_IP_SUCCESS, this._serverCheckBestIpSuccessHandler.bind(this));//ip选点,获取最佳ip完成
+
+
     // 底层MCU消息层
     _mcu = Mcu;
     _mcu.on('*', (type, data) => this._emit(type, data));
@@ -295,6 +304,7 @@ export default class MessageEntrance extends Emiter {
     GlobalConfig.password = _param.password || "";
     GlobalConfig.hasCamera=(typeof  _param.hasCamera=="boolean")? _param.hasCamera:false;
     GlobalConfig.hasMicrophone=(typeof  _param.hasMicrophone=="boolean")? _param.hasMicrophone:false;
+
     //debugger;
     //开始校验
     if (_sass) {
@@ -355,107 +365,12 @@ export default class MessageEntrance extends Emiter {
      mcu	字符串			Mcu列表
      rs	字符串			Rs列表
      doc	字符串			Doc列表*/
-
-    /*    {
-     "record": "112.126.80.182:80",
-     "flag": "true",
-     "h5Module": 1,
-     "maxVideoChannels": 1,
-     "mcu": "123.56.73.119:7000;123.56.69.230:7000;112.126.80.182:7000",
-     "ms": "pubms.3mang.com:1935",
-     "doc": "101.200.150.192:80",
-     "rs": "pubms.3mang.com:1935",
-     "type": 1,
-     "maxAudioChannels": 1,
-     "h5_mcu_list": "123.56.73.119:7001;123.56.69.230:7001;112.126.80.182:7001"
-     }*/
-
-    /*
-     if (_data.h5_mcu_list) {
-     //MCU地址默认使用第一个
-     let server = _data.h5_mcu_list.split(";")[0];
-     GlobalConfig.MCUServerIP = server.split(":")[0];
-     GlobalConfig.MCUServerPort = server.split(":")[1];
-     }
-
-     //视频推流播流地址
-     if (_data.ms) {
-     //MS地址默认使用第一个
-     let server = _data.ms.split(";")[0];
-     GlobalConfig.MSServerIP = server.split(":")[0];
-     GlobalConfig.MSServerPort = server.split(":")[1];
-     }
-
-     //m3u8播流地址
-     if(_data.rs){
-     //RS地址默认使用第一个
-     let server = _data.rs.split(";")[0];
-     GlobalConfig.RSServerIP = server.split(":")[0];
-     GlobalConfig.RSServerPort = server.split(":")[1];
-     }
-
-     GlobalConfig.docServer = _data.doc;
-     GlobalConfig.h5_mcu_list = _data.h5_mcu_list;
-     GlobalConfig.h5Module = _data.h5Module;
-     GlobalConfig.mcu = _data.mcu;
-     GlobalConfig.ms = _data.ms;
-     GlobalConfig.record = _data.record;
-     GlobalConfig.rs = _data.rs;
-     GlobalConfig.maxVideoChannels = _data.maxVideoChannels;
-     GlobalConfig.maxAudioChannels = _data.maxAudioChannels;
-
-     */
-
-
-   /*
-   //这个接口获取的数据在getClassParam接口的数据中都有,内容重复,这个接口废弃
-    //获取会议基本信息
-    if (_sass) {
-      _sass.getClassDetail();
-    }
-    */
-
     //获取会议最完整的数据
     if (_sass) {
       _sass.getClassParam();
     }
   }
 
-  //获取会议基本信息getClassH5
-  _sassGetClassDetailSuccessHandler(_data) {
-    loger.log('获取getClassDetail完成.');
-    /*  {
-     "cycle": 0,
-     "repeatmonthweekweek": 0,
-     "status": 1,
-     "repeatmonthday": 0,
-     "repeatmode": 0,
-     "beginTime": "2017-02-03 09:00:00",
-     "frequency": 1,
-     "endmode": 0,
-     "meetingContent": "",
-     "endTime": "2017-03-31 11:00:00",
-     "repeatweek": "",
-     "category": "",
-     "finalenddate": "",
-     "repeatday": 0,
-     "meetingName": "mcu1",
-     "errorCode": 0,
-     "monthType": 0,
-     "repeatmonthweekday": 0,
-     "endcount": 1
-     }*/
-    GlobalConfig.classDetail = _data;
-    GlobalConfig.className = _data.meetingName || "";
-    GlobalConfig.classBeginTime = _data.beginTime || "";
-    GlobalConfig.classEndTime = _data.endTime || "";
-
-    //获取会议所有信息和以前保存的会议状态信息(最全的信息)
-    if (_sass) {
-      _sass.getClassParam();
-    }
-  }
-
   //获取会议所有参数 api/meeting/detail.do?      flash中的接口文件是 getClassParam.do
   _sassGetClassParamSuccessHandler(_data) {
     //console.log(GlobalConfig.classStatusInfo)
@@ -467,6 +382,7 @@ export default class MessageEntrance extends Emiter {
       GlobalConfig.className = _data.meetingName || "";
       GlobalConfig.classBeginTime = _data.beginTime || "";
       GlobalConfig.classEndTime = _data.endTime || "";
+      GlobalConfig.userIp=_data.userIp||"";
 
       GlobalConfig.maxVideoChannels = _data.maxVideoChannels;
       GlobalConfig.maxAudioChannels = _data.maxAudioChannels;
@@ -526,7 +442,18 @@ export default class MessageEntrance extends Emiter {
       loger.log("还没有保存过会议状信息");
     }
 
-    //所有Sass流程完成,开始MCU连接
+    //根据用户的userIp获取信息
+    this.getUserIpInfo();
+  }
+  //根据UserIp获取ip信息
+  getUserIpInfo(){
+    if(_serverCheck){
+      _serverCheck.getUserIpInfo("",GlobalConfig.userIp);
+    }
+  }
+  //MCU MS ip选点完成,开加入MCU
+  _serverCheckBestIpSuccessHandler(_data){
+    loger .log("_serverCheckBestIpSuccessHandler,IP选点结束");
     this._joinMCU();
   }
 
@@ -569,49 +496,50 @@ export default class MessageEntrance extends Emiter {
     GlobalConfig.setCurrentStatus(GlobalConfig.statusCode_2);
 
     //返回给客户端初始化成功的数据
-    let initSuccessCallBackData = {};
-
-    initSuccessCallBackData.DOCServerIP =GlobalConfig.DOCServerIP;
-    initSuccessCallBackData.DOCServerPort =GlobalConfig.DOCServerPort;
-
-    initSuccessCallBackData.classId = GlobalConfig.classId;
-    initSuccessCallBackData.className = GlobalConfig.className;
-    initSuccessCallBackData.h5Module = GlobalConfig.h5Module;
-    initSuccessCallBackData.isHost = GlobalConfig.isHost;
-    initSuccessCallBackData.maxAudioChannels = GlobalConfig.maxAudioChannels;
-    initSuccessCallBackData.maxVideoChannels = GlobalConfig.maxVideoChannels;
-    initSuccessCallBackData.mcuDelay = GlobalConfig.mcuDelay;
-
-    initSuccessCallBackData.msType = GlobalConfig.msType;
-    initSuccessCallBackData.nodeId = GlobalConfig.nodeId;
-    initSuccessCallBackData.password = GlobalConfig.password;
-    initSuccessCallBackData.passwordRequired = GlobalConfig.passwordRequired;//  老师的默认是true
+    let joinClassSuccessCallBackData = {};
+
+    joinClassSuccessCallBackData.DOCServerIP =GlobalConfig.DOCServerIP;
+    joinClassSuccessCallBackData.DOCServerPort =GlobalConfig.DOCServerPort;
+
+    joinClassSuccessCallBackData.classId = GlobalConfig.classId;
+    joinClassSuccessCallBackData.className = GlobalConfig.className;
+    joinClassSuccessCallBackData.h5Module = GlobalConfig.h5Module;
+    joinClassSuccessCallBackData.isHost = GlobalConfig.isHost;
+    joinClassSuccessCallBackData.maxAudioChannels = GlobalConfig.maxAudioChannels;
+    joinClassSuccessCallBackData.maxVideoChannels = GlobalConfig.maxVideoChannels;
+    joinClassSuccessCallBackData.mcuDelay = GlobalConfig.mcuDelay;
+
+    joinClassSuccessCallBackData.msType = GlobalConfig.msType;
+    joinClassSuccessCallBackData.nodeId = GlobalConfig.nodeId;
+    joinClassSuccessCallBackData.password = GlobalConfig.password;
+    joinClassSuccessCallBackData.passwordRequired = GlobalConfig.passwordRequired;//  老师的默认是true
     //GlobalConfig.passwordRequired  老师的默认是true
     //GlobalConfig.portal=_data.portal;
-    initSuccessCallBackData.role = GlobalConfig.role;
-    initSuccessCallBackData.siteId = GlobalConfig.siteId;
-    initSuccessCallBackData.topNodeID = GlobalConfig.topNodeID;
-    initSuccessCallBackData.userId = GlobalConfig.userId;
-    initSuccessCallBackData.userName = GlobalConfig.userName;
-    initSuccessCallBackData.userRole = GlobalConfig.userRole;
-    initSuccessCallBackData.userType = GlobalConfig.userType;
+    joinClassSuccessCallBackData.role = GlobalConfig.role;
+    joinClassSuccessCallBackData.siteId = GlobalConfig.siteId;
+    joinClassSuccessCallBackData.topNodeID = GlobalConfig.topNodeID;
+    joinClassSuccessCallBackData.userId = GlobalConfig.userId;
+    joinClassSuccessCallBackData.userName = GlobalConfig.userName;
+    joinClassSuccessCallBackData.userRole = GlobalConfig.userRole;
+    joinClassSuccessCallBackData.userType = GlobalConfig.userType;
+
+    joinClassSuccessCallBackData.siteId = GlobalConfig.siteId;
+    joinClassSuccessCallBackData.classId = GlobalConfig.classId;
+    joinClassSuccessCallBackData.userRole = GlobalConfig.userRole;
+    joinClassSuccessCallBackData.userId = GlobalConfig.userId;
+    joinClassSuccessCallBackData.passwordRequired = GlobalConfig.passwordRequired;
+    joinClassSuccessCallBackData.classType = GlobalConfig.classType || ApeConsts.CLASS_TYPE_INTERACT;
+
+    joinClassSuccessCallBackData.country = GlobalConfig.country;//国家
+    joinClassSuccessCallBackData.city = GlobalConfig.city;//城市
+    joinClassSuccessCallBackData.province = GlobalConfig.province;//服务商
+    joinClassSuccessCallBackData.isp = GlobalConfig.isp;//服务商
 
-    initSuccessCallBackData.siteId = GlobalConfig.siteId;
-    initSuccessCallBackData.classId = GlobalConfig.classId;
-    initSuccessCallBackData.userRole = GlobalConfig.userRole;
-    initSuccessCallBackData.userId = GlobalConfig.userId;
-    initSuccessCallBackData.passwordRequired = GlobalConfig.passwordRequired;
-    initSuccessCallBackData.classType = GlobalConfig.classType || ApeConsts.CLASS_TYPE_INTERACT;
     loger.log('加入会议成功');
-    console.log(initSuccessCallBackData);
+    console.log(joinClassSuccessCallBackData);
 
     //加入会议成功,广播消息
-    this._emit(MessageTypes.CLASS_JOIN_SUCCESS,initSuccessCallBackData);
-
-/*    //返回给客户数据
-    if (_joinClassSuccessCallBackFun) {
-      _joinClassSuccessCallBackFun(initSuccessCallBackData);
-    }*/
+    this._emit(MessageTypes.CLASS_JOIN_SUCCESS,joinClassSuccessCallBackData);
   }
 
   //Sass删除文档数据
@@ -746,6 +674,11 @@ export default class MessageEntrance extends Emiter {
   }
 
   _getVideoPublishPath(_param){
+    if(!_mcu.connected){
+      loger.warn(GlobalConfig.getCurrentStatus());
+      return;
+    }
+
     if(_video_ape){
       return _video_ape.getPublishVideoPath(_param);
     }
@@ -801,6 +734,10 @@ export default class MessageEntrance extends Emiter {
   }
 
   _getPublishAudioPath(_param){
+    if(!_mcu.connected){
+      loger.warn(GlobalConfig.getCurrentStatus());
+      return;
+    }
     if(_audio_ape){
       return _audio_ape.getAudioPublishPath(_param);
     }
diff --git a/src/GlobalConfig.js b/src/GlobalConfig.js
index d1e3028..cac9c54 100644
--- a/src/GlobalConfig.js
+++ b/src/GlobalConfig.js
@@ -272,7 +272,7 @@ GlobalConfig.hasCamera=false;//摄像头是否可用
 GlobalConfig.hasMicrophone=false;//麦克风是否可用
 
 GlobalConfig.deviceType=0; //设备类型  0:电脑  1:安卓  2:ios
-GlobalConfig.userIP="";//用户当前IP
+GlobalConfig.userIp="";//用户当前IP
 GlobalConfig.userId=0;
 GlobalConfig.userName="";
 
@@ -326,4 +326,9 @@ GlobalConfig.musicListPrepare=[];//提提前上传的music集合
 GlobalConfig.rsList=[];
 
 
+GlobalConfig.country ="";//国家
+GlobalConfig.city ="";//城市
+GlobalConfig.province = "";//服务商
+GlobalConfig.isp ="";//服务商
+
 export  default GlobalConfig;
diff --git a/src/Sass.js b/src/Sass.js
index 2b2c985..a148de6 100644
--- a/src/Sass.js
+++ b/src/Sass.js
@@ -4,6 +4,9 @@ import MessageTypes from 'MessageTypes';
 import GlobalConfig from 'GlobalConfig';
 import MD5 from "md5";
 import ApeConsts from 'apes/ApeConsts';
+import iphunter from 'iphunter';
+
+import fetchJsonp from 'fetch-jsonp';
 
 // 日志对象
 const loger = Loger.getLoger('Sass');
@@ -384,23 +387,23 @@ class Sass extends Emiter {
     //保存录制的信息,主要是录制文件的名称,必须和MCU录制的文件名相同
     saveClassRecordContrlInfo(_param) {
         loger.log('保存开始录制信息');
-        let key       = "3mang123A";
-        let siteID	 = GlobalConfig.siteId;
+        let key = "3mang123A";
+        let siteID = GlobalConfig.siteId;
         let meetingID = GlobalConfig.classId;
-        let userID	 = GlobalConfig.userId;
-        let userName  = GlobalConfig.userName;
+        let userID = GlobalConfig.userId;
+        let userName = GlobalConfig.userName;
         let meetingName = GlobalConfig.className;
-        let startTime   =GlobalConfig.classBeginTime;
-        let endTime	   = GlobalConfig.classEndTime;
-        let playUrl	   = "";
-        let streamName   = GlobalConfig.recordFileName;
-        let confRecordFileName=GlobalConfig.recordFileName;
-        let downloadUrl  = "";
-        let recordStatus  = GlobalConfig.classStatus;
-        let recordTimestamp  =GlobalConfig.classTimestamp;
+        let startTime = GlobalConfig.classBeginTime;
+        let endTime = GlobalConfig.classEndTime;
+        let playUrl = "";
+        let streamName = GlobalConfig.recordFileName;
+        let confRecordFileName = GlobalConfig.recordFileName;
+        let downloadUrl = "";
+        let recordStatus = GlobalConfig.classStatus;
+        let recordTimestamp = GlobalConfig.classTimestamp;
 
-        let timestamp    = new Date().getTime();;
-        let authId       = MD5(key + siteID + meetingID + timestamp);
+        let timestamp = new Date().getTime();
+        let authId = MD5(key + siteID + meetingID + timestamp);
         let url = `http://${confInfo.portal}/3m/recordingMeeting/insertRecordingMeeting.do?siteID=${siteID}&meetingID=${meetingID}&userID=${userID}&userName=${userName}&meetingName=${meetingName}&startTime=${startTime}&endTime=${endTime}&playUrl=${playUrl}&streamName=${streamName}&downloadUrl=${downloadUrl}&configFile=${confRecordFileName}&timestamp=${timestamp}&recordTimestamp=${recordTimestamp}&authId=${authId}`;
         loger.log('saveClassRecordContrlInfo', url);
 
@@ -420,7 +423,7 @@ class Sass extends Emiter {
                     loger.log('保存开始录制信息 完成');
                     this._emit(Sass.CLASS_SAVE_RECORD_INFO_SUCCESS, _param);
                 } else {
-                    loger.warn('保存开始录制信息 失败.',ret);
+                    loger.warn('保存开始录制信息 失败.', ret);
                 }
             })
             .catch(err => {
@@ -428,7 +431,6 @@ class Sass extends Emiter {
             });
 
     }
-
 }
 
 Sass.prototype.SUCCESS = Sass.SUCCESS = 'Sass_success';
diff --git a/src/ServerCheck.js b/src/ServerCheck.js
new file mode 100644
index 0000000..1a491d9
--- /dev/null
+++ b/src/ServerCheck.js
@@ -0,0 +1,276 @@
+import Emiter from 'Emiter';
+import Loger from 'Loger';
+import MessageTypes from 'MessageTypes';
+import GlobalConfig from 'GlobalConfig';
+import MD5 from "md5";
+import ApeConsts from 'apes/ApeConsts';
+import iphunter from 'iphunter';
+import Server from "config/Server";
+import fetchJsonp from 'fetch-jsonp';
+
+// 日志对象
+const loger = Loger.getLoger('ServerCheck');
+
+//ip选点流程的状态
+let isRequestMcuCallback = false;//是否获取最佳mcu返回
+let isRequestMsCallback = false;//是否获取ms最佳返回
+let isTestFromSass=false;
+let isTestFromServer=false;
+
+let tempMcuIp="";
+let tempMcuPort="";
+let tempMsIp="";
+let tempMsPort="";
+class ServerCheck extends Emiter {
+    constructor() {
+        super();
+
+    }
+
+    //根据userIp获取ip相关的信息,参数是userIp
+    getUserIpInfo(token, userIp) {
+        //重置ip选点流程状态
+        isRequestMcuCallback = false;
+        isRequestMsCallback = false;
+        isTestFromSass=false;
+        isTestFromServer=false;
+
+        let userIpInfo = new Object;
+        userIpInfo.ret = -1;
+
+        let ip = userIp;
+        let md5Str = MD5("addr=" + ip + "&token=b657c3507b324353e09c1958ee956a98efceb3e3");//("addr=" + ip + "&token=b657c3507b324353e09c1958ee956a98efceb3e3"),转成MD5
+        let timestamp = new Date().getTime();
+        let location = `http://ipapi.ipip.net/find?addr=${ip}&sid=14&uid=5237&sig=${md5Str}&_=${timestamp}`;
+        loger.log('获取IP信息 ', userIp, location);
+
+        fetchJsonp(location, {
+            timeout: 3000,
+        }).then(function (response) {
+            return response.json()
+        }).then(function (json) {
+            loger.log('获取IP信息返回', json)
+            if (json) {
+                userIpInfo.ret = json.ret;
+                userIpInfo.country = json.data[0];//国家
+                userIpInfo.province = json.data[1];//省份
+                userIpInfo.city = json.data[2];//城市
+                userIpInfo.isp = json.data[4];//运营商
+            }
+            this.serverGetUserIpInfoCallback(userIpInfo);
+        }.bind(this)).catch(function (ex) {
+            loger.log('获取IP信息失败', ex.message)
+            this.serverGetUserIpInfoCallback(userIpInfo);
+        }.bind(this));
+    }
+
+    //获取ip信息返回
+    serverGetUserIpInfoCallback(userIpInfo) {
+        loger.log("获取IP详情,开始处理", userIpInfo);
+        if (userIpInfo.ret == "ok") {
+            GlobalConfig.country = userIpInfo.country;//国家
+            GlobalConfig.city = userIpInfo.city;//城市
+            GlobalConfig.province = userIpInfo.province;//服务商
+            GlobalConfig.isp = userIpInfo.isp;//服务商
+            loger.log("获取ip详情成功,country:" + GlobalConfig.country + ",city:" + GlobalConfig.city + ",isp:" + GlobalConfig.isp);
+            this._chooseBestIpFromServer();
+        }
+        else {
+            loger.log("获取ip详情失败");
+            this._chooseBestIpFromSassParam();
+        }
+    }
+
+    //从IPIP服务列表中选择最快的IP
+    _chooseBestIpFromServer() {
+        loger.log("从IPIP服务列表中选择最快的IP");
+        isRequestMcuCallback=false;
+        isRequestMsCallback=false;
+        isTestFromServer=true;
+        isTestFromSass=false;
+
+        let mcuIpGroup =this._returnServerMS();
+        let msIpGroup = this._returnServerMCU();
+        this.getBestMcuServer(mcuIpGroup);
+        this.getBestMsServer(msIpGroup);
+    }
+    //从Sass返回的msList   mcuList中选点
+    _chooseBestIpFromSassParam() {
+        loger.log("从Sass服务列表中选择最快的IP");
+        isRequestMcuCallback=false;
+        isRequestMsCallback=false;
+        isTestFromServer=true;
+        isTestFromSass=true;
+        //MCU
+        let mcuIpGroup = [];
+        let speedTestPort = ':8080';//测速端口统一
+        for (let i = 0; i < GlobalConfig.mcuList.length; i++) {
+            let ipPort = GlobalConfig.mcuList[i].ip+speedTestPort;
+            mcuIpGroup.push(ipPort)
+        }
+        this.getBestMcuServer(mcuIpGroup);
+
+
+        //MS
+        let msIpGroup = [];
+        for (let i = 0; i < GlobalConfig.msList.length; i++) {
+            let ipPort = GlobalConfig.msList[i].ip+speedTestPort;
+            msIpGroup.push(ipPort)
+        }
+        this.getBestMsServer(msIpGroup);
+    }
+
+
+    //获取最快的MCU服务器地址,参数是一个ip数组
+    getBestMcuServer(_param) {
+        loger.log('getBestMcuServer ', _param);
+        if(_param==null||_param.length<1){
+            this._getBestMcuServerCallbackHandler("")
+            return;
+        }
+        iphunter(_param, function (fatest_ip_response) {
+            if (!fatest_ip_response) {
+                loger.warn('getBestMcuServer -> nothing!');
+                this._getBestMcuServerCallbackHandler("")
+            } else {
+                loger.log('getBestMcuServer done -> ', fatest_ip_response);
+                this._getBestMcuServerCallbackHandler(fatest_ip_response)
+            }
+        }.bind(this), 3000);
+    }
+
+    //获取最快的MS服务器地址,参数是一个ip数组
+    getBestMsServer(_param) {
+        loger.log('getBestMsServer ', _param);
+        if(_param==null||_param.length<1){
+            this._getBestMsServerCallbackHandler("")
+            return;
+        }
+        iphunter(_param, function (fatest_ip_response) {
+            if (!fatest_ip_response) {
+                loger.warn('getBestMsServer -> nothing!');
+                this._getBestMsServerCallbackHandler("");
+            } else {
+                loger.log('getBestMsServer done -> ', fatest_ip_response);
+                this._getBestMsServerCallbackHandler(fatest_ip_response);
+            }
+        }.bind(this), 3000);
+    }
+
+    _getBestMcuServerCallbackHandler(_data) {
+        loger.log("_getBestMcuServerCallbackHandler", _data);
+        if (isRequestMcuCallback) {
+            loger.log("_getBestMcuServerCallbackHandler,已经有返回");
+            return;
+        }
+        isRequestMcuCallback = true;
+        if (_data) {
+            let server = _data.split(":");
+            if (server[0]) {
+               tempMcuIp= server[0];
+            }
+            if (server[1]) {
+                tempMcuPort = server[1];
+            }
+        }
+        //loger.log("_getBestMcuServerCallbackHandler",tempMcuIp,tempMcuPort);
+        this._startConnectMcu();
+    }
+
+    _getBestMsServerCallbackHandler(_data) {
+        loger.log("_getBestMsServerCallbackHandler", _data);
+        if (isRequestMsCallback) {
+            loger.log("_getBestMsServerCallbackHandler,已经有返回");
+            return;
+        }
+        isRequestMsCallback = true;
+        if (_data) {
+            let server = _data.split(":");
+            if (server[0]) {
+                tempMsIp= server[0];
+            }
+            if (server[1]) {
+               tempMsPort= server[1];
+            }
+        }
+        //loger.log("_getBestMsServerCallbackHandler", tempMsIp,tempMsPort);
+        this._startConnectMcu();
+    }
+
+    //ip选点结束,开始连接MCU
+    _startConnectMcu() {
+        if (isRequestMcuCallback && isRequestMsCallback) {
+            if (isTestFromServer && !isTestFromSass) {
+                //从Server服务列表中选点结束,如果没有选到合适的,从Sass的列表中获取
+                if(!tempMcuIp||!tempMsIp){
+                    this._chooseBestIpFromSassParam();
+                }else {
+                    this._emit(ServerCheck.SEVER_CHECK_BEST_IP_SUCCESS);
+                }
+            } else {
+                //从Sass返回的服务列表中选点结束
+                this._emit(ServerCheck.SEVER_CHECK_BEST_IP_SUCCESS);
+            }
+        } else {
+            loger.warn("_startConnectMcu 正在选点", isRequestMcuCallback, isRequestMsCallback);
+        }
+    }
+
+    //检测MCU连接地址
+    _returnServerMCU(country, province, ctiy, isp, jsona) {
+        let arr=[];
+        return arr;
+    }
+    //检测MS连接地址
+    //Config.ipInfo
+    _returnServerMS(country, province, ctiy, isp, jsona) {
+        let arr = [];
+     /*   let acquire = false;
+        if (isp != "") {
+            for (let obja in jsona.MS.isp) {
+                if (isp.indexOf(obja.idc) != -1) {
+                    arr = obja.mslist;
+                    acquire = true;
+                    break;
+                }
+            }
+        }
+        if (country == "中国" && !acquire) {
+            for (let obja in jsona.MS.china) {
+                if (obja.province.indexOf(province) != -1 && province != "") {
+                    arr = obja.mslist;
+                    acquire = true;
+                    break;
+                }
+            }
+            if (!acquire) {
+                arr = jsona.MS.china[jsona.MS.china.length - 1].mslist;
+                acquire = true;
+            }
+        }
+
+        if (country != "中国" && country != "") {
+            for (let obja:Object in jsona.MS.international) {
+                if (obja.country.indexOf(country) != -1) {
+                    arr = obja.mslist;
+                    acquire = true;
+                    break;
+                }
+            }
+            if (!acquire) {
+                arr = jsona.MS.international[jsona.MS.international.length - 1].mslist;
+                acquire = true;
+            }
+        }
+        else if (!acquire) {
+            arr = jsona.MS.Default;
+        }
+
+        loger.info("ms匹配结束;" + arr.length);*/
+        return arr;
+    }
+}
+
+ServerCheck.prototype.SEVER_CHECK_BEST_IP_SUCCESS = ServerCheck.SEVER_CHECK_BEST_IP_SUCCESS = 'severCheck_checkBestIpSuccess_message';//获取最快的MS地址
+export default new ServerCheck;
+
diff --git a/src/config/Server.js b/src/config/Server.js
new file mode 100644
index 0000000..0380d19
--- /dev/null
+++ b/src/config/Server.js
@@ -0,0 +1,610 @@
+/*
+ * 全局数据管理
+ * */
+import Loger from 'Loger';
+import ApeConsts from "apes/ApeConsts";
+
+let loger = Loger.getLoger('Server');
+import EngineUtils from 'EngineUtils';
+
+class Server {
+    constructor() {
+
+    }
+    static get serverList(){
+        return {
+            "共享地址": [
+                {
+                    "ip": "106.3.130.98",
+                    "name": "BGP多线3"
+                },
+                {
+                    "ip": "123.56.75.60",
+                    "name": "BGP多线4"
+                },
+                {
+                    "ip": "221.228.109.123",
+                    "name": "无锡"
+                },
+                {
+                    "ip": "qims.3mang.com",
+                    "name": "全球通-备"
+                },
+                {
+                    "ip": "liantong.ms.3mang.com",
+                    "name": "联通专线"
+                },
+                {
+                    "ip": "yidong.ms.3mang.com",
+                    "name": "移动专线"
+                },
+                {
+                    "ip": "dianxin.ms.3mang.com",
+                    "name": "电信专线"
+                },
+                {
+                    "ip": "103.235.232.128",
+                    "name": "BGP多线2"
+                },
+                {
+                    "ip": "lanxms.3mang.com",
+                    "name": "国内专线"
+                },
+                {
+                    "ip": "116.213.102.217",
+                    "name": "BGP多线1"
+                }
+            ],
+            "局域网": [
+                {
+                    "ip": "106.3.130.98",
+                    "name": "BGP多线3"
+                },
+                {
+                    "ip": "123.56.75.60",
+                    "name": "BGP多线4"
+                },
+                {
+                    "ip": "221.228.109.123",
+                    "name": "无锡"
+                },
+                {
+                    "ip": "qims.3mang.com",
+                    "name": "全球通-备"
+                },
+                {
+                    "ip": "liantong.ms.3mang.com",
+                    "name": "联通专线"
+                },
+                {
+                    "ip": "yidong.ms.3mang.com",
+                    "name": "移动专线"
+                },
+                {
+                    "ip": "dianxin.ms.3mang.com",
+                    "name": "电信专线"
+                },
+                {
+                    "ip": "103.235.232.128",
+                    "name": "BGP多线2"
+                },
+                {
+                    "ip": "lanxms.3mang.com",
+                    "name": "国内专线"
+                },
+                {
+                    "ip": "116.213.102.217",
+                    "name": "BGP多线1"
+                }
+            ],
+            "中国": {
+                "province": {
+                    "香港": [
+                        {
+                            "ip": "bjms1.3mang.com",
+                            "name": "全球通"
+                        },
+                        {
+                            "ip": "199.59.231.234",
+                            "name": "全球通香港"
+                        },
+                        {
+                            "ip": "qims.3mang.com",
+                            "name": "全球通-备"
+                        }
+                    ],
+                    "台湾": [
+                        {
+                            "ip": "bjms1.3mang.com",
+                            "name": "全球通"
+                        },
+                        {
+                            "ip": "159.100.205.129",
+                            "name": "全球通台湾"
+                        },
+                        {
+                            "ip": "qims.3mang.com",
+                            "name": "全球通-备"
+                        }
+                    ]
+                },
+                "isp": {
+                    "鹏博士": [
+                        {
+                            "ip": "124.192.148.139",
+                            "name": "鹏博士"
+                        },
+                        {
+                            "ip": "123.56.75.60",
+                            "name": "BGP多线4"
+                        },
+                        {
+                            "ip": "106.3.130.98",
+                            "name": "BGP多线3"
+                        },
+                        {
+                            "ip": "lanxms.3mang.com",
+                            "name": "国内专线"
+                        },
+                        {
+                            "ip": "qims.3mang.com",
+                            "name": "全球通-备"
+                        }
+                    ],
+                    "长城": [
+                        {
+                            "ip": "124.192.148.139",
+                            "name": "鹏博士"
+                        },
+                        {
+                            "ip": "123.56.75.60",
+                            "name": "BGP多线4"
+                        },
+                        {
+                            "ip": "106.3.130.98",
+                            "name": "BGP多线3"
+                        },
+                        {
+                            "ip": "lanxms.3mang.com",
+                            "name": "国内专线"
+                        },
+                        {
+                            "ip": "qims.3mang.com",
+                            "name": "全球通-备"
+                        }
+                    ],
+                    "移动": [
+                        {
+                            "ip": "yidong.ms.3mang.com",
+                            "name": "移动专线"
+                        },
+                        {
+                            "ip": "116.213.102.217",
+                            "name": "BGP多线1"
+                        },
+                        {
+                            "ip": "123.56.75.60",
+                            "name": "BGP多线4"
+                        },
+                        {
+                            "ip": "103.235.232.128",
+                            "name": "BGP多线2"
+                        },
+                        {
+                            "ip": "lanxms.3mang.com",
+                            "name": "国内专线"
+                        },
+                        {
+                            "ip": "qims.3mang.com",
+                            "name": "全球通-备"
+                        }
+                    ],
+                    "电信": [
+                        {
+                            "ip": "dianxin.ms.3mang.com",
+                            "name": "电信专线"
+                        },
+                        {
+                            "ip": "116.213.102.217",
+                            "name": "BGP多线1"
+                        },
+                        {
+                            "ip": "103.235.232.128",
+                            "name": "BGP多线2"
+                        },
+                        {
+                            "ip": "123.56.75.60",
+                            "name": "BGP多线4"
+                        },
+                        {
+                            "ip": "lanxms.3mang.com",
+                            "name": "国内专线"
+                        },
+                        {
+                            "ip": "qims.3mang.com",
+                            "name": "全球通-备"
+                        }
+                    ],
+                    "联通": [
+                        {
+                            "ip": "116.213.102.217",
+                            "name": "BGP多线1"
+                        },
+                        {
+                            "ip": "103.235.232.128",
+                            "name": "BGP多线2"
+                        },
+                        {
+                            "ip": "liantong.ms.3mang.com",
+                            "name": "联通专线"
+                        },
+                        {
+                            "ip": "123.56.75.60",
+                            "name": "BGP多线4"
+                        },
+                        {
+                            "ip": "106.3.130.98",
+                            "name": "BGP多线3"
+                        },
+                        {
+                            "ip": "lanxms.3mang.com",
+                            "name": "国内专线"
+                        },
+                        {
+                            "ip": "qims.3mang.com",
+                            "name": "全球通-备"
+                        }
+                    ]
+                },
+                "default": [
+                    {
+                        "ip": "106.3.130.98",
+                        "name": "BGP多线3"
+                    },
+                    {
+                        "ip": "221.228.109.123",
+                        "name": "无锡"
+                    },
+                    {
+                        "ip": "qims.3mang.com",
+                        "name": "全球通-备"
+                    },
+                    {
+                        "ip": "liantong.ms.3mang.com",
+                        "name": "联通专线"
+                    },
+                    {
+                        "ip": "yidong.ms.3mang.com",
+                        "name": "移动专线"
+                    },
+                    {
+                        "ip": "dianxin.ms.3mang.com",
+                        "name": "电信专线"
+                    },
+                    {
+                        "ip": "lanxms.3mang.com",
+                        "name": "国内专线"
+                    },
+                    {
+                        "ip": "123.56.75.60",
+                        "name": "BGP多线4"
+                    },
+                    {
+                        "ip": "103.235.232.128",
+                        "name": "BGP多线2"
+                    },
+                    {
+                        "ip": "116.213.102.217",
+                        "name": "BGP多线1"
+                    }
+                ]
+            },
+            "美国": [
+                {
+                    "ip": "38.83.109.142",
+                    "name": "达拉斯"
+                },
+                {
+                    "ip": "45.126.244.41",
+                    "name": "全球通达拉斯"
+                },
+                {
+                    "ip": "185.114.76.243",
+                    "name": "全球通阿什本"
+                },
+                {
+                    "ip": "159.100.196.217",
+                    "name": "全球通迈阿密"
+                },
+                {
+                    "ip": "185.114.77.84",
+                    "name": "全球通圣何塞"
+                },
+                {
+                    "ip": "159.100.195.230",
+                    "name": "全球通洛杉矶"
+                },
+                {
+                    "ip": "bjms1.3mang.com",
+                    "name": "全球通"
+                },
+                {
+                    "ip": "159.100.192.188",
+                    "name": "全球通芝加哥"
+                }
+            ],
+            "加拿大": [
+                {
+                    "ip": "38.83.109.142",
+                    "name": "达拉斯"
+                },
+                {
+                    "ip": "45.126.244.41",
+                    "name": "全球通达拉斯"
+                },
+                {
+                    "ip": "185.114.76.243",
+                    "name": "全球通阿什本"
+                },
+                {
+                    "ip": "159.100.196.217",
+                    "name": "全球通迈阿密"
+                },
+                {
+                    "ip": "185.114.77.84",
+                    "name": "全球通圣何塞"
+                },
+                {
+                    "ip": "159.100.195.230",
+                    "name": "全球通洛杉矶"
+                },
+                {
+                    "ip": "bjms1.3mang.com",
+                    "name": "全球通"
+                },
+                {
+                    "ip": "159.100.192.188",
+                    "name": "全球通芝加哥"
+                }
+            ],
+            "墨西哥": [
+                {
+                    "ip": "38.83.109.142",
+                    "name": "达拉斯"
+                },
+                {
+                    "ip": "45.126.244.41",
+                    "name": "全球通达拉斯"
+                },
+                {
+                    "ip": "185.114.76.243",
+                    "name": "全球通阿什本"
+                },
+                {
+                    "ip": "159.100.196.217",
+                    "name": "全球通迈阿密"
+                },
+                {
+                    "ip": "185.114.77.84",
+                    "name": "全球通圣何塞"
+                },
+                {
+                    "ip": "159.100.195.230",
+                    "name": "全球通洛杉矶"
+                },
+                {
+                    "ip": "bjms1.3mang.com",
+                    "name": "全球通"
+                },
+                {
+                    "ip": "159.100.192.188",
+                    "name": "全球通芝加哥"
+                }
+            ],
+            "菲律宾": [
+                {
+                    "ip": "223.255.248.122",
+                    "name": "香港"
+                },
+                {
+                    "ip": "118.193.20.130",
+                    "name": "日本"
+                },
+                {
+                    "ip": "118.193.24.38",
+                    "name": "新加坡"
+                },
+                {
+                    "ip": "bjms1.3mang.com",
+                    "name": "全球通"
+                },
+                {
+                    "ip": "159.100.194.120",
+                    "name": "全球通日本"
+                },
+                {
+                    "ip": "185.114.78.179",
+                    "name": "全球通新加坡"
+                },
+                {
+                    "ip": "159.100.205.129",
+                    "name": "全球通台湾"
+                },
+                {
+                    "ip": "192.158.245.185",
+                    "name": "全球通韩国"
+                },
+                {
+                    "ip": "103.29.35.63",
+                    "name": "全球通印度"
+                },
+                {
+                    "ip": "199.59.231.234",
+                    "name": "全球通香港"
+                }
+            ],
+            "越南": [
+                {
+                    "ip": "223.255.248.122",
+                    "name": "香港"
+                },
+                {
+                    "ip": "118.193.20.130",
+                    "name": "日本"
+                },
+                {
+                    "ip": "118.193.24.38",
+                    "name": "新加坡"
+                },
+                {
+                    "ip": "bjms1.3mang.com",
+                    "name": "全球通"
+                },
+                {
+                    "ip": "159.100.194.120",
+                    "name": "全球通日本"
+                },
+                {
+                    "ip": "185.114.78.179",
+                    "name": "全球通新加坡"
+                },
+                {
+                    "ip": "159.100.205.129",
+                    "name": "全球通台湾"
+                },
+                {
+                    "ip": "192.158.245.185",
+                    "name": "全球通韩国"
+                },
+                {
+                    "ip": "103.29.35.63",
+                    "name": "全球通印度"
+                },
+                {
+                    "ip": "199.59.231.234",
+                    "name": "全球通香港"
+                }
+            ],
+            "泰国": [
+                {
+                    "ip": "223.255.248.122",
+                    "name": "香港"
+                },
+                {
+                    "ip": "118.193.20.130",
+                    "name": "日本"
+                },
+                {
+                    "ip": "118.193.24.38",
+                    "name": "新加坡"
+                },
+                {
+                    "ip": "bjms1.3mang.com",
+                    "name": "全球通"
+                },
+                {
+                    "ip": "159.100.194.120",
+                    "name": "全球通日本"
+                },
+                {
+                    "ip": "185.114.78.179",
+                    "name": "全球通新加坡"
+                },
+                {
+                    "ip": "159.100.205.129",
+                    "name": "全球通台湾"
+                },
+                {
+                    "ip": "192.158.245.185",
+                    "name": "全球通韩国"
+                },
+                {
+                    "ip": "103.29.35.63",
+                    "name": "全球通印度"
+                },
+                {
+                    "ip": "199.59.231.234",
+                    "name": "全球通香港"
+                }
+            ],
+            "default": [
+                {
+                    "ip": "bjms1.3mang.com",
+                    "name": "全球通"
+                },
+                {
+                    "ip": "qims.3mang.com",
+                    "name": "全球通-备"
+                },
+                {
+                    "ip": "38.83.109.142",
+                    "name": "达拉斯"
+                },
+                {
+                    "ip": "118.193.20.130",
+                    "name": "日本"
+                },
+                {
+                    "ip": "38.123.107.18",
+                    "name": "德国"
+                },
+                {
+                    "ip": "223.255.248.122",
+                    "name": "香港"
+                },
+                {
+                    "ip": "148.153.8.22",
+                    "port": "1935",
+                    "name": "纽约"
+                },
+                {
+                    "ip": "38.121.61.242",
+                    "port": "1935",
+                    "name": "洛杉矶"
+                },
+                {
+                    "ip": "118.193.24.38",
+                    "name": "新加坡"
+                },
+                {
+                    "ip": "159.100.194.120",
+                    "name": "全球通日本"
+                },
+                {
+                    "ip": "159.100.192.188",
+                    "name": "全球通芝加哥"
+                },
+                {
+                    "ip": "45.126.244.41",
+                    "name": "全球通达拉斯"
+                },
+                {
+                    "ip": "45.126.246.148",
+                    "name": "全球通德国"
+                },
+                {
+                    "ip": "192.158.245.185",
+                    "name": "全球通韩国"
+                },
+                {
+                    "ip": "103.29.35.63",
+                    "name": "全球通印度"
+                },
+                {
+                    "ip": "202.127.74.126",
+                    "name": "全球通新加坡"
+                },
+                {
+                    "ip": "199.59.231.234",
+                    "name": "全球通香港"
+                },
+                {
+                    "ip": "159.100.205.129",
+                    "name": "全球通台湾"
+                }
+            ]
+        }
+    }
+}
+
+export  default Server;
\ No newline at end of file