Giter Club home page Giter Club logo

puppetcam's Introduction

Puppetcam

Example to export chrome tab as a video

  1. Exported videos are stored in Downloads folder
  2. Specify bitrate to control quality of the exported video by adjusting videoBitsPerSecond property in background.js

Dependencies

  1. xvfb
  2. npm modules listed in package.json

Usage

npm install
node export.js http://tobiasahlin.com/spinkit/ spinner.webm

Thanks to @cretz for helping with automatic tab selection and avoiding the permission dialog

Motivation

Was looking for a method to export a video of user actions rendered using our custom player used in uxlens. Export has to happen on a server in an automated fashion and hence the usage of xvfb.

Sample video

Puppetcam

puppetcam's People

Contributors

gazben avatar muralikg avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

puppetcam's Issues

Long duration recordings

Hey,

My use case is to record a url where a video conference meeting is happening for more than an hour. There could be >30 meetings happening concurrently.
There is a cron job which runs at 11AM daily, which fetches the scheduled meetings for today and spawns instances of export.js when the meeting starts.
Some recordings do happen successfully but most of them emits one of the following errors -

Error: Protocol error (Runtime.callFunctionOn): Session closed. Most likely the page has been closed.
Error: Page crashed!
Error: Protocol error (Runtime.callFunctionOn): Target closed.

My guess was that the recording video chunks are kept in browser memory (in background.js) and then on stop they are written to disk, and if the video file is large the process runs out of memory, so I tried running node export.js script with --max-old-space-size=2048 but still the same errors. Does the puppeteer browser run out of memory? How to increase the limit for that? I saw the CPU usage of machine, it is well under limit.

Also this might not be the appropriate place to ask this, but I am desperate to get an answer.

Errors when executing `npm install`

Tried to install on Ubuntu 18.04 headless server

$ npm install

> [email protected] install /home/ubuntu/puppetcam/node_modules/sleep
> node-gyp rebuild

make: Entering directory '/home/ubuntu/puppetcam/node_modules/sleep/build'
  CXX(target) Release/obj.target/node_sleep/sleep.o
In file included from ../../nan/nan_converters.h:67:0,
                 from ../../nan/nan.h:202,
                 from ../sleep.cc:2:
../../nan/nan_converters_43_inl.h: In static member function ‘static Nan::imp::ToFactoryBase<v8::Boolean>::return_t Nan::imp::ToFactory<v8::Boolean>::convert(v8::Local<v8::Value>)’:
../../nan/nan_converters_43_inl.h:18:51: error: no matching function for call to ‘v8::Value::ToBoolean(v8::Local<v8::Context>)’
       val->To ## TYPE(isolate->GetCurrentContext())                            \
                                                   ^
../../nan/nan_converters_43_inl.h:22:1: note: in expansion of macro ‘X’
 X(Boolean)
 ^
In file included from /home/ubuntu/.cache/node-gyp/14.3.0/include/node/node.h:67:0,
                 from ../../nan/nan.h:51,
                 from ../sleep.cc:2:
/home/ubuntu/.cache/node-gyp/14.3.0/include/node/v8.h:2845:18: note: candidate: v8::Local<v8::Boolean> v8::Value::ToBoolean(v8::Isolate*) const
   Local<Boolean> ToBoolean(Isolate* isolate) const;
                  ^~~~~~~~~
/home/ubuntu/.cache/node-gyp/14.3.0/include/node/v8.h:2845:18: note:   no known conversion for argument 1 from ‘v8::Local<v8::Context>’ to ‘v8::Isolate*’
In file included from ../../nan/nan_converters.h:67:0,
                 from ../../nan/nan.h:202,
                 from ../sleep.cc:2:
../../nan/nan_converters_43_inl.h: In static member function ‘static Nan::imp::ValueFactoryBase<bool>::return_t Nan::imp::ToFactory<bool>::convert(v8::Local<v8::Value>)’:
../../nan/nan_converters_43_inl.h:37:57: error: no matching function for call to ‘v8::Value::BooleanValue(v8::Local<v8::Context>)’
   return val->NAME ## Value(isolate->GetCurrentContext());                     \
                                                         ^
../../nan/nan_converters_43_inl.h:40:1: note: in expansion of macro ‘X’
 X(bool, Boolean)
 ^
In file included from /home/ubuntu/.cache/node-gyp/14.3.0/include/node/node.h:67:0,
                 from ../../nan/nan.h:51,
                 from ../sleep.cc:2:
/home/ubuntu/.cache/node-gyp/14.3.0/include/node/v8.h:2855:8: note: candidate: bool v8::Value::BooleanValue(v8::Isolate*) const
   bool BooleanValue(Isolate* isolate) const;
        ^~~~~~~~~~~~
/home/ubuntu/.cache/node-gyp/14.3.0/include/node/v8.h:2855:8: note:   no known conversion for argument 1 from ‘v8::Local<v8::Context>’ to ‘v8::Isolate*’
In file included from ../../nan/nan_new.h:189:0,
                 from ../../nan/nan.h:203,
                 from ../sleep.cc:2:
../../nan/nan_implementation_12_inl.h: In static member function ‘static Nan::imp::FactoryBase<v8::Function>::return_t Nan::imp::Factory<v8::Function>::New(Nan::FunctionCallback, v8::Local<v8::Value>)’:
../../nan/nan_implementation_12_inl.h:105:32: error: no matching function for call to ‘v8::Function::New(v8::Isolate*&, void (&)(const v8::FunctionCallbackInfo<v8::Value>&), v8::Local<v8::Object>&)’
                           , obj));
                                ^
