From 061332d2f7611cc771cb8ac6f0c44fcdc6152d89 Mon Sep 17 00:00:00 2001 From: Anton Date: Mon, 30 Jan 2017 13:11:27 +0000 Subject: [PATCH 1/6] set cropRect and captureMouseClicks --- castro.js | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/castro.js b/castro.js index 9ddf170..adb05d5 100644 --- a/castro.js +++ b/castro.js @@ -11,7 +11,11 @@ var $ = require('nodobjc'); $.framework('AVFoundation'); $.framework('Foundation'); -var Castro = function(){ +function checkRect(rect) { + return typeof rect === 'object' && 'x' in rect && 'y' in rect && 'w' in rect && 'h' in rect; +} + +var Castro = function(rect, captureMouseClicks){ this._started = false; this._used = false; this.pool = $.NSAutoreleasePool('alloc')('init'); @@ -20,7 +24,18 @@ var Castro = function(){ // Set the main display as capture input this.displayId = $.CGMainDisplayID(); + const cropRect = checkRect(rect) + ? $.CGRectMake(rect.x, rect.y, rect.w, rect.h) + : null; this.input = $.AVCaptureScreenInput('alloc')('initWithDisplayID', this.displayId); + + if (cropRect) { + this.input('setCropRect', cropRect); + } + if (captureMouseClicks) { + this.input('setCapturesMouseClicks', true); + } + if (this.session('canAddInput', this.input)) { this.session('addInput', this.input); } From 779f1e820eb662aa338d632a3699740a0164104e Mon Sep 17 00:00:00 2001 From: Anton Date: Mon, 30 Jan 2017 16:34:27 +0000 Subject: [PATCH 2/6] add scale factor --- castro.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/castro.js b/castro.js index adb05d5..6bfbeec 100644 --- a/castro.js +++ b/castro.js @@ -15,7 +15,7 @@ function checkRect(rect) { return typeof rect === 'object' && 'x' in rect && 'y' in rect && 'w' in rect && 'h' in rect; } -var Castro = function(rect, captureMouseClicks){ +var Castro = function(rect, captureMouseClicks, scaleFactor){ this._started = false; this._used = false; this.pool = $.NSAutoreleasePool('alloc')('init'); @@ -35,6 +35,9 @@ var Castro = function(rect, captureMouseClicks){ if (captureMouseClicks) { this.input('setCapturesMouseClicks', true); } + if (scaleFactor) { + this.input('setScaleFactor', scaleFactor); + } if (this.session('canAddInput', this.input)) { this.session('addInput', this.input); From 875ea06f66e226f37ea5af6f5727e91b8ab10c39 Mon Sep 17 00:00:00 2001 From: Anton Date: Mon, 30 Jan 2017 16:34:56 +0000 Subject: [PATCH 3/6] 0.0.4 --- package.json | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index 784d983..6358f6e 100644 --- a/package.json +++ b/package.json @@ -1,9 +1,14 @@ { "name": "castro", - "version": "0.0.3", + "version": "0.0.4", "description": "Automated screen recording", "author": "Jason Huggins ", - "keywords": ["video", "recording", "screencast", "osx"], + "keywords": [ + "video", + "recording", + "screencast", + "osx" + ], "homepage": "https://github.com/hugs/node-castro", "repository": { "type": "git", @@ -11,13 +16,13 @@ }, "main": "./castro.js", "engines": { - "node": "*" + "node": "*" }, "bugs": { - "url" : "https://github.com/hugs/node-castro/issues" + "url": "https://github.com/hugs/node-castro/issues" }, - "license": "MIT", + "license": "MIT", "dependencies": { - "nodobjc": "*" + "nodobjc": "*" } } From 8c067aea02b175a17725e09d8f73a1472e4a2526 Mon Sep 17 00:00:00 2001 From: Anton Date: Tue, 31 Jan 2017 18:54:10 +0000 Subject: [PATCH 4/6] emit events --- castro.js | 166 +++++++++++++++++++++++++++++++----------------------- 1 file changed, 97 insertions(+), 69 deletions(-) diff --git a/castro.js b/castro.js index 6bfbeec..40765f3 100644 --- a/castro.js +++ b/castro.js @@ -7,7 +7,8 @@ // Handle pause/restart // Capture frame/screenshots: (https://developer.apple.com/library/ios/qa/qa1702/_index.html) -var $ = require('nodobjc'); +const EventEmmiter = require('events'); +const $ = require('nodobjc'); $.framework('AVFoundation'); $.framework('Foundation'); @@ -15,56 +16,84 @@ function checkRect(rect) { return typeof rect === 'object' && 'x' in rect && 'y' in rect && 'w' in rect && 'h' in rect; } -var Castro = function(rect, captureMouseClicks, scaleFactor){ - this._started = false; - this._used = false; - this.pool = $.NSAutoreleasePool('alloc')('init'); +class Castro extends EventEmmiter { + constructor(rect, captureMouseClicks, scaleFactor) { + super(); + this._started = false; + this._used = false; + this.pool = $.NSAutoreleasePool('alloc')('init'); - this.session = $.AVCaptureSession('alloc')('init'); + this.session = $.AVCaptureSession('alloc')('init'); - // Set the main display as capture input - this.displayId = $.CGMainDisplayID(); - const cropRect = checkRect(rect) - ? $.CGRectMake(rect.x, rect.y, rect.w, rect.h) - : null; - this.input = $.AVCaptureScreenInput('alloc')('initWithDisplayID', this.displayId); + // Set the main display as capture input + this.displayId = $.CGMainDisplayID(); - if (cropRect) { - this.input('setCropRect', cropRect); - } - if (captureMouseClicks) { - this.input('setCapturesMouseClicks', true); - } - if (scaleFactor) { - this.input('setScaleFactor', scaleFactor); - } + const cropRect = checkRect(rect) + ? $.CGRectMake(rect.x, rect.y, rect.w, rect.h) + : null; - if (this.session('canAddInput', this.input)) { - this.session('addInput', this.input); - } + this.input = $.AVCaptureScreenInput('alloc')('initWithDisplayID', this.displayId); + this.input('setCapturesMouseClicks', !!captureMouseClicks); + if (cropRect) { + this.input('setCropRect', cropRect); + } + if (scaleFactor) { + this.input('setScaleFactor', scaleFactor); + } - // Set a movie file as output - this.movieFileOutput = $.AVCaptureMovieFileOutput('alloc')('init'); - if (this.session('canAddOutput', this.movieFileOutput)) { - this.session('addOutput', this.movieFileOutput); - } + if (this.session('canAddInput', this.input)) { + this.session('addInput', this.input); + } - this.session('startRunning'); - this.setLocation(); -} + // Set a movie file as output + this.movieFileOutput = $.AVCaptureMovieFileOutput('alloc')('init'); + + if (this.session('canAddOutput', this.movieFileOutput)) { + this.session('addOutput', this.movieFileOutput); + } -Castro.prototype = { + // sampling code + + // const FileOutputDelegate = $.NSObject.extend('AVCaptureFileOutputDelegate'); + // FileOutputDelegate.addMethod('captureOutputShouldProvideSampleAccurateRecordingStart:', 'B@:@', (self, _cmd, captureOutput) => { + // console.log('captureOutputShouldProvideSampleAccurateRecordingStart', captureOutput) + // return $.YES + // }); + // FileOutputDelegate.addMethod('captureOutput:didOutputSampleBuffer:fromConnection:', 'v@:@@@', (self, _cmd, captureOutput, didSampleBuffer, fromConnection) => { + // // console.log('captureOutput:didOutputSampleBuffer:fromConnection:', captureOutput, didSampleBuffer, fromConnection) + // }); + // FileOutputDelegate.register() + // const fileOutputDelegate = FileOutputDelegate('alloc')('init') + // this.movieFileOutput('setDelegate', fileOutputDelegate) + + const Delegate = $.NSObject.extend('AVCaptureFileOutputRecordingDelegate'); + Delegate.addMethod('captureOutput:didFinishRecordingToOutputFileAtURL:fromConnections:error:', 'v@:@@@', () => { + // didFinishRecordingToOutputFileAtURL never fires + // we have to listen for willFinishRecordingToOutputFileAtURL and wait for a few s to avoid corruption of file + }); + Delegate.addMethod('captureOutput:didStartRecordingToOutputFileAtURL:fromConnections:', 'v@:@@@', () => { + this.emit('didStartRecording'); + }); + Delegate.addMethod('captureOutput:willFinishRecordingToOutputFileAtURL:fromConnections:error:', 'v@:@@@@', () => { + this.emit('willFinishRecording'); + }); + Delegate.register(); + this.delegate = Delegate('alloc')('init'); + + this.session('startRunning'); + this.setLocation(); + } // Set recording file location - setLocation: function(path) { + setLocation(path) { // TODO: Does file exist at the file location path? If so, do something about it... //var defaultManager = $.NSFileManager('alloc')('init') //if (defaultManager('fileExistsAtPath',NSlocation)) { - // console.log("File already exists!") + // console.log('File already exists!') //} if (!path){ - // Default Destination: e.g. "/Users/hugs/Desktop/Castro_uul3di.mov" + // Default Destination: e.g. '/Users/hugs/Desktop/Castro_uul3di.mov' var homeDir = $.NSHomeDirectory(); var desktopDir = homeDir.toString() + '/Desktop/'; var randomString = (Math.random() + 1).toString(36).substring(12); @@ -76,55 +105,54 @@ Castro.prototype = { } this.NSlocation = $.NSString('stringWithUTF8String', this.location); this.NSlocationURL = $.NSURL('fileURLWithPath', this.NSlocation); - }, + } // Start recording - start: function() { - if (!this._started) { - if (!this._used) { - this.movieFileOutput('startRecordingToOutputFileURL', this.NSlocationURL, - 'recordingDelegate', this.movieFileOutput); - this._started = true; - } else { - throw new Error("Recording has completed. To make a new recording, create a new Castro object."); - } - } else { - throw new Error("A recording is already in progress."); + start() { + if (this._started) { + throw new Error('A recording is already in progress.'); + } + if (this._used) { + throw new Error('Recording has completed. To make a new recording, create a new Castro object.'); } - }, + this.movieFileOutput( + 'startRecordingToOutputFileURL', this.NSlocationURL, + 'recordingDelegate', this.delegate + ); + this._started = true; + } // Stop recording - stop: function() { - if (!this._used) { - if (this._started) { - this.movieFileOutput('stopRecording'); - this.pool('drain'); - this._started = false; - this._used = true; - return this.location; - } else { - throw new Error("Try starting it first!"); - } - } else { - throw new Error("Recording has completed. To make a new recording, create a new Castro object."); + stop() { + if (this._used) { + throw new Error('Recording has completed. To make a new recording, create a new Castro object.'); + } + if (!this._started) { + throw new Error('Try starting it first!'); } - }, + this.movieFileOutput('stopRecording'); + this.session('stopRunning'); + this.pool('drain'); + this._started = false; + this._used = true; + return this.location; + } - test: function() { - console.log("Castro will record the main display for 10 seconds..."); + test() { + console.log('Castro will record the main display for 10 seconds...'); - console.log("Now starting..."); + console.log('Now starting...'); this.start(); - setTimeout(function(_this){ - console.log("Now stopping..."); + console.log('Now stopping...'); _this.stop(); - console.log("File location:"); + console.log('File location:'); console.log(_this.location); }, 10*1000, this); } } +module.exports = Castro; module.exports.Castro = Castro; From 0a3ba95336ffa2ac0ffb04425fb63f0cafcedd51 Mon Sep 17 00:00:00 2001 From: Anton Date: Fri, 3 Feb 2017 17:15:28 +0000 Subject: [PATCH 5/6] castro session --- castro.js | 123 +++++++++++------------------------------------------- 1 file changed, 24 insertions(+), 99 deletions(-) diff --git a/castro.js b/castro.js index 40765f3..88e9543 100644 --- a/castro.js +++ b/castro.js @@ -16,61 +16,34 @@ function checkRect(rect) { return typeof rect === 'object' && 'x' in rect && 'y' in rect && 'w' in rect && 'h' in rect; } +function getNSURL(path) { + const NSlocation = $.NSString('stringWithUTF8String', path); + return $.NSURL('fileURLWithPath', NSlocation); +} + class Castro extends EventEmmiter { constructor(rect, captureMouseClicks, scaleFactor) { super(); this._started = false; - this._used = false; - this.pool = $.NSAutoreleasePool('alloc')('init'); this.session = $.AVCaptureSession('alloc')('init'); // Set the main display as capture input this.displayId = $.CGMainDisplayID(); - const cropRect = checkRect(rect) - ? $.CGRectMake(rect.x, rect.y, rect.w, rect.h) - : null; + const cropRect = checkRect(rect) ? $.CGRectMake(rect.x, rect.y, rect.w, rect.h) : null; this.input = $.AVCaptureScreenInput('alloc')('initWithDisplayID', this.displayId); - this.input('setCapturesMouseClicks', !!captureMouseClicks); - if (cropRect) { - this.input('setCropRect', cropRect); - } - if (scaleFactor) { - this.input('setScaleFactor', scaleFactor); - } - - if (this.session('canAddInput', this.input)) { - this.session('addInput', this.input); - } - - // Set a movie file as output - this.movieFileOutput = $.AVCaptureMovieFileOutput('alloc')('init'); - - if (this.session('canAddOutput', this.movieFileOutput)) { - this.session('addOutput', this.movieFileOutput); - } - - // sampling code - - // const FileOutputDelegate = $.NSObject.extend('AVCaptureFileOutputDelegate'); - // FileOutputDelegate.addMethod('captureOutputShouldProvideSampleAccurateRecordingStart:', 'B@:@', (self, _cmd, captureOutput) => { - // console.log('captureOutputShouldProvideSampleAccurateRecordingStart', captureOutput) - // return $.YES - // }); - // FileOutputDelegate.addMethod('captureOutput:didOutputSampleBuffer:fromConnection:', 'v@:@@@', (self, _cmd, captureOutput, didSampleBuffer, fromConnection) => { - // // console.log('captureOutput:didOutputSampleBuffer:fromConnection:', captureOutput, didSampleBuffer, fromConnection) - // }); - // FileOutputDelegate.register() - // const fileOutputDelegate = FileOutputDelegate('alloc')('init') - // this.movieFileOutput('setDelegate', fileOutputDelegate) + this.output = $.AVCaptureMovieFileOutput('alloc')('init'); + + if (captureMouseClicks) this.input('setCapturesMouseClicks', true); + if (cropRect) this.input('setCropRect', cropRect); + if (scaleFactor) this.input('setScaleFactor', scaleFactor); + + if (this.session('canAddInput', this.input)) this.session('addInput', this.input); + if (this.session('canAddOutput', this.output)) this.session('addOutput', this.output); const Delegate = $.NSObject.extend('AVCaptureFileOutputRecordingDelegate'); - Delegate.addMethod('captureOutput:didFinishRecordingToOutputFileAtURL:fromConnections:error:', 'v@:@@@', () => { - // didFinishRecordingToOutputFileAtURL never fires - // we have to listen for willFinishRecordingToOutputFileAtURL and wait for a few s to avoid corruption of file - }); Delegate.addMethod('captureOutput:didStartRecordingToOutputFileAtURL:fromConnections:', 'v@:@@@', () => { this.emit('didStartRecording'); }); @@ -81,76 +54,28 @@ class Castro extends EventEmmiter { this.delegate = Delegate('alloc')('init'); this.session('startRunning'); - this.setLocation(); } - // Set recording file location - setLocation(path) { - // TODO: Does file exist at the file location path? If so, do something about it... - //var defaultManager = $.NSFileManager('alloc')('init') - //if (defaultManager('fileExistsAtPath',NSlocation)) { - // console.log('File already exists!') - //} - - if (!path){ - // Default Destination: e.g. '/Users/hugs/Desktop/Castro_uul3di.mov' - var homeDir = $.NSHomeDirectory(); - var desktopDir = homeDir.toString() + '/Desktop/'; - var randomString = (Math.random() + 1).toString(36).substring(12); - var filename = 'Castro_' + randomString + '.mp4'; - this.location = desktopDir + filename; - } else { - // TODO: Make sure path is legit. - this.location = path; - } - this.NSlocation = $.NSString('stringWithUTF8String', this.location); - this.NSlocationURL = $.NSURL('fileURLWithPath', this.NSlocation); - } + start(videoLocation) { + this.pool = $.NSAutoreleasePool('alloc')('init'); - // Start recording - start() { - if (this._started) { - throw new Error('A recording is already in progress.'); - } - if (this._used) { - throw new Error('Recording has completed. To make a new recording, create a new Castro object.'); - } - this.movieFileOutput( - 'startRecordingToOutputFileURL', this.NSlocationURL, + if (this._started) throw new Error('A recording is already in progress.'); + + this.output( + 'startRecordingToOutputFileURL', getNSURL(videoLocation), 'recordingDelegate', this.delegate ); this._started = true; } - // Stop recording stop() { - if (this._used) { - throw new Error('Recording has completed. To make a new recording, create a new Castro object.'); - } - if (!this._started) { - throw new Error('Try starting it first!'); - } - this.movieFileOutput('stopRecording'); - this.session('stopRunning'); + if (!this._started) throw new Error('Video did not start recording.'); + + this.output('stopRecording'); this.pool('drain'); this._started = false; - this._used = true; - return this.location; - } - test() { - console.log('Castro will record the main display for 10 seconds...'); - - console.log('Now starting...'); - this.start(); - - setTimeout(function(_this){ - console.log('Now stopping...'); - _this.stop(); - - console.log('File location:'); - console.log(_this.location); - }, 10*1000, this); + return this.location; } } From 2f8e02aaeb669846f01d2f2537b71ed7e2025a76 Mon Sep 17 00:00:00 2001 From: Anton Date: Fri, 3 Feb 2017 18:12:01 +0000 Subject: [PATCH 6/6] pass options for video output in start method --- castro.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/castro.js b/castro.js index 88e9543..bbea79e 100644 --- a/castro.js +++ b/castro.js @@ -22,7 +22,7 @@ function getNSURL(path) { } class Castro extends EventEmmiter { - constructor(rect, captureMouseClicks, scaleFactor) { + constructor() { super(); this._started = false; @@ -31,15 +31,9 @@ class Castro extends EventEmmiter { // Set the main display as capture input this.displayId = $.CGMainDisplayID(); - const cropRect = checkRect(rect) ? $.CGRectMake(rect.x, rect.y, rect.w, rect.h) : null; - this.input = $.AVCaptureScreenInput('alloc')('initWithDisplayID', this.displayId); this.output = $.AVCaptureMovieFileOutput('alloc')('init'); - if (captureMouseClicks) this.input('setCapturesMouseClicks', true); - if (cropRect) this.input('setCropRect', cropRect); - if (scaleFactor) this.input('setScaleFactor', scaleFactor); - if (this.session('canAddInput', this.input)) this.session('addInput', this.input); if (this.session('canAddOutput', this.output)) this.session('addOutput', this.output); @@ -56,7 +50,13 @@ class Castro extends EventEmmiter { this.session('startRunning'); } - start(videoLocation) { + start(videoLocation, rect, captureMouseClicks, scaleFactor) { + const cropRect = checkRect(rect) ? $.CGRectMake(rect.x, rect.y, rect.w, rect.h) : null; + + if (captureMouseClicks) this.input('setCapturesMouseClicks', true); + if (cropRect) this.input('setCropRect', cropRect); + if (scaleFactor) this.input('setScaleFactor', scaleFactor); + this.pool = $.NSAutoreleasePool('alloc')('init'); if (this._started) throw new Error('A recording is already in progress.');