diff --git a/.gitignore b/.gitignore
index 8450d6e9c8816c874fbbec539e5c395c4cb46148..23c1a956bc8381a05673482e04ecf2c193fc0980 100644
--- a/.gitignore
+++ b/.gitignore
@@ -10,3 +10,4 @@ lab-designs/viewer
 .vscode
 doc/
 web-service/npm-debug.log
+web-service/server/npm-debug.log
\ No newline at end of file
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 5a81ac8cbd6f27bb4388eab11d6d4b1b15875410..c6173a686d387b22961dcd2ee6b90c120dc39631 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -8,7 +8,7 @@ stages:
  - all
 # - build
 # - test
-# - deploy
+ - deploy
 
 #cache:
 #  paths:
@@ -29,6 +29,20 @@ linux:
     - make
     - ctest --output-on-failure
 
+webserver-deploy:
+  only:
+    - master
+  stage: deploy
+  tags:
+    - linux
+  variables:
+    NODE_SERVER: '10.0.0.9'
+  script:
+    - npm install web-service/server
+    - browserify web-service/public/js/index.js -o web-service/public/js/bundle.js
+    - rsync -vr --delete web-service/ nodejs@${NODE_SERVER}:/srv/nodejs/web-service
+    - ssh nodejs@${NODE_SERVER} -- "npm install web-service/server && pm2 restart web-service"
+
 windows:
   stage: all
   variables:
diff --git a/applications/player/src/main.cpp b/applications/player/src/main.cpp
index 33e146880668fb29224aaffc8942cb4255ebf0c1..2741cac2ef832f3cc60073f7320d443ea9620c9d 100644
--- a/applications/player/src/main.cpp
+++ b/applications/player/src/main.cpp
@@ -40,6 +40,21 @@ static void visualizeDepthMap(	const cv::Mat &depth, cv::Mat &out,
 	//out.setTo(cv::Scalar(255, 255, 255), mask);
 }
 
+static std::string nameForCodec(ftl::codecs::codec_t c) {
+	switch(c) {
+	case codec_t::JPG 	: return "JPEG";
+	case codec_t::PNG	: return "PNG";
+	case codec_t::H264	: return "H264";
+	case codec_t::HEVC	: return "HEVC";
+	case codec_t::JSON	: return "JSON";
+	case codec_t::POSE	: return "POSE";
+	case codec_t::RAW	: return "RAW";
+	case codec_t::CALIBRATION : return "CALIBRATION";
+	case codec_t::MSGPACK : return "MSGPACK";
+	default: return std::string("UNKNOWN (") + std::to_string((int)c) + std::string(")");
+	}
+}
+
 int main(int argc, char **argv) {
     std::string filename(argv[1]);
     LOG(INFO) << "Playing: " << filename;
@@ -73,6 +88,11 @@ int main(int argc, char **argv) {
 			if (!(channel_mask[spkt.streamID][(int)spkt.channel])) {
 				channel_mask[spkt.streamID].set((int)spkt.channel);
 				LOG(INFO) << " - Channel " << (int)spkt.channel << " found (" << (int)spkt.streamID << ")";
+				LOG(INFO) << "     - Codec = " << nameForCodec(pkt.codec);
+				LOG(INFO) << "     - Width = " << ftl::codecs::getWidth(pkt.definition);
+				LOG(INFO) << "     - Height = " << ftl::codecs::getHeight(pkt.definition);
+				LOG(INFO) << "     - Start Time = " << float(spkt.timestamp - r.getStartTime()) / 1000.0f << "(s)";
+				LOG(INFO) << "     - Blocks = " << (int)pkt.block_total;
 			}
 
 			if (spkt.streamID == current_stream) {
diff --git a/applications/reconstruct/src/ilw/discontinuity.cu b/applications/reconstruct/src/ilw/discontinuity.cu
index fe78d47158e81ce02be82953601151f5bf31703a..fcadde03efa6078c8041a244b717c99fbb24581f 100644
--- a/applications/reconstruct/src/ilw/discontinuity.cu
+++ b/applications/reconstruct/src/ilw/discontinuity.cu
@@ -5,11 +5,12 @@
 using ftl::cuda::Mask;
 
 template <int RADIUS>
-__global__ void discontinuity_kernel(ftl::cuda::TextureObject<int> mask_out, ftl::cuda::TextureObject<float> depth, ftl::rgbd::Camera params) {
+__global__ void discontinuity_kernel(ftl::cuda::TextureObject<int> mask_out, ftl::cuda::TextureObject<float> depth,
+										const cv::Size size, const double minDepth, const double maxDepth) {
 	const unsigned int x = blockIdx.x*blockDim.x + threadIdx.x;
 	const unsigned int y = blockIdx.y*blockDim.y + threadIdx.y;
 
-	if (x < params.width && y < params.height) {
+	if (x < size.width && y < size.height) {
 		Mask mask(0);
 
 		const float d = depth.tex2D((int)x, (int)y);
@@ -17,7 +18,7 @@ __global__ void discontinuity_kernel(ftl::cuda::TextureObject<int> mask_out, ftl
 		// Calculate depth between 0.0 and 1.0
 		//float p = (d - params.minDepth) / (params.maxDepth - params.minDepth);
 
-		if (d >= params.minDepth && d <= params.maxDepth) {
+		if (d >= minDepth && d <= maxDepth) {
 			/* Orts-Escolano S. et al. 2016. Holoportation: Virtual 3D teleportation in real-time. */
 			// Is there a discontinuity nearby?
 			for (int u=-RADIUS; u<=RADIUS; ++u) {
@@ -26,22 +27,25 @@ __global__ void discontinuity_kernel(ftl::cuda::TextureObject<int> mask_out, ftl
 					if (fabs(depth.tex2D((int)x+u, (int)y+v) - d) > 0.1f) mask.isDiscontinuity(true);
 				}
 			}
-        }
-        
-        mask_out(x,y) = (int)mask;
+		}
+		
+		mask_out(x,y) = (int)mask;
 	}
 }
 
-void ftl::cuda::discontinuity(ftl::cuda::TextureObject<int> &mask_out, ftl::cuda::TextureObject<float> &depth, const ftl::rgbd::Camera &params, uint discon, cudaStream_t stream) {
-	const dim3 gridSize((params.width + T_PER_BLOCK - 1)/T_PER_BLOCK, (params.height + T_PER_BLOCK - 1)/T_PER_BLOCK);
+void ftl::cuda::discontinuity(ftl::cuda::TextureObject<int> &mask_out, ftl::cuda::TextureObject<float> &depth,
+								const cv::Size size, const double minDepth, const double maxDepth,
+								uint discon, cudaStream_t stream) {
+			
+	const dim3 gridSize((size.width + T_PER_BLOCK - 1)/T_PER_BLOCK, (size.height + T_PER_BLOCK - 1)/T_PER_BLOCK);
 	const dim3 blockSize(T_PER_BLOCK, T_PER_BLOCK);
 
 	switch (discon) {
-    case 5 :	discontinuity_kernel<5><<<gridSize, blockSize, 0, stream>>>(mask_out, depth, params); break;
-	case 4 :	discontinuity_kernel<4><<<gridSize, blockSize, 0, stream>>>(mask_out, depth, params); break;
-	case 3 :	discontinuity_kernel<3><<<gridSize, blockSize, 0, stream>>>(mask_out, depth, params); break;
-	case 2 :	discontinuity_kernel<2><<<gridSize, blockSize, 0, stream>>>(mask_out, depth, params); break;
-	case 1 :	discontinuity_kernel<1><<<gridSize, blockSize, 0, stream>>>(mask_out, depth, params); break;
+	case 5 :	discontinuity_kernel<5><<<gridSize, blockSize, 0, stream>>>(mask_out, depth, size, minDepth, maxDepth); break;
+	case 4 :	discontinuity_kernel<4><<<gridSize, blockSize, 0, stream>>>(mask_out, depth, size, minDepth, maxDepth); break;
+	case 3 :	discontinuity_kernel<3><<<gridSize, blockSize, 0, stream>>>(mask_out, depth, size, minDepth, maxDepth); break;
+	case 2 :	discontinuity_kernel<2><<<gridSize, blockSize, 0, stream>>>(mask_out, depth, size, minDepth, maxDepth); break;
+	case 1 :	discontinuity_kernel<1><<<gridSize, blockSize, 0, stream>>>(mask_out, depth, size, minDepth, maxDepth); break;
 	default:	break;
 	}
 	cudaSafeCall( cudaGetLastError() );
diff --git a/applications/reconstruct/src/ilw/ilw_cuda.hpp b/applications/reconstruct/src/ilw/ilw_cuda.hpp
index fad97afbdb8385d9381eff5af40fb272f172ca1a..94e522347bb64d9b0b091c3f0cd4914b8abd91c2 100644
--- a/applications/reconstruct/src/ilw/ilw_cuda.hpp
+++ b/applications/reconstruct/src/ilw/ilw_cuda.hpp
@@ -10,15 +10,15 @@ namespace ftl {
 namespace cuda {
 
 struct ILWParams {
-    float spatial_smooth;
-    float colour_smooth;
+	float spatial_smooth;
+	float colour_smooth;
 	float fill_match;
 	float fill_threshold;
 	float match_threshold;
-    float cost_ratio;
-    float cost_threshold;
+	float cost_ratio;
+	float cost_threshold;
 	float range;
-    uint flags;
+	uint flags;
 };
 
 static const uint kILWFlag_IgnoreBad = 0x0001;
@@ -29,7 +29,9 @@ static const uint kILWFlag_ColourConfidenceOnly = 0x0008;
 void discontinuity(
 	ftl::cuda::TextureObject<int> &mask_out,
 	ftl::cuda::TextureObject<float> &depth,
-	const ftl::rgbd::Camera &params,
+	const cv::Size size,
+	const double minDepth,
+	const double maxDepth,
 	uint discon, cudaStream_t stream
 );
 
@@ -49,32 +51,32 @@ void preprocess_depth(
 );
 
 void correspondence(
-    ftl::cuda::TextureObject<float> &d1,
-    ftl::cuda::TextureObject<float> &d2,
-    ftl::cuda::TextureObject<uchar4> &c1,
-    ftl::cuda::TextureObject<uchar4> &c2,
-    ftl::cuda::TextureObject<float> &dout,
-    ftl::cuda::TextureObject<float> &conf,
+	ftl::cuda::TextureObject<float> &d1,
+	ftl::cuda::TextureObject<float> &d2,
+	ftl::cuda::TextureObject<uchar4> &c1,
+	ftl::cuda::TextureObject<uchar4> &c2,
+	ftl::cuda::TextureObject<float> &dout,
+	ftl::cuda::TextureObject<float> &conf,
 	ftl::cuda::TextureObject<int> &mask,
-    float4x4 &pose1,
-    float4x4 &pose1_inv,
-    float4x4 &pose2,
-    const ftl::rgbd::Camera &cam1,
-    const ftl::rgbd::Camera &cam2,
-    const ILWParams &params, int win,
-    cudaStream_t stream
+	float4x4 &pose1,
+	float4x4 &pose1_inv,
+	float4x4 &pose2,
+	const ftl::rgbd::Camera &cam1,
+	const ftl::rgbd::Camera &cam2,
+	const ILWParams &params, int win,
+	cudaStream_t stream
 );
 
 void move_points(
-    ftl::cuda::TextureObject<float> &d_old,
-    ftl::cuda::TextureObject<float> &d_new,
+	ftl::cuda::TextureObject<float> &d_old,
+	ftl::cuda::TextureObject<float> &d_new,
 	ftl::cuda::TextureObject<float> &conf,
-    const ftl::rgbd::Camera &camera,
-    const float4x4 &pose,
+	const ftl::rgbd::Camera &camera,
+	const float4x4 &pose,
 	const ILWParams &params,
-    float rate,
-    int radius,
-    cudaStream_t stream
+	float rate,
+	int radius,
+	cudaStream_t stream
 );
 
 }
diff --git a/applications/reconstruct/src/main.cpp b/applications/reconstruct/src/main.cpp
index ce571918c28a237cb962bd263dbe5ace6b903655..9cb5c0819d144c66cc49f125adbe64dc360deed5 100644
--- a/applications/reconstruct/src/main.cpp
+++ b/applications/reconstruct/src/main.cpp
@@ -156,6 +156,7 @@ static void run(ftl::Configurable *root) {
 
 		for (auto &input : sources) {
 			string uri = input->getURI();
+
 			auto T = transformations.find(uri);
 			if (T == transformations.end()) {
 				LOG(WARNING) << "Camera pose for " + uri + " not found in transformations";
diff --git a/applications/reconstruct/src/reconstruction.cpp b/applications/reconstruct/src/reconstruction.cpp
index be99d1581c3555b1d52aab156bb9ed316d559de6..8a868e56eeeba0a459a939f086d891652093c7e2 100644
--- a/applications/reconstruct/src/reconstruction.cpp
+++ b/applications/reconstruct/src/reconstruction.cpp
@@ -60,10 +60,36 @@ Reconstruction::Reconstruction(nlohmann::json &config, const std::string name) :
 
 		ftl::pool.push([this](int id) {
 			UNIQUE_LOCK(fs_align_.mtx, lk);
+			
+			/*rgb_.resize(fs_align_.frames.size());
+			for (size_t i = 0; i < rgb_.size(); i++) {
+				auto &depth = fs_align_.frames[i].get<cv::cuda::GpuMat>(ftl::codecs::Channel::Depth);
+				auto &color = fs_align_.frames[i].get<cv::cuda::GpuMat>(ftl::codecs::Channel::Colour);
+
+				if (depth.size() != color.size()) {
+					std::swap(rgb_[i], color);
+					cv::cuda::resize(rgb_[i], color, depth.size(), 0.0, 0.0, cv::INTER_LINEAR);
+				}
+			}*/
+
 			pipeline_->apply(fs_align_, fs_align_, 0);
 			
 			// TODO: To use second GPU, could do a download, swap, device change,
 			// then upload to other device. Or some direct device-2-device copy.
+			/*
+			for (size_t i = 0; i < rgb_.size(); i++) {
+				auto &depth = fs_align_.frames[i].get<cv::cuda::GpuMat>(ftl::codecs::Channel::Depth);
+				auto &color = fs_align_.frames[i].get<cv::cuda::GpuMat>(ftl::codecs::Channel::Colour);
+				auto &tmp = rgb_[i];
+
+				// TODO doesn't always work correctly if resolution changes
+				if (!tmp.empty() && (depth.size() != tmp.size())) {
+					std::swap(tmp, color);
+					fs_align_.frames[i].resetTexture(ftl::codecs::Channel::Colour);
+					fs_align_.frames[i].createTexture<uchar4>(ftl::codecs::Channel::Colour, true);
+				}
+			}*/
+
 			fs_align_.swapTo(fs_render_);
 
 			LOG(INFO) << "Align complete... " << fs_align_.timestamp;
diff --git a/applications/reconstruct/src/reconstruction.hpp b/applications/reconstruct/src/reconstruction.hpp
index 6546f85c163142d4b1cbdf9f84081ca21d703907..50441bedc32b33bb2e64bfac953eda7a579edcb7 100644
--- a/applications/reconstruct/src/reconstruction.hpp
+++ b/applications/reconstruct/src/reconstruction.hpp
@@ -27,11 +27,14 @@ class Reconstruction : public ftl::Configurable {
 
 	private:
 	bool busy_;
+	
 	ftl::rgbd::FrameSet fs_render_;
 	ftl::rgbd::FrameSet fs_align_;
 	ftl::rgbd::Group *group_;
 	ftl::operators::Graph *pipeline_;
 	ftl::render::Triangular *renderer_;
+
+	std::vector<cv::cuda::GpuMat> rgb_;
 };
 
 }
diff --git a/components/codecs/include/ftl/codecs/bitrates.hpp b/components/codecs/include/ftl/codecs/bitrates.hpp
index d34ede8adb88c647949051853dde33f81221a8d2..fbacb49790577d8d354e9acff69b275834803a1e 100644
--- a/components/codecs/include/ftl/codecs/bitrates.hpp
+++ b/components/codecs/include/ftl/codecs/bitrates.hpp
@@ -49,6 +49,8 @@ enum struct definition_t : uint8_t {
 	Invalid
 };
 
+definition_t findDefinition(int width, int height);
+
 /**
  * Get width in pixels of definition.
  */
@@ -97,10 +99,8 @@ static const preset_t kPresetMinimum = -1;
  * Represents the details of each preset codec configuration.
  */
 struct CodecPreset {
-	definition_t colour_res;
-	definition_t depth_res;
-	bitrate_t colour_qual;
-	bitrate_t depth_qual;
+	definition_t res;
+	bitrate_t qual;
 };
 
 /**
diff --git a/components/codecs/include/ftl/codecs/channels.hpp b/components/codecs/include/ftl/codecs/channels.hpp
index 6673275fe7b4f874b0807c48e690e24b4aaddb51..9aa2143020c89c1331f3d408f33f2ef359cf77b8 100644
--- a/components/codecs/include/ftl/codecs/channels.hpp
+++ b/components/codecs/include/ftl/codecs/channels.hpp
@@ -8,30 +8,31 @@ namespace ftl {
 namespace codecs {
 
 enum struct Channel : int {
-    None			= -1,
-    Colour			= 0,	// 8UC3 or 8UC4
-    Left			= 0,
-    Depth			= 1,	// 32S or 32F
-    Right			= 2,	// 8UC3 or 8UC4
-    Colour2			= 2,
-    Disparity		= 3,
-    Depth2			= 3,
-    Deviation		= 4,
-    Screen          = 4,
-    Normals			= 5,	// 32FC4
-    Points			= 6,	// 32FC4 (should be deprecated)
-    Confidence		= 7,	// 32F
-    Contribution	= 7,	// 32F
-    EnergyVector	= 8,	// 32FC4
-    Flow			= 9,	// 32F
-    Smoothing       = 9,    // 32F
-    Energy			= 10,	// 32F
+	None			= -1,
+	Colour			= 0,	// 8UC3 or 8UC4
+	Left			= 0,
+	Depth			= 1,	// 32S or 32F
+	Right			= 2,	// 8UC3 or 8UC4
+	Colour2			= 2,
+	Disparity		= 3,
+	Depth2			= 3,
+	Deviation		= 4,
+	Screen			= 4,
+	Normals			= 5,	// 32FC4
+	Points			= 6,	// 32FC4 (should be deprecated)
+	Confidence		= 7,	// 32F
+	Contribution	= 7,	// 32F
+	EnergyVector	= 8,	// 32FC4
+	Flow			= 9,	// 32F
+	Smoothing		= 9,	// 32F
+	Energy			= 10,	// 32F
 	Mask			= 11,	// 32U
 	Density			= 12,	// 32F
 	Support1		= 13,	// 8UC4 (currently)
 	Support2		= 14,	// 8UC4 (currently)
-    Segmentation	= 15,	// 32S?
-    ColourNormals   = 16,   // 8UC4
+	Segmentation	= 15,	// 32S?
+	ColourNormals	= 16,	// 8UC4
+	ColourHighRes	= 20,	// 8UC3 or 8UC4
 
 	AudioLeft		= 32,
 	AudioRight		= 33,
@@ -39,7 +40,7 @@ enum struct Channel : int {
 	Configuration	= 64,	// JSON Data
 	Calibration		= 65,	// Camera Parameters Object
 	Pose			= 66,	// Eigen::Matrix4d
-    Index           = 67,
+	Index           = 67,
 	Data			= 2048	// Custom data, any codec.
 };
 
@@ -51,7 +52,7 @@ std::string name(Channel c);
 int type(Channel c);
 
 class Channels {
-    public:
+	public:
 
 	class iterator {
 		public:
@@ -67,48 +68,48 @@ class Channels {
 		unsigned int ix_;
 	};
 
-    inline Channels() { mask = 0; }
-    inline explicit Channels(unsigned int m) { mask = m; }
-    inline explicit Channels(Channel c) { mask = (c == Channel::None) ? 0 : 0x1 << static_cast<unsigned int>(c); }
-    inline Channels &operator=(Channel c) { mask = (c == Channel::None) ? 0 : 0x1 << static_cast<unsigned int>(c); return *this; }
-    inline Channels operator|(Channel c) const { return (c == Channel::None) ? Channels(mask) : Channels(mask | (0x1 << static_cast<unsigned int>(c))); }
-    inline Channels operator+(Channel c) const { return (c == Channel::None) ? Channels(mask) : Channels(mask | (0x1 << static_cast<unsigned int>(c))); }
-    inline Channels &operator|=(Channel c) { mask |= (c == Channel::None) ? 0 : (0x1 << static_cast<unsigned int>(c)); return *this; }
-    inline Channels &operator+=(Channel c) { mask |= (c == Channel::None) ? 0 : (0x1 << static_cast<unsigned int>(c)); return *this; }
-    inline Channels &operator-=(Channel c) { mask &= ~((c == Channel::None) ? 0 : (0x1 << static_cast<unsigned int>(c))); return *this; }
-    inline Channels &operator+=(unsigned int c) { mask |= (0x1 << c); return *this; }
-    inline Channels &operator-=(unsigned int c) { mask &= ~(0x1 << c); return *this; }
-
-    inline bool has(Channel c) const {
-        return (c == Channel::None) ? true : mask & (0x1 << static_cast<unsigned int>(c));
-    }
-
-    inline bool has(unsigned int c) const {
-        return mask & (0x1 << c);
-    }
+	inline Channels() { mask = 0; }
+	inline explicit Channels(unsigned int m) { mask = m; }
+	inline explicit Channels(Channel c) { mask = (c == Channel::None) ? 0 : 0x1 << static_cast<unsigned int>(c); }
+	inline Channels &operator=(Channel c) { mask = (c == Channel::None) ? 0 : 0x1 << static_cast<unsigned int>(c); return *this; }
+	inline Channels operator|(Channel c) const { return (c == Channel::None) ? Channels(mask) : Channels(mask | (0x1 << static_cast<unsigned int>(c))); }
+	inline Channels operator+(Channel c) const { return (c == Channel::None) ? Channels(mask) : Channels(mask | (0x1 << static_cast<unsigned int>(c))); }
+	inline Channels &operator|=(Channel c) { mask |= (c == Channel::None) ? 0 : (0x1 << static_cast<unsigned int>(c)); return *this; }
+	inline Channels &operator+=(Channel c) { mask |= (c == Channel::None) ? 0 : (0x1 << static_cast<unsigned int>(c)); return *this; }
+	inline Channels &operator-=(Channel c) { mask &= ~((c == Channel::None) ? 0 : (0x1 << static_cast<unsigned int>(c))); return *this; }
+	inline Channels &operator+=(unsigned int c) { mask |= (0x1 << c); return *this; }
+	inline Channels &operator-=(unsigned int c) { mask &= ~(0x1 << c); return *this; }
+
+	inline bool has(Channel c) const {
+		return (c == Channel::None) ? true : mask & (0x1 << static_cast<unsigned int>(c));
+	}
+
+	inline bool has(unsigned int c) const {
+		return mask & (0x1 << c);
+	}
 
 	inline iterator begin() { return iterator(*this, 0); }
 	inline iterator end() { return iterator(*this, 32); }
 
-    inline operator unsigned int() { return mask; }
-    inline operator bool() { return mask > 0; }
-    inline operator Channel() {
-        if (mask == 0) return Channel::None;
-        int ix = 0;
-        int tmask = mask;
-        while (!(tmask & 0x1) && ++ix < 32) tmask >>= 1;
-        return static_cast<Channel>(ix);
-    }
-    
-    inline size_t count() { return std::bitset<32>(mask).count(); }
-    inline void clear() { mask = 0; }
-
-    static const size_t kMax = 32;
+	inline operator unsigned int() { return mask; }
+	inline operator bool() { return mask > 0; }
+	inline operator Channel() {
+		if (mask == 0) return Channel::None;
+		int ix = 0;
+		int tmask = mask;
+		while (!(tmask & 0x1) && ++ix < 32) tmask >>= 1;
+		return static_cast<Channel>(ix);
+	}
+	
+	inline size_t count() { return std::bitset<32>(mask).count(); }
+	inline void clear() { mask = 0; }
+
+	static const size_t kMax = 32;
 
 	static Channels All();
 
-    private:
-    unsigned int mask;
+	private:
+	unsigned int mask;
 };
 
 inline Channels::iterator Channels::iterator::operator++() { Channels::iterator i = *this; while (++ix_ < 32 && !channels_.has(ix_)); return i; }
@@ -124,9 +125,9 @@ static const Channels kAllChannels(0xFFFFFFFFu);
 inline bool isFloatChannel(ftl::codecs::Channel chan) {
 	switch (chan) {
 	case Channel::Depth		:
-    //case Channel::Normals   :
+	//case Channel::Normals   :
 	case Channel::Confidence:
-    case Channel::Flow      :
+	case Channel::Flow      :
 	case Channel::Density:
 	case Channel::Energy	: return true;
 	default					: return false;
@@ -139,11 +140,11 @@ inline bool isFloatChannel(ftl::codecs::Channel chan) {
 MSGPACK_ADD_ENUM(ftl::codecs::Channel);
 
 inline ftl::codecs::Channels operator|(ftl::codecs::Channel a, ftl::codecs::Channel b) {
-    return ftl::codecs::Channels(a) | b;
+	return ftl::codecs::Channels(a) | b;
 }
 
 inline ftl::codecs::Channels operator+(ftl::codecs::Channel a, ftl::codecs::Channel b) {
-    return ftl::codecs::Channels(a) | b;
+	return ftl::codecs::Channels(a) | b;
 }
 
 #endif  // _FTL_RGBD_CHANNELS_HPP_
diff --git a/components/codecs/include/ftl/codecs/encoder.hpp b/components/codecs/include/ftl/codecs/encoder.hpp
index 9c3aa8fefc64810bf7660e323b44a3c4440d5098..ed817f7b1c5a59b133c36317195a5c2da9203e56 100644
--- a/components/codecs/include/ftl/codecs/encoder.hpp
+++ b/components/codecs/include/ftl/codecs/encoder.hpp
@@ -46,16 +46,16 @@ void free(Encoder *&e);
  * convert an OpenCV Mat or GpuMat into a compressed byte array of some form.
  */
 class Encoder {
-    public:
-    friend Encoder *allocateEncoder(ftl::codecs::definition_t,
+	public:
+	friend Encoder *allocateEncoder(ftl::codecs::definition_t,
 			ftl::codecs::device_t, ftl::codecs::codec_t);
-    friend void free(Encoder *&);
+	friend void free(Encoder *&);
 
-    public:
-    Encoder(ftl::codecs::definition_t maxdef,
+	public:
+	Encoder(ftl::codecs::definition_t maxdef,
 			ftl::codecs::definition_t mindef,
 			ftl::codecs::device_t dev);
-    virtual ~Encoder();
+	virtual ~Encoder();
 
 	/**
 	 * Wrapper encode to allow use of presets.
@@ -76,21 +76,21 @@ class Encoder {
 	 * @param cb Callback containing compressed data
 	 * @return True if succeeded with encoding.
 	 */
-    virtual bool encode(
+	virtual bool encode(
 			const cv::cuda::GpuMat &in,
 			ftl::codecs::definition_t definition,
 			ftl::codecs::bitrate_t bitrate,
 			const std::function<void(const ftl::codecs::Packet&)> &cb)=0;
 
 	// TODO: Eventually, use GPU memory directly since some encoders can support this
-    //virtual bool encode(const cv::cuda::GpuMat &in, std::vector<uint8_t> &out, bitrate_t bix, bool)=0;
+	//virtual bool encode(const cv::cuda::GpuMat &in, std::vector<uint8_t> &out, bitrate_t bix, bool)=0;
 
 	virtual void reset() {}
 
 	virtual bool supports(ftl::codecs::codec_t codec)=0;
 
-    protected:
-    bool available;
+	protected:
+	bool available;
 	const ftl::codecs::definition_t max_definition;
 	const ftl::codecs::definition_t min_definition;
 	const ftl::codecs::device_t device;
diff --git a/components/codecs/include/ftl/codecs/hevc.hpp b/components/codecs/include/ftl/codecs/hevc.hpp
index f658635d6f239b4aa7a21331f60f6936c517ba93..b3a32246544f3cf24a4ad09345c2f47a96eb0735 100644
--- a/components/codecs/include/ftl/codecs/hevc.hpp
+++ b/components/codecs/include/ftl/codecs/hevc.hpp
@@ -97,6 +97,10 @@ inline NALType getNALType(const std::vector<uint8_t> &data) {
 	return static_cast<NALType>((data[4] >> 1) & 0x3F);
 }
 
+inline bool validNAL(const std::vector<uint8_t> &data) {
+	return data[0] == 0 && data[1] == 0 && data[2] == 0 && data[3] == 1;
+}
+
 /**
  * Check the HEVC bitstream for an I-Frame. With NvPipe, all I-Frames start
  * with a VPS NAL unit so just check for this.
diff --git a/components/codecs/include/ftl/codecs/nvpipe_encoder.hpp b/components/codecs/include/ftl/codecs/nvpipe_encoder.hpp
index 5d04068c53cf3b46dee73c63cf8e2fcf674f148d..07c874d128b8915265f7f4035c2fcf294b4ea07a 100644
--- a/components/codecs/include/ftl/codecs/nvpipe_encoder.hpp
+++ b/components/codecs/include/ftl/codecs/nvpipe_encoder.hpp
@@ -8,20 +8,20 @@ namespace ftl {
 namespace codecs {
 
 class NvPipeEncoder : public ftl::codecs::Encoder {
-    public:
-    NvPipeEncoder(ftl::codecs::definition_t maxdef,
+	public:
+	NvPipeEncoder(ftl::codecs::definition_t maxdef,
 			ftl::codecs::definition_t mindef);
-    ~NvPipeEncoder();
+	~NvPipeEncoder();
 
 	bool encode(const cv::cuda::GpuMat &in, ftl::codecs::preset_t preset,
 			const std::function<void(const ftl::codecs::Packet&)> &cb) {
 		return Encoder::encode(in, preset, cb);
 	}
 
-    bool encode(const cv::cuda::GpuMat &in, ftl::codecs::definition_t definition, ftl::codecs::bitrate_t bitrate,
+	bool encode(const cv::cuda::GpuMat &in, ftl::codecs::definition_t definition, ftl::codecs::bitrate_t bitrate,
 			const std::function<void(const ftl::codecs::Packet&)>&) override;
 
-    //bool encode(const cv::cuda::GpuMat &in, std::vector<uint8_t> &out, bitrate_t bix, bool);
+	//bool encode(const cv::cuda::GpuMat &in, std::vector<uint8_t> &out, bitrate_t bix, bool);
 
 	void reset();
 
@@ -29,18 +29,18 @@ class NvPipeEncoder : public ftl::codecs::Encoder {
 
 	static constexpr int kFlagRGB = 0x00000001;
 
-    private:
-    NvPipe *nvenc_;
-    definition_t current_definition_;
-    bool is_float_channel_;
+	private:
+	NvPipe *nvenc_;
+	definition_t current_definition_;
+	bool is_float_channel_;
 	bool was_reset_;
 	ftl::codecs::codec_t preference_;
 	cv::cuda::GpuMat tmp_;
 	cv::cuda::GpuMat tmp2_;
 	cv::cuda::Stream stream_;
 
-    bool _encoderMatch(const cv::cuda::GpuMat &in, definition_t def);
-    bool _createEncoder(const cv::cuda::GpuMat &in, definition_t def, bitrate_t rate);
+	bool _encoderMatch(const cv::cuda::GpuMat &in, definition_t def);
+	bool _createEncoder(const cv::cuda::GpuMat &in, definition_t def, bitrate_t rate);
 	ftl::codecs::definition_t _verifiedDefinition(ftl::codecs::definition_t def, const cv::cuda::GpuMat &in);
 };
 
diff --git a/components/codecs/src/bitrates.cpp b/components/codecs/src/bitrates.cpp
index 45a5057687f8add5cbfdaf02718e880a3361bd40..37889f5a55bf0337d1b3b750538587d1cc81f537 100644
--- a/components/codecs/src/bitrates.cpp
+++ b/components/codecs/src/bitrates.cpp
@@ -8,21 +8,18 @@ using ftl::codecs::preset_t;
 using ftl::codecs::definition_t;
 using ftl::codecs::codec_t;
 
+
 static const CodecPreset special_presets[] = {
-	definition_t::HTC_VIVE, definition_t::HTC_VIVE, bitrate_t::High, bitrate_t::High
+	definition_t::HTC_VIVE, bitrate_t::High
 };
 
 static const CodecPreset presets[] = {
-	definition_t::HD1080, definition_t::HD1080, bitrate_t::High, bitrate_t::High,
-	definition_t::HD1080, definition_t::HD720, bitrate_t::Standard, bitrate_t::Standard,
-	definition_t::HD720, definition_t::HD720, bitrate_t::High, bitrate_t::High,
-	definition_t::HD720, definition_t::SD576, bitrate_t::Standard, bitrate_t::Standard,
-	definition_t::SD576, definition_t::SD576, bitrate_t::High, bitrate_t::High,
-	definition_t::SD576, definition_t::SD480, bitrate_t::Standard, bitrate_t::Standard,
-	definition_t::SD480, definition_t::SD480, bitrate_t::High, bitrate_t::High,
-	definition_t::SD480, definition_t::LD360, bitrate_t::Standard, bitrate_t::Standard,
-	definition_t::LD360, definition_t::LD360, bitrate_t::Standard, bitrate_t::Standard,
-	definition_t::LD360, definition_t::LD360, bitrate_t::Low, bitrate_t::Low
+	definition_t::HD1080, bitrate_t::High,
+	definition_t::HD720, bitrate_t::High,
+	definition_t::SD576, bitrate_t::High,
+	definition_t::SD480, bitrate_t::High,
+	definition_t::LD360, bitrate_t::Standard,
+	definition_t::LD360, bitrate_t::Low
 };
 
 static const float kAspectRatio = 1.777778f;
@@ -53,11 +50,27 @@ int ftl::codecs::getHeight(definition_t d) {
 	return resolutions[static_cast<int>(d)].height;
 }
 
+definition_t ftl::codecs::findDefinition(int width, int height) {
+	int best = 0;
+	bool smaller = true;
+
+	for(const Resolution res : resolutions) {
+		if ((res.width == width) && (res.height == height)) {
+			return static_cast<definition_t>(best);
+		}
+		best++;
+	}
+
+	// TODO error!
+	return definition_t::Any;
+}
+
+/*
 const CodecPreset &ftl::codecs::getPreset(preset_t p) {
 	if (p < 0 && p >= -1) return special_presets[std::abs(p+1)];
-    if (p > kPresetWorst) return presets[kPresetWorst];
-    if (p < kPresetBest) return presets[kPresetBest];
-    return presets[p];
+	if (p > kPresetWorst) return presets[kPresetWorst];
+	if (p < kPresetBest) return presets[kPresetBest];
+	return presets[p];
 }
 
 preset_t ftl::codecs::findPreset(size_t width, size_t height) {
@@ -80,10 +93,11 @@ preset_t ftl::codecs::findPreset(size_t width, size_t height) {
 	for (preset_t i=kPresetMinimum; i<=kPresetWorst; ++i) {
 		const auto &preset = getPreset(i);
 
-		if ((int)preset.colour_res == best_def && (int)preset.depth_res == best_def) {
+		if ((int)preset.res == best_def) {
 			return i;
 		}
 	}
 
 	return kPresetWorst;
 }
+*/
diff --git a/components/codecs/src/encoder.cpp b/components/codecs/src/encoder.cpp
index 9a7eac72def3a20b966e4332d45d8073f57c47f6..7c7f9a35848441597cd4650fc7d8eaf87bdd01e4 100644
--- a/components/codecs/src/encoder.cpp
+++ b/components/codecs/src/encoder.cpp
@@ -36,7 +36,7 @@ static MUTEX mutex;
 
 Encoder *ftl::codecs::allocateEncoder(ftl::codecs::definition_t maxdef,
 		ftl::codecs::device_t dev, ftl::codecs::codec_t codec) {
-    UNIQUE_LOCK(mutex, lk);
+	UNIQUE_LOCK(mutex, lk);
 	if (!has_been_init) init_encoders();
 
 	for (auto i=encoders.begin(); i!=encoders.end(); ++i) {
@@ -55,10 +55,10 @@ Encoder *ftl::codecs::allocateEncoder(ftl::codecs::definition_t maxdef,
 }
 
 void ftl::codecs::free(Encoder *&enc) {
-    UNIQUE_LOCK(mutex, lk);
-    enc->reset();
+	UNIQUE_LOCK(mutex, lk);
+	enc->reset();
 	enc->available = true;
-    enc = nullptr;
+	enc = nullptr;
 }
 
 Encoder::Encoder(definition_t maxdef, definition_t mindef, device_t dev) :
@@ -72,9 +72,8 @@ Encoder::~Encoder() {
 
 bool Encoder::encode(const cv::cuda::GpuMat &in, preset_t preset,
 			const std::function<void(const ftl::codecs::Packet&)> &cb) {
-	const auto &settings = ftl::codecs::getPreset(preset);
-	const definition_t definition = (in.type() == CV_32F) ? settings.depth_res : settings.colour_res;
-	const bitrate_t bitrate = (in.type() == CV_32F) ? settings.depth_qual : settings.colour_qual;
+	const definition_t definition = ftl::codecs::findDefinition(in.size().width, in.size().height);
+	const bitrate_t bitrate = bitrate_t::High;
 
 	return encode(in, definition, bitrate, cb);
 }
diff --git a/components/codecs/src/nvpipe_decoder.cpp b/components/codecs/src/nvpipe_decoder.cpp
index 77a3105f88b84f2b9c00f5dba152bbc9814c70db..d6652549c73fa5c5d6388030c480e73f331a4a7c 100644
--- a/components/codecs/src/nvpipe_decoder.cpp
+++ b/components/codecs/src/nvpipe_decoder.cpp
@@ -37,6 +37,7 @@ bool NvPipeDecoder::decode(const ftl::codecs::Packet &pkt, cv::cuda::GpuMat &out
 	is_float_channel_ = is_float_frame;
 	last_definition_ = pkt.definition;
 
+	//LOG(INFO) << "DECODE OUT: " << out.rows << ", " << out.type();
 	//LOG(INFO) << "DECODE RESOLUTION: (" << (int)pkt.definition << ") " << ftl::codecs::getWidth(pkt.definition) << "x" << ftl::codecs::getHeight(pkt.definition);
 
 	// Build a decoder instance of the correct kind
@@ -49,8 +50,6 @@ bool NvPipeDecoder::decode(const ftl::codecs::Packet &pkt, cv::cuda::GpuMat &out
 		if (!nv_decoder_) {
 			//LOG(INFO) << "Bitrate=" << (int)bitrate << " width=" << ABRController::getColourWidth(bitrate);
 			LOG(FATAL) << "Could not create decoder: " << NvPipe_GetError(NULL);
-		} else {
-			DLOG(INFO) << "Decoder created";
 		}
 
 		seen_iframe_ = false;
@@ -60,38 +59,46 @@ bool NvPipeDecoder::decode(const ftl::codecs::Packet &pkt, cv::cuda::GpuMat &out
 	tmp_.create(cv::Size(ftl::codecs::getWidth(pkt.definition),ftl::codecs::getHeight(pkt.definition)), (is_float_frame) ? CV_16U : CV_8UC4);
 
 	// Check for an I-Frame
-	if (pkt.codec == ftl::codecs::codec_t::HEVC) {
-		if (ftl::codecs::hevc::isIFrame(pkt.data)) seen_iframe_ = true;
-	} else if (pkt.codec == ftl::codecs::codec_t::H264) {
-		if (ftl::codecs::h264::isIFrame(pkt.data)) seen_iframe_ = true;
+	if (!seen_iframe_) {
+		if (pkt.codec == ftl::codecs::codec_t::HEVC) {
+			if (ftl::codecs::hevc::isIFrame(pkt.data)) seen_iframe_ = true;
+		} else if (pkt.codec == ftl::codecs::codec_t::H264) {
+			if (ftl::codecs::h264::isIFrame(pkt.data)) seen_iframe_ = true;
+		}
 	}
 
 	// No I-Frame yet so don't attempt to decode P-Frames.
 	if (!seen_iframe_) return false;
 
+	// Final checks for validity
+	if (pkt.data.size() == 0 || tmp_.size() != out.size()) { // || !ftl::codecs::hevc::validNAL(pkt.data)) {
+		LOG(ERROR) << "Failed to decode packet";
+		return false;
+	}
+
 	int rc = NvPipe_Decode(nv_decoder_, pkt.data.data(), pkt.data.size(), tmp_.data, tmp_.cols, tmp_.rows, tmp_.step);
 	if (rc == 0) LOG(ERROR) << "NvPipe decode error: " << NvPipe_GetError(nv_decoder_);
 
 	if (is_float_frame) {
 		// Is the received frame the same size as requested output?
-		if (out.rows == ftl::codecs::getHeight(pkt.definition)) {
+		//if (out.rows == ftl::codecs::getHeight(pkt.definition)) {
 			tmp_.convertTo(out, CV_32FC1, 1.0f/1000.0f, stream_);
-		} else {
+		/*} else {
 			LOG(WARNING) << "Resizing decoded frame from " << tmp_.size() << " to " << out.size();
 			// FIXME: This won't work on GPU
 			tmp_.convertTo(tmp_, CV_32FC1, 1.0f/1000.0f, stream_);
 			cv::cuda::resize(tmp_, out, out.size(), 0, 0, cv::INTER_NEAREST, stream_);
-		}
+		}*/
 	} else {
 		// Is the received frame the same size as requested output?
-		if (out.rows == ftl::codecs::getHeight(pkt.definition)) {
+		//if (out.rows == ftl::codecs::getHeight(pkt.definition)) {
 			// Flag 0x1 means frame is in RGB so needs conversion to BGR
 			if (pkt.flags & 0x1) {
 				cv::cuda::cvtColor(tmp_, out, cv::COLOR_RGBA2BGR, 0, stream_);
 			} else {
 				cv::cuda::cvtColor(tmp_, out, cv::COLOR_BGRA2BGR, 0, stream_);
 			}
-		} else {
+		/*} else {
 			LOG(WARNING) << "Resizing decoded frame from " << tmp_.size() << " to " << out.size();
 			// FIXME: This won't work on GPU, plus it allocates extra memory...
 			// Flag 0x1 means frame is in RGB so needs conversion to BGR
@@ -101,7 +108,7 @@ bool NvPipeDecoder::decode(const ftl::codecs::Packet &pkt, cv::cuda::GpuMat &out
 				cv::cuda::cvtColor(tmp_, tmp_, cv::COLOR_BGRA2BGR, 0, stream_);
 			}
 			cv::cuda::resize(tmp_, out, out.size(), 0.0, 0.0, cv::INTER_LINEAR, stream_);
-		}
+		}*/
 	}
 
 	stream_.waitForCompletion();
diff --git a/components/codecs/src/nvpipe_encoder.cpp b/components/codecs/src/nvpipe_encoder.cpp
index 132a3209ad0849dd76f1a5f7438eba8f5655b854..86fccdefc0d91f85694b105986eb49a423cc5863 100644
--- a/components/codecs/src/nvpipe_encoder.cpp
+++ b/components/codecs/src/nvpipe_encoder.cpp
@@ -123,7 +123,7 @@ bool NvPipeEncoder::encode(const cv::cuda::GpuMat &in, definition_t odefinition,
 	pkt.data.resize(cs);
 	was_reset_ = false;
 
-	if (cs == 0) {
+	if (cs == 0 || cs >= ftl::codecs::kVideoBufferSize) {
 		LOG(ERROR) << "Could not encode video frame: " << NvPipe_GetError(nvenc_);
 		return false;
 	} else {
diff --git a/components/codecs/src/opencv_decoder.cpp b/components/codecs/src/opencv_decoder.cpp
index 0b9feea46e5925f16ce5ab323747d94d8bdb1d2a..c3c5e9567deb6752bf2395d0c36e345fcead50ee 100644
--- a/components/codecs/src/opencv_decoder.cpp
+++ b/components/codecs/src/opencv_decoder.cpp
@@ -18,7 +18,7 @@ bool OpenCVDecoder::accepts(const ftl::codecs::Packet &pkt) {
 }
 
 bool OpenCVDecoder::decode(const ftl::codecs::Packet &pkt, cv::cuda::GpuMat &out) {
-
+	//CHECK(cv::Size(ftl::codecs::getWidth(pkt.definition), ftl::codecs::getHeight(pkt.definition)) == out.size()); 
 	int chunk_dim = std::sqrt(pkt.block_total);
 	int chunk_width = out.cols / chunk_dim;
 	int chunk_height = out.rows / chunk_dim;
@@ -37,7 +37,6 @@ bool OpenCVDecoder::decode(const ftl::codecs::Packet &pkt, cv::cuda::GpuMat &out
 	// Apply colour correction to chunk
 	//ftl::rgbd::colourCorrection(tmp_rgb, gamma_, temperature_);
 
-
 	// TODO:(Nick) Decode directly into double buffer if no scaling
 	// Can either check JPG/PNG headers or just use pkt definition.
 
diff --git a/components/codecs/src/opencv_encoder.cpp b/components/codecs/src/opencv_encoder.cpp
index 5dc1995a82e6147184572d6e45d20a8a49561ddc..772922e7bf740a1b47ecea2260d448afca217fe0 100644
--- a/components/codecs/src/opencv_encoder.cpp
+++ b/components/codecs/src/opencv_encoder.cpp
@@ -17,7 +17,7 @@ OpenCVEncoder::OpenCVEncoder(ftl::codecs::definition_t maxdef,
 }
 
 OpenCVEncoder::~OpenCVEncoder() {
-    
+	
 }
 
 bool OpenCVEncoder::supports(ftl::codecs::codec_t codec) {
@@ -30,9 +30,12 @@ bool OpenCVEncoder::supports(ftl::codecs::codec_t codec) {
 
 bool OpenCVEncoder::encode(const cv::cuda::GpuMat &in, definition_t definition, bitrate_t bitrate, const std::function<void(const ftl::codecs::Packet&)> &cb) {
 	bool is_colour = in.type() != CV_32F;
-	current_definition_ = definition;
+
+	// Ensure definition does not exceed max
+	current_definition_ = ((int)definition < (int)max_definition) ? max_definition : definition;
 
 	in.download(tmp_);
+	//CHECK(cv::Size(ftl::codecs::getWidth(definition), ftl::codecs::getHeight(definition)) == in.size()); 
 
 	// Scale down image to match requested definition...
 	if (ftl::codecs::getHeight(current_definition_) < in.rows) {
@@ -42,11 +45,12 @@ bool OpenCVEncoder::encode(const cv::cuda::GpuMat &in, definition_t definition,
 	}
 
 	// Represent float at 16bit int
-    if (!is_colour) {
+	if (!is_colour) {
 		tmp_.convertTo(tmp_, CV_16UC1, 1000);
 	}
 
-	chunk_dim_ = (definition == definition_t::LD360) ? 1 : 4;
+	// FIXME: Chunking is broken so forced to single chunk
+	chunk_dim_ = 1; //(definition == definition_t::LD360) ? 1 : 4;
 	chunk_count_ = chunk_dim_ * chunk_dim_;
 	jobs_ = chunk_count_;
 
@@ -94,6 +98,7 @@ bool OpenCVEncoder::_encodeBlock(const cv::Mat &in, ftl::codecs::Packet &pkt, bi
 	int cx = (pkt.block_number % chunk_dim_) * chunk_width;
 	int cy = (pkt.block_number / chunk_dim_) * chunk_height;
 	cv::Rect roi(cx,cy,chunk_width,chunk_height);
+
 	cv::Mat chunkHead = in(roi);
 
 	if (pkt.codec == codec_t::PNG) {
diff --git a/components/codecs/test/nvpipe_codec_unit.cpp b/components/codecs/test/nvpipe_codec_unit.cpp
index 609ce56a50059978af931b718de57098201a0c1a..dccc65f9671a70ddc1879d12d0b8ef38aa9a1f01 100644
--- a/components/codecs/test/nvpipe_codec_unit.cpp
+++ b/components/codecs/test/nvpipe_codec_unit.cpp
@@ -22,19 +22,18 @@ namespace ftl {
 	}
 }
 
+/*
 TEST_CASE( "NvPipeEncoder::encode() - A colour test image at preset 0" ) {
 	ftl::codecs::NvPipeEncoder encoder(definition_t::HD1080, definition_t::SD480);
 	cv::cuda::GpuMat m(cv::Size(1920,1080), CV_8UC3, cv::Scalar(0,0,0));
 
 	int block_total = 0;
 	std::atomic<int> block_count = 0;
-
-	const CodecPreset &preset = ftl::codecs::getPreset(ftl::codecs::kPreset0);
-
-	bool r = encoder.encode(m, ftl::codecs::kPreset0, [&block_total, &block_count, preset, m](const ftl::codecs::Packet &pkt) {
+	encoder.encode()
+	bool r = encoder.encode(m, definition::H, [&block_total, &block_count, preset, m](const ftl::codecs::Packet &pkt) {
 		REQUIRE( pkt.codec == codec_t::HEVC );
 		REQUIRE( pkt.data.size() > 0 );
-		REQUIRE( pkt.definition == preset.colour_res );
+		REQUIRE( pkt.definition == definition_t::HD1080 );
 
 		block_total = pkt.block_total;
 		block_count++;
@@ -51,12 +50,10 @@ TEST_CASE( "NvPipeEncoder::encode() - A depth test image at preset 0" ) {
 	int block_total = 0;
 	std::atomic<int> block_count = 0;
 
-	const CodecPreset &preset = ftl::codecs::getPreset(ftl::codecs::kPreset0);
-
 	bool r = encoder.encode(m, ftl::codecs::kPreset0, [&block_total, &block_count, preset](const ftl::codecs::Packet &pkt) {
 		REQUIRE( pkt.codec == codec_t::HEVC );
 		REQUIRE( pkt.data.size() > 0 );
-		REQUIRE( pkt.definition == preset.depth_res );
+		REQUIRE( pkt.definition == definition_t::HD1080 );
 
 		block_total = pkt.block_total;
 		block_count++;
@@ -65,6 +62,7 @@ TEST_CASE( "NvPipeEncoder::encode() - A depth test image at preset 0" ) {
 	REQUIRE( r );
 	REQUIRE( block_count == block_total );
 }
+*/
 
 TEST_CASE( "NvPipeDecoder::decode() - A colour test image" ) {
 	ftl::codecs::NvPipeEncoder encoder(definition_t::HD1080, definition_t::SD480);
@@ -83,7 +81,8 @@ TEST_CASE( "NvPipeDecoder::decode() - A colour test image" ) {
 		});
 	}
 
-	SECTION("Full HD in, 720 out, FHD encoding") {
+	// No longer supported
+	/*SECTION("Full HD in, 720 out, FHD encoding") {
 		in = cv::cuda::GpuMat(cv::Size(1920,1080), CV_8UC3, cv::Scalar(255,0,0));
 		out = cv::cuda::GpuMat(cv::Size(1280,720), CV_8UC3, cv::Scalar(0,0,0));
 
@@ -92,9 +91,10 @@ TEST_CASE( "NvPipeDecoder::decode() - A colour test image" ) {
 		});
 
 		REQUIRE( (out.rows == 720) );
-	}
+	}*/
 
-	SECTION("HHD in, FHD out, FHD encoding") {
+	// No longer supported
+	/*SECTION("HHD in, FHD out, FHD encoding") {
 		in = cv::cuda::GpuMat(cv::Size(1280,720), CV_8UC3, cv::Scalar(255,0,0));
 		out = cv::cuda::GpuMat(cv::Size(1920,1080), CV_8UC3, cv::Scalar(0,0,0));
 
@@ -103,9 +103,10 @@ TEST_CASE( "NvPipeDecoder::decode() - A colour test image" ) {
 		});
 
 		REQUIRE( (out.rows == 1080) );
-	}
+	}*/
 
-	SECTION("FHD in, HHD out, SD encoding") {
+	// No longer supported
+	/*SECTION("FHD in, HHD out, SD encoding") {
 		in = cv::cuda::GpuMat(cv::Size(1920,1080), CV_8UC3, cv::Scalar(255,0,0));
 		out = cv::cuda::GpuMat(cv::Size(1280,720), CV_8UC3, cv::Scalar(0,0,0));
 
@@ -114,7 +115,7 @@ TEST_CASE( "NvPipeDecoder::decode() - A colour test image" ) {
 		});
 
 		REQUIRE( (out.rows == 720) );
-	}
+	}*/
 
 	REQUIRE( r );
 	REQUIRE( (cv::cuda::sum(out) != cv::Scalar(0,0,0)) );
diff --git a/components/codecs/test/opencv_codec_unit.cpp b/components/codecs/test/opencv_codec_unit.cpp
index 2505eeb8994e1397bc19175f7f0903bdb3000c9d..961658db5d4da887e8597dd90f093fa8e6abc5b2 100644
--- a/components/codecs/test/opencv_codec_unit.cpp
+++ b/components/codecs/test/opencv_codec_unit.cpp
@@ -21,15 +21,17 @@ namespace ftl {
 	}
 	}
 }
-
+/*
 TEST_CASE( "OpenCVEncoder::encode() - A colour test image at preset 0" ) {
 	ftl::codecs::OpenCVEncoder encoder(definition_t::HD1080, definition_t::SD480);
-	cv::cuda::GpuMat m(cv::Size(1024,576), CV_8UC3, cv::Scalar(0,0,0));
 
 	int block_total = 0;
 	std::atomic<int> block_count = 0;
 
 	const CodecPreset &preset = ftl::codecs::getPreset(ftl::codecs::kPreset4);
+	cv::cuda::GpuMat m(cv::Size(ftl::codecs::getWidth(preset.res),
+								ftl::codecs::getHeight(preset.res)),
+						CV_8UC3, cv::Scalar(0,0,0));
 
 	std::mutex mtx;
 
@@ -37,7 +39,7 @@ TEST_CASE( "OpenCVEncoder::encode() - A colour test image at preset 0" ) {
 		std::unique_lock<std::mutex> lk(mtx);
 		REQUIRE( pkt.codec == codec_t::JPG );
 		REQUIRE( pkt.data.size() > 0 );
-		REQUIRE( pkt.definition == preset.colour_res );
+		REQUIRE( pkt.definition == preset.res );
 
 		block_total = pkt.block_total;
 		block_count++;
@@ -66,7 +68,7 @@ TEST_CASE( "OpenCVEncoder::encode() - A depth test image at preset 0" ) {
 		std::unique_lock<std::mutex> lk(mtx);
 		REQUIRE( pkt.codec == codec_t::PNG );
 		REQUIRE( pkt.data.size() > 0 );
-		REQUIRE( pkt.definition == preset.depth_res );
+		REQUIRE( pkt.definition == preset.res );
 
 		block_total = pkt.block_total;
 		block_count++;
@@ -78,7 +80,7 @@ TEST_CASE( "OpenCVEncoder::encode() - A depth test image at preset 0" ) {
 	REQUIRE( r );
 	REQUIRE( block_count == block_total );
 }
-
+*/
 TEST_CASE( "OpenCVDecoder::decode() - A colour test image no resolution change" ) {
 	ftl::codecs::OpenCVEncoder encoder(definition_t::HD1080, definition_t::SD480);
 	ftl::codecs::OpenCVDecoder decoder;
diff --git a/components/common/cpp/src/configuration.cpp b/components/common/cpp/src/configuration.cpp
index f8c982a811652252f14205b00e7f778d906ab8ed..9fd9d3081f18781ce0474438a3e1829d3b082b33 100644
--- a/components/common/cpp/src/configuration.cpp
+++ b/components/common/cpp/src/configuration.cpp
@@ -438,7 +438,7 @@ static bool findConfiguration(const string &file, const vector<string> &paths) {
 	}
 
 	if (found) {
-		_indexConfig(config);
+		//_indexConfig(config);
 		return true;
 	} else {
 		return false;
@@ -593,6 +593,9 @@ Configurable *ftl::config::configure(int argc, char **argv, const std::string &r
 
 	string root_str = (options.find("root") != options.end()) ? nlohmann::json::parse(options["root"]).get<string>() : root;
 
+	if (options.find("id") != options.end()) config["$id"] = nlohmann::json::parse(options["id"]).get<string>();
+	_indexConfig(config);
+
 	Configurable *rootcfg = create<Configurable>(config);
 	if (root_str.size() > 0) {
 		LOG(INFO) << "Setting root to " << root_str;
diff --git a/components/net/js/src/peer.js b/components/net/js/src/peer.js
index d330f93c38290283f2255abe4628a0b9a6be241d..75ffcff1d4bfa81002ad68cbfa0f17886921935b 100644
--- a/components/net/js/src/peer.js
+++ b/components/net/js/src/peer.js
@@ -1,280 +1,243 @@
-const net = require('net');
-const ws = require('ws');
-const urijs = require('uri-js');
-const binary = require('bops');
-const browser = require('detect-browser').detect();
-const isbrowser = !browser || browser.name != "node";
+const msgpack = require('msgpack5')()
+  , encode  = msgpack.encode
+  , decode  = msgpack.decode;
 
-class Peer {
-	constructor(uri) {
-		let t = typeof uri;
-		this.handlers_ = {
-			'open': [],
-			'data': [],
-			'error': [],
-			'close': []
-		};
-		this.handshake_ = false;
-		this.lasterr_ = null;
-		this.connected_ = false;
-		this.valid_ = false;
-		this.uuid_ = binary.create(16);
-		if (!isbrowser) {
-			this.buffer_ = new Buffer(0); // Only in nodejs
-		}
-		if (t == "string") {
-			this._fromURI(uri);
-		}
-		else if (t == "object") {
-			this._fromObject(uri);
-		}
-	}
-	error(errno) {
-		this.lasterr_ = errno;
-		this.dispatch('error', [errno]);
-	}
-	isValid() {
-		return this.valid_;
-	}
-    /**
-     * Construct the correct kind of socket connection from a URI.
-     */
-	_fromURI(uri) {
-		let uriobj = urijs.parse(uri);
-		this.uri_ = uri;
-		this.scheme_ = uriobj.scheme;
-		// Could not parse uri so report error
-		if (uriobj.scheme === undefined || uriobj.host === undefined) {
-			this.error(Socket.ERROR_MALFORMEDURI);
-			return;
-		}
-		// Websocket protocol
-		if (this.scheme_ == "ws") {
-			// Detect if in browser or not, choose correct websocket object
-			if (typeof WebSocket == "undefined") {
-				// Nodejs
-				this.socket_ = new ws(uri);
-			}
-			else {
-				// Browser
-				this.socket_ = new WebSocket(uri);
-			}
-			this._initWebsocket();
-			// TCP
-		}
-		else if (this.scheme_ == "tcp") {
-			if (!isbrowser) {
-				this.socket_ = net.connect(uriobj.port, uriobj.host);
-				this._initTCPSocket();
-			}
-			else {
-				this.error(Socket.ERROR_TCPINBROWSER);
-			}
-			// Unrecognised protocol
-		}
-		else {
-			this.error(Socket.ERROR_BADPROTOCOL);
-		}
-	}
-	_fromObject(sock) {
-		this.socket_ = sock;
-		if (typeof WebSocket == "undefined") {
-			if (sock instanceof ws)
-				this.scheme_ = "ws";
-			else if (sock instanceof net.Socket)
-				this.scheme_ = "tcp";
-			else
-				this.scheme_ = null;
-		}
-		else {
-			if (sock instanceof WebSocket)
-				this.scheme_ = "ws";
-			else
-				this.scheme_ = null;
-		}
-		if (this.scheme_ == "ws")
-			this._initWebsocket();
-		else if (this.scheme_ == "tcp")
-			this._initTCPSocket();
-	}
-    /**
-     * Setup correct handlers for a websocket connection.
-     */
-	_initWebsocket() {
-		this.valid_ = true;
-		let dataHandler = (data) => {
-			this.processMessage(data);
-		};
-		if (this.socket_.addEventHandler) {
-			this.socket_.addEventHandler('message', event => {
-				dataHandler(event.data);
-			});
-		}
-		else {
-			this.socket_.on('message', dataHandler);
-		}
-		this.socket_.on('open', () => {
-			//this.connected_ = true;
-			//this.dispatch('open', []);
-		});
-		this.socket_.on('error', (err) => {
-			this.connected_ = false;
-			this.valid_ = false;
-			switch (err.errno) {
-				case 'ENOTFOUND':
-					this.lasterr_ = Socket.ERROR_BADHOST;
-					break;
-				default: this.lasterr_ = err.errno;
-			}
-			this.dispatch('error', [this.lasterr_]);
-		});
-		this.socket_.on('close', () => {
-			this.dispatch('close', []);
-		});
-	}
-	processMessage(buffer) {
-		if (!this.handshake_) {
-			// Check handshake
-			if (!checkMagic(buffer)) {
+const kConnecting = 1;
+const kConnected = 2;
+const kDisconnected = 3;
+
+// Generate a unique id for this webservice
+let my_uuid = new Uint8Array(16);
+my_uuid[0] = 44;
+my_uuid = Buffer.from(my_uuid);
+
+const kMagic = 0x0009340053640912;
+const kVersion = 0;
+
+/**
+ * Wrap a web socket with a MsgPack RCP protocol that works with our C++ version.
+ * @param {websocket} ws Websocket object
+ */
+function Peer(ws) {
+	this.sock = ws;
+	this.status = kConnecting;
+	this.id = null;
+	this.string_id = "";
+	this.bindings = {};
+	this.proxies = {};
+	this.events = {};
+	this.callbacks = {};
+	this.cbid = 0;
+
+	this.uri = "unknown";
+	this.name = "unknown";
+	this.master = false;
+
+	this.sock.on("message", (raw) => {
+		// console.log(raw)
+		let msg = decode(raw);
+		console.log("MSG", msg)
+		if (this.status == kConnecting) {
+			if (msg[1] != "__handshake__") {
+				console.log("Bad handshake");
 				this.close();
-				this.error(Socket.ERROR_BADHANDSHAKE);
-				return 0;
 			}
-			binary.copy(buffer, this.uuid_, 0, 8, 16);
-			let proto_size = binary.readUInt32LE(buffer, 24);
-			this.handshake_ = true;
-			this.connected_ = true;
-			this.dispatch('open', []);
-			return 28 + proto_size;
 		}
-		else {
-			let size = binary.readUInt32LE(buffer, 0);
-			let service = binary.readUInt32LE(buffer, 4);
-			console.log("Message: " + service + "(size=" + size + ")");
-			// Do we have a complete message yet?
-			if (size > 1024 * 1024 * 100) {
-				this.error(Socket.ERROR_LARGEMESSAGE);
-				this.close();
-				return 0;
-			}
-			else if (buffer.length - 4 >= size) {
-				// Yes, so dispatch
-				this.dispatch(service, [size, binary.subarray(buffer, 8)]);
-				return size + 4;
-			}
-			else {
-				return 0;
+		//console.log("MSG", msg);
+		if (msg[0] == 0) {
+			// Notification
+			if (msg.length == 3) {
+				this._dispatchNotification(msg[1], msg[2]);
+			// Call
+			} else {
+				this._dispatchCall(msg[2], msg[1], msg[3]);
 			}
+		} else if (msg[0] == 1) {
+			this._dispatchResponse(msg[1], msg[3]);
 		}
+	});
+
+	this.sock.on("close", () => {
+		this.status = kDisconnected;
+		this._notify("disconnect", this);
+	});
+
+	this.sock.on("error", () => {
+		console.error("Socket error");
+		this.sock.close();
+		this.status = kDisconnected;
+	});
+
+	this.bind("__handshake__", (magic, version, id) => {
+		if (magic == kMagic) {
+			console.log("Handshake received");
+			this.status = kConnected;
+			this.id = id.buffer;
+			this.string_id  = id.toString('hex');
+			this._notify("connect", this);
+		} else {
+			console.log("Magic does not match");
+			this.close();
+		}
+	});
+
+	this.send("__handshake__", kMagic, kVersion, [my_uuid]);
+}
+
+Peer.uuid = my_uuid;
+
+/**
+ * @private
+ */
+Peer.prototype._dispatchNotification = function(name, args) {
+	if (this.bindings.hasOwnProperty(name)) {
+		//console.log("Notification for: ", name);
+		this.bindings[name].apply(this, args);
+	} else {
+		console.log("Missing handler for: ", name);
 	}
-    /**
-     * Setup TCP socket handlers and message buffering mechanism.
-     */
-	_initTCPSocket() {
-		this.valid_ = true;
-		let dataHandler = (data) => {
-			this.buffer_ = Buffer.concat([this.buffer_, data]);
-			while (this.buffer_.length >= 8) {
-				let s = this.processMessage(this.buffer_);
-				if (s == 0)
-					break;
-				this.buffer_ = binary.subarray(this.buffer_, s);
-			}
-		};
-		this.socket_.on('data', dataHandler);
-		this.socket_.on('connect', () => {
-			//this.connected_ = true;
-			//this.dispatch('open', []);
-		});
-		this.socket_.on('error', (err) => {
-			this.connected_ = false;
-			this.valid_ = false;
-			switch (err.errno) {
-				case 'ENOTFOUND':
-					this.error(Socket.ERROR_BADHOST);
-					break;
-				default: this.error(err.errno);
+}
+
+/**
+ * @private
+ */
+Peer.prototype._dispatchCall = function(name, id, args) {
+	if (this.bindings.hasOwnProperty(name)) {
+		//console.log("Call for:", name, id);
+
+		try {
+			let res = this.bindings[name].apply(this, args);
+			this.sock.send(encode([1,id,name,res]));
+		} catch(e) {
+			console.error("Could to dispatch or return call");
+			this.close();
+		}
+	} else if (this.proxies.hasOwnProperty(name)) {
+		//console.log("Proxy for:", name, id);
+		args.unshift((res) => {
+			try {
+				this.sock.send(encode([1,id,name,res]));
+			} catch(e) {
+				this.close();
 			}
 		});
-		this.socket_.on('close', () => {
-			this.dispatch('close', []);
-		});
-	}
-	isConnected() {
-		return this.connected_;
-	}
-    /**
-     * Register event handlers.
-     */
-	on(name, f) {
-		if (typeof name == "string") {
-			if (this.handlers_.hasOwnProperty(name)) {
-				this.handlers_[name].push(f);
-			}
-			else {
-				console.error("Unrecognised handler: ", name);
-			}
-			if (name == "error" && this.lasterr_ != null) {
-				f(this.lasterr_);
-			}
-		}
-		else if (typeof name == "number") {
-			if (this.handlers_[name] === undefined)
-				this.handlers_[name] = [];
-			this.handlers_[name].push(f);
-		}
-		else {
-			console.error("Invalid handler: ", name);
-		}
+		this.proxies[name].apply(this, args);
+	} else {
+		console.log("Missing handler for: ", name);
 	}
-	dispatch(h, args) {
-		if (this.handlers_.hasOwnProperty(h)) {
-			let hs = this.handlers_[h];
-			for (var i = 0; i < hs.length; i++) {
-				hs[i].apply(this, args);
-			}
-			return true;
-		}
-		else {
-			return false;
-		}
+}
+
+/**
+ * @private
+ */
+Peer.prototype._dispatchResponse = function(id, res) {
+	if (this.callbacks.hasOwnProperty(id)) {
+		this.callbacks[id].call(this, res);
+		delete this.callbacks[id];
+	} else {
+		console.log("Missing callback");
 	}
-	close() {
-		if (this.socket_ == null)
-			return;
-		if (this.scheme_ == "ws") {
-			this.socket_.close();
-		}
-		else {
-			this.socket_.destroy();
-		}
-		this.socket_ = null;
+}
+
+/**
+ * Register an RPC handler that will be called from a remote machine. Remotely
+ * passed arguments are provided to the given function as normal arguments, and
+ * if the function returns a value, it will be returned over the network also.
+ * 
+ * @param {string} name The name of the function
+ * @param {function} f A function or lambda to be callable remotely
+ */
+Peer.prototype.bind = function(name, f) {
+	if (this.bindings.hasOwnProperty(name)) {
+		//console.error("Duplicate bind to same procedure");
+		this.bindings[name] = f;
+	} else {
+		this.bindings[name] = f;
 	}
-	_socket() {
-		return this.socket_;
+}
+
+/**
+ * Allow an RPC call to pass through to another machine with minimal local
+ * processing.
+ */
+Peer.prototype.proxy = function(name, f) {
+	if (this.proxies.hasOwnProperty(name)) {
+		//console.error("Duplicate proxy to same procedure");
+		this.proxies[name] = f;
+	} else {
+		this.proxies[name] = f;
 	}
-	getURI() {
-		return this.uri_;
+}
+
+/**
+ * Call a procedure on a remote machine.
+ * 
+ * @param {string} name Name of the procedure
+ * @param {function} cb Callback to receive return value as argument
+ * @param {...} args Any number of arguments to also pass to remote procedure
+ */
+Peer.prototype.rpc = function(name, cb, ...args) {
+	let id = this.cbid++;
+	this.callbacks[id] = cb;
+
+	try {
+		this.sock.send(encode([0, id, name, args]));
+	} catch(e) {
+		this.close();
 	}
-	asyncCall(name, cb /*, ...*/) {
+}
+
+Peer.prototype.sendB = function(name, args) {
+	try {
+		this.sock.send(encode([0, name, args]));
+	} catch(e) {
+		this.close();
 	}
-	send(id /*, ...*/) {
-		//this.socket_.write(
+}
+
+/**
+ * Call a remote procedure but with no return value expected.
+ * 
+ * @param {string} name Name of the procedure
+ * @param {...} args Any number of arguments to also pass to remote procedure
+ */
+Peer.prototype.send = function(name, ...args) {
+	try {
+		this.sock.send(encode([0, name, args]));
+	} catch(e) {
+		this.close();
 	}
 }
 
-Peer.ERROR_BADPROTOCOL = "Bad Protocol";
-Peer.ERROR_BADHOST = "Unknown host";
-Peer.ERROR_BADHANDSHAKE = "Invalid Handshake";
-Peer.ERROR_MALFORMEDURI = "Malformed URI";
-Peer.ERROR_TCPINBROWSER = "TCP invalid in browser";
-Peer.ERROR_LARGEMESSAGE = "Network message too large";
+Peer.prototype.close = function() {
+	this.sock.close();
+	this.status = kDisconnected;
+}
+
+/**
+ * @private
+ */
+Peer.prototype._notify = function(evt, ...args) {
+	if (this.events.hasOwnProperty(evt)) {
+		for (let i=0; i<this.events[evt].length; i++) {
+			let f = this.events[evt][i];
+			f.apply(this, args);
+		}
+	}
+}
 
-function checkMagic(buffer) {
-	if (buffer.length < 8) return false;
-	let lo_magic = binary.readUInt32LE(buffer,0);
-	let hi_magic = binary.readUInt32LE(buffer,4);
-	return (lo_magic == 0x53640912 && hi_magic == 0x10993400)
+/**
+ * Register a callback for socket events. Events include: 'connect',
+ * 'disconnect' and 'error'.
+ * 
+ * @param {string} evt Event name
+ * @param {function} f Callback on event
+ */
+Peer.prototype.on = function(evt, f) {
+	if (!this.events.hasOwnProperty(evt)) {
+		this.events[evt] = [];
+	}
+	this.events[evt].push(f);
 }
 
 module.exports = Peer;
diff --git a/components/operators/src/colours.cpp b/components/operators/src/colours.cpp
index 9c6fff8b887fc645da71fbc99e018a6dc99b6313..6a49f6ede7f4cd7f130a889378ce41880bfff617 100644
--- a/components/operators/src/colours.cpp
+++ b/components/operators/src/colours.cpp
@@ -14,11 +14,12 @@ ColourChannels::~ColourChannels() {
 bool ColourChannels::apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, ftl::rgbd::Source *s, cudaStream_t stream) {
 	auto cvstream = cv::cuda::StreamAccessor::wrapStream(stream);
 
+	auto &col = in.get<cv::cuda::GpuMat>(Channel::Colour);
+
 	// Convert colour from BGR to BGRA if needed
-	if (in.get<cv::cuda::GpuMat>(Channel::Colour).type() == CV_8UC3) {
+	if (col.type() == CV_8UC3) {
 		//cv::cuda::Stream cvstream = cv::cuda::StreamAccessor::wrapStream(stream);
 		// Convert to 4 channel colour
-		auto &col = in.get<cv::cuda::GpuMat>(Channel::Colour);
 		temp_.create(col.size(), CV_8UC4);
 		cv::cuda::swap(col, temp_);
 		cv::cuda::cvtColor(temp_,col, cv::COLOR_BGR2BGRA, 0, cvstream);
@@ -27,5 +28,13 @@ bool ColourChannels::apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, ftl::rgb
 	//in.resetTexture(Channel::Colour);
 	in.createTexture<uchar4>(Channel::Colour, true);
 
+	auto &depth = in.get<cv::cuda::GpuMat>(Channel::Depth);
+	if (depth.size() != col.size()) {
+		auto &col2 = in.create<cv::cuda::GpuMat>(Channel::ColourHighRes);
+		cv::cuda::resize(col, col2, depth.size(), 0.0, 0.0, cv::INTER_LINEAR, cvstream);
+		in.createTexture<uchar4>(Channel::ColourHighRes, true);
+		in.swapChannels(Channel::Colour, Channel::ColourHighRes);
+	}
+
 	return true;
 }
diff --git a/components/operators/src/mask.cpp b/components/operators/src/mask.cpp
index f923f11d06a39df882eaf83289b37296aba0ada5..c7dcbb2ac40ef9ff3b4f445eb467e89663cebccb 100644
--- a/components/operators/src/mask.cpp
+++ b/components/operators/src/mask.cpp
@@ -22,7 +22,9 @@ bool DiscontinuityMask::apply(ftl::rgbd::Frame &in, ftl::rgbd::Frame &out, ftl::
 		out.createTexture<int>(Channel::Mask, ftl::rgbd::Format<int>(in.get<cv::cuda::GpuMat>(Channel::Depth).size())),
 		in.createTexture<uchar4>(Channel::Support1),
 		in.createTexture<float>(Channel::Depth),
-		s->parameters(), radius, threshold, stream
+		in.get<cv::cuda::GpuMat>(Channel::Depth).size(),
+		s->parameters().minDepth, s->parameters().maxDepth,
+		radius, threshold, stream
 	);
 
 	return true;
diff --git a/components/operators/src/mask.cu b/components/operators/src/mask.cu
index e385f41b14459802dbf52ef85aef2f891eceff08..91ddf19dd3b6451d7802cc622a08a660adfdd360 100644
--- a/components/operators/src/mask.cu
+++ b/components/operators/src/mask.cu
@@ -4,16 +4,21 @@
 
 using ftl::cuda::Mask;
 
-__global__ void discontinuity_kernel(ftl::cuda::TextureObject<int> mask_out, ftl::cuda::TextureObject<uchar4> support, ftl::cuda::TextureObject<float> depth, ftl::rgbd::Camera params, float threshold, int radius) {
+__global__ void discontinuity_kernel(ftl::cuda::TextureObject<int> mask_out,
+										ftl::cuda::TextureObject<uchar4> support,
+										ftl::cuda::TextureObject<float> depth, 
+										const cv::Size size, const double minDepth, const double maxDepth,
+										float threshold, int radius) {
+	
 	const unsigned int x = blockIdx.x*blockDim.x + threadIdx.x;
 	const unsigned int y = blockIdx.y*blockDim.y + threadIdx.y;
 
-	if (x < params.width && y < params.height) {
+	if (x < size.width && y < size.height) {
 		Mask mask(0);
 
 		const float d = depth.tex2D((int)x, (int)y);
 
-		if (d >= params.minDepth && d <= params.maxDepth) {
+		if (d >= minDepth && d <= maxDepth) {
 			/* Orts-Escolano S. et al. 2016. Holoportation: Virtual 3D teleportation in real-time. */
 
 			// If colour cross support region terminates within the requested
@@ -37,17 +42,21 @@ __global__ void discontinuity_kernel(ftl::cuda::TextureObject<int> mask_out, ftl
 				float dS = depth.tex2D((int)x, (int)y + sup.w + radius);
 				if (fabs(dS - d) > threshold) mask.isDiscontinuity(true);
 			}
-        }
-        
-        mask_out(x,y) = (int)mask;
+		}
+		
+		mask_out(x,y) = (int)mask;
 	}
 }
 
-void ftl::cuda::discontinuity(ftl::cuda::TextureObject<int> &mask_out, ftl::cuda::TextureObject<uchar4> &support, ftl::cuda::TextureObject<float> &depth, const ftl::rgbd::Camera &params, int discon, float thresh, cudaStream_t stream) {
-	const dim3 gridSize((params.width + T_PER_BLOCK - 1)/T_PER_BLOCK, (params.height + T_PER_BLOCK - 1)/T_PER_BLOCK);
+void ftl::cuda::discontinuity(	ftl::cuda::TextureObject<int> &mask_out, ftl::cuda::TextureObject<uchar4> &support,
+								ftl::cuda::TextureObject<float> &depth,
+								const cv::Size size, const double minDepth, const double maxDepth,
+								int discon, float thresh, cudaStream_t stream) {
+	
+	const dim3 gridSize((size.width + T_PER_BLOCK - 1)/T_PER_BLOCK, (size.height + T_PER_BLOCK - 1)/T_PER_BLOCK);
 	const dim3 blockSize(T_PER_BLOCK, T_PER_BLOCK);
 
-    discontinuity_kernel<<<gridSize, blockSize, 0, stream>>>(mask_out, support, depth, params, thresh, discon);
+	discontinuity_kernel<<<gridSize, blockSize, 0, stream>>>(mask_out, support, depth, size, minDepth, maxDepth, thresh, discon);
 	cudaSafeCall( cudaGetLastError() );
 
 #ifdef _DEBUG
@@ -55,8 +64,6 @@ void ftl::cuda::discontinuity(ftl::cuda::TextureObject<int> &mask_out, ftl::cuda
 #endif
 }
 
-
-
 __global__ void cull_discontinuity_kernel(ftl::cuda::TextureObject<int> mask, ftl::cuda::TextureObject<float> depth) {
 	const unsigned int x = blockIdx.x*blockDim.x + threadIdx.x;
 	const unsigned int y = blockIdx.y*blockDim.y + threadIdx.y;
@@ -71,7 +78,7 @@ void ftl::cuda::cull_discontinuity(ftl::cuda::TextureObject<int> &mask, ftl::cud
 	const dim3 gridSize((depth.width() + T_PER_BLOCK - 1)/T_PER_BLOCK, (depth.height() + T_PER_BLOCK - 1)/T_PER_BLOCK);
 	const dim3 blockSize(T_PER_BLOCK, T_PER_BLOCK);
 
-    cull_discontinuity_kernel<<<gridSize, blockSize, 0, stream>>>(mask, depth);
+	cull_discontinuity_kernel<<<gridSize, blockSize, 0, stream>>>(mask, depth);
 	cudaSafeCall( cudaGetLastError() );
 
 #ifdef _DEBUG
diff --git a/components/operators/src/mask_cuda.hpp b/components/operators/src/mask_cuda.hpp
index 6a02aafdbbdfbbbd200355bddc3c7ba33a605483..20c266290f10ce6aca19af3cfe45a9d3f7c03355 100644
--- a/components/operators/src/mask_cuda.hpp
+++ b/components/operators/src/mask_cuda.hpp
@@ -19,7 +19,7 @@ class Mask {
 	#endif
 	__device__ inline operator int() const { return v_; }
 
-    __device__ inline bool is(int m) const { return v_ & m; }
+	__device__ inline bool is(int m) const { return v_ & m; }
 
 	__device__ inline bool isFilled() const { return v_ & kMask_Filled; }
 	__device__ inline bool isDiscontinuity() const { return v_ & kMask_Discontinuity; }
@@ -31,7 +31,7 @@ class Mask {
 	__device__ inline void hasCorrespondence(bool v) { v_ = (v) ? v_ | kMask_Correspondence : v_ & (~kMask_Correspondence); }
 	__device__ inline void isBad(bool v) { v_ = (v) ? v_ | kMask_Bad : v_ & (~kMask_Bad); }
 
-    static constexpr int kMask_Filled = 0x0001;
+	static constexpr int kMask_Filled = 0x0001;
 	static constexpr int kMask_Discontinuity = 0x0002;
 	static constexpr int kMask_Correspondence = 0x0004;
 	static constexpr int kMask_Bad = 0x0008;
@@ -44,7 +44,9 @@ void discontinuity(
 		ftl::cuda::TextureObject<int> &mask,
 		ftl::cuda::TextureObject<uchar4> &support,
 		ftl::cuda::TextureObject<float> &depth,
-		const ftl::rgbd::Camera &params,
+		const cv::Size size,
+		const double minDepth,
+		const double maxDepth,
 		int radius, float threshold,
 		cudaStream_t stream);
 
diff --git a/components/renderers/cpp/src/reprojection.cu b/components/renderers/cpp/src/reprojection.cu
index 9c414f8927572f93795ea4bfb2e17c491f2deb44..72b7cd07275f3c9b41c207009dd4b7eef6ad7c9b 100644
--- a/components/renderers/cpp/src/reprojection.cu
+++ b/components/renderers/cpp/src/reprojection.cu
@@ -94,7 +94,10 @@ __global__ void reprojection_kernel(
 	const float dotproduct = (max(dot(ray,n),-0.1f)+0.1) / 1.1f;
     
 	const float d2 = depth_src.tex2D(int(screenPos.x+0.5f), int(screenPos.y+0.5f));
-	const auto input = in.tex2D(screenPos.x, screenPos.y); //generateInput(in.tex2D((int)screenPos.x, (int)screenPos.y), params, worldPos);
+
+	const float inSX = float(in.width()) / float(depth_src.width());
+	const float inSY = float(in.height()) / float(depth_src.height());
+	const auto input = in.tex2D(screenPos.x*inSX, screenPos.y*inSY); //generateInput(in.tex2D((int)screenPos.x, (int)screenPos.y), params, worldPos);
 
 	// TODO: Z checks need to interpolate between neighbors if large triangles are used
 	//float weight = ftl::cuda::weighting(fabs(camPos.z - d2), params.depthThreshold);
@@ -213,7 +216,11 @@ __global__ void reprojection_kernel(
 	if (screenPos.x >= depth_src.width() || screenPos.y >= depth_src.height()) return;
     
 	const float d2 = depth_src.tex2D((int)(screenPos.x+0.5f), (int)(screenPos.y+0.5f));
-	const auto input = in.tex2D(screenPos.x, screenPos.y); //generateInput(in.tex2D((int)screenPos.x, (int)screenPos.y), params, worldPos);
+
+	const float inSX = float(in.width()) / float(depth_src.width());
+	const float inSY = float(in.height()) / float(depth_src.height());
+	const auto input = in.tex2D(screenPos.x*inSX, screenPos.y*inSY); //generateInput(in.tex2D((int)screenPos.x, (int)screenPos.y), params, worldPos);
+
 	float weight = ftl::cuda::weighting(fabs(camPos.z - d2), 0.02f);
 	const B weighted = make<B>(input) * weight;
 
diff --git a/components/renderers/cpp/src/tri_render.cpp b/components/renderers/cpp/src/tri_render.cpp
index 06d4fe2626f01e989645e19e795ef4df925c96c2..d1d0894f476dfa534ccd075782f17205e6422895 100644
--- a/components/renderers/cpp/src/tri_render.cpp
+++ b/components/renderers/cpp/src/tri_render.cpp
@@ -220,13 +220,13 @@ void Triangular::__reprojectChannel(ftl::rgbd::Frame &output, ftl::codecs::Chann
 		auto &f = scene_->frames[i];
 		auto *s = scene_->sources[i];
 		
-		if (f.get<GpuMat>(in).type() == CV_8UC3) {
+		/*if (f.get<GpuMat>(in).type() == CV_8UC3) {
 			// Convert to 4 channel colour
 			auto &col = f.get<GpuMat>(in);
 			GpuMat tmp(col.size(), CV_8UC4);
 			cv::cuda::swap(col, tmp);
 			cv::cuda::cvtColor(tmp,col, cv::COLOR_BGR2BGRA);
-		}
+		}*/
 
 		auto transform = MatrixConversion::toCUDA(s->getPose().cast<float>().inverse() * t.cast<float>().inverse()) * params_.m_viewMatrixInverse;
 		auto transformR = MatrixConversion::toCUDA(s->getPose().cast<float>().inverse()).getFloat3x3();
@@ -607,7 +607,11 @@ bool Triangular::render(ftl::rgbd::VirtualSource *src, ftl::rgbd::Frame &out, co
 	}
 
 	// Reprojection of colours onto surface
-	_renderChannel(out, Channel::Colour, Channel::Colour, t, stream_);
+	auto main_channel = (scene_->frames[0].hasChannel(Channel::ColourHighRes)) ? Channel::ColourHighRes : Channel::Colour;
+	//if (scene_->frames[0].hasChannel(Channel::ColourHighRes)) {
+	//	LOG(INFO) << "HAVE HIGH RES: " << scene_->frames[0].get<GpuMat>(Channel::ColourHighRes).rows;
+	//}
+	_renderChannel(out, main_channel, Channel::Colour, t, stream_);
 
 	if (value("cool_effect", false)) {
 		auto pose = params.m_viewMatrixInverse.getFloat3x3();
diff --git a/components/rgbd-sources/include/ftl/rgbd/frame.hpp b/components/rgbd-sources/include/ftl/rgbd/frame.hpp
index e7a949600e6ba097aeda54460e83a1529851371e..8411c71a626e23216fcedac5df35e0ce49863f3b 100644
--- a/components/rgbd-sources/include/ftl/rgbd/frame.hpp
+++ b/components/rgbd-sources/include/ftl/rgbd/frame.hpp
@@ -223,7 +223,7 @@ ftl::cuda::TextureObject<T> &Frame::createTexture(ftl::codecs::Channel c, const
 		//LOG(INFO) << "Creating texture object";
 		m.tex = ftl::cuda::TextureObject<T>(m.gpu, interpolated);
 	} else if (m.tex.cvType() != ftl::traits::OpenCVType<T>::value || m.tex.width() != m.gpu.cols || m.tex.height() != m.gpu.rows) {
-		LOG(INFO) << "Recreating texture object for '" << ftl::codecs::name(c) << "'";
+		//LOG(INFO) << "Recreating texture object for '" << ftl::codecs::name(c) << "'";
 		m.tex.free();
 		m.tex = ftl::cuda::TextureObject<T>(m.gpu, interpolated);
 	}
@@ -256,7 +256,7 @@ ftl::cuda::TextureObject<T> &Frame::createTexture(ftl::codecs::Channel c, bool i
 		//LOG(INFO) << "Creating texture object";
 		m.tex = ftl::cuda::TextureObject<T>(m.gpu, interpolated);
 	} else if (m.tex.cvType() != ftl::traits::OpenCVType<T>::value || m.tex.width() != m.gpu.cols || m.tex.height() != m.gpu.rows || m.tex.devicePtr() != m.gpu.data) {
-		LOG(INFO) << "Recreating texture object for '" << ftl::codecs::name(c) << "'.";
+		//LOG(INFO) << "Recreating texture object for '" << ftl::codecs::name(c) << "'.";
 		m.tex.free();
 		m.tex = ftl::cuda::TextureObject<T>(m.gpu, interpolated);
 	}
diff --git a/components/rgbd-sources/src/abr.cpp b/components/rgbd-sources/src/abr.cpp
index c338d4725ed2ab493fd61143d24b9b7241453622..d387cde26990f5e5acc1d38530375f73733d3789 100644
--- a/components/rgbd-sources/src/abr.cpp
+++ b/components/rgbd-sources/src/abr.cpp
@@ -41,7 +41,7 @@ bitrate_t ABRController::selectBitrate(const NetFrame &frame) {
 
     float actual_mbps = (float(frame.tx_size) * 8.0f * (1000.0f / float(frame.tx_latency))) / 1048576.0f;
     float min_mbps = (float(frame.tx_size) * 8.0f * (1000.0f / float(ftl::timer::getInterval()))) / 1048576.0f;
-    //LOG(INFO) << "Bitrate = " << actual_mbps << "Mbps, min required = " << min_mbps << "Mbps";
+    //if (actual_mbps < min_mbps) LOG(WARNING) << "Bitrate = " << actual_mbps << "Mbps, min required = " << min_mbps << "Mbps";
     float ratio = actual_mbps / min_mbps;
     //LOG(INFO) << "Rate Ratio = " << frame.tx_latency;
 
diff --git a/components/rgbd-sources/src/group.cpp b/components/rgbd-sources/src/group.cpp
index 625d62e2c9767ee6164d2835e832de20994ec983..aad850d2cf9d501d6d655b1f77978de6f1bab39e 100644
--- a/components/rgbd-sources/src/group.cpp
+++ b/components/rgbd-sources/src/group.cpp
@@ -214,8 +214,8 @@ void Group::sync(std::function<bool(ftl::rgbd::FrameSet &)> cb) {
 				try {
 					cb(*fs);
 					//LOG(INFO) << "Frameset processed (" << name_ << "): " << fs->timestamp;
-				} catch(...) {
-					LOG(ERROR) << "Exception in group sync callback";
+				} catch(std::exception &e) {
+					LOG(ERROR) << "Exception in group sync callback: " << e.what();
 				}
 
 				// The buffers are invalid after callback so mark stale
diff --git a/components/rgbd-sources/src/source.cpp b/components/rgbd-sources/src/source.cpp
index 13cdd5487edf0b7cbc99f7cd9dd7032b43d31185..f4f217af128b559c45c0d109de3a670c29393159 100644
--- a/components/rgbd-sources/src/source.cpp
+++ b/components/rgbd-sources/src/source.cpp
@@ -303,27 +303,6 @@ void Source::notify(int64_t ts, cv::cuda::GpuMat &c1, cv::cuda::GpuMat &c2) {
 	int max_width = max(impl_->params_.width, max(c1.cols, c2.cols));
 	int max_height = max(impl_->params_.height, max(c1.rows, c2.rows));
 
-	// Do we need to scale camera parameters
-	if (impl_->params_.width < max_width || impl_->params_.height < max_height) {
-		impl_->params_ = impl_->params_.scaled(max_width, max_height);
-	}
-
-	// Should channel 1 be scaled?
-	if (c1.cols < max_width || c1.rows < max_height) {
-		LOG(WARNING) << "Resizing on GPU";
-		cv::cuda::resize(c1, c1, cv::Size(max_width, max_height));
-	}
-
-	// Should channel 2 be scaled?
-	if (!c2.empty() && (c2.cols < max_width || c2.rows < max_height)) {
-		LOG(WARNING) << "Resizing on GPU";
-		if (c2.type() == CV_32F) {
-			cv::cuda::resize(c2, c2, cv::Size(max_width, max_height), 0.0, 0.0, cv::INTER_NEAREST);
-		} else {
-			cv::cuda::resize(c2, c2, cv::Size(max_width, max_height));
-		}
-	}
-
 	if (callback_) callback_(ts, c1, c2);
 }
 
@@ -335,12 +314,15 @@ void Source::inject(const Eigen::Matrix4d &pose) {
 	spkt.channel_count = 0;
 	spkt.channel = Channel::Pose;
 	spkt.streamID = 0;
-	pkt.codec = ftl::codecs::codec_t::POSE;
+	pkt.codec = ftl::codecs::codec_t::MSGPACK;
 	pkt.definition = ftl::codecs::definition_t::Any;
 	pkt.block_number = 0;
 	pkt.block_total = 1;
 	pkt.flags = 0;
-	pkt.data = std::move(std::vector<uint8_t>((uint8_t*)pose.data(), (uint8_t*)pose.data() + 4*4*sizeof(double)));
+
+	std::vector<double> data(pose.data(), pose.data() + 4*4*sizeof(double));
+	VectorBuffer buf(pkt.data);
+	msgpack::pack(buf, data);
 
 	notifyRaw(spkt, pkt);
 }
diff --git a/components/rgbd-sources/src/sources/ftlfile/file_source.cpp b/components/rgbd-sources/src/sources/ftlfile/file_source.cpp
index 0962c1886dc199e50530343c0d01edf4e74e37f0..554a558b3b5cecffc94f59ae8f3f98a65051d72e 100644
--- a/components/rgbd-sources/src/sources/ftlfile/file_source.cpp
+++ b/components/rgbd-sources/src/sources/ftlfile/file_source.cpp
@@ -93,7 +93,12 @@ void FileSource::_processPose(ftl::codecs::Packet &pkt) {
 		Eigen::Matrix4d p = Eigen::Map<Eigen::Matrix4d>((double*)pkt.data.data());
 		host_->setPose(p);
 	} else if (pkt.codec == codec_t::MSGPACK) {
+		auto unpacked = msgpack::unpack((const char*)pkt.data.data(), pkt.data.size());
+		std::vector<double> posevec;
+		unpacked.get().convert(posevec);
 
+		Eigen::Matrix4d p(posevec.data());
+		host_->setPose(p);
 	}
 }
 
@@ -184,21 +189,34 @@ bool FileSource::compute(int n, int b) {
 
 		if (c.spkt.channel == Channel::Colour) {
 			rgb_.create(cv::Size(ftl::codecs::getWidth(c.pkt.definition),ftl::codecs::getHeight(c.pkt.definition)), CV_8UC3);
-		} else {
+			_createDecoder(0, c.pkt);
+
+			try {
+				decoders_[0]->decode(c.pkt, rgb_);
+			} catch (std::exception &e) {
+				LOG(INFO) << "Decoder exception: " << e.what();
+			}
+		} else if (host_->getChannel() == c.spkt.channel) {
 			depth_.create(cv::Size(ftl::codecs::getWidth(c.pkt.definition),ftl::codecs::getHeight(c.pkt.definition)), CV_32F);
+			_createDecoder(1, c.pkt);
+			try {
+				decoders_[1]->decode(c.pkt, depth_);
+			} catch (std::exception &e) {
+				LOG(INFO) << "Decoder exception: " << e.what();
+			}
 		}
 	
-		_createDecoder((c.spkt.channel == Channel::Colour) ? 0 : 1, c.pkt);
+		//_createDecoder((c.spkt.channel == Channel::Colour) ? 0 : 1, c.pkt);
 
-		try {
+		/*try {
 			decoders_[(c.spkt.channel == Channel::Colour) ? 0 : 1]->decode(c.pkt, (c.spkt.channel == Channel::Colour) ? rgb_ : depth_);
 		} catch (std::exception &e) {
 			LOG(INFO) << "Decoder exception: " << e.what();
-		}
+		}*/
 	}
 
 	// FIXME: Consider case of Channel::None
-	if (lastc != 2) {
+	if (lastc < 2) {
 		LOG(ERROR) << "Channels not in sync (" << sourceid_ << "): " << lastts;
 		return false;
 	}
diff --git a/components/rgbd-sources/src/sources/net/net.cpp b/components/rgbd-sources/src/sources/net/net.cpp
index e4073536a574255965de81ab5f2294e008695032..c29ad5491b0b0a15a3d8549936bfe14b18762330 100644
--- a/components/rgbd-sources/src/sources/net/net.cpp
+++ b/components/rgbd-sources/src/sources/net/net.cpp
@@ -8,6 +8,7 @@
 #include "colour.hpp"
 
 #include <ftl/rgbd/streamer.hpp>
+#include <ftl/codecs/bitrates.hpp>
 
 using ftl::rgbd::detail::NetFrame;
 using ftl::rgbd::detail::NetFrameQueue;
@@ -21,6 +22,7 @@ using std::this_thread::sleep_for;
 using std::chrono::milliseconds;
 using std::tuple;
 using ftl::codecs::Channel;
+using ftl::codecs::codec_t;
 
 // ===== NetFrameQueue =========================================================
 
@@ -52,8 +54,8 @@ NetFrame &NetFrameQueue::getFrame(int64_t ts, const cv::Size &s, int c1type, int
 			f.chunk_total[1] = 0;
 			f.channel_count = 0;
 			f.tx_size = 0;
-			f.channel[0].create(s, c1type);
-			f.channel[1].create(s, c2type);
+			//f.channel[0].create(s, c1type);
+			//f.channel[1].create(s, c2type);
 			return f;
 		}
 		oldest = (f.timestamp < oldest) ? f.timestamp : oldest;
@@ -72,8 +74,8 @@ NetFrame &NetFrameQueue::getFrame(int64_t ts, const cv::Size &s, int c1type, int
 			f.chunk_total[1] = 0;
 			f.channel_count = 0;
 			f.tx_size = 0;
-			f.channel[0].create(s, c1type);
-			f.channel[1].create(s, c2type);
+			//f.channel[0].create(s, c1type);
+			//f.channel[1].create(s, c2type);
 			return f;
 		}
 	}
@@ -250,7 +252,24 @@ void NetSource::_processCalibration(const ftl::codecs::Packet &pkt) {
 }
 
 void NetSource::_processPose(const ftl::codecs::Packet &pkt) {
-	LOG(INFO) << "Got POSE channel";
+	if (pkt.codec == ftl::codecs::codec_t::POSE) {
+		Eigen::Matrix4d p = Eigen::Map<Eigen::Matrix4d>((double*)pkt.data.data());
+		//host_->setPose(p);
+	} else if (pkt.codec == ftl::codecs::codec_t::MSGPACK) {
+		auto unpacked = msgpack::unpack((const char*)pkt.data.data(), pkt.data.size());
+		std::vector<double> posevec;
+		unpacked.get().convert(posevec);
+
+		Eigen::Matrix4d p(posevec.data());
+		//host_->setPose(p);
+		// TODO: What to do with pose?
+	}
+}
+
+void NetSource::_checkDataRate(size_t tx_size, int64_t tx_latency) {
+	float actual_mbps = (float(tx_size) * 8.0f * (1000.0f / float(tx_latency))) / 1048576.0f;
+    float min_mbps = (float(tx_size) * 8.0f * (1000.0f / float(ftl::timer::getInterval()))) / 1048576.0f;
+    if (actual_mbps < min_mbps) LOG(WARNING) << "Bitrate = " << actual_mbps << "Mbps, min required = " << min_mbps << "Mbps";
 }
 
 void NetSource::_recvPacket(short ttimeoff, const ftl::codecs::StreamPacket &spkt, const ftl::codecs::Packet &pkt) {
@@ -277,11 +296,40 @@ void NetSource::_recvPacket(short ttimeoff, const ftl::codecs::StreamPacket &spk
 		return;
 	}
 
-	NetFrame &frame = queue_.getFrame(spkt.timestamp, cv::Size(params_.width, params_.height), CV_8UC3, (isFloatChannel(chan) ? CV_32FC1 : CV_8UC3));
+	//LOG(INFO) << "PACKET: " << spkt.timestamp << ", " << (int)spkt.channel << ", " << (int)pkt.codec;
+	
+	const cv::Size size = cv::Size(ftl::codecs::getWidth(pkt.definition), ftl::codecs::getHeight(pkt.definition));
+	NetFrame &frame = queue_.getFrame(spkt.timestamp, size, CV_8UC3, (isFloatChannel(chan) ? CV_32FC1 : CV_8UC3));
+
+	if (timestamp_ > 0 && frame.timestamp <= timestamp_) {
+		LOG(ERROR) << "Duplicate frame - " << frame.timestamp << " received=" << int(rchan) << " uri=" << uri_;
+		return;
+	}
+
+	// Calculate how many packets to expect for this channel
+	if (frame.chunk_total[channum] == 0) {
+		frame.chunk_total[channum] = pkt.block_total;
+	}
+
+	// Capture tx time of first received chunk
+	if (frame.chunk_count[0] == 0 && frame.chunk_count[1] == 0) {
+		UNIQUE_LOCK(frame.mtx, flk);
+		if (frame.chunk_count[0] == 0 && frame.chunk_count[1] == 0) {
+			frame.tx_latency = int64_t(ttimeoff);
+		}
+	}	
+
+	++frame.chunk_count[channum];
+	if (frame.chunk_count[channum] > frame.chunk_total[channum]) {
+		LOG(WARNING) << "Too many channel packets received, discarding";
+		return;
+	}
 
 	// Update frame statistics
 	frame.tx_size += pkt.data.size();
 
+	frame.channel[channum].create(size, (isFloatChannel(rchan) ? CV_32FC1 : CV_8UC3));
+
 	// Only decode if this channel is wanted.
 	if (rchan == Channel::Colour || rchan == chan) {
 		_createDecoder(channum, pkt);
@@ -290,7 +338,7 @@ void NetSource::_recvPacket(short ttimeoff, const ftl::codecs::StreamPacket &spk
 			LOG(ERROR) << "No frame decoder available";
 			return;
 		}
-
+	
 		decoder->decode(pkt, frame.channel[channum]);
 	} else if (chan != Channel::None && rchan != Channel::Colour) {
 		// Didn't receive correct second channel so just clear the images
@@ -305,29 +353,10 @@ void NetSource::_recvPacket(short ttimeoff, const ftl::codecs::StreamPacket &spk
 	//ftl::rgbd::colourCorrection(tmp_rgb, gamma_, temperature_);
 
 	// TODO:(Nick) Decode directly into double buffer if no scaling
+	
+	_checkDataRate(pkt.data.size(), now-(spkt.timestamp+ttimeoff));
 
-	if (timestamp_ > 0 && frame.timestamp <= timestamp_) {
-		LOG(ERROR) << "BAD DUPLICATE FRAME - " << frame.timestamp << " received=" << int(rchan) << " uri=" << uri_;
-		return;
-	}
-
-	// Calculate how many packets to expect for this channel
-	if (frame.chunk_total[channum] == 0) {
-		frame.chunk_total[channum] = pkt.block_total;
-	}		
-
-	++frame.chunk_count[channum];
 	if (frame.chunk_count[channum] == frame.chunk_total[channum]) ++frame.channel_count;
-	if (frame.chunk_count[channum] > frame.chunk_total[channum]) LOG(FATAL) << "TOO MANY CHUNKS";
-
-	// Capture tx time of first received chunk
-	// FIXME: This seems broken
-	if (channum == 1 && frame.chunk_count[channum] == 1) {
-		UNIQUE_LOCK(frame.mtx, flk);
-		if (frame.chunk_count[channum] == 1) {
-			frame.tx_latency = int64_t(ttimeoff);
-		}
-	}
 
 	// Last chunk of both channels now received, so we are done.
 	if (frame.channel_count == spkt.channel_count) {
diff --git a/components/rgbd-sources/src/sources/net/net.hpp b/components/rgbd-sources/src/sources/net/net.hpp
index 5cef2726d2cdc5c34c161a74b25d45234f55ce48..515bb8a5ff7ee5d788530d9ad00495f7f880b83d 100644
--- a/components/rgbd-sources/src/sources/net/net.hpp
+++ b/components/rgbd-sources/src/sources/net/net.hpp
@@ -89,6 +89,7 @@ class NetSource : public detail::Source {
 	void _processCalibration(const ftl::codecs::Packet &pkt);
 	void _processConfig(const ftl::codecs::Packet &pkt);
 	void _processPose(const ftl::codecs::Packet &pkt);
+	void _checkDataRate(size_t tx_size, int64_t tx_latency);
 };
 
 }
diff --git a/components/rgbd-sources/src/sources/snapshot/snapshot_source.cpp b/components/rgbd-sources/src/sources/snapshot/snapshot_source.cpp
index 136a2e7dfcc7b279228cdd5c6efc0bfb8d303baa..61acdb9d814560c72fcd15e7f05cdfaf8fda66cd 100644
--- a/components/rgbd-sources/src/sources/snapshot/snapshot_source.cpp
+++ b/components/rgbd-sources/src/sources/snapshot/snapshot_source.cpp
@@ -53,6 +53,8 @@ SnapshotSource::SnapshotSource(ftl::rgbd::Source *host, Snapshot &snapshot, cons
     host->setPose(pose);
 
 	mspf_ = 1000 / host_->value("fps", 20);
+
+	cudaStreamCreate(&stream_);
 }
 
 bool SnapshotSource::compute(int n, int b) {
@@ -61,11 +63,14 @@ bool SnapshotSource::compute(int n, int b) {
 
 	//snap_rgb_.copyTo(rgb_);
 	//snap_depth_.copyTo(depth_);
-	rgb_.upload(snap_rgb_);
-	depth_.upload(snap_depth_);
-
-	auto cb = host_->callback();
-	if (cb) cb(timestamp_, rgb_, depth_);
+	cv::cuda::Stream cvstream = cv::cuda::StreamAccessor::wrapStream(stream_);
+	rgb_.upload(snap_rgb_, cvstream);
+	depth_.upload(snap_depth_, cvstream);
+	cudaStreamSynchronize(stream_);
+
+	//auto cb = host_->callback();
+	//if (cb) cb(timestamp_, rgb_, depth_);
+	host_->notify(timestamp_, rgb_, depth_);
 
 	frame_idx_ = (frame_idx_ + 1) % snapshot_.getFramesCount();
 
diff --git a/components/rgbd-sources/src/sources/snapshot/snapshot_source.hpp b/components/rgbd-sources/src/sources/snapshot/snapshot_source.hpp
index de1b0df48be79df732f51144226f5c7e6d2f0478..80a0bf392b39fb9d5215dd80034768d806ac7957 100644
--- a/components/rgbd-sources/src/sources/snapshot/snapshot_source.hpp
+++ b/components/rgbd-sources/src/sources/snapshot/snapshot_source.hpp
@@ -32,6 +32,7 @@ class SnapshotSource : public detail::Source {
 	cv::Mat snap_rgb_;
 	cv::Mat snap_depth_;
 	int mspf_;
+	cudaStream_t stream_;
 };
 
 }
diff --git a/components/rgbd-sources/src/sources/stereovideo/calibrate.cpp b/components/rgbd-sources/src/sources/stereovideo/calibrate.cpp
index fc99d701fa40a8b824248c775a7020ed7d449fd1..88d69ab9c6d2fdc23bcd8f629df3e8041d6a75d9 100644
--- a/components/rgbd-sources/src/sources/stereovideo/calibrate.cpp
+++ b/components/rgbd-sources/src/sources/stereovideo/calibrate.cpp
@@ -211,6 +211,26 @@ void Calibrate::_updateIntrinsics() {
 	map2_gpu_.second.upload(map2_.second);
 }
 
+cv::Mat Calibrate::getCameraMatrixLeft(const cv::Size res) {
+	double scale_x = ((double) res.width) / ((double) img_size_.width);
+	double scale_y = ((double) res.height) / ((double) img_size_.height);
+	Mat scale(cv::Size(3, 3), CV_64F, 0.0);
+	scale.at<double>(0, 0) = scale_x;
+	scale.at<double>(1, 1) = scale_y;
+	scale.at<double>(2, 2) = 1.0;
+	return scale * Kl_;
+}
+
+cv::Mat Calibrate::getCameraMatrixRight(const cv::Size res) {
+	double scale_x = ((double) res.width) / ((double) img_size_.width);
+	double scale_y = ((double) res.height) / ((double) img_size_.height);
+	Mat scale(cv::Size(3, 3), CV_64F, 0.0);
+	scale.at<double>(0, 0) = scale_x;
+	scale.at<double>(1, 1) = scale_y;
+	scale.at<double>(2, 2) = 1.0;
+	return scale * Kr_;
+}
+
 void Calibrate::rectifyStereo(GpuMat &l, GpuMat &r, Stream &stream) {
 	// cv::cuda::remap() can not use same Mat for input and output
 
diff --git a/components/rgbd-sources/src/sources/stereovideo/calibrate.hpp b/components/rgbd-sources/src/sources/stereovideo/calibrate.hpp
index 4561b90a79129dd5a0d46d9d54bd005147d766a7..39ec301a98d8419cc19dfb0bb60747e20a6327ff 100644
--- a/components/rgbd-sources/src/sources/stereovideo/calibrate.hpp
+++ b/components/rgbd-sources/src/sources/stereovideo/calibrate.hpp
@@ -49,15 +49,11 @@ class Calibrate : public ftl::Configurable {
 
 	void updateCalibration(const ftl::rgbd::Camera &p);
 	
-	/**
-	 * Get the camera matrix. Used to convert disparity map back to depth and
-	 * a 3D point cloud.
-	 */
+	// Get disparity to depth matrix.
 	const cv::Mat &getQ() const { return Q_; }
 
-	const cv::Mat &getCameraMatrixLeft() { return Kl_; }
-	const cv::Mat &getCameraMatrixRight() { return Kr_; }
-	const cv::Mat &getCameraMatrix() { return getCameraMatrixLeft(); }
+	cv::Mat getCameraMatrixLeft(const cv::Size res);
+	cv::Mat getCameraMatrixRight(const cv::Size res);
 
 private:
 	void _updateIntrinsics();
diff --git a/components/rgbd-sources/src/sources/stereovideo/stereovideo.cpp b/components/rgbd-sources/src/sources/stereovideo/stereovideo.cpp
index 84d1e574b8e7aff91c774a1035c75280cd8ac03c..e8eff732be9120de3cdc9efc2b25003e8635a65f 100644
--- a/components/rgbd-sources/src/sources/stereovideo/stereovideo.cpp
+++ b/components/rgbd-sources/src/sources/stereovideo/stereovideo.cpp
@@ -8,7 +8,6 @@
 #include "ftl/operators/opticalflow.hpp"
 #endif
 
-
 #include "ftl/operators/smoothing.hpp"
 #include "ftl/operators/colours.hpp"
 #include "ftl/operators/normals.hpp"
@@ -69,14 +68,35 @@ void StereoVideoSource::init(const string &file) {
 		lsrc_ = ftl::create<LocalSource>(host_, "feed");
 	}
 
-	cv::Size size = cv::Size(lsrc_->width(), lsrc_->height());
+	color_size_ = cv::Size(lsrc_->width(), lsrc_->height());
 	frames_ = std::vector<Frame>(2);
 
-	calib_ = ftl::create<Calibrate>(host_, "calibration", size, stream_);
+	pipeline_input_ = ftl::config::create<ftl::operators::Graph>(host_, "input");
+	#ifdef HAVE_OPTFLOW
+	pipeline_input_->append<ftl::operators::NVOpticalFlow>("optflow");
+	#endif
+
+	pipeline_depth_ = ftl::config::create<ftl::operators::Graph>(host_, "disparity");
+	depth_size_ = cv::Size(	pipeline_depth_->value("width", color_size_.width),
+							pipeline_depth_->value("height", color_size_.height));
+
+	pipeline_depth_->append<ftl::operators::FixstarsSGM>("algorithm");
+	#ifdef HAVE_OPTFLOW
+	pipeline_depth_->append<ftl::operators::OpticalFlowTemporalSmoothing>("optflow_filter");
+	#endif
+	pipeline_depth_->append<ftl::operators::DisparityBilateralFilter>("bilateral_filter");
+	pipeline_depth_->append<ftl::operators::DisparityToDepth>("calculate_depth");
+	pipeline_depth_->append<ftl::operators::ColourChannels>("colour");  // Convert BGR to BGRA
+	pipeline_depth_->append<ftl::operators::Normals>("normals");  // Estimate surface normals
+	pipeline_depth_->append<ftl::operators::CrossSupport>("cross");
+	pipeline_depth_->append<ftl::operators::DiscontinuityMask>("discontinuity_mask");
+	pipeline_depth_->append<ftl::operators::AggreMLS>("mls");  // Perform MLS (using smoothing channel)
+
+	calib_ = ftl::create<Calibrate>(host_, "calibration", color_size_, stream_);
 	if (!calib_->isCalibrated()) LOG(WARNING) << "Cameras are not calibrated!";
 
 	// Generate camera parameters from camera matrix
-	cv::Mat K = calib_->getCameraMatrix();
+	cv::Mat K = calib_->getCameraMatrixLeft(depth_size_);
 	params_ = {
 		K.at<double>(0,0),	// Fx
 		K.at<double>(1,1),	// Fy
@@ -126,49 +146,34 @@ void StereoVideoSource::init(const string &file) {
 	mask_l_gpu.download(mask_l);
 	mask_l_ = (mask_l == 0);
 	
-	pipeline_input_ = ftl::config::create<ftl::operators::Graph>(host_, "input");
-	#ifdef HAVE_OPTFLOW
-	pipeline_input_->append<ftl::operators::NVOpticalFlow>("optflow");
-	#endif
-
-	pipeline_depth_ = ftl::config::create<ftl::operators::Graph>(host_, "disparity");
-	pipeline_depth_->append<ftl::operators::FixstarsSGM>("algorithm");
-
-	#ifdef HAVE_OPTFLOW
-	pipeline_depth_->append<ftl::operators::OpticalFlowTemporalSmoothing>("optflow_filter");
-	#endif
-	pipeline_depth_->append<ftl::operators::DisparityBilateralFilter>("bilateral_filter");
-	pipeline_depth_->append<ftl::operators::DisparityToDepth>("calculate_depth");
-	pipeline_depth_->append<ftl::operators::ColourChannels>("colour");  // Convert BGR to BGRA
-	pipeline_depth_->append<ftl::operators::Normals>("normals");  // Estimate surface normals
-	pipeline_depth_->append<ftl::operators::CrossSupport>("cross");
-	pipeline_depth_->append<ftl::operators::DiscontinuityMask>("discontinuity_mask");
-	pipeline_depth_->append<ftl::operators::AggreMLS>("mls");  // Perform MLS (using smoothing channel)
-
 	LOG(INFO) << "StereoVideo source ready...";
 	ready_ = true;
 }
 
 ftl::rgbd::Camera StereoVideoSource::parameters(Channel chan) {
+	cv::Mat K;
+	
 	if (chan == Channel::Right) {
-		cv::Mat q = calib_->getCameraMatrixRight();
-		ftl::rgbd::Camera params = {
-			q.at<double>(0,0),	// Fx
-			q.at<double>(1,1),	// Fy
-			-q.at<double>(0,2),	// Cx
-			-q.at<double>(1,2),	// Cy
-			(unsigned int)lsrc_->width(),
-			(unsigned int)lsrc_->height(),
-			0.0f,	// 0m min
-			15.0f,	// 15m max
-			1.0 / calib_->getQ().at<double>(3,2), // Baseline
-			0.0f  // doffs
-		};
-		return params;
-		//params_.doffs = -calib_->getQ().at<double>(3,3) * params_.baseline;
+		K = calib_->getCameraMatrixRight(depth_size_);
 	} else {
-		return params_;
+		K = calib_->getCameraMatrixLeft(depth_size_);
 	}
+
+	// TODO: remove hardcoded values (min/max)
+	ftl::rgbd::Camera params = {
+		K.at<double>(0,0),	// Fx
+		K.at<double>(1,1),	// Fy
+		-K.at<double>(0,2),	// Cx
+		-K.at<double>(1,2),	// Cy
+		(unsigned int) depth_size_.width,
+		(unsigned int) depth_size_.height,
+		0.0f,	// 0m min
+		15.0f,	// 15m max
+		1.0 / calib_->getQ().at<double>(3,2), // Baseline
+		0.0f  // doffs
+	};
+	
+	return params;
 }
 
 bool StereoVideoSource::capture(int64_t ts) {
@@ -205,8 +210,31 @@ bool StereoVideoSource::compute(int n, int b) {
 	}
 
 	if (chan == Channel::Depth) {
-		pipeline_depth_->apply(frame, frame, host_, cv::cuda::StreamAccessor::getStream(stream_));	
+		// stereo algorithms assume input same size as output
+		bool resize = (depth_size_ != color_size_);
+
+		cv::cuda::GpuMat& left = frame.get<cv::cuda::GpuMat>(Channel::Left);
+		cv::cuda::GpuMat& right = frame.get<cv::cuda::GpuMat>(Channel::Right);
+
+		if (left.empty() || right.empty()) {
+			return false;
+		}
+
+		if (resize) {
+			cv::cuda::swap(fullres_left_, left);
+			cv::cuda::swap(fullres_right_, right);
+			cv::cuda::resize(fullres_left_, left, depth_size_, 0, 0, cv::INTER_CUBIC, stream_);
+			cv::cuda::resize(fullres_right_, right, depth_size_, 0, 0, cv::INTER_CUBIC, stream_);
+		}
+
+		pipeline_depth_->apply(frame, frame, host_, cv::cuda::StreamAccessor::getStream(stream_));
 		stream_.waitForCompletion();
+		
+		if (resize) {
+			cv::cuda::swap(fullres_left_, left);
+			cv::cuda::swap(fullres_right_, right);
+		}
+
 		host_->notify(timestamp_,
 						frame.get<cv::cuda::GpuMat>(Channel::Left),
 						frame.get<cv::cuda::GpuMat>(Channel::Depth));
diff --git a/components/rgbd-sources/src/sources/stereovideo/stereovideo.hpp b/components/rgbd-sources/src/sources/stereovideo/stereovideo.hpp
index 78fcdbcf809eeae0d5de6da30b99ce3dc07f9214..9532e618889e78da51e1e152673195000e93be7f 100644
--- a/components/rgbd-sources/src/sources/stereovideo/stereovideo.hpp
+++ b/components/rgbd-sources/src/sources/stereovideo/stereovideo.hpp
@@ -17,9 +17,7 @@ class Disparity;
 
 /**
  * RGBD source from either a stereo video file with left + right images, or
- * direct from two camera devices. A variety of algorithms are included for
- * calculating disparity, before converting to depth.  Calibration of the images
- * is also performed.
+ * direct from two camera devices. 
  */
 class StereoVideoSource : public detail::Source {
 	public:
@@ -32,15 +30,21 @@ class StereoVideoSource : public detail::Source {
 	bool retrieve();
 	bool compute(int n, int b);
 	bool isReady();
-	Camera parameters(ftl::codecs::Channel chan);
+	Camera parameters(ftl::codecs::Channel chan) override;
 
 	private:
 	LocalSource *lsrc_;
 	Calibrate *calib_;
 
+	cv::Size color_size_;
+	cv::Size depth_size_;
+
 	ftl::operators::Graph *pipeline_input_;
 	ftl::operators::Graph *pipeline_depth_;
 
+	cv::cuda::GpuMat fullres_left_;
+	cv::cuda::GpuMat fullres_right_;
+
 	bool ready_;
 	
 	cv::cuda::Stream stream_;
diff --git a/components/rgbd-sources/src/streamer.cpp b/components/rgbd-sources/src/streamer.cpp
index e05e7faf9305ac0a3ede6dade21596bfe8251db7..a672c1bd136e03ed52f99dbd38237b52efb67472 100644
--- a/components/rgbd-sources/src/streamer.cpp
+++ b/components/rgbd-sources/src/streamer.cpp
@@ -193,7 +193,7 @@ void Streamer::add(Source *src) {
 			if (spkt.channel == Channel::Calibration) {
 				// Calibration changed, so lets re-check the bitrate presets
 				const auto &params = src->parameters();
-				s->hq_bitrate = ftl::codecs::findPreset(params.width, params.height);
+				s->hq_bitrate = ftl::codecs::kPresetBest;
 			}
 
 			//LOG(INFO) << "RAW CALLBACK";
@@ -294,11 +294,15 @@ void Streamer::_addClient(const string &source, int N, int rate, const ftl::UUID
 	for (auto &client : s->clients) {
 		// If already listening, just update chunk counters
 		if (client.peerid == peer) {
+			// Allow for same client but different quality (beyond threshold)
+			if ((client.preset < kQualityThreshold && rate >= kQualityThreshold) ||
+				(client.preset >= kQualityThreshold && rate < kQualityThreshold)) continue;
+				
 			client.txmax = N;
 			client.txcount = 0;
 
 			// Possible switch from high quality to low quality encoding or vice versa
-			if (client.preset < kQualityThreshold && rate >= kQualityThreshold) {
+			/*if (client.preset < kQualityThreshold && rate >= kQualityThreshold) {
 				s->hq_count--;
 				s->lq_count++;
 				if (s->lq_encoder_c1) s->lq_encoder_c1->reset();
@@ -308,7 +312,8 @@ void Streamer::_addClient(const string &source, int N, int rate, const ftl::UUID
 				s->lq_count--;
 				if (s->hq_encoder_c1) s->hq_encoder_c1->reset();
 				if (s->hq_encoder_c2) s->hq_encoder_c2->reset();
-			}
+				break;
+			}*/
 
 			client.preset = rate;
 			return;
@@ -338,7 +343,7 @@ void Streamer::_addClient(const string &source, int N, int rate, const ftl::UUID
 	// Finally, inject calibration and config data
 	s->src->inject(Channel::Calibration, s->src->parameters(Channel::Left), Channel::Left, s->src->getCapabilities());
 	s->src->inject(Channel::Calibration, s->src->parameters(Channel::Right), Channel::Right, s->src->getCapabilities());
-	//s->src->inject(s->src->getPose());
+	s->src->inject(s->src->getPose());
 	//if (!(*s->src->get<nlohmann::json>("meta")).is_null()) {
 		s->src->inject(Channel::Configuration, "/original", s->src->getConfig().dump());
 	//}
@@ -459,20 +464,32 @@ void Streamer::_process(ftl::rgbd::FrameSet &fs) {
 				auto *enc1 = src->hq_encoder_c1;
 				auto *enc2 = src->hq_encoder_c2;
 
-				// Important to send channel 2 first if needed...
-				// Receiver only waits for channel 1 by default
-				// TODO: Each encode could be done in own thread
-				if (hasChan2) {
-					// TODO: Stagger the reset between nodes... random phasing
-					if (fs.timestamp % (10*ftl::timer::getInterval()) == 0) enc2->reset();
+				MUTEX mtx;
+				std::condition_variable cv;
+				bool chan2done = false;
 
-					auto chan = fs.sources[j]->getChannel();
-
-					enc2->encode(fs.frames[j].get<cv::cuda::GpuMat>(chan), src->hq_bitrate, [this,src,hasChan2,chan](const ftl::codecs::Packet &blk){
-						_transmitPacket(src, blk, chan, hasChan2, Quality::High);
+				if (hasChan2) {
+					ftl::pool.push([this,&fs,enc2,src,hasChan2,&cv,j,&chan2done](int id) {
+						// TODO: Stagger the reset between nodes... random phasing
+						if (fs.timestamp % (10*ftl::timer::getInterval()) == 0) enc2->reset();
+
+						auto chan = fs.sources[j]->getChannel();
+
+						try {
+							enc2->encode(fs.frames[j].get<cv::cuda::GpuMat>(chan), src->hq_bitrate, [this,src,hasChan2,chan,&cv,&chan2done](const ftl::codecs::Packet &blk){
+								_transmitPacket(src, blk, chan, hasChan2, Quality::High);
+								chan2done = true;
+								cv.notify_one();
+							});
+						} catch (std::exception &e) {
+							LOG(ERROR) << "Exception in encoder: " << e.what();
+							chan2done = true;
+							cv.notify_one();
+						}
 					});
 				} else {
 					if (enc2) enc2->reset();
+					chan2done = true;
 				}
 
 				// TODO: Stagger the reset between nodes... random phasing
@@ -480,6 +497,10 @@ void Streamer::_process(ftl::rgbd::FrameSet &fs) {
 				enc1->encode(fs.frames[j].get<cv::cuda::GpuMat>(Channel::Colour), src->hq_bitrate, [this,src,hasChan2](const ftl::codecs::Packet &blk){
 					_transmitPacket(src, blk, Channel::Colour, hasChan2, Quality::High);
 				});
+
+				// Ensure both channels have been completed.
+				std::unique_lock<std::mutex> lk(mtx);
+				cv.wait(lk, [&chan2done]{ return chan2done; });
 			}
 		}
 
@@ -512,51 +533,6 @@ void Streamer::_process(ftl::rgbd::FrameSet &fs) {
 				});
 			}
 		}
-
-		// Do we need to do low quality encoding?
-		/*if (src->lq_count > 0) {
-			if (!src->lq_encoder_c1) src->lq_encoder_c1 = ftl::codecs::allocateLQEncoder();
-			if (!src->lq_encoder_c2) src->lq_encoder_c2 = ftl::codecs::allocateLQEncoder();
-
-			// Do we have the resources to do a LQ encoding?
-			if (src->lq_encoder_c1 && src->lq_encoder_c2) {
-				const auto *enc1 = src->lq_encoder_c1;
-				const auto *enc2 = src->lq_encoder_c2;
-
-				// Do entire frame as single step
-				if (!enc1->useBlocks() || !enc2->useBlocks()) {
-					ftl::pool.push([this,&fs,j,src](int id) {
-						_encodeLQAndTransmit(src, fs.channel1[j], fs.channel2[j], -1);
-						std::unique_lock<std::mutex> lk(job_mtx_);
-						--jobs_;
-						if (jobs_ == 0) job_cv_.notify_one();
-					});
-
-					jobs_++;
-				// Or divide frame into blocks and encode each
-				} else {
-					// Create jobs for each chunk
-					for (int i=0; i<chunk_count_; ++i) {
-						// Add chunk job to thread pool
-						ftl::pool.push([this,&fs,j,i,src](int id) {
-							int chunk = i;
-							try {
-								_encodeLQAndTransmit(src, fs.channel1[j], fs.channel2[j], chunk);
-							} catch(...) {
-								LOG(ERROR) << "Encode Exception: " << chunk;
-							}
-
-							//src->jobs--;
-							std::unique_lock<std::mutex> lk(job_mtx_);
-							--jobs_;
-							if (jobs_ == 0) job_cv_.notify_one();
-						});
-					}
-
-					jobs_ += chunk_count_;
-				}
-			}
-		}*/
 	}
 
 	/*std::unique_lock<std::mutex> lk(job_mtx_);
@@ -619,219 +595,3 @@ void Streamer::_transmitPacket(StreamSource *src, const ftl::codecs::StreamPacke
 		++c;
 	}
 }
-
-/*void Streamer::_encodeHQAndTransmit(StreamSource *src, const cv::Mat &c1, const cv::Mat &c2, int block) {
-	bool hasChan2 = (!c2.empty() && src->src->getChannel() != ftl::rgbd::kChanNone);
-
-	LOG(INFO) << "Encode HQ: " << block;
-
-	vector<unsigned char> c1buff;
-	vector<unsigned char> c2buff;
-
-	if (block == -1) {
-		src->hq_encoder_c1->encode(c1, c1buff, src->hq_bitrate, false);
-		if (hasChan2) src->hq_encoder_c2->encode(c2, c2buff, src->hq_bitrate, false);
-	} else {
-		//bool delta = (chunk+src->frame) % 8 > 0;  // Do XOR or not
-		int chunk_width = c1.cols / chunk_dim_;
-		int chunk_height = c1.rows / chunk_dim_;
-
-		// Build chunk heads
-		int cx = (block % chunk_dim_) * chunk_width;
-		int cy = (block / chunk_dim_) * chunk_height;
-		cv::Rect roi(cx,cy,chunk_width,chunk_height);
-		//vector<unsigned char> rgb_buf;
-		cv::Mat chunkRGB = c1(roi);
-		src->hq_encoder_c1->encode(chunkRGB, c1buff, src->hq_bitrate, false);
-
-		if (hasChan2) {
-			cv::Mat chunkDepth = c2(roi);
-			src->hq_encoder_c2->encode(chunkDepth, c2buff, src->hq_bitrate, false);
-		}
-	}
-
-	// Lock to prevent clients being added / removed
-	SHARED_LOCK(src->mutex,lk);
-	auto c = src->clients.begin();
-	while (c != src->clients.end()) {
-		const int b = (*c).bitrate;
-		if (b >= kQualityThreshold) continue; // Not a HQ request
-
-		try {
-			// TODO:(Nick) Send pose
-			short pre_transmit_latency = short(ftl::timer::get_time() - frame_no_);
-			if (!net_->send((*c).peerid, (*c).uri, frame_no_, pre_transmit_latency, uint8_t(src->hq_bitrate), block, c1buff, c2buff)) {
-				// Send failed so mark as client stream completed
-				(*c).txcount = (*c).txmax;
-			} else {
-				++(*c).txcount;
-				//LOG(INFO) << "SENT CHUNK : " << frame_no_ << "-" << chunk;
-			}
-		} catch(...) {
-			(*c).txcount = (*c).txmax;
-		}
-		++c;
-	}
-}
-
-void Streamer::_encodeLQAndTransmit(StreamSource *src, const cv::Mat &c1, const cv::Mat &c2, int block) {
-	bool hasChan2 = (!c2.empty() && src->src->getChannel() != ftl::rgbd::kChanNone);
-
-	LOG(INFO) << "Encode LQ: " << block;
-
-	vector<unsigned char> c1buff;
-	vector<unsigned char> c2buff;
-
-	if (block == -1) {
-		src->lq_encoder_c1->encode(c1, c1buff, src->lq_bitrate, false);
-		if (hasChan2) src->lq_encoder_c2->encode(c2, c2buff, src->lq_bitrate, false);
-	} else {
-		//bool delta = (chunk+src->frame) % 8 > 0;  // Do XOR or not
-		int chunk_width = c1.cols / chunk_dim_;
-		int chunk_height = c1.rows / chunk_dim_;
-
-		// Build chunk heads
-		int cx = (block % chunk_dim_) * chunk_width;
-		int cy = (block / chunk_dim_) * chunk_height;
-		cv::Rect roi(cx,cy,chunk_width,chunk_height);
-		//vector<unsigned char> rgb_buf;
-		cv::Mat chunkRGB = c1(roi);
-		//cv::resize(chunkRGB, downrgb, cv::Size(ABRController::getColourWidth(b) / chunk_dim_, ABRController::getColourHeight(b) / chunk_dim_));
-
-		src->lq_encoder_c1->encode(chunkRGB, c1buff, src->lq_bitrate, false);
-
-		if (hasChan2) {
-			cv::Mat chunkDepth = c2(roi);
-			//cv::resize(chunkDepth, tmp, cv::Size(ABRController::getDepthWidth(b) / chunk_dim_, ABRController::getDepthHeight(b) / chunk_dim_), 0, 0, cv::INTER_NEAREST);
-			src->lq_encoder_c2->encode(chunkDepth, c2buff, src->lq_bitrate, false);
-		}
-	}
-
-	// Lock to prevent clients being added / removed
-	SHARED_LOCK(src->mutex,lk);
-	auto c = src->clients.begin();
-	while (c != src->clients.end()) {
-		const int b = (*c).bitrate;
-		if (b < kQualityThreshold) continue; // Not an LQ request
-
-		try {
-			// TODO:(Nick) Send pose
-			short pre_transmit_latency = short(ftl::timer::get_time() - frame_no_);
-			if (!net_->send((*c).peerid, (*c).uri, frame_no_, pre_transmit_latency, uint8_t(src->hq_bitrate), block, c1buff, c2buff)) {
-				// Send failed so mark as client stream completed
-				(*c).txcount = (*c).txmax;
-			} else {
-				++(*c).txcount;
-				//LOG(INFO) << "SENT CHUNK : " << frame_no_ << "-" << chunk;
-			}
-		} catch(...) {
-			(*c).txcount = (*c).txmax;
-		}
-		++c;
-	}
-}*/
-
-/*void Streamer::_encodeImagesAndTransmit(StreamSource *src, const cv::Mat &rgb, const cv::Mat &depth, int chunk) {
-	bool hasChan2 = (!depth.empty() && src->src->getChannel() != ftl::rgbd::kChanNone);
-
-	//bool delta = (chunk+src->frame) % 8 > 0;  // Do XOR or not
-	int chunk_width = rgb.cols / chunk_dim_;
-	int chunk_height = rgb.rows / chunk_dim_;
-
-	// Build chunk heads
-	int cx = (chunk % chunk_dim_) * chunk_width;
-	int cy = (chunk / chunk_dim_) * chunk_height;
-	cv::Rect roi(cx,cy,chunk_width,chunk_height);
-	//vector<unsigned char> rgb_buf;
-	cv::Mat chunkRGB = rgb(roi);
-	cv::Mat chunkDepth;
-	//cv::Mat chunkDepthPrev = src->prev_depth(roi);
-
-	cv::Mat d2, d3;
-	//vector<unsigned char> d_buf;
-
-	if (hasChan2) {
-		chunkDepth = depth(roi);
-		if (chunkDepth.type() == CV_32F) chunkDepth.convertTo(d2, CV_16UC1, 1000); // 16*10);
-		else d2 = chunkDepth;
-		//if (delta) d3 = (d2 * 2) - chunkDepthPrev;
-		//else d3 = d2;
-		//d2.copyTo(chunkDepthPrev);
-	}
-
-	// TODO: Verify these don't allocate memory if not needed.
-	// TODO: Reuse these buffers to reduce allocations.
-	vector<unsigned char> brgb[ftl::rgbd::detail::kMaxBitrateLevels];
-	vector<unsigned char> bdepth[ftl::rgbd::detail::kMaxBitrateLevels];
-
-	// Lock to prevent clients being added / removed
-	SHARED_LOCK(src->mutex,lk);
-	auto c = src->clients.begin();
-	while (c != src->clients.end()) {
-		const int b = (*c).bitrate;
-
-		if (brgb[b].empty()) {
-			// Max bitrate means no changes
-			if (b == 0) {
-				_encodeImageChannel1(chunkRGB, brgb[b], b);
-				if (hasChan2) _encodeImageChannel2(d2, bdepth[b], src->src->getChannel(), b);
-
-			// Otherwise must downscale and change compression params
-			} else {
-				cv::Mat downrgb, downdepth;
-				cv::resize(chunkRGB, downrgb, cv::Size(ABRController::getColourWidth(b) / chunk_dim_, ABRController::getColourHeight(b) / chunk_dim_));
-				if (hasChan2) cv::resize(d2, downdepth, cv::Size(ABRController::getDepthWidth(b) / chunk_dim_, ABRController::getDepthHeight(b) / chunk_dim_), 0, 0, cv::INTER_NEAREST);
-
-				_encodeImageChannel1(downrgb, brgb[b], b);
-				if (hasChan2) _encodeImageChannel2(downdepth, bdepth[b], src->src->getChannel(), b);
-			}
-		}
-
-		try {
-			// TODO:(Nick) Send pose
-			short pre_transmit_latency = short(ftl::timer::get_time() - frame_no_);
-			if (!net_->send((*c).peerid, (*c).uri, frame_no_, pre_transmit_latency, uint8_t(b), chunk, brgb[b], bdepth[b])) {
-				// Send failed so mark as client stream completed
-				(*c).txcount = (*c).txmax;
-			} else {
-				++(*c).txcount;
-				//LOG(INFO) << "SENT CHUNK : " << frame_no_ << "-" << chunk;
-			}
-		} catch(...) {
-			(*c).txcount = (*c).txmax;
-		}
-		++c;
-	}
-}
-
-void Streamer::_encodeImageChannel1(const cv::Mat &in, vector<unsigned char> &out, unsigned int b) {
-	vector<int> jpgparams = {cv::IMWRITE_JPEG_QUALITY, ABRController::getColourQuality(b)};
-	cv::imencode(".jpg", in, out, jpgparams);
-}
-
-bool Streamer::_encodeImageChannel2(const cv::Mat &in, vector<unsigned char> &out, ftl::codecs::Channel_t c, unsigned int b) {
-	if (c == ftl::rgbd::kChanNone) return false;  // NOTE: Should not happen
-
-	if (isFloatChannel(c) && in.type() == CV_16U && in.channels() == 1) {
-		vector<int> params = {cv::IMWRITE_PNG_COMPRESSION, ABRController::getDepthQuality(b)};
-		if (!cv::imencode(".png", in, out, params)) {
-			LOG(ERROR) << "PNG Encoding error";
-			return false;
-		}
-		return true;
-	} else if (!isFloatChannel(c) && in.type() == CV_8UC3) {
-		vector<int> params = {cv::IMWRITE_JPEG_QUALITY, ABRController::getColourQuality(b)};
-		cv::imencode(".jpg", in, out, params);
-		return true;
-	} else {
-		LOG(ERROR) << "Bad channel configuration: channel=" << c << " imagetype=" << in.type(); 
-	}
-
-	return false;
-}
-
-Source *Streamer::get(const std::string &uri) {
-	SHARED_LOCK(mutex_,slk);
-	if (sources_.find(uri) != sources_.end()) return sources_[uri]->src;
-	else return nullptr;
-}*/
diff --git a/web-service/public/css/index.css b/web-service/public/css/index.css
new file mode 100644
index 0000000000000000000000000000000000000000..c134bb2304be9bc7f9f1a884ec6dd9c8ce964664
--- /dev/null
+++ b/web-service/public/css/index.css
@@ -0,0 +1,56 @@
+body {
+    margin-left: auto;
+    margin-right: auto;
+  }
+  a {
+    -webkit-tap-highlight-color: transparent;
+    text-decoration: none;
+   }
+   
+   .button {
+    display: inline-block;
+    max-width: 300px;
+    margin-top: 50px;
+    border: 0;
+    padding: 0 18px;
+    text-align: left;
+    width: 100%;
+    height: 37px;
+    border-radius: 4px;
+    -webkit-font-smoothing: antialiased;
+    -moz-osx-font-smoothing: grayscale;
+    -moz-font-feature-settings: "liga" on;
+    color: rgba(0, 0, 0, 0.84) !important;
+    fill: rgba(0, 0, 0, 0.84) !important;
+    box-shadow: 0 1px 7px rgba(0, 0, 0, 0.05);
+    font: inherit;
+    outline: none;
+   }
+   
+   .button .svgIcon {
+    vertical-align: middle;
+    fill: rgba(0, 0, 0, 0.54);
+    padding-right: 4px;
+    height: 37px;
+    display: inline-block;
+   }
+
+  .ftlab-card-component {
+    width: 250px;
+    height: auto;
+    margin: auto;
+    text-align: center;
+    border: 1px solid black;
+  }
+
+  #ftlab-stream-thumbnails {
+    padding: 0;
+    margin: 0;
+    display: -webkit-box;
+    display: -moz-box;
+    display: -ms-flexbox;
+    display: -webkit-flex;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+  }
\ No newline at end of file
diff --git a/web-service/public/index.html b/web-service/public/index.html
new file mode 100644
index 0000000000000000000000000000000000000000..ab2a6db71824e35217a8b501626f5b781404f6f0
--- /dev/null
+++ b/web-service/public/index.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<html>
+    <head>
+        <title>FTL web-service</title>
+        <link rel="stylesheet" href="./css/index.css">
+        <meta charset="utf-8"/>
+    </head>
+    <body onload='checkIfLoggedIn();'>
+        <div id="container" style="padding-top: 150px; text-align: center"></div>
+    </body>
+    <script src="./js/bundle.js"></script>
+</html>
\ No newline at end of file
diff --git a/web-service/public/js/bundle.js b/web-service/public/js/bundle.js
new file mode 100644
index 0000000000000000000000000000000000000000..df7eec769a1efa2850c96cdede4cd698835a6c56
--- /dev/null
+++ b/web-service/public/js/bundle.js
@@ -0,0 +1,10397 @@
+(function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i<t.length;i++)o(t[i]);return o}return r})()({1:[function(require,module,exports){
+const Peer = require('../../server/src/peer')
+const VideoConverter = require('./lib/dist/video-converter');
+
+let current_data = {};
+let peer;
+let player;
+
+/**
+ * Validates that the user is logged in by sending the token 
+ */
+checkIfLoggedIn = async () => {
+    //     const token = window.localStorage.getItem('token')
+    //     console.log(token)
+    //     if(!token){
+    //         console.log("You need to login")
+    //         renderLogin()
+    //     }else{
+
+    //         //Check if the token is valid
+    //         const response = await fetch('http://localhost:8080/auth/validation', {
+    //             method: 'POST',
+    //             headers: {'Authorization': token}
+    //         })
+    //         console.log('RESPONSE', response)
+            
+    //         //Token is valid, show available streams
+    //         if(response.status === 200){
+    //             console.log("SUCCESS")
+                 renderThumbnails()
+
+    //         }
+    //     }
+}
+
+/**
+ * Returns a list of available streams
+ */
+getAvailableStreams = async () => {
+    try{
+        const streamsInJson = await fetch(`./streams`);
+        const streams = await streamsInJson.json();
+        console.log('AVAILABLE', streams)
+        return streams;
+    }catch(err){
+        console.log(err)
+    }
+}
+
+
+createVideoPlayer = () => {
+    const containerDiv = document.getElementById('container')
+    containerDiv.innerHTML = `<h1>Stream from source ${current_data.uri}</h1><br>
+        <button onclick="renderThumbnails(); closeStream()">Go back</button>
+        <button onclick="connectToStream()">Start Stream</button><br>
+        <button onclick="webSocketTest()">WebSocket Test</button><br>
+        <video id="ftlab-stream-video" width="640" height="360"></video>`;
+    containerDiv.innerHTML += '<br>'
+    containerDiv.innerHTML += ''
+    createPeer();
+    connectToStream();
+}
+
+/**
+ * Creates thumbnail (image) for all available streams and adds them to div class='container'
+ */
+renderThumbnails = async () => {
+    const thumbnails = await getAvailableStreams();
+    const containerDiv = document.getElementById('container')
+    containerDiv.innerHTML = '';
+    containerDiv.innerHTML = `<button onClick="configs()">change configs</button>`
+    containerDiv.innerHTML += `<div class="ftlab-stream-thumbnails"></div>`
+    if(thumbnails.length === 0){
+        containerDiv.innerHTML = `<h3>No streams running currently</h3>`
+    }else{
+        for(var i=0; i<thumbnails.length; i++){
+            const encodedURI = encodeURIComponent(thumbnails[i])
+            current_data.uri = thumbnails[i]
+            try{
+                const someData = await fetch(`./stream/rgb?uri=${encodedURI}`)
+                if(!someData.ok){
+                    throw new Error('Image not found')
+                }
+                const myBlob = await someData.blob();
+                const objectURL = URL.createObjectURL(myBlob);
+                // containerDiv.innerHTML += createCard()
+                containerDiv.innerHTML += createCard(objectURL, i+4)
+            }catch(err){
+                console.log("Couldn't create thumbnail");
+                console.log(err) 
+            }
+        }
+    }
+}
+
+
+/** 
+ * Method to create a single thumbnail
+ */
+createCard = (url, viewers) => {
+    return `<div class='ftlab-card-component' >
+                <img src='${url}' class="thumbnail-img" alt="Hups" width="250px"></img>
+                <p>Viewers: ${viewers}</p>
+                <button onclick="createVideoPlayer()">button</button>
+            </div>`
+}
+
+
+createPeer = () => {
+    // FOR PRODUCTION
+    const ws = new WebSocket("ws://" + location.host + ":" + (location.port == "" ? "80" : location.port) + location.pathname);
+    // const ws = new WebSocket("ws://localhost:8080")
+    ws.binaryType = "arraybuffer";
+    peer = new Peer(ws)
+}
+
+webSocketTest = () => {
+    peer.send("update_cfg", "ftl://utu.fi#reconstruction_default/0/renderer/cool_effect", "true")    
+}
+
+
+connectToStream = () => {
+    const element = document.getElementById('ftlab-stream-video');
+    const converter = new VideoConverter.default(element, 20, 6);
+
+    peer.bind(current_data.uri, (latency, streampckg, pckg) => {
+        if(pckg[0] === 2){
+            function decode(value){
+                converter.appendRawData(value);
+            }
+            decode(pckg[5]);
+            converter.play();
+        };
+    })
+
+    // Start the transaction
+    peer.send("get_stream", (current_data.uri, 30, 0, current_data.uri));
+}
+
+closeStream = () => {
+    peer.sock.close()
+}
+
+
+
+/**
+ * **************
+ * CONFIGURATIONS
+ * **************
+ */
+
+
+current_data.configURI = "ftl://utu.fi#reconstruction_snap8/net"
+
+configs = () => {
+    const container = document.getElementById("container");
+    container.innerHTML = `<div class="ftlab-configurations"></div>`;
+    renderConfigOptions();
+}
+
+
+renderConfigOptions = () => {
+    const input = `<p>input1</p><br>ftl://utu.fi#<input type="text">`
+    const doc = document.getElementsByClassName('ftlab-configurations')[0];
+    doc.innerHTML = input;
+}
+
+/**
+ * 
+ */
+loadConfigs = async (str) => {
+    const configURI = encodeURIComponent(`ftl://utu.fi#reconstruction_snap8${str}`);
+    const uri = encodeURIComponent(current_data.uri)
+    const rawResp = await fetch(`./stream/config?settings=${configURI}&uri=${uri}`)
+    const response = await rawResp.json();
+    const content = JSON.parse(response);
+    container.innerHTML += `<p>${response}</p>`;
+}
+
+// current_data.configData = '{"peers": 1}';
+
+/**
+ * Method to send configurations to backend 
+ */
+saveConfigs = async () => {
+    let {uri, configURI, configData} = current_data
+    const rawResp = await fetch('./stream/config', {
+        method: 'POST',
+        headers: {
+            'Accept': 'application/json',
+            'Content-Type': 'application/json'
+        },
+        body: JSON.stringify({peerURI: uri, configURI, data: configData, saveToCPP: true})
+    });
+    const content = await rawResp.json();
+}
+},{"../../server/src/peer":36,"./lib/dist/video-converter":9}],2:[function(require,module,exports){
+"use strict";
+Object.defineProperty(exports, "__esModule", { value: true });
+var bit_stream_1 = require("./util/bit-stream");
+var debug = require("./util/debug");
+var NALU_1 = require("./util/NALU");
+var H264Parser = (function () {
+    function H264Parser(remuxer) {
+        this.remuxer = remuxer;
+        this.track = remuxer.mp4track;
+    }
+    H264Parser.prototype.parseSEI = function (sei) {
+        var messages = H264Parser.readSEI(sei);
+        for (var _i = 0, messages_1 = messages; _i < messages_1.length; _i++) {
+            var m = messages_1[_i];
+            switch (m.type) {
+                case 0:
+                    this.track.seiBuffering = true;
+                    break;
+                case 5:
+                    return true;
+                default:
+                    break;
+            }
+        }
+        return false;
+    };
+    H264Parser.prototype.parseSPS = function (sps) {
+        var config = H264Parser.readSPS(sps);
+        this.track.width = config.width;
+        this.track.height = config.height;
+        this.track.sps = [sps];
+        this.track.codec = 'avc1.';
+        var codecArray = new DataView(sps.buffer, sps.byteOffset + 1, 4);
+        for (var i = 0; i < 3; ++i) {
+            var h = codecArray.getUint8(i).toString(16);
+            if (h.length < 2) {
+                h = '0' + h;
+            }
+            this.track.codec += h;
+        }
+    };
+    H264Parser.prototype.parsePPS = function (pps) {
+        this.track.pps = [pps];
+    };
+    H264Parser.prototype.parseNAL = function (unit) {
+        if (!unit) {
+            return false;
+        }
+        var push = false;
+        switch (unit.type()) {
+            case NALU_1.default.NDR:
+            case NALU_1.default.IDR:
+                push = true;
+                break;
+            case NALU_1.default.SEI:
+                push = this.parseSEI(unit.getData().subarray(4));
+                break;
+            case NALU_1.default.SPS:
+                if (this.track.sps.length === 0) {
+                    this.parseSPS(unit.getData().subarray(4));
+                    debug.log(" Found SPS type NALU frame.");
+                    if (!this.remuxer.readyToDecode && this.track.pps.length > 0 && this.track.sps.length > 0) {
+                        this.remuxer.readyToDecode = true;
+                    }
+                }
+                break;
+            case NALU_1.default.PPS:
+                if (this.track.pps.length === 0) {
+                    this.parsePPS(unit.getData().subarray(4));
+                    debug.log(" Found PPS type NALU frame.");
+                    if (!this.remuxer.readyToDecode && this.track.pps.length > 0 && this.track.sps.length > 0) {
+                        this.remuxer.readyToDecode = true;
+                    }
+                }
+                break;
+            default:
+                debug.log(" Found Unknown type NALU frame. type=" + unit.type());
+                break;
+        }
+        return push;
+    };
+    H264Parser.skipScalingList = function (decoder, count) {
+        var lastScale = 8;
+        var nextScale = 8;
+        for (var j = 0; j < count; j++) {
+            if (nextScale !== 0) {
+                var deltaScale = decoder.readEG();
+                nextScale = (lastScale + deltaScale + 256) % 256;
+            }
+            lastScale = (nextScale === 0) ? lastScale : nextScale;
+        }
+    };
+    H264Parser.readSPS = function (data) {
+        var decoder = new bit_stream_1.default(data);
+        var frameCropLeftOffset = 0;
+        var frameCropRightOffset = 0;
+        var frameCropTopOffset = 0;
+        var frameCropBottomOffset = 0;
+        var sarScale = 1;
+        decoder.readUByte();
+        var profileIdc = decoder.readUByte();
+        decoder.skipBits(5);
+        decoder.skipBits(3);
+        decoder.skipBits(8);
+        decoder.skipUEG();
+        if (profileIdc === 100 ||
+            profileIdc === 110 ||
+            profileIdc === 122 ||
+            profileIdc === 244 ||
+            profileIdc === 44 ||
+            profileIdc === 83 ||
+            profileIdc === 86 ||
+            profileIdc === 118 ||
+            profileIdc === 128) {
+            var chromaFormatIdc = decoder.readUEG();
+            if (chromaFormatIdc === 3) {
+                decoder.skipBits(1);
+            }
+            decoder.skipUEG();
+            decoder.skipUEG();
+            decoder.skipBits(1);
+            if (decoder.readBoolean()) {
+                var scalingListCount = (chromaFormatIdc !== 3) ? 8 : 12;
+                for (var i = 0; i < scalingListCount; ++i) {
+                    if (decoder.readBoolean()) {
+                        if (i < 6) {
+                            H264Parser.skipScalingList(decoder, 16);
+                        }
+                        else {
+                            H264Parser.skipScalingList(decoder, 64);
+                        }
+                    }
+                }
+            }
+        }
+        decoder.skipUEG();
+        var picOrderCntType = decoder.readUEG();
+        if (picOrderCntType === 0) {
+            decoder.readUEG();
+        }
+        else if (picOrderCntType === 1) {
+            decoder.skipBits(1);
+            decoder.skipEG();
+            decoder.skipEG();
+            var numRefFramesInPicOrderCntCycle = decoder.readUEG();
+            for (var i = 0; i < numRefFramesInPicOrderCntCycle; ++i) {
+                decoder.skipEG();
+            }
+        }
+        decoder.skipUEG();
+        decoder.skipBits(1);
+        var picWidthInMbsMinus1 = decoder.readUEG();
+        var picHeightInMapUnitsMinus1 = decoder.readUEG();
+        var frameMbsOnlyFlag = decoder.readBits(1);
+        if (frameMbsOnlyFlag === 0) {
+            decoder.skipBits(1);
+        }
+        decoder.skipBits(1);
+        if (decoder.readBoolean()) {
+            frameCropLeftOffset = decoder.readUEG();
+            frameCropRightOffset = decoder.readUEG();
+            frameCropTopOffset = decoder.readUEG();
+            frameCropBottomOffset = decoder.readUEG();
+        }
+        if (decoder.readBoolean()) {
+            if (decoder.readBoolean()) {
+                var sarRatio = void 0;
+                var aspectRatioIdc = decoder.readUByte();
+                switch (aspectRatioIdc) {
+                    case 1:
+                        sarRatio = [1, 1];
+                        break;
+                    case 2:
+                        sarRatio = [12, 11];
+                        break;
+                    case 3:
+                        sarRatio = [10, 11];
+                        break;
+                    case 4:
+                        sarRatio = [16, 11];
+                        break;
+                    case 5:
+                        sarRatio = [40, 33];
+                        break;
+                    case 6:
+                        sarRatio = [24, 11];
+                        break;
+                    case 7:
+                        sarRatio = [20, 11];
+                        break;
+                    case 8:
+                        sarRatio = [32, 11];
+                        break;
+                    case 9:
+                        sarRatio = [80, 33];
+                        break;
+                    case 10:
+                        sarRatio = [18, 11];
+                        break;
+                    case 11:
+                        sarRatio = [15, 11];
+                        break;
+                    case 12:
+                        sarRatio = [64, 33];
+                        break;
+                    case 13:
+                        sarRatio = [160, 99];
+                        break;
+                    case 14:
+                        sarRatio = [4, 3];
+                        break;
+                    case 15:
+                        sarRatio = [3, 2];
+                        break;
+                    case 16:
+                        sarRatio = [2, 1];
+                        break;
+                    case 255: {
+                        sarRatio = [decoder.readUByte() << 8 | decoder.readUByte(), decoder.readUByte() << 8 | decoder.readUByte()];
+                        break;
+                    }
+                    default: {
+                        debug.error("  H264: Unknown aspectRatioIdc=" + aspectRatioIdc);
+                    }
+                }
+                if (sarRatio) {
+                    sarScale = sarRatio[0] / sarRatio[1];
+                }
+            }
+            if (decoder.readBoolean()) {
+                decoder.skipBits(1);
+            }
+            if (decoder.readBoolean()) {
+                decoder.skipBits(4);
+                if (decoder.readBoolean()) {
+                    decoder.skipBits(24);
+                }
+            }
+            if (decoder.readBoolean()) {
+                decoder.skipUEG();
+                decoder.skipUEG();
+            }
+            if (decoder.readBoolean()) {
+                var unitsInTick = decoder.readUInt();
+                var timeScale = decoder.readUInt();
+                var fixedFrameRate = decoder.readBoolean();
+                var frameDuration = timeScale / (2 * unitsInTick);
+                debug.log("timescale: " + timeScale + "; unitsInTick: " + unitsInTick + "; " +
+                    ("fixedFramerate: " + fixedFrameRate + "; avgFrameDuration: " + frameDuration));
+            }
+        }
+        return {
+            width: Math.ceil((((picWidthInMbsMinus1 + 1) * 16) - frameCropLeftOffset * 2 - frameCropRightOffset * 2) * sarScale),
+            height: ((2 - frameMbsOnlyFlag) * (picHeightInMapUnitsMinus1 + 1) * 16) -
+                ((frameMbsOnlyFlag ? 2 : 4) * (frameCropTopOffset + frameCropBottomOffset)),
+        };
+    };
+    H264Parser.readSEI = function (data) {
+        var decoder = new bit_stream_1.default(data);
+        decoder.skipBits(8);
+        var result = [];
+        while (decoder.bitsAvailable > 3 * 8) {
+            result.push(this.readSEIMessage(decoder));
+        }
+        return result;
+    };
+    H264Parser.readSEIMessage = function (decoder) {
+        function get() {
+            var result = 0;
+            while (true) {
+                var value = decoder.readUByte();
+                result += value;
+                if (value !== 0xff) {
+                    break;
+                }
+            }
+            return result;
+        }
+        var payloadType = get();
+        var payloadSize = get();
+        return this.readSEIPayload(decoder, payloadType, payloadSize);
+    };
+    H264Parser.readSEIPayload = function (decoder, type, size) {
+        var result;
+        switch (type) {
+            default:
+                result = { type: type };
+                decoder.skipBits(size * 8);
+        }
+        decoder.skipBits(decoder.bitsAvailable % 8);
+        return result;
+    };
+    return H264Parser;
+}());
+exports.default = H264Parser;
+
+},{"./util/NALU":5,"./util/bit-stream":6,"./util/debug":7}],3:[function(require,module,exports){
+"use strict";
+Object.defineProperty(exports, "__esModule", { value: true });
+var h264_parser_1 = require("./h264-parser");
+var debug = require("./util/debug");
+var NALU_1 = require("./util/NALU");
+var trackId = 1;
+var H264Remuxer = (function () {
+    function H264Remuxer(fps, framePerFragment, timescale) {
+        this.fps = fps;
+        this.framePerFragment = framePerFragment;
+        this.timescale = timescale;
+        this.readyToDecode = false;
+        this.totalDTS = 0;
+        this.stepDTS = Math.round(this.timescale / this.fps);
+        this.frameCount = 0;
+        this.seq = 1;
+        this.mp4track = {
+            id: H264Remuxer.getTrackID(),
+            type: 'video',
+            len: 0,
+            codec: '',
+            sps: [],
+            pps: [],
+            seiBuffering: false,
+            width: 0,
+            height: 0,
+            timescale: timescale,
+            duration: timescale,
+            samples: [],
+            isKeyFrame: true,
+        };
+        this.unitSamples = [[]];
+        this.parser = new h264_parser_1.default(this);
+    }
+    H264Remuxer.getTrackID = function () {
+        return trackId++;
+    };
+    Object.defineProperty(H264Remuxer.prototype, "seqNum", {
+        get: function () {
+            return this.seq;
+        },
+        enumerable: true,
+        configurable: true
+    });
+    H264Remuxer.prototype.remux = function (nalu) {
+        if (this.mp4track.seiBuffering && nalu.type() === NALU_1.default.SEI) {
+            return this.createNextFrame();
+        }
+        if (this.parser.parseNAL(nalu)) {
+            this.unitSamples[this.unitSamples.length - 1].push(nalu);
+            this.mp4track.len += nalu.getSize();
+        }
+        if (!this.mp4track.seiBuffering && (nalu.type() === NALU_1.default.IDR || nalu.type() === NALU_1.default.NDR)) {
+            return this.createNextFrame();
+        }
+        return;
+    };
+    H264Remuxer.prototype.createNextFrame = function () {
+        if (this.mp4track.len > 0) {
+            this.frameCount++;
+            if (this.frameCount % this.framePerFragment === 0) {
+                var fragment = this.getFragment();
+                if (fragment) {
+                    var dts = this.totalDTS;
+                    this.totalDTS = this.stepDTS * this.frameCount;
+                    return [dts, fragment];
+                }
+                else {
+                    debug.log("No mp4 sample data.");
+                }
+            }
+            this.unitSamples.push([]);
+        }
+        return;
+    };
+    H264Remuxer.prototype.flush = function () {
+        this.seq++;
+        this.mp4track.len = 0;
+        this.mp4track.samples = [];
+        this.mp4track.isKeyFrame = false;
+        this.unitSamples = [[]];
+    };
+    H264Remuxer.prototype.getFragment = function () {
+        if (!this.checkReadyToDecode()) {
+            return undefined;
+        }
+        var payload = new Uint8Array(this.mp4track.len);
+        this.mp4track.samples = [];
+        var offset = 0;
+        for (var i = 0, len = this.unitSamples.length; i < len; i++) {
+            var units = this.unitSamples[i];
+            if (units.length === 0) {
+                continue;
+            }
+            var mp4Sample = {
+                size: 0,
+                cts: this.stepDTS * i,
+            };
+            for (var _i = 0, units_1 = units; _i < units_1.length; _i++) {
+                var unit = units_1[_i];
+                mp4Sample.size += unit.getSize();
+                payload.set(unit.getData(), offset);
+                offset += unit.getSize();
+            }
+            this.mp4track.samples.push(mp4Sample);
+        }
+        if (offset === 0) {
+            return undefined;
+        }
+        return payload;
+    };
+    H264Remuxer.prototype.checkReadyToDecode = function () {
+        if (!this.readyToDecode || this.unitSamples.filter(function (array) { return array.length > 0; }).length === 0) {
+            debug.log("Not ready to decode! readyToDecode(" + this.readyToDecode + ") is false or units is empty.");
+            return false;
+        }
+        return true;
+    };
+    return H264Remuxer;
+}());
+exports.default = H264Remuxer;
+
+},{"./h264-parser":2,"./util/NALU":5,"./util/debug":7}],4:[function(require,module,exports){
+"use strict";
+Object.defineProperty(exports, "__esModule", { value: true });
+var MP4 = (function () {
+    function MP4() {
+    }
+    MP4.init = function () {
+        MP4.initalized = true;
+        MP4.types = {
+            avc1: [],
+            avcC: [],
+            btrt: [],
+            dinf: [],
+            dref: [],
+            esds: [],
+            ftyp: [],
+            hdlr: [],
+            mdat: [],
+            mdhd: [],
+            mdia: [],
+            mfhd: [],
+            minf: [],
+            moof: [],
+            moov: [],
+            mp4a: [],
+            mvex: [],
+            mvhd: [],
+            sdtp: [],
+            stbl: [],
+            stco: [],
+            stsc: [],
+            stsd: [],
+            stsz: [],
+            stts: [],
+            styp: [],
+            tfdt: [],
+            tfhd: [],
+            traf: [],
+            trak: [],
+            trun: [],
+            trep: [],
+            trex: [],
+            tkhd: [],
+            vmhd: [],
+            smhd: [],
+        };
+        for (var type in MP4.types) {
+            if (MP4.types.hasOwnProperty(type)) {
+                MP4.types[type] = [
+                    type.charCodeAt(0),
+                    type.charCodeAt(1),
+                    type.charCodeAt(2),
+                    type.charCodeAt(3),
+                ];
+            }
+        }
+        var hdlr = new Uint8Array([
+            0x00,
+            0x00, 0x00, 0x00,
+            0x00, 0x00, 0x00, 0x00,
+            0x76, 0x69, 0x64, 0x65,
+            0x00, 0x00, 0x00, 0x00,
+            0x00, 0x00, 0x00, 0x00,
+            0x00, 0x00, 0x00, 0x00,
+            0x56, 0x69, 0x64, 0x65,
+            0x6f, 0x48, 0x61, 0x6e,
+            0x64, 0x6c, 0x65, 0x72, 0x00,
+        ]);
+        var dref = new Uint8Array([
+            0x00,
+            0x00, 0x00, 0x00,
+            0x00, 0x00, 0x00, 0x01,
+            0x00, 0x00, 0x00, 0x0c,
+            0x75, 0x72, 0x6c, 0x20,
+            0x00,
+            0x00, 0x00, 0x01,
+        ]);
+        var stco = new Uint8Array([
+            0x00,
+            0x00, 0x00, 0x00,
+            0x00, 0x00, 0x00, 0x00,
+        ]);
+        MP4.STTS = MP4.STSC = MP4.STCO = stco;
+        MP4.STSZ = new Uint8Array([
+            0x00,
+            0x00, 0x00, 0x00,
+            0x00, 0x00, 0x00, 0x00,
+            0x00, 0x00, 0x00, 0x00,
+        ]);
+        MP4.VMHD = new Uint8Array([
+            0x00,
+            0x00, 0x00, 0x01,
+            0x00, 0x00,
+            0x00, 0x00,
+            0x00, 0x00,
+            0x00, 0x00,
+        ]);
+        MP4.SMHD = new Uint8Array([
+            0x00,
+            0x00, 0x00, 0x00,
+            0x00, 0x00,
+            0x00, 0x00,
+        ]);
+        MP4.STSD = new Uint8Array([
+            0x00,
+            0x00, 0x00, 0x00,
+            0x00, 0x00, 0x00, 0x01
+        ]);
+        MP4.FTYP = MP4.box(MP4.types.ftyp, new Uint8Array([
+            0x69, 0x73, 0x6f, 0x35,
+            0x00, 0x00, 0x00, 0x01,
+            0x61, 0x76, 0x63, 0x31,
+            0x69, 0x73, 0x6f, 0x35,
+            0x64, 0x61, 0x73, 0x68,
+        ]));
+        MP4.STYP = MP4.box(MP4.types.styp, new Uint8Array([
+            0x6d, 0x73, 0x64, 0x68,
+            0x00, 0x00, 0x00, 0x00,
+            0x6d, 0x73, 0x64, 0x68,
+            0x6d, 0x73, 0x69, 0x78,
+        ]));
+        MP4.DINF = MP4.box(MP4.types.dinf, MP4.box(MP4.types.dref, dref));
+        MP4.HDLR = MP4.box(MP4.types.hdlr, hdlr);
+    };
+    MP4.box = function (type) {
+        var payload = [];
+        for (var _i = 1; _i < arguments.length; _i++) {
+            payload[_i - 1] = arguments[_i];
+        }
+        var size = 8;
+        for (var _a = 0, payload_1 = payload; _a < payload_1.length; _a++) {
+            var p = payload_1[_a];
+            size += p.byteLength;
+        }
+        var result = new Uint8Array(size);
+        result[0] = (size >> 24) & 0xff;
+        result[1] = (size >> 16) & 0xff;
+        result[2] = (size >> 8) & 0xff;
+        result[3] = size & 0xff;
+        result.set(type, 4);
+        size = 8;
+        for (var _b = 0, payload_2 = payload; _b < payload_2.length; _b++) {
+            var box = payload_2[_b];
+            result.set(box, size);
+            size += box.byteLength;
+        }
+        return result;
+    };
+    MP4.mdat = function (data) {
+        return MP4.box(MP4.types.mdat, data);
+    };
+    MP4.mdhd = function (timescale) {
+        return MP4.box(MP4.types.mdhd, new Uint8Array([
+            0x00,
+            0x00, 0x00, 0x00,
+            0x00, 0x00, 0x00, 0x01,
+            0x00, 0x00, 0x00, 0x02,
+            (timescale >> 24) & 0xFF,
+            (timescale >> 16) & 0xFF,
+            (timescale >> 8) & 0xFF,
+            timescale & 0xFF,
+            0x00, 0x00, 0x00, 0x00,
+            0x55, 0xc4,
+            0x00, 0x00,
+        ]));
+    };
+    MP4.mdia = function (track) {
+        return MP4.box(MP4.types.mdia, MP4.mdhd(track.timescale), MP4.HDLR, MP4.minf(track));
+    };
+    MP4.mfhd = function (sequenceNumber) {
+        return MP4.box(MP4.types.mfhd, new Uint8Array([
+            0x00,
+            0x00, 0x00, 0x00,
+            (sequenceNumber >> 24),
+            (sequenceNumber >> 16) & 0xFF,
+            (sequenceNumber >> 8) & 0xFF,
+            sequenceNumber & 0xFF,
+        ]));
+    };
+    MP4.minf = function (track) {
+        return MP4.box(MP4.types.minf, MP4.box(MP4.types.vmhd, MP4.VMHD), MP4.DINF, MP4.stbl(track));
+    };
+    MP4.moof = function (sn, baseMediaDecodeTime, track) {
+        return MP4.box(MP4.types.moof, MP4.mfhd(sn), MP4.traf(track, baseMediaDecodeTime));
+    };
+    MP4.moov = function (tracks, duration, timescale) {
+        var boxes = [];
+        for (var _i = 0, tracks_1 = tracks; _i < tracks_1.length; _i++) {
+            var track = tracks_1[_i];
+            boxes.push(MP4.trak(track));
+        }
+        return MP4.box.apply(MP4, [MP4.types.moov, MP4.mvhd(timescale, duration), MP4.mvex(tracks)].concat(boxes));
+    };
+    MP4.mvhd = function (timescale, duration) {
+        var bytes = new Uint8Array([
+            0x00,
+            0x00, 0x00, 0x00,
+            0x00, 0x00, 0x00, 0x01,
+            0x00, 0x00, 0x00, 0x02,
+            (timescale >> 24) & 0xFF,
+            (timescale >> 16) & 0xFF,
+            (timescale >> 8) & 0xFF,
+            timescale & 0xFF,
+            (duration >> 24) & 0xFF,
+            (duration >> 16) & 0xFF,
+            (duration >> 8) & 0xFF,
+            duration & 0xFF,
+            0x00, 0x01, 0x00, 0x00,
+            0x01, 0x00,
+            0x00, 0x00,
+            0x00, 0x00, 0x00, 0x00,
+            0x00, 0x00, 0x00, 0x00,
+            0x00, 0x01, 0x00, 0x00,
+            0x00, 0x00, 0x00, 0x00,
+            0x00, 0x00, 0x00, 0x00,
+            0x00, 0x00, 0x00, 0x00,
+            0x00, 0x01, 0x00, 0x00,
+            0x00, 0x00, 0x00, 0x00,
+            0x00, 0x00, 0x00, 0x00,
+            0x00, 0x00, 0x00, 0x00,
+            0x40, 0x00, 0x00, 0x00,
+            0x00, 0x00, 0x00, 0x00,
+            0x00, 0x00, 0x00, 0x00,
+            0x00, 0x00, 0x00, 0x00,
+            0x00, 0x00, 0x00, 0x00,
+            0x00, 0x00, 0x00, 0x00,
+            0x00, 0x00, 0x00, 0x00,
+            0x00, 0x00, 0x00, 0x02,
+        ]);
+        return MP4.box(MP4.types.mvhd, bytes);
+    };
+    MP4.mvex = function (tracks) {
+        var boxes = [];
+        for (var _i = 0, tracks_2 = tracks; _i < tracks_2.length; _i++) {
+            var track = tracks_2[_i];
+            boxes.push(MP4.trex(track));
+        }
+        return MP4.box.apply(MP4, [MP4.types.mvex].concat(boxes, [MP4.trep()]));
+    };
+    MP4.trep = function () {
+        return MP4.box(MP4.types.trep, new Uint8Array([
+            0x00,
+            0x00, 0x00, 0x00,
+            0x00, 0x00, 0x00, 0x01,
+        ]));
+    };
+    MP4.stbl = function (track) {
+        return MP4.box(MP4.types.stbl, MP4.stsd(track), MP4.box(MP4.types.stts, MP4.STTS), MP4.box(MP4.types.stsc, MP4.STSC), MP4.box(MP4.types.stsz, MP4.STSZ), MP4.box(MP4.types.stco, MP4.STCO));
+    };
+    MP4.avc1 = function (track) {
+        var sps = [];
+        var pps = [];
+        for (var _i = 0, _a = track.sps; _i < _a.length; _i++) {
+            var data = _a[_i];
+            var len = data.byteLength;
+            sps.push((len >>> 8) & 0xFF);
+            sps.push((len & 0xFF));
+            sps = sps.concat(Array.prototype.slice.call(data));
+        }
+        for (var _b = 0, _c = track.pps; _b < _c.length; _b++) {
+            var data = _c[_b];
+            var len = data.byteLength;
+            pps.push((len >>> 8) & 0xFF);
+            pps.push((len & 0xFF));
+            pps = pps.concat(Array.prototype.slice.call(data));
+        }
+        var avcc = MP4.box(MP4.types.avcC, new Uint8Array([
+            0x01,
+            sps[3],
+            sps[4],
+            sps[5],
+            0xfc | 3,
+            0xE0 | track.sps.length,
+        ].concat(sps).concat([
+            track.pps.length,
+        ]).concat(pps)));
+        var width = track.width;
+        var height = track.height;
+        return MP4.box(MP4.types.avc1, new Uint8Array([
+            0x00, 0x00, 0x00,
+            0x00, 0x00, 0x00,
+            0x00, 0x01,
+            0x00, 0x00,
+            0x00, 0x00,
+            0x00, 0x00, 0x00, 0x00,
+            0x00, 0x00, 0x00, 0x00,
+            0x00, 0x00, 0x00, 0x00,
+            (width >> 8) & 0xFF,
+            width & 0xff,
+            (height >> 8) & 0xFF,
+            height & 0xff,
+            0x00, 0x48, 0x00, 0x00,
+            0x00, 0x48, 0x00, 0x00,
+            0x00, 0x00, 0x00, 0x00,
+            0x00, 0x01,
+            0x12,
+            0x62, 0x69, 0x6E, 0x65,
+            0x6C, 0x70, 0x72, 0x6F,
+            0x2E, 0x72, 0x75, 0x00,
+            0x00, 0x00, 0x00, 0x00,
+            0x00, 0x00, 0x00, 0x00,
+            0x00, 0x00, 0x00, 0x00,
+            0x00, 0x00, 0x00, 0x00,
+            0x00, 0x00, 0x00,
+            0x00, 0x18,
+            0x11, 0x11
+        ]), avcc, MP4.box(MP4.types.btrt, new Uint8Array([
+            0x00, 0x00, 0x00, 0x00,
+            0x00, 0x2d, 0xc6, 0xc0,
+            0x00, 0x2d, 0xc6, 0xc0,
+        ])));
+    };
+    MP4.stsd = function (track) {
+        return MP4.box(MP4.types.stsd, MP4.STSD, MP4.avc1(track));
+    };
+    MP4.tkhd = function (track) {
+        var id = track.id;
+        var width = track.width;
+        var height = track.height;
+        return MP4.box(MP4.types.tkhd, new Uint8Array([
+            0x00,
+            0x00, 0x00, 0x01,
+            0x00, 0x00, 0x00, 0x01,
+            0x00, 0x00, 0x00, 0x02,
+            (id >> 24) & 0xFF,
+            (id >> 16) & 0xFF,
+            (id >> 8) & 0xFF,
+            id & 0xFF,
+            0x00, 0x00, 0x00, 0x00,
+            0x00, 0x00, 0x00, 0x00,
+            0x00, 0x00, 0x00, 0x00,
+            0x00, 0x00, 0x00, 0x00,
+            0x00, 0x00,
+            0x00, 0x00,
+            (track.type === 'audio' ? 0x01 : 0x00), 0x00,
+            0x00, 0x00,
+            0x00, 0x01, 0x00, 0x00,
+            0x00, 0x00, 0x00, 0x00,
+            0x00, 0x00, 0x00, 0x00,
+            0x00, 0x00, 0x00, 0x00,
+            0x00, 0x01, 0x00, 0x00,
+            0x00, 0x00, 0x00, 0x00,
+            0x00, 0x00, 0x00, 0x00,
+            0x00, 0x00, 0x00, 0x00,
+            0x40, 0x00, 0x00, 0x00,
+            (width >> 8) & 0xFF,
+            width & 0xFF,
+            0x00, 0x00,
+            (height >> 8) & 0xFF,
+            height & 0xFF,
+            0x00, 0x00,
+        ]));
+    };
+    MP4.traf = function (track, baseMediaDecodeTime) {
+        var id = track.id;
+        return MP4.box(MP4.types.traf, MP4.box(MP4.types.tfhd, new Uint8Array([
+            0x00,
+            0x02, 0x00, 0x00,
+            (id >> 24),
+            (id >> 16) & 0XFF,
+            (id >> 8) & 0XFF,
+            (id & 0xFF),
+        ])), MP4.box(MP4.types.tfdt, new Uint8Array([
+            0x00,
+            0x00, 0x00, 0x00,
+            (baseMediaDecodeTime >> 24),
+            (baseMediaDecodeTime >> 16) & 0XFF,
+            (baseMediaDecodeTime >> 8) & 0XFF,
+            (baseMediaDecodeTime & 0xFF),
+        ])), MP4.trun(track, 16 +
+            16 +
+            8 +
+            16 +
+            8 +
+            8));
+    };
+    MP4.trak = function (track) {
+        track.duration = track.duration || 0xffffffff;
+        return MP4.box(MP4.types.trak, MP4.tkhd(track), MP4.mdia(track));
+    };
+    MP4.trex = function (track) {
+        var id = track.id;
+        return MP4.box(MP4.types.trex, new Uint8Array([
+            0x00,
+            0x00, 0x00, 0x00,
+            (id >> 24),
+            (id >> 16) & 0XFF,
+            (id >> 8) & 0XFF,
+            (id & 0xFF),
+            0x00, 0x00, 0x00, 0x01,
+            0x00, 0x00, 0x00, 0x3c,
+            0x00, 0x00, 0x00, 0x00,
+            0x00, 0x01, 0x00, 0x00,
+        ]));
+    };
+    MP4.trun = function (track, offset) {
+        var samples = track.samples || [];
+        var len = samples.length;
+        var additionalLen = track.isKeyFrame ? 4 : 0;
+        var arraylen = 12 + additionalLen + (4 * len);
+        var array = new Uint8Array(arraylen);
+        offset += 8 + arraylen;
+        array.set([
+            0x00,
+            0x00, 0x02, (track.isKeyFrame ? 0x05 : 0x01),
+            (len >>> 24) & 0xFF,
+            (len >>> 16) & 0xFF,
+            (len >>> 8) & 0xFF,
+            len & 0xFF,
+            (offset >>> 24) & 0xFF,
+            (offset >>> 16) & 0xFF,
+            (offset >>> 8) & 0xFF,
+            offset & 0xFF,
+        ], 0);
+        if (track.isKeyFrame) {
+            array.set([
+                0x00, 0x00, 0x00, 0x00,
+            ], 12);
+        }
+        for (var i = 0; i < len; i++) {
+            var sample = samples[i];
+            var size = sample.size;
+            array.set([
+                (size >>> 24) & 0xFF,
+                (size >>> 16) & 0xFF,
+                (size >>> 8) & 0xFF,
+                size & 0xFF,
+            ], 12 + additionalLen + 4 * i);
+        }
+        return MP4.box(MP4.types.trun, array);
+    };
+    MP4.initSegment = function (tracks, duration, timescale) {
+        if (!MP4.initalized) {
+            MP4.init();
+        }
+        var movie = MP4.moov(tracks, duration, timescale);
+        var result = new Uint8Array(MP4.FTYP.byteLength + movie.byteLength);
+        result.set(MP4.FTYP);
+        result.set(movie, MP4.FTYP.byteLength);
+        return result;
+    };
+    MP4.fragmentSegment = function (sn, baseMediaDecodeTime, track, payload) {
+        var moof = MP4.moof(sn, baseMediaDecodeTime, track);
+        var mdat = MP4.mdat(payload);
+        var result = new Uint8Array(MP4.STYP.byteLength + moof.byteLength + mdat.byteLength);
+        result.set(MP4.STYP);
+        result.set(moof, MP4.STYP.byteLength);
+        result.set(mdat, MP4.STYP.byteLength + moof.byteLength);
+        return result;
+    };
+    return MP4;
+}());
+MP4.types = {};
+MP4.initalized = false;
+exports.default = MP4;
+
+},{}],5:[function(require,module,exports){
+"use strict";
+Object.defineProperty(exports, "__esModule", { value: true });
+var NALU = (function () {
+    function NALU(data) {
+        this.data = data;
+        this.nri = (data[0] & 0x60) >> 5;
+        this.ntype = data[0] & 0x1f;
+    }
+    Object.defineProperty(NALU, "NDR", {
+        get: function () { return 1; },
+        enumerable: true,
+        configurable: true
+    });
+    Object.defineProperty(NALU, "IDR", {
+        get: function () { return 5; },
+        enumerable: true,
+        configurable: true
+    });
+    Object.defineProperty(NALU, "SEI", {
+        get: function () { return 6; },
+        enumerable: true,
+        configurable: true
+    });
+    Object.defineProperty(NALU, "SPS", {
+        get: function () { return 7; },
+        enumerable: true,
+        configurable: true
+    });
+    Object.defineProperty(NALU, "PPS", {
+        get: function () { return 8; },
+        enumerable: true,
+        configurable: true
+    });
+    Object.defineProperty(NALU, "TYPES", {
+        get: function () {
+            return _a = {},
+                _a[NALU.IDR] = 'IDR',
+                _a[NALU.SEI] = 'SEI',
+                _a[NALU.SPS] = 'SPS',
+                _a[NALU.PPS] = 'PPS',
+                _a[NALU.NDR] = 'NDR',
+                _a;
+            var _a;
+        },
+        enumerable: true,
+        configurable: true
+    });
+    NALU.type = function (nalu) {
+        if (nalu.ntype in NALU.TYPES) {
+            return NALU.TYPES[nalu.ntype];
+        }
+        else {
+            return 'UNKNOWN';
+        }
+    };
+    NALU.prototype.type = function () {
+        return this.ntype;
+    };
+    NALU.prototype.isKeyframe = function () {
+        return this.ntype === NALU.IDR;
+    };
+    NALU.prototype.getSize = function () {
+        return 4 + this.data.byteLength;
+    };
+    NALU.prototype.getData = function () {
+        var result = new Uint8Array(this.getSize());
+        var view = new DataView(result.buffer);
+        view.setUint32(0, this.getSize() - 4);
+        result.set(this.data, 4);
+        return result;
+    };
+    return NALU;
+}());
+exports.default = NALU;
+
+},{}],6:[function(require,module,exports){
+"use strict";
+Object.defineProperty(exports, "__esModule", { value: true });
+var BitStream = (function () {
+    function BitStream(data) {
+        this.data = data;
+        this.index = 0;
+        this.bitLength = data.byteLength * 8;
+    }
+    Object.defineProperty(BitStream.prototype, "bitsAvailable", {
+        get: function () {
+            return this.bitLength - this.index;
+        },
+        enumerable: true,
+        configurable: true
+    });
+    BitStream.prototype.skipBits = function (size) {
+        if (this.bitsAvailable < size) {
+            throw new Error('no bytes available');
+        }
+        this.index += size;
+    };
+    BitStream.prototype.readBits = function (size) {
+        var result = this.getBits(size, this.index);
+        return result;
+    };
+    BitStream.prototype.getBits = function (size, offsetBits, moveIndex) {
+        if (moveIndex === void 0) { moveIndex = true; }
+        if (this.bitsAvailable < size) {
+            throw new Error('no bytes available');
+        }
+        var offset = offsetBits % 8;
+        var byte = this.data[(offsetBits / 8) | 0] & (0xff >>> offset);
+        var bits = 8 - offset;
+        if (bits >= size) {
+            if (moveIndex) {
+                this.index += size;
+            }
+            return byte >> (bits - size);
+        }
+        else {
+            if (moveIndex) {
+                this.index += bits;
+            }
+            var nextSize = size - bits;
+            return (byte << nextSize) | this.getBits(nextSize, offsetBits + bits, moveIndex);
+        }
+    };
+    BitStream.prototype.skipLZ = function () {
+        var leadingZeroCount;
+        for (leadingZeroCount = 0; leadingZeroCount < this.bitLength - this.index; ++leadingZeroCount) {
+            if (0 !== this.getBits(1, this.index + leadingZeroCount, false)) {
+                this.index += leadingZeroCount;
+                return leadingZeroCount;
+            }
+        }
+        return leadingZeroCount;
+    };
+    BitStream.prototype.skipUEG = function () {
+        this.skipBits(1 + this.skipLZ());
+    };
+    BitStream.prototype.skipEG = function () {
+        this.skipBits(1 + this.skipLZ());
+    };
+    BitStream.prototype.readUEG = function () {
+        var prefix = this.skipLZ();
+        return this.readBits(prefix + 1) - 1;
+    };
+    BitStream.prototype.readEG = function () {
+        var value = this.readUEG();
+        if (0x01 & value) {
+            return (1 + value) >>> 1;
+        }
+        else {
+            return -1 * (value >>> 1);
+        }
+    };
+    BitStream.prototype.readBoolean = function () {
+        return 1 === this.readBits(1);
+    };
+    BitStream.prototype.readUByte = function () {
+        return this.readBits(8);
+    };
+    BitStream.prototype.readUShort = function () {
+        return this.readBits(16);
+    };
+    BitStream.prototype.readUInt = function () {
+        return this.readBits(32);
+    };
+    return BitStream;
+}());
+exports.default = BitStream;
+
+},{}],7:[function(require,module,exports){
+"use strict";
+Object.defineProperty(exports, "__esModule", { value: true });
+var logger;
+var errorLogger;
+function setLogger(log, error) {
+    logger = log;
+    errorLogger = error != null ? error : log;
+}
+exports.setLogger = setLogger;
+function isEnable() {
+    return logger != null;
+}
+exports.isEnable = isEnable;
+function log(message) {
+    var optionalParams = [];
+    for (var _i = 1; _i < arguments.length; _i++) {
+        optionalParams[_i - 1] = arguments[_i];
+    }
+    if (logger) {
+        logger.apply(void 0, [message].concat(optionalParams));
+    }
+}
+exports.log = log;
+function error(message) {
+    var optionalParams = [];
+    for (var _i = 1; _i < arguments.length; _i++) {
+        optionalParams[_i - 1] = arguments[_i];
+    }
+    if (errorLogger) {
+        errorLogger.apply(void 0, [message].concat(optionalParams));
+    }
+}
+exports.error = error;
+
+},{}],8:[function(require,module,exports){
+"use strict";
+Object.defineProperty(exports, "__esModule", { value: true });
+var NALU_1 = require("./NALU");
+var VideoStreamBuffer = (function () {
+    function VideoStreamBuffer() {
+    }
+    VideoStreamBuffer.prototype.clear = function () {
+        this.buffer = undefined;
+    };
+    VideoStreamBuffer.prototype.append = function (value) {
+        var nextNalHeader = function (b) {
+            var i = 3;
+            return function () {
+                var count = 0;
+                for (; i < b.length; i++) {
+                    switch (b[i]) {
+                        case 0:
+                            count++;
+                            break;
+                        case 1:
+                            if (count === 3) {
+                                return i - 3;
+                            }
+                        default:
+                            count = 0;
+                    }
+                }
+                return;
+            };
+        };
+        var result = [];
+        var buffer;
+        if (this.buffer) {
+            if (value[3] === 1 && value[2] === 0 && value[1] === 0 && value[0] === 0) {
+                result.push(new NALU_1.default(this.buffer.subarray(4)));
+                buffer = Uint8Array.from(value);
+            }
+        }
+        if (buffer == null) {
+            buffer = this.mergeBuffer(value);
+        }
+        var lastIndex = 0;
+        var f = nextNalHeader(buffer);
+        for (var index = f(); index != null; index = f()) {
+            result.push(new NALU_1.default(buffer.subarray(lastIndex + 4, index)));
+            lastIndex = index;
+        }
+        this.buffer = buffer.subarray(lastIndex);
+        return result;
+    };
+    VideoStreamBuffer.prototype.mergeBuffer = function (value) {
+        if (this.buffer == null) {
+            return Uint8Array.from(value);
+        }
+        else {
+            var newBuffer = new Uint8Array(this.buffer.byteLength + value.length);
+            if (this.buffer.byteLength > 0) {
+                newBuffer.set(this.buffer, 0);
+            }
+            newBuffer.set(value, this.buffer.byteLength);
+            return newBuffer;
+        }
+    };
+    return VideoStreamBuffer;
+}());
+exports.default = VideoStreamBuffer;
+
+},{"./NALU":5}],9:[function(require,module,exports){
+"use strict";
+Object.defineProperty(exports, "__esModule", { value: true });
+var h264_remuxer_1 = require("./h264-remuxer");
+var mp4_generator_1 = require("./mp4-generator");
+var debug = require("./util/debug");
+var nalu_stream_buffer_1 = require("./util/nalu-stream-buffer");
+exports.mimeType = 'video/mp4; codecs="avc1.42E01E"';
+var VideoConverter = (function () {
+    function VideoConverter(element, fps, fpf) {
+        if (fps === void 0) { fps = 60; }
+        if (fpf === void 0) { fpf = fps; }
+        this.element = element;
+        this.fps = fps;
+        this.fpf = fpf;
+        this.receiveBuffer = new nalu_stream_buffer_1.default();
+        this.queue = [];
+        if (!MediaSource || !MediaSource.isTypeSupported(exports.mimeType)) {
+            throw new Error("Your browser is not supported: " + exports.mimeType);
+        }
+        this.reset();
+    }
+    Object.defineProperty(VideoConverter, "errorNotes", {
+        get: function () {
+            return _a = {},
+                _a[MediaError.MEDIA_ERR_ABORTED] = 'fetching process aborted by user',
+                _a[MediaError.MEDIA_ERR_NETWORK] = 'error occurred when downloading',
+                _a[MediaError.MEDIA_ERR_DECODE] = 'error occurred when decoding',
+                _a[MediaError.MEDIA_ERR_SRC_NOT_SUPPORTED] = 'audio/video not supported',
+                _a;
+            var _a;
+        },
+        enumerable: true,
+        configurable: true
+    });
+    VideoConverter.prototype.setup = function () {
+        var _this = this;
+        this.mediaReadyPromise = new Promise(function (resolve, _reject) {
+            _this.mediaSource.addEventListener('sourceopen', function () {
+                debug.log("Media Source opened.");
+                _this.sourceBuffer = _this.mediaSource.addSourceBuffer(exports.mimeType);
+                _this.sourceBuffer.addEventListener('updateend', function () {
+                    debug.log("  SourceBuffer updateend");
+                    debug.log("    sourceBuffer.buffered.length=" + _this.sourceBuffer.buffered.length);
+                    for (var i = 0, len = _this.sourceBuffer.buffered.length; i < len; i++) {
+                        debug.log("    sourceBuffer.buffered [" + i + "]: " +
+                            (_this.sourceBuffer.buffered.start(i) + ", " + _this.sourceBuffer.buffered.end(i)));
+                    }
+                    debug.log("  mediasource.duration=" + _this.mediaSource.duration);
+                    debug.log("  mediasource.readyState=" + _this.mediaSource.readyState);
+                    debug.log("  video.duration=" + _this.element.duration);
+                    debug.log("    video.buffered.length=" + _this.element.buffered.length);
+                    if (debug.isEnable()) {
+                        for (var i = 0, len = _this.element.buffered.length; i < len; i++) {
+                            debug.log("    video.buffered [" + i + "]: " + _this.element.buffered.start(i) + ", " + _this.element.buffered.end(i));
+                        }
+                    }
+                    debug.log("  video.currentTime=" + _this.element.currentTime);
+                    debug.log("  video.readyState=" + _this.element.readyState);
+                    var data = _this.queue.shift();
+                    if (data) {
+                        _this.writeBuffer(data);
+                    }
+                });
+                _this.sourceBuffer.addEventListener('error', function () {
+                    debug.error('  SourceBuffer errored!');
+                });
+                _this.mediaReady = true;
+                resolve();
+            }, false);
+            _this.mediaSource.addEventListener('sourceclose', function () {
+                debug.log("Media Source closed.");
+                _this.mediaReady = false;
+            }, false);
+            _this.element.src = URL.createObjectURL(_this.mediaSource);
+        });
+        return this.mediaReadyPromise;
+    };
+    VideoConverter.prototype.play = function () {
+        var _this = this;
+        if (!this.element.paused) {
+            return;
+        }
+        if (this.mediaReady && this.element.readyState >= 2) {
+            this.element.play();
+        }
+        else {
+            var handler_1 = function () {
+                _this.play();
+                _this.element.removeEventListener('canplaythrough', handler_1);
+            };
+            this.element.addEventListener('canplaythrough', handler_1);
+        }
+    };
+    VideoConverter.prototype.pause = function () {
+        if (this.element.paused) {
+            return;
+        }
+        this.element.pause();
+    };
+    VideoConverter.prototype.reset = function () {
+        this.receiveBuffer.clear();
+        if (this.mediaSource && this.mediaSource.readyState === 'open') {
+            this.mediaSource.duration = 0;
+            this.mediaSource.endOfStream();
+        }
+        this.mediaSource = new MediaSource();
+        this.remuxer = new h264_remuxer_1.default(this.fps, this.fpf, this.fps * 60);
+        this.mediaReady = false;
+        this.mediaReadyPromise = undefined;
+        this.queue = [];
+        this.isFirstFrame = true;
+        this.setup();
+    };
+    VideoConverter.prototype.appendRawData = function (data) {
+        var nalus = this.receiveBuffer.append(data);
+        for (var _i = 0, nalus_1 = nalus; _i < nalus_1.length; _i++) {
+            var nalu = nalus_1[_i];
+            var ret = this.remuxer.remux(nalu);
+            if (ret) {
+                this.writeFragment(ret[0], ret[1]);
+            }
+        }
+    };
+    VideoConverter.prototype.writeFragment = function (dts, pay) {
+        var remuxer = this.remuxer;
+        if (remuxer.mp4track.isKeyFrame) {
+            this.writeBuffer(mp4_generator_1.default.initSegment([remuxer.mp4track], Infinity, remuxer.timescale));
+        }
+        if (pay && pay.byteLength) {
+            debug.log(" Put fragment: " + remuxer.seqNum + ", frames=" + remuxer.mp4track.samples.length + ", size=" + pay.byteLength);
+            var fragment = mp4_generator_1.default.fragmentSegment(remuxer.seqNum, dts, remuxer.mp4track, pay);
+            this.writeBuffer(fragment);
+            remuxer.flush();
+        }
+        else {
+            debug.error("Nothing payload!");
+        }
+    };
+    VideoConverter.prototype.writeBuffer = function (data) {
+        var _this = this;
+        if (this.mediaReady) {
+            if (this.sourceBuffer.updating) {
+                this.queue.push(data);
+            }
+            else {
+                this.doAppend(data);
+            }
+        }
+        else {
+            this.queue.push(data);
+            if (this.mediaReadyPromise) {
+                this.mediaReadyPromise.then(function () {
+                    if (!_this.sourceBuffer.updating) {
+                        var d = _this.queue.shift();
+                        if (d) {
+                            _this.writeBuffer(d);
+                        }
+                    }
+                });
+                this.mediaReadyPromise = undefined;
+            }
+        }
+    };
+    VideoConverter.prototype.doAppend = function (data) {
+        var error = this.element.error;
+        if (error) {
+            debug.error("MSE Error Occured: " + VideoConverter.errorNotes[error.code]);
+            this.element.pause();
+            if (this.mediaSource.readyState === 'open') {
+                this.mediaSource.endOfStream();
+            }
+        }
+        else {
+            try {
+                this.sourceBuffer.appendBuffer(data);
+                debug.log("  appended buffer: size=" + data.byteLength);
+            }
+            catch (err) {
+                debug.error("MSE Error occured while appending buffer. " + err.name + ": " + err.message);
+            }
+        }
+    };
+    return VideoConverter;
+}());
+exports.default = VideoConverter;
+
+},{"./h264-remuxer":3,"./mp4-generator":4,"./util/debug":7,"./util/nalu-stream-buffer":8}],10:[function(require,module,exports){
+'use strict'
+var DuplexStream = require('readable-stream').Duplex
+  , util         = require('util')
+  , Buffer       = require('safe-buffer').Buffer
+
+function BufferList (callback) {
+  if (!(this instanceof BufferList))
+    return new BufferList(callback)
+
+  this._bufs  = []
+  this.length = 0
+
+  if (typeof callback == 'function') {
+    this._callback = callback
+
+    var piper = function piper (err) {
+      if (this._callback) {
+        this._callback(err)
+        this._callback = null
+      }
+    }.bind(this)
+
+    this.on('pipe', function onPipe (src) {
+      src.on('error', piper)
+    })
+    this.on('unpipe', function onUnpipe (src) {
+      src.removeListener('error', piper)
+    })
+  } else {
+    this.append(callback)
+  }
+
+  DuplexStream.call(this)
+}
+
+
+util.inherits(BufferList, DuplexStream)
+
+
+BufferList.prototype._offset = function _offset (offset) {
+  var tot = 0, i = 0, _t
+  if (offset === 0) return [ 0, 0 ]
+  for (; i < this._bufs.length; i++) {
+    _t = tot + this._bufs[i].length
+    if (offset < _t || i == this._bufs.length - 1) {
+      return [ i, offset - tot ]
+    }
+    tot = _t
+  }
+}
+
+BufferList.prototype._reverseOffset = function (blOffset) {
+  var bufferId = blOffset[0]
+  var offset = blOffset[1]
+  for (var i = 0; i < bufferId; i++) {
+    offset += this._bufs[i].length
+  }
+  return offset
+}
+
+BufferList.prototype.append = function append (buf) {
+  var i = 0
+
+  if (Buffer.isBuffer(buf)) {
+    this._appendBuffer(buf)
+  } else if (Array.isArray(buf)) {
+    for (; i < buf.length; i++)
+      this.append(buf[i])
+  } else if (buf instanceof BufferList) {
+    // unwrap argument into individual BufferLists
+    for (; i < buf._bufs.length; i++)
+      this.append(buf._bufs[i])
+  } else if (buf != null) {
+    // coerce number arguments to strings, since Buffer(number) does
+    // uninitialized memory allocation
+    if (typeof buf == 'number')
+      buf = buf.toString()
+
+    this._appendBuffer(Buffer.from(buf))
+  }
+
+  return this
+}
+
+
+BufferList.prototype._appendBuffer = function appendBuffer (buf) {
+  this._bufs.push(buf)
+  this.length += buf.length
+}
+
+
+BufferList.prototype._write = function _write (buf, encoding, callback) {
+  this._appendBuffer(buf)
+
+  if (typeof callback == 'function')
+    callback()
+}
+
+
+BufferList.prototype._read = function _read (size) {
+  if (!this.length)
+    return this.push(null)
+
+  size = Math.min(size, this.length)
+  this.push(this.slice(0, size))
+  this.consume(size)
+}
+
+
+BufferList.prototype.end = function end (chunk) {
+  DuplexStream.prototype.end.call(this, chunk)
+
+  if (this._callback) {
+    this._callback(null, this.slice())
+    this._callback = null
+  }
+}
+
+
+BufferList.prototype.get = function get (index) {
+  if (index > this.length || index < 0) {
+    return undefined
+  }
+  var offset = this._offset(index)
+  return this._bufs[offset[0]][offset[1]]
+}
+
+
+BufferList.prototype.slice = function slice (start, end) {
+  if (typeof start == 'number' && start < 0)
+    start += this.length
+  if (typeof end == 'number' && end < 0)
+    end += this.length
+  return this.copy(null, 0, start, end)
+}
+
+
+BufferList.prototype.copy = function copy (dst, dstStart, srcStart, srcEnd) {
+  if (typeof srcStart != 'number' || srcStart < 0)
+    srcStart = 0
+  if (typeof srcEnd != 'number' || srcEnd > this.length)
+    srcEnd = this.length
+  if (srcStart >= this.length)
+    return dst || Buffer.alloc(0)
+  if (srcEnd <= 0)
+    return dst || Buffer.alloc(0)
+
+  var copy   = !!dst
+    , off    = this._offset(srcStart)
+    , len    = srcEnd - srcStart
+    , bytes  = len
+    , bufoff = (copy && dstStart) || 0
+    , start  = off[1]
+    , l
+    , i
+
+  // copy/slice everything
+  if (srcStart === 0 && srcEnd == this.length) {
+    if (!copy) { // slice, but full concat if multiple buffers
+      return this._bufs.length === 1
+        ? this._bufs[0]
+        : Buffer.concat(this._bufs, this.length)
+    }
+
+    // copy, need to copy individual buffers
+    for (i = 0; i < this._bufs.length; i++) {
+      this._bufs[i].copy(dst, bufoff)
+      bufoff += this._bufs[i].length
+    }
+
+    return dst
+  }
+
+  // easy, cheap case where it's a subset of one of the buffers
+  if (bytes <= this._bufs[off[0]].length - start) {
+    return copy
+      ? this._bufs[off[0]].copy(dst, dstStart, start, start + bytes)
+      : this._bufs[off[0]].slice(start, start + bytes)
+  }
+
+  if (!copy) // a slice, we need something to copy in to
+    dst = Buffer.allocUnsafe(len)
+
+  for (i = off[0]; i < this._bufs.length; i++) {
+    l = this._bufs[i].length - start
+
+    if (bytes > l) {
+      this._bufs[i].copy(dst, bufoff, start)
+    } else {
+      this._bufs[i].copy(dst, bufoff, start, start + bytes)
+      break
+    }
+
+    bufoff += l
+    bytes -= l
+
+    if (start)
+      start = 0
+  }
+
+  return dst
+}
+
+BufferList.prototype.shallowSlice = function shallowSlice (start, end) {
+  start = start || 0
+  end = typeof end !== 'number' ? this.length : end
+
+  if (start < 0)
+    start += this.length
+  if (end < 0)
+    end += this.length
+
+  if (start === end) {
+    return new BufferList()
+  }
+  var startOffset = this._offset(start)
+    , endOffset = this._offset(end)
+    , buffers = this._bufs.slice(startOffset[0], endOffset[0] + 1)
+
+  if (endOffset[1] == 0)
+    buffers.pop()
+  else
+    buffers[buffers.length-1] = buffers[buffers.length-1].slice(0, endOffset[1])
+
+  if (startOffset[1] != 0)
+    buffers[0] = buffers[0].slice(startOffset[1])
+
+  return new BufferList(buffers)
+}
+
+BufferList.prototype.toString = function toString (encoding, start, end) {
+  return this.slice(start, end).toString(encoding)
+}
+
+BufferList.prototype.consume = function consume (bytes) {
+  while (this._bufs.length) {
+    if (bytes >= this._bufs[0].length) {
+      bytes -= this._bufs[0].length
+      this.length -= this._bufs[0].length
+      this._bufs.shift()
+    } else {
+      this._bufs[0] = this._bufs[0].slice(bytes)
+      this.length -= bytes
+      break
+    }
+  }
+  return this
+}
+
+
+BufferList.prototype.duplicate = function duplicate () {
+  var i = 0
+    , copy = new BufferList()
+
+  for (; i < this._bufs.length; i++)
+    copy.append(this._bufs[i])
+
+  return copy
+}
+
+
+BufferList.prototype.destroy = function destroy () {
+  this._bufs.length = 0
+  this.length = 0
+  this.push(null)
+}
+
+
+BufferList.prototype.indexOf = function (search, offset, encoding) {
+  if (encoding === undefined && typeof offset === 'string') {
+    encoding = offset
+    offset = undefined
+  }
+  if (typeof search === 'function' || Array.isArray(search)) {
+    throw new TypeError('The "value" argument must be one of type string, Buffer, BufferList, or Uint8Array.')
+  } else if (typeof search === 'number') {
+      search = Buffer.from([search])
+  } else if (typeof search === 'string') {
+    search = Buffer.from(search, encoding)
+  } else if (search instanceof BufferList) {
+    search = search.slice()
+  } else if (!Buffer.isBuffer(search)) {
+    search = Buffer.from(search)
+  }
+
+  offset = Number(offset || 0)
+  if (isNaN(offset)) {
+    offset = 0
+  }
+
+  if (offset < 0) {
+    offset = this.length + offset
+  }
+
+  if (offset < 0) {
+    offset = 0
+  }
+
+  if (search.length === 0) {
+    return offset > this.length ? this.length : offset
+  }
+
+  var blOffset = this._offset(offset)
+  var blIndex = blOffset[0] // index of which internal buffer we're working on
+  var buffOffset = blOffset[1] // offset of the internal buffer we're working on
+
+  // scan over each buffer
+  for (blIndex; blIndex < this._bufs.length; blIndex++) {
+    var buff = this._bufs[blIndex]
+    while(buffOffset < buff.length) {
+      var availableWindow = buff.length - buffOffset
+      if (availableWindow >= search.length) {
+        var nativeSearchResult = buff.indexOf(search, buffOffset)
+        if (nativeSearchResult !== -1) {
+          return this._reverseOffset([blIndex, nativeSearchResult])
+        }
+        buffOffset = buff.length - search.length + 1 // end of native search window
+      } else {
+        var revOffset = this._reverseOffset([blIndex, buffOffset])
+        if (this._match(revOffset, search)) {
+          return revOffset
+        }
+        buffOffset++
+      }
+    }
+    buffOffset = 0
+  }
+  return -1
+}
+
+BufferList.prototype._match = function(offset, search) {
+  if (this.length - offset < search.length) {
+    return false
+  }
+  for (var searchOffset = 0; searchOffset < search.length ; searchOffset++) {
+    if(this.get(offset + searchOffset) !== search[searchOffset]){
+      return false
+    }
+  }
+  return true
+}
+
+
+;(function () {
+  var methods = {
+      'readDoubleBE' : 8
+    , 'readDoubleLE' : 8
+    , 'readFloatBE'  : 4
+    , 'readFloatLE'  : 4
+    , 'readInt32BE'  : 4
+    , 'readInt32LE'  : 4
+    , 'readUInt32BE' : 4
+    , 'readUInt32LE' : 4
+    , 'readInt16BE'  : 2
+    , 'readInt16LE'  : 2
+    , 'readUInt16BE' : 2
+    , 'readUInt16LE' : 2
+    , 'readInt8'     : 1
+    , 'readUInt8'    : 1
+    , 'readIntBE'    : null
+    , 'readIntLE'    : null
+    , 'readUIntBE'   : null
+    , 'readUIntLE'   : null
+  }
+
+  for (var m in methods) {
+    (function (m) {
+      if (methods[m] === null) {
+        BufferList.prototype[m] = function (offset, byteLength) {
+          return this.slice(offset, offset + byteLength)[m](0, byteLength)
+        }
+      }
+      else {
+        BufferList.prototype[m] = function (offset) {
+          return this.slice(offset, offset + methods[m])[m](0)
+        }
+      }
+    }(m))
+  }
+}())
+
+
+module.exports = BufferList
+
+},{"readable-stream":28,"safe-buffer":29,"util":53}],11:[function(require,module,exports){
+(function (Buffer){
+// Copyright Joyent, Inc. and other Node contributors.
+//
+// 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.
+
+// NOTE: These type checking functions intentionally don't use `instanceof`
+// because it is fragile and can be easily faked with `Object.create()`.
+
+function isArray(arg) {
+  if (Array.isArray) {
+    return Array.isArray(arg);
+  }
+  return objectToString(arg) === '[object Array]';
+}
+exports.isArray = isArray;
+
+function isBoolean(arg) {
+  return typeof arg === 'boolean';
+}
+exports.isBoolean = isBoolean;
+
+function isNull(arg) {
+  return arg === null;
+}
+exports.isNull = isNull;
+
+function isNullOrUndefined(arg) {
+  return arg == null;
+}
+exports.isNullOrUndefined = isNullOrUndefined;
+
+function isNumber(arg) {
+  return typeof arg === 'number';
+}
+exports.isNumber = isNumber;
+
+function isString(arg) {
+  return typeof arg === 'string';
+}
+exports.isString = isString;
+
+function isSymbol(arg) {
+  return typeof arg === 'symbol';
+}
+exports.isSymbol = isSymbol;
+
+function isUndefined(arg) {
+  return arg === void 0;
+}
+exports.isUndefined = isUndefined;
+
+function isRegExp(re) {
+  return objectToString(re) === '[object RegExp]';
+}
+exports.isRegExp = isRegExp;
+
+function isObject(arg) {
+  return typeof arg === 'object' && arg !== null;
+}
+exports.isObject = isObject;
+
+function isDate(d) {
+  return objectToString(d) === '[object Date]';
+}
+exports.isDate = isDate;
+
+function isError(e) {
+  return (objectToString(e) === '[object Error]' || e instanceof Error);
+}
+exports.isError = isError;
+
+function isFunction(arg) {
+  return typeof arg === 'function';
+}
+exports.isFunction = isFunction;
+
+function isPrimitive(arg) {
+  return arg === null ||
+         typeof arg === 'boolean' ||
+         typeof arg === 'number' ||
+         typeof arg === 'string' ||
+         typeof arg === 'symbol' ||  // ES6 symbol
+         typeof arg === 'undefined';
+}
+exports.isPrimitive = isPrimitive;
+
+exports.isBuffer = Buffer.isBuffer;
+
+function objectToString(o) {
+  return Object.prototype.toString.call(o);
+}
+
+}).call(this,{"isBuffer":require("../../../../../../../../../usr/local/lib/node_modules/browserify/node_modules/is-buffer/index.js")})
+},{"../../../../../../../../../usr/local/lib/node_modules/browserify/node_modules/is-buffer/index.js":47}],12:[function(require,module,exports){
+if (typeof Object.create === 'function') {
+  // implementation from standard node.js 'util' module
+  module.exports = function inherits(ctor, superCtor) {
+    ctor.super_ = superCtor
+    ctor.prototype = Object.create(superCtor.prototype, {
+      constructor: {
+        value: ctor,
+        enumerable: false,
+        writable: true,
+        configurable: true
+      }
+    });
+  };
+} else {
+  // old school shim for old browsers
+  module.exports = function inherits(ctor, superCtor) {
+    ctor.super_ = superCtor
+    var TempCtor = function () {}
+    TempCtor.prototype = superCtor.prototype
+    ctor.prototype = new TempCtor()
+    ctor.prototype.constructor = ctor
+  }
+}
+
+},{}],13:[function(require,module,exports){
+var toString = {}.toString;
+
+module.exports = Array.isArray || function (arr) {
+  return toString.call(arr) == '[object Array]';
+};
+
+},{}],14:[function(require,module,exports){
+'use strict'
+
+var Buffer = require('safe-buffer').Buffer
+var assert = require('assert')
+var bl = require('bl')
+var streams = require('./lib/streams')
+var buildDecode = require('./lib/decoder')
+var buildEncode = require('./lib/encoder')
+
+function msgpack (options) {
+  var encodingTypes = []
+  var decodingTypes = []
+
+  options = options || {
+    forceFloat64: false,
+    compatibilityMode: false,
+    disableTimestampEncoding: false // if true, skips encoding Dates using the msgpack timestamp ext format (-1)
+  }
+
+  function registerEncoder (check, encode) {
+    assert(check, 'must have an encode function')
+    assert(encode, 'must have an encode function')
+
+    encodingTypes.push({
+      check: check, encode: encode
+    })
+
+    return this
+  }
+
+  function registerDecoder (type, decode) {
+    assert(type >= 0, 'must have a non-negative type')
+    assert(decode, 'must have a decode function')
+
+    decodingTypes.push({
+      type: type, decode: decode
+    })
+
+    return this
+  }
+
+  function register (type, constructor, encode, decode) {
+    assert(constructor, 'must have a constructor')
+    assert(encode, 'must have an encode function')
+    assert(type >= 0, 'must have a non-negative type')
+    assert(decode, 'must have a decode function')
+
+    function check (obj) {
+      return (obj instanceof constructor)
+    }
+
+    function reEncode (obj) {
+      var buf = bl()
+      var header = Buffer.allocUnsafe(1)
+
+      header.writeInt8(type, 0)
+
+      buf.append(header)
+      buf.append(encode(obj))
+
+      return buf
+    }
+
+    this.registerEncoder(check, reEncode)
+    this.registerDecoder(type, decode)
+
+    return this
+  }
+
+  return {
+    encode: buildEncode(encodingTypes, options.forceFloat64, options.compatibilityMode, options.disableTimestampEncoding),
+    decode: buildDecode(decodingTypes),
+    register: register,
+    registerEncoder: registerEncoder,
+    registerDecoder: registerDecoder,
+    encoder: streams.encoder,
+    decoder: streams.decoder,
+    // needed for levelup support
+    buffer: true,
+    type: 'msgpack5',
+    IncompleteBufferError: buildDecode.IncompleteBufferError
+  }
+}
+
+module.exports = msgpack
+
+},{"./lib/decoder":15,"./lib/encoder":16,"./lib/streams":17,"assert":38,"bl":10,"safe-buffer":29}],15:[function(require,module,exports){
+'use strict'
+
+var bl = require('bl')
+var util = require('util')
+
+function IncompleteBufferError (message) {
+  Error.call(this) // super constructor
+  if (Error.captureStackTrace) {
+    Error.captureStackTrace(this, this.constructor) // super helper method to include stack trace in error object
+  }
+  this.name = this.constructor.name
+  this.message = message || 'unable to decode'
+}
+
+util.inherits(IncompleteBufferError, Error)
+
+module.exports = function buildDecode (decodingTypes) {
+  return decode
+
+  function getSize (first) {
+    switch (first) {
+      case 0xc4:
+        return 2
+      case 0xc5:
+        return 3
+      case 0xc6:
+        return 5
+      case 0xc7:
+        return 3
+      case 0xc8:
+        return 4
+      case 0xc9:
+        return 6
+      case 0xca:
+        return 5
+      case 0xcb:
+        return 9
+      case 0xcc:
+        return 2
+      case 0xcd:
+        return 3
+      case 0xce:
+        return 5
+      case 0xcf:
+        return 9
+      case 0xd0:
+        return 2
+      case 0xd1:
+        return 3
+      case 0xd2:
+        return 5
+      case 0xd3:
+        return 9
+      case 0xd4:
+        return 3
+      case 0xd5:
+        return 4
+      case 0xd6:
+        return 6
+      case 0xd7:
+        return 10
+      case 0xd8:
+        return 18
+      case 0xd9:
+        return 2
+      case 0xda:
+        return 3
+      case 0xdb:
+        return 5
+      case 0xde:
+        return 3
+      default:
+        return -1
+    }
+  }
+
+  function hasMinBufferSize (first, length) {
+    var size = getSize(first)
+
+    if (size !== -1 && length < size) {
+      return false
+    } else {
+      return true
+    }
+  }
+
+  function isValidDataSize (dataLength, bufLength, headerLength) {
+    return bufLength >= headerLength + dataLength
+  }
+
+  function buildDecodeResult (value, bytesConsumed) {
+    return {
+      value: value,
+      bytesConsumed: bytesConsumed
+    }
+  }
+
+  function decode (buf) {
+    if (!(buf instanceof bl)) {
+      buf = bl().append(buf)
+    }
+
+    var result = tryDecode(buf)
+    if (result) {
+      buf.consume(result.bytesConsumed)
+      return result.value
+    } else {
+      throw new IncompleteBufferError()
+    }
+  }
+
+  function tryDecode (buf, offset) {
+    offset = offset === undefined ? 0 : offset
+    var bufLength = buf.length - offset
+    if (bufLength <= 0) {
+      return null
+    }
+
+    var first = buf.readUInt8(offset)
+    var length
+    var result = 0
+    var type
+    var bytePos
+
+    if (!hasMinBufferSize(first, bufLength)) {
+      return null
+    }
+
+    switch (first) {
+      case 0xc0:
+        return buildDecodeResult(null, 1)
+      case 0xc2:
+        return buildDecodeResult(false, 1)
+      case 0xc3:
+        return buildDecodeResult(true, 1)
+      case 0xcc:
+        // 1-byte unsigned int
+        result = buf.readUInt8(offset + 1)
+        return buildDecodeResult(result, 2)
+      case 0xcd:
+        // 2-bytes BE unsigned int
+        result = buf.readUInt16BE(offset + 1)
+        return buildDecodeResult(result, 3)
+      case 0xce:
+        // 4-bytes BE unsigned int
+        result = buf.readUInt32BE(offset + 1)
+        return buildDecodeResult(result, 5)
+      case 0xcf:
+        // 8-bytes BE unsigned int
+        // Read long byte by byte, big-endian
+        for (bytePos = 7; bytePos >= 0; bytePos--) {
+          result += (buf.readUInt8(offset + bytePos + 1) * Math.pow(2, (8 * (7 - bytePos))))
+        }
+        return buildDecodeResult(result, 9)
+      case 0xd0:
+        // 1-byte signed int
+        result = buf.readInt8(offset + 1)
+        return buildDecodeResult(result, 2)
+      case 0xd1:
+        // 2-bytes signed int
+        result = buf.readInt16BE(offset + 1)
+        return buildDecodeResult(result, 3)
+      case 0xd2:
+        // 4-bytes signed int
+        result = buf.readInt32BE(offset + 1)
+        return buildDecodeResult(result, 5)
+      case 0xd3:
+        result = readInt64BE(buf.slice(offset + 1, offset + 9), 0)
+        return buildDecodeResult(result, 9)
+      case 0xca:
+        // 4-bytes float
+        result = buf.readFloatBE(offset + 1)
+        return buildDecodeResult(result, 5)
+      case 0xcb:
+        // 8-bytes double
+        result = buf.readDoubleBE(offset + 1)
+        return buildDecodeResult(result, 9)
+      case 0xd9:
+        // strings up to 2^8 - 1 bytes
+        length = buf.readUInt8(offset + 1)
+        if (!isValidDataSize(length, bufLength, 2)) {
+          return null
+        }
+        result = buf.toString('utf8', offset + 2, offset + 2 + length)
+        return buildDecodeResult(result, 2 + length)
+      case 0xda:
+        // strings up to 2^16 - 2 bytes
+        length = buf.readUInt16BE(offset + 1)
+        if (!isValidDataSize(length, bufLength, 3)) {
+          return null
+        }
+        result = buf.toString('utf8', offset + 3, offset + 3 + length)
+        return buildDecodeResult(result, 3 + length)
+      case 0xdb:
+        // strings up to 2^32 - 4 bytes
+        length = buf.readUInt32BE(offset + 1)
+        if (!isValidDataSize(length, bufLength, 5)) {
+          return null
+        }
+        result = buf.toString('utf8', offset + 5, offset + 5 + length)
+        return buildDecodeResult(result, 5 + length)
+      case 0xc4:
+        // buffers up to 2^8 - 1 bytes
+        length = buf.readUInt8(offset + 1)
+        if (!isValidDataSize(length, bufLength, 2)) {
+          return null
+        }
+        result = buf.slice(offset + 2, offset + 2 + length)
+        return buildDecodeResult(result, 2 + length)
+      case 0xc5:
+        // buffers up to 2^16 - 1 bytes
+        length = buf.readUInt16BE(offset + 1)
+        if (!isValidDataSize(length, bufLength, 3)) {
+          return null
+        }
+        result = buf.slice(offset + 3, offset + 3 + length)
+        return buildDecodeResult(result, 3 + length)
+      case 0xc6:
+        // buffers up to 2^32 - 1 bytes
+        length = buf.readUInt32BE(offset + 1)
+        if (!isValidDataSize(length, bufLength, 5)) {
+          return null
+        }
+        result = buf.slice(offset + 5, offset + 5 + length)
+        return buildDecodeResult(result, 5 + length)
+      case 0xdc:
+        // array up to 2^16 elements - 2 bytes
+        if (bufLength < 3) {
+          return null
+        }
+
+        length = buf.readUInt16BE(offset + 1)
+        return decodeArray(buf, offset, length, 3)
+      case 0xdd:
+        // array up to 2^32 elements - 4 bytes
+        if (bufLength < 5) {
+          return null
+        }
+
+        length = buf.readUInt32BE(offset + 1)
+        return decodeArray(buf, offset, length, 5)
+      case 0xde:
+        // maps up to 2^16 elements - 2 bytes
+        length = buf.readUInt16BE(offset + 1)
+        return decodeMap(buf, offset, length, 3)
+      case 0xdf:
+        length = buf.readUInt32BE(offset + 1)
+        return decodeMap(buf, offset, length, 5)
+      case 0xd4:
+        return decodeFixExt(buf, offset, 1)
+      case 0xd5:
+        return decodeFixExt(buf, offset, 2)
+      case 0xd6:
+        return decodeFixExt(buf, offset, 4)
+      case 0xd7:
+        return decodeFixExt(buf, offset, 8)
+      case 0xd8:
+        return decodeFixExt(buf, offset, 16)
+      case 0xc7:
+        // ext up to 2^8 - 1 bytes
+        length = buf.readUInt8(offset + 1)
+        type = buf.readUInt8(offset + 2)
+        if (!isValidDataSize(length, bufLength, 3)) {
+          return null
+        }
+        return decodeExt(buf, offset, type, length, 3)
+      case 0xc8:
+        // ext up to 2^16 - 1 bytes
+        length = buf.readUInt16BE(offset + 1)
+        type = buf.readUInt8(offset + 3)
+        if (!isValidDataSize(length, bufLength, 4)) {
+          return null
+        }
+        return decodeExt(buf, offset, type, length, 4)
+      case 0xc9:
+        // ext up to 2^32 - 1 bytes
+        length = buf.readUInt32BE(offset + 1)
+        type = buf.readUInt8(offset + 5)
+        if (!isValidDataSize(length, bufLength, 6)) {
+          return null
+        }
+        return decodeExt(buf, offset, type, length, 6)
+    }
+
+    if ((first & 0xf0) === 0x90) {
+      // we have an array with less than 15 elements
+      length = first & 0x0f
+      return decodeArray(buf, offset, length, 1)
+    } else if ((first & 0xf0) === 0x80) {
+      // we have a map with less than 15 elements
+      length = first & 0x0f
+      return decodeMap(buf, offset, length, 1)
+    } else if ((first & 0xe0) === 0xa0) {
+      // fixstr up to 31 bytes
+      length = first & 0x1f
+      if (isValidDataSize(length, bufLength, 1)) {
+        result = buf.toString('utf8', offset + 1, offset + length + 1)
+        return buildDecodeResult(result, length + 1)
+      } else {
+        return null
+      }
+    } else if (first >= 0xe0) {
+      // 5 bits negative ints
+      result = first - 0x100
+      return buildDecodeResult(result, 1)
+    } else if (first < 0x80) {
+      // 7-bits positive ints
+      return buildDecodeResult(first, 1)
+    } else {
+      throw new Error('not implemented yet')
+    }
+  }
+
+  function readInt64BE (buf, offset) {
+    var negate = (buf[offset] & 0x80) == 0x80 // eslint-disable-line
+
+    if (negate) {
+      var carry = 1
+      for (var i = offset + 7; i >= offset; i--) {
+        var v = (buf[i] ^ 0xff) + carry
+        buf[i] = v & 0xff
+        carry = v >> 8
+      }
+    }
+
+    var hi = buf.readUInt32BE(offset + 0)
+    var lo = buf.readUInt32BE(offset + 4)
+    return (hi * 4294967296 + lo) * (negate ? -1 : +1)
+  }
+
+  function decodeArray (buf, offset, length, headerLength) {
+    var result = []
+    var i
+    var totalBytesConsumed = 0
+
+    offset += headerLength
+    for (i = 0; i < length; i++) {
+      var decodeResult = tryDecode(buf, offset)
+      if (decodeResult) {
+        result.push(decodeResult.value)
+        offset += decodeResult.bytesConsumed
+        totalBytesConsumed += decodeResult.bytesConsumed
+      } else {
+        return null
+      }
+    }
+    return buildDecodeResult(result, headerLength + totalBytesConsumed)
+  }
+
+  function decodeMap (buf, offset, length, headerLength) {
+    var result = {}
+    var key
+    var i
+    var totalBytesConsumed = 0
+
+    offset += headerLength
+    for (i = 0; i < length; i++) {
+      var keyResult = tryDecode(buf, offset)
+      if (keyResult) {
+        offset += keyResult.bytesConsumed
+        var valueResult = tryDecode(buf, offset)
+        if (valueResult) {
+          key = keyResult.value
+          result[key] = valueResult.value
+          offset += valueResult.bytesConsumed
+          totalBytesConsumed += (keyResult.bytesConsumed + valueResult.bytesConsumed)
+        } else {
+          return null
+        }
+      } else {
+        return null
+      }
+    }
+    return buildDecodeResult(result, headerLength + totalBytesConsumed)
+  }
+
+  function decodeFixExt (buf, offset, size) {
+    var type = buf.readInt8(offset + 1) // Signed
+    return decodeExt(buf, offset, type, size, 2)
+  }
+
+  function decodeTimestamp (buf, size, headerSize) {
+    var seconds
+    var nanoseconds = 0
+
+    switch (size) {
+      case 4:
+        // timestamp 32 stores the number of seconds that have elapsed since 1970-01-01 00:00:00 UTC in an 32-bit unsigned integer
+        seconds = buf.readUInt32BE(0)
+        break
+
+      case 8:
+        // Timestamp 64 stores the number of seconds and nanoseconds that have elapsed
+        // since 1970-01-01 00:00:00 UTC in 32-bit unsigned integers, split 30/34 bits
+        var upper = buf.readUInt32BE(0)
+        var lower = buf.readUInt32BE(4)
+        nanoseconds = upper / 4
+        seconds = ((upper & 0x03) * Math.pow(2, 32)) + lower // If we use bitwise operators, we get truncated to 32bits
+        break
+
+      case 12:
+        throw new Error('timestamp 96 is not yet implemented')
+    }
+
+    var millis = (seconds * 1000) + Math.round(nanoseconds / 1E6)
+    return buildDecodeResult(new Date(millis), size + headerSize)
+  }
+
+  function decodeExt (buf, offset, type, size, headerSize) {
+    var i,
+      toDecode
+
+    offset += headerSize
+
+    // Pre-defined
+    if (type < 0) { // Reserved for future extensions
+      switch (type) {
+        case -1: // Tiemstamp https://github.com/msgpack/msgpack/blob/master/spec.md#timestamp-extension-type
+          toDecode = buf.slice(offset, offset + size)
+          return decodeTimestamp(toDecode, size, headerSize)
+      }
+    }
+
+    for (i = 0; i < decodingTypes.length; i++) {
+      if (type === decodingTypes[i].type) {
+        toDecode = buf.slice(offset, offset + size)
+        var value = decodingTypes[i].decode(toDecode)
+        return buildDecodeResult(value, headerSize + size)
+      }
+    }
+
+    throw new Error('unable to find ext type ' + type)
+  }
+}
+
+module.exports.IncompleteBufferError = IncompleteBufferError
+
+},{"bl":10,"util":53}],16:[function(require,module,exports){
+'use strict'
+
+var Buffer = require('safe-buffer').Buffer
+var bl = require('bl')
+
+module.exports = function buildEncode (encodingTypes, forceFloat64, compatibilityMode, disableTimestampEncoding) {
+  function encode (obj, avoidSlice) {
+    var buf
+    var len
+
+    if (obj === undefined) {
+      throw new Error('undefined is not encodable in msgpack!')
+    } else if (isNaN(obj)) {
+      throw new Error('NaN is not encodable in msgpack!')
+    } else if (obj === null) {
+      buf = Buffer.allocUnsafe(1)
+      buf[0] = 0xc0
+    } else if (obj === true) {
+      buf = Buffer.allocUnsafe(1)
+      buf[0] = 0xc3
+    } else if (obj === false) {
+      buf = Buffer.allocUnsafe(1)
+      buf[0] = 0xc2
+    } else if (typeof obj === 'string') {
+      len = Buffer.byteLength(obj)
+      if (len < 32) {
+        buf = Buffer.allocUnsafe(1 + len)
+        buf[0] = 0xa0 | len
+        if (len > 0) {
+          buf.write(obj, 1)
+        }
+      } else if (len <= 0xff && !compatibilityMode) {
+        // str8, but only when not in compatibility mode
+        buf = Buffer.allocUnsafe(2 + len)
+        buf[0] = 0xd9
+        buf[1] = len
+        buf.write(obj, 2)
+      } else if (len <= 0xffff) {
+        buf = Buffer.allocUnsafe(3 + len)
+        buf[0] = 0xda
+        buf.writeUInt16BE(len, 1)
+        buf.write(obj, 3)
+      } else {
+        buf = Buffer.allocUnsafe(5 + len)
+        buf[0] = 0xdb
+        buf.writeUInt32BE(len, 1)
+        buf.write(obj, 5)
+      }
+    } else if (obj && (obj.readUInt32LE || obj instanceof Uint8Array)) {
+      if (obj instanceof Uint8Array) {
+        obj = Buffer.from(obj)
+      }
+      // weird hack to support Buffer
+      // and Buffer-like objects
+      if (obj.length <= 0xff) {
+        buf = Buffer.allocUnsafe(2)
+        buf[0] = 0xc4
+        buf[1] = obj.length
+      } else if (obj.length <= 0xffff) {
+        buf = Buffer.allocUnsafe(3)
+        buf[0] = 0xc5
+        buf.writeUInt16BE(obj.length, 1)
+      } else {
+        buf = Buffer.allocUnsafe(5)
+        buf[0] = 0xc6
+        buf.writeUInt32BE(obj.length, 1)
+      }
+
+      buf = bl([buf, obj])
+    } else if (Array.isArray(obj)) {
+      if (obj.length < 16) {
+        buf = Buffer.allocUnsafe(1)
+        buf[0] = 0x90 | obj.length
+      } else if (obj.length < 65536) {
+        buf = Buffer.allocUnsafe(3)
+        buf[0] = 0xdc
+        buf.writeUInt16BE(obj.length, 1)
+      } else {
+        buf = Buffer.allocUnsafe(5)
+        buf[0] = 0xdd
+        buf.writeUInt32BE(obj.length, 1)
+      }
+
+      buf = obj.reduce(function (acc, obj) {
+        acc.append(encode(obj, true))
+        return acc
+      }, bl().append(buf))
+    } else if (!disableTimestampEncoding && typeof obj.getDate === 'function') {
+      return encodeDate(obj)
+    } else if (typeof obj === 'object') {
+      buf = encodeExt(obj) || encodeObject(obj)
+    } else if (typeof obj === 'number') {
+      if (isFloat(obj)) {
+        return encodeFloat(obj, forceFloat64)
+      } else if (obj >= 0) {
+        if (obj < 128) {
+          buf = Buffer.allocUnsafe(1)
+          buf[0] = obj
+        } else if (obj < 256) {
+          buf = Buffer.allocUnsafe(2)
+          buf[0] = 0xcc
+          buf[1] = obj
+        } else if (obj < 65536) {
+          buf = Buffer.allocUnsafe(3)
+          buf[0] = 0xcd
+          buf.writeUInt16BE(obj, 1)
+        } else if (obj <= 0xffffffff) {
+          buf = Buffer.allocUnsafe(5)
+          buf[0] = 0xce
+          buf.writeUInt32BE(obj, 1)
+        } else if (obj <= 9007199254740991) {
+          buf = Buffer.allocUnsafe(9)
+          buf[0] = 0xcf
+          write64BitUint(buf, obj)
+        } else {
+          return encodeFloat(obj, true)
+        }
+      } else {
+        if (obj >= -32) {
+          buf = Buffer.allocUnsafe(1)
+          buf[0] = 0x100 + obj
+        } else if (obj >= -128) {
+          buf = Buffer.allocUnsafe(2)
+          buf[0] = 0xd0
+          buf.writeInt8(obj, 1)
+        } else if (obj >= -32768) {
+          buf = Buffer.allocUnsafe(3)
+          buf[0] = 0xd1
+          buf.writeInt16BE(obj, 1)
+        } else if (obj > -214748365) {
+          buf = Buffer.allocUnsafe(5)
+          buf[0] = 0xd2
+          buf.writeInt32BE(obj, 1)
+        } else if (obj >= -9007199254740991) {
+          buf = Buffer.allocUnsafe(9)
+          buf[0] = 0xd3
+          write64BitInt(buf, 1, obj)
+        } else {
+          return encodeFloat(obj, true)
+        }
+      }
+    }
+
+    if (!buf) {
+      throw new Error('not implemented yet')
+    }
+
+    if (avoidSlice) {
+      return buf
+    } else {
+      return buf.slice()
+    }
+  }
+
+  function encodeDate (dt) {
+    var encoded
+    var millis = dt * 1
+    var seconds = Math.floor(millis / 1000)
+    var nanos = (millis - (seconds * 1000)) * 1E6
+
+    if (nanos || seconds > 0xFFFFFFFF) {
+      // Timestamp64
+      encoded = Buffer.allocUnsafe(10)
+      encoded[0] = 0xd7
+      encoded[1] = -1
+
+      var upperNanos = ((nanos * 4))
+      var upperSeconds = seconds / Math.pow(2, 32)
+      var upper = (upperNanos + upperSeconds) & 0xFFFFFFFF
+      var lower = seconds & 0xFFFFFFFF
+
+      encoded.writeInt32BE(upper, 2)
+      encoded.writeInt32BE(lower, 6)
+    } else {
+      // Timestamp32
+      encoded = Buffer.allocUnsafe(6)
+      encoded[0] = 0xd6
+      encoded[1] = -1
+      encoded.writeUInt32BE(Math.floor(millis / 1000), 2)
+    }
+    return bl().append(encoded)
+  }
+
+  function encodeExt (obj) {
+    var i
+    var encoded
+    var length = -1
+    var headers = []
+
+    for (i = 0; i < encodingTypes.length; i++) {
+      if (encodingTypes[i].check(obj)) {
+        encoded = encodingTypes[i].encode(obj)
+        break
+      }
+    }
+
+    if (!encoded) {
+      return null
+    }
+
+    // we subtract 1 because the length does not
+    // include the type
+    length = encoded.length - 1
+
+    if (length === 1) {
+      headers.push(0xd4)
+    } else if (length === 2) {
+      headers.push(0xd5)
+    } else if (length === 4) {
+      headers.push(0xd6)
+    } else if (length === 8) {
+      headers.push(0xd7)
+    } else if (length === 16) {
+      headers.push(0xd8)
+    } else if (length < 256) {
+      headers.push(0xc7)
+      headers.push(length)
+    } else if (length < 0x10000) {
+      headers.push(0xc8)
+      headers.push(length >> 8)
+      headers.push(length & 0x00ff)
+    } else {
+      headers.push(0xc9)
+      headers.push(length >> 24)
+      headers.push((length >> 16) & 0x000000ff)
+      headers.push((length >> 8) & 0x000000ff)
+      headers.push(length & 0x000000ff)
+    }
+
+    return bl().append(Buffer.from(headers)).append(encoded)
+  }
+
+  function encodeObject (obj) {
+    var acc = []
+    var length = 0
+    var key
+    var header
+
+    for (key in obj) {
+      if (obj.hasOwnProperty(key) &&
+        obj[key] !== undefined &&
+        typeof obj[key] !== 'function') {
+        ++length
+        acc.push(encode(key, true))
+        acc.push(encode(obj[key], true))
+      }
+    }
+
+    if (length < 16) {
+      header = Buffer.allocUnsafe(1)
+      header[0] = 0x80 | length
+    } else if (length < 0xFFFF) {
+      header = Buffer.allocUnsafe(3)
+      header[0] = 0xde
+      header.writeUInt16BE(length, 1)
+    } else {
+      header = Buffer.allocUnsafe(5)
+      header[0] = 0xdf
+      header.writeUInt32BE(length, 1)
+    }
+
+    acc.unshift(header)
+
+    var result = acc.reduce(function (list, buf) {
+      return list.append(buf)
+    }, bl())
+
+    return result
+  }
+
+  return encode
+}
+
+function write64BitUint (buf, obj) {
+  // Write long byte by byte, in big-endian order
+  for (var currByte = 7; currByte >= 0; currByte--) {
+    buf[currByte + 1] = (obj & 0xff)
+    obj = obj / 256
+  }
+}
+
+function write64BitInt (buf, offset, num) {
+  var negate = num < 0
+
+  if (negate) {
+    num = Math.abs(num)
+  }
+
+  var lo = num % 4294967296
+  var hi = num / 4294967296
+  buf.writeUInt32BE(Math.floor(hi), offset + 0)
+  buf.writeUInt32BE(lo, offset + 4)
+
+  if (negate) {
+    var carry = 1
+    for (var i = offset + 7; i >= offset; i--) {
+      var v = (buf[i] ^ 0xff) + carry
+      buf[i] = v & 0xff
+      carry = v >> 8
+    }
+  }
+}
+
+function isFloat (n) {
+  return n % 1 !== 0
+}
+
+function isNaN (n) {
+  /* eslint-disable no-self-compare */
+  return n !== n && typeof n === 'number'
+  /* eslint-enable no-self-compare */
+}
+
+function encodeFloat (obj, forceFloat64) {
+  var useDoublePrecision = true
+
+  // If `fround` is supported, we can check if a float
+  // is double or single precision by rounding the object
+  // to single precision and comparing the difference.
+  // If it's not supported, it's safer to use a 64 bit
+  // float so we don't lose precision without meaning to.
+  if (Math.fround) {
+    useDoublePrecision = Math.fround(obj) !== obj
+  }
+
+  if (forceFloat64) {
+    useDoublePrecision = true
+  }
+
+  var buf
+
+  if (useDoublePrecision) {
+    buf = Buffer.allocUnsafe(9)
+    buf[0] = 0xcb
+    buf.writeDoubleBE(obj, 1)
+  } else {
+    buf = Buffer.allocUnsafe(5)
+    buf[0] = 0xca
+    buf.writeFloatBE(obj, 1)
+  }
+
+  return buf
+}
+
+},{"bl":10,"safe-buffer":29}],17:[function(require,module,exports){
+'use strict'
+
+var Transform = require('readable-stream').Transform
+var inherits = require('inherits')
+var bl = require('bl')
+
+function Base (opts) {
+  opts = opts || {}
+
+  opts.objectMode = true
+  opts.highWaterMark = 16
+
+  Transform.call(this, opts)
+
+  this._msgpack = opts.msgpack
+}
+
+inherits(Base, Transform)
+
+function Encoder (opts) {
+  if (!(this instanceof Encoder)) {
+    opts = opts || {}
+    opts.msgpack = this
+    return new Encoder(opts)
+  }
+
+  Base.call(this, opts)
+  this._wrap = ('wrap' in opts) && opts.wrap
+}
+
+inherits(Encoder, Base)
+
+Encoder.prototype._transform = function (obj, enc, done) {
+  var buf = null
+
+  try {
+    buf = this._msgpack.encode(this._wrap ? obj.value : obj).slice(0)
+  } catch (err) {
+    this.emit('error', err)
+    return done()
+  }
+
+  this.push(buf)
+  done()
+}
+
+function Decoder (opts) {
+  if (!(this instanceof Decoder)) {
+    opts = opts || {}
+    opts.msgpack = this
+    return new Decoder(opts)
+  }
+
+  Base.call(this, opts)
+
+  this._chunks = bl()
+  this._wrap = ('wrap' in opts) && opts.wrap
+}
+
+inherits(Decoder, Base)
+
+Decoder.prototype._transform = function (buf, enc, done) {
+  if (buf) {
+    this._chunks.append(buf)
+  }
+
+  try {
+    var result = this._msgpack.decode(this._chunks)
+    if (this._wrap) {
+      result = {value: result}
+    }
+    this.push(result)
+  } catch (err) {
+    if (err instanceof this._msgpack.IncompleteBufferError) {
+      done()
+    } else {
+      this.emit('error', err)
+    }
+    return
+  }
+
+  if (this._chunks.length > 0) {
+    this._transform(null, enc, done)
+  } else {
+    done()
+  }
+}
+
+module.exports.decoder = Decoder
+module.exports.encoder = Encoder
+
+},{"bl":10,"inherits":12,"readable-stream":28}],18:[function(require,module,exports){
+(function (process){
+'use strict';
+
+if (typeof process === 'undefined' ||
+    !process.version ||
+    process.version.indexOf('v0.') === 0 ||
+    process.version.indexOf('v1.') === 0 && process.version.indexOf('v1.8.') !== 0) {
+  module.exports = { nextTick: nextTick };
+} else {
+  module.exports = process
+}
+
+function nextTick(fn, arg1, arg2, arg3) {
+  if (typeof fn !== 'function') {
+    throw new TypeError('"callback" argument must be a function');
+  }
+  var len = arguments.length;
+  var args, i;
+  switch (len) {
+  case 0:
+  case 1:
+    return process.nextTick(fn);
+  case 2:
+    return process.nextTick(function afterTickOne() {
+      fn.call(null, arg1);
+    });
+  case 3:
+    return process.nextTick(function afterTickTwo() {
+      fn.call(null, arg1, arg2);
+    });
+  case 4:
+    return process.nextTick(function afterTickThree() {
+      fn.call(null, arg1, arg2, arg3);
+    });
+  default:
+    args = new Array(len - 1);
+    i = 0;
+    while (i < args.length) {
+      args[i++] = arguments[i];
+    }
+    return process.nextTick(function afterTick() {
+      fn.apply(null, args);
+    });
+  }
+}
+
+
+}).call(this,require('_process'))
+},{"_process":49}],19:[function(require,module,exports){
+// Copyright Joyent, Inc. and other Node contributors.
+//
+// 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.
+
+// a duplex stream is just a stream that is both readable and writable.
+// Since JS doesn't have multiple prototypal inheritance, this class
+// prototypally inherits from Readable, and then parasitically from
+// Writable.
+
+'use strict';
+
+/*<replacement>*/
+
+var pna = require('process-nextick-args');
+/*</replacement>*/
+
+/*<replacement>*/
+var objectKeys = Object.keys || function (obj) {
+  var keys = [];
+  for (var key in obj) {
+    keys.push(key);
+  }return keys;
+};
+/*</replacement>*/
+
+module.exports = Duplex;
+
+/*<replacement>*/
+var util = require('core-util-is');
+util.inherits = require('inherits');
+/*</replacement>*/
+
+var Readable = require('./_stream_readable');
+var Writable = require('./_stream_writable');
+
+util.inherits(Duplex, Readable);
+
+{
+  // avoid scope creep, the keys array can then be collected
+  var keys = objectKeys(Writable.prototype);
+  for (var v = 0; v < keys.length; v++) {
+    var method = keys[v];
+    if (!Duplex.prototype[method]) Duplex.prototype[method] = Writable.prototype[method];
+  }
+}
+
+function Duplex(options) {
+  if (!(this instanceof Duplex)) return new Duplex(options);
+
+  Readable.call(this, options);
+  Writable.call(this, options);
+
+  if (options && options.readable === false) this.readable = false;
+
+  if (options && options.writable === false) this.writable = false;
+
+  this.allowHalfOpen = true;
+  if (options && options.allowHalfOpen === false) this.allowHalfOpen = false;
+
+  this.once('end', onend);
+}
+
+Object.defineProperty(Duplex.prototype, 'writableHighWaterMark', {
+  // making it explicit this property is not enumerable
+  // because otherwise some prototype manipulation in
+  // userland will fail
+  enumerable: false,
+  get: function () {
+    return this._writableState.highWaterMark;
+  }
+});
+
+// the no-half-open enforcer
+function onend() {
+  // if we allow half-open state, or if the writable side ended,
+  // then we're ok.
+  if (this.allowHalfOpen || this._writableState.ended) return;
+
+  // no more data can be written.
+  // But allow more writes to happen in this tick.
+  pna.nextTick(onEndNT, this);
+}
+
+function onEndNT(self) {
+  self.end();
+}
+
+Object.defineProperty(Duplex.prototype, 'destroyed', {
+  get: function () {
+    if (this._readableState === undefined || this._writableState === undefined) {
+      return false;
+    }
+    return this._readableState.destroyed && this._writableState.destroyed;
+  },
+  set: function (value) {
+    // we ignore the value if the stream
+    // has not been initialized yet
+    if (this._readableState === undefined || this._writableState === undefined) {
+      return;
+    }
+
+    // backward compatibility, the user is explicitly
+    // managing destroyed
+    this._readableState.destroyed = value;
+    this._writableState.destroyed = value;
+  }
+});
+
+Duplex.prototype._destroy = function (err, cb) {
+  this.push(null);
+  this.end();
+
+  pna.nextTick(cb, err);
+};
+},{"./_stream_readable":21,"./_stream_writable":23,"core-util-is":11,"inherits":12,"process-nextick-args":18}],20:[function(require,module,exports){
+// Copyright Joyent, Inc. and other Node contributors.
+//
+// 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.
+
+// a passthrough stream.
+// basically just the most minimal sort of Transform stream.
+// Every written chunk gets output as-is.
+
+'use strict';
+
+module.exports = PassThrough;
+
+var Transform = require('./_stream_transform');
+
+/*<replacement>*/
+var util = require('core-util-is');
+util.inherits = require('inherits');
+/*</replacement>*/
+
+util.inherits(PassThrough, Transform);
+
+function PassThrough(options) {
+  if (!(this instanceof PassThrough)) return new PassThrough(options);
+
+  Transform.call(this, options);
+}
+
+PassThrough.prototype._transform = function (chunk, encoding, cb) {
+  cb(null, chunk);
+};
+},{"./_stream_transform":22,"core-util-is":11,"inherits":12}],21:[function(require,module,exports){
+(function (process,global){
+// Copyright Joyent, Inc. and other Node contributors.
+//
+// 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.
+
+'use strict';
+
+/*<replacement>*/
+
+var pna = require('process-nextick-args');
+/*</replacement>*/
+
+module.exports = Readable;
+
+/*<replacement>*/
+var isArray = require('isarray');
+/*</replacement>*/
+
+/*<replacement>*/
+var Duplex;
+/*</replacement>*/
+
+Readable.ReadableState = ReadableState;
+
+/*<replacement>*/
+var EE = require('events').EventEmitter;
+
+var EElistenerCount = function (emitter, type) {
+  return emitter.listeners(type).length;
+};
+/*</replacement>*/
+
+/*<replacement>*/
+var Stream = require('./internal/streams/stream');
+/*</replacement>*/
+
+/*<replacement>*/
+
+var Buffer = require('safe-buffer').Buffer;
+var OurUint8Array = global.Uint8Array || function () {};
+function _uint8ArrayToBuffer(chunk) {
+  return Buffer.from(chunk);
+}
+function _isUint8Array(obj) {
+  return Buffer.isBuffer(obj) || obj instanceof OurUint8Array;
+}
+
+/*</replacement>*/
+
+/*<replacement>*/
+var util = require('core-util-is');
+util.inherits = require('inherits');
+/*</replacement>*/
+
+/*<replacement>*/
+var debugUtil = require('util');
+var debug = void 0;
+if (debugUtil && debugUtil.debuglog) {
+  debug = debugUtil.debuglog('stream');
+} else {
+  debug = function () {};
+}
+/*</replacement>*/
+
+var BufferList = require('./internal/streams/BufferList');
+var destroyImpl = require('./internal/streams/destroy');
+var StringDecoder;
+
+util.inherits(Readable, Stream);
+
+var kProxyEvents = ['error', 'close', 'destroy', 'pause', 'resume'];
+
+function prependListener(emitter, event, fn) {
+  // Sadly this is not cacheable as some libraries bundle their own
+  // event emitter implementation with them.
+  if (typeof emitter.prependListener === 'function') return emitter.prependListener(event, fn);
+
+  // This is a hack to make sure that our error handler is attached before any
+  // userland ones.  NEVER DO THIS. This is here only because this code needs
+  // to continue to work with older versions of Node.js that do not include
+  // the prependListener() method. The goal is to eventually remove this hack.
+  if (!emitter._events || !emitter._events[event]) emitter.on(event, fn);else if (isArray(emitter._events[event])) emitter._events[event].unshift(fn);else emitter._events[event] = [fn, emitter._events[event]];
+}
+
+function ReadableState(options, stream) {
+  Duplex = Duplex || require('./_stream_duplex');
+
+  options = options || {};
+
+  // Duplex streams are both readable and writable, but share
+  // the same options object.
+  // However, some cases require setting options to different
+  // values for the readable and the writable sides of the duplex stream.
+  // These options can be provided separately as readableXXX and writableXXX.
+  var isDuplex = stream instanceof Duplex;
+
+  // object stream flag. Used to make read(n) ignore n and to
+  // make all the buffer merging and length checks go away
+  this.objectMode = !!options.objectMode;
+
+  if (isDuplex) this.objectMode = this.objectMode || !!options.readableObjectMode;
+
+  // the point at which it stops calling _read() to fill the buffer
+  // Note: 0 is a valid value, means "don't call _read preemptively ever"
+  var hwm = options.highWaterMark;
+  var readableHwm = options.readableHighWaterMark;
+  var defaultHwm = this.objectMode ? 16 : 16 * 1024;
+
+  if (hwm || hwm === 0) this.highWaterMark = hwm;else if (isDuplex && (readableHwm || readableHwm === 0)) this.highWaterMark = readableHwm;else this.highWaterMark = defaultHwm;
+
+  // cast to ints.
+  this.highWaterMark = Math.floor(this.highWaterMark);
+
+  // A linked list is used to store data chunks instead of an array because the
+  // linked list can remove elements from the beginning faster than
+  // array.shift()
+  this.buffer = new BufferList();
+  this.length = 0;
+  this.pipes = null;
+  this.pipesCount = 0;
+  this.flowing = null;
+  this.ended = false;
+  this.endEmitted = false;
+  this.reading = false;
+
+  // a flag to be able to tell if the event 'readable'/'data' is emitted
+  // immediately, or on a later tick.  We set this to true at first, because
+  // any actions that shouldn't happen until "later" should generally also
+  // not happen before the first read call.
+  this.sync = true;
+
+  // whenever we return null, then we set a flag to say
+  // that we're awaiting a 'readable' event emission.
+  this.needReadable = false;
+  this.emittedReadable = false;
+  this.readableListening = false;
+  this.resumeScheduled = false;
+
+  // has it been destroyed
+  this.destroyed = false;
+
+  // Crypto is kind of old and crusty.  Historically, its default string
+  // encoding is 'binary' so we have to make this configurable.
+  // Everything else in the universe uses 'utf8', though.
+  this.defaultEncoding = options.defaultEncoding || 'utf8';
+
+  // the number of writers that are awaiting a drain event in .pipe()s
+  this.awaitDrain = 0;
+
+  // if true, a maybeReadMore has been scheduled
+  this.readingMore = false;
+
+  this.decoder = null;
+  this.encoding = null;
+  if (options.encoding) {
+    if (!StringDecoder) StringDecoder = require('string_decoder/').StringDecoder;
+    this.decoder = new StringDecoder(options.encoding);
+    this.encoding = options.encoding;
+  }
+}
+
+function Readable(options) {
+  Duplex = Duplex || require('./_stream_duplex');
+
+  if (!(this instanceof Readable)) return new Readable(options);
+
+  this._readableState = new ReadableState(options, this);
+
+  // legacy
+  this.readable = true;
+
+  if (options) {
+    if (typeof options.read === 'function') this._read = options.read;
+
+    if (typeof options.destroy === 'function') this._destroy = options.destroy;
+  }
+
+  Stream.call(this);
+}
+
+Object.defineProperty(Readable.prototype, 'destroyed', {
+  get: function () {
+    if (this._readableState === undefined) {
+      return false;
+    }
+    return this._readableState.destroyed;
+  },
+  set: function (value) {
+    // we ignore the value if the stream
+    // has not been initialized yet
+    if (!this._readableState) {
+      return;
+    }
+
+    // backward compatibility, the user is explicitly
+    // managing destroyed
+    this._readableState.destroyed = value;
+  }
+});
+
+Readable.prototype.destroy = destroyImpl.destroy;
+Readable.prototype._undestroy = destroyImpl.undestroy;
+Readable.prototype._destroy = function (err, cb) {
+  this.push(null);
+  cb(err);
+};
+
+// Manually shove something into the read() buffer.
+// This returns true if the highWaterMark has not been hit yet,
+// similar to how Writable.write() returns true if you should
+// write() some more.
+Readable.prototype.push = function (chunk, encoding) {
+  var state = this._readableState;
+  var skipChunkCheck;
+
+  if (!state.objectMode) {
+    if (typeof chunk === 'string') {
+      encoding = encoding || state.defaultEncoding;
+      if (encoding !== state.encoding) {
+        chunk = Buffer.from(chunk, encoding);
+        encoding = '';
+      }
+      skipChunkCheck = true;
+    }
+  } else {
+    skipChunkCheck = true;
+  }
+
+  return readableAddChunk(this, chunk, encoding, false, skipChunkCheck);
+};
+
+// Unshift should *always* be something directly out of read()
+Readable.prototype.unshift = function (chunk) {
+  return readableAddChunk(this, chunk, null, true, false);
+};
+
+function readableAddChunk(stream, chunk, encoding, addToFront, skipChunkCheck) {
+  var state = stream._readableState;
+  if (chunk === null) {
+    state.reading = false;
+    onEofChunk(stream, state);
+  } else {
+    var er;
+    if (!skipChunkCheck) er = chunkInvalid(state, chunk);
+    if (er) {
+      stream.emit('error', er);
+    } else if (state.objectMode || chunk && chunk.length > 0) {
+      if (typeof chunk !== 'string' && !state.objectMode && Object.getPrototypeOf(chunk) !== Buffer.prototype) {
+        chunk = _uint8ArrayToBuffer(chunk);
+      }
+
+      if (addToFront) {
+        if (state.endEmitted) stream.emit('error', new Error('stream.unshift() after end event'));else addChunk(stream, state, chunk, true);
+      } else if (state.ended) {
+        stream.emit('error', new Error('stream.push() after EOF'));
+      } else {
+        state.reading = false;
+        if (state.decoder && !encoding) {
+          chunk = state.decoder.write(chunk);
+          if (state.objectMode || chunk.length !== 0) addChunk(stream, state, chunk, false);else maybeReadMore(stream, state);
+        } else {
+          addChunk(stream, state, chunk, false);
+        }
+      }
+    } else if (!addToFront) {
+      state.reading = false;
+    }
+  }
+
+  return needMoreData(state);
+}
+
+function addChunk(stream, state, chunk, addToFront) {
+  if (state.flowing && state.length === 0 && !state.sync) {
+    stream.emit('data', chunk);
+    stream.read(0);
+  } else {
+    // update the buffer info.
+    state.length += state.objectMode ? 1 : chunk.length;
+    if (addToFront) state.buffer.unshift(chunk);else state.buffer.push(chunk);
+
+    if (state.needReadable) emitReadable(stream);
+  }
+  maybeReadMore(stream, state);
+}
+
+function chunkInvalid(state, chunk) {
+  var er;
+  if (!_isUint8Array(chunk) && typeof chunk !== 'string' && chunk !== undefined && !state.objectMode) {
+    er = new TypeError('Invalid non-string/buffer chunk');
+  }
+  return er;
+}
+
+// if it's past the high water mark, we can push in some more.
+// Also, if we have no data yet, we can stand some
+// more bytes.  This is to work around cases where hwm=0,
+// such as the repl.  Also, if the push() triggered a
+// readable event, and the user called read(largeNumber) such that
+// needReadable was set, then we ought to push more, so that another
+// 'readable' event will be triggered.
+function needMoreData(state) {
+  return !state.ended && (state.needReadable || state.length < state.highWaterMark || state.length === 0);
+}
+
+Readable.prototype.isPaused = function () {
+  return this._readableState.flowing === false;
+};
+
+// backwards compatibility.
+Readable.prototype.setEncoding = function (enc) {
+  if (!StringDecoder) StringDecoder = require('string_decoder/').StringDecoder;
+  this._readableState.decoder = new StringDecoder(enc);
+  this._readableState.encoding = enc;
+  return this;
+};
+
+// Don't raise the hwm > 8MB
+var MAX_HWM = 0x800000;
+function computeNewHighWaterMark(n) {
+  if (n >= MAX_HWM) {
+    n = MAX_HWM;
+  } else {
+    // Get the next highest power of 2 to prevent increasing hwm excessively in
+    // tiny amounts
+    n--;
+    n |= n >>> 1;
+    n |= n >>> 2;
+    n |= n >>> 4;
+    n |= n >>> 8;
+    n |= n >>> 16;
+    n++;
+  }
+  return n;
+}
+
+// This function is designed to be inlinable, so please take care when making
+// changes to the function body.
+function howMuchToRead(n, state) {
+  if (n <= 0 || state.length === 0 && state.ended) return 0;
+  if (state.objectMode) return 1;
+  if (n !== n) {
+    // Only flow one buffer at a time
+    if (state.flowing && state.length) return state.buffer.head.data.length;else return state.length;
+  }
+  // If we're asking for more than the current hwm, then raise the hwm.
+  if (n > state.highWaterMark) state.highWaterMark = computeNewHighWaterMark(n);
+  if (n <= state.length) return n;
+  // Don't have enough
+  if (!state.ended) {
+    state.needReadable = true;
+    return 0;
+  }
+  return state.length;
+}
+
+// you can override either this method, or the async _read(n) below.
+Readable.prototype.read = function (n) {
+  debug('read', n);
+  n = parseInt(n, 10);
+  var state = this._readableState;
+  var nOrig = n;
+
+  if (n !== 0) state.emittedReadable = false;
+
+  // if we're doing read(0) to trigger a readable event, but we
+  // already have a bunch of data in the buffer, then just trigger
+  // the 'readable' event and move on.
+  if (n === 0 && state.needReadable && (state.length >= state.highWaterMark || state.ended)) {
+    debug('read: emitReadable', state.length, state.ended);
+    if (state.length === 0 && state.ended) endReadable(this);else emitReadable(this);
+    return null;
+  }
+
+  n = howMuchToRead(n, state);
+
+  // if we've ended, and we're now clear, then finish it up.
+  if (n === 0 && state.ended) {
+    if (state.length === 0) endReadable(this);
+    return null;
+  }
+
+  // All the actual chunk generation logic needs to be
+  // *below* the call to _read.  The reason is that in certain
+  // synthetic stream cases, such as passthrough streams, _read
+  // may be a completely synchronous operation which may change
+  // the state of the read buffer, providing enough data when
+  // before there was *not* enough.
+  //
+  // So, the steps are:
+  // 1. Figure out what the state of things will be after we do
+  // a read from the buffer.
+  //
+  // 2. If that resulting state will trigger a _read, then call _read.
+  // Note that this may be asynchronous, or synchronous.  Yes, it is
+  // deeply ugly to write APIs this way, but that still doesn't mean
+  // that the Readable class should behave improperly, as streams are
+  // designed to be sync/async agnostic.
+  // Take note if the _read call is sync or async (ie, if the read call
+  // has returned yet), so that we know whether or not it's safe to emit
+  // 'readable' etc.
+  //
+  // 3. Actually pull the requested chunks out of the buffer and return.
+
+  // if we need a readable event, then we need to do some reading.
+  var doRead = state.needReadable;
+  debug('need readable', doRead);
+
+  // if we currently have less than the highWaterMark, then also read some
+  if (state.length === 0 || state.length - n < state.highWaterMark) {
+    doRead = true;
+    debug('length less than watermark', doRead);
+  }
+
+  // however, if we've ended, then there's no point, and if we're already
+  // reading, then it's unnecessary.
+  if (state.ended || state.reading) {
+    doRead = false;
+    debug('reading or ended', doRead);
+  } else if (doRead) {
+    debug('do read');
+    state.reading = true;
+    state.sync = true;
+    // if the length is currently zero, then we *need* a readable event.
+    if (state.length === 0) state.needReadable = true;
+    // call internal read method
+    this._read(state.highWaterMark);
+    state.sync = false;
+    // If _read pushed data synchronously, then `reading` will be false,
+    // and we need to re-evaluate how much data we can return to the user.
+    if (!state.reading) n = howMuchToRead(nOrig, state);
+  }
+
+  var ret;
+  if (n > 0) ret = fromList(n, state);else ret = null;
+
+  if (ret === null) {
+    state.needReadable = true;
+    n = 0;
+  } else {
+    state.length -= n;
+  }
+
+  if (state.length === 0) {
+    // If we have nothing in the buffer, then we want to know
+    // as soon as we *do* get something into the buffer.
+    if (!state.ended) state.needReadable = true;
+
+    // If we tried to read() past the EOF, then emit end on the next tick.
+    if (nOrig !== n && state.ended) endReadable(this);
+  }
+
+  if (ret !== null) this.emit('data', ret);
+
+  return ret;
+};
+
+function onEofChunk(stream, state) {
+  if (state.ended) return;
+  if (state.decoder) {
+    var chunk = state.decoder.end();
+    if (chunk && chunk.length) {
+      state.buffer.push(chunk);
+      state.length += state.objectMode ? 1 : chunk.length;
+    }
+  }
+  state.ended = true;
+
+  // emit 'readable' now to make sure it gets picked up.
+  emitReadable(stream);
+}
+
+// Don't emit readable right away in sync mode, because this can trigger
+// another read() call => stack overflow.  This way, it might trigger
+// a nextTick recursion warning, but that's not so bad.
+function emitReadable(stream) {
+  var state = stream._readableState;
+  state.needReadable = false;
+  if (!state.emittedReadable) {
+    debug('emitReadable', state.flowing);
+    state.emittedReadable = true;
+    if (state.sync) pna.nextTick(emitReadable_, stream);else emitReadable_(stream);
+  }
+}
+
+function emitReadable_(stream) {
+  debug('emit readable');
+  stream.emit('readable');
+  flow(stream);
+}
+
+// at this point, the user has presumably seen the 'readable' event,
+// and called read() to consume some data.  that may have triggered
+// in turn another _read(n) call, in which case reading = true if
+// it's in progress.
+// However, if we're not ended, or reading, and the length < hwm,
+// then go ahead and try to read some more preemptively.
+function maybeReadMore(stream, state) {
+  if (!state.readingMore) {
+    state.readingMore = true;
+    pna.nextTick(maybeReadMore_, stream, state);
+  }
+}
+
+function maybeReadMore_(stream, state) {
+  var len = state.length;
+  while (!state.reading && !state.flowing && !state.ended && state.length < state.highWaterMark) {
+    debug('maybeReadMore read 0');
+    stream.read(0);
+    if (len === state.length)
+      // didn't get any data, stop spinning.
+      break;else len = state.length;
+  }
+  state.readingMore = false;
+}
+
+// abstract method.  to be overridden in specific implementation classes.
+// call cb(er, data) where data is <= n in length.
+// for virtual (non-string, non-buffer) streams, "length" is somewhat
+// arbitrary, and perhaps not very meaningful.
+Readable.prototype._read = function (n) {
+  this.emit('error', new Error('_read() is not implemented'));
+};
+
+Readable.prototype.pipe = function (dest, pipeOpts) {
+  var src = this;
+  var state = this._readableState;
+
+  switch (state.pipesCount) {
+    case 0:
+      state.pipes = dest;
+      break;
+    case 1:
+      state.pipes = [state.pipes, dest];
+      break;
+    default:
+      state.pipes.push(dest);
+      break;
+  }
+  state.pipesCount += 1;
+  debug('pipe count=%d opts=%j', state.pipesCount, pipeOpts);
+
+  var doEnd = (!pipeOpts || pipeOpts.end !== false) && dest !== process.stdout && dest !== process.stderr;
+
+  var endFn = doEnd ? onend : unpipe;
+  if (state.endEmitted) pna.nextTick(endFn);else src.once('end', endFn);
+
+  dest.on('unpipe', onunpipe);
+  function onunpipe(readable, unpipeInfo) {
+    debug('onunpipe');
+    if (readable === src) {
+      if (unpipeInfo && unpipeInfo.hasUnpiped === false) {
+        unpipeInfo.hasUnpiped = true;
+        cleanup();
+      }
+    }
+  }
+
+  function onend() {
+    debug('onend');
+    dest.end();
+  }
+
+  // when the dest drains, it reduces the awaitDrain counter
+  // on the source.  This would be more elegant with a .once()
+  // handler in flow(), but adding and removing repeatedly is
+  // too slow.
+  var ondrain = pipeOnDrain(src);
+  dest.on('drain', ondrain);
+
+  var cleanedUp = false;
+  function cleanup() {
+    debug('cleanup');
+    // cleanup event handlers once the pipe is broken
+    dest.removeListener('close', onclose);
+    dest.removeListener('finish', onfinish);
+    dest.removeListener('drain', ondrain);
+    dest.removeListener('error', onerror);
+    dest.removeListener('unpipe', onunpipe);
+    src.removeListener('end', onend);
+    src.removeListener('end', unpipe);
+    src.removeListener('data', ondata);
+
+    cleanedUp = true;
+
+    // if the reader is waiting for a drain event from this
+    // specific writer, then it would cause it to never start
+    // flowing again.
+    // So, if this is awaiting a drain, then we just call it now.
+    // If we don't know, then assume that we are waiting for one.
+    if (state.awaitDrain && (!dest._writableState || dest._writableState.needDrain)) ondrain();
+  }
+
+  // If the user pushes more data while we're writing to dest then we'll end up
+  // in ondata again. However, we only want to increase awaitDrain once because
+  // dest will only emit one 'drain' event for the multiple writes.
+  // => Introduce a guard on increasing awaitDrain.
+  var increasedAwaitDrain = false;
+  src.on('data', ondata);
+  function ondata(chunk) {
+    debug('ondata');
+    increasedAwaitDrain = false;
+    var ret = dest.write(chunk);
+    if (false === ret && !increasedAwaitDrain) {
+      // If the user unpiped during `dest.write()`, it is possible
+      // to get stuck in a permanently paused state if that write
+      // also returned false.
+      // => Check whether `dest` is still a piping destination.
+      if ((state.pipesCount === 1 && state.pipes === dest || state.pipesCount > 1 && indexOf(state.pipes, dest) !== -1) && !cleanedUp) {
+        debug('false write response, pause', src._readableState.awaitDrain);
+        src._readableState.awaitDrain++;
+        increasedAwaitDrain = true;
+      }
+      src.pause();
+    }
+  }
+
+  // if the dest has an error, then stop piping into it.
+  // however, don't suppress the throwing behavior for this.
+  function onerror(er) {
+    debug('onerror', er);
+    unpipe();
+    dest.removeListener('error', onerror);
+    if (EElistenerCount(dest, 'error') === 0) dest.emit('error', er);
+  }
+
+  // Make sure our error handler is attached before userland ones.
+  prependListener(dest, 'error', onerror);
+
+  // Both close and finish should trigger unpipe, but only once.
+  function onclose() {
+    dest.removeListener('finish', onfinish);
+    unpipe();
+  }
+  dest.once('close', onclose);
+  function onfinish() {
+    debug('onfinish');
+    dest.removeListener('close', onclose);
+    unpipe();
+  }
+  dest.once('finish', onfinish);
+
+  function unpipe() {
+    debug('unpipe');
+    src.unpipe(dest);
+  }
+
+  // tell the dest that it's being piped to
+  dest.emit('pipe', src);
+
+  // start the flow if it hasn't been started already.
+  if (!state.flowing) {
+    debug('pipe resume');
+    src.resume();
+  }
+
+  return dest;
+};
+
+function pipeOnDrain(src) {
+  return function () {
+    var state = src._readableState;
+    debug('pipeOnDrain', state.awaitDrain);
+    if (state.awaitDrain) state.awaitDrain--;
+    if (state.awaitDrain === 0 && EElistenerCount(src, 'data')) {
+      state.flowing = true;
+      flow(src);
+    }
+  };
+}
+
+Readable.prototype.unpipe = function (dest) {
+  var state = this._readableState;
+  var unpipeInfo = { hasUnpiped: false };
+
+  // if we're not piping anywhere, then do nothing.
+  if (state.pipesCount === 0) return this;
+
+  // just one destination.  most common case.
+  if (state.pipesCount === 1) {
+    // passed in one, but it's not the right one.
+    if (dest && dest !== state.pipes) return this;
+
+    if (!dest) dest = state.pipes;
+
+    // got a match.
+    state.pipes = null;
+    state.pipesCount = 0;
+    state.flowing = false;
+    if (dest) dest.emit('unpipe', this, unpipeInfo);
+    return this;
+  }
+
+  // slow case. multiple pipe destinations.
+
+  if (!dest) {
+    // remove all.
+    var dests = state.pipes;
+    var len = state.pipesCount;
+    state.pipes = null;
+    state.pipesCount = 0;
+    state.flowing = false;
+
+    for (var i = 0; i < len; i++) {
+      dests[i].emit('unpipe', this, unpipeInfo);
+    }return this;
+  }
+
+  // try to find the right one.
+  var index = indexOf(state.pipes, dest);
+  if (index === -1) return this;
+
+  state.pipes.splice(index, 1);
+  state.pipesCount -= 1;
+  if (state.pipesCount === 1) state.pipes = state.pipes[0];
+
+  dest.emit('unpipe', this, unpipeInfo);
+
+  return this;
+};
+
+// set up data events if they are asked for
+// Ensure readable listeners eventually get something
+Readable.prototype.on = function (ev, fn) {
+  var res = Stream.prototype.on.call(this, ev, fn);
+
+  if (ev === 'data') {
+    // Start flowing on next tick if stream isn't explicitly paused
+    if (this._readableState.flowing !== false) this.resume();
+  } else if (ev === 'readable') {
+    var state = this._readableState;
+    if (!state.endEmitted && !state.readableListening) {
+      state.readableListening = state.needReadable = true;
+      state.emittedReadable = false;
+      if (!state.reading) {
+        pna.nextTick(nReadingNextTick, this);
+      } else if (state.length) {
+        emitReadable(this);
+      }
+    }
+  }
+
+  return res;
+};
+Readable.prototype.addListener = Readable.prototype.on;
+
+function nReadingNextTick(self) {
+  debug('readable nexttick read 0');
+  self.read(0);
+}
+
+// pause() and resume() are remnants of the legacy readable stream API
+// If the user uses them, then switch into old mode.
+Readable.prototype.resume = function () {
+  var state = this._readableState;
+  if (!state.flowing) {
+    debug('resume');
+    state.flowing = true;
+    resume(this, state);
+  }
+  return this;
+};
+
+function resume(stream, state) {
+  if (!state.resumeScheduled) {
+    state.resumeScheduled = true;
+    pna.nextTick(resume_, stream, state);
+  }
+}
+
+function resume_(stream, state) {
+  if (!state.reading) {
+    debug('resume read 0');
+    stream.read(0);
+  }
+
+  state.resumeScheduled = false;
+  state.awaitDrain = 0;
+  stream.emit('resume');
+  flow(stream);
+  if (state.flowing && !state.reading) stream.read(0);
+}
+
+Readable.prototype.pause = function () {
+  debug('call pause flowing=%j', this._readableState.flowing);
+  if (false !== this._readableState.flowing) {
+    debug('pause');
+    this._readableState.flowing = false;
+    this.emit('pause');
+  }
+  return this;
+};
+
+function flow(stream) {
+  var state = stream._readableState;
+  debug('flow', state.flowing);
+  while (state.flowing && stream.read() !== null) {}
+}
+
+// wrap an old-style stream as the async data source.
+// This is *not* part of the readable stream interface.
+// It is an ugly unfortunate mess of history.
+Readable.prototype.wrap = function (stream) {
+  var _this = this;
+
+  var state = this._readableState;
+  var paused = false;
+
+  stream.on('end', function () {
+    debug('wrapped end');
+    if (state.decoder && !state.ended) {
+      var chunk = state.decoder.end();
+      if (chunk && chunk.length) _this.push(chunk);
+    }
+
+    _this.push(null);
+  });
+
+  stream.on('data', function (chunk) {
+    debug('wrapped data');
+    if (state.decoder) chunk = state.decoder.write(chunk);
+
+    // don't skip over falsy values in objectMode
+    if (state.objectMode && (chunk === null || chunk === undefined)) return;else if (!state.objectMode && (!chunk || !chunk.length)) return;
+
+    var ret = _this.push(chunk);
+    if (!ret) {
+      paused = true;
+      stream.pause();
+    }
+  });
+
+  // proxy all the other methods.
+  // important when wrapping filters and duplexes.
+  for (var i in stream) {
+    if (this[i] === undefined && typeof stream[i] === 'function') {
+      this[i] = function (method) {
+        return function () {
+          return stream[method].apply(stream, arguments);
+        };
+      }(i);
+    }
+  }
+
+  // proxy certain important events.
+  for (var n = 0; n < kProxyEvents.length; n++) {
+    stream.on(kProxyEvents[n], this.emit.bind(this, kProxyEvents[n]));
+  }
+
+  // when we try to consume some more bytes, simply unpause the
+  // underlying stream.
+  this._read = function (n) {
+    debug('wrapped _read', n);
+    if (paused) {
+      paused = false;
+      stream.resume();
+    }
+  };
+
+  return this;
+};
+
+Object.defineProperty(Readable.prototype, 'readableHighWaterMark', {
+  // making it explicit this property is not enumerable
+  // because otherwise some prototype manipulation in
+  // userland will fail
+  enumerable: false,
+  get: function () {
+    return this._readableState.highWaterMark;
+  }
+});
+
+// exposed for testing purposes only.
+Readable._fromList = fromList;
+
+// Pluck off n bytes from an array of buffers.
+// Length is the combined lengths of all the buffers in the list.
+// This function is designed to be inlinable, so please take care when making
+// changes to the function body.
+function fromList(n, state) {
+  // nothing buffered
+  if (state.length === 0) return null;
+
+  var ret;
+  if (state.objectMode) ret = state.buffer.shift();else if (!n || n >= state.length) {
+    // read it all, truncate the list
+    if (state.decoder) ret = state.buffer.join('');else if (state.buffer.length === 1) ret = state.buffer.head.data;else ret = state.buffer.concat(state.length);
+    state.buffer.clear();
+  } else {
+    // read part of list
+    ret = fromListPartial(n, state.buffer, state.decoder);
+  }
+
+  return ret;
+}
+
+// Extracts only enough buffered data to satisfy the amount requested.
+// This function is designed to be inlinable, so please take care when making
+// changes to the function body.
+function fromListPartial(n, list, hasStrings) {
+  var ret;
+  if (n < list.head.data.length) {
+    // slice is the same for buffers and strings
+    ret = list.head.data.slice(0, n);
+    list.head.data = list.head.data.slice(n);
+  } else if (n === list.head.data.length) {
+    // first chunk is a perfect match
+    ret = list.shift();
+  } else {
+    // result spans more than one buffer
+    ret = hasStrings ? copyFromBufferString(n, list) : copyFromBuffer(n, list);
+  }
+  return ret;
+}
+
+// Copies a specified amount of characters from the list of buffered data
+// chunks.
+// This function is designed to be inlinable, so please take care when making
+// changes to the function body.
+function copyFromBufferString(n, list) {
+  var p = list.head;
+  var c = 1;
+  var ret = p.data;
+  n -= ret.length;
+  while (p = p.next) {
+    var str = p.data;
+    var nb = n > str.length ? str.length : n;
+    if (nb === str.length) ret += str;else ret += str.slice(0, n);
+    n -= nb;
+    if (n === 0) {
+      if (nb === str.length) {
+        ++c;
+        if (p.next) list.head = p.next;else list.head = list.tail = null;
+      } else {
+        list.head = p;
+        p.data = str.slice(nb);
+      }
+      break;
+    }
+    ++c;
+  }
+  list.length -= c;
+  return ret;
+}
+
+// Copies a specified amount of bytes from the list of buffered data chunks.
+// This function is designed to be inlinable, so please take care when making
+// changes to the function body.
+function copyFromBuffer(n, list) {
+  var ret = Buffer.allocUnsafe(n);
+  var p = list.head;
+  var c = 1;
+  p.data.copy(ret);
+  n -= p.data.length;
+  while (p = p.next) {
+    var buf = p.data;
+    var nb = n > buf.length ? buf.length : n;
+    buf.copy(ret, ret.length - n, 0, nb);
+    n -= nb;
+    if (n === 0) {
+      if (nb === buf.length) {
+        ++c;
+        if (p.next) list.head = p.next;else list.head = list.tail = null;
+      } else {
+        list.head = p;
+        p.data = buf.slice(nb);
+      }
+      break;
+    }
+    ++c;
+  }
+  list.length -= c;
+  return ret;
+}
+
+function endReadable(stream) {
+  var state = stream._readableState;
+
+  // If we get here before consuming all the bytes, then that is a
+  // bug in node.  Should never happen.
+  if (state.length > 0) throw new Error('"endReadable()" called on non-empty stream');
+
+  if (!state.endEmitted) {
+    state.ended = true;
+    pna.nextTick(endReadableNT, state, stream);
+  }
+}
+
+function endReadableNT(state, stream) {
+  // Check that we didn't get one last unshift.
+  if (!state.endEmitted && state.length === 0) {
+    state.endEmitted = true;
+    stream.readable = false;
+    stream.emit('end');
+  }
+}
+
+function indexOf(xs, x) {
+  for (var i = 0, l = xs.length; i < l; i++) {
+    if (xs[i] === x) return i;
+  }
+  return -1;
+}
+}).call(this,require('_process'),typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"./_stream_duplex":19,"./internal/streams/BufferList":24,"./internal/streams/destroy":25,"./internal/streams/stream":26,"_process":49,"core-util-is":11,"events":45,"inherits":12,"isarray":13,"process-nextick-args":18,"safe-buffer":29,"string_decoder/":27,"util":43}],22:[function(require,module,exports){
+// Copyright Joyent, Inc. and other Node contributors.
+//
+// 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.
+
+// a transform stream is a readable/writable stream where you do
+// something with the data.  Sometimes it's called a "filter",
+// but that's not a great name for it, since that implies a thing where
+// some bits pass through, and others are simply ignored.  (That would
+// be a valid example of a transform, of course.)
+//
+// While the output is causally related to the input, it's not a
+// necessarily symmetric or synchronous transformation.  For example,
+// a zlib stream might take multiple plain-text writes(), and then
+// emit a single compressed chunk some time in the future.
+//
+// Here's how this works:
+//
+// The Transform stream has all the aspects of the readable and writable
+// stream classes.  When you write(chunk), that calls _write(chunk,cb)
+// internally, and returns false if there's a lot of pending writes
+// buffered up.  When you call read(), that calls _read(n) until
+// there's enough pending readable data buffered up.
+//
+// In a transform stream, the written data is placed in a buffer.  When
+// _read(n) is called, it transforms the queued up data, calling the
+// buffered _write cb's as it consumes chunks.  If consuming a single
+// written chunk would result in multiple output chunks, then the first
+// outputted bit calls the readcb, and subsequent chunks just go into
+// the read buffer, and will cause it to emit 'readable' if necessary.
+//
+// This way, back-pressure is actually determined by the reading side,
+// since _read has to be called to start processing a new chunk.  However,
+// a pathological inflate type of transform can cause excessive buffering
+// here.  For example, imagine a stream where every byte of input is
+// interpreted as an integer from 0-255, and then results in that many
+// bytes of output.  Writing the 4 bytes {ff,ff,ff,ff} would result in
+// 1kb of data being output.  In this case, you could write a very small
+// amount of input, and end up with a very large amount of output.  In
+// such a pathological inflating mechanism, there'd be no way to tell
+// the system to stop doing the transform.  A single 4MB write could
+// cause the system to run out of memory.
+//
+// However, even in such a pathological case, only a single written chunk
+// would be consumed, and then the rest would wait (un-transformed) until
+// the results of the previous transformed chunk were consumed.
+
+'use strict';
+
+module.exports = Transform;
+
+var Duplex = require('./_stream_duplex');
+
+/*<replacement>*/
+var util = require('core-util-is');
+util.inherits = require('inherits');
+/*</replacement>*/
+
+util.inherits(Transform, Duplex);
+
+function afterTransform(er, data) {
+  var ts = this._transformState;
+  ts.transforming = false;
+
+  var cb = ts.writecb;
+
+  if (!cb) {
+    return this.emit('error', new Error('write callback called multiple times'));
+  }
+
+  ts.writechunk = null;
+  ts.writecb = null;
+
+  if (data != null) // single equals check for both `null` and `undefined`
+    this.push(data);
+
+  cb(er);
+
+  var rs = this._readableState;
+  rs.reading = false;
+  if (rs.needReadable || rs.length < rs.highWaterMark) {
+    this._read(rs.highWaterMark);
+  }
+}
+
+function Transform(options) {
+  if (!(this instanceof Transform)) return new Transform(options);
+
+  Duplex.call(this, options);
+
+  this._transformState = {
+    afterTransform: afterTransform.bind(this),
+    needTransform: false,
+    transforming: false,
+    writecb: null,
+    writechunk: null,
+    writeencoding: null
+  };
+
+  // start out asking for a readable event once data is transformed.
+  this._readableState.needReadable = true;
+
+  // we have implemented the _read method, and done the other things
+  // that Readable wants before the first _read call, so unset the
+  // sync guard flag.
+  this._readableState.sync = false;
+
+  if (options) {
+    if (typeof options.transform === 'function') this._transform = options.transform;
+
+    if (typeof options.flush === 'function') this._flush = options.flush;
+  }
+
+  // When the writable side finishes, then flush out anything remaining.
+  this.on('prefinish', prefinish);
+}
+
+function prefinish() {
+  var _this = this;
+
+  if (typeof this._flush === 'function') {
+    this._flush(function (er, data) {
+      done(_this, er, data);
+    });
+  } else {
+    done(this, null, null);
+  }
+}
+
+Transform.prototype.push = function (chunk, encoding) {
+  this._transformState.needTransform = false;
+  return Duplex.prototype.push.call(this, chunk, encoding);
+};
+
+// This is the part where you do stuff!
+// override this function in implementation classes.
+// 'chunk' is an input chunk.
+//
+// Call `push(newChunk)` to pass along transformed output
+// to the readable side.  You may call 'push' zero or more times.
+//
+// Call `cb(err)` when you are done with this chunk.  If you pass
+// an error, then that'll put the hurt on the whole operation.  If you
+// never call cb(), then you'll never get another chunk.
+Transform.prototype._transform = function (chunk, encoding, cb) {
+  throw new Error('_transform() is not implemented');
+};
+
+Transform.prototype._write = function (chunk, encoding, cb) {
+  var ts = this._transformState;
+  ts.writecb = cb;
+  ts.writechunk = chunk;
+  ts.writeencoding = encoding;
+  if (!ts.transforming) {
+    var rs = this._readableState;
+    if (ts.needTransform || rs.needReadable || rs.length < rs.highWaterMark) this._read(rs.highWaterMark);
+  }
+};
+
+// Doesn't matter what the args are here.
+// _transform does all the work.
+// That we got here means that the readable side wants more data.
+Transform.prototype._read = function (n) {
+  var ts = this._transformState;
+
+  if (ts.writechunk !== null && ts.writecb && !ts.transforming) {
+    ts.transforming = true;
+    this._transform(ts.writechunk, ts.writeencoding, ts.afterTransform);
+  } else {
+    // mark that we need a transform, so that any data that comes in
+    // will get processed, now that we've asked for it.
+    ts.needTransform = true;
+  }
+};
+
+Transform.prototype._destroy = function (err, cb) {
+  var _this2 = this;
+
+  Duplex.prototype._destroy.call(this, err, function (err2) {
+    cb(err2);
+    _this2.emit('close');
+  });
+};
+
+function done(stream, er, data) {
+  if (er) return stream.emit('error', er);
+
+  if (data != null) // single equals check for both `null` and `undefined`
+    stream.push(data);
+
+  // if there's nothing in the write buffer, then that means
+  // that nothing more will ever be provided
+  if (stream._writableState.length) throw new Error('Calling transform done when ws.length != 0');
+
+  if (stream._transformState.transforming) throw new Error('Calling transform done when still transforming');
+
+  return stream.push(null);
+}
+},{"./_stream_duplex":19,"core-util-is":11,"inherits":12}],23:[function(require,module,exports){
+(function (process,global,setImmediate){
+// Copyright Joyent, Inc. and other Node contributors.
+//
+// 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.
+
+// A bit simpler than readable streams.
+// Implement an async ._write(chunk, encoding, cb), and it'll handle all
+// the drain event emission and buffering.
+
+'use strict';
+
+/*<replacement>*/
+
+var pna = require('process-nextick-args');
+/*</replacement>*/
+
+module.exports = Writable;
+
+/* <replacement> */
+function WriteReq(chunk, encoding, cb) {
+  this.chunk = chunk;
+  this.encoding = encoding;
+  this.callback = cb;
+  this.next = null;
+}
+
+// It seems a linked list but it is not
+// there will be only 2 of these for each stream
+function CorkedRequest(state) {
+  var _this = this;
+
+  this.next = null;
+  this.entry = null;
+  this.finish = function () {
+    onCorkedFinish(_this, state);
+  };
+}
+/* </replacement> */
+
+/*<replacement>*/
+var asyncWrite = !process.browser && ['v0.10', 'v0.9.'].indexOf(process.version.slice(0, 5)) > -1 ? setImmediate : pna.nextTick;
+/*</replacement>*/
+
+/*<replacement>*/
+var Duplex;
+/*</replacement>*/
+
+Writable.WritableState = WritableState;
+
+/*<replacement>*/
+var util = require('core-util-is');
+util.inherits = require('inherits');
+/*</replacement>*/
+
+/*<replacement>*/
+var internalUtil = {
+  deprecate: require('util-deprecate')
+};
+/*</replacement>*/
+
+/*<replacement>*/
+var Stream = require('./internal/streams/stream');
+/*</replacement>*/
+
+/*<replacement>*/
+
+var Buffer = require('safe-buffer').Buffer;
+var OurUint8Array = global.Uint8Array || function () {};
+function _uint8ArrayToBuffer(chunk) {
+  return Buffer.from(chunk);
+}
+function _isUint8Array(obj) {
+  return Buffer.isBuffer(obj) || obj instanceof OurUint8Array;
+}
+
+/*</replacement>*/
+
+var destroyImpl = require('./internal/streams/destroy');
+
+util.inherits(Writable, Stream);
+
+function nop() {}
+
+function WritableState(options, stream) {
+  Duplex = Duplex || require('./_stream_duplex');
+
+  options = options || {};
+
+  // Duplex streams are both readable and writable, but share
+  // the same options object.
+  // However, some cases require setting options to different
+  // values for the readable and the writable sides of the duplex stream.
+  // These options can be provided separately as readableXXX and writableXXX.
+  var isDuplex = stream instanceof Duplex;
+
+  // object stream flag to indicate whether or not this stream
+  // contains buffers or objects.
+  this.objectMode = !!options.objectMode;
+
+  if (isDuplex) this.objectMode = this.objectMode || !!options.writableObjectMode;
+
+  // the point at which write() starts returning false
+  // Note: 0 is a valid value, means that we always return false if
+  // the entire buffer is not flushed immediately on write()
+  var hwm = options.highWaterMark;
+  var writableHwm = options.writableHighWaterMark;
+  var defaultHwm = this.objectMode ? 16 : 16 * 1024;
+
+  if (hwm || hwm === 0) this.highWaterMark = hwm;else if (isDuplex && (writableHwm || writableHwm === 0)) this.highWaterMark = writableHwm;else this.highWaterMark = defaultHwm;
+
+  // cast to ints.
+  this.highWaterMark = Math.floor(this.highWaterMark);
+
+  // if _final has been called
+  this.finalCalled = false;
+
+  // drain event flag.
+  this.needDrain = false;
+  // at the start of calling end()
+  this.ending = false;
+  // when end() has been called, and returned
+  this.ended = false;
+  // when 'finish' is emitted
+  this.finished = false;
+
+  // has it been destroyed
+  this.destroyed = false;
+
+  // should we decode strings into buffers before passing to _write?
+  // this is here so that some node-core streams can optimize string
+  // handling at a lower level.
+  var noDecode = options.decodeStrings === false;
+  this.decodeStrings = !noDecode;
+
+  // Crypto is kind of old and crusty.  Historically, its default string
+  // encoding is 'binary' so we have to make this configurable.
+  // Everything else in the universe uses 'utf8', though.
+  this.defaultEncoding = options.defaultEncoding || 'utf8';
+
+  // not an actual buffer we keep track of, but a measurement
+  // of how much we're waiting to get pushed to some underlying
+  // socket or file.
+  this.length = 0;
+
+  // a flag to see when we're in the middle of a write.
+  this.writing = false;
+
+  // when true all writes will be buffered until .uncork() call
+  this.corked = 0;
+
+  // a flag to be able to tell if the onwrite cb is called immediately,
+  // or on a later tick.  We set this to true at first, because any
+  // actions that shouldn't happen until "later" should generally also
+  // not happen before the first write call.
+  this.sync = true;
+
+  // a flag to know if we're processing previously buffered items, which
+  // may call the _write() callback in the same tick, so that we don't
+  // end up in an overlapped onwrite situation.
+  this.bufferProcessing = false;
+
+  // the callback that's passed to _write(chunk,cb)
+  this.onwrite = function (er) {
+    onwrite(stream, er);
+  };
+
+  // the callback that the user supplies to write(chunk,encoding,cb)
+  this.writecb = null;
+
+  // the amount that is being written when _write is called.
+  this.writelen = 0;
+
+  this.bufferedRequest = null;
+  this.lastBufferedRequest = null;
+
+  // number of pending user-supplied write callbacks
+  // this must be 0 before 'finish' can be emitted
+  this.pendingcb = 0;
+
+  // emit prefinish if the only thing we're waiting for is _write cbs
+  // This is relevant for synchronous Transform streams
+  this.prefinished = false;
+
+  // True if the error was already emitted and should not be thrown again
+  this.errorEmitted = false;
+
+  // count buffered requests
+  this.bufferedRequestCount = 0;
+
+  // allocate the first CorkedRequest, there is always
+  // one allocated and free to use, and we maintain at most two
+  this.corkedRequestsFree = new CorkedRequest(this);
+}
+
+WritableState.prototype.getBuffer = function getBuffer() {
+  var current = this.bufferedRequest;
+  var out = [];
+  while (current) {
+    out.push(current);
+    current = current.next;
+  }
+  return out;
+};
+
+(function () {
+  try {
+    Object.defineProperty(WritableState.prototype, 'buffer', {
+      get: internalUtil.deprecate(function () {
+        return this.getBuffer();
+      }, '_writableState.buffer is deprecated. Use _writableState.getBuffer ' + 'instead.', 'DEP0003')
+    });
+  } catch (_) {}
+})();
+
+// Test _writableState for inheritance to account for Duplex streams,
+// whose prototype chain only points to Readable.
+var realHasInstance;
+if (typeof Symbol === 'function' && Symbol.hasInstance && typeof Function.prototype[Symbol.hasInstance] === 'function') {
+  realHasInstance = Function.prototype[Symbol.hasInstance];
+  Object.defineProperty(Writable, Symbol.hasInstance, {
+    value: function (object) {
+      if (realHasInstance.call(this, object)) return true;
+      if (this !== Writable) return false;
+
+      return object && object._writableState instanceof WritableState;
+    }
+  });
+} else {
+  realHasInstance = function (object) {
+    return object instanceof this;
+  };
+}
+
+function Writable(options) {
+  Duplex = Duplex || require('./_stream_duplex');
+
+  // Writable ctor is applied to Duplexes, too.
+  // `realHasInstance` is necessary because using plain `instanceof`
+  // would return false, as no `_writableState` property is attached.
+
+  // Trying to use the custom `instanceof` for Writable here will also break the
+  // Node.js LazyTransform implementation, which has a non-trivial getter for
+  // `_writableState` that would lead to infinite recursion.
+  if (!realHasInstance.call(Writable, this) && !(this instanceof Duplex)) {
+    return new Writable(options);
+  }
+
+  this._writableState = new WritableState(options, this);
+
+  // legacy.
+  this.writable = true;
+
+  if (options) {
+    if (typeof options.write === 'function') this._write = options.write;
+
+    if (typeof options.writev === 'function') this._writev = options.writev;
+
+    if (typeof options.destroy === 'function') this._destroy = options.destroy;
+
+    if (typeof options.final === 'function') this._final = options.final;
+  }
+
+  Stream.call(this);
+}
+
+// Otherwise people can pipe Writable streams, which is just wrong.
+Writable.prototype.pipe = function () {
+  this.emit('error', new Error('Cannot pipe, not readable'));
+};
+
+function writeAfterEnd(stream, cb) {
+  var er = new Error('write after end');
+  // TODO: defer error events consistently everywhere, not just the cb
+  stream.emit('error', er);
+  pna.nextTick(cb, er);
+}
+
+// Checks that a user-supplied chunk is valid, especially for the particular
+// mode the stream is in. Currently this means that `null` is never accepted
+// and undefined/non-string values are only allowed in object mode.
+function validChunk(stream, state, chunk, cb) {
+  var valid = true;
+  var er = false;
+
+  if (chunk === null) {
+    er = new TypeError('May not write null values to stream');
+  } else if (typeof chunk !== 'string' && chunk !== undefined && !state.objectMode) {
+    er = new TypeError('Invalid non-string/buffer chunk');
+  }
+  if (er) {
+    stream.emit('error', er);
+    pna.nextTick(cb, er);
+    valid = false;
+  }
+  return valid;
+}
+
+Writable.prototype.write = function (chunk, encoding, cb) {
+  var state = this._writableState;
+  var ret = false;
+  var isBuf = !state.objectMode && _isUint8Array(chunk);
+
+  if (isBuf && !Buffer.isBuffer(chunk)) {
+    chunk = _uint8ArrayToBuffer(chunk);
+  }
+
+  if (typeof encoding === 'function') {
+    cb = encoding;
+    encoding = null;
+  }
+
+  if (isBuf) encoding = 'buffer';else if (!encoding) encoding = state.defaultEncoding;
+
+  if (typeof cb !== 'function') cb = nop;
+
+  if (state.ended) writeAfterEnd(this, cb);else if (isBuf || validChunk(this, state, chunk, cb)) {
+    state.pendingcb++;
+    ret = writeOrBuffer(this, state, isBuf, chunk, encoding, cb);
+  }
+
+  return ret;
+};
+
+Writable.prototype.cork = function () {
+  var state = this._writableState;
+
+  state.corked++;
+};
+
+Writable.prototype.uncork = function () {
+  var state = this._writableState;
+
+  if (state.corked) {
+    state.corked--;
+
+    if (!state.writing && !state.corked && !state.finished && !state.bufferProcessing && state.bufferedRequest) clearBuffer(this, state);
+  }
+};
+
+Writable.prototype.setDefaultEncoding = function setDefaultEncoding(encoding) {
+  // node::ParseEncoding() requires lower case.
+  if (typeof encoding === 'string') encoding = encoding.toLowerCase();
+  if (!(['hex', 'utf8', 'utf-8', 'ascii', 'binary', 'base64', 'ucs2', 'ucs-2', 'utf16le', 'utf-16le', 'raw'].indexOf((encoding + '').toLowerCase()) > -1)) throw new TypeError('Unknown encoding: ' + encoding);
+  this._writableState.defaultEncoding = encoding;
+  return this;
+};
+
+function decodeChunk(state, chunk, encoding) {
+  if (!state.objectMode && state.decodeStrings !== false && typeof chunk === 'string') {
+    chunk = Buffer.from(chunk, encoding);
+  }
+  return chunk;
+}
+
+Object.defineProperty(Writable.prototype, 'writableHighWaterMark', {
+  // making it explicit this property is not enumerable
+  // because otherwise some prototype manipulation in
+  // userland will fail
+  enumerable: false,
+  get: function () {
+    return this._writableState.highWaterMark;
+  }
+});
+
+// if we're already writing something, then just put this
+// in the queue, and wait our turn.  Otherwise, call _write
+// If we return false, then we need a drain event, so set that flag.
+function writeOrBuffer(stream, state, isBuf, chunk, encoding, cb) {
+  if (!isBuf) {
+    var newChunk = decodeChunk(state, chunk, encoding);
+    if (chunk !== newChunk) {
+      isBuf = true;
+      encoding = 'buffer';
+      chunk = newChunk;
+    }
+  }
+  var len = state.objectMode ? 1 : chunk.length;
+
+  state.length += len;
+
+  var ret = state.length < state.highWaterMark;
+  // we must ensure that previous needDrain will not be reset to false.
+  if (!ret) state.needDrain = true;
+
+  if (state.writing || state.corked) {
+    var last = state.lastBufferedRequest;
+    state.lastBufferedRequest = {
+      chunk: chunk,
+      encoding: encoding,
+      isBuf: isBuf,
+      callback: cb,
+      next: null
+    };
+    if (last) {
+      last.next = state.lastBufferedRequest;
+    } else {
+      state.bufferedRequest = state.lastBufferedRequest;
+    }
+    state.bufferedRequestCount += 1;
+  } else {
+    doWrite(stream, state, false, len, chunk, encoding, cb);
+  }
+
+  return ret;
+}
+
+function doWrite(stream, state, writev, len, chunk, encoding, cb) {
+  state.writelen = len;
+  state.writecb = cb;
+  state.writing = true;
+  state.sync = true;
+  if (writev) stream._writev(chunk, state.onwrite);else stream._write(chunk, encoding, state.onwrite);
+  state.sync = false;
+}
+
+function onwriteError(stream, state, sync, er, cb) {
+  --state.pendingcb;
+
+  if (sync) {
+    // defer the callback if we are being called synchronously
+    // to avoid piling up things on the stack
+    pna.nextTick(cb, er);
+    // this can emit finish, and it will always happen
+    // after error
+    pna.nextTick(finishMaybe, stream, state);
+    stream._writableState.errorEmitted = true;
+    stream.emit('error', er);
+  } else {
+    // the caller expect this to happen before if
+    // it is async
+    cb(er);
+    stream._writableState.errorEmitted = true;
+    stream.emit('error', er);
+    // this can emit finish, but finish must
+    // always follow error
+    finishMaybe(stream, state);
+  }
+}
+
+function onwriteStateUpdate(state) {
+  state.writing = false;
+  state.writecb = null;
+  state.length -= state.writelen;
+  state.writelen = 0;
+}
+
+function onwrite(stream, er) {
+  var state = stream._writableState;
+  var sync = state.sync;
+  var cb = state.writecb;
+
+  onwriteStateUpdate(state);
+
+  if (er) onwriteError(stream, state, sync, er, cb);else {
+    // Check if we're actually ready to finish, but don't emit yet
+    var finished = needFinish(state);
+
+    if (!finished && !state.corked && !state.bufferProcessing && state.bufferedRequest) {
+      clearBuffer(stream, state);
+    }
+
+    if (sync) {
+      /*<replacement>*/
+      asyncWrite(afterWrite, stream, state, finished, cb);
+      /*</replacement>*/
+    } else {
+      afterWrite(stream, state, finished, cb);
+    }
+  }
+}
+
+function afterWrite(stream, state, finished, cb) {
+  if (!finished) onwriteDrain(stream, state);
+  state.pendingcb--;
+  cb();
+  finishMaybe(stream, state);
+}
+
+// Must force callback to be called on nextTick, so that we don't
+// emit 'drain' before the write() consumer gets the 'false' return
+// value, and has a chance to attach a 'drain' listener.
+function onwriteDrain(stream, state) {
+  if (state.length === 0 && state.needDrain) {
+    state.needDrain = false;
+    stream.emit('drain');
+  }
+}
+
+// if there's something in the buffer waiting, then process it
+function clearBuffer(stream, state) {
+  state.bufferProcessing = true;
+  var entry = state.bufferedRequest;
+
+  if (stream._writev && entry && entry.next) {
+    // Fast case, write everything using _writev()
+    var l = state.bufferedRequestCount;
+    var buffer = new Array(l);
+    var holder = state.corkedRequestsFree;
+    holder.entry = entry;
+
+    var count = 0;
+    var allBuffers = true;
+    while (entry) {
+      buffer[count] = entry;
+      if (!entry.isBuf) allBuffers = false;
+      entry = entry.next;
+      count += 1;
+    }
+    buffer.allBuffers = allBuffers;
+
+    doWrite(stream, state, true, state.length, buffer, '', holder.finish);
+
+    // doWrite is almost always async, defer these to save a bit of time
+    // as the hot path ends with doWrite
+    state.pendingcb++;
+    state.lastBufferedRequest = null;
+    if (holder.next) {
+      state.corkedRequestsFree = holder.next;
+      holder.next = null;
+    } else {
+      state.corkedRequestsFree = new CorkedRequest(state);
+    }
+    state.bufferedRequestCount = 0;
+  } else {
+    // Slow case, write chunks one-by-one
+    while (entry) {
+      var chunk = entry.chunk;
+      var encoding = entry.encoding;
+      var cb = entry.callback;
+      var len = state.objectMode ? 1 : chunk.length;
+
+      doWrite(stream, state, false, len, chunk, encoding, cb);
+      entry = entry.next;
+      state.bufferedRequestCount--;
+      // if we didn't call the onwrite immediately, then
+      // it means that we need to wait until it does.
+      // also, that means that the chunk and cb are currently
+      // being processed, so move the buffer counter past them.
+      if (state.writing) {
+        break;
+      }
+    }
+
+    if (entry === null) state.lastBufferedRequest = null;
+  }
+
+  state.bufferedRequest = entry;
+  state.bufferProcessing = false;
+}
+
+Writable.prototype._write = function (chunk, encoding, cb) {
+  cb(new Error('_write() is not implemented'));
+};
+
+Writable.prototype._writev = null;
+
+Writable.prototype.end = function (chunk, encoding, cb) {
+  var state = this._writableState;
+
+  if (typeof chunk === 'function') {
+    cb = chunk;
+    chunk = null;
+    encoding = null;
+  } else if (typeof encoding === 'function') {
+    cb = encoding;
+    encoding = null;
+  }
+
+  if (chunk !== null && chunk !== undefined) this.write(chunk, encoding);
+
+  // .end() fully uncorks
+  if (state.corked) {
+    state.corked = 1;
+    this.uncork();
+  }
+
+  // ignore unnecessary end() calls.
+  if (!state.ending && !state.finished) endWritable(this, state, cb);
+};
+
+function needFinish(state) {
+  return state.ending && state.length === 0 && state.bufferedRequest === null && !state.finished && !state.writing;
+}
+function callFinal(stream, state) {
+  stream._final(function (err) {
+    state.pendingcb--;
+    if (err) {
+      stream.emit('error', err);
+    }
+    state.prefinished = true;
+    stream.emit('prefinish');
+    finishMaybe(stream, state);
+  });
+}
+function prefinish(stream, state) {
+  if (!state.prefinished && !state.finalCalled) {
+    if (typeof stream._final === 'function') {
+      state.pendingcb++;
+      state.finalCalled = true;
+      pna.nextTick(callFinal, stream, state);
+    } else {
+      state.prefinished = true;
+      stream.emit('prefinish');
+    }
+  }
+}
+
+function finishMaybe(stream, state) {
+  var need = needFinish(state);
+  if (need) {
+    prefinish(stream, state);
+    if (state.pendingcb === 0) {
+      state.finished = true;
+      stream.emit('finish');
+    }
+  }
+  return need;
+}
+
+function endWritable(stream, state, cb) {
+  state.ending = true;
+  finishMaybe(stream, state);
+  if (cb) {
+    if (state.finished) pna.nextTick(cb);else stream.once('finish', cb);
+  }
+  state.ended = true;
+  stream.writable = false;
+}
+
+function onCorkedFinish(corkReq, state, err) {
+  var entry = corkReq.entry;
+  corkReq.entry = null;
+  while (entry) {
+    var cb = entry.callback;
+    state.pendingcb--;
+    cb(err);
+    entry = entry.next;
+  }
+  if (state.corkedRequestsFree) {
+    state.corkedRequestsFree.next = corkReq;
+  } else {
+    state.corkedRequestsFree = corkReq;
+  }
+}
+
+Object.defineProperty(Writable.prototype, 'destroyed', {
+  get: function () {
+    if (this._writableState === undefined) {
+      return false;
+    }
+    return this._writableState.destroyed;
+  },
+  set: function (value) {
+    // we ignore the value if the stream
+    // has not been initialized yet
+    if (!this._writableState) {
+      return;
+    }
+
+    // backward compatibility, the user is explicitly
+    // managing destroyed
+    this._writableState.destroyed = value;
+  }
+});
+
+Writable.prototype.destroy = destroyImpl.destroy;
+Writable.prototype._undestroy = destroyImpl.undestroy;
+Writable.prototype._destroy = function (err, cb) {
+  this.end();
+  cb(err);
+};
+}).call(this,require('_process'),typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {},require("timers").setImmediate)
+},{"./_stream_duplex":19,"./internal/streams/destroy":25,"./internal/streams/stream":26,"_process":49,"core-util-is":11,"inherits":12,"process-nextick-args":18,"safe-buffer":29,"timers":50,"util-deprecate":30}],24:[function(require,module,exports){
+'use strict';
+
+function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
+
+var Buffer = require('safe-buffer').Buffer;
+var util = require('util');
+
+function copyBuffer(src, target, offset) {
+  src.copy(target, offset);
+}
+
+module.exports = function () {
+  function BufferList() {
+    _classCallCheck(this, BufferList);
+
+    this.head = null;
+    this.tail = null;
+    this.length = 0;
+  }
+
+  BufferList.prototype.push = function push(v) {
+    var entry = { data: v, next: null };
+    if (this.length > 0) this.tail.next = entry;else this.head = entry;
+    this.tail = entry;
+    ++this.length;
+  };
+
+  BufferList.prototype.unshift = function unshift(v) {
+    var entry = { data: v, next: this.head };
+    if (this.length === 0) this.tail = entry;
+    this.head = entry;
+    ++this.length;
+  };
+
+  BufferList.prototype.shift = function shift() {
+    if (this.length === 0) return;
+    var ret = this.head.data;
+    if (this.length === 1) this.head = this.tail = null;else this.head = this.head.next;
+    --this.length;
+    return ret;
+  };
+
+  BufferList.prototype.clear = function clear() {
+    this.head = this.tail = null;
+    this.length = 0;
+  };
+
+  BufferList.prototype.join = function join(s) {
+    if (this.length === 0) return '';
+    var p = this.head;
+    var ret = '' + p.data;
+    while (p = p.next) {
+      ret += s + p.data;
+    }return ret;
+  };
+
+  BufferList.prototype.concat = function concat(n) {
+    if (this.length === 0) return Buffer.alloc(0);
+    if (this.length === 1) return this.head.data;
+    var ret = Buffer.allocUnsafe(n >>> 0);
+    var p = this.head;
+    var i = 0;
+    while (p) {
+      copyBuffer(p.data, ret, i);
+      i += p.data.length;
+      p = p.next;
+    }
+    return ret;
+  };
+
+  return BufferList;
+}();
+
+if (util && util.inspect && util.inspect.custom) {
+  module.exports.prototype[util.inspect.custom] = function () {
+    var obj = util.inspect({ length: this.length });
+    return this.constructor.name + ' ' + obj;
+  };
+}
+},{"safe-buffer":29,"util":43}],25:[function(require,module,exports){
+'use strict';
+
+/*<replacement>*/
+
+var pna = require('process-nextick-args');
+/*</replacement>*/
+
+// undocumented cb() API, needed for core, not for public API
+function destroy(err, cb) {
+  var _this = this;
+
+  var readableDestroyed = this._readableState && this._readableState.destroyed;
+  var writableDestroyed = this._writableState && this._writableState.destroyed;
+
+  if (readableDestroyed || writableDestroyed) {
+    if (cb) {
+      cb(err);
+    } else if (err && (!this._writableState || !this._writableState.errorEmitted)) {
+      pna.nextTick(emitErrorNT, this, err);
+    }
+    return this;
+  }
+
+  // we set destroyed to true before firing error callbacks in order
+  // to make it re-entrance safe in case destroy() is called within callbacks
+
+  if (this._readableState) {
+    this._readableState.destroyed = true;
+  }
+
+  // if this is a duplex stream mark the writable part as destroyed as well
+  if (this._writableState) {
+    this._writableState.destroyed = true;
+  }
+
+  this._destroy(err || null, function (err) {
+    if (!cb && err) {
+      pna.nextTick(emitErrorNT, _this, err);
+      if (_this._writableState) {
+        _this._writableState.errorEmitted = true;
+      }
+    } else if (cb) {
+      cb(err);
+    }
+  });
+
+  return this;
+}
+
+function undestroy() {
+  if (this._readableState) {
+    this._readableState.destroyed = false;
+    this._readableState.reading = false;
+    this._readableState.ended = false;
+    this._readableState.endEmitted = false;
+  }
+
+  if (this._writableState) {
+    this._writableState.destroyed = false;
+    this._writableState.ended = false;
+    this._writableState.ending = false;
+    this._writableState.finished = false;
+    this._writableState.errorEmitted = false;
+  }
+}
+
+function emitErrorNT(self, err) {
+  self.emit('error', err);
+}
+
+module.exports = {
+  destroy: destroy,
+  undestroy: undestroy
+};
+},{"process-nextick-args":18}],26:[function(require,module,exports){
+module.exports = require('events').EventEmitter;
+
+},{"events":45}],27:[function(require,module,exports){
+// Copyright Joyent, Inc. and other Node contributors.
+//
+// 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.
+
+'use strict';
+
+/*<replacement>*/
+
+var Buffer = require('safe-buffer').Buffer;
+/*</replacement>*/
+
+var isEncoding = Buffer.isEncoding || function (encoding) {
+  encoding = '' + encoding;
+  switch (encoding && encoding.toLowerCase()) {
+    case 'hex':case 'utf8':case 'utf-8':case 'ascii':case 'binary':case 'base64':case 'ucs2':case 'ucs-2':case 'utf16le':case 'utf-16le':case 'raw':
+      return true;
+    default:
+      return false;
+  }
+};
+
+function _normalizeEncoding(enc) {
+  if (!enc) return 'utf8';
+  var retried;
+  while (true) {
+    switch (enc) {
+      case 'utf8':
+      case 'utf-8':
+        return 'utf8';
+      case 'ucs2':
+      case 'ucs-2':
+      case 'utf16le':
+      case 'utf-16le':
+        return 'utf16le';
+      case 'latin1':
+      case 'binary':
+        return 'latin1';
+      case 'base64':
+      case 'ascii':
+      case 'hex':
+        return enc;
+      default:
+        if (retried) return; // undefined
+        enc = ('' + enc).toLowerCase();
+        retried = true;
+    }
+  }
+};
+
+// Do not cache `Buffer.isEncoding` when checking encoding names as some
+// modules monkey-patch it to support additional encodings
+function normalizeEncoding(enc) {
+  var nenc = _normalizeEncoding(enc);
+  if (typeof nenc !== 'string' && (Buffer.isEncoding === isEncoding || !isEncoding(enc))) throw new Error('Unknown encoding: ' + enc);
+  return nenc || enc;
+}
+
+// StringDecoder provides an interface for efficiently splitting a series of
+// buffers into a series of JS strings without breaking apart multi-byte
+// characters.
+exports.StringDecoder = StringDecoder;
+function StringDecoder(encoding) {
+  this.encoding = normalizeEncoding(encoding);
+  var nb;
+  switch (this.encoding) {
+    case 'utf16le':
+      this.text = utf16Text;
+      this.end = utf16End;
+      nb = 4;
+      break;
+    case 'utf8':
+      this.fillLast = utf8FillLast;
+      nb = 4;
+      break;
+    case 'base64':
+      this.text = base64Text;
+      this.end = base64End;
+      nb = 3;
+      break;
+    default:
+      this.write = simpleWrite;
+      this.end = simpleEnd;
+      return;
+  }
+  this.lastNeed = 0;
+  this.lastTotal = 0;
+  this.lastChar = Buffer.allocUnsafe(nb);
+}
+
+StringDecoder.prototype.write = function (buf) {
+  if (buf.length === 0) return '';
+  var r;
+  var i;
+  if (this.lastNeed) {
+    r = this.fillLast(buf);
+    if (r === undefined) return '';
+    i = this.lastNeed;
+    this.lastNeed = 0;
+  } else {
+    i = 0;
+  }
+  if (i < buf.length) return r ? r + this.text(buf, i) : this.text(buf, i);
+  return r || '';
+};
+
+StringDecoder.prototype.end = utf8End;
+
+// Returns only complete characters in a Buffer
+StringDecoder.prototype.text = utf8Text;
+
+// Attempts to complete a partial non-UTF-8 character using bytes from a Buffer
+StringDecoder.prototype.fillLast = function (buf) {
+  if (this.lastNeed <= buf.length) {
+    buf.copy(this.lastChar, this.lastTotal - this.lastNeed, 0, this.lastNeed);
+    return this.lastChar.toString(this.encoding, 0, this.lastTotal);
+  }
+  buf.copy(this.lastChar, this.lastTotal - this.lastNeed, 0, buf.length);
+  this.lastNeed -= buf.length;
+};
+
+// Checks the type of a UTF-8 byte, whether it's ASCII, a leading byte, or a
+// continuation byte. If an invalid byte is detected, -2 is returned.
+function utf8CheckByte(byte) {
+  if (byte <= 0x7F) return 0;else if (byte >> 5 === 0x06) return 2;else if (byte >> 4 === 0x0E) return 3;else if (byte >> 3 === 0x1E) return 4;
+  return byte >> 6 === 0x02 ? -1 : -2;
+}
+
+// Checks at most 3 bytes at the end of a Buffer in order to detect an
+// incomplete multi-byte UTF-8 character. The total number of bytes (2, 3, or 4)
+// needed to complete the UTF-8 character (if applicable) are returned.
+function utf8CheckIncomplete(self, buf, i) {
+  var j = buf.length - 1;
+  if (j < i) return 0;
+  var nb = utf8CheckByte(buf[j]);
+  if (nb >= 0) {
+    if (nb > 0) self.lastNeed = nb - 1;
+    return nb;
+  }
+  if (--j < i || nb === -2) return 0;
+  nb = utf8CheckByte(buf[j]);
+  if (nb >= 0) {
+    if (nb > 0) self.lastNeed = nb - 2;
+    return nb;
+  }
+  if (--j < i || nb === -2) return 0;
+  nb = utf8CheckByte(buf[j]);
+  if (nb >= 0) {
+    if (nb > 0) {
+      if (nb === 2) nb = 0;else self.lastNeed = nb - 3;
+    }
+    return nb;
+  }
+  return 0;
+}
+
+// Validates as many continuation bytes for a multi-byte UTF-8 character as
+// needed or are available. If we see a non-continuation byte where we expect
+// one, we "replace" the validated continuation bytes we've seen so far with
+// a single UTF-8 replacement character ('\ufffd'), to match v8's UTF-8 decoding
+// behavior. The continuation byte check is included three times in the case
+// where all of the continuation bytes for a character exist in the same buffer.
+// It is also done this way as a slight performance increase instead of using a
+// loop.
+function utf8CheckExtraBytes(self, buf, p) {
+  if ((buf[0] & 0xC0) !== 0x80) {
+    self.lastNeed = 0;
+    return '\ufffd';
+  }
+  if (self.lastNeed > 1 && buf.length > 1) {
+    if ((buf[1] & 0xC0) !== 0x80) {
+      self.lastNeed = 1;
+      return '\ufffd';
+    }
+    if (self.lastNeed > 2 && buf.length > 2) {
+      if ((buf[2] & 0xC0) !== 0x80) {
+        self.lastNeed = 2;
+        return '\ufffd';
+      }
+    }
+  }
+}
+
+// Attempts to complete a multi-byte UTF-8 character using bytes from a Buffer.
+function utf8FillLast(buf) {
+  var p = this.lastTotal - this.lastNeed;
+  var r = utf8CheckExtraBytes(this, buf, p);
+  if (r !== undefined) return r;
+  if (this.lastNeed <= buf.length) {
+    buf.copy(this.lastChar, p, 0, this.lastNeed);
+    return this.lastChar.toString(this.encoding, 0, this.lastTotal);
+  }
+  buf.copy(this.lastChar, p, 0, buf.length);
+  this.lastNeed -= buf.length;
+}
+
+// Returns all complete UTF-8 characters in a Buffer. If the Buffer ended on a
+// partial character, the character's bytes are buffered until the required
+// number of bytes are available.
+function utf8Text(buf, i) {
+  var total = utf8CheckIncomplete(this, buf, i);
+  if (!this.lastNeed) return buf.toString('utf8', i);
+  this.lastTotal = total;
+  var end = buf.length - (total - this.lastNeed);
+  buf.copy(this.lastChar, 0, end);
+  return buf.toString('utf8', i, end);
+}
+
+// For UTF-8, a replacement character is added when ending on a partial
+// character.
+function utf8End(buf) {
+  var r = buf && buf.length ? this.write(buf) : '';
+  if (this.lastNeed) return r + '\ufffd';
+  return r;
+}
+
+// UTF-16LE typically needs two bytes per character, but even if we have an even
+// number of bytes available, we need to check if we end on a leading/high
+// surrogate. In that case, we need to wait for the next two bytes in order to
+// decode the last character properly.
+function utf16Text(buf, i) {
+  if ((buf.length - i) % 2 === 0) {
+    var r = buf.toString('utf16le', i);
+    if (r) {
+      var c = r.charCodeAt(r.length - 1);
+      if (c >= 0xD800 && c <= 0xDBFF) {
+        this.lastNeed = 2;
+        this.lastTotal = 4;
+        this.lastChar[0] = buf[buf.length - 2];
+        this.lastChar[1] = buf[buf.length - 1];
+        return r.slice(0, -1);
+      }
+    }
+    return r;
+  }
+  this.lastNeed = 1;
+  this.lastTotal = 2;
+  this.lastChar[0] = buf[buf.length - 1];
+  return buf.toString('utf16le', i, buf.length - 1);
+}
+
+// For UTF-16LE we do not explicitly append special replacement characters if we
+// end on a partial character, we simply let v8 handle that.
+function utf16End(buf) {
+  var r = buf && buf.length ? this.write(buf) : '';
+  if (this.lastNeed) {
+    var end = this.lastTotal - this.lastNeed;
+    return r + this.lastChar.toString('utf16le', 0, end);
+  }
+  return r;
+}
+
+function base64Text(buf, i) {
+  var n = (buf.length - i) % 3;
+  if (n === 0) return buf.toString('base64', i);
+  this.lastNeed = 3 - n;
+  this.lastTotal = 3;
+  if (n === 1) {
+    this.lastChar[0] = buf[buf.length - 1];
+  } else {
+    this.lastChar[0] = buf[buf.length - 2];
+    this.lastChar[1] = buf[buf.length - 1];
+  }
+  return buf.toString('base64', i, buf.length - n);
+}
+
+function base64End(buf) {
+  var r = buf && buf.length ? this.write(buf) : '';
+  if (this.lastNeed) return r + this.lastChar.toString('base64', 0, 3 - this.lastNeed);
+  return r;
+}
+
+// Pass bytes on through for single-byte encodings (e.g. ascii, latin1, hex)
+function simpleWrite(buf) {
+  return buf.toString(this.encoding);
+}
+
+function simpleEnd(buf) {
+  return buf && buf.length ? this.write(buf) : '';
+}
+},{"safe-buffer":29}],28:[function(require,module,exports){
+exports = module.exports = require('./lib/_stream_readable.js');
+exports.Stream = exports;
+exports.Readable = exports;
+exports.Writable = require('./lib/_stream_writable.js');
+exports.Duplex = require('./lib/_stream_duplex.js');
+exports.Transform = require('./lib/_stream_transform.js');
+exports.PassThrough = require('./lib/_stream_passthrough.js');
+
+},{"./lib/_stream_duplex.js":19,"./lib/_stream_passthrough.js":20,"./lib/_stream_readable.js":21,"./lib/_stream_transform.js":22,"./lib/_stream_writable.js":23}],29:[function(require,module,exports){
+/* eslint-disable node/no-deprecated-api */
+var buffer = require('buffer')
+var Buffer = buffer.Buffer
+
+// alternative to using Object.keys for old browsers
+function copyProps (src, dst) {
+  for (var key in src) {
+    dst[key] = src[key]
+  }
+}
+if (Buffer.from && Buffer.alloc && Buffer.allocUnsafe && Buffer.allocUnsafeSlow) {
+  module.exports = buffer
+} else {
+  // Copy properties from require('buffer')
+  copyProps(buffer, exports)
+  exports.Buffer = SafeBuffer
+}
+
+function SafeBuffer (arg, encodingOrOffset, length) {
+  return Buffer(arg, encodingOrOffset, length)
+}
+
+// Copy static methods from Buffer
+copyProps(Buffer, SafeBuffer)
+
+SafeBuffer.from = function (arg, encodingOrOffset, length) {
+  if (typeof arg === 'number') {
+    throw new TypeError('Argument must not be a number')
+  }
+  return Buffer(arg, encodingOrOffset, length)
+}
+
+SafeBuffer.alloc = function (size, fill, encoding) {
+  if (typeof size !== 'number') {
+    throw new TypeError('Argument must be a number')
+  }
+  var buf = Buffer(size)
+  if (fill !== undefined) {
+    if (typeof encoding === 'string') {
+      buf.fill(fill, encoding)
+    } else {
+      buf.fill(fill)
+    }
+  } else {
+    buf.fill(0)
+  }
+  return buf
+}
+
+SafeBuffer.allocUnsafe = function (size) {
+  if (typeof size !== 'number') {
+    throw new TypeError('Argument must be a number')
+  }
+  return Buffer(size)
+}
+
+SafeBuffer.allocUnsafeSlow = function (size) {
+  if (typeof size !== 'number') {
+    throw new TypeError('Argument must be a number')
+  }
+  return buffer.SlowBuffer(size)
+}
+
+},{"buffer":44}],30:[function(require,module,exports){
+(function (global){
+
+/**
+ * Module exports.
+ */
+
+module.exports = deprecate;
+
+/**
+ * Mark that a method should not be used.
+ * Returns a modified function which warns once by default.
+ *
+ * If `localStorage.noDeprecation = true` is set, then it is a no-op.
+ *
+ * If `localStorage.throwDeprecation = true` is set, then deprecated functions
+ * will throw an Error when invoked.
+ *
+ * If `localStorage.traceDeprecation = true` is set, then deprecated functions
+ * will invoke `console.trace()` instead of `console.error()`.
+ *
+ * @param {Function} fn - the function to deprecate
+ * @param {String} msg - the string to print to the console when `fn` is invoked
+ * @returns {Function} a new "deprecated" version of `fn`
+ * @api public
+ */
+
+function deprecate (fn, msg) {
+  if (config('noDeprecation')) {
+    return fn;
+  }
+
+  var warned = false;
+  function deprecated() {
+    if (!warned) {
+      if (config('throwDeprecation')) {
+        throw new Error(msg);
+      } else if (config('traceDeprecation')) {
+        console.trace(msg);
+      } else {
+        console.warn(msg);
+      }
+      warned = true;
+    }
+    return fn.apply(this, arguments);
+  }
+
+  return deprecated;
+}
+
+/**
+ * Checks `localStorage` for boolean values for the given `name`.
+ *
+ * @param {String} name
+ * @returns {Boolean}
+ * @api private
+ */
+
+function config (name) {
+  // accessing global.localStorage can trigger a DOMException in sandboxed iframes
+  try {
+    if (!global.localStorage) return false;
+  } catch (_) {
+    return false;
+  }
+  var val = global.localStorage[name];
+  if (null == val) return false;
+  return String(val).toLowerCase() === 'true';
+}
+
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{}],31:[function(require,module,exports){
+var v1 = require('./v1');
+var v4 = require('./v4');
+
+var uuid = v4;
+uuid.v1 = v1;
+uuid.v4 = v4;
+
+module.exports = uuid;
+
+},{"./v1":34,"./v4":35}],32:[function(require,module,exports){
+/**
+ * Convert array of 16 byte values to UUID string format of the form:
+ * XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX
+ */
+var byteToHex = [];
+for (var i = 0; i < 256; ++i) {
+  byteToHex[i] = (i + 0x100).toString(16).substr(1);
+}
+
+function bytesToUuid(buf, offset) {
+  var i = offset || 0;
+  var bth = byteToHex;
+  // join used to fix memory issue caused by concatenation: https://bugs.chromium.org/p/v8/issues/detail?id=3175#c4
+  return ([bth[buf[i++]], bth[buf[i++]], 
+	bth[buf[i++]], bth[buf[i++]], '-',
+	bth[buf[i++]], bth[buf[i++]], '-',
+	bth[buf[i++]], bth[buf[i++]], '-',
+	bth[buf[i++]], bth[buf[i++]], '-',
+	bth[buf[i++]], bth[buf[i++]],
+	bth[buf[i++]], bth[buf[i++]],
+	bth[buf[i++]], bth[buf[i++]]]).join('');
+}
+
+module.exports = bytesToUuid;
+
+},{}],33:[function(require,module,exports){
+// Unique ID creation requires a high quality random # generator.  In the
+// browser this is a little complicated due to unknown quality of Math.random()
+// and inconsistent support for the `crypto` API.  We do the best we can via
+// feature-detection
+
+// getRandomValues needs to be invoked in a context where "this" is a Crypto
+// implementation. Also, find the complete implementation of crypto on IE11.
+var getRandomValues = (typeof(crypto) != 'undefined' && crypto.getRandomValues && crypto.getRandomValues.bind(crypto)) ||
+                      (typeof(msCrypto) != 'undefined' && typeof window.msCrypto.getRandomValues == 'function' && msCrypto.getRandomValues.bind(msCrypto));
+
+if (getRandomValues) {
+  // WHATWG crypto RNG - http://wiki.whatwg.org/wiki/Crypto
+  var rnds8 = new Uint8Array(16); // eslint-disable-line no-undef
+
+  module.exports = function whatwgRNG() {
+    getRandomValues(rnds8);
+    return rnds8;
+  };
+} else {
+  // Math.random()-based (RNG)
+  //
+  // If all else fails, use Math.random().  It's fast, but is of unspecified
+  // quality.
+  var rnds = new Array(16);
+
+  module.exports = function mathRNG() {
+    for (var i = 0, r; i < 16; i++) {
+      if ((i & 0x03) === 0) r = Math.random() * 0x100000000;
+      rnds[i] = r >>> ((i & 0x03) << 3) & 0xff;
+    }
+
+    return rnds;
+  };
+}
+
+},{}],34:[function(require,module,exports){
+var rng = require('./lib/rng');
+var bytesToUuid = require('./lib/bytesToUuid');
+
+// **`v1()` - Generate time-based UUID**
+//
+// Inspired by https://github.com/LiosK/UUID.js
+// and http://docs.python.org/library/uuid.html
+
+var _nodeId;
+var _clockseq;
+
+// Previous uuid creation time
+var _lastMSecs = 0;
+var _lastNSecs = 0;
+
+// See https://github.com/broofa/node-uuid for API details
+function v1(options, buf, offset) {
+  var i = buf && offset || 0;
+  var b = buf || [];
+
+  options = options || {};
+  var node = options.node || _nodeId;
+  var clockseq = options.clockseq !== undefined ? options.clockseq : _clockseq;
+
+  // node and clockseq need to be initialized to random values if they're not
+  // specified.  We do this lazily to minimize issues related to insufficient
+  // system entropy.  See #189
+  if (node == null || clockseq == null) {
+    var seedBytes = rng();
+    if (node == null) {
+      // Per 4.5, create and 48-bit node id, (47 random bits + multicast bit = 1)
+      node = _nodeId = [
+        seedBytes[0] | 0x01,
+        seedBytes[1], seedBytes[2], seedBytes[3], seedBytes[4], seedBytes[5]
+      ];
+    }
+    if (clockseq == null) {
+      // Per 4.2.2, randomize (14 bit) clockseq
+      clockseq = _clockseq = (seedBytes[6] << 8 | seedBytes[7]) & 0x3fff;
+    }
+  }
+
+  // UUID timestamps are 100 nano-second units since the Gregorian epoch,
+  // (1582-10-15 00:00).  JSNumbers aren't precise enough for this, so
+  // time is handled internally as 'msecs' (integer milliseconds) and 'nsecs'
+  // (100-nanoseconds offset from msecs) since unix epoch, 1970-01-01 00:00.
+  var msecs = options.msecs !== undefined ? options.msecs : new Date().getTime();
+
+  // Per 4.2.1.2, use count of uuid's generated during the current clock
+  // cycle to simulate higher resolution clock
+  var nsecs = options.nsecs !== undefined ? options.nsecs : _lastNSecs + 1;
+
+  // Time since last uuid creation (in msecs)
+  var dt = (msecs - _lastMSecs) + (nsecs - _lastNSecs)/10000;
+
+  // Per 4.2.1.2, Bump clockseq on clock regression
+  if (dt < 0 && options.clockseq === undefined) {
+    clockseq = clockseq + 1 & 0x3fff;
+  }
+
+  // Reset nsecs if clock regresses (new clockseq) or we've moved onto a new
+  // time interval
+  if ((dt < 0 || msecs > _lastMSecs) && options.nsecs === undefined) {
+    nsecs = 0;
+  }
+
+  // Per 4.2.1.2 Throw error if too many uuids are requested
+  if (nsecs >= 10000) {
+    throw new Error('uuid.v1(): Can\'t create more than 10M uuids/sec');
+  }
+
+  _lastMSecs = msecs;
+  _lastNSecs = nsecs;
+  _clockseq = clockseq;
+
+  // Per 4.1.4 - Convert from unix epoch to Gregorian epoch
+  msecs += 12219292800000;
+
+  // `time_low`
+  var tl = ((msecs & 0xfffffff) * 10000 + nsecs) % 0x100000000;
+  b[i++] = tl >>> 24 & 0xff;
+  b[i++] = tl >>> 16 & 0xff;
+  b[i++] = tl >>> 8 & 0xff;
+  b[i++] = tl & 0xff;
+
+  // `time_mid`
+  var tmh = (msecs / 0x100000000 * 10000) & 0xfffffff;
+  b[i++] = tmh >>> 8 & 0xff;
+  b[i++] = tmh & 0xff;
+
+  // `time_high_and_version`
+  b[i++] = tmh >>> 24 & 0xf | 0x10; // include version
+  b[i++] = tmh >>> 16 & 0xff;
+
+  // `clock_seq_hi_and_reserved` (Per 4.2.2 - include variant)
+  b[i++] = clockseq >>> 8 | 0x80;
+
+  // `clock_seq_low`
+  b[i++] = clockseq & 0xff;
+
+  // `node`
+  for (var n = 0; n < 6; ++n) {
+    b[i + n] = node[n];
+  }
+
+  return buf ? buf : bytesToUuid(b);
+}
+
+module.exports = v1;
+
+},{"./lib/bytesToUuid":32,"./lib/rng":33}],35:[function(require,module,exports){
+var rng = require('./lib/rng');
+var bytesToUuid = require('./lib/bytesToUuid');
+
+function v4(options, buf, offset) {
+  var i = buf && offset || 0;
+
+  if (typeof(options) == 'string') {
+    buf = options === 'binary' ? new Array(16) : null;
+    options = null;
+  }
+  options = options || {};
+
+  var rnds = options.random || (options.rng || rng)();
+
+  // Per 4.4, set bits for version and `clock_seq_hi_and_reserved`
+  rnds[6] = (rnds[6] & 0x0f) | 0x40;
+  rnds[8] = (rnds[8] & 0x3f) | 0x80;
+
+  // Copy bytes to buffer, if provided
+  if (buf) {
+    for (var ii = 0; ii < 16; ++ii) {
+      buf[i + ii] = rnds[ii];
+    }
+  }
+
+  return buf || bytesToUuid(rnds);
+}
+
+module.exports = v4;
+
+},{"./lib/bytesToUuid":32,"./lib/rng":33}],36:[function(require,module,exports){
+(function (Buffer){
+const msgpack = require('msgpack5')()
+  , encode  = msgpack.encode
+  , decode  = msgpack.decode;
+const uuidv4 = require('uuid') //Deprecated method, should use require('uuid/v4')
+const uuidParser = require('./utils/uuidParser')
+
+const kConnecting = 1;
+const kConnected = 2;
+const kDisconnected = 3;
+
+// Generate a unique id for this webservice
+let uuid = uuidv4();
+let my_uuid = uuidParser.parse(uuid)
+my_uuid = new Uint8Array(my_uuid);
+// my_uuid[0] = 44;
+// console.log(my_uuid)
+my_uuid = Buffer.from(my_uuid);
+
+const kMagic = 0x0009340053640912;
+const kVersion = 0;
+
+
+/**
+ * Wrap a web socket with a MsgPack RCP protocol that works with our C++ version.
+ * @param {websocket} ws Websocket object
+ */
+function Peer(ws) {
+	this.sock = ws;
+	this.status = kConnecting;
+	this.id = null;
+	this.string_id = "";
+	this.bindings = {};
+	this.proxies = {};
+	this.events = {};
+	this.callbacks = {};
+	this.cbid = 0;
+
+	this.uri = "unknown";
+	this.name = "unknown";
+	this.master = false;
+
+	let message = (raw) => {
+		// console.log(raw)
+		//Gets right data for client
+		if(this.sock.on === undefined){
+			raw = raw.data;
+		}
+		let msg = decode(raw);
+		// console.log('MSG', msg)
+		if (this.status == kConnecting) {
+			if (msg[1] != "__handshake__") {
+				console.log("Bad handshake");
+				this.close();
+			}
+		}
+		if (msg[0] == 0) {
+			// console.log("MSG...", msg[2]);
+			// Notification
+			if (msg.length == 3) {
+				this._dispatchNotification(msg[1], msg[2]);
+			// Call
+			} else {
+				this._dispatchCall(msg[2], msg[1], msg[3]);
+			}
+		} else if (msg[0] == 1) {
+			this._dispatchResponse(msg[1], msg[3]);
+		}
+	}
+
+	let close = () => {
+		this.status = kDisconnected;
+		this._notify("disconnect", this);
+	}
+
+	let error = () => {
+		console.error("Socket error");
+		this.sock.close();
+		this.status = kDisconnected;
+	}
+
+	//if undefined, peer is being used by client
+	if(this.sock.on === undefined){
+		this.sock.onmessage = message;
+		this.sock.onclose = close;
+		this.sock.onopen = (event) => {
+			this.send("__handshake__", kMagic, kVersion, [my_uuid]);
+		}
+	//else peer is being used by server
+	}else{
+		this.sock.on("message", message);
+		this.sock.on("close", close);
+		this.sock.on("error", error);
+	}
+
+	this.bind("__handshake__", (magic, version, id) => {
+		if (magic == kMagic) {
+			console.log("Handshake received");
+			this.status = kConnected;
+			this.id = id.buffer;
+			this.string_id  = id.toString('hex');
+			this._notify("connect", this);
+			// if(this.sock.on === undefined){
+			// 	this.send("__handshake__", kMagic, kVersion, [my_uuid]);
+			// }
+		} else {
+			console.log("Magic does not match");
+			this.close();
+		}
+	});
+	this.send("__handshake__", kMagic, kVersion, [my_uuid]);
+}		
+
+
+Peer.uuid = my_uuid;
+
+/**
+ * @private
+ */
+Peer.prototype._dispatchNotification = function(name, args) {
+	if (this.bindings.hasOwnProperty(name)) {
+		//console.log("Notification for: ", name);
+		this.bindings[name].apply(this, args);
+	} else {
+		console.log("Missing handler for: ", name);
+	}
+}
+
+/**
+ * @private
+ */
+Peer.prototype._dispatchCall = function(name, id, args) {
+	console.log("DISPATCHCALL", name, id, args)
+	if (this.bindings.hasOwnProperty(name)) {
+		//console.log("Call for:", name, id);
+
+		try {
+			let res = this.bindings[name].apply(this, args);
+			this.sock.send(encode([1,id,name,res]));
+		} catch(e) {
+			console.error("Could to dispatch or return call", e);
+			this.close();
+		}
+	} else if (this.proxies.hasOwnProperty(name)) {
+		//console.log("Proxy for:", name, id);
+		args.unshift((res) => {
+			try {
+				this.sock.send(encode([1,id,name,res]));
+			} catch(e) {
+				console.log("ERROR")
+				this.close();
+			}
+		});
+		this.proxies[name].apply(this, args);
+	} else {
+		console.log("Missing handler for: ", name);
+	}
+}
+
+/**
+ * @private
+ */
+Peer.prototype._dispatchResponse = function(id, res) {
+	if (this.callbacks.hasOwnProperty(id)) {
+		this.callbacks[id].call(this, res);
+		delete this.callbacks[id];
+	} else {
+		console.log("Missing callback");
+	}
+}
+
+/**
+ * Register an RPC handler that will be called from a remote machine. Remotely
+ * passed arguments are provided to the given function as normal arguments, and
+ * if the function returns a value, it will be returned over the network also.
+ * 
+ * @param {string} name The name of the function
+ * @param {function} f A function or lambda to be callable remotely
+ */
+Peer.prototype.bind = function(name, f) {
+	if (this.bindings.hasOwnProperty(name)) {
+		//console.error("Duplicate bind to same procedure");
+		this.bindings[name] = f;
+	} else {
+		this.bindings[name] = f;
+	}
+}
+
+/**
+ * Allow an RPC call to pass through to another machine with minimal local
+ * processing.
+ */
+Peer.prototype.proxy = function(name, f) {
+	if (this.proxies.hasOwnProperty(name)) {
+		//console.error("Duplicate proxy to same procedure");
+		this.proxies[name] = f;
+	} else {
+		this.proxies[name] = f;
+	}
+}
+
+/**
+ * Call a procedure on a remote machine.
+ * 
+ * @param {string} name Name of the procedure
+ * @param {function} cb Callback to receive return value as argument
+ * @param {...} args Any number of arguments to also pass to remote procedure
+ */
+Peer.prototype.rpc = function(name, cb, ...args) {
+	let id = this.cbid++;
+	this.callbacks[id] = cb;
+
+	try {
+		this.sock.send(encode([0, id, name, args]));
+	} catch(e) {
+		this.close();
+	}
+}
+
+Peer.prototype.sendB = function(name, args) {
+	try {
+		this.sock.send(encode([0, name, args]));
+	} catch(e) {
+		this.close();
+	}
+}
+
+/**
+ * Call a remote procedure but with no return value expected.
+ * 
+ * @param {string} name Name of the procedure
+ * @param {...} args Any number of arguments to also pass to remote procedure
+ */
+Peer.prototype.send = function(name, ...args) {
+	try {
+		this.sock.send(encode([0, name, args]));
+	} catch(e) {
+		this.close();
+	}
+}
+
+/**
+ * Closes the socket
+ */
+Peer.prototype.close = function() {
+	if(this.sock.on !== undefined){
+		this.sock.close();
+	}
+	this.status = kDisconnected;
+}
+
+/**
+ * @private
+ */
+Peer.prototype._notify = function(evt, ...args) {
+	if (this.events.hasOwnProperty(evt)) {
+		for (let i=0; i<this.events[evt].length; i++) {
+			let f = this.events[evt][i];
+			f.apply(this, args);
+		}
+	}
+}
+
+/**
+ * Register a callback for socket events. Events include: 'connect',
+ * 'disconnect' and 'error'.
+ * 
+ * @param {string} evt Event name
+ * @param {function} f Callback on event
+ */
+Peer.prototype.on = function(evt, f) {
+	if (!this.events.hasOwnProperty(evt)) {
+		this.events[evt] = [];
+	}
+	this.events[evt].push(f);
+}
+
+
+Peer.prototype.getUuid = function() {
+	return uuid;
+}
+
+module.exports = Peer;
+
+}).call(this,require("buffer").Buffer)
+},{"./utils/uuidParser":37,"buffer":44,"msgpack5":14,"uuid":31}],37:[function(require,module,exports){
+// Maps for number <-> hex string conversion
+var _byteToHex = [];
+var _hexToByte = {};
+for (var i = 0; i < 256; i++) {
+  _byteToHex[i] = (i + 0x100).toString(16).substr(1);
+  _hexToByte[_byteToHex[i]] = i;
+}
+
+/** 
+ * `parse()` - Parse a UUID into it's component bytes
+ * 
+ * Turns UUID into Buffer
+ **/ 
+function parse(s, buf, offset) {
+  var i = (buf && offset) || 0;
+  var ii = 0;
+
+  buf = buf || [];
+  s.toLowerCase().replace(/[0-9a-f]{2}/g, function(oct) {
+    if (ii < 16) { // Don't overflow!
+      buf[i + ii++] = _hexToByte[oct];
+    }
+  });
+
+  // Zero out remaining bytes if string was short
+  while (ii < 16) {
+    buf[i + ii++] = 0;
+  }
+
+  return buf;
+}
+
+/**
+ * `unparse()` - Convert UUID byte array (ala parse()) into a string
+ * 
+ * Turns Buffer into UUID
+ * */
+function unparse(buf, offset) {
+  var i = offset || 0;
+  var bth = _byteToHex;
+  return  bth[buf[i++]] + bth[buf[i++]] +
+          bth[buf[i++]] + bth[buf[i++]] + '-' +
+          bth[buf[i++]] + bth[buf[i++]] + '-' +
+          bth[buf[i++]] + bth[buf[i++]] + '-' +
+          bth[buf[i++]] + bth[buf[i++]] + '-' +
+          bth[buf[i++]] + bth[buf[i++]] +
+          bth[buf[i++]] + bth[buf[i++]] +
+          bth[buf[i++]] + bth[buf[i++]];
+}
+
+module.exports = {
+  parse: parse,
+  unparse: unparse
+};
+},{}],38:[function(require,module,exports){
+(function (global){
+'use strict';
+
+var objectAssign = require('object-assign');
+
+// compare and isBuffer taken from https://github.com/feross/buffer/blob/680e9e5e488f22aac27599a57dc844a6315928dd/index.js
+// original notice:
+
+/*!
+ * The buffer module from node.js, for the browser.
+ *
+ * @author   Feross Aboukhadijeh <feross@feross.org> <http://feross.org>
+ * @license  MIT
+ */
+function compare(a, b) {
+  if (a === b) {
+    return 0;
+  }
+
+  var x = a.length;
+  var y = b.length;
+
+  for (var i = 0, len = Math.min(x, y); i < len; ++i) {
+    if (a[i] !== b[i]) {
+      x = a[i];
+      y = b[i];
+      break;
+    }
+  }
+
+  if (x < y) {
+    return -1;
+  }
+  if (y < x) {
+    return 1;
+  }
+  return 0;
+}
+function isBuffer(b) {
+  if (global.Buffer && typeof global.Buffer.isBuffer === 'function') {
+    return global.Buffer.isBuffer(b);
+  }
+  return !!(b != null && b._isBuffer);
+}
+
+// based on node assert, original notice:
+// NB: The URL to the CommonJS spec is kept just for tradition.
+//     node-assert has evolved a lot since then, both in API and behavior.
+
+// http://wiki.commonjs.org/wiki/Unit_Testing/1.0
+//
+// THIS IS NOT TESTED NOR LIKELY TO WORK OUTSIDE V8!
+//
+// Originally from narwhal.js (http://narwhaljs.org)
+// Copyright (c) 2009 Thomas Robinson <280north.com>
+//
+// 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 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.
+
+var util = require('util/');
+var hasOwn = Object.prototype.hasOwnProperty;
+var pSlice = Array.prototype.slice;
+var functionsHaveNames = (function () {
+  return function foo() {}.name === 'foo';
+}());
+function pToString (obj) {
+  return Object.prototype.toString.call(obj);
+}
+function isView(arrbuf) {
+  if (isBuffer(arrbuf)) {
+    return false;
+  }
+  if (typeof global.ArrayBuffer !== 'function') {
+    return false;
+  }
+  if (typeof ArrayBuffer.isView === 'function') {
+    return ArrayBuffer.isView(arrbuf);
+  }
+  if (!arrbuf) {
+    return false;
+  }
+  if (arrbuf instanceof DataView) {
+    return true;
+  }
+  if (arrbuf.buffer && arrbuf.buffer instanceof ArrayBuffer) {
+    return true;
+  }
+  return false;
+}
+// 1. The assert module provides functions that throw
+// AssertionError's when particular conditions are not met. The
+// assert module must conform to the following interface.
+
+var assert = module.exports = ok;
+
+// 2. The AssertionError is defined in assert.
+// new assert.AssertionError({ message: message,
+//                             actual: actual,
+//                             expected: expected })
+
+var regex = /\s*function\s+([^\(\s]*)\s*/;
+// based on https://github.com/ljharb/function.prototype.name/blob/adeeeec8bfcc6068b187d7d9fb3d5bb1d3a30899/implementation.js
+function getName(func) {
+  if (!util.isFunction(func)) {
+    return;
+  }
+  if (functionsHaveNames) {
+    return func.name;
+  }
+  var str = func.toString();
+  var match = str.match(regex);
+  return match && match[1];
+}
+assert.AssertionError = function AssertionError(options) {
+  this.name = 'AssertionError';
+  this.actual = options.actual;
+  this.expected = options.expected;
+  this.operator = options.operator;
+  if (options.message) {
+    this.message = options.message;
+    this.generatedMessage = false;
+  } else {
+    this.message = getMessage(this);
+    this.generatedMessage = true;
+  }
+  var stackStartFunction = options.stackStartFunction || fail;
+  if (Error.captureStackTrace) {
+    Error.captureStackTrace(this, stackStartFunction);
+  } else {
+    // non v8 browsers so we can have a stacktrace
+    var err = new Error();
+    if (err.stack) {
+      var out = err.stack;
+
+      // try to strip useless frames
+      var fn_name = getName(stackStartFunction);
+      var idx = out.indexOf('\n' + fn_name);
+      if (idx >= 0) {
+        // once we have located the function frame
+        // we need to strip out everything before it (and its line)
+        var next_line = out.indexOf('\n', idx + 1);
+        out = out.substring(next_line + 1);
+      }
+
+      this.stack = out;
+    }
+  }
+};
+
+// assert.AssertionError instanceof Error
+util.inherits(assert.AssertionError, Error);
+
+function truncate(s, n) {
+  if (typeof s === 'string') {
+    return s.length < n ? s : s.slice(0, n);
+  } else {
+    return s;
+  }
+}
+function inspect(something) {
+  if (functionsHaveNames || !util.isFunction(something)) {
+    return util.inspect(something);
+  }
+  var rawname = getName(something);
+  var name = rawname ? ': ' + rawname : '';
+  return '[Function' +  name + ']';
+}
+function getMessage(self) {
+  return truncate(inspect(self.actual), 128) + ' ' +
+         self.operator + ' ' +
+         truncate(inspect(self.expected), 128);
+}
+
+// At present only the three keys mentioned above are used and
+// understood by the spec. Implementations or sub modules can pass
+// other keys to the AssertionError's constructor - they will be
+// ignored.
+
+// 3. All of the following functions must throw an AssertionError
+// when a corresponding condition is not met, with a message that
+// may be undefined if not provided.  All assertion methods provide
+// both the actual and expected values to the assertion error for
+// display purposes.
+
+function fail(actual, expected, message, operator, stackStartFunction) {
+  throw new assert.AssertionError({
+    message: message,
+    actual: actual,
+    expected: expected,
+    operator: operator,
+    stackStartFunction: stackStartFunction
+  });
+}
+
+// EXTENSION! allows for well behaved errors defined elsewhere.
+assert.fail = fail;
+
+// 4. Pure assertion tests whether a value is truthy, as determined
+// by !!guard.
+// assert.ok(guard, message_opt);
+// This statement is equivalent to assert.equal(true, !!guard,
+// message_opt);. To test strictly for the value true, use
+// assert.strictEqual(true, guard, message_opt);.
+
+function ok(value, message) {
+  if (!value) fail(value, true, message, '==', assert.ok);
+}
+assert.ok = ok;
+
+// 5. The equality assertion tests shallow, coercive equality with
+// ==.
+// assert.equal(actual, expected, message_opt);
+
+assert.equal = function equal(actual, expected, message) {
+  if (actual != expected) fail(actual, expected, message, '==', assert.equal);
+};
+
+// 6. The non-equality assertion tests for whether two objects are not equal
+// with != assert.notEqual(actual, expected, message_opt);
+
+assert.notEqual = function notEqual(actual, expected, message) {
+  if (actual == expected) {
+    fail(actual, expected, message, '!=', assert.notEqual);
+  }
+};
+
+// 7. The equivalence assertion tests a deep equality relation.
+// assert.deepEqual(actual, expected, message_opt);
+
+assert.deepEqual = function deepEqual(actual, expected, message) {
+  if (!_deepEqual(actual, expected, false)) {
+    fail(actual, expected, message, 'deepEqual', assert.deepEqual);
+  }
+};
+
+assert.deepStrictEqual = function deepStrictEqual(actual, expected, message) {
+  if (!_deepEqual(actual, expected, true)) {
+    fail(actual, expected, message, 'deepStrictEqual', assert.deepStrictEqual);
+  }
+};
+
+function _deepEqual(actual, expected, strict, memos) {
+  // 7.1. All identical values are equivalent, as determined by ===.
+  if (actual === expected) {
+    return true;
+  } else if (isBuffer(actual) && isBuffer(expected)) {
+    return compare(actual, expected) === 0;
+
+  // 7.2. If the expected value is a Date object, the actual value is
+  // equivalent if it is also a Date object that refers to the same time.
+  } else if (util.isDate(actual) && util.isDate(expected)) {
+    return actual.getTime() === expected.getTime();
+
+  // 7.3 If the expected value is a RegExp object, the actual value is
+  // equivalent if it is also a RegExp object with the same source and
+  // properties (`global`, `multiline`, `lastIndex`, `ignoreCase`).
+  } else if (util.isRegExp(actual) && util.isRegExp(expected)) {
+    return actual.source === expected.source &&
+           actual.global === expected.global &&
+           actual.multiline === expected.multiline &&
+           actual.lastIndex === expected.lastIndex &&
+           actual.ignoreCase === expected.ignoreCase;
+
+  // 7.4. Other pairs that do not both pass typeof value == 'object',
+  // equivalence is determined by ==.
+  } else if ((actual === null || typeof actual !== 'object') &&
+             (expected === null || typeof expected !== 'object')) {
+    return strict ? actual === expected : actual == expected;
+
+  // If both values are instances of typed arrays, wrap their underlying
+  // ArrayBuffers in a Buffer each to increase performance
+  // This optimization requires the arrays to have the same type as checked by
+  // Object.prototype.toString (aka pToString). Never perform binary
+  // comparisons for Float*Arrays, though, since e.g. +0 === -0 but their
+  // bit patterns are not identical.
+  } else if (isView(actual) && isView(expected) &&
+             pToString(actual) === pToString(expected) &&
+             !(actual instanceof Float32Array ||
+               actual instanceof Float64Array)) {
+    return compare(new Uint8Array(actual.buffer),
+                   new Uint8Array(expected.buffer)) === 0;
+
+  // 7.5 For all other Object pairs, including Array objects, equivalence is
+  // determined by having the same number of owned properties (as verified
+  // with Object.prototype.hasOwnProperty.call), the same set of keys
+  // (although not necessarily the same order), equivalent values for every
+  // corresponding key, and an identical 'prototype' property. Note: this
+  // accounts for both named and indexed properties on Arrays.
+  } else if (isBuffer(actual) !== isBuffer(expected)) {
+    return false;
+  } else {
+    memos = memos || {actual: [], expected: []};
+
+    var actualIndex = memos.actual.indexOf(actual);
+    if (actualIndex !== -1) {
+      if (actualIndex === memos.expected.indexOf(expected)) {
+        return true;
+      }
+    }
+
+    memos.actual.push(actual);
+    memos.expected.push(expected);
+
+    return objEquiv(actual, expected, strict, memos);
+  }
+}
+
+function isArguments(object) {
+  return Object.prototype.toString.call(object) == '[object Arguments]';
+}
+
+function objEquiv(a, b, strict, actualVisitedObjects) {
+  if (a === null || a === undefined || b === null || b === undefined)
+    return false;
+  // if one is a primitive, the other must be same
+  if (util.isPrimitive(a) || util.isPrimitive(b))
+    return a === b;
+  if (strict && Object.getPrototypeOf(a) !== Object.getPrototypeOf(b))
+    return false;
+  var aIsArgs = isArguments(a);
+  var bIsArgs = isArguments(b);
+  if ((aIsArgs && !bIsArgs) || (!aIsArgs && bIsArgs))
+    return false;
+  if (aIsArgs) {
+    a = pSlice.call(a);
+    b = pSlice.call(b);
+    return _deepEqual(a, b, strict);
+  }
+  var ka = objectKeys(a);
+  var kb = objectKeys(b);
+  var key, i;
+  // having the same number of owned properties (keys incorporates
+  // hasOwnProperty)
+  if (ka.length !== kb.length)
+    return false;
+  //the same set of keys (although not necessarily the same order),
+  ka.sort();
+  kb.sort();
+  //~~~cheap key test
+  for (i = ka.length - 1; i >= 0; i--) {
+    if (ka[i] !== kb[i])
+      return false;
+  }
+  //equivalent values for every corresponding key, and
+  //~~~possibly expensive deep test
+  for (i = ka.length - 1; i >= 0; i--) {
+    key = ka[i];
+    if (!_deepEqual(a[key], b[key], strict, actualVisitedObjects))
+      return false;
+  }
+  return true;
+}
+
+// 8. The non-equivalence assertion tests for any deep inequality.
+// assert.notDeepEqual(actual, expected, message_opt);
+
+assert.notDeepEqual = function notDeepEqual(actual, expected, message) {
+  if (_deepEqual(actual, expected, false)) {
+    fail(actual, expected, message, 'notDeepEqual', assert.notDeepEqual);
+  }
+};
+
+assert.notDeepStrictEqual = notDeepStrictEqual;
+function notDeepStrictEqual(actual, expected, message) {
+  if (_deepEqual(actual, expected, true)) {
+    fail(actual, expected, message, 'notDeepStrictEqual', notDeepStrictEqual);
+  }
+}
+
+
+// 9. The strict equality assertion tests strict equality, as determined by ===.
+// assert.strictEqual(actual, expected, message_opt);
+
+assert.strictEqual = function strictEqual(actual, expected, message) {
+  if (actual !== expected) {
+    fail(actual, expected, message, '===', assert.strictEqual);
+  }
+};
+
+// 10. The strict non-equality assertion tests for strict inequality, as
+// determined by !==.  assert.notStrictEqual(actual, expected, message_opt);
+
+assert.notStrictEqual = function notStrictEqual(actual, expected, message) {
+  if (actual === expected) {
+    fail(actual, expected, message, '!==', assert.notStrictEqual);
+  }
+};
+
+function expectedException(actual, expected) {
+  if (!actual || !expected) {
+    return false;
+  }
+
+  if (Object.prototype.toString.call(expected) == '[object RegExp]') {
+    return expected.test(actual);
+  }
+
+  try {
+    if (actual instanceof expected) {
+      return true;
+    }
+  } catch (e) {
+    // Ignore.  The instanceof check doesn't work for arrow functions.
+  }
+
+  if (Error.isPrototypeOf(expected)) {
+    return false;
+  }
+
+  return expected.call({}, actual) === true;
+}
+
+function _tryBlock(block) {
+  var error;
+  try {
+    block();
+  } catch (e) {
+    error = e;
+  }
+  return error;
+}
+
+function _throws(shouldThrow, block, expected, message) {
+  var actual;
+
+  if (typeof block !== 'function') {
+    throw new TypeError('"block" argument must be a function');
+  }
+
+  if (typeof expected === 'string') {
+    message = expected;
+    expected = null;
+  }
+
+  actual = _tryBlock(block);
+
+  message = (expected && expected.name ? ' (' + expected.name + ').' : '.') +
+            (message ? ' ' + message : '.');
+
+  if (shouldThrow && !actual) {
+    fail(actual, expected, 'Missing expected exception' + message);
+  }
+
+  var userProvidedMessage = typeof message === 'string';
+  var isUnwantedException = !shouldThrow && util.isError(actual);
+  var isUnexpectedException = !shouldThrow && actual && !expected;
+
+  if ((isUnwantedException &&
+      userProvidedMessage &&
+      expectedException(actual, expected)) ||
+      isUnexpectedException) {
+    fail(actual, expected, 'Got unwanted exception' + message);
+  }
+
+  if ((shouldThrow && actual && expected &&
+      !expectedException(actual, expected)) || (!shouldThrow && actual)) {
+    throw actual;
+  }
+}
+
+// 11. Expected to throw an error:
+// assert.throws(block, Error_opt, message_opt);
+
+assert.throws = function(block, /*optional*/error, /*optional*/message) {
+  _throws(true, block, error, message);
+};
+
+// EXTENSION! This is annoying to write outside this module.
+assert.doesNotThrow = function(block, /*optional*/error, /*optional*/message) {
+  _throws(false, block, error, message);
+};
+
+assert.ifError = function(err) { if (err) throw err; };
+
+// Expose a strict only variant of assert
+function strict(value, message) {
+  if (!value) fail(value, true, message, '==', strict);
+}
+assert.strict = objectAssign(strict, assert, {
+  equal: assert.strictEqual,
+  deepEqual: assert.deepStrictEqual,
+  notEqual: assert.notStrictEqual,
+  notDeepEqual: assert.notDeepStrictEqual
+});
+assert.strict.strict = assert.strict;
+
+var objectKeys = Object.keys || function (obj) {
+  var keys = [];
+  for (var key in obj) {
+    if (hasOwn.call(obj, key)) keys.push(key);
+  }
+  return keys;
+};
+
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"object-assign":48,"util/":41}],39:[function(require,module,exports){
+arguments[4][12][0].apply(exports,arguments)
+},{"dup":12}],40:[function(require,module,exports){
+module.exports = function isBuffer(arg) {
+  return arg && typeof arg === 'object'
+    && typeof arg.copy === 'function'
+    && typeof arg.fill === 'function'
+    && typeof arg.readUInt8 === 'function';
+}
+},{}],41:[function(require,module,exports){
+(function (process,global){
+// Copyright Joyent, Inc. and other Node contributors.
+//
+// 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.
+
+var formatRegExp = /%[sdj%]/g;
+exports.format = function(f) {
+  if (!isString(f)) {
+    var objects = [];
+    for (var i = 0; i < arguments.length; i++) {
+      objects.push(inspect(arguments[i]));
+    }
+    return objects.join(' ');
+  }
+
+  var i = 1;
+  var args = arguments;
+  var len = args.length;
+  var str = String(f).replace(formatRegExp, function(x) {
+    if (x === '%%') return '%';
+    if (i >= len) return x;
+    switch (x) {
+      case '%s': return String(args[i++]);
+      case '%d': return Number(args[i++]);
+      case '%j':
+        try {
+          return JSON.stringify(args[i++]);
+        } catch (_) {
+          return '[Circular]';
+        }
+      default:
+        return x;
+    }
+  });
+  for (var x = args[i]; i < len; x = args[++i]) {
+    if (isNull(x) || !isObject(x)) {
+      str += ' ' + x;
+    } else {
+      str += ' ' + inspect(x);
+    }
+  }
+  return str;
+};
+
+
+// Mark that a method should not be used.
+// Returns a modified function which warns once by default.
+// If --no-deprecation is set, then it is a no-op.
+exports.deprecate = function(fn, msg) {
+  // Allow for deprecating things in the process of starting up.
+  if (isUndefined(global.process)) {
+    return function() {
+      return exports.deprecate(fn, msg).apply(this, arguments);
+    };
+  }
+
+  if (process.noDeprecation === true) {
+    return fn;
+  }
+
+  var warned = false;
+  function deprecated() {
+    if (!warned) {
+      if (process.throwDeprecation) {
+        throw new Error(msg);
+      } else if (process.traceDeprecation) {
+        console.trace(msg);
+      } else {
+        console.error(msg);
+      }
+      warned = true;
+    }
+    return fn.apply(this, arguments);
+  }
+
+  return deprecated;
+};
+
+
+var debugs = {};
+var debugEnviron;
+exports.debuglog = function(set) {
+  if (isUndefined(debugEnviron))
+    debugEnviron = process.env.NODE_DEBUG || '';
+  set = set.toUpperCase();
+  if (!debugs[set]) {
+    if (new RegExp('\\b' + set + '\\b', 'i').test(debugEnviron)) {
+      var pid = process.pid;
+      debugs[set] = function() {
+        var msg = exports.format.apply(exports, arguments);
+        console.error('%s %d: %s', set, pid, msg);
+      };
+    } else {
+      debugs[set] = function() {};
+    }
+  }
+  return debugs[set];
+};
+
+
+/**
+ * Echos the value of a value. Trys to print the value out
+ * in the best way possible given the different types.
+ *
+ * @param {Object} obj The object to print out.
+ * @param {Object} opts Optional options object that alters the output.
+ */
+/* legacy: obj, showHidden, depth, colors*/
+function inspect(obj, opts) {
+  // default options
+  var ctx = {
+    seen: [],
+    stylize: stylizeNoColor
+  };
+  // legacy...
+  if (arguments.length >= 3) ctx.depth = arguments[2];
+  if (arguments.length >= 4) ctx.colors = arguments[3];
+  if (isBoolean(opts)) {
+    // legacy...
+    ctx.showHidden = opts;
+  } else if (opts) {
+    // got an "options" object
+    exports._extend(ctx, opts);
+  }
+  // set default options
+  if (isUndefined(ctx.showHidden)) ctx.showHidden = false;
+  if (isUndefined(ctx.depth)) ctx.depth = 2;
+  if (isUndefined(ctx.colors)) ctx.colors = false;
+  if (isUndefined(ctx.customInspect)) ctx.customInspect = true;
+  if (ctx.colors) ctx.stylize = stylizeWithColor;
+  return formatValue(ctx, obj, ctx.depth);
+}
+exports.inspect = inspect;
+
+
+// http://en.wikipedia.org/wiki/ANSI_escape_code#graphics
+inspect.colors = {
+  'bold' : [1, 22],
+  'italic' : [3, 23],
+  'underline' : [4, 24],
+  'inverse' : [7, 27],
+  'white' : [37, 39],
+  'grey' : [90, 39],
+  'black' : [30, 39],
+  'blue' : [34, 39],
+  'cyan' : [36, 39],
+  'green' : [32, 39],
+  'magenta' : [35, 39],
+  'red' : [31, 39],
+  'yellow' : [33, 39]
+};
+
+// Don't use 'blue' not visible on cmd.exe
+inspect.styles = {
+  'special': 'cyan',
+  'number': 'yellow',
+  'boolean': 'yellow',
+  'undefined': 'grey',
+  'null': 'bold',
+  'string': 'green',
+  'date': 'magenta',
+  // "name": intentionally not styling
+  'regexp': 'red'
+};
+
+
+function stylizeWithColor(str, styleType) {
+  var style = inspect.styles[styleType];
+
+  if (style) {
+    return '\u001b[' + inspect.colors[style][0] + 'm' + str +
+           '\u001b[' + inspect.colors[style][1] + 'm';
+  } else {
+    return str;
+  }
+}
+
+
+function stylizeNoColor(str, styleType) {
+  return str;
+}
+
+
+function arrayToHash(array) {
+  var hash = {};
+
+  array.forEach(function(val, idx) {
+    hash[val] = true;
+  });
+
+  return hash;
+}
+
+
+function formatValue(ctx, value, recurseTimes) {
+  // Provide a hook for user-specified inspect functions.
+  // Check that value is an object with an inspect function on it
+  if (ctx.customInspect &&
+      value &&
+      isFunction(value.inspect) &&
+      // Filter out the util module, it's inspect function is special
+      value.inspect !== exports.inspect &&
+      // Also filter out any prototype objects using the circular check.
+      !(value.constructor && value.constructor.prototype === value)) {
+    var ret = value.inspect(recurseTimes, ctx);
+    if (!isString(ret)) {
+      ret = formatValue(ctx, ret, recurseTimes);
+    }
+    return ret;
+  }
+
+  // Primitive types cannot have properties
+  var primitive = formatPrimitive(ctx, value);
+  if (primitive) {
+    return primitive;
+  }
+
+  // Look up the keys of the object.
+  var keys = Object.keys(value);
+  var visibleKeys = arrayToHash(keys);
+
+  if (ctx.showHidden) {
+    keys = Object.getOwnPropertyNames(value);
+  }
+
+  // IE doesn't make error fields non-enumerable
+  // http://msdn.microsoft.com/en-us/library/ie/dww52sbt(v=vs.94).aspx
+  if (isError(value)
+      && (keys.indexOf('message') >= 0 || keys.indexOf('description') >= 0)) {
+    return formatError(value);
+  }
+
+  // Some type of object without properties can be shortcutted.
+  if (keys.length === 0) {
+    if (isFunction(value)) {
+      var name = value.name ? ': ' + value.name : '';
+      return ctx.stylize('[Function' + name + ']', 'special');
+    }
+    if (isRegExp(value)) {
+      return ctx.stylize(RegExp.prototype.toString.call(value), 'regexp');
+    }
+    if (isDate(value)) {
+      return ctx.stylize(Date.prototype.toString.call(value), 'date');
+    }
+    if (isError(value)) {
+      return formatError(value);
+    }
+  }
+
+  var base = '', array = false, braces = ['{', '}'];
+
+  // Make Array say that they are Array
+  if (isArray(value)) {
+    array = true;
+    braces = ['[', ']'];
+  }
+
+  // Make functions say that they are functions
+  if (isFunction(value)) {
+    var n = value.name ? ': ' + value.name : '';
+    base = ' [Function' + n + ']';
+  }
+
+  // Make RegExps say that they are RegExps
+  if (isRegExp(value)) {
+    base = ' ' + RegExp.prototype.toString.call(value);
+  }
+
+  // Make dates with properties first say the date
+  if (isDate(value)) {
+    base = ' ' + Date.prototype.toUTCString.call(value);
+  }
+
+  // Make error with message first say the error
+  if (isError(value)) {
+    base = ' ' + formatError(value);
+  }
+
+  if (keys.length === 0 && (!array || value.length == 0)) {
+    return braces[0] + base + braces[1];
+  }
+
+  if (recurseTimes < 0) {
+    if (isRegExp(value)) {
+      return ctx.stylize(RegExp.prototype.toString.call(value), 'regexp');
+    } else {
+      return ctx.stylize('[Object]', 'special');
+    }
+  }
+
+  ctx.seen.push(value);
+
+  var output;
+  if (array) {
+    output = formatArray(ctx, value, recurseTimes, visibleKeys, keys);
+  } else {
+    output = keys.map(function(key) {
+      return formatProperty(ctx, value, recurseTimes, visibleKeys, key, array);
+    });
+  }
+
+  ctx.seen.pop();
+
+  return reduceToSingleString(output, base, braces);
+}
+
+
+function formatPrimitive(ctx, value) {
+  if (isUndefined(value))
+    return ctx.stylize('undefined', 'undefined');
+  if (isString(value)) {
+    var simple = '\'' + JSON.stringify(value).replace(/^"|"$/g, '')
+                                             .replace(/'/g, "\\'")
+                                             .replace(/\\"/g, '"') + '\'';
+    return ctx.stylize(simple, 'string');
+  }
+  if (isNumber(value))
+    return ctx.stylize('' + value, 'number');
+  if (isBoolean(value))
+    return ctx.stylize('' + value, 'boolean');
+  // For some reason typeof null is "object", so special case here.
+  if (isNull(value))
+    return ctx.stylize('null', 'null');
+}
+
+
+function formatError(value) {
+  return '[' + Error.prototype.toString.call(value) + ']';
+}
+
+
+function formatArray(ctx, value, recurseTimes, visibleKeys, keys) {
+  var output = [];
+  for (var i = 0, l = value.length; i < l; ++i) {
+    if (hasOwnProperty(value, String(i))) {
+      output.push(formatProperty(ctx, value, recurseTimes, visibleKeys,
+          String(i), true));
+    } else {
+      output.push('');
+    }
+  }
+  keys.forEach(function(key) {
+    if (!key.match(/^\d+$/)) {
+      output.push(formatProperty(ctx, value, recurseTimes, visibleKeys,
+          key, true));
+    }
+  });
+  return output;
+}
+
+
+function formatProperty(ctx, value, recurseTimes, visibleKeys, key, array) {
+  var name, str, desc;
+  desc = Object.getOwnPropertyDescriptor(value, key) || { value: value[key] };
+  if (desc.get) {
+    if (desc.set) {
+      str = ctx.stylize('[Getter/Setter]', 'special');
+    } else {
+      str = ctx.stylize('[Getter]', 'special');
+    }
+  } else {
+    if (desc.set) {
+      str = ctx.stylize('[Setter]', 'special');
+    }
+  }
+  if (!hasOwnProperty(visibleKeys, key)) {
+    name = '[' + key + ']';
+  }
+  if (!str) {
+    if (ctx.seen.indexOf(desc.value) < 0) {
+      if (isNull(recurseTimes)) {
+        str = formatValue(ctx, desc.value, null);
+      } else {
+        str = formatValue(ctx, desc.value, recurseTimes - 1);
+      }
+      if (str.indexOf('\n') > -1) {
+        if (array) {
+          str = str.split('\n').map(function(line) {
+            return '  ' + line;
+          }).join('\n').substr(2);
+        } else {
+          str = '\n' + str.split('\n').map(function(line) {
+            return '   ' + line;
+          }).join('\n');
+        }
+      }
+    } else {
+      str = ctx.stylize('[Circular]', 'special');
+    }
+  }
+  if (isUndefined(name)) {
+    if (array && key.match(/^\d+$/)) {
+      return str;
+    }
+    name = JSON.stringify('' + key);
+    if (name.match(/^"([a-zA-Z_][a-zA-Z_0-9]*)"$/)) {
+      name = name.substr(1, name.length - 2);
+      name = ctx.stylize(name, 'name');
+    } else {
+      name = name.replace(/'/g, "\\'")
+                 .replace(/\\"/g, '"')
+                 .replace(/(^"|"$)/g, "'");
+      name = ctx.stylize(name, 'string');
+    }
+  }
+
+  return name + ': ' + str;
+}
+
+
+function reduceToSingleString(output, base, braces) {
+  var numLinesEst = 0;
+  var length = output.reduce(function(prev, cur) {
+    numLinesEst++;
+    if (cur.indexOf('\n') >= 0) numLinesEst++;
+    return prev + cur.replace(/\u001b\[\d\d?m/g, '').length + 1;
+  }, 0);
+
+  if (length > 60) {
+    return braces[0] +
+           (base === '' ? '' : base + '\n ') +
+           ' ' +
+           output.join(',\n  ') +
+           ' ' +
+           braces[1];
+  }
+
+  return braces[0] + base + ' ' + output.join(', ') + ' ' + braces[1];
+}
+
+
+// NOTE: These type checking functions intentionally don't use `instanceof`
+// because it is fragile and can be easily faked with `Object.create()`.
+function isArray(ar) {
+  return Array.isArray(ar);
+}
+exports.isArray = isArray;
+
+function isBoolean(arg) {
+  return typeof arg === 'boolean';
+}
+exports.isBoolean = isBoolean;
+
+function isNull(arg) {
+  return arg === null;
+}
+exports.isNull = isNull;
+
+function isNullOrUndefined(arg) {
+  return arg == null;
+}
+exports.isNullOrUndefined = isNullOrUndefined;
+
+function isNumber(arg) {
+  return typeof arg === 'number';
+}
+exports.isNumber = isNumber;
+
+function isString(arg) {
+  return typeof arg === 'string';
+}
+exports.isString = isString;
+
+function isSymbol(arg) {
+  return typeof arg === 'symbol';
+}
+exports.isSymbol = isSymbol;
+
+function isUndefined(arg) {
+  return arg === void 0;
+}
+exports.isUndefined = isUndefined;
+
+function isRegExp(re) {
+  return isObject(re) && objectToString(re) === '[object RegExp]';
+}
+exports.isRegExp = isRegExp;
+
+function isObject(arg) {
+  return typeof arg === 'object' && arg !== null;
+}
+exports.isObject = isObject;
+
+function isDate(d) {
+  return isObject(d) && objectToString(d) === '[object Date]';
+}
+exports.isDate = isDate;
+
+function isError(e) {
+  return isObject(e) &&
+      (objectToString(e) === '[object Error]' || e instanceof Error);
+}
+exports.isError = isError;
+
+function isFunction(arg) {
+  return typeof arg === 'function';
+}
+exports.isFunction = isFunction;
+
+function isPrimitive(arg) {
+  return arg === null ||
+         typeof arg === 'boolean' ||
+         typeof arg === 'number' ||
+         typeof arg === 'string' ||
+         typeof arg === 'symbol' ||  // ES6 symbol
+         typeof arg === 'undefined';
+}
+exports.isPrimitive = isPrimitive;
+
+exports.isBuffer = require('./support/isBuffer');
+
+function objectToString(o) {
+  return Object.prototype.toString.call(o);
+}
+
+
+function pad(n) {
+  return n < 10 ? '0' + n.toString(10) : n.toString(10);
+}
+
+
+var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep',
+              'Oct', 'Nov', 'Dec'];
+
+// 26 Feb 16:19:34
+function timestamp() {
+  var d = new Date();
+  var time = [pad(d.getHours()),
+              pad(d.getMinutes()),
+              pad(d.getSeconds())].join(':');
+  return [d.getDate(), months[d.getMonth()], time].join(' ');
+}
+
+
+// log is just a thin wrapper to console.log that prepends a timestamp
+exports.log = function() {
+  console.log('%s - %s', timestamp(), exports.format.apply(exports, arguments));
+};
+
+
+/**
+ * Inherit the prototype methods from one constructor into another.
+ *
+ * The Function.prototype.inherits from lang.js rewritten as a standalone
+ * function (not on Function.prototype). NOTE: If this file is to be loaded
+ * during bootstrapping this function needs to be rewritten using some native
+ * functions as prototype setup using normal JavaScript does not work as
+ * expected during bootstrapping (see mirror.js in r114903).
+ *
+ * @param {function} ctor Constructor function which needs to inherit the
+ *     prototype.
+ * @param {function} superCtor Constructor function to inherit prototype from.
+ */
+exports.inherits = require('inherits');
+
+exports._extend = function(origin, add) {
+  // Don't do anything if add isn't an object
+  if (!add || !isObject(add)) return origin;
+
+  var keys = Object.keys(add);
+  var i = keys.length;
+  while (i--) {
+    origin[keys[i]] = add[keys[i]];
+  }
+  return origin;
+};
+
+function hasOwnProperty(obj, prop) {
+  return Object.prototype.hasOwnProperty.call(obj, prop);
+}
+
+}).call(this,require('_process'),typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"./support/isBuffer":40,"_process":49,"inherits":39}],42:[function(require,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
+}
+
+// Support decoding URL-safe base64 strings, as Node.js does.
+// See: https://en.wikipedia.org/wiki/Base64#URL_applications
+revLookup['-'.charCodeAt(0)] = 62
+revLookup['_'.charCodeAt(0)] = 63
+
+function getLens (b64) {
+  var len = b64.length
+
+  if (len % 4 > 0) {
+    throw new Error('Invalid string. Length must be a multiple of 4')
+  }
+
+  // Trim off extra bytes after placeholder bytes are found
+  // See: https://github.com/beatgammit/base64-js/issues/42
+  var validLen = b64.indexOf('=')
+  if (validLen === -1) validLen = len
+
+  var placeHoldersLen = validLen === len
+    ? 0
+    : 4 - (validLen % 4)
+
+  return [validLen, placeHoldersLen]
+}
+
+// base64 is 4/3 + up to two characters of the original data
+function byteLength (b64) {
+  var lens = getLens(b64)
+  var validLen = lens[0]
+  var placeHoldersLen = lens[1]
+  return ((validLen + placeHoldersLen) * 3 / 4) - placeHoldersLen
+}
+
+function _byteLength (b64, validLen, placeHoldersLen) {
+  return ((validLen + placeHoldersLen) * 3 / 4) - placeHoldersLen
+}
+
+function toByteArray (b64) {
+  var tmp
+  var lens = getLens(b64)
+  var validLen = lens[0]
+  var placeHoldersLen = lens[1]
+
+  var arr = new Arr(_byteLength(b64, validLen, placeHoldersLen))
+
+  var curByte = 0
+
+  // if there are placeholders, only get up to the last complete 4 chars
+  var len = placeHoldersLen > 0
+    ? validLen - 4
+    : validLen
+
+  var i
+  for (i = 0; i < len; i += 4) {
+    tmp =
+      (revLookup[b64.charCodeAt(i)] << 18) |
+      (revLookup[b64.charCodeAt(i + 1)] << 12) |
+      (revLookup[b64.charCodeAt(i + 2)] << 6) |
+      revLookup[b64.charCodeAt(i + 3)]
+    arr[curByte++] = (tmp >> 16) & 0xFF
+    arr[curByte++] = (tmp >> 8) & 0xFF
+    arr[curByte++] = tmp & 0xFF
+  }
+
+  if (placeHoldersLen === 2) {
+    tmp =
+      (revLookup[b64.charCodeAt(i)] << 2) |
+      (revLookup[b64.charCodeAt(i + 1)] >> 4)
+    arr[curByte++] = tmp & 0xFF
+  }
+
+  if (placeHoldersLen === 1) {
+    tmp =
+      (revLookup[b64.charCodeAt(i)] << 10) |
+      (revLookup[b64.charCodeAt(i + 1)] << 4) |
+      (revLookup[b64.charCodeAt(i + 2)] >> 2)
+    arr[curByte++] = (tmp >> 8) & 0xFF
+    arr[curByte++] = 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) & 0xFF0000) +
+      ((uint8[i + 1] << 8) & 0xFF00) +
+      (uint8[i + 2] & 0xFF)
+    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 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]
+    parts.push(
+      lookup[tmp >> 2] +
+      lookup[(tmp << 4) & 0x3F] +
+      '=='
+    )
+  } else if (extraBytes === 2) {
+    tmp = (uint8[len - 2] << 8) + uint8[len - 1]
+    parts.push(
+      lookup[tmp >> 10] +
+      lookup[(tmp >> 4) & 0x3F] +
+      lookup[(tmp << 2) & 0x3F] +
+      '='
+    )
+  }
+
+  return parts.join('')
+}
+
+},{}],43:[function(require,module,exports){
+
+},{}],44:[function(require,module,exports){
+(function (Buffer){
+/*!
+ * The buffer module from node.js, for the browser.
+ *
+ * @author   Feross Aboukhadijeh <https://feross.org>
+ * @license  MIT
+ */
+/* eslint-disable no-proto */
+
+'use strict'
+
+var base64 = require('base64-js')
+var ieee754 = require('ieee754')
+var customInspectSymbol =
+  (typeof Symbol === 'function' && typeof Symbol.for === 'function')
+    ? Symbol.for('nodejs.util.inspect.custom')
+    : null
+
+exports.Buffer = Buffer
+exports.SlowBuffer = SlowBuffer
+exports.INSPECT_MAX_BYTES = 50
+
+var K_MAX_LENGTH = 0x7fffffff
+exports.kMaxLength = K_MAX_LENGTH
+
+/**
+ * If `Buffer.TYPED_ARRAY_SUPPORT`:
+ *   === true    Use Uint8Array implementation (fastest)
+ *   === false   Print warning and recommend using `buffer` v4.x which has an Object
+ *               implementation (most compatible, even IE6)
+ *
+ * Browsers that support typed arrays are IE 10+, Firefox 4+, Chrome 7+, Safari 5.1+,
+ * Opera 11.6+, iOS 4.2+.
+ *
+ * We report that the browser does not support typed arrays if the are not subclassable
+ * using __proto__. Firefox 4-29 lacks support for adding new properties to `Uint8Array`
+ * (See: https://bugzilla.mozilla.org/show_bug.cgi?id=695438). IE 10 lacks support
+ * for __proto__ and has a buggy typed array implementation.
+ */
+Buffer.TYPED_ARRAY_SUPPORT = typedArraySupport()
+
+if (!Buffer.TYPED_ARRAY_SUPPORT && typeof console !== 'undefined' &&
+    typeof console.error === 'function') {
+  console.error(
+    'This browser lacks typed array (Uint8Array) support which is required by ' +
+    '`buffer` v5.x. Use `buffer` v4.x if you require old browser support.'
+  )
+}
+
+function typedArraySupport () {
+  // Can typed array instances can be augmented?
+  try {
+    var arr = new Uint8Array(1)
+    var proto = { foo: function () { return 42 } }
+    Object.setPrototypeOf(proto, Uint8Array.prototype)
+    Object.setPrototypeOf(arr, proto)
+    return arr.foo() === 42
+  } catch (e) {
+    return false
+  }
+}
+
+Object.defineProperty(Buffer.prototype, 'parent', {
+  enumerable: true,
+  get: function () {
+    if (!Buffer.isBuffer(this)) return undefined
+    return this.buffer
+  }
+})
+
+Object.defineProperty(Buffer.prototype, 'offset', {
+  enumerable: true,
+  get: function () {
+    if (!Buffer.isBuffer(this)) return undefined
+    return this.byteOffset
+  }
+})
+
+function createBuffer (length) {
+  if (length > K_MAX_LENGTH) {
+    throw new RangeError('The value "' + length + '" is invalid for option "size"')
+  }
+  // Return an augmented `Uint8Array` instance
+  var buf = new Uint8Array(length)
+  Object.setPrototypeOf(buf, Buffer.prototype)
+  return buf
+}
+
+/**
+ * The Buffer constructor returns instances of `Uint8Array` that have their
+ * prototype changed to `Buffer.prototype`. Furthermore, `Buffer` is a subclass of
+ * `Uint8Array`, so the returned instances will have all the node `Buffer` methods
+ * and the `Uint8Array` methods. Square bracket notation works as expected -- it
+ * returns a single octet.
+ *
+ * The `Uint8Array` prototype remains unmodified.
+ */
+
+function Buffer (arg, encodingOrOffset, length) {
+  // Common case.
+  if (typeof arg === 'number') {
+    if (typeof encodingOrOffset === 'string') {
+      throw new TypeError(
+        'The "string" argument must be of type string. Received type number'
+      )
+    }
+    return allocUnsafe(arg)
+  }
+  return from(arg, encodingOrOffset, length)
+}
+
+// Fix subarray() in ES2016. See: https://github.com/feross/buffer/pull/97
+if (typeof Symbol !== 'undefined' && Symbol.species != null &&
+    Buffer[Symbol.species] === Buffer) {
+  Object.defineProperty(Buffer, Symbol.species, {
+    value: null,
+    configurable: true,
+    enumerable: false,
+    writable: false
+  })
+}
+
+Buffer.poolSize = 8192 // not used by this implementation
+
+function from (value, encodingOrOffset, length) {
+  if (typeof value === 'string') {
+    return fromString(value, encodingOrOffset)
+  }
+
+  if (ArrayBuffer.isView(value)) {
+    return fromArrayLike(value)
+  }
+
+  if (value == null) {
+    throw new TypeError(
+      'The first argument must be one of type string, Buffer, ArrayBuffer, Array, ' +
+      'or Array-like Object. Received type ' + (typeof value)
+    )
+  }
+
+  if (isInstance(value, ArrayBuffer) ||
+      (value && isInstance(value.buffer, ArrayBuffer))) {
+    return fromArrayBuffer(value, encodingOrOffset, length)
+  }
+
+  if (typeof value === 'number') {
+    throw new TypeError(
+      'The "value" argument must not be of type number. Received type number'
+    )
+  }
+
+  var valueOf = value.valueOf && value.valueOf()
+  if (valueOf != null && valueOf !== value) {
+    return Buffer.from(valueOf, encodingOrOffset, length)
+  }
+
+  var b = fromObject(value)
+  if (b) return b
+
+  if (typeof Symbol !== 'undefined' && Symbol.toPrimitive != null &&
+      typeof value[Symbol.toPrimitive] === 'function') {
+    return Buffer.from(
+      value[Symbol.toPrimitive]('string'), encodingOrOffset, length
+    )
+  }
+
+  throw new TypeError(
+    'The first argument must be one of type string, Buffer, ArrayBuffer, Array, ' +
+    'or Array-like Object. Received type ' + (typeof value)
+  )
+}
+
+/**
+ * Functionally equivalent to Buffer(arg, encoding) but throws a TypeError
+ * if value is a number.
+ * Buffer.from(str[, encoding])
+ * Buffer.from(array)
+ * Buffer.from(buffer)
+ * Buffer.from(arrayBuffer[, byteOffset[, length]])
+ **/
+Buffer.from = function (value, encodingOrOffset, length) {
+  return from(value, encodingOrOffset, length)
+}
+
+// Note: Change prototype *after* Buffer.from is defined to workaround Chrome bug:
+// https://github.com/feross/buffer/pull/148
+Object.setPrototypeOf(Buffer.prototype, Uint8Array.prototype)
+Object.setPrototypeOf(Buffer, Uint8Array)
+
+function assertSize (size) {
+  if (typeof size !== 'number') {
+    throw new TypeError('"size" argument must be of type number')
+  } else if (size < 0) {
+    throw new RangeError('The value "' + size + '" is invalid for option "size"')
+  }
+}
+
+function alloc (size, fill, encoding) {
+  assertSize(size)
+  if (size <= 0) {
+    return createBuffer(size)
+  }
+  if (fill !== undefined) {
+    // Only pay attention to encoding if it's a string. This
+    // prevents accidentally sending in a number that would
+    // be interpretted as a start offset.
+    return typeof encoding === 'string'
+      ? createBuffer(size).fill(fill, encoding)
+      : createBuffer(size).fill(fill)
+  }
+  return createBuffer(size)
+}
+
+/**
+ * Creates a new filled Buffer instance.
+ * alloc(size[, fill[, encoding]])
+ **/
+Buffer.alloc = function (size, fill, encoding) {
+  return alloc(size, fill, encoding)
+}
+
+function allocUnsafe (size) {
+  assertSize(size)
+  return createBuffer(size < 0 ? 0 : checked(size) | 0)
+}
+
+/**
+ * Equivalent to Buffer(num), by default creates a non-zero-filled Buffer instance.
+ * */
+Buffer.allocUnsafe = function (size) {
+  return allocUnsafe(size)
+}
+/**
+ * Equivalent to SlowBuffer(num), by default creates a non-zero-filled Buffer instance.
+ */
+Buffer.allocUnsafeSlow = function (size) {
+  return allocUnsafe(size)
+}
+
+function fromString (string, encoding) {
+  if (typeof encoding !== 'string' || encoding === '') {
+    encoding = 'utf8'
+  }
+
+  if (!Buffer.isEncoding(encoding)) {
+    throw new TypeError('Unknown encoding: ' + encoding)
+  }
+
+  var length = byteLength(string, encoding) | 0
+  var buf = createBuffer(length)
+
+  var actual = buf.write(string, encoding)
+
+  if (actual !== length) {
+    // Writing a hex string, for example, that contains invalid characters will
+    // cause everything after the first invalid character to be ignored. (e.g.
+    // 'abxxcd' will be treated as 'ab')
+    buf = buf.slice(0, actual)
+  }
+
+  return buf
+}
+
+function fromArrayLike (array) {
+  var length = array.length < 0 ? 0 : checked(array.length) | 0
+  var buf = createBuffer(length)
+  for (var i = 0; i < length; i += 1) {
+    buf[i] = array[i] & 255
+  }
+  return buf
+}
+
+function fromArrayBuffer (array, byteOffset, length) {
+  if (byteOffset < 0 || array.byteLength < byteOffset) {
+    throw new RangeError('"offset" is outside of buffer bounds')
+  }
+
+  if (array.byteLength < byteOffset + (length || 0)) {
+    throw new RangeError('"length" is outside of buffer bounds')
+  }
+
+  var buf
+  if (byteOffset === undefined && length === undefined) {
+    buf = new Uint8Array(array)
+  } else if (length === undefined) {
+    buf = new Uint8Array(array, byteOffset)
+  } else {
+    buf = new Uint8Array(array, byteOffset, length)
+  }
+
+  // Return an augmented `Uint8Array` instance
+  Object.setPrototypeOf(buf, Buffer.prototype)
+
+  return buf
+}
+
+function fromObject (obj) {
+  if (Buffer.isBuffer(obj)) {
+    var len = checked(obj.length) | 0
+    var buf = createBuffer(len)
+
+    if (buf.length === 0) {
+      return buf
+    }
+
+    obj.copy(buf, 0, 0, len)
+    return buf
+  }
+
+  if (obj.length !== undefined) {
+    if (typeof obj.length !== 'number' || numberIsNaN(obj.length)) {
+      return createBuffer(0)
+    }
+    return fromArrayLike(obj)
+  }
+
+  if (obj.type === 'Buffer' && Array.isArray(obj.data)) {
+    return fromArrayLike(obj.data)
+  }
+}
+
+function checked (length) {
+  // Note: cannot use `length < K_MAX_LENGTH` here because that fails when
+  // length is NaN (which is otherwise coerced to zero.)
+  if (length >= K_MAX_LENGTH) {
+    throw new RangeError('Attempt to allocate Buffer larger than maximum ' +
+                         'size: 0x' + K_MAX_LENGTH.toString(16) + ' bytes')
+  }
+  return length | 0
+}
+
+function SlowBuffer (length) {
+  if (+length != length) { // eslint-disable-line eqeqeq
+    length = 0
+  }
+  return Buffer.alloc(+length)
+}
+
+Buffer.isBuffer = function isBuffer (b) {
+  return b != null && b._isBuffer === true &&
+    b !== Buffer.prototype // so Buffer.isBuffer(Buffer.prototype) will be false
+}
+
+Buffer.compare = function compare (a, b) {
+  if (isInstance(a, Uint8Array)) a = Buffer.from(a, a.offset, a.byteLength)
+  if (isInstance(b, Uint8Array)) b = Buffer.from(b, b.offset, b.byteLength)
+  if (!Buffer.isBuffer(a) || !Buffer.isBuffer(b)) {
+    throw new TypeError(
+      'The "buf1", "buf2" arguments must be one of type Buffer or Uint8Array'
+    )
+  }
+
+  if (a === b) return 0
+
+  var x = a.length
+  var y = b.length
+
+  for (var i = 0, len = Math.min(x, y); i < len; ++i) {
+    if (a[i] !== b[i]) {
+      x = a[i]
+      y = b[i]
+      break
+    }
+  }
+
+  if (x < y) return -1
+  if (y < x) return 1
+  return 0
+}
+
+Buffer.isEncoding = function isEncoding (encoding) {
+  switch (String(encoding).toLowerCase()) {
+    case 'hex':
+    case 'utf8':
+    case 'utf-8':
+    case 'ascii':
+    case 'latin1':
+    case 'binary':
+    case 'base64':
+    case 'ucs2':
+    case 'ucs-2':
+    case 'utf16le':
+    case 'utf-16le':
+      return true
+    default:
+      return false
+  }
+}
+
+Buffer.concat = function concat (list, length) {
+  if (!Array.isArray(list)) {
+    throw new TypeError('"list" argument must be an Array of Buffers')
+  }
+
+  if (list.length === 0) {
+    return Buffer.alloc(0)
+  }
+
+  var i
+  if (length === undefined) {
+    length = 0
+    for (i = 0; i < list.length; ++i) {
+      length += list[i].length
+    }
+  }
+
+  var buffer = Buffer.allocUnsafe(length)
+  var pos = 0
+  for (i = 0; i < list.length; ++i) {
+    var buf = list[i]
+    if (isInstance(buf, Uint8Array)) {
+      buf = Buffer.from(buf)
+    }
+    if (!Buffer.isBuffer(buf)) {
+      throw new TypeError('"list" argument must be an Array of Buffers')
+    }
+    buf.copy(buffer, pos)
+    pos += buf.length
+  }
+  return buffer
+}
+
+function byteLength (string, encoding) {
+  if (Buffer.isBuffer(string)) {
+    return string.length
+  }
+  if (ArrayBuffer.isView(string) || isInstance(string, ArrayBuffer)) {
+    return string.byteLength
+  }
+  if (typeof string !== 'string') {
+    throw new TypeError(
+      'The "string" argument must be one of type string, Buffer, or ArrayBuffer. ' +
+      'Received type ' + typeof string
+    )
+  }
+
+  var len = string.length
+  var mustMatch = (arguments.length > 2 && arguments[2] === true)
+  if (!mustMatch && len === 0) return 0
+
+  // Use a for loop to avoid recursion
+  var loweredCase = false
+  for (;;) {
+    switch (encoding) {
+      case 'ascii':
+      case 'latin1':
+      case 'binary':
+        return len
+      case 'utf8':
+      case 'utf-8':
+        return utf8ToBytes(string).length
+      case 'ucs2':
+      case 'ucs-2':
+      case 'utf16le':
+      case 'utf-16le':
+        return len * 2
+      case 'hex':
+        return len >>> 1
+      case 'base64':
+        return base64ToBytes(string).length
+      default:
+        if (loweredCase) {
+          return mustMatch ? -1 : utf8ToBytes(string).length // assume utf8
+        }
+        encoding = ('' + encoding).toLowerCase()
+        loweredCase = true
+    }
+  }
+}
+Buffer.byteLength = byteLength
+
+function slowToString (encoding, start, end) {
+  var loweredCase = false
+
+  // No need to verify that "this.length <= MAX_UINT32" since it's a read-only
+  // property of a typed array.
+
+  // This behaves neither like String nor Uint8Array in that we set start/end
+  // to their upper/lower bounds if the value passed is out of range.
+  // undefined is handled specially as per ECMA-262 6th Edition,
+  // Section 13.3.3.7 Runtime Semantics: KeyedBindingInitialization.
+  if (start === undefined || start < 0) {
+    start = 0
+  }
+  // Return early if start > this.length. Done here to prevent potential uint32
+  // coercion fail below.
+  if (start > this.length) {
+    return ''
+  }
+
+  if (end === undefined || end > this.length) {
+    end = this.length
+  }
+
+  if (end <= 0) {
+    return ''
+  }
+
+  // Force coersion to uint32. This will also coerce falsey/NaN values to 0.
+  end >>>= 0
+  start >>>= 0
+
+  if (end <= start) {
+    return ''
+  }
+
+  if (!encoding) encoding = 'utf8'
+
+  while (true) {
+    switch (encoding) {
+      case 'hex':
+        return hexSlice(this, start, end)
+
+      case 'utf8':
+      case 'utf-8':
+        return utf8Slice(this, start, end)
+
+      case 'ascii':
+        return asciiSlice(this, start, end)
+
+      case 'latin1':
+      case 'binary':
+        return latin1Slice(this, start, end)
+
+      case 'base64':
+        return base64Slice(this, start, end)
+
+      case 'ucs2':
+      case 'ucs-2':
+      case 'utf16le':
+      case 'utf-16le':
+        return utf16leSlice(this, start, end)
+
+      default:
+        if (loweredCase) throw new TypeError('Unknown encoding: ' + encoding)
+        encoding = (encoding + '').toLowerCase()
+        loweredCase = true
+    }
+  }
+}
+
+// This property is used by `Buffer.isBuffer` (and the `is-buffer` npm package)
+// to detect a Buffer instance. It's not possible to use `instanceof Buffer`
+// reliably in a browserify context because there could be multiple different
+// copies of the 'buffer' package in use. This method works even for Buffer
+// instances that were created from another copy of the `buffer` package.
+// See: https://github.com/feross/buffer/issues/154
+Buffer.prototype._isBuffer = true
+
+function swap (b, n, m) {
+  var i = b[n]
+  b[n] = b[m]
+  b[m] = i
+}
+
+Buffer.prototype.swap16 = function swap16 () {
+  var len = this.length
+  if (len % 2 !== 0) {
+    throw new RangeError('Buffer size must be a multiple of 16-bits')
+  }
+  for (var i = 0; i < len; i += 2) {
+    swap(this, i, i + 1)
+  }
+  return this
+}
+
+Buffer.prototype.swap32 = function swap32 () {
+  var len = this.length
+  if (len % 4 !== 0) {
+    throw new RangeError('Buffer size must be a multiple of 32-bits')
+  }
+  for (var i = 0; i < len; i += 4) {
+    swap(this, i, i + 3)
+    swap(this, i + 1, i + 2)
+  }
+  return this
+}
+
+Buffer.prototype.swap64 = function swap64 () {
+  var len = this.length
+  if (len % 8 !== 0) {
+    throw new RangeError('Buffer size must be a multiple of 64-bits')
+  }
+  for (var i = 0; i < len; i += 8) {
+    swap(this, i, i + 7)
+    swap(this, i + 1, i + 6)
+    swap(this, i + 2, i + 5)
+    swap(this, i + 3, i + 4)
+  }
+  return this
+}
+
+Buffer.prototype.toString = function toString () {
+  var length = this.length
+  if (length === 0) return ''
+  if (arguments.length === 0) return utf8Slice(this, 0, length)
+  return slowToString.apply(this, arguments)
+}
+
+Buffer.prototype.toLocaleString = Buffer.prototype.toString
+
+Buffer.prototype.equals = function equals (b) {
+  if (!Buffer.isBuffer(b)) throw new TypeError('Argument must be a Buffer')
+  if (this === b) return true
+  return Buffer.compare(this, b) === 0
+}
+
+Buffer.prototype.inspect = function inspect () {
+  var str = ''
+  var max = exports.INSPECT_MAX_BYTES
+  str = this.toString('hex', 0, max).replace(/(.{2})/g, '$1 ').trim()
+  if (this.length > max) str += ' ... '
+  return '<Buffer ' + str + '>'
+}
+if (customInspectSymbol) {
+  Buffer.prototype[customInspectSymbol] = Buffer.prototype.inspect
+}
+
+Buffer.prototype.compare = function compare (target, start, end, thisStart, thisEnd) {
+  if (isInstance(target, Uint8Array)) {
+    target = Buffer.from(target, target.offset, target.byteLength)
+  }
+  if (!Buffer.isBuffer(target)) {
+    throw new TypeError(
+      'The "target" argument must be one of type Buffer or Uint8Array. ' +
+      'Received type ' + (typeof target)
+    )
+  }
+
+  if (start === undefined) {
+    start = 0
+  }
+  if (end === undefined) {
+    end = target ? target.length : 0
+  }
+  if (thisStart === undefined) {
+    thisStart = 0
+  }
+  if (thisEnd === undefined) {
+    thisEnd = this.length
+  }
+
+  if (start < 0 || end > target.length || thisStart < 0 || thisEnd > this.length) {
+    throw new RangeError('out of range index')
+  }
+
+  if (thisStart >= thisEnd && start >= end) {
+    return 0
+  }
+  if (thisStart >= thisEnd) {
+    return -1
+  }
+  if (start >= end) {
+    return 1
+  }
+
+  start >>>= 0
+  end >>>= 0
+  thisStart >>>= 0
+  thisEnd >>>= 0
+
+  if (this === target) return 0
+
+  var x = thisEnd - thisStart
+  var y = end - start
+  var len = Math.min(x, y)
+
+  var thisCopy = this.slice(thisStart, thisEnd)
+  var targetCopy = target.slice(start, end)
+
+  for (var i = 0; i < len; ++i) {
+    if (thisCopy[i] !== targetCopy[i]) {
+      x = thisCopy[i]
+      y = targetCopy[i]
+      break
+    }
+  }
+
+  if (x < y) return -1
+  if (y < x) return 1
+  return 0
+}
+
+// Finds either the first index of `val` in `buffer` at offset >= `byteOffset`,
+// OR the last index of `val` in `buffer` at offset <= `byteOffset`.
+//
+// Arguments:
+// - buffer - a Buffer to search
+// - val - a string, Buffer, or number
+// - byteOffset - an index into `buffer`; will be clamped to an int32
+// - encoding - an optional encoding, relevant is val is a string
+// - dir - true for indexOf, false for lastIndexOf
+function bidirectionalIndexOf (buffer, val, byteOffset, encoding, dir) {
+  // Empty buffer means no match
+  if (buffer.length === 0) return -1
+
+  // Normalize byteOffset
+  if (typeof byteOffset === 'string') {
+    encoding = byteOffset
+    byteOffset = 0
+  } else if (byteOffset > 0x7fffffff) {
+    byteOffset = 0x7fffffff
+  } else if (byteOffset < -0x80000000) {
+    byteOffset = -0x80000000
+  }
+  byteOffset = +byteOffset // Coerce to Number.
+  if (numberIsNaN(byteOffset)) {
+    // byteOffset: it it's undefined, null, NaN, "foo", etc, search whole buffer
+    byteOffset = dir ? 0 : (buffer.length - 1)
+  }
+
+  // Normalize byteOffset: negative offsets start from the end of the buffer
+  if (byteOffset < 0) byteOffset = buffer.length + byteOffset
+  if (byteOffset >= buffer.length) {
+    if (dir) return -1
+    else byteOffset = buffer.length - 1
+  } else if (byteOffset < 0) {
+    if (dir) byteOffset = 0
+    else return -1
+  }
+
+  // Normalize val
+  if (typeof val === 'string') {
+    val = Buffer.from(val, encoding)
+  }
+
+  // Finally, search either indexOf (if dir is true) or lastIndexOf
+  if (Buffer.isBuffer(val)) {
+    // Special case: looking for empty string/buffer always fails
+    if (val.length === 0) {
+      return -1
+    }
+    return arrayIndexOf(buffer, val, byteOffset, encoding, dir)
+  } else if (typeof val === 'number') {
+    val = val & 0xFF // Search for a byte value [0-255]
+    if (typeof Uint8Array.prototype.indexOf === 'function') {
+      if (dir) {
+        return Uint8Array.prototype.indexOf.call(buffer, val, byteOffset)
+      } else {
+        return Uint8Array.prototype.lastIndexOf.call(buffer, val, byteOffset)
+      }
+    }
+    return arrayIndexOf(buffer, [val], byteOffset, encoding, dir)
+  }
+
+  throw new TypeError('val must be string, number or Buffer')
+}
+
+function arrayIndexOf (arr, val, byteOffset, encoding, dir) {
+  var indexSize = 1
+  var arrLength = arr.length
+  var valLength = val.length
+
+  if (encoding !== undefined) {
+    encoding = String(encoding).toLowerCase()
+    if (encoding === 'ucs2' || encoding === 'ucs-2' ||
+        encoding === 'utf16le' || encoding === 'utf-16le') {
+      if (arr.length < 2 || val.length < 2) {
+        return -1
+      }
+      indexSize = 2
+      arrLength /= 2
+      valLength /= 2
+      byteOffset /= 2
+    }
+  }
+
+  function read (buf, i) {
+    if (indexSize === 1) {
+      return buf[i]
+    } else {
+      return buf.readUInt16BE(i * indexSize)
+    }
+  }
+
+  var i
+  if (dir) {
+    var foundIndex = -1
+    for (i = byteOffset; i < arrLength; i++) {
+      if (read(arr, i) === read(val, foundIndex === -1 ? 0 : i - foundIndex)) {
+        if (foundIndex === -1) foundIndex = i
+        if (i - foundIndex + 1 === valLength) return foundIndex * indexSize
+      } else {
+        if (foundIndex !== -1) i -= i - foundIndex
+        foundIndex = -1
+      }
+    }
+  } else {
+    if (byteOffset + valLength > arrLength) byteOffset = arrLength - valLength
+    for (i = byteOffset; i >= 0; i--) {
+      var found = true
+      for (var j = 0; j < valLength; j++) {
+        if (read(arr, i + j) !== read(val, j)) {
+          found = false
+          break
+        }
+      }
+      if (found) return i
+    }
+  }
+
+  return -1
+}
+
+Buffer.prototype.includes = function includes (val, byteOffset, encoding) {
+  return this.indexOf(val, byteOffset, encoding) !== -1
+}
+
+Buffer.prototype.indexOf = function indexOf (val, byteOffset, encoding) {
+  return bidirectionalIndexOf(this, val, byteOffset, encoding, true)
+}
+
+Buffer.prototype.lastIndexOf = function lastIndexOf (val, byteOffset, encoding) {
+  return bidirectionalIndexOf(this, val, byteOffset, encoding, false)
+}
+
+function hexWrite (buf, string, offset, length) {
+  offset = Number(offset) || 0
+  var remaining = buf.length - offset
+  if (!length) {
+    length = remaining
+  } else {
+    length = Number(length)
+    if (length > remaining) {
+      length = remaining
+    }
+  }
+
+  var strLen = string.length
+
+  if (length > strLen / 2) {
+    length = strLen / 2
+  }
+  for (var i = 0; i < length; ++i) {
+    var parsed = parseInt(string.substr(i * 2, 2), 16)
+    if (numberIsNaN(parsed)) return i
+    buf[offset + i] = parsed
+  }
+  return i
+}
+
+function utf8Write (buf, string, offset, length) {
+  return blitBuffer(utf8ToBytes(string, buf.length - offset), buf, offset, length)
+}
+
+function asciiWrite (buf, string, offset, length) {
+  return blitBuffer(asciiToBytes(string), buf, offset, length)
+}
+
+function latin1Write (buf, string, offset, length) {
+  return asciiWrite(buf, string, offset, length)
+}
+
+function base64Write (buf, string, offset, length) {
+  return blitBuffer(base64ToBytes(string), buf, offset, length)
+}
+
+function ucs2Write (buf, string, offset, length) {
+  return blitBuffer(utf16leToBytes(string, buf.length - offset), buf, offset, length)
+}
+
+Buffer.prototype.write = function write (string, offset, length, encoding) {
+  // Buffer#write(string)
+  if (offset === undefined) {
+    encoding = 'utf8'
+    length = this.length
+    offset = 0
+  // Buffer#write(string, encoding)
+  } else if (length === undefined && typeof offset === 'string') {
+    encoding = offset
+    length = this.length
+    offset = 0
+  // Buffer#write(string, offset[, length][, encoding])
+  } else if (isFinite(offset)) {
+    offset = offset >>> 0
+    if (isFinite(length)) {
+      length = length >>> 0
+      if (encoding === undefined) encoding = 'utf8'
+    } else {
+      encoding = length
+      length = undefined
+    }
+  } else {
+    throw new Error(
+      'Buffer.write(string, encoding, offset[, length]) is no longer supported'
+    )
+  }
+
+  var remaining = this.length - offset
+  if (length === undefined || length > remaining) length = remaining
+
+  if ((string.length > 0 && (length < 0 || offset < 0)) || offset > this.length) {
+    throw new RangeError('Attempt to write outside buffer bounds')
+  }
+
+  if (!encoding) encoding = 'utf8'
+
+  var loweredCase = false
+  for (;;) {
+    switch (encoding) {
+      case 'hex':
+        return hexWrite(this, string, offset, length)
+
+      case 'utf8':
+      case 'utf-8':
+        return utf8Write(this, string, offset, length)
+
+      case 'ascii':
+        return asciiWrite(this, string, offset, length)
+
+      case 'latin1':
+      case 'binary':
+        return latin1Write(this, string, offset, length)
+
+      case 'base64':
+        // Warning: maxLength not taken into account in base64Write
+        return base64Write(this, string, offset, length)
+
+      case 'ucs2':
+      case 'ucs-2':
+      case 'utf16le':
+      case 'utf-16le':
+        return ucs2Write(this, string, offset, length)
+
+      default:
+        if (loweredCase) throw new TypeError('Unknown encoding: ' + encoding)
+        encoding = ('' + encoding).toLowerCase()
+        loweredCase = true
+    }
+  }
+}
+
+Buffer.prototype.toJSON = function toJSON () {
+  return {
+    type: 'Buffer',
+    data: Array.prototype.slice.call(this._arr || this, 0)
+  }
+}
+
+function base64Slice (buf, start, end) {
+  if (start === 0 && end === buf.length) {
+    return base64.fromByteArray(buf)
+  } else {
+    return base64.fromByteArray(buf.slice(start, end))
+  }
+}
+
+function utf8Slice (buf, start, end) {
+  end = Math.min(buf.length, end)
+  var res = []
+
+  var i = start
+  while (i < end) {
+    var firstByte = buf[i]
+    var codePoint = null
+    var bytesPerSequence = (firstByte > 0xEF) ? 4
+      : (firstByte > 0xDF) ? 3
+        : (firstByte > 0xBF) ? 2
+          : 1
+
+    if (i + bytesPerSequence <= end) {
+      var secondByte, thirdByte, fourthByte, tempCodePoint
+
+      switch (bytesPerSequence) {
+        case 1:
+          if (firstByte < 0x80) {
+            codePoint = firstByte
+          }
+          break
+        case 2:
+          secondByte = buf[i + 1]
+          if ((secondByte & 0xC0) === 0x80) {
+            tempCodePoint = (firstByte & 0x1F) << 0x6 | (secondByte & 0x3F)
+            if (tempCodePoint > 0x7F) {
+              codePoint = tempCodePoint
+            }
+          }
+          break
+        case 3:
+          secondByte = buf[i + 1]
+          thirdByte = buf[i + 2]
+          if ((secondByte & 0xC0) === 0x80 && (thirdByte & 0xC0) === 0x80) {
+            tempCodePoint = (firstByte & 0xF) << 0xC | (secondByte & 0x3F) << 0x6 | (thirdByte & 0x3F)
+            if (tempCodePoint > 0x7FF && (tempCodePoint < 0xD800 || tempCodePoint > 0xDFFF)) {
+              codePoint = tempCodePoint
+            }
+          }
+          break
+        case 4:
+          secondByte = buf[i + 1]
+          thirdByte = buf[i + 2]
+          fourthByte = buf[i + 3]
+          if ((secondByte & 0xC0) === 0x80 && (thirdByte & 0xC0) === 0x80 && (fourthByte & 0xC0) === 0x80) {
+            tempCodePoint = (firstByte & 0xF) << 0x12 | (secondByte & 0x3F) << 0xC | (thirdByte & 0x3F) << 0x6 | (fourthByte & 0x3F)
+            if (tempCodePoint > 0xFFFF && tempCodePoint < 0x110000) {
+              codePoint = tempCodePoint
+            }
+          }
+      }
+    }
+
+    if (codePoint === null) {
+      // we did not generate a valid codePoint so insert a
+      // replacement char (U+FFFD) and advance only 1 byte
+      codePoint = 0xFFFD
+      bytesPerSequence = 1
+    } else if (codePoint > 0xFFFF) {
+      // encode to utf16 (surrogate pair dance)
+      codePoint -= 0x10000
+      res.push(codePoint >>> 10 & 0x3FF | 0xD800)
+      codePoint = 0xDC00 | codePoint & 0x3FF
+    }
+
+    res.push(codePoint)
+    i += bytesPerSequence
+  }
+
+  return decodeCodePointsArray(res)
+}
+
+// Based on http://stackoverflow.com/a/22747272/680742, the browser with
+// the lowest limit is Chrome, with 0x10000 args.
+// We go 1 magnitude less, for safety
+var MAX_ARGUMENTS_LENGTH = 0x1000
+
+function decodeCodePointsArray (codePoints) {
+  var len = codePoints.length
+  if (len <= MAX_ARGUMENTS_LENGTH) {
+    return String.fromCharCode.apply(String, codePoints) // avoid extra slice()
+  }
+
+  // Decode in chunks to avoid "call stack size exceeded".
+  var res = ''
+  var i = 0
+  while (i < len) {
+    res += String.fromCharCode.apply(
+      String,
+      codePoints.slice(i, i += MAX_ARGUMENTS_LENGTH)
+    )
+  }
+  return res
+}
+
+function asciiSlice (buf, start, end) {
+  var ret = ''
+  end = Math.min(buf.length, end)
+
+  for (var i = start; i < end; ++i) {
+    ret += String.fromCharCode(buf[i] & 0x7F)
+  }
+  return ret
+}
+
+function latin1Slice (buf, start, end) {
+  var ret = ''
+  end = Math.min(buf.length, end)
+
+  for (var i = start; i < end; ++i) {
+    ret += String.fromCharCode(buf[i])
+  }
+  return ret
+}
+
+function hexSlice (buf, start, end) {
+  var len = buf.length
+
+  if (!start || start < 0) start = 0
+  if (!end || end < 0 || end > len) end = len
+
+  var out = ''
+  for (var i = start; i < end; ++i) {
+    out += hexSliceLookupTable[buf[i]]
+  }
+  return out
+}
+
+function utf16leSlice (buf, start, end) {
+  var bytes = buf.slice(start, end)
+  var res = ''
+  for (var i = 0; i < bytes.length; i += 2) {
+    res += String.fromCharCode(bytes[i] + (bytes[i + 1] * 256))
+  }
+  return res
+}
+
+Buffer.prototype.slice = function slice (start, end) {
+  var len = this.length
+  start = ~~start
+  end = end === undefined ? len : ~~end
+
+  if (start < 0) {
+    start += len
+    if (start < 0) start = 0
+  } else if (start > len) {
+    start = len
+  }
+
+  if (end < 0) {
+    end += len
+    if (end < 0) end = 0
+  } else if (end > len) {
+    end = len
+  }
+
+  if (end < start) end = start
+
+  var newBuf = this.subarray(start, end)
+  // Return an augmented `Uint8Array` instance
+  Object.setPrototypeOf(newBuf, Buffer.prototype)
+
+  return newBuf
+}
+
+/*
+ * Need to make sure that buffer isn't trying to write out of bounds.
+ */
+function checkOffset (offset, ext, length) {
+  if ((offset % 1) !== 0 || offset < 0) throw new RangeError('offset is not uint')
+  if (offset + ext > length) throw new RangeError('Trying to access beyond buffer length')
+}
+
+Buffer.prototype.readUIntLE = function readUIntLE (offset, byteLength, noAssert) {
+  offset = offset >>> 0
+  byteLength = byteLength >>> 0
+  if (!noAssert) checkOffset(offset, byteLength, this.length)
+
+  var val = this[offset]
+  var mul = 1
+  var i = 0
+  while (++i < byteLength && (mul *= 0x100)) {
+    val += this[offset + i] * mul
+  }
+
+  return val
+}
+
+Buffer.prototype.readUIntBE = function readUIntBE (offset, byteLength, noAssert) {
+  offset = offset >>> 0
+  byteLength = byteLength >>> 0
+  if (!noAssert) {
+    checkOffset(offset, byteLength, this.length)
+  }
+
+  var val = this[offset + --byteLength]
+  var mul = 1
+  while (byteLength > 0 && (mul *= 0x100)) {
+    val += this[offset + --byteLength] * mul
+  }
+
+  return val
+}
+
+Buffer.prototype.readUInt8 = function readUInt8 (offset, noAssert) {
+  offset = offset >>> 0
+  if (!noAssert) checkOffset(offset, 1, this.length)
+  return this[offset]
+}
+
+Buffer.prototype.readUInt16LE = function readUInt16LE (offset, noAssert) {
+  offset = offset >>> 0
+  if (!noAssert) checkOffset(offset, 2, this.length)
+  return this[offset] | (this[offset + 1] << 8)
+}
+
+Buffer.prototype.readUInt16BE = function readUInt16BE (offset, noAssert) {
+  offset = offset >>> 0
+  if (!noAssert) checkOffset(offset, 2, this.length)
+  return (this[offset] << 8) | this[offset + 1]
+}
+
+Buffer.prototype.readUInt32LE = function readUInt32LE (offset, noAssert) {
+  offset = offset >>> 0
+  if (!noAssert) checkOffset(offset, 4, this.length)
+
+  return ((this[offset]) |
+      (this[offset + 1] << 8) |
+      (this[offset + 2] << 16)) +
+      (this[offset + 3] * 0x1000000)
+}
+
+Buffer.prototype.readUInt32BE = function readUInt32BE (offset, noAssert) {
+  offset = offset >>> 0
+  if (!noAssert) checkOffset(offset, 4, this.length)
+
+  return (this[offset] * 0x1000000) +
+    ((this[offset + 1] << 16) |
+    (this[offset + 2] << 8) |
+    this[offset + 3])
+}
+
+Buffer.prototype.readIntLE = function readIntLE (offset, byteLength, noAssert) {
+  offset = offset >>> 0
+  byteLength = byteLength >>> 0
+  if (!noAssert) checkOffset(offset, byteLength, this.length)
+
+  var val = this[offset]
+  var mul = 1
+  var i = 0
+  while (++i < byteLength && (mul *= 0x100)) {
+    val += this[offset + i] * mul
+  }
+  mul *= 0x80
+
+  if (val >= mul) val -= Math.pow(2, 8 * byteLength)
+
+  return val
+}
+
+Buffer.prototype.readIntBE = function readIntBE (offset, byteLength, noAssert) {
+  offset = offset >>> 0
+  byteLength = byteLength >>> 0
+  if (!noAssert) checkOffset(offset, byteLength, this.length)
+
+  var i = byteLength
+  var mul = 1
+  var val = this[offset + --i]
+  while (i > 0 && (mul *= 0x100)) {
+    val += this[offset + --i] * mul
+  }
+  mul *= 0x80
+
+  if (val >= mul) val -= Math.pow(2, 8 * byteLength)
+
+  return val
+}
+
+Buffer.prototype.readInt8 = function readInt8 (offset, noAssert) {
+  offset = offset >>> 0
+  if (!noAssert) checkOffset(offset, 1, this.length)
+  if (!(this[offset] & 0x80)) return (this[offset])
+  return ((0xff - this[offset] + 1) * -1)
+}
+
+Buffer.prototype.readInt16LE = function readInt16LE (offset, noAssert) {
+  offset = offset >>> 0
+  if (!noAssert) checkOffset(offset, 2, this.length)
+  var val = this[offset] | (this[offset + 1] << 8)
+  return (val & 0x8000) ? val | 0xFFFF0000 : val
+}
+
+Buffer.prototype.readInt16BE = function readInt16BE (offset, noAssert) {
+  offset = offset >>> 0
+  if (!noAssert) checkOffset(offset, 2, this.length)
+  var val = this[offset + 1] | (this[offset] << 8)
+  return (val & 0x8000) ? val | 0xFFFF0000 : val
+}
+
+Buffer.prototype.readInt32LE = function readInt32LE (offset, noAssert) {
+  offset = offset >>> 0
+  if (!noAssert) checkOffset(offset, 4, this.length)
+
+  return (this[offset]) |
+    (this[offset + 1] << 8) |
+    (this[offset + 2] << 16) |
+    (this[offset + 3] << 24)
+}
+
+Buffer.prototype.readInt32BE = function readInt32BE (offset, noAssert) {
+  offset = offset >>> 0
+  if (!noAssert) checkOffset(offset, 4, this.length)
+
+  return (this[offset] << 24) |
+    (this[offset + 1] << 16) |
+    (this[offset + 2] << 8) |
+    (this[offset + 3])
+}
+
+Buffer.prototype.readFloatLE = function readFloatLE (offset, noAssert) {
+  offset = offset >>> 0
+  if (!noAssert) checkOffset(offset, 4, this.length)
+  return ieee754.read(this, offset, true, 23, 4)
+}
+
+Buffer.prototype.readFloatBE = function readFloatBE (offset, noAssert) {
+  offset = offset >>> 0
+  if (!noAssert) checkOffset(offset, 4, this.length)
+  return ieee754.read(this, offset, false, 23, 4)
+}
+
+Buffer.prototype.readDoubleLE = function readDoubleLE (offset, noAssert) {
+  offset = offset >>> 0
+  if (!noAssert) checkOffset(offset, 8, this.length)
+  return ieee754.read(this, offset, true, 52, 8)
+}
+
+Buffer.prototype.readDoubleBE = function readDoubleBE (offset, noAssert) {
+  offset = offset >>> 0
+  if (!noAssert) checkOffset(offset, 8, this.length)
+  return ieee754.read(this, offset, false, 52, 8)
+}
+
+function checkInt (buf, value, offset, ext, max, min) {
+  if (!Buffer.isBuffer(buf)) throw new TypeError('"buffer" argument must be a Buffer instance')
+  if (value > max || value < min) throw new RangeError('"value" argument is out of bounds')
+  if (offset + ext > buf.length) throw new RangeError('Index out of range')
+}
+
+Buffer.prototype.writeUIntLE = function writeUIntLE (value, offset, byteLength, noAssert) {
+  value = +value
+  offset = offset >>> 0
+  byteLength = byteLength >>> 0
+  if (!noAssert) {
+    var maxBytes = Math.pow(2, 8 * byteLength) - 1
+    checkInt(this, value, offset, byteLength, maxBytes, 0)
+  }
+
+  var mul = 1
+  var i = 0
+  this[offset] = value & 0xFF
+  while (++i < byteLength && (mul *= 0x100)) {
+    this[offset + i] = (value / mul) & 0xFF
+  }
+
+  return offset + byteLength
+}
+
+Buffer.prototype.writeUIntBE = function writeUIntBE (value, offset, byteLength, noAssert) {
+  value = +value
+  offset = offset >>> 0
+  byteLength = byteLength >>> 0
+  if (!noAssert) {
+    var maxBytes = Math.pow(2, 8 * byteLength) - 1
+    checkInt(this, value, offset, byteLength, maxBytes, 0)
+  }
+
+  var i = byteLength - 1
+  var mul = 1
+  this[offset + i] = value & 0xFF
+  while (--i >= 0 && (mul *= 0x100)) {
+    this[offset + i] = (value / mul) & 0xFF
+  }
+
+  return offset + byteLength
+}
+
+Buffer.prototype.writeUInt8 = function writeUInt8 (value, offset, noAssert) {
+  value = +value
+  offset = offset >>> 0
+  if (!noAssert) checkInt(this, value, offset, 1, 0xff, 0)
+  this[offset] = (value & 0xff)
+  return offset + 1
+}
+
+Buffer.prototype.writeUInt16LE = function writeUInt16LE (value, offset, noAssert) {
+  value = +value
+  offset = offset >>> 0
+  if (!noAssert) checkInt(this, value, offset, 2, 0xffff, 0)
+  this[offset] = (value & 0xff)
+  this[offset + 1] = (value >>> 8)
+  return offset + 2
+}
+
+Buffer.prototype.writeUInt16BE = function writeUInt16BE (value, offset, noAssert) {
+  value = +value
+  offset = offset >>> 0
+  if (!noAssert) checkInt(this, value, offset, 2, 0xffff, 0)
+  this[offset] = (value >>> 8)
+  this[offset + 1] = (value & 0xff)
+  return offset + 2
+}
+
+Buffer.prototype.writeUInt32LE = function writeUInt32LE (value, offset, noAssert) {
+  value = +value
+  offset = offset >>> 0
+  if (!noAssert) checkInt(this, value, offset, 4, 0xffffffff, 0)
+  this[offset + 3] = (value >>> 24)
+  this[offset + 2] = (value >>> 16)
+  this[offset + 1] = (value >>> 8)
+  this[offset] = (value & 0xff)
+  return offset + 4
+}
+
+Buffer.prototype.writeUInt32BE = function writeUInt32BE (value, offset, noAssert) {
+  value = +value
+  offset = offset >>> 0
+  if (!noAssert) checkInt(this, value, offset, 4, 0xffffffff, 0)
+  this[offset] = (value >>> 24)
+  this[offset + 1] = (value >>> 16)
+  this[offset + 2] = (value >>> 8)
+  this[offset + 3] = (value & 0xff)
+  return offset + 4
+}
+
+Buffer.prototype.writeIntLE = function writeIntLE (value, offset, byteLength, noAssert) {
+  value = +value
+  offset = offset >>> 0
+  if (!noAssert) {
+    var limit = Math.pow(2, (8 * byteLength) - 1)
+
+    checkInt(this, value, offset, byteLength, limit - 1, -limit)
+  }
+
+  var i = 0
+  var mul = 1
+  var sub = 0
+  this[offset] = value & 0xFF
+  while (++i < byteLength && (mul *= 0x100)) {
+    if (value < 0 && sub === 0 && this[offset + i - 1] !== 0) {
+      sub = 1
+    }
+    this[offset + i] = ((value / mul) >> 0) - sub & 0xFF
+  }
+
+  return offset + byteLength
+}
+
+Buffer.prototype.writeIntBE = function writeIntBE (value, offset, byteLength, noAssert) {
+  value = +value
+  offset = offset >>> 0
+  if (!noAssert) {
+    var limit = Math.pow(2, (8 * byteLength) - 1)
+
+    checkInt(this, value, offset, byteLength, limit - 1, -limit)
+  }
+
+  var i = byteLength - 1
+  var mul = 1
+  var sub = 0
+  this[offset + i] = value & 0xFF
+  while (--i >= 0 && (mul *= 0x100)) {
+    if (value < 0 && sub === 0 && this[offset + i + 1] !== 0) {
+      sub = 1
+    }
+    this[offset + i] = ((value / mul) >> 0) - sub & 0xFF
+  }
+
+  return offset + byteLength
+}
+
+Buffer.prototype.writeInt8 = function writeInt8 (value, offset, noAssert) {
+  value = +value
+  offset = offset >>> 0
+  if (!noAssert) checkInt(this, value, offset, 1, 0x7f, -0x80)
+  if (value < 0) value = 0xff + value + 1
+  this[offset] = (value & 0xff)
+  return offset + 1
+}
+
+Buffer.prototype.writeInt16LE = function writeInt16LE (value, offset, noAssert) {
+  value = +value
+  offset = offset >>> 0
+  if (!noAssert) checkInt(this, value, offset, 2, 0x7fff, -0x8000)
+  this[offset] = (value & 0xff)
+  this[offset + 1] = (value >>> 8)
+  return offset + 2
+}
+
+Buffer.prototype.writeInt16BE = function writeInt16BE (value, offset, noAssert) {
+  value = +value
+  offset = offset >>> 0
+  if (!noAssert) checkInt(this, value, offset, 2, 0x7fff, -0x8000)
+  this[offset] = (value >>> 8)
+  this[offset + 1] = (value & 0xff)
+  return offset + 2
+}
+
+Buffer.prototype.writeInt32LE = function writeInt32LE (value, offset, noAssert) {
+  value = +value
+  offset = offset >>> 0
+  if (!noAssert) checkInt(this, value, offset, 4, 0x7fffffff, -0x80000000)
+  this[offset] = (value & 0xff)
+  this[offset + 1] = (value >>> 8)
+  this[offset + 2] = (value >>> 16)
+  this[offset + 3] = (value >>> 24)
+  return offset + 4
+}
+
+Buffer.prototype.writeInt32BE = function writeInt32BE (value, offset, noAssert) {
+  value = +value
+  offset = offset >>> 0
+  if (!noAssert) checkInt(this, value, offset, 4, 0x7fffffff, -0x80000000)
+  if (value < 0) value = 0xffffffff + value + 1
+  this[offset] = (value >>> 24)
+  this[offset + 1] = (value >>> 16)
+  this[offset + 2] = (value >>> 8)
+  this[offset + 3] = (value & 0xff)
+  return offset + 4
+}
+
+function checkIEEE754 (buf, value, offset, ext, max, min) {
+  if (offset + ext > buf.length) throw new RangeError('Index out of range')
+  if (offset < 0) throw new RangeError('Index out of range')
+}
+
+function writeFloat (buf, value, offset, littleEndian, noAssert) {
+  value = +value
+  offset = offset >>> 0
+  if (!noAssert) {
+    checkIEEE754(buf, value, offset, 4, 3.4028234663852886e+38, -3.4028234663852886e+38)
+  }
+  ieee754.write(buf, value, offset, littleEndian, 23, 4)
+  return offset + 4
+}
+
+Buffer.prototype.writeFloatLE = function writeFloatLE (value, offset, noAssert) {
+  return writeFloat(this, value, offset, true, noAssert)
+}
+
+Buffer.prototype.writeFloatBE = function writeFloatBE (value, offset, noAssert) {
+  return writeFloat(this, value, offset, false, noAssert)
+}
+
+function writeDouble (buf, value, offset, littleEndian, noAssert) {
+  value = +value
+  offset = offset >>> 0
+  if (!noAssert) {
+    checkIEEE754(buf, value, offset, 8, 1.7976931348623157E+308, -1.7976931348623157E+308)
+  }
+  ieee754.write(buf, value, offset, littleEndian, 52, 8)
+  return offset + 8
+}
+
+Buffer.prototype.writeDoubleLE = function writeDoubleLE (value, offset, noAssert) {
+  return writeDouble(this, value, offset, true, noAssert)
+}
+
+Buffer.prototype.writeDoubleBE = function writeDoubleBE (value, offset, noAssert) {
+  return writeDouble(this, value, offset, false, noAssert)
+}
+
+// copy(targetBuffer, targetStart=0, sourceStart=0, sourceEnd=buffer.length)
+Buffer.prototype.copy = function copy (target, targetStart, start, end) {
+  if (!Buffer.isBuffer(target)) throw new TypeError('argument should be a Buffer')
+  if (!start) start = 0
+  if (!end && end !== 0) end = this.length
+  if (targetStart >= target.length) targetStart = target.length
+  if (!targetStart) targetStart = 0
+  if (end > 0 && end < start) end = start
+
+  // Copy 0 bytes; we're done
+  if (end === start) return 0
+  if (target.length === 0 || this.length === 0) return 0
+
+  // Fatal error conditions
+  if (targetStart < 0) {
+    throw new RangeError('targetStart out of bounds')
+  }
+  if (start < 0 || start >= this.length) throw new RangeError('Index out of range')
+  if (end < 0) throw new RangeError('sourceEnd out of bounds')
+
+  // Are we oob?
+  if (end > this.length) end = this.length
+  if (target.length - targetStart < end - start) {
+    end = target.length - targetStart + start
+  }
+
+  var len = end - start
+
+  if (this === target && typeof Uint8Array.prototype.copyWithin === 'function') {
+    // Use built-in when available, missing from IE11
+    this.copyWithin(targetStart, start, end)
+  } else if (this === target && start < targetStart && targetStart < end) {
+    // descending copy from end
+    for (var i = len - 1; i >= 0; --i) {
+      target[i + targetStart] = this[i + start]
+    }
+  } else {
+    Uint8Array.prototype.set.call(
+      target,
+      this.subarray(start, end),
+      targetStart
+    )
+  }
+
+  return len
+}
+
+// Usage:
+//    buffer.fill(number[, offset[, end]])
+//    buffer.fill(buffer[, offset[, end]])
+//    buffer.fill(string[, offset[, end]][, encoding])
+Buffer.prototype.fill = function fill (val, start, end, encoding) {
+  // Handle string cases:
+  if (typeof val === 'string') {
+    if (typeof start === 'string') {
+      encoding = start
+      start = 0
+      end = this.length
+    } else if (typeof end === 'string') {
+      encoding = end
+      end = this.length
+    }
+    if (encoding !== undefined && typeof encoding !== 'string') {
+      throw new TypeError('encoding must be a string')
+    }
+    if (typeof encoding === 'string' && !Buffer.isEncoding(encoding)) {
+      throw new TypeError('Unknown encoding: ' + encoding)
+    }
+    if (val.length === 1) {
+      var code = val.charCodeAt(0)
+      if ((encoding === 'utf8' && code < 128) ||
+          encoding === 'latin1') {
+        // Fast path: If `val` fits into a single byte, use that numeric value.
+        val = code
+      }
+    }
+  } else if (typeof val === 'number') {
+    val = val & 255
+  } else if (typeof val === 'boolean') {
+    val = Number(val)
+  }
+
+  // Invalid ranges are not set to a default, so can range check early.
+  if (start < 0 || this.length < start || this.length < end) {
+    throw new RangeError('Out of range index')
+  }
+
+  if (end <= start) {
+    return this
+  }
+
+  start = start >>> 0
+  end = end === undefined ? this.length : end >>> 0
+
+  if (!val) val = 0
+
+  var i
+  if (typeof val === 'number') {
+    for (i = start; i < end; ++i) {
+      this[i] = val
+    }
+  } else {
+    var bytes = Buffer.isBuffer(val)
+      ? val
+      : Buffer.from(val, encoding)
+    var len = bytes.length
+    if (len === 0) {
+      throw new TypeError('The value "' + val +
+        '" is invalid for argument "value"')
+    }
+    for (i = 0; i < end - start; ++i) {
+      this[i + start] = bytes[i % len]
+    }
+  }
+
+  return this
+}
+
+// HELPER FUNCTIONS
+// ================
+
+var INVALID_BASE64_RE = /[^+/0-9A-Za-z-_]/g
+
+function base64clean (str) {
+  // Node takes equal signs as end of the Base64 encoding
+  str = str.split('=')[0]
+  // Node strips out invalid characters like \n and \t from the string, base64-js does not
+  str = str.trim().replace(INVALID_BASE64_RE, '')
+  // Node converts strings with length < 2 to ''
+  if (str.length < 2) return ''
+  // Node allows for non-padded base64 strings (missing trailing ===), base64-js does not
+  while (str.length % 4 !== 0) {
+    str = str + '='
+  }
+  return str
+}
+
+function utf8ToBytes (string, units) {
+  units = units || Infinity
+  var codePoint
+  var length = string.length
+  var leadSurrogate = null
+  var bytes = []
+
+  for (var i = 0; i < length; ++i) {
+    codePoint = string.charCodeAt(i)
+
+    // is surrogate component
+    if (codePoint > 0xD7FF && codePoint < 0xE000) {
+      // last char was a lead
+      if (!leadSurrogate) {
+        // no lead yet
+        if (codePoint > 0xDBFF) {
+          // unexpected trail
+          if ((units -= 3) > -1) bytes.push(0xEF, 0xBF, 0xBD)
+          continue
+        } else if (i + 1 === length) {
+          // unpaired lead
+          if ((units -= 3) > -1) bytes.push(0xEF, 0xBF, 0xBD)
+          continue
+        }
+
+        // valid lead
+        leadSurrogate = codePoint
+
+        continue
+      }
+
+      // 2 leads in a row
+      if (codePoint < 0xDC00) {
+        if ((units -= 3) > -1) bytes.push(0xEF, 0xBF, 0xBD)
+        leadSurrogate = codePoint
+        continue
+      }
+
+      // valid surrogate pair
+      codePoint = (leadSurrogate - 0xD800 << 10 | codePoint - 0xDC00) + 0x10000
+    } else if (leadSurrogate) {
+      // valid bmp char, but last char was a lead
+      if ((units -= 3) > -1) bytes.push(0xEF, 0xBF, 0xBD)
+    }
+
+    leadSurrogate = null
+
+    // encode utf8
+    if (codePoint < 0x80) {
+      if ((units -= 1) < 0) break
+      bytes.push(codePoint)
+    } else if (codePoint < 0x800) {
+      if ((units -= 2) < 0) break
+      bytes.push(
+        codePoint >> 0x6 | 0xC0,
+        codePoint & 0x3F | 0x80
+      )
+    } else if (codePoint < 0x10000) {
+      if ((units -= 3) < 0) break
+      bytes.push(
+        codePoint >> 0xC | 0xE0,
+        codePoint >> 0x6 & 0x3F | 0x80,
+        codePoint & 0x3F | 0x80
+      )
+    } else if (codePoint < 0x110000) {
+      if ((units -= 4) < 0) break
+      bytes.push(
+        codePoint >> 0x12 | 0xF0,
+        codePoint >> 0xC & 0x3F | 0x80,
+        codePoint >> 0x6 & 0x3F | 0x80,
+        codePoint & 0x3F | 0x80
+      )
+    } else {
+      throw new Error('Invalid code point')
+    }
+  }
+
+  return bytes
+}
+
+function asciiToBytes (str) {
+  var byteArray = []
+  for (var i = 0; i < str.length; ++i) {
+    // Node's code seems to be doing this and not & 0x7F..
+    byteArray.push(str.charCodeAt(i) & 0xFF)
+  }
+  return byteArray
+}
+
+function utf16leToBytes (str, units) {
+  var c, hi, lo
+  var byteArray = []
+  for (var i = 0; i < str.length; ++i) {
+    if ((units -= 2) < 0) break
+
+    c = str.charCodeAt(i)
+    hi = c >> 8
+    lo = c % 256
+    byteArray.push(lo)
+    byteArray.push(hi)
+  }
+
+  return byteArray
+}
+
+function base64ToBytes (str) {
+  return base64.toByteArray(base64clean(str))
+}
+
+function blitBuffer (src, dst, offset, length) {
+  for (var i = 0; i < length; ++i) {
+    if ((i + offset >= dst.length) || (i >= src.length)) break
+    dst[i + offset] = src[i]
+  }
+  return i
+}
+
+// ArrayBuffer or Uint8Array objects from other contexts (i.e. iframes) do not pass
+// the `instanceof` check but they should be treated as of that type.
+// See: https://github.com/feross/buffer/issues/166
+function isInstance (obj, type) {
+  return obj instanceof type ||
+    (obj != null && obj.constructor != null && obj.constructor.name != null &&
+      obj.constructor.name === type.name)
+}
+function numberIsNaN (obj) {
+  // For IE11 support
+  return obj !== obj // eslint-disable-line no-self-compare
+}
+
+// Create lookup table for `toString('hex')`
+// See: https://github.com/feross/buffer/issues/219
+var hexSliceLookupTable = (function () {
+  var alphabet = '0123456789abcdef'
+  var table = new Array(256)
+  for (var i = 0; i < 16; ++i) {
+    var i16 = i * 16
+    for (var j = 0; j < 16; ++j) {
+      table[i16 + j] = alphabet[i] + alphabet[j]
+    }
+  }
+  return table
+})()
+
+}).call(this,require("buffer").Buffer)
+},{"base64-js":42,"buffer":44,"ieee754":46}],45:[function(require,module,exports){
+// Copyright Joyent, Inc. and other Node contributors.
+//
+// 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.
+
+var objectCreate = Object.create || objectCreatePolyfill
+var objectKeys = Object.keys || objectKeysPolyfill
+var bind = Function.prototype.bind || functionBindPolyfill
+
+function EventEmitter() {
+  if (!this._events || !Object.prototype.hasOwnProperty.call(this, '_events')) {
+    this._events = objectCreate(null);
+    this._eventsCount = 0;
+  }
+
+  this._maxListeners = this._maxListeners || undefined;
+}
+module.exports = EventEmitter;
+
+// Backwards-compat with node 0.10.x
+EventEmitter.EventEmitter = EventEmitter;
+
+EventEmitter.prototype._events = undefined;
+EventEmitter.prototype._maxListeners = undefined;
+
+// By default EventEmitters will print a warning if more than 10 listeners are
+// added to it. This is a useful default which helps finding memory leaks.
+var defaultMaxListeners = 10;
+
+var hasDefineProperty;
+try {
+  var o = {};
+  if (Object.defineProperty) Object.defineProperty(o, 'x', { value: 0 });
+  hasDefineProperty = o.x === 0;
+} catch (err) { hasDefineProperty = false }
+if (hasDefineProperty) {
+  Object.defineProperty(EventEmitter, 'defaultMaxListeners', {
+    enumerable: true,
+    get: function() {
+      return defaultMaxListeners;
+    },
+    set: function(arg) {
+      // check whether the input is a positive number (whose value is zero or
+      // greater and not a NaN).
+      if (typeof arg !== 'number' || arg < 0 || arg !== arg)
+        throw new TypeError('"defaultMaxListeners" must be a positive number');
+      defaultMaxListeners = arg;
+    }
+  });
+} else {
+  EventEmitter.defaultMaxListeners = defaultMaxListeners;
+}
+
+// Obviously not all Emitters should be limited to 10. This function allows
+// that to be increased. Set to zero for unlimited.
+EventEmitter.prototype.setMaxListeners = function setMaxListeners(n) {
+  if (typeof n !== 'number' || n < 0 || isNaN(n))
+    throw new TypeError('"n" argument must be a positive number');
+  this._maxListeners = n;
+  return this;
+};
+
+function $getMaxListeners(that) {
+  if (that._maxListeners === undefined)
+    return EventEmitter.defaultMaxListeners;
+  return that._maxListeners;
+}
+
+EventEmitter.prototype.getMaxListeners = function getMaxListeners() {
+  return $getMaxListeners(this);
+};
+
+// These standalone emit* functions are used to optimize calling of event
+// handlers for fast cases because emit() itself often has a variable number of
+// arguments and can be deoptimized because of that. These functions always have
+// the same number of arguments and thus do not get deoptimized, so the code
+// inside them can execute faster.
+function emitNone(handler, isFn, self) {
+  if (isFn)
+    handler.call(self);
+  else {
+    var len = handler.length;
+    var listeners = arrayClone(handler, len);
+    for (var i = 0; i < len; ++i)
+      listeners[i].call(self);
+  }
+}
+function emitOne(handler, isFn, self, arg1) {
+  if (isFn)
+    handler.call(self, arg1);
+  else {
+    var len = handler.length;
+    var listeners = arrayClone(handler, len);
+    for (var i = 0; i < len; ++i)
+      listeners[i].call(self, arg1);
+  }
+}
+function emitTwo(handler, isFn, self, arg1, arg2) {
+  if (isFn)
+    handler.call(self, arg1, arg2);
+  else {
+    var len = handler.length;
+    var listeners = arrayClone(handler, len);
+    for (var i = 0; i < len; ++i)
+      listeners[i].call(self, arg1, arg2);
+  }
+}
+function emitThree(handler, isFn, self, arg1, arg2, arg3) {
+  if (isFn)
+    handler.call(self, arg1, arg2, arg3);
+  else {
+    var len = handler.length;
+    var listeners = arrayClone(handler, len);
+    for (var i = 0; i < len; ++i)
+      listeners[i].call(self, arg1, arg2, arg3);
+  }
+}
+
+function emitMany(handler, isFn, self, args) {
+  if (isFn)
+    handler.apply(self, args);
+  else {
+    var len = handler.length;
+    var listeners = arrayClone(handler, len);
+    for (var i = 0; i < len; ++i)
+      listeners[i].apply(self, args);
+  }
+}
+
+EventEmitter.prototype.emit = function emit(type) {
+  var er, handler, len, args, i, events;
+  var doError = (type === 'error');
+
+  events = this._events;
+  if (events)
+    doError = (doError && events.error == null);
+  else if (!doError)
+    return false;
+
+  // If there is no 'error' event listener then throw.
+  if (doError) {
+    if (arguments.length > 1)
+      er = arguments[1];
+    if (er instanceof Error) {
+      throw er; // Unhandled 'error' event
+    } else {
+      // At least give some kind of context to the user
+      var err = new Error('Unhandled "error" event. (' + er + ')');
+      err.context = er;
+      throw err;
+    }
+    return false;
+  }
+
+  handler = events[type];
+
+  if (!handler)
+    return false;
+
+  var isFn = typeof handler === 'function';
+  len = arguments.length;
+  switch (len) {
+      // fast cases
+    case 1:
+      emitNone(handler, isFn, this);
+      break;
+    case 2:
+      emitOne(handler, isFn, this, arguments[1]);
+      break;
+    case 3:
+      emitTwo(handler, isFn, this, arguments[1], arguments[2]);
+      break;
+    case 4:
+      emitThree(handler, isFn, this, arguments[1], arguments[2], arguments[3]);
+      break;
+      // slower
+    default:
+      args = new Array(len - 1);
+      for (i = 1; i < len; i++)
+        args[i - 1] = arguments[i];
+      emitMany(handler, isFn, this, args);
+  }
+
+  return true;
+};
+
+function _addListener(target, type, listener, prepend) {
+  var m;
+  var events;
+  var existing;
+
+  if (typeof listener !== 'function')
+    throw new TypeError('"listener" argument must be a function');
+
+  events = target._events;
+  if (!events) {
+    events = target._events = objectCreate(null);
+    target._eventsCount = 0;
+  } else {
+    // To avoid recursion in the case that type === "newListener"! Before
+    // adding it to the listeners, first emit "newListener".
+    if (events.newListener) {
+      target.emit('newListener', type,
+          listener.listener ? listener.listener : listener);
+
+      // Re-assign `events` because a newListener handler could have caused the
+      // this._events to be assigned to a new object
+      events = target._events;
+    }
+    existing = events[type];
+  }
+
+  if (!existing) {
+    // Optimize the case of one listener. Don't need the extra array object.
+    existing = events[type] = listener;
+    ++target._eventsCount;
+  } else {
+    if (typeof existing === 'function') {
+      // Adding the second element, need to change to array.
+      existing = events[type] =
+          prepend ? [listener, existing] : [existing, listener];
+    } else {
+      // If we've already got an array, just append.
+      if (prepend) {
+        existing.unshift(listener);
+      } else {
+        existing.push(listener);
+      }
+    }
+
+    // Check for listener leak
+    if (!existing.warned) {
+      m = $getMaxListeners(target);
+      if (m && m > 0 && existing.length > m) {
+        existing.warned = true;
+        var w = new Error('Possible EventEmitter memory leak detected. ' +
+            existing.length + ' "' + String(type) + '" listeners ' +
+            'added. Use emitter.setMaxListeners() to ' +
+            'increase limit.');
+        w.name = 'MaxListenersExceededWarning';
+        w.emitter = target;
+        w.type = type;
+        w.count = existing.length;
+        if (typeof console === 'object' && console.warn) {
+          console.warn('%s: %s', w.name, w.message);
+        }
+      }
+    }
+  }
+
+  return target;
+}
+
+EventEmitter.prototype.addListener = function addListener(type, listener) {
+  return _addListener(this, type, listener, false);
+};
+
+EventEmitter.prototype.on = EventEmitter.prototype.addListener;
+
+EventEmitter.prototype.prependListener =
+    function prependListener(type, listener) {
+      return _addListener(this, type, listener, true);
+    };
+
+function onceWrapper() {
+  if (!this.fired) {
+    this.target.removeListener(this.type, this.wrapFn);
+    this.fired = true;
+    switch (arguments.length) {
+      case 0:
+        return this.listener.call(this.target);
+      case 1:
+        return this.listener.call(this.target, arguments[0]);
+      case 2:
+        return this.listener.call(this.target, arguments[0], arguments[1]);
+      case 3:
+        return this.listener.call(this.target, arguments[0], arguments[1],
+            arguments[2]);
+      default:
+        var args = new Array(arguments.length);
+        for (var i = 0; i < args.length; ++i)
+          args[i] = arguments[i];
+        this.listener.apply(this.target, args);
+    }
+  }
+}
+
+function _onceWrap(target, type, listener) {
+  var state = { fired: false, wrapFn: undefined, target: target, type: type, listener: listener };
+  var wrapped = bind.call(onceWrapper, state);
+  wrapped.listener = listener;
+  state.wrapFn = wrapped;
+  return wrapped;
+}
+
+EventEmitter.prototype.once = function once(type, listener) {
+  if (typeof listener !== 'function')
+    throw new TypeError('"listener" argument must be a function');
+  this.on(type, _onceWrap(this, type, listener));
+  return this;
+};
+
+EventEmitter.prototype.prependOnceListener =
+    function prependOnceListener(type, listener) {
+      if (typeof listener !== 'function')
+        throw new TypeError('"listener" argument must be a function');
+      this.prependListener(type, _onceWrap(this, type, listener));
+      return this;
+    };
+
+// Emits a 'removeListener' event if and only if the listener was removed.
+EventEmitter.prototype.removeListener =
+    function removeListener(type, listener) {
+      var list, events, position, i, originalListener;
+
+      if (typeof listener !== 'function')
+        throw new TypeError('"listener" argument must be a function');
+
+      events = this._events;
+      if (!events)
+        return this;
+
+      list = events[type];
+      if (!list)
+        return this;
+
+      if (list === listener || list.listener === listener) {
+        if (--this._eventsCount === 0)
+          this._events = objectCreate(null);
+        else {
+          delete events[type];
+          if (events.removeListener)
+            this.emit('removeListener', type, list.listener || listener);
+        }
+      } else if (typeof list !== 'function') {
+        position = -1;
+
+        for (i = list.length - 1; i >= 0; i--) {
+          if (list[i] === listener || list[i].listener === listener) {
+            originalListener = list[i].listener;
+            position = i;
+            break;
+          }
+        }
+
+        if (position < 0)
+          return this;
+
+        if (position === 0)
+          list.shift();
+        else
+          spliceOne(list, position);
+
+        if (list.length === 1)
+          events[type] = list[0];
+
+        if (events.removeListener)
+          this.emit('removeListener', type, originalListener || listener);
+      }
+
+      return this;
+    };
+
+EventEmitter.prototype.removeAllListeners =
+    function removeAllListeners(type) {
+      var listeners, events, i;
+
+      events = this._events;
+      if (!events)
+        return this;
+
+      // not listening for removeListener, no need to emit
+      if (!events.removeListener) {
+        if (arguments.length === 0) {
+          this._events = objectCreate(null);
+          this._eventsCount = 0;
+        } else if (events[type]) {
+          if (--this._eventsCount === 0)
+            this._events = objectCreate(null);
+          else
+            delete events[type];
+        }
+        return this;
+      }
+
+      // emit removeListener for all listeners on all events
+      if (arguments.length === 0) {
+        var keys = objectKeys(events);
+        var key;
+        for (i = 0; i < keys.length; ++i) {
+          key = keys[i];
+          if (key === 'removeListener') continue;
+          this.removeAllListeners(key);
+        }
+        this.removeAllListeners('removeListener');
+        this._events = objectCreate(null);
+        this._eventsCount = 0;
+        return this;
+      }
+
+      listeners = events[type];
+
+      if (typeof listeners === 'function') {
+        this.removeListener(type, listeners);
+      } else if (listeners) {
+        // LIFO order
+        for (i = listeners.length - 1; i >= 0; i--) {
+          this.removeListener(type, listeners[i]);
+        }
+      }
+
+      return this;
+    };
+
+function _listeners(target, type, unwrap) {
+  var events = target._events;
+
+  if (!events)
+    return [];
+
+  var evlistener = events[type];
+  if (!evlistener)
+    return [];
+
+  if (typeof evlistener === 'function')
+    return unwrap ? [evlistener.listener || evlistener] : [evlistener];
+
+  return unwrap ? unwrapListeners(evlistener) : arrayClone(evlistener, evlistener.length);
+}
+
+EventEmitter.prototype.listeners = function listeners(type) {
+  return _listeners(this, type, true);
+};
+
+EventEmitter.prototype.rawListeners = function rawListeners(type) {
+  return _listeners(this, type, false);
+};
+
+EventEmitter.listenerCount = function(emitter, type) {
+  if (typeof emitter.listenerCount === 'function') {
+    return emitter.listenerCount(type);
+  } else {
+    return listenerCount.call(emitter, type);
+  }
+};
+
+EventEmitter.prototype.listenerCount = listenerCount;
+function listenerCount(type) {
+  var events = this._events;
+
+  if (events) {
+    var evlistener = events[type];
+
+    if (typeof evlistener === 'function') {
+      return 1;
+    } else if (evlistener) {
+      return evlistener.length;
+    }
+  }
+
+  return 0;
+}
+
+EventEmitter.prototype.eventNames = function eventNames() {
+  return this._eventsCount > 0 ? Reflect.ownKeys(this._events) : [];
+};
+
+// About 1.5x faster than the two-arg version of Array#splice().
+function spliceOne(list, index) {
+  for (var i = index, k = i + 1, n = list.length; k < n; i += 1, k += 1)
+    list[i] = list[k];
+  list.pop();
+}
+
+function arrayClone(arr, n) {
+  var copy = new Array(n);
+  for (var i = 0; i < n; ++i)
+    copy[i] = arr[i];
+  return copy;
+}
+
+function unwrapListeners(arr) {
+  var ret = new Array(arr.length);
+  for (var i = 0; i < ret.length; ++i) {
+    ret[i] = arr[i].listener || arr[i];
+  }
+  return ret;
+}
+
+function objectCreatePolyfill(proto) {
+  var F = function() {};
+  F.prototype = proto;
+  return new F;
+}
+function objectKeysPolyfill(obj) {
+  var keys = [];
+  for (var k in obj) if (Object.prototype.hasOwnProperty.call(obj, k)) {
+    keys.push(k);
+  }
+  return k;
+}
+function functionBindPolyfill(context) {
+  var fn = this;
+  return function () {
+    return fn.apply(context, arguments);
+  };
+}
+
+},{}],46:[function(require,module,exports){
+exports.read = function (buffer, offset, isLE, mLen, nBytes) {
+  var e, m
+  var eLen = (nBytes * 8) - mLen - 1
+  var eMax = (1 << eLen) - 1
+  var eBias = eMax >> 1
+  var nBits = -7
+  var i = isLE ? (nBytes - 1) : 0
+  var d = isLE ? -1 : 1
+  var 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)
+}
+
+exports.write = function (buffer, value, offset, isLE, mLen, nBytes) {
+  var e, m, c
+  var eLen = (nBytes * 8) - mLen - 1
+  var eMax = (1 << eLen) - 1
+  var eBias = eMax >> 1
+  var rt = (mLen === 23 ? Math.pow(2, -24) - Math.pow(2, -77) : 0)
+  var i = isLE ? 0 : (nBytes - 1)
+  var d = isLE ? 1 : -1
+  var 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
+}
+
+},{}],47:[function(require,module,exports){
+/*!
+ * Determine if an object is a Buffer
+ *
+ * @author   Feross Aboukhadijeh <https://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))
+}
+
+},{}],48:[function(require,module,exports){
+/*
+object-assign
+(c) Sindre Sorhus
+@license MIT
+*/
+
+'use strict';
+/* eslint-disable no-unused-vars */
+var getOwnPropertySymbols = Object.getOwnPropertySymbols;
+var hasOwnProperty = Object.prototype.hasOwnProperty;
+var propIsEnumerable = Object.prototype.propertyIsEnumerable;
+
+function toObject(val) {
+	if (val === null || val === undefined) {
+		throw new TypeError('Object.assign cannot be called with null or undefined');
+	}
+
+	return Object(val);
+}
+
+function shouldUseNative() {
+	try {
+		if (!Object.assign) {
+			return false;
+		}
+
+		// Detect buggy property enumeration order in older V8 versions.
+
+		// https://bugs.chromium.org/p/v8/issues/detail?id=4118
+		var test1 = new String('abc');  // eslint-disable-line no-new-wrappers
+		test1[5] = 'de';
+		if (Object.getOwnPropertyNames(test1)[0] === '5') {
+			return false;
+		}
+
+		// https://bugs.chromium.org/p/v8/issues/detail?id=3056
+		var test2 = {};
+		for (var i = 0; i < 10; i++) {
+			test2['_' + String.fromCharCode(i)] = i;
+		}
+		var order2 = Object.getOwnPropertyNames(test2).map(function (n) {
+			return test2[n];
+		});
+		if (order2.join('') !== '0123456789') {
+			return false;
+		}
+
+		// https://bugs.chromium.org/p/v8/issues/detail?id=3056
+		var test3 = {};
+		'abcdefghijklmnopqrst'.split('').forEach(function (letter) {
+			test3[letter] = letter;
+		});
+		if (Object.keys(Object.assign({}, test3)).join('') !==
+				'abcdefghijklmnopqrst') {
+			return false;
+		}
+
+		return true;
+	} catch (err) {
+		// We don't expect any of the above to throw, but better to be safe.
+		return false;
+	}
+}
+
+module.exports = shouldUseNative() ? Object.assign : function (target, source) {
+	var from;
+	var to = toObject(target);
+	var symbols;
+
+	for (var s = 1; s < arguments.length; s++) {
+		from = Object(arguments[s]);
+
+		for (var key in from) {
+			if (hasOwnProperty.call(from, key)) {
+				to[key] = from[key];
+			}
+		}
+
+		if (getOwnPropertySymbols) {
+			symbols = getOwnPropertySymbols(from);
+			for (var i = 0; i < symbols.length; i++) {
+				if (propIsEnumerable.call(from, symbols[i])) {
+					to[symbols[i]] = from[symbols[i]];
+				}
+			}
+		}
+	}
+
+	return to;
+};
+
+},{}],49:[function(require,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.prependListener = noop;
+process.prependOnceListener = noop;
+
+process.listeners = function (name) { return [] }
+
+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; };
+
+},{}],50:[function(require,module,exports){
+(function (setImmediate,clearImmediate){
+var nextTick = require('process/browser.js').nextTick;
+var apply = Function.prototype.apply;
+var slice = Array.prototype.slice;
+var immediateIds = {};
+var nextImmediateId = 0;
+
+// DOM APIs, for completeness
+
+exports.setTimeout = function() {
+  return new Timeout(apply.call(setTimeout, window, arguments), clearTimeout);
+};
+exports.setInterval = function() {
+  return new Timeout(apply.call(setInterval, window, arguments), clearInterval);
+};
+exports.clearTimeout =
+exports.clearInterval = function(timeout) { timeout.close(); };
+
+function Timeout(id, clearFn) {
+  this._id = id;
+  this._clearFn = clearFn;
+}
+Timeout.prototype.unref = Timeout.prototype.ref = function() {};
+Timeout.prototype.close = function() {
+  this._clearFn.call(window, this._id);
+};
+
+// Does not start the time, just sets up the members needed.
+exports.enroll = function(item, msecs) {
+  clearTimeout(item._idleTimeoutId);
+  item._idleTimeout = msecs;
+};
+
+exports.unenroll = function(item) {
+  clearTimeout(item._idleTimeoutId);
+  item._idleTimeout = -1;
+};
+
+exports._unrefActive = exports.active = function(item) {
+  clearTimeout(item._idleTimeoutId);
+
+  var msecs = item._idleTimeout;
+  if (msecs >= 0) {
+    item._idleTimeoutId = setTimeout(function onTimeout() {
+      if (item._onTimeout)
+        item._onTimeout();
+    }, msecs);
+  }
+};
+
+// That's not how node.js implements it but the exposed api is the same.
+exports.setImmediate = typeof setImmediate === "function" ? setImmediate : function(fn) {
+  var id = nextImmediateId++;
+  var args = arguments.length < 2 ? false : slice.call(arguments, 1);
+
+  immediateIds[id] = true;
+
+  nextTick(function onNextTick() {
+    if (immediateIds[id]) {
+      // fn.call() is faster so we optimize for the common use-case
+      // @see http://jsperf.com/call-apply-segu
+      if (args) {
+        fn.apply(null, args);
+      } else {
+        fn.call(null);
+      }
+      // Prevent ids from leaking
+      exports.clearImmediate(id);
+    }
+  });
+
+  return id;
+};
+
+exports.clearImmediate = typeof clearImmediate === "function" ? clearImmediate : function(id) {
+  delete immediateIds[id];
+};
+}).call(this,require("timers").setImmediate,require("timers").clearImmediate)
+},{"process/browser.js":49,"timers":50}],51:[function(require,module,exports){
+arguments[4][12][0].apply(exports,arguments)
+},{"dup":12}],52:[function(require,module,exports){
+arguments[4][40][0].apply(exports,arguments)
+},{"dup":40}],53:[function(require,module,exports){
+arguments[4][41][0].apply(exports,arguments)
+},{"./support/isBuffer":52,"_process":49,"dup":41,"inherits":51}]},{},[1]);
diff --git a/web-service/public/js/index.js b/web-service/public/js/index.js
new file mode 100644
index 0000000000000000000000000000000000000000..8ea42a108eefed4a1bcf9a3bddddb2a6c4fa628b
--- /dev/null
+++ b/web-service/public/js/index.js
@@ -0,0 +1,195 @@
+const Peer = require('../../server/src/peer')
+const VideoConverter = require('./lib/dist/video-converter');
+
+let current_data = {};
+let peer;
+let player;
+
+/**
+ * Validates that the user is logged in by sending the token 
+ */
+checkIfLoggedIn = async () => {
+    //     const token = window.localStorage.getItem('token')
+    //     console.log(token)
+    //     if(!token){
+    //         console.log("You need to login")
+    //         renderLogin()
+    //     }else{
+
+    //         //Check if the token is valid
+    //         const response = await fetch('http://localhost:8080/auth/validation', {
+    //             method: 'POST',
+    //             headers: {'Authorization': token}
+    //         })
+    //         console.log('RESPONSE', response)
+            
+    //         //Token is valid, show available streams
+    //         if(response.status === 200){
+    //             console.log("SUCCESS")
+                 renderThumbnails()
+
+    //         }
+    //     }
+}
+
+/**
+ * Returns a list of available streams
+ */
+getAvailableStreams = async () => {
+    try{
+        const streamsInJson = await fetch(`./streams`);
+        const streams = await streamsInJson.json();
+        console.log('AVAILABLE', streams)
+        return streams;
+    }catch(err){
+        console.log(err)
+    }
+}
+
+
+createVideoPlayer = () => {
+    const containerDiv = document.getElementById('container')
+    containerDiv.innerHTML = `<h1>Stream from source ${current_data.uri}</h1><br>
+        <button onclick="renderThumbnails(); closeStream()">Go back</button>
+        <button onclick="connectToStream()">Start Stream</button><br>
+        <button onclick="webSocketTest()">WebSocket Test</button><br>
+        <video id="ftlab-stream-video" width="640" height="360"></video>`;
+    containerDiv.innerHTML += '<br>'
+    containerDiv.innerHTML += ''
+    createPeer();
+    connectToStream();
+}
+
+/**
+ * Creates thumbnail (image) for all available streams and adds them to div class='container'
+ */
+renderThumbnails = async () => {
+    const thumbnails = await getAvailableStreams();
+    const containerDiv = document.getElementById('container')
+    containerDiv.innerHTML = '';
+    containerDiv.innerHTML = `<button onClick="configs()">change configs</button>`
+    containerDiv.innerHTML += `<div class="ftlab-stream-thumbnails"></div>`
+    if(thumbnails.length === 0){
+        containerDiv.innerHTML = `<h3>No streams running currently</h3>`
+    }else{
+        for(var i=0; i<thumbnails.length; i++){
+            const encodedURI = encodeURIComponent(thumbnails[i])
+            current_data.uri = thumbnails[i]
+            try{
+                const someData = await fetch(`./stream/rgb?uri=${encodedURI}`)
+                if(!someData.ok){
+                    throw new Error('Image not found')
+                }
+                const myBlob = await someData.blob();
+                const objectURL = URL.createObjectURL(myBlob);
+                // containerDiv.innerHTML += createCard()
+                containerDiv.innerHTML += createCard(objectURL, i+4)
+            }catch(err){
+                console.log("Couldn't create thumbnail");
+                console.log(err) 
+            }
+        }
+    }
+}
+
+
+/** 
+ * Method to create a single thumbnail
+ */
+createCard = (url, viewers) => {
+    return `<div class='ftlab-card-component' >
+                <img src='${url}' class="thumbnail-img" alt="Hups" width="250px"></img>
+                <p>Viewers: ${viewers}</p>
+                <button onclick="createVideoPlayer()">button</button>
+            </div>`
+}
+
+
+createPeer = () => {
+    // FOR PRODUCTION
+    const ws = new WebSocket("ws://" + location.host + ":" + (location.port == "" ? "80" : location.port) + location.pathname);
+    // const ws = new WebSocket("ws://localhost:8080")
+    ws.binaryType = "arraybuffer";
+    peer = new Peer(ws)
+}
+
+webSocketTest = () => {
+    peer.send("update_cfg", "ftl://utu.fi#reconstruction_default/0/renderer/cool_effect", "true")    
+}
+
+
+connectToStream = () => {
+    const element = document.getElementById('ftlab-stream-video');
+    const converter = new VideoConverter.default(element, 20, 6);
+
+    peer.bind(current_data.uri, (latency, streampckg, pckg) => {
+        if(pckg[0] === 2){
+            function decode(value){
+                converter.appendRawData(value);
+            }
+            decode(pckg[5]);
+            converter.play();
+        };
+    })
+
+    // Start the transaction
+    peer.send("get_stream", (current_data.uri, 30, 0, current_data.uri));
+}
+
+closeStream = () => {
+    peer.sock.close()
+}
+
+
+
+/**
+ * **************
+ * CONFIGURATIONS
+ * **************
+ */
+
+
+current_data.configURI = "ftl://utu.fi#reconstruction_snap8/net"
+
+configs = () => {
+    const container = document.getElementById("container");
+    container.innerHTML = `<div class="ftlab-configurations"></div>`;
+    renderConfigOptions();
+}
+
+
+renderConfigOptions = () => {
+    const input = `<p>input1</p><br>ftl://utu.fi#<input type="text">`
+    const doc = document.getElementsByClassName('ftlab-configurations')[0];
+    doc.innerHTML = input;
+}
+
+/**
+ * 
+ */
+loadConfigs = async (str) => {
+    const configURI = encodeURIComponent(`ftl://utu.fi#reconstruction_snap8${str}`);
+    const uri = encodeURIComponent(current_data.uri)
+    const rawResp = await fetch(`./stream/config?settings=${configURI}&uri=${uri}`)
+    const response = await rawResp.json();
+    const content = JSON.parse(response);
+    container.innerHTML += `<p>${response}</p>`;
+}
+
+// current_data.configData = '{"peers": 1}';
+
+/**
+ * Method to send configurations to backend 
+ */
+saveConfigs = async () => {
+    let {uri, configURI, configData} = current_data
+    const rawResp = await fetch('./stream/config', {
+        method: 'POST',
+        headers: {
+            'Accept': 'application/json',
+            'Content-Type': 'application/json'
+        },
+        body: JSON.stringify({peerURI: uri, configURI, data: configData, saveToCPP: true})
+    });
+    const content = await rawResp.json();
+}
\ No newline at end of file
diff --git a/web-service/public/js/lib/configs.js b/web-service/public/js/lib/configs.js
new file mode 100644
index 0000000000000000000000000000000000000000..084cee119ebc3084da00840267e3ca13d9e8a712
--- /dev/null
+++ b/web-service/public/js/lib/configs.js
@@ -0,0 +1,7 @@
+/**
+ * Mayby someday index.js will be refactored and this file will be used
+ */
+
+
+const container = document.getElementById("container")
+container.innerHTML = `<div class="ftlab-configurations"></div>`
\ No newline at end of file
diff --git a/web-service/public/js/lib/dist/controller.d.ts b/web-service/public/js/lib/dist/controller.d.ts
new file mode 100644
index 0000000000000000000000000000000000000000..e9d88fa431d98d570ab9c5e6721092a7596aca00
--- /dev/null
+++ b/web-service/public/js/lib/dist/controller.d.ts
@@ -0,0 +1,24 @@
+export declare const mimeType = "video/mp4; codecs=\"avc1.42E01E\"";
+export declare class VideoController {
+    private element;
+    private mediaSource;
+    private sourceBuffer;
+    private receiveBuffer;
+    private remuxer;
+    private mediaReady;
+    private mediaReadyPromise;
+    private queue;
+    private isFirstFrame;
+    static readonly errorNotes: {
+        [x: number]: string;
+    };
+    constructor(element: HTMLVideoElement);
+    setup(): Promise<void>;
+    play(): void;
+    pause(): void;
+    reset(): void;
+    appendRawData(data: ArrayLike<number>): void;
+    private writeFragment(dts, pay);
+    private writeBuffer(data);
+    private doAppend(data);
+}
diff --git a/web-service/public/js/lib/dist/controller.js b/web-service/public/js/lib/dist/controller.js
new file mode 100644
index 0000000000000000000000000000000000000000..b944a55db158280547417f5bf829b1e2e43cf5df
--- /dev/null
+++ b/web-service/public/js/lib/dist/controller.js
@@ -0,0 +1,241 @@
+"use strict";
+Object.defineProperty(exports, "__esModule", { value: true });
+var h264_1 = require("./h264");
+var mp4_generator_1 = require("./mp4-generator");
+var NALU_1 = require("./util/NALU");
+exports.mimeType = 'video/mp4; codecs="avc1.42E01E"';
+var fps = 30;
+var fpf = 6;
+var VideoController = (function () {
+    function VideoController(element) {
+        this.element = element;
+        this.receiveBuffer = new VideoStreamBuffer();
+        this.queue = [];
+        if (!MediaSource || !MediaSource.isTypeSupported(exports.mimeType)) {
+            throw new Error("Your browser is not supported: " + exports.mimeType);
+        }
+        this.reset();
+    }
+    Object.defineProperty(VideoController, "errorNotes", {
+        get: function () {
+            return _a = {},
+                _a[MediaError.MEDIA_ERR_ABORTED] = 'fetching process aborted by user',
+                _a[MediaError.MEDIA_ERR_NETWORK] = 'error occurred when downloading',
+                _a[MediaError.MEDIA_ERR_DECODE] = 'error occurred when decoding',
+                _a[MediaError.MEDIA_ERR_SRC_NOT_SUPPORTED] = 'audio/video not supported',
+                _a;
+            var _a;
+        },
+        enumerable: true,
+        configurable: true
+    });
+    VideoController.prototype.setup = function () {
+        var _this = this;
+        this.mediaReadyPromise = new Promise(function (resolve, _reject) {
+            _this.mediaSource.addEventListener('sourceopen', function () {
+                console.log("Media Source opened.");
+                _this.sourceBuffer = _this.mediaSource.addSourceBuffer(exports.mimeType);
+                _this.sourceBuffer.addEventListener('updateend', function () {
+                    console.log("  SourceBuffer updateend");
+                    console.log("    sourceBuffer.buffered.length=" + _this.sourceBuffer.buffered.length);
+                    for (var i = 0, len = _this.sourceBuffer.buffered.length; i < len; i++) {
+                        console.log("    sourceBuffer.buffered [" + i + "]: " + _this.sourceBuffer.buffered.start(i) + ", " + _this.sourceBuffer.buffered.end(i));
+                    }
+                    console.log("  mediasource.duration=" + _this.mediaSource.duration);
+                    console.log("  mediasource.readyState=" + _this.mediaSource.readyState);
+                    console.log("  video.duration=" + _this.element.duration);
+                    console.log("    video.buffered.length=" + _this.element.buffered.length);
+                    for (var i = 0, len = _this.element.buffered.length; i < len; i++) {
+                        console.log("    video.buffered [" + i + "]: " + _this.element.buffered.start(i) + ", " + _this.element.buffered.end(i));
+                    }
+                    console.log("  video.currentTimen=" + _this.element.currentTime);
+                    console.log("  video.readyState=" + _this.element.readyState);
+                    var data = _this.queue.shift();
+                    if (data) {
+                        _this.writeBuffer(data);
+                    }
+                });
+                _this.sourceBuffer.addEventListener('error', function () {
+                    console.error('  SourceBuffer errored!');
+                });
+                _this.mediaReady = true;
+                resolve();
+            }, false);
+            _this.mediaSource.addEventListener('sourceclose', function () {
+                console.log("Media Source closed.");
+                _this.mediaReady = false;
+            }, false);
+            _this.element.src = URL.createObjectURL(_this.mediaSource);
+        });
+        return this.mediaReadyPromise;
+    };
+    VideoController.prototype.play = function () {
+        var _this = this;
+        if (!this.element.paused) {
+            return;
+        }
+        if (this.mediaReady && this.element.readyState >= 2) {
+            this.element.play();
+        }
+        else {
+            var handler_1 = function () {
+                _this.play();
+                _this.element.removeEventListener('canplaythrough', handler_1);
+            };
+            this.element.addEventListener('canplaythrough', handler_1);
+        }
+    };
+    VideoController.prototype.pause = function () {
+        if (this.element.paused) {
+            return;
+        }
+        this.element.pause();
+    };
+    VideoController.prototype.reset = function () {
+        this.receiveBuffer.clear();
+        if (this.mediaSource && this.mediaSource.readyState === 'open') {
+            this.mediaSource.duration = 0;
+            this.mediaSource.endOfStream();
+        }
+        this.mediaSource = new MediaSource();
+        this.remuxer = new h264_1.H264Remuxer(fps, fpf, fps * 60);
+        this.mediaReady = false;
+        this.mediaReadyPromise = undefined;
+        this.queue = [];
+        this.isFirstFrame = true;
+    };
+    VideoController.prototype.appendRawData = function (data) {
+        var nalus = this.receiveBuffer.append(data);
+        for (var _i = 0, nalus_1 = nalus; _i < nalus_1.length; _i++) {
+            var nalu = nalus_1[_i];
+            var ret = this.remuxer.remux(nalu);
+            if (ret) {
+                this.writeFragment(ret[0], ret[1]);
+            }
+        }
+    };
+    VideoController.prototype.writeFragment = function (dts, pay) {
+        var remuxer = this.remuxer;
+        if (remuxer.mp4track.isKeyFrame) {
+            this.writeBuffer(mp4_generator_1.MP4.initSegment([remuxer.mp4track], Infinity, remuxer.timescale));
+        }
+        if (pay && pay.byteLength) {
+            console.log(" Put framgment: " + remuxer.seq + ", frames=" + remuxer.mp4track.samples.length + ", size=" + pay.byteLength);
+            var fragment = mp4_generator_1.MP4.fragmentSegment(remuxer.seq, dts, remuxer.mp4track, pay);
+            this.writeBuffer(fragment);
+            remuxer.flush();
+        }
+        else {
+            console.error("Nothing payload!");
+        }
+    };
+    VideoController.prototype.writeBuffer = function (data) {
+        var _this = this;
+        if (this.mediaReady) {
+            if (this.sourceBuffer.updating) {
+                this.queue.push(data);
+            }
+            else {
+                this.doAppend(data);
+            }
+        }
+        else {
+            this.queue.push(data);
+            if (this.mediaReadyPromise) {
+                this.mediaReadyPromise.then(function () {
+                    if (!_this.sourceBuffer.updating) {
+                        var d = _this.queue.shift();
+                        if (d) {
+                            _this.writeBuffer(d);
+                        }
+                    }
+                });
+                this.mediaReadyPromise = undefined;
+            }
+        }
+    };
+    VideoController.prototype.doAppend = function (data) {
+        var error = this.element.error;
+        if (error) {
+            console.error("MSE Error Occured: " + VideoController.errorNotes[error.code]);
+            this.element.pause();
+            if (this.mediaSource.readyState === 'open') {
+                this.mediaSource.endOfStream();
+            }
+        }
+        else {
+            try {
+                this.sourceBuffer.appendBuffer(data);
+                console.log("  appended buffer: size=" + data.byteLength);
+            }
+            catch (err) {
+                console.error("MSE Error occured while appending buffer. " + err.name + ": " + err.message);
+            }
+        }
+    };
+    return VideoController;
+}());
+exports.VideoController = VideoController;
+var VideoStreamBuffer = (function () {
+    function VideoStreamBuffer() {
+    }
+    VideoStreamBuffer.prototype.clear = function () {
+        this.buffer = undefined;
+    };
+    VideoStreamBuffer.prototype.append = function (value) {
+        var nextNalHeader = function (b) {
+            var i = 3;
+            return function () {
+                var count = 0;
+                for (; i < b.length; i++) {
+                    switch (b[i]) {
+                        case 0:
+                            count++;
+                            break;
+                        case 1:
+                            if (count === 3) {
+                                return i - 3;
+                            }
+                        default:
+                            count = 0;
+                    }
+                }
+                return;
+            };
+        };
+        var result = [];
+        var buffer;
+        if (this.buffer) {
+            if (value[3] === 1 && value[2] === 0 && value[1] === 0 && value[0] === 0) {
+                result.push(new NALU_1.NALU(this.buffer.subarray(4)));
+                buffer = Uint8Array.from(value);
+            }
+        }
+        if (buffer == null) {
+            buffer = this.mergeBuffer(value);
+        }
+        var index;
+        var lastIndex = 0;
+        var f = nextNalHeader(buffer);
+        while (index = f()) {
+            result.push(new NALU_1.NALU(buffer.subarray(lastIndex + 4, index)));
+            lastIndex = index;
+        }
+        this.buffer = buffer.subarray(lastIndex);
+        return result;
+    };
+    VideoStreamBuffer.prototype.mergeBuffer = function (value) {
+        if (this.buffer == null) {
+            return Uint8Array.from(value);
+        }
+        else {
+            var newBuffer = new Uint8Array(this.buffer.byteLength + value.length);
+            if (this.buffer.byteLength > 0) {
+                newBuffer.set(this.buffer, 0);
+            }
+            newBuffer.set(value, this.buffer.byteLength);
+            return newBuffer;
+        }
+    };
+    return VideoStreamBuffer;
+}());
diff --git a/web-service/public/js/lib/dist/h264-parser.d.ts b/web-service/public/js/lib/dist/h264-parser.d.ts
new file mode 100644
index 0000000000000000000000000000000000000000..8bea94fb4455a7ea35f76402744136828d306d24
--- /dev/null
+++ b/web-service/public/js/lib/dist/h264-parser.d.ts
@@ -0,0 +1,19 @@
+import H264Remuxer from './h264-remuxer';
+import NALU from './util/NALU';
+export interface SEIMessage {
+    type: number;
+}
+export default class H264Parser {
+    private remuxer;
+    private track;
+    constructor(remuxer: H264Remuxer);
+    private parseSEI(sei);
+    private parseSPS(sps);
+    private parsePPS(pps);
+    parseNAL(unit: NALU): boolean;
+    private static skipScalingList(decoder, count);
+    private static readSPS(data);
+    private static readSEI(data);
+    private static readSEIMessage(decoder);
+    private static readSEIPayload(decoder, type, size);
+}
diff --git a/web-service/public/js/lib/dist/h264-parser.js b/web-service/public/js/lib/dist/h264-parser.js
new file mode 100644
index 0000000000000000000000000000000000000000..2cb5245f880a9508d1c4560546ed05f9f1041c7f
--- /dev/null
+++ b/web-service/public/js/lib/dist/h264-parser.js
@@ -0,0 +1,295 @@
+"use strict";
+Object.defineProperty(exports, "__esModule", { value: true });
+var bit_stream_1 = require("./util/bit-stream");
+var debug = require("./util/debug");
+var NALU_1 = require("./util/NALU");
+var H264Parser = (function () {
+    function H264Parser(remuxer) {
+        this.remuxer = remuxer;
+        this.track = remuxer.mp4track;
+    }
+    H264Parser.prototype.parseSEI = function (sei) {
+        var messages = H264Parser.readSEI(sei);
+        for (var _i = 0, messages_1 = messages; _i < messages_1.length; _i++) {
+            var m = messages_1[_i];
+            switch (m.type) {
+                case 0:
+                    this.track.seiBuffering = true;
+                    break;
+                case 5:
+                    return true;
+                default:
+                    break;
+            }
+        }
+        return false;
+    };
+    H264Parser.prototype.parseSPS = function (sps) {
+        var config = H264Parser.readSPS(sps);
+        this.track.width = config.width;
+        this.track.height = config.height;
+        this.track.sps = [sps];
+        this.track.codec = 'avc1.';
+        var codecArray = new DataView(sps.buffer, sps.byteOffset + 1, 4);
+        for (var i = 0; i < 3; ++i) {
+            var h = codecArray.getUint8(i).toString(16);
+            if (h.length < 2) {
+                h = '0' + h;
+            }
+            this.track.codec += h;
+        }
+    };
+    H264Parser.prototype.parsePPS = function (pps) {
+        this.track.pps = [pps];
+    };
+    H264Parser.prototype.parseNAL = function (unit) {
+        if (!unit) {
+            return false;
+        }
+        var push = false;
+        switch (unit.type()) {
+            case NALU_1.default.NDR:
+            case NALU_1.default.IDR:
+                push = true;
+                break;
+            case NALU_1.default.SEI:
+                push = this.parseSEI(unit.getData().subarray(4));
+                break;
+            case NALU_1.default.SPS:
+                if (this.track.sps.length === 0) {
+                    this.parseSPS(unit.getData().subarray(4));
+                    debug.log(" Found SPS type NALU frame.");
+                    if (!this.remuxer.readyToDecode && this.track.pps.length > 0 && this.track.sps.length > 0) {
+                        this.remuxer.readyToDecode = true;
+                    }
+                }
+                break;
+            case NALU_1.default.PPS:
+                if (this.track.pps.length === 0) {
+                    this.parsePPS(unit.getData().subarray(4));
+                    debug.log(" Found PPS type NALU frame.");
+                    if (!this.remuxer.readyToDecode && this.track.pps.length > 0 && this.track.sps.length > 0) {
+                        this.remuxer.readyToDecode = true;
+                    }
+                }
+                break;
+            default:
+                debug.log(" Found Unknown type NALU frame. type=" + unit.type());
+                break;
+        }
+        return push;
+    };
+    H264Parser.skipScalingList = function (decoder, count) {
+        var lastScale = 8;
+        var nextScale = 8;
+        for (var j = 0; j < count; j++) {
+            if (nextScale !== 0) {
+                var deltaScale = decoder.readEG();
+                nextScale = (lastScale + deltaScale + 256) % 256;
+            }
+            lastScale = (nextScale === 0) ? lastScale : nextScale;
+        }
+    };
+    H264Parser.readSPS = function (data) {
+        var decoder = new bit_stream_1.default(data);
+        var frameCropLeftOffset = 0;
+        var frameCropRightOffset = 0;
+        var frameCropTopOffset = 0;
+        var frameCropBottomOffset = 0;
+        var sarScale = 1;
+        decoder.readUByte();
+        var profileIdc = decoder.readUByte();
+        decoder.skipBits(5);
+        decoder.skipBits(3);
+        decoder.skipBits(8);
+        decoder.skipUEG();
+        if (profileIdc === 100 ||
+            profileIdc === 110 ||
+            profileIdc === 122 ||
+            profileIdc === 244 ||
+            profileIdc === 44 ||
+            profileIdc === 83 ||
+            profileIdc === 86 ||
+            profileIdc === 118 ||
+            profileIdc === 128) {
+            var chromaFormatIdc = decoder.readUEG();
+            if (chromaFormatIdc === 3) {
+                decoder.skipBits(1);
+            }
+            decoder.skipUEG();
+            decoder.skipUEG();
+            decoder.skipBits(1);
+            if (decoder.readBoolean()) {
+                var scalingListCount = (chromaFormatIdc !== 3) ? 8 : 12;
+                for (var i = 0; i < scalingListCount; ++i) {
+                    if (decoder.readBoolean()) {
+                        if (i < 6) {
+                            H264Parser.skipScalingList(decoder, 16);
+                        }
+                        else {
+                            H264Parser.skipScalingList(decoder, 64);
+                        }
+                    }
+                }
+            }
+        }
+        decoder.skipUEG();
+        var picOrderCntType = decoder.readUEG();
+        if (picOrderCntType === 0) {
+            decoder.readUEG();
+        }
+        else if (picOrderCntType === 1) {
+            decoder.skipBits(1);
+            decoder.skipEG();
+            decoder.skipEG();
+            var numRefFramesInPicOrderCntCycle = decoder.readUEG();
+            for (var i = 0; i < numRefFramesInPicOrderCntCycle; ++i) {
+                decoder.skipEG();
+            }
+        }
+        decoder.skipUEG();
+        decoder.skipBits(1);
+        var picWidthInMbsMinus1 = decoder.readUEG();
+        var picHeightInMapUnitsMinus1 = decoder.readUEG();
+        var frameMbsOnlyFlag = decoder.readBits(1);
+        if (frameMbsOnlyFlag === 0) {
+            decoder.skipBits(1);
+        }
+        decoder.skipBits(1);
+        if (decoder.readBoolean()) {
+            frameCropLeftOffset = decoder.readUEG();
+            frameCropRightOffset = decoder.readUEG();
+            frameCropTopOffset = decoder.readUEG();
+            frameCropBottomOffset = decoder.readUEG();
+        }
+        if (decoder.readBoolean()) {
+            if (decoder.readBoolean()) {
+                var sarRatio = void 0;
+                var aspectRatioIdc = decoder.readUByte();
+                switch (aspectRatioIdc) {
+                    case 1:
+                        sarRatio = [1, 1];
+                        break;
+                    case 2:
+                        sarRatio = [12, 11];
+                        break;
+                    case 3:
+                        sarRatio = [10, 11];
+                        break;
+                    case 4:
+                        sarRatio = [16, 11];
+                        break;
+                    case 5:
+                        sarRatio = [40, 33];
+                        break;
+                    case 6:
+                        sarRatio = [24, 11];
+                        break;
+                    case 7:
+                        sarRatio = [20, 11];
+                        break;
+                    case 8:
+                        sarRatio = [32, 11];
+                        break;
+                    case 9:
+                        sarRatio = [80, 33];
+                        break;
+                    case 10:
+                        sarRatio = [18, 11];
+                        break;
+                    case 11:
+                        sarRatio = [15, 11];
+                        break;
+                    case 12:
+                        sarRatio = [64, 33];
+                        break;
+                    case 13:
+                        sarRatio = [160, 99];
+                        break;
+                    case 14:
+                        sarRatio = [4, 3];
+                        break;
+                    case 15:
+                        sarRatio = [3, 2];
+                        break;
+                    case 16:
+                        sarRatio = [2, 1];
+                        break;
+                    case 255: {
+                        sarRatio = [decoder.readUByte() << 8 | decoder.readUByte(), decoder.readUByte() << 8 | decoder.readUByte()];
+                        break;
+                    }
+                    default: {
+                        debug.error("  H264: Unknown aspectRatioIdc=" + aspectRatioIdc);
+                    }
+                }
+                if (sarRatio) {
+                    sarScale = sarRatio[0] / sarRatio[1];
+                }
+            }
+            if (decoder.readBoolean()) {
+                decoder.skipBits(1);
+            }
+            if (decoder.readBoolean()) {
+                decoder.skipBits(4);
+                if (decoder.readBoolean()) {
+                    decoder.skipBits(24);
+                }
+            }
+            if (decoder.readBoolean()) {
+                decoder.skipUEG();
+                decoder.skipUEG();
+            }
+            if (decoder.readBoolean()) {
+                var unitsInTick = decoder.readUInt();
+                var timeScale = decoder.readUInt();
+                var fixedFrameRate = decoder.readBoolean();
+                var frameDuration = timeScale / (2 * unitsInTick);
+                debug.log("timescale: " + timeScale + "; unitsInTick: " + unitsInTick + "; " +
+                    ("fixedFramerate: " + fixedFrameRate + "; avgFrameDuration: " + frameDuration));
+            }
+        }
+        return {
+            width: Math.ceil((((picWidthInMbsMinus1 + 1) * 16) - frameCropLeftOffset * 2 - frameCropRightOffset * 2) * sarScale),
+            height: ((2 - frameMbsOnlyFlag) * (picHeightInMapUnitsMinus1 + 1) * 16) -
+                ((frameMbsOnlyFlag ? 2 : 4) * (frameCropTopOffset + frameCropBottomOffset)),
+        };
+    };
+    H264Parser.readSEI = function (data) {
+        var decoder = new bit_stream_1.default(data);
+        decoder.skipBits(8);
+        var result = [];
+        while (decoder.bitsAvailable > 3 * 8) {
+            result.push(this.readSEIMessage(decoder));
+        }
+        return result;
+    };
+    H264Parser.readSEIMessage = function (decoder) {
+        function get() {
+            var result = 0;
+            while (true) {
+                var value = decoder.readUByte();
+                result += value;
+                if (value !== 0xff) {
+                    break;
+                }
+            }
+            return result;
+        }
+        var payloadType = get();
+        var payloadSize = get();
+        return this.readSEIPayload(decoder, payloadType, payloadSize);
+    };
+    H264Parser.readSEIPayload = function (decoder, type, size) {
+        var result;
+        switch (type) {
+            default:
+                result = { type: type };
+                decoder.skipBits(size * 8);
+        }
+        decoder.skipBits(decoder.bitsAvailable % 8);
+        return result;
+    };
+    return H264Parser;
+}());
+exports.default = H264Parser;
diff --git a/web-service/public/js/lib/dist/h264-remuxer.d.ts b/web-service/public/js/lib/dist/h264-remuxer.d.ts
new file mode 100644
index 0000000000000000000000000000000000000000..0042feddbb3ab7ffc01027f330988d477e7c8f91
--- /dev/null
+++ b/web-service/public/js/lib/dist/h264-remuxer.d.ts
@@ -0,0 +1,23 @@
+import { Track } from './types';
+import NALU from './util/NALU';
+export default class H264Remuxer {
+    fps: number;
+    framePerFragment: number;
+    timescale: number;
+    readyToDecode: boolean;
+    private totalDTS;
+    private stepDTS;
+    private frameCount;
+    private seq;
+    mp4track: Track;
+    private unitSamples;
+    private parser;
+    private static getTrackID();
+    constructor(fps: number, framePerFragment: number, timescale: number);
+    readonly seqNum: number;
+    remux(nalu: NALU): [number, Uint8Array] | undefined;
+    private createNextFrame();
+    flush(): void;
+    private getFragment();
+    private checkReadyToDecode();
+}
diff --git a/web-service/public/js/lib/dist/h264-remuxer.js b/web-service/public/js/lib/dist/h264-remuxer.js
new file mode 100644
index 0000000000000000000000000000000000000000..f3d181c811e3bb19da2415c2fce5315b500a0f71
--- /dev/null
+++ b/web-service/public/js/lib/dist/h264-remuxer.js
@@ -0,0 +1,121 @@
+"use strict";
+Object.defineProperty(exports, "__esModule", { value: true });
+var h264_parser_1 = require("./h264-parser");
+var debug = require("./util/debug");
+var NALU_1 = require("./util/NALU");
+var trackId = 1;
+var H264Remuxer = (function () {
+    function H264Remuxer(fps, framePerFragment, timescale) {
+        this.fps = fps;
+        this.framePerFragment = framePerFragment;
+        this.timescale = timescale;
+        this.readyToDecode = false;
+        this.totalDTS = 0;
+        this.stepDTS = Math.round(this.timescale / this.fps);
+        this.frameCount = 0;
+        this.seq = 1;
+        this.mp4track = {
+            id: H264Remuxer.getTrackID(),
+            type: 'video',
+            len: 0,
+            codec: '',
+            sps: [],
+            pps: [],
+            seiBuffering: false,
+            width: 0,
+            height: 0,
+            timescale: timescale,
+            duration: timescale,
+            samples: [],
+            isKeyFrame: true,
+        };
+        this.unitSamples = [[]];
+        this.parser = new h264_parser_1.default(this);
+    }
+    H264Remuxer.getTrackID = function () {
+        return trackId++;
+    };
+    Object.defineProperty(H264Remuxer.prototype, "seqNum", {
+        get: function () {
+            return this.seq;
+        },
+        enumerable: true,
+        configurable: true
+    });
+    H264Remuxer.prototype.remux = function (nalu) {
+        if (this.mp4track.seiBuffering && nalu.type() === NALU_1.default.SEI) {
+            return this.createNextFrame();
+        }
+        if (this.parser.parseNAL(nalu)) {
+            this.unitSamples[this.unitSamples.length - 1].push(nalu);
+            this.mp4track.len += nalu.getSize();
+        }
+        if (!this.mp4track.seiBuffering && (nalu.type() === NALU_1.default.IDR || nalu.type() === NALU_1.default.NDR)) {
+            return this.createNextFrame();
+        }
+        return;
+    };
+    H264Remuxer.prototype.createNextFrame = function () {
+        if (this.mp4track.len > 0) {
+            this.frameCount++;
+            if (this.frameCount % this.framePerFragment === 0) {
+                var fragment = this.getFragment();
+                if (fragment) {
+                    var dts = this.totalDTS;
+                    this.totalDTS = this.stepDTS * this.frameCount;
+                    return [dts, fragment];
+                }
+                else {
+                    debug.log("No mp4 sample data.");
+                }
+            }
+            this.unitSamples.push([]);
+        }
+        return;
+    };
+    H264Remuxer.prototype.flush = function () {
+        this.seq++;
+        this.mp4track.len = 0;
+        this.mp4track.samples = [];
+        this.mp4track.isKeyFrame = false;
+        this.unitSamples = [[]];
+    };
+    H264Remuxer.prototype.getFragment = function () {
+        if (!this.checkReadyToDecode()) {
+            return undefined;
+        }
+        var payload = new Uint8Array(this.mp4track.len);
+        this.mp4track.samples = [];
+        var offset = 0;
+        for (var i = 0, len = this.unitSamples.length; i < len; i++) {
+            var units = this.unitSamples[i];
+            if (units.length === 0) {
+                continue;
+            }
+            var mp4Sample = {
+                size: 0,
+                cts: this.stepDTS * i,
+            };
+            for (var _i = 0, units_1 = units; _i < units_1.length; _i++) {
+                var unit = units_1[_i];
+                mp4Sample.size += unit.getSize();
+                payload.set(unit.getData(), offset);
+                offset += unit.getSize();
+            }
+            this.mp4track.samples.push(mp4Sample);
+        }
+        if (offset === 0) {
+            return undefined;
+        }
+        return payload;
+    };
+    H264Remuxer.prototype.checkReadyToDecode = function () {
+        if (!this.readyToDecode || this.unitSamples.filter(function (array) { return array.length > 0; }).length === 0) {
+            debug.log("Not ready to decode! readyToDecode(" + this.readyToDecode + ") is false or units is empty.");
+            return false;
+        }
+        return true;
+    };
+    return H264Remuxer;
+}());
+exports.default = H264Remuxer;
diff --git a/web-service/public/js/lib/dist/h264.d.ts b/web-service/public/js/lib/dist/h264.d.ts
new file mode 100644
index 0000000000000000000000000000000000000000..6b55d9904fbe5b408ae079622fdc2ed1c6e2f80a
--- /dev/null
+++ b/web-service/public/js/lib/dist/h264.d.ts
@@ -0,0 +1,23 @@
+import { Track } from './types';
+import { NALU } from './util/NALU';
+export default class H264Remuxer {
+    fps: number;
+    framePerFragment: number;
+    timescale: number;
+    readyToDecode: boolean;
+    private totalDTS;
+    private stepDTS;
+    private frameCount;
+    private seq;
+    mp4track: Track;
+    private unitSamples;
+    private parser;
+    private static getTrackID();
+    constructor(fps: number, framePerFragment: number, timescale: number);
+    readonly seqNum: number;
+    remux(nalu: NALU): [number, Uint8Array] | undefined;
+    private createNextFrame();
+    flush(): void;
+    private getFragment();
+    private checkReadyToDecode();
+}
diff --git a/web-service/public/js/lib/dist/h264.js b/web-service/public/js/lib/dist/h264.js
new file mode 100644
index 0000000000000000000000000000000000000000..206ad79f4a12725e18662d249e0d3fb64d13616d
--- /dev/null
+++ b/web-service/public/js/lib/dist/h264.js
@@ -0,0 +1,118 @@
+"use strict";
+Object.defineProperty(exports, "__esModule", { value: true });
+var h264_parser_1 = require("./h264-parser");
+var NALU_1 = require("./util/NALU");
+var trackId = 1;
+var H264Remuxer = (function () {
+    function H264Remuxer(fps, framePerFragment, timescale) {
+        this.fps = fps;
+        this.framePerFragment = framePerFragment;
+        this.timescale = timescale;
+        this.readyToDecode = false;
+        this.totalDTS = 0;
+        this.stepDTS = Math.round(this.timescale / this.fps);
+        this.frameCount = 0;
+        this.seq = 1;
+        this.mp4track = {
+            id: H264Remuxer.getTrackID(),
+            type: 'video',
+            len: 0,
+            codec: '',
+            sps: [],
+            pps: [],
+            seiBuffering: false,
+            width: 0,
+            height: 0,
+            timescale: timescale,
+            duration: timescale,
+            samples: [],
+            isKeyFrame: true,
+        };
+        this.unitSamples = [[]];
+        this.parser = new h264_parser_1.H264Parser(this);
+    }
+    H264Remuxer.getTrackID = function () {
+        return trackId++;
+    };
+    Object.defineProperty(H264Remuxer.prototype, "seqNum", {
+        get: function () {
+            return this.seq;
+        },
+        enumerable: true,
+        configurable: true
+    });
+    H264Remuxer.prototype.remux = function (nalu) {
+        if (this.mp4track.seiBuffering && nalu.type() === NALU_1.NALU.SEI) {
+            return this.createNextFrame();
+        }
+        if (this.parser.parseNAL(nalu)) {
+            this.unitSamples[this.unitSamples.length - 1].push(nalu);
+            this.mp4track.len += nalu.getSize();
+        }
+        if (!this.mp4track.seiBuffering && (nalu.type() === NALU_1.NALU.IDR || nalu.type() === NALU_1.NALU.NDR)) {
+            return this.createNextFrame();
+        }
+        return;
+    };
+    H264Remuxer.prototype.createNextFrame = function () {
+        if (this.mp4track.len > 0) {
+            this.frameCount++;
+            if (this.frameCount % this.framePerFragment === 0) {
+                var fragment = this.getFragment();
+                if (fragment) {
+                    var dts = this.totalDTS;
+                    this.totalDTS = this.stepDTS * this.frameCount;
+                    return [dts, fragment];
+                }
+            }
+            this.unitSamples.push([]);
+        }
+        return;
+    };
+    H264Remuxer.prototype.flush = function () {
+        this.seq++;
+        this.mp4track.len = 0;
+        this.mp4track.samples = [];
+        this.mp4track.isKeyFrame = false;
+        this.unitSamples = [[]];
+    };
+    H264Remuxer.prototype.getFragment = function () {
+        if (!this.checkReadyToDecode()) {
+            return undefined;
+        }
+        var payload = new Uint8Array(this.mp4track.len);
+        this.mp4track.samples = [];
+        var offset = 0;
+        for (var i = 0, len = this.unitSamples.length; i < len; i++) {
+            var units = this.unitSamples[i];
+            if (units.length === 0) {
+                continue;
+            }
+            var mp4Sample = {
+                size: 0,
+                cts: this.stepDTS * i,
+            };
+            for (var _i = 0, units_1 = units; _i < units_1.length; _i++) {
+                var unit = units_1[_i];
+                mp4Sample.size += unit.getSize();
+                payload.set(unit.getData(), offset);
+                offset += unit.getSize();
+            }
+            this.mp4track.samples.push(mp4Sample);
+        }
+        if (offset === 0) {
+            console.log("No mp4 sample data.");
+            return undefined;
+        }
+        return payload;
+    };
+    H264Remuxer.prototype.checkReadyToDecode = function () {
+        if (!this.readyToDecode || this.unitSamples.filter(function (array) { return array.length > 0; }).length === 0) {
+            console.log("Not ready to decode! readyToDecode(" + this.readyToDecode + ") is false or units is empty.");
+            return false;
+        }
+        return true;
+    };
+    return H264Remuxer;
+}());
+exports.default = H264Remuxer;
diff --git a/web-service/public/js/lib/dist/index.d.ts b/web-service/public/js/lib/dist/index.d.ts
new file mode 100644
index 0000000000000000000000000000000000000000..12f0901b72982089e0fcdaab54fd95f6dcccb58c
--- /dev/null
+++ b/web-service/public/js/lib/dist/index.d.ts
@@ -0,0 +1,27 @@
+export declare const mimeType = "video/mp4; codecs=\"avc1.42E01E\"";
+export { setLogger } from './util/debug';
+export default class VideoConverter {
+    private element;
+    private fps;
+    private fpf;
+    private mediaSource;
+    private sourceBuffer;
+    private receiveBuffer;
+    private remuxer;
+    private mediaReady;
+    private mediaReadyPromise;
+    private queue;
+    private isFirstFrame;
+    static readonly errorNotes: {
+        [x: number]: string;
+    };
+    constructor(element: HTMLVideoElement, fps?: number, fpf?: number);
+    private setup();
+    play(): void;
+    pause(): void;
+    reset(): void;
+    appendRawData(data: ArrayLike<number>): void;
+    private writeFragment(dts, pay);
+    private writeBuffer(data);
+    private doAppend(data);
+}
diff --git a/web-service/public/js/lib/dist/index.js b/web-service/public/js/lib/dist/index.js
new file mode 100644
index 0000000000000000000000000000000000000000..cefdec014662f8021818d7618c37bbaa28277787
--- /dev/null
+++ b/web-service/public/js/lib/dist/index.js
@@ -0,0 +1,187 @@
+"use strict";
+Object.defineProperty(exports, "__esModule", { value: true });
+var h264_remuxer_1 = require("./h264-remuxer");
+var mp4_generator_1 = require("./mp4-generator");
+var debug = require("./util/debug");
+var nalu_stream_buffer_1 = require("./util/nalu-stream-buffer");
+exports.mimeType = 'video/mp4; codecs="avc1.42E01E"';
+var debug_1 = require("./util/debug");
+exports.setLogger = debug_1.setLogger;
+var VideoConverter = (function () {
+    function VideoConverter(element, fps, fpf) {
+        if (fps === void 0) { fps = 60; }
+        if (fpf === void 0) { fpf = fps; }
+        this.element = element;
+        this.fps = fps;
+        this.fpf = fpf;
+        this.receiveBuffer = new nalu_stream_buffer_1.default();
+        this.queue = [];
+        if (!MediaSource || !MediaSource.isTypeSupported(exports.mimeType)) {
+            throw new Error("Your browser is not supported: " + exports.mimeType);
+        }
+        this.reset();
+    }
+    Object.defineProperty(VideoConverter, "errorNotes", {
+        get: function () {
+            return _a = {},
+                _a[MediaError.MEDIA_ERR_ABORTED] = 'fetching process aborted by user',
+                _a[MediaError.MEDIA_ERR_NETWORK] = 'error occurred when downloading',
+                _a[MediaError.MEDIA_ERR_DECODE] = 'error occurred when decoding',
+                _a[MediaError.MEDIA_ERR_SRC_NOT_SUPPORTED] = 'audio/video not supported',
+                _a;
+            var _a;
+        },
+        enumerable: true,
+        configurable: true
+    });
+    VideoConverter.prototype.setup = function () {
+        var _this = this;
+        this.mediaReadyPromise = new Promise(function (resolve, _reject) {
+            _this.mediaSource.addEventListener('sourceopen', function () {
+                debug.log("Media Source opened.");
+                _this.sourceBuffer = _this.mediaSource.addSourceBuffer(exports.mimeType);
+                _this.sourceBuffer.addEventListener('updateend', function () {
+                    debug.log("  SourceBuffer updateend");
+                    debug.log("    sourceBuffer.buffered.length=" + _this.sourceBuffer.buffered.length);
+                    for (var i = 0, len = _this.sourceBuffer.buffered.length; i < len; i++) {
+                        debug.log("    sourceBuffer.buffered [" + i + "]: " +
+                            (_this.sourceBuffer.buffered.start(i) + ", " + _this.sourceBuffer.buffered.end(i)));
+                    }
+                    debug.log("  mediasource.duration=" + _this.mediaSource.duration);
+                    debug.log("  mediasource.readyState=" + _this.mediaSource.readyState);
+                    debug.log("  video.duration=" + _this.element.duration);
+                    debug.log("    video.buffered.length=" + _this.element.buffered.length);
+                    if (debug.isEnable()) {
+                        for (var i = 0, len = _this.element.buffered.length; i < len; i++) {
+                            debug.log("    video.buffered [" + i + "]: " + _this.element.buffered.start(i) + ", " + _this.element.buffered.end(i));
+                        }
+                    }
+                    debug.log("  video.currentTime=" + _this.element.currentTime);
+                    debug.log("  video.readyState=" + _this.element.readyState);
+                    var data = _this.queue.shift();
+                    if (data) {
+                        _this.writeBuffer(data);
+                    }
+                });
+                _this.sourceBuffer.addEventListener('error', function () {
+                    debug.error('  SourceBuffer errored!');
+                });
+                _this.mediaReady = true;
+                resolve();
+            }, false);
+            _this.mediaSource.addEventListener('sourceclose', function () {
+                debug.log("Media Source closed.");
+                _this.mediaReady = false;
+            }, false);
+            _this.element.src = URL.createObjectURL(_this.mediaSource);
+        });
+        return this.mediaReadyPromise;
+    };
+    VideoConverter.prototype.play = function () {
+        var _this = this;
+        if (!this.element.paused) {
+            return;
+        }
+        if (this.mediaReady && this.element.readyState >= 2) {
+            this.element.play();
+        }
+        else {
+            var handler_1 = function () {
+                _this.play();
+                _this.element.removeEventListener('canplaythrough', handler_1);
+            };
+            this.element.addEventListener('canplaythrough', handler_1);
+        }
+    };
+    VideoConverter.prototype.pause = function () {
+        if (this.element.paused) {
+            return;
+        }
+        this.element.pause();
+    };
+    VideoConverter.prototype.reset = function () {
+        this.receiveBuffer.clear();
+        if (this.mediaSource && this.mediaSource.readyState === 'open') {
+            this.mediaSource.duration = 0;
+            this.mediaSource.endOfStream();
+        }
+        this.mediaSource = new MediaSource();
+        this.remuxer = new h264_remuxer_1.default(this.fps, this.fpf, this.fps * 60);
+        this.mediaReady = false;
+        this.mediaReadyPromise = undefined;
+        this.queue = [];
+        this.isFirstFrame = true;
+        this.setup();
+    };
+    VideoConverter.prototype.appendRawData = function (data) {
+        var nalus = this.receiveBuffer.append(data);
+        for (var _i = 0, nalus_1 = nalus; _i < nalus_1.length; _i++) {
+            var nalu = nalus_1[_i];
+            var ret = this.remuxer.remux(nalu);
+            if (ret) {
+                this.writeFragment(ret[0], ret[1]);
+            }
+        }
+    };
+    VideoConverter.prototype.writeFragment = function (dts, pay) {
+        var remuxer = this.remuxer;
+        if (remuxer.mp4track.isKeyFrame) {
+            this.writeBuffer(mp4_generator_1.default.initSegment([remuxer.mp4track], Infinity, remuxer.timescale));
+        }
+        if (pay && pay.byteLength) {
+            debug.log(" Put fragment: " + remuxer.seqNum + ", frames=" + remuxer.mp4track.samples.length + ", size=" + pay.byteLength);
+            var fragment = mp4_generator_1.default.fragmentSegment(remuxer.seqNum, dts, remuxer.mp4track, pay);
+            this.writeBuffer(fragment);
+            remuxer.flush();
+        }
+        else {
+            debug.error("Nothing payload!");
+        }
+    };
+    VideoConverter.prototype.writeBuffer = function (data) {
+        var _this = this;
+        if (this.mediaReady) {
+            if (this.sourceBuffer.updating) {
+                this.queue.push(data);
+            }
+            else {
+                this.doAppend(data);
+            }
+        }
+        else {
+            this.queue.push(data);
+            if (this.mediaReadyPromise) {
+                this.mediaReadyPromise.then(function () {
+                    if (!_this.sourceBuffer.updating) {
+                        var d = _this.queue.shift();
+                        if (d) {
+                            _this.writeBuffer(d);
+                        }
+                    }
+                });
+                this.mediaReadyPromise = undefined;
+            }
+        }
+    };
+    VideoConverter.prototype.doAppend = function (data) {
+        var error = this.element.error;
+        if (error) {
+            debug.error("MSE Error Occured: " + VideoConverter.errorNotes[error.code]);
+            this.element.pause();
+            if (this.mediaSource.readyState === 'open') {
+                this.mediaSource.endOfStream();
+            }
+        }
+        else {
+            try {
+                this.sourceBuffer.appendBuffer(data);
+                debug.log("  appended buffer: size=" + data.byteLength);
+            }
+            catch (err) {
+                debug.error("MSE Error occured while appending buffer. " + err.name + ": " + err.message);
+            }
+        }
+    };
+    return VideoConverter;
+}());
+exports.default = VideoConverter;
diff --git a/web-service/public/js/lib/dist/mp4-generator.d.ts b/web-service/public/js/lib/dist/mp4-generator.d.ts
new file mode 100644
index 0000000000000000000000000000000000000000..c3ac851fa3c1c3ec72fe3918fe0552379abe2a42
--- /dev/null
+++ b/web-service/public/js/lib/dist/mp4-generator.d.ts
@@ -0,0 +1,38 @@
+import { Track } from './types';
+export default class MP4 {
+    private static types;
+    private static initalized;
+    private static FTYP;
+    private static HDLR;
+    private static DINF;
+    private static STSD;
+    private static SMHD;
+    private static VMHD;
+    private static STSZ;
+    private static STTS;
+    private static STSC;
+    private static STCO;
+    private static STYP;
+    private static init();
+    static box(type: number[], ...payload: Uint8Array[]): Uint8Array;
+    static mdat(data: Uint8Array): Uint8Array;
+    static mdhd(timescale: number): Uint8Array;
+    static mdia(track: Track): Uint8Array;
+    static mfhd(sequenceNumber: number): Uint8Array;
+    static minf(track: Track): Uint8Array;
+    static moof(sn: number, baseMediaDecodeTime: number, track: Track): Uint8Array;
+    static moov(tracks: Track[], duration: number, timescale: number): Uint8Array;
+    static mvhd(timescale: number, duration: number): Uint8Array;
+    static mvex(tracks: Track[]): Uint8Array;
+    static trep(): Uint8Array;
+    static stbl(track: Track): Uint8Array;
+    static avc1(track: Track): Uint8Array;
+    static stsd(track: Track): Uint8Array;
+    static tkhd(track: Track): Uint8Array;
+    static traf(track: Track, baseMediaDecodeTime: number): Uint8Array;
+    static trak(track: Track): Uint8Array;
+    static trex(track: Track): Uint8Array;
+    static trun(track: Track, offset: number): Uint8Array;
+    static initSegment(tracks: Track[], duration: number, timescale: number): Uint8Array;
+    static fragmentSegment(sn: number, baseMediaDecodeTime: number, track: Track, payload: Uint8Array): Uint8Array;
+}
diff --git a/web-service/public/js/lib/dist/mp4-generator.js b/web-service/public/js/lib/dist/mp4-generator.js
new file mode 100644
index 0000000000000000000000000000000000000000..a91748998c479aa7a398b66379c8a0c0bfdd43b0
--- /dev/null
+++ b/web-service/public/js/lib/dist/mp4-generator.js
@@ -0,0 +1,454 @@
+"use strict";
+Object.defineProperty(exports, "__esModule", { value: true });
+var MP4 = (function () {
+    function MP4() {
+    }
+    MP4.init = function () {
+        MP4.initalized = true;
+        MP4.types = {
+            avc1: [],
+            avcC: [],
+            btrt: [],
+            dinf: [],
+            dref: [],
+            esds: [],
+            ftyp: [],
+            hdlr: [],
+            mdat: [],
+            mdhd: [],
+            mdia: [],
+            mfhd: [],
+            minf: [],
+            moof: [],
+            moov: [],
+            mp4a: [],
+            mvex: [],
+            mvhd: [],
+            sdtp: [],
+            stbl: [],
+            stco: [],
+            stsc: [],
+            stsd: [],
+            stsz: [],
+            stts: [],
+            styp: [],
+            tfdt: [],
+            tfhd: [],
+            traf: [],
+            trak: [],
+            trun: [],
+            trep: [],
+            trex: [],
+            tkhd: [],
+            vmhd: [],
+            smhd: [],
+        };
+        for (var type in MP4.types) {
+            if (MP4.types.hasOwnProperty(type)) {
+                MP4.types[type] = [
+                    type.charCodeAt(0),
+                    type.charCodeAt(1),
+                    type.charCodeAt(2),
+                    type.charCodeAt(3),
+                ];
+            }
+        }
+        var hdlr = new Uint8Array([
+            0x00,
+            0x00, 0x00, 0x00,
+            0x00, 0x00, 0x00, 0x00,
+            0x76, 0x69, 0x64, 0x65,
+            0x00, 0x00, 0x00, 0x00,
+            0x00, 0x00, 0x00, 0x00,
+            0x00, 0x00, 0x00, 0x00,
+            0x56, 0x69, 0x64, 0x65,
+            0x6f, 0x48, 0x61, 0x6e,
+            0x64, 0x6c, 0x65, 0x72, 0x00,
+        ]);
+        var dref = new Uint8Array([
+            0x00,
+            0x00, 0x00, 0x00,
+            0x00, 0x00, 0x00, 0x01,
+            0x00, 0x00, 0x00, 0x0c,
+            0x75, 0x72, 0x6c, 0x20,
+            0x00,
+            0x00, 0x00, 0x01,
+        ]);
+        var stco = new Uint8Array([
+            0x00,
+            0x00, 0x00, 0x00,
+            0x00, 0x00, 0x00, 0x00,
+        ]);
+        MP4.STTS = MP4.STSC = MP4.STCO = stco;
+        MP4.STSZ = new Uint8Array([
+            0x00,
+            0x00, 0x00, 0x00,
+            0x00, 0x00, 0x00, 0x00,
+            0x00, 0x00, 0x00, 0x00,
+        ]);
+        MP4.VMHD = new Uint8Array([
+            0x00,
+            0x00, 0x00, 0x01,
+            0x00, 0x00,
+            0x00, 0x00,
+            0x00, 0x00,
+            0x00, 0x00,
+        ]);
+        MP4.SMHD = new Uint8Array([
+            0x00,
+            0x00, 0x00, 0x00,
+            0x00, 0x00,
+            0x00, 0x00,
+        ]);
+        MP4.STSD = new Uint8Array([
+            0x00,
+            0x00, 0x00, 0x00,
+            0x00, 0x00, 0x00, 0x01
+        ]);
+        MP4.FTYP = MP4.box(MP4.types.ftyp, new Uint8Array([
+            0x69, 0x73, 0x6f, 0x35,
+            0x00, 0x00, 0x00, 0x01,
+            0x61, 0x76, 0x63, 0x31,
+            0x69, 0x73, 0x6f, 0x35,
+            0x64, 0x61, 0x73, 0x68,
+        ]));
+        MP4.STYP = MP4.box(MP4.types.styp, new Uint8Array([
+            0x6d, 0x73, 0x64, 0x68,
+            0x00, 0x00, 0x00, 0x00,
+            0x6d, 0x73, 0x64, 0x68,
+            0x6d, 0x73, 0x69, 0x78,
+        ]));
+        MP4.DINF = MP4.box(MP4.types.dinf, MP4.box(MP4.types.dref, dref));
+        MP4.HDLR = MP4.box(MP4.types.hdlr, hdlr);
+    };
+    MP4.box = function (type) {
+        var payload = [];
+        for (var _i = 1; _i < arguments.length; _i++) {
+            payload[_i - 1] = arguments[_i];
+        }
+        var size = 8;
+        for (var _a = 0, payload_1 = payload; _a < payload_1.length; _a++) {
+            var p = payload_1[_a];
+            size += p.byteLength;
+        }
+        var result = new Uint8Array(size);
+        result[0] = (size >> 24) & 0xff;
+        result[1] = (size >> 16) & 0xff;
+        result[2] = (size >> 8) & 0xff;
+        result[3] = size & 0xff;
+        result.set(type, 4);
+        size = 8;
+        for (var _b = 0, payload_2 = payload; _b < payload_2.length; _b++) {
+            var box = payload_2[_b];
+            result.set(box, size);
+            size += box.byteLength;
+        }
+        return result;
+    };
+    MP4.mdat = function (data) {
+        return MP4.box(MP4.types.mdat, data);
+    };
+    MP4.mdhd = function (timescale) {
+        return MP4.box(MP4.types.mdhd, new Uint8Array([
+            0x00,
+            0x00, 0x00, 0x00,
+            0x00, 0x00, 0x00, 0x01,
+            0x00, 0x00, 0x00, 0x02,
+            (timescale >> 24) & 0xFF,
+            (timescale >> 16) & 0xFF,
+            (timescale >> 8) & 0xFF,
+            timescale & 0xFF,
+            0x00, 0x00, 0x00, 0x00,
+            0x55, 0xc4,
+            0x00, 0x00,
+        ]));
+    };
+    MP4.mdia = function (track) {
+        return MP4.box(MP4.types.mdia, MP4.mdhd(track.timescale), MP4.HDLR, MP4.minf(track));
+    };
+    MP4.mfhd = function (sequenceNumber) {
+        return MP4.box(MP4.types.mfhd, new Uint8Array([
+            0x00,
+            0x00, 0x00, 0x00,
+            (sequenceNumber >> 24),
+            (sequenceNumber >> 16) & 0xFF,
+            (sequenceNumber >> 8) & 0xFF,
+            sequenceNumber & 0xFF,
+        ]));
+    };
+    MP4.minf = function (track) {
+        return MP4.box(MP4.types.minf, MP4.box(MP4.types.vmhd, MP4.VMHD), MP4.DINF, MP4.stbl(track));
+    };
+    MP4.moof = function (sn, baseMediaDecodeTime, track) {
+        return MP4.box(MP4.types.moof, MP4.mfhd(sn), MP4.traf(track, baseMediaDecodeTime));
+    };
+    MP4.moov = function (tracks, duration, timescale) {
+        var boxes = [];
+        for (var _i = 0, tracks_1 = tracks; _i < tracks_1.length; _i++) {
+            var track = tracks_1[_i];
+            boxes.push(MP4.trak(track));
+        }
+        return MP4.box.apply(MP4, [MP4.types.moov, MP4.mvhd(timescale, duration), MP4.mvex(tracks)].concat(boxes));
+    };
+    MP4.mvhd = function (timescale, duration) {
+        var bytes = new Uint8Array([
+            0x00,
+            0x00, 0x00, 0x00,
+            0x00, 0x00, 0x00, 0x01,
+            0x00, 0x00, 0x00, 0x02,
+            (timescale >> 24) & 0xFF,
+            (timescale >> 16) & 0xFF,
+            (timescale >> 8) & 0xFF,
+            timescale & 0xFF,
+            (duration >> 24) & 0xFF,
+            (duration >> 16) & 0xFF,
+            (duration >> 8) & 0xFF,
+            duration & 0xFF,
+            0x00, 0x01, 0x00, 0x00,
+            0x01, 0x00,
+            0x00, 0x00,
+            0x00, 0x00, 0x00, 0x00,
+            0x00, 0x00, 0x00, 0x00,
+            0x00, 0x01, 0x00, 0x00,
+            0x00, 0x00, 0x00, 0x00,
+            0x00, 0x00, 0x00, 0x00,
+            0x00, 0x00, 0x00, 0x00,
+            0x00, 0x01, 0x00, 0x00,
+            0x00, 0x00, 0x00, 0x00,
+            0x00, 0x00, 0x00, 0x00,
+            0x00, 0x00, 0x00, 0x00,
+            0x40, 0x00, 0x00, 0x00,
+            0x00, 0x00, 0x00, 0x00,
+            0x00, 0x00, 0x00, 0x00,
+            0x00, 0x00, 0x00, 0x00,
+            0x00, 0x00, 0x00, 0x00,
+            0x00, 0x00, 0x00, 0x00,
+            0x00, 0x00, 0x00, 0x00,
+            0x00, 0x00, 0x00, 0x02,
+        ]);
+        return MP4.box(MP4.types.mvhd, bytes);
+    };
+    MP4.mvex = function (tracks) {
+        var boxes = [];
+        for (var _i = 0, tracks_2 = tracks; _i < tracks_2.length; _i++) {
+            var track = tracks_2[_i];
+            boxes.push(MP4.trex(track));
+        }
+        return MP4.box.apply(MP4, [MP4.types.mvex].concat(boxes, [MP4.trep()]));
+    };
+    MP4.trep = function () {
+        return MP4.box(MP4.types.trep, new Uint8Array([
+            0x00,
+            0x00, 0x00, 0x00,
+            0x00, 0x00, 0x00, 0x01,
+        ]));
+    };
+    MP4.stbl = function (track) {
+        return MP4.box(MP4.types.stbl, MP4.stsd(track), MP4.box(MP4.types.stts, MP4.STTS), MP4.box(MP4.types.stsc, MP4.STSC), MP4.box(MP4.types.stsz, MP4.STSZ), MP4.box(MP4.types.stco, MP4.STCO));
+    };
+    MP4.avc1 = function (track) {
+        var sps = [];
+        var pps = [];
+        for (var _i = 0, _a = track.sps; _i < _a.length; _i++) {
+            var data = _a[_i];
+            var len = data.byteLength;
+            sps.push((len >>> 8) & 0xFF);
+            sps.push((len & 0xFF));
+            sps = sps.concat(Array.prototype.slice.call(data));
+        }
+        for (var _b = 0, _c = track.pps; _b < _c.length; _b++) {
+            var data = _c[_b];
+            var len = data.byteLength;
+            pps.push((len >>> 8) & 0xFF);
+            pps.push((len & 0xFF));
+            pps = pps.concat(Array.prototype.slice.call(data));
+        }
+        var avcc = MP4.box(MP4.types.avcC, new Uint8Array([
+            0x01,
+            sps[3],
+            sps[4],
+            sps[5],
+            0xfc | 3,
+            0xE0 | track.sps.length,
+        ].concat(sps).concat([
+            track.pps.length,
+        ]).concat(pps)));
+        var width = track.width;
+        var height = track.height;
+        return MP4.box(MP4.types.avc1, new Uint8Array([
+            0x00, 0x00, 0x00,
+            0x00, 0x00, 0x00,
+            0x00, 0x01,
+            0x00, 0x00,
+            0x00, 0x00,
+            0x00, 0x00, 0x00, 0x00,
+            0x00, 0x00, 0x00, 0x00,
+            0x00, 0x00, 0x00, 0x00,
+            (width >> 8) & 0xFF,
+            width & 0xff,
+            (height >> 8) & 0xFF,
+            height & 0xff,
+            0x00, 0x48, 0x00, 0x00,
+            0x00, 0x48, 0x00, 0x00,
+            0x00, 0x00, 0x00, 0x00,
+            0x00, 0x01,
+            0x12,
+            0x62, 0x69, 0x6E, 0x65,
+            0x6C, 0x70, 0x72, 0x6F,
+            0x2E, 0x72, 0x75, 0x00,
+            0x00, 0x00, 0x00, 0x00,
+            0x00, 0x00, 0x00, 0x00,
+            0x00, 0x00, 0x00, 0x00,
+            0x00, 0x00, 0x00, 0x00,
+            0x00, 0x00, 0x00,
+            0x00, 0x18,
+            0x11, 0x11
+        ]), avcc, MP4.box(MP4.types.btrt, new Uint8Array([
+            0x00, 0x00, 0x00, 0x00,
+            0x00, 0x2d, 0xc6, 0xc0,
+            0x00, 0x2d, 0xc6, 0xc0,
+        ])));
+    };
+    MP4.stsd = function (track) {
+        return MP4.box(MP4.types.stsd, MP4.STSD, MP4.avc1(track));
+    };
+    MP4.tkhd = function (track) {
+        var id = track.id;
+        var width = track.width;
+        var height = track.height;
+        return MP4.box(MP4.types.tkhd, new Uint8Array([
+            0x00,
+            0x00, 0x00, 0x01,
+            0x00, 0x00, 0x00, 0x01,
+            0x00, 0x00, 0x00, 0x02,
+            (id >> 24) & 0xFF,
+            (id >> 16) & 0xFF,
+            (id >> 8) & 0xFF,
+            id & 0xFF,
+            0x00, 0x00, 0x00, 0x00,
+            0x00, 0x00, 0x00, 0x00,
+            0x00, 0x00, 0x00, 0x00,
+            0x00, 0x00, 0x00, 0x00,
+            0x00, 0x00,
+            0x00, 0x00,
+            (track.type === 'audio' ? 0x01 : 0x00), 0x00,
+            0x00, 0x00,
+            0x00, 0x01, 0x00, 0x00,
+            0x00, 0x00, 0x00, 0x00,
+            0x00, 0x00, 0x00, 0x00,
+            0x00, 0x00, 0x00, 0x00,
+            0x00, 0x01, 0x00, 0x00,
+            0x00, 0x00, 0x00, 0x00,
+            0x00, 0x00, 0x00, 0x00,
+            0x00, 0x00, 0x00, 0x00,
+            0x40, 0x00, 0x00, 0x00,
+            (width >> 8) & 0xFF,
+            width & 0xFF,
+            0x00, 0x00,
+            (height >> 8) & 0xFF,
+            height & 0xFF,
+            0x00, 0x00,
+        ]));
+    };
+    MP4.traf = function (track, baseMediaDecodeTime) {
+        var id = track.id;
+        return MP4.box(MP4.types.traf, MP4.box(MP4.types.tfhd, new Uint8Array([
+            0x00,
+            0x02, 0x00, 0x00,
+            (id >> 24),
+            (id >> 16) & 0XFF,
+            (id >> 8) & 0XFF,
+            (id & 0xFF),
+        ])), MP4.box(MP4.types.tfdt, new Uint8Array([
+            0x00,
+            0x00, 0x00, 0x00,
+            (baseMediaDecodeTime >> 24),
+            (baseMediaDecodeTime >> 16) & 0XFF,
+            (baseMediaDecodeTime >> 8) & 0XFF,
+            (baseMediaDecodeTime & 0xFF),
+        ])), MP4.trun(track, 16 +
+            16 +
+            8 +
+            16 +
+            8 +
+            8));
+    };
+    MP4.trak = function (track) {
+        track.duration = track.duration || 0xffffffff;
+        return MP4.box(MP4.types.trak, MP4.tkhd(track), MP4.mdia(track));
+    };
+    MP4.trex = function (track) {
+        var id = track.id;
+        return MP4.box(MP4.types.trex, new Uint8Array([
+            0x00,
+            0x00, 0x00, 0x00,
+            (id >> 24),
+            (id >> 16) & 0XFF,
+            (id >> 8) & 0XFF,
+            (id & 0xFF),
+            0x00, 0x00, 0x00, 0x01,
+            0x00, 0x00, 0x00, 0x3c,
+            0x00, 0x00, 0x00, 0x00,
+            0x00, 0x01, 0x00, 0x00,
+        ]));
+    };
+    MP4.trun = function (track, offset) {
+        var samples = track.samples || [];
+        var len = samples.length;
+        var additionalLen = track.isKeyFrame ? 4 : 0;
+        var arraylen = 12 + additionalLen + (4 * len);
+        var array = new Uint8Array(arraylen);
+        offset += 8 + arraylen;
+        array.set([
+            0x00,
+            0x00, 0x02, (track.isKeyFrame ? 0x05 : 0x01),
+            (len >>> 24) & 0xFF,
+            (len >>> 16) & 0xFF,
+            (len >>> 8) & 0xFF,
+            len & 0xFF,
+            (offset >>> 24) & 0xFF,
+            (offset >>> 16) & 0xFF,
+            (offset >>> 8) & 0xFF,
+            offset & 0xFF,
+        ], 0);
+        if (track.isKeyFrame) {
+            array.set([
+                0x00, 0x00, 0x00, 0x00,
+            ], 12);
+        }
+        for (var i = 0; i < len; i++) {
+            var sample = samples[i];
+            var size = sample.size;
+            array.set([
+                (size >>> 24) & 0xFF,
+                (size >>> 16) & 0xFF,
+                (size >>> 8) & 0xFF,
+                size & 0xFF,
+            ], 12 + additionalLen + 4 * i);
+        }
+        return MP4.box(MP4.types.trun, array);
+    };
+    MP4.initSegment = function (tracks, duration, timescale) {
+        if (!MP4.initalized) {
+            MP4.init();
+        }
+        var movie = MP4.moov(tracks, duration, timescale);
+        var result = new Uint8Array(MP4.FTYP.byteLength + movie.byteLength);
+        result.set(MP4.FTYP);
+        result.set(movie, MP4.FTYP.byteLength);
+        return result;
+    };
+    MP4.fragmentSegment = function (sn, baseMediaDecodeTime, track, payload) {
+        var moof = MP4.moof(sn, baseMediaDecodeTime, track);
+        var mdat = MP4.mdat(payload);
+        var result = new Uint8Array(MP4.STYP.byteLength + moof.byteLength + mdat.byteLength);
+        result.set(MP4.STYP);
+        result.set(moof, MP4.STYP.byteLength);
+        result.set(mdat, MP4.STYP.byteLength + moof.byteLength);
+        return result;
+    };
+    return MP4;
+}());
+MP4.types = {};
+MP4.initalized = false;
+exports.default = MP4;
diff --git a/web-service/public/js/lib/dist/types.d.ts b/web-service/public/js/lib/dist/types.d.ts
new file mode 100644
index 0000000000000000000000000000000000000000..9805ff829dd34868f2bf97357420c37044b5b8fb
--- /dev/null
+++ b/web-service/public/js/lib/dist/types.d.ts
@@ -0,0 +1,18 @@
+export interface Track {
+    id: number;
+    type: 'video' | 'audio';
+    len: number;
+    codec: string;
+    sps: Uint8Array[];
+    pps: Uint8Array[];
+    seiBuffering: boolean;
+    width: number;
+    height: number;
+    timescale: number;
+    duration: number;
+    samples: TrackSample[];
+    isKeyFrame: boolean;
+}
+export interface TrackSample {
+    size: number;
+}
diff --git a/web-service/public/js/lib/dist/types.js b/web-service/public/js/lib/dist/types.js
new file mode 100644
index 0000000000000000000000000000000000000000..c8ad2e549bdc6801e0d1c80b0308d4b9bd4985ce
--- /dev/null
+++ b/web-service/public/js/lib/dist/types.js
@@ -0,0 +1,2 @@
+"use strict";
+Object.defineProperty(exports, "__esModule", { value: true });
diff --git a/web-service/public/js/lib/dist/util/NALU.d.ts b/web-service/public/js/lib/dist/util/NALU.d.ts
new file mode 100644
index 0000000000000000000000000000000000000000..10d1b657a1c59ca9a35d465c7d5cf09bfa802017
--- /dev/null
+++ b/web-service/public/js/lib/dist/util/NALU.d.ts
@@ -0,0 +1,19 @@
+export default class NALU {
+    data: Uint8Array;
+    nri: number;
+    ntype: number;
+    static readonly NDR: number;
+    static readonly IDR: number;
+    static readonly SEI: number;
+    static readonly SPS: number;
+    static readonly PPS: number;
+    static readonly TYPES: {
+        [x: number]: string;
+    };
+    static type(nalu: NALU): string;
+    constructor(data: Uint8Array);
+    type(): number;
+    isKeyframe(): boolean;
+    getSize(): number;
+    getData(): Uint8Array;
+}
diff --git a/web-service/public/js/lib/dist/util/NALU.js b/web-service/public/js/lib/dist/util/NALU.js
new file mode 100644
index 0000000000000000000000000000000000000000..f9c66be6a79aa16971997f6066bedecf585937c4
--- /dev/null
+++ b/web-service/public/js/lib/dist/util/NALU.js
@@ -0,0 +1,74 @@
+"use strict";
+Object.defineProperty(exports, "__esModule", { value: true });
+var NALU = (function () {
+    function NALU(data) {
+        this.data = data;
+        this.nri = (data[0] & 0x60) >> 5;
+        this.ntype = data[0] & 0x1f;
+    }
+    Object.defineProperty(NALU, "NDR", {
+        get: function () { return 1; },
+        enumerable: true,
+        configurable: true
+    });
+    Object.defineProperty(NALU, "IDR", {
+        get: function () { return 5; },
+        enumerable: true,
+        configurable: true
+    });
+    Object.defineProperty(NALU, "SEI", {
+        get: function () { return 6; },
+        enumerable: true,
+        configurable: true
+    });
+    Object.defineProperty(NALU, "SPS", {
+        get: function () { return 7; },
+        enumerable: true,
+        configurable: true
+    });
+    Object.defineProperty(NALU, "PPS", {
+        get: function () { return 8; },
+        enumerable: true,
+        configurable: true
+    });
+    Object.defineProperty(NALU, "TYPES", {
+        get: function () {
+            return _a = {},
+                _a[NALU.IDR] = 'IDR',
+                _a[NALU.SEI] = 'SEI',
+                _a[NALU.SPS] = 'SPS',
+                _a[NALU.PPS] = 'PPS',
+                _a[NALU.NDR] = 'NDR',
+                _a;
+            var _a;
+        },
+        enumerable: true,
+        configurable: true
+    });
+    NALU.type = function (nalu) {
+        if (nalu.ntype in NALU.TYPES) {
+            return NALU.TYPES[nalu.ntype];
+        }
+        else {
+            return 'UNKNOWN';
+        }
+    };
+    NALU.prototype.type = function () {
+        return this.ntype;
+    };
+    NALU.prototype.isKeyframe = function () {
+        return this.ntype === NALU.IDR;
+    };
+    NALU.prototype.getSize = function () {
+        return 4 + this.data.byteLength;
+    };
+    NALU.prototype.getData = function () {
+        var result = new Uint8Array(this.getSize());
+        var view = new DataView(result.buffer);
+        view.setUint32(0, this.getSize() - 4);
+        result.set(this.data, 4);
+        return result;
+    };
+    return NALU;
+}());
+exports.default = NALU;
diff --git a/web-service/public/js/lib/dist/util/bit-stream.d.ts b/web-service/public/js/lib/dist/util/bit-stream.d.ts
new file mode 100644
index 0000000000000000000000000000000000000000..d2a1b9c1013ebf799f2bce6fa28cdac8830eb4d2
--- /dev/null
+++ b/web-service/public/js/lib/dist/util/bit-stream.d.ts
@@ -0,0 +1,19 @@
+export default class BitStream {
+    private data;
+    private index;
+    private bitLength;
+    constructor(data: Uint8Array);
+    readonly bitsAvailable: number;
+    skipBits(size: number): void;
+    readBits(size: number): number;
+    private getBits(size, offsetBits, moveIndex?);
+    skipLZ(): number;
+    skipUEG(): void;
+    skipEG(): void;
+    readUEG(): number;
+    readEG(): number;
+    readBoolean(): boolean;
+    readUByte(): number;
+    readUShort(): number;
+    readUInt(): number;
+}
diff --git a/web-service/public/js/lib/dist/util/bit-stream.js b/web-service/public/js/lib/dist/util/bit-stream.js
new file mode 100644
index 0000000000000000000000000000000000000000..6983ed17d9192d6cabfdc9766ce9bc37b4a660af
--- /dev/null
+++ b/web-service/public/js/lib/dist/util/bit-stream.js
@@ -0,0 +1,91 @@
+"use strict";
+Object.defineProperty(exports, "__esModule", { value: true });
+var BitStream = (function () {
+    function BitStream(data) {
+        this.data = data;
+        this.index = 0;
+        this.bitLength = data.byteLength * 8;
+    }
+    Object.defineProperty(BitStream.prototype, "bitsAvailable", {
+        get: function () {
+            return this.bitLength - this.index;
+        },
+        enumerable: true,
+        configurable: true
+    });
+    BitStream.prototype.skipBits = function (size) {
+        if (this.bitsAvailable < size) {
+            throw new Error('no bytes available');
+        }
+        this.index += size;
+    };
+    BitStream.prototype.readBits = function (size) {
+        var result = this.getBits(size, this.index);
+        return result;
+    };
+    BitStream.prototype.getBits = function (size, offsetBits, moveIndex) {
+        if (moveIndex === void 0) { moveIndex = true; }
+        if (this.bitsAvailable < size) {
+            throw new Error('no bytes available');
+        }
+        var offset = offsetBits % 8;
+        var byte = this.data[(offsetBits / 8) | 0] & (0xff >>> offset);
+        var bits = 8 - offset;
+        if (bits >= size) {
+            if (moveIndex) {
+                this.index += size;
+            }
+            return byte >> (bits - size);
+        }
+        else {
+            if (moveIndex) {
+                this.index += bits;
+            }
+            var nextSize = size - bits;
+            return (byte << nextSize) | this.getBits(nextSize, offsetBits + bits, moveIndex);
+        }
+    };
+    BitStream.prototype.skipLZ = function () {
+        var leadingZeroCount;
+        for (leadingZeroCount = 0; leadingZeroCount < this.bitLength - this.index; ++leadingZeroCount) {
+            if (0 !== this.getBits(1, this.index + leadingZeroCount, false)) {
+                this.index += leadingZeroCount;
+                return leadingZeroCount;
+            }
+        }
+        return leadingZeroCount;
+    };
+    BitStream.prototype.skipUEG = function () {
+        this.skipBits(1 + this.skipLZ());
+    };
+    BitStream.prototype.skipEG = function () {
+        this.skipBits(1 + this.skipLZ());
+    };
+    BitStream.prototype.readUEG = function () {
+        var prefix = this.skipLZ();
+        return this.readBits(prefix + 1) - 1;
+    };
+    BitStream.prototype.readEG = function () {
+        var value = this.readUEG();
+        if (0x01 & value) {
+            return (1 + value) >>> 1;
+        }
+        else {
+            return -1 * (value >>> 1);
+        }
+    };
+    BitStream.prototype.readBoolean = function () {
+        return 1 === this.readBits(1);
+    };
+    BitStream.prototype.readUByte = function () {
+        return this.readBits(8);
+    };
+    BitStream.prototype.readUShort = function () {
+        return this.readBits(16);
+    };
+    BitStream.prototype.readUInt = function () {
+        return this.readBits(32);
+    };
+    return BitStream;
+}());
+exports.default = BitStream;
diff --git a/web-service/public/js/lib/dist/util/debug.d.ts b/web-service/public/js/lib/dist/util/debug.d.ts
new file mode 100644
index 0000000000000000000000000000000000000000..c7801f1030732d051a163064646352216c66502d
--- /dev/null
+++ b/web-service/public/js/lib/dist/util/debug.d.ts
@@ -0,0 +1,5 @@
+export declare type Logger = (message?: any, ...optionalParams: any[]) => void;
+export declare function setLogger(log: Logger, error?: Logger): void;
+export declare function isEnable(): boolean;
+export declare function log(message?: any, ...optionalParams: any[]): void;
+export declare function error(message?: any, ...optionalParams: any[]): void;
diff --git a/web-service/public/js/lib/dist/util/debug.js b/web-service/public/js/lib/dist/util/debug.js
new file mode 100644
index 0000000000000000000000000000000000000000..6e4354cf09a7414c655324df9843453f1e707de5
--- /dev/null
+++ b/web-service/public/js/lib/dist/util/debug.js
@@ -0,0 +1,33 @@
+"use strict";
+Object.defineProperty(exports, "__esModule", { value: true });
+var logger;
+var errorLogger;
+function setLogger(log, error) {
+    logger = log;
+    errorLogger = error != null ? error : log;
+}
+exports.setLogger = setLogger;
+function isEnable() {
+    return logger != null;
+}
+exports.isEnable = isEnable;
+function log(message) {
+    var optionalParams = [];
+    for (var _i = 1; _i < arguments.length; _i++) {
+        optionalParams[_i - 1] = arguments[_i];
+    }
+    if (logger) {
+        logger.apply(void 0, [message].concat(optionalParams));
+    }
+}
+exports.log = log;
+function error(message) {
+    var optionalParams = [];
+    for (var _i = 1; _i < arguments.length; _i++) {
+        optionalParams[_i - 1] = arguments[_i];
+    }
+    if (errorLogger) {
+        errorLogger.apply(void 0, [message].concat(optionalParams));
+    }
+}
+exports.error = error;
diff --git a/web-service/public/js/lib/dist/util/nalu-stream-buffer.d.ts b/web-service/public/js/lib/dist/util/nalu-stream-buffer.d.ts
new file mode 100644
index 0000000000000000000000000000000000000000..591ab4aae4c4e07fd15e59a915a9166b8d478b2e
--- /dev/null
+++ b/web-service/public/js/lib/dist/util/nalu-stream-buffer.d.ts
@@ -0,0 +1,7 @@
+import NALU from './NALU';
+export default class VideoStreamBuffer {
+    private buffer;
+    clear(): void;
+    append(value: ArrayLike<number>): NALU[];
+    private mergeBuffer(value);
+}
diff --git a/web-service/public/js/lib/dist/util/nalu-stream-buffer.js b/web-service/public/js/lib/dist/util/nalu-stream-buffer.js
new file mode 100644
index 0000000000000000000000000000000000000000..9d76aa0f657a9b0a77ab1e6b69c746cfcb2d3c72
--- /dev/null
+++ b/web-service/public/js/lib/dist/util/nalu-stream-buffer.js
@@ -0,0 +1,66 @@
+"use strict";
+Object.defineProperty(exports, "__esModule", { value: true });
+var NALU_1 = require("./NALU");
+var VideoStreamBuffer = (function () {
+    function VideoStreamBuffer() {
+    }
+    VideoStreamBuffer.prototype.clear = function () {
+        this.buffer = undefined;
+    };
+    VideoStreamBuffer.prototype.append = function (value) {
+        var nextNalHeader = function (b) {
+            var i = 3;
+            return function () {
+                var count = 0;
+                for (; i < b.length; i++) {
+                    switch (b[i]) {
+                        case 0:
+                            count++;
+                            break;
+                        case 1:
+                            if (count === 3) {
+                                return i - 3;
+                            }
+                        default:
+                            count = 0;
+                    }
+                }
+                return;
+            };
+        };
+        var result = [];
+        var buffer;
+        if (this.buffer) {
+            if (value[3] === 1 && value[2] === 0 && value[1] === 0 && value[0] === 0) {
+                result.push(new NALU_1.default(this.buffer.subarray(4)));
+                buffer = Uint8Array.from(value);
+            }
+        }
+        if (buffer == null) {
+            buffer = this.mergeBuffer(value);
+        }
+        var lastIndex = 0;
+        var f = nextNalHeader(buffer);
+        for (var index = f(); index != null; index = f()) {
+            result.push(new NALU_1.default(buffer.subarray(lastIndex + 4, index)));
+            lastIndex = index;
+        }
+        this.buffer = buffer.subarray(lastIndex);
+        return result;
+    };
+    VideoStreamBuffer.prototype.mergeBuffer = function (value) {
+        if (this.buffer == null) {
+            return Uint8Array.from(value);
+        }
+        else {
+            var newBuffer = new Uint8Array(this.buffer.byteLength + value.length);
+            if (this.buffer.byteLength > 0) {
+                newBuffer.set(this.buffer, 0);
+            }
+            newBuffer.set(value, this.buffer.byteLength);
+            return newBuffer;
+        }
+    };
+    return VideoStreamBuffer;
+}());
+exports.default = VideoStreamBuffer;
diff --git a/web-service/public/js/lib/dist/video-converter.d.ts b/web-service/public/js/lib/dist/video-converter.d.ts
new file mode 100644
index 0000000000000000000000000000000000000000..aa02344c1c60c8fda36923659b5a35631453169a
--- /dev/null
+++ b/web-service/public/js/lib/dist/video-converter.d.ts
@@ -0,0 +1,26 @@
+export declare const mimeType = "video/mp4; codecs=\"avc1.42E01E\"";
+export default class VideoConverter {
+    private element;
+    private fps;
+    private fpf;
+    private mediaSource;
+    private sourceBuffer;
+    private receiveBuffer;
+    private remuxer;
+    private mediaReady;
+    private mediaReadyPromise;
+    private queue;
+    private isFirstFrame;
+    static readonly errorNotes: {
+        [x: number]: string;
+    };
+    constructor(element: HTMLVideoElement, fps?: number, fpf?: number);
+    private setup();
+    play(): void;
+    pause(): void;
+    reset(): void;
+    appendRawData(data: ArrayLike<number>): void;
+    private writeFragment(dts, pay);
+    private writeBuffer(data);
+    private doAppend(data);
+}
diff --git a/web-service/public/js/lib/dist/video-converter.js b/web-service/public/js/lib/dist/video-converter.js
new file mode 100644
index 0000000000000000000000000000000000000000..c0d768fec8655cef6d336e77d881d374c03d2d3a
--- /dev/null
+++ b/web-service/public/js/lib/dist/video-converter.js
@@ -0,0 +1,185 @@
+"use strict";
+Object.defineProperty(exports, "__esModule", { value: true });
+var h264_remuxer_1 = require("./h264-remuxer");
+var mp4_generator_1 = require("./mp4-generator");
+var debug = require("./util/debug");
+var nalu_stream_buffer_1 = require("./util/nalu-stream-buffer");
+exports.mimeType = 'video/mp4; codecs="avc1.42E01E"';
+var VideoConverter = (function () {
+    function VideoConverter(element, fps, fpf) {
+        if (fps === void 0) { fps = 60; }
+        if (fpf === void 0) { fpf = fps; }
+        this.element = element;
+        this.fps = fps;
+        this.fpf = fpf;
+        this.receiveBuffer = new nalu_stream_buffer_1.default();
+        this.queue = [];
+        if (!MediaSource || !MediaSource.isTypeSupported(exports.mimeType)) {
+            throw new Error("Your browser is not supported: " + exports.mimeType);
+        }
+        this.reset();
+    }
+    Object.defineProperty(VideoConverter, "errorNotes", {
+        get: function () {
+            return _a = {},
+                _a[MediaError.MEDIA_ERR_ABORTED] = 'fetching process aborted by user',
+                _a[MediaError.MEDIA_ERR_NETWORK] = 'error occurred when downloading',
+                _a[MediaError.MEDIA_ERR_DECODE] = 'error occurred when decoding',
+                _a[MediaError.MEDIA_ERR_SRC_NOT_SUPPORTED] = 'audio/video not supported',
+                _a;
+            var _a;
+        },
+        enumerable: true,
+        configurable: true
+    });
+    VideoConverter.prototype.setup = function () {
+        var _this = this;
+        this.mediaReadyPromise = new Promise(function (resolve, _reject) {
+            _this.mediaSource.addEventListener('sourceopen', function () {
+                debug.log("Media Source opened.");
+                _this.sourceBuffer = _this.mediaSource.addSourceBuffer(exports.mimeType);
+                _this.sourceBuffer.addEventListener('updateend', function () {
+                    debug.log("  SourceBuffer updateend");
+                    debug.log("    sourceBuffer.buffered.length=" + _this.sourceBuffer.buffered.length);
+                    for (var i = 0, len = _this.sourceBuffer.buffered.length; i < len; i++) {
+                        debug.log("    sourceBuffer.buffered [" + i + "]: " +
+                            (_this.sourceBuffer.buffered.start(i) + ", " + _this.sourceBuffer.buffered.end(i)));
+                    }
+                    debug.log("  mediasource.duration=" + _this.mediaSource.duration);
+                    debug.log("  mediasource.readyState=" + _this.mediaSource.readyState);
+                    debug.log("  video.duration=" + _this.element.duration);
+                    debug.log("    video.buffered.length=" + _this.element.buffered.length);
+                    if (debug.isEnable()) {
+                        for (var i = 0, len = _this.element.buffered.length; i < len; i++) {
+                            debug.log("    video.buffered [" + i + "]: " + _this.element.buffered.start(i) + ", " + _this.element.buffered.end(i));
+                        }
+                    }
+                    debug.log("  video.currentTime=" + _this.element.currentTime);
+                    debug.log("  video.readyState=" + _this.element.readyState);
+                    var data = _this.queue.shift();
+                    if (data) {
+                        _this.writeBuffer(data);
+                    }
+                });
+                _this.sourceBuffer.addEventListener('error', function () {
+                    debug.error('  SourceBuffer errored!');
+                });
+                _this.mediaReady = true;
+                resolve();
+            }, false);
+            _this.mediaSource.addEventListener('sourceclose', function () {
+                debug.log("Media Source closed.");
+                _this.mediaReady = false;
+            }, false);
+            _this.element.src = URL.createObjectURL(_this.mediaSource);
+        });
+        return this.mediaReadyPromise;
+    };
+    VideoConverter.prototype.play = function () {
+        var _this = this;
+        if (!this.element.paused) {
+            return;
+        }
+        if (this.mediaReady && this.element.readyState >= 2) {
+            this.element.play();
+        }
+        else {
+            var handler_1 = function () {
+                _this.play();
+                _this.element.removeEventListener('canplaythrough', handler_1);
+            };
+            this.element.addEventListener('canplaythrough', handler_1);
+        }
+    };
+    VideoConverter.prototype.pause = function () {
+        if (this.element.paused) {
+            return;
+        }
+        this.element.pause();
+    };
+    VideoConverter.prototype.reset = function () {
+        this.receiveBuffer.clear();
+        if (this.mediaSource && this.mediaSource.readyState === 'open') {
+            this.mediaSource.duration = 0;
+            this.mediaSource.endOfStream();
+        }
+        this.mediaSource = new MediaSource();
+        this.remuxer = new h264_remuxer_1.default(this.fps, this.fpf, this.fps * 60);
+        this.mediaReady = false;
+        this.mediaReadyPromise = undefined;
+        this.queue = [];
+        this.isFirstFrame = true;
+        this.setup();
+    };
+    VideoConverter.prototype.appendRawData = function (data) {
+        var nalus = this.receiveBuffer.append(data);
+        for (var _i = 0, nalus_1 = nalus; _i < nalus_1.length; _i++) {
+            var nalu = nalus_1[_i];
+            var ret = this.remuxer.remux(nalu);
+            if (ret) {
+                this.writeFragment(ret[0], ret[1]);
+            }
+        }
+    };
+    VideoConverter.prototype.writeFragment = function (dts, pay) {
+        var remuxer = this.remuxer;
+        if (remuxer.mp4track.isKeyFrame) {
+            this.writeBuffer(mp4_generator_1.default.initSegment([remuxer.mp4track], Infinity, remuxer.timescale));
+        }
+        if (pay && pay.byteLength) {
+            debug.log(" Put fragment: " + remuxer.seqNum + ", frames=" + remuxer.mp4track.samples.length + ", size=" + pay.byteLength);
+            var fragment = mp4_generator_1.default.fragmentSegment(remuxer.seqNum, dts, remuxer.mp4track, pay);
+            this.writeBuffer(fragment);
+            remuxer.flush();
+        }
+        else {
+            debug.error("Nothing payload!");
+        }
+    };
+    VideoConverter.prototype.writeBuffer = function (data) {
+        var _this = this;
+        if (this.mediaReady) {
+            if (this.sourceBuffer.updating) {
+                this.queue.push(data);
+            }
+            else {
+                this.doAppend(data);
+            }
+        }
+        else {
+            this.queue.push(data);
+            if (this.mediaReadyPromise) {
+                this.mediaReadyPromise.then(function () {
+                    if (!_this.sourceBuffer.updating) {
+                        var d = _this.queue.shift();
+                        if (d) {
+                            _this.writeBuffer(d);
+                        }
+                    }
+                });
+                this.mediaReadyPromise = undefined;
+            }
+        }
+    };
+    VideoConverter.prototype.doAppend = function (data) {
+        var error = this.element.error;
+        if (error) {
+            debug.error("MSE Error Occured: " + VideoConverter.errorNotes[error.code]);
+            this.element.pause();
+            if (this.mediaSource.readyState === 'open') {
+                this.mediaSource.endOfStream();
+            }
+        }
+        else {
+            try {
+                this.sourceBuffer.appendBuffer(data);
+                debug.log("  appended buffer: size=" + data.byteLength);
+            }
+            catch (err) {
+                debug.error("MSE Error occured while appending buffer. " + err.name + ": " + err.message);
+            }
+        }
+    };
+    return VideoConverter;
+}());
+exports.default = VideoConverter;
diff --git a/web-service/server/package-lock.json b/web-service/server/package-lock.json
new file mode 100644
index 0000000000000000000000000000000000000000..4aafecdec0df9686bd9cd3f779655a8f77cac605
--- /dev/null
+++ b/web-service/server/package-lock.json
@@ -0,0 +1,1780 @@
+{
+  "name": "@ftl/web-service",
+  "version": "0.0.1",
+  "lockfileVersion": 1,
+  "requires": true,
+  "dependencies": {
+    "JSONStream": {
+      "version": "1.3.5",
+      "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.5.tgz",
+      "integrity": "sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==",
+      "dev": true,
+      "requires": {
+        "jsonparse": "^1.2.0",
+        "through": ">=2.2.7 <3"
+      }
+    },
+    "accepts": {
+      "version": "1.3.7",
+      "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz",
+      "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==",
+      "requires": {
+        "mime-types": "~2.1.24",
+        "negotiator": "0.6.2"
+      }
+    },
+    "acorn": {
+      "version": "7.1.0",
+      "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.1.0.tgz",
+      "integrity": "sha512-kL5CuoXA/dgxlBbVrflsflzQ3PAas7RYZB52NOm/6839iVYJgKMJ3cQJD+t2i5+qFa8h3MDpEOJiS64E8JLnSQ==",
+      "dev": true
+    },
+    "acorn-node": {
+      "version": "1.8.2",
+      "resolved": "https://registry.npmjs.org/acorn-node/-/acorn-node-1.8.2.tgz",
+      "integrity": "sha512-8mt+fslDufLYntIoPAaIMUe/lrbrehIiwmR3t2k9LljIzoigEPF27eLk2hy8zSGzmR/ogr7zbRKINMo1u0yh5A==",
+      "dev": true,
+      "requires": {
+        "acorn": "^7.0.0",
+        "acorn-walk": "^7.0.0",
+        "xtend": "^4.0.2"
+      }
+    },
+    "acorn-walk": {
+      "version": "7.0.0",
+      "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.0.0.tgz",
+      "integrity": "sha512-7Bv1We7ZGuU79zZbb6rRqcpxo3OY+zrdtloZWoyD8fmGX+FeXRjE+iuGkZjSXLVovLzrsvMGMy0EkwA0E0umxg==",
+      "dev": true
+    },
+    "array-flatten": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
+      "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI="
+    },
+    "asn1.js": {
+      "version": "4.10.1",
+      "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-4.10.1.tgz",
+      "integrity": "sha512-p32cOF5q0Zqs9uBiONKYLm6BClCoBCM5O9JfeUSlnQLBTxYdTK+pW+nXflm8UkKd2UYlEbYz5qEi0JuZR9ckSw==",
+      "dev": true,
+      "requires": {
+        "bn.js": "^4.0.0",
+        "inherits": "^2.0.1",
+        "minimalistic-assert": "^1.0.0"
+      }
+    },
+    "assert": {
+      "version": "1.5.0",
+      "resolved": "https://registry.npmjs.org/assert/-/assert-1.5.0.tgz",
+      "integrity": "sha512-EDsgawzwoun2CZkCgtxJbv392v4nbk9XDD06zI+kQYoBM/3RBWLlEyJARDOmhAAosBjWACEkKL6S+lIZtcAubA==",
+      "dev": true,
+      "requires": {
+        "object-assign": "^4.1.1",
+        "util": "0.10.3"
+      },
+      "dependencies": {
+        "inherits": {
+          "version": "2.0.1",
+          "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz",
+          "integrity": "sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE=",
+          "dev": true
+        },
+        "util": {
+          "version": "0.10.3",
+          "resolved": "https://registry.npmjs.org/util/-/util-0.10.3.tgz",
+          "integrity": "sha1-evsa/lCAUkZInj23/g7TeTNqwPk=",
+          "dev": true,
+          "requires": {
+            "inherits": "2.0.1"
+          }
+        }
+      }
+    },
+    "async-limiter": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz",
+      "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ=="
+    },
+    "balanced-match": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
+      "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=",
+      "dev": true
+    },
+    "base64-js": {
+      "version": "1.3.1",
+      "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz",
+      "integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g==",
+      "dev": true
+    },
+    "bl": {
+      "version": "2.2.0",
+      "resolved": "https://registry.npmjs.org/bl/-/bl-2.2.0.tgz",
+      "integrity": "sha512-wbgvOpqopSr7uq6fJrLH8EsvYMJf9gzfo2jCsL2eTy75qXPukA4pCgHamOQkZtY5vmfVtjB+P3LNlMHW5CEZXA==",
+      "requires": {
+        "readable-stream": "^2.3.5",
+        "safe-buffer": "^5.1.1"
+      }
+    },
+    "bluebird": {
+      "version": "3.5.1",
+      "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.1.tgz",
+      "integrity": "sha512-MKiLiV+I1AA596t9w1sQJ8jkiSr5+ZKi0WKrYGUn6d1Fx+Ij4tIj+m2WMQSGczs5jZVxV339chE8iwk6F64wjA=="
+    },
+    "bn.js": {
+      "version": "4.11.8",
+      "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz",
+      "integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA==",
+      "dev": true
+    },
+    "body-parser": {
+      "version": "1.19.0",
+      "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz",
+      "integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==",
+      "requires": {
+        "bytes": "3.1.0",
+        "content-type": "~1.0.4",
+        "debug": "2.6.9",
+        "depd": "~1.1.2",
+        "http-errors": "1.7.2",
+        "iconv-lite": "0.4.24",
+        "on-finished": "~2.3.0",
+        "qs": "6.7.0",
+        "raw-body": "2.4.0",
+        "type-is": "~1.6.17"
+      }
+    },
+    "brace-expansion": {
+      "version": "1.1.11",
+      "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
+      "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
+      "dev": true,
+      "requires": {
+        "balanced-match": "^1.0.0",
+        "concat-map": "0.0.1"
+      }
+    },
+    "brorand": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz",
+      "integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=",
+      "dev": true
+    },
+    "browser-pack": {
+      "version": "6.1.0",
+      "resolved": "https://registry.npmjs.org/browser-pack/-/browser-pack-6.1.0.tgz",
+      "integrity": "sha512-erYug8XoqzU3IfcU8fUgyHqyOXqIE4tUTTQ+7mqUjQlvnXkOO6OlT9c/ZoJVHYoAaqGxr09CN53G7XIsO4KtWA==",
+      "dev": true,
+      "requires": {
+        "JSONStream": "^1.0.3",
+        "combine-source-map": "~0.8.0",
+        "defined": "^1.0.0",
+        "safe-buffer": "^5.1.1",
+        "through2": "^2.0.0",
+        "umd": "^3.0.0"
+      }
+    },
+    "browser-resolve": {
+      "version": "1.11.3",
+      "resolved": "https://registry.npmjs.org/browser-resolve/-/browser-resolve-1.11.3.tgz",
+      "integrity": "sha512-exDi1BYWB/6raKHmDTCicQfTkqwN5fioMFV4j8BsfMU4R2DK/QfZfK7kOVkmWCNANf0snkBzqGqAJBao9gZMdQ==",
+      "dev": true,
+      "requires": {
+        "resolve": "1.1.7"
+      },
+      "dependencies": {
+        "resolve": {
+          "version": "1.1.7",
+          "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.1.7.tgz",
+          "integrity": "sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs=",
+          "dev": true
+        }
+      }
+    },
+    "browserify": {
+      "version": "16.5.0",
+      "resolved": "https://registry.npmjs.org/browserify/-/browserify-16.5.0.tgz",
+      "integrity": "sha512-6bfI3cl76YLAnCZ75AGu/XPOsqUhRyc0F/olGIJeCxtfxF2HvPKEcmjU9M8oAPxl4uBY1U7Nry33Q6koV3f2iw==",
+      "dev": true,
+      "requires": {
+        "JSONStream": "^1.0.3",
+        "assert": "^1.4.0",
+        "browser-pack": "^6.0.1",
+        "browser-resolve": "^1.11.0",
+        "browserify-zlib": "~0.2.0",
+        "buffer": "^5.0.2",
+        "cached-path-relative": "^1.0.0",
+        "concat-stream": "^1.6.0",
+        "console-browserify": "^1.1.0",
+        "constants-browserify": "~1.0.0",
+        "crypto-browserify": "^3.0.0",
+        "defined": "^1.0.0",
+        "deps-sort": "^2.0.0",
+        "domain-browser": "^1.2.0",
+        "duplexer2": "~0.1.2",
+        "events": "^2.0.0",
+        "glob": "^7.1.0",
+        "has": "^1.0.0",
+        "htmlescape": "^1.1.0",
+        "https-browserify": "^1.0.0",
+        "inherits": "~2.0.1",
+        "insert-module-globals": "^7.0.0",
+        "labeled-stream-splicer": "^2.0.0",
+        "mkdirp": "^0.5.0",
+        "module-deps": "^6.0.0",
+        "os-browserify": "~0.3.0",
+        "parents": "^1.0.1",
+        "path-browserify": "~0.0.0",
+        "process": "~0.11.0",
+        "punycode": "^1.3.2",
+        "querystring-es3": "~0.2.0",
+        "read-only-stream": "^2.0.0",
+        "readable-stream": "^2.0.2",
+        "resolve": "^1.1.4",
+        "shasum": "^1.0.0",
+        "shell-quote": "^1.6.1",
+        "stream-browserify": "^2.0.0",
+        "stream-http": "^3.0.0",
+        "string_decoder": "^1.1.1",
+        "subarg": "^1.0.0",
+        "syntax-error": "^1.1.1",
+        "through2": "^2.0.0",
+        "timers-browserify": "^1.0.1",
+        "tty-browserify": "0.0.1",
+        "url": "~0.11.0",
+        "util": "~0.10.1",
+        "vm-browserify": "^1.0.0",
+        "xtend": "^4.0.0"
+      }
+    },
+    "browserify-aes": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz",
+      "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==",
+      "dev": true,
+      "requires": {
+        "buffer-xor": "^1.0.3",
+        "cipher-base": "^1.0.0",
+        "create-hash": "^1.1.0",
+        "evp_bytestokey": "^1.0.3",
+        "inherits": "^2.0.1",
+        "safe-buffer": "^5.0.1"
+      }
+    },
+    "browserify-cipher": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/browserify-cipher/-/browserify-cipher-1.0.1.tgz",
+      "integrity": "sha512-sPhkz0ARKbf4rRQt2hTpAHqn47X3llLkUGn+xEJzLjwY8LRs2p0v7ljvI5EyoRO/mexrNunNECisZs+gw2zz1w==",
+      "dev": true,
+      "requires": {
+        "browserify-aes": "^1.0.4",
+        "browserify-des": "^1.0.0",
+        "evp_bytestokey": "^1.0.0"
+      }
+    },
+    "browserify-des": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/browserify-des/-/browserify-des-1.0.2.tgz",
+      "integrity": "sha512-BioO1xf3hFwz4kc6iBhI3ieDFompMhrMlnDFC4/0/vd5MokpuAc3R+LYbwTA9A5Yc9pq9UYPqffKpW2ObuwX5A==",
+      "dev": true,
+      "requires": {
+        "cipher-base": "^1.0.1",
+        "des.js": "^1.0.0",
+        "inherits": "^2.0.1",
+        "safe-buffer": "^5.1.2"
+      }
+    },
+    "browserify-rsa": {
+      "version": "4.0.1",
+      "resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.0.1.tgz",
+      "integrity": "sha1-IeCr+vbyApzy+vsTNWenAdQTVSQ=",
+      "dev": true,
+      "requires": {
+        "bn.js": "^4.1.0",
+        "randombytes": "^2.0.1"
+      }
+    },
+    "browserify-sign": {
+      "version": "4.0.4",
+      "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.0.4.tgz",
+      "integrity": "sha1-qk62jl17ZYuqa/alfmMMvXqT0pg=",
+      "dev": true,
+      "requires": {
+        "bn.js": "^4.1.1",
+        "browserify-rsa": "^4.0.0",
+        "create-hash": "^1.1.0",
+        "create-hmac": "^1.1.2",
+        "elliptic": "^6.0.0",
+        "inherits": "^2.0.1",
+        "parse-asn1": "^5.0.0"
+      }
+    },
+    "browserify-zlib": {
+      "version": "0.2.0",
+      "resolved": "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.2.0.tgz",
+      "integrity": "sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA==",
+      "dev": true,
+      "requires": {
+        "pako": "~1.0.5"
+      }
+    },
+    "bson": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/bson/-/bson-1.1.1.tgz",
+      "integrity": "sha512-jCGVYLoYMHDkOsbwJZBCqwMHyH4c+wzgI9hG7Z6SZJRXWr+x58pdIbm2i9a/jFGCkRJqRUr8eoI7lDWa0hTkxg=="
+    },
+    "buffer": {
+      "version": "5.4.3",
+      "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.4.3.tgz",
+      "integrity": "sha512-zvj65TkFeIt3i6aj5bIvJDzjjQQGs4o/sNoezg1F1kYap9Nu2jcUdpwzRSJTHMMzG0H7bZkn4rNQpImhuxWX2A==",
+      "dev": true,
+      "requires": {
+        "base64-js": "^1.0.2",
+        "ieee754": "^1.1.4"
+      }
+    },
+    "buffer-from": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz",
+      "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==",
+      "dev": true
+    },
+    "buffer-xor": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz",
+      "integrity": "sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk=",
+      "dev": true
+    },
+    "builtin-status-codes": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz",
+      "integrity": "sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug=",
+      "dev": true
+    },
+    "bytes": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz",
+      "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg=="
+    },
+    "cached-path-relative": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/cached-path-relative/-/cached-path-relative-1.0.2.tgz",
+      "integrity": "sha512-5r2GqsoEb4qMTTN9J+WzXfjov+hjxT+j3u5K+kIVNIwAd99DLCJE9pBIMP1qVeybV6JiijL385Oz0DcYxfbOIg==",
+      "dev": true
+    },
+    "cipher-base": {
+      "version": "1.0.4",
+      "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz",
+      "integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==",
+      "dev": true,
+      "requires": {
+        "inherits": "^2.0.1",
+        "safe-buffer": "^5.0.1"
+      }
+    },
+    "combine-source-map": {
+      "version": "0.8.0",
+      "resolved": "https://registry.npmjs.org/combine-source-map/-/combine-source-map-0.8.0.tgz",
+      "integrity": "sha1-pY0N8ELBhvz4IqjoAV9UUNLXmos=",
+      "dev": true,
+      "requires": {
+        "convert-source-map": "~1.1.0",
+        "inline-source-map": "~0.6.0",
+        "lodash.memoize": "~3.0.3",
+        "source-map": "~0.5.3"
+      }
+    },
+    "concat-map": {
+      "version": "0.0.1",
+      "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
+      "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=",
+      "dev": true
+    },
+    "concat-stream": {
+      "version": "1.6.2",
+      "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz",
+      "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==",
+      "dev": true,
+      "requires": {
+        "buffer-from": "^1.0.0",
+        "inherits": "^2.0.3",
+        "readable-stream": "^2.2.2",
+        "typedarray": "^0.0.6"
+      }
+    },
+    "console-browserify": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.2.0.tgz",
+      "integrity": "sha512-ZMkYO/LkF17QvCPqM0gxw8yUzigAOZOSWSHg91FH6orS7vcEj5dVZTidN2fQ14yBSdg97RqhSNwLUXInd52OTA==",
+      "dev": true
+    },
+    "constants-browserify": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/constants-browserify/-/constants-browserify-1.0.0.tgz",
+      "integrity": "sha1-wguW2MYXdIqvHBYCF2DNJ/y4y3U=",
+      "dev": true
+    },
+    "content-disposition": {
+      "version": "0.5.3",
+      "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz",
+      "integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==",
+      "requires": {
+        "safe-buffer": "5.1.2"
+      }
+    },
+    "content-type": {
+      "version": "1.0.4",
+      "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz",
+      "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA=="
+    },
+    "convert-source-map": {
+      "version": "1.1.3",
+      "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.1.3.tgz",
+      "integrity": "sha1-SCnId+n+SbMWHzvzZziI4gRpmGA=",
+      "dev": true
+    },
+    "cookie": {
+      "version": "0.4.0",
+      "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz",
+      "integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg=="
+    },
+    "cookie-signature": {
+      "version": "1.0.6",
+      "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
+      "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw="
+    },
+    "core-util-is": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
+      "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac="
+    },
+    "create-ecdh": {
+      "version": "4.0.3",
+      "resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.3.tgz",
+      "integrity": "sha512-GbEHQPMOswGpKXM9kCWVrremUcBmjteUaQ01T9rkKCPDXfUHX0IoP9LpHYo2NPFampa4e+/pFDc3jQdxrxQLaw==",
+      "dev": true,
+      "requires": {
+        "bn.js": "^4.1.0",
+        "elliptic": "^6.0.0"
+      }
+    },
+    "create-hash": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz",
+      "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==",
+      "dev": true,
+      "requires": {
+        "cipher-base": "^1.0.1",
+        "inherits": "^2.0.1",
+        "md5.js": "^1.3.4",
+        "ripemd160": "^2.0.1",
+        "sha.js": "^2.4.0"
+      }
+    },
+    "create-hmac": {
+      "version": "1.1.7",
+      "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz",
+      "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==",
+      "dev": true,
+      "requires": {
+        "cipher-base": "^1.0.3",
+        "create-hash": "^1.1.0",
+        "inherits": "^2.0.1",
+        "ripemd160": "^2.0.0",
+        "safe-buffer": "^5.0.1",
+        "sha.js": "^2.4.8"
+      }
+    },
+    "crypto-browserify": {
+      "version": "3.12.0",
+      "resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.12.0.tgz",
+      "integrity": "sha512-fz4spIh+znjO2VjL+IdhEpRJ3YN6sMzITSBijk6FK2UvTqruSQW+/cCZTSNsMiZNvUeq0CqurF+dAbyiGOY6Wg==",
+      "dev": true,
+      "requires": {
+        "browserify-cipher": "^1.0.0",
+        "browserify-sign": "^4.0.0",
+        "create-ecdh": "^4.0.0",
+        "create-hash": "^1.1.0",
+        "create-hmac": "^1.1.0",
+        "diffie-hellman": "^5.0.0",
+        "inherits": "^2.0.1",
+        "pbkdf2": "^3.0.3",
+        "public-encrypt": "^4.0.0",
+        "randombytes": "^2.0.0",
+        "randomfill": "^1.0.3"
+      }
+    },
+    "dash-ast": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/dash-ast/-/dash-ast-1.0.0.tgz",
+      "integrity": "sha512-Vy4dx7gquTeMcQR/hDkYLGUnwVil6vk4FOOct+djUnHOUWt+zJPJAaRIXaAFkPXtJjvlY7o3rfRu0/3hpnwoUA==",
+      "dev": true
+    },
+    "debug": {
+      "version": "2.6.9",
+      "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+      "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+      "requires": {
+        "ms": "2.0.0"
+      }
+    },
+    "defined": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/defined/-/defined-1.0.0.tgz",
+      "integrity": "sha1-yY2bzvdWdBiOEQlpFRGZ45sfppM=",
+      "dev": true
+    },
+    "depd": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz",
+      "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak="
+    },
+    "deps-sort": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/deps-sort/-/deps-sort-2.0.1.tgz",
+      "integrity": "sha512-1orqXQr5po+3KI6kQb9A4jnXT1PBwggGl2d7Sq2xsnOeI9GPcE/tGcF9UiSZtZBM7MukY4cAh7MemS6tZYipfw==",
+      "dev": true,
+      "requires": {
+        "JSONStream": "^1.0.3",
+        "shasum-object": "^1.0.0",
+        "subarg": "^1.0.0",
+        "through2": "^2.0.0"
+      }
+    },
+    "des.js": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/des.js/-/des.js-1.0.1.tgz",
+      "integrity": "sha512-Q0I4pfFrv2VPd34/vfLrFOoRmlYj3OV50i7fskps1jZWK1kApMWWT9G6RRUeYedLcBDIhnSDaUvJMb3AhUlaEA==",
+      "dev": true,
+      "requires": {
+        "inherits": "^2.0.1",
+        "minimalistic-assert": "^1.0.0"
+      }
+    },
+    "destroy": {
+      "version": "1.0.4",
+      "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz",
+      "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA="
+    },
+    "detective": {
+      "version": "5.2.0",
+      "resolved": "https://registry.npmjs.org/detective/-/detective-5.2.0.tgz",
+      "integrity": "sha512-6SsIx+nUUbuK0EthKjv0zrdnajCCXVYGmbYYiYjFVpzcjwEs/JMDZ8tPRG29J/HhN56t3GJp2cGSWDRjjot8Pg==",
+      "dev": true,
+      "requires": {
+        "acorn-node": "^1.6.1",
+        "defined": "^1.0.0",
+        "minimist": "^1.1.1"
+      }
+    },
+    "diffie-hellman": {
+      "version": "5.0.3",
+      "resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz",
+      "integrity": "sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==",
+      "dev": true,
+      "requires": {
+        "bn.js": "^4.1.0",
+        "miller-rabin": "^4.0.0",
+        "randombytes": "^2.0.0"
+      }
+    },
+    "domain-browser": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-1.2.0.tgz",
+      "integrity": "sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA==",
+      "dev": true
+    },
+    "duplexer2": {
+      "version": "0.1.4",
+      "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz",
+      "integrity": "sha1-ixLauHjA1p4+eJEFFmKjL8a93ME=",
+      "dev": true,
+      "requires": {
+        "readable-stream": "^2.0.2"
+      }
+    },
+    "ee-first": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
+      "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0="
+    },
+    "elliptic": {
+      "version": "6.5.2",
+      "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.2.tgz",
+      "integrity": "sha512-f4x70okzZbIQl/NSRLkI/+tteV/9WqL98zx+SQ69KbXxmVrmjwsNUPn/gYJJ0sHvEak24cZgHIPegRePAtA/xw==",
+      "dev": true,
+      "requires": {
+        "bn.js": "^4.4.0",
+        "brorand": "^1.0.1",
+        "hash.js": "^1.0.0",
+        "hmac-drbg": "^1.0.0",
+        "inherits": "^2.0.1",
+        "minimalistic-assert": "^1.0.0",
+        "minimalistic-crypto-utils": "^1.0.0"
+      }
+    },
+    "encodeurl": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
+      "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k="
+    },
+    "escape-html": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
+      "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg="
+    },
+    "etag": {
+      "version": "1.8.1",
+      "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
+      "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc="
+    },
+    "events": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/events/-/events-2.1.0.tgz",
+      "integrity": "sha512-3Zmiobend8P9DjmKAty0Era4jV8oJ0yGYe2nJJAxgymF9+N8F2m0hhZiMoWtcfepExzNKZumFU3ksdQbInGWCg==",
+      "dev": true
+    },
+    "evp_bytestokey": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz",
+      "integrity": "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==",
+      "dev": true,
+      "requires": {
+        "md5.js": "^1.3.4",
+        "safe-buffer": "^5.1.1"
+      }
+    },
+    "express": {
+      "version": "4.17.1",
+      "resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz",
+      "integrity": "sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==",
+      "requires": {
+        "accepts": "~1.3.7",
+        "array-flatten": "1.1.1",
+        "body-parser": "1.19.0",
+        "content-disposition": "0.5.3",
+        "content-type": "~1.0.4",
+        "cookie": "0.4.0",
+        "cookie-signature": "1.0.6",
+        "debug": "2.6.9",
+        "depd": "~1.1.2",
+        "encodeurl": "~1.0.2",
+        "escape-html": "~1.0.3",
+        "etag": "~1.8.1",
+        "finalhandler": "~1.1.2",
+        "fresh": "0.5.2",
+        "merge-descriptors": "1.0.1",
+        "methods": "~1.1.2",
+        "on-finished": "~2.3.0",
+        "parseurl": "~1.3.3",
+        "path-to-regexp": "0.1.7",
+        "proxy-addr": "~2.0.5",
+        "qs": "6.7.0",
+        "range-parser": "~1.2.1",
+        "safe-buffer": "5.1.2",
+        "send": "0.17.1",
+        "serve-static": "1.14.1",
+        "setprototypeof": "1.1.1",
+        "statuses": "~1.5.0",
+        "type-is": "~1.6.18",
+        "utils-merge": "1.0.1",
+        "vary": "~1.1.2"
+      }
+    },
+    "express-ws": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/express-ws/-/express-ws-4.0.0.tgz",
+      "integrity": "sha512-KEyUw8AwRET2iFjFsI1EJQrJ/fHeGiJtgpYgEWG3yDv4l/To/m3a2GaYfeGyB3lsWdvbesjF5XCMx+SVBgAAYw==",
+      "requires": {
+        "ws": "^5.2.0"
+      }
+    },
+    "fast-safe-stringify": {
+      "version": "2.0.7",
+      "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.0.7.tgz",
+      "integrity": "sha512-Utm6CdzT+6xsDk2m8S6uL8VHxNwI6Jub+e9NYTcAms28T84pTa25GJQV9j0CY0N1rM8hK4x6grpF2BQf+2qwVA==",
+      "dev": true
+    },
+    "finalhandler": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz",
+      "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==",
+      "requires": {
+        "debug": "2.6.9",
+        "encodeurl": "~1.0.2",
+        "escape-html": "~1.0.3",
+        "on-finished": "~2.3.0",
+        "parseurl": "~1.3.3",
+        "statuses": "~1.5.0",
+        "unpipe": "~1.0.0"
+      }
+    },
+    "forwarded": {
+      "version": "0.1.2",
+      "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz",
+      "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ="
+    },
+    "fresh": {
+      "version": "0.5.2",
+      "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
+      "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac="
+    },
+    "fs.realpath": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
+      "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=",
+      "dev": true
+    },
+    "function-bind": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
+      "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==",
+      "dev": true
+    },
+    "get-assigned-identifiers": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/get-assigned-identifiers/-/get-assigned-identifiers-1.2.0.tgz",
+      "integrity": "sha512-mBBwmeGTrxEMO4pMaaf/uUEFHnYtwr8FTe8Y/mer4rcV/bye0qGm6pw1bGZFGStxC5O76c5ZAVBGnqHmOaJpdQ==",
+      "dev": true
+    },
+    "glob": {
+      "version": "7.1.6",
+      "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz",
+      "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==",
+      "dev": true,
+      "requires": {
+        "fs.realpath": "^1.0.0",
+        "inflight": "^1.0.4",
+        "inherits": "2",
+        "minimatch": "^3.0.4",
+        "once": "^1.3.0",
+        "path-is-absolute": "^1.0.0"
+      }
+    },
+    "h264-converter": {
+      "version": "0.1.0",
+      "resolved": "https://registry.npmjs.org/h264-converter/-/h264-converter-0.1.0.tgz",
+      "integrity": "sha512-2/7PanZzecPXyaQDYjzNsjQNqfr8wqnAnrw6cWQSilmfIIxyy9U/hshgrvkAxw1VWrtdZxTrkZLTJaaoJQExzA==",
+      "requires": {
+        "tslib": "^1.7.1"
+      }
+    },
+    "has": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
+      "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
+      "dev": true,
+      "requires": {
+        "function-bind": "^1.1.1"
+      }
+    },
+    "hash-base": {
+      "version": "3.0.4",
+      "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.0.4.tgz",
+      "integrity": "sha1-X8hoaEfs1zSZQDMZprCj8/auSRg=",
+      "dev": true,
+      "requires": {
+        "inherits": "^2.0.1",
+        "safe-buffer": "^5.0.1"
+      }
+    },
+    "hash.js": {
+      "version": "1.1.7",
+      "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz",
+      "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==",
+      "dev": true,
+      "requires": {
+        "inherits": "^2.0.3",
+        "minimalistic-assert": "^1.0.1"
+      }
+    },
+    "hmac-drbg": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz",
+      "integrity": "sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=",
+      "dev": true,
+      "requires": {
+        "hash.js": "^1.0.3",
+        "minimalistic-assert": "^1.0.0",
+        "minimalistic-crypto-utils": "^1.0.1"
+      }
+    },
+    "htmlescape": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/htmlescape/-/htmlescape-1.1.1.tgz",
+      "integrity": "sha1-OgPtwiFLyjtmQko+eVk0lQnLA1E=",
+      "dev": true
+    },
+    "http-errors": {
+      "version": "1.7.2",
+      "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz",
+      "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==",
+      "requires": {
+        "depd": "~1.1.2",
+        "inherits": "2.0.3",
+        "setprototypeof": "1.1.1",
+        "statuses": ">= 1.5.0 < 2",
+        "toidentifier": "1.0.0"
+      }
+    },
+    "https-browserify": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-1.0.0.tgz",
+      "integrity": "sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM=",
+      "dev": true
+    },
+    "iconv-lite": {
+      "version": "0.4.24",
+      "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
+      "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
+      "requires": {
+        "safer-buffer": ">= 2.1.2 < 3"
+      }
+    },
+    "ieee754": {
+      "version": "1.1.13",
+      "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz",
+      "integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==",
+      "dev": true
+    },
+    "inflight": {
+      "version": "1.0.6",
+      "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
+      "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
+      "dev": true,
+      "requires": {
+        "once": "^1.3.0",
+        "wrappy": "1"
+      }
+    },
+    "inherits": {
+      "version": "2.0.3",
+      "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
+      "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4="
+    },
+    "inline-source-map": {
+      "version": "0.6.2",
+      "resolved": "https://registry.npmjs.org/inline-source-map/-/inline-source-map-0.6.2.tgz",
+      "integrity": "sha1-+Tk0ccGKedFyT4Y/o4tYY3Ct4qU=",
+      "dev": true,
+      "requires": {
+        "source-map": "~0.5.3"
+      }
+    },
+    "insert-module-globals": {
+      "version": "7.2.0",
+      "resolved": "https://registry.npmjs.org/insert-module-globals/-/insert-module-globals-7.2.0.tgz",
+      "integrity": "sha512-VE6NlW+WGn2/AeOMd496AHFYmE7eLKkUY6Ty31k4og5vmA3Fjuwe9v6ifH6Xx/Hz27QvdoMoviw1/pqWRB09Sw==",
+      "dev": true,
+      "requires": {
+        "JSONStream": "^1.0.3",
+        "acorn-node": "^1.5.2",
+        "combine-source-map": "^0.8.0",
+        "concat-stream": "^1.6.1",
+        "is-buffer": "^1.1.0",
+        "path-is-absolute": "^1.0.1",
+        "process": "~0.11.0",
+        "through2": "^2.0.0",
+        "undeclared-identifiers": "^1.1.2",
+        "xtend": "^4.0.0"
+      }
+    },
+    "ipaddr.js": {
+      "version": "1.9.0",
+      "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.0.tgz",
+      "integrity": "sha512-M4Sjn6N/+O6/IXSJseKqHoFc+5FdGJ22sXqnjTpdZweHK64MzEPAyQZyEU3R/KRv2GLoa7nNtg/C2Ev6m7z+eA=="
+    },
+    "is-buffer": {
+      "version": "1.1.6",
+      "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz",
+      "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==",
+      "dev": true
+    },
+    "isarray": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
+      "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE="
+    },
+    "json-stable-stringify": {
+      "version": "0.0.1",
+      "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-0.0.1.tgz",
+      "integrity": "sha1-YRwj6BTbN1Un34URk9tZ3Sryf0U=",
+      "dev": true,
+      "requires": {
+        "jsonify": "~0.0.0"
+      }
+    },
+    "jsonify": {
+      "version": "0.0.0",
+      "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz",
+      "integrity": "sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM=",
+      "dev": true
+    },
+    "jsonparse": {
+      "version": "1.3.1",
+      "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz",
+      "integrity": "sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA=",
+      "dev": true
+    },
+    "kareem": {
+      "version": "2.3.1",
+      "resolved": "https://registry.npmjs.org/kareem/-/kareem-2.3.1.tgz",
+      "integrity": "sha512-l3hLhffs9zqoDe8zjmb/mAN4B8VT3L56EUvKNqLFVs9YlFA+zx7ke1DO8STAdDyYNkeSo1nKmjuvQeI12So8Xw=="
+    },
+    "labeled-stream-splicer": {
+      "version": "2.0.2",
+      "resolved": "https://registry.npmjs.org/labeled-stream-splicer/-/labeled-stream-splicer-2.0.2.tgz",
+      "integrity": "sha512-Ca4LSXFFZUjPScRaqOcFxneA0VpKZr4MMYCljyQr4LIewTLb3Y0IUTIsnBBsVubIeEfxeSZpSjSsRM8APEQaAw==",
+      "dev": true,
+      "requires": {
+        "inherits": "^2.0.1",
+        "stream-splicer": "^2.0.0"
+      }
+    },
+    "lodash.memoize": {
+      "version": "3.0.4",
+      "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-3.0.4.tgz",
+      "integrity": "sha1-LcvSwofLwKVcxCMovQxzYVDVPj8=",
+      "dev": true
+    },
+    "md5.js": {
+      "version": "1.3.5",
+      "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz",
+      "integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==",
+      "dev": true,
+      "requires": {
+        "hash-base": "^3.0.0",
+        "inherits": "^2.0.1",
+        "safe-buffer": "^5.1.2"
+      }
+    },
+    "media-typer": {
+      "version": "0.3.0",
+      "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
+      "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g="
+    },
+    "merge-descriptors": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz",
+      "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E="
+    },
+    "methods": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
+      "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4="
+    },
+    "miller-rabin": {
+      "version": "4.0.1",
+      "resolved": "https://registry.npmjs.org/miller-rabin/-/miller-rabin-4.0.1.tgz",
+      "integrity": "sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA==",
+      "dev": true,
+      "requires": {
+        "bn.js": "^4.0.0",
+        "brorand": "^1.0.1"
+      }
+    },
+    "mime": {
+      "version": "1.6.0",
+      "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
+      "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg=="
+    },
+    "mime-db": {
+      "version": "1.40.0",
+      "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.40.0.tgz",
+      "integrity": "sha512-jYdeOMPy9vnxEqFRRo6ZvTZ8d9oPb+k18PKoYNYUe2stVEBPPwsln/qWzdbmaIvnhZ9v2P+CuecK+fpUfsV2mA=="
+    },
+    "mime-types": {
+      "version": "2.1.24",
+      "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.24.tgz",
+      "integrity": "sha512-WaFHS3MCl5fapm3oLxU4eYDw77IQM2ACcxQ9RIxfaC3ooc6PFuBMGZZsYpvoXS5D5QTWPieo1jjLdAm3TBP3cQ==",
+      "requires": {
+        "mime-db": "1.40.0"
+      }
+    },
+    "minimalistic-assert": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz",
+      "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==",
+      "dev": true
+    },
+    "minimalistic-crypto-utils": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz",
+      "integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=",
+      "dev": true
+    },
+    "minimatch": {
+      "version": "3.0.4",
+      "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
+      "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
+      "dev": true,
+      "requires": {
+        "brace-expansion": "^1.1.7"
+      }
+    },
+    "minimist": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
+      "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=",
+      "dev": true
+    },
+    "mkdirp": {
+      "version": "0.5.1",
+      "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
+      "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=",
+      "dev": true,
+      "requires": {
+        "minimist": "0.0.8"
+      },
+      "dependencies": {
+        "minimist": {
+          "version": "0.0.8",
+          "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz",
+          "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=",
+          "dev": true
+        }
+      }
+    },
+    "module-deps": {
+      "version": "6.2.1",
+      "resolved": "https://registry.npmjs.org/module-deps/-/module-deps-6.2.1.tgz",
+      "integrity": "sha512-UnEn6Ah36Tu4jFiBbJVUtt0h+iXqxpLqDvPS8nllbw5RZFmNJ1+Mz5BjYnM9ieH80zyxHkARGLnMIHlPK5bu6A==",
+      "dev": true,
+      "requires": {
+        "JSONStream": "^1.0.3",
+        "browser-resolve": "^1.7.0",
+        "cached-path-relative": "^1.0.2",
+        "concat-stream": "~1.6.0",
+        "defined": "^1.0.0",
+        "detective": "^5.0.2",
+        "duplexer2": "^0.1.2",
+        "inherits": "^2.0.1",
+        "parents": "^1.0.0",
+        "readable-stream": "^2.0.2",
+        "resolve": "^1.4.0",
+        "stream-combiner2": "^1.1.1",
+        "subarg": "^1.0.0",
+        "through2": "^2.0.0",
+        "xtend": "^4.0.0"
+      }
+    },
+    "mongodb": {
+      "version": "3.3.2",
+      "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-3.3.2.tgz",
+      "integrity": "sha512-fqJt3iywelk4yKu/lfwQg163Bjpo5zDKhXiohycvon4iQHbrfflSAz9AIlRE6496Pm/dQKQK5bMigdVo2s6gBg==",
+      "requires": {
+        "bson": "^1.1.1",
+        "require_optional": "^1.0.1",
+        "safe-buffer": "^5.1.2"
+      }
+    },
+    "mongoose": {
+      "version": "5.7.3",
+      "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-5.7.3.tgz",
+      "integrity": "sha512-CKCCCAhFnJRtmdmver8Ud9/NZ9m7D2H/xLgmrcL6cb9D4nril/idL8lsWWpBsJI81AOCVsktiZJ4X4vfo2S0fw==",
+      "requires": {
+        "bson": "~1.1.1",
+        "kareem": "2.3.1",
+        "mongodb": "3.3.2",
+        "mongoose-legacy-pluralize": "1.0.2",
+        "mpath": "0.6.0",
+        "mquery": "3.2.2",
+        "ms": "2.1.2",
+        "regexp-clone": "1.0.0",
+        "safe-buffer": "5.1.2",
+        "sift": "7.0.1",
+        "sliced": "1.0.1"
+      },
+      "dependencies": {
+        "ms": {
+          "version": "2.1.2",
+          "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+          "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
+        }
+      }
+    },
+    "mongoose-legacy-pluralize": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/mongoose-legacy-pluralize/-/mongoose-legacy-pluralize-1.0.2.tgz",
+      "integrity": "sha512-Yo/7qQU4/EyIS8YDFSeenIvXxZN+ld7YdV9LqFVQJzTLye8unujAWPZ4NWKfFA+RNjh+wvTWKY9Z3E5XM6ZZiQ=="
+    },
+    "mpath": {
+      "version": "0.6.0",
+      "resolved": "https://registry.npmjs.org/mpath/-/mpath-0.6.0.tgz",
+      "integrity": "sha512-i75qh79MJ5Xo/sbhxrDrPSEG0H/mr1kcZXJ8dH6URU5jD/knFxCVqVC/gVSW7GIXL/9hHWlT9haLbCXWOll3qw=="
+    },
+    "mquery": {
+      "version": "3.2.2",
+      "resolved": "https://registry.npmjs.org/mquery/-/mquery-3.2.2.tgz",
+      "integrity": "sha512-XB52992COp0KP230I3qloVUbkLUxJIu328HBP2t2EsxSFtf4W1HPSOBWOXf1bqxK4Xbb66lfMJ+Bpfd9/yZE1Q==",
+      "requires": {
+        "bluebird": "3.5.1",
+        "debug": "3.1.0",
+        "regexp-clone": "^1.0.0",
+        "safe-buffer": "5.1.2",
+        "sliced": "1.0.1"
+      },
+      "dependencies": {
+        "debug": {
+          "version": "3.1.0",
+          "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
+          "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
+          "requires": {
+            "ms": "2.0.0"
+          }
+        }
+      }
+    },
+    "ms": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+      "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
+    },
+    "msgpack5": {
+      "version": "4.2.1",
+      "resolved": "https://registry.npmjs.org/msgpack5/-/msgpack5-4.2.1.tgz",
+      "integrity": "sha512-Xo7nE9ZfBVonQi1rSopNAqPdts/QHyuSEUwIEzAkB+V2FtmkkLUbP6MyVqVVQxsZYI65FpvW3Bb8Z9ZWEjbgHQ==",
+      "requires": {
+        "bl": "^2.0.1",
+        "inherits": "^2.0.3",
+        "readable-stream": "^2.3.6",
+        "safe-buffer": "^5.1.2"
+      }
+    },
+    "negotiator": {
+      "version": "0.6.2",
+      "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz",
+      "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw=="
+    },
+    "object-assign": {
+      "version": "4.1.1",
+      "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
+      "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=",
+      "dev": true
+    },
+    "on-finished": {
+      "version": "2.3.0",
+      "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz",
+      "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=",
+      "requires": {
+        "ee-first": "1.1.1"
+      }
+    },
+    "once": {
+      "version": "1.4.0",
+      "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
+      "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
+      "dev": true,
+      "requires": {
+        "wrappy": "1"
+      }
+    },
+    "os-browserify": {
+      "version": "0.3.0",
+      "resolved": "https://registry.npmjs.org/os-browserify/-/os-browserify-0.3.0.tgz",
+      "integrity": "sha1-hUNzx/XCMVkU/Jv8a9gjj92h7Cc=",
+      "dev": true
+    },
+    "pako": {
+      "version": "1.0.10",
+      "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.10.tgz",
+      "integrity": "sha512-0DTvPVU3ed8+HNXOu5Bs+o//Mbdj9VNQMUOe9oKCwh8l0GNwpTDMKCWbRjgtD291AWnkAgkqA/LOnQS8AmS1tw==",
+      "dev": true
+    },
+    "parents": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/parents/-/parents-1.0.1.tgz",
+      "integrity": "sha1-/t1NK/GTp3dF/nHjcdc8MwfZx1E=",
+      "dev": true,
+      "requires": {
+        "path-platform": "~0.11.15"
+      }
+    },
+    "parse-asn1": {
+      "version": "5.1.5",
+      "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.5.tgz",
+      "integrity": "sha512-jkMYn1dcJqF6d5CpU689bq7w/b5ALS9ROVSpQDPrZsqqesUJii9qutvoT5ltGedNXMO2e16YUWIghG9KxaViTQ==",
+      "dev": true,
+      "requires": {
+        "asn1.js": "^4.0.0",
+        "browserify-aes": "^1.0.0",
+        "create-hash": "^1.1.0",
+        "evp_bytestokey": "^1.0.0",
+        "pbkdf2": "^3.0.3",
+        "safe-buffer": "^5.1.1"
+      }
+    },
+    "parseurl": {
+      "version": "1.3.3",
+      "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
+      "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ=="
+    },
+    "path-browserify": {
+      "version": "0.0.1",
+      "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-0.0.1.tgz",
+      "integrity": "sha512-BapA40NHICOS+USX9SN4tyhq+A2RrN/Ws5F0Z5aMHDp98Fl86lX8Oti8B7uN93L4Ifv4fHOEA+pQw87gmMO/lQ==",
+      "dev": true
+    },
+    "path-is-absolute": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
+      "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=",
+      "dev": true
+    },
+    "path-parse": {
+      "version": "1.0.6",
+      "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz",
+      "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==",
+      "dev": true
+    },
+    "path-platform": {
+      "version": "0.11.15",
+      "resolved": "https://registry.npmjs.org/path-platform/-/path-platform-0.11.15.tgz",
+      "integrity": "sha1-6GQhf3TDaFDwhSt43Hv31KVyG/I=",
+      "dev": true
+    },
+    "path-to-regexp": {
+      "version": "0.1.7",
+      "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
+      "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w="
+    },
+    "pbkdf2": {
+      "version": "3.0.17",
+      "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.0.17.tgz",
+      "integrity": "sha512-U/il5MsrZp7mGg3mSQfn742na2T+1/vHDCG5/iTI3X9MKUuYUZVLQhyRsg06mCgDBTd57TxzgZt7P+fYfjRLtA==",
+      "dev": true,
+      "requires": {
+        "create-hash": "^1.1.2",
+        "create-hmac": "^1.1.4",
+        "ripemd160": "^2.0.1",
+        "safe-buffer": "^5.0.1",
+        "sha.js": "^2.4.8"
+      }
+    },
+    "process": {
+      "version": "0.11.10",
+      "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz",
+      "integrity": "sha1-czIwDoQBYb2j5podHZGn1LwW8YI=",
+      "dev": true
+    },
+    "process-nextick-args": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
+      "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag=="
+    },
+    "proxy-addr": {
+      "version": "2.0.5",
+      "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.5.tgz",
+      "integrity": "sha512-t/7RxHXPH6cJtP0pRG6smSr9QJidhB+3kXu0KgXnbGYMgzEnUxRQ4/LDdfOwZEMyIh3/xHb8PX3t+lfL9z+YVQ==",
+      "requires": {
+        "forwarded": "~0.1.2",
+        "ipaddr.js": "1.9.0"
+      }
+    },
+    "public-encrypt": {
+      "version": "4.0.3",
+      "resolved": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.3.tgz",
+      "integrity": "sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q==",
+      "dev": true,
+      "requires": {
+        "bn.js": "^4.1.0",
+        "browserify-rsa": "^4.0.0",
+        "create-hash": "^1.1.0",
+        "parse-asn1": "^5.0.0",
+        "randombytes": "^2.0.1",
+        "safe-buffer": "^5.1.2"
+      }
+    },
+    "punycode": {
+      "version": "1.4.1",
+      "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz",
+      "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=",
+      "dev": true
+    },
+    "qs": {
+      "version": "6.7.0",
+      "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz",
+      "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ=="
+    },
+    "querystring": {
+      "version": "0.2.0",
+      "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz",
+      "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=",
+      "dev": true
+    },
+    "querystring-es3": {
+      "version": "0.2.1",
+      "resolved": "https://registry.npmjs.org/querystring-es3/-/querystring-es3-0.2.1.tgz",
+      "integrity": "sha1-nsYfeQSYdXB9aUFFlv2Qek1xHnM=",
+      "dev": true
+    },
+    "querystringify": {
+      "version": "2.1.1",
+      "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.1.1.tgz",
+      "integrity": "sha512-w7fLxIRCRT7U8Qu53jQnJyPkYZIaR4n5151KMfcJlO/A9397Wxb1amJvROTK6TOnp7PfoAmg/qXiNHI+08jRfA=="
+    },
+    "randombytes": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz",
+      "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==",
+      "dev": true,
+      "requires": {
+        "safe-buffer": "^5.1.0"
+      }
+    },
+    "randomfill": {
+      "version": "1.0.4",
+      "resolved": "https://registry.npmjs.org/randomfill/-/randomfill-1.0.4.tgz",
+      "integrity": "sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw==",
+      "dev": true,
+      "requires": {
+        "randombytes": "^2.0.5",
+        "safe-buffer": "^5.1.0"
+      }
+    },
+    "range-parser": {
+      "version": "1.2.1",
+      "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
+      "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg=="
+    },
+    "raw-body": {
+      "version": "2.4.0",
+      "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz",
+      "integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==",
+      "requires": {
+        "bytes": "3.1.0",
+        "http-errors": "1.7.2",
+        "iconv-lite": "0.4.24",
+        "unpipe": "1.0.0"
+      }
+    },
+    "read-only-stream": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/read-only-stream/-/read-only-stream-2.0.0.tgz",
+      "integrity": "sha1-JyT9aoET1zdkrCiNQ4YnDB2/F/A=",
+      "dev": true,
+      "requires": {
+        "readable-stream": "^2.0.2"
+      }
+    },
+    "readable-stream": {
+      "version": "2.3.6",
+      "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
+      "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==",
+      "requires": {
+        "core-util-is": "~1.0.0",
+        "inherits": "~2.0.3",
+        "isarray": "~1.0.0",
+        "process-nextick-args": "~2.0.0",
+        "safe-buffer": "~5.1.1",
+        "string_decoder": "~1.1.1",
+        "util-deprecate": "~1.0.1"
+      }
+    },
+    "regexp-clone": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/regexp-clone/-/regexp-clone-1.0.0.tgz",
+      "integrity": "sha512-TuAasHQNamyyJ2hb97IuBEif4qBHGjPHBS64sZwytpLEqtBQ1gPJTnOaQ6qmpET16cK14kkjbazl6+p0RRv0yw=="
+    },
+    "require_optional": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/require_optional/-/require_optional-1.0.1.tgz",
+      "integrity": "sha512-qhM/y57enGWHAe3v/NcwML6a3/vfESLe/sGM2dII+gEO0BpKRUkWZow/tyloNqJyN6kXSl3RyyM8Ll5D/sJP8g==",
+      "requires": {
+        "resolve-from": "^2.0.0",
+        "semver": "^5.1.0"
+      }
+    },
+    "requires-port": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz",
+      "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8="
+    },
+    "resolve": {
+      "version": "1.13.1",
+      "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.13.1.tgz",
+      "integrity": "sha512-CxqObCX8K8YtAhOBRg+lrcdn+LK+WYOS8tSjqSFbjtrI5PnS63QPhZl4+yKfrU9tdsbMu9Anr/amegT87M9Z6w==",
+      "dev": true,
+      "requires": {
+        "path-parse": "^1.0.6"
+      }
+    },
+    "resolve-from": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-2.0.0.tgz",
+      "integrity": "sha1-lICrIOlP+h2egKgEx+oUdhGWa1c="
+    },
+    "ripemd160": {
+      "version": "2.0.2",
+      "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz",
+      "integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==",
+      "dev": true,
+      "requires": {
+        "hash-base": "^3.0.0",
+        "inherits": "^2.0.1"
+      }
+    },
+    "safe-buffer": {
+      "version": "5.1.2",
+      "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
+      "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
+    },
+    "safer-buffer": {
+      "version": "2.1.2",
+      "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
+      "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
+    },
+    "semver": {
+      "version": "5.7.1",
+      "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
+      "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ=="
+    },
+    "send": {
+      "version": "0.17.1",
+      "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz",
+      "integrity": "sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==",
+      "requires": {
+        "debug": "2.6.9",
+        "depd": "~1.1.2",
+        "destroy": "~1.0.4",
+        "encodeurl": "~1.0.2",
+        "escape-html": "~1.0.3",
+        "etag": "~1.8.1",
+        "fresh": "0.5.2",
+        "http-errors": "~1.7.2",
+        "mime": "1.6.0",
+        "ms": "2.1.1",
+        "on-finished": "~2.3.0",
+        "range-parser": "~1.2.1",
+        "statuses": "~1.5.0"
+      },
+      "dependencies": {
+        "ms": {
+          "version": "2.1.1",
+          "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz",
+          "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg=="
+        }
+      }
+    },
+    "serve-static": {
+      "version": "1.14.1",
+      "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz",
+      "integrity": "sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==",
+      "requires": {
+        "encodeurl": "~1.0.2",
+        "escape-html": "~1.0.3",
+        "parseurl": "~1.3.3",
+        "send": "0.17.1"
+      }
+    },
+    "setprototypeof": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz",
+      "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw=="
+    },
+    "sha.js": {
+      "version": "2.4.11",
+      "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz",
+      "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==",
+      "dev": true,
+      "requires": {
+        "inherits": "^2.0.1",
+        "safe-buffer": "^5.0.1"
+      }
+    },
+    "shasum": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/shasum/-/shasum-1.0.2.tgz",
+      "integrity": "sha1-5wEjENj0F/TetXEhUOVni4euVl8=",
+      "dev": true,
+      "requires": {
+        "json-stable-stringify": "~0.0.0",
+        "sha.js": "~2.4.4"
+      }
+    },
+    "shasum-object": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/shasum-object/-/shasum-object-1.0.0.tgz",
+      "integrity": "sha512-Iqo5rp/3xVi6M4YheapzZhhGPVs0yZwHj7wvwQ1B9z8H6zk+FEnI7y3Teq7qwnekfEhu8WmG2z0z4iWZaxLWVg==",
+      "dev": true,
+      "requires": {
+        "fast-safe-stringify": "^2.0.7"
+      }
+    },
+    "shell-quote": {
+      "version": "1.7.2",
+      "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.7.2.tgz",
+      "integrity": "sha512-mRz/m/JVscCrkMyPqHc/bczi3OQHkLTqXHEFu0zDhK/qfv3UcOA4SVmRCLmos4bhjr9ekVQubj/R7waKapmiQg==",
+      "dev": true
+    },
+    "sift": {
+      "version": "7.0.1",
+      "resolved": "https://registry.npmjs.org/sift/-/sift-7.0.1.tgz",
+      "integrity": "sha512-oqD7PMJ+uO6jV9EQCl0LrRw1OwsiPsiFQR5AR30heR+4Dl7jBBbDLnNvWiak20tzZlSE1H7RB30SX/1j/YYT7g=="
+    },
+    "simple-concat": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.0.tgz",
+      "integrity": "sha1-c0TLuLbib7J9ZrL8hvn21Zl1IcY=",
+      "dev": true
+    },
+    "sliced": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/sliced/-/sliced-1.0.1.tgz",
+      "integrity": "sha1-CzpmK10Ewxd7GSa+qCsD+Dei70E="
+    },
+    "source-map": {
+      "version": "0.5.7",
+      "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
+      "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=",
+      "dev": true
+    },
+    "statuses": {
+      "version": "1.5.0",
+      "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz",
+      "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow="
+    },
+    "stream-browserify": {
+      "version": "2.0.2",
+      "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-2.0.2.tgz",
+      "integrity": "sha512-nX6hmklHs/gr2FuxYDltq8fJA1GDlxKQCz8O/IM4atRqBH8OORmBNgfvW5gG10GT/qQ9u0CzIvr2X5Pkt6ntqg==",
+      "dev": true,
+      "requires": {
+        "inherits": "~2.0.1",
+        "readable-stream": "^2.0.2"
+      }
+    },
+    "stream-combiner2": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/stream-combiner2/-/stream-combiner2-1.1.1.tgz",
+      "integrity": "sha1-+02KFCDqNidk4hrUeAOXvry0HL4=",
+      "dev": true,
+      "requires": {
+        "duplexer2": "~0.1.0",
+        "readable-stream": "^2.0.2"
+      }
+    },
+    "stream-http": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/stream-http/-/stream-http-3.1.0.tgz",
+      "integrity": "sha512-cuB6RgO7BqC4FBYzmnvhob5Do3wIdIsXAgGycHJnW+981gHqoYcYz9lqjJrk8WXRddbwPuqPYRl+bag6mYv4lw==",
+      "dev": true,
+      "requires": {
+        "builtin-status-codes": "^3.0.0",
+        "inherits": "^2.0.1",
+        "readable-stream": "^3.0.6",
+        "xtend": "^4.0.0"
+      },
+      "dependencies": {
+        "readable-stream": {
+          "version": "3.4.0",
+          "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.4.0.tgz",
+          "integrity": "sha512-jItXPLmrSR8jmTRmRWJXCnGJsfy85mB3Wd/uINMXA65yrnFo0cPClFIUWzo2najVNSl+mx7/4W8ttlLWJe99pQ==",
+          "dev": true,
+          "requires": {
+            "inherits": "^2.0.3",
+            "string_decoder": "^1.1.1",
+            "util-deprecate": "^1.0.1"
+          }
+        }
+      }
+    },
+    "stream-splicer": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/stream-splicer/-/stream-splicer-2.0.1.tgz",
+      "integrity": "sha512-Xizh4/NPuYSyAXyT7g8IvdJ9HJpxIGL9PjyhtywCZvvP0OPIdqyrr4dMikeuvY8xahpdKEBlBTySe583totajg==",
+      "dev": true,
+      "requires": {
+        "inherits": "^2.0.1",
+        "readable-stream": "^2.0.2"
+      }
+    },
+    "string_decoder": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
+      "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
+      "requires": {
+        "safe-buffer": "~5.1.0"
+      }
+    },
+    "subarg": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/subarg/-/subarg-1.0.0.tgz",
+      "integrity": "sha1-9izxdYHplrSPyWVpn1TAauJouNI=",
+      "dev": true,
+      "requires": {
+        "minimist": "^1.1.0"
+      }
+    },
+    "syntax-error": {
+      "version": "1.4.0",
+      "resolved": "https://registry.npmjs.org/syntax-error/-/syntax-error-1.4.0.tgz",
+      "integrity": "sha512-YPPlu67mdnHGTup2A8ff7BC2Pjq0e0Yp/IyTFN03zWO0RcK07uLcbi7C2KpGR2FvWbaB0+bfE27a+sBKebSo7w==",
+      "dev": true,
+      "requires": {
+        "acorn-node": "^1.2.0"
+      }
+    },
+    "through": {
+      "version": "2.3.8",
+      "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz",
+      "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=",
+      "dev": true
+    },
+    "through2": {
+      "version": "2.0.5",
+      "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz",
+      "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==",
+      "dev": true,
+      "requires": {
+        "readable-stream": "~2.3.6",
+        "xtend": "~4.0.1"
+      }
+    },
+    "timers-browserify": {
+      "version": "1.4.2",
+      "resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-1.4.2.tgz",
+      "integrity": "sha1-ycWLV1voQHN1y14kYtrO50NZ9B0=",
+      "dev": true,
+      "requires": {
+        "process": "~0.11.0"
+      }
+    },
+    "toidentifier": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz",
+      "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw=="
+    },
+    "tslib": {
+      "version": "1.10.0",
+      "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.10.0.tgz",
+      "integrity": "sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ=="
+    },
+    "tty-browserify": {
+      "version": "0.0.1",
+      "resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.1.tgz",
+      "integrity": "sha512-C3TaO7K81YvjCgQH9Q1S3R3P3BtN3RIM8n+OvX4il1K1zgE8ZhI0op7kClgkxtutIE8hQrcrHBXvIheqKUUCxw==",
+      "dev": true
+    },
+    "type-is": {
+      "version": "1.6.18",
+      "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
+      "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==",
+      "requires": {
+        "media-typer": "0.3.0",
+        "mime-types": "~2.1.24"
+      }
+    },
+    "typedarray": {
+      "version": "0.0.6",
+      "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz",
+      "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=",
+      "dev": true
+    },
+    "umd": {
+      "version": "3.0.3",
+      "resolved": "https://registry.npmjs.org/umd/-/umd-3.0.3.tgz",
+      "integrity": "sha512-4IcGSufhFshvLNcMCV80UnQVlZ5pMOC8mvNPForqwA4+lzYQuetTESLDQkeLmihq8bRcnpbQa48Wb8Lh16/xow==",
+      "dev": true
+    },
+    "undeclared-identifiers": {
+      "version": "1.1.3",
+      "resolved": "https://registry.npmjs.org/undeclared-identifiers/-/undeclared-identifiers-1.1.3.tgz",
+      "integrity": "sha512-pJOW4nxjlmfwKApE4zvxLScM/njmwj/DiUBv7EabwE4O8kRUy+HIwxQtZLBPll/jx1LJyBcqNfB3/cpv9EZwOw==",
+      "dev": true,
+      "requires": {
+        "acorn-node": "^1.3.0",
+        "dash-ast": "^1.0.0",
+        "get-assigned-identifiers": "^1.2.0",
+        "simple-concat": "^1.0.0",
+        "xtend": "^4.0.1"
+      }
+    },
+    "unpipe": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
+      "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw="
+    },
+    "url": {
+      "version": "0.11.0",
+      "resolved": "https://registry.npmjs.org/url/-/url-0.11.0.tgz",
+      "integrity": "sha1-ODjpfPxgUh63PFJajlW/3Z4uKPE=",
+      "dev": true,
+      "requires": {
+        "punycode": "1.3.2",
+        "querystring": "0.2.0"
+      },
+      "dependencies": {
+        "punycode": {
+          "version": "1.3.2",
+          "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz",
+          "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=",
+          "dev": true
+        }
+      }
+    },
+    "url-parse": {
+      "version": "1.4.7",
+      "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.4.7.tgz",
+      "integrity": "sha512-d3uaVyzDB9tQoSXFvuSUNFibTd9zxd2bkVrDRvF5TmvWWQwqE4lgYJ5m+x1DbecWkw+LK4RNl2CU1hHuOKPVlg==",
+      "requires": {
+        "querystringify": "^2.1.1",
+        "requires-port": "^1.0.0"
+      }
+    },
+    "util": {
+      "version": "0.10.4",
+      "resolved": "https://registry.npmjs.org/util/-/util-0.10.4.tgz",
+      "integrity": "sha512-0Pm9hTQ3se5ll1XihRic3FDIku70C+iHUdT/W926rSgHV5QgXsYbKZN8MSC3tJtSkhuROzvsQjAaFENRXr+19A==",
+      "dev": true,
+      "requires": {
+        "inherits": "2.0.3"
+      }
+    },
+    "util-deprecate": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
+      "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8="
+    },
+    "utils-merge": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
+      "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM="
+    },
+    "uuid": {
+      "version": "3.3.3",
+      "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.3.tgz",
+      "integrity": "sha512-pW0No1RGHgzlpHJO1nsVrHKpOEIxkGg1xB+v0ZmdNH5OAeAwzAVrCnI2/6Mtx+Uys6iaylxa+D3g4j63IKKjSQ=="
+    },
+    "vary": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
+      "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw="
+    },
+    "vm-browserify": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-1.1.2.tgz",
+      "integrity": "sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ==",
+      "dev": true
+    },
+    "wrappy": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
+      "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
+      "dev": true
+    },
+    "ws": {
+      "version": "5.2.2",
+      "resolved": "https://registry.npmjs.org/ws/-/ws-5.2.2.tgz",
+      "integrity": "sha512-jaHFD6PFv6UgoIVda6qZllptQsMlDEJkTQcybzzXDYM1XO9Y8em691FGMPmM46WGyLU4z9KMgQN+qrux/nhlHA==",
+      "requires": {
+        "async-limiter": "~1.0.0"
+      }
+    },
+    "xtend": {
+      "version": "4.0.2",
+      "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
+      "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==",
+      "dev": true
+    }
+  }
+}
diff --git a/web-service/package.json b/web-service/server/package.json
similarity index 60%
rename from web-service/package.json
rename to web-service/server/package.json
index 6dee7625014e37a61dca840eb027af0d6bbfe81c..af0c459ede99227ca71debcef2883c69047ae1aa 100644
--- a/web-service/package.json
+++ b/web-service/server/package.json
@@ -8,13 +8,22 @@
   },
   "scripts": {
     "start": "node src/index.js",
+    "watch": "nodemon src/index.js",
     "test": "mocha test"
   },
   "author": "Nicolas Pope",
   "license": "ISC",
   "dependencies": {
+    "body-parser": "^1.19.0",
     "express": "^4.16.4",
     "express-ws": "^4.0.0",
-    "msgpack5": "^4.2.1"
+    "h264-converter": "^0.1.0",
+    "mongoose": "^5.7.3",
+    "msgpack5": "^4.2.1",
+    "url-parse": "^1.4.7",
+    "uuid": "^3.3.3"
+  },
+  "devDependencies": {
+    "browserify": "^16.5.0"
   }
 }
diff --git a/web-service/src/index.js b/web-service/server/src/index.js
similarity index 54%
rename from web-service/src/index.js
rename to web-service/server/src/index.js
index 7dc39a2a177eb8e9999f0a889740245ec54efcd6..c4664d2721b5dc7de5a44bbe222423c9f2ec5c12 100644
--- a/web-service/src/index.js
+++ b/web-service/server/src/index.js
@@ -2,15 +2,36 @@ const express = require('express');
 const app = express();
 const expressWs = require('express-ws')(app);
 const Peer = require('./peer.js');
+const mongoose = require('mongoose')
+const config = require('./utils/config')
+const User = require('./models/users')
+const Configs = require('./models/generic')
+const bodyParser = require('body-parser')
+const Url = require('url-parse')
 
 // ---- INDEXES ----------------------------------------------------------------
+app.use(express.static(__dirname + '/../../public'));
+app.use(bodyParser.json())
+
+// //CONNECTS THE APP TO MONGODB
+// mongoose.connect(config.MONGODB_URI, { useNewUrlParser: true, useUnifiedTopology: true })
+// 	.then(() => {
+// 		console.log('Connected to MongoDB');
+// 	})
+// 	.catch((err) => {
+// 		console.log(err);
+// 	})
 
 let peer_by_id = {};
-//let uri_to_peer = {};
+
+let uri_to_peer = {};
+
 let peer_uris = {};
 
 let uri_data = {};
 
+let peer_data = [];
+
 /**
  * A client stream request object. Each source maintains a list of clients who
  * are wanting frames from that source. Clients can only request N frames at a
@@ -84,7 +105,7 @@ RGBDStream.prototype.addClient = function(peer, N, rate, dest) {
 	}
 
 	this.clients.push(new RGBDClient(peer, N, rate, dest));
-
+	console.log("MINMAX", this.rxcount, this.rxmax);
 	if (this.rxcount >= this.rxmax) {
 		this.subscribe();
 	}
@@ -96,12 +117,15 @@ RGBDStream.prototype.subscribe = function() {
 	//console.log("Subscribe to ", this.uri);
 	// TODO: Don't hard code 9 here, instead use 9 for thumbnails and 0 for
 	// the video...
-	this.peer.send("get_stream", this.uri, 10, 9, [Peer.uuid], this.uri);
+	this.peer.send("get_stream", this.uri, 10, 0, [Peer.uuid], this.uri);
 }
 
 RGBDStream.prototype.pushFrames = function(latency, spacket, packet) {
-	if (spacket[1] & 0x1) this.depth = packet[4];
-	else this.rgb = packet[4];
+	//Checks that the type is jpg
+	if (packet[0] === 0){
+		if (spacket[3] > 0) this.depth = packet[5];
+		else this.rgb = packet[5];
+	}
 
 	console.log("Frame = ", packet[0], packet[1]);
 
@@ -124,29 +148,102 @@ app.get('/', (req, res) => {
 	res.end();
 });
 
+
 app.get('/streams', (req, res) => {
 	res.json(Object.keys(uri_data));
 });
 
+
+/**
+ * A list that has Object.keys(uri_data) values and also the image that is 
+ * binded to that 
+ */
 app.get('/stream/rgb', (req, res) => {
 	let uri = req.query.uri;
 	if (uri_data.hasOwnProperty(uri)) {
+		uri_data[uri].peer.send("get_stream", uri, 3, 9, [Peer.uuid], uri);
 		res.writeHead(200, {'Content-Type': 'image/jpeg'});
-    	res.end(uri_data[uri].rgb);
+		res.end(uri_data[uri].rgb);
 	}
 	res.end();
 });
 
+
 app.get('/stream/depth', (req, res) => {
 	let uri = req.query.uri;
-	if (uri_data.hasOwnProperty(uri)) {
+	const parsedURI = stringSplitter(uri)
+	if (uri_data.hasOwnProperty(parsedURI)) {
 		res.writeHead(200, {'Content-Type': 'image/png'});
-    	res.end(uri_data[uri].depth);
+    	res.end(uri_data[parsedURI].depth);
 	}
 	res.end();
 });
 
-//app.get('/stream', (req, res))
+app.post('/stream/config', async (req, res) => {
+	// const rawData = JSON.parse(req.body);
+	const {peerURI, configURI, data, saveToCPP} = req.body;
+	const parsedURI = stringSplitter(peerURI)
+
+	if(saveToCPP){
+		try{
+			let peer = uri_data[parsedURI].peer
+			if(peer){
+				peer.send("update_cfg", configURI, data)
+				return res.status(200).json("Successfully saved configs")
+			}
+			return res.status(502).json("Something went wrong")
+		}catch(e) {
+			console.log(e)
+		}
+
+	}// else{
+	// //Save to MongoDB
+	// const savedConfigs = new Configs({
+	// 	settingsURI: configURI,
+	// 	data
+	// });
+
+	// try{
+	// 	await savedConfigs.save();
+	// 	return res.status(200).json('Your configurations were saved successfully')
+	// }catch(err){
+	// 	console.log(err)
+	// 	return res.status(500).json("Something's wrong I can feel it")
+	// }
+	// }
+
+})
+
+app.get('/stream/config', async(req, res) => {
+	
+	//example of uri ftlab.utu.fi/stream/config?uri=ftl://utu.fi#reconstruction_snap10/merge
+	const settings = req.query.settings;
+	const uri = req.query.uri;
+	const parsedURI = stringSplitter(uri)
+
+	// //Checks if DB has data
+	// let dbData = await Configs.find({Settings: settings});
+	// if(dbData[0].data){
+	// 	return res.status(200).json(dbData[0]);
+	// }else{
+		let peer = uri_data[parsedURI].peer
+		if(peer){
+			peer.rpc("get_cfg", (response) => {
+				if(response){
+					return res.status(200).json(response);
+				}
+			}, settings)
+		}
+	// }
+})
+
+
+app.get('/stream', (req, res) => {
+	//If wanted, this could render new html file dedicated to the actual livestream function
+	let uri = req.query.uri;
+	res.end();
+})
+
 
 function checkStreams(peer) {
 	if (!peer.master) {
@@ -154,8 +251,9 @@ function checkStreams(peer) {
 			console.log("STREAMS", streams);
 			for (let i=0; i<streams.length; i++) {
 				//uri_to_peer[streams[i]] = peer;
-				peer_uris[peer.string_id].push(streams[i]);
-
+				let parsedURI = stringSplitter(streams[i])
+				peer_uris[peer.string_id].push(parsedURI);
+				uri_to_peer[parsedURI] = peer;
 				uri_data[streams[i]] = new RGBDStream(streams[i], peer);
 			}
 		});
@@ -170,10 +268,13 @@ function broadcastExcept(exc, name, ...args) {
 	}
 }
 
+
 app.ws('/', (ws, req) => {
 	console.log("New web socket request");
-
+	//console.log('WEBSOCKET', ws)
+	
 	let p = new Peer(ws);
+	peer_data.push(p);
 
 	p.on("connect", (peer) => {
 		console.log("Node connected...", peer.string_id);
@@ -188,7 +289,6 @@ app.ws('/', (ws, req) => {
 			peer.master = (obj.kind == "master");
 			console.log("Peer name = ", peer.name);
 			console.log("Details: ", details);
-
 			checkStreams(peer);
 		});
 	});
@@ -219,7 +319,7 @@ app.ws('/', (ws, req) => {
 	});
 
 	p.bind("node_details", () => {
-		return ['{"title": "FTL Web-Service", "id": "0", "kind": "master"}'];
+		return [`{"title": "FTL Web-Service", "id": "${p.getUuid()}", "kind": "master"}`];
 	});
 
 	p.bind("list_streams", () => {
@@ -227,7 +327,8 @@ app.ws('/', (ws, req) => {
 	});
 
 	p.bind("find_stream", (uri) => {
-		if (uri_data.hasOwnProperty(uri)) {
+		const parsedURI = stringSplitter(uri)
+		if (uri_to_peer.hasOwnProperty(parsedURI)) {
 			console.log("Stream found: ", uri);
 			return [Peer.uuid];
 		} else {
@@ -238,51 +339,122 @@ app.ws('/', (ws, req) => {
 
 	// Requests camera calibration information
 	p.proxy("source_details", (cb, uri, chan) => {
-		let peer = uri_data[uri].peer;
-		if (peer) {
-			peer.rpc("source_details", cb, uri, chan);
+		const parsedURI = stringSplitter(uri);
+		if(uri_to_peer[parsedURI]){
+			let peer = uri_to_peer[parsedURI].peer
+			if (peer) {
+				peer.rpc("source_details", cb, uri, chan);
+			}
+		}else{
+			console.log("Failed to get source details for URI", uri);
+			return "{}"
 		}
 	});
 
 	// Get the current position of a camera
 	p.proxy("get_pose", (cb, uri) => {
 		//console.log("SET POSE");
-		let peer = uri_data[uri].peer;
-		if (peer) {
-			peer.rpc("get_pose", cb, uri);
+		const parsedURI = stringSplitter(uri);
+		if(uri_to_peer[parsedURI]){
+			let peer = uri_to_peer[parsedURI].peer
+			if (peer) {
+				peer.rpc("get_pose", cb, uri);
+			}
+		}else{
+			console.log("Failed to get pose for URI", uri);
+			return "{}"
 		}
 	});
 
 	// Change the position of a camera
 	p.bind("set_pose", (uri, vec) => {
-		let peer = uri_data[uri].peer;
-		if (peer) {
-			uri_data[uri].pose = vec;
-			peer.send("set_pose", uri, vec);
+		const parsedURI = stringSplitter(uri);
+		if(uri_to_peer[parsedURI]){
+			let peer = uri_to_peer[parsedURI].peer
+			if (peer) {
+				uri_data[parsedURI].pose = vec;
+				peer.send("set_pose", uri, vec);
+			}
+		}else{
+			console.log("Couldn't set pose for URI", uri)
+			return "{}";
 		}
 	});
 
 	// Request from frames from a source
-	p.bind("get_stream", (uri, N, rate, pid, dest) => {
-		let peer = uri_data[uri].peer;
-		if (peer) {
-			uri_data[uri].addClient(p, N, rate, dest);
+	p.bind("get_stream", (uri, N, rate, /*pid,*/ dest) => {
+		console.log(uri)
+		const parsedURI = stringSplitter(uri);
+		if(uri_data[uri]){
+			let peer = uri_data[uri].peer
+			console.log(peer)
+			if (peer) {
+				console.log("THIS GETS LOGGED")
+				uri_data[uri].addClient(p, N, rate, dest);
+				console.log("SO DOES THIS")
 			//peer.send("get_stream", uri, N, rate, [Peer.uuid], dest);
+			}
+		}else{
+			console.log("Couldn't get stream for ", uri)
+			return "{}";
 		}
 	});
 
+	/**
+	 * Get JSON values for stream configuration
+	 */ 
+	p.bind("get_cfg", (cb, uri) => {
+		const parsedURI = stringSplitter(uri);
+		if(uri_to_peer[parsedURI]){
+			let peer = uri_to_peer[parsedURI].peer
+			if(peer){
+				peer.rpc("get_cfg", cb, uri)
+			}	
+		}else{
+			console.log("Config not found", uri)
+			return "{}";
+		}
+	})
+
+	/**
+	 * Update certain URIs values
+	 */
+	 p.bind("update_cfg", (uri, json) => {
+		const parsedURI = stringSplitter(uri)
+		console.log("URI", uri)
+		console.log("JSON", json)
+		if(uri_to_peer[parsedURI]){
+			let peer = uri_to_peer[parsedURI]
+			peer.send("update_cfg", uri, json)
+		}else{
+			console.log("Failed to update the configuration uri", uri)
+			return "{}";
+		}
+	 })
+
 	// Register a new stream
 	p.bind("add_stream", (uri) => {
+		const parsedURI = stringSplitter(uri)
 		console.log("Adding stream: ", uri);
 		//uri_to_peer[streams[i]] = peer;
-		peer_uris[p.string_id].push(uri);
-
+		peer_uris[p.string_id].push(parsedURI);
+		uri_to_peer[parsedURI] = p;
 		uri_data[uri] = new RGBDStream(uri, p);
 
 		broadcastExcept(p, "add_stream", uri);
 	});
 });
 
+/**
+ * Returns the first part of the URI
+ * e.g. ftl://utu.fi or ftl://something.fi
+ * @param {uri} uri 
+ */
+function stringSplitter(uri) {
+	const url = new Url(uri)
+	return url.origin;
+}
+
 console.log("Listening or port 8080");
 app.listen(8080);
 
diff --git a/web-service/server/src/models/generic.js b/web-service/server/src/models/generic.js
new file mode 100644
index 0000000000000000000000000000000000000000..7fd22085c6b7ab59be647354507e9753b44e33c3
--- /dev/null
+++ b/web-service/server/src/models/generic.js
@@ -0,0 +1,53 @@
+/**
+ * This is the generic model for the MongoDB
+ * 
+ * Single collection contains the following values
+ * URI: {type: String, not null}
+ * data: Object
+ *      the actual data
+ * 
+ * 
+ * e.g
+ * 
+ * URI: 'ftl://utu.fi/stream/configurations/calibrations
+ * data: {
+ *      default: {
+ *              board_size: [9,7]
+ *              square_size: 1
+ *      }
+ * 
+ *      HD_quality: {
+ *              board_size: [5,2]
+ *              square_size: 5
+ *      }
+ * }
+ * 
+ * URI: 'ftl://utu.fi/stream/configurations/disparity/name/'
+ * data: {
+ *      default: {
+ *              name: 'default'
+ *      }
+ * 
+ *      HD_quality: {
+ *              name: 'HD_quality'
+ *      }
+ * 
+ * }
+ */
+
+
+const mongoose = require('mongoose')
+
+const configsSchema = mongoose.Schema({
+    settingsURI: String,
+    data: Object
+  })
+
+  configsSchema.set('toJSON', {
+    transform: (document, returnedObject) => {
+      delete returnedObject._id
+      delete returnedObject.__v
+    }
+  })
+  
+module.exports = mongoose.model('configs', configsSchema)
\ No newline at end of file
diff --git a/web-service/server/src/models/users.js b/web-service/server/src/models/users.js
new file mode 100644
index 0000000000000000000000000000000000000000..7baa6448f93c7f4eced75857f1c018bebe131ad6
--- /dev/null
+++ b/web-service/server/src/models/users.js
@@ -0,0 +1,8 @@
+const mongoose = require('mongoose')
+
+const userSchema = mongoose.Schema({
+    googleID: Number,
+    hakaID: String
+  })
+  
+module.exports = mongoose.model('User', userSchema)
\ No newline at end of file
diff --git a/web-service/src/peer.js b/web-service/server/src/peer.js
similarity index 80%
rename from web-service/src/peer.js
rename to web-service/server/src/peer.js
index 51cd78e6a9ad07ec17f478646b3b60171e6280bc..dd943a09387f6de8925e32c36c2d383ffa5030f7 100644
--- a/web-service/src/peer.js
+++ b/web-service/server/src/peer.js
@@ -1,19 +1,25 @@
 const msgpack = require('msgpack5')()
   , encode  = msgpack.encode
   , decode  = msgpack.decode;
+const uuidv4 = require('uuid') //Deprecated method, should use require('uuid/v4')
+const uuidParser = require('./utils/uuidParser')
 
 const kConnecting = 1;
 const kConnected = 2;
 const kDisconnected = 3;
 
 // Generate a unique id for this webservice
-let my_uuid = new Uint8Array(16);
-my_uuid[0] = 44;
+let uuid = uuidv4();
+let my_uuid = uuidParser.parse(uuid)
+my_uuid = new Uint8Array(my_uuid);
+// my_uuid[0] = 44;
+// console.log(my_uuid)
 my_uuid = Buffer.from(my_uuid);
 
 const kMagic = 0x0009340053640912;
 const kVersion = 0;
 
+
 /**
  * Wrap a web socket with a MsgPack RCP protocol that works with our C++ version.
  * @param {websocket} ws Websocket object
@@ -33,16 +39,22 @@ function Peer(ws) {
 	this.name = "unknown";
 	this.master = false;
 
-	this.sock.on("message", (raw) => {
+	let message = (raw) => {
+		// console.log(raw)
+		//Gets right data for client
+		if(this.sock.on === undefined){
+			raw = raw.data;
+		}
 		let msg = decode(raw);
+		// console.log('MSG', msg)
 		if (this.status == kConnecting) {
 			if (msg[1] != "__handshake__") {
 				console.log("Bad handshake");
 				this.close();
 			}
 		}
-		//console.log("MSG", msg);
 		if (msg[0] == 0) {
+			// console.log("MSG...", msg[2]);
 			// Notification
 			if (msg.length == 3) {
 				this._dispatchNotification(msg[1], msg[2]);
@@ -53,18 +65,32 @@ function Peer(ws) {
 		} else if (msg[0] == 1) {
 			this._dispatchResponse(msg[1], msg[3]);
 		}
-	});
+	}
 
-	this.sock.on("close", () => {
+	let close = () => {
 		this.status = kDisconnected;
 		this._notify("disconnect", this);
-	});
+	}
 
-	this.sock.on("error", () => {
+	let error = () => {
 		console.error("Socket error");
 		this.sock.close();
 		this.status = kDisconnected;
-	});
+	}
+
+	//if undefined, peer is being used by client
+	if(this.sock.on === undefined){
+		this.sock.onmessage = message;
+		this.sock.onclose = close;
+		this.sock.onopen = (event) => {
+			this.send("__handshake__", kMagic, kVersion, [my_uuid]);
+		}
+	//else peer is being used by server
+	}else{
+		this.sock.on("message", message);
+		this.sock.on("close", close);
+		this.sock.on("error", error);
+	}
 
 	this.bind("__handshake__", (magic, version, id) => {
 		if (magic == kMagic) {
@@ -73,14 +99,17 @@ function Peer(ws) {
 			this.id = id.buffer;
 			this.string_id  = id.toString('hex');
 			this._notify("connect", this);
+			// if(this.sock.on === undefined){
+			// 	this.send("__handshake__", kMagic, kVersion, [my_uuid]);
+			// }
 		} else {
 			console.log("Magic does not match");
 			this.close();
 		}
 	});
-
 	this.send("__handshake__", kMagic, kVersion, [my_uuid]);
-}
+}		
+
 
 Peer.uuid = my_uuid;
 
@@ -100,6 +129,7 @@ Peer.prototype._dispatchNotification = function(name, args) {
  * @private
  */
 Peer.prototype._dispatchCall = function(name, id, args) {
+	console.log("DISPATCHCALL", name, id, args)
 	if (this.bindings.hasOwnProperty(name)) {
 		//console.log("Call for:", name, id);
 
@@ -107,7 +137,7 @@ Peer.prototype._dispatchCall = function(name, id, args) {
 			let res = this.bindings[name].apply(this, args);
 			this.sock.send(encode([1,id,name,res]));
 		} catch(e) {
-			console.error("Could to dispatch or return call");
+			console.error("Could to dispatch or return call", e);
 			this.close();
 		}
 	} else if (this.proxies.hasOwnProperty(name)) {
@@ -116,6 +146,7 @@ Peer.prototype._dispatchCall = function(name, id, args) {
 			try {
 				this.sock.send(encode([1,id,name,res]));
 			} catch(e) {
+				console.log("ERROR")
 				this.close();
 			}
 		});
@@ -207,8 +238,13 @@ Peer.prototype.send = function(name, ...args) {
 	}
 }
 
+/**
+ * Closes the socket
+ */
 Peer.prototype.close = function() {
-	this.sock.close();
+	if(this.sock.on !== undefined){
+		this.sock.close();
+	}
 	this.status = kDisconnected;
 }
 
@@ -238,4 +274,9 @@ Peer.prototype.on = function(evt, f) {
 	this.events[evt].push(f);
 }
 
+
+Peer.prototype.getUuid = function() {
+	return uuid;
+}
+
 module.exports = Peer;
diff --git a/web-service/server/src/utils/config.js b/web-service/server/src/utils/config.js
new file mode 100644
index 0000000000000000000000000000000000000000..a30bba3549f654d62c106edb11552fa8faa3455d
--- /dev/null
+++ b/web-service/server/src/utils/config.js
@@ -0,0 +1,9 @@
+module.exports = {
+    /**
+     * The URI for MongoDB needs to be added here
+     * 
+     * When installing MongoDB lookup the models for
+     * user and generic from the models folder
+     */
+    MONGODB_URI: '' //Add the mongo URI here
+}
\ No newline at end of file
diff --git a/web-service/server/src/utils/uuidParser.js b/web-service/server/src/utils/uuidParser.js
new file mode 100644
index 0000000000000000000000000000000000000000..e4d705601b12111cc0ebcc0cb37e5c63b1420fbf
--- /dev/null
+++ b/web-service/server/src/utils/uuidParser.js
@@ -0,0 +1,54 @@
+// Maps for number <-> hex string conversion
+var _byteToHex = [];
+var _hexToByte = {};
+for (var i = 0; i < 256; i++) {
+  _byteToHex[i] = (i + 0x100).toString(16).substr(1);
+  _hexToByte[_byteToHex[i]] = i;
+}
+
+/** 
+ * `parse()` - Parse a UUID into it's component bytes
+ * 
+ * Turns UUID into Buffer
+ **/ 
+function parse(s, buf, offset) {
+  var i = (buf && offset) || 0;
+  var ii = 0;
+
+  buf = buf || [];
+  s.toLowerCase().replace(/[0-9a-f]{2}/g, function(oct) {
+    if (ii < 16) { // Don't overflow!
+      buf[i + ii++] = _hexToByte[oct];
+    }
+  });
+
+  // Zero out remaining bytes if string was short
+  while (ii < 16) {
+    buf[i + ii++] = 0;
+  }
+
+  return buf;
+}
+
+/**
+ * `unparse()` - Convert UUID byte array (ala parse()) into a string
+ * 
+ * Turns Buffer into UUID
+ * */
+function unparse(buf, offset) {
+  var i = offset || 0;
+  var bth = _byteToHex;
+  return  bth[buf[i++]] + bth[buf[i++]] +
+          bth[buf[i++]] + bth[buf[i++]] + '-' +
+          bth[buf[i++]] + bth[buf[i++]] + '-' +
+          bth[buf[i++]] + bth[buf[i++]] + '-' +
+          bth[buf[i++]] + bth[buf[i++]] + '-' +
+          bth[buf[i++]] + bth[buf[i++]] +
+          bth[buf[i++]] + bth[buf[i++]] +
+          bth[buf[i++]] + bth[buf[i++]];
+}
+
+module.exports = {
+  parse: parse,
+  unparse: unparse
+};
\ No newline at end of file