In file included from /home/ubuntu/.cache/node-gyp/14.3.0/include/node/node.h:67:0,
                 from ../../nan/nan.h:51,
                 from ../sleep.cc:2:
/home/ubuntu/.cache/node-gyp/14.3.0/include/node/v8.h:4407:31: note: candidate: static v8::MaybeLocal<v8::Function> v8::Function::New(v8::Local<v8::Context>, v8::FunctionCallback, v8::Local<v8::Value>, int, v8::ConstructorBehavior, v8::SideEffectType)
   static MaybeLocal<Function> New(
                               ^~~
/home/ubuntu/.cache/node-gyp/14.3.0/include/node/v8.h:4407:31: note:   no known conversion for argument 1 from ‘v8::Isolate*’ to ‘v8::Local<v8::Context>’
In file included from ../../nan/nan_new.h:189:0,
                 from ../../nan/nan.h:203,
                 from ../sleep.cc:2:
../../nan/nan_implementation_12_inl.h: In static member function ‘static Nan::imp::FactoryBase<v8::StringObject>::return_t Nan::imp::Factory<v8::StringObject>::New(v8::Local<v8::String>)’:
../../nan/nan_implementation_12_inl.h:337:37: error: no matching function for call to ‘v8::StringObject::New(v8::Local<v8::String>&)’
   return v8::StringObject::New(value).As<v8::StringObject>();
                                     ^
In file included from /home/ubuntu/.cache/node-gyp/14.3.0/include/node/node.h:67:0,
                 from ../../nan/nan.h:51,
                 from ../sleep.cc:2:
/home/ubuntu/.cache/node-gyp/14.3.0/include/node/v8.h:5784:23: note: candidate: static v8::Local<v8::Value> v8::StringObject::New(v8::Isolate*, v8::Local<v8::String>)
   static Local<Value> New(Isolate* isolate, Local<String> value);
                       ^~~
/home/ubuntu/.cache/node-gyp/14.3.0/include/node/v8.h:5784:23: note:   candidate expects 2 arguments, 1 provided
In file included from ../../nan/nan_new.h:189:0,
                 from ../../nan/nan.h:203,
                 from ../sleep.cc:2:
../../nan/nan_implementation_12_inl.h:337:58: error: expected primary-expression before ‘>’ token
   return v8::StringObject::New(value).As<v8::StringObject>();
                                                          ^
../../nan/nan_implementation_12_inl.h:337:60: error: expected primary-expression before ‘)’ token
   return v8::StringObject::New(value).As<v8::StringObject>();
                                                            ^
In file included from ../sleep.cc:2:0:
../../nan/nan.h: In constructor ‘Nan::Utf8String::Utf8String(v8::Local<v8::Value>)’:
../../nan/nan.h:1034:53: error: no matching function for call to ‘v8::Value::ToString()’
       v8::Local<v8::String> string = from->ToString();
                                                     ^
In file included from /home/ubuntu/.cache/node-gyp/14.3.0/include/node/node.h:67:0,
                 from ../../nan/nan.h:51,
                 from ../sleep.cc:2:
