Comparing version
234
index.d.ts
declare namespace aperture { | ||
type Screen = { | ||
id: number; | ||
name: string; | ||
}; | ||
type Screen = { | ||
id: number; | ||
name: string; | ||
}; | ||
type AudioDevice = { | ||
id: string; | ||
name: string; | ||
}; | ||
type AudioDevice = { | ||
id: string; | ||
name: string; | ||
}; | ||
type VideoCodec = 'h264' | 'hevc' | 'proRes422' | 'proRes4444'; | ||
type VideoCodec = 'h264' | 'hevc' | 'proRes422' | 'proRes4444'; | ||
type RecordingOptions = { | ||
/** | ||
Number of frames per seconds. | ||
*/ | ||
readonly fps?: number; | ||
type RecordingOptions = { | ||
/** | ||
Number of frames per seconds. | ||
*/ | ||
readonly fps?: number; | ||
/** | ||
Record only an area of the screen. | ||
*/ | ||
readonly cropArea?: { | ||
x: number; | ||
y: number; | ||
width: number; | ||
height: number; | ||
}; | ||
/** | ||
Record only an area of the screen. | ||
*/ | ||
readonly cropArea?: { | ||
x: number; | ||
y: number; | ||
width: number; | ||
height: number; | ||
}; | ||
/** | ||
Show the cursor in the screen recording. | ||
*/ | ||
readonly showCursor?: boolean; | ||
/** | ||
Show the cursor in the screen recording. | ||
*/ | ||
readonly showCursor?: boolean; | ||
/** | ||
Highlight cursor clicks in the screen recording. | ||
/** | ||
Highlight cursor clicks in the screen recording. | ||
Enabling this will also enable the `showCursor` option. | ||
*/ | ||
readonly highlightClicks?: boolean; | ||
Enabling this will also enable the `showCursor` option. | ||
*/ | ||
readonly highlightClicks?: boolean; | ||
/** | ||
Screen to record. | ||
/** | ||
Screen to record. | ||
Defaults to primary screen. | ||
*/ | ||
readonly screenId?: number; | ||
Defaults to primary screen. | ||
*/ | ||
readonly screenId?: number; | ||
/** | ||
Audio device to include in the screen recording. | ||
/** | ||
Audio device to include in the screen recording. | ||
Should be one of the `id`'s from `aperture.audioDevices()`. | ||
*/ | ||
readonly audioDeviceId?: string; | ||
Should be one of the `id`'s from `aperture.audioDevices()`. | ||
*/ | ||
readonly audioDeviceId?: string; | ||
/** | ||
Video codec to use. | ||
/** | ||
Video codec to use. | ||
A computer with Intel 6th generation processor or newer is strongly recommended for the `hevc` codec, as otherwise it will use software encoding, which only produces 3 FPS fullscreen recording. | ||
A computer with Intel 6th generation processor or newer is strongly recommended for the `hevc` codec, as otherwise it will use software encoding, which only produces 3 FPS fullscreen recording. | ||
The `proRes422` and `proRes4444` codecs are uncompressed data. They will create huge files. | ||
*/ | ||
readonly videoCodec?: VideoCodec; | ||
}; | ||
The `proRes422` and `proRes4444` codecs are uncompressed data. They will create huge files. | ||
*/ | ||
readonly videoCodec?: VideoCodec; | ||
}; | ||
interface Recorder { | ||
/** | ||
Returns a `Promise` that fullfills when the recording starts or rejects if the recording didn't start after 5 seconds. | ||
*/ | ||
startRecording: (options?: RecordingOptions) => Promise<void>; | ||
interface Recorder { | ||
/** | ||
Returns a `Promise` that fullfills when the recording starts or rejects if the recording didn't start after 5 seconds. | ||
*/ | ||
startRecording: (options?: RecordingOptions) => Promise<void>; | ||
/** | ||
`Promise` that fullfills with the path to the screen recording file when it's ready. This will never reject. | ||
/** | ||
`Promise` that fullfills with the path to the screen recording file when it's ready. This will never reject. | ||
Only available while a recording is happening, `undefined` otherwise. | ||
Only available while a recording is happening, `undefined` otherwise. | ||
Usually, this resolves around 1 second before the recording starts, but that's not guaranteed. | ||
*/ | ||
isFileReady: Promise<string> | undefined; | ||
Usually, this resolves around 1 second before the recording starts, but that's not guaranteed. | ||
*/ | ||
isFileReady: Promise<string> | undefined; | ||
/** | ||
Pauses the recording. To resume, call `recorder.resume()`. | ||
/** | ||
Pauses the recording. To resume, call `recorder.resume()`. | ||
Returns a `Promise` that fullfills when the recording has been paused. | ||
*/ | ||
pause: () => Promise<void>; | ||
Returns a `Promise` that fullfills when the recording has been paused. | ||
*/ | ||
pause: () => Promise<void>; | ||
/** | ||
Resumes the recording if it's been paused. | ||
/** | ||
Resumes the recording if it's been paused. | ||
Returns a `Promise` that fullfills when the recording has been resumed. | ||
*/ | ||
resume: () => Promise<void>; | ||
Returns a `Promise` that fullfills when the recording has been resumed. | ||
*/ | ||
resume: () => Promise<void>; | ||
/** | ||
Returns a `Promise` that resolves with a boolean indicating whether or not the recording is currently paused. | ||
*/ | ||
isPaused: () => Promise<boolean>; | ||
/** | ||
Returns a `Promise` that resolves with a boolean indicating whether or not the recording is currently paused. | ||
*/ | ||
isPaused: () => Promise<boolean>; | ||
/** | ||
Returns a `Promise` for the path to the screen recording file. | ||
*/ | ||
stopRecording: () => Promise<string>; | ||
} | ||
/** | ||
Returns a `Promise` for the path to the screen recording file. | ||
*/ | ||
stopRecording: () => Promise<string>; | ||
} | ||
} | ||
declare const aperture: (() => aperture.Recorder) & { | ||
/** | ||
Get a list of available video codecs. | ||
/** | ||
Get a list of available video codecs. | ||
The key is the `videoCodec` option name and the value is the codec name. | ||
The key is the `videoCodec` option name and the value is the codec name. | ||
It only returns `hevc` if your computer supports HEVC hardware encoding. | ||
It only returns `hevc` if your computer supports HEVC hardware encoding. | ||
@example | ||
``` | ||
Map { | ||
'h264' => 'H264', | ||
'hevc' => 'HEVC', | ||
'proRes422' => 'Apple ProRes 422', | ||
'proRes4444' => 'Apple ProRes 4444' | ||
} | ||
``` | ||
*/ | ||
videoCodecs: Map<aperture.VideoCodec, string>; | ||
@example | ||
``` | ||
Map { | ||
'h264' => 'H264', | ||
'hevc' => 'HEVC', | ||
'proRes422' => 'Apple ProRes 422', | ||
'proRes4444' => 'Apple ProRes 4444' | ||
} | ||
``` | ||
*/ | ||
videoCodecs: Map<aperture.VideoCodec, string>; | ||
/** | ||
Get a list of screens. | ||
/** | ||
Get a list of screens. | ||
The first screen is the primary screen. | ||
The first screen is the primary screen. | ||
@example | ||
``` | ||
[{ | ||
id: 69732482, | ||
name: 'Color LCD' | ||
}] | ||
``` | ||
*/ | ||
screens: () => Promise<aperture.Screen[]>; | ||
@example | ||
``` | ||
[{ | ||
id: 69732482, | ||
name: 'Color LCD' | ||
}] | ||
``` | ||
*/ | ||
screens: () => Promise<aperture.Screen[]>; | ||
/** | ||
Get a list of audio devices. | ||
/** | ||
Get a list of audio devices. | ||
@example | ||
``` | ||
[{ | ||
id: 'AppleHDAEngineInput:1B,0,1,0:1', | ||
name: 'Built-in Microphone' | ||
}] | ||
``` | ||
*/ | ||
audioDevices: () => Promise<aperture.AudioDevice[]>; | ||
@example | ||
``` | ||
[{ | ||
id: 'AppleHDAEngineInput:1B,0,1,0:1', | ||
name: 'Built-in Microphone' | ||
}] | ||
``` | ||
*/ | ||
audioDevices: () => Promise<aperture.AudioDevice[]>; | ||
}; | ||
export = aperture; |
393
index.js
@@ -19,213 +19,212 @@ 'use strict'; | ||
const supportsHevcHardwareEncoding = (() => { | ||
const cpuModel = os.cpus()[0].model; | ||
const cpuModel = os.cpus()[0].model; | ||
// All Apple silicon Macs support HEVC hardware encoding. | ||
if (cpuModel.startsWith('Apple ')) { // Source string example: `'Apple M1'` | ||
return true; | ||
} | ||
// All Apple silicon Macs support HEVC hardware encoding. | ||
if (cpuModel.startsWith('Apple ')) { // Source string example: `'Apple M1'` | ||
return true; | ||
} | ||
// Get the Intel Core generation, the `4` in `Intel(R) Core(TM) i7-4850HQ CPU @ 2.30GHz` | ||
// More info: https://www.intel.com/content/www/us/en/processors/processor-numbers.html | ||
// Example strings: | ||
// - `Intel(R) Core(TM) i9-9980HK CPU @ 2.40GHz` | ||
// - `Intel(R) Core(TM) i7-4850HQ CPU @ 2.30GHz` | ||
const result = /Intel.*Core.*i\d+-(\d)/.exec(cpuModel); | ||
// Get the Intel Core generation, the `4` in `Intel(R) Core(TM) i7-4850HQ CPU @ 2.30GHz` | ||
// More info: https://www.intel.com/content/www/us/en/processors/processor-numbers.html | ||
// Example strings: | ||
// - `Intel(R) Core(TM) i9-9980HK CPU @ 2.40GHz` | ||
// - `Intel(R) Core(TM) i7-4850HQ CPU @ 2.30GHz` | ||
const result = /Intel.*Core.*i\d+-(\d)/.exec(cpuModel); | ||
// Intel Core generation 6 or higher supports HEVC hardware encoding | ||
return result && Number.parseInt(result[1], 10) >= 6; | ||
// Intel Core generation 6 or higher supports HEVC hardware encoding | ||
return result && Number.parseInt(result[1], 10) >= 6; | ||
})(); | ||
class Aperture { | ||
constructor() { | ||
macosVersion.assertGreaterThanOrEqualTo('10.13'); | ||
} | ||
constructor() { | ||
macosVersion.assertGreaterThanOrEqualTo('10.13'); | ||
} | ||
startRecording({ | ||
fps = 30, | ||
cropArea = undefined, | ||
showCursor = true, | ||
highlightClicks = false, | ||
screenId = 0, | ||
audioDeviceId = undefined, | ||
videoCodec = undefined | ||
} = {}) { | ||
this.processId = getRandomId(); | ||
startRecording({ | ||
fps = 30, | ||
cropArea = undefined, | ||
showCursor = true, | ||
highlightClicks = false, | ||
screenId = 0, | ||
audioDeviceId = undefined, | ||
videoCodec = 'h264' | ||
} = {}) { | ||
this.processId = getRandomId(); | ||
return new Promise((resolve, reject) => { | ||
if (this.recorder !== undefined) { | ||
reject(new Error('Call `.stopRecording()` first')); | ||
return; | ||
} | ||
return new Promise((resolve, reject) => { | ||
if (this.recorder !== undefined) { | ||
reject(new Error('Call `.stopRecording()` first')); | ||
return; | ||
} | ||
this.tmpPath = tempy.file({extension: 'mp4'}); | ||
this.tmpPath = tempy.file({extension: 'mp4'}); | ||
if (highlightClicks === true) { | ||
showCursor = true; | ||
} | ||
if (highlightClicks === true) { | ||
showCursor = true; | ||
} | ||
if (typeof cropArea === 'object' && | ||
( | ||
typeof cropArea.x !== 'number' || | ||
typeof cropArea.y !== 'number' || | ||
typeof cropArea.width !== 'number' || | ||
typeof cropArea.height !== 'number' | ||
) | ||
) { | ||
reject(new Error('Invalid `cropArea` option object')); | ||
return; | ||
} | ||
if (typeof cropArea === 'object' && | ||
( | ||
typeof cropArea.x !== 'number' || | ||
typeof cropArea.y !== 'number' || | ||
typeof cropArea.width !== 'number' || | ||
typeof cropArea.height !== 'number' | ||
) | ||
) { | ||
reject(new Error('Invalid `cropArea` option object')); | ||
return; | ||
} | ||
const recorderOptions = { | ||
destination: fileUrl(this.tmpPath), | ||
framesPerSecond: fps, | ||
showCursor, | ||
highlightClicks, | ||
screenId, | ||
audioDeviceId | ||
}; | ||
const recorderOptions = { | ||
destination: fileUrl(this.tmpPath), | ||
framesPerSecond: fps, | ||
showCursor, | ||
highlightClicks, | ||
screenId, | ||
audioDeviceId | ||
}; | ||
if (cropArea) { | ||
recorderOptions.cropRect = [ | ||
[cropArea.x, cropArea.y], | ||
[cropArea.width, cropArea.height] | ||
]; | ||
} | ||
if (cropArea) { | ||
recorderOptions.cropRect = [ | ||
[cropArea.x, cropArea.y], | ||
[cropArea.width, cropArea.height] | ||
]; | ||
} | ||
if (videoCodec) { | ||
const codecMap = new Map([ | ||
['h264', 'avc1'], | ||
['hevc', 'hvc1'], | ||
['proRes422', 'apcn'], | ||
['proRes4444', 'ap4h'] | ||
]); | ||
if (videoCodec) { | ||
const codecMap = new Map([ | ||
['h264', 'avc1'], | ||
['hevc', 'hvc1'], | ||
['proRes422', 'apcn'], | ||
['proRes4444', 'ap4h'] | ||
]); | ||
if (!supportsHevcHardwareEncoding) { | ||
codecMap.delete('hevc'); | ||
} | ||
if (!supportsHevcHardwareEncoding) { | ||
codecMap.delete('hevc'); | ||
} | ||
if (!codecMap.has(videoCodec)) { | ||
throw new Error(`Unsupported video codec specified: ${videoCodec}`); | ||
} | ||
if (!codecMap.has(videoCodec)) { | ||
throw new Error(`Unsupported video codec specified: ${videoCodec}`); | ||
} | ||
recorderOptions.videoCodec = codecMap.get(videoCodec); | ||
} | ||
recorderOptions.videoCodec = codecMap.get(videoCodec); | ||
} | ||
this.recorder = execa( | ||
BIN, [ | ||
'record', | ||
'--process-id', | ||
this.processId, | ||
JSON.stringify(recorderOptions) | ||
] | ||
); | ||
this.recorder = execa( | ||
BIN, [ | ||
'record', | ||
'--process-id', | ||
this.processId, | ||
JSON.stringify(recorderOptions) | ||
] | ||
); | ||
this.isFileReady = (async () => { | ||
await this.waitForEvent('onFileReady'); | ||
return this.tmpPath; | ||
})(); | ||
this.isFileReady = (async () => { | ||
await this.waitForEvent('onFileReady'); | ||
return this.tmpPath; | ||
})(); | ||
const timeout = setTimeout(() => { | ||
// `.stopRecording()` was called already | ||
if (this.recorder === undefined) { | ||
return; | ||
} | ||
const timeout = setTimeout(() => { | ||
// `.stopRecording()` was called already | ||
if (this.recorder === undefined) { | ||
return; | ||
} | ||
const error = new Error('Could not start recording within 5 seconds'); | ||
error.code = 'RECORDER_TIMEOUT'; | ||
this.recorder.kill(); | ||
delete this.recorder; | ||
reject(error); | ||
}, 5000); | ||
const error = new Error('Could not start recording within 5 seconds'); | ||
error.code = 'RECORDER_TIMEOUT'; | ||
this.recorder.kill(); | ||
delete this.recorder; | ||
reject(error); | ||
}, 5000); | ||
this.recorder.catch(error => { | ||
clearTimeout(timeout); | ||
delete this.recorder; | ||
reject(error); | ||
}); | ||
this.recorder.catch(error => { | ||
clearTimeout(timeout); | ||
delete this.recorder; | ||
reject(error); | ||
}); | ||
this.recorder.stdout.setEncoding('utf8'); | ||
this.recorder.stdout.on('data', log); | ||
this.recorder.stdout.setEncoding('utf8'); | ||
this.recorder.stdout.on('data', log); | ||
(async () => { | ||
try { | ||
await this.waitForEvent('onStart'); | ||
clearTimeout(timeout); | ||
setTimeout(resolve, 1000); | ||
} catch (error) { | ||
reject(error); | ||
} | ||
})(); | ||
}); | ||
} | ||
(async () => { | ||
try { | ||
await this.waitForEvent('onStart'); | ||
clearTimeout(timeout); | ||
setTimeout(resolve, 1000); | ||
} catch (error) { | ||
reject(error); | ||
} | ||
})(); | ||
}); | ||
} | ||
async waitForEvent(name, parse) { | ||
const {stdout} = await execa( | ||
BIN, [ | ||
'events', | ||
'listen', | ||
'--process-id', | ||
this.processId, | ||
'--exit', | ||
name | ||
] | ||
); | ||
async waitForEvent(name, parse) { | ||
const {stdout} = await execa( | ||
BIN, [ | ||
'events', | ||
'listen', | ||
'--process-id', | ||
this.processId, | ||
'--exit', | ||
name | ||
] | ||
); | ||
if (parse) { | ||
return parse(stdout.trim()); | ||
} | ||
} | ||
if (parse) { | ||
return parse(stdout.trim()); | ||
} | ||
} | ||
async sendEvent(name, parse) { | ||
const {stdout} = await execa( | ||
BIN, [ | ||
'events', | ||
'send', | ||
'--process-id', | ||
this.processId, | ||
name | ||
] | ||
); | ||
async sendEvent(name, parse) { | ||
const {stdout} = await execa( | ||
BIN, [ | ||
'events', | ||
'send', | ||
'--process-id', | ||
this.processId, | ||
name | ||
] | ||
); | ||
if (parse) { | ||
return parse(stdout.trim()); | ||
} | ||
} | ||
if (parse) { | ||
return parse(stdout.trim()); | ||
} | ||
} | ||
throwIfNotStarted() { | ||
if (this.recorder === undefined) { | ||
throw new Error('Call `.startRecording()` first'); | ||
} | ||
} | ||
throwIfNotStarted() { | ||
if (this.recorder === undefined) { | ||
throw new Error('Call `.startRecording()` first'); | ||
} | ||
} | ||
async pause() { | ||
this.throwIfNotStarted(); | ||
async pause() { | ||
this.throwIfNotStarted(); | ||
await this.sendEvent('pause'); | ||
} | ||
await this.sendEvent('pause'); | ||
} | ||
async resume() { | ||
this.throwIfNotStarted(); | ||
async resume() { | ||
this.throwIfNotStarted(); | ||
await this.sendEvent('resume'); | ||
await this.sendEvent('resume'); | ||
// It takes about 1s after the promise resolves for the recording to actually start | ||
await delay(1000); | ||
} | ||
// It takes about 1s after the promise resolves for the recording to actually start | ||
await delay(1000); | ||
} | ||
async isPaused() { | ||
this.throwIfNotStarted(); | ||
async isPaused() { | ||
this.throwIfNotStarted(); | ||
return this.sendEvent('isPaused', value => value === 'true'); | ||
} | ||
return this.sendEvent('isPaused', value => value === 'true'); | ||
} | ||
async stopRecording() { | ||
this.throwIfNotStarted(); | ||
async stopRecording() { | ||
this.throwIfNotStarted(); | ||
this.recorder.kill(); | ||
await this.recorder; | ||
delete this.recorder; | ||
delete this.isFileReady; | ||
this.recorder.kill(); | ||
await this.recorder; | ||
delete this.recorder; | ||
delete this.isFileReady; | ||
return this.tmpPath; | ||
} | ||
return this.tmpPath; | ||
} | ||
} | ||
@@ -236,36 +235,36 @@ | ||
module.exports.screens = async () => { | ||
const {stderr} = await execa(BIN, ['list', 'screens']); | ||
const {stderr} = await execa(BIN, ['list', 'screens']); | ||
try { | ||
return JSON.parse(stderr); | ||
} catch { | ||
return stderr; | ||
} | ||
try { | ||
return JSON.parse(stderr); | ||
} catch { | ||
return stderr; | ||
} | ||
}; | ||
module.exports.audioDevices = async () => { | ||
const {stderr} = await execa(BIN, ['list', 'audio-devices']); | ||
const {stderr} = await execa(BIN, ['list', 'audio-devices']); | ||
try { | ||
return JSON.parse(stderr); | ||
} catch { | ||
return stderr; | ||
} | ||
try { | ||
return JSON.parse(stderr); | ||
} catch { | ||
return stderr; | ||
} | ||
}; | ||
Object.defineProperty(module.exports, 'videoCodecs', { | ||
get() { | ||
const codecs = new Map([ | ||
['h264', 'H264'], | ||
['hevc', 'HEVC'], | ||
['proRes422', 'Apple ProRes 422'], | ||
['proRes4444', 'Apple ProRes 4444'] | ||
]); | ||
get() { | ||
const codecs = new Map([ | ||
['h264', 'H264'], | ||
['hevc', 'HEVC'], | ||
['proRes422', 'Apple ProRes 422'], | ||
['proRes4444', 'Apple ProRes 4444'] | ||
]); | ||
if (!supportsHevcHardwareEncoding) { | ||
codecs.delete('hevc'); | ||
} | ||
if (!supportsHevcHardwareEncoding) { | ||
codecs.delete('hevc'); | ||
} | ||
return codecs; | ||
} | ||
return codecs; | ||
} | ||
}); |
{ | ||
"name": "aperture", | ||
"version": "6.0.1", | ||
"description": "Record the screen on macOS", | ||
"license": "MIT", | ||
"repository": "wulkano/aperture-node", | ||
"engines": { | ||
"node": ">=10" | ||
}, | ||
"scripts": { | ||
"test": "xo && ava && tsd", | ||
"build": "swift build --configuration=release && mv .build/release/aperture .", | ||
"prepublish": "npm run build" | ||
}, | ||
"files": [ | ||
"index.js", | ||
"aperture", | ||
"index.d.ts" | ||
], | ||
"dependencies": { | ||
"delay": "^5.0.0", | ||
"electron-util": "^0.14.2", | ||
"execa": "^5.0.0", | ||
"file-url": "^3.0.0", | ||
"macos-version": "^5.2.1", | ||
"tempy": "^1.0.0" | ||
}, | ||
"devDependencies": { | ||
"ava": "^2.4.0", | ||
"file-type": "^12.0.0", | ||
"read-chunk": "^3.2.0", | ||
"tsd": "^0.14.0", | ||
"xo": "^0.38.2" | ||
}, | ||
"xo": { | ||
"space": true | ||
} | ||
"name": "aperture", | ||
"version": "6.1.0", | ||
"description": "Record the screen on macOS", | ||
"license": "MIT", | ||
"repository": "wulkano/aperture-node", | ||
"engines": { | ||
"node": ">=10" | ||
}, | ||
"scripts": { | ||
"test": "xo && ava && tsd", | ||
"build": "swift build --configuration=release --arch arm64 --arch x86_64 && mv .build/apple/Products/Release/aperture .", | ||
"prepublish": "npm run build" | ||
}, | ||
"files": [ | ||
"index.js", | ||
"aperture", | ||
"index.d.ts" | ||
], | ||
"dependencies": { | ||
"delay": "^5.0.0", | ||
"electron-util": "^0.14.2", | ||
"execa": "^5.0.0", | ||
"file-url": "^3.0.0", | ||
"macos-version": "^5.2.1", | ||
"tempy": "^1.0.0" | ||
}, | ||
"devDependencies": { | ||
"ava": "^2.4.0", | ||
"file-type": "^12.0.0", | ||
"read-chunk": "^3.2.0", | ||
"tsd": "^0.14.0", | ||
"xo": "^0.38.2" | ||
} | ||
} |
@@ -7,5 +7,5 @@ # aperture-node | ||
```sh | ||
npm install aperture | ||
``` | ||
$ npm install aperture | ||
``` | ||
@@ -21,16 +21,16 @@ *Requires macOS 10.13 or later.* | ||
const options = { | ||
fps: 30, | ||
cropArea: { | ||
x: 100, | ||
y: 100, | ||
width: 500, | ||
height: 500 | ||
} | ||
fps: 30, | ||
cropArea: { | ||
x: 100, | ||
y: 100, | ||
width: 500, | ||
height: 500 | ||
} | ||
}; | ||
(async () => { | ||
await aperture.startRecording(options); | ||
await delay(3000); | ||
console.log(await aperture.stopRecording()); | ||
//=> '/private/var/folders/3x/jf5977fn79jbglr7rk0tq4d00000gn/T/cdf4f7df426c97880f8c10a1600879f7.mp4' | ||
await aperture.startRecording(options); | ||
await delay(3000); | ||
console.log(await aperture.stopRecording()); | ||
//=> '/private/var/folders/3x/jf5977fn79jbglr7rk0tq4d00000gn/T/cdf4f7df426c97880f8c10a1600879f7.mp4' | ||
})(); | ||
@@ -51,4 +51,4 @@ ``` | ||
[{ | ||
id: 69732482, | ||
name: 'Color LCD' | ||
id: 69732482, | ||
name: 'Color LCD' | ||
}] | ||
@@ -65,4 +65,4 @@ ``` | ||
[{ | ||
id: 'AppleHDAEngineInput:1B,0,1,0:1', | ||
name: 'Built-in Microphone' | ||
id: 'AppleHDAEngineInput:1B,0,1,0:1', | ||
name: 'Built-in Microphone' | ||
}] | ||
@@ -79,6 +79,6 @@ ``` | ||
Map { | ||
'h264' => 'H264', | ||
'hevc' => 'HEVC', | ||
'proRes422' => 'Apple ProRes 422', | ||
'proRes4444' => 'Apple ProRes 4444' | ||
'h264' => 'H264', | ||
'hevc' => 'HEVC', | ||
'proRes422' => 'Apple ProRes 422', | ||
'proRes4444' => 'Apple ProRes 4444' | ||
} | ||
@@ -85,0 +85,0 @@ ``` |
Sorry, the diff of this file is not supported yet
Native code
Supply chain riskContains native code (e.g., compiled binaries or shared libraries). Including native code can obscure malicious behavior.
Found 1 instance in 1 package
3621264
96.89%0
-100%