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 ¶ms, 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 ¶ms, + 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 ¶ms, int win, - cudaStream_t stream + float4x4 &pose1, + float4x4 &pose1_inv, + float4x4 &pose2, + const ftl::rgbd::Camera &cam1, + const ftl::rgbd::Camera &cam2, + const ILWParams ¶ms, 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 ¶ms, - 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 ¶ms, 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 ¶ms, + 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 ¶ms = 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