/home/ubuntu/.cache/node-gyp/14.3.0/include/node/v8.h:2807:44: note: candidate: v8::MaybeLocal<v8::String> v8::Value::ToString(v8::Local<v8::Context>) const
   V8_WARN_UNUSED_RESULT MaybeLocal<String> ToString(
                                            ^~~~~~~~
/home/ubuntu/.cache/node-gyp/14.3.0/include/node/v8.h:2807:44: note:   candidate expects 1 argument, 0 provided
In file included from ../sleep.cc:2:0:
../../nan/nan.h:1044:74: error: no matching function for call to ‘v8::String::WriteUtf8(char*&, int, int, const int&)’
         length_ = string->WriteUtf8(str_, static_cast<int>(len), 0, flags);
                                                                          ^
In file included from /home/ubuntu/.cache/node-gyp/14.3.0/include/node/node.h:67:0,
                 from ../../nan/nan.h:51,
                 from ../sleep.cc:2:
/home/ubuntu/.cache/node-gyp/14.3.0/include/node/v8.h:3034:7: note: candidate: int v8::String::WriteUtf8(v8::Isolate*, char*, int, int*, int) const
   int WriteUtf8(Isolate* isolate, char* buffer, int length = -1,
       ^~~~~~~~~
/home/ubuntu/.cache/node-gyp/14.3.0/include/node/v8.h:3034:7: note:   no known conversion for argument 1 from ‘char*’ to ‘v8::Isolate*’
In file included from ../sleep.cc:2:0:
../../nan/nan.h: In member function ‘void Nan::AsyncWorker::SaveToPersistent(const char*, const v8::Local<v8::Value>&)’:
../../nan/nan.h:1818:64: error: no matching function for call to ‘v8::Object::Set(v8::Local<v8::String>, const v8::Local<v8::Value>&)’
     New(persistentHandle)->Set(New(key).ToLocalChecked(), value);
                                                                ^
In file included from /home/ubuntu/.cache/node-gyp/14.3.0/include/node/node.h:67:0,
                 from ../../nan/nan.h:51,
                 from ../sleep.cc:2:
/home/ubuntu/.cache/node-gyp/14.3.0/include/node/v8.h:3639:37: note: candidate: v8::Maybe<bool> v8::Object::Set(v8::Local<v8::Context>, v8::Local<v8::Value>, v8::Local<v8::Value>)
   V8_WARN_UNUSED_RESULT Maybe<bool> Set(Local<Context> context,
                                     ^~~
/home/ubuntu/.cache/node-gyp/14.3.0/include/node/v8.h:3639:37: note:   candidate expects 3 arguments, 2 provided
/home/ubuntu/.cache/node-gyp/14.3.0/include/node/v8.h:3642:37: note: candidate: v8::Maybe<bool> v8::Object::Set(v8::Local<v8::Context>, uint32_t, v8::Local<v8::Value>)
   V8_WARN_UNUSED_RESULT Maybe<bool> Set(Local<Context> context, uint32_t index,
                                     ^~~
/home/ubuntu/.cache/node-gyp/14.3.0/include/node/v8.h:3642:37: note:   candidate expects 3 arguments, 2 provided
In file included from ../sleep.cc:2:0:
../../nan/nan.h: In member function ‘void Nan::AsyncWorker::SaveToPersistent(const v8::Local<v8::String>&, const v8::Local<v8::Value>&)’:
../../nan/nan.h:1824:42: error: no matching function for call to ‘v8::Object::Set(const v8::Local<v8::String>&, const v8::Local<v8::Value>&)’
     New(persistentHandle)->Set(key, value);
                                          ^
In file included from /home/ubuntu/.cache/node-gyp/14.3.0/include/node/node.h:67:0,
                 from ../../nan/nan.h:51,
                 from ../sleep.cc:2:
/home/ubuntu/.cache/node-gyp/14.3.0/include/node/v8.h:3639:37: note: candidate: v8::Maybe<bool> v8::Object::Set(v8::Local<v8::Context>, v8::Local<v8::Value>, v8::Local<v8::Value>)
   V8_WARN_UNUSED_RESULT Maybe<bool> Set(Local<Context> context,
                                     ^~~
/home/ubuntu/.cache/node-gyp/14.3.0/include/node/v8.h:3639:37: note:   candidate expects 3 arguments, 2 provided
/home/ubuntu/.cache/node-gyp/14.3.0/include/node/v8.h:3642:37: note: candidate: v8::Maybe<bool> v8::Object::Set(v8::Local<v8::Context>, uint32_t, v8::Local<v8::Value>)
   V8_WARN_UNUSED_RESULT Maybe<bool> Set(Local<Context> context, uint32_t index,
                                     ^~~
/home/ubuntu/.cache/node-gyp/14.3.0/include/node/v8.h:3642:37: note:   candidate expects 3 arguments, 2 provided
In file included from ../sleep.cc:2:0:
../../nan/nan.h: In member function ‘void Nan::AsyncWorker::SaveToPersistent(uint32_t, const v8::Local<v8::Value>&)’:
../../nan/nan.h:1830:44: error: no matching function for call to ‘v8::Object::Set(uint32_t&, const v8::Local<v8::Value>&)’
     New(persistentHandle)->Set(index, value);
                                            ^
In file included from /home/ubuntu/.cache/node-gyp/14.3.0/include/node/node.h:67:0,
                 from ../../nan/nan.h:51,
                 from ../sleep.cc:2:
/home/ubuntu/.cache/node-gyp/14.3.0/include/node/v8.h:3639:37: note: candidate: v8::Maybe<bool> v8::Object::Set(v8::Local<v8::Context>, v8::Local<v8::Value>, v8::Local<v8::Value>)
   V8_WARN_UNUSED_RESULT Maybe<bool> Set(Local<Context> context,
                                     ^~~
/home/ubuntu/.cache/node-gyp/14.3.0/include/node/v8.h:3639:37: note:   candidate expects 3 arguments, 2 provided
/home/ubuntu/.cache/node-gyp/14.3.0/include/node/v8.h:3642:37: note: candidate: v8::Maybe<bool> v8::Object::Set(v8::Local<v8::Context>, uint32_t, v8::Local<v8::Value>)
   V8_WARN_UNUSED_RESULT Maybe<bool> Set(Local<Context> context, uint32_t index,
                                     ^~~
/home/ubuntu/.cache/node-gyp/14.3.0/include/node/v8.h:3642:37: note:   candidate expects 3 arguments, 2 provided
In file included from ../sleep.cc:2:0:
../../nan/nan.h: In member function ‘v8::Local<v8::Value> Nan::AsyncWorker::GetFromPersistent(const char*) const’:
../../nan/nan.h:1836:61: error: no matching function for call to ‘v8::Object::Get(v8::Local<v8::String>)’
         New(persistentHandle)->Get(New(key).ToLocalChecked()));
                                                             ^
In file included from /home/ubuntu/.cache/node-gyp/14.3.0/include/node/node.h:67:0,
                 from ../../nan/nan.h:51,
                 from ../sleep.cc:2:
/home/ubuntu/.cache/node-gyp/14.3.0/include/node/v8.h:3686:43: note: candidate: v8::MaybeLocal<v8::Value> v8::Object::Get(v8::Local<v8::Context>, v8::Local<v8::Value>)
   V8_WARN_UNUSED_RESULT MaybeLocal<Value> Get(Local<Context> context,
                                           ^~~
/home/ubuntu/.cache/node-gyp/14.3.0/include/node/v8.h:3686:43: note:   candidate expects 2 arguments, 1 provided
/home/ubuntu/.cache/node-gyp/14.3.0/include/node/v8.h:3689:43: note: candidate: v8::MaybeLocal<v8::Value> v8::Object::Get(v8::Local<v8::Context>, uint32_t)
   V8_WARN_UNUSED_RESULT MaybeLocal<Value> Get(Local<Context> context,
                                           ^~~
/home/ubuntu/.cache/node-gyp/14.3.0/include/node/v8.h:3689:43: note:   candidate expects 2 arguments, 1 provided
In file included from ../sleep.cc:2:0:
../../nan/nan.h: In member function ‘v8::Local<v8::Value> Nan::AsyncWorker::GetFromPersistent(const v8::Local<v8::String>&) const’:
../../nan/nan.h:1842:55: error: no matching function for call to ‘v8::Object::Get(const v8::Local<v8::String>&)’
     return scope.Escape(New(persistentHandle)->Get(key));
                                                       ^
In file included from /home/ubuntu/.cache/node-gyp/14.3.0/include/node/node.h:67:0,
                 from ../../nan/nan.h:51,
                 from ../sleep.cc:2:
/home/ubuntu/.cache/node-gyp/14.3.0/include/node/v8.h:3686:43: note: candidate: v8::MaybeLocal<v8::Value> v8::Object::Get(v8::Local<v8::Context>, v8::Local<v8::Value>)
   V8_WARN_UNUSED_RESULT MaybeLocal<Value> Get(Local<Context> context,
                                           ^~~
/home/ubuntu/.cache/node-gyp/14.3.0/include/node/v8.h:3686:43: note:   candidate expects 2 arguments, 1 provided
/home/ubuntu/.cache/node-gyp/14.3.0/include/node/v8.h:3689:43: note: candidate: v8::MaybeLocal<v8::Value> v8::Object::Get(v8::Local<v8::Context>, uint32_t)
   V8_WARN_UNUSED_RESULT MaybeLocal<Value> Get(Local<Context> context,
                                           ^~~
/home/ubuntu/.cache/node-gyp/14.3.0/include/node/v8.h:3689:43: note:   candidate expects 2 arguments, 1 provided
In file included from ../sleep.cc:2:0:
../../nan/nan.h: In member function ‘v8::Local<v8::Value> Nan::AsyncWorker::GetFromPersistent(uint32_t) const’:
../../nan/nan.h:1847:57: error: no matching function for call to ‘v8::Object::Get(uint32_t&)’
     return scope.Escape(New(persistentHandle)->Get(index));
                                                         ^
In file included from /home/ubuntu/.cache/node-gyp/14.3.0/include/node/node.h:67:0,
                 from ../../nan/nan.h:51,
                 from ../sleep.cc:2:
/home/ubuntu/.cache/node-gyp/14.3.0/include/node/v8.h:3686:43: note: candidate: v8::MaybeLocal<v8::Value> v8::Object::Get(v8::Local<v8::Context>, v8::Local<v8::Value>)
   V8_WARN_UNUSED_RESULT MaybeLocal<Value> Get(Local<Context> context,
                                           ^~~
/home/ubuntu/.cache/node-gyp/14.3.0/include/node/v8.h:3686:43: note:   candidate expects 2 arguments, 1 provided
/home/ubuntu/.cache/node-gyp/14.3.0/include/node/v8.h:3689:43: note: candidate: v8::MaybeLocal<v8::Value> v8::Object::Get(v8::Local<v8::Context>, uint32_t)
   V8_WARN_UNUSED_RESULT MaybeLocal<Value> Get(Local<Context> context,
                                           ^~~
/home/ubuntu/.cache/node-gyp/14.3.0/include/node/v8.h:3689:43: note:   candidate expects 2 arguments, 1 provided
In file included from /usr/include/c++/7/cassert:44:0,
                 from /home/ubuntu/.cache/node-gyp/14.3.0/include/node/node_object_wrap.h:26,
                 from ../../nan/nan.h:53,
                 from ../sleep.cc:2:
../../nan/nan_object_wrap.h: In destructor ‘virtual Nan::ObjectWrap::~ObjectWrap()’:
../../nan/nan_object_wrap.h:24:25: error: ‘class Nan::Persistent<v8::Object>’ has no member named ‘IsNearDeath’
     assert(persistent().IsNearDeath());
                         ^
In file included from ../../nan/nan.h:2657:0,
                 from ../sleep.cc:2:
../../nan/nan_object_wrap.h: In member function ‘void Nan::ObjectWrap::MakeWeak()’:
../../nan/nan_object_wrap.h:67:18: error: ‘class Nan::Persistent<v8::Object>’ has no member named ‘MarkIndependent’
     persistent().MarkIndependent();
                  ^~~~~~~~~~~~~~~
In file included from /usr/include/c++/7/cassert:44:0,
                 from /home/ubuntu/.cache/node-gyp/14.3.0/include/node/node_object_wrap.h:26,
                 from ../../nan/nan.h:53,
                 from ../sleep.cc:2:
../../nan/nan_object_wrap.h: In static member function ‘static void Nan::ObjectWrap::WeakCallback(const v8::WeakCallbackInfo<Nan::ObjectWrap>&)’:
../../nan/nan_object_wrap.h:124:26: error: ‘class Nan::Persistent<v8::Object>’ has no member named ‘IsNearDeath’
     assert(wrap->handle_.IsNearDeath());
                          ^
../sleep.cc: At global scope:
../sleep.cc:6:11: error: ‘v8::Handle’ has not been declared
 using v8::Handle;
           ^~~~~~
../sleep.cc: In function ‘Nan::NAN_METHOD_RETURN_TYPE Sleep(Nan::NAN_METHOD_ARGS_TYPE)’:
../sleep.cc:42:30: error: no matching function for call to ‘v8::Value::Uint32Value()’
   sleep(info[0]->Uint32Value());
                              ^
In file included from /home/ubuntu/.cache/node-gyp/14.3.0/include/node/node.h:67:0,
                 from ../../nan/nan.h:51,
                 from ../sleep.cc:2:
/home/ubuntu/.cache/node-gyp/14.3.0/include/node/v8.h:2863:41: note: candidate: v8::Maybe<unsigned int> v8::Value::Uint32Value(v8::Local<v8::Context>) const
   V8_WARN_UNUSED_RESULT Maybe<uint32_t> Uint32Value(
                                         ^~~~~~~~~~~
/home/ubuntu/.cache/node-gyp/14.3.0/include/node/v8.h:2863:41: note:   candidate expects 1 argument, 0 provided
../sleep.cc: In function ‘Nan::NAN_METHOD_RETURN_TYPE USleep(Nan::NAN_METHOD_ARGS_TYPE)’:
../sleep.cc:54:31: error: no matching function for call to ‘v8::Value::Uint32Value()’
   usleep(info[0]->Uint32Value());
                               ^
In file included from /home/ubuntu/.cache/node-gyp/14.3.0/include/node/node.h:67:0,
                 from ../../nan/nan.h:51,
                 from ../sleep.cc:2:
/home/ubuntu/.cache/node-gyp/14.3.0/include/node/v8.h:2863:41: note: candidate: v8::Maybe<unsigned int> v8::Value::Uint32Value(v8::Local<v8::Context>) const
   V8_WARN_UNUSED_RESULT Maybe<uint32_t> Uint32Value(
                                         ^~~~~~~~~~~
/home/ubuntu/.cache/node-gyp/14.3.0/include/node/v8.h:2863:41: note:   candidate expects 1 argument, 0 provided
../sleep.cc: In function ‘void init(Nan::ADDON_REGISTER_FUNCTION_ARGS_TYPE)’:
../sleep.cc:62:52: error: no matching function for call to ‘v8::FunctionTemplate::GetFunction()’
     Nan::New<FunctionTemplate>(Sleep)->GetFunction());
                                                    ^
In file included from /home/ubuntu/.cache/node-gyp/14.3.0/include/node/node.h:67:0,
                 from ../../nan/nan.h:51,
                 from ../sleep.cc:2:
/home/ubuntu/.cache/node-gyp/14.3.0/include/node/v8.h:6404:46: note: candidate: v8::MaybeLocal<v8::Function> v8::FunctionTemplate::GetFunction(v8::Local<v8::Context>)
   V8_WARN_UNUSED_RESULT MaybeLocal<Function> GetFunction(
                                              ^~~~~~~~~~~
/home/ubuntu/.cache/node-gyp/14.3.0/include/node/v8.h:6404:46: note:   candidate expects 1 argument, 0 provided
../sleep.cc:64:53: error: no matching function for call to ‘v8::FunctionTemplate::GetFunction()’
     Nan::New<FunctionTemplate>(USleep)->GetFunction());
                                                     ^
In file included from /home/ubuntu/.cache/node-gyp/14.3.0/include/node/node.h:67:0,
                 from ../../nan/nan.h:51,
                 from ../sleep.cc:2:
/home/ubuntu/.cache/node-gyp/14.3.0/include/node/v8.h:6404:46: note: candidate: v8::MaybeLocal<v8::Function> v8::FunctionTemplate::GetFunction(v8::Local<v8::Context>)
   V8_WARN_UNUSED_RESULT MaybeLocal<Function> GetFunction(
                                              ^~~~~~~~~~~
/home/ubuntu/.cache/node-gyp/14.3.0/include/node/v8.h:6404:46: note:   candidate expects 1 argument, 0 provided
node_sleep.target.mk:111: recipe for target 'Release/obj.target/node_sleep/sleep.o' failed
make: *** [Release/obj.target/node_sleep/sleep.o] Error 1
make: Leaving directory '/home/ubuntu/puppetcam/node_modules/sleep/build'
gyp ERR! build error 
gyp ERR! stack Error: `make` failed with exit code: 2
gyp ERR! stack     at ChildProcess.onExit (/usr/lib/node_modules/npm/node_modules/node-gyp/lib/build.js:194:23)
gyp ERR! stack     at ChildProcess.emit (events.js:315:20)
gyp ERR! stack     at Process.ChildProcess._handle.onexit (internal/child_process.js:276:12)
gyp ERR! System Linux 5.0.0-1014-oracle
gyp ERR! command "/usr/bin/node" "/usr/lib/node_modules/npm/node_modules/node-gyp/bin/node-gyp.js" "rebuild"
gyp ERR! cwd /home/ubuntu/puppetcam/node_modules/sleep
gyp ERR! node -v v14.3.0
gyp ERR! node-gyp -v v5.1.0
gyp ERR! not ok 

> [email protected] install /home/ubuntu/puppetcam/node_modules/puppeteer
> node install.js

Downloading Chromium r571375 - 101.4 Mb [====================] 100% 0.0s 
Chromium downloaded to /home/ubuntu/puppetcam/node_modules/puppeteer/.local-chromium/linux-571375
npm WARN [email protected] No repository field.
npm WARN [email protected] No license field.
npm WARN optional SKIPPING OPTIONAL DEPENDENCY: [email protected] (node_modules/sleep):
npm WARN optional SKIPPING OPTIONAL DEPENDENCY: [email protected] install: `node-gyp rebuild`
npm WARN optional SKIPPING OPTIONAL DEPENDENCY: Exit status 1

added 42 packages from 22 contributors and audited 44 packages in 60.713s
found 3 vulnerabilities (1 low, 2 high)
  run `npm audit fix` to fix them, or `npm audit` for details
``

Background script seems to "freeze" after getUserMedia

Hey there,

first of all thanks a lot for sharing this!
I could make it work running inside a Docker container running locally on Docker for Windows so yay! :)

I'm now running the same Docker image in a Azure Function App and for some reason the scripts hangs waiting for the download completed signal.

To be more specific:

The last signal I am getting from the background script is the first line of the navigator.webkitGetUserMedia callback (I'm using postMessage to report back to the content script where I can then capture the console.logs via puppeteer).

After that I'm not getting any messages not even from a setInterval(()=> port.postMessage(...), 1000) I'm runningin the background script

I'm digging more into this adding as much logging as possible and using all the debugging tools available so I hope to report back soon.

But maybe you have an idea what could be going on?

Thanks

Wanted to thank you. It works great. Good job.

Exported video are different to original video dimension

I try to record a video with custom dimension 720x1280 or 1280x720.
I expected the output to have the same dimension with the original video but the recorded video add some black space on top and bottom and (width, height) are different then original dimension.

Tell us about your environment:
Puppeteer version: 2.1.1
Platform: docker ubuntu

Landscape example:
Dimension used in export.js and background.js (width: 1280, height: 720)
Original video:

Exported record video:

If you see the last frame from exported video are the real size from original video size.

Portrait example
Dimension used in export.js and background.js (width: 720, height: 1280)
Original video:

Exported record video:

Used code for bunny example, in second example just dimension are different.
export.js

const express = require ('express');
const puppeteer = require('puppeteer');
const Xvfb      = require('xvfb');
var xvfb        = new Xvfb({silent: false});

const chromeExtensionPath = `${__dirname}/chrome-extension`;

// export DISPLAY=:99 && /etc/init.d/xvfb start

async function main() {
    const PARTIAL_CONTENT_STATUS = 206;

    return new Promise(async (resolve, reject) => {
        try {
            let browser = await puppeteer.launch({
                headless: false,
                devtools: false,
                executablePath: '/usr/bin/google-chrome',
                ignoreDefaultArgs: ['--mute-audio'],
                defaultViewport: {
                    width: 1280,
                    height: 720,
                },
                args: [
                    '--enable-usermedia-screen-capturing',
                    '--allow-http-screen-capture',
                    '--auto-select-desktop-capture-source=puppetcam',
                    '--load-extension=' + chromeExtensionPath,
                    '--disable-extensions-except=' + chromeExtensionPath,
                    '--disable-infobars',
                    '--no-sandbox',
                    '--hide-scrollbars',
                    '--window-size=1280,720',
                ],
            });

            const page = await browser.newPage();

            page.setViewport({
                width: 1280,
                height: 720,
            })

            page.on('console', msg => console.log('PAGE LOG:', msg.text()));
            page.on('error', (e) => {
                reject(e);
                browser.close().catch(console.error);
            });
            page.on('requestfailed', (req) => {
                const response = req.response();
                if (response && response.status() === PARTIAL_CONTENT_STATUS) {
                    console.log([
                        'Ignoring failed request for ',
                        req.url(),
                        ' with status 206 Partial Content. Range: ',
                        response && response.headers()['content-range'],
                    ].join(''));
                } else {
                    console.error('requestfailed', req.url(), req.failure().errorText);
                }
            });

            const response = await page.goto('http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4', {waitUntil: 'networkidle2'})

            await page.waitFor(17000)
            xvfb.startSync()
            await page.evaluate(filename=>{
                window.postMessage({type: 'SET_EXPORT_PATH', filename: filename}, '*')
                window.postMessage({type: 'REC_STOP'}, '*')
            }, 'bunny.webm')

            // Wait for download of webm to complete
            await page.waitForSelector('html.downloadComplete', {timeout: 0})
            xvfb.stopSync()
            if (response.status() < 200 || response.status() >= 400) {
                throw new Error(`Preview server response error: ${await response.text()}`);
            }

            browser.close().catch(console.error);
        } catch (e) {
            console.error(e);

            throw e;
        }
    });
}

    const app = express();

    // cors
    app.use((req, res, next) => {
        res.header('Access-Control-Allow-Origin', '*');
        res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept');
        next();
    });

    // healthCheck @TODO: make it better
    app.get('/health', (req, res) => {
        res.status(200).send({ code: 200, body: 'OK' });
    });

    app.listen(4000, () => {
        console.log(`Started to listen on port 4000`);
    });


    app.get('/export', () => {
        console.log('here');
        main()
    });

background.js:

/* global chrome, MediaRecorder, FileReader */

console.log(`Background script loading`);
let recorder = null;
let filename = null;
chrome.runtime.onConnect.addListener(port => {
  port.onMessage.addListener(msg => {
    console.log(msg);
    switch (msg.type) {
      case 'SET_EXPORT_PATH':
        console.log(`set export path`);
        filename = msg.filename;
        break;
      case 'REC_STOP':
        console.log(`REC STOP`);
        recorder.stop();
        break;
      case 'REC_CLIENT_PLAY':
        if (recorder) {
          return;
        }
        const tab = port.sender.tab;
        tab.url = msg.data.url;
        const size = {width: 1280, height: 720};
        chrome.desktopCapture.chooseDesktopMedia(['tab', 'audio'], streamId => {
          // Get the stream
          navigator.webkitGetUserMedia(
            {
            audio: {
                mandatory: {
                    chromeMediaSource: 'system'
                }
            },
              video: {
                mandatory: {
                  chromeMediaSource: 'desktop',
                  chromeMediaSourceId: streamId,
                  minWidth: size.width,
                  maxWidth: size.width,
                  minHeight: size.height,
                  maxHeight: size.height,
                  minFrameRate: 60,
                },
              },
            },
            stream => {
              var chunks = [];
              recorder = new MediaRecorder(stream, {
                videoBitsPerSecond: 2500000,
                ignoreMutedMedia: true,
                mimeType: 'video/webm',
              });
              recorder.ondataavailable = function(event) {
                if (event.data.size > 0) {
                  chunks.push(event.data);
                }
              };

              recorder.onstop = function() {
                var superBuffer = new Blob(chunks, {
                  type: 'video/webm',
                });

                var url = URL.createObjectURL(superBuffer);
                // var a = document.createElement('a');
                // document.body.appendChild(a);
                // a.style = 'display: none';
                // a.href = url;
                // a.download = 'test.webm';
                // a.click();

                chrome.downloads.download(
                  {
                    url: url,
                    filename: filename,
                  },
                  () => {
                    console.log(arguments);
                  },
                );
              };

              recorder.start();
            },
            error => console.log('Unable to get user media', error),
          );
        });
        break;
      default:
        console.log('Unrecognized message', msg);
    }
  });

  chrome.downloads.onChanged.addListener(function(delta) {
    if (!delta.state || delta.state.current != 'complete') {
      return;
    }
    try {
      port.postMessage({downloadComplete: true});
    } catch (e) {}
  });
});

Dockerfile:

FROM ubuntu

ARG DEBIAN_FRONTEND=noninteractive

ENV TZ=Europe/Bucharest
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone

RUN apt-get update
RUN apt-get  install -y curl xvfb x11vnc x11-xkb-utils xfonts-100dpi xfonts-75dpi xfonts-scalable xfonts-cyrillic x11-apps nodejs npm tightvncserver xfce4 xfce4-goodies apt-utils gconf-service libasound2 libatk1.0-0 libc6 libcairo2 libcups2 libdbus-1-3 libexpat1 libfontconfig1 libgcc1 libgconf-2-4 libgdk-pixbuf2.0-0 libglib2.0-0 libgtk-3-0 libnspr4 libpango-1.0-0 libpangocairo-1.0-0 libstdc++6 libx11-6 libx11-xcb1 libxcb1 libxcomposite1 libxcursor1 libxdamage1 libxext6 libxfixes3 libxi6 libxrandr2 libxrender1 libxss1 libxtst6 ca-certificates fonts-liberation libappindicator1 libnss3 lsb-release xdg-utils wget x11vnc x11-xkb-utils xfonts-100dpi xfonts-75dpi xfonts-scalable xfonts-cyrillic x11-apps xvfb apt-utils openvpn fping nano libgtk-3.0 libatk-bridge2.0-0


RUN wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add - \
    && sh -c 'echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google.list' \
    && apt-get update \
    && apt-get install -y google-chrome-stable fonts-ipafont-gothic fonts-wqy-zenhei fonts-thai-tlwg fonts-kacst fonts-freefont-ttf \
      --no-install-recommends \
    && rm -rf /var/lib/apt/lists/* \
    && apt-get purge --auto-remove -y curl \
    && rm -rf /src/*.deb

## Sound options for RHEL7 OpenClient
RUN snd_opts="--device /dev/snd \
	-e PULSE_SERVER=unix:${XDG_RUNTIME_DIR}/pulse/native \
	-v ${XDG_RUNTIME_DIR}/pulse/native:${XDG_RUNTIME_DIR}/pulse/native \
	--group-add $(getent group audio | cut -d: -f3)"

RUN groupmod -g 92 audio

ADD xvfb_init /etc/init.d/xvfb
RUN chmod a+x /etc/init.d/xvfb
ADD xvfb_daemon_run /usr/bin/xvfb-daemon-run
RUN chmod a+x /usr/bin/xvfb-daemon-run

WORKDIR /cac
ADD package.json .
RUN npm install

COPY ./app ./app

ENV DISPLAY :99

can I record video of more than one tabs at the same time?

First of all amazing work!
I wanted to convert your code to an API. So users will provide me a URL and time and I will record that web page for specified time and will give the video to users. I just want to confirm that multiple requests will work fine? it would be creating new tabs and record video separately every time a user sends a request?
I know its a silly question but just wanted to confirm. :p

Regards,
Junaid

Black strips on top and bottom in video, while using "executablePath" of newest version of chrome (84).

Trying to get 4k output, just added executablePath but in the output video it's showing the black border at the top and bottom of the video, check this screenshot below.
out
Using Node v12.18.0, and [email protected]
also I noticed this bar not sure if this is the problem
Untitled

Here's the modified code, else everything is same.

const puppeteer = require("puppeteer-core");
let width = 3840;
let height = 2160;
let options = {
    headless: false,
    executablePath:
        "C:\\Program Files (x86)\\Google\\Chrome\\Application\\Chrome.exe",
    args: [
        "--enable-usermedia-screen-capturing",
        "--allow-http-screen-capture",
        "--auto-select-desktop-capture-source=puppetcam",
        "--load-extension=" + __dirname,
        "--disable-extensions-except=" + __dirname,
        "--disable-infobars",
        `--window-size=${width},${height}`,
    ],
};

trouble running on WSL

chromium won't run on WSL & Xvfb won't run on Windows; why is headless: false? wouldnt running on a server require headless: true?

background.js - unable to see console.log output

Hi,

I am unable to see console.log output which are specified in background.js.

For example, I should be able to see output for line #8 console.log(msg);. However, I don't see that when I execute the script using node export.js some_url some_file.webm.

Any idea?

unable to run code

error log on MacOS

/Users/username/NodeJS/puppetcam/node_modules/xvfb/index.js:84
        throw new Error('Could not start Xvfb.');
                ^

Error: Could not start Xvfb.
    at Xvfb.startSync (/Users/username/NodeJS/puppetcam/node_modules/xvfb/index.js:84:17)
    at main (/Users/username/NodeJS/puppetcam/export.js:20:10)
    at Object.<anonymous> (/Users/username/NodeJS/puppetcam/export.js:45:1)
    at Module._compile (node:internal/modules/cjs/loader:1095:14)
    at Object.Module._extensions..js (node:internal/modules/cjs/loader:1147:10)
    at Module.load (node:internal/modules/cjs/loader:975:32)
    at Function.Module._load (node:internal/modules/cjs/loader:822:12)
    at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:81:12)
    at node:internal/main/run_main_module:17:47

Changing download folder?

I'm currently working through the docs to try and find this as well, but have you seen any way to change the download location? Ideally I'd like to just download the video into a subdirectory relative to where the script is being run from, ie -

/puppetcam
    export.js
    ... etc
    /videos
        spinner.webm <- can we get it here?

Currently it gets dropped into the ~/Downloads/spinner.webm folder on my virtual machine, then I have to manually move to a linked folder to access (using Vagrant and Virtualbox with a *nix image)

I tried with a relative path as a filename, but no luck, still dropped it in ~/Downloads

Thanks

Thanks for the great work.
It's really help me a lot, thanks.

Working with navigation and other steps?

Thanks for putting this working example together, it works great as-is 👍 I just had a question while trying to add more steps to be recorded - it seems like the saved video will only have a few (if even more than one) frame saved.

Here's your main() function with my few extra lines for testing

async function main() {
	xvfb.startSync()
	var url = process.argv[2], exportname = process.argv[3]
	if(!url){ url = 'http://tobiasahlin.com/spinkit/' }
	if(!exportname){ exportname = 'spinner.webm' }
	const browser = await puppeteer.launch(options)
	const pages = await browser.pages()
	const page = pages[0]
	await page._client.send('Emulation.clearDeviceMetricsOverride')
	await page.goto(url, {waitUntil: 'networkidle2'})
	await page.setBypassCSP(true)

	// Perform any actions that have to be captured in the exported video
	await page.waitFor(8000)

	// Added steps
	await page.goto('https://google.com', {waitUntil: 'networkidle2'})
    	await page.type('#tsf [name="q"]', 'sample search query');
    	let navigationPromise = page.waitForNavigation();
    	await page.click('#tsf [type=submit]');
    	await navigationPromise;
    	await page.waitFor(5000)
	// -----

	await page.evaluate(filename=>{
		window.postMessage({type: 'SET_EXPORT_PATH', filename: filename}, '*')
		window.postMessage({type: 'REC_STOP'}, '*')
	}, exportname)

	// Wait for download of webm to complete
	await page.waitForSelector('html.downloadComplete', {timeout: 30000})
	await browser.close()
	xvfb.stopSync()
}

And here is the single frame spinner.webm that was saved
image

Any idea what's going on?

Mp4 video format

Does this support mp4 video files? I've changed the MIME type to video/mp4 along with the Blob type but it seems like the video never finishes downloading. Does MediaRecorder not support this MIME type?

What about audio ?

Hello, How can I have a perfect recording of what happens in the tab including audio?

Support h.264

Changing the mimeType:

recorder = new MediaRecorder(stream, {
  videoBitsPerSecond: 2500000,
  ignoreMutedMedia: true,
  mimeType: 'video/webm;codecs=h264'
});

Still produces a VP8 encoded file. How do you recommend we support h.264?

run example fail

OS: ubuntu 14.04
Node: v8.11.3

fail log:

(node:10399) UnhandledPromiseRejectionWarning: Error: Could not start Xvfb.
    at Xvfb.startSync (/home/test/puppetcam/node_modules/xvfb/index.js:84:17)
    at main (/home/test/puppetcam/export.js:20:10)
    at Object.<anonymous> (/home/test/puppetcam/export.js:45:1)
    at Module._compile (module.js:652:30)
    at Object.Module._extensions..js (module.js:663:10)
    at Module.load (module.js:565:32)
    at tryModuleLoad (module.js:505:12)
    at Function.Module._load (module.js:497:3)
    at Function.Module.runMain (module.js:693:10)
    at startup (bootstrap_node.js:191:16)
(node:10399) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). (rejection id: 1)
(node:10399) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.

(node:1425) UnhandledPromiseRejectionWarning: Error: Could not start Xvfb.

Hey! I just tried cloning your repo to try and run some basic Puppeteer commands to output as a video and after running npm install I'm getting an error on node export.js http://localhost:9000 spinner.webm

$ node export.js http://localhost:9000 spinner.webm
(node:1425) UnhandledPromiseRejectionWarning: Error: Could not start Xvfb.
    at Xvfb.startSync ({home directory}/puppetcam/node_modules/xvfb/index.js:84:17)
    ... rest of stack trace
(node:1425) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). (rejection id: 1)
(node:1425) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.

Running on Mac OSX 10.13.4, Node 10.5.0, NPM 6.1.0

Any idea what this could be?

should audio recording work?

Thanks for putting this together.

I'm trying to record video w/ audio. Out of the box this doesn't seem to work (on Ubuntu)--is it something you've looked into?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.