@0biwank/screen-capture 2.0.1 → 2.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,178 +0,0 @@
1
- import {existsSync, mkdirSync, readFileSync, rmSync, writeFileSync, copyFileSync} from 'fs';
2
- import {fileURLToPath} from 'url';
3
- import {join, resolve} from 'path';
4
- import {spawnSync} from 'child_process';
5
- import os from 'os';
6
- import {createHash} from 'crypto';
7
-
8
- const FFMPEG_VERSION = '8.1';
9
- const FFMPEG_SHA256 = 'b072aed6871998cce9b36e7774033105ca29e33632be5b6347f3206898e0756a';
10
- const X264_REF = 'b35605ace3ddf7c1a5d67a2eb553f034aef41d55';
11
- const X264_SHA256 = '6eeb82934e69fd51e043bd8c5b0d152839638d1ce7aa4eea65a3fedcf83ff224';
12
- const MACOS_DEPLOYMENT_TARGET = '14.0';
13
-
14
- const packageRoot = resolve(fileURLToPath(new URL('..', import.meta.url)));
15
- const requestedArch =
16
- process.env.NATIVE_CAPTURE_ARCH ||
17
- process.env.TARGET_ARCH ||
18
- process.env.npm_config_arch ||
19
- process.arch;
20
- const targetArch = requestedArch === 'x86_64' ? 'x64' : requestedArch;
21
- const vendorRoot = resolve(packageRoot, 'vendor', 'ffmpeg');
22
- const vendorDir = join(vendorRoot, `darwin-${targetArch}`);
23
- const workRoot = join(os.tmpdir(), `freeboom-native-ffmpeg-${targetArch}`);
24
- const ffmpegArchive = join(workRoot, `ffmpeg-${FFMPEG_VERSION}.tar.xz`);
25
- const x264Archive = join(workRoot, `x264-${X264_REF}.tar.bz2`);
26
- const ffmpegSrc = join(workRoot, 'ffmpeg-src');
27
- const x264Src = join(workRoot, 'x264-src');
28
- const x264Install = join(workRoot, 'x264-install');
29
-
30
- function run(command, args, options = {}) {
31
- console.log(`[NativeVendor] ${command} ${args.join(' ')}`);
32
- const result = spawnSync(command, args, {
33
- stdio: 'inherit',
34
- shell: process.platform === 'win32',
35
- ...options,
36
- env: {
37
- ...process.env,
38
- MACOSX_DEPLOYMENT_TARGET: MACOS_DEPLOYMENT_TARGET,
39
- ...options.env,
40
- },
41
- });
42
-
43
- if (result.status !== 0) {
44
- process.exit(result.status ?? 1);
45
- }
46
- }
47
-
48
- function sha256(file) {
49
- return createHash('sha256').update(readFileSync(file)).digest('hex');
50
- }
51
-
52
- function download(url, output, expectedSha256) {
53
- if (existsSync(output) && sha256(output) === expectedSha256) {
54
- console.log(`[NativeVendor] Reusing ${output}`);
55
- return;
56
- }
57
-
58
- rmSync(output, {force: true});
59
- run('curl', ['-fL', '--retry', '3', '--retry-delay', '2', url, '-o', output]);
60
- const actualSha256 = sha256(output);
61
- if (actualSha256 !== expectedSha256) {
62
- rmSync(output, {force: true});
63
- throw new Error(
64
- `[NativeVendor] SHA-256 mismatch for ${url}: expected ${expectedSha256}, received ${actualSha256}`,
65
- );
66
- }
67
- }
68
-
69
- function patchFfmpegHlsMuxer() {
70
- const hlsencPath = join(ffmpegSrc, 'libavformat', 'hlsenc.c');
71
- const source = readFileSync(hlsencPath, 'utf8');
72
- const needle = ` if (hls->segment_type == SEGMENT_TYPE_FMP4) {
73
- EXTERN const FFOutputFormat ff_mp4_muxer;
74
- vs->oformat = &ff_mp4_muxer.p;
75
- } else {
76
- EXTERN const FFOutputFormat ff_mpegts_muxer;
77
- vs->oformat = &ff_mpegts_muxer.p;
78
- }`;
79
- const replacement = ` if (hls->segment_type == SEGMENT_TYPE_FMP4) {
80
- av_log(s, AV_LOG_ERROR, "fmp4 HLS segments are disabled in this static build\\n");
81
- return AVERROR_PATCHWELCOME;
82
- } else {
83
- EXTERN const FFOutputFormat ff_mpegts_muxer;
84
- vs->oformat = &ff_mpegts_muxer.p;
85
- }`;
86
-
87
- if (!source.includes(needle)) {
88
- throw new Error('Unable to patch FFmpeg HLS muxer; expected fMP4 branch was not found');
89
- }
90
-
91
- writeFileSync(hlsencPath, source.replace(needle, replacement));
92
- }
93
-
94
- if (process.platform !== 'darwin') {
95
- console.log('[NativeVendor] Skipping FFmpeg static vendor build on', process.platform);
96
- process.exit(0);
97
- }
98
-
99
- if (!['arm64', 'x64'].includes(targetArch)) {
100
- throw new Error(`[NativeVendor] Unsupported macOS architecture: ${targetArch}`);
101
- }
102
-
103
- const compilerArch = targetArch === 'x64' ? 'x86_64' : 'arm64';
104
- const host = targetArch === 'x64' ? 'x86_64-apple-darwin' : 'aarch64-apple-darwin';
105
- const architectureFlags = `-arch ${compilerArch} -mmacosx-version-min=${MACOS_DEPLOYMENT_TARGET}`;
106
-
107
- rmSync(ffmpegSrc, {recursive: true, force: true});
108
- rmSync(x264Src, {recursive: true, force: true});
109
- rmSync(x264Install, {recursive: true, force: true});
110
- rmSync(vendorDir, {recursive: true, force: true});
111
- mkdirSync(workRoot, {recursive: true});
112
- mkdirSync(ffmpegSrc, {recursive: true});
113
- mkdirSync(x264Src, {recursive: true});
114
- mkdirSync(x264Install, {recursive: true});
115
- mkdirSync(vendorDir, {recursive: true});
116
-
117
- download(
118
- `https://ffmpeg.org/releases/ffmpeg-${FFMPEG_VERSION}.tar.xz`,
119
- ffmpegArchive,
120
- FFMPEG_SHA256,
121
- );
122
- download(
123
- `https://code.videolan.org/videolan/x264/-/archive/${X264_REF}/x264-${X264_REF}.tar.bz2`,
124
- x264Archive,
125
- X264_SHA256,
126
- );
127
-
128
- run('tar', ['-xJf', ffmpegArchive, '-C', ffmpegSrc, '--strip-components=1']);
129
- run('tar', ['-xjf', x264Archive, '-C', x264Src, '--strip-components=1']);
130
-
131
- run(
132
- './configure',
133
- [
134
- `--prefix=${x264Install}`,
135
- '--enable-static',
136
- '--disable-cli',
137
- '--disable-opencl',
138
- `--host=${host}`,
139
- `--extra-cflags=${architectureFlags}`,
140
- `--extra-ldflags=${architectureFlags}`,
141
- ],
142
- {cwd: x264Src},
143
- );
144
- run('make', [`-j${os.cpus().length}`, 'install'], {cwd: x264Src});
145
-
146
- patchFfmpegHlsMuxer();
147
-
148
- run(
149
- './configure',
150
- [
151
- `--prefix=${vendorDir}`,
152
- '--disable-shared',
153
- '--enable-static',
154
- '--disable-programs',
155
- '--disable-doc',
156
- '--disable-debug',
157
- '--disable-autodetect',
158
- '--disable-network',
159
- '--enable-gpl',
160
- '--enable-libx264',
161
- '--target-os=darwin',
162
- `--arch=${compilerArch}`,
163
- '--cc=clang',
164
- `--extra-cflags=-I${join(x264Install, 'include')} ${architectureFlags}`,
165
- `--extra-ldflags=-L${join(x264Install, 'lib')} ${architectureFlags}`,
166
- `--pkg-config-flags=--static`,
167
- ],
168
- {
169
- cwd: ffmpegSrc,
170
- env: {
171
- PKG_CONFIG_PATH: join(x264Install, 'lib', 'pkgconfig'),
172
- },
173
- },
174
- );
175
- run('make', [`-j${os.cpus().length}`, 'install'], {cwd: ffmpegSrc});
176
-
177
- copyFileSync(join(x264Install, 'lib', 'libx264.a'), join(vendorDir, 'lib', 'libx264.a'));
178
- console.log(`[NativeVendor] Static FFmpeg vendor bundle ready: ${vendorDir}`);
package/scripts/build.mjs DELETED
@@ -1,53 +0,0 @@
1
- import {existsSync} from 'fs';
2
- import {fileURLToPath} from 'url';
3
- import {join, resolve} from 'path';
4
- import {spawnSync} from 'child_process';
5
-
6
- if (process.platform !== 'darwin') {
7
- console.log('[NativeCapture] Skipping macOS native capture build on', process.platform);
8
- process.exit(0);
9
- }
10
-
11
- const packageRoot = resolve(fileURLToPath(new URL('..', import.meta.url)));
12
- const requestedArch =
13
- process.env.NATIVE_CAPTURE_ARCH ||
14
- process.env.TARGET_ARCH ||
15
- process.env.npm_config_arch ||
16
- process.arch;
17
- const targetArch = requestedArch === 'x86_64' ? 'x64' : requestedArch;
18
- const vendorLibDir = join(packageRoot, 'vendor', 'ffmpeg', `darwin-${targetArch}`, 'lib');
19
- const requiredVendorLibs = [
20
- 'libavformat.a',
21
- 'libavcodec.a',
22
- 'libswscale.a',
23
- 'libswresample.a',
24
- 'libavutil.a',
25
- 'libx264.a',
26
- ];
27
-
28
- function run(command, args, options = {}) {
29
- const result = spawnSync(command, args, {
30
- cwd: packageRoot,
31
- stdio: 'inherit',
32
- shell: process.platform === 'win32',
33
- ...options,
34
- });
35
-
36
- if (result.status !== 0) {
37
- process.exit(result.status ?? 1);
38
- }
39
- }
40
-
41
- const hasVendorBundle = requiredVendorLibs.every(lib => existsSync(join(vendorLibDir, lib)));
42
- if (!hasVendorBundle && process.env.SKIP_NATIVE_FFMPEG_VENDOR_BUILD !== '1') {
43
- console.log('[NativeCapture] Static FFmpeg vendor bundle missing; building it now');
44
- run('node', ['scripts/build-ffmpeg-vendor.mjs'], {
45
- env: {
46
- ...process.env,
47
- NATIVE_CAPTURE_ARCH: targetArch,
48
- },
49
- });
50
- }
51
-
52
- console.log('[NativeCapture] Building native addon');
53
- run('node-gyp', ['configure', 'build', `--arch=${targetArch}`]);
@@ -1,28 +0,0 @@
1
- import {chmodSync, copyFileSync, existsSync, mkdirSync} from 'fs';
2
- import {join, resolve} from 'path';
3
- import {fileURLToPath} from 'url';
4
-
5
- const packageRoot = resolve(fileURLToPath(new URL('..', import.meta.url)));
6
- const requestedArch =
7
- process.env.NATIVE_CAPTURE_ARCH ||
8
- process.env.TARGET_ARCH ||
9
- process.env.npm_config_arch ||
10
- process.arch;
11
- const arch = requestedArch === 'x86_64' ? 'x64' : requestedArch;
12
-
13
- if (!['arm64', 'x64'].includes(arch)) {
14
- throw new Error(`Unsupported prebuild architecture: ${arch}`);
15
- }
16
-
17
- const source = join(packageRoot, 'build', 'Release', 'native_capture.node');
18
- const destinationDir = join(packageRoot, 'prebuilds', `darwin-${arch}`);
19
- const destination = join(destinationDir, 'native_capture.node');
20
-
21
- if (!existsSync(source)) {
22
- throw new Error(`Native addon has not been built: ${source}`);
23
- }
24
-
25
- mkdirSync(destinationDir, {recursive: true});
26
- copyFileSync(source, destination);
27
- chmodSync(destination, 0o755);
28
- console.log(`[NativeCapture] Staged darwin-${arch} prebuild at ${destination}`);
@@ -1,39 +0,0 @@
1
- import {createHash} from 'crypto';
2
- import {readdirSync, readFileSync, statSync} from 'fs';
3
- import {join, resolve} from 'path';
4
- import {spawnSync} from 'child_process';
5
- import {fileURLToPath} from 'url';
6
-
7
- const packageRoot = resolve(fileURLToPath(new URL('..', import.meta.url)));
8
- const expected = [
9
- {arch: 'arm64', machine: 'arm64'},
10
- {arch: 'x64', machine: 'x86_64'},
11
- ];
12
- const checksumLines = [];
13
-
14
- for (const {arch, machine} of expected) {
15
- const relativePath = `darwin-${arch}/native_capture.node`;
16
- const filePath = join(packageRoot, 'prebuilds', relativePath);
17
- const size = statSync(filePath).size;
18
- if (size < 1024 * 1024 || size > 64 * 1024 * 1024) {
19
- throw new Error(`Unexpected prebuild size for ${relativePath}: ${size}`);
20
- }
21
-
22
- const inspection = spawnSync('file', [filePath], {encoding: 'utf8'});
23
- if (inspection.status !== 0 || !inspection.stdout.includes(machine)) {
24
- throw new Error(`Invalid architecture for ${relativePath}: ${inspection.stdout}`);
25
- }
26
-
27
- const checksum = createHash('sha256').update(readFileSync(filePath)).digest('hex');
28
- checksumLines.push(`${checksum} ${relativePath}`);
29
- }
30
-
31
- const directories = readdirSync(join(packageRoot, 'prebuilds'), {withFileTypes: true})
32
- .filter(entry => entry.isDirectory())
33
- .map(entry => entry.name)
34
- .sort();
35
- if (directories.join(',') !== 'darwin-arm64,darwin-x64') {
36
- throw new Error(`Unexpected prebuild directories: ${directories.join(', ')}`);
37
- }
38
-
39
- process.stdout.write(`${checksumLines.join('\n')}\n`);
@@ -1,40 +0,0 @@
1
- import {readFileSync} from 'fs';
2
-
3
- const reportPath = process.argv[2];
4
- if (!reportPath) throw new Error('Usage: node scripts/verify-packlist.mjs <npm-pack-report.json>');
5
-
6
- const [report] = JSON.parse(readFileSync(reportPath, 'utf8'));
7
- if (!report?.files) throw new Error('npm pack report does not contain a file list');
8
-
9
- const paths = report.files.map(file => file.path);
10
- const required = [
11
- 'index.js',
12
- 'index.d.ts',
13
- 'LICENSE',
14
- 'SOURCE.md',
15
- 'THIRD_PARTY_NOTICES.md',
16
- 'prebuilds/SHA256SUMS',
17
- 'prebuilds/darwin-arm64/native_capture.node',
18
- 'prebuilds/darwin-x64/native_capture.node',
19
- ];
20
-
21
- for (const path of required) {
22
- if (!paths.includes(path)) throw new Error(`Required package file is missing: ${path}`);
23
- }
24
-
25
- const forbidden = paths.filter(
26
- path =>
27
- path.startsWith('build/') ||
28
- path.startsWith('node_modules/') ||
29
- path.includes('/vendor/ffmpeg/darwin-') ||
30
- /\.(a|o|dylib)$/.test(path),
31
- );
32
- if (forbidden.length > 0) {
33
- throw new Error(`Forbidden package files found:\n${forbidden.join('\n')}`);
34
- }
35
-
36
- if (report.unpackedSize > 64 * 1024 * 1024) {
37
- throw new Error(`Unpacked package is unexpectedly large: ${report.unpackedSize} bytes`);
38
- }
39
-
40
- console.log(`Verified npm pack contents: ${paths.length} files, ${report.unpackedSize} bytes`);
@@ -1,28 +0,0 @@
1
- 'use strict';
2
-
3
- const capture = require('../index.js');
4
-
5
- const expectedFunctions = [
6
- 'startCapture',
7
- 'setSegmentReadyCallback',
8
- 'stopCapture',
9
- 'getLastError',
10
- 'listDisplaySources',
11
- 'listWindowSources',
12
- 'listAudioInputDevices',
13
- 'listVideoInputDevices',
14
- 'pauseCapture',
15
- 'resumeCapture',
16
- ];
17
-
18
- for (const name of expectedFunctions) {
19
- if (typeof capture[name] !== 'function') {
20
- throw new Error(`Missing native export: ${name}`);
21
- }
22
- }
23
-
24
- if (typeof capture.getLastError() !== 'string') {
25
- throw new Error('getLastError() must return a string');
26
- }
27
-
28
- console.log(`Verified ${expectedFunctions.length} native exports on darwin-${process.arch}`);
@@ -1,154 +0,0 @@
1
- #import <AVFoundation/AVFoundation.h>
2
- #import <CoreMedia/CoreMedia.h>
3
- #include "CameraCapturer.h"
4
- #include <iostream>
5
-
6
- /**
7
- * CameraDelegate
8
- * --------------
9
- * Internal ObjC class to receive frames from AVCaptureVideoDataOutput.
10
- */
11
- @interface CameraDelegate : NSObject <AVCaptureVideoDataOutputSampleBufferDelegate> {
12
- CameraFrameCallback _callback;
13
- }
14
- - (void)setCallback:(CameraFrameCallback)cb;
15
- @end
16
-
17
- @implementation CameraDelegate
18
- - (void)setCallback:(CameraFrameCallback)cb {
19
- _callback = cb;
20
- }
21
-
22
- - (void)captureOutput:(AVCaptureOutput *)output
23
- didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer
24
- fromConnection:(AVCaptureConnection *)connection {
25
- if (_callback) {
26
- _callback(static_cast<void*>(sampleBuffer));
27
- }
28
- }
29
- @end
30
-
31
- CameraCapturer::CameraCapturer() : m_session(nullptr), m_delegate(nullptr) {}
32
-
33
- CameraCapturer::~CameraCapturer() {
34
- stop();
35
- }
36
-
37
- std::vector<CameraDevice> CameraCapturer::listDevices() {
38
- std::vector<CameraDevice> result;
39
-
40
- // Modern API for listing video devices
41
- AVCaptureDeviceDiscoverySession *discoverySession = [AVCaptureDeviceDiscoverySession
42
- discoverySessionWithDeviceTypes:@[AVCaptureDeviceTypeBuiltInWideAngleCamera, AVCaptureDeviceTypeExternal]
43
- mediaType:AVMediaTypeVideo
44
- position:AVCaptureDevicePositionUnspecified];
45
-
46
- for (AVCaptureDevice *device in discoverySession.devices) {
47
- CameraDevice d;
48
- d.uid = [[device uniqueID] UTF8String];
49
- d.name = [[device localizedName] UTF8String];
50
- result.push_back(d);
51
- }
52
-
53
- return result;
54
- }
55
-
56
- bool CameraCapturer::start(const std::string& device_uid, uint32_t& width, uint32_t& height, int fps, CameraFrameCallback cb) {
57
- stop(); // Ensure clean state
58
-
59
- AVCaptureSession *session = [[AVCaptureSession alloc] init];
60
- AVCaptureDevice *device = nil;
61
-
62
- if (device_uid.empty()) {
63
- device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
64
- } else {
65
- device = [AVCaptureDevice deviceWithUniqueID:[NSString stringWithUTF8String:device_uid.c_str()]];
66
- }
67
-
68
- if (!device) {
69
- std::cerr << "[Camera] Requested device not found: " << device_uid << std::endl;
70
- return false;
71
- }
72
-
73
- NSError *error = nil;
74
- AVCaptureDeviceInput *input = [AVCaptureDeviceInput deviceInputWithDevice:device error:&error];
75
- if (error || !input) {
76
- std::cerr << "[Camera] Failed to create input: " << [[error localizedDescription] UTF8String] << std::endl;
77
- return false;
78
- }
79
-
80
- if ([session canAddInput:input]) {
81
- [session addInput:input];
82
- } else {
83
- return false;
84
- }
85
-
86
- AVCaptureVideoDataOutput *output = [[AVCaptureVideoDataOutput alloc] init];
87
-
88
- // Use 32BGRA for easy conversion / preview
89
- [output setVideoSettings:@{
90
- (id)kCVPixelBufferPixelFormatTypeKey: @(kCVPixelFormatType_32BGRA)
91
- }];
92
-
93
- CameraDelegate *delegate = [[CameraDelegate alloc] init];
94
- [delegate setCallback:cb];
95
-
96
- dispatch_queue_t queue = dispatch_queue_create("com.boom.camera.queue", DISPATCH_QUEUE_SERIAL);
97
- [output setSampleBufferDelegate:delegate queue:queue];
98
-
99
- if ([session canAddOutput:output]) {
100
- [session addOutput:output];
101
- } else {
102
- return false;
103
- }
104
-
105
- // Find best match for requested dimensions
106
- AVCaptureDeviceFormat *bestFormat = nil;
107
- int bestDiff = 9999999;
108
-
109
- for (AVCaptureDeviceFormat *vFormat in [device formats]) {
110
- CMFormatDescriptionRef description = [vFormat formatDescription];
111
- CMVideoDimensions dims = CMVideoFormatDescriptionGetDimensions(description);
112
-
113
- int diff = std::abs((int)dims.width - (int)width) + std::abs((int)dims.height - (int)height);
114
- if (diff < bestDiff) {
115
- bestDiff = diff;
116
- bestFormat = vFormat;
117
- }
118
- }
119
-
120
- if (bestFormat) {
121
- if ([device lockForConfiguration:nil]) {
122
- device.activeFormat = bestFormat;
123
- device.activeVideoMinFrameDuration = CMTimeMake(1, fps);
124
- device.activeVideoMaxFrameDuration = CMTimeMake(1, fps);
125
- [device unlockForConfiguration];
126
-
127
- CMFormatDescriptionRef description = [bestFormat formatDescription];
128
- CMVideoDimensions dims = CMVideoFormatDescriptionGetDimensions(description);
129
- width = dims.width;
130
- height = dims.height;
131
- std::cout << "[Camera] Selected format: " << width << "x" << height << " @ " << fps << "fps" << std::endl;
132
- }
133
- }
134
-
135
- [session startRunning];
136
-
137
- m_session = (__bridge_retained void*)session;
138
- m_delegate = (__bridge_retained void*)delegate;
139
-
140
- return true;
141
- }
142
-
143
- void CameraCapturer::stop() {
144
- if (m_session) {
145
- AVCaptureSession *session = (__bridge_transfer AVCaptureSession*)m_session;
146
- [session stopRunning];
147
- m_session = nullptr;
148
- }
149
- if (m_delegate) {
150
- CameraDelegate *delegate = (__bridge_transfer CameraDelegate*)m_delegate;
151
- [delegate setCallback:nullptr];
152
- m_delegate = nullptr;
153
- }
154
- }