@mediaway/device 0.1.0
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.
- package/dist/index.d.ts +95 -0
- package/dist/index.js +169 -0
- package/package.json +35 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @mediaway/device — device capability: camera / screen / microphone capture.
|
|
3
|
+
*
|
|
4
|
+
* Implements the DX contract in bindings/nodejs/README.md over the
|
|
5
|
+
* mediaway-ffi C ABI (via @mediaway/ffi). The ABI is domain-split
|
|
6
|
+
* (`adr/0004-domain-feature-split.md`): this package folds it back into the
|
|
7
|
+
* three DX functions. CPU-storage only: Camera delivers CPU frames; Screen
|
|
8
|
+
* capture raises CaptureUnavailableError from the C ABI today (it needs a
|
|
9
|
+
* live GPU device handle with no CPU fallback). Audio capture delivers raw
|
|
10
|
+
* interleaved f32le PCM — there is no audio encoder in the ABI.
|
|
11
|
+
*/
|
|
12
|
+
import { MediawayError, type Rational } from "@mediaway/container";
|
|
13
|
+
export type { Rational } from "@mediaway/container";
|
|
14
|
+
export interface VideoFrame {
|
|
15
|
+
pts: number;
|
|
16
|
+
duration: number;
|
|
17
|
+
width: number;
|
|
18
|
+
height: number;
|
|
19
|
+
pixelFormat: "nv12" | "bgra8";
|
|
20
|
+
data: Buffer;
|
|
21
|
+
}
|
|
22
|
+
export interface AudioFrame {
|
|
23
|
+
pts: number;
|
|
24
|
+
sampleRate: number;
|
|
25
|
+
channels: number;
|
|
26
|
+
data: Buffer;
|
|
27
|
+
}
|
|
28
|
+
/** No capture device/backend is available, or the ABI rejects this
|
|
29
|
+
* configuration as unsupported (today: Screen capture) — expected outcomes,
|
|
30
|
+
* not hard failures. */
|
|
31
|
+
export declare class CaptureUnavailableError extends MediawayError {
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* A Camera capture session. Negotiated geometry/format are authoritative over
|
|
35
|
+
* the request — read them from the session properties.
|
|
36
|
+
*/
|
|
37
|
+
export declare class CameraSession {
|
|
38
|
+
readonly width: number;
|
|
39
|
+
readonly height: number;
|
|
40
|
+
readonly pixelFormat: "nv12";
|
|
41
|
+
readonly timeBase: Rational;
|
|
42
|
+
private handle;
|
|
43
|
+
/** Wrap a native camera capture handle. Prefer the openCamera() factory. */
|
|
44
|
+
constructor(handle: unknown, width: number, height: number, timeBase: Rational);
|
|
45
|
+
/** Poll the next frame; null when nothing is ready yet. Sync, never blocks. */
|
|
46
|
+
pollFrame(): VideoFrame | null;
|
|
47
|
+
/** Joins the backend worker thread — can block up to one frame interval. */
|
|
48
|
+
close(): Promise<void>;
|
|
49
|
+
}
|
|
50
|
+
/** Open camera `index` at `timeBase`; throws CaptureUnavailableError when no
|
|
51
|
+
* camera exists on this machine. */
|
|
52
|
+
export declare function openCamera(options: {
|
|
53
|
+
index: number;
|
|
54
|
+
timeBase: Rational;
|
|
55
|
+
}): Promise<CameraSession>;
|
|
56
|
+
/** A Microphone capture session (raw interleaved f32le PCM). */
|
|
57
|
+
export declare class MicSession {
|
|
58
|
+
readonly sampleRate: number;
|
|
59
|
+
readonly channels: number;
|
|
60
|
+
private handle;
|
|
61
|
+
/** Wrap a native mic capture handle. Prefer the openMicrophone() factory. */
|
|
62
|
+
constructor(handle: unknown, sampleRate: number, channels: number);
|
|
63
|
+
/** Poll the next PCM chunk; null when nothing is ready yet. */
|
|
64
|
+
pollFrame(): AudioFrame | null;
|
|
65
|
+
/** Joins the backend worker thread — can block up to one period interval. */
|
|
66
|
+
close(): Promise<void>;
|
|
67
|
+
}
|
|
68
|
+
/** Open the microphone at `sampleRate` Hz; throws CaptureUnavailableError
|
|
69
|
+
* when no mic/backend exists. */
|
|
70
|
+
export declare function openMicrophone(options: {
|
|
71
|
+
sampleRate: number;
|
|
72
|
+
channels?: number;
|
|
73
|
+
}): Promise<MicSession>;
|
|
74
|
+
/**
|
|
75
|
+
* A Screen capture session — NOT available from the C ABI today. Screen needs
|
|
76
|
+
* a live GPU device handle (ID3D11Device*) with no CPU fallback, and its C
|
|
77
|
+
* representation is deferred (crates/mediaway-ffi/adr/0001 § Deferred),
|
|
78
|
+
* so openScreenCapture always throws CaptureUnavailableError. The ideal DX
|
|
79
|
+
* (BGRA8 CPU frames at the display's native geometry) is what the aspirational
|
|
80
|
+
* screen-record example targets.
|
|
81
|
+
*/
|
|
82
|
+
export declare class ScreenSession {
|
|
83
|
+
readonly width = 0;
|
|
84
|
+
readonly height = 0;
|
|
85
|
+
readonly pixelFormat: "bgra8";
|
|
86
|
+
readonly timeBase: Rational;
|
|
87
|
+
/** Screen session (always throws — no C-ABI screen path yet). */
|
|
88
|
+
constructor(timeBase: Rational);
|
|
89
|
+
pollFrame(): VideoFrame | null;
|
|
90
|
+
close(): Promise<void>;
|
|
91
|
+
}
|
|
92
|
+
export declare function openScreenCapture(options: {
|
|
93
|
+
timeBase: Rational;
|
|
94
|
+
monitorIndex?: number;
|
|
95
|
+
}): Promise<ScreenSession>;
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @mediaway/device — device capability: camera / screen / microphone capture.
|
|
3
|
+
*
|
|
4
|
+
* Implements the DX contract in bindings/nodejs/README.md over the
|
|
5
|
+
* mediaway-ffi C ABI (via @mediaway/ffi). The ABI is domain-split
|
|
6
|
+
* (`adr/0004-domain-feature-split.md`): this package folds it back into the
|
|
7
|
+
* three DX functions. CPU-storage only: Camera delivers CPU frames; Screen
|
|
8
|
+
* capture raises CaptureUnavailableError from the C ABI today (it needs a
|
|
9
|
+
* live GPU device handle with no CPU fallback). Audio capture delivers raw
|
|
10
|
+
* interleaved f32le PCM — there is no audio encoder in the ABI.
|
|
11
|
+
*/
|
|
12
|
+
import { MwAudioFrame, MwAudioConfig, MwCameraConfig, MwCameraFrame, device, copyBytes, } from "@mediaway/ffi";
|
|
13
|
+
import { MediawayError } from "@mediaway/container";
|
|
14
|
+
/** No capture device/backend is available, or the ABI rejects this
|
|
15
|
+
* configuration as unsupported (today: Screen capture) — expected outcomes,
|
|
16
|
+
* not hard failures. */
|
|
17
|
+
export class CaptureUnavailableError extends MediawayError {
|
|
18
|
+
}
|
|
19
|
+
function checkDevice(status) {
|
|
20
|
+
if (status === 0)
|
|
21
|
+
return;
|
|
22
|
+
if (status === 4 || status === 6 || status === 8) {
|
|
23
|
+
throw new CaptureUnavailableError(status, "no capture backend or device available");
|
|
24
|
+
}
|
|
25
|
+
if (status === 3) {
|
|
26
|
+
throw new CaptureUnavailableError(status, "this capture configuration is unsupported by the ABI");
|
|
27
|
+
}
|
|
28
|
+
const names = {
|
|
29
|
+
1: "invalid argument",
|
|
30
|
+
2: "handle poisoned by an earlier panic",
|
|
31
|
+
5: "bad capture config",
|
|
32
|
+
7: "session already closed or not open",
|
|
33
|
+
9: "unknown error",
|
|
34
|
+
10: "internal panic (handle poisoned)",
|
|
35
|
+
11: "callback already registered",
|
|
36
|
+
12: "callback mode active (poll disabled)",
|
|
37
|
+
13: "timed out waiting for a frame",
|
|
38
|
+
};
|
|
39
|
+
throw new MediawayError(status, names[status] ?? "unknown device error");
|
|
40
|
+
}
|
|
41
|
+
// ── Camera ─────────────────────────────────────────────────────────────────────
|
|
42
|
+
/**
|
|
43
|
+
* A Camera capture session. Negotiated geometry/format are authoritative over
|
|
44
|
+
* the request — read them from the session properties.
|
|
45
|
+
*/
|
|
46
|
+
export class CameraSession {
|
|
47
|
+
width;
|
|
48
|
+
height;
|
|
49
|
+
pixelFormat;
|
|
50
|
+
timeBase;
|
|
51
|
+
handle;
|
|
52
|
+
/** Wrap a native camera capture handle. Prefer the openCamera() factory. */
|
|
53
|
+
constructor(handle, width, height, timeBase) {
|
|
54
|
+
this.handle = handle;
|
|
55
|
+
this.width = width;
|
|
56
|
+
this.height = height;
|
|
57
|
+
this.pixelFormat = "nv12";
|
|
58
|
+
this.timeBase = timeBase;
|
|
59
|
+
}
|
|
60
|
+
/** Poll the next frame; null when nothing is ready yet. Sync, never blocks. */
|
|
61
|
+
pollFrame() {
|
|
62
|
+
const raw = {};
|
|
63
|
+
const has = [false];
|
|
64
|
+
checkDevice(device.cameraPollFrame(this.handle, raw, has));
|
|
65
|
+
if (!has[0])
|
|
66
|
+
return null;
|
|
67
|
+
const data = copyBytes(raw.data, raw.data_len);
|
|
68
|
+
device.cameraFrameFree(raw);
|
|
69
|
+
return {
|
|
70
|
+
pts: Number(raw.pts),
|
|
71
|
+
duration: Number(raw.duration),
|
|
72
|
+
width: raw.width,
|
|
73
|
+
height: raw.height,
|
|
74
|
+
pixelFormat: "nv12",
|
|
75
|
+
data,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
/** Joins the backend worker thread — can block up to one frame interval. */
|
|
79
|
+
async close() {
|
|
80
|
+
if (this.handle) {
|
|
81
|
+
checkDevice(device.cameraClose(this.handle));
|
|
82
|
+
this.handle = null;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
/** Open camera `index` at `timeBase`; throws CaptureUnavailableError when no
|
|
87
|
+
* camera exists on this machine. */
|
|
88
|
+
export async function openCamera(options) {
|
|
89
|
+
const config = device.cameraConfigDefault(options.index, { num: BigInt(options.timeBase.num), den: options.timeBase.den });
|
|
90
|
+
const out = [null];
|
|
91
|
+
checkDevice(device.cameraOpen(config, out));
|
|
92
|
+
if (!out[0])
|
|
93
|
+
throw new MediawayError(9, "capture open returned no handle");
|
|
94
|
+
const width = [0];
|
|
95
|
+
const height = [0];
|
|
96
|
+
checkDevice(device.cameraGeometry(out[0], width, height));
|
|
97
|
+
return new CameraSession(out[0], width[0], height[0], options.timeBase);
|
|
98
|
+
}
|
|
99
|
+
// ── Microphone ─────────────────────────────────────────────────────────────────
|
|
100
|
+
/** A Microphone capture session (raw interleaved f32le PCM). */
|
|
101
|
+
export class MicSession {
|
|
102
|
+
sampleRate;
|
|
103
|
+
channels;
|
|
104
|
+
handle;
|
|
105
|
+
/** Wrap a native mic capture handle. Prefer the openMicrophone() factory. */
|
|
106
|
+
constructor(handle, sampleRate, channels) {
|
|
107
|
+
this.handle = handle;
|
|
108
|
+
this.sampleRate = sampleRate;
|
|
109
|
+
this.channels = channels;
|
|
110
|
+
}
|
|
111
|
+
/** Poll the next PCM chunk; null when nothing is ready yet. */
|
|
112
|
+
pollFrame() {
|
|
113
|
+
const raw = {};
|
|
114
|
+
const has = [false];
|
|
115
|
+
checkDevice(device.audioPollFrame(this.handle, raw, has));
|
|
116
|
+
if (!has[0])
|
|
117
|
+
return null;
|
|
118
|
+
const data = copyBytes(raw.data, raw.data_len);
|
|
119
|
+
device.audioFrameFree(raw);
|
|
120
|
+
return { pts: Number(raw.pts), sampleRate: raw.sample_rate, channels: raw.channels, data };
|
|
121
|
+
}
|
|
122
|
+
/** Joins the backend worker thread — can block up to one period interval. */
|
|
123
|
+
async close() {
|
|
124
|
+
if (this.handle) {
|
|
125
|
+
checkDevice(device.audioClose(this.handle));
|
|
126
|
+
this.handle = null;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
/** Open the microphone at `sampleRate` Hz; throws CaptureUnavailableError
|
|
131
|
+
* when no mic/backend exists. */
|
|
132
|
+
export async function openMicrophone(options) {
|
|
133
|
+
const config = device.audioConfigMicrophone({ num: 1n, den: options.sampleRate });
|
|
134
|
+
const out = [null];
|
|
135
|
+
checkDevice(device.audioOpen(config, out));
|
|
136
|
+
if (!out[0])
|
|
137
|
+
throw new MediawayError(9, "capture open returned no handle");
|
|
138
|
+
const rate = [0];
|
|
139
|
+
const channels = [0];
|
|
140
|
+
checkDevice(device.audioFormat(out[0], rate, channels));
|
|
141
|
+
return new MicSession(out[0], rate[0], channels[0]);
|
|
142
|
+
}
|
|
143
|
+
// ── Screen (ideal only) ────────────────────────────────────────────────────────
|
|
144
|
+
/**
|
|
145
|
+
* A Screen capture session — NOT available from the C ABI today. Screen needs
|
|
146
|
+
* a live GPU device handle (ID3D11Device*) with no CPU fallback, and its C
|
|
147
|
+
* representation is deferred (crates/mediaway-ffi/adr/0001 § Deferred),
|
|
148
|
+
* so openScreenCapture always throws CaptureUnavailableError. The ideal DX
|
|
149
|
+
* (BGRA8 CPU frames at the display's native geometry) is what the aspirational
|
|
150
|
+
* screen-record example targets.
|
|
151
|
+
*/
|
|
152
|
+
export class ScreenSession {
|
|
153
|
+
width = 0;
|
|
154
|
+
height = 0;
|
|
155
|
+
pixelFormat = "bgra8";
|
|
156
|
+
timeBase;
|
|
157
|
+
/** Screen session (always throws — no C-ABI screen path yet). */
|
|
158
|
+
constructor(timeBase) {
|
|
159
|
+
this.timeBase = timeBase;
|
|
160
|
+
}
|
|
161
|
+
pollFrame() {
|
|
162
|
+
throw new CaptureUnavailableError(3, "Screen capture is not available from this binding today");
|
|
163
|
+
}
|
|
164
|
+
async close() { }
|
|
165
|
+
}
|
|
166
|
+
export async function openScreenCapture(options) {
|
|
167
|
+
void options;
|
|
168
|
+
throw new CaptureUnavailableError(3, "Screen capture needs a live GPU device handle with no CPU fallback, and its C representation is deferred — not available from this binding today");
|
|
169
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@mediaway/device",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Mediaway camera/mic/screen capture over the C ABI (mediaway-ffi)",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.js"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"dist"
|
|
16
|
+
],
|
|
17
|
+
"license": "MIT OR Apache-2.0",
|
|
18
|
+
"repository": {
|
|
19
|
+
"type": "git",
|
|
20
|
+
"url": "git+https://github.com/nyxways/mediaway.git"
|
|
21
|
+
},
|
|
22
|
+
"publishConfig": {
|
|
23
|
+
"access": "public"
|
|
24
|
+
},
|
|
25
|
+
"scripts": {
|
|
26
|
+
"build": "tsc -p tsconfig.json"
|
|
27
|
+
},
|
|
28
|
+
"dependencies": {
|
|
29
|
+
"@mediaway/ffi": "0.1.0",
|
|
30
|
+
"@mediaway/container": "0.1.0"
|
|
31
|
+
},
|
|
32
|
+
"devDependencies": {
|
|
33
|
+
"typescript": "^5.6.0"
|
|
34
|
+
}
|
|
35
|
+
}
|